下水道的美人鱼观看,三国志关羽列传,2011河南招警
markdown 是我们每一位开发者的必备技能,在写 markdown 过程中,总是寻找了各种各样的编辑器,但每种编辑器都只能满足某一方面的需要,却不能都满足于日常写作的各种需求。
所以萌生出自己动手试试,利用 electron 折腾一个 markdown 编辑器出来。
下面罗列出我所理想的 markdown 编辑器的痛点需求:
环境搭建
使用 electron 作为跨平台开发框架,是目前最理想的选择,再者说,如:vs code、atom 等大佬级别的应用也是基于 electron 开发的。
electron
使用 javascript, html 和 css 构建跨平台的桌面应用
初次使用 electron,我们下载回来运行看看:
# 克隆示例项目的仓库 $ git clone https://github.com/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 来渲染完成,所以我们首先在 renderer/components/
下创建 vue 页面: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>
编辑区
左侧使用插件: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 { 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
注:这里只是写死的头部和尾部内容。
有了头尾部内容,和编辑器的 markdown 内容,我们就可以将这些内容整合,然后输出给我们的右侧 preview
组件了。
ipcmain.on('newcontenttorender', function(event, content) { const rendered = rendercontent(headercontent, footercontent, content, csscontent, 'layout1.html'); const previewurl = newcontent(rendered); mainwindow.webcontents.send('newcontenttopreview', previewurl); });
其中,rendercontent(headercontent, footercontent, content, csscontent, 'layout1.html')
方法就是将我们的头部、尾部、markdown内容、css 样式和我们的模板 layout1.html
载入。这个就比较简单了,直接看代码:
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> `;
这里,使用插件 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,很肤浅,但至少学到了一些知识:
接下来一步步完善该应用,目标是满足于自己的需要,然后就是:也许哪天就开源了呢。
解决中文编码问题
由于我们使用 iframe
,所以需要在 iframe
内嵌的 <html></html>
增加 <meta charset='utf-8'>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。
如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复
VUE+elementui组件在table-cell单元格中绘制微型echarts图
Vue通过getAction的finally来最大程度避免影响主数据呈现问题
vue 路由懒加载中给 Webpack Chunks 命名的方法
网友评论