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

上面的工具栏其实就是添加事件然后往光标插入对应的语句而已,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" style="margin: -60px -20px -30px -20px"> <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" style="width: 100%"> <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: ' 下载地址: JS原生双栏穿梭选择框的实现示例 vue实现全选功能 |