使用NW.js进行跨平台开发

更多的应用程序正在利用Web技术。 例如, BracketsPeppermintPinegrow是由HTML,JavaScript和CSS制成的程序员编辑器。 这允许使用熟悉的工具,但也使应用程序本质上更具跨平台性。 在本教程中,我将向您展示如何使用NW.js制作可在Windows,Mac OS X和Linux中使用的简单程序员编辑器。

简介和下载NW.js

NW.js (最初称为Node Webkit)是将Node.js和WebKit HTML呈现程序打包在运行本地应用程序的程序包中。 NW.js版本正在使用io.js ,这是V8 JavaScript引擎的最新版本,具有更多ES6支持。 由于io.js与最新的Node.js 100%兼容,因此所有使用Node.js的库和程序也可以与io.js一起使用。

首先,请从NW.js下载所有三个OS版本,或仅下载要运行该项目的版本。 我将在MacBook Air上进行开发,但您可以使用所需的任何系统。 该项目是Fun Editor :一个易于使用的单一文档代码编辑器。 它是根据Linux座右铭构建的:一项任务做得很好!

获取零件

首先,必须在系统上安装nodeio.js。 一旦安装了node或io.js, npm命令就会出现在您的系统上。 此命令用于安装不同JavaScript库。

第一个图书馆是Bower 。 在命令行提示符下键入以下内容:

npm install -g bower

在某些系统上,您可能需要在npm命令之前使用sudo在超级用户模式下运行它。

bower命令是许多Web技术的软件包管理器。 它提供了一种简单的方法来将与Web相关的项目添加到您的项目中。

为了对DOM执行自定义操作,此编辑器将使用Zepto.js JavaScript库而不是jQuery。 由于Fun Editor仅将库用于DOM工作,因此Zepto只需花一小部分就可以解决问题。

为项目创建一个新目录。 在带有命令行的目录中,键入以下内容:

bower install less
bower install zepto

现在将有一个名为bower_components的新目录。 在该目录中,bower安装了lesszepto库。 这比查找网站和下载要容易得多。

Ace JavaScript库是此编辑器的基础。 它是JavaScript中的一种灵活且易于使用的编辑器,旨在嵌入网站中。 要安装,请在我们的项目目录的终端中键入以下内容:

git clone git://github.com/ajaxorg/ace.git

现在有一个新目录ace 。 库的所有源都在此目录中。 该库需要编译为紧凑格式,以加快加载速度。 在命令行中,输入:

cd ace
npm install
node Makefile.dryice.js full -m --target ./build

这些命令将您放入ace目录,安装ace编辑器所需的所有库,并在build子目录中创建最小化的库文件。

该项目将使用node-watch库来了解编辑器中的文件是否已更改。 每当指定文件更改时,该库就会激活回调函数。 要将这个库安装到我们的项目中,项目目录必须是节点包目录。 在项目目录的命令行中,键入以下内容:

cd ..
npm init

npm程序将询问有关该项目的几个问题。 回答问题后, package.json文件将位于项目目录中。 npm命令将存储此文件中安装的所有库的名称。 这样,将项目文件提供给其他人可以使他们创建相同的工作环境。

现在要安装node-watch库,请键入以下内容:

npm install node-watch --save

完成后,将在其中包含库的node_modules目录中。 –save标志告诉npm将库保存到项目中,而不是全局。

最后安装的软件是Emmet 。 没有Emmet,您将没有代码编辑器。 从GitHub上的Emmet获取Emmet代码,并将其保存到js目录中的emmet.js文件中。

现在所有项目都已放置在项目文件夹中,一切准备就绪,可以放在一起!

放在一起

NW.js项目所需的第一项是其项目文件。 不幸的是,节点项目文件使用相同的文件名。 由于在开发过程中不需要节点项目文件,因此可以将其移到一边,直到再次需要为止。 在项目目录的命令行中,键入以下内容:

mv package.json node.package.json
touch package.json

这些命令将原始package.json文件移至node.package.json并创建了一个名为package.json.的空文件package.json.package.json文件中,键入以下内容:

{
    "description": "A very small code editor.",
    "main": "main.html",
    "name": "Fun Editor",
    "version": "1.0",
    "window": {
        "height": 600,
        "width": 650,
        "show": false,
        "title": "Fun Editor",
        "toolbar": false,
        "icon": "icon.png"
    }
}

该文件告诉NW.js如何启动程序。 不同的字段是:

描述

这应该是该程序及其功能的简短描述。

主要

这是要打开的主要HTML文件。 该文件应包含程序主页的所有HTML。

名称

这是程序的名称。

这应该是程序的版本号。

窗口

这是一个json值数组,用于描述程序的用户界面。 该数组具有以下各项:

高度

这是启动时程序窗口高度的像素数。

宽度

这是启动时程序窗口宽度的像素数。

表演

这是一个布尔值,告诉NW.js加载时是否显示主窗口。 我将其设置为true 。 如果将其设置为false ,请确保以后有某种方法可以将其激活。

标题

这是程序的默认标题。 加载时将使用它,直到程序中的某些代码对其进行更改为止。

工具栏

此布尔值告诉NW.js是否具有工具栏。 由于FunEditor是一个简约的编辑器,因此将其设置为false 。 但是,如果您需要使用开发工具来调试编辑器,则可以将其设置为true,并且开发工具的图标将在那里。

图标

这是用于程序的图标的相对路径。 项目目录应该是该文件引用的基础。

由于main.html文件已在配置中,因此它是下一个文件。 在项目目录中,创建main.html文件,并将以下代码放入其中:

<!DOCTYPE html>
<html>

    <head>
		<title>Fun Editor</title>
		<script type="text/javascript" src="bower_components/zepto/zepto.min.js"></script>
		<script type="text/javascript" src="js/emmet.js"></script>
		<script type="text/javascript" src="ace/build/src-min-noconflict/ace.js"></script>
		<script type="text/javascript" src="ace/build/src-min-noconflict/ext-emmet.js"></script>
		<script type="text/javascript" src="ace/build/src-min-noconflict/ext-language_tools.js"></script>
		<script type="text/javascript" src="ace/build/src-min-noconflict/ext-spellcheck.js"></script>
		<script type="text/javascript" src="ace/build/src-min-noconflict/keybinding-vim.js"></script>
		<script type="text/javascript" src="js/FunEditor.js"></script>
		<link rel="stylesheet/less" type="text/css" href="less/default.less">
		<script type="text/javascript" src="bower_components/less/dist/less.min.js"></script>
	</head>

	<body>
		<div id="editor"></div>

		<div class="info">
			<span id="editMode" class="statuslineitem">Normal</span>
			<span class="title statuslineitem">
			<span class="arrow-right-editMode statuslineitem"></span>
			<span id="title">No file</span>
			</span>
			<span class="mode statuslineitem">
				<span class="arrow-right-title statuslineitem"></span>
			<span id="mode">JavaScript</span>
			</span>
			<span class="linenum statuslineitem">
				<span class="arrow-right-mode statuslineitem"></span>
			<span id="linenum">1</span>
			</span>
			<span class="colnum statuslineitem">
				<span class="arrow-right-linenum statuslineitem"></span>
			<span id="colnum">1</span>
			</span>
			<span class="arrow-right-colnum statuslineitem"></span>
		</div>

		<input style="display:none;" id="openFile" type="file" />
		<input style="display:none;" id="saveFile" type="file" />
	</body>
</html>

那是主程序。 这是一个简单HTML页面,主要包含一个要加载JavaScript文件列表,一个用于样式化所有内容的Less文件,一个具有editor id的div(用于编辑器)和一个具有info id的div和用于执行状态的跨度线。 我从“ 用Powerline弄乱Spiffy”教程中获得了状态行的样式。 它不使用电力线-看起来像。 底部是两个未显示的输入。 这些用于NW.js打开文件对话框和保存文件对话框。

良好的编码习惯会要求在页面末尾加载JavaScript。 由于直到程序告诉显示页面的窗口才会显示,所以没关系。

为了使HTML看起来正确,样式是下一个要创建的项目。 在项目目录中创建一个名为less的新目录。 然后创建文件default.less并将其添加到其中:

@StatusLineEditMode:    rgb(98, 105, 255);
@StatusLineTitle:       rgb(98, 247, 255);
@StatusLineMode:        rgb(98, 255, 149);
@StatusLineLineNum:     rgb(224, 217, 87);
@StatusLineColNum:      rgb(87, 212, 224);
@StatusLineBackground:  white;

body {
  margin: 0;
  padding: 0;
  margin-top: 0;
  overflow: hidden;
}

#editor {
  position: absolute;
  top: 0px;
  bottom: 25px;
  left: 0;
  right: 0;
  margin: 0px;
  padding: 0px;
}

.info {
  position: absolute;
  display: inline;
  bottom: 0px;
  font-family: monospace;
  white-space: nowrap;
  bottom: 0;
  background: @StatusLineBackground;
  padding: 0px;
  height: 25px;
  left: 0;
  width: 100%;
}

.statuslineitem {
    height: 25px;
    line-height: 25px;
    text-align: center;
    vertical-align: middle;
    margin: 0px;
    padding-top: 0px;
    padding-bottom: 0px;
    padding-right: 3px;
    padding-left: 0px;
    border: 0px;
    float: left;
}

.arrow-right-editMode {
    width: 0;
	height: 0;
	border-top: 13px solid transparent;
	border-bottom: 13px solid transparent;

	border-left: 13px solid @StatusLineEditMode;
	margin: 0px;
	padding: 0px;
	margin-left: 0px;
}

.arrow-right-title {
	width: 0;
	height: 0;
	border-top: 13px solid transparent;
	border-bottom: 13px solid transparent;

	border-left: 13px solid @StatusLineTitle;
	margin: 0px;
	padding: 0px;
	margin-left: 0px;
}

.arrow-right-mode {
	width: 0;
	height: 0;
	border-top: 13px solid transparent;
	border-bottom: 13px solid transparent;

	border-left: 13px solid @StatusLineMode;
	margin: 0px;
	padding: 0px;
	margin-left: 0px;
}

.arrow-right-linenum {
	width: 0;
	height: 0;
	border-top: 13px solid transparent;
	border-bottom: 13px solid transparent;

	border-left: 13px solid @StatusLineLineNum;
	margin: 0px;
	padding: 0px;
	margin-left: 0px;
}

.arrow-right-colnum {
	width: 0;
	height: 0;
	border-top: 13px solid transparent;
	border-bottom: 13px solid transparent;

	border-left: 13px solid @StatusLineColNum;
	margin: 0px;
	padding: 0px;
}

#editMode {
    background: @StatusLineEditMode;
    padding-left: 5px;
}

#title {
    background: @StatusLineTitle;
    padding-left: 5px;
}

#mode {
    background: @StatusLineMode;
    padding-left: 5px;
}

#linenum {
    background: @StatusLineLineNum;
    padding-left: 5px;
}

#colnum {
    background: @StatusLineColNum;
    padding-left: 5px;
}

#editMode {
    background: @StatusLineEditMode;
    padding-left: 5px;
}

.title {
    background: @StatusLineTitle;
}

.mode {
    background: @StatusLineMode;
}

.linenum {
    background: @StatusLineLineNum;
}

.colnum {
    background: @StatusLineColNum;
}

该样式信息用于使编辑器div位置绝对,并占用除底部25 px以外的所有浏览器窗口区域。 该区域用于状态行。

在文件的顶部,您会注意到几个Less变量定义。 由于相同的颜色在多个地方使用(即行号的跨度及其后的“箭头”跨度),因此我将它们设置为Less变量。 这样,我只需更改一项即可更改所有项目。 还可以使用手写笔或SASS,但是Less允许在代码中动态更改它们。 非常适合主题改变。

现在用于主程序文件。 在js目录中,创建一个名为FunEditor.js的文件,并放置以下代码:

//
// Program:     	Fun Editor
//
// Description: 	This is a basic editor built using NW.js.
//			This is more of a project to learn how to use
//			NW.js, but I am finding that I really like
//			the editor!
//
// Author:		Richard Guay (raguay@customct.com)
// License: 		MIT
//

//
// Class:		FunEditor
//
// Description: 	This class contains the information and functionality
//			of the Fun Editor. There should be only one instance
//			of this class per editor.
//
// Class Variables:
// 					editor 		Keeps the Editor object
// 					menu 		keeps the menu object for the pop-up
//								menu.
// 					menuEdit 		Edit menu for the popup menu
// 					menuEditMain 	The Edit menu for the main menu
// 					menuFile 		File menu for the popup menu
// 					menuFileMain 	File menu for the main menu
// 					nativeMenuBar 	The menu bar for OSX Main menu
// 					hasWriteAccess 	boolean for if the file is
//									writable or not.
// 					origFileName 	Last file name opened.
// 					watch		The node-watch library object.
// 					osenv		The osenv library object.
// 					gui 			The NW.js gui library
//								object.
// 					fs			The fs library object.
// 					clipboard 		The clipboard library object.
//
function FunEditor() {}

FunEditor.prototype.filesOpened = 0;
FunEditor.prototype.saving = false;
FunEditor.prototype.editor = null;
FunEditor.prototype.menu = null;
FunEditor.prototype.menuEdit = null;
FunEditor.prototype.menuFile = null;
FunEditor.prototype.menuEditMain = null;
FunEditor.prototype.menuFileMain = null;
FunEditor.prototype.nativeMenuBar = null;
FunEditor.prototype.fileEntry = null;
FunEditor.prototype.hasWriteAccess = false;
FunEditor.prototype.origFileName = "";
FunEditor.prototype.theme = {};
FunEditor.prototype.lastCursor = { line: 0, col: 0 };
FunEditor.prototype.watch = require("node-watch");
FunEditor.prototype.gui = require("nw.gui");
FunEditor.prototype.fs = require("fs");
FunEditor.prototype.osenv = require("osenv");
FunEditor.prototype.os = require("os");

var FE = new FunEditor();

FunEditor.prototype.clipboard = FE.gui.Clipboard.get();
FunEditor.prototype.win = FE.gui.Window.get();

//
// Function:	handleDocumentChange
//
// Description: 	This function is called whenever the document
//			is changed. This function will get the title set,
//			remove the old document name from the window
//			list, set the syntax highlighting based on the
//			file extension, and update the status line.
//
// Inputs:
// 			title 	Title of the new document
//
FunEditor.prototype.handleDocumentChange = function(title) {
	 //
	 // Setup the default syntax highlighting mode.
	 //
	 var mode = "ace/mode/javascript";
	 var modeName = "JavaScript";

	 //
	 // Set the new file name. If the title is blank, reflect that for
	 // setting a new one later.
	 //
	 this.fileEntry = title;

	 //
	 // Set the syntax highlighting based on the file ending.
	 //
	 if (title) {
	 	//
	 	// Set up file watching with node-watch. The file being edited is
	 	// watched for changes. If the file changes, then reload the file
	 	// into the editor.
	 	//
		this.watch(this.fileEntry, function() {
			//
			// The file changed. Load it into the editor. Needs implemented:
			// ask the user if they want the file to be reloaded.
			//
			if(! FE.saving ) {
    				FE.readFileIntoEditor(FE.fileEntry);
			}
		});

		//
		// If there is a title, then setup everything by that title.
		// The title will be the file name.
		//
		if(this.os.platform()  == "win32") {
		    title = title.match(/[^\\]+$/)[0];
		} else {
		    title = title.match(/[^/]+$/)[0];
		}
		if (this.origFileName.indexOf(title) == -1) {
			//
			// Remove whatever the old file was loaded and put in
			// the new file.
			//
			this.origFileName = title;
		}

		//
		// Check for OS permissions for writing. NOTE: Not implemented.
		//
		this.hasWriteAccess = true;

		//
		// Set the document's title to the file name.
		//
		document.getElementById("title").innerHTML = title;
		document.title = title;

		//
		// Set the syntax highlighting mode based on extension of the file.
		//
		if (title.match(/\.js$/)) {
			mode = "ace/mode/javascript";
			modeName = "JavaScript";
		} else if (title.match(/\.html$/)) {
			mode = "ace/mode/html";
			modeName = "HTML";
		} else if (title.match(/\.css$/)) {
			mode = "ace/mode/css";
			modeName = "CSS";
		} else if (title.match(/\.less$/)) {
			mode = "ace/mode/less";
			modeName = "LESS";
		}  else if (title.match(/\.md$/)) {
			mode = "ace/mode/markdown";
			modeName = "Markdown";
		} else if (title.match(/\.ft$/)) {
			mode = "ace/mode/markdown";
			modeName = "FoldingText";
		} else if (title.match(/\.markdown$/)) {
			mode = "ace/mode/markdown";
			modeName = "Markdown";
		} else if (title.match(/\.php$/)) {
			mode = "ace/mode/php";
			modeName = "PHP";
		}
	} else {
		//
		// Setting an empty document. Leave syntax highlighting as the last
		// file.
		//
		document.getElementById("title").innerHTML = "[no document loaded]";
		this.origFileName = "";
	}

	//
	// Tell the Editor and setup the status bar with the syntax highlight mode.
	//
	this.editor.getSession().setMode(mode);
	document.getElementById("mode").innerHTML = modeName;
};

//
// Function:        setCursorLast
//
// Description:     Set the cursor to the last stored state.
//
FunEditor.prototype.setCursorLast = function() {
    this.editor.moveCursorTo(this.lastCursor.line, this.lastCursor.col);
}

//
// Function: 		newFile
//
// Description: 	This function is called to set the global
//			variables properly for a new empty file.
//
// Inputs:
//
FunEditor.prototype.newFile = function() {
	this.fileEntry = null;
	this.hasWriteAccess = false;
	this.handleDocumentChange(null);
	this.editor.setValue("");
};

//
// Function:  	readFileIntoEditor
//
// Description: 	This function handles the reading of the file
//			contents into the editor. If reading fails, a
//			log entry is created.
//
// Inputs:
//			theFileEntry 	The path and file name
//
FunEditor.prototype.readFileIntoEditor = function(theFileEntry) {
	this.fs.readFile(theFileEntry, function(err, data) {
		if (err) {
			console.log("Error Reading file.");
		}

		//
		// Set the file properties.
		//
		FE.handleDocumentChange(theFileEntry);

		//
		// Set the file contents.
		//
		FE.editor.setValue(String(data));

		//
		// Remove the selection.
		//
		FE.editor.session.selection.clearSelection();

		//
		// Put the cursor to the last know position.
		//
		FE.setCursorLast();
	});
};

//
// Function:	writeEditorToFile
//
// Description: 	This function takes what is in the editor
//			and writes it out to the file.
//
// Inputs:
//			theFileEntry 	File to write the contents to.
//
FunEditor.prototype.writeEditorToFile = function(theFileEntry) {
	var str = this.editor.getValue();
	this.fs.writeFile(theFileEntry, str, function(err) {
		if (err) {
			console.log("Error Writing file.");
			return;
		}
	});
};

//
// Function: 	copyFunction
//
// Description: 	This function takes the current selection and copies it
//			to the clipboard.
//
FunEditor.prototype.copyFunction = function() {
	this.clipboard.set(this.editor.getCopyText());
};

//
// Function: 	cutFunction
//
// Description: 	This function cuts out the current selection and copies it
//			to the clipboard.
//
FunEditor.prototype.cutFunction = function() {
	this.clipboard.set(this.editor.getCopyText());
	this.editor.session.replace(this.editor.selection.getRange(),"");
};

//
// Function: 	pasteFunction
//
// Description: 	This function takes the clipboard and pastes it to the
//                  current location.
//
FunEditor.prototype.pasteFunction = function() {
	this.editor.session.replace(this.editor.selection.getRange(), this.clipboard.get());
};

//
// Function: 	openFile
//
// Description: 	This function opens the select a file dialog for opening
//			into the editor.
//
FunEditor.prototype.openFile = function() {
	$("#openFile").trigger("click");
};

//
// Function: 	saveFile
//
// Description: 	This function saves to the currently open file or
// 			opens the save file dialog.
//
FunEditor.prototype.saveFile = function() {
    this.saving = true;
	if (this.fileEntry && this.hasWriteAccess) {
		this.writeEditorToFile(this.fileEntry);
	} else {
		$("#saveFile").trigger("click");
	}
	this.saving = false;
};

//
// Function:	initMenus
//
// Description:	This function setups the right click menu and system used
//			in the editor.
//
// Inputs:
//
FunEditor.prototype.initMenus = function() {
	this.menu = new this.gui.Menu();
	this.menuFile = new this.gui.Menu();
	this.menuEdit = new this.gui.Menu();
	this.menuFile.append(new this.gui.MenuItem({
		label: "New",
		click: function() {
			FE.newFile();
		}
	}));
	this.menuFile.append(new this.gui.MenuItem({
		label: "Open",
		click: function() {
			FE.openFile();
		}
	}));
	this.menuFile.append(new this.gui.MenuItem({
		label: "Save",
		click: function() {
			FE.saveFile();
		}
	}));

	this.menuEdit.append(new this.gui.MenuItem({
		label: "Copy",
		click: function() {
			FE.copyFunction();
		}
	}));
	 this.menuEdit.append(new this.gui.MenuItem({
		  label: "Cut",
		  click: function() {
			FE.cutFunction();
		  }
	 }));
	 this.menuEdit.append(new this.gui.MenuItem({
		  label: "Paste",
		  click: function() {
		  	FE.pasteFunction();
		  }
	 }));

	 this.menuFileMain = new this.gui.Menu();
	 this.menuEditMain = new this.gui.Menu();
	 this.menuFileMain.append(new this.gui.MenuItem({
		  label: "New",
		  click: function() {
		  	FE.newFile();
		  }
	 }));
	 this.menuFileMain.append(new this.gui.MenuItem({
		  label: "Open",
		  click: function() {
		  	FE.openFile();
		  }
	 }));
	 this.menuFileMain.append(new this.gui.MenuItem({
		label: "Save",
		click: function() {
			FE.saveFile();
		}
	 }));

	 this.menuEditMain.append(new this.gui.MenuItem({
		label: "Copy",
		click: function() {
			FE.copyFunction();
		}
	 }));
	 this.menuEditMain.append(new this.gui.MenuItem({
		label: "Cut",
		click: function() {
			FE.cutFunction();
		}
	 }));
	 this.menuEditMain.append(new this.gui.MenuItem({
		label: "Paste",
		click: function() {
			FE.pasteFunction();
		}
	 }));

	 this.menu.append(new this.gui.MenuItem({
		label: "File",
		submenu: FE.menuFile
	 }));

	 this.menu.append(new this.gui.MenuItem({
		label: "Edit",
		submenu: FE.menuEdit
	 }));

	 //
	 // Create the main Mac menu also.
	 //
	 this.nativeMenuBar = new this.gui.Menu({
		type: "menubar"
	 });

	 if(this.os.platform()  == "darwin") {
		 this.nativeMenuBar.createMacBuiltin("Fun Editor", {
			hideEdit: true,
			hideWindow: true
		 });
	}

	 this.nativeMenuBar.append(new this.gui.MenuItem({
		label: "File",
		submenu: FE.menuFileMain
	 }));

	 this.nativeMenuBar.append(new this.gui.MenuItem({
		label: "Edit",
		submenu: FE.menuEditMain
	 }));
	 this.win.menu = this.nativeMenuBar;

	//
	// Add the menu to the contextmenu event listener.
	//
	document.getElementById("editor").addEventListener("contextmenu",
		function(ev) {
			ev.preventDefault();
			FE.menu.popup(ev.x, ev.y);
			return false;
		}
	);
};

//
// Function: 		onChosenFileToOpen
//
// Description: 	This function is called whenever a open
//			file dialog is closed with a file selection.
//			This is an automatically made function in
//			NW.js that needs to be set by your app.
//
// Inputs:
//			theFileEntry 		The path to the file selected.
//
onChosenFileToOpen = function(theFileEntry) {
	FE.readFileIntoEditor(theFileEntry);
};

//
// Function: 		onChosenFileToSave
//
// Description: 	When a file is selected to save into, this
//			function is called. It is originally set by
//			NW.js.
//
// Inputs:
//			theFileEntry 		The path to the file selected.
//
onChosenFileToSave = function(theFileEntry) {
	 FE.writeEditorToFile(theFileEntry);
};

//
// Function: 		onload
//
// Description: 	This function is setup by NW.js to be
// 					called when the page representing the application
// 					is loaded. The application overrides this by
// 					assigning it's own function.
//
// 					Here, we initialize everything needed for the
// 					Editor. It also loads the initial document for
// 					the editor, any plugins, and theme.
//
// Inputs:
//
onload = function() {

	 //
	 // Initialize the context menu.
	 //
	 FE.initMenus();

	 //
	 // Set the change function for saveFile and openFile.
	 //
	 $("#saveFile").change(function(evt) {
		onChosenFileToSave($(this).val());
	});
	$("#openFile").change(function(evt) {
		onChosenFileToOpen($(this).val());
	});

	FE.editor = ace.edit("editor");
	FE.editor.$blockScrolling = Infinity;
	FE.editor.setTheme("ace/theme/solarized_dark");
	FE.editor.getSession().setMode("ace/mode/javascript");
	FE.editor.setKeyboardHandler("ace/keyboard/vim");
	FE.editor.setOption("enableEmmet", true);
	FE.editor.setOption("selectionStyle","text");
	FE.editor.setOption("highlightActiveLine",true);
	FE.editor.setOption("cursorStyle","slim");
	FE.editor.setOption("autoScrollEditorIntoView",true);
	FE.editor.setOption("tabSize",4);
	FE.editor.setOption("enableSnippets",true);
	FE.editor.setOption("spellcheck",true);
	FE.editor.setOption("wrap",true);
	FE.editor.setOption("enableBasicAutocompletion",true);
	FE.editor.setOption("enableLiveAutocompletion",false);
	FE.editor.commands.addCommand({
		name: "myCopy",
		bindKey: {win: "Ctrl-C",  mac: "Command-C"},
		exec: function(editor) {
			FE.copyFunction();
		},
		readOnly: false
	});
	FE.editor.commands.addCommand({
		name: "myPaste",
		bindKey: {win: "Ctrl-V",  mac: "Command-V"},
		exec: function(editor) {
			FE.pasteFunction();
		},
		readOnly: false
	});
	FE.editor.commands.addCommand({
		name: "myCut",
		bindKey: {win: "Ctrl-X",  mac: "Command-X"},
		exec: function(editor) {
			FE.cutFunction();
		},
		readOnly: false
	});
	FE.editor.commands.addCommand({
		name: "mySave",
		bindKey: {win: "Ctrl-S",  mac: "Command-S"},
		exec: function(editor) {
			FE.saveFile();
		},
		readOnly: false
	});

	//
	// Tie into the Vim mode save function. FE one took some digging to find!
	//
	FE.editor.state.cm.save = function() {
		FE.saveFile();
	}

	//
	// Setup on events listeners. The first one is listen for cursor
	// movements to update the position in the file in the status line.
	// Next, setup the listener for Vim mode changing to update the
	// status line. Lastly, run function on window closing to remove
	// the current file from the open file list.
	//
	FE.editor.on("changeStatus", function() {
		//
		// Get the current cursor to set the row and column.
		//
		var cursor = FE.editor.selection.lead;
		document.getElementById("linenum").innerHTML = cursor.row + 1;
		document.getElementById("colnum").innerHTML = cursor.column + 1;

       		//
        		// Save a copy of the cursor location.
        		//
        		FE.lastCursor.line = cursor.row;
        		FE.lastCursor.col = cursor.column;

		//
		// Get the text mode to set the Normal, Visual, or Insert vim
		// modes in the status line.
		//
		var mode = FE.editor.keyBinding.getStatusText(editor);
		if (mode == "") {
			document.getElementById("editMode").innerHTML = "Normal";
		} else if (mode == "VISUAL") {
			document.getElementById("editMode").innerHTML = "Visual";
		} else if (mode == "INSERT") {
			document.getElementById("editMode").innerHTML = "Insert";
		}
	});

	//
	// Capture the Ace editor's copy and paste signals to get
	// or put to the system clipboard.
	//
	FE.editor.on("copy",function(text) {
		FE.clipboard.set(text);
	});
	FE.editor.on("paste", function(e) {
		e.text = FE.clipboard.get();
	});

	//
	// Capture the window close and make
	// sure the file has been saved.
	//
	 FE.win.on("close", function() {
	 	//
	 	// Make sure the contents are saved.
	 	//
	 	if (this.fileEntry && this.hasWriteAccess) {
		 	FE.saveFile();
		 }

	 	//
	 	// Close the program.
	 	//
		this.close(true);
	 });

	 //
	 // Setup for having a new empty file loaded.
	 //
	 FE.newFile();
	 onresize();

	 //
	 // Show the program and set the focus (focus does not work!).
	 //
	 FE.win.show();
	 FE.win.focus();
};

//
// Function: 		onresize
//
// Description: 	Another NW.js function that is called every time
//			the application is resized.
//
// Inputs:
//
onresize = function() {
};

在文件的顶部,您将看到FunEditor对象的声明和一些变量。 在变量之后,有三个require语句。 这些语句将加载用于监视文件更改的node-watch库,用于与图形用户界面进行交互的nw.gui库,用于访问文件系统的fs库以及用于与操作系统一起使用的os库。 。 guifsos库是NW.js程序的一部分,而node-watchnpm下载的库

对于gui库, FunEditor.clipboard变量包含用于访问系统剪贴板的剪贴板对象。 同样, FunEditor.win变量包含NW.js为此程序创建的主窗口的window对象。

加载库和全局变量后,将创建几个帮助器函数。 以FunEditor开头的所有函数都是用于运行编辑器的函数,并不特定于NW.js。 NW.js需要其他功能。

我在这里描述了每个功能:

FunEditor.handleDocumentChange

此函数根据加载的文档为编辑器加载正确的语法高亮显示。 它还在状态行中设置窗口标题和文档名称。 设置窗口标题后,它将检查FunEditor.os.platform()使用的操作系统。 如果是Windows操作系统,则最后一个目录分隔符的搜索必须不同,因为Windows使用\ ,而OS X和Linux使用/

此功能还可以使用FunEditor.watch设置文件监视。 更改给函数的文件后,回调函数会将文件重新加载到编辑器中。

FunEditor.setCursorLast

此功能会将光标设置到最后保存的位置。 每次移动光标并将其记录在状态行中时,都会保存该位置。 更改后只要重新加载文件,此功能便会用于将光标移回最后一个已知位置。

FunEditor.newFile

此功能用于创建新的空白文件。

FunEditor.readFileIntoEditor

此函数使用FunEditor.fs库参考变量从文件系统中读取文件并将其放入编辑器。 然后,它清除选择并将光标放在最后一个已知的位置。

FunEditor.writeEditorToFile

该函数获取编辑器的当前状态,并使用FunEditor.fs变量将其保存到文件系统中。

FunEditor.copyFunction

此函数使用NW.js库获取当前选定的文本并将其放入系统剪贴板。

FunEditor.cutFunction

该函数与FunEditor.copyFunction相同,除了它从编辑器中删除所选文本。

FunEditor.pasteFunction

此功能使用剪贴板的NW.js库获取剪贴板的内容,并将其粘贴到编辑器中的当前光标位置。

FunEditor.openFile

此功能通过触发对隐藏输入元素的单击来打开系统打开文件对话框。 这也是NW.js特有的。

FunEditor.saveFile

如果编辑器的内容是从文件加载的,并且该文件具有写访问权,则此功能会将其保存到文件中。 否则,它将打开系统另存为对话框供用户选择文件。 这也可以通过触发对隐藏的saveFile元素的单击来实现。

FunEditor.initMenus

此功能为上下文菜单和系统菜单创建图形菜单元素。 这利用了FunEditor.gui库。

Mac OS X的主菜单添加了功能。 在该部分中, FunEditor.os库告诉软件平台。 如果是Mac OS X,则执行主菜单所需的其他功能。

打开文件

关闭“ 打开文件”对话框时,此NW.js定义的函数将获取选定的文件。 然后,此函数将使用FunEditor.readFileIntoEditor函数将该文件加载到编辑器中。

保存选择文件

另存为文件对话框关闭时,此NW.js定义的函数将获取选定的文件。 然后,此函数将使用FunEditor.writeEditorToFile函数将编辑器内容保存到给定文件中。

负载

每当main.html文件中定义的所有内容都加载到NW.js中时,都会调用此NW.js函数。 等效于document.onload = function(){}; 或jQuery $(document).ready(funcition(){});

此功能使编辑器可以根据需要进行设置。 主题是黑色的日光,vim布局的键盘,活动的行高亮显示等。用于复制,粘贴,剪切和保存的编辑器键盘快捷键使用Windows和Mac默认设置。 ace编辑器负责跨平台问题。

编辑器初始化后,将设置changeStatuscopypastesave编辑器事件的侦听器。 这些使编辑器可以在ace编辑器中强制使用系统剪贴板,并更新状态行中的行和列信息。 changeStatus侦听器将更新vim模式和当前光标位置。

我还研究了Vim模式具有的保存功能( :w )。 这花费了一些工作,但我也终于弄清楚了。 Ace编辑器从“代码镜像”中借用代码来创建Vim键盘布局。 它有效,但不是可靠的解决方案。

大小调整

每当调整窗口大小时,都会调用此NW.js定义的函数。 对于FunEditor,无需执行任何操作。

在不同平台上运行

这就是编辑器的全部编程工作。 现在,在每个平台上运行它。 如果尚未为每个平台下载NW.js,请立即下载并按照每个平台的说明进行操作。 提取项目目录中的所有文件,并将其压缩到zip存档中。 完成后,将存档的名称更改为FunEditor.nw

在每个平台上,您始终可以转到命令行并运行 项目目录中的nw命令。 不幸的是,基本安装不会将可执行文件放在系统路径中。 因此,根据所使用的外壳,可以为可执行文件创建一个nw别名。 在开发时,我在项目目录中使用它来测试:

nw .

创建FunEditor.nw ,我将使用:

nw FunEditor.nw

验证工作正常后,我便开始为其他系统打包。

苹果电脑

在Mac上安装NW.js之后,您将在Applications目录中拥有nwjs.app文件。 在压缩应用程序的目录中,可以使用以下命令行运行该程序:

/Applications/nwjs.app/Contents/MacOS/nwjs FunEditor.nw

为了使点击包,改变FunEditor.nw文件app.nw 。 将/Applications/nwjs.app复制到/Applications/FunEditor.app 。 在Finder中 ,右键单击该应用程序,然后选择“ 显示目录” 。 将app.nw文件放置在Resources目录中,然后将图标更改为所需的图标。 我在下载中包含一个。 请记住保留nw.icns文件名。

要在菜单中获得正确的名称,您将必须更改info.plist文件。 打开它,并将内容更改为:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>BuildMachineOSBuild</key>
	<string>12F45</string>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleDisplayName</key>
	<string>FunEditor</string>
	<key>CFBundleExecutable</key>
	<string>nwjs</string>
	<key>CFBundleIconFile</key>
	<string>nw.icns</string>
	<key>CFBundleIdentifier</key>
	<string>io.nwjs.nw</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>FunEditor</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleVersion</key>
	<string>1.0</string>
	<key>DTSDKBuild</key>
	<string>12F37</string>
	<key>DTSDKName</key>
	<string>macosx10.8</string>
	<key>DTXcode</key>
	<string>0511</string>
	<key>DTXcodeBuild</key>
	<string>5B1008</string>
	<key>LSFileQuarantineEnabled</key>
	<false/>
	<key>LSMinimumSystemVersion</key>
	<string>10.6.0</string>
	<key>NSPrincipalClass</key>
	<string>NSApplication</string>
	<key>NSSupportsAutomaticGraphicsSwitching</key>
	<true/>
	<key>SCMRevision</key>
	<string>df30fb73b312044486237d93cf96f3606862f2a3</string>
</dict>
</plist>

我为NW.js提取了info.plist文件,删除了所有特定于它的文件,以nw扩展名启动文件,将版本更改为1.0,并将名称更改为FunEditor

Mac OS X上的FunEditor
Mac OS X上的FunEditor

完成后,您将拥有一个可单击的FunEditor应用程序。

视窗

在Windows上运行编辑器的最简单方法是使用批处理文件。 在系统路径中的某个位置创建文件FunEditor.bat 。 在文件中输入以下内容:

<path to nw.exe>\nw.exe <path to FunEditor>\FunEdit.nw
Windows 7上的FunEditor
Windows 7上的FunEditor

双击批处理文件时,FunEditor将打开。

的Linux

下载NW.js程序后,请确保路径所在的目录。 创建一个脚本文件,该脚本文件将与您的程序一起调用NW.js程序。 使用以下命令在路径中创建一个名为FunEditor的文件:

#!/usr/bin/bash
nwjs="<path to nwjs directory>/nwjs";
fe="<path to the FunEditor.nw file>";

$nwjs $fe

使用以下命令保存并设置可执行权限:

chmod a+x FunEditor

如果现在运行该命令,它将启动Linux编辑器!

Arch Linux上的FunEditor
Arch Linux上的FunEditor

在我的Arch Linux上,FunEditor使用起来很有趣!

结论

通过该项目,您学习了如何利用NW.js创建可在Windows,Linux和Mac OS X上运行的编辑器。该项目还有很大的改进空间。 将这个小小的编辑器变成您的理想编辑器,并在每个操作系统上使用它,祝您玩得开心!

翻译自: https://code.tutsplus.com/tutorials/cross-platform-development-with-nwjs--cms-23281

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值