我们将在Electron中创建一个新项目,如我们在第1章中所示,名为"编辑器",我们将在下一章中使用它来创建编辑器;在index.js中,这是我们的主要过程;请记住为Electron软件包放置必要的依赖项:
bash
npm init -y
cnpm install --save-dev electron
package.json
json
"start":"DEBUG=true electron ."
新建index.js
js
const { app, BrowserWindow } = require("electron");
function createWindow() {
let win = new BrowserWindow({
width: 800,
height: 600,
});
win.loadFile("index.html");
}
app.whenReady().then(createWindow);
新建index.html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initialscale=1.0">
<title>My Editor</title>
</head>
<body>
<textarea id="MyID"></textarea>
</body>
</html>
npm start
启动项目之后我们将看到如下的情形
配置文本编辑器让我们配置以下文本编辑器:
https://www.npmjs.com/package/simplemde
我们在项目中安装它时使用:
bash
npm i simplemde -registry=https://registry.npm.taobao.org
# 或者yarn安装
yarn add simplemde
修改index.html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initialscale=1.0">
<title>My Editor</title>
<link rel="stylesheet" href="./node_modules/simplemde/dist/simplemde.min.css">
<script src="./node_modules/simplemde/dist/simplemde.min.js">
</script>
</head>
<body>
<textarea id="MyID"></textarea>
<script>
var simplemde = new SimpleMDE({
element:
document.getElementById("MyID")
});
</script>
</body>
</html>
启动项目
bash
npm run start
在上面的代码中,我们加载编辑器插件的JS和CSS,并使用以下方法进行配置:
js
var simplemde = new SimpleMDE({
element:
document.getElementById("MyID")
});
其中,我们将先前创建的TEXTAREA的选择器作为引用传递。为了使编辑器填充屏幕的宽度,我们将使用CSS:
css
html,
body {
display: flex;
flex-direction: column;
height: 100%;
}
.CodeMirror {
flex: 1
}
编辑器应用程序和进程之间的通信
双方事件的使用对于Electron中的任何应用程序都是至关重要的,也是Electron应用程序的核心,对于我们使用编辑器的notes应用程序,能够为以下功能执行这项工作是至关重要的:
- 打开文件
- 保存文件
- 对文本执行一些操作,例如下划线、加粗等
这一点很重要,因为这取决于我们在哪里实施这些选项,我们可能必须沟通进程,例如,如果我们定义了一个带有粗体选项的菜单,该菜单是从渲染过程,因为它是由具有Electron对操作系统的相应调用;让我们请记住,我们与操作人员进行的所有沟通系统是通过渲染过程完成的,但我们想要的操作执行(将粗体应用于文本)将在编辑器中完成,其是安装在网页或呈现过程上的插件,因此,当单击菜单中的此选项时,我们必须发送从主进程到呈现进程的消息。
菜单结构
在本节中,我们将为应用程序创建选项菜单,该菜单由一组选项组成,既可以编辑内容,也可以执行文件操作,特别是打开和保存文件。
创建和打开文件
对于使用文件的部分,即保存或打开文件,我们将在项目中的新文件中执行此操作:editor-options.js
创建文件
为了保存文件,我们将创建一个新函数,它将接收窗口和我们要插入的数据作为参数,特别是与用户在编辑器中编写的HTML内容相对应的数据:
js
module.exports.save_file = function(win,data){}
要使用弹窗创建文件,请执行以下操作:
保存文件需要哪个模块,我们使用该模块:
js
const { dialog } = require('electron')
其模块包含打开文件保存对话框的功能:
js
const path = dialog.showSaveDialogSync(win,option)
其中:
win
,对应于将打开对话框的窗口。option
,选项对应于我们要处理的文件的标题和类型,在本例中为txt文件
js
const option = {
title: "Guardar archivo",
filters: [
{
name: "archivo",
extensions: ["txt"],
},
],
};
"showSaveDialogSync()"函数返回用户选择的文件;如果对话框被取消,则返回undefined。有了文件路径,我们可以用它来写,在Node中处理文件,我们有了模块:
js
const fs = require('fs')
内容如下:
js
fs.writeFileSync(path,data)
最后,函数变为:
js
const { app, dialog } = require("electron");
const fs = require("fs");
const path = require("path");
module.exports.save_file = function (win, data) {
const option = {
title: "Guardar archivo",
filters: [
{
name: "archivo",
extensions: ["txt"],
},
],
};
const path = dialog.showSaveDialogSync(win, option);
// console.log(path)
fs.writeFileSync(path, data);
/* dialog.showSaveDialog(win,option).then(result => {
console.log(result.canceled)
console.log(result.filePaths)
})*/
};
读取文件
要读取上述函数生成的文件,我们将使用一个新函数:
editor-options.js
js
module.exports.open_file = function(win){}
要使用alter窗口打开文件:
打开我们使用与上一个功能相同的模块的项目文件是必要的:
js
const { dialog } = require('electron')
我们有:
js
paths = dialog.showOpenDialogSync(win,option)
返回用户选择的文件的路径; 如果对话框被取消,则返回undefined。
有了这个路径,我们就可以通过fs模块读取文件的内容,并将数据返回给渲染进程或者网页; 最后的实现如下:
js
module.exports.open_file = function (win) {
const option = {
title: "Abrir archivo",
filters: [
{
name: "archivo",
extensions: ["txt"],
},
],
};
paths = dialog.showOpenDialogSync(win, option);
if (paths && paths.length > 0) {
const content = fs.readFileSync(paths[0]).toString();
win.webContents.send("file-open", content);
var filename = path.basename(paths[0]);
win.webContents.send("title-change", app.name + " " + filename);
}
};
菜单选项
在本节中,我们将为编辑器应用程序创建一个菜单,如第二章所述,我们将使用自定义选项,例如预定义的角色和分隔符,以及快捷键或键盘快捷键; 为了保持应用程序的组织性和模块化,我们将在单独的文件中定义菜单。
由于菜单是在渲染进程中定义的,并且将具有能够操作在网页中定义的 simplemde 插件中定义的文本的选项,因此我们必须在进程之间发送消息才能实现此操作; 例如,如果从菜单中,我们有一个将文本设置为粗体的选项,那么我们必须从菜单(主进程)向网页发送一条消息,并从这里实现将所选文本设置为粗体的功能 ; 考虑到这一点,我们有以下结构:
menu.js
js
const {
app,
ipcMain,
Menu,
shell,
BrowserWindow,
globalShortcut,
} = require("electron");
const { open_file, save_file } = require("./editor-options");
const template = [
{
label: "About us",
submenu: [
{
label: "About us",
click() {
shell.openExternal("https://www.electronjs.org");
},
},
],
},
{
label: "File",
submenu: [
{
label: "Save",
accelerator: "CommandOrControl+S",
click() {
const win = BrowserWindow.getFocusedWindow();
win.webContents.send("editorchannel", "file-save");
},
},
{
label: "Open",
accelerator: "CommandOrControl+O",
click() {
const win = BrowserWindow.getFocusedWindow();
open_file(win);
},
},
],
},
{
label: "Style and format",
submenu: [
{
label: "Bold",
click() {
const win = BrowserWindow.getFocusedWindow();
win.webContents.send("editorchannel", "style-bold");
console.log("Negritas");
},
},
{
label: "Italic",
click() {
const win = BrowserWindow.getFocusedWindow();
win.webContents.send("editor-channel", "style-italic");
},
},
{
type: "separator",
},
{
label: "H1",
click() {
const win = BrowserWindow.getFocusedWindow();
win.webContents.send("editor-channel", "style-h1");
},
},
{
label: "H2",
click() {
const win = BrowserWindow.getFocusedWindow();
win.webContents.send("editor-channel", "style-h2");
},
},
],
},
];
const menu = Menu.buildFromTemplate(template);
module.exports = menu;
在上面的脚本中,我们还定义了其他选项,例如打开外部链接:
js
shell.openExternal
通过定义到菜单项的加速器选项,我们可以指示激活这些选项的快捷键。
如果你想为每个操作系统设置自定义选项,你所要做的就是询问所使用的平台; 例如,如果我们想在使用 Windows 或 MacOS 时显示特定选项:
·menu.js·
js
if (process.platform == "win32" || process.platform == "darwin") {
template.push({
label: "Default",
submenu: [
{ role: "reload" },
{ role: "forceReload" },
{ role: "toggleDevTools" },
{ role: "resetZoom" },
{ role: "zoomIn" },
{ role: "zoomOut" },
{ type: "separator" },
{ role: "togglefullscreen" },
],
});
}
我们还将为应用程序实现一系列键盘快捷键,专门用于打开文件或保存内容,为此,我们将分别使用 ctrl/command+S 和 ctrl/command+O 的组合:
menu.js
js
ipcMain.on("editor-channel", (event, arg) => {
console.log("Mensaje recibido del canal 'editor-channel': " + arg);
});
ipcMain.on("file-open", (event, arg) => {
const win = BrowserWindow.getFocusedWindow();
open_file(win);
});
ipcMain.on("file-save", (event, arg) => {
const win = BrowserWindow.getFocusedWindow();
save_file(win, arg);
});
app.on("ready", () => {
globalShortcut.register("CommandOrControl+S", () => {
const win = BrowserWindow.getFocusedWindow();
win.webContents.send("editor-channel", "file-save");
});
globalShortcut.register("CommandOrControl+O", () => {
const win = BrowserWindow.getFocusedWindow();
open_file(win);
});
});
虽然,当在菜单中定义这些选项时:不需要通过 globalShortcut API 定义键盘快捷键,可以通过如下方式:
js
const template = [
{
label: 'About us',
click() {
shell.openExternal("https://www.electronjs.org")
}
},
{ label: 'File',
submenu: [
{
label: "Save",
accelerator: 'CommandOrControl+S',
***
},
{
label: "Open",
accelerator: 'CommandOrControl+O',
***
},
]
}
]
在 index.js 中,我们导入菜单,以便我们可以在应用程序中使用它:index.js
js
const menu = require('./menu')
//***
Menu.setApplicationMenu(menu)
最后,该菜单的所有处理主要基于发送消息,其中一些包括使用特定模块来执行其操作,例如打开或保存文件的对话框。
最后,在网页流程中,我们定义从menu.js调用的事件并执行相应的操作来编辑所选内容:
index.html
html
<script>
const { ipcRenderer } = require('electron')
ipcRenderer.on('message', (event, arg) => {
alert("Mensaje recibido desde el proceso principal: " + arg);
});
ipcRenderer.send('editor-channel', 'Página cargada')
ipcRenderer.on('file-open', (event, arg) => {
console.log("Mensaje recibido desde el proceso principal: " + arg);
simplemde.value(arg)
});
ipcRenderer.on('editorchannel', (event, arg) => {
console.log("Mensaje recibido desde el proceso principal: " + arg);
editorOptions(event, arg)
});
ipcRenderer.on('title-change', (event, arg) => {
document.title = arg;
});
function editorOptions(event, arg) {
console.log("editor")
switch (arg) {
case 'style-bold':
simplemde.toggleBold()
break;
case 'style-italic':
simplemde.toggleItalic()
break;
case 'style-h1':
simplemde.toggleHeading1()
break;
case 'style-h2':
simplemde.toggleHeading2()
break;
case 'file-save':
console.log(simplemde.value())
event.sender.send('file-save', simplemde.value())
break;
}
}
</script>
index.js完整代码如下
js
const { app, BrowserWindow, Menu } = require("electron");
const menu = require("./menu");
function createWindow() {
let win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
},
});
win.loadFile("index.html");
}
app.whenReady().then(createWindow);
Menu.setApplicationMenu(menu);
来自同一事件:
js
event.sender
我们可以将消息发送回发送事件的窗口。
simplemde 插件实现了一系列功能,允许您以编程方式编辑所选内容; 例如,toggleBold() 函数允许您将文本转换为粗体。
作为一个网页,我们可以使用 JavaScript 以及 HTML 和 CSS 的任何脚本,例如,我们可以实现其他附加选项,例如在客户端使用纯 JavaScript 的暗模式:
index.html
html
<script>
var simplemde = new SimpleMDE({
element: document.getElementById("MyID")
});
function darkMode() {
document.querySelector(".CodeMirror").style.background = "black"
}
</script>
按钮代码如下
index.html
html
<button onclick="darkMode()">Dark Mode</button>
对于打开文件的功能,由于它是一个必须由主进程处理的进程,我们发送一个事件,该函数用于上面显示的editorOptions()函数:
index.html
js
function openFile() {
ipcRenderer.send('file-open', 'file-open')
}
我们还可以使用HTML API拖放来实现打开文件的功能:
index.html
js
document.ondrop = function (event) {
event.preventDefault();
if (event.dataTransfer.items) {
item = event.dataTransfer.items[0];
if (item.kind == "file" && item.type == "text/plain") {
var reader = new FileReader()
var file = item.getAsFile();
reader.onload = e => {
//console.log(e.target.result);
simplemde.value(e.target.result);
}
reader.readAsText(file);
}
}
}
这些选项是多种多样的,但它们意味着主进程和渲染进程之间的通信,甚至可能有必要根据情况再次与渲染进程通信;例如,要打开一个文件,在该文件中,我们传递文件的内容:
editor-options.js
js
win.webContents.send('file-open',content)
在网页上,我们可以选择打开文件,稍后我们将看到:
index.html
js
function openFile() {
ipcRenderer.send('file-open', 'file-open')
}
另一个例子是关于保存文件的选项;按下菜单中的选项(主进程)会通过Electron中的事件向渲染进程发送一条消息(如我们在第2章中看到的):
从渲染过程中,我们获得编辑器中生成的HTML,将带有HTML内容的消息发送回渲染过程,以便它从操作系统请求对话框保存带有所述HTML内容的文件:
最后,当使用以下内容运行应用程序时:
bash
npm run start