这里文章都是从个人的github博客直接复制过来的,排版可能有点乱. 原始地址 http://benq.im

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

在这篇里我们将实现以下功能:

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

工具条

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

样式

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  1. <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这个功能,这在布局的时候非常的方便,比如:

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

工具条截图


配色比较丑,一开始我是只在乎功能的,UI是我的弱项,我们还是先能用再好用最后才好看吧.

状态栏消息

状态栏消息这功能很简单,用来显示各种操作的信息.这个功能为全局可用,因此把功能写到app.js

  1. 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
  1. //消息等级
    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: 文件保存成功时显示

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
  1. 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.jssetFile方法

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  1. //设置当前文件
    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. 1
    2
    3
    4
    5
    6
    7
  1. studio.directive('studioNewfile', function () {
    return function ($scope, elem) {
    $(elem[0]).on('click',function(){
    hmd.editor.setFile();
    });
    };
    });

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

保存文件

  1. 1
  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. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
  1. 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();
    });
  2.  
  3. $(elem[0]).on('click',function(){
    editor.save();
    });
    };
    });

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

editor.js里保存功能的实现

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
  1. //保存文件
    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. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
  1. //另存为对话框
    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');
  2.  
  3. hmd.msg('保存新文件');
    },

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

打开文件

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

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

通过按钮打开

通过按钮打开与另存为类似,直接上代码,不用多做解释.

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  1. 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. 1
  1. <a studio-openfile href="javascript://" class="btn btn-primary" title="打开文件" ng-click="openTerminal()">省略..</a>
  1. 1
    2
    3
    4
    5
    6
    7
  1. studio.directive('studioOpenfile', function () {
    return function ($scope, elem) {
    $(elem[0]).on('click',function(){
    hmd.editor.openFile();
    });
    };
    });

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

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

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
  1. 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. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
  1. 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文件,就可以看到打开功能正常了.在软件已启动的状态双击文件也可以打开该文件.

总结

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

附件

本篇程序打包
项目地址

自己动手制作更好用的markdown编辑器-02的更多相关文章

  1. 自己动手制作更好用的markdown编辑器-01

    这里文章都是从个人的github博客直接复制过来的,排版可能有点乱. 原始地址  http://benq.im   文章目录 1. 简介 2. 项目结构 3. 程序主界面 4. 拖动窗口 5. app ...

  2. 自己动手制作更好用的markdown编辑器-03

    这里文章都是从个人的github博客直接复制过来的,排版可能有点乱. 原始地址 http://benq.im/2015/04/24/hexomd-03/ 文章目录 1. 系统模块 2. 记录上次打开的 ...

  3. 自己动手开发更好用的markdown编辑器-04(实时预览)

    这里文章都是从个人的github博客直接复制过来的,排版可能有点乱. 原始地址 http://benq.im/2015/04/25/hexomd-04/   程序打包   文章目录 1. 打开新窗口 ...

  4. 自己动手开发更好用的markdown编辑器-07(扩展语法)

    这里文章都是从个人的github博客直接复制过来的,排版可能有点乱. 原始地址 http://benq.im/2015/05/19/hexomd-07/   文章目录 1. 准备工作 2. 目录语法 ...

  5. 自己动手开发更好用的markdown编辑器-06(自动更新)

    这里文章都是从个人的github博客直接复制过来的,排版可能有点乱. 原始地址 http://benq.im/2015/05/12/hexomd-06/   文章目录 1. 自动更新方案 2. 实现 ...

  6. 自己动手开发更好用的markdown编辑器-05(粘贴上传图片)

    这里文章都是从个人的github博客直接复制过来的,排版可能有点乱. 原始地址 http://benq.im/2015/04/28/hexomd-05/   文章目录 1. 七牛云存储 1.1. 系统 ...

  7. 任由文字肆意流淌,更自由的开源 Markdown 编辑器

    对于创作平台来说内容编辑器是十分重要的功能,强大的编辑器可以让创作者专注于创作"笔"下生花.而最好取悦程序员创作者的方法之一就是支持 Markdown 写作,因为大多数程序员都是用 ...

  8. 市面上有没有靠谱的PM2.5检测仪?如何自己动手制作PM2.5检测仪

     市面上能买到的11中常见的pm2.5检测仪 网上大佬实测并不是很准,我这里没测过(全买下来有点贵,贫穷限制了我的想象力) 这些检测仪多数是复合式.多功能的空气质量检测仪.具体就不一一介绍了.这篇文章 ...

  9. 更轻便的markdown 编辑器Typora

    更轻便的markdown 编辑器 Typora 所见即所得的键入方式 https://typora.io 文章来源:刘俊涛的博客 欢迎关注,有问题一起学习欢迎留言.评论.

随机推荐

  1. LCA+差分【p4427】[BJOI2018]求和

    Description master 对树上的求和非常感兴趣.他生成了一棵有根树,并且希望多次询问这棵树上一段路径上所有节点深度的\(k\) 次方和,而且每次的\(k\) 可能是不同的.此处节点深度的 ...

  2. C++线段树模板(区间和、区间加)

    操作说明: segtree<T>tree(len) =>创建一个内部元素类型为T.区间为1-len的线段树tree tree.build(l,r) =>以[l,r]区间建立线段 ...

  3. Eclipse Build all and build project not working - jar missing

    Eclipse Build all and build project not working - jar missing

  4. 转:mysql group by 用法解析(详细)

    group by 用法解析 group by语法可以根据给定数据列的每个成员对查询结果进行分组统计,最终得到一个分组汇总表. SELECT子句中的列名必须为分组列或列函数.列函数对于GROUP BY子 ...

  5. Linux下 编译C++/C以及常用的几种命令(ubuntu)

    http://blog.csdn.net/bob1993_dev/article/details/45973919

  6. luogu P1291 [SHOI2002]百事世界杯之旅

    题目链接 luogu P1291 [SHOI2002]百事世界杯之旅 题解 设\(f[k]\)表示还有\(k\)个球员没有收集到的概率 再买一瓶,买到的概率是\(k/n\),买不到的概率是\((n-k ...

  7. 【bzoj1455】【罗马游戏】左偏树+并查集(模板)

    Description 罗马皇帝很喜欢玩杀人游戏. 他的军队里面有n个人,每个人都是一个独立的团.最近举行了一次平面几何测试,每个人都得到了一个分数. 皇帝很喜欢平面几何,他对那些得分很低的人嗤之以鼻 ...

  8. Problem S: 零起点学算法14——三位数反转

    #include<stdio.h> #include<stdlib.h> int main() { int a,b,c,s; scanf("%d",& ...

  9. debian中添加sudo命令

    解决方法(root命令) apt-get install sudochmod u+w /etc/sudoers //给此文件增加写入权限gedit /etc/sudoers找到root ALL=(AL ...

  10. [Linux]nginx tomcat做负载均衡

    之前使用nginx做过web反向代理,没有做过负载均衡,今天有个同学须要做tomcat的负载均衡,我也研究下. 一共是2个机器,一个物理机(win7)上面部署2个tomcat,使用不同的port启动. ...