亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

利用Electron簡單擼一個Markdown編輯器的方法

2019-11-19 11:21:57
字體:
來源:轉載
供稿:網友

Markdown 是我們每一位開發者的必備技能,在寫 Markdown 過程中,總是尋找了各種各樣的編輯器,但每種編輯器都只能滿足某一方面的需要,卻不能都滿足于日常寫作的各種需求。

所以萌生出自己動手試試,利用 Electron 折騰一個 Markdown 編輯器出來。

下面羅列出我所理想的 Markdown 編輯器的痛點需求:

  • 必須要有圖床功能,而且還可以直接上傳到自己的圖片后臺,如七牛;
  • 樣式必須是可以自定義的;
  • 導出的 HTML 內容可以直接粘貼到公眾號編輯器里,直接發布,而不會出現格式的問題;
  • 可以自定義固定模塊,如文章的頭部,或者尾部。
  • 可以自定義功能,如:自動載入隨機圖片,豐富我們的文章內容。
  • 必須是跨平臺的。
  • 其它。

環境搭建

使用 Electron 作為跨平臺開發框架,是目前最理想的選擇,再者說,如:VS Code、Atom 等大佬級別的應用也是基于 Electron 開發的。

Electron

使用 JavaScript, HTML 和 CSS 構建跨平臺的桌面應用

https://electronjs.org/

初次使用 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: https://github.com/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 = falseexport 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 classexport 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 進程間通信,使用的是 ipcMainipcRenderer。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'>

復制代碼 代碼如下:
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
色吧影院999| 在线亚洲国产精品网| 91久久久国产精品| 国产97在线观看| 日韩成人av在线| 欧美视频在线观看免费网址| 亚洲图片欧洲图片av| 中文字幕亚洲字幕| 欧美老女人bb| 一本色道久久88精品综合| 2020久久国产精品| 视频在线一区二区| 隔壁老王国产在线精品| 日韩亚洲欧美中文在线| 国产99久久精品一区二区| 成人精品视频99在线观看免费| 欧美精品做受xxx性少妇| 青青草原一区二区| 国产成人啪精品视频免费网| 国产精品亚洲欧美导航| 91sao在线观看国产| 欧美性videos高清精品| 91香蕉嫩草神马影院在线观看| 亚洲乱码国产乱码精品精天堂| 国产精品羞羞答答| 国产精品久久久久影院日本| 日韩中文字幕在线播放| 懂色av一区二区三区| 久久91精品国产| 91精品国产高清久久久久久91| 国产一区二区三区四区福利| 日韩精品免费看| 97在线视频免费观看| 九九精品视频在线观看| 国产一区二区三区视频免费| 色综合色综合久久综合频道88| 欧洲亚洲女同hd| 日本久久久久久久| 欧美亚洲国产成人精品| 国产精品免费福利| 69国产精品成人在线播放| 国产精品18久久久久久首页狼| 亚洲黄色av网站| 日韩在线视频中文字幕| 日韩国产精品一区| 国产在线a不卡| 日韩高清免费在线| 亚洲欧美激情另类校园| 国产精品偷伦视频免费观看国产| 久久久免费观看视频| 成人免费看黄网站| 久久综合88中文色鬼| 亚洲精品成人久久电影| 日本精品久久久久久久| 久久久噜噜噜久久| 欧美性猛交xxxx久久久| 亚洲另类欧美自拍| 中文字幕精品一区久久久久| 成人午夜激情免费视频| 5278欧美一区二区三区| 成人久久精品视频| 国产精品久久久久久久天堂| 亚洲人精品午夜在线观看| 亚洲精品日产aⅴ| 亚洲成人黄色网址| www.精品av.com| 欧美日韩不卡合集视频| 欧美在线视频播放| 成人性生交大片免费看小说| 色综合视频网站| 中文字幕欧美精品日韩中文字幕| 丝袜亚洲欧美日韩综合| 国产欧美一区二区三区四区| 伊人久久久久久久久久久久久| 日韩精品视频中文在线观看| 日韩av有码在线| 91久久精品日日躁夜夜躁国产| 成人xvideos免费视频| 在线播放日韩精品| 亚洲欧美日本精品| 亚洲xxxxx电影| 欧美黄色片免费观看| 欧美精品videosex极品1| 国产精品爽黄69| 国产精品678| 一区二区三区在线播放欧美| 亚洲最大激情中文字幕| 岛国视频午夜一区免费在线观看| 成人高清视频观看www| 日韩欧美一区视频| 国内精品美女av在线播放| 最近2019年手机中文字幕| 5566日本婷婷色中文字幕97| 久久久久成人精品| 久久国产精品久久久久久| 国产日韩欧美在线观看| 日本一本a高清免费不卡| 日韩精品一区二区三区第95| 成人久久18免费网站图片| 亚洲第一中文字幕| 91欧美日韩一区| 亚洲www永久成人夜色| 亚洲精品成人网| 欧美日韩精品在线播放| 国产97免费视| 在线亚洲男人天堂| 国产精品扒开腿爽爽爽视频| 欧美成人第一页| 91亚洲精品一区二区| 亚洲免费一级电影| 久久免费视频网| 亚洲精品视频免费在线观看| 欧美日韩一区二区三区在线免费观看| 琪琪第一精品导航| 久久国产精品影片| 日本伊人精品一区二区三区介绍| 久久99精品视频一区97| 国产精品自产拍在线观看中文| 欧美成在线观看| 国产精品欧美激情在线播放| 伊是香蕉大人久久| 亚洲大胆人体av| 国产精品久久久久秋霞鲁丝| 国产伦精品一区二区三区精品视频| 国产日韩亚洲欧美| 久久久免费观看| 精品国产依人香蕉在线精品| 国产成人亚洲综合青青| 久久久久久久久久久成人| 亚洲精品视频在线观看视频| 91av中文字幕| 色爱精品视频一区| 欧美国产日韩在线| 国产精品九九久久久久久久| 亚洲国产欧美一区二区丝袜黑人| 日韩毛片在线看| 精品久久久久久久中文字幕| 欧美性猛交xxxx偷拍洗澡| 久久久噜久噜久久综合| 欧美精品一二区| 91精品国产成人www| 国产在线精品成人一区二区三区| 黑人巨大精品欧美一区二区免费| 成人免费午夜电影| 国产91在线视频| 国产一区二区三区三区在线观看| 91av在线网站| 日韩视频亚洲视频| 九色精品免费永久在线| 在线日韩中文字幕| 超碰97人人做人人爱少妇| 美女啪啪无遮挡免费久久网站| 日韩有码在线观看| 久久艹在线视频| 日韩欧美在线视频日韩欧美在线视频| 91精品久久久久久久久久久| 国产99久久精品一区二区 夜夜躁日日躁| 88国产精品欧美一区二区三区| 久久免费高清视频| 欧美日韩精品在线播放| 精品无码久久久久久国产| 91在线观看欧美日韩| 97国产精品视频人人做人人爱| 97视频在线观看播放|