上一篇我们搭建好了项目结构,简单的实现了第一个模块(studio)的基本功能,已经能够进行简单的markdown编辑.
在这篇里我们将实现以下功能:
由于工具条按钮绑定的都是studio模块下的功能,因此我把index.html
上的工具条移动到了studio
模块的视图模版modules/studio/views/studio.html
里.
123456789101112 | <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这个功能,这在布局的时候非常的方便,比如:
123456 | body { height: calc(100% - 50px); overflow: hidden; color: #fff; background: #1E1E1E;} |
配色比较丑,一开始我是只在乎功能的,UI是我的弱项,我们还是先能用再好用最后才好看吧.
状态栏消息这功能很简单,用来显示各种操作的信息.这个功能为全局可用,因此把功能写到app.js
里
1234567891011121314151617181920212223242526 | //消息等级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: 文件保存成功时显示
123456789 | 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与外部的耦合度降低.
工具条的bt-group里有三个按钮,分别绑定了三个studio下的directive:studio-newfile
,studio-openfile
,studio-save
.现在打开modules/studio/directives.js
文件,开始实现这3个功能.
这个功能很简单,只要把当前文件设为空,并且清空编辑器内容就算是新建文件了,保存的时候才会让用户选择保存路径.
修改editor.js
的setFile
方法
123456789101112 | //设置当前文件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
,点击按钮时调用编辑器的setFile
让filepath
为空
1234567 | 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}"
.
12345678910111213141516171819 | 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里保存功能的实现
1234567891011 | //保存文件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方法来引导用户保存到新建的文件里.
1234567891011121314 | //另存为对话框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()保存文件.
将实现常用的三种打开文件方式:
通过按钮打开
通过按钮打开与另存为类似,直接上代码,不用多做解释.
12345678910 | 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> |
1234567 | studio.directive('studioOpenfile', function () { return function ($scope, elem) { $(elem[0]).on('click',function(){ hmd.editor.openF |