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:
- Figure bed must have a function, but you can also upload your own pictures directly to the background, such as the seven cattle;
- Styles can be customized;
- Export HTML content can be pasted directly into public number editor, published directly, and the problem does not occur formats;
- Fixed module can be customized, such as the head of the article, or tail.
- You can customize features such as: automatic load random images, enrich our article content.
- It must be cross-platform.
- 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
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 src
folder 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 Editor
input Markdown content, content spread out.
watch: {
input: function(newContent, oldContent) {
messageBus.newContentToRender(newContent);
}
},
复制代码
Which here messageBus
is 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-store
for 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 Preview
components 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.html
loaded. 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>
`;
复制代码
- Here, the use of plug-ins
markdown-it
to parse Markdown, and then use ejs.render (various locations to populate the content of the template).- 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 Preview
assembly, that is, bymainWindow.webContents.send('newContentToPreview', previewURL);
Note: In the process of communication between Main and Renderer, using
ipcMain
andipcRenderer
.ipcMain
I can not take the initiative to a messageipcRenderer
. Because theipcMain
only.on()
method does not.send()
approach. You can only usewebContents
.
Preview area
Time is on the right side using a iframe
control, 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 Preview
components that we use the vue $on
monitor newContentToPreview
events 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 dev
run to see results:
to sum up
The first time you use Electron, very superficial, but at least learned some knowledge:
- 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()
.- Each page has a corresponding Renderer process for rendering the page. Of course, a corresponding ipcRenderer for receiving and sending events.
- In vue page components, we still vue of the aid
$on
and `` $ 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 iframe
embedded <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("<!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>");document.close();}())" style="height: 400px;"></iframe>
复制代码
reference
- How to store user data in Electron medium.com/cameron-nok…
- Electron-vue develop practical 1 - Main Renderer development process and simple process. molunerfinn.com/electron-vu...
- 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…