add support for mermaid diagram to markdown editor

This commit is contained in:
Hiroyasu Nishiyama 2023-01-05 17:25:16 +09:00
parent 928131cf08
commit 038f75e48f
13 changed files with 202 additions and 9 deletions

View File

@ -151,6 +151,7 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/js/font-awesome.js",
"packages/node_modules/@node-red/editor-client/src/js/history.js",
"packages/node_modules/@node-red/editor-client/src/js/validators.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/mermaid.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/utils.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js",
@ -225,7 +226,7 @@ module.exports = function(grunt) {
"node_modules/jsonata/jsonata-es5.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js",
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ace.js",
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ext-language_tools.js",
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ext-language_tools.js"
],
// "packages/node_modules/@node-red/editor-client/public/vendor/vendor.css": [
// // TODO: resolve relative resource paths in
@ -234,6 +235,9 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/public/vendor/ace/worker-jsonata.js": [
"node_modules/jsonata/jsonata-es5.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/worker-jsonata.js"
],
"packages/node_modules/@node-red/editor-client/public/vendor/mermaid/mermaid.min.js": [
"node_modules/mermaid/dist/mermaid.min.js"
]
}
}

View File

@ -109,6 +109,7 @@
"jquery-i18next": "1.2.1",
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
"marked": "4.2.3",
"mermaid": "^9.3.0",
"minami": "1.2.3",
"mocha": "9.2.2",
"node-red-node-test-helper": "^0.3.0",

View File

@ -0,0 +1,33 @@
// Dynamic loading support for editor libraries
var apiUtils = require("../util");
var fs = require("fs");
var path = require("path");
var lib2path = {
"mermaid": "../../../editor-client/public/vendor/mermaid/mermaid.min.js",
};
module.exports = {
init: function(_settings, _runtimeAPI) {
settings = _settings;
},
get: function(req,res) {
var name = req.params.name;
if (name in lib2path) {
try {
var lib = path.join(__dirname, lib2path[name]);
var code = fs.readFileSync(lib);
res.send(code);
}
catch (e) {
res.status(500).json({code: "runtime_error", message: e.toString()});
}
}
else {
res.status(400).json({code: "invalid_request", message: `no library: ${name}`});
}
},
}

View File

@ -116,6 +116,11 @@ module.exports = {
// SSH keys
editorApp.use("/settings/user/keys",needsPermission("settings.write"),info.sshkeys());
// Editor Libraries
var editorLibs = require("./editor-libs");
editorLibs.init(settings, runtimeAPI);
editorApp.get("/editor-libs/:name", needsPermission("editor-libs.read"), editorLibs.get, apiUtil.errorHandler);
return editorApp;
}
},

View File

@ -987,7 +987,10 @@
"quote": "Quote",
"link": "Link",
"horizontal-rule": "Horizontal rule",
"toggle-preview": "Toggle preview"
"toggle-preview": "Toggle preview",
"mermaid": {
"summary": "Mermaid Diagram"
}
},
"bufferEditor": {
"title": "Buffer editor",

View File

@ -987,7 +987,10 @@
"quote": "引用",
"link": "リンク",
"horizontal-rule": "区切り線",
"toggle-preview": "プレビュー表示切替え"
"toggle-preview": "プレビュー表示切替え",
"mermaid": {
"summary": "Mermaid図"
}
},
"bufferEditor": {
"title": "バッファエディタ",

View File

@ -114,6 +114,7 @@
var currentScrollTop = $(".red-ui-editor-type-markdown-panel-preview").scrollTop();
$(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
$(".red-ui-editor-type-markdown-panel-preview").scrollTop(currentScrollTop);
mermaid.init();
},200);
})
if (options.header) {
@ -122,6 +123,7 @@
if (value) {
$(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
mermaid.init();
}
panels = RED.panels.create({
id:"red-ui-editor-type-markdown-panels",

View File

@ -0,0 +1,46 @@
// Mermaid diagram stub library for on-demand dynamic loading
// Will be overwritten after script loading by $.getScript
var mermaid = (function () {
var enabled /* = undefined */;
var initializing = false;
var initCalled = false;
function initialize(opt) {
if (enabled === undefined) {
if (RED.settings.markdownEditor &&
RED.settings.markdownEditor.mermaid) {
enabled = RED.settings.markdownEditor.mermaid.enabled;
}
else {
enabled = true;
}
}
if (enabled) {
initializing = true;
$.getScript("/editor-libs/mermaid",
function (data, stat, jqxhr) {
$(".mermaid").show();
// invoke loaded mermaid API
initializing = false;
mermaid.initialize(opt);
if (initCalled) {
mermaid.init();
initCalled = false;
}
});
}
}
function init() {
if (initializing) {
$(".mermaid").hide();
initCalled = true;
}
}
return {
initialize: initialize,
init: init,
};
})();

View File

@ -463,7 +463,8 @@ RED.sidebar.info = (function() {
el = el.next();
}
$(this).toggleClass('expanded',!isExpanded);
})
});
mermaid.init();
}
var tips = (function() {

View File

@ -96,6 +96,37 @@ RED.utils = (function() {
}
}
var mermaidIsInitialized = false;
var mermaidIsEnabled /* = undefined */;
renderer.code = function (code, lang) {
if(lang === "mermaid") {
// mermaid diagram rendering
if (mermaidIsEnabled === undefined) {
if (RED.settings.markdownEditor &&
RED.settings.markdownEditor.mermaid) {
mermaidIsEnabled = RED.settings.markdownEditor.mermaid.enabled;
}
else {
mermaidIsEnabled = true;
}
}
if (mermaidIsEnabled) {
if (!mermaidIsInitialized) {
mermaidIsInitialized = true;
mermaid.initialize({startOnLoad:false});
}
return `<pre class='mermaid'>${code}</pre>`;
}
else {
return `<details><summary>${RED._("markdownEditor.mermaid.summary")}</summary><pre><code>${code}</code></pre></details>`;
}
}
else {
return "<pre><code>" +code +"</code></pre>";
}
};
window._marked.setOptions({
renderer: renderer,
gfm: true,

View File

@ -89,10 +89,16 @@ var api = module.exports = {
if (!runtime.settings.disableEditor) {
safeSettings.context = runtime.nodes.listContextStores();
if (runtime.settings.editorTheme && runtime.settings.editorTheme.codeEditor) {
safeSettings.codeEditor = runtime.settings.editorTheme.codeEditor || {};
safeSettings.codeEditor.lib = safeSettings.codeEditor.lib || "monaco";
safeSettings.codeEditor.options = safeSettings.codeEditor.options || {};
if (runtime.settings.editorTheme) {
if (runtime.settings.editorTheme.codeEditor) {
safeSettings.codeEditor = runtime.settings.editorTheme.codeEditor || {};
safeSettings.codeEditor.lib = safeSettings.codeEditor.lib || "monaco";
safeSettings.codeEditor.options = safeSettings.codeEditor.options || {};
}
if (runtime.settings.editorTheme.markdownEditor) {
safeSettings.markdownEditor = runtime.settings.editorTheme.markdownEditor || {};
safeSettings.markdownEditor.mermaid = safeSettings.markdownEditor.mermaid || { enabled: true };
}
}
safeSettings.libraries = runtime.library.getLibraries();
if (util.isArray(runtime.settings.paletteCategories)) {

View File

@ -422,7 +422,16 @@ module.exports = {
//fontFamily: "Cascadia Code, Fira Code, Consolas, 'Courier New', monospace",
//fontLigatures: true,
}
}
},
markdownEditor: {
mermaid: {
/** enable or disable mermaid diagram in markdown document
*/
enabled: true
}
},
},
/*******************************************************************************

View File

@ -0,0 +1,49 @@
const should = require("should");
const request = require('supertest');
const express = require('express');
const bodyParser = require("body-parser");
const sinon = require('sinon');
let app;
const NR_TEST_UTILS = require("nr-test-utils");
const editorLibs = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/editor-libs");
describe("api/editor/editor-libs", function() {
before(function() {
app = express();
app.use(bodyParser.json());
app.get("/editor-libs/:name", editorLibs.get);
});
it("returns the editor library for mermaid", function(done) {
const settings = {};
const runtimeAPI = {};
editorLibs.init(settings, runtimeAPI);
request(app)
.get("/editor-libs/mermaid")
.expect(200)
.end(function(err,res) {
if (err || (typeof res.error === "object")) {
return done(err || res.error);
}
res.should.have.property("statusCode",200);
res.should.have.property("_body");
done();
});
});
it('should error when called with unknown library', function(done) {
const settings = {};
const runtimeAPI = {};
editorLibs.init(settings, runtimeAPI);
request(app)
.get("/editor-libs/unknown")
.expect(400)
.end(done);
});
});