From 5ec075a49a18d5463a39772d1e347e55212a467b Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 16 Jun 2025 16:54:01 +0100 Subject: [PATCH 01/15] Allow source node to be set in setSuggestedFlow api --- .../editor-client/src/js/ui/typeSearch.js | 3 ++ .../@node-red/editor-client/src/js/ui/view.js | 48 +++++++++++++++++-- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js index 089898a95..8b64a3231 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js @@ -319,6 +319,9 @@ RED.typeSearch = (function() { } visible = true; } else { + if (suggestCallback) { + suggestCallback(null); + } dialog.hide(); searchResultsDiv.hide(); } 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 08ab0ec0a..4745fa782 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 @@ -6379,10 +6379,10 @@ RED.view = (function() { nn.w = RED.view.node_width; nn.h = Math.max(RED.view.node_height, (nn.outputs || 0) * 15); nn.resize = true; - if (x != null && typeof x == "number" && x >= 0) { + if (x != null && typeof x == "number") { nn.x = x; } - if (y != null && typeof y == "number" && y >= 0) { + if (y != null && typeof y == "number") { nn.y = y; } var historyEvent = { @@ -6472,7 +6472,9 @@ RED.view = (function() { * x: 0, * y: 0, * } - * ] + * ], + * "source": , + * "sourcePort": , * } * If `nodes` is a single node without an id property, it will be generated * using its default properties. @@ -6480,6 +6482,9 @@ RED.view = (function() { * If `nodes` has multiple, they must all have ids and will be assumed to be 'importable'. * In other words, a piece of valid flow json. * + * `source`/`sourcePort` are option and used to indicate a node the suggestion should be connected to. + * If provided, a ghost wire will be added between the source and the first node in the suggestion. + * * Limitations: * - does not support groups, subflows or whole tabs * - does not support config nodes @@ -6579,6 +6584,15 @@ RED.view = (function() { suggestedLinks.push(link) } }) + if (suggestion.source && suggestedNodes[0]?._def?.inputs > 0) { + suggestedLinks.push({ + source: suggestion.source, + sourcePort: suggestion.sourcePort || 0, + target: suggestedNodes[0], + targetPort: 0, + __ghost: true + }) + } } if (ghostNode) { if (suggestedNodes.length > 0) { @@ -6599,14 +6613,40 @@ RED.view = (function() { function applySuggestedFlow () { if (currentSuggestion && currentSuggestion.nodes) { const nodesToImport = currentSuggestion.nodes + const sourceNode = currentSuggestion.source + const sourcePort = currentSuggestion.sourcePort || 0 setSuggestedFlow(null) - return importNodes(nodesToImport, { + const result = importNodes(nodesToImport, { generateIds: true, touchImport: true, notify: false, // Ensure the node gets all of its defaults applied applyNodeDefaults: true }) + if (sourceNode) { + const firstNode = result.nodeMap[nodesToImport[0].id] + if (firstNode && firstNode._def?.inputs > 0) { + // Connect the source node to the first node in the suggestion + const link = { + source: sourceNode, + target: RED.nodes.node(firstNode.id), + sourcePort: sourcePort, + targetPort: 0 + }; + RED.nodes.addLink(link) + const historyEvent = RED.history.peek(); + if (historyEvent.t === "multi") { + historyEvent = historyEvent.events.find(e => e.t === "add") + } + if (historyEvent) { + historyEvent.links = historyEvent.links || []; + historyEvent.links.push(link); + } + RED.view.redraw(true); + } + } + + return result } } From d9715eab9925249c5b8a97b1f5cbff857e81d6a8 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 16 Jun 2025 17:01:21 +0100 Subject: [PATCH 02/15] Fix lint --- 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 4745fa782..8000e2662 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 @@ -6634,7 +6634,7 @@ RED.view = (function() { targetPort: 0 }; RED.nodes.addLink(link) - const historyEvent = RED.history.peek(); + let historyEvent = RED.history.peek(); if (historyEvent.t === "multi") { historyEvent = historyEvent.events.find(e => e.t === "add") } From af0903eae1fc8563694e134e8e91e199c0cef283 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 17 Jun 2025 10:41:32 +0100 Subject: [PATCH 03/15] Add clickToApply flag to suggestion --- .../@node-red/editor-client/src/js/ui/view.js | 20 +++++++++++++++++++ 1 file changed, 20 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 8000e2662..1eec57f5b 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 @@ -4762,6 +4762,10 @@ RED.view = (function() { .on("touchend",nodeTouchEnd) .on("mouseover",nodeMouseOver) .on("mouseout",nodeMouseOut); + } else if (d.__ghostClick) { + d3.select(mainRect) + .on("mousedown",d.__ghostClick) + .on("touchstart",d.__ghostClick) } nodeContents.appendChild(mainRect); //node.append("rect").attr("class", "node-gradient-top").attr("rx", 6).attr("ry", 6).attr("height",30).attr("stroke","none").attr("fill","url(#gradient-top)").style("pointer-events","none"); @@ -5004,6 +5008,10 @@ RED.view = (function() { .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);}) .on("mouseout",function(d) {portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);}); RED.hooks.trigger("viewAddPort",{node:d,el: this, port: inputGroup[0][0], portType: "input", portIndex: 0}) + } else if (d.__ghostClick) { + inputGroupPorts + .on("mousedown",d.__ghostClick) + .on("touchstart",d.__ghostClick) } } var numOutputs = d.outputs; @@ -5062,6 +5070,9 @@ RED.view = (function() { portPort.addEventListener("touchend", portTouchEndProxy); portPort.addEventListener("mouseover", portMouseOverProxy); portPort.addEventListener("mouseout", portMouseOutProxy); + } else if (d.__ghostClick) { + portPort.addEventListener("mousedown", d.__ghostClick) + portPort.addEventListener("touchstart", d.__ghostClick) } this.appendChild(portGroup); @@ -5363,6 +5374,10 @@ RED.view = (function() { } } }) + } else if (d.__ghostClick) { + d3.select(pathBack) + .on("mousedown",d.__ghostClick) + .on("touchstart",d.__ghostClick) } var pathOutline = document.createElementNS("http://www.w3.org/2000/svg","path"); @@ -6556,6 +6571,11 @@ RED.view = (function() { node.id = nodeConfig.id || node.id node.__ghost = true; node.dirty = true; + if (suggestion.clickToApply) { + node.__ghostClick = function () { + applySuggestedFlow() + } + } nodeMap[node.id] = node if (nodeConfig.wires) { From 63b4615e78ae7a405bd053c5fa24c040ba13f288 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 23 Jun 2025 10:08:27 +0100 Subject: [PATCH 04/15] Add escape to clear suggested flow --- .../editor-client/src/js/ui/typeSearch.js | 5 +++-- .../@node-red/editor-client/src/js/ui/view.js | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js index 8b64a3231..dfaa55e76 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js @@ -539,9 +539,10 @@ RED.typeSearch = (function() { } return { - show: show, + show, refresh: refreshTypeList, - hide: hide + hide, + isVisible: () => visible }; })(); 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 1eec57f5b..ae470d63c 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 @@ -876,6 +876,19 @@ RED.view = (function() { } }) + RED.actions.add("core:apply-suggested-flow", function() { + // TypeSeach will handle this if it is visible; don't want to cause conflicts + if (!RED.typeSearch.isVisible()) { + RED.view.applySuggestedFlow(); + } + }) + RED.actions.add("core:clear-suggested-flow", function() { + // TypeSeach will handle this if it is visible; don't want to cause conflicts + if (!RED.typeSearch.isVisible()) { + RED.view.setSuggestedFlow(null); + } + }) + RED.view.annotations.init(); RED.view.navigator.init(); RED.view.tools.init(); @@ -6613,6 +6626,7 @@ RED.view = (function() { __ghost: true }) } + $(window).on('keyup',disableSuggestedFlowEventHandler); } if (ghostNode) { if (suggestedNodes.length > 0) { @@ -6625,6 +6639,7 @@ RED.view = (function() { } function clearSuggestedFlow () { + $(window).off('keyup', disableSuggestedFlowEventHandler) currentSuggestion = null suggestedNodes = [] suggestedLinks = [] @@ -6670,6 +6685,12 @@ RED.view = (function() { } } + function disableSuggestedFlowEventHandler(evt) { + if (evt.keyCode === 27) { // ESCAPE + RED.actions.invoke("core:clear-suggested-flow") + } + } + return { init: init, state:function(state) { From 99b8b4c91b24658f24634c6f52acd78b14f965af Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 23 Jun 2025 10:14:45 +0100 Subject: [PATCH 05/15] Add tab to apply current suggested flow --- .../node_modules/@node-red/editor-client/src/js/keymap.json | 3 ++- .../node_modules/@node-red/editor-client/src/js/ui/keyboard.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/keymap.json b/packages/node_modules/@node-red/editor-client/src/js/keymap.json index 4cf28d227..a2a19c94b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/keymap.json +++ b/packages/node_modules/@node-red/editor-client/src/js/keymap.json @@ -91,7 +91,8 @@ "alt-shift-w": "core:show-last-hidden-flow", "ctrl-+": "core:zoom-in", "ctrl--": "core:zoom-out", - "ctrl-0": "core:zoom-reset" + "ctrl-0": "core:zoom-reset", + "tab": "core:apply-suggested-flow" }, "red-ui-editor-stack": { "ctrl-enter": "core:confirm-edit-tray", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js b/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js index fdf4fe1ef..a9868bb05 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js @@ -35,6 +35,7 @@ RED.keyboard = (function() { "backspace": 8, "delete": 46, "space": 32, + "tab": 9, ";":186, "=":187, "+":187, // <- QWERTY specific From a1060524d444220656030ecf2163af73e246f7f9 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 23 Jun 2025 11:02:01 +0100 Subject: [PATCH 06/15] Add flow suggestion plugin api --- .../editor-client/src/js/ui/typeSearch.js | 54 ++++++++++--------- .../@node-red/editor-client/src/js/ui/view.js | 7 ++- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js index dfaa55e76..9cedb068f 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js @@ -463,32 +463,34 @@ RED.typeSearch = (function() { let index = 0; - // const suggestionItem = { - // suggestionPlaceholder: true, - // label: 'loading suggestions...', - // separator: true, - // i: index++ - // } - // searchResults.editableList('addItem', suggestionItem); - // setTimeout(function() { - // searchResults.editableList('removeItem', suggestionItem); - - // const suggestedItem = { - // suggestion: true, - // label: 'Change/Debug Combo', - // separator: true, - // i: suggestionItem.i, - // nodes: [ - // { id: 'suggestion-1', type: 'change', x: 0, y: 0, wires:[['suggestion-2']] }, - // { id: 'suggestion-2', type: 'function', outputs: 3, x: 200, y: 0, wires:[['suggestion-3'],['suggestion-4'],['suggestion-6']] }, - // { id: 'suggestion-3', _g: 'suggestion-group-1', type: 'debug', x: 375, y: -40 }, - // { id: 'suggestion-4', _g: 'suggestion-group-1', type: 'debug', x: 375, y: 0 }, - // { id: 'suggestion-5', _g: 'suggestion-group-1', type: 'debug', x: 410, y: 40 }, - // { id: 'suggestion-6', type: 'junction', wires: [['suggestion-5']], x:325, y:40 } - // ] - // } - // searchResults.editableList('addItem', suggestedItem); - // }, 1000) + if (!opts.context?.virtualLink) { + // Check for suggestion plugins + const suggestionPlugins = RED.plugins.getPluginsByType('node-red-flow-suggestion-source'); + if (suggestionPlugins.length > 0) { + const suggestionItem = { + suggestionPlaceholder: true, + label: 'loading suggestions...', // TODO: NLS + separator: true, + i: index++ + } + searchResults.editableList('addItem', suggestionItem); + suggestionPlugins[0].getSuggestions(opts.context).then(function (suggestedFlows) { + searchResults.editableList('removeItem', suggestionItem); + if (!Array.isArray(suggestedFlows)) { + suggestedFlows = [suggestedFlows]; + } + suggestedFlows.forEach(function(suggestion, index) { + const suggestedItem = { + suggestion: true, + separator: index === suggestedFlows.length - 1, + i: suggestionItem.i, + ...suggestion + } + searchResults.editableList('addItem', suggestedItem); + }) + }) + } + } for(i=0;i Date: Tue, 24 Jun 2025 09:58:24 +0100 Subject: [PATCH 07/15] Do not mark workspace dirty when generating names without history --- .../@node-red/editor-client/src/js/ui/view-tools.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index f5e0df05f..4ce791369 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -1147,8 +1147,8 @@ RED.view.tools = (function() { t: 'multi', events: historyEvents }) + RED.nodes.dirty(true) } - RED.nodes.dirty(true) RED.view.redraw() } } From 412d47b3037fba7a30c49d1b585663114d497eab Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 24 Jun 2025 14:10:02 +0100 Subject: [PATCH 08/15] Allow suggestion to be automatically positioned relative to source --- .../@node-red/editor-client/src/js/ui/view.js | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 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 df5c63f67..c2aefa499 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 @@ -6536,6 +6536,24 @@ RED.view = (function() { if (suggestion?.nodes?.length > 0) { const nodeMap = {} const links = [] + const positionOffset = { x: 0, y: 0 } + if (suggestion.source && suggestion.position === 'relative') { + // If the suggestion is relative to a source node, use its position plus a suitable offset + let targetX = suggestion.source.x + (suggestion.source.w || 120) / 2 + 77 + const targetY = suggestion.source.y + // Keep targetY where it is, but ensure targetX is grid aligned + if (snapGrid) { + // This isn't a perfect grid snap, as we don't have the true node width at this point. + // TODO: defer grid snapping until the node is created? + const gridOffset = RED.view.tools.calculateGridSnapOffsets({ x: targetX, y: targetY, w: node_width, h: node_height }); + targetX += gridOffset.x + } + + positionOffset.x = targetX - (suggestion.nodes[0].x || 0) + positionOffset.y = targetY - (suggestion.nodes[0].y || 0) + } + + suggestion.nodes.forEach(nodeConfig => { if (!nodeConfig.type || nodeConfig.type === 'group' || nodeConfig.type === 'subflow' || nodeConfig.type === 'tab') { // A node type we don't support previewing @@ -6543,8 +6561,9 @@ RED.view = (function() { } let node - if (nodeConfig.type === 'junction') { + nodeConfig.x = (nodeConfig.x || 0) + positionOffset.x + nodeConfig.y = (nodeConfig.y || 0) + positionOffset.y node = { _def: {defaults:{}}, type: 'junction', @@ -6565,6 +6584,8 @@ RED.view = (function() { // TODO: unknown node types could happen... return } + nodeConfig.x = (nodeConfig.x || 0) + positionOffset.x + nodeConfig.y = (nodeConfig.y || 0) + positionOffset.y const result = createNode(nodeConfig.type, nodeConfig.x, nodeConfig.y) if (!result) { return From 1717e0f39bbd1edc0cc0d1a0cc3da479f0427cbc Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 25 Jun 2025 13:43:13 +0100 Subject: [PATCH 09/15] Remove suggestedFlow actions and hardcode key handling --- .../editor-client/src/js/keymap.json | 3 +- .../@node-red/editor-client/src/js/ui/view.js | 40 +++++++++---------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/keymap.json b/packages/node_modules/@node-red/editor-client/src/js/keymap.json index a2a19c94b..4cf28d227 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/keymap.json +++ b/packages/node_modules/@node-red/editor-client/src/js/keymap.json @@ -91,8 +91,7 @@ "alt-shift-w": "core:show-last-hidden-flow", "ctrl-+": "core:zoom-in", "ctrl--": "core:zoom-out", - "ctrl-0": "core:zoom-reset", - "tab": "core:apply-suggested-flow" + "ctrl-0": "core:zoom-reset" }, "red-ui-editor-stack": { "ctrl-enter": "core:confirm-edit-tray", 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 c2aefa499..eee833c31 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 @@ -876,19 +876,6 @@ RED.view = (function() { } }) - RED.actions.add("core:apply-suggested-flow", function() { - // TypeSeach will handle this if it is visible; don't want to cause conflicts - if (!RED.typeSearch.isVisible()) { - RED.view.applySuggestedFlow(); - } - }) - RED.actions.add("core:clear-suggested-flow", function() { - // TypeSeach will handle this if it is visible; don't want to cause conflicts - if (!RED.typeSearch.isVisible()) { - RED.view.setSuggestedFlow(null); - } - }) - RED.view.annotations.init(); RED.view.navigator.init(); RED.view.tools.init(); @@ -6526,6 +6513,7 @@ RED.view = (function() { * @param {Object} suggestion - The suggestion object */ function setSuggestedFlow (suggestion) { + $(window).off('keydown.suggestedFlow') if (!currentSuggestion && !suggestion) { // Avoid unnecessary redraws return @@ -6650,7 +6638,22 @@ RED.view = (function() { __ghost: true }) } - $(window).on('keyup',disableSuggestedFlowEventHandler); + if (!RED.typeSearch.isVisible()) { + $(window).on('keydown.suggestedFlow', function (evt) { + console.log('kd') + if (evt.keyCode === 9) { // tab + applySuggestedFlow(); + } else { + clearSuggestedFlow(); + RED.view.redraw(true); + } + }); + } + if (suggestion.clickToApply) { + $(window).on('mousedown.suggestedFlow', function (evnt) { + clearSuggestedFlow(); + }) + } } if (ghostNode) { if (suggestedNodes.length > 0) { @@ -6663,7 +6666,8 @@ RED.view = (function() { } function clearSuggestedFlow () { - $(window).off('keyup', disableSuggestedFlowEventHandler) + $(window).off('mousedown.suggestedFlow'); + $(window).off('keydown.suggestedFlow') currentSuggestion = null suggestedNodes = [] suggestedLinks = [] @@ -6709,12 +6713,6 @@ RED.view = (function() { } } - function disableSuggestedFlowEventHandler(evt) { - if (evt.keyCode === 27) { // ESCAPE - RED.actions.invoke("core:clear-suggested-flow") - } - } - return { init: init, state:function(state) { From e65728e94bc837c911a46e1d69d8cdd4d127b338 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 25 Jun 2025 13:49:50 +0100 Subject: [PATCH 10/15] Better grid snapping for suggestion auto-placement --- 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 eee833c31..b3d63cc0e 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 @@ -6527,7 +6527,7 @@ RED.view = (function() { const positionOffset = { x: 0, y: 0 } if (suggestion.source && suggestion.position === 'relative') { // If the suggestion is relative to a source node, use its position plus a suitable offset - let targetX = suggestion.source.x + (suggestion.source.w || 120) / 2 + 77 + let targetX = suggestion.source.x + (suggestion.source.w || 120) + (3 * gridSize) const targetY = suggestion.source.y // Keep targetY where it is, but ensure targetX is grid aligned if (snapGrid) { From b2d6eeadbde7a5d901bd42607f9f7e449f408dfc Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 25 Jun 2025 14:18:44 +0100 Subject: [PATCH 11/15] Fix deleting suggested node with wire --- .../node_modules/@node-red/editor-client/src/js/ui/view.js | 6 +++--- 1 file changed, 3 insertions(+), 3 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 b3d63cc0e..35d49e01b 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 @@ -1472,8 +1472,9 @@ RED.view = (function() { if (quickAddLink) { // Need to attach the link to the suggestion. This is assumed to be the first // node in the array - as that's the one we've focussed on. - const targetNode = importResult.nodeMap[type.nodes[0].id] - + // We need to map from the suggested node's id to the imported node's id, + // and then get the proxy object for the node + const targetNode = RED.nodes.node(importResult.nodeMap[type.nodes[0].id].id) const drag_line = quickAddLink; let src = null, dst, src_port; if (drag_line.portType === PORT_TYPE_OUTPUT && (targetNode.inputs > 0 || drag_line.virtualLink) ) { @@ -6640,7 +6641,6 @@ RED.view = (function() { } if (!RED.typeSearch.isVisible()) { $(window).on('keydown.suggestedFlow', function (evt) { - console.log('kd') if (evt.keyCode === 9) { // tab applySuggestedFlow(); } else { From 1552fe20c5a28cf0b83693319706a680746ade3b Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 25 Jun 2025 14:21:09 +0100 Subject: [PATCH 12/15] Use type label if suggestion doesn't provide one --- .../@node-red/editor-client/src/js/ui/typeSearch.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js index 9cedb068f..ceff6f5d5 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js @@ -486,6 +486,9 @@ RED.typeSearch = (function() { i: suggestionItem.i, ...suggestion } + if (!suggestion.label && suggestion.nodes && suggestion.nodes.length === 1 && suggestion.nodes[0].type) { + suggestedItem.label = getTypeLabel(suggestion.nodes[0].type, RED.nodes.getType(suggestion.nodes[0].type)); + } searchResults.editableList('addItem', suggestedItem); }) }) From 303e606a74c2df1bcbc9d0a77888c3a0c70adc00 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 25 Jun 2025 17:24:58 +0100 Subject: [PATCH 13/15] NLS the suggestion label --- .../@node-red/editor-client/locales/en-US/editor.json | 1 + .../@node-red/editor-client/src/js/ui/typeSearch.js | 3 +-- 2 files changed, 2 insertions(+), 2 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 1aa92c370..57b9e681e 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 @@ -573,6 +573,7 @@ "filter": "filter nodes", "search": "search modules", "addCategory": "Add new...", + "loadingSuggestions": "Loading suggestions...", "label": { "subflows": "subflows", "network": "network", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js index ceff6f5d5..5a959961b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js @@ -182,7 +182,6 @@ RED.typeSearch = (function() { addItem: function(container, i, nodeItem) { // nodeItem can take multiple forms // - A node type: {type: "inject", def: RED.nodes.getType("inject"), label: "Inject"} - // - A flow suggestion: { suggestion: true, nodes: [] } // - A placeholder suggestion: { suggestionPlaceholder: true, label: 'loading suggestions...' } let nodeDef = nodeItem.def; @@ -469,7 +468,7 @@ RED.typeSearch = (function() { if (suggestionPlugins.length > 0) { const suggestionItem = { suggestionPlaceholder: true, - label: 'loading suggestions...', // TODO: NLS + label: RED._('palette.loadingSuggestions'), separator: true, i: index++ } From 0b172be7a5a2eb9d661c3cc4552d6fd012e9ad20 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 26 Jun 2025 10:30:25 +0100 Subject: [PATCH 14/15] Don't require x/y when providing single node in suggestion api --- .../@node-red/editor-client/src/js/ui/view.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 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 35d49e01b..65f947d5f 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 @@ -1747,15 +1747,11 @@ RED.view = (function() { suggest: function (suggestion) { if (suggestion?.nodes?.length > 0) { // Reposition the suggestion relative to the existing ghost node position - const deltaX = suggestion.nodes[0].x - point[0] - const deltaY = suggestion.nodes[0].y - point[1] + const deltaX = (suggestion.nodes[0].x || 0) - point[0] + const deltaY = (suggestion.nodes[0].y || 0) - point[1] suggestion.nodes.forEach(node => { - if (Object.hasOwn(node, 'x')) { - node.x = node.x - deltaX - } - if (Object.hasOwn(node, 'y')) { - node.y = node.y - deltaY - } + node.x = (node.x || 0) - deltaX + node.y = (node.y || 0) - deltaY }) } setSuggestedFlow(suggestion); From 2d67d082c698da949a4079e0d4c4554b69fda228 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 26 Jun 2025 10:36:06 +0100 Subject: [PATCH 15/15] On update suggestion view when it changes --- .../@node-red/editor-client/src/js/ui/typeSearch.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js index 5a959961b..56f4bf145 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js @@ -261,7 +261,12 @@ RED.typeSearch = (function() { } + let activeSuggestion function updateSuggestion(nodeItem) { + if (nodeItem === activeSuggestion) { + return + } + activeSuggestion = nodeItem if (suggestCallback) { if (!nodeItem) { suggestCallback(null); @@ -318,9 +323,7 @@ RED.typeSearch = (function() { } visible = true; } else { - if (suggestCallback) { - suggestCallback(null); - } + updateSuggestion(null) dialog.hide(); searchResultsDiv.hide(); } @@ -361,9 +364,7 @@ RED.typeSearch = (function() { },200); } function hide(fast) { - if (suggestCallback) { - suggestCallback(null); - } + updateSuggestion(null) if (visible) { visible = false; if (dialog !== null) {