@node-red/editor-client 4.1.1 → 4.1.3

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
@@ -2139,8 +2139,8 @@ RED.comms = (function() {
2139
2139
  subscribers[i](msg.topic,msg.data);
2140
2140
  } catch (error) {
2141
2141
  // need to decide what to do with this uncaught error
2142
- console.warn('Uncaught error from RED.comms.subscribe: ' + err.toString())
2143
- console.warn(err)
2142
+ console.warn('Uncaught error from RED.comms.subscribe: ' + error.toString())
2143
+ console.warn(error)
2144
2144
  }
2145
2145
  }
2146
2146
  }
@@ -9321,7 +9321,15 @@ RED.history = (function() {
9321
9321
  }
9322
9322
  }
9323
9323
  }
9324
-
9324
+ if (ev.node.type === 'subflow') {
9325
+ // Ensure ports get a refresh in case of a label change
9326
+ if (ev.changes.inputLabels) {
9327
+ ev.node.in.forEach(function(input) { input.dirty = true; });
9328
+ }
9329
+ if (ev.changes.outputLabels) {
9330
+ ev.node.out.forEach(function(output) { output.dirty = true; });
9331
+ }
9332
+ }
9325
9333
  ev.node.dirty = true;
9326
9334
  ev.node.changed = ev.changed;
9327
9335
 
@@ -9632,8 +9640,8 @@ RED.history = (function() {
9632
9640
  return {
9633
9641
  //TODO: this function is a placeholder until there is a 'save' event that can be listened to
9634
9642
  markAllDirty: function() {
9635
- for (var i=0;i<undoHistory.length;i++) {
9636
- undoHistory[i].dirty = true;
9643
+ for (const event of [...undoHistory, ...redoHistory]) {
9644
+ event.dirty = true;
9637
9645
  }
9638
9646
  },
9639
9647
  list: function() {
@@ -10039,7 +10047,13 @@ RED.utils = (function() {
10039
10047
  var copyPayload = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-clipboard"></i></button>').appendTo(copyTools).on("click", function(e) {
10040
10048
  e.preventDefault();
10041
10049
  e.stopPropagation();
10042
- RED.clipboard.copyText(msg,copyPayload,"clipboard.copyMessageValue");
10050
+ var payloadToCopy;
10051
+ if (typeof msg === "number") {
10052
+ payloadToCopy = obj.find(".red-ui-debug-msg-type-number").first().text();
10053
+ } else {
10054
+ payloadToCopy = msg;
10055
+ }
10056
+ RED.clipboard.copyText(payloadToCopy, copyPayload, "clipboard.copyMessageValue");
10043
10057
  })
10044
10058
  RED.popover.tooltip(copyPayload,RED._("node-red:debug.sidebar.copyPayload"));
10045
10059
  if (enablePinning && strippedKey !== undefined && strippedKey !== '') {
@@ -10367,7 +10381,7 @@ RED.utils = (function() {
10367
10381
  var sr = $('<div class="red-ui-debug-msg-object-entry collapsed"></div>').appendTo(stringRow);
10368
10382
  var stringEncoding = "";
10369
10383
  try {
10370
- stringEncoding = String.fromCharCode.apply(null, new Uint16Array(data))
10384
+ stringEncoding = new TextDecoder().decode(new Uint8Array(data));
10371
10385
  } catch(err) {
10372
10386
  console.log(err);
10373
10387
  }
@@ -11660,8 +11674,10 @@ RED.utils = (function() {
11660
11674
  var deleteButton = $('<a/>',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(li);
11661
11675
  $('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton);
11662
11676
  li.addClass("red-ui-editableList-item-removable");
11677
+ var removeTip = RED.popover.tooltip(deleteButton, RED._("common.label.delete"));
11663
11678
  deleteButton.on("click", function(evt) {
11664
11679
  evt.preventDefault();
11680
+ removeTip.close();
11665
11681
  var data = row.data('data');
11666
11682
  li.addClass("red-ui-editableList-item-deleting")
11667
11683
  li.fadeOut(300, function() {
@@ -11969,7 +11985,10 @@ RED.utils = (function() {
11969
11985
  } else {
11970
11986
  that._topList.find(".focus").removeClass("focus")
11971
11987
  }
11972
- target.treeList.label.addClass('focus')
11988
+ if (target.treeList.label) {
11989
+ target.treeList.label.addClass('focus')
11990
+ }
11991
+ that.reveal(target);
11973
11992
  }
11974
11993
  });
11975
11994
  this._data = [];
@@ -12660,6 +12679,10 @@ RED.utils = (function() {
12660
12679
  }
12661
12680
 
12662
12681
  that._topList.find(".focus").removeClass("focus");
12682
+
12683
+ if (item.treeList.label) {
12684
+ item.treeList.label.addClass("focus");
12685
+ }
12663
12686
 
12664
12687
  if (triggerEvent !== false) {
12665
12688
  this._trigger("select",null,item)
@@ -14759,12 +14782,18 @@ RED.tabs = (function() {
14759
14782
  }
14760
14783
  function activatePreviousTab() {
14761
14784
  var previous = findPreviousVisibleTab();
14785
+ if (previous.length === 0) {
14786
+ previous = ul.find("li.red-ui-tab:not(.hide-tab)").last();
14787
+ }
14762
14788
  if (previous.length > 0) {
14763
14789
  activateTab(previous.find("a"));
14764
14790
  }
14765
14791
  }
14766
14792
  function activateNextTab() {
14767
14793
  var next = findNextVisibleTab();
14794
+ if (next.length === 0) {
14795
+ next = ul.find("li.red-ui-tab:not(.hide-tab)").first();
14796
+ }
14768
14797
  if (next.length > 0) {
14769
14798
  activateTab(next.find("a"));
14770
14799
  }
@@ -17791,7 +17820,7 @@ RED.deploy = (function() {
17791
17820
  }
17792
17821
 
17793
17822
  function updateLockedState() {
17794
- if (RED.settings.user?.permissions === 'read') {
17823
+ if (!RED.user.hasPermission('flows.write')) {
17795
17824
  $(".red-ui-deploy-button-group").addClass("readOnly");
17796
17825
  $("#red-ui-header-button-deploy").addClass("disabled");
17797
17826
  } else {
@@ -22742,6 +22771,7 @@ RED.view = (function() {
22742
22771
  let suggestedLinks = [];
22743
22772
  let suggestedJunctions = [];
22744
22773
 
22774
+ let forceFullRedraw = false
22745
22775
  // Note: these are the permitted status colour aliases. The actual RGB values
22746
22776
  // are set in the CSS - flow.scss/colors.scss
22747
22777
  const status_colours = {
@@ -24014,6 +24044,13 @@ RED.view = (function() {
24014
24044
  quickAddLink.virtualLink = true;
24015
24045
  }
24016
24046
  hideDragLines();
24047
+ } else if (quickAddLink) {
24048
+ // continuing an existing quick add - set the filter accordingly
24049
+ if (quickAddLink.portType === PORT_TYPE_OUTPUT) {
24050
+ filter = {input:true}
24051
+ } else {
24052
+ filter = {output:true}
24053
+ }
24017
24054
  }
24018
24055
  if (linkToSplice || spliceMultipleLinks) {
24019
24056
  filter = {
@@ -25852,6 +25889,7 @@ RED.view = (function() {
25852
25889
 
25853
25890
  function portMouseDown(d,portType,portIndex, evt) {
25854
25891
  if (RED.view.DEBUG) { console.warn("portMouseDown", mouse_mode,d,portType,portIndex); }
25892
+ clearSuggestedFlow();
25855
25893
  RED.contextMenu.hide();
25856
25894
  evt = evt || d3.event;
25857
25895
  if (evt === 1) {
@@ -26275,9 +26313,9 @@ RED.view = (function() {
26275
26313
  return tooltip;
26276
26314
  }
26277
26315
 
26278
- function portMouseOver(port,d,portType,portIndex) {
26316
+ function portMouseOver(port,d,portType,portIndex, event) {
26279
26317
  if (mouse_mode === RED.state.SELECTING_NODE) {
26280
- d3.event.stopPropagation();
26318
+ (d3.event || event).stopPropagation();
26281
26319
  return;
26282
26320
  }
26283
26321
  clearTimeout(portLabelHoverTimeout);
@@ -26316,9 +26354,9 @@ RED.view = (function() {
26316
26354
  }
26317
26355
  port.classed("red-ui-flow-port-hovered",active);
26318
26356
  }
26319
- function portMouseOut(port,d,portType,portIndex) {
26357
+ function portMouseOut(port,d,portType,portIndex, event) {
26320
26358
  if (mouse_mode === RED.state.SELECTING_NODE) {
26321
- d3.event.stopPropagation();
26359
+ (d3.event || event).stopPropagation();
26322
26360
  return;
26323
26361
  }
26324
26362
  clearTimeout(portLabelHoverTimeout);
@@ -26436,6 +26474,7 @@ RED.view = (function() {
26436
26474
  }
26437
26475
  function nodeMouseDown(d) {
26438
26476
  if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); }
26477
+ clearSuggestedFlow()
26439
26478
  focusView();
26440
26479
  RED.contextMenu.hide();
26441
26480
  if (d3.event.button === 1) {
@@ -27061,130 +27100,211 @@ RED.view = (function() {
27061
27100
  }
27062
27101
  }
27063
27102
 
27103
+ function buildSubflowPort (d) {
27104
+ const NODE_TYPE = d.direction === "in" ? PORT_TYPE_INPUT : PORT_TYPE_OUTPUT;
27105
+ // PORT_TYPE is the 'opposite' of NODE_TYPE
27106
+ const PORT_TYPE = NODE_TYPE === PORT_TYPE_INPUT ? PORT_TYPE_OUTPUT : PORT_TYPE_INPUT;
27107
+ var node = d3.select(this);
27108
+ var nodeContents = document.createDocumentFragment();
27109
+
27110
+ d.h = 40;
27111
+ d.resize = true;
27112
+ d.dirty = true;
27113
+
27114
+ var mainRect = document.createElementNS("http://www.w3.org/2000/svg","rect");
27115
+ mainRect.__data__ = d;
27116
+ mainRect.setAttribute("class", "red-ui-flow-subflow-port");
27117
+ mainRect.setAttribute("rx", 8);
27118
+ mainRect.setAttribute("ry", 8);
27119
+ mainRect.setAttribute("width", 40);
27120
+ mainRect.setAttribute("height", 40);
27121
+ node[0][0].__mainRect__ = mainRect;
27122
+ d3.select(mainRect)
27123
+ .on("mouseup",nodeMouseUp)
27124
+ .on("mousedown",nodeMouseDown)
27125
+ .on("touchstart",nodeTouchStart)
27126
+ .on("touchend",nodeTouchEnd)
27127
+ nodeContents.appendChild(mainRect);
27128
+
27129
+ const port_label_group = document.createElementNS("http://www.w3.org/2000/svg","g");
27130
+ port_label_group.setAttribute("x",0);
27131
+ port_label_group.setAttribute("y",0);
27132
+ node[0][0].__portLabelGroup__ = port_label_group;
27133
+
27134
+ const port_label = document.createElementNS("http://www.w3.org/2000/svg","text");
27135
+ port_label.setAttribute("class","red-ui-flow-port-label");
27136
+ port_label.style["font-size"] = "10px";
27137
+ port_label.textContent = NODE_TYPE === PORT_TYPE_INPUT? "input" : "output";
27138
+ port_label_group.appendChild(port_label);
27139
+ node[0][0].__portLabel__ = port_label;
27140
+
27141
+ if (NODE_TYPE === PORT_TYPE_OUTPUT) {
27142
+ const port_number = document.createElementNS("http://www.w3.org/2000/svg","text");
27143
+ port_number.setAttribute("class","red-ui-flow-port-label red-ui-flow-port-index");
27144
+ port_number.setAttribute("x",0);
27145
+ port_number.setAttribute("y",0);
27146
+ port_number.textContent = d.i+1;
27147
+ port_label_group.appendChild(port_number);
27148
+ node[0][0].__portNumber__ = port_number;
27149
+ }
27150
+
27151
+ const port_border = document.createElementNS("http://www.w3.org/2000/svg","path");
27152
+ port_border.setAttribute("d","M 40 1 l 0 38")
27153
+ port_border.setAttribute("class", "red-ui-flow-node-icon-shade-border")
27154
+ port_label_group.appendChild(port_border);
27155
+ node[0][0].__portBorder__ = port_border;
27156
+
27157
+ nodeContents.appendChild(port_label_group);
27158
+
27159
+ var text = document.createElementNS("http://www.w3.org/2000/svg","g");
27160
+ text.setAttribute("class","red-ui-flow-port-label");
27161
+ text.setAttribute("transform","translate(38,0)");
27162
+ text.setAttribute('style', 'fill : #888'); // hard coded here!
27163
+ node[0][0].__textGroup__ = text;
27164
+ nodeContents.append(text);
27165
+
27166
+ var portEl = document.createElementNS("http://www.w3.org/2000/svg","g");
27167
+ portEl.setAttribute('transform','translate(-5,15)')
27168
+
27169
+ var port = document.createElementNS("http://www.w3.org/2000/svg","rect");
27170
+ port.setAttribute("class","red-ui-flow-port");
27171
+ port.setAttribute("rx",3);
27172
+ port.setAttribute("ry",3);
27173
+ port.setAttribute("width",10);
27174
+ port.setAttribute("height",10);
27175
+ portEl.appendChild(port);
27176
+ port.__data__ = d;
27177
+
27178
+ d3.select(port)
27179
+ .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE,0);} )
27180
+ .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE,0);d3.event.preventDefault();} )
27181
+ .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE,0);})
27182
+ .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE,0);d3.event.preventDefault();} )
27183
+ .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE,0);})
27184
+ .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE,0);});
27185
+
27186
+ node[0][0].__port__ = portEl
27187
+ nodeContents.appendChild(portEl);
27188
+ node[0][0].appendChild(nodeContents);
27189
+ }
27190
+ function updateSubflowPort (d) {
27191
+ if (d.dirty) {
27192
+ const port_height = 40;
27193
+ const NODE_TYPE = d.direction === "in" ? PORT_TYPE_INPUT : PORT_TYPE_OUTPUT;
27194
+ // PORT_TYPE is the 'opposite' of NODE_TYPE
27195
+ const PORT_TYPE = NODE_TYPE === PORT_TYPE_INPUT ? PORT_TYPE_OUTPUT : PORT_TYPE_INPUT;
27196
+
27197
+ var label = getPortLabel(activeSubflow, NODE_TYPE, d.i) || "";
27198
+ var hideLabel = (label.length < 1)
27199
+ var labelParts;
27200
+ if (d.resize || this.__hideLabel__ !== hideLabel || this.__label__ !== label) {
27201
+ labelParts = getLabelParts(label, "red-ui-flow-node-label");
27202
+ if (labelParts.lines.length !== this.__labelLineCount__ || this.__label__ !== label) {
27203
+ d.resize = true;
27204
+ }
27205
+ this.__label__ = label;
27206
+ this.__labelLineCount__ = labelParts.lines.length;
27207
+
27208
+ if (hideLabel) {
27209
+ d.h = Math.max(port_height,(d.outputs || 0) * 15);
27210
+ } else {
27211
+ d.h = Math.max(6+24*labelParts.lines.length,(d.outputs || 0) * 15, port_height);
27212
+ }
27213
+ this.__hideLabel__ = hideLabel;
27214
+ }
27215
+
27216
+ if (d.resize) {
27217
+ var ow = d.w;
27218
+ if (hideLabel) {
27219
+ d.w = port_height;
27220
+ } else {
27221
+ d.w = Math.max(port_height,20*(Math.ceil((labelParts.width+50+7)/20)) );
27222
+ }
27223
+ if (ow !== undefined) {
27224
+ d.x += (d.w-ow)/2;
27225
+ }
27226
+ d.resize = false;
27227
+ }
27228
+
27229
+ this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")");
27230
+ // This might be the first redraw after a node has been click-dragged to start a move.
27231
+ // So its selected state might have changed since the last redraw.
27232
+ this.classList.toggle("red-ui-flow-node-selected", !!d.selected )
27233
+ if (mouse_mode != RED.state.MOVING_ACTIVE) {
27234
+ this.classList.toggle("red-ui-flow-node-disabled", d.d === true);
27235
+ this.__mainRect__.setAttribute("width", d.w)
27236
+ this.__mainRect__.setAttribute("height", d.h)
27237
+ this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted );
27238
+
27239
+ if (labelParts) {
27240
+ // The label has changed
27241
+ var sa = labelParts.lines;
27242
+ var sn = labelParts.lines.length;
27243
+ var textLines = this.__textGroup__.childNodes;
27244
+ while(textLines.length > sn) {
27245
+ textLines[textLines.length-1].remove();
27246
+ }
27247
+ for (var i=0; i<sn; i++) {
27248
+ if (i===textLines.length) {
27249
+ var line = document.createElementNS("http://www.w3.org/2000/svg","text");
27250
+ line.setAttribute("class","red-ui-flow-node-label-text");
27251
+ line.setAttribute("x",0);
27252
+ line.setAttribute("y",i*24);
27253
+ this.__textGroup__.appendChild(line);
27254
+ }
27255
+ textLines[i].textContent = sa[i];
27256
+ }
27257
+ }
27258
+
27259
+ var textClass = "red-ui-flow-node-label"+(hideLabel?" hide":"");
27260
+ this.__textGroup__.setAttribute("class", textClass);
27261
+ var yp = d.h / 2 - (this.__labelLineCount__ / 2) * 24 + 13;
27262
+
27263
+ // this.__textGroup__.classList.remove("red-ui-flow-node-label-right");
27264
+ this.__textGroup__.setAttribute("transform", "translate(48,"+yp+")");
27265
+
27266
+ this.__portBorder__.setAttribute("d","M 40 1 l 0 "+(hideLabel?0:(d.h - 2)));
27267
+ const portX = PORT_TYPE === PORT_TYPE_OUTPUT ? d.w - 5 : -5
27268
+ this.__port__.setAttribute("transform","translate("+portX+","+((d.h/2)-5)+")");
27269
+ if (NODE_TYPE === PORT_TYPE_OUTPUT) {
27270
+ this.__portLabel__.setAttribute("transform","translate(20,"+((d.h/2)-8)+")");
27271
+ this.__portNumber__.setAttribute("transform","translate(20,"+((d.h/2)+7)+")");
27272
+ this.__portNumber__.textContent = d.i+1;
27273
+ } else {
27274
+ this.__portLabel__.setAttribute("transform","translate(20,"+(d.h/2)+")");
27275
+ }
27276
+ }
27277
+ d.dirty = false;
27278
+ return true
27279
+ }
27280
+ return false
27281
+ }
27282
+
27064
27283
  function _redraw() {
27065
27284
  eventLayer.attr("transform","scale("+scaleFactor+")");
27066
27285
  outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor);
27067
27286
 
27068
27287
  // Don't bother redrawing nodes if we're drawing links
27069
- if (showAllLinkPorts !== -1 || mouse_mode != RED.state.JOINING) {
27070
-
27288
+ if (forceFullRedraw || showAllLinkPorts !== -1 || mouse_mode != RED.state.JOINING) {
27289
+ forceFullRedraw = false
27071
27290
  var dirtyNodes = {};
27072
27291
 
27073
27292
  if (activeSubflow) {
27074
27293
  var subflowOutputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-output").data(activeSubflow.out,function(d,i){ return d.id;});
27075
27294
  subflowOutputs.exit().remove();
27076
27295
  var outGroup = subflowOutputs.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-output")
27077
- outGroup.each(function(d,i) {
27078
- var node = d3.select(this);
27079
- var nodeContents = document.createDocumentFragment();
27080
-
27081
- d.h = 40;
27082
- d.resize = true;
27083
- d.dirty = true;
27084
-
27085
- var mainRect = document.createElementNS("http://www.w3.org/2000/svg","rect");
27086
- mainRect.__data__ = d;
27087
- mainRect.setAttribute("class", "red-ui-flow-subflow-port");
27088
- mainRect.setAttribute("rx", 8);
27089
- mainRect.setAttribute("ry", 8);
27090
- mainRect.setAttribute("width", 40);
27091
- mainRect.setAttribute("height", 40);
27092
- node[0][0].__mainRect__ = mainRect;
27093
- d3.select(mainRect)
27094
- .on("mouseup",nodeMouseUp)
27095
- .on("mousedown",nodeMouseDown)
27096
- .on("touchstart",nodeTouchStart)
27097
- .on("touchend",nodeTouchEnd)
27098
- nodeContents.appendChild(mainRect);
27099
-
27100
- var output_groupEl = document.createElementNS("http://www.w3.org/2000/svg","g");
27101
- output_groupEl.setAttribute("x",0);
27102
- output_groupEl.setAttribute("y",0);
27103
- node[0][0].__outputLabelGroup__ = output_groupEl;
27104
-
27105
- var output_output = document.createElementNS("http://www.w3.org/2000/svg","text");
27106
- output_output.setAttribute("class","red-ui-flow-port-label");
27107
- output_output.style["font-size"] = "10px";
27108
- output_output.textContent = "output";
27109
- output_groupEl.appendChild(output_output);
27110
- node[0][0].__outputOutput__ = output_output;
27111
-
27112
- var output_number = document.createElementNS("http://www.w3.org/2000/svg","text");
27113
- output_number.setAttribute("class","red-ui-flow-port-label red-ui-flow-port-index");
27114
- output_number.setAttribute("x",0);
27115
- output_number.setAttribute("y",0);
27116
- output_number.textContent = d.i+1;
27117
- output_groupEl.appendChild(output_number);
27118
- node[0][0].__outputNumber__ = output_number;
27119
-
27120
- var output_border = document.createElementNS("http://www.w3.org/2000/svg","path");
27121
- output_border.setAttribute("d","M 40 1 l 0 38")
27122
- output_border.setAttribute("class", "red-ui-flow-node-icon-shade-border")
27123
- output_groupEl.appendChild(output_border);
27124
- node[0][0].__outputBorder__ = output_border;
27125
-
27126
- nodeContents.appendChild(output_groupEl);
27127
-
27128
- var text = document.createElementNS("http://www.w3.org/2000/svg","g");
27129
- text.setAttribute("class","red-ui-flow-port-label");
27130
- text.setAttribute("transform","translate(38,0)");
27131
- text.setAttribute('style', 'fill : #888'); // hard coded here!
27132
- node[0][0].__textGroup__ = text;
27133
- nodeContents.append(text);
27134
-
27135
- var portEl = document.createElementNS("http://www.w3.org/2000/svg","g");
27136
- portEl.setAttribute('transform','translate(-5,15)')
27137
-
27138
- var port = document.createElementNS("http://www.w3.org/2000/svg","rect");
27139
- port.setAttribute("class","red-ui-flow-port");
27140
- port.setAttribute("rx",3);
27141
- port.setAttribute("ry",3);
27142
- port.setAttribute("width",10);
27143
- port.setAttribute("height",10);
27144
- portEl.appendChild(port);
27145
- port.__data__ = d;
27146
-
27147
- d3.select(port)
27148
- .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} )
27149
- .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} )
27150
- .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);})
27151
- .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} )
27152
- .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);})
27153
- .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);});
27154
-
27155
- node[0][0].__port__ = portEl
27156
- nodeContents.appendChild(portEl);
27157
- node[0][0].appendChild(nodeContents);
27158
- });
27296
+ outGroup.each(buildSubflowPort);
27159
27297
 
27160
27298
  var subflowInputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-input").data(activeSubflow.in,function(d,i){ return d.id;});
27161
27299
  subflowInputs.exit().remove();
27162
27300
  var inGroup = subflowInputs.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-input").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"});
27163
- inGroup.each(function(d,i) {
27164
- d.w=40;
27165
- d.h=40;
27166
- });
27167
- inGroup.append("rect").attr("class","red-ui-flow-subflow-port").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40)
27168
- // TODO: This is exactly the same set of handlers used for regular nodes - DRY
27169
- .on("mouseup",nodeMouseUp)
27170
- .on("mousedown",nodeMouseDown)
27171
- .on("touchstart",nodeTouchStart)
27172
- .on("touchend", nodeTouchEnd);
27173
-
27174
- inGroup.append("g").attr('transform','translate(35,15)').append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10)
27175
- .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_OUTPUT,i);} )
27176
- .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_OUTPUT,i);d3.event.preventDefault();} )
27177
- .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_OUTPUT,i);})
27178
- .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_OUTPUT,i);d3.event.preventDefault();} )
27179
- .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_OUTPUT,0);})
27180
- .on("mouseout",function(d) {portMouseOut(d3.select(this),d,PORT_TYPE_OUTPUT,0);});
27181
-
27182
- inGroup.append("svg:text").attr("class","red-ui-flow-port-label").attr("x",18).attr("y",20).style("font-size","10px").text("input");
27301
+ inGroup.each(buildSubflowPort);
27183
27302
 
27184
27303
  var subflowStatus = nodeLayer.selectAll(".red-ui-flow-subflow-port-status").data(activeSubflow.status?[activeSubflow.status]:[],function(d,i){ return d.id;});
27185
27304
  subflowStatus.exit().remove();
27186
27305
 
27187
27306
  var statusGroup = subflowStatus.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-status").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"});
27307
+ // TODO: use buildSubflowPort/updateSubflowPort for status port
27188
27308
  statusGroup.each(function(d,i) {
27189
27309
  d.w=40;
27190
27310
  d.h=40;
@@ -27206,104 +27326,16 @@ RED.view = (function() {
27206
27326
 
27207
27327
  statusGroup.append("svg:text").attr("class","red-ui-flow-port-label").attr("x",22).attr("y",20).style("font-size","10px").text("status");
27208
27328
 
27209
- subflowOutputs.each(function(d,i) {
27210
- if (d.dirty) {
27211
-
27212
- var port_height = 40;
27213
-
27214
- var self = this;
27215
- var thisNode = d3.select(this);
27216
-
27329
+ subflowOutputs.each(function (d,i) {
27330
+ if (updateSubflowPort.call(this, d)) {
27217
27331
  dirtyNodes[d.id] = d;
27218
-
27219
- var label = getPortLabel(activeSubflow, PORT_TYPE_OUTPUT, d.i) || "";
27220
- var hideLabel = (label.length < 1)
27221
-
27222
- var labelParts;
27223
- if (d.resize || this.__hideLabel__ !== hideLabel || this.__label__ !== label) {
27224
- labelParts = getLabelParts(label, "red-ui-flow-node-label");
27225
- if (labelParts.lines.length !== this.__labelLineCount__ || this.__label__ !== label) {
27226
- d.resize = true;
27227
- }
27228
- this.__label__ = label;
27229
- this.__labelLineCount__ = labelParts.lines.length;
27230
-
27231
- if (hideLabel) {
27232
- d.h = Math.max(port_height,(d.outputs || 0) * 15);
27233
- } else {
27234
- d.h = Math.max(6+24*labelParts.lines.length,(d.outputs || 0) * 15, port_height);
27235
- }
27236
- this.__hideLabel__ = hideLabel;
27237
- }
27238
-
27239
- if (d.resize) {
27240
- var ow = d.w;
27241
- if (hideLabel) {
27242
- d.w = port_height;
27243
- } else {
27244
- d.w = Math.max(port_height,20*(Math.ceil((labelParts.width+50+7)/20)) );
27245
- }
27246
- if (ow !== undefined) {
27247
- d.x += (d.w-ow)/2;
27248
- }
27249
- d.resize = false;
27250
- }
27251
-
27252
- this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")");
27253
- // This might be the first redraw after a node has been click-dragged to start a move.
27254
- // So its selected state might have changed since the last redraw.
27255
- this.classList.toggle("red-ui-flow-node-selected", !!d.selected )
27256
- if (mouse_mode != RED.state.MOVING_ACTIVE) {
27257
- this.classList.toggle("red-ui-flow-node-disabled", d.d === true);
27258
- this.__mainRect__.setAttribute("width", d.w)
27259
- this.__mainRect__.setAttribute("height", d.h)
27260
- this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted );
27261
-
27262
- if (labelParts) {
27263
- // The label has changed
27264
- var sa = labelParts.lines;
27265
- var sn = labelParts.lines.length;
27266
- var textLines = this.__textGroup__.childNodes;
27267
- while(textLines.length > sn) {
27268
- textLines[textLines.length-1].remove();
27269
- }
27270
- for (var i=0; i<sn; i++) {
27271
- if (i===textLines.length) {
27272
- var line = document.createElementNS("http://www.w3.org/2000/svg","text");
27273
- line.setAttribute("class","red-ui-flow-node-label-text");
27274
- line.setAttribute("x",0);
27275
- line.setAttribute("y",i*24);
27276
- this.__textGroup__.appendChild(line);
27277
- }
27278
- textLines[i].textContent = sa[i];
27279
- }
27280
- }
27281
-
27282
- var textClass = "red-ui-flow-node-label"+(hideLabel?" hide":"");
27283
- this.__textGroup__.setAttribute("class", textClass);
27284
- var yp = d.h / 2 - (this.__labelLineCount__ / 2) * 24 + 13;
27285
-
27286
- // this.__textGroup__.classList.remove("red-ui-flow-node-label-right");
27287
- this.__textGroup__.setAttribute("transform", "translate(48,"+yp+")");
27288
-
27289
- this.__outputBorder__.setAttribute("d","M 40 1 l 0 "+(hideLabel?0:(d.h - 2)));
27290
- this.__port__.setAttribute("transform","translate(-5,"+((d.h/2)-5)+")");
27291
- this.__outputOutput__.setAttribute("transform","translate(20,"+((d.h/2)-8)+")");
27292
- this.__outputNumber__.setAttribute("transform","translate(20,"+((d.h/2)+7)+")");
27293
- this.__outputNumber__.textContent = d.i+1;
27294
- }
27295
- d.dirty = false;
27296
27332
  }
27297
- });
27298
- subflowInputs.each(function(d,i) {
27299
- if (d.dirty) {
27300
- var input = d3.select(this);
27301
- input.classed("red-ui-flow-node-selected",function(d) { return d.selected; })
27302
- input.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; });
27333
+ })
27334
+ subflowInputs.each(function (d,i) {
27335
+ if (updateSubflowPort.call(this, d)) {
27303
27336
  dirtyNodes[d.id] = d;
27304
- d.dirty = false;
27305
27337
  }
27306
- });
27338
+ })
27307
27339
  subflowStatus.each(function(d,i) {
27308
27340
  if (d.dirty) {
27309
27341
  var output = d3.select(this);
@@ -29186,6 +29218,9 @@ RED.view = (function() {
29186
29218
  suggestedNodes = [suggestedNodes]
29187
29219
  }
29188
29220
  suggestedNodes = suggestedNodes.filter(n => {
29221
+ if (n.type === 'junction') {
29222
+ return true
29223
+ }
29189
29224
  const def = RED.nodes.getType(n.type)
29190
29225
  if (def?.set && def.set.enabled === false) {
29191
29226
  // Exclude disabled node set
@@ -29357,7 +29392,6 @@ RED.view = (function() {
29357
29392
  refreshSuggestedFlow();
29358
29393
  } else { // Anything else; clear the suggestion
29359
29394
  clearSuggestedFlow();
29360
- RED.view.redraw(true);
29361
29395
  // manually push the event to the keyboard handler
29362
29396
  RED.keyboard.handle(evt)
29363
29397
  }
@@ -29366,7 +29400,6 @@ RED.view = (function() {
29366
29400
  if (suggestion.clickToApply) {
29367
29401
  $(window).on('mousedown.suggestedFlow', function (evnt) {
29368
29402
  clearSuggestedFlow();
29369
- RED.view.redraw(true);
29370
29403
  })
29371
29404
  }
29372
29405
  }
@@ -29387,12 +29420,16 @@ RED.view = (function() {
29387
29420
  }
29388
29421
 
29389
29422
  function clearSuggestedFlow () {
29390
- $(window).off('mousedown.suggestedFlow');
29391
- $(window).off('keydown.suggestedFlow')
29392
- RED.keyboard.enable()
29393
- currentSuggestion = null
29394
- suggestedNodes = []
29395
- suggestedLinks = []
29423
+ if (currentSuggestion) {
29424
+ $(window).off('mousedown.suggestedFlow');
29425
+ $(window).off('keydown.suggestedFlow')
29426
+ RED.keyboard.enable()
29427
+ currentSuggestion = null
29428
+ suggestedNodes = []
29429
+ suggestedLinks = []
29430
+ forceFullRedraw = true
29431
+ RED.view.redraw(true);
29432
+ }
29396
29433
  }
29397
29434
 
29398
29435
  function applySuggestedFlow () {
@@ -40019,6 +40056,9 @@ RED.editor = (function() {
40019
40056
  changes.inputLabels = node.inputLabels;
40020
40057
  node.inputLabels = newValue;
40021
40058
  changed = true;
40059
+ if (node.type === "subflow") {
40060
+ node.in[0].dirty = true
40061
+ }
40022
40062
  }
40023
40063
  hasNonBlankLabel = false;
40024
40064
  newValue = new Array(node.outputs);
@@ -47495,7 +47535,7 @@ RED.library = (function() {
47495
47535
  icon: 'fa fa-cube',
47496
47536
  label: options.type,
47497
47537
  path: "",
47498
- expanded: false,
47538
+ expanded: true,
47499
47539
  children: function(done, item) {
47500
47540
  loadLibraryFolder(lib.id, options.url, "", function(children) {
47501
47541
  item.children = children;
@@ -48438,9 +48478,13 @@ RED.search = (function() {
48438
48478
  function indexNode(n) {
48439
48479
  var l = RED.utils.getNodeLabel(n);
48440
48480
  if (l) {
48441
- l = (""+l).toLowerCase();
48442
- index[l] = index[l] || {};
48443
- index[l][n.id] = {node:n,label:l}
48481
+ const originalLabel = "" + l;
48482
+ const indexLabel = originalLabel.toLowerCase();
48483
+ index[indexLabel] = index[indexLabel] || {};
48484
+ index[indexLabel][n.id] = {
48485
+ node: n,
48486
+ label: originalLabel
48487
+ };
48444
48488
  }
48445
48489
  l = l||n.label||n.name||n.id||"";
48446
48490
 
@@ -48829,6 +48873,12 @@ RED.search = (function() {
48829
48873
  $('<div>',{class:"red-ui-search-result-node-type"}).text(node.type).appendTo(contentDiv);
48830
48874
  $('<div>',{class:"red-ui-search-result-node-id"}).text(node.id).appendTo(contentDiv);
48831
48875
 
48876
+ div.on("mouseover", function(evt) {
48877
+ if ( node.z == RED.workspaces.active() ) {
48878
+ RED.view.reveal(node.id)
48879
+ }
48880
+ });
48881
+
48832
48882
  div.on("click", function(evt) {
48833
48883
  evt.preventDefault();
48834
48884
  currentIndex = i;
@@ -49066,10 +49116,20 @@ RED.search = (function() {
49066
49116
  show: show,
49067
49117
  hide: hide,
49068
49118
  search: search,
49069
- getSearchOptions: getSearchOptions
49119
+ getSearchOptions: getSearchOptions,
49120
+ // Expose internals for testing
49121
+ _indexNode: indexNode,
49122
+ get _index() { return index; },
49123
+ set _index(val) { index = val; }
49070
49124
  };
49071
49125
 
49072
49126
  })();
49127
+
49128
+
49129
+ // Allow CommonJS import for testing
49130
+ if (typeof module !== "undefined" && module.exports) {
49131
+ module.exports = RED.search;
49132
+ }
49073
49133
  ;RED.contextMenu = (function () {
49074
49134
 
49075
49135
  let menu;
@@ -49295,6 +49355,11 @@ RED.search = (function() {
49295
49355
  { onselect: 'core:show-export-dialog', label: RED._("menu.label.export") }
49296
49356
  )
49297
49357
  }
49358
+ if (hasSelection && canEdit) {
49359
+ menuItems.push(
49360
+ { onselect: 'core:convert-to-subflow', label: RED._("menu.label.selectionToSubflow") }
49361
+ )
49362
+ }
49298
49363
  menuItems.push(
49299
49364
  { onselect: 'core:select-all-nodes', label: RED._("keyboard.selectAll") }
49300
49365
  )
@@ -53373,7 +53438,7 @@ RED.projects = (function() {
53373
53438
  var validateForm = function() {
53374
53439
  var valid = true;
53375
53440
  var flowFile = projectFlowFileInput.val();
53376
- if (flowFile === "" || !/\.json$/.test(flowFile)) {
53441
+ if (flowFile === "" || !/^[a-zA-Z0-9\-_]+\.json$/.test(flowFile)) {
53377
53442
  valid = false;
53378
53443
  if (!projectFlowFileInput.hasClass("input-error")) {
53379
53444
  projectFlowFileInput.addClass("input-error");
@@ -53795,7 +53860,7 @@ RED.projects = (function() {
53795
53860
 
53796
53861
  } else if (projectType === 'empty') {
53797
53862
  var flowFile = projectFlowFileInput.val();
53798
- if (flowFile === "" || !/\.json$/.test(flowFile)) {
53863
+ if (flowFile === "" || !/^[a-zA-Z0-9\-_]+\.json$/.test(flowFile)) {
53799
53864
  valid = false;
53800
53865
  if (!projectFlowFileInput.hasClass("input-error")) {
53801
53866
  projectFlowFileInput.addClass("input-error");