前言
这个项目原本是为了升级 XK-Note 而创造的,后来因为功能的不断完善,逐渐的改成了可复用的组件。XK-Note 原本是使用 Editor.md 的编辑器,使用起来也很不错,但是总有一些不如意的地方,于是便打算自己造个编辑器。
XK-Editor 基于 Vue 是因为双向绑定这个特性,同时也是模板语法,便于开发,之所以采用 ACE 编辑器其实是当时觉得 CodeMirror 太丑了,实际开发中 ACE 实在太难弄了,文档跟不存在一样,只能阅读源码来实现一些不常见的功能,同时也修补了 Editor.md 中一些不如意的地方,比如没有打字机模式,每次写到屏幕下方的时候总是要重新滚动,代码高亮不够漂亮,没有自动补全等等。。。又引入了 Tiny MCE 使得编辑器拥有了富文本编辑的功能。
简介 Introduction
XK-Editor
=Vue2.0
+Ace
+TinyMCE
;
XK-Editor 支持富文本编辑和 Markdown ,同时可以在 Markdown 和 HTML 互转,满足各种人的需求。
特性 Feature
- [两种编辑器] 支持富文本编辑和 Markdown 编辑
- [两种格式互转] 支持 Markdown 和 HTML 互转
- [打字机模式] 支持打字机模式,编辑时无需使用鼠标滚轮,并可调节定位位置,满足各种屏幕和使用者的需求
- [粘贴自动格式化] 支持在粘贴 HTML 时自动将 HTML 格式化为 Markdown
- [图片粘贴自动上传] 支持粘贴图片自动上传,并自动填充链接
- [嵌入运行块] 支持嵌入可运行的代码块,通过与后端交互可以支持多种语言
- [下载] 支持导出下载 Markdown 和 HTML 格式的文件
- [即时保存] 支持即时保存到浏览器,无惧网络波动
- [移动端优化] 优化移动端的编辑体验,支持惯性滚动,并默认关闭实时预览
- [滚动绑定] 支持双向/单向滚动绑定,使预览能够跟随输入
- [树形 TOC] 目录可折叠,不用再翻阅很久来定位
- [自定义主题] 支持自定义主题,可以通过替换 CSS 来更换显示样式
- [Emoji 表情] 支持 Github 语法的表情
:smile:
- [Task lists] 支持创建 Task 列表
- [TeX 公式] 支持插入 KaTex 公式
- [流程图/时序图/甘特图] 支持 mermaid 语法编写各种图
- [解析 HTML] 支持解析各种 HTML 标签,并支持过滤标签
- [独有的扩展语法] 拥有一些扩展语法
- [自动补全] 支持语法自动补全
- [图片上传] 支持图片上传
- 还有多种神奇的功能等待你的发掘。
演示 Demo
安装 Install
注意事项
XK-Editor 还在不断的改进中 API 可能会更改
从 Version 1.0.8 开始,为了减小 Vendor 体积,防止加载时间过长,XK-Editor 默认使用 jsDelivr CDN
加载部分 node_modules
需要在 index.html 中添加以下 script 标签,若您不打算使用该方式加载,请将 node_modules/xkeditor/components
下的文件中所有的 import
注释取消。
从 Version 1.4.6 开始,模块默认通过 webpack externals
方式导入,所以您需要修改 webpack
的配置文件,添加对应的配置,如下
<!-- ACE Editor -->
<script src="https://cdn.jsdelivr.net/npm/ace-builds@1.4.4/src-noconflict/ace.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/ace-builds@1.4.4/src-noconflict/ext-language_tools.js"></script>
<!-- Marked -->
<script src="https://cdn.jsdelivr.net/npm/marked@0.7.0/lib/marked.min.js"></script>
<!-- Turndown -->
<script src="https://cdn.jsdelivr.net/npm/turndown@5.0.3/dist/turndown.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/turndown-plugin-gfm@1.0.2/dist/turndown-plugin-gfm.min.js"></script>
<!-- Preitter -->
<script src="https://cdn.jsdelivr.net/npm/prettier@1.18.2/standalone.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prettier@1.18.2/parser-markdown.js"></script>
<!-- Prism.js -->
<script src="/static/prism.js"></script>
<link rel="stylesheet" href="/static/prism-okaidia.css" />
<link rel="stylesheet" href="/static/prism-line-numbers.css" />
<link rel="stylesheet" href="/static/prism-toolbar.css" />
<!-- Katex -->
<script src="https://cdn.jsdelivr.net/npm/katex@0.10.2/dist/katex.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/katex@0.10.2/dist/contrib/auto-render.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.10.2/dist/katex.min.css" />
<!-- Mermaid -->
<script src="https://cdn.jsdelivr.net/npm/mermaid@8.4.3/dist/mermaid.min.js"></script>
<!-- Emoji-js -->
<script src="https://cdn.jsdelivr.net/npm/emoji-js@3.4.1/lib/emoji.min.js"></script>
<!-- TinyMCE -->
<script src="https://cdn.jsdelivr.net/npm/tinymce@5.0.5/tinymce.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/tinymce@5.0.5/themes/silver/theme.min.js"></script>
<script src="https://cdn.jsdelivr.net/combine/npm/tinymce@5.0.5/plugins/print/plugin.min.js,npm/tinymce@5.0.5/plugins/preview/plugin.min.js,npm/tinymce@5.0.5/plugins/fullpage/plugin.min.js,npm/tinymce@5.0.5/plugins/fullscreen/plugin.min.js,npm/tinymce@5.0.5/plugins/searchreplace/plugin.min.js,npm/tinymce@5.0.5/plugins/autolink/plugin.min.js,npm/tinymce@5.0.5/plugins/directionality/plugin.min.js,npm/tinymce@5.0.5/plugins/code/plugin.min.js,npm/tinymce@5.0.5/plugins/visualblocks/plugin.min.js,npm/tinymce@5.0.5/plugins/visualchars/plugin.min.js,npm/tinymce@5.0.5/plugins/image/plugin.min.js,npm/tinymce@5.0.5/plugins/link/plugin.min.js,npm/tinymce@5.0.5/plugins/media/plugin.min.js,npm/tinymce@5.0.5/plugins/template/plugin.min.js,npm/tinymce@5.0.5/plugins/codesample/plugin.min.js,npm/tinymce@5.0.5/plugins/table/plugin.min.js,npm/tinymce@5.0.5/plugins/charmap/plugin.min.js,npm/tinymce@5.0.5/plugins/hr/plugin.min.js,npm/tinymce@5.0.5/plugins/pagebreak/plugin.min.js,npm/tinymce@5.0.5/plugins/nonbreaking/plugin.min.js,npm/tinymce@5.0.5/plugins/anchor/plugin.min.js,npm/tinymce@5.0.5/plugins/toc/plugin.min.js,npm/tinymce@5.0.5/plugins/insertdatetime/plugin.min.js,npm/tinymce@5.0.5/plugins/advlist/plugin.min.js,npm/tinymce@5.0.5/plugins/lists/plugin.min.js,npm/tinymce@5.0.5/plugins/wordcount/plugin.min.js,npm/tinymce@5.0.5/plugins/imagetools/plugin.min.js,npm/tinymce@5.0.5/plugins/textpattern/plugin.min.js"></script>
<!-- ACE Editor -->
<script src="https://cdn.jsdelivr.net/npm/ace-builds@1.4.4/src-noconflict/ace.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/ace-builds@1.4.4/src-noconflict/ext-language_tools.js"></script>
<!-- Marked -->
<script src="https://cdn.jsdelivr.net/npm/marked@0.7.0/lib/marked.min.js"></script>
<!-- Turndown -->
<script src="https://cdn.jsdelivr.net/npm/turndown@5.0.3/dist/turndown.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/turndown-plugin-gfm@1.0.2/dist/turndown-plugin-gfm.min.js"></script>
<!-- Preitter -->
<script src="https://cdn.jsdelivr.net/npm/prettier@1.18.2/standalone.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prettier@1.18.2/parser-markdown.js"></script>
<!-- Prism.js -->
<script src="/static/prism.js"></script>
<link rel="stylesheet" href="/static/prism-okaidia.css" />
<link rel="stylesheet" href="/static/prism-line-numbers.css" />
<link rel="stylesheet" href="/static/prism-toolbar.css" />
<!-- Katex -->
<script src="https://cdn.jsdelivr.net/npm/katex@0.10.2/dist/katex.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/katex@0.10.2/dist/contrib/auto-render.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.10.2/dist/katex.min.css" />
<!-- Mermaid -->
<script src="https://cdn.jsdelivr.net/npm/mermaid@8.4.3/dist/mermaid.min.js"></script>
<!-- Emoji-js -->
<script src="https://cdn.jsdelivr.net/npm/emoji-js@3.4.1/lib/emoji.min.js"></script>
<!-- TinyMCE -->
<script src="https://cdn.jsdelivr.net/npm/tinymce@5.0.5/tinymce.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/tinymce@5.0.5/themes/silver/theme.min.js"></script>
<script src="https://cdn.jsdelivr.net/combine/npm/tinymce@5.0.5/plugins/print/plugin.min.js,npm/tinymce@5.0.5/plugins/preview/plugin.min.js,npm/tinymce@5.0.5/plugins/fullpage/plugin.min.js,npm/tinymce@5.0.5/plugins/fullscreen/plugin.min.js,npm/tinymce@5.0.5/plugins/searchreplace/plugin.min.js,npm/tinymce@5.0.5/plugins/autolink/plugin.min.js,npm/tinymce@5.0.5/plugins/directionality/plugin.min.js,npm/tinymce@5.0.5/plugins/code/plugin.min.js,npm/tinymce@5.0.5/plugins/visualblocks/plugin.min.js,npm/tinymce@5.0.5/plugins/visualchars/plugin.min.js,npm/tinymce@5.0.5/plugins/image/plugin.min.js,npm/tinymce@5.0.5/plugins/link/plugin.min.js,npm/tinymce@5.0.5/plugins/media/plugin.min.js,npm/tinymce@5.0.5/plugins/template/plugin.min.js,npm/tinymce@5.0.5/plugins/codesample/plugin.min.js,npm/tinymce@5.0.5/plugins/table/plugin.min.js,npm/tinymce@5.0.5/plugins/charmap/plugin.min.js,npm/tinymce@5.0.5/plugins/hr/plugin.min.js,npm/tinymce@5.0.5/plugins/pagebreak/plugin.min.js,npm/tinymce@5.0.5/plugins/nonbreaking/plugin.min.js,npm/tinymce@5.0.5/plugins/anchor/plugin.min.js,npm/tinymce@5.0.5/plugins/toc/plugin.min.js,npm/tinymce@5.0.5/plugins/insertdatetime/plugin.min.js,npm/tinymce@5.0.5/plugins/advlist/plugin.min.js,npm/tinymce@5.0.5/plugins/lists/plugin.min.js,npm/tinymce@5.0.5/plugins/wordcount/plugin.min.js,npm/tinymce@5.0.5/plugins/imagetools/plugin.min.js,npm/tinymce@5.0.5/plugins/textpattern/plugin.min.js"></script>
module.exports = {
externals: {
"ace-builds": "ace",
marked: "marked",
turndown: "TurndownService",
"turndown-plugin-gfm": "turndownPluginGfm",
prismjs: "Prism",
"emoji-js": "EmojiConvertor",
"tinymce/tinymce": "tinyMCE",
mermaid: "mermaid",
katex: "katex",
"katex/dist/contrib/auto-render": "renderMathInElement",
},
};
module.exports = {
externals: {
"ace-builds": "ace",
marked: "marked",
turndown: "TurndownService",
"turndown-plugin-gfm": "turndownPluginGfm",
prismjs: "Prism",
"emoji-js": "EmojiConvertor",
"tinymce/tinymce": "tinyMCE",
mermaid: "mermaid",
katex: "katex",
"katex/dist/contrib/auto-render": "renderMathInElement",
},
};
从 NPM 安装
你可以轻松将 XK-Editor 引入你现有的项目
- 安装 XK-Editor
npm i --save xkeditor
npm i --save xkeditor
- 将 XK-Editor static 文件复制到项目根目录
cp -r ./node_modules/xkeditor/public/static ./
cp -r ./node_modules/xkeditor/public/static ./
- 导入 XK-Editor 组件
import XK_Editor from "_posts/xkeditor";
export default {
components: {
"xk-editor": XK_Editor,
},
};
import XK_Editor from "_posts/xkeditor";
export default {
components: {
"xk-editor": XK_Editor,
},
};
- 使用 XK-Editor 组件
<xk-editor :config="config" v-model="content" :data.sync="data" />
<!-- config(Object) 是下方 config 内容,该参数是单向的,Editor 内部的设置变动不会同步到父组件 -->
<!-- v-model(String) 对应 Markdown 内容,该参数是双向的,由于 ACE 和 TinyMCE 编辑器的限制,当该参数被外部修改的时候,即与内部 Markdown 内容不一致时,会触发 ACE 编辑器和 TinyMCE 的 setValue,此时光标将会重置。 -->
<!-- data(Object) 是 XK-Editor 扩展的数据内容,用来存储一些非 Markdown 的数据,该参数是双向的 -->
<xk-editor :config="config" v-model="content" :data.sync="data" />
<!-- config(Object) 是下方 config 内容,该参数是单向的,Editor 内部的设置变动不会同步到父组件 -->
<!-- v-model(String) 对应 Markdown 内容,该参数是双向的,由于 ACE 和 TinyMCE 编辑器的限制,当该参数被外部修改的时候,即与内部 Markdown 内容不一致时,会触发 ACE 编辑器和 TinyMCE 的 setValue,此时光标将会重置。 -->
<!-- data(Object) 是 XK-Editor 扩展的数据内容,用来存储一些非 Markdown 的数据,该参数是双向的 -->
所需依赖
ace-builds
@fortawesome/fontawesome-svg-core
@fortawesome/free-solid-svg-icons
@fortawesome/vue-fontawesome
@tinymce/tinymce-vue
axios
emoji-js
katex
marked
mermaid
prismjs
tinymce
turndown
turndown-plugin-gfm
ace-builds
@fortawesome/fontawesome-svg-core
@fortawesome/free-solid-svg-icons
@fortawesome/vue-fontawesome
@tinymce/tinymce-vue
axios
emoji-js
katex
marked
mermaid
prismjs
tinymce
turndown
turndown-plugin-gfm
config && data
var config = {
// 该设置为 TinyMCE 的设置,详情见 TinyMCE 编辑器的文档
tinymceSetting: {
language_url: "/static/tinymce/langs/zh_CN.js",
language: "zh_CN",
skin_url: "/static/tinymce/skins/ui/oxide",
body_class: "markdown-body",
content_css: "/static/github-markdown.css",
plugins:
"print preview fullpage searchreplace autolink directionality code visualblocks visualchars fullscreen image link media template codesample table charmap hr pagebreak nonbreaking anchor toc insertdatetime advlist lists wordcount imagetools textpattern",
toolbar:
"formatselect | fontsizeselect | bold italic underline strikethrough blockquote forecolor backcolor prismjs | link image media pageembed | alignleft aligncenter alignright alignjustify | numlist bullist outdent indent | tex-$ tex-math flow seq gantt mermaid | removeformat code toMarkdownEditor | undo redo",
image_advtab: true,
importcss_append: true,
height: "100%",
template_cdate_format: "[CDATE: %m/%d/%Y : %H:%M:%S]",
template_mdate_format: "[MDATE: %m/%d/%Y : %H:%M:%S]",
image_caption: true,
spellchecker_dialog: true,
spellchecker_whitelist: ["Ephox", "Moxiecode"],
},
// 该设置为 ACE 编辑器的设置,详情见 ACE 编辑器文档
aceSetting: {
// toolbar 为 XK-Editor 扩展的字段,语法和 tinymce 的 toolbar 字段一致
toolbar:
"h1 h2 h3 h4 h5 h6 | bold italic underline strikethrough quote mark code | sup sub tex-$ tex-math | flow seq gantt mermaid | ul ol minus table time | link image video graff | toLine search toc typewriter switchPreview fullPreview fullScreen toHtmlEditor toTinyMCE format empty setting | undo redo | setLocalStorage getLocalStorage removeLocalStorage | help info | pasteFormat",
minLines: 10,
fontSize: "17px",
theme: "ace/theme/solarized_light",
mode: "ace/mode/markdown",
tabSize: 4,
wrap: true,
enableSnippets: true,
enableLiveAutocompletion: true,
enableBasicAutocompletion: true,
value: "# XK-Editor",
},
// XK-Editor 相关设置
xkSetting: {
// 暂时无用
apiBaseUrl: "",
// 预览的 CSS 文件,类似于主题
previewCss: "/static/github-markdown.css",
// 预览的 class
previewClass: "markdown-body",
// 延迟渲染时间 (ms),由于性能因素 XK-Editor 限制该选项不能低于 500ms
delayToHtml: 500,
// 滚动绑定,(left,right,both)
scrollBind: "both",
// 图片上传的地址
imgUpload: "http://example.com/upload.php",
// 滚动模式,默认使用 JavaScript 的方式来滚动,防止与 Hash Router 冲突
scrollMode: "javascript",
// 粘贴格式化
pasteFormat: true,
// 粘贴自动上传(仅对复制图像时有效,混合内容无效,需要设置图片上传地址)
pasteImageUpload: true,
// 是否开启 TinyMCE 编辑器
enableTinyMCE: true,
// run-code 的地址
judge0API: "https://example.com",
// run-code 语言列表
runCodeLangList: {
c: 1,
cpp: 2,
bash: 3,
csharp: 4,
go: 5,
java: 6,
node: 7,
php: 8,
python: 9,
python2: 10,
ruby: 11,
rust: 12,
scala: 13,
typescript: 14,
},
},
};
var data = {
graff: {
// SVG Hash
"6b3117":
// viewBox|SVG innerHTML
'8.399999618530273 9 423.9984436035156 154|<path d="M 290.4,100 L 293.4,104 L 304.4,112 L 326.4,123 L 345.4,131 L 360.4,136 L 378.4,140 L 395.4,145 L 405.4,148 L 415.4,152 L 420.4,152 L 421.4,153 L 422.4,153" fill="none" stroke="#6190e8" stroke-width="2"></path><rect x="18.4" y="19" fill="none" stroke="#6190e8" stroke-width="2" width="131" height="69" d="M 18.4 19 h 131 v 69 h -131 Z"></rect>',
},
};
var config = {
// 该设置为 TinyMCE 的设置,详情见 TinyMCE 编辑器的文档
tinymceSetting: {
language_url: "/static/tinymce/langs/zh_CN.js",
language: "zh_CN",
skin_url: "/static/tinymce/skins/ui/oxide",
body_class: "markdown-body",
content_css: "/static/github-markdown.css",
plugins:
"print preview fullpage searchreplace autolink directionality code visualblocks visualchars fullscreen image link media template codesample table charmap hr pagebreak nonbreaking anchor toc insertdatetime advlist lists wordcount imagetools textpattern",
toolbar:
"formatselect | fontsizeselect | bold italic underline strikethrough blockquote forecolor backcolor prismjs | link image media pageembed | alignleft aligncenter alignright alignjustify | numlist bullist outdent indent | tex-$ tex-math flow seq gantt mermaid | removeformat code toMarkdownEditor | undo redo",
image_advtab: true,
importcss_append: true,
height: "100%",
template_cdate_format: "[CDATE: %m/%d/%Y : %H:%M:%S]",
template_mdate_format: "[MDATE: %m/%d/%Y : %H:%M:%S]",
image_caption: true,
spellchecker_dialog: true,
spellchecker_whitelist: ["Ephox", "Moxiecode"],
},
// 该设置为 ACE 编辑器的设置,详情见 ACE 编辑器文档
aceSetting: {
// toolbar 为 XK-Editor 扩展的字段,语法和 tinymce 的 toolbar 字段一致
toolbar:
"h1 h2 h3 h4 h5 h6 | bold italic underline strikethrough quote mark code | sup sub tex-$ tex-math | flow seq gantt mermaid | ul ol minus table time | link image video graff | toLine search toc typewriter switchPreview fullPreview fullScreen toHtmlEditor toTinyMCE format empty setting | undo redo | setLocalStorage getLocalStorage removeLocalStorage | help info | pasteFormat",
minLines: 10,
fontSize: "17px",
theme: "ace/theme/solarized_light",
mode: "ace/mode/markdown",
tabSize: 4,
wrap: true,
enableSnippets: true,
enableLiveAutocompletion: true,
enableBasicAutocompletion: true,
value: "# XK-Editor",
},
// XK-Editor 相关设置
xkSetting: {
// 暂时无用
apiBaseUrl: "",
// 预览的 CSS 文件,类似于主题
previewCss: "/static/github-markdown.css",
// 预览的 class
previewClass: "markdown-body",
// 延迟渲染时间 (ms),由于性能因素 XK-Editor 限制该选项不能低于 500ms
delayToHtml: 500,
// 滚动绑定,(left,right,both)
scrollBind: "both",
// 图片上传的地址
imgUpload: "http://example.com/upload.php",
// 滚动模式,默认使用 JavaScript 的方式来滚动,防止与 Hash Router 冲突
scrollMode: "javascript",
// 粘贴格式化
pasteFormat: true,
// 粘贴自动上传(仅对复制图像时有效,混合内容无效,需要设置图片上传地址)
pasteImageUpload: true,
// 是否开启 TinyMCE 编辑器
enableTinyMCE: true,
// run-code 的地址
judge0API: "https://example.com",
// run-code 语言列表
runCodeLangList: {
c: 1,
cpp: 2,
bash: 3,
csharp: 4,
go: 5,
java: 6,
node: 7,
php: 8,
python: 9,
python2: 10,
ruby: 11,
rust: 12,
scala: 13,
typescript: 14,
},
},
};
var data = {
graff: {
// SVG Hash
"6b3117":
// viewBox|SVG innerHTML
'8.399999618530273 9 423.9984436035156 154|<path d="M 290.4,100 L 293.4,104 L 304.4,112 L 326.4,123 L 345.4,131 L 360.4,136 L 378.4,140 L 395.4,145 L 405.4,148 L 415.4,152 L 420.4,152 L 421.4,153 L 422.4,153" fill="none" stroke="#6190e8" stroke-width="2"></path><rect x="18.4" y="19" fill="none" stroke="#6190e8" stroke-width="2" width="131" height="69" d="M 18.4 19 h 131 v 69 h -131 Z"></rect>',
},
};
运行代码块
运行代码块除 javascript 不需要使用后端,其他语言都需要有后端负责执行代码。 后端执行器基于 judge0/api 重新 build 而来,支持的语言详见 run-code,若您有其他语言的需求,您可以自行参照教程重新 build
文档 Doc
Github
求 star = ̄ ω  ̄=
维护者 Maintainer
XK-Editor 由 Otstar Lin 和下列 贡献者 的帮助下撰写和维护。
Otstar Lin -Personal Website·Blog·Github
许可证 License
根据 Apache License 2.0 许可证开源。
渲染 Render
结语
首先感谢你能看到最后,同时感谢你对该项目的兴趣。 该项目虽然不大,大部分核心的部件也是使用现成的组件,但是对我来说也是一次良好的经验,也是我第一个完整的前端项目,所以难免会有 Bug,若您在使用遇到了问题,请及时联系我进行修复,或者到 Github 上提交 issue,我会尽快的处理,另外若您有其他好的建议或者有希望增加的功能,请联系我进行添加或者提交 Pull Request。
Coding 最大的乐趣,不在 Coding,而在 Creating。 --- Otstar Lin
XK-Editor - 一个支持富文本和Markdown的编辑器
https://blog.ixk.me/post/xkeditor许可协议
BY-NC-SA
本文作者
Otstar Lin
发布于
2019/04/27
转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!