feat: 更新 MarkdownEditor 组件及其 editor.md 源码

This commit is contained in:
Frankie Huang 2025-04-13 18:02:54 +08:00
parent a43f75db00
commit a9678c0400
7 changed files with 251 additions and 57 deletions

View File

@ -295,6 +295,18 @@
info : "fa-info-circle" info : "fa-info-circle"
}, },
toolbarIconTexts : {}, toolbarIconTexts : {},
toolbarNoFocus : [
"link",
"reference-link",
"image",
"code-block",
"preformatted-text",
"watch",
"preview",
"search",
"fullscreen",
"info",
],
lang : { lang : {
name : "zh-cn", name : "zh-cn",
@ -437,6 +449,49 @@
var classPrefix = this.classPrefix = editormd.classPrefix; var classPrefix = this.classPrefix = editormd.classPrefix;
var settings = this.settings = $.extend(true, editormd.defaults, options); var settings = this.settings = $.extend(true, editormd.defaults, options);
// 支持新增或覆盖原有 toolbar要求 settings.toolbarIcons 非自定义模式)
if (settings.appendToolbar && typeof settings.toolbarIcons === "string" && settings.toolbarIcons in editormd.toolbarModes) {
settings.appendToolbar.forEach(toolbar => {
// 如果没有 name 字段,则忽略该 toolbar
if (!('name' in toolbar)) {
return;
}
// 如果是 | 分隔符,则仅用于占位分隔
if (toolbar.name === "|") {
editormd.toolbarModes[settings.toolbarIcons].push("|");
return;
}
// 如果未定义该 toolbar则执行 append否则覆盖原有 toolbar
if (!editormd.toolbarModes[settings.toolbarIcons].includes(toolbar.name)) {
editormd.toolbarModes[settings.toolbarIcons].push(toolbar.name);
}
if ('icon' in toolbar) {
settings.toolbarIconsClass[toolbar.name] = toolbar.icon;
}
if ('title' in toolbar) {
settings.lang.toolbar[toolbar.name] = toolbar.title;
}
if ('handler' in toolbar) {
editormd.toolbarHandlers[toolbar.name] = toolbar.handler;
if ('shortcut' in toolbar) {
// 给新增的工具栏图标绑定快捷键
toolbar.shortcut.forEach(key => editormd.keyMaps[key] = toolbar.handler);
}
}
if ('nofocus' in toolbar && toolbar.nofocus === true) {
if (!settings.toolbarNoFocus.includes[toolbar.name]) {
settings.toolbarNoFocus.push(toolbar.name);
}
}
});
}
if (settings.shortcutFunction) {
for (const key in settings.shortcutFunction) {
editormd.keyMaps[key] = settings.shortcutFunction[key];
}
}
id = (typeof id === "object") ? settings.id : id; id = (typeof id === "object") ? settings.id : id;
var editor = this.editor = $("#" + id); var editor = this.editor = $("#" + id);
@ -1339,12 +1394,12 @@
if (typeof settings.toolbarHandlers[name] !== "undefined") if (typeof settings.toolbarHandlers[name] !== "undefined")
{ {
$.proxy(settings.toolbarHandlers[name], _this)(cm, icon, cursor, selection); $.proxy(settings.toolbarHandlers[name], _this)(cm, icon, cursor, selection);
} else {
alert("This feature is not yet implemented.")
} }
} }
if (name !== "link" && name !== "reference-link" && name !== "image" && name !== "code-block" && if (!settings.toolbarNoFocus.includes(name)) {
name !== "preformatted-text" && name !== "watch" && name !== "preview" && name !== "search" && name !== "fullscreen" && name !== "info")
{
cm.focus(); cm.focus();
} }
@ -3901,8 +3956,8 @@
if (typeof attrs !== "undefined") if (typeof attrs !== "undefined")
{ {
// 将 html 标签的 attr value 中存在的 <> 进行转义 // 将 html 标签的 attr value 中可能存在的 <> 尖括号进行转义,避免后面的正则替换发生错误
// 示例 <a name="<p>code</p>"></a> 替换为 <a name="&lt;p&gt;code&lt;/p&gt;"></a> // 示例 <a name="<p>code</p>"></a> 转化为 <a name="&lt;p&gt;code&lt;/p&gt;"></a>
html = html.replace(/(\w+)="([^"]*)"/g, (match, key, value) => { html = html.replace(/(\w+)="([^"]*)"/g, (match, key, value) => {
const escapedValue = value.replace(/</g, '&lt;').replace(/>/g, '&gt;'); const escapedValue = value.replace(/</g, '&lt;').replace(/>/g, '&gt;');
return `${key}="${escapedValue}"`; return `${key}="${escapedValue}"`;

File diff suppressed because one or more lines are too long

View File

@ -225,6 +225,18 @@
info : "fa-info-circle" info : "fa-info-circle"
}, },
toolbarIconTexts : {}, toolbarIconTexts : {},
toolbarNoFocus : [
"link",
"reference-link",
"image",
"code-block",
"preformatted-text",
"watch",
"preview",
"search",
"fullscreen",
"info",
],
lang : { lang : {
name : "zh-cn", name : "zh-cn",
@ -367,6 +379,49 @@
var classPrefix = this.classPrefix = editormd.classPrefix; var classPrefix = this.classPrefix = editormd.classPrefix;
var settings = this.settings = $.extend(true, {}, editormd.defaults, options); var settings = this.settings = $.extend(true, {}, editormd.defaults, options);
// 支持新增或覆盖原有 toolbar要求 settings.toolbarIcons 非自定义模式)
if (settings.appendToolbar && typeof settings.toolbarIcons === "string" && settings.toolbarIcons in editormd.toolbarModes) {
settings.appendToolbar.forEach(toolbar => {
// 如果没有 name 字段,则忽略该 toolbar
if (!('name' in toolbar)) {
return;
}
// 如果是 | 分隔符,则仅用于占位分隔
if (toolbar.name === "|") {
editormd.toolbarModes[settings.toolbarIcons].push("|");
return;
}
// 如果未定义该 toolbar则执行 append否则覆盖原有 toolbar
if (!editormd.toolbarModes[settings.toolbarIcons].includes(toolbar.name)) {
editormd.toolbarModes[settings.toolbarIcons].push(toolbar.name);
}
if ('icon' in toolbar) {
settings.toolbarIconsClass[toolbar.name] = toolbar.icon;
}
if ('title' in toolbar) {
settings.lang.toolbar[toolbar.name] = toolbar.title;
}
if ('handler' in toolbar) {
editormd.toolbarHandlers[toolbar.name] = toolbar.handler;
if ('shortcut' in toolbar) {
// 给新增的工具栏图标绑定快捷键
toolbar.shortcut.forEach(key => editormd.keyMaps[key] = toolbar.handler);
}
}
if ('nofocus' in toolbar && toolbar.nofocus === true) {
if (!settings.toolbarNoFocus.includes[toolbar.name]) {
settings.toolbarNoFocus.push(toolbar.name);
}
}
});
}
if (settings.shortcutFunction) {
for (const key in settings.shortcutFunction) {
editormd.keyMaps[key] = settings.shortcutFunction[key];
}
}
id = (typeof id === "object") ? settings.id : id; id = (typeof id === "object") ? settings.id : id;
var editor = this.editor = $("#" + id); var editor = this.editor = $("#" + id);
@ -1269,12 +1324,12 @@
if (typeof settings.toolbarHandlers[name] !== "undefined") if (typeof settings.toolbarHandlers[name] !== "undefined")
{ {
$.proxy(settings.toolbarHandlers[name], _this)(cm, icon, cursor, selection); $.proxy(settings.toolbarHandlers[name], _this)(cm, icon, cursor, selection);
} else {
alert("This feature is not yet implemented.")
} }
} }
if (name !== "link" && name !== "reference-link" && name !== "image" && name !== "code-block" && if (!settings.toolbarNoFocus.includes(name)) {
name !== "preformatted-text" && name !== "watch" && name !== "preview" && name !== "search" && name !== "fullscreen" && name !== "info")
{
cm.focus(); cm.focus();
} }
@ -3832,8 +3887,8 @@
if (typeof attrs !== "undefined") if (typeof attrs !== "undefined")
{ {
// 将 html 标签的 attr value 中存在的 <> 进行转义 // 将 html 标签的 attr value 中可能存在的 <> 尖括号进行转义,避免后面的正则替换发生错误
// 示例 <a name="<p>code</p>"></a> 替换为 <a name="&lt;p&gt;code&lt;/p&gt;"></a> // 示例 <a name="<p>code</p>"></a> 转化为 <a name="&lt;p&gt;code&lt;/p&gt;"></a>
html = html.replace(/(\w+)="([^"]*)"/g, (match, key, value) => { html = html.replace(/(\w+)="([^"]*)"/g, (match, key, value) => {
const escapedValue = value.replace(/</g, '&lt;').replace(/>/g, '&gt;'); const escapedValue = value.replace(/</g, '&lt;').replace(/>/g, '&gt;');
return `${key}="${escapedValue}"`; return `${key}="${escapedValue}"`;

File diff suppressed because one or more lines are too long

View File

@ -46,7 +46,6 @@
action += "&callback=" + settings.uploadCallbackURL + "&dialog_id=editormd-image-dialog-" + guid; action += "&callback=" + settings.uploadCallbackURL + "&dialog_id=editormd-image-dialog-" + guid;
} }
var imageFileName = classPrefix + "image-file";
// 将 action 置为 # 号。后面使用 ajax 重写图片上传请求 // 将 action 置为 # 号。后面使用 ajax 重写图片上传请求
var dialogContent = ( (settings.imageUpload) ? "<form action=\"#\" target=\"" + iframeName + "\" method=\"post\" enctype=\"multipart/form-data\" class=\"" + classPrefix + "form\">" : "<div class=\"" + classPrefix + "form\">" ) + var dialogContent = ( (settings.imageUpload) ? "<form action=\"#\" target=\"" + iframeName + "\" method=\"post\" enctype=\"multipart/form-data\" class=\"" + classPrefix + "form\">" : "<div class=\"" + classPrefix + "form\">" ) +
( (settings.imageUpload) ? "<iframe name=\"" + iframeName + "\" id=\"" + iframeName + "\" guid=\"" + guid + "\"></iframe>" : "" ) + ( (settings.imageUpload) ? "<iframe name=\"" + iframeName + "\" id=\"" + iframeName + "\" guid=\"" + guid + "\"></iframe>" : "" ) +
@ -61,8 +60,8 @@
"<label>" + imageLang.url + "</label>" + "<label>" + imageLang.url + "</label>" +
"<input type=\"text\" data-url />" + (function(){ "<input type=\"text\" data-url />" + (function(){
return (settings.imageUpload) ? "<div class=\"" + classPrefix + "file-input\">" + return (settings.imageUpload) ? "<div class=\"" + classPrefix + "file-input\">" +
// 这里增加 id = {imageFileName},方便后面拿到文件数据 // 这里增加 id 属性,方便后面拿到文件数据
"<input type=\"file\" name=\"" + imageFileName + "\" id=\"" + imageFileName + "\" accept=\"image/*\" />" + "<input type=\"file\" id=\"" + classPrefix + "image-file\" name=\"" + classPrefix + "image-file\" accept=\"image/*\" />" +
"<input type=\"submit\" value=\"" + imageLang.uploadButton + "\" />" + "<input type=\"submit\" value=\"" + imageLang.uploadButton + "\" />" +
"</div>" : ""; "</div>" : "";
})() + })() +
@ -145,16 +144,18 @@
// 给按钮绑定事件,支持手动更新 settings.imageUploadURL // 给按钮绑定事件,支持手动更新 settings.imageUploadURL
$('#lock-or-unlock').on('click', function() { $('#lock-or-unlock').on('click', function() {
let uploadURLInput = $('#upload-url-input') let uploadURLInput = $('#upload-url-input')
if (uploadURLInput.attr('disabled') == undefined) { if (uploadURLInput.attr('disabled') === undefined) {
// 如果输入框未被禁用,则禁用按钮 // 如果输入框未被禁用,则禁用按钮
$(this).text('Unlock'); $(this).text('Unlock');
uploadURLInput.attr('disabled', 'disabled'); uploadURLInput.attr('disabled', 'disabled');
// 用输入框内容更新 imageUploadURL // 用输入框内容更新 imageUploadURL
if (uploadURLInput.val().trim().length > 0) { if (uploadURLInput.val().trim().length > 0) {
settings.imageUploadURL = uploadURLInput.val().trim() settings.imageUploadURL = uploadURLInput.val().trim()
// 触发回调事件,支持向上暴露 // 更新 imageUploadURL 触发回调事件
if (typeof settings.imageUploadURLChange === 'function') {
settings.imageUploadURLChange(settings.imageUploadURL) settings.imageUploadURLChange(settings.imageUploadURL)
} }
}
} else { } else {
// 如果输入框为禁用状态,则解锁输入框 // 如果输入框为禁用状态,则解锁输入框
$(this).text('Update'); $(this).text('Update');
@ -163,7 +164,7 @@
} }
}) })
var fileInput = dialog.find("[name=\"" + imageFileName + "\"]"); var fileInput = dialog.find("[name=\"" + classPrefix + "image-file\"]");
fileInput.bind("change", function() { fileInput.bind("change", function() {
var fileName = fileInput.val(); var fileName = fileInput.val();
@ -193,7 +194,7 @@
loading(false); loading(false);
// 注释掉官方写法,这里存在 iframe 跨域问题 // 注释掉原来的写法,这里存在 iframe 跨域问题
// var body = (uploadIframe.contentWindow ? uploadIframe.contentWindow : uploadIframe.contentDocument).document.body; // var body = (uploadIframe.contentWindow ? uploadIframe.contentWindow : uploadIframe.contentDocument).document.body;
// var json = (body.innerText) ? body.innerText : ( (body.textContent) ? body.textContent : null); // var json = (body.innerText) ? body.innerText : ( (body.textContent) ? body.textContent : null);
@ -211,10 +212,13 @@
// } // }
// } // }
// 重写图片上传方法 // 重写图片上传方法,支持 token 鉴权
var formData = new FormData();
formData.append(imageFileName, $("#" + imageFileName)[0].files[0]);
var action = settings.imageUploadURL + (settings.imageUploadURL.indexOf("?") >= 0 ? "&" : "?") + "guid=" + guid; var action = settings.imageUploadURL + (settings.imageUploadURL.indexOf("?") >= 0 ? "&" : "?") + "guid=" + guid;
var formData = new FormData();
formData.append(classPrefix + "image-file", $("#" + classPrefix + "image-file")[0].files[0]);
if (settings.imageUploadToken !== undefined && settings.imageUploadToken.length > 0) {
formData.append("token", settings.imageUploadToken);
}
$.ajax({ $.ajax({
url: action, url: action,
type: "POST", type: "POST",

View File

@ -213,6 +213,18 @@
info : "fa-info-circle" info : "fa-info-circle"
}, },
toolbarIconTexts : {}, toolbarIconTexts : {},
toolbarNoFocus : [
"link",
"reference-link",
"image",
"code-block",
"preformatted-text",
"watch",
"preview",
"search",
"fullscreen",
"info",
],
lang : { lang : {
name : "zh-cn", name : "zh-cn",
@ -355,6 +367,49 @@
var classPrefix = this.classPrefix = editormd.classPrefix; var classPrefix = this.classPrefix = editormd.classPrefix;
var settings = this.settings = $.extend(true, editormd.defaults, options); var settings = this.settings = $.extend(true, editormd.defaults, options);
// 支持新增或覆盖原有 toolbar要求 settings.toolbarIcons 非自定义模式)
if (settings.appendToolbar && typeof settings.toolbarIcons === "string" && settings.toolbarIcons in editormd.toolbarModes) {
settings.appendToolbar.forEach(toolbar => {
// 如果没有 name 字段,则忽略该 toolbar
if (!('name' in toolbar)) {
return;
}
// 如果是 | 分隔符,则仅用于占位分隔
if (toolbar.name === "|") {
editormd.toolbarModes[settings.toolbarIcons].push("|");
return;
}
// 如果未定义该 toolbar则执行 append否则覆盖原有 toolbar
if (!editormd.toolbarModes[settings.toolbarIcons].includes(toolbar.name)) {
editormd.toolbarModes[settings.toolbarIcons].push(toolbar.name);
}
if ('icon' in toolbar) {
settings.toolbarIconsClass[toolbar.name] = toolbar.icon;
}
if ('title' in toolbar) {
settings.lang.toolbar[toolbar.name] = toolbar.title;
}
if ('handler' in toolbar) {
editormd.toolbarHandlers[toolbar.name] = toolbar.handler;
if ('shortcut' in toolbar) {
// 给新增的工具栏图标绑定快捷键
toolbar.shortcut.forEach(key => editormd.keyMaps[key] = toolbar.handler);
}
}
if ('nofocus' in toolbar && toolbar.nofocus === true) {
if (!settings.toolbarNoFocus.includes[toolbar.name]) {
settings.toolbarNoFocus.push(toolbar.name);
}
}
});
}
if (settings.shortcutFunction) {
for (const key in settings.shortcutFunction) {
editormd.keyMaps[key] = settings.shortcutFunction[key];
}
}
id = (typeof id === "object") ? settings.id : id; id = (typeof id === "object") ? settings.id : id;
var editor = this.editor = $("#" + id); var editor = this.editor = $("#" + id);
@ -1257,12 +1312,12 @@
if (typeof settings.toolbarHandlers[name] !== "undefined") if (typeof settings.toolbarHandlers[name] !== "undefined")
{ {
$.proxy(settings.toolbarHandlers[name], _this)(cm, icon, cursor, selection); $.proxy(settings.toolbarHandlers[name], _this)(cm, icon, cursor, selection);
} else {
alert("This feature is not yet implemented.")
} }
} }
if (name !== "link" && name !== "reference-link" && name !== "image" && name !== "code-block" && if (!settings.toolbarNoFocus.includes(name)) {
name !== "preformatted-text" && name !== "watch" && name !== "preview" && name !== "search" && name !== "fullscreen" && name !== "info")
{
cm.focus(); cm.focus();
} }
@ -3822,8 +3877,8 @@
if (typeof attrs !== "undefined") if (typeof attrs !== "undefined")
{ {
// 将 html 标签的 attr value 中存在的 <> 进行转义 // 将 html 标签的 attr value 中可能存在的 <> 尖括号进行转义,避免后面的正则替换发生错误
// 示例 <a name="<p>code</p>"></a> 替换为 <a name="&lt;p&gt;code&lt;/p&gt;"></a> // 示例 <a name="<p>code</p>"></a> 转化为 <a name="&lt;p&gt;code&lt;/p&gt;"></a>
html = html.replace(/(\w+)="([^"]*)"/g, (match, key, value) => { html = html.replace(/(\w+)="([^"]*)"/g, (match, key, value) => {
const escapedValue = value.replace(/</g, '&lt;').replace(/>/g, '&gt;'); const escapedValue = value.replace(/</g, '&lt;').replace(/>/g, '&gt;');
return `${key}="${escapedValue}"`; return `${key}="${escapedValue}"`;

View File

@ -40,7 +40,7 @@ export default {
required: false, required: false,
default: '720px' default: '720px'
}, },
// //
imageUpload: { imageUpload: {
type: Boolean, type: Boolean,
required: false, required: false,
@ -52,6 +52,12 @@ export default {
required: false, required: false,
default: '' default: ''
}, },
// token
imageUploadToken: {
type: String,
required: false,
default: ''
},
// image-dialog imageUploadURL // image-dialog imageUploadURL
imageUploadURLChange: { imageUploadURLChange: {
type: Function, type: Function,
@ -60,6 +66,24 @@ export default {
console.log("new imageUploadURL: " + newURL) console.log("new imageUploadURL: " + newURL)
} }
}, },
// :
// - name: . | toolbar name toolbar
// - icon: font-awesome icon. editormd.css
// - title: toolbar
// - handler: toolbar
// - shortcut: toolbar
// - nofoucs: false true toolbar cm.focus()
appendToolbar: {
type: Array,
required: false,
default: [],
},
//
shortcutFunction: {
type: Object,
required: false,
default: {},
},
// //
onload: { onload: {
type: Function, type: Function,
@ -67,13 +91,13 @@ export default {
default: () => { } default: () => { }
}, },
// //
onfullscreen: { onFullScreen: {
type: Function, type: Function,
required: false, required: false,
default: () => { } default: () => { }
}, },
// 退 // 退
onfullscreenExit: { onFullScreenExit: {
type: Function, type: Function,
required: false, required: false,
default: () => { } default: () => { }
@ -87,16 +111,13 @@ export default {
mounted() { mounted() {
if (this.autoInit) { if (this.autoInit) {
this.initEditor() this.initEditor()
} // markdown
// markdown,,
// setTimeout(() => { // setTimeout(() => {
// this.initEditor() // this.initEditor()
// }, 300) // }, 300)
}
}, },
methods: { methods: {
isEditorLoadingCompleted() {
return editorClient != defaultEditorValue && this.loadingCompleted
},
fetchScript(url) { fetchScript(url) {
return new Promise((resolve) => { return new Promise((resolve) => {
scriptjs(url, () => { scriptjs(url, () => {
@ -106,7 +127,7 @@ export default {
}, },
initEditor() { initEditor() {
(async () => { (async () => {
await this.fetchScript('/static/jQuery/jquery.min.js') await this.fetchScript('/static/jquery/jquery.min.js')
await this.fetchScript('/static/editor.md/editormd.js') await this.fetchScript('/static/editor.md/editormd.js')
// await this.fetchScript('/static/editor.md/editorrmd.amd.js') // await this.fetchScript('/static/editor.md/editorrmd.amd.js')
// JS // JS
@ -132,27 +153,31 @@ export default {
taskList: true, taskList: true,
// //
imageUpload: vm.imageUpload, // imageUploadURL imageUpload: vm.imageUpload, // imageUploadURL
imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp", "ico"], imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp", "ico"],
imageUploadURL: vm.imageUploadURL, // imageUploadURL: vm.imageUploadURL, //
imageUploadURLChange: function (newURL) { imageUploadToken: vm.imageUploadToken,
// image-dialog imageUploadURL imageUploadURLChange: vm.imageUploadURLChange,
vm.imageUploadURLChange(newURL)
},
// //
// theme: (localStorage.theme) ? localStorage.theme : "dark", // theme: (localStorage.theme) ? localStorage.theme : "dark",
// editorTheme: (localStorage.editorTheme) ? localStorage.editorThheme : "3024-night", // editorTheme: (localStorage.editorTheme) ? localStorage.editorThheme : "3024-night",
// previewTheme: (localStorage.previewTheme) ? localStorage.previewTheme : "default", // previewTheme: (localStorage.previewTheme) ? localStorage.previewTheme : "default",
//
appendToolbar: vm.appendToolbar,
//
shortcutFunction: vm.shortcutFunction,
// //
onfullscreen: function () { onfullscreen: function () {
vm.onfullscreen() vm.onFullScreen()
}, },
// 退 // 退
onfullscreenExit: function () { onfullscreenExit: function () {
vm.onfullscreenExit() vm.onFullScreenExit()
}, },
// //
@ -181,6 +206,9 @@ export default {
}) })
})() })()
}, },
isEditorLoadingCompleted() {
return editorClient != defaultEditorValue && this.loadingCompleted
},
// editor.md API // editor.md API
resetHeight(height) { resetHeight(height) {