本身動手製做更好用的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文件,就能夠看到打開功能正常了.在軟件已啓動的狀態雙擊文件也能夠打開該文件.

總結

今天實現了文件操做功能,這樣咱們的編輯器已經可用了.明天將實現系統設置的功能.

附件

本篇程序打包
項目地址

相關文章
相關標籤/搜索