From ebb5bdb14b320c2a7b1624120208d30ca8cb97f0 Mon Sep 17 00:00:00 2001 From: Seanly Liu Date: Sat, 1 Jul 2023 23:20:10 +0800 Subject: [PATCH] implement drawio support Signed-off-by: Ys Liu --- conf/lang/en-us.ini | 1 + conf/lang/zh-cn.ini | 1 + static/editor.md/editormd.js | 6 +- static/js/markdown.js | 345 ++++++++++++++++++---- views/document/default_read.tpl | 9 + views/document/markdown_edit_template.tpl | 3 +- 6 files changed, 307 insertions(+), 58 deletions(-) diff --git a/conf/lang/en-us.ini b/conf/lang/en-us.ini index 1484ef03..c6a7b715 100644 --- a/conf/lang/en-us.ini +++ b/conf/lang/en-us.ini @@ -367,6 +367,7 @@ gfm_task = GFM task attachment = attachment json_to_table = Json converted to table template = template +draw = draw close_preview = disable preview modify_history = modify history sidebar = sidebar diff --git a/conf/lang/zh-cn.ini b/conf/lang/zh-cn.ini index d1a0b728..f08944bf 100644 --- a/conf/lang/zh-cn.ini +++ b/conf/lang/zh-cn.ini @@ -367,6 +367,7 @@ gfm_task = GFM 任务列表 attachment = 附件 json_to_table = Json转换为表格 template = 模板 +draw = 画图 close_preview = 关闭实时预览 modify_history = 修改历史 sidebar = 边栏 diff --git a/static/editor.md/editormd.js b/static/editor.md/editormd.js index 53480349..70013ce0 100755 --- a/static/editor.md/editormd.js +++ b/static/editor.md/editormd.js @@ -3950,9 +3950,12 @@ } return ""+code+""; } + if (lang === "drawio") { + var svgCode = decodeURIComponent(escape(window.atob(code))) + return "
" + svgCode + "
" + } else { - return marked.Renderer.prototype.code.apply(this, arguments); } }; @@ -4029,7 +4032,6 @@ html += "
  • " + text + "
  • "; lastLevel = level; } - console.log(html); var tocContainer = container.find(".markdown-toc"); diff --git a/static/js/markdown.js b/static/js/markdown.js index b906abfe..ef477258 100644 --- a/static/js/markdown.js +++ b/static/js/markdown.js @@ -3,6 +3,150 @@ $(function () { js : window.katex.js, css : window.katex.css }; + var drawio = new Object() + + drawio.processMarkers = function (from, to) { + var _this = this + var found = null + var foundStart = 0 + var cm = window.editor.cm; + cm.doc.getAllMarks().forEach(mk => { + if (mk.__kind) { + mk.clear() + } + }) + cm.eachLine(from, to, function (ln) { + const line = ln.lineNo() + + if (ln.text.startsWith('```drawio')) { + found = 'drawio' + foundStart = line + } else if (ln.text === '```' && found) { + switch (found) { + // -> DRAWIO + case 'drawio': { + if (line - foundStart !== 2) { + return + } + _this.addMarker({ + kind: 'drawio', + from: { line: foundStart, ch: 3 }, + to: { line: foundStart, ch: 10 }, + text: 'drawio', + action: (function (start, end) { + return function (ev) { + cm.doc.setSelection({ line: start, ch: 0 }, { line: end, ch: 3 }) + try { + // save state data + const raw = cm.doc.getLine(end - 1) + window.sessionStorage.setItem("drawio", raw); + _this.show() + } catch (err) { + console.log(err) + } + } + })(foundStart, line) + }) + + if (ln.height > 0) { + cm.foldCode(foundStart) + } + break; + } + } + found = null + } + }) + } + + drawio.addMarker = function ({ kind, from, to, text, action }) { + + const markerElm = document.createElement('span') + markerElm.appendChild(document.createTextNode(text)) + markerElm.className = 'CodeMirror-buttonmarker' + markerElm.addEventListener('click', action) + + var cm = window.editor.cm; + cm.markText(from, to, { replacedWith: markerElm, __kind: kind }) + } + + drawio.show = function () { + + const drawUrl = 'https://embed.diagrams.net/?embed=1&libraries=1&proto=json&spin=1&saveAndExit=1&noSaveBtn=1&noExitBtn=0'; + this.div = document.createElement('div'); + this.div.id = 'diagram'; + this.gXml = ''; + this.div.innerHTML = ''; + this.iframe = document.createElement('iframe'); + this.iframe.setAttribute('frameborder', '0'); + this.iframe.style.zIndex = 9999; + this.iframe.style.width = "100%"; + this.iframe.style.height = "100%"; + this.iframe.style.position = "absolute"; + this.iframe.style.top = window.scrollY + "px"; + binded = this.postMessage.bind(this); + window.addEventListener("message", binded, false); + this.iframe.setAttribute('src', drawUrl); + document.body.appendChild(this.iframe); + } + + drawio.postMessage = function (evt) { + if (evt.data.length < 1) return + var msg = JSON.parse(evt.data) + var svg = ''; + + switch (msg.event) { + case "configure": + this.iframe.contentWindow.postMessage( + JSON.stringify({ + action: "configure", + config: { + defaultFonts: ["Humor Sans", "Helvetica", "Times New Roman"], + }, + }), + "*" + ); + break; + case "init": + code = window.sessionStorage.getItem("drawio") + svg = decodeURIComponent(escape(window.atob(code))) + this.iframe.contentWindow.postMessage( + JSON.stringify({ action: "load", autosave: 1, xml: svg }), + "*" + ); + break; + case "autosave": + window.sessionStorage.setItem("drawio", svg); + break; + case "save": + this.iframe.contentWindow.postMessage( + JSON.stringify({ + action: "export", + format: "xmlsvg", + xml: msg.xml, + spin: "Updating page", + }), + "*" + ); + break; + case "export": + svgData = msg.data.substring(msg.data.indexOf(',') + 1); + // clean event bind + window.removeEventListener("message", this.binded); + document.body.removeChild(this.iframe); + + // write back svg data + var cm = window.editor.cm; + cm.doc.replaceSelection('```drawio\n' + svgData + '\n```', 'start') + // clean state data + window.sessionStorage.setItem("drawio", ''); + break; + case "exit": + window.removeEventListener("message", this.binded); + document.body.removeChild(this.iframe); + break; + } + } window.editormdLocales = { 'zh-CN': { @@ -81,8 +225,10 @@ $(function () { highlightStyle: window.highlightStyle ? window.highlightStyle : "github", tex:true, saveHTMLToTextarea: true, + codeFold: true, onload: function() { + this.registerHelper() this.hideToolbar(); var keyMap = { "Ctrl-S": function(cm) { @@ -111,15 +257,89 @@ $(function () { } } }); - + window.isLoad = true; this.tableEditor = TableEditor.initTableEditor(this.cm) }, onchange: function () { + /** + * 实现画图的事件注入 + * + * 1. 分析文本,添加点击编辑事件,processMarkers + * 2. 获取内容,存储状态数据 + * 3. 打开编辑画面 + * 4. 推出触发变更事件,并回写数据 + */ + + var cm = window.editor.cm; + drawio.processMarkers(cm.firstLine(), cm.lastLine() + 1) + resetEditorChanged(true); } }); + editormd.fn.registerHelper = function () { + + const maxDepth = 100 + const codeBlockStartMatch = /^`{3}[a-zA-Z0-9]+$/ + const codeBlockEndMatch = /^`{3}$/ + + + editormd.$CodeMirror.registerHelper('fold', 'markdown', function (cm, start) { + const firstLine = cm.getLine(start.line) + const lastLineNo = cm.lastLine() + let end + + function isHeader(lineNo) { + const tokentype = cm.getTokenTypeAt(CodeMirror.Pos(lineNo, 0)) + return tokentype && /\bheader\b/.test(tokentype) + } + + function headerLevel(lineNo, line, nextLine) { + let match = line && line.match(/^#+/) + if (match && isHeader(lineNo)) return match[0].length + match = nextLine && nextLine.match(/^[=-]+\s*$/) + if (match && isHeader(lineNo + 1)) return nextLine[0] === '=' ? 1 : 2 + return maxDepth + } + + // -> CODE BLOCK + + if (codeBlockStartMatch.test(cm.getLine(start.line))) { + end = start.line + let nextNextLine = cm.getLine(end + 1) + while (end < lastLineNo) { + if (codeBlockEndMatch.test(nextNextLine)) { + end++ + break + } + end++ + nextNextLine = cm.getLine(end + 1) + } + } else { + // -> HEADER + + let nextLine = cm.getLine(start.line + 1) + const level = headerLevel(start.line, firstLine, nextLine) + if (level === maxDepth) return undefined + + end = start.line + let nextNextLine = cm.getLine(end + 2) + while (end < lastLineNo) { + if (headerLevel(end + 1, nextLine, nextNextLine) <= level) break + ++end + nextLine = nextNextLine + nextNextLine = cm.getLine(end + 2) + } + } + + return { + from: CodeMirror.Pos(start.line, firstLine.length), + to: CodeMirror.Pos(end, cm.getLine(end).length) + } + }) + } + function insertToMarkdown(body) { window.isLoad = true; window.editor.insertValue(body); @@ -138,20 +358,20 @@ $(function () { * 实现标题栏操作 */ $("#editormd-tools").on("click", "a[class!='disabled']", function () { - var name = $(this).find("i").attr("name"); - if (name === "attachment") { - $("#uploadAttachModal").modal("show"); - } else if (name === "history") { - window.documentHistory(); - } else if (name === "save") { + var name = $(this).find("i").attr("name"); + if (name === "attachment") { + $("#uploadAttachModal").modal("show"); + } else if (name === "history") { + window.documentHistory(); + } else if (name === "save") { saveDocument(false); - } else if (name === "template") { - $("#documentTemplateModal").modal("show"); + } else if (name === "template") { + $("#documentTemplateModal").modal("show"); } else if(name === "save-template"){ - $("#saveTemplateModal").modal("show"); + $("#saveTemplateModal").modal("show"); } else if(name === 'json'){ - $("#convertJsonToTableModal").modal("show"); - } else if (name === "sidebar") { + $("#convertJsonToTableModal").modal("show"); + } else if (name === "sidebar") { $("#manualCategory").toggle(0, "swing", function () { var $then = $("#manualEditorContainer"); var left = parseInt($then.css("left")); @@ -163,7 +383,7 @@ $(function () { } window.editor.resize(); }); - } else if (name === "release") { + } else if (name === "release") { 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); @@ -177,31 +397,46 @@ $(function () { } else { layer.msg(editormdLocales[lang].noDocNeedPublish) } - } else if (name === "tasks") { - // 插入 GFM 任务列表 - var cm = window.editor.cm; - var selection = cm.getSelection(); + } else if (name === "tasks") { + // 插入 GFM 任务列表 + var cm = window.editor.cm; + var selection = cm.getSelection(); var cursor = cm.getCursor(); - if (selection === "") { - cm.setCursor(cursor.line, 0); - cm.replaceSelection("- [x] " + selection); - cm.setCursor(cursor.line, cursor.ch + 6); - } else { - var selectionText = selection.split("\n"); + if (selection === "") { + cm.setCursor(cursor.line, 0); + cm.replaceSelection("- [x] " + selection); + cm.setCursor(cursor.line, cursor.ch + 6); + } else { + var selectionText = selection.split("\n"); - for (var i = 0, len = selectionText.length; i < len; i++) { - selectionText[i] = (selectionText[i] === "") ? "" : "- [x] " + selectionText[i]; - } - cm.replaceSelection(selectionText.join("\n")); - } - } else { - var action = window.editor.toolbarHandlers[name]; + for (var i = 0, len = selectionText.length; i < len; i++) { + selectionText[i] = (selectionText[i] === "") ? "" : "- [x] " + selectionText[i]; + } + cm.replaceSelection(selectionText.join("\n")); + } + } else if (name === "drawio") { + /** + * TODO: 画图功能实现 + * + * 1. 获取光标处数据,存储数据 + * 2. 打开画图页面,初始化数据(获取数据) + */ + window.sessionStorage.setItem("drawio", ''); - if (!!action && action !== "undefined") { - $.proxy(action, window.editor)(); - window.editor.focus(); - } - } + var cm = window.editor.cm; + const selStartLine = cm.getCursor('from').line + const selEndLine = cm.getCursor('to').line + 1 + + drawio.processMarkers(selStartLine, selEndLine) + drawio.show() + } else { + var action = window.editor.toolbarHandlers[name]; + + if (!!action && action !== "undefined") { + $.proxy(action, window.editor)(); + window.editor.focus(); + } + } }) ; /*** @@ -321,7 +556,7 @@ $(function () { } }); } - + /** * 设置编辑器变更状态 @@ -496,8 +731,8 @@ $(function () { }, url : window.template.listUrl, data: {"identify":window.book.identify}, - type: "POST", - dataType: "html", + type: "POST", + dataType: "html", success: function ($res) { $("#displayCustomsTemplateList").html($res); }, @@ -567,9 +802,9 @@ $(function () { type: "get", success : function ($res) { if ($res.errcode !== 0){ - layer.msg($res.message); - return; - } + layer.msg($res.message); + return; + } window.isLoad = true; window.editor.clear(); window.editor.insertValue($res.data.template_content); @@ -606,30 +841,30 @@ $(function () { }); $("#btnInsertTable").on("click",function () { - var content = $("#jsonContent").val(); + var content = $("#jsonContent").val(); if(content !== "") { - try { - var jsonObj = $.parseJSON(content); + try { + var jsonObj = $.parseJSON(content); var data = foreachJson(jsonObj,""); - var table = "| " + window.editormdLocales[window.lang].paramName - + " | " + window.editormdLocales[window.lang].paramType - + " | " + window.editormdLocales[window.lang].example - + " | " + window.editormdLocales[window.lang].remark - + " |\n| ------------ | ------------ | ------------ | ------------ |\n"; + var table = "| " + window.editormdLocales[window.lang].paramName + + " | " + window.editormdLocales[window.lang].paramType + + " | " + window.editormdLocales[window.lang].example + + " | " + window.editormdLocales[window.lang].remark + + " |\n| ------------ | ------------ | ------------ | ------------ |\n"; $.each(data,function (i,item) { table += "|" + item.key + "|" + item.type + "|" + item.value +"| |\n"; - }); + }); insertToMarkdown(table); }catch (e) { showError("Json 格式错误:" + e.toString(),"#json-error-message"); - return; - } - } - $("#convertJsonToTableModal").modal("hide"); + return; + } + } + $("#convertJsonToTableModal").modal("hide"); }); $("#convertJsonToTableModal").on("hidden.bs.modal",function () { $("#jsonContent").val(""); }).on("shown.bs.modal",function () { $("#jsonContent").focus(); }); -}); \ No newline at end of file +}); diff --git a/views/document/default_read.tpl b/views/document/default_read.tpl index 22b80ffe..b65e531f 100644 --- a/views/document/default_read.tpl +++ b/views/document/default_read.tpl @@ -51,6 +51,15 @@ display: none; } } + + .svg { + display: inline-block; + position: relative; + width: 100%; + height: 100%; + vertical-align: middle; + overflow: auto; + } diff --git a/views/document/markdown_edit_template.tpl b/views/document/markdown_edit_template.tpl index 66a88aa4..e22e2327 100755 --- a/views/document/markdown_edit_template.tpl +++ b/views/document/markdown_edit_template.tpl @@ -101,6 +101,7 @@ + @@ -450,7 +451,7 @@ - +