From 43df2318d49de52e2b15ba32aed0af101ecbfa80 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 27 Jan 2025 16:36:57 +0000 Subject: [PATCH 1/9] Show subflow flow context under node section of sidebar --- packages/node_modules/@node-red/runtime/lib/api/context.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/runtime/lib/api/context.js b/packages/node_modules/@node-red/runtime/lib/api/context.js index f27075577..a5612997a 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/context.js +++ b/packages/node_modules/@node-red/runtime/lib/api/context.js @@ -96,7 +96,11 @@ var api = module.exports = { } else if (scope === 'node') { var node = runtime.nodes.getNode(id); if (node) { - ctx = node.context(); + if (/^subflow:/.test(node.type)) { + ctx = runtime.nodes.getContext(node.id); + } else { + ctx = node.context(); + } } } if (ctx) { From ffdbd94927887a721f6ccd785a1af1d8c47aaf9f Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Jan 2025 14:22:44 +0000 Subject: [PATCH 2/9] Handle dragging node into group and splicing link at same time Fixes #5026 --- .../@node-red/editor-client/src/js/ui/view.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 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 92d4593c6..198af0850 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 @@ -2170,19 +2170,24 @@ RED.view = (function() { n.n.moved = true; } } - - // Check to see if we need to splice a link + // If a node has moved and ends up being spliced into a link, keep + // track of which historyEvent to add the splice info to + let targetSpliceEvent = null if (moveEvent.nodes.length > 0) { historyEvent.events.push(moveEvent) - if (activeSpliceLink) { - var linkToSplice = d3.select(activeSpliceLink).data()[0]; - spliceLink(linkToSplice, movingSet.get(0).n, moveEvent) - } + targetSpliceEvent = moveEvent } if (moveAndChangedGroupEvent.nodes.length > 0) { historyEvent.events.push(moveAndChangedGroupEvent) + targetSpliceEvent = moveAndChangedGroupEvent } - + // activeSpliceLink will only be set if the movingSet has a single + // node that is able to splice. + if (targetSpliceEvent && activeSpliceLink) { + var linkToSplice = d3.select(activeSpliceLink).data()[0]; + spliceLink(linkToSplice, movingSet.get(0).n, targetSpliceEvent) + } + // Only continue if something has moved if (historyEvent.events.length > 0) { RED.nodes.dirty(true); From ca61efc98698480e4e938295d7a876cb1a58dc86 Mon Sep 17 00:00:00 2001 From: matiseni51 Date: Sat, 1 Feb 2025 11:44:57 +0100 Subject: [PATCH 3/9] feat: Add custom label for default deploy button in settings.editorTheme --- .../node_modules/@node-red/editor-client/src/js/ui/deploy.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js index ed503bcfe..d77a4dba2 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js @@ -44,6 +44,7 @@ RED.deploy = (function() { /** * options: * type: "default" - Button with drop-down options - no further customisation available + * label: the text to display - default: "Deploy" * type: "simple" - Button without dropdown. Customisations: * label: the text to display - default: "Deploy" * icon : the icon to use. Null removes the icon. default: "red/images/deploy-full-o.svg" @@ -51,13 +52,14 @@ RED.deploy = (function() { function init(options) { options = options || {}; var type = options.type || "default"; + var label = options.label || RED._("deploy.deploy"); if (type == "default") { $('
  • '+ ''+ ''+ ' '+ - ''+RED._("deploy.deploy")+''+ + ''+label+''+ ''+ ''+ ''+ @@ -78,7 +80,6 @@ RED.deploy = (function() { mainMenuItems.push({id:"deploymenu-item-reload", icon:"red/images/deploy-reload.svg",label:RED._("deploy.restartFlows"),sublabel:RED._("deploy.restartFlowsDesc"),onselect:"core:restart-flows"}) RED.menu.init({id:"red-ui-header-button-deploy-options", options: mainMenuItems }); } else if (type == "simple") { - var label = options.label || RED._("deploy.deploy"); var icon = 'red/images/deploy-full-o.svg'; if (options.hasOwnProperty('icon')) { icon = options.icon; From 7d9e09f5a7abad231a8f0e2bc86690e56cfc5ad9 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 10 Feb 2025 16:23:13 +0000 Subject: [PATCH 4/9] Handle long auto-complete suggests Fixes #5028 --- .../src/js/ui/common/autoComplete.js | 2 +- .../src/js/ui/common/typedInput.js | 42 ++++++++++++------- .../src/sass/ui/common/autoComplete.scss | 11 +++++ .../@node-red/runtime/lib/api/context.js | 20 +++++++-- 4 files changed, 56 insertions(+), 19 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/autoComplete.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/autoComplete.js index a3ce0bcd1..a4de3f2a9 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/autoComplete.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/autoComplete.js @@ -61,7 +61,7 @@ } this.menu = RED.popover.menu({ tabSelect: true, - width: 300, + width: Math.max(300, this.element.width()), maxHeight: 200, class: "red-ui-autoComplete-container", options: completions, diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js index 47355565f..22e509d08 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js @@ -63,6 +63,7 @@ pre: value.substring(0,idx), match: value.substring(idx,idx+len), post: value.substring(idx+len), + exact: idx === 0 && value.length === searchValue.length } } function generateSpans(match) { @@ -83,7 +84,7 @@ const srcMatch = getMatch(optSrc, val); if (valMatch.found || srcMatch.found) { const element = $('
    ',{style: "display: flex"}); - const valEl = $('
    ',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"}); + const valEl = $('
    ',{ class: "red-ui-autoComplete-completion" }); valEl.append(generateSpans(valMatch)); valEl.appendTo(element); if (optSrc) { @@ -159,7 +160,7 @@ if (valMatch.found) { const optSrc = envVarsMap[v] const element = $('
    ',{style: "display: flex"}); - const valEl = $('
    ',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"}); + const valEl = $('
    ',{ class: "red-ui-autoComplete-completion" }); valEl.append(generateSpans(valMatch)) valEl.appendTo(element) @@ -201,7 +202,7 @@ const that = this const getContextKeysFromRuntime = function(scope, store, searchKey, done) { contextKnownKeys[scope] = contextKnownKeys[scope] || {} - contextKnownKeys[scope][store] = contextKnownKeys[scope][store] || new Set() + contextKnownKeys[scope][store] = contextKnownKeys[scope][store] || new Map() if (searchKey.length > 0) { try { RED.utils.normalisePropertyExpression(searchKey) @@ -223,11 +224,12 @@ const result = data[store] || {} const keys = result.keys || [] const keyPrefix = searchKey + (searchKey.length > 0 ? '.' : '') - keys.forEach(key => { + keys.forEach(keyInfo => { + const key = keyInfo.key if (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(key)) { - contextKnownKeys[scope][store].add(keyPrefix + key) + contextKnownKeys[scope][store].set(keyPrefix + key, keyInfo) } else { - contextKnownKeys[scope][store].add(searchKey + "[\""+key.replace(/"/,"\\\"")+"\"]") + contextKnownKeys[scope][store].set(searchKey + "[\""+key.replace(/"/,"\\\"")+"\"]", keyInfo) } }) done() @@ -242,14 +244,14 @@ // Get the flow id of the node we're editing const editStack = RED.editor.getEditStack() if (editStack.length === 0) { - done([]) + done(new Map()) return } const editingNode = editStack.pop() if (editingNode.z) { scope = `${scope}/${editingNode.z}` } else { - done([]) + done(new Map()) return } } @@ -269,17 +271,29 @@ return function(val, done) { getContextKeys(val, function (keys) { const matches = [] - keys.forEach(v => { + keys.forEach((keyInfo, v) => { let optVal = v let valMatch = getMatch(optVal, val); - if (!valMatch.found && val.length > 0 && val.endsWith('.')) { - // Search key ends in '.' - but doesn't match. Check again - // with [" at the end instead so we match bracket notation - valMatch = getMatch(optVal, val.substring(0, val.length - 1) + '["') + if (!valMatch.found && val.length > 0) { + if (val.endsWith('.')) { + // Search key ends in '.' - but doesn't match. Check again + // with [" at the end instead so we match bracket notation + valMatch = getMatch(optVal, val.substring(0, val.length - 1) + '["') + // } else if (val.endsWith('[') && /^array/.test(keyInfo.format)) { + // console.log('this case') + } } if (valMatch.found) { const element = $('
    ',{style: "display: flex"}); - const valEl = $('
    ',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"}); + const valEl = $('
    ',{ class: "red-ui-autoComplete-completion" }); + // if (keyInfo.format) { + // valMatch.post += ' ' + keyInfo.format + // } + if (valMatch.exact && /^array/.test(keyInfo.format)) { + valMatch.post += `[0-${keyInfo.length}]` + optVal += '[' + + } valEl.append(generateSpans(valMatch)) valEl.appendTo(element) matches.push({ diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/autoComplete.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/autoComplete.scss index 0501bb6a2..85cb4f1db 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/autoComplete.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/autoComplete.scss @@ -2,4 +2,15 @@ &.red-ui-popover-panel { border-top: none; } + + +} +.red-ui-autoComplete-completion { + font-family: var(--red-ui-monospace-font); + white-space: nowrap; + overflow: hidden; + flex-grow: 1; + text-overflow: ellipsis; + direction: rtl; + text-align: left; } diff --git a/packages/node_modules/@node-red/runtime/lib/api/context.js b/packages/node_modules/@node-red/runtime/lib/api/context.js index f27075577..c13beb9f6 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/context.js +++ b/packages/node_modules/@node-red/runtime/lib/api/context.js @@ -104,13 +104,25 @@ var api = module.exports = { store = store || availableStores.default; ctx.get(key,store,function(err, v) { if (opts.keysOnly) { + const result = {} if (Array.isArray(v)) { - resolve({ [store]: { format: `array[${v.length}]`}}) + result.format = `array[${v.length}]` } else if (typeof v === 'object') { - resolve({ [store]: { keys: Object.keys(v), format: 'Object' } }) + result.keys = Object.keys(v).map(k => { + if (Array.isArray(v[k])) { + return { key: k, format: `array[${v[k].length}]`, length: v[k].length } + } else if (typeof v[k] === 'object') { + return { key: k, format: 'object' } + } else { + return { key: k } + } + }) + result.format = 'object' } else { - resolve({ [store]: { keys: [] }}) + result.keys = [] } + resolve({ [store]: result }) + return } var encoded = util.encodeObject({msg:v}); if (store !== availableStores.default) { @@ -147,7 +159,7 @@ var api = module.exports = { } return } - result[store] = { keys } + result[store] = { keys: keys.map(key => { return { key }}) } c--; if (c === 0) { if (!errorReported) { From 77c4ccf8fbce63724abe6ec3c80236bba73c9ae5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 10 Feb 2025 16:41:18 +0000 Subject: [PATCH 5/9] Handle undefined username when generating user icon Fixes #5036 --- .../@node-red/editor-client/src/js/multiplayer.js | 7 +++---- .../node_modules/@node-red/editor-client/src/js/user.js | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/multiplayer.js b/packages/node_modules/@node-red/editor-client/src/js/multiplayer.js index b37d90fcb..fed5f518a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/multiplayer.js +++ b/packages/node_modules/@node-red/editor-client/src/js/multiplayer.js @@ -398,14 +398,13 @@ RED.multiplayer = (function () { anonIconBody.setAttribute("d",`M ${radius/2} ${radius/2 + 5} h -2.5 c -2 1 -2 -5 0.5 -4.5 c 2 1 2 1 4 0 c 2.5 -0.5 2.5 5.5 0 4.5 z`); group.appendChild(anonIconBody) } else { - const labelText = user.username ? user.username.substring(0,2) : user const label = document.createElementNS("http://www.w3.org/2000/svg","text"); - if (user.username) { + if (user.username || user.email) { label.setAttribute("class","red-ui-multiplayer-annotation-label"); - label.textContent = user.username.substring(0,2) + label.textContent = (user.username || user.email).substring(0,2) } else { label.setAttribute("class","red-ui-multiplayer-annotation-label red-ui-multiplayer-user-count") - label.textContent = user + label.textContent = 'nr' } label.setAttribute("text-anchor", "middle") label.setAttribute("x",radius/2); diff --git a/packages/node_modules/@node-red/editor-client/src/js/user.js b/packages/node_modules/@node-red/editor-client/src/js/user.js index e2c3cb577..e626f4ec0 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/user.js +++ b/packages/node_modules/@node-red/editor-client/src/js/user.js @@ -351,10 +351,10 @@ RED.user = (function() { userIcon.css({ backgroundImage: "url("+user.image+")", }) - } else if (user.anonymous) { + } else if (user.anonymous || (!user.username && !user.email)) { $('').appendTo(userIcon); } else { - $('').text(user.username.substring(0,2)).appendTo(userIcon); + $('').text((user.username || user.email).substring(0,2)).appendTo(userIcon); } if (user.profileColor !== undefined) { userIcon.addClass('red-ui-user-profile-color-' + user.profileColor) From 4984af48f135f71e27266a677a4650b983d2a55c Mon Sep 17 00:00:00 2001 From: Stephen McLaughlin <44235289+Steve-Mcl@users.noreply.github.com> Date: Wed, 12 Feb 2025 09:42:55 +0000 Subject: [PATCH 6/9] Update monaco.js for symbol error --- .../editor-client/src/js/ui/editors/code-editors/monaco.js | 1 + 1 file changed, 1 insertion(+) 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 b9f586944..b35e60d50 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 @@ -691,6 +691,7 @@ RED.editor.codeEditor.monaco = (function() { 2322, //Type 'unknown' is not assignable to type 'string' 2339, //property does not exist on 2345, //Argument of type xxx is not assignable to parameter of type 'DateTimeFormatOptions' + 2538, //Ignore symbols as index property error. 7043, //i forget what this one is, 80001, //Convert to ES6 module 80004, //JSDoc types may be moved to TypeScript types. From 27463197cdaf4cb8552e5646189e0ca128898ed9 Mon Sep 17 00:00:00 2001 From: aikitori Date: Thu, 13 Feb 2025 08:55:10 +0100 Subject: [PATCH 7/9] Add details for the dynamic subscription to match the English documentation --- .../nodes/locales/de/network/10-mqtt.html | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/locales/de/network/10-mqtt.html b/packages/node_modules/@node-red/nodes/locales/de/network/10-mqtt.html index 2aa31c081..e5c7b5fb5 100644 --- a/packages/node_modules/@node-red/nodes/locales/de/network/10-mqtt.html +++ b/packages/node_modules/@node-red/nodes/locales/de/network/10-mqtt.html @@ -39,10 +39,36 @@
    MQTTv5: Ablaufzeit der Nachricht in Sekunden.

    Details

    -

    Das abonnierte Topic darf MQTT-Platzhalterzeichen (wildcards) enthalten (+ für eine Ebene und # für mehrere Ebenen).

    -

    Dieser Node erfordert eine Verbindung zu einem MQTT-Broker, der über die Auswahlliste selektiert werden kann. - Eine neue Verbindung wird durch Klicken auf das Stiftsymbol erstellt.

    +

    Das abonnierte Topic darf MQTT-Platzhalterzeichen (wildcards) enthalten (+ für eine Ebene und # für mehrere Ebenen).

    +

    Diese Node erfordert eine Verbindung zu einem MQTT-Broker, der über die Auswahlliste selektiert werden kann. Eine neue Verbindung wird durch Klicken auf das Stiftsymbol erstellt.

    Mehrere MQTT-Nodes (in oder out) können bei Bedarf dieselbe Broker-Verbindung nutzen.

    +

    Dynamische Steuerung

    + Die von der Node genutzte Verbindung kann dynamisch gesteuert werden, wenn die MQTT-Node eine der folgenden Nachrichten erhält. Die Payload dieser Nachrichten werden nicht veröffentlicht. +

    Eingangsdaten

    +

    Nur Verfügbar, wenn die Node für dynamische Abonnements konfiguriert wurde.

    +
    +
    action string
    +
    Der Name der Aktion, die die MQTT-Node ausführen soll. Verfügbare Aktionen sind: "connect", "disconnect", "getSubscriptions", "subscribe" und "unsubscribe".
    +
    topic string|object|array
    +
    Bei den Aktionen "subscribe" und "unsubscribe" gibt diese Eigenschaft die MQTT-Topic an. Dabei kann es sich um Folgendes handeln: +
      +
    • eine Zeichenfolge, die den Topic-Filter enthält
    • +
    • ein Objekt mit den Eigenschaften topic und qos
    • +
    • ein Array aus Zeichenfolgen oder Objekten, um mehrere Topics gleichzeitig zu verwalten
    • +
    +
    +
    broker broker
    +
    Für die Aktion "connect" kann diese Eigenschaft jede der einzelnen Broker-Konfigurationseinstellungen überschreiben, einschließlich:
      +
    • broker
    • +
    • port
    • +
    • url - überschreibt Broker/Port, um eine vollständige Verbindungs-URL bereitzustellen
    • +
    • username
    • +
    • password
    • +
    +

    Wenn diese Eigenschaft gesetzt ist und der Broker bereits verbunden ist, wird ein Fehler protokolliert, es sei denn, die Eigenschaft force gesetzt - in diesem Fall wird die Verbindung zum Broker getrennt, die neuen Einstellungen angewendet und erneut verbunden.

    +
    +
    +