上一篇 UEditor集成Markdown编辑功能方案,实现的思路是支持Markdown格式的内容插入功能,后来一想,既然可以将Markdown格式转换为HTML格式,为什么不同时支持HTML格式转Markdown格式呢!
同时对界面也做了样式上的美化;
点击 Ueditor 工具栏上的 markdown 图标,如果 编辑器内有内容,就直接转换为 Markdown 格式,显示在Markdown 格式编辑器的编辑框内,这样就实现的双向的编辑与转换。
效果如下:
依赖库
markdown-it 用于将 Markdown 格式转换为 HTML 格式
turndown 用于将 HTML 格式转换为 Markdown 格式
UI代码
<div id="mdOverlay" style="display:none;position: fixed; top:0; left:0; width:100%; height:100%; background: rgba(0,0,0,0.5); opacity: 0.3; filter: alpha(opacity=30); z-index: 9999;"></div>
<div id="mdModal" style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%,-50%); background: white; box-shadow: 1px 1px 30px rgba(0,0,0,0.3); z-index: 10000; border-radius: 3px;">
<div style="padding:10px; line-height: 22px; border-bottom: 1px solid #ddd; font-size: 14px; color: #333; overflow: hidden; background-color: #f8f8f8; border-radius: 3px 3px 0 0;">Markdown格式编辑器
<span class="mdDlgCloseBtn" onclick="closeMDDialog()" style="float:right; width: 16px;
height: 16px;
justify-content: center;
align-items: center;
color: rgb(255, 255, 255);
background-color: rgb(0, 0, 0);
opacity: 0.7;
cursor: pointer;
line-height: 16px;
text-align: center;
border-radius: 50%;
transition: all 0.3s ease 0s;"><svg width="8" height="10" viewBox="0 0 9 9" fill="none" xmlns="http://www.w3.org/2000/svg" hanging="8"><path d="M7.83725 1.30615L1.30664 7.83676M1.30664 1.30615L7.83725 7.83676" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"></path></svg></span>
</div>
<textarea id="mdContent" style="width:600px;height:300px; margin: 10px 10px 0 10px;"></textarea>
<div style="text-align: right; padding: 10px;">
<button type="button" onclick="updateEditor()">确定</button>
<button type="button" onclick="closeMDDialog()">关闭</button>
</div>
</div>
脚本依赖
<script type="text/javascript" charset="utf-8" src="third-party/turndown720.js"></script>
<script type="text/javascript" charset="utf-8" src="third-party/markdown-it1301.min.js"></script>
完整脚本实现
//以下为Markdown格式内容器相关代码
// 初始化转换器
const mdParser = window.markdownit();
const turndownService = new TurndownService({
codeBlockStyle: 'fenced', // 强制使用围栏代码块
headingStyle: 'atx' // 保证标题转换兼容性
});
// ================= 多行代码块规则(高优先级) =================
turndownService.addRule('codeBlocks', {
filter: function(node) {
// 匹配两种情况:1. PRE>CODE 2. 单独的PRE(兼容异常情况)
return node.nodeName === 'PRE' && (
node.firstChild?.nodeName === 'CODE' ||
!node.querySelector('code')
);
},
replacement: function(content, node) {
// 提取代码内容
const codeNode = node.querySelector('code') || node;
let codeText = codeNode.textContent;
// 优化1:清理首尾空白
codeText = codeText.replace(/^\n+|\n+$/g, '');
// 优化2:转义内部反引号
codeText = codeText.replace(/\`/g, '\\\`');
// 优化3:识别语言类型(支持language-xxx和lang-xxx格式)
const langMatch = codeNode.className.match(/(?:lang|language)-(\w+)/i);
const lang = langMatch ? langMatch[1] : '';
// 优化4:处理混合内容的情况
if (node !== codeNode.parentNode) {
return \`\\`\\`\\`${lang}\n${codeText}\n\\`\\`\\`\n\n\`;
}
return \`\\`\\`\\`${lang}\n${codeText}\n\\`\\`\\`\n\n\`;
}
});
// ================= 行内代码/意外多行代码(低优先级) =================
turndownService.addRule('inlineCode', {
filter: ['code'],
replacement: function(content, node) {
// 优化5:检测是否包含换行符
const hasNewline = /\n/.test(content);
// 优化6:处理嵌套在pre外的代码
if (hasNewline || node.parentNode.nodeName !== 'PRE') {
const escaped = content.replace(/\`/g, '\\\`');
return hasNewline
? \`\\`\\`\\`\n${escaped}\n\\`\\`\\`\`
: \`\\`${escaped}\\`\`;
}
return content; // 已在codeBlocks规则处理
}
});
turndownService.addRule('table', { // 添加名为'table'的新转换规则
filter: 'table', // 指定过滤目标为<table>标签
replacement: (content) => \`\n${content}\n\`
// 定义转换方式:在表格内容前后添加换行符
});
let currentEditor = null;
function showMDDialog(editor) {
currentEditor = editor;
// 获取编辑器原始内容并转换
const htmlContent = editor.getContent();
const markdownContent = turndownService.turndown(htmlContent);
document.getElementById('mdContent').value = markdownContent;
document.getElementById('mdModal').style.display = 'block';
document.getElementById('mdOverlay').style.display = 'block';
}
function closeMDDialog() {
document.getElementById('mdModal').style.display = 'none';
document.getElementById('mdOverlay').style.display = 'none';
}
// 更新编辑器内容
function updateEditor() {
const markdownContent = document.getElementById('mdContent').value;
const newHtml = mdParser.render(markdownContent);
// 使用setContent完全替换编辑器内容
currentEditor.setContent(newHtml);
closeMDDialog();
}
UE.registerUI('insert_markdown', function(editor, uiName) {
editor.registerCommand(uiName, {
execCommand: function() {
showMDDialog(editor);
}
});
var btn = new UE.ui.Button({
name: uiName,
title: "Markdown格式编辑器",
cssRules: 'background-position: -775px -76px;',
onclick: function() {
editor.execCommand(uiName);
}
});
editor.addListener('selectionchange', function() {
var state = editor.queryCommandState(uiName);
if (state == -1) {
btn.setDisabled(true);
btn.setChecked(false);
} else {
btn.setDisabled(false);
btn.setChecked(state);
}
});
return btn;});