Electron简易编辑器实现:打开保存文件,自定义菜单,代码高亮提示等功能
ipcmain.js
var {Menu,shell,ipcMain,BrowserWindow,app} =require('electron'); var template = [ { label: '文件', submenu: [ { label: '新建', accelerator:"Ctrl+N", click: function(){ //主进程通知渲染进程操作文件 BrowserWindow.getFocusedWindow().webContents.send('action','new'); } }, { label: '打开', accelerator:"Ctrl+O", click: function(){ //主进程通知渲染进程操作文件 BrowserWindow.getFocusedWindow().webContents.send('action','open'); } }, { accelerator:"Ctrl+S", label: '保存', click: function(){ BrowserWindow.getFocusedWindow().webContents.send('action','save'); } }, { type: 'separator' }, { label: '打印', accelerator:"Ctrl+P", click: function(){ //打印功能通过 webContents https://electronjs.org/docs/api/web-contents BrowserWindow.getFocusedWindow().webContents.print(); } }, { label: '退出', accelerator:"Ctrl+Q", click: function(){ //要提示用户保存 未保存的文件 //主进程通知渲染进程执行退出操作 BrowserWindow.getFocusedWindow().webContents.send('action','exit'); } } ] }, { label: '编辑', submenu: [ { label: '撤销', role: 'undo' }, { label: '恢复', role: 'redo' }, { type: 'separator' }, { label: '截切', role: 'cut' }, { label: '复制', role: 'copy' }, { label: '黏贴', role: 'paste' }, { label: '删除', role: 'delete' }, { label: '全选', role: 'selectall' } ] }, { label: '视图', submenu: [ { label: '加载', role: 'reload' }, { label: '缩小', role: 'zoomin' }, { label: '放大', role: 'zoomout' }, { label: '重置缩放', role: 'resetzoom' }, { type: 'separator' }, { label: '全屏', role: 'togglefullscreen' } ] }, { label: '帮助', submenu: [ { label: '关于', click() { shell.openExternal('https://www.itying.com'); } } ] } ]; var m=Menu.buildFromTemplate(template); Menu.setApplicationMenu(m); //右键菜单 const contextMenuTemplate=[ { label: '撤销', role: 'undo' }, { label: '恢复', role: 'redo' }, { type: 'separator' }, { label: '截切', role: 'cut' }, { label: '复制', role: 'copy' }, { label: '黏贴', role: 'paste' }, { type: 'separator' }, //分隔线 { label: '全选', role: 'selectall' } //Select All菜单项 ]; var contextMenu=Menu.buildFromTemplate(contextMenuTemplate); // 监听右键事件 ipcMain.on('contextMenu',function(){ contextMenu.popup(BrowserWindow.getFocusedWindow()) }) //监听客户端的退出操作 ipcMain.on('exit-app',()=>{ app.quit(); })
ipcrender.js
var {ipcRenderer,remote}=require('electron'); var fs=require('fs'); document.title='无标题' //获取文本框dom var textAreaDom=document.querySelector("#textArea"); /* 问题: 1、新建 打开 保存的问题 2、如果已经保存 第二次保存的时候不提示直接保存 3、判断文件是否已经保存 改变软件左上角的内容 */ var isSave=true; //判断文件是否保存 var currentFile=''; //保存当前文件的路径 //内容变化的时候 让isSave等于false textAreaDom.oninput=function(){ if(isSave){document.title+=" *"} isSave=false; } document.addEventListener('contextmenu',function(e){ e.preventDefault(); ipcRenderer.send('contextMenu'); }) //监听主进程的操作 ipcRenderer.on('action',function(event,action){ console.log(action); switch(action){ case "new": //判断文件是否保存 如果没有保存提示 并保存 askSaveDialog(); textAreaDom.value=''; break; case "open": //判断文件是否保存 如果没有保存提示 并保存 askSaveDialog(); //通过dialog打开文件 var dir= remote.dialog.showOpenDialog({ properties:['openFile'] }); if(dir){ var fsData=fs.readFileSync(dir[0]); //获取文件里面的东西 // textAreaDom.value=fsData; editor.setValue(fsData.toString()); //注意传入的数据 } break; case "save": saveCurrentDoc(); break; case "exit": askSaveDialog(); //同步方法 //通知主进程退出应用 ipcRenderer.send('exit-app') break; } }) //判断文件师傅保存并执行保存功能 function askSaveDialog(){ if(!isSave){ var index=remote.dialog.showMessageBox({ type:"question", message:'是否要保存此文件?', buttons:['Yes','No'] }) if(index==0){ //执行保存操作 saveCurrentDoc(); } } } //执行保存的方法 function saveCurrentDoc(){ if(!currentFile){ //当前文件路径不存在 提示保存 var dir=remote.dialog.showSaveDialog({ defaultPath:'aaa.txt', filters: [ {name: 'All Files', extensions: ['*']} ] }); if(dir){ currentFile=dir; fs.writeFileSync(currentFile,editor.getValue()); isSave=true; //改变软件的标题 document.title=currentFile; } }else{ // editor.getValue() 获取编辑器的值 // fs.writeFileSync(currentFile,textAreaDom.value); fs.writeFileSync(currentFile,editor.getValue()); isSave=true; //改变软件的标题 document.title=currentFile; } }
index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <link rel="stylesheet" href="./static/css/index.css"> </head> <body> <textarea id="textArea"></textarea> </body> <link rel=stylesheet href="static/codemirror/doc/docs.css"> <link rel="stylesheet" href="static/codemirror/lib/codemirror.css"> <script src="static/codemirror/lib/codemirror.js"></script> <script src="static/codemirror/addon/selection/selection-pointer.js"></script> <script src="static/codemirror/mode/xml/xml.js"></script> <script src="static/codemirror/mode/javascript/javascript.js"></script> <script src="static/codemirror/mode/css/css.js"></script> <script src="static/codemirror/mode/vbscript/vbscript.js"></script> <script src="static/codemirror/mode/htmlmixed/htmlmixed.js"></script> <script> // Define an extended mixed-mode that understands vbscript and // leaves mustache/handlebars embedded templates in html mode var mixedMode = { name: "htmlmixed", scriptTypes: [{matches: /\/x-handlebars-template|\/x-mustache/i, mode: null}, {matches: /(text|application)\/(x-)?vb(a|script)/i, mode: "vbscript"}] }; var editor = CodeMirror.fromTextArea(document.getElementById("textArea"), { mode: mixedMode, selectionPointer: true }); </script> <style> .CodeMirror{ height: 100%; } </style> <script src="./renderer/ipcRenderer.js"></script> </html>
index.js
import { app, BrowserWindow } from 'electron'; // Handle creating/removing shortcuts on Windows when installing/uninstalling. if (require('electron-squirrel-startup')) { // eslint-disable-line global-require app.quit(); } // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. let mainWindow; const createWindow = () => { // Create the browser window. mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true } }); // and load the index.html of the app. mainWindow.loadURL(`file://${__dirname}/index.html`); // Open the DevTools. mainWindow.webContents.openDevTools(); // Emitted when the window is closed. mainWindow.on('closed', () => { // Dereference the window object, usually you would store windows // in an array if your app supports multi windows, this is the time // when you should delete the corresponding element. mainWindow = null; }); //引入icpMain require('./main/ipcMain.js'); }; // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.on('ready', createWindow); // Quit when all windows are closed. app.on('window-all-closed', () => { // On OS X it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform !== 'darwin') { app.quit(); } }); app.on('activate', () => { // On OS X it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (mainWindow === null) { createWindow(); } }); // In this file you can include the rest of your app's specific main process // code. You can also put them in separate files and import them here.
备注:
CodeMirror在线编辑器插件: github地址:https://github.com/codemirror/CodeMirror 官网:http://codemirror.net/ CodeMirror api: http://codemirror.net/doc/manual.html#api 使用CodeMirror: 1.下载: 2、看文档使用 http://codemirror.net/mode/index.html 注意: https://github.com/codemirror/CodeMirror CodeMirror最新版本使用的是es6的语法,但是由于nodejs不支持 es6的import 所以我们的项目里面没法用最新的版本 教程使用的是codemirror-5.2 下载codemirror-5.2,然后看官方文档使用 插件要设置和获取值: doc.setValue设置值 doc.getValue 或获取值 var editor = CodeMirror.fromTextArea(document.getElementById("textArea"), { mode: mixedMode, selectionPointer: true }); editor.setValue() editor.getValue() codemirror-5.2修改codemirror.js的地方: 17行左右: this.CodeMirror = mod(); 改为 window.CodeMirror = mod(); 8400行左右 return string.split(/\r\n?|\n/); 改为 return string.toString().split(/\r\n?|\n/); 或者可以不修改,但是传入数据必须是string fsData.toString()
运行项目:
npm start
最后,关注【码上加油站】微信公众号后,有疑惑有问题想加油的小伙伴可以码上加入社群,让我们一起码上加油吧!!!