最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

利用Electron簡(jiǎn)單擼一個(gè)Markdown編輯器的方法

 更新時(shí)間:2019年06月10日 11:03:19   作者:Coding01  
這篇文章主要介紹了利用Electron簡(jiǎn)單擼一個(gè)Markdown編輯器的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧

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

所以萌生出自己動(dòng)手試試,利用 Electron 折騰一個(gè) Markdown 編輯器出來(lái)。

下面羅列出我所理想的 Markdown 編輯器的痛點(diǎn)需求:

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

環(huán)境搭建

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

Electron

使用 JavaScript, HTML 和 CSS 構(gòu)建跨平臺(tái)的桌面應(yīng)用

https://electronjs.org/

初次使用 Electron,我們下載回來(lái)運(yùn)行看看:

# 克隆示例項(xiàng)目的倉(cāng)庫(kù)
$ git clone https://github.com/electron/electron-quick-start

# 進(jìn)入這個(gè)倉(cāng)庫(kù)
$ cd electron-quick-start

# 安裝依賴并運(yùn)行
$ npm install && npm start

VUE

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

electron-vue

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

vue init simulatedgreg/electron-vue FanlyMD

安裝插件,并運(yùn)行:

npm installnpm run dev

選擇插件

1. Ace Editor

選擇一個(gè)好的編輯器至關(guān)重要:

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 內(nèi)容,我選擇是用插件:markdown-it

npm install markdown-it --save

3. electron-store

既然是編輯器應(yīng)用,所有很多個(gè)性化設(shè)置和內(nèi)容,就有必要存于本地,如編輯器所需要的樣式文件、自定義的頭部尾部?jī)?nèi)容等。這里我選擇:electron-store

npm install electron-store --save

整合

萬(wàn)事俱備,接下來(lái)我們就開始著手實(shí)現(xiàn)簡(jiǎn)單的 Markdown 的編輯和預(yù)覽功能。

先看 src 文件夾結(jié)構(gòu):

.
├── 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

整個(gè) APP 主要分成左右兩列結(jié)構(gòu),左側(cè)編輯 Markdown 內(nèi)容,右側(cè)實(shí)時(shí)看到效果,而頁(yè)面視圖主要由 Renderer 來(lái)渲染完成,所以我們首先在 renderer/components/ 下創(chuàng)建 vue 頁(yè)面: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>

編輯區(qū)

左側(cè)使用插件:require('vue2-ace-editor'),處理實(shí)時(shí)監(jiān)聽 Editor 輸入 Markdown 內(nèi)容,將內(nèi)容傳出去。

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

其中這里的 messageBus 就是把 vue 和 ipcRenderer 相關(guān)邏輯事件放在一起的 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: '<App/>'
}).$mount('#app')

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

一個(gè) Electron 應(yīng)用只有一個(gè) Main 主進(jìn)程,很多和本地化東西 (如:本地存儲(chǔ),文件讀寫等) 更多的交由 Main 進(jìn)程來(lái)處理。

如本案例中,想要實(shí)現(xiàn)的第一個(gè)功能就是,「可以自定義固定模塊,如文章的頭部,或者尾部」

我們使用一個(gè)插件:electron-store,用于存儲(chǔ)頭部和尾部?jī)?nèi)容,創(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', `<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 期待您繼續(xù)關(guān)注</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
注:這里只是寫死的頭部和尾部?jī)?nèi)容。

有了頭尾部?jī)?nèi)容,和編輯器的 Markdown 內(nèi)容,我們就可以將這些內(nèi)容整合,然后輸出給我們的右側(cè) 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內(nèi)容、css 樣式和我們的模板 layout1.html 載入。這個(gè)就比較簡(jiǎn)單了,直接看代碼:

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 來(lái)解析 Markdown 內(nèi)容,然后使用ejs.render() 來(lái)填充模板的各個(gè)位置內(nèi)容。這里,同時(shí)也為我們的目標(biāo):樣式必須是可以自定義的 和封裝各種不同情況下,使用不同的頭部、尾部、模板、和樣式提供了伏筆

當(dāng)有了內(nèi)容后,我們還需要把它放到「服務(wù)器」上,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 對(duì)象,轉(zhuǎn)給我們右側(cè)的 Preview 組件,即通過(guò) mainWindow.webContents.send('newContentToPreview', previewURL);

注:在 Main 和 Renderer 進(jìn)程間通信,使用的是 ipcMainipcRenderer。ipcMain 無(wú)法主動(dòng)發(fā)消息給 ipcRenderer。因?yàn)?code>ipcMain只有 .on() 方法沒(méi)有 .send() 的方法。所以只能用 webContents

預(yù)覽區(qū)

右側(cè)使用的時(shí)間上就是一個(gè) iframe 控件,具體做成一個(gè)組件 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 監(jiān)聽 newContentToPreview 事件,實(shí)時(shí)載入 URL 對(duì)象。

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

到此為止,我們基本實(shí)現(xiàn)了最基礎(chǔ)版的 Markdown 編輯器功能,yarn run dev 運(yùn)行看看效果:

總結(jié)

第一次使用 Electron,很膚淺,但至少學(xué)到了一些知識(shí):

  • 每個(gè) Electron 應(yīng)用只有一個(gè) Main 進(jìn)程,主要用于和系統(tǒng)打交道和創(chuàng)建應(yīng)用窗口,在 Main 進(jìn)程中,利用 ipcMain 監(jiān)聽來(lái)自 ipcRenderer的事件,但沒(méi)有 send 方法,只能利用 BrowserWindow。webContents.send()。
  • 每個(gè)頁(yè)面都有對(duì)應(yīng)的 Renderer 進(jìn)程,用于渲染頁(yè)面。當(dāng)然也有對(duì)應(yīng)的 ipcRenderer 用于接收和發(fā)送事件。
  • 在 vue 頁(yè)面組件中,我們還是借助 vue 的 $on 和 `$emit 傳遞和接收消息。

接下來(lái)一步步完善該應(yīng)用,目標(biāo)是滿足于自己的需要,然后就是:也許哪天就開源了呢。

解決中文編碼問(wèn)題

由于我們使用 iframe,所以需要在 iframe 內(nèi)嵌的 <html></html> 增加 <meta charset='utf-8'>

復(fù)制代碼 代碼如下:
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Vue?響應(yīng)式系統(tǒng)依賴收集過(guò)程原理解析

    Vue?響應(yīng)式系統(tǒng)依賴收集過(guò)程原理解析

    Vue 初始化時(shí)就會(huì)通過(guò) Object.defineProperty 攔截屬性的 getter 和 setter ,為對(duì)象的每個(gè)值創(chuàng)建一個(gè) dep 并用 Dep.addSub() 來(lái)存儲(chǔ)該屬性值的 watcher 列表,這篇文章主要介紹了Vue?響應(yīng)式系統(tǒng)依賴收集過(guò)程分析,需要的朋友可以參考下
    2022-06-06
  • M1 pro芯片啟動(dòng)Vue項(xiàng)目的方法步驟

    M1 pro芯片啟動(dòng)Vue項(xiàng)目的方法步驟

    本文主要介紹了M1 pro芯片啟動(dòng)Vue項(xiàng)目的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • vue自定義鍵盤信息、監(jiān)聽數(shù)據(jù)變化的方法示例【基于vm.$watch】

    vue自定義鍵盤信息、監(jiān)聽數(shù)據(jù)變化的方法示例【基于vm.$watch】

    這篇文章主要介紹了vue自定義鍵盤信息、監(jiān)聽數(shù)據(jù)變化的方法,結(jié)合實(shí)例形式分析了vue.js基于vm.$watch進(jìn)行事件監(jiān)聽相關(guān)操作技巧,需要的朋友可以參考下
    2019-03-03
  • vue如何在自定義組件中使用v-model

    vue如何在自定義組件中使用v-model

    本篇文章主要介紹了vue如何在自定義組件中使用v-model,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-05-05
  • vue element-ui v-for循環(huán)el-upload上傳圖片 動(dòng)態(tài)添加、刪除方式

    vue element-ui v-for循環(huán)el-upload上傳圖片 動(dòng)態(tài)添加、刪除方式

    這篇文章主要介紹了vue element-ui v-for循環(huán)el-upload上傳圖片 動(dòng)態(tài)添加、刪除方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • vue v-viewer組件使用示例詳解(v-viewer輪播圖)

    vue v-viewer組件使用示例詳解(v-viewer輪播圖)

    這篇文章主要介紹了vue v-viewer組件使用示例詳解(v-viewer輪播圖),本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-02-02
  • vue全局自定義指令和局部自定義指令的使用

    vue全局自定義指令和局部自定義指令的使用

    這篇文章主要介紹了vue全局自定義指令和局部自定義指令的使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • vue3使用svg圖標(biāo)的方式總結(jié)

    vue3使用svg圖標(biāo)的方式總結(jié)

    在Vue?3中,可以使用多種方式來(lái)使用SVG圖標(biāo),這篇文章主要為大家總結(jié)了五個(gè)常用的方式,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下
    2023-08-08
  • Vue實(shí)現(xiàn)簡(jiǎn)單可擴(kuò)展甘特圖的方法詳解

    Vue實(shí)現(xiàn)簡(jiǎn)單可擴(kuò)展甘特圖的方法詳解

    Ganttastic是一個(gè)小型的Vue.js組件,用于在Web應(yīng)用程序上呈現(xiàn)一個(gè)可配置的、可拖動(dòng)的甘特圖。本文就將用它來(lái)實(shí)現(xiàn)簡(jiǎn)單可擴(kuò)展的甘特圖,感興趣的可以嘗試一下
    2022-11-11
  • VUE3安裝element?ui失敗的原因以及解決辦法

    VUE3安裝element?ui失敗的原因以及解決辦法

    這篇文章主要給大家介紹了關(guān)于VUE3安裝element?ui失敗的原因以及解決的相關(guān)資料,很多朋友升級(jí)使用Vue3了,但在安裝element?ui失敗出錯(cuò)了,這里給大家總結(jié)下,需要的朋友可以參考下
    2023-09-09

最新評(píng)論

福贡县| 土默特右旗| 神池县| 滦南县| 乳源| 灵石县| 高青县| 柘城县| 隆安县| 怀集县| 福清市| 禄劝| 共和县| 沙坪坝区| 华安县| 大姚县| 大洼县| 三台县| 安多县| 马公市| 莱州市| 平邑县| 黔南| 澄迈县| 绥芬河市| 左贡县| 区。| 红原县| 瑞丽市| 郧西县| 招远市| 唐海县| 道孚县| 天长市| 磐石市| 古浪县| 民和| 眉山市| 黄陵县| 辰溪县| 龙岩市|