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"
},
toolbarIconTexts : {},
toolbarNoFocus : [
"link",
"reference-link",
"image",
"code-block",
"preformatted-text",
"watch",
"preview",
"search",
"fullscreen",
"info",
],
lang : {
name : "zh-cn",
@ -436,7 +448,50 @@
var _this = this;
var classPrefix = this.classPrefix = editormd.classPrefix;
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;
var editor = this.editor = $("#" + id);
@ -1339,12 +1394,12 @@
if (typeof settings.toolbarHandlers[name] !== "undefined")
{
$.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" &&
name !== "preformatted-text" && name !== "watch" && name !== "preview" && name !== "search" && name !== "fullscreen" && name !== "info")
{
if (!settings.toolbarNoFocus.includes(name)) {
cm.focus();
}
@ -3901,8 +3956,8 @@
if (typeof attrs !== "undefined")
{
// 将 html 标签的 attr value 中存在的 <> 进行转义
// 示例 <a name="<p>code</p>"></a> 替换为 <a name="&lt;p&gt;code&lt;/p&gt;"></a>
// 将 html 标签的 attr value 中可能存在的 <> 尖括号进行转义,避免后面的正则替换发生错误
// 示例 <a name="<p>code</p>"></a> 转化为 <a name="&lt;p&gt;code&lt;/p&gt;"></a>
html = html.replace(/(\w+)="([^"]*)"/g, (match, key, value) => {
const escapedValue = value.replace(/</g, '&lt;').replace(/>/g, '&gt;');
return `${key}="${escapedValue}"`;

File diff suppressed because one or more lines are too long

View File

@ -225,6 +225,18 @@
info : "fa-info-circle"
},
toolbarIconTexts : {},
toolbarNoFocus : [
"link",
"reference-link",
"image",
"code-block",
"preformatted-text",
"watch",
"preview",
"search",
"fullscreen",
"info",
],
lang : {
name : "zh-cn",
@ -366,7 +378,50 @@
var _this = this;
var classPrefix = this.classPrefix = editormd.classPrefix;
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;
var editor = this.editor = $("#" + id);
@ -1269,12 +1324,12 @@
if (typeof settings.toolbarHandlers[name] !== "undefined")
{
$.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" &&
name !== "preformatted-text" && name !== "watch" && name !== "preview" && name !== "search" && name !== "fullscreen" && name !== "info")
{
if (!settings.toolbarNoFocus.includes(name)) {
cm.focus();
}
@ -3832,8 +3887,8 @@
if (typeof attrs !== "undefined")
{
// 将 html 标签的 attr value 中存在的 <> 进行转义
// 示例 <a name="<p>code</p>"></a> 替换为 <a name="&lt;p&gt;code&lt;/p&gt;"></a>
// 将 html 标签的 attr value 中可能存在的 <> 尖括号进行转义,避免后面的正则替换发生错误
// 示例 <a name="<p>code</p>"></a> 转化为 <a name="&lt;p&gt;code&lt;/p&gt;"></a>
html = html.replace(/(\w+)="([^"]*)"/g, (match, key, value) => {
const escapedValue = value.replace(/</g, '&lt;').replace(/>/g, '&gt;');
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;
}
var imageFileName = classPrefix + "image-file";
// 将 action 置为 # 号。后面使用 ajax 重写图片上传请求
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>" : "" ) +
@ -61,8 +60,8 @@
"<label>" + imageLang.url + "</label>" +
"<input type=\"text\" data-url />" + (function(){
return (settings.imageUpload) ? "<div class=\"" + classPrefix + "file-input\">" +
// 这里增加 id = {imageFileName},方便后面拿到文件数据
"<input type=\"file\" name=\"" + imageFileName + "\" id=\"" + imageFileName + "\" accept=\"image/*\" />" +
// 这里增加 id 属性,方便后面拿到文件数据
"<input type=\"file\" id=\"" + classPrefix + "image-file\" name=\"" + classPrefix + "image-file\" accept=\"image/*\" />" +
"<input type=\"submit\" value=\"" + imageLang.uploadButton + "\" />" +
"</div>" : "";
})() +
@ -145,15 +144,17 @@
// 给按钮绑定事件,支持手动更新 settings.imageUploadURL
$('#lock-or-unlock').on('click', function() {
let uploadURLInput = $('#upload-url-input')
if (uploadURLInput.attr('disabled') == undefined) {
if (uploadURLInput.attr('disabled') === undefined) {
// 如果输入框未被禁用,则禁用按钮
$(this).text('Unlock');
uploadURLInput.attr('disabled', 'disabled');
// 用输入框内容更新 imageUploadURL
if (uploadURLInput.val().trim().length > 0) {
settings.imageUploadURL = uploadURLInput.val().trim()
// 触发回调事件,支持向上暴露
settings.imageUploadURLChange(settings.imageUploadURL)
// 更新 imageUploadURL 触发回调事件
if (typeof settings.imageUploadURLChange === 'function') {
settings.imageUploadURLChange(settings.imageUploadURL)
}
}
} else {
// 如果输入框为禁用状态,则解锁输入框
@ -163,7 +164,7 @@
}
})
var fileInput = dialog.find("[name=\"" + imageFileName + "\"]");
var fileInput = dialog.find("[name=\"" + classPrefix + "image-file\"]");
fileInput.bind("change", function() {
var fileName = fileInput.val();
@ -193,7 +194,7 @@
loading(false);
// 注释掉官方写法,这里存在 iframe 跨域问题
// 注释掉原来的写法,这里存在 iframe 跨域问题
// var body = (uploadIframe.contentWindow ? uploadIframe.contentWindow : uploadIframe.contentDocument).document.body;
// var json = (body.innerText) ? body.innerText : ( (body.textContent) ? body.textContent : null);
@ -211,10 +212,13 @@
// }
// }
// 重写图片上传方法
var formData = new FormData();
formData.append(imageFileName, $("#" + imageFileName)[0].files[0]);
// 重写图片上传方法,支持 token 鉴权
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({
url: action,
type: "POST",

View File

@ -213,6 +213,18 @@
info : "fa-info-circle"
},
toolbarIconTexts : {},
toolbarNoFocus : [
"link",
"reference-link",
"image",
"code-block",
"preformatted-text",
"watch",
"preview",
"search",
"fullscreen",
"info",
],
lang : {
name : "zh-cn",
@ -354,7 +366,50 @@
var _this = this;
var classPrefix = this.classPrefix = editormd.classPrefix;
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;
var editor = this.editor = $("#" + id);
@ -1257,12 +1312,12 @@
if (typeof settings.toolbarHandlers[name] !== "undefined")
{
$.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" &&
name !== "preformatted-text" && name !== "watch" && name !== "preview" && name !== "search" && name !== "fullscreen" && name !== "info")
{
if (!settings.toolbarNoFocus.includes(name)) {
cm.focus();
}
@ -3822,8 +3877,8 @@
if (typeof attrs !== "undefined")
{
// 将 html 标签的 attr value 中存在的 <> 进行转义
// 示例 <a name="<p>code</p>"></a> 替换为 <a name="&lt;p&gt;code&lt;/p&gt;"></a>
// 将 html 标签的 attr value 中可能存在的 <> 尖括号进行转义,避免后面的正则替换发生错误
// 示例 <a name="<p>code</p>"></a> 转化为 <a name="&lt;p&gt;code&lt;/p&gt;"></a>
html = html.replace(/(\w+)="([^"]*)"/g, (match, key, value) => {
const escapedValue = value.replace(/</g, '&lt;').replace(/>/g, '&gt;');
return `${key}="${escapedValue}"`;

View File

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