這裏文章都是從我的的github博客直接複製過來的,排版可能有點亂. 原始地址
http://benq.im/2015/04/25/hexomd-04/
上一篇咱們實現了系統模塊的一些功能,對angular
的使用更深刻了一點.javascript
今天這篇咱們要實現實時預覽
的功能,將學習到如何使用nw.js
打開額外新窗口,窗口之間如何通訊,並將引入新的開源框架marked,用於markdown的解析.css
打開新窗口
預覽的功能我將在編輯器以外的新窗口裏實現,由於我日常都習慣使用雙顯示器,這樣能把預覽放在另外一個顯示器.html
先在studio/views
裏新增preview.html,做爲預覽的窗口頁面.java
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
裏增長打開預覽窗口的directivenode
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) { 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" }); hmd.previewWin.on('close', function () { hmd.previewWin = null; this.close(true); }); } }); }; }); |
預覽窗口每次只能打開一個,因此打開以前會先判斷hmd.previewWin
是否已存在,而且窗口關閉事件裏將hmd.previewWin
置空.jquery
studio/views/studio.html
裏綁定預覽按鈕git
1 2 3 |
... <a studio-preview href="javascript://" class="btn btn-primary" title="預覽"><i class="glyphicon glyphicon-eye-open"></i></a> ... |
這樣就實現了點擊預覽按鈕打開預覽窗口
github
預覽功能
markdown
的解析我使用開源的marked.npm
安裝marked
打開命令行,進入app
目錄,輸入安裝命令:markdown

爲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 }); },
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){
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.js對scroll
事件進行封裝.
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');
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個不經常使用的功能,不如把最經常使用的一個功能作好.
最終效果截圖

主窗口

預覽窗口
附件
本篇程序打包
項目地址