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


調(diào)研Markdown編輯器
國內(nèi)、國外關(guān)于Markdown編輯器有很多。
editor.md
網(wǎng)址:https://pandao.github.io/editor.md/
是一款開源的、可嵌入的 Markdown 在線編輯器(組件),基于 CodeMirror、jQuery 和 Marked 構(gòu)建。這個組件好像是國內(nèi)開發(fā)的,個人之前用著還可以。
typora
網(wǎng)址:https://www.typora.io/
Typora是一款免費的輕量級Markdown編輯器,它沒有Mou,Haroopad等Markdown編輯器那么大名鼎鼎,算是較為小眾的一款產(chǎn)品。憑良心說話,我用過的Markdown編輯器也有好幾款,其中包括:小書匠,Haroopad,Atom等,但Typora是最合我心意的一款編輯器了,其輕量、快速、易于上手,使用起來簡直不要太舒服!!
tui-editor
網(wǎng)址:https://ui.toast.com/tui-editor
這是一款Markdown組件,通過調(diào)研決定用它。為什么?確認過眼神~
技術(shù)棧
- Vue.js
 - tui-editor
 
實戰(zhàn)
確定好技術(shù)棧之后,我們就得腳踏實地地干活了。
1. 搭建Vue腳手架
我們會使用VueCLI搭建一個最基礎(chǔ)的項目,這里暫時不需要Vue-router、Vuex這些插件,所以盡可能輕裝。
2. 創(chuàng)建編輯器組件
我們會在components文件目錄下創(chuàng)建一個Editor.vue文件,這個文件也就是我們的主戰(zhàn)場,大部分操作都會在這個文件。
3. 配置編輯器組件
在配置編輯器時,有以下幾點使我非常困惑,以致于花費了大量時間。
- 代碼沒有被高亮
 - 語言不是中文
 - 編輯器樣式有問題
 
以上這幾個問題通過以下措施才得以解決:
- 通過閱讀文檔:https://nhn.github.io/tui.editor/latest/
 - 訪問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: "你想寫點什么...",
 - 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ā)這個程序的初衷是更好地方便自己寫文章,所以,我定下了這幾個需求:
- 可復制HTML格式文本,方便復制到微信公眾號
 - 可復制Markdown文本,方便可以復制到稀土掘金、csdn這些博客網(wǎng)站上發(fā)布
 - 可下載Markdown文件,更加方便保存和移動
 
因篇幅原因,先奉上主要邏輯代碼。這里我使用了clipboard這個將文本復制到剪貼板的插件。網(wǎng)址:https://clipboardjs.com/。
另外,downloadBlobAsFile方法主要是創(chuàng)建Blob對象,然后通過a標簽的download屬性進行下載。
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"
 - >復制HTML
 - </el-button
 - >
 - <el-button type="primary" @click="getMd" class="mdbtn"
 - >復制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:['頁面簡約','功能實用','支持稀土掘金、CSDN、微信公眾號、知乎','可復制HTML、MarkDown','可下載MarkDown文件'],
 - authorList:['作者:Vam的金豆之路','歡迎關(guān)注我的公眾號:前端歷劫之路','我創(chuàng)建了一個技術(shù)交流、文章分享群,群里有很多大廠的前端大佬,關(guān)注公眾號后,點擊下方菜單了解更多即可加我微信,期待你的加入']
 - };
 - },
 - methods: {
 - // 復制HTML
 - getHtml() {
 - const clipboard = new Clipboard(".htmlbtn", {
 - target: () => this.editor.preview.el,
 - });
 - clipboard.on("success", () => {
 - this.$message({
 - message: "復制成功",
 - type: "success",
 - });
 - clipboard.destroy();
 - });
 - clipboard.on("error", () => {
 - this.$message.error("復制失敗");
 - clipboard.destroy();
 - });
 - },
 - // 復制Markdown
 - getMd() {
 - const clipboard = new Clipboard(".mdbtn", {
 - text: () => this.editor.getMarkdown(),
 - });
 - clipboard.on("success", () => {
 - this.$message({
 - message: "復制成功",
 - type: "success",
 - });
 - clipboard.destroy();
 - });
 - clipboard.on("error", () => {
 - this.$message.error("復制失敗");
 - 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: "你想寫點什么...",
 - 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>
 
針對微信公眾號進行樣式優(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;
 - }
 
線上體驗
https://www.maomin.club/site/mdeditor/
結(jié)語
謝謝閱讀,希望沒有浪費你的時間。
源碼地址:
https://github.com/maomincoding/simpleMdEditor
















 
 
 









 
 
 
 