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

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

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

打开新窗口

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
<!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

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
//预览
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里绑定预览按钮

1
2
3
...
<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解析的方法,输出当前编辑器内容解析后的结果.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
});
},
//解析markdown
parse:function(){
return this.marked(this.cm.getValue());
},

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

修改directive

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
27
28
29
30
//预览
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里引用

1
2
3
4
var gui = require('nw.gui'), win = gui.Window.get();
win.on('change', function (mdHtml) {
$('#content').html(mdHtml);
});

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

优化体验

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

滚动条随动

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

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

1
2
3
4
//滚动事件
this.cm.on('scroll',function(cm){
me.fire('scroll',cm.getScrollInfo());
});

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

1
2
3
4
5
6
7
8
9
10
11
12
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事件,并更新预览页面的滚动条高度

1
2
3
4
win.on('editorScroll',function(scrollInfo){
var scrollTop = $(document.body).height()*scrollInfo.top/scrollInfo.height;
$(document.body).scrollTop(scrollTop);
});

样式美化

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

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

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

system/model.js增加默认配置

1
2
3
4
5
6
7
8
9
//默认设置
var defaultSystemData = {
//最后一次打开的文件
lastFile: null,
//编辑器样式
theme:'ambiance',
//预览窗口样式
preViewTheme:'default'
};

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

1
2
3
4
5
6
7
8
9
10
11
<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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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将系统设置传递给预览窗口

1
2
3
4
5
6
7
8
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

1
2
3
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

1
2
3
4
5
6
7
8
9
10
11
//默认设置
var defaultSystemData = {
//最后一次打开的文件
lastFile: null,
//编辑器样式
theme:'ambiance',
//预览窗口样式
preViewTheme:'github',
//预览代码块样式
preViewHighLightTheme:'default'
};

system.html

1
2
3
4
5
6
7
...
<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

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

1
2
3
4
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

1
2
3
4
5
6
7
...
win.on('close', function () {
var me = this;
hmd.previewWin && hmd.previewWin.close();
me.close(true);
});
...

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

总结

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

最终效果截图

主窗口


预览窗口

附件

本篇程序打包
项目地址

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

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

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

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

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

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

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

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

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

  5. Vim安装插件支持 MarkDown 语法、实时预览等

    使用 markdown-preview.vim 插件可以实时通过浏览器预览 markdown 文件 使用该插件需要 vim 支持py2/py3 安装 使用 vim-plug: 在 .vimrc 或 i ...

  6. 详细讲解使用Sublime Text 3进行Markdown编辑和实时预览

    所需安装的插件 Markdown Editing // Markdown编辑和语法高亮 Markdown Preview// Markdown导出html预览 LiveReload// 时时预览 安装 ...

  7. Sublime 配置 Markdown,并实时预览

    准备: 找到菜单栏:Preferences → Package Control → Package Control:Install Package 需要安装的插件: [Markdown Editing ...

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

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

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

    这里文章都是从个人的github博客直接复制过来的,排版可能有点乱. 原始地址 http://benq.im 文章目录 1. 工具条 1.1. 样式 1.2. 工具条截图 2. 状态栏消息 3. 文件 ...

随机推荐

  1. hdu6049

    hdu6049 题意 给出一串由 \([1, n]\) 组成的 \(n\) 个数,每个数字都不相同.现在要尽可能的分成多个块,每个块内的数可以任意排序,且分完块后可以交换两个块的位置,问使得最后序列有 ...

  2. POJ1325Machine Schedule(匈牙利算法)

                                                          Machine Schedule Time Limit: 1000MS   Memory L ...

  3. jQuery的实用技巧

    1.禁用页面的右键菜单 $(document).ready(function(){ $(document).bind("contextmenu",function(e){ retu ...

  4. Codeforces 702 D Road to Post Office

    题目描述 Vasiliy has a car and he wants to get from home to the post office. The distance which he needs ...

  5. UVA 10160 Servicing Stations(状态压缩+迭代加深)

    [题目链接] LInk [题目大意] 给出一些点和边,选择一个点就能把这个点和相邻的点都覆盖,求最小点覆盖 [题解] 我们压缩点被覆盖的状态,迭代加深搜索覆盖的最小点数, 当剩余的点全部选上时都无法完 ...

  6. Java概述--Java开发实战经典

    1)Java有三个发展方向,分别是Java SE,Java EE,Java ME.以下简要介绍. a.Java SE,Java Standard Edition(java标准版),包含了构成java语 ...

  7. MR实现--矩阵乘法

    import java.io.IOException; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.io ...

  8. sql字段字符用做其他类型查询

    select * FROM aa where parent = %@ ORDER BY cast(seq as integer) ASC

  9. 微软自家的.Net下的JavaScript引擎——ClearScript

    之前我介绍过一个开源的.Net下的Javascript引擎Javascript .NET,今天发现微软自己也开源了一个JavaScript引擎——ClearScript(当然,也支持VB Script ...

  10. Shiro+SpringMVC 实现更安全的登录(加密匹配&登录失败超次数锁定帐号)

    原文:http://blog.csdn.net/wlwlwlwl015/article/details/48518003 前言 初学shiro,shiro提供了一系列安全相关的解决方案,根据官方的介绍 ...