我熬夜開(kāi)發(fā)了一款簡(jiǎn)約實(shí)用、支持多平臺(tái)的Markdown在線編輯器(開(kāi)源)
前言
之前,一直想開(kāi)發(fā)一款屬于自己的Markdown編輯器,主要是自己平常寫(xiě)文章可以更加靈活操作,另外擴(kuò)寬自己的視野也是非常不錯(cuò)的選擇啊!所以在周末就決定玩耍一番。首先我調(diào)研了很多線上熱門(mén)的md編輯器,都很優(yōu)秀。不為超過(guò)他們,主要自己用著舒服點(diǎn)。這篇文章主要是記錄下我是如何從0到1是完成一款還算拿得出手的Markdown編輯器。
完成項(xiàng)目一覽


調(diào)研Markdown編輯器
國(guó)內(nèi)、國(guó)外關(guān)于Markdown編輯器有很多。
editor.md
網(wǎng)址:https://pandao.github.io/editor.md/
是一款開(kāi)源的、可嵌入的 Markdown 在線編輯器(組件),基于 CodeMirror、jQuery 和 Marked 構(gòu)建。這個(gè)組件好像是國(guó)內(nèi)開(kāi)發(fā)的,個(gè)人之前用著還可以。
typora
網(wǎng)址:https://www.typora.io/
Typora是一款免費(fèi)的輕量級(jí)Markdown編輯器,它沒(méi)有Mou,Haroopad等Markdown編輯器那么大名鼎鼎,算是較為小眾的一款產(chǎn)品。憑良心說(shuō)話,我用過(guò)的Markdown編輯器也有好幾款,其中包括:小書(shū)匠,Haroopad,Atom等,但Typora是最合我心意的一款編輯器了,其輕量、快速、易于上手,使用起來(lái)簡(jiǎn)直不要太舒服!!
tui-editor
網(wǎng)址:https://ui.toast.com/tui-editor
這是一款Markdown組件,通過(guò)調(diào)研決定用它。為什么?確認(rèn)過(guò)眼神~
技術(shù)棧
- Vue.js
- tui-editor
實(shí)戰(zhàn)
確定好技術(shù)棧之后,我們就得腳踏實(shí)地地干活了。
1. 搭建Vue腳手架
我們會(huì)使用VueCLI搭建一個(gè)最基礎(chǔ)的項(xiàng)目,這里暫時(shí)不需要Vue-router、Vuex這些插件,所以盡可能輕裝。
2. 創(chuàng)建編輯器組件
我們會(huì)在components文件目錄下創(chuàng)建一個(gè)Editor.vue文件,這個(gè)文件也就是我們的主戰(zhàn)場(chǎng),大部分操作都會(huì)在這個(gè)文件。
3. 配置編輯器組件
在配置編輯器時(shí),有以下幾點(diǎn)使我非常困惑,以致于花費(fèi)了大量時(shí)間。
- 代碼沒(méi)有被高亮
- 語(yǔ)言不是中文
- 編輯器樣式有問(wèn)題
以上這幾個(gè)問(wèn)題通過(guò)以下措施才得以解決:
- 通過(guò)閱讀文檔:https://nhn.github.io/tui.editor/latest/
- 訪問(wèn)Github網(wǎng)站:https://github.com/nhn/tui.editor
Editor.vue
- <template>
- <div class="main">
- <div id="editor"></div>
- </div>
- </template>
- <script>
- import Editor from "@toast-ui/editor";
- import hljs from "highlight.js";
- import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight";
- import '@toast-ui/editor/dist/i18n/zh-cn.js';
- import "highlight.js/styles/github.css";
- import "codemirror/lib/codemirror.css"; // Editor's Dependency Style
- import "@toast-ui/editor/dist/toastui-editor.css"; // Editor's Style
- import "@/styles/index.css";
- export default {
- components: {},
- data() {
- return {
- editor: null
- };
- },
- mounted() {
- this.editor = new Editor({
- el: document.getElementById("editor"),
- plugins: [[codeSyntaxHighlight, {hljs}]],
- previewStyle: "vertical",
- height: "100vh",
- initialEditType: "markdown",
- minHeight: "200px",
- initialValue: "",
- placeholder: "你想寫(xiě)點(diǎn)什么...",
- language:'zh-CN',
- useCommandShortcut: true,
- useDefaultHTMLSanitizer: true,
- usageStatistics: false,
- hideModeSwitch: false,
- viewer: true,
- toolbarItems: [
- "heading",
- "bold",
- "italic",
- "strike",
- "divider",
- "hr",
- "quote",
- "divider",
- "ul",
- "ol",
- "task",
- "indent",
- "outdent",
- "divider",
- "table",
- "image",
- "link",
- "divider",
- "code",
- "codeblock",
- ],
- });
- this.editor.getUI().getToolbar().removeItem("21");
- },
- };
- </script>
看似上面幾行代碼,但是也是很費(fèi)勁才得以完成。
增加功能
首先,我開(kāi)發(fā)這個(gè)程序的初衷是更好地方便自己寫(xiě)文章,所以,我定下了這幾個(gè)需求:
- 可復(fù)制HTML格式文本,方便復(fù)制到微信公眾號(hào)
- 可復(fù)制Markdown文本,方便可以復(fù)制到稀土掘金、csdn這些博客網(wǎng)站上發(fā)布
- 可下載Markdown文件,更加方便保存和移動(dòng)
因篇幅原因,先奉上主要邏輯代碼。這里我使用了clipboard這個(gè)將文本復(fù)制到剪貼板的插件。網(wǎng)址:https://clipboardjs.com/。
另外,downloadBlobAsFile方法主要是創(chuàng)建Blob對(duì)象,然后通過(guò)a標(biāo)簽的download屬性進(jìn)行下載。
downloadBlobAsFile.js
- export default function downloadBlobAsFile(data, filename) {
- const contentType = 'application/octet-stream';
- if (!data) {
- console.error(' No data');
- return;
- }
- if (!filename) {
- filename = 'filetodonwload.txt';
- }
- if (typeof data === 'object') {
- data = JSON.stringify(data, undefined, 4);
- }
- let blob = new Blob([data], {type: contentType});
- let e = document.createEvent('MouseEvents');
- let a = document.createElement('a');
- a.download = filename;
- a.href = URL.createObjectURL(blob);
- a.dataset.downloadurl = [contentType, a.download, a.href].join(':');
- e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
- a.dispatchEvent(e);
- }
Editor.vue
- <template>
- <div class="main">
- <div class="tools">
- <el-button
- size="mini"
- type="primary"
- @click="drawer = true"
- >工具</el-button>
- <el-button
- size="mini"
- type="primary"
- @click="aboutView = true"
- >關(guān)于</el-button>
- <el-dialog
- :title="'工具'"
- :visible.sync="drawer"
- :append-to-body="true"
- >
- <div class="tool-innter">
- <el-button type="primary" @click="getHtml" class="htmlbtn"
- >復(fù)制HTML
- </el-button
- >
- <el-button type="primary" @click="getMd" class="mdbtn"
- >復(fù)制MarkDown
- </el-button
- >
- <el-button type="primary" @click="downloadMd" class="downloadbtn"
- >下載MarkDown
- </el-button
- >
- </div>
- </el-dialog>
- <el-dialog
- :title="'關(guān)于'"
- :visible.sync="aboutView"
- :append-to-body="true"
- >
- <h3>Simple·MarkDown編輯器</h3>
- <ul class="functionList">
- <li v-for="(item,index) in functionList" :key="index">
- {{item}}
- </li>
- </ul>
- <h3>作者</h3>
- <ul class="functionList">
- <li v-for="(item,index) in authorList" :key="index">{{item}}</li>
- </ul>
- <div class="wxcode">
- <img src="../assets/wxcode.jpeg" alt="">
- </div>
- </el-dialog>
- </div>
- <div id="editor"></div>
- </div>
- </template>
- <script>
- import Editor from "@toast-ui/editor";
- import Clipboard from "clipboard";
- import hljs from "highlight.js";
- import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight";
- import '@toast-ui/editor/dist/i18n/zh-cn.js';
- import downloadBlobAsFile from "../utils/download";
- import "highlight.js/styles/github.css"; //https://github.com/highlightjs/highlight.js/tree/master/src/styles
- import "codemirror/lib/codemirror.css"; // Editor's Dependency Style
- import "@toast-ui/editor/dist/toastui-editor.css"; // Editor's Style
- import "@/styles/index.css";
- export default {
- components: {},
- data() {
- return {
- editor: null,
- drawer: false,
- aboutView: false,
- functionList:['頁(yè)面簡(jiǎn)約','功能實(shí)用','支持稀土掘金、CSDN、微信公眾號(hào)、知乎','可復(fù)制HTML、MarkDown','可下載MarkDown文件'],
- authorList:['作者:Vam的金豆之路','歡迎關(guān)注我的公眾號(hào):前端歷劫之路','我創(chuàng)建了一個(gè)技術(shù)交流、文章分享群,群里有很多大廠的前端大佬,關(guān)注公眾號(hào)后,點(diǎn)擊下方菜單了解更多即可加我微信,期待你的加入']
- };
- },
- methods: {
- // 復(fù)制HTML
- getHtml() {
- const clipboard = new Clipboard(".htmlbtn", {
- target: () => this.editor.preview.el,
- });
- clipboard.on("success", () => {
- this.$message({
- message: "復(fù)制成功",
- type: "success",
- });
- clipboard.destroy();
- });
- clipboard.on("error", () => {
- this.$message.error("復(fù)制失敗");
- clipboard.destroy();
- });
- },
- // 復(fù)制Markdown
- getMd() {
- const clipboard = new Clipboard(".mdbtn", {
- text: () => this.editor.getMarkdown(),
- });
- clipboard.on("success", () => {
- this.$message({
- message: "復(fù)制成功",
- type: "success",
- });
- clipboard.destroy();
- });
- clipboard.on("error", () => {
- this.$message.error("復(fù)制失敗");
- clipboard.destroy();
- });
- },
- // 下載Markdown
- downloadMd() {
- if (this.editor.getMarkdown().trim()) {
- downloadBlobAsFile(this.editor.getMarkdown(), "unnamed.md");
- } else {
- this.$message.error("下載失敗");
- }
- },
- },
- mounted() {
- this.editor = new Editor({
- el: document.getElementById("editor"),
- plugins: [[codeSyntaxHighlight, {hljs}]],
- previewStyle: "vertical",
- height: "100vh",
- initialEditType: "markdown",
- minHeight: "200px",
- initialValue: "",
- placeholder: "你想寫(xiě)點(diǎn)什么...",
- language:'zh-CN',
- useCommandShortcut: true,
- useDefaultHTMLSanitizer: true,
- usageStatistics: false,
- hideModeSwitch: false,
- viewer: true,
- toolbarItems: [
- "heading",
- "bold",
- "italic",
- "strike",
- "divider",
- "hr",
- "quote",
- "divider",
- "ul",
- "ol",
- "task",
- "indent",
- "outdent",
- "divider",
- "table",
- "image",
- "link",
- "divider",
- "code",
- "codeblock",
- ],
- });
- this.editor.getUI().getToolbar().removeItem("21");
- },
- };
- </script>
針對(duì)微信公眾號(hào)進(jìn)行樣式優(yōu)化
::v-deep是深度作用選擇器,主要是為了覆蓋原有的樣式所用。
- ::v-deep ul li {
- list-style-type: disc !important;
- }
- ::v-deep ol li {
- list-style-type: decimal !important;
- }
- ::v-deep ul li::before, ::v-deep ol li::before {
- content: none;
- }
- ::v-deep .tui-editor-contents p>code{
- background-color: #fff5f5;
- color: #ff502c;
- }
- ::v-deep .tui-editor-contents pre {
- width: 100%;
- overflow: auto;
- }
線上體驗(yàn)
https://www.maomin.club/site/mdeditor/
結(jié)語(yǔ)
謝謝閱讀,希望沒(méi)有浪費(fèi)你的時(shí)間。
源碼地址:
https://github.com/maomincoding/simpleMdEditor