真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯網站制作重慶分公司

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

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

10余年的淇縣網站建設經驗,針對設計、前端、開發(fā)、售后、文案、推廣等六對一服務,響應快,48小時及時工作處理。成都營銷網站建設的優(yōu)勢是能夠根據用戶設備顯示端的尺寸不同,自動調整淇縣建站的顯示方式,使網站能夠適用不同顯示終端,在瀏覽器中調整網站的寬度,無論在任何一種瀏覽器上瀏覽網站,都能展現優(yōu)雅布局與設計,從而大程度地提升瀏覽體驗。成都創(chuàng)新互聯從事“淇縣網站設計”,“淇縣網站推廣”以來,每個客戶項目都認真落實執(zhí)行。

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

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

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

環(huán)境搭建

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

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

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

VUE

VUE 是當前的前端框架的佼佼者,而且還是我們國人開發(fā)的,不得不服。本人也是 VUE 的忠實粉絲,在還沒火的 1.0 版本開始,我就使用 VUE 了。

electron-vue

將這兩者結合在一起,也就是本文推薦使用的 simulatedgreg/electron-vue

vue init simulatedgreg/electron-vue FanlyMD

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

安裝插件,并運行:

npm installnpm run dev

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

選擇插件

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/ 下創(chuàng)建 vue 頁面:EditorPage.vue

編輯區(qū)

左側使用插件:require('vue2-ace-editor'),處理實時監(jiān)聽 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() { }
 }
});

// 監(jiān)聽 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: ''
}).$mount('#app')

編輯器的內容,將實時由 ipcRenderer.send('newContentToRender', newContent); 下發(fā)出去,即由 Main 進程的 ipcMain.on('newContentToRender', function(event, content) 事件獲取。

一個 Electron 應用只有一個 Main 主進程,很多和本地化東西 (如:本地存儲,文件讀寫等) 更多的交由 Main 進程來處理。

如本案例中,想要實現的第一個功能就是,「可以自定義固定模塊,如文章的頭部,或者尾部」

我們使用一個插件:electron-store,用于存儲頭部和尾部內容,創(chuàng)建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', `
        
本文 111字,需要 1分鐘
`) this.estore.set('footercontent', `
coding01 期待您繼續(xù)關注 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 (
) breaks: false, // Convert '\n' in paragraphs into
// 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'] = ` <%= title %>
<%- headerContent %>
<%- content %>
`;
這里,使用插件 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 無法主動發(fā)消息給 ipcRenderer。因為ipcMain只有 .on() 方法沒有 .send() 的方法。所以只能用 webContents

預覽區(qū)

右側使用的時間上就是一個 iframe 控件,具體做成一個組件 Preview