文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

怎么使用基于Ace的Markdown编辑器

2023-06-25 11:11

关注

本篇内容介绍了“怎么使用基于Ace的Markdown编辑器”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

我认为的编辑器分成两类,一种是分为左右两边实现即时渲染;一种是先写语法,然后通过按钮实现渲染。

其实即时渲染也不难,共同需要考虑的问题就是xss,因为渲染库能自定义第三方的xss过滤(之前是通过设置来实现,也就是本身自带,不过在某个版本后被取消了),所以xss就用官方推荐的dompurify。即时渲染可以通过编辑器本身api实现文本变动监听来实现,还有一个需要考虑的问题就是代码与渲染区域的对应。但因为这与我的需求相悖,在这里就不介绍了,相信小老板们都能轻松实现

统一惯例,我们来看看效果图

怎么使用基于Ace的Markdown编辑器
怎么使用基于Ace的Markdown编辑器

上面的工具栏其实就是添加事件然后往光标插入对应的语句而已,emoji暂时没有实现,貌似需要第三方库支持。

整体来说并没有难点,只不过对于这些东西来说,要么是文档分散讲得不清楚,要么就是找不到什么文档。要是真没有文档的话,或者官方简陋的文档,你可能真的想问候一下他,哈哈哈。这个时候一个能用的代码就显得尤为重要,尽管它可能没什么注释,但相信聪明的你肯定能理解其中的意思。话不多说,上代码吧~

<template>  <div>    <div class="section-ace">      <el-row>        <el-col :span="6">          <el-row>            <el-col :span="12">              <a class="editor-tab-content" :class="isEditActive"  @click="showEdit">                <i class="fa fa-pencil-square-o" aria-hidden="true"></i>                编辑              </a>            </el-col>            <el-col :span="12">              <a class="preview-tab-content" :class="isPreviewActive" @click="showPreview">                <i class="fa fa-eye" aria-hidden="true"></i>                预览              </a>            </el-col>          </el-row>        </el-col>        <el-col :push="8" :span="18">          <el-row>            <div class="toolbar">              <el-col :span="1">                <div>                  <i @click="insertBoldCode" class="fa fa-bold" aria-hidden="true"></i>                </div>              </el-col>              <el-col :span="1">                <div>                  <i @click="insertItalicCode" class="fa fa-italic" aria-hidden="true"></i>                </div>              </el-col>              <el-col :span="1">                <div>                  <i @click="insertMinusCode" class="fa fa-minus" aria-hidden="true"></i>                </div>              </el-col>              <el-col :span="1">                <el-popover placement="bottom"                            width="125"                            transition="fade-in-linear"                            trigger="click"                            content="">                  <i slot="reference" class="fa fa-header" aria-hidden="true"></i>                  <div>                    <div class="header1-btn" :class="isHeader1Active" @click="insertHeader1Code">                       1 (Ctrl+Alt+1)                    </div>                    <div class="header2-btn" :class="isHeader2Active" @click="insertHeader2Code">                       2 (Ctrl+Alt+2)                    </div>                    <div class="header3-btn" :class="isHeader3Active" @click="insertHeader3Code">                       3 (Ctrl+Alt+3)                    </div>                  </div>                </el-popover>              </el-col>              <el-col :span="1">                <el-popover placement="bottom"                            width="125"                            transition="fade-in-linear"                            trigger="click"                            content="">                  <i slot="reference" class="fa fa-code" aria-hidden="true"></i>                  <div>                    <div class="text-btn" :class="isTextActive" @click="insertText">                      文本 (Ctrl+Alt+P)                    </div>                    <div class="code-btn" :class="isCodeActive" @click="insertCode">                      代码 (Ctrl+Alt+C)                    </div>                  </div>                </el-popover>              </el-col>              <el-col :span="1">                <div>                  <i @click="insertQuoteCode" class="fa fa-quote-left" aria-hidden="true"></i>                </div>              </el-col>              <el-col :span="1">                <div>                  <i @click="insertUlCode" class="fa fa-list-ul" aria-hidden="true"></i>                </div>              </el-col>              <el-col :span="1">                <div>                  <i @click="insertOlCode" class="fa fa-list-ol" aria-hidden="true"></i>                </div>              </el-col>              <el-col :span="1">                <div>                  <i @click="insertLinkCode" class="fa fa-link" aria-hidden="true"></i>                </div>              </el-col>              <el-col :span="1">                <div>                  <i @click="insertImgCode" class="fa fa-picture-o" aria-hidden="true"></i>                </div>              </el-col>              <el-col :span="1">                <div>                  <el-upload                    class="upload-demo"                    action="https://jsonplaceholder.typicode.com/posts/"                    :limit="1">                    <i class="fa fa-cloud-upload" aria-hidden="true"></i>                  </el-upload>                </div>              </el-col>              <el-col :span="1">                <div>                  <i @click="selectEmoji" class="fa fa-smile-o" aria-hidden="true"></i>                </div>              </el-col>              <el-col :span="1">                <div>                  <i @click="toggleMaximize" class="fa fa-arrows-alt" aria-hidden="true"></i>                </div>              </el-col>              <el-col :span="1">                <i @click="toggleHelp" class="fa fa-question-circle" aria-hidden="true"></i>                <el-dialog :visible.sync="dialogHelpVisible"                           :show-close="false"                           top="5vh"                           width="60%"                           :append-to-body="true"                           :close-on-press-escape="true">                  <el-card class="box-card" >                    <div slot="header" class="helpHeader">                      <i class="fa fa-question-circle" aria-hidden="true"><span>Markdown Guide</span></i>                    </div>                    <p>This site is powered by Markdown. For full documentation,                      <a href="http://commonmark.org/help/" rel="external nofollow"  target="_blank">click here</a>                    </p>                    <el-table                      :data="tableData"                      stripe                      border                      :highlight-current-row="true"                      >                      <el-table-column                        prop="code"                        label="Code"                        width="150">                        <template slot-scope="scope">                          <p v-html='scope.row.code'></p>                        </template>                      </el-table-column>                      <el-table-column                        prop="or"                        label="Or"                        width="180">                        <template slot-scope="scope">                          <p v-html='scope.row.or'></p>                        </template>                      </el-table-column>                      <el-table-column                        prop="devices"                        label="Linux/Windows">                      </el-table-column>                      <el-table-column                        prop="device"                        label="Mac OS"                        width="180">                      </el-table-column>                      <el-table-column                        prop="showOff"                        label="... to Get"                      width="200">                        <template slot-scope="scope">                          <p v-html='scope.row.showOff'></p>                        </template>                      </el-table-column>                    </el-table>                  </el-card>                </el-dialog>              </el-col>            </div>          </el-row>        </el-col>      </el-row>    </div>    <br>    <div id="container">      <div class="show-panel">        <div ref="markdown" class="ace" v-show="!isShowPreview"></div>        <div class="panel-preview" ref="preview" v-show="isShowPreview"></div>      </div>    </div>  </div></template><script>import ace from 'ace-builds'// 在 webpack 环境中使用必须要导入import 'ace-builds/webpack-resolver';import marked  from 'marked'import highlight from "highlight.js";import "highlight.js/styles/foundation.css";import katex from 'katex'import 'katex/dist/katex.css'import DOMPurify from 'dompurify';const renderer = new marked.Renderer();function toHtml(text){  let temp = document.createElement("div");  temp.innerHTML = text;  let output = temp.innerText || temp.textContent;  temp = null;  return output;}function mathsExpression(expr) {  if (expr.match(/^\$\$[\s\S]*\$\$$/)) {    expr = expr.substr(2, expr.length - 4);    return katex.renderToString(expr, { displayMode: true });  } else if (expr.match(/^\$[\s\S]*\$$/)) {    expr = toHtml(expr); // temp solution    expr = expr.substr(1, expr.length - 2);    //Does that mean your text is getting dynamically added to the page? If so, someone must be calling KaTeX to render    // it, and that call needs to have the strict flag set to false as well. 即控制台警告,比如%为转义或者中文    // link: https://katex.org/docs/options.html    return katex.renderToString(expr, { displayMode: false , strict: false});  }}const unchanged = new marked.Renderer()renderer.code = function(code, language, escaped) {  console.log(language);  const isMarkup = ['c++', 'cpp', 'golang', 'java', 'js', 'javascript', 'python'].includes(language);  let hled = '';  if (isMarkup) {    const math = mathsExpression(code);    if (math) {      return math;    } else {      console.log("highlight");      hled = highlight.highlight(language, code).value;    }  } else {    console.log("highlightAuto");    hled = highlight.highlightAuto(code).value;  }  return `<pre class="hljs ${language}"><code class="${language}">${hled}</code></pre>`;  // return unchanged.code(code, language, escaped);};renderer.codespan = function(text) {  const math = mathsExpression(text);  if (math) {    return math;  }  return unchanged.codespan(text);};export default {  name: "abc",  props: {    value: {      type: String,      required: true    }  },  data() {    return {      tableData: [{        code: ':emoji_name:',        or: '—',        devices: '—',        device: '—',        showOff: '?'      },{        code: '*Italic*',        or: '_Italic_',        devices: 'Ctrl+I',        device: 'Command+I',        showOff: '<em>Italic</em>'      },{        code: '**Bold**',        or: '__Bold__',        devices: 'Ctrl+B',        device: 'Command+B',        showOff: '<em>Bold</em>'      },{        code: '++Underscores++',        or: '—',        devices: 'Shift+U',        device: 'Option+U',        showOff: '<ins>Underscores</ins>'      },{        code: '~~Strikethrough~~',        or: '—',        devices: 'Shift+S',        device: 'Option+S',        showOff: '<del>Strikethrough</del>'      },{        code: '# Heading 1',        or: 'Heading 1<br>=========',        devices: 'Ctrl+Alt+1',        device: 'Command+Option+1',        showOff: '<h2>Heading 1</h2>'      },{        code: '## Heading 2',        or: 'Heading 2<br>-----------',        devices: 'Ctrl+Alt+2',        device: 'Command+Option+2',        showOff: '<h3>Heading 1</h3>'      },{        code: '[Link](https://a.com)',        or: '[Link][1]<br>⁝<br>[1]: https://b.org',        devices: 'Ctrl+L',        device: 'Command+L',        showOff: '<a href="https://commonmark.org/" rel="external nofollow" >Link</a>'      },{        code: '![Image](http://url/a.png)',        or: '![Image][1]<br>⁝<br>[1]: http://url/b.jpg',        devices: 'Ctrl+Shift+I',        device: 'Command+Option+I',        showOff: '<img src="https://cdn.acwing.com/static/plugins/images/commonmark.png" width="36" height="36" alt="Markdown">'      },{        code: '> Blockquote',        or: '—',        devices: 'Ctrl+Q',        device: 'Command+Q',        showOff: '<blockquote><p>Blockquote</p></blockquote>'      },{        code: 'A paragraph.<br><br>A paragraph after 1 blank line.',        or: '—',        devices: '—',        device: '—',        showOff: '<p>A paragraph.</p><p>A paragraph after 1 blank line.</p>'      },{        code: '<p>* List<br> * List<br> * List</p>',        or: '<p> - List<br> - List<br> - List<br></p>',        devices: 'Ctrl+U',        device: 'Command+U',        showOff: '<ul><li>List</li><li>List</li><li>List</li></ul>'      },{        code: '<p> 1. One<br> 2. Two<br> 3. Three</p>',        or: '<p> 1) One<br> 2) Two<br> 3) Three</p>',        devices: 'Ctrl+Shift+O',        device: 'Command+Option+O',        showOff: '<ol><li>One</li><li>Two</li><li>Three</li></ol>'      },{        code: 'Horizontal Rule<br><br>-----------',        or: 'Horizontal Rule<br><br>***********',        devices: 'Ctrl+H',        device: 'Command+H',        showOff: 'Horizontal Rule<hr>'      },{        code: '`Inline code` with backticks',        or: '—',        devices: 'Ctrl+Alt+C',        device: 'Command+Option+C',        showOff: '<code>Inline code</code>with backticks'      },{        code: '```<br> def whatever(foo):<br>&nbsp;&nbsp;&nbsp;&nbsp;return foo<br>```',        or: '<b>with tab / 4 spaces</b><br>....def whatever(foo):<br>....&nbsp;&nbsp;&nbsp;&nbsp;return foo',        devices: 'Ctrl+Alt+P',        device: 'Command+Option+P',        showOff: '<pre class="hljs"><code class=""><span class="hljs-function"><span class="hljs-keyword">def</span>' +          '<span class="hljs-title">whatever</span><span class="hljs-params">(foo)</span></span>:\n' +          '    <span class="hljs-keyword">return</span> foo</code></pre>'      }],      dialogHelpVisible: false,      isTextActive: '',      isCodeActive: '',      isHeader1Active: '',      isHeader2Active: '',      isHeader3Active: '',      isShowPreview: false,      isEditActive: "active",      isPreviewActive: "",      aceEditor: null,      themePath: 'ace/theme/crimson_editor', // 不导入 webpack-resolver,该模块路径会报错      modePath: 'ace/mode/markdown', // 同上      codeValue: this.value || '',    };  },  methods: {    insertBoldCode() {      this.aceEditor.insert("****");      let cursorPosition = this.aceEditor.getCursorPosition();      this.aceEditor.moveCursorTo(cursorPosition.row, cursorPosition.column - 2);    },    insertItalicCode() {      this.aceEditor.insert("__");      let cursorPosition = this.aceEditor.getCursorPosition();      this.aceEditor.moveCursorTo(cursorPosition.row, cursorPosition.column - 1);    },    insertMinusCode() {      let cursorPosition = this.aceEditor.getCursorPosition();      this.aceEditor.insert("\n\n");      this.aceEditor.insert("----------");      this.aceEditor.insert("\n\n");      this.aceEditor.gotoLine(cursorPosition.row + 5, cursorPosition.column,true);    },    insertHeader1Code() {      this.isHeader2Active = this.isHeader3Active = '';      this.isHeader1Active = 'active';      this.aceEditor.insert("\n\n");      this.aceEditor.insert("#");    },    insertHeader2Code() {      this.isHeader1Active = this.isHeader3Active = '';      this.isHeader2Active = 'active';      this.aceEditor.insert("\n\n");      this.aceEditor.insert("##");    },    insertHeader3Code() {      this.isHeader1Active = this.isHeader2Active = '';      this.isHeader3Active = 'active';      this.aceEditor.insert("\n\n");      this.aceEditor.insert("###");    },    insertText() {      let cursorPosition = this.aceEditor.getCursorPosition();      this.isCodeActive = '';      this.isTextActive = 'active';      this.aceEditor.insert("```\n\n```");      this.aceEditor.gotoLine(cursorPosition.row + 2, cursorPosition.column,true);    },    insertCode() {      let cursorPosition = this.aceEditor.getCursorPosition();      this.isTextActive = '';      this.isCodeActive = 'active';      this.aceEditor.insert("``");      this.aceEditor.moveCursorTo(cursorPosition.row, cursorPosition.column + 1);    },    insertQuoteCode() {      this.aceEditor.insert("\n>");      let cursorPosition = this.aceEditor.getCursorPosition();      this.aceEditor.moveCursorTo(cursorPosition.row, cursorPosition.column + 1);    },    insertUlCode() {      this.aceEditor.insert("\n*");      let cursorPosition = this.aceEditor.getCursorPosition();      this.aceEditor.moveCursorTo(cursorPosition.row, cursorPosition.column + 1);    },    insertOlCode() {      this.aceEditor.insert("\n1.");      let cursorPosition = this.aceEditor.getCursorPosition();      this.aceEditor.moveCursorTo(cursorPosition.row, cursorPosition.column + 1);    },    insertLinkCode() {      this.aceEditor.insert("[]()");      let cursorPosition = this.aceEditor.getCursorPosition();      this.aceEditor.moveCursorTo(cursorPosition.row, cursorPosition.column - 3);    },    insertImgCode() {      this.aceEditor.insert("![]()");      let cursorPosition = this.aceEditor.getCursorPosition();      this.aceEditor.moveCursorTo(cursorPosition.row, cursorPosition.column - 3);    },    uploadImg() {      this.aceEditor.insert("![]()");    },    selectEmoji() {      this.aceEditor.insert("****");    },    toggleMaximize() {      this.aceEditor.insert("****");    },    toggleHelp() {      this.dialogHelpVisible = !this.dialogHelpVisible;    },    showEdit() {      this.$refs.preview.innerHTML = '';      this.isEditActive = 'active';      this.isPreviewActive = '';      this.isShowPreview = false;    },    showPreview() {      this.show();      this.isEditActive = '';      this.isPreviewActive = 'active';      this.isShowPreview = true;    },    show(data) {      let value = this.aceEditor.session.getValue();      this.$refs.preview.innerHTML = DOMPurify.sanitize(marked(value));      console.log(DOMPurify.sanitize(marked(value)));    },  },  mounted() {    this.aceEditor = ace.edit(this.$refs.markdown,{      selectionStyle: 'line', //选中样式      maxLines: 1000, // 最大行数,超过会自动出现滚动条      minLines: 22, // 最小行数,还未到最大行数时,编辑器会自动伸缩大小      fontSize: 14, // 编辑器内字体大小      theme: this.themePath, // 默认设置的主题      mode: this.modePath, // 默认设置的语言模式      tabSize: 4, // 制表符设置为 4 个空格大小      readOnly: false, //只读      wrap: true,      highlightActiveLine: true,      value: this.codeValue    });    marked.setOptions({      renderer: renderer,      // highlight: function (code) {      //   return highlight.highlightAuto(code).value;      // },      gfm: true,//默认为true。 允许 Git Hub标准的markdown.      tables: true,//默认为true。 允许支持表格语法。该选项要求 gfm 为true。      breaks: false,//默认为false。 允许回车换行。该选项要求 gfm 为true。      pedantic: false,//默认为false。 尽可能地兼容 markdown.pl的晦涩部分。不纠正原始模型任何的不良行为和错误。      // sanitize: false,//对输出进行过滤(清理) 不支持了,用sanitizer 或者直接渲染的时候过滤      xhtml: true, // If true, emit self-closing HTML tags for void elements (<br/>, <img/>, etc.) with a "/" as required by XHTML.      silent: true, //If true, the parser does not throw any exception.      smartLists: true,      smartypants: false//使用更为时髦的标点,比如在引用语法中加入破折号。    });    // this.aceEditor.session.on('change', this.show);    // let that = this;    // this.aceEditor.commands.addCommand({    //   name: '复制',    //   bindKey: {win: 'Ctrl-C',  mac: 'Command-M'},    //   exec: function(editor) {    //     that.$message.success("复制成功");    //   }    // });    // this.aceEditor.commands.addCommand({    //   name: '粘贴',    //   bindKey: {win: 'Ctrl-V',  mac: 'Command-M'},    //   exec: function(editor) {    //     that.$message.success("粘贴成功");    //   }    // });  },  watch: {    value(newVal) {      console.log(newVal);      this.aceEditor.setValue(newVal);    }  }}</script><style scoped lang="scss">.toolbar {  cursor: pointer;//鼠标手型}.show-panel {  padding: 5px;  border: 1px solid lightgray;  .ace {    position: relative !important;    border-top: 1px solid lightgray;    display: block;    margin: auto;    height: auto;    width: 100%;  }  .panel-preview {    padding: 1rem;    margin: 0 0 0 0;    width: auto;    background-color: white;  }}.editor-tab-content, .preview-tab-content, .header1-btn, .header2-btn, .header3-btn, .text-btn, .code-btn{  border-bottom-color: transparent;  border-bottom-style: solid;  border-radius: 0;  padding: .85714286em 1.14285714em 1.29999714em 1.14285714em;  border-bottom-width: 2px;  transition: color .1s ease;  cursor: pointer;//鼠标手型}.header1-btn, .header2-btn, .header3-btn, .code-btn, .text-btn {  font-size: 5px;  padding: .78571429em 1.14285714em!important;}.active {  background-color: transparent;  box-shadow: none;  border-color: #1B1C1D;  font-weight: 700;  color: rgba(0,0,0,.95);}.header1-btn:hover, .header2-btn:hover, .header3-btn:hover, .text-btn:hover, .code-btn:hover {  cursor: pointer;//鼠标手型  background: rgba(0,0,0,.05)!important;  color: rgba(0,0,0,.95)!important;}.helpHeader {  font-size: 1.228571rem;  line-height: 1.2857em;  font-weight: 700;  border-top-left-radius: .28571429rem;  border-top-right-radius: .28571429rem;  display: block;  font-family: Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;  background: #FFF;  box-shadow: none;  color: rgba(0,0,0,.85);}</style>

这次的代码同样需要在引用时绑定value,也就是编辑框里的内容

<MarkdownEditor v-bind:value="''"></MarkdownEditor>

哦,对了,忘记讲一些东西了。关于代码块高亮以及latex渲染的问题。

高亮使用的是highlight.js,marked是支持这个库的,直接使用就行,它能自动识别语言,要是不想调用那个函数,你也可以自行判断用户会使用到的语言。主题的使用,需要引用包下style对应的css。还有一个最重要的就是渲染的标签必须要有class为hljs的属性,不然你只能看到代码是高亮的。至于class属性怎么添加,如果你没有letax需求,那么只需要在渲染的时候套一层标签,它的class属性是这个即可。

剩下的就是latex了,因为marked本身是不支持latex的,但是它支持重写render函数,通过这一方法来实现对latex的支持,在这里我使用的是katex,感兴趣的小老板可以试试mathjax。不过有一个不太好的地方就是数学公式需要被代码块包住,即$a * b$。不过这都不是大问题,能好好渲染才是王道。

“怎么使用基于Ace的Markdown编辑器”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯