在 CKEditor 中集成自定义 Markdown 插件
一背景与动机
传统的所见即所得(WYSIWYG)编辑器擅长可视化排版,但在书写包含大量公式代码长文档时,Markdown 的简洁与可读性更胜一筹。将两者结合,可以既享受 Markdown 的轻量书写,又能即时预览所见效果。本示例基于 CKEditor 4,演示如何编写一个自定义插件,将 Markdown 转换预览以及公式渲染一并集成到对话框中。
二前置条件
-
已经在页面中正确加载了 CKEditor 4。
-
具备对 CKEditor 插件目录(通常在
static/ckeditor/plugins/
)的读写权限。 -
可以访问外部 CDN(或自行下载并部署以下依赖):
三插件目录结构
假设将插件放在 plugins/md/
目录下,建议结构如下:
md/
├── dialogDefinition.js // 对话框定义(如前端所见输入预览)
├── dialogs/
│ └── md.js // 对话框脚本
├── icon.png // 工具栏图标
└── plugin.js // 插件主入口
四核心代码解析
1. 插件入口:plugin.js
CKEDITOR.plugins.add('md' {
requires: 'dialog'
init: function(editor) {
// 允许并保护 <script> 标签,确保 MathJax 能正常注入
editor.filter.allow('script[type]')
if (editor.config.protectedSource) {
editor.config.protectedSource.push(/<script[\s\S]*?<\/script>/g)
}
// 加载 marked.js
if (!window.marked) {
CKEDITOR.scriptLoader.load(
'https://cdn.jsdelivr.net/npm/marked/marked.min.js'
)
}
// 配置并加载 MathJax
if (!window._mdMathJaxLoading) {
window._mdMathJaxLoading = true
window.MathJax = {
tex: {
inlineMath: [['$''$'] ['\\(''\\)']]
displayMath: [['$$''$$'] ['\\[''\\]']]
}
options: {
skipHtmlTags: {'[-]':['script''noscript''style''textarea''pre''code']}
}
}
CKEDITOR.scriptLoader.load(
'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js'
function(){ window._mdMathJaxReady = true }
)
}
// 在编辑器 iframe 中注入 MathJax 配置与脚本
editor.on('contentDom' function() {
var head = editor.document.getHead()
if (!editor.document.getById('_mj_conf')) {
head.appendHtml(
'<script id="_mj_conf">window.MathJax={tex:{inlineMath:[["$""$"]["\\\\(""\\\\)"]]displayMath:[["$$""$$"]["\\\\[""\\\\]"]]}options:{skipHtmlTags:{"[-]":["script""noscript""style""textarea""pre""code"]}}}</script>'
)
}
if (!editor.document.getById('_mj_script')) {
head.appendHtml(
'<script id="_mj_script" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>'
)
}
})
// 注册对话框命令和按钮
CKEDITOR.dialog.add('md' this.path + 'dialogs/md.js')
editor.addCommand('md' new CKEDITOR.dialogCommand('md'))
editor.ui.addButton('md' {
label: '插入 Markdown'
command: 'md'
toolbar: 'insert'
icon: this.path + 'icon.png'
})
}
})
- scriptLoader:动态加载外部脚本,以避免手动在页面中插入。
- protectedSource:保护
<script>
标签,防止 CKEditor 自动过滤掉 MathJax 配置。 - contentDom 事件:在编辑器内部 iframe 头部注入 MathJax,以保证编辑时也能渲染公式。
2. 对话框定义:dialogs/md.js
(function() {
function injectCss() {
if (window._mdCssInjected) return
CKEDITOR.document.appendStyleSheet(
'https://cdn.jsdelivr.net/npm/github-markdown-css@5/github-markdown.min.css'
)
CKEDITOR.document.appendStyleText(
'textarea.cke_dialog_ui_input_textarea { font-family: monospace padding:8px border:1px solid #ccc border-radius:4px resize:vertical}'
+ '#md_preview { background:#fafafa border:1px solid #ddd padding:10px height:100% overflow:auto}'
+ '.markdown-body { background:#fff padding:10px border-radius:4px }'
)
window._mdCssInjected = true
}
function sanitize(html) {
// 去除中英文逗号分号,并保护 LaTeX 里的方括号
return html
.replace(/[]/g '')
.replace(/\[(\d+)pt\]/g '\\[$1pt]')
}
function updatePreview(mdText) {
injectCss()
var dv = document.getElementById('md_preview')
if (!dv) return
var html = window.marked
? marked.parse(mdText { gfm: true breaks: true })
: mdText
html = sanitize(html)
dv.innerHTML = '<div class="markdown-body">' + html + '</div>'
if (window._mdMathJaxReady && window.MathJax && MathJax.typesetPromise) {
MathJax.typesetPromise([dv]).catch(console.error)
}
}
CKEDITOR.dialog.add('md' function(editor) {
return {
title: 'Markdown 转换'
minWidth: 700
minHeight: 500
contents: [{
id: 'tab-md'
label: 'Markdown'
elements: [{
type: 'hbox'
widths: [ཀྵ%' ཱི%']
children: [
{
id: 'inputMd'
type: 'textarea'
label: 'Markdown 输入'
rows: 20
onKeyUp: function() {
updatePreview(this.getValue())
}
}
{
id: 'preview'
type: 'html'
html: '<div id="md_preview"><p style="color:#999margin:0">预览中…</p></div>'
}
]
}]
}]
onShow: function() {
this.getContentElement('tab-md''inputMd').setValue('')
updatePreview('')
}
onOk: function() {
var mdText = this.getContentElement('tab-md''inputMd').getValue()
var html = window.marked
? marked.parse(mdText { gfm: true breaks: true })
: mdText
html = sanitize(html)
editor.insertHtml('<div class="markdown-body">' + html + '</div>')
// 在编辑区触发 MathJax 渲染
var win = editor.document.getWindow().$
if (win.MathJax && win.MathJax.typesetPromise) {
win.MathJax.typesetPromise([editor.document.getBody().$]).catch(console.error)
}
}
}
})
})()
- injectCss:一次性注入 GitHub Markdown CSS 和自定义样式,保证预览一致性。
- sanitize:示例去除多余标点,可根据需求扩展,比如过滤 XSS 或自定义标签。
- updatePreview:实时将 Markdown 渲染为 HTML 并调用 MathJax。
五使用效果
- 点击工具栏上的“插入 Markdown”按钮,弹出对话框。
- 左侧输入 Markdown(支持 GFM自动换行水平线等),右侧实时预览。
- 支持 LaTeX 公式(
$...$
$$...$$
)渲染。 - 点击确定,将渲染后的 HTML(含
.markdown-body
容器)插入编辑区,并在编辑区内执行一次 MathJax 渲染。
六常见问题与扩展
- 样式冲突:若页面已有全局 Markdown 样式,请根据选择器优先级调整或限定作用域。
- 性能考虑:对话框每次 onKeyUp 都触发解析与渲染,大文本时可能卡顿,可考虑节流或延迟执行。
- 更多语法支持:可替换或扩展 marked.js 配置,引入插件支持表格任务列表自定义容器等。
- 安全过滤:生产环境下建议对用户输入进行严格过滤(例如使用 DOMPurify)以防 XSS 风险。
本文作者: 永生
本文链接: https://yys.zone/detail/?id=441
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
评论列表 (0 条评论)