我熬夜开发了一款简约实用、支持多平台的Markdown在线编辑器(开源)

前言

之前,一直想开发一款属于自己的Markdown编辑器,主要是自己平常写文章可以更加灵活操作,另外扩宽自己的视野也是非常不错的选择啊!所以在周末就决定玩耍一番。首先我调研了很多线上热门的md编辑器,都很优秀。不为超过他们,主要自己用着舒服点。这篇文章主要是记录下我是如何从0到1是完成一款还算拿得出手的Markdown编辑器。

完成项目一览

调研Markdown编辑器


国内、国外关于Markdown编辑器有很多。

  • editor.md

网址:https://pandao.github.io/editor.md/

是一款开源的、可嵌入的 Markdown 在线编辑器(组件),基于 CodeMirror、jQuery 和 Marked 构建。这个组件好像是国内开发的,个人之前用着还可以。

  • typora

网址:https://www.typora.io/

Typora是一款免费的轻量级Markdown编辑器,它没有Mou,Haroopad等Markdown编辑器那么大名鼎鼎,算是较为小众的一款产品。凭良心说话,我用过的Markdown编辑器也有好几款,其中包括:小书匠,Haroopad,Atom等,但Typora是最合我心意的一款编辑器了,其轻量、快速、易于上手,使用起来简直不要太舒服!!

  • tui-editor

网址:https://ui.toast.com/tui-editor

这是一款Markdown组件,通过调研决定用它。为什么?确认过眼神~

技术栈

  • Vue.js

  • tui-editor

实战

确定好技术栈之后,我们就得脚踏实地地干活了。

1. 搭建Vue脚手架

我们会使用VueCLI搭建一个最基础的项目,这里暂时不需要Vue-routerVuex这些插件,所以尽可能轻装。

2. 创建编辑器组件

我们会在components文件目录下创建一个Editor.vue文件,这个文件也就是我们的主战场,大部分操作都会在这个文件。

3. 配置编辑器组件

在配置编辑器时,有以下几点使我非常困惑,以致于花费了大量时间。

  1. 代码没有被高亮

  2. 语言不是中文

  3. 编辑器样式有问题

以上这几个问题通过以下措施才得以解决:

  1. 通过阅读文档:https://nhn.github.io/tui.editor/latest/

  2. 访问Github网站:https://github.com/nhn/tui.editor

Editor.vue

1<template> 2 <div class="main"> 3 <div id="editor"></div> 4 </div> 5</template> 6<script> 7import Editor from "@toast-ui/editor"; 8import hljs from "highlight.js"; 9import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight"; 10import '@toast-ui/editor/dist/i18n/zh-cn.js'; 11 12import "highlight.js/styles/github.css"; 13import "codemirror/lib/codemirror.css"; // Editor's Dependency Style 14import "@toast-ui/editor/dist/toastui-editor.css"; // Editor's Style 15import "@/styles/index.css"; 16export default { 17 components: {}, 18 data() { 19 return { 20 editor: null 21 }; 22 }, 23 mounted() { 24 this.editor = new Editor({ 25 el: document.getElementById("editor"), 26 plugins: [[codeSyntaxHighlight, {hljs}]], 27 previewStyle: "vertical", 28 height: "100vh", 29 initialEditType: "markdown", 30 minHeight: "200px", 31 initialValue: "", 32 placeholder: "你想写点什么...", 33 language:'zh-CN', 34 useCommandShortcut: true, 35 useDefaultHTMLSanitizer: true, 36 usageStatistics: false, 37 hideModeSwitch: false, 38 viewer: true, 39 toolbarItems: [ 40 "heading", 41 "bold", 42 "italic", 43 "strike", 44 "divider", 45 "hr", 46 "quote", 47 "divider", 48 "ul", 49 "ol", 50 "task", 51 "indent", 52 "outdent", 53 "divider", 54 "table", 55 "image", 56 "link", 57 "divider", 58 "code", 59 "codeblock", 60 ], 61 }); 62 this.editor.getUI().getToolbar().removeItem("21"); 63 }, 64}; 65</script> 66

看似上面几行代码,但是也是很费劲才得以完成。

增加功能

首先,我开发这个程序的初衷是更好地方便自己写文章,所以,我定下了这几个需求:

  1. 可复制HTML格式文本,方便复制到微信公众号

  2. 可复制Markdown文本,方便可以复制到稀土掘金、csdn这些博客网站上发布

  3. 可下载Markdown文件,更加方便保存和移动

因篇幅原因,先奉上主要逻辑代码。这里我使用了clipboard这个将文本复制到剪贴板的插件。网址:https://clipboardjs.com/
另外,downloadBlobAsFile方法主要是创建Blob对象,然后通过a标签的download属性进行下载。

downloadBlobAsFile.js

1export default function downloadBlobAsFile(data, filename) { 2 const contentType = 'application/octet-stream'; 3 if (!data) { 4 console.error(' No data'); 5 return; 6 } 7 8 if (!filename) { 9 filename = 'filetodonwload.txt'; 10 } 11 12 if (typeof data === 'object') { 13 data = JSON.stringify(data, undefined, 4); 14 } 15 16 let blob = new Blob([data], {type: contentType}); 17 let e = document.createEvent('MouseEvents'); 18 let a = document.createElement('a'); 19 20 a.download = filename; 21 a.href = URL.createObjectURL(blob); 22 a.dataset.downloadurl = [contentType, a.download, a.href].join(':'); 23 e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); 24 a.dispatchEvent(e); 25} 26 27

Editor.vue

1<template> 2 <div class="main"> 3 <div class="tools"> 4 <el-button 5 size="mini" 6 type="primary" 7 @click="drawer = true" 8 >工具</el-button> 9 <el-button 10 size="mini" 11 type="primary" 12 @click="aboutView = true" 13 >关于</el-button> 14 <el-dialog 15 :title="'工具'" 16 :visible.sync="drawer" 17 :append-to-body="true" 18 > 19 <div class="tool-innter"> 20 <el-button type="primary" @click="getHtml" class="htmlbtn" 21 >复制HTML 22 </el-button 23 > 24 <el-button type="primary" @click="getMd" class="mdbtn" 25 >复制MarkDown 26 </el-button 27 > 28 <el-button type="primary" @click="downloadMd" class="downloadbtn" 29 >下载MarkDown 30 </el-button 31 > 32 </div> 33 </el-dialog> 34 <el-dialog 35 :title="'关于'" 36 :visible.sync="aboutView" 37 :append-to-body="true" 38 > 39 <h3>Simple·MarkDown编辑器</h3> 40 <ul class="functionList"> 41 <li v-for="(item,index) in functionList" :key="index"> 42 {{item}} 43 </li> 44 </ul> 45 <h3>作者</h3> 46 <ul class="functionList"> 47 <li v-for="(item,index) in authorList" :key="index">{{item}}</li> 48 </ul> 49 <div class="wxcode"> 50 <img src="../assets/wxcode.jpeg" alt=""> 51 </div> 52 </el-dialog> 53 </div> 54 <div id="editor"></div> 55 </div> 56</template> 57<script> 58import Editor from "@toast-ui/editor"; 59import Clipboard from "clipboard"; 60import hljs from "highlight.js"; 61import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight"; 62import '@toast-ui/editor/dist/i18n/zh-cn.js'; 63import downloadBlobAsFile from "../utils/download"; 64 65import "highlight.js/styles/github.css"; //https://github.com/highlightjs/highlight.js/tree/master/src/styles 66import "codemirror/lib/codemirror.css"; // Editor's Dependency Style 67import "@toast-ui/editor/dist/toastui-editor.css"; // Editor's Style 68import "@/styles/index.css"; 69export default { 70 components: {}, 71 data() { 72 return { 73 editor: null, 74 drawer: false, 75 aboutView: false, 76 functionList:['页面简约','功能实用','支持稀土掘金、CSDN、微信公众号、知乎','可复制HTML、MarkDown','可下载MarkDown文件'], 77 authorList:['作者:Vam的金豆之路','欢迎关注我的公众号:前端历劫之路','我创建了一个技术交流、文章分享群,群里有很多大厂的前端大佬,关注公众号后,点击下方菜单了解更多即可加我微信,期待你的加入'] 78 }; 79 }, 80 methods: { 81 // 复制HTML 82 getHtml() { 83 const clipboard = new Clipboard(".htmlbtn", { 84 target: () => this.editor.preview.el, 85 }); 86 clipboard.on("success", () => { 87 this.$message({ 88 message: "复制成功", 89 type: "success", 90 }); 91 clipboard.destroy(); 92 }); 93 clipboard.on("error", () => { 94 this.$message.error("复制失败"); 95 clipboard.destroy(); 96 }); 97 }, 98 // 复制Markdown 99 getMd() { 100 const clipboard = new Clipboard(".mdbtn", { 101 text: () => this.editor.getMarkdown(), 102 }); 103 clipboard.on("success", () => { 104 this.$message({ 105 message: "复制成功", 106 type: "success", 107 }); 108 clipboard.destroy(); 109 }); 110 clipboard.on("error", () => { 111 this.$message.error("复制失败"); 112 clipboard.destroy(); 113 }); 114 }, 115 // 下载Markdown 116 downloadMd() { 117 if (this.editor.getMarkdown().trim()) { 118 downloadBlobAsFile(this.editor.getMarkdown(), "unnamed.md"); 119 } else { 120 this.$message.error("下载失败"); 121 } 122 }, 123 }, 124 mounted() { 125 this.editor = new Editor({ 126 el: document.getElementById("editor"), 127 plugins: [[codeSyntaxHighlight, {hljs}]], 128 previewStyle: "vertical", 129 height: "100vh", 130 initialEditType: "markdown", 131 minHeight: "200px", 132 initialValue: "", 133 placeholder: "你想写点什么...", 134 language:'zh-CN', 135 useCommandShortcut: true, 136 useDefaultHTMLSanitizer: true, 137 usageStatistics: false, 138 hideModeSwitch: false, 139 viewer: true, 140 toolbarItems: [ 141 "heading", 142 "bold", 143 "italic", 144 "strike", 145 "divider", 146 "hr", 147 "quote", 148 "divider", 149 "ul", 150 "ol", 151 "task", 152 "indent", 153 "outdent", 154 "divider", 155 "table", 156 "image", 157 "link", 158 "divider", 159 "code", 160 "codeblock", 161 ], 162 }); 163 this.editor.getUI().getToolbar().removeItem("21"); 164 }, 165}; 166</script> 167

针对微信公众号进行样式优化

::v-deep是深度作用选择器,主要是为了覆盖原有的样式所用。

1::v-deep ul li { 2 list-style-type: disc !important; 3} 4 5::v-deep ol li { 6 list-style-type: decimal !important; 7} 8 9::v-deep ul li::before, ::v-deep ol li::before { 10 content: none; 11} 12::v-deep .tui-editor-contents p>code{ 13 background-color: #fff5f5; 14 color: #ff502c; 15} 16::v-deep .tui-editor-contents pre { 17 width: 100%; 18 overflow: auto; 19} 20

线上体验

1https://www.maomin.club/site/mdeditor/ 2

结语

谢谢阅读,希望没有浪费你的时间。

源码地址:

1https://github.com/maomincoding/simpleMdEditor 2

如果对你有帮助,欢迎Star~

本文转转自微信公众号** 前端历劫之路**原创https://mp.weixin.qq.com/s/mWVc92ifBVm7Jo36uxVcOg,如有侵权,请联系删除。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )