這裏文章都是從我的的github博客直接複製過來的,排版可能有點亂. 原始地址
http://benq.im
前段時間用hexo從新搭了我的博客,順便寫了個簡單的博客搭建教程.javascript
用markdown寫起博客流暢不少,可是用了幾個markdown編輯器,都沒有一個適合本身使用的。因而就想本身動手作一個,固然不是徹底從0開始作,語法高亮和markdown解析都用的是開源的項目.
從這篇開始,我會把整個開發過程記錄成系列隨筆,所以開發進度較爲緩慢.css
博客寫得少,像這樣寫長一點的隨筆就有點混亂,看不懂的請用力噴,我會努力改進.html
簡介
先介紹下開發過程當中用到的一些比較重要的開源項目:java
- nw.js,原名node-webkit,用webkit和node來作基於web技術的跨平臺客戶端軟件.
- CodeMirror,基於web技術實現的文本編輯器,實現了大部分的IDE功能以及幾乎所有你會用到的語言的支持.目前我平常開發都是用這個IDE,甚至在作hexomd這個項目時用的IDE也是CodeMirror作的.
- angularjs,google的mvvm開發框架,這個相信不用我多作介紹.我用的不熟,以爲好用就拿來即用,沒有深刻的瞭解過.
關於這些開源項目的使用,我在這系列文章裏不會詳細解釋,若是有疑問,能夠去看官網的入門教程和wiki,固然也歡迎討論.node
項目結構
圖片裏的是我目前的項目結構,大概講解一下一些目錄和文件的用途。jquery
-
icudtl.dat
,nw.exe
,nw.pak
這3個是nw.js在windows運行所必須的文件.git
-
package.json
nw.js的配置文件angularjs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
{ "name": "HexoMD", "description": "Markdown for hexo", "main": "app/index.html", "author": "hmjlr123@gmail.com", "license": "MIT", "directories": { "test": "no" }, "devDependencies": {}, "window": { "title": "HexoMD", "icon": "app/img/logo.png", "toolbar": true, "frame": false, "width": 1000, "height": 700, "position": "center", "min_width": 600, "min_height": 400 } } |
-
app目錄
程序的全部源代碼的根目錄.github
-
app/lib
存放angular,jquery,codemirror等開源庫/框架的源代碼web
-
app/helpers
存放一些node的工具函數
-
app/modules
程序代碼在這個目錄,按功能模塊分紅不一樣的子目錄.
modules/app.js
是整個程序的入口點
-
app/package.json
node模塊配置,注意與上層的package.json意義不一樣
-
app/index.html
程序的主界面窗口
程序主界面
index.html
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 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Hexo Markdown</title> <link href="css/bootstrap.css" rel="stylesheet"> <link href="lib/codemirror/lib/codemirror.css" rel="stylesheet" /> <link href="css/index.css" rel="stylesheet"> </head> <body> <nav class="navbar navbar-inverse navbar-fixed-top"> 省略... </nav> <article class="container app" ui-view ng-animate="'view'"></article> <footer class="tool"></footer> <script src="lib/jquery-2.1.3.js"></script> <script src="lib/angular.js"></script> <script src="lib/angular-ui-router.js"></script> <script src="modules/app.js"></script> <script> angular.bootstrap($('body'), ['hmd']); </script> </body> </html> |
只貼出部分代碼.之後的全部代碼也相似,都只會把重要的貼出來,並給出完整的連接.
界面採用比較簡潔的三欄佈局,分別爲導航欄
、內容區
、狀態欄/工具條
.
最頂部的地址欄只有在開發的時候爲了方便調試纔開啓,發佈時會關閉掉.
拖動窗口
爲了美觀,咱們在配置裏去掉了系統自帶的邊框.所以要實現自定義的拖動窗口功能還須要增長一些設置.
所謂的設置,其實只要加上對應的樣式便可,功能都由nw.js實現了.
1 2 3 |
.navbar{ -webkit-app-region: drag; } |
帶有此樣式的元素能夠做爲窗口的拖拽區域,而且雙擊時最大化/還原窗口.
1 2 3 |
.navbar .navbar-collapse a { -webkit-app-region: no-drag; } |
被標誌爲可drag
的容器裏的連接將不可點擊,所以要特別爲連接加上no-drag
另外爲了讓程序看起來更像客戶端一點,我默認禁用掉了文本選擇,防止一些被做爲按鈕的a標籤的文本被選中
1 2 3 4 |
html { height: 100%; -webkit-user-select: none; } |
app.js
app.js做爲程序的入口點,定義了整個項目代碼的結構,須要特別拿出來講明一下.
1 |
angular.module('hmd', ['ui.router','hmd.studio']) |
定義angular模塊,modules
全部的業務模塊都會放到單獨的子目錄裏,如這裏註冊的hmd.studio
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//模塊根目錄 var baseModuleDir = './app/modules/'; //引入模塊,模塊內js文件會被自動加載到頁面中 hmd.regModule = function (name, reqModule) { hmd[name] = angular.module('hmd.' + name, reqModule || []); hmd[name].moduleName = name; //模塊存儲數據的目錄 hmd[name].dataPath = hmd.storeDir + '\\' + hmd[name].moduleName; fs.readdirSync(baseModuleDir + name) .forEach(function (file) { if (~file.indexOf('.js')) { document.write('<script src="modules/' + name + '/' + file + '"></script>'); } }); }; |
regModule
方法實現最簡單的模塊載入,自動加載模塊內的全部腳本到頁面中,併爲每一個模塊賦予一個單獨的數據存儲目錄dataPath
1 |
hmd.storeDir = require('nw.gui').App.dataPath; |
程序的數據存儲目錄
導航欄按鈕
導航欄右邊有4個按鈕,分別爲:檢查更新
、最小化
、最大化
、關閉
1 2 3 4 5 6 7 8 9 10 |
...
<div class="btn-group window-tool"> <a class="btn rectbtn" href="javascript://" title="點擊檢查更新"> <i class="glyphicon mdfi_action_system_update_tv"></i></a> <a class="btn rectbtn" href="javascript://"><i class="glyphicon glyphicon-minus"></i></a> <a class="btn rectbtn" href="javascript://"><i class="glyphicon glyphicon-fullscreen"></i></a> <a class="btn rectbtn" href="javascript://"><i class="glyphicon glyphicon-remove"></i></a> </div> ... |
檢查更新等之後再實現.如今先實現後面3個功能
由於這3個功能是全局的,所以在modules根目錄新建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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
(function () { var gui = require('nw.gui'), win = gui.Window.get(),winMaximize = false; angular.module('hmd.directives', []) .directive('hmdMinisize', [function () { return function (scope, elem) { $(elem[0]).on('click', function () { win.minimize(); }); }; }]) .directive('hmdMaxToggle', [function () { return function (scope, elem) { win.on('maximize', function () { winMaximize = true; $(elem[0]).find('i').removeClass('glyphicon-fullscreen').addClass('glyphicon-resize-small'); }); win.on('unmaximize', function () { winMaximize = false; $(elem[0]).find('i').removeClass('glyphicon-resize-small').addClass('glyphicon-fullscreen'); }); $(elem[0]).on('click', function () { if (winMaximize) { win.unmaximize(); } else { win.maximize(); } }); }; }]) .directive('hmdClose', [function () { return function (scope, elem) { $(elem[0]).on('click', function () { require('nw.gui').Window.get().close(); }); }; }]); })(); |
定義了全局directive模塊angular.module('hmd.directives', [])
,實現了3個Directive
.
接下來將directive應用到按鈕上
1 2 3 4 5 |
... <a class="btn rectbtn" href="javascript://" hmd-minisize><i class="glyphicon glyphicon-minus"></i></a> <a class="btn rectbtn" href="javascript://" hmd-max-toggle><i class="glyphicon glyphicon-fullscreen"></i></a> <a class="btn rectbtn" href="javascript://" hmd-close><i class="glyphicon glyphicon-remove"></i></a> ... |
將腳本引用<script src="modules/directives.js"></script>
添加到index.html
的app.js以後
app.js裏的angular模塊註冊裏增長hmd.directives模塊angular.module('hmd', ['ui.router','hmd.directives','hmd.studio'])
刷新程序,三個按鈕已經生效.
實現簡單的markdown編輯器
先在頁面添加相應的codemirror腳本引用
1 2 3 4 5 6 7 8 9 10 11 |
... <footer class="tool"></footer> <script src="lib/codemirror/lib/codemirror.js"></script> <script src="lib/codemirror/addon/mode/overlay.js"></script> <script src="lib/codemirror/addon/edit/continuelist.js"></script> <script src="lib/codemirror/mode/markdown/markdown.js"></script> <script src="lib/codemirror/mode/gfm/gfm.js"></script> <script src="lib/jquery-2.1.3.js"></script> ... |
而後在modules
目錄下新增studio
子目錄,全部編輯器功能都在這個模塊裏實現.
在app.js
裏增長加載studio
模塊的代碼
1 |
hmd.regModule('studio'); |
每一個子模塊通常都會包含route.js
,controllers.js
,directive.js
這三個基本的angular功能.以及views
子目錄,用於存放模塊用到的html視圖
studio
模塊多了一個editor.js
,咱們將編輯器的一些基本功能封裝在這個腳本里
定義路由
route.js
1 2 3 4 5 6 7 8 |
hmd.studio.config(function ($stateProvider, $urlRouterProvider) { $stateProvider .state('studio', { url: "/studio", templateUrl: "modules/studio/views/studio.html", controller: 'studio' }); }); |
修改app.js
,將默認路由指定到/studio
模塊
1 2 3 |
hmd.config(function ($stateProvider, $urlRouterProvider) { $urlRouterProvider.otherwise("/studio"); }); |
實現controller
controllers.js
1 2 3 4 5 |
var studio = hmd.studio; studio .controller('studio', function ($scope, $state, $stateParams) { console.log('stuido controller'); }); |
添加視圖模版
views/studio.html
1 2 3 |
<div class="content studio-wrap"> <textarea name="" cols="30" rows="10"></textarea> </div> |
從新打開應用,能夠看到模塊跳到了studio路由,而且執行了對應的控制器
實現editor
editor.js
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
var util = require('./helpers/util'); var defaultConfig = { theme: 'ambiance', mode: 'gfm', lineNumbers: false, extraKeys: {"Enter": "newlineAndIndentContinueMarkdownList"}, dragDrop: false, autofocus: true, lineWrapping: true, foldGutter: true, styleActiveLine: true };
hmd.editor = { init: function (options,filepath) { var el = options.el,txt,me = this; options = $.extend({}, defaultConfig, options); if(options.theme != 'default'){ $('head').append('<link href="lib/codemirror/theme/'+options.theme+'.css" rel="stylesheet" />'); } this.cm = this.cm || CodeMirror.fromTextArea(el, options); filepath && this.setFile(filepath); this.cm.on('change', function (em, changeObj) { me.hasChange = true; me.fire('change', { em: em, changeObj: changeObj }); }); this.cm.addKeyMap({ "Ctrl-S": function () { me.save(); } }); }, setFile:function(filepath){ var txt = util.readFileSync(filepath); this.filepath = filepath; this.cm.setValue(txt); }, saveAs:function(){ hmd.msg('保存新文件'); }, save: function () { var txt = this.cm.getValue(); if(this.filepath){ util.writeFileSync(this.filepath, txt); this.hasChange = false; var fileNameArr = this.filepath.split('\\'); hmd.msg('文件:' + fileNameArr[fileNameArr.length - 1] + '保存成功!'); this.fire('save'); } else{ this.saveAs(); } }, events: {}, fire: function (eventName, obj) { var me = this; this.events[eventName] && this.events[eventName].forEach(function (fn) { fn.call(me,obj); }); }, on: function (eventName, fn) { this.events[eventName] = this.events[eventName] || []; this.events[eventName].push(fn); } }; |
咱們將編輯器的實現封裝在hmd.editor
這個對象上.
編輯器的模式設置爲GFM.
實現directive
directives.js
1 2 3 4 5 6 7 8 |
var studio = hmd.studio;
studio.directive('hmdEditor', function () { return function ($scope, elem) { hmd.editor.init({el:elem[0]},'E:\\Temp\\test\\test.md'); }; }); |
定義了'hmd-editor
,用於綁定hmd.editor的調用.
在視圖模版裏調用hmd-deitor
1 2 3 |
<div class="content studio-wrap"> <textarea name="" cols="30" rows="10" hmd-editor></textarea> </div> |
刷新應用,能夠看到textarea已經變成markdown編輯器,按ctrl+s
保存會有簡單的提示.
最終效果
總結
到目前爲止,只是搭建了開發環境,實現了基礎的編輯器功能,還徹底不能真正的使用.
接下來幾篇暫定計劃是:
- 打開文件,保存新文件,系統設置等基本功能.
- 自動更新功能.
- 實時預覽窗口.
- 自動上傳圖片.
- 表情功能.
- 集成hexo命令.
附件
本篇結果打包
github項目地址