diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js index 2e85a5ef8..a555fbac6 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js @@ -55,6 +55,7 @@ RED.editor.codeEditor.monaco = (function() { const type = "monaco"; const monacoThemes = ["vs","vs-dark","hc-black"]; //TODO: consider setting hc-black autmatically based on acessability? let userSelectedTheme; + let tsFuncData; //used to cache the original func.d.ts data for later use when updating linkcall targets //TODO: get from externalModules.js For now this is enough for feature parity with ACE (and then some). const knownModules = { @@ -143,6 +144,9 @@ RED.editor.codeEditor.monaco = (function() { var typePath = "types/" + libPath; $.get(typePath) .done(function(data) { + if (libPath === "node-red/func.d.ts") { + tsFuncData = data; //cache the original func.d.ts data for later use when updating linkcall targets + } modulesCache[libPath] = data; if(!preloadOnly) { loadedLibs.JS[libModule] = monaco.languages.typescript.javascriptDefaults.addExtraLib(data, "file://types/" + libPackage + "/" + libModule + "/index.d.ts"); @@ -924,6 +928,62 @@ RED.editor.codeEditor.monaco = (function() { //check if extraLibs are to be loaded (e.g. fs or os) refreshModuleLibs(editorOptions.extraLibs) + // update func.d.ts definitions for up-to-date link-in target names in the node.linkcall() definition + if (tsFuncData && options.node && options.node.type === 'function') { + const getUpdatedLinkcallDefs = () => { + const linkInNodes = RED.nodes.filterNodes({type:'link in'}) + const viableLinkTargets = [] + for (const target of linkInNodes) { + // links on same flow/subflow as caller node are valid targets + // however links inside a (different) subflow are not valid targets. + if (target.z === options.node.z || !RED.nodes.subflow(target.z)) { + viableLinkTargets.push(target) + } + } + const targetsByName = [...new Set(viableLinkTargets.map(node => node.name || '').filter(name => name.length > 0))].map(s => JSON.stringify(s)); + const targetsById = [...new Set(viableLinkTargets.map(node => node.id))].map(s => JSON.stringify(s)); + const targets = [...targetsByName, ...targetsById].filter(s => s && s.length > 0).join("|") || 'string'; + return ` + // #region:linkcall + /** + * Utility function to call, a reusable flow defined as a subroutine (link-in ~ ~ link-out). + * When the \`linkcall\` function resolves, it returns the \`msg\` object returned by the link-out + * (return) node of the subroutine. + * + * @param {string} target - the name or ID of the target link-in subroutine to call + * @param {Object} msg - the message object to pass to the subroutine + * @param {Object} [options] - call options + * @param {number} [options.timeout=5000] - the maximum time to wait for a response (default: 5000ms) + * @return {Promise} - resolves with the returned message + * + * @example Call "greeting-person" subroutine by name: + * \`\`\`javascript + * msg.payload = "Joe Bloggs"; + * const resultMsg = await node.linkcall("greeting-person", msg, {timeout: 10000}); + * msg.payload = resultMsg.payload; // payload = "Hello Joe Bloggs" + * return msg; // return updated msg + * \`\`\` + * + * @example Call "greeting-person" subroutine by node id: + * \`\`\`javascript + * msg.payload = "John Doe"; + * const result = await node.linkcall("a1b2c3d4e5f6", msg); // result.payload will be "Hello John Doe" + * return result; // return new result object + * \`\`\` + */ + static linkcall(target: ${targets}, msg: object, options?: { timeout?: number; [key: string]: any }): Promise; + // #endregion:linkcall +` + } + const newDefs = getUpdatedLinkcallDefs(); + data = tsFuncData.replace(/\/\/ #region:linkcall[\s\S]*?\/\/ #endregion:linkcall/g, newDefs); + if (loadedLibs.JS.func) { + loadedLibs.JS.func.dispose(); + loadedLibs.JS.func = null; + } + loadedLibs.JS.func = monaco.languages.typescript.javascriptDefaults.addExtraLib(data, 'file://types/node-red/func/index.d.ts'); + } + function refreshModuleLibs(extraModuleLibs) { var defs = []; var imports = []; diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.html b/packages/node_modules/@node-red/nodes/core/function/10-function.html index 4b63373a1..8bf058b0c 100644 --- a/packages/node_modules/@node-red/nodes/core/function/10-function.html +++ b/packages/node_modules/@node-red/nodes/core/function/10-function.html @@ -483,9 +483,15 @@ } }); - var buildEditor = function(id, stateId, focus, value, defaultValue, extraLibs, offset) { - var editor = RED.editor.createEditor({ + const buildEditor = function(id, node, focus, value, defaultValue, extraLibs, offset) { + const stateId = `${node.id}/${id}`; + const editor = RED.editor.createEditor({ id: id, + node: { + id: node.id, + type: node.type, + z: node.z + }, mode: 'ace/mode/nrjavascript', value: value || defaultValue || "", stateId: stateId, @@ -512,9 +518,9 @@ editor.__stateId = stateId; return editor; } - this.initEditor = buildEditor('node-input-init-editor', this.id + "/" + "initEditor", false, $("#node-input-initialize").val(), RED._("node-red:function.text.initialize"), undefined, 0); - this.editor = buildEditor('node-input-func-editor', this.id + "/" + "editor", true, $("#node-input-func").val(), undefined, that.libs || [], undefined, -1); - this.finalizeEditor = buildEditor('node-input-finalize-editor', this.id + "/" + "finalizeEditor", false, $("#node-input-finalize").val(), RED._("node-red:function.text.finalize"), undefined, 0); + this.initEditor = buildEditor('node-input-init-editor', this, false, $("#node-input-initialize").val(), RED._("node-red:function.text.initialize"), undefined, 0); + this.editor = buildEditor('node-input-func-editor', this, true, $("#node-input-func").val(), undefined, that.libs || [], undefined, -1); + this.finalizeEditor = buildEditor('node-input-finalize-editor', this, false, $("#node-input-finalize").val(), RED._("node-red:function.text.finalize"), undefined, 0); RED.library.create({ url:"functions", // where to get the data from