@node-red/editor-client 4.0.7 → 4.0.9

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
@@ -2017,10 +2017,10 @@ RED.user = (function() {
2017
2017
  userIcon.css({
2018
2018
  backgroundImage: "url("+user.image+")",
2019
2019
  })
2020
- } else if (user.anonymous) {
2020
+ } else if (user.anonymous || (!user.username && !user.email)) {
2021
2021
  $('<i class="fa fa-user"></i>').appendTo(userIcon);
2022
2022
  } else {
2023
- $('<span>').text(user.username.substring(0,2)).appendTo(userIcon);
2023
+ $('<span>').text((user.username || user.email).substring(0,2)).appendTo(userIcon);
2024
2024
  }
2025
2025
  if (user.profileColor !== undefined) {
2026
2026
  userIcon.addClass('red-ui-user-profile-color-' + user.profileColor)
@@ -2715,14 +2715,13 @@ RED.comms = (function() {
2715
2715
  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`);
2716
2716
  group.appendChild(anonIconBody)
2717
2717
  } else {
2718
- const labelText = user.username ? user.username.substring(0,2) : user
2719
2718
  const label = document.createElementNS("http://www.w3.org/2000/svg","text");
2720
- if (user.username) {
2719
+ if (user.username || user.email) {
2721
2720
  label.setAttribute("class","red-ui-multiplayer-annotation-label");
2722
- label.textContent = user.username.substring(0,2)
2721
+ label.textContent = (user.username || user.email).substring(0,2)
2723
2722
  } else {
2724
2723
  label.setAttribute("class","red-ui-multiplayer-annotation-label red-ui-multiplayer-user-count")
2725
- label.textContent = user
2724
+ label.textContent = 'nr'
2726
2725
  }
2727
2726
  label.setAttribute("text-anchor", "middle")
2728
2727
  label.setAttribute("x",radius/2);
@@ -6552,6 +6551,8 @@ RED.nodes = (function() {
6552
6551
  activeWorkspace = RED.workspaces.active();
6553
6552
  }
6554
6553
 
6554
+ const pendingConfigNodes = []
6555
+ const pendingConfigNodeIds = new Set()
6555
6556
  // Find all config nodes and add them
6556
6557
  for (i=0;i<newNodes.length;i++) {
6557
6558
  n = newNodes[i];
@@ -6611,7 +6612,8 @@ RED.nodes = (function() {
6611
6612
  type:n.type,
6612
6613
  info: n.info,
6613
6614
  users:[],
6614
- _config:{}
6615
+ _config:{},
6616
+ _configNodeReferences: new Set()
6615
6617
  };
6616
6618
  if (!n.z) {
6617
6619
  delete configNode.z;
@@ -6626,6 +6628,9 @@ RED.nodes = (function() {
6626
6628
  if (def.defaults.hasOwnProperty(d)) {
6627
6629
  configNode[d] = n[d];
6628
6630
  configNode._config[d] = JSON.stringify(n[d]);
6631
+ if (def.defaults[d].type) {
6632
+ configNode._configNodeReferences.add(n[d])
6633
+ }
6629
6634
  }
6630
6635
  }
6631
6636
  if (def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
@@ -6642,25 +6647,54 @@ RED.nodes = (function() {
6642
6647
  configNode.id = getID();
6643
6648
  }
6644
6649
  node_map[n.id] = configNode;
6645
- new_nodes.push(configNode);
6650
+ pendingConfigNodes.push(configNode);
6651
+ pendingConfigNodeIds.add(configNode.id)
6646
6652
  }
6647
6653
  }
6648
6654
  }
6649
6655
 
6650
- // Config node can use another config node, must ensure that this other
6651
- // config node is added before to exists when updating the user list
6652
- const configNodeFilter = function (node) {
6653
- let count = 0;
6654
- if (node._def?.defaults) {
6655
- for (const def of Object.values(node._def.defaults)) {
6656
- if (def.type) {
6657
- count++;
6658
- }
6656
+ // We need to sort new_nodes (which only contains config nodes at this point)
6657
+ // to ensure they get added in the right order. If NodeA depends on NodeB, then
6658
+ // NodeB must be added first.
6659
+
6660
+ // Limit us to 5 full iterations of the list - this should be more than
6661
+ // enough to process the list as config->config node relationships are
6662
+ // not very common
6663
+ let iterationLimit = pendingConfigNodes.length * 5
6664
+ const handledConfigNodes = new Set()
6665
+ while (pendingConfigNodes.length > 0 && iterationLimit > 0) {
6666
+ const node = pendingConfigNodes.shift()
6667
+ let hasPending = false
6668
+ // Loop through the nodes referenced by this node to see if anything
6669
+ // is pending
6670
+ node._configNodeReferences.forEach(id => {
6671
+ if (pendingConfigNodeIds.has(id) && !handledConfigNodes.has(id)) {
6672
+ // This reference is for a node we know is in this import, but
6673
+ // it isn't added yet - flag as pending
6674
+ hasPending = true
6659
6675
  }
6660
- }
6661
- return count;
6662
- };
6663
- new_nodes.sort((a, b) => configNodeFilter(a) - configNodeFilter(b));
6676
+ })
6677
+ if (!hasPending) {
6678
+ // This node has no pending config node references - safe to add
6679
+ delete node._configNodeReferences
6680
+ new_nodes.push(node)
6681
+ handledConfigNodes.add(node.id)
6682
+ } else {
6683
+ // This node has pending config node references
6684
+ // Put to the back of the queue
6685
+ pendingConfigNodes.push(node)
6686
+ }
6687
+ iterationLimit--
6688
+ }
6689
+ if (pendingConfigNodes.length > 0) {
6690
+ // We exceeded the iteration count. Could be due to reference loops
6691
+ // between the config nodes. At this point, just add the remaining
6692
+ // nodes as-is
6693
+ pendingConfigNodes.forEach(node => {
6694
+ delete node._configNodeReferences
6695
+ new_nodes.push(node)
6696
+ })
6697
+ }
6664
6698
 
6665
6699
  // Find regular flow nodes and subflow instances
6666
6700
  for (i=0;i<newNodes.length;i++) {
@@ -9760,7 +9794,7 @@ RED.utils = (function() {
9760
9794
  var pinnedPaths = {};
9761
9795
  var formattedPaths = {};
9762
9796
 
9763
- function addMessageControls(obj,sourceId,key,msg,rootPath,strippedKey,extraTools) {
9797
+ function addMessageControls(obj,sourceId,key,msg,rootPath,strippedKey,extraTools,enablePinning) {
9764
9798
  if (!pinnedPaths.hasOwnProperty(sourceId)) {
9765
9799
  pinnedPaths[sourceId] = {}
9766
9800
  }
@@ -9780,7 +9814,7 @@ RED.utils = (function() {
9780
9814
  RED.clipboard.copyText(msg,copyPayload,"clipboard.copyMessageValue");
9781
9815
  })
9782
9816
  RED.popover.tooltip(copyPayload,RED._("node-red:debug.sidebar.copyPayload"));
9783
- if (strippedKey !== undefined && strippedKey !== '') {
9817
+ if (enablePinning && strippedKey !== undefined && strippedKey !== '') {
9784
9818
  var isPinned = pinnedPaths[sourceId].hasOwnProperty(strippedKey);
9785
9819
 
9786
9820
  var pinPath = $('<button class="red-ui-button red-ui-button-small red-ui-debug-msg-tools-pin"><i class="fa fa-map-pin"></i></button>').appendTo(tools).on("click", function(e) {
@@ -9811,13 +9845,16 @@ RED.utils = (function() {
9811
9845
  }
9812
9846
  }
9813
9847
  }
9814
- function checkExpanded(strippedKey,expandPaths,minRange,maxRange) {
9848
+ function checkExpanded(strippedKey, expandPaths, { minRange, maxRange, expandLeafNodes }) {
9815
9849
  if (expandPaths && expandPaths.length > 0) {
9816
9850
  if (strippedKey === '' && minRange === undefined) {
9817
9851
  return true;
9818
9852
  }
9819
9853
  for (var i=0;i<expandPaths.length;i++) {
9820
9854
  var p = expandPaths[i];
9855
+ if (expandLeafNodes && p === strippedKey) {
9856
+ return true
9857
+ }
9821
9858
  if (p.indexOf(strippedKey) === 0 && (p[strippedKey.length] === "." || p[strippedKey.length] === "[") ) {
9822
9859
 
9823
9860
  if (minRange !== undefined && p[strippedKey.length] === "[") {
@@ -9924,6 +9961,8 @@ RED.utils = (function() {
9924
9961
  var sourceId = options.sourceId;
9925
9962
  var rootPath = options.rootPath;
9926
9963
  var expandPaths = options.expandPaths;
9964
+ const enablePinning = options.enablePinning
9965
+ const expandLeafNodes = options.expandLeafNodes;
9927
9966
  var ontoggle = options.ontoggle;
9928
9967
  var exposeApi = options.exposeApi;
9929
9968
  var tools = options.tools;
@@ -9946,11 +9985,11 @@ RED.utils = (function() {
9946
9985
  }
9947
9986
  header = $('<span class="red-ui-debug-msg-row"></span>').appendTo(element);
9948
9987
  if (sourceId) {
9949
- addMessageControls(header,sourceId,path,obj,rootPath,strippedKey,tools);
9988
+ addMessageControls(header,sourceId,path,obj,rootPath,strippedKey,tools, enablePinning);
9950
9989
  }
9951
9990
  if (!key) {
9952
9991
  element.addClass("red-ui-debug-msg-top-level");
9953
- if (sourceId) {
9992
+ if (sourceId && !expandPaths) {
9954
9993
  var pinned = pinnedPaths[sourceId];
9955
9994
  expandPaths = [];
9956
9995
  if (pinned) {
@@ -10006,7 +10045,7 @@ RED.utils = (function() {
10006
10045
  $('<span class="red-ui-debug-msg-type-meta red-ui-debug-msg-object-type-header"></span>').text(typeHint||'string').appendTo(header);
10007
10046
  var row = $('<div class="red-ui-debug-msg-object-entry collapsed"></div>').appendTo(element);
10008
10047
  $('<pre class="red-ui-debug-msg-type-string"></pre>').text(obj).appendTo(row);
10009
- },function(state) {if (ontoggle) { ontoggle(path,state);}}, checkExpanded(strippedKey,expandPaths));
10048
+ },function(state) {if (ontoggle) { ontoggle(path,state);}}, checkExpanded(strippedKey, expandPaths, { expandLeafNodes }));
10010
10049
  }
10011
10050
  e = $('<span class="red-ui-debug-msg-type-string red-ui-debug-msg-object-header"></span>').html('"'+formatString(sanitize(obj))+'"').appendTo(entryObj);
10012
10051
  if (/^#[0-9a-f]{6}$/i.test(obj)) {
@@ -10122,14 +10161,16 @@ RED.utils = (function() {
10122
10161
  typeHint: type==='buffer'?'hex':false,
10123
10162
  hideKey: false,
10124
10163
  path: path+"["+i+"]",
10125
- sourceId: sourceId,
10126
- rootPath: rootPath,
10127
- expandPaths: expandPaths,
10128
- ontoggle: ontoggle,
10129
- exposeApi: exposeApi,
10164
+ sourceId,
10165
+ rootPath,
10166
+ expandPaths,
10167
+ expandLeafNodes,
10168
+ ontoggle,
10169
+ exposeApi,
10130
10170
  // tools: tools // Do not pass tools down as we
10131
10171
  // keep them attached to the top-level header
10132
10172
  nodeSelector: options.nodeSelector,
10173
+ enablePinning
10133
10174
  }
10134
10175
  ).appendTo(row);
10135
10176
  }
@@ -10153,21 +10194,23 @@ RED.utils = (function() {
10153
10194
  typeHint: type==='buffer'?'hex':false,
10154
10195
  hideKey: false,
10155
10196
  path: path+"["+i+"]",
10156
- sourceId: sourceId,
10157
- rootPath: rootPath,
10158
- expandPaths: expandPaths,
10159
- ontoggle: ontoggle,
10160
- exposeApi: exposeApi,
10197
+ sourceId,
10198
+ rootPath,
10199
+ expandPaths,
10200
+ expandLeafNodes,
10201
+ ontoggle,
10202
+ exposeApi,
10161
10203
  // tools: tools // Do not pass tools down as we
10162
10204
  // keep them attached to the top-level header
10163
10205
  nodeSelector: options.nodeSelector,
10206
+ enablePinning
10164
10207
  }
10165
10208
  ).appendTo(row);
10166
10209
  }
10167
10210
  }
10168
10211
  })(),
10169
10212
  (function() { var path = path+"["+i+"]"; return function(state) {if (ontoggle) { ontoggle(path,state);}}})(),
10170
- checkExpanded(strippedKey,expandPaths,minRange,Math.min(fullLength-1,(minRange+9))));
10213
+ checkExpanded(strippedKey,expandPaths,{ minRange, maxRange: Math.min(fullLength-1,(minRange+9)), expandLeafNodes}));
10171
10214
  $('<span class="red-ui-debug-msg-object-key"></span>').html("["+minRange+" &hellip; "+Math.min(fullLength-1,(minRange+9))+"]").appendTo(header);
10172
10215
  }
10173
10216
  if (fullLength < originalLength) {
@@ -10176,7 +10219,7 @@ RED.utils = (function() {
10176
10219
  }
10177
10220
  },
10178
10221
  function(state) {if (ontoggle) { ontoggle(path,state);}},
10179
- checkExpanded(strippedKey,expandPaths));
10222
+ checkExpanded(strippedKey, expandPaths, { expandLeafNodes }));
10180
10223
  }
10181
10224
  } else if (typeof obj === 'object') {
10182
10225
  element.addClass('collapsed');
@@ -10210,14 +10253,16 @@ RED.utils = (function() {
10210
10253
  typeHint: false,
10211
10254
  hideKey: false,
10212
10255
  path: newPath,
10213
- sourceId: sourceId,
10214
- rootPath: rootPath,
10215
- expandPaths: expandPaths,
10216
- ontoggle: ontoggle,
10217
- exposeApi: exposeApi,
10256
+ sourceId,
10257
+ rootPath,
10258
+ expandPaths,
10259
+ expandLeafNodes,
10260
+ ontoggle,
10261
+ exposeApi,
10218
10262
  // tools: tools // Do not pass tools down as we
10219
10263
  // keep them attached to the top-level header
10220
10264
  nodeSelector: options.nodeSelector,
10265
+ enablePinning
10221
10266
  }
10222
10267
  ).appendTo(row);
10223
10268
  }
@@ -10226,7 +10271,7 @@ RED.utils = (function() {
10226
10271
  }
10227
10272
  },
10228
10273
  function(state) {if (ontoggle) { ontoggle(path,state);}},
10229
- checkExpanded(strippedKey,expandPaths));
10274
+ checkExpanded(strippedKey, expandPaths, { expandLeafNodes }));
10230
10275
  }
10231
10276
  if (key) {
10232
10277
  $('<span class="red-ui-debug-msg-type-meta"></span>').text(type).appendTo(entryObj);
@@ -15254,6 +15299,7 @@ RED.stack = (function() {
15254
15299
  pre: value.substring(0,idx),
15255
15300
  match: value.substring(idx,idx+len),
15256
15301
  post: value.substring(idx+len),
15302
+ exact: idx === 0 && value.length === searchValue.length
15257
15303
  }
15258
15304
  }
15259
15305
  function generateSpans(match) {
@@ -15274,7 +15320,7 @@ RED.stack = (function() {
15274
15320
  const srcMatch = getMatch(optSrc, val);
15275
15321
  if (valMatch.found || srcMatch.found) {
15276
15322
  const element = $('<div>',{style: "display: flex"});
15277
- const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
15323
+ const valEl = $('<div/>',{ class: "red-ui-autoComplete-completion" });
15278
15324
  valEl.append(generateSpans(valMatch));
15279
15325
  valEl.appendTo(element);
15280
15326
  if (optSrc) {
@@ -15350,7 +15396,7 @@ RED.stack = (function() {
15350
15396
  if (valMatch.found) {
15351
15397
  const optSrc = envVarsMap[v]
15352
15398
  const element = $('<div>',{style: "display: flex"});
15353
- const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
15399
+ const valEl = $('<div/>',{ class: "red-ui-autoComplete-completion" });
15354
15400
  valEl.append(generateSpans(valMatch))
15355
15401
  valEl.appendTo(element)
15356
15402
 
@@ -15392,7 +15438,7 @@ RED.stack = (function() {
15392
15438
  const that = this
15393
15439
  const getContextKeysFromRuntime = function(scope, store, searchKey, done) {
15394
15440
  contextKnownKeys[scope] = contextKnownKeys[scope] || {}
15395
- contextKnownKeys[scope][store] = contextKnownKeys[scope][store] || new Set()
15441
+ contextKnownKeys[scope][store] = contextKnownKeys[scope][store] || new Map()
15396
15442
  if (searchKey.length > 0) {
15397
15443
  try {
15398
15444
  RED.utils.normalisePropertyExpression(searchKey)
@@ -15414,11 +15460,12 @@ RED.stack = (function() {
15414
15460
  const result = data[store] || {}
15415
15461
  const keys = result.keys || []
15416
15462
  const keyPrefix = searchKey + (searchKey.length > 0 ? '.' : '')
15417
- keys.forEach(key => {
15463
+ keys.forEach(keyInfo => {
15464
+ const key = keyInfo.key
15418
15465
  if (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(key)) {
15419
- contextKnownKeys[scope][store].add(keyPrefix + key)
15466
+ contextKnownKeys[scope][store].set(keyPrefix + key, keyInfo)
15420
15467
  } else {
15421
- contextKnownKeys[scope][store].add(searchKey + "[\""+key.replace(/"/,"\\\"")+"\"]")
15468
+ contextKnownKeys[scope][store].set(searchKey + "[\""+key.replace(/"/,"\\\"")+"\"]", keyInfo)
15422
15469
  }
15423
15470
  })
15424
15471
  done()
@@ -15433,14 +15480,14 @@ RED.stack = (function() {
15433
15480
  // Get the flow id of the node we're editing
15434
15481
  const editStack = RED.editor.getEditStack()
15435
15482
  if (editStack.length === 0) {
15436
- done([])
15483
+ done(new Map())
15437
15484
  return
15438
15485
  }
15439
15486
  const editingNode = editStack.pop()
15440
15487
  if (editingNode.z) {
15441
15488
  scope = `${scope}/${editingNode.z}`
15442
15489
  } else {
15443
- done([])
15490
+ done(new Map())
15444
15491
  return
15445
15492
  }
15446
15493
  }
@@ -15460,17 +15507,29 @@ RED.stack = (function() {
15460
15507
  return function(val, done) {
15461
15508
  getContextKeys(val, function (keys) {
15462
15509
  const matches = []
15463
- keys.forEach(v => {
15510
+ keys.forEach((keyInfo, v) => {
15464
15511
  let optVal = v
15465
15512
  let valMatch = getMatch(optVal, val);
15466
- if (!valMatch.found && val.length > 0 && val.endsWith('.')) {
15467
- // Search key ends in '.' - but doesn't match. Check again
15468
- // with [" at the end instead so we match bracket notation
15469
- valMatch = getMatch(optVal, val.substring(0, val.length - 1) + '["')
15513
+ if (!valMatch.found && val.length > 0) {
15514
+ if (val.endsWith('.')) {
15515
+ // Search key ends in '.' - but doesn't match. Check again
15516
+ // with [" at the end instead so we match bracket notation
15517
+ valMatch = getMatch(optVal, val.substring(0, val.length - 1) + '["')
15518
+ // } else if (val.endsWith('[') && /^array/.test(keyInfo.format)) {
15519
+ // console.log('this case')
15520
+ }
15470
15521
  }
15471
15522
  if (valMatch.found) {
15472
15523
  const element = $('<div>',{style: "display: flex"});
15473
- const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
15524
+ const valEl = $('<div/>',{ class: "red-ui-autoComplete-completion" });
15525
+ // if (keyInfo.format) {
15526
+ // valMatch.post += ' ' + keyInfo.format
15527
+ // }
15528
+ if (valMatch.exact && /^array/.test(keyInfo.format)) {
15529
+ valMatch.post += `[0-${keyInfo.length}]`
15530
+ optVal += '['
15531
+
15532
+ }
15474
15533
  valEl.append(generateSpans(valMatch))
15475
15534
  valEl.appendTo(element)
15476
15535
  matches.push({
@@ -16758,7 +16817,8 @@ RED.stack = (function() {
16758
16817
  if (tooltip) {
16759
16818
  tooltip.setContent(valid);
16760
16819
  } else {
16761
- tooltip = RED.popover.tooltip(this.elementDiv, valid);
16820
+ const target = this.typeMap[type]?.options ? this.optionSelectLabel : this.elementDiv;
16821
+ tooltip = RED.popover.tooltip(target, valid);
16762
16822
  this.element.data("tooltip", tooltip);
16763
16823
  }
16764
16824
  }
@@ -16990,7 +17050,7 @@ RED.stack = (function() {
16990
17050
  }
16991
17051
  this.menu = RED.popover.menu({
16992
17052
  tabSelect: true,
16993
- width: 300,
17053
+ width: Math.max(300, this.element.width()),
16994
17054
  maxHeight: 200,
16995
17055
  class: "red-ui-autoComplete-container",
16996
17056
  options: completions,
@@ -17193,6 +17253,7 @@ RED.deploy = (function() {
17193
17253
  /**
17194
17254
  * options:
17195
17255
  * type: "default" - Button with drop-down options - no further customisation available
17256
+ * label: the text to display - default: "Deploy"
17196
17257
  * type: "simple" - Button without dropdown. Customisations:
17197
17258
  * label: the text to display - default: "Deploy"
17198
17259
  * icon : the icon to use. Null removes the icon. default: "red/images/deploy-full-o.svg"
@@ -17200,13 +17261,14 @@ RED.deploy = (function() {
17200
17261
  function init(options) {
17201
17262
  options = options || {};
17202
17263
  var type = options.type || "default";
17264
+ var label = options.label || RED._("deploy.deploy");
17203
17265
 
17204
17266
  if (type == "default") {
17205
17267
  $('<li><span class="red-ui-deploy-button-group button-group">'+
17206
17268
  '<a id="red-ui-header-button-deploy" class="red-ui-deploy-button disabled" href="#">'+
17207
17269
  '<span class="red-ui-deploy-button-content">'+
17208
17270
  '<img id="red-ui-header-button-deploy-icon" src="red/images/deploy-full-o.svg"> '+
17209
- '<span>'+RED._("deploy.deploy")+'</span>'+
17271
+ '<span>'+label+'</span>'+
17210
17272
  '</span>'+
17211
17273
  '<span class="red-ui-deploy-button-spinner hide">'+
17212
17274
  '<img src="red/images/spin.svg"/>'+
@@ -17227,7 +17289,6 @@ RED.deploy = (function() {
17227
17289
  mainMenuItems.push({id:"deploymenu-item-reload", icon:"red/images/deploy-reload.svg",label:RED._("deploy.restartFlows"),sublabel:RED._("deploy.restartFlowsDesc"),onselect:"core:restart-flows"})
17228
17290
  RED.menu.init({id:"red-ui-header-button-deploy-options", options: mainMenuItems });
17229
17291
  } else if (type == "simple") {
17230
- var label = options.label || RED._("deploy.deploy");
17231
17292
  var icon = 'red/images/deploy-full-o.svg';
17232
17293
  if (options.hasOwnProperty('icon')) {
17233
17294
  icon = options.icon;
@@ -23410,11 +23471,6 @@ RED.view = (function() {
23410
23471
  var targetGroup = options.group;
23411
23472
  var touchTrigger = options.touchTrigger;
23412
23473
 
23413
- if (targetGroup) {
23414
- selectedGroups.add(targetGroup,false);
23415
- RED.view.redraw();
23416
- }
23417
-
23418
23474
  // `point` is the place in the workspace the mouse has clicked.
23419
23475
  // This takes into account scrolling and scaling of the workspace.
23420
23476
  var ox = point[0];
@@ -23736,9 +23792,6 @@ RED.view = (function() {
23736
23792
  // auto select dropped node - so info shows (if visible)
23737
23793
  clearSelection();
23738
23794
  nn.selected = true;
23739
- if (targetGroup) {
23740
- selectedGroups.add(targetGroup,false);
23741
- }
23742
23795
  movingSet.add(nn);
23743
23796
  updateActiveNodes();
23744
23797
  updateSelection();
@@ -24323,19 +24376,24 @@ RED.view = (function() {
24323
24376
  n.n.moved = true;
24324
24377
  }
24325
24378
  }
24326
-
24327
- // Check to see if we need to splice a link
24379
+ // If a node has moved and ends up being spliced into a link, keep
24380
+ // track of which historyEvent to add the splice info to
24381
+ let targetSpliceEvent = null
24328
24382
  if (moveEvent.nodes.length > 0) {
24329
24383
  historyEvent.events.push(moveEvent)
24330
- if (activeSpliceLink) {
24331
- var linkToSplice = d3.select(activeSpliceLink).data()[0];
24332
- spliceLink(linkToSplice, movingSet.get(0).n, moveEvent)
24333
- }
24384
+ targetSpliceEvent = moveEvent
24334
24385
  }
24335
24386
  if (moveAndChangedGroupEvent.nodes.length > 0) {
24336
24387
  historyEvent.events.push(moveAndChangedGroupEvent)
24388
+ targetSpliceEvent = moveAndChangedGroupEvent
24337
24389
  }
24338
-
24390
+ // activeSpliceLink will only be set if the movingSet has a single
24391
+ // node that is able to splice.
24392
+ if (targetSpliceEvent && activeSpliceLink) {
24393
+ var linkToSplice = d3.select(activeSpliceLink).data()[0];
24394
+ spliceLink(linkToSplice, movingSet.get(0).n, targetSpliceEvent)
24395
+ }
24396
+
24339
24397
  // Only continue if something has moved
24340
24398
  if (historyEvent.events.length > 0) {
24341
24399
  RED.nodes.dirty(true);
@@ -33794,8 +33852,6 @@ RED.sidebar.context = (function() {
33794
33852
  var content;
33795
33853
  var sections;
33796
33854
 
33797
- var localCache = {};
33798
-
33799
33855
  var flowAutoRefresh;
33800
33856
  var nodeAutoRefresh;
33801
33857
  var nodeSection;
@@ -33803,6 +33859,8 @@ RED.sidebar.context = (function() {
33803
33859
  var flowSection;
33804
33860
  var globalSection;
33805
33861
 
33862
+ const expandedPaths = {}
33863
+
33806
33864
  var currentNode;
33807
33865
  var currentFlow;
33808
33866
 
@@ -33988,14 +34046,41 @@ RED.sidebar.context = (function() {
33988
34046
  var l = keys.length;
33989
34047
  for (var i = 0; i < l; i++) {
33990
34048
  sortedData[keys[i]].forEach(function(v) {
33991
- var k = keys[i];
33992
- var l2 = sortedData[k].length;
33993
- var propRow = $('<tr class="red-ui-help-info-row"><td class="red-ui-sidebar-context-property"></td><td></td></tr>').appendTo(container);
33994
- var obj = $(propRow.children()[0]);
34049
+ const k = keys[i];
34050
+ let payload = v.msg;
34051
+ let format = v.format;
34052
+ const tools = $('<span class="button-group"></span>');
34053
+ expandedPaths[id + "." + k] = expandedPaths[id + "." + k] || new Set()
34054
+ const objectElementOptions = {
34055
+ typeHint: format,
34056
+ sourceId: id + "." + k,
34057
+ tools,
34058
+ path: k,
34059
+ rootPath: k,
34060
+ exposeApi: true,
34061
+ ontoggle: function(path,state) {
34062
+ path = path.substring(k.length+1)
34063
+ if (state) {
34064
+ expandedPaths[id+"."+k].add(path)
34065
+ } else {
34066
+ // if 'a' has been collapsed, we want to remove 'a.b' and 'a[0]...' from the set
34067
+ // of collapsed paths
34068
+ for (let expandedPath of expandedPaths[id+"."+k]) {
34069
+ if (expandedPath.startsWith(path+".") || expandedPath.startsWith(path+"[")) {
34070
+ expandedPaths[id+"."+k].delete(expandedPath)
34071
+ }
34072
+ }
34073
+ expandedPaths[id+"."+k].delete(path)
34074
+ }
34075
+ },
34076
+ expandPaths: [ ...expandedPaths[id+"."+k] ].sort(),
34077
+ expandLeafNodes: true
34078
+ }
34079
+ const propRow = $('<tr class="red-ui-help-info-row"><td class="red-ui-sidebar-context-property"></td><td></td></tr>').appendTo(container);
34080
+ const obj = $(propRow.children()[0]);
33995
34081
  obj.text(k);
33996
- var tools = $('<span class="button-group"></span>');
33997
34082
  const urlSafeK = encodeURIComponent(k)
33998
- var refreshItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-refresh"></i></button>').appendTo(tools).on("click", function(e) {
34083
+ const refreshItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-refresh"></i></button>').appendTo(tools).on("click", function(e) {
33999
34084
  e.preventDefault();
34000
34085
  e.stopPropagation();
34001
34086
  $.getJSON(baseUrl+"/"+urlSafeK+"?store="+v.store, function(data) {
@@ -34005,16 +34090,14 @@ RED.sidebar.context = (function() {
34005
34090
  tools.detach();
34006
34091
  $(propRow.children()[1]).empty();
34007
34092
  RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
34093
+ ...objectElementOptions,
34008
34094
  typeHint: data.format,
34009
- sourceId: id+"."+k,
34010
- tools: tools,
34011
- path: k
34012
34095
  }).appendTo(propRow.children()[1]);
34013
34096
  }
34014
34097
  })
34015
34098
  });
34016
34099
  RED.popover.tooltip(refreshItem,RED._("sidebar.context.refrsh"));
34017
- var deleteItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-trash"></i></button>').appendTo(tools).on("click", function(e) {
34100
+ const deleteItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-trash"></i></button>').appendTo(tools).on("click", function(e) {
34018
34101
  e.preventDefault();
34019
34102
  e.stopPropagation();
34020
34103
  var popover = RED.popover.create({
@@ -34022,7 +34105,7 @@ RED.sidebar.context = (function() {
34022
34105
  target: propRow,
34023
34106
  direction: "left",
34024
34107
  content: function() {
34025
- var content = $('<div>');
34108
+ const content = $('<div>');
34026
34109
  $('<p data-i18n="sidebar.context.deleteConfirm"></p>').appendTo(content);
34027
34110
  var row = $('<p>').appendTo(content);
34028
34111
  var bg = $('<span class="button-group"></span>').appendTo(row);
@@ -34045,16 +34128,15 @@ RED.sidebar.context = (function() {
34045
34128
  if (container.children().length === 0) {
34046
34129
  $('<tr class="red-ui-help-info-row red-ui-search-empty blank" colspan="2"><td data-i18n="sidebar.context.empty"></td></tr>').appendTo(container).i18n();
34047
34130
  }
34131
+ delete expandedPaths[id + "." + k]
34048
34132
  } else {
34049
34133
  payload = data.msg;
34050
34134
  format = data.format;
34051
34135
  tools.detach();
34052
34136
  $(propRow.children()[1]).empty();
34053
34137
  RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
34054
- typeHint: data.format,
34055
- sourceId: id+"."+k,
34056
- tools: tools,
34057
- path: k
34138
+ ...objectElementOptions,
34139
+ typeHint: data.format
34058
34140
  }).appendTo(propRow.children()[1]);
34059
34141
  }
34060
34142
  });
@@ -34069,14 +34151,7 @@ RED.sidebar.context = (function() {
34069
34151
 
34070
34152
  });
34071
34153
  RED.popover.tooltip(deleteItem,RED._("sidebar.context.delete"));
34072
- var payload = v.msg;
34073
- var format = v.format;
34074
- RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
34075
- typeHint: v.format,
34076
- sourceId: id+"."+k,
34077
- tools: tools,
34078
- path: k
34079
- }).appendTo(propRow.children()[1]);
34154
+ RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), objectElementOptions).appendTo(propRow.children()[1]);
34080
34155
  if (contextStores.length > 1) {
34081
34156
  $("<span>",{class:"red-ui-sidebar-context-property-storename"}).text(v.store).appendTo($(propRow.children()[0]))
34082
34157
  }
@@ -37414,8 +37489,18 @@ RED.editor = (function() {
37414
37489
  }
37415
37490
  });
37416
37491
  }
37417
-
37492
+ let envToRemove = new Set()
37418
37493
  if (!isSameObj(old_env, new_env)) {
37494
+ // Get a list of env properties that have been removed
37495
+ // by comparing old_env and new_env
37496
+ if (old_env) {
37497
+ old_env.forEach(env => { envToRemove.add(env.name) })
37498
+ }
37499
+ if (new_env) {
37500
+ new_env.forEach(env => {
37501
+ envToRemove.delete(env.name)
37502
+ })
37503
+ }
37419
37504
  editState.changes.env = editing_node.env;
37420
37505
  editing_node.env = new_env;
37421
37506
  editState.changed = true;
@@ -37424,10 +37509,11 @@ RED.editor = (function() {
37424
37509
 
37425
37510
 
37426
37511
  if (editState.changed) {
37427
- var wasChanged = editing_node.changed;
37512
+ let wasChanged = editing_node.changed;
37428
37513
  editing_node.changed = true;
37429
37514
  validateNode(editing_node);
37430
- var subflowInstances = [];
37515
+ let subflowInstances = [];
37516
+ let instanceHistoryEvents = []
37431
37517
  RED.nodes.eachNode(function(n) {
37432
37518
  if (n.type == "subflow:"+editing_node.id) {
37433
37519
  subflowInstances.push({
@@ -37437,13 +37523,35 @@ RED.editor = (function() {
37437
37523
  n._def.color = editing_node.color;
37438
37524
  n.changed = true;
37439
37525
  n.dirty = true;
37526
+ if (n.env) {
37527
+ const oldEnv = n.env
37528
+ const newEnv = []
37529
+ let envChanged = false
37530
+ n.env.forEach((env, index) => {
37531
+ if (envToRemove.has(env.name)) {
37532
+ envChanged = true
37533
+ } else {
37534
+ newEnv.push(env)
37535
+ }
37536
+ })
37537
+ if (envChanged) {
37538
+ instanceHistoryEvents.push({
37539
+ t: 'edit',
37540
+ node: n,
37541
+ changes: { env: oldEnv },
37542
+ dirty: n.dirty,
37543
+ changed: n.changed
37544
+ })
37545
+ n.env = newEnv
37546
+ }
37547
+ }
37440
37548
  updateNodeProperties(n);
37441
37549
  validateNode(n);
37442
37550
  }
37443
37551
  });
37444
37552
  RED.events.emit("subflows:change",editing_node);
37445
37553
  RED.nodes.dirty(true);
37446
- var historyEvent = {
37554
+ let historyEvent = {
37447
37555
  t:'edit',
37448
37556
  node:editing_node,
37449
37557
  changes:editState.changes,
@@ -37453,7 +37561,13 @@ RED.editor = (function() {
37453
37561
  instances:subflowInstances
37454
37562
  }
37455
37563
  };
37456
-
37564
+ if (instanceHistoryEvents.length > 0) {
37565
+ historyEvent = {
37566
+ t: 'multi',
37567
+ events: [ historyEvent, ...instanceHistoryEvents ],
37568
+ dirty: wasDirty
37569
+ }
37570
+ }
37457
37571
  RED.history.push(historyEvent);
37458
37572
  }
37459
37573
  editing_node.dirty = true;
@@ -42899,6 +43013,7 @@ RED.editor.codeEditor.monaco = (function() {
42899
43013
  2322, //Type 'unknown' is not assignable to type 'string'
42900
43014
  2339, //property does not exist on
42901
43015
  2345, //Argument of type xxx is not assignable to parameter of type 'DateTimeFormatOptions'
43016
+ 2538, //Ignore symbols as index property error.
42902
43017
  7043, //i forget what this one is,
42903
43018
  80001, //Convert to ES6 module
42904
43019
  80004, //JSDoc types may be moved to TypeScript types.
@@ -45748,10 +45863,15 @@ RED.library = (function() {
45748
45863
  if (lib.types && lib.types.indexOf(options.url) === -1) {
45749
45864
  return;
45750
45865
  }
45866
+ let icon = 'fa fa-hdd-o';
45867
+ if (lib.icon) {
45868
+ const fullIcon = RED.utils.separateIconPath(lib.icon);
45869
+ icon = (fullIcon.module==="font-awesome"?"fa ":"")+fullIcon.file;
45870
+ }
45751
45871
  listing.push({
45752
45872
  library: lib.id,
45753
45873
  type: options.url,
45754
- icon: lib.icon || 'fa fa-hdd-o',
45874
+ icon,
45755
45875
  label: RED._(lib.label||lib.id),
45756
45876
  path: "",
45757
45877
  expanded: true,
@@ -45806,10 +45926,15 @@ RED.library = (function() {
45806
45926
  if (lib.types && lib.types.indexOf(options.url) === -1) {
45807
45927
  return;
45808
45928
  }
45929
+ let icon = 'fa fa-hdd-o';
45930
+ if (lib.icon) {
45931
+ const fullIcon = RED.utils.separateIconPath(lib.icon);
45932
+ icon = (fullIcon.module==="font-awesome"?"fa ":"")+fullIcon.file;
45933
+ }
45809
45934
  listing.push({
45810
45935
  library: lib.id,
45811
45936
  type: options.url,
45812
- icon: lib.icon || 'fa fa-hdd-o',
45937
+ icon,
45813
45938
  label: RED._(lib.label||lib.id),
45814
45939
  path: "",
45815
45940
  expanded: true,