Electron line and use a simple Markdown editor

Markdown is every one of our developers the skills required in the process of writing in Markdown, always looking for a variety of editors, but each editor can only meet the needs of a particular area, but not all meet everyday writing needs.

So sprouted try their own hands, the use of a Markdown editor Electron toss out.

I set out below the pain point of the ideal Markdown editor needs:

  1. Figure bed must have a function, but you can also upload your own pictures directly to the background, such as the seven cattle;
  2. Styles can be customized;
  3. Export HTML content can be pasted directly into public number editor, published directly, and the problem does not occur formats;
  4. Fixed module can be customized, such as the head of the article, or tail.
  5. You can customize features such as: automatic load random images, enrich our article content.
  6. It must be cross-platform.
  7. other.

Environment to build

Use Electron as a cross-platform development framework, is the best choice Besides that, such as: VS Code, Atom and other big brother-level application is also based on Electron development.

Electron

The use of JavaScript, HTML and CSS to build cross-platform desktop applications

electronjs.org/

Initial use Electron, we take a look at running back download:

# 克隆示例项目的仓库
$ git clone https://github.com/electron/electron-quick-start

# 进入这个仓库
$ cd electron-quick-start

# 安装依赖并运行
$ npm install && npm start
复制代码

VIEW

VUE is the current leader of the front end of the frame, but also the development of our people, not against. I am also a big fan of VUE, version 1.0 did not start the fire, I use the VUE.

electron-vue

These two together, that is, the article recommended simulatedgreg/electron-vue:

vue init simulatedgreg/electron-vue FanlyMD
复制代码

Plug and run:

npm install

npm run dev
复制代码

Select the plug

1. Ace Editor

Choose a good editor is essential:

Chairuosen / Vue2-Ace-Editor: Github.Com/chairuosen/...

npm install buefy vue2-ace-editor vue-material-design-icons --save
复制代码

2. markdown-it

Markdown content can be quickly resolved, I chose to use plug-ins are:markdown-it

npm install markdown-it --save
复制代码

3. electron-store

Since it is the editor application, all a lot of personalization and content, it is necessary to store locally, such as the style file editor needed, from the head of the tail of content definition and so on. Here I chose:electron-store

npm install electron-store --save
复制代码

Integrate

Everything is ready, then we started to implement a simple Markdown editing and preview function.

Look at the srcfolder structure:

.
├── README.md
├── app-screenshot.jpg
├── appveyor.yml
├── build
│   └── icons
│       ├── 256x256.png
│       ├── icon.icns
│       └── icon.ico
├── dist
│   ├── electron
│   │   └── main.js
│   └── web
├── package.json
├── src
│   ├── index.ejs
│   ├── main
│   │   ├── index.dev.js
│   │   ├── index.js
│   │   ├── mainMenu.js
│   │   ├── preview-server.js
│   │   └── renderer.js
│   ├── renderer
│   │   ├── App.vue
│   │   ├── assets
│   │   │   ├── css
│   │   │   │   └── coding01.css
│   │   │   └── logo.png
│   │   ├── components
│   │   │   ├── EditorPage.vue
│   │   │   └── Preview.vue
│   │   └── main.js
│   └── store
│       ├── content.js
│       └── store.js
├── static
└── yarn.lock
复制代码

APP entire structure is divided into left and right two left Markdown editing content, to the right to see the results in real time, and the page view mainly by the Renderer to render complete, so we first renderer/components/vue page created under: EditorPage.vue:

<div id="wrapper">
    <div id="editor" class="columns is-gapless is-mobile">
        <editor 
            id="aceeditor"
            ref="aceeditor"
            class="column"
            v-model="input" 
            @init="editorInit" 
            lang="markdown" 
            theme="twilight" 
            width="500px" 
            height="100%"></editor>
        <preview
            id="previewor" 
            class="column"
            ref="previewor"></preview>
    </div>
</div>
复制代码

Editing area

Use plug-ins on the left: require('vue2-ace-editor')processing real-time monitoring Editorinput Markdown content, content spread out.

watch: {
    input: function(newContent, oldContent) {
        messageBus.newContentToRender(newContent);
    }
},
复制代码

Which here messageBusis to vue and ipcRenderer related events put together the logic main.js:

import Vue from 'vue';
import App from './App';
import 'buefy/dist/buefy.css';
import util from 'util';
import { ipcRenderer } from 'electron';

if (!process.env.IS_WEB) Vue.use(require('vue-electron'))
Vue.config.productionTip = false

export const messageBus = new Vue({
  methods: {
    newContentToRender(newContent) {
      ipcRenderer.send('newContentToRender', newContent);
    },
    saveCurrentFile() {  }
  }
});

// 监听 newContentToPreview,将 url2preview 传递给 vue 的newContentToPreview 事件
// 即,传给 Preview 组件获取
ipcRenderer.on('newContentToPreview', (event, url2preview) => {
  console.log(`ipcRenderer.on newContentToPreview ${util.inspect(event)} ${url2preview}`);
  messageBus.$emit('newContentToPreview', url2preview);
});

/* eslint-disable no-new */
new Vue({
  components: { App },
  template: '<App/>'
}).$mount('#app')
复制代码

Content editor, real-time by the ipcRenderer.send('newContentToRender', newContent);issue to the next, that is, from Main process of ipcMain.on('newContentToRender', function(event, content)acquiring event.

Electron is only one application of a Main primary process, and localization lot of things (such as: local storage, file read and write, etc.) more handed over to Main process to handle.

As in this case, the first function you want to achieve is that "you can customize the fixed modules, such as the head of the article, or the tail."

We use a plug-in: electron-storefor storing the head and tail content, create Class:

import {
    app
} from 'electron'
import path from 'path'
import fs from 'fs'
import EStore from 'electron-store'

class Content {
    constructor() {
        this.estore = new EStore()
        this.estore.set('headercontent', `<img src="http://bimage.coding01.cn/logo.jpeg" class="logo">
		        <section class="textword"><span class="text">本文 <span id="word">111</span>字,需要 <span id="time"></span> 1分钟</span></section>`)
        this.estore.set('footercontent', `<hr>
      		<strong>coding01 期待您继续关注</strong>
      		<img src="http://bimage.coding01.cn/coding01_me.GIF" alt="qrcode">`)
    }

    // This will just return the property on the `data` object
    get(key, val) {
        return this.estore.get('windowBounds', val)
    }

    // ...and this will set it
    set(key, val) {
        this.estore.set(key, val)
    }

    getContent(content) {
        return this.headerContent + content + this.footerContent
    }

    getHeaderContent() {
        return this.estore.get('headercontent', '')
    }
    
    getFooterContent() {
        return this.estore.get('footercontent', '')
    }
}

// expose the class
export default Content
复制代码

Note: This just write head and tail to die content.

With head tail content, and content Markdown editor, we can integrate these contents, then output to our right side Previewcomponents of.

ipcMain.on('newContentToRender', function(event, content) {
  const rendered = renderContent(headerContent, footerContent, content, cssContent, 'layout1.html');
  
  const previewURL = newContent(rendered);
  mainWindow.webContents.send('newContentToPreview', previewURL);
});
复制代码

Among them, renderContent(headerContent, footerContent, content, cssContent, 'layout1.html')the method is to our heads, tails, Markdown content, css styles and our template layout1.htmlloaded. This is relatively simple, direct look at the code:

import mdit from 'markdown-it';
import ejs from 'ejs';

const mditConfig = {
    html:         true,  // Enable html tags in source
    xhtmlOut:     true,  // Use '/' to close single tags (<br />)
    breaks:       false, // Convert '\n' in paragraphs into <br>
    // langPrefix:   'language-',  // CSS language prefix for fenced blocks
    linkify:      true,  // Autoconvert url-like texts to links
    typographer:  false, // Enable smartypants and other sweet transforms
  
    // Highlighter function. Should return escaped html,
    // or '' if input not changed
    highlight: function (/*str, , lang*/) { return ''; }
};
const md = mdit(mditConfig);

const layouts = [];

export function renderContent(headerContent, footerContent, content, cssContent, layoutFile) {
    const text = md.render(content);
    const layout = layouts[layoutFile];
    const rendered = ejs.render(layout, {
        title: 'Page Title',
        content: text,
        cssContent: cssContent,
        headerContent: headerContent,
        footerContent: footerContent,
    });
    return rendered;
}

layouts['layout1.html'] = `
<html>
    <head>
        <meta charset='utf-8'>
        <title><%= title %></title>
        <style>
            <%- cssContent %>
        </style>
    </head>
    <body>
        <div class="markdown-body">
			<section class="body_header">
		        <%- headerContent %>
		    </section>
		    <div id="content">
		    	<%- content %>
            </div>
            <section class="body_footer">
		        <%- footerContent %>
		    </section>
		</div>
    </body>
</html>
`;
复制代码
  1. Here, the use of plug-ins markdown-itto parse Markdown, and then use ejs.render (various locations to populate the content of the template).
  2. Here, but also for our goal: the style must be customized under different situations and packages use different heads, tails, templates, style and provide a hint

When the contents have, we need to put it on the "server"const previewURL = newContent(rendered);

import http from 'http';
import url from 'url';

var server;
var content;

export function createServer() {
    if (server) throw new Error("Server already started");
    server = http.createServer(requestHandler);
    server.listen(0, "127.0.0.1");
}

export function newContent(text) {
    content = text;
    return genurl('content');
}

export function currentContent() {
    return content;
}

function genurl(pathname) {
    const url2preview = url.format({
        protocol: 'http',
        hostname: server.address().address,
        port: server.address().port,
        pathname: pathname
    });
    return url2preview;
}

function requestHandler(req, res) {
    try {
        res.writeHead(200, {
            'Content-Type': 'text/html',
            'Content-Length': content.length
        });
        res.end(content);
    } catch(err) {
        res.writeHead(500, {
            'Content-Type': 'text/plain'
        });
        res.end(err.stack);
    }
}
复制代码

Finally get a URL object, we transferred the right of Previewassembly, that is, bymainWindow.webContents.send('newContentToPreview', previewURL);

Note: In the process of communication between Main and Renderer, using ipcMainand ipcRenderer. ipcMainI can not take the initiative to a message ipcRenderer. Because the ipcMainonly .on()method does not .send()approach. You can only use webContents.

Preview area

Time is on the right side using a iframecontrol, make a specific component Preview:

<template>
    <iframe src=""/>
</template>

<script>
import { messageBus } from '../main.js';

export default {
    methods: {
        reload(previewSrcURL) {
            this.$el.src = previewSrcURL;
        }
    },
    created: function() {
        messageBus.$on('newContentToPreview', (url2preview) => {
            console.log(`newContentToPreview ${url2preview}`);
            this.reload(url2preview);
        });
    }
}
</script>

<style scoped>
iframe { height: 100%; }
</style>
复制代码

In the Previewcomponents that we use the vue $onmonitor newContentToPreviewevents in real time to load URL object.

messageBus.$on('newContentToPreview', (url2preview) => {
    this.reload(url2preview);
});
复制代码

So far, we have basically achieved the most basic version of Markdown editor functions, yarn run devrun to see results:

to sum up

The first time you use Electron, very superficial, but at least learned some knowledge:

  1. Each application has only one Main Electron process, and is mainly used for creating applications and systems to deal with window, Main course, the use of ipcMain listens for events from ipcRenderer, but did not send, you can only use BrowserWindow。webContents.send().
  2. Each page has a corresponding Renderer process for rendering the page. Of course, a corresponding ipcRenderer for receiving and sending events.
  3. In vue page components, we still vue of the aid $onand `` $ emit` deliver and receive messages.

Next step by step to improve the application, the goal is to meet their own needs, then is this: Maybe someday open sourced it.

Chinese coding problem solving

Since we are using iframe, so it is necessary in the iframeembedded <html></html>increase<meta charset='utf-8'>

<iframe id="ueditor_0" allowtransparency="true" width="100%" height="100%" frameborder="0" src="javascript:void(function(){document.open();document.write(&quot;<!DOCTYPE html><html xmlns='http://www.w3.org/1999/xhtml' ><head><style type='text/css'>body{font-family:sans-serif;}</style><link rel='stylesheet' type='text/css' href='https://res.wx.qq.com/mpres/zh_CN/htmledition/comm_htmledition/style/widget/ueditor_new/themes/iframe3f3927.css'/></head><body class='view' lang='en' ></body><script type='text/javascript'  id='_initialScript'>setTimeout(function(){window.parent.UE.instants['ueditorInstant0']._setup(document);},0);var _tmpScript = document.getElementById('_initialScript');_tmpScript.parentNode.removeChild(_tmpScript);</script></html>&quot;);document.close();}())" style="height: 400px;"></iframe>
复制代码

reference

  1. How to store user data in Electron medium.com/cameron-nok…
  2. Electron-vue develop practical 1 - Main Renderer development process and simple process. molunerfinn.com/electron-vu...
  3. An Electron & Vue.js quick start boilerplate with vue-cli scaffolding, common Vue plugins, electron-packager/electron-builder, unit/e2e testing, vue-devtools, and webpack. github.com/SimulatedGR…

Guess you like

Origin blog.csdn.net/weixin_33674437/article/details/91363969