@node-red/editor-client 4.1.0-beta.1 → 4.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/red/red.js CHANGED
@@ -6501,29 +6501,29 @@ RED.nodes = (function() {
6501
6501
  // Provide option to install missing modules
6502
6502
  notificationOptions.buttons = [
6503
6503
  {
6504
- text: RED._("palette.editor.manageModules"),
6504
+ text: RED._("palette.editor.installAll"),
6505
6505
  class: "primary",
6506
6506
  click: function(e) {
6507
6507
  unknownNotification.close();
6508
6508
 
6509
6509
  RED.actions.invoke('core:manage-palette', {
6510
- view: 'install',
6511
- filter: '"' + missingModules.join('", "') + '"'
6510
+ autoInstall: true,
6511
+ modules: missingModules.reduce((modules, moduleName) => {
6512
+ modules[moduleName] = options.modules[moduleName];
6513
+ return modules;
6514
+ }, {}),
6512
6515
  });
6513
6516
  }
6514
6517
  },
6515
6518
  {
6516
- text: RED._("palette.editor.installAll"),
6519
+ text: RED._("palette.editor.manageModules"),
6517
6520
  class: "pull-left",
6518
6521
  click: function(e) {
6519
6522
  unknownNotification.close();
6520
6523
 
6521
6524
  RED.actions.invoke('core:manage-palette', {
6522
- autoInstall: true,
6523
- modules: missingModules.reduce((modules, moduleName) => {
6524
- modules[moduleName] = options.modules[moduleName];
6525
- return modules;
6526
- }, {}),
6525
+ view: 'install',
6526
+ filter: '"' + missingModules.join('", "') + '"'
6527
6527
  });
6528
6528
  }
6529
6529
  }
@@ -9864,7 +9864,7 @@ RED.utils = (function() {
9864
9864
 
9865
9865
  renderer.code = function (code, lang) {
9866
9866
  if(lang === "mermaid") {
9867
- return `<pre class='mermaid'>${code}</pre>`;
9867
+ return `<pre style='word-break: unset;' data-c64='${btoa(code)}' class='mermaid'>${code}</pre>`;
9868
9868
  } else {
9869
9869
  return "<pre><code>" +code +"</code></pre>";
9870
9870
  }
@@ -17310,14 +17310,25 @@ RED.stack = (function() {
17310
17310
  * The function must either return auto-complete options, or pass them
17311
17311
  * to the optional 'done' parameter.
17312
17312
  * If the function signature includes 'done', it must be used
17313
+ * The auto-complete options can either be an array of strings, or an array of objects in the form:
17314
+ * {
17315
+ * value: String : the value to insert if selected
17316
+ * label: String|DOM Element : the label to display in the dropdown.
17317
+ * }
17318
+ *
17313
17319
  * minLength: number
17314
17320
  * If `minLength` is 0, pressing down arrow will show the list
17321
+ *
17322
+ * completionPluginType: String
17323
+ * If provided instead of `search`, this will look for any plugins
17324
+ * registered with the given type that implement the `getCompletions` function. This
17325
+ * can be an async function that returns an array of string completions. It does not support
17326
+ * the full options object as above.
17315
17327
  *
17316
- * The auto-complete options should be an array of objects in the form:
17317
- * {
17318
- * value: String : the value to insert if selected
17319
- * label: String|DOM Element : the label to display in the dropdown.
17320
- * }
17328
+ * node: Node
17329
+ * If provided, this will be passed to the `getCompletions` function of the plugin
17330
+ * to allow the plugin to provide context-aware completions.
17331
+ *
17321
17332
  *
17322
17333
  */
17323
17334
 
@@ -17326,6 +17337,54 @@ RED.stack = (function() {
17326
17337
  const that = this;
17327
17338
  this.completionMenuShown = false;
17328
17339
  this.options.minLength = parseInteger(this.options.minLength, 1, 0);
17340
+ if (!this.options.search) {
17341
+ // No search function provided; nothing to provide completions
17342
+ if (this.options.completionPluginType) {
17343
+ const plugins = RED.plugins.getPluginsByType(this.options.completionPluginType)
17344
+ if (plugins.length > 0) {
17345
+ this.options.search = async function (value, done) {
17346
+ // for now, only support a single plugin
17347
+ const promises = plugins.map(plugin => plugin.getCompletions(value, that.options.context))
17348
+ const completions = (await Promise.all(promises)).flat()
17349
+ const results = []
17350
+ completions.forEach(completion => {
17351
+ const element = $('<div>',{style: "display: flex"})
17352
+ const valEl = $('<div/>',{ class: "red-ui-autoComplete-completion" })
17353
+ const valMatch = getMatch(completion, value)
17354
+ if (valMatch.found) {
17355
+ valEl.append(generateSpans(valMatch))
17356
+ valEl.appendTo(element)
17357
+ results.push({
17358
+ value: completion,
17359
+ label: element,
17360
+ match: valMatch
17361
+ })
17362
+ }
17363
+ results.sort((a, b) => {
17364
+ if (a.match.exact && !b.match.exact) {
17365
+ return -1;
17366
+ } else if (!a.match.exact && b.match.exact) {
17367
+ return 1;
17368
+ } else if (a.match.index < b.match.index) {
17369
+ return -1;
17370
+ } else if (a.match.index > b.match.index) {
17371
+ return 1;
17372
+ } else {
17373
+ return 0;
17374
+ }
17375
+ })
17376
+ })
17377
+ done(results)
17378
+ }
17379
+ } else {
17380
+ // No search function and no plugins found
17381
+ return
17382
+ }
17383
+ } else {
17384
+ // No search function and no plugin type provided
17385
+ return
17386
+ }
17387
+ }
17329
17388
  this.options.search = this.options.search || function() { return [] };
17330
17389
  this.element.addClass("red-ui-autoComplete");
17331
17390
  this.element.on("keydown.red-ui-autoComplete", function(evt) {
@@ -17387,6 +17446,11 @@ RED.stack = (function() {
17387
17446
  }
17388
17447
  return
17389
17448
  }
17449
+ if (typeof completions[0] === "string") {
17450
+ completions = completions.map(function(c) {
17451
+ return { value: c, label: c };
17452
+ });
17453
+ }
17390
17454
  if (that.completionMenuShown) {
17391
17455
  that.menu.options(completions);
17392
17456
  } else {
@@ -17418,6 +17482,27 @@ RED.stack = (function() {
17418
17482
  if(isNaN(n) || n < min || n > max) { n = def || 0; }
17419
17483
  return n;
17420
17484
  }
17485
+ // TODO: this is copied from typedInput - should be a shared utility
17486
+ function getMatch(value, searchValue) {
17487
+ const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
17488
+ const len = idx > -1 ? searchValue.length : 0;
17489
+ return {
17490
+ index: idx,
17491
+ found: idx > -1,
17492
+ pre: value.substring(0,idx),
17493
+ match: value.substring(idx,idx+len),
17494
+ post: value.substring(idx+len),
17495
+ exact: idx === 0 && value.length === searchValue.length
17496
+ }
17497
+ }
17498
+ // TODO: this is copied from typedInput - should be a shared utility
17499
+ function generateSpans(match) {
17500
+ const els = [];
17501
+ if(match.pre) { els.push($('<span/>').text(match.pre)); }
17502
+ if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
17503
+ if(match.post) { els.push($('<span/>').text(match.post)); }
17504
+ return els;
17505
+ }
17421
17506
  })(jQuery);
17422
17507
  ;RED.actions = (function() {
17423
17508
  var actions = {
@@ -20657,6 +20742,7 @@ RED.keyboard = (function() {
20657
20742
  "backspace": 8,
20658
20743
  "delete": 46,
20659
20744
  "space": 32,
20745
+ "tab": 9,
20660
20746
  ";":186,
20661
20747
  "=":187,
20662
20748
  "+":187, // <- QWERTY specific
@@ -23195,7 +23281,9 @@ RED.view = (function() {
23195
23281
  return;
23196
23282
  }
23197
23283
  var historyEvent = result.historyEvent;
23198
- var nn = RED.nodes.add(result.node, { source: 'palette' });
23284
+ const linkToSplice = $(ui.helper).data("splice");
23285
+
23286
+ var nn = RED.nodes.add(result.node, { source: 'palette', splice: !!linkToSplice });
23199
23287
 
23200
23288
  var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
23201
23289
  if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
@@ -23254,7 +23342,6 @@ RED.view = (function() {
23254
23342
  nn.y -= gridOffset.y;
23255
23343
  }
23256
23344
 
23257
- var linkToSplice = $(ui.helper).data("splice");
23258
23345
  if (linkToSplice) {
23259
23346
  spliceLink(linkToSplice, nn, historyEvent)
23260
23347
  }
@@ -23937,10 +24024,12 @@ RED.view = (function() {
23937
24024
  var lastAddedX;
23938
24025
  var lastAddedWidth;
23939
24026
 
23940
- const context = {}
24027
+ const context = {
24028
+ workspace: RED.workspaces.active()
24029
+ }
23941
24030
 
23942
24031
  if (quickAddLink) {
23943
- context.source = quickAddLink.node.id;
24032
+ context.source = quickAddLink.node;
23944
24033
  context.sourcePort = quickAddLink.port;
23945
24034
  context.sourcePortType = quickAddLink.portType;
23946
24035
  if (quickAddLink?.virtualLink) {
@@ -23956,6 +24045,7 @@ RED.view = (function() {
23956
24045
  y:clientY-mainPos.top+ node_height/2 + 5 - (oy-point[1]),
23957
24046
  disableFocus: touchTrigger,
23958
24047
  filter: filter,
24048
+ context,
23959
24049
  move: function(dx,dy) {
23960
24050
  if (ghostNode) {
23961
24051
  var pos = d3.transform(ghostNode.attr("transform")).translate;
@@ -24004,8 +24094,9 @@ RED.view = (function() {
24004
24094
  if (quickAddLink) {
24005
24095
  // Need to attach the link to the suggestion. This is assumed to be the first
24006
24096
  // node in the array - as that's the one we've focussed on.
24007
- const targetNode = importResult.nodeMap[type.nodes[0].id]
24008
-
24097
+ // We need to map from the suggested node's id to the imported node's id,
24098
+ // and then get the proxy object for the node
24099
+ const targetNode = RED.nodes.node(importResult.nodeMap[type.nodes[0].id].id)
24009
24100
  const drag_line = quickAddLink;
24010
24101
  let src = null, dst, src_port;
24011
24102
  if (drag_line.portType === PORT_TYPE_OUTPUT && (targetNode.inputs > 0 || drag_line.virtualLink) ) {
@@ -24091,7 +24182,7 @@ RED.view = (function() {
24091
24182
  if (nn.type === 'junction') {
24092
24183
  nn = RED.nodes.addJunction(nn);
24093
24184
  } else {
24094
- nn = RED.nodes.add(nn, { source: 'typeSearch' });
24185
+ nn = RED.nodes.add(nn, { source: 'typeSearch', splice: !!linkToSplice });
24095
24186
  }
24096
24187
  if (quickAddLink) {
24097
24188
  var drag_line = quickAddLink;
@@ -24278,15 +24369,11 @@ RED.view = (function() {
24278
24369
  suggest: function (suggestion) {
24279
24370
  if (suggestion?.nodes?.length > 0) {
24280
24371
  // Reposition the suggestion relative to the existing ghost node position
24281
- const deltaX = suggestion.nodes[0].x - point[0]
24282
- const deltaY = suggestion.nodes[0].y - point[1]
24372
+ const deltaX = (suggestion.nodes[0].x || 0) - point[0]
24373
+ const deltaY = (suggestion.nodes[0].y || 0) - point[1]
24283
24374
  suggestion.nodes.forEach(node => {
24284
- if (Object.hasOwn(node, 'x')) {
24285
- node.x = node.x - deltaX
24286
- }
24287
- if (Object.hasOwn(node, 'y')) {
24288
- node.y = node.y - deltaY
24289
- }
24375
+ node.x = (node.x || 0) - deltaX
24376
+ node.y = (node.y || 0) - deltaY
24290
24377
  })
24291
24378
  }
24292
24379
  setSuggestedFlow(suggestion);
@@ -27297,6 +27384,10 @@ RED.view = (function() {
27297
27384
  .on("touchend",nodeTouchEnd)
27298
27385
  .on("mouseover",nodeMouseOver)
27299
27386
  .on("mouseout",nodeMouseOut);
27387
+ } else if (d.__ghostClick) {
27388
+ d3.select(mainRect)
27389
+ .on("mousedown",d.__ghostClick)
27390
+ .on("touchstart",d.__ghostClick)
27300
27391
  }
27301
27392
  nodeContents.appendChild(mainRect);
27302
27393
  //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");
@@ -27539,6 +27630,10 @@ RED.view = (function() {
27539
27630
  .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);})
27540
27631
  .on("mouseout",function(d) {portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);});
27541
27632
  RED.hooks.trigger("viewAddPort",{node:d,el: this, port: inputGroup[0][0], portType: "input", portIndex: 0})
27633
+ } else if (d.__ghostClick) {
27634
+ inputGroupPorts
27635
+ .on("mousedown",d.__ghostClick)
27636
+ .on("touchstart",d.__ghostClick)
27542
27637
  }
27543
27638
  }
27544
27639
  var numOutputs = d.outputs;
@@ -27597,6 +27692,9 @@ RED.view = (function() {
27597
27692
  portPort.addEventListener("touchend", portTouchEndProxy);
27598
27693
  portPort.addEventListener("mouseover", portMouseOverProxy);
27599
27694
  portPort.addEventListener("mouseout", portMouseOutProxy);
27695
+ } else if (d.__ghostClick) {
27696
+ portPort.addEventListener("mousedown", d.__ghostClick)
27697
+ portPort.addEventListener("touchstart", d.__ghostClick)
27600
27698
  }
27601
27699
 
27602
27700
  this.appendChild(portGroup);
@@ -27898,6 +27996,10 @@ RED.view = (function() {
27898
27996
  }
27899
27997
  }
27900
27998
  })
27999
+ } else if (d.__ghostClick) {
28000
+ d3.select(pathBack)
28001
+ .on("mousedown",d.__ghostClick)
28002
+ .on("touchstart",d.__ghostClick)
27901
28003
  }
27902
28004
 
27903
28005
  var pathOutline = document.createElementNS("http://www.w3.org/2000/svg","path");
@@ -28914,10 +29016,10 @@ RED.view = (function() {
28914
29016
  nn.w = RED.view.node_width;
28915
29017
  nn.h = Math.max(RED.view.node_height, (nn.outputs || 0) * 15);
28916
29018
  nn.resize = true;
28917
- if (x != null && typeof x == "number" && x >= 0) {
29019
+ if (x != null && typeof x == "number") {
28918
29020
  nn.x = x;
28919
29021
  }
28920
- if (y != null && typeof y == "number" && y >= 0) {
29022
+ if (y != null && typeof y == "number") {
28921
29023
  nn.y = y;
28922
29024
  }
28923
29025
  var historyEvent = {
@@ -29007,7 +29109,9 @@ RED.view = (function() {
29007
29109
  * x: 0,
29008
29110
  * y: 0,
29009
29111
  * }
29010
- * ]
29112
+ * ],
29113
+ * "source": <sourceNode>,
29114
+ * "sourcePort": <sourcePort>,
29011
29115
  * }
29012
29116
  * If `nodes` is a single node without an id property, it will be generated
29013
29117
  * using its default properties.
@@ -29015,6 +29119,9 @@ RED.view = (function() {
29015
29119
  * If `nodes` has multiple, they must all have ids and will be assumed to be 'importable'.
29016
29120
  * In other words, a piece of valid flow json.
29017
29121
  *
29122
+ * `source`/`sourcePort` are option and used to indicate a node the suggestion should be connected to.
29123
+ * If provided, a ghost wire will be added between the source and the first node in the suggestion.
29124
+ *
29018
29125
  * Limitations:
29019
29126
  * - does not support groups, subflows or whole tabs
29020
29127
  * - does not support config nodes
@@ -29025,6 +29132,7 @@ RED.view = (function() {
29025
29132
  * @param {Object} suggestion - The suggestion object
29026
29133
  */
29027
29134
  function setSuggestedFlow (suggestion) {
29135
+ $(window).off('keydown.suggestedFlow')
29028
29136
  if (!currentSuggestion && !suggestion) {
29029
29137
  // Avoid unnecessary redraws
29030
29138
  return
@@ -29035,6 +29143,24 @@ RED.view = (function() {
29035
29143
  if (suggestion?.nodes?.length > 0) {
29036
29144
  const nodeMap = {}
29037
29145
  const links = []
29146
+ const positionOffset = { x: 0, y: 0 }
29147
+ if (suggestion.source && suggestion.position === 'relative') {
29148
+ // If the suggestion is relative to a source node, use its position plus a suitable offset
29149
+ let targetX = suggestion.source.x + (suggestion.source.w || 120) + (3 * gridSize)
29150
+ const targetY = suggestion.source.y
29151
+ // Keep targetY where it is, but ensure targetX is grid aligned
29152
+ if (snapGrid) {
29153
+ // This isn't a perfect grid snap, as we don't have the true node width at this point.
29154
+ // TODO: defer grid snapping until the node is created?
29155
+ const gridOffset = RED.view.tools.calculateGridSnapOffsets({ x: targetX, y: targetY, w: node_width, h: node_height });
29156
+ targetX += gridOffset.x
29157
+ }
29158
+
29159
+ positionOffset.x = targetX - (suggestion.nodes[0].x || 0)
29160
+ positionOffset.y = targetY - (suggestion.nodes[0].y || 0)
29161
+ }
29162
+
29163
+
29038
29164
  suggestion.nodes.forEach(nodeConfig => {
29039
29165
  if (!nodeConfig.type || nodeConfig.type === 'group' || nodeConfig.type === 'subflow' || nodeConfig.type === 'tab') {
29040
29166
  // A node type we don't support previewing
@@ -29042,8 +29168,9 @@ RED.view = (function() {
29042
29168
  }
29043
29169
 
29044
29170
  let node
29045
-
29046
29171
  if (nodeConfig.type === 'junction') {
29172
+ nodeConfig.x = (nodeConfig.x || 0) + positionOffset.x
29173
+ nodeConfig.y = (nodeConfig.y || 0) + positionOffset.y
29047
29174
  node = {
29048
29175
  _def: {defaults:{}},
29049
29176
  type: 'junction',
@@ -29064,6 +29191,8 @@ RED.view = (function() {
29064
29191
  // TODO: unknown node types could happen...
29065
29192
  return
29066
29193
  }
29194
+ nodeConfig.x = (nodeConfig.x || 0) + positionOffset.x
29195
+ nodeConfig.y = (nodeConfig.y || 0) + positionOffset.y
29067
29196
  const result = createNode(nodeConfig.type, nodeConfig.x, nodeConfig.y)
29068
29197
  if (!result) {
29069
29198
  return
@@ -29086,6 +29215,11 @@ RED.view = (function() {
29086
29215
  node.id = nodeConfig.id || node.id
29087
29216
  node.__ghost = true;
29088
29217
  node.dirty = true;
29218
+ if (suggestion.clickToApply) {
29219
+ node.__ghostClick = function () {
29220
+ applySuggestedFlow()
29221
+ }
29222
+ }
29089
29223
  nodeMap[node.id] = node
29090
29224
 
29091
29225
  if (nodeConfig.wires) {
@@ -29114,6 +29248,30 @@ RED.view = (function() {
29114
29248
  suggestedLinks.push(link)
29115
29249
  }
29116
29250
  })
29251
+ if (suggestion.source && suggestedNodes[0]?._def?.inputs > 0) {
29252
+ suggestedLinks.push({
29253
+ source: suggestion.source,
29254
+ sourcePort: suggestion.sourcePort || 0,
29255
+ target: suggestedNodes[0],
29256
+ targetPort: 0,
29257
+ __ghost: true
29258
+ })
29259
+ }
29260
+ if (!RED.typeSearch.isVisible()) {
29261
+ $(window).on('keydown.suggestedFlow', function (evt) {
29262
+ if (evt.keyCode === 9) { // tab
29263
+ applySuggestedFlow();
29264
+ } else {
29265
+ clearSuggestedFlow();
29266
+ RED.view.redraw(true);
29267
+ }
29268
+ });
29269
+ }
29270
+ if (suggestion.clickToApply) {
29271
+ $(window).on('mousedown.suggestedFlow', function (evnt) {
29272
+ clearSuggestedFlow();
29273
+ })
29274
+ }
29117
29275
  }
29118
29276
  if (ghostNode) {
29119
29277
  if (suggestedNodes.length > 0) {
@@ -29126,6 +29284,8 @@ RED.view = (function() {
29126
29284
  }
29127
29285
 
29128
29286
  function clearSuggestedFlow () {
29287
+ $(window).off('mousedown.suggestedFlow');
29288
+ $(window).off('keydown.suggestedFlow')
29129
29289
  currentSuggestion = null
29130
29290
  suggestedNodes = []
29131
29291
  suggestedLinks = []
@@ -29134,14 +29294,40 @@ RED.view = (function() {
29134
29294
  function applySuggestedFlow () {
29135
29295
  if (currentSuggestion && currentSuggestion.nodes) {
29136
29296
  const nodesToImport = currentSuggestion.nodes
29297
+ const sourceNode = currentSuggestion.source
29298
+ const sourcePort = currentSuggestion.sourcePort || 0
29137
29299
  setSuggestedFlow(null)
29138
- return importNodes(nodesToImport, {
29300
+ const result = importNodes(nodesToImport, {
29139
29301
  generateIds: true,
29140
29302
  touchImport: true,
29141
29303
  notify: false,
29142
29304
  // Ensure the node gets all of its defaults applied
29143
29305
  applyNodeDefaults: true
29144
29306
  })
29307
+ if (sourceNode) {
29308
+ const firstNode = result.nodeMap[nodesToImport[0].id]
29309
+ if (firstNode && firstNode._def?.inputs > 0) {
29310
+ // Connect the source node to the first node in the suggestion
29311
+ const link = {
29312
+ source: sourceNode,
29313
+ target: RED.nodes.node(firstNode.id),
29314
+ sourcePort: sourcePort,
29315
+ targetPort: 0
29316
+ };
29317
+ RED.nodes.addLink(link)
29318
+ let historyEvent = RED.history.peek();
29319
+ if (historyEvent.t === "multi") {
29320
+ historyEvent = historyEvent.events.find(e => e.t === "add")
29321
+ }
29322
+ if (historyEvent) {
29323
+ historyEvent.links = historyEvent.links || [];
29324
+ historyEvent.links.push(link);
29325
+ }
29326
+ RED.view.redraw(true);
29327
+ }
29328
+ }
29329
+
29330
+ return result
29145
29331
  }
29146
29332
  }
29147
29333
 
@@ -30900,8 +31086,8 @@ RED.view.tools = (function() {
30900
31086
  t: 'multi',
30901
31087
  events: historyEvents
30902
31088
  })
31089
+ RED.nodes.dirty(true)
30903
31090
  }
30904
- RED.nodes.dirty(true)
30905
31091
  RED.view.redraw()
30906
31092
  }
30907
31093
  }
@@ -31562,7 +31748,7 @@ RED.palette = (function() {
31562
31748
  getNodeCount: function (visibleOnly) {
31563
31749
  const nodes = catDiv.find(".red-ui-palette-node")
31564
31750
  if (visibleOnly) {
31565
- return nodes.filter(function() { return $(this).css('display') !== 'none'}).length
31751
+ return nodes.filter(function() { return $(this).attr("data-filter") !== "true"}).length
31566
31752
  } else {
31567
31753
  return nodes.length
31568
31754
  }
@@ -32054,8 +32240,10 @@ RED.palette = (function() {
32054
32240
  var currentLabel = $(el).attr("data-palette-label");
32055
32241
  var type = $(el).attr("data-palette-type");
32056
32242
  if (val === "" || re.test(type) || re.test(currentLabel)) {
32243
+ $(el).attr("data-filter", null)
32057
32244
  $(this).show();
32058
32245
  } else {
32246
+ $(el).attr("data-filter", "true")
32059
32247
  $(this).hide();
32060
32248
  }
32061
32249
  });
@@ -35691,6 +35879,11 @@ RED.palette.editor = (function() {
35691
35879
  });
35692
35880
 
35693
35881
  RED.events.on('registry:module-updated', function(ns) {
35882
+ if (nodeEntries[ns.module]) {
35883
+ // Set the node/plugin as updated
35884
+ nodeEntries[ns.module].info.pending_version = ns.version;
35885
+ }
35886
+
35694
35887
  refreshNodeModule(ns.module);
35695
35888
  refreshUpdateStatus();
35696
35889
  });
@@ -37827,6 +38020,7 @@ RED.editor = (function() {
37827
38020
 
37828
38021
  function showEditDialog(node, defaultTab) {
37829
38022
  if (buildingEditDialog) { return }
38023
+ if (editStack.includes(node)) { return }
37830
38024
  buildingEditDialog = true;
37831
38025
  if (node.z && RED.workspaces.isLocked(node.z)) { return }
37832
38026
  var editing_node = node;
@@ -38144,6 +38338,7 @@ RED.editor = (function() {
38144
38338
  var editing_config_node = RED.nodes.node(id);
38145
38339
  var activeEditPanes = [];
38146
38340
 
38341
+ if (editStack.includes(editing_config_node)) { return }
38147
38342
  if (editing_config_node && editing_config_node.z && RED.workspaces.isLocked(editing_config_node.z)) { return }
38148
38343
 
38149
38344
  var configNodeScope = ""; // default to global
@@ -38587,6 +38782,7 @@ RED.editor = (function() {
38587
38782
 
38588
38783
  function showEditSubflowDialog(subflow, defaultTab) {
38589
38784
  if (buildingEditDialog) { return }
38785
+ if (editStack.includes(subflow)) { return }
38590
38786
  buildingEditDialog = true;
38591
38787
 
38592
38788
  editStack.push(subflow);
@@ -38803,6 +38999,7 @@ RED.editor = (function() {
38803
38999
 
38804
39000
  function showEditGroupDialog(group, defaultTab) {
38805
39001
  if (buildingEditDialog) { return }
39002
+ if (editStack.includes(group)) { return }
38806
39003
  buildingEditDialog = true;
38807
39004
  if (group.z && RED.workspaces.isLocked(group.z)) { return }
38808
39005
  var editing_node = group;
@@ -38917,6 +39114,7 @@ RED.editor = (function() {
38917
39114
 
38918
39115
  function showEditFlowDialog(workspace, defaultTab) {
38919
39116
  if (buildingEditDialog) { return }
39117
+ if (editStack.includes(workspace)) { return }
38920
39118
  buildingEditDialog = true;
38921
39119
  var activeEditPanes = [];
38922
39120
  RED.view.state(RED.state.EDITING);
@@ -43208,7 +43406,7 @@ RED.editor = (function() {
43208
43406
 
43209
43407
  nodes.forEach(async node => {
43210
43408
  if (!node.getAttribute('mermaid-processed')) {
43211
- const mermaidContent = node.innerText
43409
+ const mermaidContent = atob($(node).data('c64'))
43212
43410
  node.setAttribute('mermaid-processed', true)
43213
43411
  try {
43214
43412
  const { svg } = await mermaid.render('mermaid-render-'+Date.now()+'-'+(diagramIds++), mermaidContent);
@@ -45000,11 +45198,14 @@ RED.editor.codeEditor.monaco = (function() {
45000
45198
  **/
45001
45199
  RED.eventLog = (function() {
45002
45200
 
45003
- var template = '<script type="text/x-red" data-template-name="_eventLog"><div class="form-row node-text-editor-row"><div style="height: 100%;min-height: 150px;" class="node-text-editor" id="red-ui-event-log-editor"></div></div></script>';
45201
+ const template = '<script type="text/x-red" data-template-name="_eventLog"><div class="form-row node-text-editor-row"><div style="height: 100%;min-height: 150px;" class="node-text-editor" id="red-ui-event-log-editor"></div></div></script>';
45202
+
45203
+ let eventLogEditor;
45204
+ let backlog = [];
45205
+ let shown = false;
45206
+
45207
+ const activeLogs = new Set()
45004
45208
 
45005
- var eventLogEditor;
45006
- var backlog = [];
45007
- var shown = false;
45008
45209
 
45009
45210
  function appendLogLine(line) {
45010
45211
  backlog.push(line);
@@ -45023,6 +45224,18 @@ RED.eventLog = (function() {
45023
45224
  init: function() {
45024
45225
  $(template).appendTo("#red-ui-editor-node-configs");
45025
45226
  RED.actions.add("core:show-event-log",RED.eventLog.show);
45227
+
45228
+ const statusWidget = $('<button type="button" class="red-ui-footer-button red-ui-event-log-status" style="line-height: normal"><img style="width: 80%" src="red/images/spin.svg"/></div></button>');
45229
+ statusWidget.on("click", function(evt) {
45230
+ RED.actions.invoke("core:show-event-log");
45231
+ })
45232
+ RED.statusBar.add({
45233
+ id: "red-ui-event-log-status",
45234
+ align: "right",
45235
+ element: statusWidget
45236
+ });
45237
+ RED.statusBar.hide("red-ui-event-log-status");
45238
+
45026
45239
  },
45027
45240
  show: function() {
45028
45241
  if (shown) {
@@ -45083,6 +45296,12 @@ RED.eventLog = (function() {
45083
45296
  },
45084
45297
  log: function(id,payload) {
45085
45298
  var ts = (new Date(payload.ts)).toISOString()+" ";
45299
+ if (!payload.end) {
45300
+ activeLogs.add(id)
45301
+ } else {
45302
+ activeLogs.delete(id);
45303
+ }
45304
+
45086
45305
  if (payload.type) {
45087
45306
  ts += "["+payload.type+"] "
45088
45307
  }
@@ -45096,6 +45315,11 @@ RED.eventLog = (function() {
45096
45315
  appendLogLine(ts+line);
45097
45316
  })
45098
45317
  }
45318
+ if (activeLogs.size > 0) {
45319
+ RED.statusBar.show("red-ui-event-log-status");
45320
+ } else {
45321
+ RED.statusBar.hide("red-ui-event-log-status");
45322
+ }
45099
45323
  },
45100
45324
  startEvent: function(name) {
45101
45325
  backlog.push("");
@@ -45337,14 +45561,6 @@ RED.eventLog = (function() {
45337
45561
  editorStack = $("#red-ui-editor-stack");
45338
45562
  $(window).on("resize", handleWindowResize);
45339
45563
  RED.events.on("sidebar:resize",handleWindowResize);
45340
- $("#red-ui-editor-shade").on("click", function() {
45341
- if (!openingTray) {
45342
- var tray = stack[stack.length-1];
45343
- if (tray && tray.primaryButton) {
45344
- tray.primaryButton.click();
45345
- }
45346
- }
45347
- });
45348
45564
  },
45349
45565
  show: function show(options) {
45350
45566
  lowerTrayZ();
@@ -49422,7 +49638,6 @@ RED.actionList = (function() {
49422
49638
  addItem: function(container, i, nodeItem) {
49423
49639
  // nodeItem can take multiple forms
49424
49640
  // - A node type: {type: "inject", def: RED.nodes.getType("inject"), label: "Inject"}
49425
- // - A flow suggestion: { suggestion: true, nodes: [] }
49426
49641
  // - A placeholder suggestion: { suggestionPlaceholder: true, label: 'loading suggestions...' }
49427
49642
 
49428
49643
  let nodeDef = nodeItem.def;
@@ -49502,7 +49717,17 @@ RED.actionList = (function() {
49502
49717
 
49503
49718
  }
49504
49719
 
49720
+ let activeSuggestion
49505
49721
  function updateSuggestion(nodeItem) {
49722
+ if (nodeItem === activeSuggestion) {
49723
+ return
49724
+ }
49725
+ if (!visible && nodeItem) {
49726
+ // Do not update suggestion if the dialog is not visible
49727
+ // - for example, whilst the dialog is closing and the user mouses over a new item
49728
+ return
49729
+ }
49730
+ activeSuggestion = nodeItem
49506
49731
  if (suggestCallback) {
49507
49732
  if (!nodeItem) {
49508
49733
  suggestCallback(null);
@@ -49559,6 +49784,7 @@ RED.actionList = (function() {
49559
49784
  }
49560
49785
  visible = true;
49561
49786
  } else {
49787
+ updateSuggestion(null)
49562
49788
  dialog.hide();
49563
49789
  searchResultsDiv.hide();
49564
49790
  }
@@ -49599,9 +49825,7 @@ RED.actionList = (function() {
49599
49825
  },200);
49600
49826
  }
49601
49827
  function hide(fast) {
49602
- if (suggestCallback) {
49603
- suggestCallback(null);
49604
- }
49828
+ updateSuggestion(null)
49605
49829
  if (visible) {
49606
49830
  visible = false;
49607
49831
  if (dialog !== null) {
@@ -49700,32 +49924,37 @@ RED.actionList = (function() {
49700
49924
 
49701
49925
  let index = 0;
49702
49926
 
49703
- // const suggestionItem = {
49704
- // suggestionPlaceholder: true,
49705
- // label: 'loading suggestions...',
49706
- // separator: true,
49707
- // i: index++
49708
- // }
49709
- // searchResults.editableList('addItem', suggestionItem);
49710
- // setTimeout(function() {
49711
- // searchResults.editableList('removeItem', suggestionItem);
49712
-
49713
- // const suggestedItem = {
49714
- // suggestion: true,
49715
- // label: 'Change/Debug Combo',
49716
- // separator: true,
49717
- // i: suggestionItem.i,
49718
- // nodes: [
49719
- // { id: 'suggestion-1', type: 'change', x: 0, y: 0, wires:[['suggestion-2']] },
49720
- // { id: 'suggestion-2', type: 'function', outputs: 3, x: 200, y: 0, wires:[['suggestion-3'],['suggestion-4'],['suggestion-6']] },
49721
- // { id: 'suggestion-3', _g: 'suggestion-group-1', type: 'debug', x: 375, y: -40 },
49722
- // { id: 'suggestion-4', _g: 'suggestion-group-1', type: 'debug', x: 375, y: 0 },
49723
- // { id: 'suggestion-5', _g: 'suggestion-group-1', type: 'debug', x: 410, y: 40 },
49724
- // { id: 'suggestion-6', type: 'junction', wires: [['suggestion-5']], x:325, y:40 }
49725
- // ]
49726
- // }
49727
- // searchResults.editableList('addItem', suggestedItem);
49728
- // }, 1000)
49927
+ if (!opts.context?.virtualLink) {
49928
+ // Check for suggestion plugins
49929
+ const suggestionPlugins = RED.plugins.getPluginsByType('node-red-flow-suggestion-source');
49930
+ if (suggestionPlugins.length > 0) {
49931
+ const suggestionItem = {
49932
+ suggestionPlaceholder: true,
49933
+ label: RED._('palette.loadingSuggestions'),
49934
+ separator: true,
49935
+ i: index++
49936
+ }
49937
+ searchResults.editableList('addItem', suggestionItem);
49938
+ suggestionPlugins[0].getSuggestions(opts.context).then(function (suggestedFlows) {
49939
+ searchResults.editableList('removeItem', suggestionItem);
49940
+ if (!Array.isArray(suggestedFlows)) {
49941
+ suggestedFlows = [suggestedFlows];
49942
+ }
49943
+ suggestedFlows.forEach(function(suggestion, index) {
49944
+ const suggestedItem = {
49945
+ suggestion: true,
49946
+ separator: index === suggestedFlows.length - 1,
49947
+ i: suggestionItem.i,
49948
+ ...suggestion
49949
+ }
49950
+ if (!suggestion.label && suggestion.nodes && suggestion.nodes.length === 1 && suggestion.nodes[0].type) {
49951
+ suggestedItem.label = getTypeLabel(suggestion.nodes[0].type, RED.nodes.getType(suggestion.nodes[0].type));
49952
+ }
49953
+ searchResults.editableList('addItem', suggestedItem);
49954
+ })
49955
+ })
49956
+ }
49957
+ }
49729
49958
 
49730
49959
  for(i=0;i<common.length;i++) {
49731
49960
  let itemDef
@@ -49776,9 +50005,10 @@ RED.actionList = (function() {
49776
50005
  }
49777
50006
 
49778
50007
  return {
49779
- show: show,
50008
+ show,
49780
50009
  refresh: refreshTypeList,
49781
- hide: hide
50010
+ hide,
50011
+ isVisible: () => visible
49782
50012
  };
49783
50013
 
49784
50014
  })();