自己动手开发更好用的markdown编辑器-04(预览功能)

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

程序打包

文章目录

  1. 1. 打开新窗口
  2. 2. 预览功能
  3. 3. 优化体验
    1. 3.1. 滚动条随动
    2. 3.2. 样式美化
    3. 3.3. 代码块高亮
    4. 3.4. 关闭主程序前先自动关闭预览窗口
  4. 4. 总结
  5. 5. 附件

上一篇我们实现了系统模块的一些功能,对angular的使用更深入了一点.

今天这篇我们要实现实时预览的功能,将学习到如何使用nw.js打开额外新窗口,窗口之间如何通信,并将引入新的开源框架marked,用于markdown的解析.

打开新窗口

预览的功能我将在编辑器之外的新窗口里实现,因为我平常都习惯使用双显示器,这样能把预览放在另一个显示器.

先在studio/views里新增preview.html,作为预览的窗口页面.

12345678910111213
<!DOCTYPE html><html lang="en"><head>	<meta charset="UTF-8" />	<title>预览</title></head><body>  <article  class="markdown-body" id="content">  </article>  <script src="../../../lib/jquery-2.1.3.js"></script>  <script src="../preview.js"></script></body></html>

studio/directives.js里增加打开预览窗口的directive

1234567891011121314151617181920212223242526
//预览studio.directive(‘studioPreview‘,function(){  return function($scope,elem){    $(elem[0]).on(‘click‘,function(){      var previewWinUrl = (‘file:///‘ + require(‘path‘).dirname(process.execPath) + ‘/app/modules/studio/views/preview.html‘).replace(/\\/g,‘/‘);      if (!hmd.previewWin) {      	//开发时为了方便调试,设置toolbar:true,发布时设为false.        hmd.previewWin = require(‘nw.gui‘).Window.open(previewWinUrl, {          position: ‘center‘,          "toolbar": true,          "frame": true,          "width": 800,          "height": 600,          "min_width": 600,          "min_height": 400,          "icon": "app/img/logo.png"        });        //关闭的时候置空preivewWin变量        hmd.previewWin.on(‘close‘, function () {          hmd.previewWin = null;          this.close(true);        });      }    });  };});

预览窗口每次只能打开一个,所以打开之前会先判断hmd.previewWin是否已存在,并且窗口关闭事件里将hmd.previewWin置空.

studio/views/studio.html里绑定预览按钮

123
...<a studio-preview href="javascript://" class="btn btn-primary" title="预览"><i class="glyphicon glyphicon-eye-open"></i></a>...

这样就实现了点击预览按钮打开预览窗口

预览功能

markdown的解析我使用开源的marked.

安装marked
打开命令行,进入app目录,输入安装命令:

1
npm install marked --save

editor.js增加markdown解析的方法,输出当前编辑器内容解析后的结果.

123456789101112131415161718192021222324
init: function (options,filepath) {  ...  this.initMarked();  this.cm = CodeMirror.fromTextArea(el, options);  ...},//初始化解析模块initMarked:function(){  this.marked = require(‘../app/node_modules/marked‘);  this.marked.setOptions({  renderer: new this.marked.Renderer(),  gfm: true,  tables: true,  breaks: false,  pedantic: false,  sanitize: true,  smartLists: true,  smartypants: false  });},//解析markdownparse:function(){  return this.marked(this.cm.getValue());},

这里要注意的是this.marked = require(‘../app/node_modules/marked‘);,而不是直接require(‘marked‘),这是因为nw.js的这个问题

修改directive

123456789101112131415161718192021222324252627282930
//预览studio.directive(‘studioPreview‘,function(){  return function($scope,elem){

    //修改文本时更新预览,change事件触发非常频繁,所以这里使用setTimeout防止无意义的频繁解析.    var changeTimer;    hmd.editor.on(‘change‘,function(){      clearTimeout(changeTimer);      changeTimer = setTimeout(function(){      	hmd.previewWin && hmd.previewWin.emit(‘change‘, hmd.editor.parse());      },200);    });    //打开文件时更新预览    hmd.editor.on(‘setFiled‘,function(filepath){      hmd.previewWin && hmd.previewWin.emit(‘change‘, hmd.editor.parse());    });

    $(elem[0]).on(‘click‘,function(){      	//省略...        hmd.previewWin.on(‘loaded‘,function(){          hmd.previewWin && hmd.previewWin.emit(‘change‘, hmd.editor.parse());        });        hmd.previewWin.on(‘close‘, function () {          hmd.previewWin = null;          this.close(true);        });      }    });  };});

我们通过自定义事件emit(‘change‘, hmd.editor.parse())来与previewWin窗口通讯. 在初始化窗口,打开文件,修改文件时都触发窗口的change事件,将解析后的内容作为事件参数传递.
新建脚本文件studio/preview.js,并在preview.html里引用

1234
var gui = require(‘nw.gui‘), win = gui.Window.get();win.on(‘change‘, function (mdHtml) {  $(‘#content‘).html(mdHtml);});

preview.js里监听change事件,然后将解析后的内容直接显示到页面上.

优化体验

现在已经可以实时的预览了,但功能还是过于简单,使用起来很不方便,这一节将优化预览窗口的使用体验.

滚动条随动

如果文本太多导致出现滚动条,预览窗口还是会一直显示在第一屏,并不会跟随我们在编辑器中的查看位置来实时的更新预览的位置.我们要看预览还要手动去调整预览窗口的滚动条高度,这样的体验完全等于没法使用,因此现在来实现预览窗口随着编辑器的滚动条高度等比随动.

codemirror已经实现了scroll事件,节省了我们大量的工作量,这个框架的作者考虑的真是周到,不得不赞一下.
我们在editor.jsscroll事件进行封装.

1234
//滚动事件this.cm.on(‘scroll‘,function(cm){  me.fire(‘scroll‘,cm.getScrollInfo());});

directive里将编辑器滚动事件传递给预览窗口

123456789101112
studio.directive(‘studioPreview‘,function(){   ...     //编辑器滚动     var scrollTimer;     hmd.editor.on(‘scroll‘,function(scrollInfo){       clearTimeout(scrollTimer);       scrollTimer = setTimeout(function(){       	hmd.previewWin && hmd.previewWin.emit(‘editorScroll‘,scrollInfo);       },200);     });   ... }

同样的道理,我们应该防止太频繁的触发

最后在preview.js里响应editorScroll事件,并更新预览页面的滚动条高度

1234
win.on(‘editorScroll‘,function(scrollInfo){  var scrollTop = $(document.body).height()*scrollInfo.top/scrollInfo.height;  $(document.body).scrollTop(scrollTop);});

样式美化

默认的无样式界面看起来太不舒服了,现在来实现跟编辑器一样的可以选择或者自定义的样式.

我们将预览的样式放在/app/css/previewtheme目录下,先在里面增加两个测试用的样式文件

增加预览样式设置
这个跟上一篇的编辑器样式设置类似.

system/model.js增加默认配置

123456789
//默认设置var defaultSystemData = {  //最后一次打开的文件  lastFile: null,  //编辑器样式  theme:‘ambiance‘,  //预览窗口样式  preViewTheme:‘default‘};

system/views/system.html增加表单字段

1234567891011
<div class="content studio-wrap">  <form class="system-form" name="systemForm">    ...    <div class="form-group">      <label>预览样式</label>      <select name="preViewTheme" ng-model="systemSetting.preViewTheme"  ng-options="k as v for (k, v) in preViewThemes">      </select>    </div>    ...  </form></div>

system/controllers.js

123456789101112131415161718192021
var system = hmd.system,	fs = require(‘fs‘);//读取theme目录,生成样式列表var readCssList = function(path){  var files = fs.readdirSync(path),themes={};  files.forEach(function (file) {    if(~file.indexOf(‘.css‘)){    	file = file.replace(‘.css‘,‘‘);      themes[file] = file;    }   });  return themes;};system.controller(‘system‘, function ($scope) {  $scope.themes = readCssList(‘./app/lib/codemirror/theme‘);  $scope.preViewThemes = readCssList(‘./app/css/previewtheme‘);  $scope.systemSetting = system.get();  $scope.save = function (systemSetting) {    system.save(systemSetting);  };});

将读取目录所有样式文件生成键值对的代码封装成方法readCssList,然后增加$scope.preViewThemes绑定即可..

再一次感受angular的方便.

应用样式

预览页面加载成功后,通过事件setTheme将系统设置传递给预览窗口

12345678
studio.directive(‘studioPreview‘,function(){  ...  hmd.previewWin.on(‘loaded‘,function(){    hmd.previewWin.emit(‘setTheme‘,hmd.system.get());    hmd.previewWin && hmd.previewWin.emit(‘change‘, hmd.editor.parse());  });  ...});

preview.js

123
win.on(‘setTheme‘,function(setting){  $(‘head‘).append(‘<link href="../../../css/previewtheme/‘+setting.preViewTheme+‘.css" rel="stylesheet" />‘);});

从网上找几个常用的marddown样式文件来看看效果,你可以自己找或写更多样式.


代码块高亮

作为一个码农,写的markdown文件里都有好多代码块,肯定要把代码块弄好看点.

安装highlight.js

1
npm install highlight.js

安装完成后,代码高亮的样式文件在目录node_modules/highlight.js/styles/

在系统设置里增加预览代码样式设置,跟之前的预览样式类似,这里直接上代码,不再重复描述了.

model.js

1234567891011
//默认设置var defaultSystemData = {  //最后一次打开的文件  lastFile: null,  //编辑器样式  theme:‘ambiance‘,  //预览窗口样式  preViewTheme:‘github‘,  //预览代码块样式  preViewHighLightTheme:‘default‘};

system.html

1234567
...    <div class="form-group">      <label>代码预览样式</label>      <select name="preViewHighLightTheme" ng-model="systemSetting.preViewHighLightTheme"  ng-options="k as v for (k, v) in preViewHighLightThemes">      </select>    </div>...

controllers.js

123456789
system.controller(‘system‘, function ($scope) {  $scope.themes = readCssList(‘./app/lib/codemirror/theme‘);  $scope.preViewThemes = readCssList(‘./app/css/previewtheme‘);  $scope.preViewHighLightThemes = readCssList(‘./app/node_modules/highlight.js/styles‘);  $scope.systemSetting = system.get();  $scope.save = function (systemSetting) {    system.save(systemSetting);  };});

系统设置截图

preview.js

1234
win.on(‘setTheme‘,function(setting){  $(‘head‘).append(‘<link href="../../../node_modules/highlight.js/styles/‘ + setting.preViewHighLightTheme +‘.css" rel="stylesheet" />‘);  $(‘head‘).append(‘<link href="../../../css/previewtheme/‘+setting.preViewTheme+‘.css" rel="stylesheet" />‘);});

这样就完成了,很简单,没几行代码.

关闭主程序前先自动关闭预览窗口

现在还有个小问题,主程序关掉后,预览窗口还在.

modules/directives.js

1234567
...win.on(‘close‘, function () {  var me = this;  hmd.previewWin && hmd.previewWin.close();  me.close(true);});...

监听主窗口的关闭事件,如果有预览窗口,就先关闭预览窗口再关闭自己

总结

现在我们的markdown编辑器应该是挺好用的了,至少比一些在线的方便些,可以很灵活的定制各种样式.
做预览这个功能的时候我的想法是:一个重要的功能,不仅要实现基本功能,更重要的是完善体验,太差的体验跟没有这个功能没区别,因此我们把时间都花在优化预览的体验上.与其增加10个不常用的功能,不如把最常用的一个功能做好.

最终效果截图

主窗口


预览窗口

附件

本篇程序打包
项目地址

时间: 2024-10-04 11:43:53

自己动手开发更好用的markdown编辑器-04(预览功能)的相关文章

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

这里文章都是从个人的github博客直接复制过来的,排版可能有点乱. 原始地址 http://benq.im/2015/04/28/hexomd-05/ 文章目录 1. 七牛云存储 1.1. 系统设置 1.2. 配置七牛帐号 2. 图片上传 3. 总结 4. 附件 上一篇我们实现了实时预览功能. 今天这篇要利用免费的七牛云存储服务来实现粘贴自动上传图片的功能,涉及到以下三个内容: 七牛云存储. clipboard-apis ajax文件上传 不想看过程的朋友可以直接下载打包好的程序使用,使用之前

使用Sublime Text 3进行Markdown编辑+实时预览

使用Sublime Text 3进行Markdown编辑+实时预览 安装软件包管理器 打开Sublime Text 3 同时按下 ctrl+` ,窗口底部出现一个小控制台 复制以下代码,粘贴到控制台的编辑栏里: import urllib.request,os; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib

C#开发微信门户及应用(30)--消息的群发处理和预览功能

在很多场合下,我们可能需要利用微信公众号的优势,定期给指定用户群发送一些推广消息或者新闻内容,以便给关注客户一种经常更新公众号内容的感觉,同时也方便我们经常和用户进行互动.微信公众号的高级群发接口就是为了处理这个场景的,本文介绍在C#代码中如何封装消息的群发和预览等功能. 1.消息群发的功能和限制 对于公众号中的服务号和订阅号,群发的消息有一定的限制,具体规则如下所示. 1.对于认证订阅号,群发接口每天可成功调用1次,此次群发可选择发送给全部用户或某个分组: 2.对于认证服务号虽然开发者使用高级

让Win10更好的关键:Windows Insider预览项目成立两周

两年前的今天,微软正式启动了Windows Insider会员测试项目,作为开发和测试.发展最新的Windows10系统的重要收集建议.反馈来源.Windows Insider允许用户以极其简单的方式提前收到Win10预览版推送,用上最新功能. 有趣的是,在Windows Insider项目正式启动前,微软预计会有25万左右的人员加入测试大家庭,最高预计40万.去年前任Windows Insider负责人Gabe Aul表示,这是公司最疯狂的梦想,预计参与人数会增加.到2015年9月,Windo

Atom编辑器中预览markdown

Atom 是原生支持markdown语法的,并支持实时预览 快捷键:ctrl + shift+m  效果: 原文地址:https://www.cnblogs.com/longl/p/9352983.html

在电脑上开发手机端,如何用手机实时预览

把localhost改成当前电脑的IP 把ip:8080/#/goods复制到草料二维码里面(地址:http://cli.im/),生成二维码,然后用手机微信扫描,手机和电脑必须是同一个局域网

PHP服务器文件管理器开发小结(七):应用jQueryUI预览服务器图片

上一节讨论了利用jQueryUI实现用户友好的新建.查看和编辑文件的界面.然而,这些界面都是针对纯文本的,如果是图像的话,查看文件仅提供纯文本就很不友好了.因此,需要为前端提供图像浏览的方法. 先提供前端JavaScript的代码,首先是"原材料": <div id="dialogImage" style="display:none"> <img  src="" id="imgView"/

【万里征程——Windows App开发】用浮出控件做预览效果

在前面学习控件的时候,我们已经见过了MessageDialog了,关于Button还有一个浮出控件Flyout哦.具体是怎样用呢?接下来就一起看看咯. 我们还是延续前面的那个示例好了,那么,代码来了. <Button x:Name="btnWhat" Content="这是什么?"> <Button.Flyout> <Flyout> <StackPanel> <TextBlock Width="430&

用 grunt-contrib-connect 构建实时预览开发环境 实时刷新

本文基本是参照着 用Grunt与livereload构建实时预览的开发环境 实操了一遍,直接实现能实时预览文件列表,内容页面.不用刷新页面了,这比以前开发网页程序都简单. 这里要用到的 Grunt 插件有 grunt-contrib-connect , 用来充当一个静态文件服务器,本身集成了 livereload 功能 grunt-contrib-watch , 监视文件的改变,然后执行指定任务,这里用来刷新  grunt serve 打开的页面 以下是个辅助的插件 load-grunt-tas