本身动手制做更好用的markdown编辑器-02

这里文章都是从我的的github博客直接复制过来的,排版可能有点乱. 原始地址  http://benq.im
文章目录
  1. 1. 工具条
    1. 1.1. 样式
    2. 1.2. 工具条截图
  2. 2. 状态栏消息
  3. 3. 文件操做
    1. 3.1. 新建文件
    2. 3.2. 保存文件
    3. 3.3. 打开文件
  4. 4. 总结
  5. 5. 附件

上一篇咱们搭建好了项目结构,简单的实现了第一个模块(studio)的基本功能,已经可以进行简单的markdown编辑.javascript

在这篇里咱们将实现如下功能:css

  1. 底部工具条UI,状态栏信息
  2. 新建文件,打开文件,保存文件

工具条

因为工具条按钮绑定的都是studio模块下的功能,所以我把index.html上的工具条移动到了studio模块的视图模版modules/studio/views/studio.html里.html

样式

1
2
3
4
5
6
7
8
9
10
11
12
<div class="content studio-wrap">
<textarea name="" cols="30" rows="10" hmd-editor></textarea>
</div>
<footer class="tool">
<!--状态栏消息-->
<section class="msg" id="msg"></section>
<section class="btn-group studio-btn-group">
<a studio-newfile href="javascript://" class="btn btn-primary" style="border-radius:0;" title="新建文件"><i class="glyphicon glyphicon glyphicon-file"></i></a>
<a studio-openfile href="javascript://" class="btn btn-primary" title="打开文件" ng-click="openTerminal()"><i class="glyphicon glyphicon-folder-open"></i></a>
<a studio-save href="javascript://" class="btn btn-primary" title="保存更改" style="border-radius:0;"><i class="glyphicon glyphicon-floppy-disk"></i></a>
</section>
</footer>

CSS写得比较难看,没什么好说的.样式里我大量使用了calc这个功能,这在布局的时候很是的方便,好比:java

1
2
3
4
5
6
body {
height: calc(100% - 50px);
overflow: hidden;
color: #fff;
background: #1E1E1E;
}

 

工具条截图


配色比较丑,一开始我是只在意功能的,UI是个人弱项,咱们仍是先能用再好用最后才好看吧.git

状态栏消息

状态栏消息这功能很简单,用来显示各类操做的信息.这个功能为全局可用,所以把功能写到app.jsgithub

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//消息等级
var msgTimer = null;
var MSG_LEVEL = {
info: 'info',
warning: 'warning',
debug: 'debug',
error:'error'
};
//状态栏消息
hmd.msg = function (txt, lv) {
lv = lv || MSG_LEVEL.info;
$('#msg')
.removeClass(MSG_LEVEL.info)
.removeClass(MSG_LEVEL.warning)
.removeClass(MSG_LEVEL.debug)
.removeClass(MSG_LEVEL.error)
.addClass(lv).text(txt);
clearTimeout(msgTimer);
msgTimer = setTimeout(function () {
$('#msg')
.removeClass(MSG_LEVEL.info)
.removeClass(MSG_LEVEL.warning)
.removeClass(MSG_LEVEL.debug)
.removeClass(MSG_LEVEL.error);
}, 5000);
};

 

eg: 文件保存成功时显示windows

1
2
3
4
5
6
7
8
9
 studio.directive('hmdEditor', function () {
return function ($scope, elem) {
hmd.editor.init({el:elem[0]},'E:\\Temp\\test\\test.md');
hmd.editor.on('saved',function(filepath){
var fileNameArr = filepath.split('\\');
hmd.msg('文件:' + fileNameArr[fileNameArr.length - 1] + '保存成功!');
});
};
});

 

有一点要注意的事,editor都是尽可能采用事件的方式来对外提供接口,这样可让editor与外部的耦合度下降.markdown

文件操做

工具条的bt-group里有三个按钮,分别绑定了三个studio下的directive:studio-newfile,studio-openfile,studio-save.
如今打开modules/studio/directives.js文件,开始实现这3个功能.hexo

新建文件

这个功能很简单,只要把当前文件设为空,而且清空编辑器内容就算是新建文件了,保存的时候才会让用户选择保存路径.app

修改editor.jssetFile方法

1
2
3
4
5
6
7
8
9
10
11
12
//设置当前文件
setFile:function(filepath){
if(filepath && fs.existsSync(filepath)){
var txt = util.readFileSync(filepath);
this.filepath = filepath;
this.cm.setValue(txt);
}
else{
this.filepath = null;
this.cm.setValue('');
}
}

 

实现directive,点击按钮时调用编辑器的setFilefilepath为空

1
2
3
4
5
6
7
studio.directive('studioNewfile', function () {
return function ($scope, elem) {
$(elem[0]).on('click',function(){
hmd.editor.setFile();
});
};
});

 

这样新建文件按钮就完成了,其实这按钮的功能就是清空编辑器,真正的保存新文件功能在保存按钮功能里实现.

保存文件

1
<a studio-save ng-class="{'disabled':!editorChanged}" href="javascript://" class="btn btn-primary" title="保存更改(Ctrl+S)" style="border-radius:0;">省略..</a>

保存按钮只有在文本有改动时才可用,这样用户就能很直观的看到是否已保存(禁用按钮时,按ctrl+s依然能够保存,不少人都习惯一直按ctrl+s)
经过ng-class来实现这个功能,将classdisabled绑定到editorChanged这个上下文变量上ng-class="{'disabled':!editorChanged}".

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 studio.directive('studioSave',function(){
return function($scope,elem){
var editor = hmd.editor;
//标识是否有未保存的变动.
$scope.editorChanged = false;
editor.on('change', function (cm, change) {
$scope.editorChanged = true;
$scope.$digest();
});
editor.on('saved', function () {
$scope.editorChanged = false;
$scope.$digest();
});

$(elem[0]).on('click',function(){
editor.save();
});
};
});

 

这样$scope.editorChanged变化时,保存按钮也会跟着变化,咱们不须要直接操做dom元素.

editor.js里保存功能的实现

1
2
3
4
5
6
7
8
9
10
11
//保存文件
save : function () {
var txt = this.cm.getValue();
if(this.filepath){
util.writeFileSync(this.filepath, txt);
this.fire('saved',this.filepath);
}
else{
this.saveAs();
}
}

若是filepath不存在,那就调用saveAs方法来引导用户保存到新建的文件里.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//另存为对话框
saveAs:function(){
var me = this;
this.saveAsInput = $('<input style="display:none;" type="file" accept=".md" nwsaveas/>');
this.saveAsInput[0].addEventListener("change", function (evt) {
if(this.value){
me.filepath = this.value;
me.save();
}
}, false);
this.saveAsInput.trigger('click');

hmd.msg('保存新文件');
},

nw.js文件对话框比较特殊,能够经过代码触发单击事件来打开对话框,并无必定要用户点击的限制,做为一个客户端开发框架,这样的改动是必要的.
所以咱们上面的代码里直接建立了一个input标签,随即触发它的单击事件.其中nwsaveas是指定对话框的类型为另存为对话框.
用户输入或者选择好文件后,将filepath设置为用户指定的,并调用me.save()保存文件.

打开文件

将实现经常使用的三种打开文件方式:

  1. 经过打开文件按钮
  2. 拖动文件到编辑器
  3. 双击md文件打开

经过按钮打开

经过按钮打开与另存为相似,直接上代码,不用多作解释.

1
2
3
4
5
6
7
8
9
10
openFile:function(){
var me = this;
this.openFileInput = $('<input style="display:none;" type="file" accept=".md"/>');
this.openFileInput[0].addEventListener("change", function (evt) {
if(this.value){
me.setFile(this.value);
}
}, false);
this.openFileInput.trigger('click');
},

而后是实现按钮绑定的directive

1
<a studio-openfile href="javascript://" class="btn btn-primary" title="打开文件" ng-click="openTerminal()">省略..</a>

 

1
2
3
4
5
6
7
studio.directive('studioOpenfile', function () {
return function ($scope, elem) {
$(elem[0]).on('click',function(){
hmd.editor.openFile();
});
};
});

拖动打开
到此三个按钮的功能都已实现,可是拖动打开文件是windows上程序的基本功能,所以咱们也来实现它.

这个功能的实现放在编辑器的初始化代码后面,由于要编辑器初始化以后才能打开文件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
studio.directive('hmdEditor', function () {
return function ($scope, elem) {
hmd.editor.init({el:elem[0]});
hmd.editor.on('saved',function(filepath){
var fileNameArr = filepath.split('\\');
hmd.msg('文件:' + fileNameArr[fileNameArr.length - 1] + '保存成功!');
});
//监听拖动事件
document.ondrop = function (e) {
var path, $target = $(e.target), dir, system;
e.preventDefault();
if (!e.dataTransfer.files.length) return;
//取到文件路径
path = e.dataTransfer.files[0].path;
//非目录,而且包含.md才会在编辑器里打开
if (!fs.statSync(path).isDirectory() && ~path.indexOf('.md')) {
hmd.editor.setFile(path);
}
};
};
});

 

添加了document.ondrop这一段代码,若是拖动的是一个md文件,则打开它

双击md文件打开

编写代码以前,咱们选随便选中一个md文件,并设置默认用咱们的程序打开.

而后关闭咱们的程序,双击随便一个md文件试试,能够看到双击后咱们的程序会启动,可是并不会打开双击的文件,接下来就写代码实现它.

咱们把功能实现也放到editor的init以后.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
studio.directive('hmdEditor', function () {
return function ($scope, elem) {
hmd.editor.init({el:elem[0]});
//省略部分代码...
//双击md文件打开
var gui = require('nw.gui'),
filepath = gui.App.argv[0];
~filepath.indexOf('.md') && hmd.editor.setFile(filepath);
//若是程序已经打开,则会触发open事件
gui.App.on('open', function(cmdline) {
window.focus();
filepath = cmdline.split(' ')[2].replace(/\"/g,'');
~filepath.indexOf('.md') && hmd.editor.setFile(filepath);
});
};
});

 

相关知识
关掉程序,再次双击md文件,就能够看到打开功能正常了.在软件已启动的状态双击文件也能够打开该文件.

总结

今天实现了文件操做功能,这样咱们的编辑器已经可用了.明天将实现系统设置的功能.

附件

本篇程序打包
项目地址

相关文章
相关标签/搜索