编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

Node Webkit入门创建一个简单Markdown编辑器

wxchong 2024-08-05 01:22:35 开源技术 36 ℃ 0 评论

桌面应用程序已经消亡?如果从Node-Webkit项目在Github上的Star数量来看的话,结果并不是那么悲观。虽然现在越来越多的程序是在云上部署,还是有很多的工作需要在我们本地的电脑上完成,类似文本编辑工具,Markdown编辑器,游戏等等,都还需要离线状态下运行。

之前我就说过,我个人非常的喜欢Markdown标记语言,如果能在本地实现一个简单的Markdown编辑器的话,应该也是一件不错的事情。本文将简单的介绍一些Node Webkit的基础知识,我们将基于Windows进行演示,但是可以非常简单的部署到别的系统中。

什么是Node WebKit

Node Webkit是Chromium的一个分离版本,Chromium是著名的Google Chrome浏览器的开源基础(The open source base of Google Chome)。使用NW,我们可以使用最常见的HTML,CSS和JavaScript来创建可以运行中Windows,Max OSX,Linux桌面上的应用程序。也许,你可以使用HTML,CSS,JavaScript来创建强大的Web应用程序,但是,如果我们想对系统层面进行操作,比喻说修改系统上的某一个文件,目前你无法实现。使用NW,可以很方便实现上面的任务,这也是NW强大的部分,因为NW使用了Node.js,依赖NodeJS的模块,比喻说,fs(对系统中的文件进行操作的模块)和request(实现对各种Web Service的请求,而不需要考虑跨域的问题),基于对模块的实用,你可以实现很多之前无法完成对任务。还有振奋人心的是,你甚至可以实用很多的本地数据库的操作(NodeJS已经包含了很多对数据库进行操作的模块,比喻说MySQL),而且,因为它是分离的Chromium,所以也是支持localStorage和sessionStorage的,可以非常简单的实现持久化。

开始做点正事吧

移步到Node Webkit Github主页,根据你自己的系统下载NW。本文中,我将使用Windows(X64)版本,NodeJS已经中NW中包含了,但是如果你本地已经安装了NodeJS了的话,也是没有问题到,你也可以继续实用npm命令来安装依赖包。

zip文件下载后,解压,然后创建一个 package.json 文件,这个文件中需要包含你对应用程序的相关的配置,例如:版本,名字,需要安装的模块,Webkit的工作栏是否该显示等信息,下面是我的文件的内容。

{ "name": "markdown_editor", "main": "./html/index.html", "window": { "toolbar": true, "show": true, "icon": "./img/icon.png"

}, "dependencies": { "marked": "^0.3.3"

}

}

  • name: 应用程序的名字,这个名字不能带空格。

  • main:应用程序启动后,需要加载的HTML文件。

  • window:Webkit窗口的对象信息

    • toolbar:定义Webkit的工具栏是否该显示,工具栏包含一个刷新按钮,一个地址栏,一个可以打开Chromium 开发者工具的按钮。

    • show:打开应用程序后,窗口是否该打开。某些场景下,不需要打开窗口程序,比喻说,你只想让程序运行为一个系统托盘图标。

    • icon:窗口图标。

  • dependencies:依赖的NodeJS模块

package.json可以包含更多的选项,你可以参考下面的链接。

在项目的根目录下,我创建了4个文件夹:css,html,js和img,当然,你已经明白这4个文件夹代表的含义,

在img文件中,我增加了一个名字为'icon.png'的图片(如上面的package.json描述对应)。下面,你可以看到其他2个文件的代码块

html/index.html

<!DOCTYPE html><html><head>

<title>Markdown Editor</title>

<link rel="stylesheet" type="text/css" href="../css/screen.css" media="screen" />

<script src="../js/jquery/jquery.min.js" type="text/javascript"></script>

<script type="text/javascript" src="../js/main.js"></script></head><body>

<textarea name="markdown" id="editor" class="md_editor"></textarea>

<div class="md_result">

</div></body></html>

css/screen.css

html, body{ height:98%;

}body{ font-family:Arial;

}a{ text-decoration:none !important;

}.md_editor{ float:left; width:50%; height:100%; resize:none; padding:0; margin:0;

}.md_result{ float:left; width:49%; box-sizing:border-box; padding:10px 10px 13px 10px; margin:0; height:100%; overflow-y:scroll;

}

你可以创建js/main.js(内容会在下面逐渐完善)。

上面的操作是一个最简单的配置,目前这个程序还看到任何的有用的功能,如果你点击"nw.exe",package.json会被读取,程序会解析到应该首先加载"index.html",截止目前,页面中只会包含一个文本编辑器(text editor)和位于右边的一个空的div,接下来,我们会完善左边的编辑器,让它包含一个Markdown 标记语言编辑器,然后对应的HTML展现中右边的div中。

增加Markdown解析器

在这一节中,我们首先要增加的是一个Markdown解析器,利用这个解析器,当你在左边输入内容的时候,内容都会得到解析,并在右边的div中以HTML的形式进行展现。回头,再来看package.json,我们中dependencies这个部分增加了marked依赖,现在需要做的是,打开项目的根目录(包含package.json的目录),在这个目录中,执行 ** npm install ** (请确保npm能正常运行)。NPM会坚持项目的依赖项,然后从NPM仓库中获取,并下载到本地。执行结束后,观察项目目录,会看到一个名字为node_modules的目录,这个目录里已经包含了marked这个依赖项。

现在,marked依赖已经安装,我们可以在程序中调用了,首先,我们需要在 index.html中的部分包含此脚本。

<script type="text/javascript">global.window = window;global.$ = $;global.gui = require('nw.gui');

init();

</script>

上面的代码中,我们为global全局变量,增加了 window 变量,$(jQuery)变量和gui变量(我们可以使用这个变量来控制窗口的样式,比喻说,Webkit窗体的菜单栏)。这样做的原因是,NodeJS上下文中并不包含这些变量,NODEJS文件中,你不可以使用window.document的方式来进行调用,但是如果你执行全局变量的指定的话,你可以使用global.window.document方式来进行调用。

现在中文件js/main.js中,增加 init() 方法,目前为止,这个方法还是空的,我们再创建一个名字为js/editor.js,这个文件包含Markdown编辑器的相关逻辑,代码中包含如何的代码段

exports.reload = function(){ var marked = require("marked");

marked.setOptions({ gfm: true, tables: true, breaks: false, pedantic: false, sanitize: false, smartLists: true, smartypants: false

}); var resultDiv = global.$('.md_result'); var textEditor = global.$('#editor'); var text = textEditor.val();

resultDiv.html(marked(text));

};

该文件中,我们增加reload()exports 变量,这个变量会在require()方法被调用时候返回。而 require() 方法会在你试图加载a.js或者NODEJS模块时候被调用。这个方法调用 marked模块然后设置它的属性,其中最重要的选项是 sanitize,如果这个选项设置为true的话,Markdown中的HTML属性会被编码,这个不会是你希望的样子,因为Markdown不可避免的与HTML一起混合编写。

resultDiv是包含HTML代码展示的元素,而 textEditor是我们的文本区域,最后一行代码是将解析完毕的HTML代码展现在resultDiv中。

接下来,我们编辑 main.js ,下面是 main.js 部分的代码:

function init(){ global.$(global.window.document).ready(function(){ var editor = require("./../js/editor.js"); var textEditor = global.$('#editor');

textEditor.bind('input propertychange', function() {

editor.reload();

});

});

}

当窗体加载完成后,init方法将被调用,editor文件将被 requied,然后 raload() 方法会被调用。editor.js文件,我们使用相对路径的方式来进行加载。然后,编辑器(textEditor变量)会被调用。我们需要绑定一个文本编辑器的input propertychange 事件,达到编辑器中任何文本修改的操作都会使得 reload() 方法得到执行,新的HTML会展现在右边的DIV中。

图示:目前为止文本编辑的运行状态,HTML已经得到正确的展现。

为程序增加菜单

在这一部分,我们将为顶层的Webkit窗口增加一个菜单,这个菜单包含 "New","Open","Save" 和"Exit",我们首先要创建一个 menu.js 文件。文件内容如下:

exports.initMenu = function(){ var win = global.gui.Window.get(); var menubar = new global.gui.Menu({ type: 'menubar' }); var fileMenu = new global.gui.Menu();

fileMenu.append(new global.gui.MenuItem({

label: 'New',

click: function() {

}

}));

fileMenu.append(new global.gui.MenuItem({

label: 'Open',

click: function() {

}

}));

fileMenu.append(new global.gui.MenuItem({

label: 'Save',

click: function() {

}

}));

fileMenu.append(new global.gui.MenuItem({

label: 'Exit',

click: function() { global.gui.App.quit();

}

}));

menubar.append(new global.gui.MenuItem({ label: 'File', submenu: fileMenu}));

win.menu = menubar;

};

上面的代码中,win 对象得到请求调用。如果需要改变Webkit窗口对象的话,这个对象就需要获取到,比喻说现在,增加一个菜单。一个菜单对象被创建出来,而且增加了不同选项到子菜单,现在只有Exit菜单做一些真实的事情,关闭事件被调用时,会有动作需要执行。你需要增加这部分代码到 main.jsinit()方法中。

接下来,我们需要做 editor.js 中增加2个方法。

exports.loadText = function(text){ var textEditor = global.$('#editor');

textEditor.val(text);

exports.reload();

};

exports.loadFile = function(file){ var fs = require('fs');

fs.readFile(file, 'utf8', function (err,data) { if (err) { return console.log(err);

}

exports.loadText(data);

});

};

loadText() 会替换当前左侧编辑区域的内容,当文本替换后, reload() 会被调用,所以Markdown可以直接被解析为展现的HTML文档,做右侧的div中显示。而loadFile()方法是加载一个给定的地址的文件,读取内容,并显示在文本区域中。

现在我们可以实现 文件打开 的业务了,我们首先在 index.html中增加下面的代码。

<input style="display:none;" id="openFileDialog" type="file" />

这是一个隐藏的文件选择对话框,当"Open"按钮按下的时候,我们让这个对话框打开并显示。在editor.js中增加如下代码:

exports.chooseFile = function(name, callback) { var chooser = global.$(name);

chooser.change(function(evt) {

callback(global.$(this).val());

});

chooser.trigger('click');

};

该方法会得到文件对话框的名字,name对应的就是上面定义的#openFileDialog,第二个参数是我们选择要打开的文件后,要执行的方法,要执行成功,你需要在 menu.js 文件中 "require" editor.js ,然后在initMenu()方法中要做相应的调整。

var editor = require("./../js/editor.js");

接下来,我们可以在menu.js中完成打开文件的方法。

fileMenu.append(new global.gui.MenuItem({ label: 'Open',

click: function() {

editor.chooseFile("#openFileDialog", function(filename){

editor.loadFile(filename);

});

}

}));

这一段代码调用editor.js中的 chooseFile() 方法,如果你选择了文件的话,文件的路径会传递给loadFile()方法,然后文本区域会替换。

接下来,我们可以实现"New"命令,新建按钮按下的时候,左边的文本区域会被置空,右边的区域也会移除,在上面我们开发的代码的基础上,我们可以通过几行简单的代码来实现该功能:

fileMenu.append(new global.gui.MenuItem({ label: 'New',

click: function() {

editor.loadText("");

}

}));

最后一个要实现的功能,我们可以实现菜单中的"Save"按钮,当这个按钮被按下时,一个新文件对话框会打开,你可以指定保存的文件的位置。首先要做的是在index.html中增加一个hidden域,你可以把它放在打开文件的下面。

<input style="display:none;" type="file" id="saveFileDialog" nwsaveas="file.md" />

nwsaveas用来设置保存的文字的默认名字,然后我们在editor.js中,编写如下的代码:

fileMenu.append(new global.gui.MenuItem({ label: 'Save', click: function() {

editor.chooseFile("#saveFileDialog", function(filename){ var fs = require('fs'); var textEditor = global.$('#editor');

fs.writeFile(filename, textEditor.val(), function(err) { if(err) { console.log(err);

} else { console.log("The file was saved!");

}

});

});

}

}));

Save按钮按下时,我可以使用chooseFile()方法,文件路径和文件名字输入后,fs模块会加载,文本区域的内容会通过writeFile()方法保存到文件中。

截止目前我们编写了一个简陋但是可以运行的Markdown编写工具,我们可以看一下解析来,可以做一些什么。

其他的功能

文件后缀

我们的程序需要处理的时.md文件,所以我们需要做的是,读取传输给程序的arguments,可以在main.jsinit()方法中进行操作:

if(global.gui.App.argv.length > 0){

editor.loadFile(global.gui.App.argv[0]);

}

多选行,进行Tab

Notepad++ 这一类的程序有一个非常好用的功能是,你可以同时选择多行,然后按下Tab键时,多行同时移动,按下 Shift+Tab的时候,进行相反的移动,这里我们可以使用一个非常好用的JavaScript库,Taboverride,你需要做的是,在index.html中包含它,然后在main.js中的init()方法中进行编写。

tabOverride.set(global.window.document.getElementsByTagName('textarea'));

完结

我们已经实现了一个很好的简单的编辑奇,当然,还需要一些其他的功能,比喻说:

区分Save和Save As

现在当你点击Save按钮的时候,总会有一个保存文件的对话框出现,但是当你点击Save As按钮后,程序应该记住,你之前保存的位置,然后进行一个直接的保存。

关闭时候保存

目前,如果你正在编辑一个文件,某种情况下,关闭应用程序,不会有任何的提示,你需要保存一个变量changed,用来标记系统是不是有改动,如果有改动的话,要弹出提示,提示用户是否保存。

快捷键

现在系统还缺少快捷键的操作,比喻说常用的Ctrl+S保存,Ctrl+O打开。

打印到PDF

如果程序支持打印功能的话,就更好了,我们可以将Markdown变成PDF,然后将PDF通过打印程序打印,如果感兴趣的话也可以使用PhontomJS来进行HTML到PDF的打印。

创建一个工具条

MS Word形式的工具条也是一个非常好的功能,比喻说,我们可以 标题号,斜体,粗体,等等,放置在工具条上,方便操作

移除调试条

如果你是在进行开发,那么Debug工具条是一个非常好的帮手,但是如果你需要进行发布,你可以通过编辑package.json中的toolbar属性为false来移除.

重定向外部URL

如果你想要点击一个链接时,在Webkit窗口中打开一个外部网站的话,你可以通过捕获URL的点击,然后在默认浏览器中打开选择的URL.

打包Editor程序

如果要打包程序的话,打包成一个独立的.exe或者.app程序,你可以阅读官方的文档.

https://github.com/nwjs/nw.js/wiki/How-to-package-and-distribute-your-apps

文档中,详细描述了所有的过程.

更多文章,查看 www.wahwoo.org.

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表