最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 我熬夜开发了一款简约实用、支持多平台的Markdown在线编辑器(开源)

    正文概述 掘金(Vam的金豆之路)   2021-03-08   429

    前言

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

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元