Electron简易编辑器实现:打开保存文件,自定义菜单,代码高亮提示等功能

ipcmain.jsjavascript

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.jscss

var {ipcRenderer,remote}=require('electron');
var fs=require('fs');

document.title='无标题'

//获取文本框dom

var textAreaDom=document.querySelector("#textArea");

/*

问题:
    一、新建 打开 保存的问题

    二、若是已经保存 第二次保存的时候不提示直接保存

    三、判断文件是否已经保存  改变软件左上角的内容

*/

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.htmlhtml

<!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.jsjava

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.

备注:node

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()

运行项目:git

npm start