浏览代码

implement drawio support

Signed-off-by: Ys Liu <[email protected]>
Seanly Liu 2 年之前
父节点
当前提交
ebb5bdb14b

+ 1 - 0
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

+ 1 - 0
conf/lang/zh-cn.ini

@@ -367,6 +367,7 @@ gfm_task = GFM 任务列表
 attachment = 附件
 json_to_table = Json转换为表格
 template = 模板
+draw = 画图
 close_preview = 关闭实时预览
 modify_history = 修改历史
 sidebar = 边栏

+ 4 - 2
static/editor.md/editormd.js

@@ -3950,9 +3950,12 @@
                 }
                 return "<svg class='mindmap' style='width:100%;min-height=150px;height:"+custom_height+"px;' id='mindmap-"+ map_id +"'>"+code+"</svg>";
             }
+            if (lang === "drawio") {
+                var svgCode = decodeURIComponent(escape(window.atob(code)))
+                return "<div class=\"svg\" style=\"overflow: auto; padding: 10px;\">" + svgCode + "</div>"
+            } 
             else
             {
-
                 return marked.Renderer.prototype.code.apply(this, arguments);
             }
         };
@@ -4029,7 +4032,6 @@
             html += "<li class=\"directory-item\"><a class=\"directory-item-link directory-item-link-" + level + "\" href=\"#" + id + "\" level=\"" + level + "\">" + text + "</a></li>";
             lastLevel = level;
         }
-        console.log(html);
 
         var tocContainer = container.find(".markdown-toc");
 

+ 292 - 57
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");
-
-               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];
-
-           if (!!action && action !== "undefined") {
-               $.proxy(action, window.editor)();
-               window.editor.focus();
-           }
-       }
+            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 if (name === "drawio") {
+            /**
+             * TODO: 画图功能实现
+             * 
+             * 1. 获取光标处数据,存储数据
+             * 2. 打开画图页面,初始化数据(获取数据)
+             */
+            window.sessionStorage.setItem("drawio", '');
+
+            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();
     });
-});
+});

+ 9 - 0
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; 
+        }
     </style>
 </head>
 <body>

+ 2 - 1
views/document/markdown_edit_template.tpl

@@ -101,6 +101,7 @@
             <a href="javascript:;" data-toggle="tooltip" data-title="{{i18n .Lang "doc.gfm_task"}}"><i class="fa fa-tasks item" name="tasks" aria-hidden="true"></i></a>
             <a href="javascript:;" data-toggle="tooltip" data-title="{{i18n .Lang "doc.attachment"}}"><i class="fa fa-paperclip item" aria-hidden="true" name="attachment"></i></a>
             <a href="javascript:;" data-toggle="tooltip" data-title="{{i18n .Lang "doc.json_to_table"}}"><i class="fa fa-wrench item" aria-hidden="true" name="json"></i></a>
+            <a href="javascript:;" data-toggle="tooltip" data-title="{{i18n .Lang "doc.draw"}}"><i class="fa fa-paint-brush item" aria-hidden="true" name="drawio"></i></a>
             <a href="javascript:;" data-toggle="tooltip" data-title="{{i18n .Lang "doc.template"}}"><i class="fa fa-tachometer last" name="template"></i></a>
 
         </div>
@@ -450,7 +451,7 @@
 <script src="{{cdnjs "/static/bootstrap/js/bootstrap.min.js"}}"></script>
 <script src="{{cdnjs "/static/webuploader/webuploader.min.js"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/jstree/3.3.4/jstree.min.js"}}" type="text/javascript"></script>
-<script src="{{cdnjs "/static/editor.md/editormd.min.js" "version"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/editor.md/editormd.js" "version"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/layer/layer.js"}}" type="text/javascript" ></script>
 <script src="{{cdnjs "/static/js/jquery.form.js"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/js/array.js" "version"}}" type="text/javascript"></script>