From 6d5948b56e9f2699f8364daae3b926153c215ce2 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 8 Mar 2024 15:52:47 +0000 Subject: [PATCH 01/19] Include module list in global-config node when exporting flows --- .../@node-red/editor-client/src/js/nodes.js | 43 +++++++++++++++++-- .../editor-client/src/js/ui/clipboard.js | 6 +-- .../editor-client/src/js/ui/env-var.js | 3 +- .../@node-red/editor-client/src/js/ui/view.js | 4 +- .../nodes/core/common/91-global-config.html | 3 +- 5 files changed, 47 insertions(+), 12 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index f80a2a8d1..59b85031b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -1428,7 +1428,12 @@ RED.nodes = (function() { /** * Converts the current node selection to an exportable JSON Object **/ - function createExportableNodeSet(set, exportedIds, exportedSubflows, exportedConfigNodes) { + function createExportableNodeSet(set, { + exportedIds, + exportedSubflows, + exportedConfigNodes, + includeModuleConfig = false + } = {}) { var nns = []; exportedIds = exportedIds || {}; @@ -1462,7 +1467,7 @@ RED.nodes = (function() { subflowSet = subflowSet.concat(RED.nodes.junctions(subflowId)) subflowSet = subflowSet.concat(RED.nodes.groups(subflowId)) - var exportableSubflow = createExportableNodeSet(subflowSet, exportedIds, exportedSubflows, exportedConfigNodes); + var exportableSubflow = createExportableNodeSet(subflowSet, { exportedIds, exportedSubflows, exportedConfigNodes }); nns = exportableSubflow.concat(nns); } } @@ -1497,13 +1502,16 @@ RED.nodes = (function() { } nns.push(convertedNode); if (node.type === "group") { - nns = nns.concat(createExportableNodeSet(node.nodes, exportedIds, exportedSubflows, exportedConfigNodes)); + nns = nns.concat(createExportableNodeSet(node.nodes, { exportedIds, exportedSubflows, exportedConfigNodes })); } } else { var convertedSubflow = convertSubflow(node, { credentials: false }); nns.push(convertedSubflow); } } + if (includeModuleConfig) { + updateGlobalConfigModuleList(nns) + } return nns; } @@ -1541,6 +1549,7 @@ RED.nodes = (function() { RED.nodes.eachNode(function(n) { nns.push(convertNode(n, opts)); }) + updateGlobalConfigModuleList(nns) return nns; } @@ -2886,7 +2895,33 @@ RED.nodes = (function() { } } } - + function getModuleListForNodes(nodes) { + const modules = {} + nodes.forEach(n => { + const nodeSet = RED.nodes.registry.getNodeSetForType(n.type) + if (nodeSet) { + modules[nodeSet.module] = nodeSet.version + } + }) + return modules + } + function updateGlobalConfigModuleList(nodes) { + const modules = getModuleListForNodes(nodes) + delete modules['node-red'] + const hasModules = (Object.keys(modules).length > 0) + let globalConfigNode = nodes.find(n => n.type === 'global-config') + if (!globalConfigNode && hasModules) { + globalConfigNode = { + id: RED.nodes.id(), + type: 'global-config', + env: [], + modules + } + nodes.push(globalConfigNode) + } else if (globalConfigNode) { + globalConfigNode.modules = modules + } + } return { init: function() { RED.events.on("registry:node-type-added",function(type) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js b/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js index 690968338..afc4d5d87 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js @@ -714,7 +714,7 @@ RED.clipboard = (function() { nodes = RED.view.selection().nodes||[]; } // Don't include the subflow meta-port nodes in the exported selection - nodes = RED.nodes.createExportableNodeSet(nodes.filter(function(n) { return n.type !== 'subflow'})); + nodes = RED.nodes.createExportableNodeSet(nodes.filter(function(n) { return n.type !== 'subflow'}), { includeModuleConfig: true }); } else if (type === 'flow') { var activeWorkspace = RED.workspaces.active(); nodes = RED.nodes.groups(activeWorkspace); @@ -729,7 +729,7 @@ RED.clipboard = (function() { }); var parentNode = RED.nodes.workspace(activeWorkspace)||RED.nodes.subflow(activeWorkspace); nodes.unshift(parentNode); - nodes = RED.nodes.createExportableNodeSet(nodes); + nodes = RED.nodes.createExportableNodeSet(nodes, { includeModuleConfig: true }); } else if (type === 'full') { nodes = RED.nodes.createCompleteNodeSet({ credentials: false }); } @@ -832,7 +832,7 @@ RED.clipboard = (function() { children: [] }; treeSubflows.push(subflows[node.id]) - } else { + } else if (node.type !== 'global-config') { nodes.push(node); } }); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/env-var.js b/packages/node_modules/@node-red/editor-client/src/js/ui/env-var.js index 79c626af4..ea3dbef48 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/env-var.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/env-var.js @@ -33,8 +33,7 @@ RED.envVar = (function() { id: RED.nodes.id(), type: "global-config", env: [], - name: "global-config", - label: "", + modules: {}, hasUsers: false, users: [], credentials: cred, diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 23385940c..22f75484f 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -5801,8 +5801,8 @@ RED.view = (function() { if (globalConfig) { // merge global env to existing global-config - var env0 = gconf.env; - var env1 = globalConfig.env; + var env0 = gconf.env || []; + var env1 = globalConfig.env || [] var newEnv = Array.from(env0); var changed = false; diff --git a/packages/node_modules/@node-red/nodes/core/common/91-global-config.html b/packages/node_modules/@node-red/nodes/core/common/91-global-config.html index 3240d315d..43918f1e7 100644 --- a/packages/node_modules/@node-red/nodes/core/common/91-global-config.html +++ b/packages/node_modules/@node-red/nodes/core/common/91-global-config.html @@ -11,12 +11,13 @@ RED.nodes.registerType('global-config',{ category: 'config', defaults: { - name: { value: "" }, env: { value: [] }, + modules: { value: {} } }, credentials: { map: { type: "map" } }, + label: 'global-config', oneditprepare: function() { $('#node-input-edit-env-var').on('click', function(evt) { RED.actions.invoke('core:show-user-settings', 'envvar') From 3f8a5301faf4446d33aaafda31e32fa6f13bdd5d Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 18 Jul 2024 16:22:42 +0100 Subject: [PATCH 02/19] Allow core:manage-palette action to specify tab and filter --- .../@node-red/editor-client/locales/en-US/editor.json | 2 ++ 1 file changed, 2 insertions(+) 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 a88890125..c2e89133b 100644 --- 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 @@ -265,6 +265,8 @@ "download": "Download", "importUnrecognised": "Imported unrecognised type:", "importUnrecognised_plural": "Imported unrecognised types:", + "importWithModuleInfo": "Required dependencies missing", + "importWithModuleInfoDesc": "These nodes are not currently installed in your palette and are required for the imported flow:", "importDuplicate": "Imported duplicate node:", "importDuplicate_plural": "Imported duplicate nodes:", "nodesExported": "Nodes exported to clipboard", From be9add2a950c61eecc7e6d028559ee5c6f9cd8cc Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 18 Jul 2024 16:23:02 +0100 Subject: [PATCH 03/19] Handle import of unknown nodes that include module meta --- .../@node-red/editor-client/src/js/nodes.js | 62 +++++++++++++++++-- .../editor-client/src/js/ui/palette-editor.js | 43 ++++++++++++- .../@node-red/editor-client/src/js/ui/view.js | 52 ++++++++-------- .../nodes/core/common/98-unknown.html | 30 +++++++-- 4 files changed, 150 insertions(+), 37 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 97ea4e992..e5a66ccaf 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -1811,6 +1811,7 @@ RED.nodes = (function() { * - id:import - import as-is * - id:copy - import with new id * - id:replace - import over the top of existing + * - modules: map of module:version - hints for unknown nodes */ function importNodes(newNodesObj,options) { // createNewIds,createMissingWorkspace) { const defOpts = { generateIds: false, addFlow: false, markChanged: false, reimport: false, importMap: {} } @@ -1946,12 +1947,54 @@ RED.nodes = (function() { } if (!isInitialLoad && unknownTypes.length > 0) { - var typeList = $("
    "); - unknownTypes.forEach(function(t) { - $("
  • ").text(t).appendTo(typeList); - }) - typeList = typeList[0].outerHTML; - RED.notify("

    "+RED._("clipboard.importUnrecognised",{count:unknownTypes.length})+"

    "+typeList,"error",false,10000); + const notificationOptions = { + type: "error", + fixed: false, + timeout: 10000, + } + let unknownNotification + if (options.modules) { + notificationOptions.fixed = true + delete notificationOptions.timeout + // We have module hint list from imported global-config + // Provide option to install missing modules + notificationOptions.buttons = [ + { + text: "Manage dependencies", + class:"primary", + click: function(e) { + unknownNotification.close(); + + RED.actions.invoke('core:manage-palette', { + view: 'install', + filter: '"' + Object.keys(options.modules).join('", "') + '"' + }) + } + } + ] + let moduleList = $("
      "); + Object.keys(options.modules).forEach(function(t) { + $("
    • ").text(t).appendTo(moduleList); + }) + moduleList = moduleList[0].outerHTML; + unknownNotification = RED.notify( + "

      "+RED._("clipboard.importWithModuleInfo")+"

      "+ + "

      "+RED._("clipboard.importWithModuleInfoDesc")+"

      "+ + moduleList, + notificationOptions + ); + } else { + var typeList = $("
        "); + unknownTypes.forEach(function(t) { + $("
      • ").text(t).appendTo(typeList); + }) + typeList = typeList[0].outerHTML; + + unknownNotification = RED.notify( + "

        "+RED._("clipboard.importUnrecognised",{count:unknownTypes.length})+"

        "+typeList, + notificationOptions + ); + } } var activeWorkspace = RED.workspaces.active(); @@ -2319,6 +2362,9 @@ RED.nodes = (function() { delete node.z; } } + const unknownTypeDef = RED.nodes.getType('unknown') + node._def.oneditprepare = unknownTypeDef.oneditprepare + var orig = {}; for (var p in n) { if (n.hasOwnProperty(p) && p!="x" && p!="y" && p!="z" && p!="id" && p!="wires") { @@ -2328,6 +2374,10 @@ RED.nodes = (function() { node._orig = orig; node.name = n.type; node.type = "unknown"; + if (options.modules) { + // We have a module hint list. Attach to the unknown node so we can reference it later + node.modules = Object.keys(options.modules) + } } if (node._def.category != "config") { if (n.hasOwnProperty('inputs')) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js index 3d949f8ca..44e6146c8 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js @@ -625,8 +625,20 @@ RED.palette.editor = (function() { } }) - RED.actions.add("core:manage-palette",function() { + RED.actions.add("core:manage-palette", function(opts) { RED.userSettings.show('palette'); + if (opts) { + if (opts.view) { + editorTabs.activateTab(opts.view); + } + if (opts.filter) { + if (opts.view === 'install') { + searchInput.searchBox('value', opts.filter) + } else if (opts.view === 'nodes') { + filterInput.searchBox('value', opts.filter) + } + } + } }); RED.events.on('registry:module-updated', function(ns) { @@ -982,8 +994,35 @@ RED.palette.editor = (function() { change: function() { var searchTerm = $(this).val().trim().toLowerCase(); if (searchTerm.length > 0 || loadedList.length < 20) { + const searchTerms = [] + searchTerm.split(',').forEach(term => { + term = term.trim() + if (term) { + const isExact = term[0] === '"' && term[term.length-1] === '"' + searchTerms.push({ + exact: isExact, + term: isExact ? term.substring(1,term.length-1) : term + }) + } + }) filteredList = loadedList.filter(function(m) { - return (m.index.indexOf(searchTerm) > -1); + for (let i = 0; i < searchTerms.length; i++) { + const location = m.index.indexOf(searchTerms[i].term) + if ( + ( + searchTerms[i].exact && + ( + location === 0 && ( + m.index.length === searchTerms[i].term.length || + m.index[searchTerms[i].term.length] === ',' + ) + ) + ) || + (!searchTerms[i].exact && location > -1)) { + return true + } + } + return false }).map(function(f) { return {info:f}}); refreshFilteredItems(); searchInput.searchBox('count',filteredList.length+" / "+loadedList.length); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 14f79977a..5872c0f40 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -5664,27 +5664,29 @@ RED.view = (function() { activeSubflowChanged = activeSubflow.changed; } var filteredNodesToImport = nodesToImport; - var globalConfig = null; - var gconf = null; + var importedGlobalConfig = null; + var existingGlobalConfig = null; RED.nodes.eachConfig(function (conf) { if (conf.type === "global-config") { - gconf = conf; + existingGlobalConfig = conf; } }); - if (gconf) { + if (existingGlobalConfig) { filteredNodesToImport = nodesToImport.filter(function (n) { - return (n.type !== "global-config"); - }); - globalConfig = nodesToImport.find(function (n) { - return (n.type === "global-config"); + if (n.type === "global-config") { + importedGlobalConfig = n + return false + } + return true }); } var result = RED.nodes.import(filteredNodesToImport,{ generateIds: options.generateIds, addFlow: addNewFlow, importMap: options.importMap, - markChanged: true + markChanged: true, + modules: importedGlobalConfig ? (importedGlobalConfig.modules || {}) : {} }); if (result) { var new_nodes = result.nodes; @@ -5808,38 +5810,38 @@ RED.view = (function() { } } - if (globalConfig) { + if (importedGlobalConfig) { // merge global env to existing global-config - var env0 = gconf.env || []; - var env1 = globalConfig.env || [] - var newEnv = Array.from(env0); + var existingEnv = existingGlobalConfig.env || []; + var importedEnv = importedGlobalConfig.env || [] + var newEnv = Array.from(existingEnv); var changed = false; - env1.forEach(function (item1) { - var index = newEnv.findIndex(function (item0) { - return (item0.name === item1.name); + importedEnv.forEach(function (importedItem) { + var index = newEnv.findIndex(function (existingItem) { + return (existingItem.name === importedItem.name); }); if (index >= 0) { - var item0 = newEnv[index]; - if ((item0.type !== item1.type) || - (item0.value !== item1.value)) { - newEnv[index] = item1; + const existingItem = newEnv[index]; + if ((existingItem.type !== importedItem.type) || + (existingItem.value !== importedItem.value)) { + newEnv[index] = importedItem; changed = true; } } else { - newEnv.push(item1); + newEnv.push(importedItem); changed = true; } }); - if(changed) { - gconf.env = newEnv; + if (changed) { + existingGlobalConfig.env = newEnv; var replaceEvent = { t: "edit", - node: gconf, + node: existingGlobalConfig, changed: true, changes: { - env: env0 + env: existingEnv } }; historyEvent = { diff --git a/packages/node_modules/@node-red/nodes/core/common/98-unknown.html b/packages/node_modules/@node-red/nodes/core/common/98-unknown.html index 52071c30f..282ad3415 100644 --- a/packages/node_modules/@node-red/nodes/core/common/98-unknown.html +++ b/packages/node_modules/@node-red/nodes/core/common/98-unknown.html @@ -1,14 +1,22 @@ From 702bf3d547036e4ba435fc04f1d99d6e35e71c0e Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Wed, 23 Oct 2024 23:10:09 +0200 Subject: [PATCH 04/19] Truncate long debug messages --- .../@node-red/editor-client/src/sass/debug.scss | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss index eb550c6f5..c92df8f99 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss @@ -131,6 +131,10 @@ } .red-ui-debug-msg-topic { display: block; + max-width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; color: var(--red-ui-debug-message-text-color-meta); } .red-ui-debug-msg-name { @@ -233,7 +237,10 @@ .red-ui-debug-msg-type-number-toggle { cursor: pointer;} .red-ui-debug-msg-type-string { - white-space: pre-wrap; + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } .red-ui-debug-msg-row { From c7fd2e3cdf579aaca7df3620a92088884050544b Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Fri, 1 Nov 2024 14:23:50 +0100 Subject: [PATCH 05/19] Add `opts.includeModuleConfig` to `createCompleteNodeSet` --- .../node_modules/@node-red/editor-client/src/js/nodes.js | 5 ++++- .../@node-red/editor-client/src/js/ui/clipboard.js | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index e5a66ccaf..1118f063b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -1564,6 +1564,7 @@ RED.nodes = (function() { // Create the Flow JSON for the current configuration // opts.credentials (whether to include (known) credentials) - default: true // opts.dimensions (whether to include node dimensions) - default: false + // opts.includeModuleConfig (whether to include modules) - default: false function createCompleteNodeSet(opts) { var nns = []; var i; @@ -1595,7 +1596,9 @@ RED.nodes = (function() { RED.nodes.eachNode(function(n) { nns.push(convertNode(n, opts)); }) - updateGlobalConfigModuleList(nns) + if (opts?.includeModuleConfig) { + updateGlobalConfigModuleList(nns); + } return nns; } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js b/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js index 2b17e3ac7..45f466809 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js @@ -747,7 +747,7 @@ RED.clipboard = (function() { nodes.unshift(parentNode); nodes = RED.nodes.createExportableNodeSet(nodes, { includeModuleConfig: true }); } else if (type === 'full') { - nodes = RED.nodes.createCompleteNodeSet({ credentials: false }); + nodes = RED.nodes.createCompleteNodeSet({ credentials: false, includeModuleConfig: true }); } if (nodes !== null) { if (format === "red-ui-clipboard-dialog-export-fmt-full") { From 53f204d8b951782705adc9c05efcd751f1e885bc Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Fri, 1 Nov 2024 14:25:45 +0100 Subject: [PATCH 06/19] Do not import `global-config` if it only contains modules --- .../@node-red/editor-client/src/js/ui/view.js | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 5872c0f40..bb1e5c664 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -5676,17 +5676,38 @@ RED.view = (function() { filteredNodesToImport = nodesToImport.filter(function (n) { if (n.type === "global-config") { importedGlobalConfig = n + // Do not import it if one exists + // The properties of this one will be merged after import + return false + } + return true + }); + } else { + filteredNodesToImport = nodesToImport.filter(function (n) { + if (n.type === "global-config") { + importedGlobalConfig = n + if (n.env && n.env.length) { + // No existing global-config + // Contains env and maybe modules, so import it + return true + } + // Contains modules only - do not import it return false } return true }); } + + const modules = importedGlobalConfig?.modules || {} + // Ensure do not import modules - since it can contain it + delete importedGlobalConfig?.modules + var result = RED.nodes.import(filteredNodesToImport,{ generateIds: options.generateIds, addFlow: addNewFlow, importMap: options.importMap, markChanged: true, - modules: importedGlobalConfig ? (importedGlobalConfig.modules || {}) : {} + modules: modules }); if (result) { var new_nodes = result.nodes; @@ -5810,7 +5831,7 @@ RED.view = (function() { } } - if (importedGlobalConfig) { + if (importedGlobalConfig && existingGlobalConfig) { // merge global env to existing global-config var existingEnv = existingGlobalConfig.env || []; var importedEnv = importedGlobalConfig.env || [] From 3a27a756ccf71e750a6ba5d716888fc7c1df60c2 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:36:54 +0100 Subject: [PATCH 07/19] Replace the CSS solution with a coded one for the msg string --- .../editor-client/src/js/ui/utils.js | 20 +++++++++++++++++-- .../editor-client/src/sass/debug.scss | 5 +---- 2 files changed, 19 insertions(+), 6 deletions(-) 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 cfc7f65de..93baaa2df 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 @@ -128,10 +128,22 @@ RED.utils = (function() { function formatString(str) { return str.replace(/\r?\n/g,"↵").replace(/\t/g,"→"); } + function sanitize(m) { return m.replace(/&/g,"&").replace(//g,">"); } + /** + * Truncates a string to a specified maximum length, adding ellipsis if truncated. + * + * @param {string} str - The string to be truncated. + * @param {number} maxLength - The maximum length of the truncated string. Default `120`. + * @returns {string} The truncated string with ellipsis if it exceeds the maximum length. + */ + function truncateString(str, maxLength = 120) { + return str.length > maxLength ? str.slice(0, maxLength) + "..." : str; + } + function buildMessageSummaryValue(value) { var result; if (Array.isArray(value)) { @@ -376,6 +388,9 @@ RED.utils = (function() { } } + // Max string length before truncating + const MAX_STRING_LENGTH = 80; + /** * Create a DOM element representation of obj - as used by Debug sidebar etc * @@ -469,7 +484,7 @@ RED.utils = (function() { } else if (typeHint === "internal" || (obj.__enc__ && obj.type === 'internal')) { e = $('').text("[internal]").appendTo(entryObj); } else if (typeof obj === 'string') { - if (/[\t\n\r]/.test(obj)) { + if (/[\t\n\r]/.test(obj) || obj.length > MAX_STRING_LENGTH) { element.addClass('collapsed'); $(' ').prependTo(header); makeExpandable(header, function() { @@ -478,7 +493,7 @@ RED.utils = (function() { $('
        ').text(obj).appendTo(row);
                         },function(state) {if (ontoggle) { ontoggle(path,state);}}, checkExpanded(strippedKey,expandPaths));
                     }
        -            e = $('').html('"'+formatString(sanitize(obj))+'"').appendTo(entryObj);
        +            e = $('').html('"'+formatString(sanitize(truncateString(obj, MAX_STRING_LENGTH)))+'"').appendTo(entryObj);
                     if (/^#[0-9a-f]{6}$/i.test(obj)) {
                         $('').css('backgroundColor',obj).appendTo(e);
                     }
        @@ -1503,6 +1518,7 @@ RED.utils = (function() {
                 parseContextKey: parseContextKey,
                 createIconElement: createIconElement,
                 sanitize: sanitize,
        +        truncateString: truncateString,
                 renderMarkdown: renderMarkdown,
                 createNodeIcon: createNodeIcon,
                 getDarkerColor: getDarkerColor,
        diff --git a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss
        index c92df8f99..8e8803f9d 100644
        --- a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss
        +++ b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss
        @@ -237,10 +237,7 @@
         .red-ui-debug-msg-type-number-toggle { cursor: pointer;}
         
         .red-ui-debug-msg-type-string {
        -    display: block;
        -    overflow: hidden;
        -    white-space: nowrap;
        -    text-overflow: ellipsis;
        +    white-space: pre-wrap;
         }
         
         .red-ui-debug-msg-row {
        
        From 6a13d2d544a4789882da0f7b4a18241d89440ba2 Mon Sep 17 00:00:00 2001
        From: GogoVega <92022724+GogoVega@users.noreply.github.com>
        Date: Mon, 25 Nov 2024 21:45:00 +0100
        Subject: [PATCH 08/19] Remove logic of the topic truncation and fix the docs
        
        ---
         .../@node-red/editor-client/src/js/ui/utils.js        | 11 +++++++----
         .../@node-red/editor-client/src/sass/debug.scss       |  4 ----
         2 files changed, 7 insertions(+), 8 deletions(-)
        
        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 93baaa2df..1546f2eef 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
        @@ -134,11 +134,14 @@ RED.utils = (function() {
             }
         
             /**
        -     * Truncates a string to a specified maximum length, adding ellipsis if truncated.
        +     * Truncates a string to a specified maximum length, adding ellipsis
        +     * if truncated.
              *
        -     * @param {string} str - The string to be truncated.
        -     * @param {number} maxLength - The maximum length of the truncated string. Default `120`.
        -     * @returns {string} The truncated string with ellipsis if it exceeds the maximum length.
        +     * @param {string} str The string to be truncated.
        +     * @param {number} [maxLength = 120] The maximum length of the truncated
        +     * string. Default `120`.
        +     * @returns {string} The truncated string with ellipsis if it exceeds the
        +     * maximum length.
              */
             function truncateString(str, maxLength = 120) {
                 return str.length > maxLength ? str.slice(0, maxLength) + "..." : str;
        diff --git a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss
        index 8e8803f9d..eb550c6f5 100644
        --- a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss
        +++ b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss
        @@ -131,10 +131,6 @@
         }
         .red-ui-debug-msg-topic {
             display: block;
        -    max-width: 100%;
        -    overflow: hidden;
        -    white-space: nowrap;
        -    text-overflow: ellipsis;
             color: var(--red-ui-debug-message-text-color-meta);
         }
         .red-ui-debug-msg-name {
        
        From ba7eec44fab5bec4f06f85730cab587c0c17b466 Mon Sep 17 00:00:00 2001
        From: Nick O'Leary 
        Date: Tue, 12 Nov 2024 16:53:05 +0000
        Subject: [PATCH 09/19] Add a node annotation if there info property set
        
        ---
         .../@node-red/editor-client/src/js/ui/view.js | 33 +++++++++++++++++++
         .../editor-client/src/sass/flow.scss          | 12 +++++++
         2 files changed, 45 insertions(+)
        
        diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js
        index 198af0850..fada93327 100644
        --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js
        +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js
        @@ -865,6 +865,39 @@ RED.view = (function() {
                 RED.view.navigator.init();
                 RED.view.tools.init();
         
        +        RED.view.annotations.register("red-ui-flow-node-docs",{
        +            type: "badge",
        +            class: "red-ui-flow-node-docs",
        +            element: function(node) {
        +
        +                const docsBadge = document.createElementNS("http://www.w3.org/2000/svg","g")
        +
        +                const pageOutline = document.createElementNS("http://www.w3.org/2000/svg","rect");
        +                pageOutline.setAttribute("x",0);
        +                pageOutline.setAttribute("y",0);
        +                pageOutline.setAttribute("rx",2);
        +                pageOutline.setAttribute("width",7);
        +                pageOutline.setAttribute("height",10);
        +                docsBadge.appendChild(pageOutline)
        +
        +                const pageLines = document.createElementNS("http://www.w3.org/2000/svg","path");
        +                pageLines.setAttribute("d", "M 7 3 h -3 v -3 M 2 8 h 3 M 2 6 h 3 M 2 4 h 2")
        +                docsBadge.appendChild(pageLines)
        +
        +                $(docsBadge).on('click', function (evt) {
        +                    if (node.type === "subflow") {
        +                        RED.editor.editSubflow(activeSubflow, 'editor-tab-description');
        +                    } else if (node.type === "group") {
        +                        RED.editor.editGroup(node, 'editor-tab-description');
        +                    } else {
        +                        RED.editor.edit(node, 'editor-tab-description');
        +                    }
        +                })
        +
        +                return docsBadge;
        +            },
        +            show: function(n) { return !!n.info }
        +        })
         
                 RED.view.annotations.register("red-ui-flow-node-changed",{
                     type: "badge",
        diff --git a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss
        index 723e8ac79..c6ad12ae9 100644
        --- a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss
        +++ b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss
        @@ -242,6 +242,18 @@ svg:not(.red-ui-workspace-lasso-active) {
             stroke-linecap: round;
         }
         
        +.red-ui-flow-node-docs {
        +    stroke-width: 1px;
        +    stroke-linejoin: round;
        +    stroke-linecap: round;
        +    stroke: var(--red-ui-node-border);
        +    fill: none;
        +    cursor: pointer;
        +    rect {
        +        fill: white;
        +    }
        +}
        +
         g.red-ui-flow-node-selected {
             .red-ui-workspace-select-mode & {
                 opacity: 1;
        
        From 7285244e8dca2d112985700053ff72a060506743 Mon Sep 17 00:00:00 2001
        From: Nick O'Leary 
        Date: Mon, 17 Mar 2025 16:21:14 +0000
        Subject: [PATCH 10/19] Add user setting for showing the node info icon
        
        ---
         .../editor-client/locales/en-US/editor.json       |  1 +
         .../editor-client/src/js/ui/userSettings.js       |  1 +
         .../@node-red/editor-client/src/js/ui/view.js     | 15 ++++++++++++++-
         3 files changed, 16 insertions(+), 1 deletion(-)
        
        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 5a35135ee..d60969f9c 100644
        --- 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
        @@ -111,6 +111,7 @@
                     "userSettings": "User Settings",
                     "nodes": "Nodes",
                     "displayStatus": "Show node status",
        +            "displayInfoIcon": "Show node information icon",
                     "displayConfig": "Configuration nodes",
                     "import": "Import",
                     "importExample": "Import example flow",
        diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js b/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js
        index 1b61f396e..ef3e2198c 100644
        --- a/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js
        +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js
        @@ -140,6 +140,7 @@ RED.userSettings = (function() {
                     title: "menu.label.nodes",
                     options: [
                         {setting:"view-node-status",oldSetting:"menu-menu-item-status",label:"menu.label.displayStatus",default: true, toggle:true,onchange:"core:toggle-status"},
        +                {setting:"view-node-info-icon", label:"menu.label.displayInfoIcon", default: true, toggle:true,onchange:"core:toggle-node-info-icon"},
                         {setting:"view-node-show-label",label:"menu.label.showNodeLabelDefault",default: true, toggle:true}
                     ]
                 },
        diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js
        index fada93327..6b6c5aced 100644
        --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js
        +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js
        @@ -82,6 +82,7 @@ RED.view = (function() {
             var slicePathLast = null;
             var ghostNode = null;
             var showStatus = false;
        +    let showNodeInfo = true;
             var lastClickNode = null;
             var dblClickPrimed = null;
             var clickTime = 0;
        @@ -860,6 +861,13 @@ RED.view = (function() {
                         toggleStatus(state);
                     }
                 });
        +        RED.actions.add("core:toggle-node-info-icon", function (state) {
        +            if (state === undefined) {
        +                RED.userSettings.toggle("view-node-info-icon");
        +            } else {
        +                toggleNodeInfo(state)
        +            }
        +        })
         
                 RED.view.annotations.init();
                 RED.view.navigator.init();
        @@ -896,7 +904,7 @@ RED.view = (function() {
         
                         return docsBadge;
                     },
        -            show: function(n) { return !!n.info }
        +            show: function(n) { return showNodeInfo && !!n.info }
                 })
         
                 RED.view.annotations.register("red-ui-flow-node-changed",{
        @@ -6040,6 +6048,11 @@ RED.view = (function() {
                 //TODO: subscribe/unsubscribe here
                 redraw();
             }
        +    function toggleNodeInfo(s) {
        +        showNodeInfo = s
        +        RED.nodes.eachNode(function(n) { n.dirty = true;});
        +        redraw();
        +    }
             function setSelectedNodeState(isDisabled) {
                 if (mouse_mode === RED.state.SELECTING_NODE) {
                     return;
        
        From ea25406d0094f13742d99b178e6d388fe5d693e9 Mon Sep 17 00:00:00 2001
        From: Joe Pavitt 
        Date: Tue, 15 Apr 2025 14:26:47 +0100
        Subject: [PATCH 11/19] Sort by downloads when doing a search of modules
        
        ---
         .../editor-client/locales/en-US/editor.json   |  2 +-
         .../editor-client/src/js/ui/palette-editor.js | 22 +++++++++----------
         .../src/sass/palette-editor.scss              |  5 ++++-
         3 files changed, 15 insertions(+), 14 deletions(-)
        
        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 5a35135ee..b14e13b2f 100644
        --- 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
        @@ -647,7 +647,7 @@
                     "tab-nodes": "Nodes",
                     "tab-install": "Install",
                     "sort": "sort:",
        -            "sortRelevance": "relevance",
        +            "sortDownloads": "downloads",
                     "sortAZ": "a-z",
                     "sortRecent": "recent",
                     "more": "+ __count__ more",
        diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js
        index 3926ca430..20729d5ff 100644
        --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js
        +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js
        @@ -583,7 +583,9 @@ RED.palette.editor = (function() {
                     packageList.editableList('addItem',{count:loadedList.length})
                     return;
                 }
        +        // sort the filtered modules
                 filteredList.sort(activeSort);
        +        // render the items in the package list
                 for (var i=0;i').appendTo(sortGroup);
                 const sortAZ = $('').appendTo(sortGroup);
                 const sortRecent = $('').appendTo(sortGroup);
        -        RED.popover.tooltip(sortRelevance,RED._("palette.editor.sortRelevance"));
        +        RED.popover.tooltip(sortRelevance,RED._("palette.editor.sortDownloads"));
                 RED.popover.tooltip(sortAZ,RED._("palette.editor.sortAZ"));
                 RED.popover.tooltip(sortRecent,RED._("palette.editor.sortRecent"));
         
        @@ -1120,8 +1117,9 @@ RED.palette.editor = (function() {
                             var descRow = $('
        ').appendTo(headerRow); $('
        ',{class:"red-ui-palette-module-description"}).text(entry.description).appendTo(descRow); var metaRow = $('
        ').appendTo(headerRow); - $(' '+entry.version+'').appendTo(metaRow); - $(' '+formatUpdatedAt(entry.updated_at)+'').appendTo(metaRow); + $(' '+entry.version+'').appendTo(metaRow); + $(' '+formatUpdatedAt(entry.updated_at)+'').appendTo(metaRow); + $(' '+(new Intl.NumberFormat().format(entry.downloads.week))+'').appendTo(metaRow); if (loadedCatalogs.length > 1) { $('' + (entry.catalog.name || entry.catalog.url) + '').appendTo(metaRow); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/palette-editor.scss b/packages/node_modules/@node-red/editor-client/src/sass/palette-editor.scss index 48706bda8..cdbfa406b 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/palette-editor.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/palette-editor.scss @@ -119,6 +119,9 @@ .red-ui-palette-module-updated { margin-left: 10px; } + .red-ui-palette-module-downloads { + margin-left: 10px; + } .red-ui-palette-module-link { margin-left: 5px; } @@ -230,7 +233,7 @@ white-space: nowrap; @include mixins.enable-selection; } -.red-ui-palette-module-version, .red-ui-palette-module-updated, .red-ui-palette-module-link { +.red-ui-palette-module-version, .red-ui-palette-module-updated, .red-ui-palette-module-link, .red-ui-palette-module-downloads { font-style:italic; font-size: 0.8em; @include mixins.enable-selection; From a2cb730a520c02c6fcf2d797b2c864db5b9294a7 Mon Sep 17 00:00:00 2001 From: Joe Pavitt Date: Tue, 15 Apr 2025 17:11:25 +0100 Subject: [PATCH 12/19] Handle no info.downloads use case for custom catalogues --- .../editor-client/src/js/ui/palette-editor.js | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js index 20729d5ff..a37e2db87 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js @@ -597,10 +597,26 @@ RED.palette.editor = (function() { } } function sortModulesRelevance(A,B) { - return sortModulesDownloads(A,B); + const defaultSort = sortModulesDownloads(A,B); + const currentFilter = searchInput.searchBox('value').trim(); + if (defaultSort === 0) { + // same number of downloads + if (currentFilter === "") { + return sortModulesAZ(A,B); + } + var i = A.info.index.indexOf(currentFilter) - B.info.index.indexOf(currentFilter); + if (i === 0) { + return sortModulesAZ(A,B); + } + return i; + } + return defaultSort; } function sortModulesDownloads(A,B) { - return B.info.downloads.week - A.info.downloads.week; + // check A has the info.downloads property - if not exising, prioritise it (likely a custom user catalogue) + const a = A.info && typeof A.info.downloads !== 'undefined' ? A.info.downloads.week : Number.MAX_SAFE_INTEGER; + const b = B.info && typeof B.info.downloads !== 'undefined' ? B.info.downloads.week : Number.MAX_SAFE_INTEGER; + return b - a; } function sortModulesAZ(A,B) { return A.info.id.localeCompare(B.info.id); From 214fe51a4733903f79dfc95823b949ff725de992 Mon Sep 17 00:00:00 2001 From: Joe Pavitt <99246719+joepavitt@users.noreply.github.com> Date: Mon, 28 Apr 2025 09:52:18 +0100 Subject: [PATCH 13/19] Update packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js Co-authored-by: Nick O'Leary --- .../@node-red/editor-client/src/js/ui/palette-editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js index a37e2db87..cdd7da4b3 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js @@ -1066,7 +1066,7 @@ RED.palette.editor = (function() { const sortRelevance = $('').appendTo(sortGroup); const sortAZ = $('').appendTo(sortGroup); const sortRecent = $('').appendTo(sortGroup); - RED.popover.tooltip(sortRelevance,RED._("palette.editor.sortDownloads")); + RED.popover.tooltip(sortRelevance,RED._("palette.editor.sortRelevance")); RED.popover.tooltip(sortAZ,RED._("palette.editor.sortAZ")); RED.popover.tooltip(sortRecent,RED._("palette.editor.sortRecent")); From fc42e9f0832755a6a1c5a90961a0639eb6a340aa Mon Sep 17 00:00:00 2001 From: Joe Pavitt <99246719+joepavitt@users.noreply.github.com> Date: Mon, 28 Apr 2025 09:52:25 +0100 Subject: [PATCH 14/19] Update packages/node_modules/@node-red/editor-client/locales/en-US/editor.json Co-authored-by: Nick O'Leary --- .../@node-red/editor-client/locales/en-US/editor.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b14e13b2f..5a35135ee 100644 --- 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 @@ -647,7 +647,7 @@ "tab-nodes": "Nodes", "tab-install": "Install", "sort": "sort:", - "sortDownloads": "downloads", + "sortRelevance": "relevance", "sortAZ": "a-z", "sortRecent": "recent", "more": "+ __count__ more", From 0be14f390fddd64785ce503edc30b03bb4f14cb1 Mon Sep 17 00:00:00 2001 From: Joe Pavitt <99246719+joepavitt@users.noreply.github.com> Date: Mon, 28 Apr 2025 09:52:31 +0100 Subject: [PATCH 15/19] Update packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js Co-authored-by: Nick O'Leary --- .../@node-red/editor-client/src/js/ui/palette-editor.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js index cdd7da4b3..f993547b1 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js @@ -1135,7 +1135,9 @@ RED.palette.editor = (function() { var metaRow = $('
        ').appendTo(headerRow); $(' '+entry.version+'').appendTo(metaRow); $(' '+formatUpdatedAt(entry.updated_at)+'').appendTo(metaRow); - $(' '+(new Intl.NumberFormat().format(entry.downloads.week))+'').appendTo(metaRow); + if (entry.downloads?.week !== undefined) { + $(' '+(new Intl.NumberFormat().format(entry.downloads.week))+'').appendTo(metaRow); + } if (loadedCatalogs.length > 1) { $('' + (entry.catalog.name || entry.catalog.url) + '').appendTo(metaRow); } From cf2aab81f6e215f279664e3c601ed295a3979a2e Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 28 Apr 2025 16:18:46 +0100 Subject: [PATCH 16/19] Fix linting error --- .../@node-red/editor-client/src/js/ui/view.js | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 3228c6518..cf42ba304 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -5772,24 +5772,26 @@ RED.view = (function() { const modules = importedGlobalConfig?.modules || {} // Ensure do not import modules - since it can contain it - delete importedGlobalConfig?.modules + if (importedGlobalConfig.modules) { + delete importedGlobalConfig.modules; + } - var result = RED.nodes.import(filteredNodesToImport,{ + const importResult = RED.nodes.import(filteredNodesToImport,{ generateIds: options.generateIds, addFlow: addNewFlow, importMap: options.importMap, markChanged: true, modules: modules }); - if (result) { - var new_nodes = result.nodes; - var new_links = result.links; - var new_groups = result.groups; - var new_junctions = result.junctions; - var new_workspaces = result.workspaces; - var new_subflows = result.subflows; - var removedNodes = result.removedNodes; - var new_default_workspace = result.missingWorkspace; + if (importResult) { + var new_nodes = importResult.nodes; + var new_links = importResult.links; + var new_groups = importResult.groups; + var new_junctions = importResult.junctions; + var new_workspaces = importResult.workspaces; + var new_subflows = importResult.subflows; + var removedNodes = importResult.removedNodes; + var new_default_workspace = importResult.missingWorkspace; if (addNewFlow && new_default_workspace) { RED.workspaces.show(new_default_workspace.id); } From 2ab34b89e9960d587deb3ee24c2679fa28563428 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 28 Apr 2025 16:28:30 +0100 Subject: [PATCH 17/19] Fix linting again --- packages/node_modules/@node-red/editor-client/src/js/ui/view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index cf42ba304..e7820f83a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -5772,7 +5772,7 @@ RED.view = (function() { const modules = importedGlobalConfig?.modules || {} // Ensure do not import modules - since it can contain it - if (importedGlobalConfig.modules) { + if (importedGlobalConfig?.modules) { delete importedGlobalConfig.modules; } From 12894870792cef33d95d093c7a2da3e31a424b7a Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 28 Apr 2025 16:39:29 +0100 Subject: [PATCH 18/19] Only offer to manage dependencies when theres a missing module identified --- .../node_modules/@node-red/editor-client/src/js/nodes.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index ed2181ef4..131d40d18 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -1989,7 +1989,11 @@ RED.nodes = (function() { timeout: 10000, } let unknownNotification + let missingModules = [] if (options.modules) { + missingModules = Object.keys(options.modules).filter(module => !RED.nodes.registry.getModule(module)) + } + if (missingModules.length > 0) { notificationOptions.fixed = true delete notificationOptions.timeout // We have module hint list from imported global-config @@ -2003,13 +2007,13 @@ RED.nodes = (function() { RED.actions.invoke('core:manage-palette', { view: 'install', - filter: '"' + Object.keys(options.modules).join('", "') + '"' + filter: '"' + missingModules.join('", "') + '"' }) } } ] let moduleList = $("
          "); - Object.keys(options.modules).forEach(function(t) { + missingModules.forEach(function(t) { $("
        • ").text(t).appendTo(moduleList); }) moduleList = moduleList[0].outerHTML; From 71c85af1472a234c74a9cd47e6646ac8cc30c3e5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 28 Apr 2025 17:07:18 +0100 Subject: [PATCH 19/19] Fix filtering node tab on palette-manager via action --- .../editor-client/src/js/ui/palette-editor.js | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js index a4b716f29..e73a3a9b1 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js @@ -34,7 +34,7 @@ RED.palette.editor = (function() { let packageList; // Nodes tab - filter - let activeFilter = ""; + let activeFilterTerms = []; // Nodes tab - search input let filterInput; // Install tab - search input @@ -421,7 +421,19 @@ RED.palette.editor = (function() { } function filterChange(val) { - activeFilter = val.toLowerCase(); + const activeFilter = val.toLowerCase(); + activeFilterTerms = [] + activeFilter.split(',').forEach(term => { + term = term.trim() + if (term) { + const isExact = term[0] === '"' && term[term.length-1] === '"' + activeFilterTerms.push({ + exact: isExact, + term: isExact ? term.substring(1,term.length-1) : term + }) + } + }) + var visible = nodeList.editableList('filter'); var size = nodeList.editableList('length'); if (val === "") { @@ -669,6 +681,9 @@ RED.palette.editor = (function() { title: RED._("palette.editor.palette"), get: getSettingsPane, close: function() { + if (filterInput) { + filterInput.searchBox('value',""); + } settingsPane.detach(); }, focus: function() { @@ -870,11 +885,26 @@ RED.palette.editor = (function() { return A.info.name.localeCompare(B.info.name); }, filter: function(data) { - if (activeFilter === "" ) { + if (activeFilterTerms.length === 0) { return true; } - - return (activeFilter==="")||(data.index.indexOf(activeFilter) > -1); + for (let i = 0; i < activeFilterTerms.length; i++) { + const searchTerm = activeFilterTerms[i] + const location = data.index.indexOf(searchTerm.term) + if ( + ( + searchTerm.exact && + ( + location === 0 && ( + data.index.length === searchTerm.term.length || + data.index[searchTerm.term.length] === ',' + ) + ) + ) || + (!searchTerm.exact && location > -1)) { + return true + } + } }, addItem: function(container,i,object) { var entry = object.info;