mindoc/static/js/cherry_markdown.js

698 lines
25 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

$(function () {
window.editormdLocales = {
'zh-CN': {
placeholder: ' Markdown ',
contentUnsaved: '',
noDocNeedPublish: '',
loadDocFailed: '',
fetchDocFailed: '',
cannotAddToEmptyNode: '',
overrideModified: '',
confirm: '',
cancel: '',
contentsNameEmpty: '',
addDoc: '',
edit: '',
delete: '',
loadFailed: '',
tplNameEmpty: '',
tplContentEmpty: '',
saveSucc: '',
serverExcept: '',
paramName: '',
paramType: '',
example: '',
remark: '',
},
'en': {
placeholder: 'This editor supports Markdown editing, writing on the left and previewing on the right.',
contentUnsaved: 'The edited content is not saved, need to save it?',
noDocNeedPublish: 'No Document need to be publish',
loadDocFailed: 'Load Document failed',
fetchDocFailed: 'Fetch Document info failed',
cannotAddToEmptyNode: 'Cannot add content to empty node',
overrideModified: 'The document has been modified by someone else, are you sure to overwrite the document?',
confirm: 'Confirm',
cancel: 'Cancel',
contentsNameEmpty: 'Document Name cannot be empty',
addDoc: 'Add Document',
edit: 'Edit',
delete: 'Delete',
loadFailed: 'Failed to load, please try again',
tplNameEmpty: 'Template name cannot be empty',
tplContentEmpty: 'Template content cannot be empty',
saveSucc: 'Save success',
serverExcept: 'Server Exception',
paramName: 'Parameter',
paramType: 'Type',
example: 'Example',
remark: 'Remark',
}
};
var CustomHookA = Cherry.createSyntaxHook('codeBlock', Cherry.constants.HOOKS_TYPE_LIST.PAR, {
makeHtml(str) {
console.warn('custom hook', 'hello');
return str;
},
rule(str) {
const regex = {
begin: '',
content: '',
end: '',
};
regex.reg = new RegExp(regex.begin + regex.content + regex.end, 'g');
return regex;
},
});
/**
* 自定义一个自定义菜单
* 点第一次时,把选中的文字变成同时加粗和斜体
* 保持光标选区不变,点第二次时,把加粗斜体的文字变成普通文本
*/
var customMenuA = Cherry.createMenuHook('', {
iconName: 'font',
onClick: function (selection) {
// 获取用户选中的文字调用getSelection方法后如果用户没有选中任何文字会尝试获取光标所在位置的单词或句子
let $selection = this.getSelection(selection) || '';
// 如果是单选,并且选中内容的开始结束内没有加粗语法,则扩大选中范围
if (!this.isSelections && !/^\s*(\*\*\*)[\s\S]+(\1)/.test($selection)) {
this.getMoreSelection('***', '***', () => {
const newSelection = this.editor.editor.getSelection();
const isBoldItalic = /^\s*(\*\*\*)[\s\S]+(\1)/.test(newSelection);
if (isBoldItalic) {
$selection = newSelection;
}
return isBoldItalic;
});
}
// 如果选中的文本中已经有加粗语法了,则去掉加粗语法
if (/^\s*(\*\*\*)[\s\S]+(\1)/.test($selection)) {
return $selection.replace(/(^)(\s*)(\*\*\*)([^\n]+)(\3)(\s*)($)/gm, '$1$4$7');
}
/**
* 注册缩小选区的规则
* 注册后,插入“***TEXT***”,选中状态会变成“***【TEXT】***”
* 如果不注册,插入后效果为:“【***TEXT***】”
*/
this.registerAfterClickCb(() => {
this.setLessSelection('***', '***');
});
return $selection.replace(/(^)([^\n]+)($)/gm, '$1***$2***$3');
}
});
/**
* 定义一个空壳用于自行规划cherry已有工具栏的层级结构
*/
var customMenuB = Cherry.createMenuHook('', {
iconName: '',
onClick: releaseDocument,
});
var customMenuC = Cherry.createMenuHook("返回", {
iconName: '',
onClick: backWard,
})
var customMenuD = Cherry.createMenuHook('', {
iconName: '',
onClick: saveDocument,
});
var customMenuE = Cherry.createMenuHook('', {
iconName: '',
onClick: siderChange,
});
var customMenuF = Cherry.createMenuHook('', {
iconName: '',
onClick: showHistory,
});
var basicConfig = {
id: 'manualEditorContainer',
externals: {
echarts: window.echarts,
katex: window.katex,
MathJax: window.MathJax,
},
isPreviewOnly: false,
engine: {
global: {
urlProcessor(url, srcType) {
//console.log(`url-processor`, url, srcType);
return url;
},
},
syntax: {
codeBlock: {
theme: 'twilight',
},
table: {
enableChart: false,
// chartEngine: Engine Class
},
fontEmphasis: {
allowWhitespace: false, // 是否允许首尾空格
},
strikethrough: {
needWhitespace: false, // 是否必须有前后空格
},
mathBlock: {
engine: 'MathJax', // katex或MathJax
src: 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js', // 如果使用MathJax plugins则需要使用该url通过script标签引入
},
inlineMath: {
engine: 'MathJax', // katex或MathJax
},
emoji: {
useUnicode: false,
customResourceURL: 'https://github.githubassets.com/images/icons/emoji/unicode/${code}.png?v8',
upperCase: true,
},
// toc: {
// tocStyle: 'nested'
// }
// 'header': {
// strict: false
// }
},
customSyntax: {
// SyntaxHookClass
CustomHook: {
syntaxClass: CustomHookA,
force: false,
after: 'br',
},
},
},
toolbars: {
toolbar: [
'customMenuCName',
'undo',
'redo',
'bold',
'italic',
{
strikethrough: ['strikethrough', 'underline', 'sub', 'sup', 'ruby', 'customMenuAName'],
},
'size',
'|',
'color',
'header',
'|',
'drawIo',
'|',
'ol',
'ul',
'checklist',
'panel',
'detail',
'|',
'formula',
{
insert: ['image', 'audio', 'video', 'link', 'hr', 'br', 'code', 'formula', 'toc', 'table', 'pdf', 'word', 'ruby'],
},
'graph',
'togglePreview',
'settings',
'switchModel',
'codeTheme',
'export',
'customMenuDName',
'customMenuBName',
'customMenuEName',
'customMenuFName',
'theme'
],
bubble: ['bold', 'italic', 'underline', 'strikethrough', 'sub', 'sup', 'quote', 'ruby', '|', 'size', 'color'], // array or false
sidebar: ['mobilePreview', 'copy', 'theme'],
customMenu: {
customMenuAName: customMenuA,
customMenuBName: customMenuB,
customMenuCName: customMenuC,
customMenuDName: customMenuD,
customMenuEName: customMenuE,
customMenuFName: customMenuF,
},
},
drawioIframeUrl: '/static/cherry/drawio_demo.html',
editor: {
defaultModel: 'edit&preview',
height: "100%",
},
previewer: {
// 自定义markdown预览区域class
// className: 'markdown'
},
keydown: [],
//extensions: [],
//callback: {
//changeString2Pinyin: pinyin,
//}
};
fetch('').then((response) => response.text()).then((value) => {
//var markdownarea = document.getElementById("markdown_area").value
var config = Object.assign({}, basicConfig);// { value: markdownarea });// { value: value });不显示获取的初始化值
window.editor = new Cherry(config);
openLastSelectedNode();
uploadImage("manualEditorContainer", function ($state, $res) {
console.log("注册上传图片")
if ($state === "before") {
return layer.load(1, {
shade: [0.1, '#fff'] // 0.1 透明度的白色背景
});
} else if ($state === "success") {
if ($res.errcode === 0) {
var value = '![](' + $res.url + ')';
window.editor.insertValue(value);
}
}
});
});
function insertToMarkdown(body) {
window.isLoad = true;
window.editor.insertValue(body);
window.editor.setCursor({ line: 0, ch: 0 });
resetEditorChanged(true);
}
function insertAndClearToMarkdown(body) {
window.isLoad = true;
window.editor.clear();
window.editor.insertValue(body);
window.editor.setCursor({ line: 0, ch: 0 });
resetEditorChanged(true);
}
/***
* 加载指定的文档到编辑器中
* @param $node
*/
window.loadDocument = function ($node) {
var index = layer.load(1, {
shade: [0.1, '#fff'] // 0.1 透明度的白色背景
});
$.get(window.editURL + $node.node.id).done(function (res) {
layer.close(index);
if (res.errcode === 0) {
window.isLoad = true;
try {
window.editor.setTheme(res.data.markdown_theme);
window.editor.setMarkdown(res.data.markdown);
} catch (e) {
console.log(e);
}
var node = { "id": res.data.doc_id, 'parent': res.data.parent_id === 0 ? '#' : res.data.parent_id, "text": res.data.doc_name, "identify": res.data.identify, "version": res.data.version };
pushDocumentCategory(node);
window.selectNode = node;
pushVueLists(res.data.attach);
setLastSelectNode($node);
} else {
layer.msg(editormdLocales[lang].loadDocFailed);
}
}).fail(function () {
layer.close(index);
layer.msg(editormdLocales[lang].loadDocFailed);
});
};
/**
* 保存文档到服务器
* @param $is_cover 是否强制覆盖
*/
function saveDocument($is_cover, callback) {
var index = null;
var node = window.selectNode;
var content = window.editor.getMarkdown();
var html = window.editor.getHtml(true);
var markdownTheme = window.editor.getTheme();
var version = "";
if (!node) {
layer.msg(editormdLocales[lang].fetchDocFailed);
return;
}
if (node.a_attr && node.a_attr.disabled) {
layer.msg(editormdLocales[lang].cannotAddToEmptyNode);
return;
}
var doc_id = parseInt(node.id);
for (var i in window.documentCategory) {
var item = window.documentCategory[i];
if (item.id === doc_id) {
version = item.version;
break;
}
}
$.ajax({
beforeSend: function () {
index = layer.load(1, { shade: [0.1, '#fff'] });
window.saveing = true;
},
url: window.editURL,
data: { "identify": window.book.identify, "doc_id": doc_id, "markdown": content, "html": html, "markdown_theme": markdownTheme, "cover": $is_cover ? "yes" : "no", "version": version },
type: "post",
timeout: 30000,
dataType: "json",
success: function (res) {
if (res.errcode === 0) {
resetEditorChanged(false);
for (var i in window.documentCategory) {
var item = window.documentCategory[i];
if (item.id === doc_id) {
window.documentCategory[i].version = res.data.version;
break;
}
}
$.each(window.documentCategory, function (i, item) {
var $item = window.documentCategory[i];
if (item.id === doc_id) {
window.documentCategory[i].version = res.data.version;
}
});
if (typeof callback === "function") {
callback();
}
} else if (res.errcode === 6005) {
var confirmIndex = layer.confirm(editormdLocales[lang].overrideModified, {
btn: [editormdLocales[lang].confirm, editormdLocales[lang].cancel] // 按钮
}, function () {
layer.close(confirmIndex);
saveDocument(true, callback);
});
} else {
layer.msg(res.message);
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
layer.msg(window.editormdLocales[window.lang].serverExcept + errorThrown);
},
complete: function () {
layer.close(index);
window.saveing = false;
}
});
}
/**
* 设置编辑器变更状态
* @param $is_change
*/
function resetEditorChanged($is_change) {
if ($is_change && !window.isLoad) {
$("#markdown-save").removeClass('disabled').addClass('change');
} else {
$("#markdown-save").removeClass('change').addClass('disabled');
}
window.isLoad = false;
}
/**
* 返回上一个页面
*/
function backWard() {
history.back();
}
/**
* 发布文档
*/
function releaseDocument() {
if (Object.prototype.toString.call(window.documentCategory) === '[object Array]' && window.documentCategory.length > 0) {
if ($("#markdown-save").hasClass('change')) {
var confirm_result = confirm(editormdLocales[lang].contentUnsaved);
if (confirm_result) {
saveDocument(false, releaseBook);
return;
}
}
releaseBook();
return
}
layer.msg(editormdLocales[lang].noDocNeedPublish)
}
/**
* 显示/隐藏边栏
*/
function siderChange() {
$("#manualCategory").toggle(0, "swing", function () {
var $then = $("#manualEditorContainer");
var left = parseInt($then.css("left"));
if (left > 0) {
window.editorContainerLeft = left;
$then.css("left", "0");
} else {
$then.css("left", window.editorContainerLeft + "px");
}
});
}
/**
* 显示文档历史
*/
function showHistory() {
window.documentHistory();
}
/**
* 添加文档
*/
$("#addDocumentForm").ajaxForm({
beforeSubmit: function () {
var doc_name = $.trim($("#documentName").val());
if (doc_name === "") {
return showError(editormdLocales[lang].contentsNameEmpty, "#add-error-message")
}
$("#btnSaveDocument").button("loading");
return true;
},
success: function (res) {
if (res.errcode === 0) {
var data = {
"id": res.data.doc_id,
'parent': res.data.parent_id === 0 ? '#' : res.data.parent_id,
"text": res.data.doc_name,
"identify": res.data.identify,
"version": res.data.version,
state: { opened: res.data.is_open == 1 },
a_attr: { is_open: res.data.is_open == 1 }
};
var node = window.treeCatalog.get_node(data.id);
if (node) {
window.treeCatalog.rename_node({ "id": data.id }, data.text);
$("#sidebar").jstree(true).get_node(data.id).a_attr.is_open = data.state.opened;
} else {
window.treeCatalog.create_node(data.parent, data);
window.treeCatalog.deselect_all();
window.treeCatalog.select_node(data);
}
pushDocumentCategory(data);
$("#markdown-save").removeClass('change').addClass('disabled');
$("#addDocumentModal").modal('hide');
} else {
showError(res.message, "#add-error-message");
}
$("#btnSaveDocument").button("reset");
}
});
/**
* 文档目录树
*/
$("#sidebar").jstree({
'plugins': ["wholerow", "types", 'dnd', 'contextmenu'],
"types": {
"default": {
"icon": false // 删除默认图标
}
},
'core': {
'check_callback': true,
"multiple": false,
'animation': 0,
"data": window.documentCategory
},
"contextmenu": {
show_at_node: false,
select_node: false,
"items": {
"添加文档": {
"separator_before": false,
"separator_after": true,
"_disabled": false,
"label": window.editormdLocales[window.lang].addDoc,//"添加文档",
"icon": "fa fa-plus",
"action": function (data) {
var inst = $.jstree.reference(data.reference),
node = inst.get_node(data.reference);
openCreateCatalogDialog(node);
}
},
"编辑": {
"separator_before": false,
"separator_after": true,
"_disabled": false,
"label": window.editormdLocales[window.lang].edit,
"icon": "fa fa-edit",
"action": function (data) {
var inst = $.jstree.reference(data.reference);
var node = inst.get_node(data.reference);
openEditCatalogDialog(node);
}
},
"删除": {
"separator_before": false,
"separator_after": true,
"_disabled": false,
"label": window.editormdLocales[window.lang].delete,
"icon": "fa fa-trash-o",
"action": function (data) {
var inst = $.jstree.reference(data.reference);
var node = inst.get_node(data.reference);
openDeleteDocumentDialog(node);
}
}
}
}
}).on("ready.jstree", function () {
window.treeCatalog = $("#sidebar").jstree(true);
//如果没有选中节点则选中默认节点
// openLastSelectedNode();
}).on('select_node.jstree', function (node, selected) {
if ($("#markdown-save").hasClass('change')) {
if (confirm(window.editormdLocales[window.lang].contentUnsaved)) {
saveDocument(false, function () {
loadDocument(selected);
});
return true;
}
}
//如果是空目录则直接出发展开下一级功能
if (selected.node.a_attr && selected.node.a_attr.disabled) {
selected.instance.toggle_node(selected.node);
return false
}
loadDocument(selected);
}).on("move_node.jstree", jstree_save).on("delete_node.jstree", function ($node, $parent) {
openLastSelectedNode();
});
/**
* 打开文档模板
*/
$("#documentTemplateModal").on("click", ".section>a[data-type]", function () {
var $this = $(this).attr("data-type");
if ($this === "customs") {
$("#displayCustomsTemplateModal").modal("show");
return;
}
var body = $("#template-" + $this).html();
if (body) {
window.isLoad = true;
window.editor.clear();
window.editor.insertValue(body);
window.editor.setCursor({ line: 0, ch: 0 });
resetEditorChanged(true);
}
$("#documentTemplateModal").modal('hide');
});
});
function uploadImage($id, $callback) {
locales = {
'zh-CN': {
unsupportType: '',
uploadFailed: ''
},
'en': {
unsupportType: 'Unsupport image type',
uploadFailed: 'Upload image failed'
}
}
/** 粘贴上传图片 **/
document.getElementById($id).addEventListener('paste', function (e) {
if (e.clipboardData && e.clipboardData.items) {
var clipboard = e.clipboardData;
for (var i = 0, len = clipboard.items.length; i < len; i++) {
if (clipboard.items[i].kind === 'file' || clipboard.items[i].type.indexOf('image') > -1) {
var imageFile = clipboard.items[i].getAsFile();
var fileName = String((new Date()).valueOf());
switch (imageFile.type) {
case "image/png" :
fileName += ".png";
break;
case "image/jpg" :
fileName += ".jpg";
break;
case "image/jpeg" :
fileName += ".jpeg";
break;
case "image/gif" :
fileName += ".gif";
break;
default :
layer.msg(locales[lang].unsupportType);
return;
}
var form = new FormData();
form.append('editormd-image-file', imageFile, fileName);
var layerIndex = 0;
$.ajax({
url: window.imageUploadURL,
type: "POST",
dataType: "json",
data: form,
processData: false,
contentType: false,
beforeSend: function () {
layerIndex = $callback('before');
},
error: function () {
layer.close(layerIndex);
$callback('error');
layer.msg(locales[lang].uploadFailed);
},
success: function (data) {
layer.close(layerIndex);
$callback('success', data);
if (data.errcode !== 0) {
layer.msg(data.message);
}
}
});
e.preventDefault();
}
}
}
});
}