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

Jacquelyn38
• 阅读 1599

前言

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

完成项目一览

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

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

我熬夜开发了一款简约实用、支持多平台的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

<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>  

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

增加功能

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

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

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

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

因篇幅原因,先奉上主要逻辑代码。这里我使用了clipboard这个将文本复制到剪贴板的插件。网址:https://clipboardjs.com/。 另外,downloadBlobAsFile方法主要是创建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"  
      >关于</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="'关于'"  
          :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的金豆之路','欢迎关注我的公众号:前端历劫之路','我创建了一个技术交流、文章分享群,群里有很多大厂的前端大佬,关注公众号后,点击下方菜单了解更多即可加我微信,期待你的加入']  
    };  
  },  
  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>  

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

::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/  

结语

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

源码地址:

https://github.com/maomincoding/simpleMdEditor  

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

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

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

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
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
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
AndroidStudio封装SDK的那些事
<divclass"markdown\_views"<!flowchart箭头图标勿删<svgxmlns"http://www.w3.org/2000/svg"style"display:none;"<pathstrokelinecap"round"d"M5,00,2.55,5z"id"raphael
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这