diff --git a/Gruntfile.js b/Gruntfile.js index 4f4b3b027..dcb96a2c0 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -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" ] } } diff --git a/package.json b/package.json index ad65edd90..ed0edd2b0 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/editor-libs.js b/packages/node_modules/@node-red/editor-api/lib/editor/editor-libs.js new file mode 100644 index 000000000..98391dc12 --- /dev/null +++ b/packages/node_modules/@node-red/editor-api/lib/editor/editor-libs.js @@ -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}`}); + } + }, +} diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/index.js b/packages/node_modules/@node-red/editor-api/lib/editor/index.js index f210d90fe..66d088f3a 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/index.js @@ -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; } }, diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 9ffde4df0..e768c1cb3 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -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", diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index 30761ef14..fcdf35e3d 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -987,7 +987,10 @@ "quote": "引用", "link": "リンク", "horizontal-rule": "区切り線", - "toggle-preview": "プレビュー表示切替え" + "toggle-preview": "プレビュー表示切替え", + "mermaid": { + "summary": "Mermaid図" + } }, "bufferEditor": { "title": "バッファエディタ", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/markdown.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/markdown.js index eeb8519e6..93e34e126 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/markdown.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/markdown.js @@ -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", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/mermaid.js b/packages/node_modules/@node-red/editor-client/src/js/ui/mermaid.js new file mode 100644 index 000000000..f16aae5a0 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/mermaid.js @@ -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, + }; +})(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js index c46aa97e8..07818ffd2 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js @@ -463,7 +463,8 @@ RED.sidebar.info = (function() { el = el.next(); } $(this).toggleClass('expanded',!isExpanded); - }) + }); + mermaid.init(); } var tips = (function() { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js index 2c4cdca6b..4d8ccdd9d 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js @@ -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 `
${code}`; + } + else { + return `
${code}
" +code +"
";
+ }
+ };
+
window._marked.setOptions({
renderer: renderer,
gfm: true,
diff --git a/packages/node_modules/@node-red/runtime/lib/api/settings.js b/packages/node_modules/@node-red/runtime/lib/api/settings.js
index 6c13596ce..2399a3152 100644
--- a/packages/node_modules/@node-red/runtime/lib/api/settings.js
+++ b/packages/node_modules/@node-red/runtime/lib/api/settings.js
@@ -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)) {
diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js
index ec247a672..5d8a05e44 100644
--- a/packages/node_modules/node-red/settings.js
+++ b/packages/node_modules/node-red/settings.js
@@ -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
+ }
+ },
+
},
/*******************************************************************************
diff --git a/test/unit/@node-red/editor-api/lib/editor/editor-libs_spec.js b/test/unit/@node-red/editor-api/lib/editor/editor-libs_spec.js
new file mode 100644
index 000000000..4a8158d74
--- /dev/null
+++ b/test/unit/@node-red/editor-api/lib/editor/editor-libs_spec.js
@@ -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);
+ });
+});