开发小程序费用_使用Electron简略撸一个Markdown修改

摘要: 运用Electron简易撸一个Markdown编写器的方式 本文关键详细介绍了运用Electron简易撸一个Markdown编写器的方式,网编感觉挺好的,如今共享给大伙儿,也给大伙儿做下参照。一起追随网...

利用Electron简单撸一个Markdown编辑器的方法       这篇文章主要介绍了利用Electron简单撸一个Markdown编辑器的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

Markdown 是我们每一位开发者的必备技能,在写 Markdown 过程中,总是寻找了各种各样的编辑器,但每种编辑器都只能满足某一方面的需要,却不能都满足于日常写作的各种需求。

所以萌生出自己动手试试,利用 Electron 折腾一个 Markdown 编辑器出来。

下面罗列出我所理想的 Markdown 编辑器的痛点需求:

必须要有图床功能,而且还可以直接上传到自己的图片后台,如七牛; 样式必须是可以自定义的; 导出的 HTML 内容可以直接粘贴到公众号编辑器里,直接发布,而不会出现格式的问题; 可以自定义固定模块,如文章的头部,或者尾部。 必须是跨平台的。

环境搭建

使用 Electron 作为跨平台开发框架,是目前最理想的选择,再者说,如:VS Code、Atom 等大佬级别的应用也是基于 Electron 开发的。

Electron

使用 JavaScript, HTML 和 CSS 构建跨平台的桌面应用

初次使用 Electron,我们下载回来运行看看:

# 克隆示例项目的仓库
$ git clone electron/electron-quick-start
# 进入这个仓库
$ cd electron-quick-start
# 安装依赖并运行
$ npm install npm start

VUE

VUE 是当前的前端框架的佼佼者,而且还是我们国人开发的,不得不服。本人也是 VUE 的忠实粉丝,在还没火的 1.0 版本开始,我就使用 VUE 了。

electron-vue

将这两者结合在一起,也就是本文推荐使用的 simulatedgreg/electron-vue:

vue init simulatedgreg/electron-vue FanlyMD

安装插件,并运行:

npm installnpm run dev

选择插件

1. Ace Editor

选择一个好的编辑器至关重要:

chairuosen/vue2-ace-editor:
npm install buefy vue2-ace-editor vue-material-design-icons --save

2. markdown-it

能够快速的解析 Markdown 内容,我选择是用插件:markdown-it

npm install markdown-it --save

3. electron-store

既然是编辑器应用,所有很多个性化设置和内容,就有必要存于本地,如编辑器所需要的样式文件、自定义的头部尾部内容等。这里我选择:electron-store

npm install electron-store --save

整合

万事俱备,接下来我们就开始着手实现简单的 Markdown 的编辑和预览功能。

先看 src 文件夹结构:

├── 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 主要分成左右两列结构,左侧编辑 Markdown 内容,右侧实时看到效果,而页面视图主要由 Renderer 来渲染完成,ponents/ 下创建 vue 页面:EditorPage.vue:

 div id="wrapper" 
 div id="editor" 
 editor 
 id="aceeditor"
 ref="aceeditor"
 v-model="input" 
 @init="editorInit" 
 lang="markdown" 
 theme="twilight" 
 width="500px" 
 height="100%" /editor 
 preview
 id="previewor" 
 ref="previewor" /preview 
 /div 
 /div 

编辑区

左侧使用插件:require('vue2-ace-editor'),处理实时监听 Editor 输入 Markdown 内容,将内容传出去。

watch: {
 input: function(newContent, oldContent) {
 messageBus.newContentToRender(newContent);
},

其中这里的 messageBus 就是把 vue 和 ipcRenderer 相关逻辑事件放在一起的 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')

编辑器的内容,将实时由 ipcRenderer.send('newContentToRender', newContent); 下发出去,即由 Main 进程的 ipcMain.on('newContentToRender', function(event, content) 事件获取。

一个 Electron 应用只有一个 Main 主进程,很多和本地化东西 (如:本地存储,文件读写等) 更多的交由 Main 进程来处理。

如本案例中,想要实现的第一个功能就是,「可以自定义固定模块,如文章的头部,或者尾部」

我们使用一个插件:electron-store,用于存储头部和尾部内容,创建Class:

import {
} 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="logo.jpeg" 
 section span 本文 span id="word" 111 /span 字,需要 span id="time" /span 1分钟 /span /section `)
 this.estore.set('footercontent', ` hr 
 strong coding01 期待您继续关注 /strong 
 img src="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
注:这里只是写死的头部和尾部内容。

有了头尾部内容,和编辑器的 Markdown 内容,我们就可以将这些内容整合,然后输出给我们的右侧 Preview 组件了。

ipcMain.on('newContentToRender', function(event, content) {
 ');
 const previewURL = newContent(rendered);
 mainWindow.webContents.send('newContentToPreview', previewURL);
});
< 载入。这个就比较简单了,直接看代码:

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;
'] = `
 html 
 head 
 meta charset='utf-8' 
 title %= title % /title 
 style 
 %- cssContent % 
 /style 
 /head 
 body 
 div 
 section 
 %- headerContent % 
 /section 
 div id="content" 
 %- content % 
 /div 
 section 
 %- footerContent % 
 /section 
 /div 
 /body 
 /html 
`;
这里,使用插件 markdown-it 来解析 Markdown 内容,然后使用ejs.render() 来填充模板的各个位置内容。这里,同时也为我们的目标:样式必须是可以自定义的 和封装各种不同情况下,使用不同的头部、尾部、模板、和样式提供了伏笔

当有了内容后,我们还需要把它放到「服务器」上,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);
}

最终得到 URL 对象,转给我们右侧的 Preview 组件,即通过 mainWindow.webContents.send('newContentToPreview', previewURL);

注:在 Main 和 Renderer 进程间通信,使用的是 ipcMain 和 ipcRenderer。ipcMain 无法主动发消息给 ipcRenderer。因为ipcMain只有 .on() 方法没有 .send() 的方法。所以只能用 webContents。

预览区

右侧使用的时间上就是一个 iframe 控件,具体做成一个组件 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 

在 Preview 组件我们使用 vue 的 $on 监听 newContentToPreview 事件,实时载入 URL 对象。

messageBus.$on('newContentToPreview', (url2preview) = {
 this.reload(url2preview);
});

到此为止,我们基本实现了最基础版的 Markdown 编辑器功能,yarn run dev 运行看看效果:

总结

第一次使用 Electron,很肤浅,但至少学到了一些知识:

每个 Electron 应用只有一个 Main 进程,主要用于和系统打交道和创建应用窗口,在 Main 进程中,利用 ipcMain 监听来自 ipcRenderer的事件,但没有 send 方法,只能利用 BrowserWindow。webContents.send()。 每个页面都有对应的 Renderer 进程,用于渲染页面。当然也有对应的 ipcRenderer 用于接收和发送事件。 在 vue 页面组件中,我们还是借助 vue 的 $on 和 `$emit 传递和接收消息。

接下来一步步完善该应用,目标是满足于自己的需要,然后就是:也许哪天就开源了呢。

解决中文编码问题

由于我们使用 iframe,所以需要在 iframe 内嵌的 html /html 增加 meta charset='utf-8'

代码如下:以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持凡科。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持凡科。




联系我们

全国服务热线:4000-399-000 公司邮箱:343111187@qq.com

  工作日 9:00-18:00

关注我们

官网公众号

官网公众号

Copyright?2020 广州凡科互联网科技股份有限公司 版权所有 粤ICP备10235580号 客服热线 18720358503

技术支持:创建网站