自己動手開發更好用的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,作為預覽的窗口頁面.

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個不常用的功能,不如把最常用的一個功能做好.

最終效果截圖

主窗口


預覽窗口

附件

本篇程序打包
項目地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM