前段时间用hexo重新搭了个人博客,顺便写了个简单的博客搭建教程.
用markdown写起博客流畅很多,但是用了几个markdown编辑器,都没有一个适合自己使用的。于是就想自己动手做一个,当然不是完全从0开始做,语法高亮和markdown解析都用的是开源的项目.从这篇开始,我会把整个开发过程记录成系列随笔,因此开发进度较为缓慢.
博客写得少,像这样写长一点的随笔就有点混乱,看不懂的请用力喷,我会努力改进.
先介绍下开发过程中用到的一些比较重要的开源项目:
关于这些开源项目的使用,我在这系列文章里不会详细解释,如果有疑问,可以去看官网的入门教程和wiki,当然也欢迎讨论.

图片里的是我目前的项目结构,大概讲解一下一些目录和文件的用途。
icudtl.dat,nw.exe,nw.pak这3个是nw.js在windows运行所必须的文件.
package.jsonnw.js的配置文件
1234567891011121314151617181920212223  | {  "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", //logo    "toolbar": true, //是否显示地址栏工具条(调试的时候启用)    "frame": false, //是否显示程序边框    "width": 1000, //默认宽度    "height": 700, //默认高度    "position": "center", //启动时在屏幕中的位置    "min_width": 600, //最小宽度    "min_height": 400 //最小高度  }} | 
app目录程序的所有源代码的根目录.
app/lib存放angular,jquery,codemirror等开源库/框架的源代码
app/helpers存放一些node的工具函数
app/modules程序代码在这个目录,按功能模块分成不同的子目录.modules/app.js是整个程序的入口点
app/package.jsonnode模块配置,注意与上层的package.json意义不同
app/index.html程序的主界面窗口
index.html
123456789101112131415161718192021222324252627282930  | <!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> <!--end codemirror--> <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,hmd为自定义的根模块名 angular.bootstrap($('body'), ['hmd']);</script></body></html>  | 
只贴出部分代码.以后的所有代码也类似,都只会把重要的贴出来,并给出完整的链接.
界面采用比较简洁的三栏布局,分别为导航栏、内容区、状态栏/工具条.最顶部的地址栏只有在开发的时候为了方便调试才开启,发布时会关闭掉.
为了美观,我们在配置里去掉了系统自带的边框.因此要实现自定义的拖动窗口功能还需要增加一些设置.所谓的设置,其实只要加上对应的样式即可,功能都由nw.js实现了.
123  | .navbar{  -webkit-app-region: drag;} | 
带有此样式的元素可以作为窗口的拖拽区域,并且双击时最大化/还原窗口.
123  | .navbar .navbar-collapse a {    -webkit-app-region: no-drag;} | 
被标志为可drag的容器里的链接将不可点击,因此要特别为链接加上no-drag
另外为了让程序看起来更像客户端一点,我默认禁用掉了文本选择,防止一些被作为按钮的a标签的文本被选中
1234  | html {    height: 100%;    -webkit-user-select: none;} | 
app.js作为程序的入口点,定义了整个项目代码的结构,需要特别拿出来说明一下.
1  | angular.module('hmd', ['ui.router','hmd.studio']) | 
定义angular模块,modules所有的业务模块都会放到单独的子目录里,如这里注册的hmd.studio
123456789101112131415  | //模块根目录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个按钮,分别为:检查更新、最小化、最大化、关闭
12345678910  | ...<!--导航栏功能按钮--><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.
1234567891011121314151617181920212223242526272829303132333435363738394041424344  | (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) {      //窗口最大化和还原时会触发对应的事件,在事件里去控制按钮样式.      //TODO:这里的实现应该可以优化得更优雅一点,以后再说      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 |