关于这个markdown笔记软件(五)——electron库
项目源码
https://github.com/andytt/ErtuilEditor
目录
关于这个markdown笔记软件(五)——electron库
关于这个markdown笔记软件(六)——应用逻辑、electron对话框
package.json
node通过package.json来管理项目,其中定义了项目的依赖、程序的入口等。
{
"name": "note",
"version": "0.0.5",
"description": "A small editer",
"main": "main.js",
"dependencies": {
"bootstrap": "^3.3.7",
"jquery": "^3.2.1",
"lowdb": "^1.0.0",
"marked": "^0.3.9",
"node-schedule": "^1.2.5",
"phantom": "^4.0.12"
},
"devDependencies": {
"electron-packager": "^10.1.0",
"electron": "^1.7.10",
"electron-prebuilt": "^1.4.13",
"electron-rebuild": "^1.6.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "electron main.js",
"packager": "electron-packager . ErtuilEditer --all --out ./OutApp --version 0.0.5 --overwrite --icon=./ertuil.icns",
"packageDarwin": "electron-packager . ErtuilEditer --platform=darwin --arch=x64 --icon=./ertuil.icns --out=./OutApp --app-version=0.0.5 --overwrite",
"packageLinux": "electron-packager . ErtuilEditer --platform=linux --arch=x64 --icon=./ertuil.icns --out=./OutApp --app-version=0.0.5 --overwrite",
"packageWindow": "electron-packager . ErtuilEditer --platform=win32 --arch=x64 --icon=./ertuil.icns --out=./OutApp --app-version=0.0.5 --overwrite "
},
"author": "Ertuil",
"license": "ISC"
}
其中main
定义了程序入口,在这里就是electron的主进程main.js文件。而dependencies
则是需要的依赖,在最后使用electron-packager打包的时候需要用到。
主进程
之前也说过,electron之间的进程分为主进程和渲染进程。通俗的讲,主进程负责窗口的管理,渲染进程负责GUI的渲染。他们之间通过ipcMain和ipcRender模块来进行通信。
main.js文件中主要实现了两件事情:
1. 两个窗口的管理。
2. 关于菜单栏的实现。
窗口管理
程序中主要有两个窗口,mainWindow是主要用户窗口,编辑窗口。而viewWindow这是markdown预览窗口。代码余下:
定义
const {app, BrowserWindow,Menu,shell} = require('electron')
let fs = require("fs")
const ipcMain = require('electron').ipcMain
const globalShortcut = require('electron').globalShortcut
const database = require(__dirname+'/js/database');
let mainWindow = null;
let input_window = null;//废除
let viewWindow = null;
var db_path =__dirname+"/data/data.json";
事件:
app的’window-all-closed’事件:窗口全部关闭时,如果不是Mac系统,就退出程序
app的’ready’事件:初始化数据库、加载主要窗口
app的’activate’事件:重新加载主要窗口
ipcMain的’open-view’的事件:自己定义的事件,监听到这个事件后,加载预览窗口,给出markdown预览。
加载html
在mainWindow的对象中限制了窗体的大小,并加载了index.html文件。
mainWindow = new BrowserWindow({width: 1000, height: 800,resizable:false});
mainWindow.loadURL('file://' + __dirname + '/index.html');
mainWindow.on('closed', function() {
mainWindow = null;
});
菜单
定义菜单需要传入一个菜单模版
let template = [{
label: '文件',
submenu: [{
label: '新建笔记',
accelerator: 'CmdOrCtrl+N',
click:function(){
if(mainWindow){
mainWindow.webContents.send('info_create');
}
}
},{
label: '新建标签',
accelerator: 'CmdOrCtrl+Shift+N',
click:function(){
if(mainWindow){
mainWindow.webContents.send('info_create_label');
}
}
},{
type: 'separator'
},{
label: '导入',
accelerator: 'CmdOrCtrl+O',
click:function(){
if(mainWindow){
mainWindow.webContents.send('info_open');
}
}
},{
label: '保存',
accelerator: 'CmdOrCtrl+S',
click:function(){
if(mainWindow){
mainWindow.webContents.send('info_save');
}
}
},{
label: '导出',
accelerator: 'CmdOrCtrl+Shift+W',
click:function(){
if(mainWindow){
mainWindow.webContents.send('info_out');
}
}
},{
type: 'separator'
},{
label: '删除笔记',
accelerator: 'CmdOrCtrl+D',
click:function(){
if(mainWindow){
mainWindow.webContents.send('info_remove');
}
}
},{
label: '删除标签',
accelerator: 'CmdOrCtrl+Shift+D',
click:function(){
if(mainWindow){
mainWindow.webContents.send('info_remove_label');
}
}
}]
},{
label: '编辑',
submenu: [{
label: '撤销',
accelerator: 'CmdOrCtrl+Z',
role: 'undo'
}, {
label: '重做',
accelerator: 'Shift+CmdOrCtrl+Z',
role: 'redo'
}, {
type: 'separator'
}, {
label: '插入',
accelerator: 'CmdOrCtrl+I',
click:function(){
if(mainWindow){
mainWindow.webContents.send('info_insert');
}
}
}, {
type: 'separator'
}, {
label: '剪切',
accelerator: 'CmdOrCtrl+X',
role: 'cut'
}, {
label: '复制',
accelerator: 'CmdOrCtrl+C',
role: 'copy'
}, {
label: '粘贴',
accelerator: 'CmdOrCtrl+V',
role: 'paste'
}, {
label: '全选',
accelerator: 'CmdOrCtrl+A',
role: 'selectall'
}]
}, {
label: '展示',
submenu: [{
label: '生成',
accelerator: 'CmdOrCtrl+P',
click:function(){
if(mainWindow){
mainWindow.webContents.send('info_show');
}
}
},{
type: 'separator'
},{
label: '切换开发者工具',
accelerator: (function () {
if (process.platform === 'darwin') {
return 'Alt+Command+I'
} else {
return 'Ctrl+Shift+I'
}
})(),
click: function (item, focusedWindow) {
if (focusedWindow) {
focusedWindow.toggleDevTools();
}
}
}]
}, {
label: '窗口',
role: 'window',
submenu: [{
label: '最小化',
accelerator: 'CmdOrCtrl+M',
role: 'minimize'
}, {
label: '关闭',
accelerator: 'CmdOrCtrl+W',
role: 'close'
}, {
type: 'separator'
}, {
label: '刷新',
accelerator: 'CmdOrCtrl+R',
click: function (item,focusedWindow) {
if(focusedWindow){
focusedWindow.reload();
}else{
app.emit('activate');
}
}
}]
}, {
label: '帮助',
role: 'help',
submenu: [{
label:'作者:Ertuil'
},{
label:'个人网站',
click:function () {
shell.openExternal('http://ertuil.top')
}
},{
label:'github',
click:function () {
shell.openExternal('https://github.com/andytt')
}
},{
label: '学习更多',
click: function () {
shell.openExternal('http://electron.atom.io')
}
}]
}]
有role属性的是系统定义的相关动作,比如复制、粘贴、关闭窗口等!
有click属性的,是自己定义的相关动作,大部分是mainWindow.webContents.send(‘xxx’);函数。这个函数的作用是向渲染进程发送事件,渲染进程监听到事件后调用相应函数、完成动作。
在渲染进程即file_manage.js文件中实现了对这些事情的监听:
ipcRenderer.on('info_create',function(){
create_new();
});
ipcRenderer.on('info_create_label',function(){
new_label()});
ipcRenderer.on('info_insert',function(){
import_file()});
ipcRenderer.on('info_remove',function(){delete_note()});
ipcRenderer.on('info_remove_label',function(){delete_label()});
ipcRenderer.on('info_show',function(){save_note(false,true);});
ipcRenderer.on('info_save',function(){save_note()});
ipcRenderer.on('info_out',function(){output_file()});
ipcRenderer.on('info_open',function(){input_file()});
苹果系统下的菜单有自己的规范,因此需要在该模版之前再加一个菜单,在menucreate()函数中完成。
最后,在app的ready事件的回调函数之中,将这个模版加载,并生成了菜单:
menucreate();
var menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);