@node-red/editor-client 2.2.1 → 3.0.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.
Files changed (143) hide show
  1. package/locales/de/editor.json +14 -11
  2. package/locales/en-US/editor.json +45 -14
  3. package/locales/ja/editor.json +53 -17
  4. package/locales/ko/editor.json +5 -2
  5. package/locales/ru/editor.json +14 -11
  6. package/locales/zh-CN/editor.json +14 -11
  7. package/locales/zh-TW/editor.json +14 -11
  8. package/package.json +1 -1
  9. package/public/red/about +119 -0
  10. package/public/red/images/grip-horizontal.svg +1 -0
  11. package/public/red/images/grip.svg +1 -0
  12. package/public/red/keymap.json +5 -1
  13. package/public/red/red.js +2161 -674
  14. package/public/red/red.min.js +2 -2
  15. package/public/red/style.min.css +2 -2
  16. package/public/red/tours/images/continuous-search.png +0 -0
  17. package/public/red/tours/images/debug-path-tooltip.png +0 -0
  18. package/public/red/tours/images/junction-quick-add.png +0 -0
  19. package/public/red/tours/images/junction-slice.gif +0 -0
  20. package/public/red/tours/images/split-wire-with-links.gif +0 -0
  21. package/public/red/tours/welcome.js +80 -97
  22. package/public/types/node/assert.d.ts +5 -1
  23. package/public/types/node/async_hooks.d.ts +4 -1
  24. package/public/types/node/buffer.d.ts +68 -1
  25. package/public/types/node/child_process.d.ts +4 -1
  26. package/public/types/node/cluster.d.ts +4 -1
  27. package/public/types/node/console.d.ts +10 -1
  28. package/public/types/node/crypto.d.ts +6 -3
  29. package/public/types/node/dgram.d.ts +12 -9
  30. package/public/types/node/dns.d.ts +8 -1
  31. package/public/types/node/domain.d.ts +4 -1
  32. package/public/types/node/events.d.ts +5 -1
  33. package/public/types/node/fs.d.ts +10 -7
  34. package/public/types/node/globals.d.ts +140 -7
  35. package/public/types/node/http.d.ts +10 -4
  36. package/public/types/node/http2.d.ts +8 -5
  37. package/public/types/node/https.d.ts +4 -1
  38. package/public/types/node/module.d.ts +5 -1
  39. package/public/types/node/net.d.ts +7 -4
  40. package/public/types/node/os.d.ts +4 -1
  41. package/public/types/node/path.d.ts +5 -1
  42. package/public/types/node/perf_hooks.d.ts +4 -1
  43. package/public/types/node/process.d.ts +6 -2
  44. package/public/types/node/querystring.d.ts +4 -1
  45. package/public/types/node/readline.d.ts +5 -1
  46. package/public/types/node/stream.d.ts +13 -9
  47. package/public/types/node/string_decoder.d.ts +4 -1
  48. package/public/types/node/timers.d.ts +4 -1
  49. package/public/types/node/tls.d.ts +6 -2
  50. package/public/types/node/trace_events.d.ts +4 -1
  51. package/public/types/node/tty.d.ts +4 -1
  52. package/public/types/node/url.d.ts +35 -2
  53. package/public/types/node/util.d.ts +11 -2
  54. package/public/types/node/v8.d.ts +4 -1
  55. package/public/types/node/vm.d.ts +4 -1
  56. package/public/types/node/wasi.d.ts +4 -1
  57. package/public/types/node/worker_threads.d.ts +4 -1
  58. package/public/types/node/zlib.d.ts +4 -1
  59. package/public/vendor/monaco/dist/ThirdPartyNotices.txt +382 -217
  60. package/public/vendor/monaco/dist/ade705761eb7e702770d.ttf +0 -0
  61. package/public/vendor/monaco/dist/css.worker.js +2 -1
  62. package/public/vendor/monaco/dist/css.worker.js.LICENSE.txt +6 -0
  63. package/public/vendor/monaco/dist/editor.js +2 -5
  64. package/public/vendor/monaco/dist/editor.js.LICENSE.txt +8 -0
  65. package/public/vendor/monaco/dist/editor.worker.js +1 -1
  66. package/public/vendor/monaco/dist/html.worker.js +2 -1
  67. package/public/vendor/monaco/dist/html.worker.js.LICENSE.txt +6 -0
  68. package/public/vendor/monaco/dist/json.worker.js +2 -1
  69. package/public/vendor/monaco/dist/json.worker.js.LICENSE.txt +6 -0
  70. package/public/vendor/monaco/dist/locale/cs.js +1550 -1445
  71. package/public/vendor/monaco/dist/locale/de.js +1550 -1445
  72. package/public/vendor/monaco/dist/locale/es.js +1550 -1445
  73. package/public/vendor/monaco/dist/locale/fr.js +1550 -1445
  74. package/public/vendor/monaco/dist/locale/it.js +1550 -1445
  75. package/public/vendor/monaco/dist/locale/ja.js +1550 -1445
  76. package/public/vendor/monaco/dist/locale/ko.js +1550 -1445
  77. package/public/vendor/monaco/dist/locale/pl.js +1550 -1445
  78. package/public/vendor/monaco/dist/locale/pt-br.js +1550 -1445
  79. package/public/vendor/monaco/dist/locale/qps-ploc.js +1550 -1445
  80. package/public/vendor/monaco/dist/locale/ru.js +1550 -1445
  81. package/public/vendor/monaco/dist/locale/tr.js +1550 -1445
  82. package/public/vendor/monaco/dist/locale/zh-hans.js +1550 -1445
  83. package/public/vendor/monaco/dist/locale/zh-hant.js +1550 -1445
  84. package/public/vendor/monaco/dist/theme/ace.json +197 -0
  85. package/public/vendor/monaco/dist/theme/active4d.json +4 -0
  86. package/public/vendor/monaco/dist/theme/all-hallows-eve.json +4 -0
  87. package/public/vendor/monaco/dist/theme/amy.json +4 -0
  88. package/public/vendor/monaco/dist/theme/birds-of-paradise.json +4 -0
  89. package/public/vendor/monaco/dist/theme/blackboard.json +4 -0
  90. package/public/vendor/monaco/dist/theme/brilliance-black.json +4 -0
  91. package/public/vendor/monaco/dist/theme/brilliance-dull.json +4 -0
  92. package/public/vendor/monaco/dist/theme/chrome-devtools.json +4 -0
  93. package/public/vendor/monaco/dist/theme/clouds-midnight.json +4 -0
  94. package/public/vendor/monaco/dist/theme/clouds.json +4 -0
  95. package/public/vendor/monaco/dist/theme/cobalt.json +4 -0
  96. package/public/vendor/monaco/dist/theme/cobalt2.json +859 -0
  97. package/public/vendor/monaco/dist/theme/dawn.json +4 -0
  98. package/public/vendor/monaco/dist/theme/dracula.json +208 -0
  99. package/public/vendor/monaco/dist/theme/dreamweaver.json +4 -0
  100. package/public/vendor/monaco/dist/theme/eiffel.json +4 -0
  101. package/public/vendor/monaco/dist/theme/espresso-libre.json +4 -0
  102. package/public/vendor/monaco/dist/theme/github.json +4 -0
  103. package/public/vendor/monaco/dist/theme/idle.json +4 -0
  104. package/public/vendor/monaco/dist/theme/idlefingers.json +4 -0
  105. package/public/vendor/monaco/dist/theme/iplastic.json +4 -0
  106. package/public/vendor/monaco/dist/theme/katzenmilch.json +4 -0
  107. package/public/vendor/monaco/dist/theme/krtheme.json +4 -0
  108. package/public/vendor/monaco/dist/theme/kuroir-theme.json +4 -0
  109. package/public/vendor/monaco/dist/theme/lazy.json +4 -0
  110. package/public/vendor/monaco/dist/theme/magicwb-amiga.json +4 -0
  111. package/public/vendor/monaco/dist/theme/merbivore-soft.json +4 -0
  112. package/public/vendor/monaco/dist/theme/merbivore.json +4 -0
  113. package/public/vendor/monaco/dist/theme/monoindustrial.json +4 -0
  114. package/public/vendor/monaco/dist/theme/monokai-bright.json +4 -0
  115. package/public/vendor/monaco/dist/theme/monokai.json +4 -0
  116. package/public/vendor/monaco/dist/theme/night-owl.json +4 -0
  117. package/public/vendor/monaco/dist/theme/oceanic-next.json +4 -0
  118. package/public/vendor/monaco/dist/theme/pastels-on-dark.json +4 -0
  119. package/public/vendor/monaco/dist/theme/slush-and-poppies.json +4 -0
  120. package/public/vendor/monaco/dist/theme/solarized-dark.json +4 -0
  121. package/public/vendor/monaco/dist/theme/solarized-light.json +4 -0
  122. package/public/vendor/monaco/dist/theme/spacecadet.json +4 -0
  123. package/public/vendor/monaco/dist/theme/sunburst.json +4 -0
  124. package/public/vendor/monaco/dist/theme/textmate-mac-classic.json +4 -0
  125. package/public/vendor/monaco/dist/theme/tomorrow-night-blue.json +4 -0
  126. package/public/vendor/monaco/dist/theme/tomorrow-night-bright.json +4 -0
  127. package/public/vendor/monaco/dist/theme/tomorrow-night-eighties.json +4 -0
  128. package/public/vendor/monaco/dist/theme/tomorrow-night.json +4 -0
  129. package/public/vendor/monaco/dist/theme/tomorrow.json +4 -0
  130. package/public/vendor/monaco/dist/theme/twilight.json +4 -0
  131. package/public/vendor/monaco/dist/theme/upstream-sunburst.json +4 -0
  132. package/public/vendor/monaco/dist/theme/vibrant-ink.json +4 -0
  133. package/public/vendor/monaco/dist/theme/xcode_default.json +4 -0
  134. package/public/vendor/monaco/dist/theme/zenburnesque.json +4 -0
  135. package/public/vendor/monaco/dist/ts.worker.js +2 -16
  136. package/public/vendor/monaco/dist/ts.worker.js.LICENSE.txt +6 -0
  137. package/public/vendor/vendor.js +4 -4
  138. package/public/red/images/grip-horizontal.png +0 -0
  139. package/public/red/images/grip.png +0 -0
  140. package/public/red/tours/images/delete-repair.gif +0 -0
  141. package/public/red/tours/images/detach-repair.gif +0 -0
  142. package/public/red/tours/images/slice.gif +0 -0
  143. package/public/red/tours/images/subflow-labels.png +0 -0
package/public/red/red.js CHANGED
@@ -343,8 +343,21 @@ var RED = (function() {
343
343
  if (/^#flow\/.+$/.test(currentHash)) {
344
344
  RED.workspaces.show(currentHash.substring(6),true);
345
345
  }
346
- if (RED.workspaces.active() === 0 && RED.workspaces.count() > 0) {
347
- RED.workspaces.show(RED.nodes.getWorkspaceOrder()[0])
346
+ if (RED.workspaces.count() > 0) {
347
+ const hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
348
+ const workspaces = RED.nodes.getWorkspaceOrder();
349
+ if (RED.workspaces.active() === 0) {
350
+ for (let index = 0; index < workspaces.length; index++) {
351
+ const ws = workspaces[index];
352
+ if (!hiddenTabs[ws]) {
353
+ RED.workspaces.show(ws);
354
+ break;
355
+ }
356
+ }
357
+ }
358
+ if (RED.workspaces.active() === 0) {
359
+ RED.workspaces.show(workspaces[0]);
360
+ }
348
361
  }
349
362
  } catch(err) {
350
363
  console.warn(err);
@@ -436,6 +449,14 @@ var RED = (function() {
436
449
  } else {
437
450
  options.buttons = [
438
451
  {
452
+ text: RED._("notification.label.unknownNodesButton"),
453
+ class: "pull-left",
454
+ click: function() {
455
+ RED.actions.invoke("core:search", "type:unknown ");
456
+ }
457
+ },
458
+ {
459
+ class: "primary",
439
460
  text: RED._("common.label.close"),
440
461
  click: function() {
441
462
  persistentNotifications[notificationId].hideNotification();
@@ -552,7 +573,7 @@ var RED = (function() {
552
573
  var parts = topic.split("/");
553
574
  var node = RED.nodes.node(parts[1]);
554
575
  if (node) {
555
- if (msg.hasOwnProperty("text") && msg.text !== null && /^[a-zA-Z]/.test(msg.text)) {
576
+ if (msg.hasOwnProperty("text") && msg.text !== null && /^[@a-zA-Z]/.test(msg.text)) {
556
577
  msg.text = node._(msg.text.toString(),{defaultValue:msg.text.toString()});
557
578
  }
558
579
  node.status = msg;
@@ -693,7 +714,10 @@ var RED = (function() {
693
714
  null,
694
715
  {id: "menu-item-edit-select-all", label:RED._("keyboard.selectAll"), onselect: "core:select-all-nodes"},
695
716
  {id: "menu-item-edit-select-connected", label:RED._("keyboard.selectAllConnected"), onselect: "core:select-connected-nodes"},
696
- {id: "menu-item-edit-select-none", label:RED._("keyboard.selectNone"), onselect: "core:select-none"}
717
+ {id: "menu-item-edit-select-none", label:RED._("keyboard.selectNone"), onselect: "core:select-none"},
718
+ null,
719
+ {id: "menu-item-edit-split-wire-with-links", label:RED._("keyboard.splitWireWithLinks"), onselect: "core:split-wire-with-link-nodes"},
720
+
697
721
  ]});
698
722
 
699
723
  menuOptions.push({id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[
@@ -805,6 +829,7 @@ var RED = (function() {
805
829
  RED.search.init();
806
830
  RED.actionList.init();
807
831
  RED.editor.init();
832
+ RED.diagnostics.init();
808
833
  RED.diff.init();
809
834
 
810
835
 
@@ -1659,20 +1684,26 @@ RED.user = (function() {
1659
1684
  });
1660
1685
 
1661
1686
  } else if (data.type == "strategy") {
1687
+ var sessionMessage = /[?&]session_message=(.*?)(?:$|&)/.exec(window.location.search);
1688
+ RED.sessionMessages = RED.sessionMessages || [];
1689
+ if (sessionMessage) {
1690
+ RED.sessionMessages.push(decodeURIComponent(sessionMessage[1]));
1691
+ if (history.pushState) {
1692
+ var newurl = window.location.protocol+"//"+window.location.host+window.location.pathname
1693
+ window.history.replaceState({ path: newurl }, "", newurl);
1694
+ } else {
1695
+ window.location.search = "";
1696
+ }
1697
+ }
1698
+
1699
+ if (RED.sessionMessages.length === 0 && data.autoLogin) {
1700
+ document.location = data.loginRedirect
1701
+ return
1702
+ }
1703
+
1662
1704
  i = 0;
1663
1705
  for (;i<data.prompts.length;i++) {
1664
1706
  var field = data.prompts[i];
1665
- var sessionMessage = /[?&]session_message=(.*?)(?:$|&)/.exec(window.location.search);
1666
- if (sessionMessage) {
1667
- RED.sessionMessages = RED.sessionMessages || [];
1668
- RED.sessionMessages.push(sessionMessage[1]);
1669
- if (history.pushState) {
1670
- var newurl = window.location.protocol+"//"+window.location.host+window.location.pathname
1671
- window.history.replaceState({ path: newurl }, "", newurl);
1672
- } else {
1673
- window.location.search = "";
1674
- }
1675
- }
1676
1707
  if (RED.sessionMessages) {
1677
1708
  var sessionMessages = $("<div/>",{class:"form-row",style:"text-align: center"}).appendTo("#node-dialog-login-fields");
1678
1709
  RED.sessionMessages.forEach(function (msg) {
@@ -3540,7 +3571,8 @@ RED.state = {
3540
3571
  GROUP_DRAGGING: 12,
3541
3572
  GROUP_RESIZE: 13,
3542
3573
  DETACHED_DRAGGING: 14,
3543
- SLICING: 15
3574
+ SLICING: 15,
3575
+ SLICING_JUNCTION: 16
3544
3576
  }
3545
3577
  ;RED.plugins = (function() {
3546
3578
  var plugins = {};
@@ -3603,6 +3635,11 @@ RED.state = {
3603
3635
  * See the License for the specific language governing permissions and
3604
3636
  * limitations under the License.
3605
3637
  **/
3638
+
3639
+ /**
3640
+ * An Interface to nodes and utility functions for creating/adding/deleting nodes and links
3641
+ * @namespace RED.nodes
3642
+ */
3606
3643
  RED.nodes = (function() {
3607
3644
 
3608
3645
  var PORT_TYPE_INPUT = 1;
@@ -3623,6 +3660,9 @@ RED.nodes = (function() {
3623
3660
  var groups = {};
3624
3661
  var groupsByZ = {};
3625
3662
 
3663
+ var junctions = {};
3664
+ var junctionsByZ = {};
3665
+
3626
3666
  var initialLoad;
3627
3667
 
3628
3668
  var dirty = false;
@@ -4404,6 +4444,7 @@ RED.nodes = (function() {
4404
4444
  var removedNodes = [];
4405
4445
  var removedLinks = [];
4406
4446
  var removedGroups = [];
4447
+ var removedJunctions = [];
4407
4448
  if (ws) {
4408
4449
  delete workspaces[id];
4409
4450
  delete linkTabMap[id];
@@ -4412,7 +4453,14 @@ RED.nodes = (function() {
4412
4453
  var node;
4413
4454
 
4414
4455
  if (allNodes.hasTab(id)) {
4415
- removedNodes = allNodes.getNodes(id).slice()
4456
+ removedNodes = allNodes.getNodes(id).filter(n => {
4457
+ if (n.type === 'junction') {
4458
+ removedJunctions.push(n)
4459
+ return false
4460
+ } else {
4461
+ return true
4462
+ }
4463
+ })
4416
4464
  }
4417
4465
  for (i in configNodes) {
4418
4466
  if (configNodes.hasOwnProperty(i)) {
@@ -4427,6 +4475,10 @@ RED.nodes = (function() {
4427
4475
  var result = removeNode(removedNodes[i].id);
4428
4476
  removedLinks = removedLinks.concat(result.links);
4429
4477
  }
4478
+ for (i=0;i<removedJunctions.length;i++) {
4479
+ var result = removeJunction(removedJunctions[i])
4480
+ removedLinks = removedLinks.concat(result.links)
4481
+ }
4430
4482
 
4431
4483
  // Must get 'removedGroups' in the right order.
4432
4484
  // - start with the top-most groups
@@ -4446,7 +4498,7 @@ RED.nodes = (function() {
4446
4498
  allNodes.removeTab(id);
4447
4499
  RED.events.emit('flows:remove',ws);
4448
4500
  }
4449
- return {nodes:removedNodes,links:removedLinks, groups: removedGroups};
4501
+ return {nodes:removedNodes,links:removedLinks, groups: removedGroups, junctions: removedJunctions};
4450
4502
  }
4451
4503
 
4452
4504
  function addSubflow(sf, createNewIds) {
@@ -4703,7 +4755,7 @@ RED.nodes = (function() {
4703
4755
  delete node.env;
4704
4756
  }
4705
4757
  }
4706
- if (n._def.category != "config") {
4758
+ if (n._def.category != "config" || n.type === 'junction') {
4707
4759
  node.x = n.x;
4708
4760
  node.y = n.y;
4709
4761
  if (exportDimensions) {
@@ -4966,6 +5018,11 @@ RED.nodes = (function() {
4966
5018
  nns.push(convertNode(groups[i], opts));
4967
5019
  }
4968
5020
  }
5021
+ for (i in junctions) {
5022
+ if (junctions.hasOwnProperty(i)) {
5023
+ nns.push(convertNode(junctions[i], opts));
5024
+ }
5025
+ }
4969
5026
  for (i in configNodes) {
4970
5027
  if (configNodes.hasOwnProperty(i)) {
4971
5028
  nns.push(convertNode(configNodes[i], opts));
@@ -5047,6 +5104,7 @@ RED.nodes = (function() {
5047
5104
  tabs: {},
5048
5105
  subflows: {},
5049
5106
  groups: {},
5107
+ junctions: {},
5050
5108
  configs: {},
5051
5109
  nodes: {},
5052
5110
  all: [],
@@ -5062,6 +5120,8 @@ RED.nodes = (function() {
5062
5120
  imported.subflows[n.id] = n;
5063
5121
  } else if (n.type === "group") {
5064
5122
  imported.groups[n.id] = n;
5123
+ } else if (n.type === "junction") {
5124
+ imported.junctions[n.id] = n;
5065
5125
  } else if (n.hasOwnProperty("x") && n.hasOwnProperty("y")) {
5066
5126
  imported.nodes[n.id] = n;
5067
5127
  } else {
@@ -5070,7 +5130,7 @@ RED.nodes = (function() {
5070
5130
  var nodeZ = n.z || "__global__";
5071
5131
  imported.zMap[nodeZ] = imported.zMap[nodeZ] || [];
5072
5132
  imported.zMap[nodeZ].push(n)
5073
- if (allNodes.hasNode(n.id) || configNodes[n.id] || workspaces[n.id] || subflows[n.id] || groups[n.id]) {
5133
+ if (allNodes.hasNode(n.id) || configNodes[n.id] || workspaces[n.id] || subflows[n.id] || groups[n.id] || junctions[n.id]) {
5074
5134
  imported.conflicted[n.id] = n;
5075
5135
  }
5076
5136
  })
@@ -5236,7 +5296,7 @@ RED.nodes = (function() {
5236
5296
  if (!options.generateIds) {
5237
5297
  if (!options.importMap[id]) {
5238
5298
  // No conflict resolution for this node
5239
- var existing = allNodes.getNode(id) || configNodes[id] || workspaces[id] || subflows[id] || groups[id];
5299
+ var existing = allNodes.getNode(id) || configNodes[id] || workspaces[id] || subflows[id] || groups[id] || junctions[id];
5240
5300
  if (existing) {
5241
5301
  existingNodes.push({existing:existing, imported:n});
5242
5302
  }
@@ -5290,6 +5350,7 @@ RED.nodes = (function() {
5290
5350
  n.type != "tab" &&
5291
5351
  n.type != "subflow" &&
5292
5352
  n.type != "group" &&
5353
+ n.type != 'junction' &&
5293
5354
  !registry.getNodeType(n.type) &&
5294
5355
  n.type.substring(0,8) != "subflow:" &&
5295
5356
  unknownTypes.indexOf(n.type)==-1) {
@@ -5362,6 +5423,7 @@ RED.nodes = (function() {
5362
5423
  var new_nodes = [];
5363
5424
  var new_links = [];
5364
5425
  var new_groups = [];
5426
+ var new_junctions = [];
5365
5427
  var new_group_set = new Set();
5366
5428
  var nid;
5367
5429
  var def;
@@ -5553,12 +5615,15 @@ RED.nodes = (function() {
5553
5615
  changed:false,
5554
5616
  _config:{}
5555
5617
  }
5556
- if (n.type !== "group") {
5618
+ if (n.type !== "group" && n.type !== 'junction') {
5557
5619
  node.wires = n.wires||[];
5558
5620
  node.inputLabels = n.inputLabels;
5559
5621
  node.outputLabels = n.outputLabels;
5560
5622
  node.icon = n.icon;
5561
5623
  }
5624
+ if (n.type === 'junction') {
5625
+ node.wires = n.wires||[];
5626
+ }
5562
5627
  if (n.hasOwnProperty('l')) {
5563
5628
  node.l = n.l;
5564
5629
  }
@@ -5627,6 +5692,15 @@ RED.nodes = (function() {
5627
5692
  node.outputs = subflow.out.length;
5628
5693
  node.inputs = subflow.in.length;
5629
5694
  node.env = n.env;
5695
+ } else if (n.type === 'junction') {
5696
+ node._def = {defaults:{}}
5697
+ node._config.x = node.x
5698
+ node._config.y = node.y
5699
+ node.inputs = 1
5700
+ node.outputs = 1
5701
+ node.w = 0;
5702
+ node.h = 0;
5703
+
5630
5704
  } else {
5631
5705
  if (!node._def) {
5632
5706
  if (node.x && node.y) {
@@ -5710,7 +5784,9 @@ RED.nodes = (function() {
5710
5784
  node_map[n.id] = node;
5711
5785
  // If an 'unknown' config node, it will not have been caught by the
5712
5786
  // proper config node handling, so needs adding to new_nodes here
5713
- if (node.type === "unknown" || node._def.category !== "config") {
5787
+ if (node.type === 'junction') {
5788
+ new_junctions.push(node)
5789
+ } else if (node.type === "unknown" || node._def.category !== "config") {
5714
5790
  new_nodes.push(node);
5715
5791
  } else if (node.type === "group") {
5716
5792
  new_groups.push(node);
@@ -5721,11 +5797,15 @@ RED.nodes = (function() {
5721
5797
  }
5722
5798
 
5723
5799
  // Remap all wires and config node references
5724
- for (i=0;i<new_nodes.length;i++) {
5725
- n = new_nodes[i];
5800
+ for (i=0;i<new_nodes.length+new_junctions.length;i++) {
5801
+ if (i<new_nodes.length) {
5802
+ n = new_nodes[i];
5803
+ } else {
5804
+ n = new_junctions[i - new_nodes.length]
5805
+ }
5726
5806
  if (n.wires) {
5727
5807
  for (var w1=0;w1<n.wires.length;w1++) {
5728
- var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]];
5808
+ var wires = (Array.isArray(n.wires[w1]))?n.wires[w1]:[n.wires[w1]];
5729
5809
  for (var w2=0;w2<wires.length;w2++) {
5730
5810
  if (node_map.hasOwnProperty(wires[w2])) {
5731
5811
  if (n.z === node_map[wires[w2]].z) {
@@ -5861,6 +5941,12 @@ RED.nodes = (function() {
5861
5941
  addGroup(n);
5862
5942
  }
5863
5943
 
5944
+ for (i=0;i<new_junctions.length;i++) {
5945
+ var junction = new_junctions[i];
5946
+ addJunction(junction);
5947
+ }
5948
+
5949
+
5864
5950
  // Now the nodes have been fully updated, add them.
5865
5951
  for (i=0;i<new_nodes.length;i++) {
5866
5952
  var node = new_nodes[i];
@@ -5891,6 +5977,7 @@ RED.nodes = (function() {
5891
5977
  nodes:new_nodes,
5892
5978
  links:new_links,
5893
5979
  groups:new_groups,
5980
+ junctions: new_junctions,
5894
5981
  workspaces:new_workspaces,
5895
5982
  subflows:new_subflows,
5896
5983
  missingWorkspace: missingWorkspace,
@@ -5997,6 +6084,8 @@ RED.nodes = (function() {
5997
6084
  workspacesOrder = [];
5998
6085
  groups = {};
5999
6086
  groupsByZ = {};
6087
+ junctions = {};
6088
+ junctionsByZ = {};
6000
6089
 
6001
6090
  var subflowIds = Object.keys(subflows);
6002
6091
  subflowIds.forEach(function(id) {
@@ -6046,6 +6135,30 @@ RED.nodes = (function() {
6046
6135
  RED.events.emit("groups:remove",group);
6047
6136
  }
6048
6137
 
6138
+ function addJunction(junction) {
6139
+ junctionsByZ[junction.z] = junctionsByZ[junction.z] || []
6140
+ junctionsByZ[junction.z].push(junction)
6141
+ junctions[junction.id] = junction;
6142
+ if (!nodeLinks[junction.id]) {
6143
+ nodeLinks[junction.id] = {in:[],out:[]};
6144
+ }
6145
+ RED.events.emit("junctions:add", junction)
6146
+ }
6147
+ function removeJunction(junction) {
6148
+ var i = junctionsByZ[junction.z].indexOf(junction)
6149
+ junctionsByZ[junction.z].splice(i, 1)
6150
+ if (junctionsByZ[junction.z].length === 0) {
6151
+ delete junctionsByZ[junction.z]
6152
+ }
6153
+ delete junctions[junction.id]
6154
+ delete nodeLinks[junction.id];
6155
+ RED.events.emit("junctions:remove", junction)
6156
+
6157
+ var removedLinks = links.filter(function(l) { return (l.source === junction) || (l.target === junction); });
6158
+ removedLinks.forEach(removeLink);
6159
+ return { links: removedLinks }
6160
+ }
6161
+
6049
6162
  function getNodeHelp(type) {
6050
6163
  var helpContent = "";
6051
6164
  var helpElement = $("script[data-help-name='"+type+"']");
@@ -6274,7 +6387,6 @@ RED.nodes = (function() {
6274
6387
  getType: registry.getNodeType,
6275
6388
  getNodeHelp: getNodeHelp,
6276
6389
  convertNode: convertNode,
6277
-
6278
6390
  add: addNode,
6279
6391
  remove: removeNode,
6280
6392
  clear: clear,
@@ -6320,6 +6432,11 @@ RED.nodes = (function() {
6320
6432
  group: function(id) { return groups[id] },
6321
6433
  groups: function(z) { return groupsByZ[z]?groupsByZ[z].slice():[] },
6322
6434
 
6435
+ addJunction: addJunction,
6436
+ removeJunction: removeJunction,
6437
+ junction: function(id) { return junctions[id] },
6438
+ junctions: function(z) { return junctionsByZ[z]?junctionsByZ[z].slice():[] },
6439
+
6323
6440
  eachNode: function(cb) {
6324
6441
  allNodes.eachNode(cb);
6325
6442
  },
@@ -7227,6 +7344,11 @@ RED.nodes.fontAwesome = (function() {
7227
7344
  * See the License for the specific language governing permissions and
7228
7345
  * limitations under the License.
7229
7346
  **/
7347
+
7348
+ /**
7349
+ * An API for undo / redo history buffer
7350
+ * @namespace RED.history
7351
+ */
7230
7352
  RED.history = (function() {
7231
7353
  var undoHistory = [];
7232
7354
  var redoHistory = [];
@@ -7315,6 +7437,23 @@ RED.history = (function() {
7315
7437
  RED.nodes.removeLink(ev.links[i]);
7316
7438
  }
7317
7439
  }
7440
+ if (ev.junctions) {
7441
+ inverseEv.junctions = [];
7442
+ for (i=0;i<ev.junctions.length;i++) {
7443
+ inverseEv.junctions.push(ev.junctions[i]);
7444
+ RED.nodes.removeJunction(ev.junctions[i]);
7445
+ if (ev.junctions[i].g) {
7446
+ var group = RED.nodes.group(ev.junctions[i].g);
7447
+ var index = group.nodes.indexOf(ev.junctions[i]);
7448
+ if (index !== -1) {
7449
+ group.nodes.splice(index,1);
7450
+ RED.group.markDirty(group);
7451
+ }
7452
+ }
7453
+
7454
+
7455
+ }
7456
+ }
7318
7457
  if (ev.groups) {
7319
7458
  inverseEv.groups = [];
7320
7459
  for (i = ev.groups.length - 1;i>=0;i--) {
@@ -7481,6 +7620,21 @@ RED.history = (function() {
7481
7620
  }
7482
7621
  }
7483
7622
  }
7623
+ if (ev.junctions) {
7624
+ inverseEv.junctions = [];
7625
+ for (i=0;i<ev.junctions.length;i++) {
7626
+ inverseEv.junctions.push(ev.junctions[i]);
7627
+ RED.nodes.addJunction(ev.junctions[i]);
7628
+ if (ev.junctions[i].g) {
7629
+ group = RED.nodes.group(ev.junctions[i].g);
7630
+ if (group.nodes.indexOf(ev.junctions[i]) === -1) {
7631
+ group.nodes.push(ev.junctions[i]);
7632
+ }
7633
+ RED.group.markDirty(group)
7634
+ }
7635
+
7636
+ }
7637
+ }
7484
7638
  if (ev.links) {
7485
7639
  inverseEv.links = [];
7486
7640
  for (i=0;i<ev.links.length;i++) {
@@ -7941,24 +8095,74 @@ RED.history = (function() {
7941
8095
  * limitations under the License.
7942
8096
  **/
7943
8097
  RED.validators = {
7944
- number: function(blankAllowed){return function(v) { return (blankAllowed&&(v===''||v===undefined)) || (v!=='' && !isNaN(v));}},
7945
- regex: function(re){return function(v) { return re.test(v);}},
7946
- typedInput: function(ptypeName,isConfig) { return function(v) {
7947
- var ptype = $("#node-"+(isConfig?"config-":"")+"input-"+ptypeName).val() || this[ptypeName];
7948
- if (ptype === 'json') {
7949
- try {
7950
- JSON.parse(v);
8098
+ number: function(blankAllowed,mopt){
8099
+ return function(v, opt) {
8100
+ if ((blankAllowed&&(v===''||v===undefined)) || (v!=='' && !isNaN(v))) {
7951
8101
  return true;
7952
- } catch(err) {
7953
- return false;
7954
8102
  }
7955
- } else if (ptype === 'msg' || ptype === 'flow' || ptype === 'global' ) {
7956
- return RED.utils.validatePropertyExpression(v);
7957
- } else if (ptype === 'num') {
7958
- return /^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v);
7959
- }
7960
- return true;
7961
- }}
8103
+ if (opt && opt.label) {
8104
+ return RED._("validator.errors.invalid-num-prop", {
8105
+ prop: opt.label
8106
+ });
8107
+ }
8108
+ return opt ? RED._("validator.errors.invalid-num") : false;
8109
+ };
8110
+ },
8111
+ regex: function(re, mopt) {
8112
+ return function(v, opt) {
8113
+ if (re.test(v)) {
8114
+ return true;
8115
+ }
8116
+ if (opt && opt.label) {
8117
+ return RED._("validator.errors.invalid-regex-prop", {
8118
+ prop: opt.label
8119
+ });
8120
+ }
8121
+ return opt ? RED._("validator.errors.invalid-regexp") : false;
8122
+ };
8123
+ },
8124
+ typedInput: function(ptypeName,isConfig,mopt) {
8125
+ return function(v, opt) {
8126
+ var ptype = $("#node-"+(isConfig?"config-":"")+"input-"+ptypeName).val() || this[ptypeName];
8127
+ if (ptype === 'json') {
8128
+ try {
8129
+ JSON.parse(v);
8130
+ return true;
8131
+ } catch(err) {
8132
+ if (opt && opt.label) {
8133
+ return RED._("validator.errors.invalid-json-prop", {
8134
+ error: err.message,
8135
+ prop: opt.label,
8136
+ });
8137
+ }
8138
+ return opt ? RED._("validator.errors.invalid-json", {
8139
+ error: err.message
8140
+ }) : false;
8141
+ }
8142
+ } else if (ptype === 'msg' || ptype === 'flow' || ptype === 'global' ) {
8143
+ if (RED.utils.validatePropertyExpression(v)) {
8144
+ return true;
8145
+ }
8146
+ if (opt && opt.label) {
8147
+ return RED._("validator.errors.invalid-prop-prop", {
8148
+ prop: opt.label
8149
+ });
8150
+ }
8151
+ return opt ? RED._("validator.errors.invalid-prop") : false;
8152
+ } else if (ptype === 'num') {
8153
+ if (/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v)) {
8154
+ return true;
8155
+ }
8156
+ if (opt && opt.label) {
8157
+ return RED._("validator.errors.invalid-num-prop", {
8158
+ prop: opt.label
8159
+ });
8160
+ }
8161
+ return opt ? RED._("validator.errors.invalid-num") : false;
8162
+ }
8163
+ return true;
8164
+ };
8165
+ }
7962
8166
  };
7963
8167
  ;/**
7964
8168
  * Copyright JS Foundation and other contributors, http://js.foundation
@@ -8327,7 +8531,16 @@ RED.utils = (function() {
8327
8531
  }
8328
8532
  }
8329
8533
 
8330
- function buildMessageElement(obj,options) {
8534
+ /**
8535
+ * Create a DOM element representation of obj - as used by Debug sidebar etc
8536
+ *
8537
+ * @params obj - the data to display
8538
+ * @params options - a bag of options
8539
+ *
8540
+ * - If you want the Copy Value button, then set `sourceId`
8541
+ * - If you want the Copy Path button, also set `path` to the value to be copied
8542
+ */
8543
+ function createObjectElement(obj,options) {
8331
8544
  options = options || {};
8332
8545
  var key = options.key;
8333
8546
  var typeHint = options.typeHint;
@@ -8517,7 +8730,7 @@ RED.utils = (function() {
8517
8730
  if (fullLength <= 10) {
8518
8731
  for (i=0;i<fullLength;i++) {
8519
8732
  row = $('<div class="red-ui-debug-msg-object-entry collapsed"></div>').appendTo(arrayRows);
8520
- subElements[path+"["+i+"]"] = buildMessageElement(
8733
+ subElements[path+"["+i+"]"] = createObjectElement(
8521
8734
  data[i],
8522
8735
  {
8523
8736
  key: ""+i,
@@ -8547,7 +8760,7 @@ RED.utils = (function() {
8547
8760
  return function() {
8548
8761
  for (var i=min;i<=max;i++) {
8549
8762
  var row = $('<div class="red-ui-debug-msg-object-entry collapsed"></div>').appendTo(parent);
8550
- subElements[path+"["+i+"]"] = buildMessageElement(
8763
+ subElements[path+"["+i+"]"] = createObjectElement(
8551
8764
  data[i],
8552
8765
  {
8553
8766
  key: ""+i,
@@ -8603,7 +8816,7 @@ RED.utils = (function() {
8603
8816
  newPath += "[\""+keys[i].replace(/"/,"\\\"")+"\"]"
8604
8817
  }
8605
8818
  }
8606
- subElements[newPath] = buildMessageElement(
8819
+ subElements[newPath] = createObjectElement(
8607
8820
  data[keys[i]],
8608
8821
  {
8609
8822
  key: keys[i],
@@ -8981,6 +9194,8 @@ RED.utils = (function() {
8981
9194
  return "font-awesome/fa-object-ungroup";
8982
9195
  } else if (node && node.type === 'group') {
8983
9196
  return "font-awesome/fa-object-group"
9197
+ } else if ((node && node.type === 'junction') || (def.type === "junction") ) {
9198
+ return "font-awesome/fa-circle-o"
8984
9199
  } else if (def.category === 'config') {
8985
9200
  return RED.settings.apiRootUrl+"icons/node-red/cog.svg"
8986
9201
  } else if (node && node.type === 'tab') {
@@ -9046,6 +9261,8 @@ RED.utils = (function() {
9046
9261
  l = node.label || defaultLabel
9047
9262
  } else if (node.type === 'group') {
9048
9263
  l = node.name || defaultLabel
9264
+ } else if (node.type === 'junction') {
9265
+ l = 'junction'
9049
9266
  } else {
9050
9267
  l = node._def.label;
9051
9268
  try {
@@ -9058,6 +9275,18 @@ RED.utils = (function() {
9058
9275
  return RED.text.bidi.enforceTextDirectionWithUCC(l);
9059
9276
  }
9060
9277
 
9278
+ function getPaletteLabel(nodeType, def) {
9279
+ var label = nodeType;
9280
+ if (typeof def.paletteLabel !== "undefined") {
9281
+ try {
9282
+ label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||"";
9283
+ } catch(err) {
9284
+ console.log("Definition error: "+nodeType+".paletteLabel",err);
9285
+ }
9286
+ }
9287
+ return label
9288
+ }
9289
+
9061
9290
  var nodeColorCache = {};
9062
9291
  function clearNodeColorCache() {
9063
9292
  nodeColorCache = {};
@@ -9200,6 +9429,8 @@ RED.utils = (function() {
9200
9429
  nodeDiv.addClass("red-ui-palette-icon-selection");
9201
9430
  } else if (node.type === "group") {
9202
9431
  nodeDiv.addClass("red-ui-palette-icon-group");
9432
+ } else if (node.type === "junction") {
9433
+ nodeDiv.addClass("red-ui-palette-icon-junction");
9203
9434
  } else if (node.type === 'tab') {
9204
9435
  nodeDiv.addClass("red-ui-palette-icon-flow");
9205
9436
  } else {
@@ -9331,7 +9562,7 @@ RED.utils = (function() {
9331
9562
  }
9332
9563
 
9333
9564
  return {
9334
- createObjectElement: buildMessageElement,
9565
+ createObjectElement: createObjectElement,
9335
9566
  getMessageProperty: getMessageProperty,
9336
9567
  setMessageProperty: setMessageProperty,
9337
9568
  normalisePropertyExpression: normalisePropertyExpression,
@@ -9341,6 +9572,7 @@ RED.utils = (function() {
9341
9572
  getNodeIcon: getNodeIcon,
9342
9573
  getNodeLabel: getNodeLabel,
9343
9574
  getNodeColor: getNodeColor,
9575
+ getPaletteLabel: getPaletteLabel,
9344
9576
  clearNodeColorCache: clearNodeColorCache,
9345
9577
  addSpinnerOverlay: addSpinnerOverlay,
9346
9578
  decodeObject: decodeObject,
@@ -11671,6 +11903,7 @@ RED.popover = (function() {
11671
11903
  setTimeout(closePopup,delay.hide);
11672
11904
  }
11673
11905
  });
11906
+
11674
11907
  if (trigger === 'hover') {
11675
11908
  target.on('mouseenter',function(e) {
11676
11909
  clearTimeout(timer);
@@ -11782,6 +12015,11 @@ RED.popover = (function() {
11782
12015
  popover.setAction = function(newAction) {
11783
12016
  action = newAction;
11784
12017
  }
12018
+ popover.delete = function() {
12019
+ popover.close(true)
12020
+ target.off("mouseenter");
12021
+ target.off("mouseleave");
12022
+ };
11785
12023
  return popover;
11786
12024
 
11787
12025
  },
@@ -12083,8 +12321,8 @@ RED.popover = (function() {
12083
12321
  }
12084
12322
  });
12085
12323
  this.element.on("keydown",function(e) {
12086
- if (!menuShown && e.keyCode === 40) {
12087
- //DOWN
12324
+ if (!menuShown && e.keyCode === 40 && $(this).val() === '') {
12325
+ //DOWN (only show menu if search field is emty)
12088
12326
  showMenu();
12089
12327
  }
12090
12328
  });
@@ -12829,7 +13067,7 @@ RED.tabs = (function() {
12829
13067
  }
12830
13068
  var link = $("<a/>",{href:"#"+tab.id, class:"red-ui-tab-label"}).appendTo(li);
12831
13069
  if (tab.icon) {
12832
- $('<img src="'+tab.icon+'" class="red-ui-tab-icon"/>').appendTo(link);
13070
+ $('<i>',{class:"red-ui-tab-icon", style:"mask-image: url("+tab.icon+"); -webkit-mask-image: url("+tab.icon+");"}).appendTo(link);
12833
13071
  } else if (tab.iconClass) {
12834
13072
  $('<i>',{class:"red-ui-tab-icon "+tab.iconClass}).appendTo(link);
12835
13073
  }
@@ -13408,34 +13646,46 @@ RED.stack = (function() {
13408
13646
  }
13409
13647
 
13410
13648
  var autoComplete = function(options) {
13649
+ function getMatch(value, searchValue) {
13650
+ const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
13651
+ const len = idx > -1 ? searchValue.length : 0;
13652
+ return {
13653
+ index: idx,
13654
+ found: idx > -1,
13655
+ pre: value.substring(0,idx),
13656
+ match: value.substring(idx,idx+len),
13657
+ post: value.substring(idx+len),
13658
+ }
13659
+ }
13660
+ function generateSpans(match) {
13661
+ const els = [];
13662
+ if(match.pre) { els.push($('<span/>').text(match.pre)); }
13663
+ if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
13664
+ if(match.post) { els.push($('<span/>').text(match.post)); }
13665
+ return els;
13666
+ }
13411
13667
  return function(val) {
13412
13668
  var matches = [];
13413
13669
  options.forEach(opt => {
13414
- let v = opt.value;
13415
- var i = v.toLowerCase().indexOf(val.toLowerCase());
13416
- if (i > -1) {
13417
- var pre = v.substring(0,i);
13418
- var matchedVal = v.substring(i,i+val.length);
13419
- var post = v.substring(i+val.length)
13420
-
13421
- var el = $('<div/>',{style:"white-space:nowrap; overflow: hidden; flex-grow:1"});
13422
- $('<span/>').text(pre).appendTo(el);
13423
- $('<span/>',{style:"font-weight: bold"}).text(matchedVal).appendTo(el);
13424
- $('<span/>').text(post).appendTo(el);
13425
-
13426
- var element = $('<div>',{style: "display: flex"});
13427
- el.appendTo(element);
13428
- if (opt.source) {
13429
- $('<div>').css({
13430
- "font-size": "0.8em"
13431
- }).text(opt.source.join(",")).appendTo(element);
13432
- }
13433
-
13434
- matches.push({
13435
- value: v,
13436
- label: element,
13437
- i:i
13438
- })
13670
+ const optVal = opt.value;
13671
+ const optSrc = (opt.source||[]).join(",");
13672
+ const valMatch = getMatch(optVal, val);
13673
+ const srcMatch = getMatch(optSrc, val);
13674
+ if (valMatch.found || srcMatch.found) {
13675
+ const element = $('<div>',{style: "display: flex"});
13676
+ const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
13677
+ valEl.append(generateSpans(valMatch));
13678
+ valEl.appendTo(element);
13679
+ if (optSrc) {
13680
+ const optEl = $('<div>').css({ "font-size": "0.8em" });
13681
+ optEl.append(generateSpans(srcMatch));
13682
+ optEl.appendTo(element);
13683
+ }
13684
+ matches.push({
13685
+ value: optVal,
13686
+ label: element,
13687
+ i: (valMatch.found ? valMatch.index : srcMatch.index)
13688
+ });
13439
13689
  }
13440
13690
  })
13441
13691
  matches.sort(function(A,B){return A.i-B.i})
@@ -13446,45 +13696,65 @@ RED.stack = (function() {
13446
13696
  // This is a hand-generated list of completions for the core nodes (based on the node help html).
13447
13697
  var msgCompletions = [
13448
13698
  { value: "payload" },
13449
- { value: "req", source: ["http in"]},
13450
- { value: "req.body", source: ["http in"]},
13451
- { value: "req.headers", source: ["http in"]},
13452
- { value: "req.query", source: ["http in"]},
13453
- { value: "req.params", source: ["http in"]},
13454
- { value: "req.cookies", source: ["http in"]},
13455
- { value: "req.files", source: ["http in"]},
13699
+ { value: "topic", source: ["mqtt","inject","rbe"] },
13700
+ { value: "action", source: ["mqtt"] },
13456
13701
  { value: "complete", source: ["join"] },
13457
13702
  { value: "contentType", source: ["mqtt"] },
13458
- { value: "cookies", source: ["http in","http request"] },
13703
+ { value: "cookies", source: ["http request","http response"] },
13459
13704
  { value: "correlationData", source: ["mqtt"] },
13460
13705
  { value: "delay", source: ["delay","trigger"] },
13461
13706
  { value: "encoding", source: ["file"] },
13462
13707
  { value: "error", source: ["catch"] },
13708
+ { value: "error.message", source: ["catch"] },
13709
+ { value: "error.source", source: ["catch"] },
13710
+ { value: "error.source.id", source: ["catch"] },
13711
+ { value: "error.source.type", source: ["catch"] },
13712
+ { value: "error.source.name", source: ["catch"] },
13463
13713
  { value: "filename", source: ["file","file in"] },
13464
13714
  { value: "flush", source: ["delay"] },
13465
13715
  { value: "followRedirects", source: ["http request"] },
13466
- { value: "headers", source: ["http in"," http request"] },
13716
+ { value: "headers", source: ["http response","http request"] },
13717
+ { value: "host", source: ["tcp request","http request"] },
13718
+ { value: "ip", source: ["udp out"] },
13467
13719
  { value: "kill", source: ["exec"] },
13468
13720
  { value: "messageExpiryInterval", source: ["mqtt"] },
13469
- { value: "method", source: ["http-request"] },
13721
+ { value: "method", source: ["http request"] },
13470
13722
  { value: "options", source: ["xml"] },
13471
- { value: "parts", source: ["split","join"] },
13723
+ { value: "parts", source: ["split","join","batch","sort"] },
13472
13724
  { value: "pid", source: ["exec"] },
13725
+ { value: "port", source: ["tcp request"," udp out"] },
13473
13726
  { value: "qos", source: ["mqtt"] },
13474
13727
  { value: "rate", source: ["delay"] },
13475
13728
  { value: "rejectUnauthorized", source: ["http request"] },
13729
+ { value: "req", source: ["http in"]},
13730
+ { value: "req.body", source: ["http in"]},
13731
+ { value: "req.headers", source: ["http in"]},
13732
+ { value: "req.query", source: ["http in"]},
13733
+ { value: "req.params", source: ["http in"]},
13734
+ { value: "req.cookies", source: ["http in"]},
13735
+ { value: "req.files", source: ["http in"]},
13476
13736
  { value: "requestTimeout", source: ["http request"] },
13477
13737
  { value: "reset", source: ["delay","trigger","join","rbe"] },
13738
+ { value: "responseCookies", source: ["http request"] },
13478
13739
  { value: "responseTopic", source: ["mqtt"] },
13740
+ { value: "responseURL", source: ["http request"] },
13479
13741
  { value: "restartTimeout", source: ["join"] },
13480
13742
  { value: "retain", source: ["mqtt"] },
13743
+ { value: "schema", source: ["json"] },
13481
13744
  { value: "select", source: ["html"] },
13482
- { value: "statusCode", source: ["http in"] },
13745
+ { value: "statusCode", source: ["http response","http request"] },
13746
+ { value: "status", source: ["status"] },
13747
+ { value: "status.text", source: ["status"] },
13748
+ { value: "status.source", source: ["status"] },
13749
+ { value: "status.source.type", source: ["status"] },
13750
+ { value: "status.source.id", source: ["status"] },
13751
+ { value: "status.source.name", source: ["status"] },
13752
+ { value: "target", source: ["link call"] },
13483
13753
  { value: "template", source: ["template"] },
13484
13754
  { value: "toFront", source: ["delay"] },
13485
- { value: "topic", source: ["inject","mqtt","rbe"] },
13486
13755
  { value: "url", source: ["http request"] },
13487
- { value: "userProperties", source: ["mqtt"] }
13756
+ { value: "userProperties", source: ["mqtt"] },
13757
+ { value: "_session", source: ["websocket out","tcp out"] },
13488
13758
  ]
13489
13759
  var allOptions = {
13490
13760
  msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: autoComplete(msgCompletions)},
@@ -13519,6 +13789,8 @@ RED.stack = (function() {
13519
13789
  }
13520
13790
  RED.editor.editJSON({
13521
13791
  value: value,
13792
+ stateId: RED.editor.generateViewStateId("typedInput", that, "json"),
13793
+ focus: true,
13522
13794
  complete: function(v) {
13523
13795
  var value = v;
13524
13796
  try {
@@ -13541,6 +13813,8 @@ RED.stack = (function() {
13541
13813
  var that = this;
13542
13814
  RED.editor.editExpression({
13543
13815
  value: this.value().replace(/\t/g,"\n"),
13816
+ stateId: RED.editor.generateViewStateId("typedInput", that, "jsonata"),
13817
+ focus: true,
13544
13818
  complete: function(v) {
13545
13819
  that.value(v.replace(/\n/g,"\t"));
13546
13820
  }
@@ -13555,6 +13829,8 @@ RED.stack = (function() {
13555
13829
  var that = this;
13556
13830
  RED.editor.editBuffer({
13557
13831
  value: this.value(),
13832
+ stateId: RED.editor.generateViewStateId("typedInput", that, "bin"),
13833
+ focus: true,
13558
13834
  complete: function(v) {
13559
13835
  that.value(v);
13560
13836
  }
@@ -13990,7 +14266,7 @@ RED.stack = (function() {
13990
14266
  if (opt.icon.indexOf("<") === 0) {
13991
14267
  $(opt.icon).prependTo(op);
13992
14268
  } else if (opt.icon.indexOf("/") !== -1) {
13993
- $('<img>',{src:mapDeprecatedIcon(opt.icon),style:"margin-right: 4px; height: 18px;"}).prependTo(op);
14269
+ $('<i>',{class:"red-ui-typedInput-icon", style:"mask-image: url("+opt.icon+"); -webkit-mask-image: url("+opt.icon+");"}).prependTo(op);
13994
14270
  } else {
13995
14271
  $('<i>',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(op);
13996
14272
  }
@@ -14341,10 +14617,7 @@ RED.stack = (function() {
14341
14617
  $(opt.icon).prependTo(this.selectLabel);
14342
14618
  }
14343
14619
  else if (opt.icon.indexOf("/") !== -1) {
14344
- image = new Image();
14345
- image.name = opt.icon;
14346
- image.src = mapDeprecatedIcon(opt.icon);
14347
- $('<img>',{src:mapDeprecatedIcon(opt.icon),style:"margin-right: 4px;height: 18px;"}).prependTo(this.selectLabel);
14620
+ $('<i>',{class:"red-ui-typedInput-icon", style:"mask-image: url("+opt.icon+"); -webkit-mask-image: url("+opt.icon+"); margin-right: 4px;height: 18px;width:13px"}).prependTo(this.selectLabel);
14348
14621
  }
14349
14622
  else {
14350
14623
  $('<i>',{class:"red-ui-typedInput-icon "+opt.icon,style:"min-width: 13px; margin-right: 4px;"}).prependTo(this.selectLabel);
@@ -14500,7 +14773,8 @@ RED.stack = (function() {
14500
14773
  this.elementDiv.show();
14501
14774
  if (opt.autoComplete) {
14502
14775
  this.input.autoComplete({
14503
- search: opt.autoComplete
14776
+ search: opt.autoComplete,
14777
+ minLength: 0
14504
14778
  })
14505
14779
  }
14506
14780
  }
@@ -14730,12 +15004,14 @@ RED.stack = (function() {
14730
15004
  *
14731
15005
  * options:
14732
15006
  *
14733
- * search : function(value, [done])
14734
- * A function that is passed the current contents of the input whenever
14735
- * it changes.
14736
- * The function must either return auto-complete options, or pass them
14737
- * to the optional 'done' parameter.
14738
- * If the function signature includes 'done', it must be used
15007
+ * search: function(value, [done])
15008
+ * A function that is passed the current contents of the input whenever
15009
+ * it changes.
15010
+ * The function must either return auto-complete options, or pass them
15011
+ * to the optional 'done' parameter.
15012
+ * If the function signature includes 'done', it must be used
15013
+ * minLength: number
15014
+ * If `minLength` is 0, pressing down arrow will show the list
14739
15015
  *
14740
15016
  * The auto-complete options should be an array of objects in the form:
14741
15017
  * {
@@ -14747,10 +15023,11 @@ RED.stack = (function() {
14747
15023
 
14748
15024
  $.widget( "nodered.autoComplete", {
14749
15025
  _create: function() {
14750
- var that = this;
15026
+ const that = this;
14751
15027
  this.completionMenuShown = false;
14752
- this.options.search = this.options.search || function() { return [] }
14753
- this.element.addClass("red-ui-autoComplete")
15028
+ this.options.minLength = parseInteger(this.options.minLength, 1, 0);
15029
+ this.options.search = this.options.search || function() { return [] };
15030
+ this.element.addClass("red-ui-autoComplete");
14754
15031
  this.element.on("keydown.red-ui-autoComplete", function(evt) {
14755
15032
  if ((evt.keyCode === 13 || evt.keyCode === 9) && that.completionMenuShown) {
14756
15033
  var opts = that.menu.options();
@@ -14792,8 +15069,8 @@ RED.stack = (function() {
14792
15069
  this.completionMenuShown = true;
14793
15070
  },
14794
15071
  _updateCompletions: function(val) {
14795
- var that = this;
14796
- if (val.trim() === "") {
15072
+ const that = this;
15073
+ if (val.trim().length < this.options.minLength) {
14797
15074
  if (this.completionMenuShown) {
14798
15075
  this.menu.hide();
14799
15076
  }
@@ -14817,7 +15094,7 @@ RED.stack = (function() {
14817
15094
  }
14818
15095
  }
14819
15096
  if (this.options.search.length === 2) {
14820
- var requestId = 1+Math.floor(Math.random()*10000);
15097
+ const requestId = 1+Math.floor(Math.random()*10000);
14821
15098
  this.pendingRequest = requestId;
14822
15099
  this.options.search(val,function(completions) { displayResults(completions,requestId);})
14823
15100
  } else {
@@ -14833,6 +15110,14 @@ RED.stack = (function() {
14833
15110
  }
14834
15111
  }
14835
15112
  });
15113
+ function parseInteger(input, def, min, max) {
15114
+ if(input == null) { return (def || 0); }
15115
+ min = min == null ? Number.NEGATIVE_INFINITY : min;
15116
+ max = max == null ? Number.POSITIVE_INFINITY : max;
15117
+ let n = parseInt(input);
15118
+ if(isNaN(n) || n < min || n > max) { n = def || 0; }
15119
+ return n;
15120
+ }
14836
15121
  })(jQuery);
14837
15122
  ;RED.actions = (function() {
14838
15123
  var actions = {
@@ -15240,205 +15525,248 @@ RED.deploy = (function() {
15240
15525
  },delta);
15241
15526
  });
15242
15527
  }
15243
- function save(skipValidation,force) {
15244
- if (!$("#red-ui-header-button-deploy").hasClass("disabled")) {
15245
- if (!RED.user.hasPermission("flows.write")) {
15246
- RED.notify(RED._("user.errors.deploy"),"error");
15247
- return;
15248
- }
15249
- if (!skipValidation) {
15250
- var hasUnknown = false;
15251
- var hasInvalid = false;
15252
- var hasUnusedConfig = false;
15253
-
15254
- var unknownNodes = [];
15255
- var invalidNodes = [];
15528
+ function save(skipValidation, force) {
15529
+ if ($("#red-ui-header-button-deploy").hasClass("disabled")) {
15530
+ return; //deploy is disabled
15531
+ }
15532
+ if ($("#red-ui-header-shade").is(":visible")) {
15533
+ return; //deploy is shaded
15534
+ }
15535
+ if (!RED.user.hasPermission("flows.write")) {
15536
+ RED.notify(RED._("user.errors.deploy"), "error");
15537
+ return;
15538
+ }
15539
+ let hasUnusedConfig = false;
15540
+ if (!skipValidation) {
15541
+ let hasUnknown = false;
15542
+ let hasInvalid = false;
15543
+ const unknownNodes = [];
15544
+ const invalidNodes = [];
15256
15545
 
15257
- RED.nodes.eachConfig(function(node) {
15258
- if (node.valid === undefined) {
15259
- RED.editor.validateNode(node);
15546
+ RED.nodes.eachConfig(function (node) {
15547
+ if (node.valid === undefined) {
15548
+ RED.editor.validateNode(node);
15549
+ }
15550
+ if (!node.valid && !node.d) {
15551
+ invalidNodes.push(getNodeInfo(node));
15552
+ }
15553
+ if (node.type === "unknown") {
15554
+ if (unknownNodes.indexOf(node.name) == -1) {
15555
+ unknownNodes.push(node.name);
15260
15556
  }
15261
- if (!node.valid && !node.d) {
15262
- invalidNodes.push(getNodeInfo(node));
15557
+ }
15558
+ });
15559
+ RED.nodes.eachNode(function (node) {
15560
+ if (!node.valid && !node.d) {
15561
+ invalidNodes.push(getNodeInfo(node));
15562
+ }
15563
+ if (node.type === "unknown") {
15564
+ if (unknownNodes.indexOf(node.name) == -1) {
15565
+ unknownNodes.push(node.name);
15263
15566
  }
15264
- if (node.type === "unknown") {
15265
- if (unknownNodes.indexOf(node.name) == -1) {
15266
- unknownNodes.push(node.name);
15567
+ }
15568
+ });
15569
+ hasUnknown = unknownNodes.length > 0;
15570
+ hasInvalid = invalidNodes.length > 0;
15571
+
15572
+ const unusedConfigNodes = [];
15573
+ RED.nodes.eachConfig(function (node) {
15574
+ if ((node._def.hasUsers !== false) && (node.users.length === 0)) {
15575
+ unusedConfigNodes.push(getNodeInfo(node));
15576
+ hasUnusedConfig = true;
15577
+ }
15578
+ });
15579
+
15580
+ let showWarning = false;
15581
+ let notificationMessage;
15582
+ let notificationButtons = [];
15583
+ let notification;
15584
+ if (hasUnknown && !ignoreDeployWarnings.unknown) {
15585
+ showWarning = true;
15586
+ notificationMessage = "<p>" + RED._('deploy.confirm.unknown') + "</p>" +
15587
+ '<ul class="red-ui-deploy-dialog-confirm-list"><li>' + cropList(unknownNodes).map(function (n) { return sanitize(n) }).join("</li><li>") + "</li></ul><p>" +
15588
+ RED._('deploy.confirm.confirm') +
15589
+ "</p>";
15590
+
15591
+ notificationButtons = [
15592
+ {
15593
+ text: RED._("deploy.unknownNodesButton"),
15594
+ class: "pull-left",
15595
+ click: function() {
15596
+ notification.close();
15597
+ RED.actions.invoke("core:search","type:unknown ");
15598
+ }
15599
+ },
15600
+ {
15601
+ id: "red-ui-deploy-dialog-confirm-deploy-deploy",
15602
+ text: RED._("deploy.confirm.button.confirm"),
15603
+ class: "primary",
15604
+ click: function () {
15605
+ save(true);
15606
+ notification.close();
15267
15607
  }
15268
- }
15269
- });
15270
- RED.nodes.eachNode(function(node) {
15271
- if (!node.valid && !node.d) {
15272
- invalidNodes.push(getNodeInfo(node));
15273
15608
  }
15274
- if (node.type === "unknown") {
15275
- if (unknownNodes.indexOf(node.name) == -1) {
15276
- unknownNodes.push(node.name);
15609
+ ];
15610
+ } else if (hasInvalid && !ignoreDeployWarnings.invalid) {
15611
+ showWarning = true;
15612
+ invalidNodes.sort(sortNodeInfo);
15613
+
15614
+ notificationMessage = "<p>" + RED._('deploy.confirm.improperlyConfigured') + "</p>" +
15615
+ '<ul class="red-ui-deploy-dialog-confirm-list"><li>' + cropList(invalidNodes.map(function (A) { return sanitize((A.tab ? "[" + A.tab + "] " : "") + A.label + " (" + A.type + ")") })).join("</li><li>") + "</li></ul><p>" +
15616
+ RED._('deploy.confirm.confirm') +
15617
+ "</p>";
15618
+ notificationButtons = [
15619
+ {
15620
+ text: RED._("deploy.invalidNodesButton"),
15621
+ class: "pull-left",
15622
+ click: function() {
15623
+ notification.close();
15624
+ RED.actions.invoke("core:search","is:invalid ");
15625
+ }
15626
+ },
15627
+ {
15628
+ id: "red-ui-deploy-dialog-confirm-deploy-deploy",
15629
+ text: RED._("deploy.confirm.button.confirm"),
15630
+ class: "primary",
15631
+ click: function () {
15632
+ save(true);
15633
+ notification.close();
15277
15634
  }
15278
15635
  }
15279
- });
15280
- hasUnknown = unknownNodes.length > 0;
15281
- hasInvalid = invalidNodes.length > 0;
15282
-
15283
- var unusedConfigNodes = [];
15284
- RED.nodes.eachConfig(function(node) {
15285
- if ((node._def.hasUsers !== false) && (node.users.length === 0)) {
15286
- unusedConfigNodes.push(getNodeInfo(node));
15287
- hasUnusedConfig = true;
15636
+ ];
15637
+ }
15638
+ if (showWarning) {
15639
+ notificationButtons.unshift(
15640
+ {
15641
+ text: RED._("common.label.cancel"),
15642
+ click: function () {
15643
+ notification.close();
15644
+ }
15288
15645
  }
15646
+ );
15647
+ notification = RED.notify(notificationMessage, {
15648
+ modal: true,
15649
+ fixed: true,
15650
+ buttons: notificationButtons
15289
15651
  });
15652
+ return;
15653
+ }
15654
+ }
15655
+
15656
+ const nns = RED.nodes.createCompleteNodeSet();
15657
+ const startTime = Date.now();
15658
+
15659
+ $(".red-ui-deploy-button-content").css('opacity', 0);
15660
+ $(".red-ui-deploy-button-spinner").show();
15661
+ $("#red-ui-header-button-deploy").addClass("disabled");
15662
+
15663
+ const data = { flows: nns };
15290
15664
 
15291
- var showWarning = false;
15292
- var notificationMessage;
15293
- var notificationButtons = [];
15294
- var notification;
15295
- if (hasUnknown && !ignoreDeployWarnings.unknown) {
15296
- showWarning = true;
15297
- notificationMessage = "<p>"+RED._('deploy.confirm.unknown')+"</p>"+
15298
- '<ul class="red-ui-deploy-dialog-confirm-list"><li>'+cropList(unknownNodes).map(function(n) { return sanitize(n) }).join("</li><li>")+"</li></ul><p>"+
15299
- RED._('deploy.confirm.confirm')+
15300
- "</p>";
15301
-
15302
- notificationButtons= [
15665
+ if (!force) {
15666
+ data.rev = RED.nodes.version();
15667
+ }
15668
+
15669
+ deployInflight = true;
15670
+ $("#red-ui-header-shade").show();
15671
+ $("#red-ui-editor-shade").show();
15672
+ $("#red-ui-palette-shade").show();
15673
+ $("#red-ui-sidebar-shade").show();
15674
+ $.ajax({
15675
+ url: "flows",
15676
+ type: "POST",
15677
+ data: JSON.stringify(data),
15678
+ contentType: "application/json; charset=utf-8",
15679
+ headers: {
15680
+ "Node-RED-Deployment-Type": deploymentType
15681
+ }
15682
+ }).done(function (data, textStatus, xhr) {
15683
+ RED.nodes.dirty(false);
15684
+ RED.nodes.version(data.rev);
15685
+ RED.nodes.originalFlow(nns);
15686
+ if (hasUnusedConfig) {
15687
+ let notification;
15688
+ const opts = {
15689
+ type: "success",
15690
+ fixed: false,
15691
+ timeout: 6000,
15692
+ buttons: [
15303
15693
  {
15304
- id: "red-ui-deploy-dialog-confirm-deploy-deploy",
15305
- text: RED._("deploy.confirm.button.confirm"),
15306
- class: "primary",
15694
+ text: RED._("deploy.unusedConfigNodesButton"),
15695
+ class: "pull-left",
15307
15696
  click: function() {
15308
- save(true);
15309
15697
  notification.close();
15698
+ RED.actions.invoke("core:search","is:config is:unused ");
15310
15699
  }
15311
- }
15312
- ];
15313
- } else if (hasInvalid && !ignoreDeployWarnings.invalid) {
15314
- showWarning = true;
15315
- invalidNodes.sort(sortNodeInfo);
15316
-
15317
- notificationMessage = "<p>"+RED._('deploy.confirm.improperlyConfigured')+"</p>"+
15318
- '<ul class="red-ui-deploy-dialog-confirm-list"><li>'+cropList(invalidNodes.map(function(A) { return sanitize( (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")")})).join("</li><li>")+"</li></ul><p>"+
15319
- RED._('deploy.confirm.confirm')+
15320
- "</p>";
15321
- notificationButtons= [
15700
+ },
15322
15701
  {
15323
- id: "red-ui-deploy-dialog-confirm-deploy-deploy",
15324
- text: RED._("deploy.confirm.button.confirm"),
15702
+ text: RED._("common.label.close"),
15325
15703
  class: "primary",
15326
- click: function() {
15704
+ click: function () {
15327
15705
  save(true);
15328
15706
  notification.close();
15329
15707
  }
15330
15708
  }
15331
- ];
15332
- }
15333
- if (showWarning) {
15334
- notificationButtons.unshift(
15335
- {
15336
- text: RED._("common.label.cancel"),
15337
- click: function() {
15338
- notification.close();
15339
- }
15340
- }
15341
- );
15342
- notification = RED.notify(notificationMessage,{
15343
- modal: true,
15344
- fixed: true,
15345
- buttons:notificationButtons
15346
- });
15347
- return;
15709
+ ]
15348
15710
  }
15711
+ notification = RED.notify(
15712
+ '<p>' + RED._("deploy.successfulDeploy") + '</p>' +
15713
+ '<p>' + RED._("deploy.unusedConfigNodes") + '</p>', opts);
15714
+ } else {
15715
+ RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success");
15349
15716
  }
15350
-
15351
- var nns = RED.nodes.createCompleteNodeSet();
15352
-
15353
- var startTime = Date.now();
15354
- $(".red-ui-deploy-button-content").css('opacity',0);
15355
- $(".red-ui-deploy-button-spinner").show();
15356
- $("#red-ui-header-button-deploy").addClass("disabled");
15357
-
15358
- var data = {flows:nns};
15359
-
15360
- if (!force) {
15361
- data.rev = RED.nodes.version();
15362
- }
15363
-
15364
- deployInflight = true;
15365
- $("#red-ui-header-shade").show();
15366
- $("#red-ui-editor-shade").show();
15367
- $("#red-ui-palette-shade").show();
15368
- $("#red-ui-sidebar-shade").show();
15369
- $.ajax({
15370
- url:"flows",
15371
- type: "POST",
15372
- data: JSON.stringify(data),
15373
- contentType: "application/json; charset=utf-8",
15374
- headers: {
15375
- "Node-RED-Deployment-Type":deploymentType
15717
+ RED.nodes.eachNode(function (node) {
15718
+ if (node.changed) {
15719
+ node.dirty = true;
15720
+ node.changed = false;
15376
15721
  }
15377
- }).done(function(data,textStatus,xhr) {
15378
- RED.nodes.dirty(false);
15379
- RED.nodes.version(data.rev);
15380
- RED.nodes.originalFlow(nns);
15381
- if (hasUnusedConfig) {
15382
- RED.notify(
15383
- '<p>'+RED._("deploy.successfulDeploy")+'</p>'+
15384
- '<p>'+RED._("deploy.unusedConfigNodes")+' <a href="#" onclick="RED.sidebar.config.show(true); return false;">'+RED._("deploy.unusedConfigNodesLink")+'</a></p>',"success",false,6000);
15385
- } else {
15386
- RED.notify('<p>'+RED._("deploy.successfulDeploy")+'</p>',"success");
15722
+ if (node.moved) {
15723
+ node.dirty = true;
15724
+ node.moved = false;
15387
15725
  }
15388
- RED.nodes.eachNode(function(node) {
15389
- if (node.changed) {
15390
- node.dirty = true;
15391
- node.changed = false;
15392
- }
15393
- if (node.moved) {
15394
- node.dirty = true;
15395
- node.moved = false;
15396
- }
15397
- if(node.credentials) {
15398
- delete node.credentials;
15399
- }
15400
- });
15401
- RED.nodes.eachConfig(function (confNode) {
15402
- confNode.changed = false;
15403
- if (confNode.credentials) {
15404
- delete confNode.credentials;
15405
- }
15406
- });
15407
- RED.nodes.eachSubflow(function(subflow) {
15408
- subflow.changed = false;
15409
- });
15410
- RED.nodes.eachWorkspace(function(ws) {
15411
- ws.changed = false;
15412
- });
15413
- // Once deployed, cannot undo back to a clean state
15414
- RED.history.markAllDirty();
15415
- RED.view.redraw();
15416
- RED.events.emit("deploy");
15417
- }).fail(function(xhr,textStatus,err) {
15418
- RED.nodes.dirty(true);
15419
- $("#red-ui-header-button-deploy").removeClass("disabled");
15420
- if (xhr.status === 401) {
15421
- RED.notify(RED._("deploy.deployFailed",{message:RED._("user.notAuthorized")}),"error");
15422
- } else if (xhr.status === 409) {
15423
- resolveConflict(nns, true);
15424
- } else if (xhr.responseText) {
15425
- RED.notify(RED._("deploy.deployFailed",{message:xhr.responseText}),"error");
15426
- } else {
15427
- RED.notify(RED._("deploy.deployFailed",{message:RED._("deploy.errors.noResponse")}),"error");
15726
+ if (node.credentials) {
15727
+ delete node.credentials;
15428
15728
  }
15429
- }).always(function() {
15430
- deployInflight = false;
15431
- var delta = Math.max(0,300-(Date.now()-startTime));
15432
- setTimeout(function() {
15433
- $(".red-ui-deploy-button-content").css('opacity',1);
15434
- $(".red-ui-deploy-button-spinner").hide();
15435
- $("#red-ui-header-shade").hide();
15436
- $("#red-ui-editor-shade").hide();
15437
- $("#red-ui-palette-shade").hide();
15438
- $("#red-ui-sidebar-shade").hide();
15439
- },delta);
15440
15729
  });
15441
- }
15730
+ RED.nodes.eachConfig(function (confNode) {
15731
+ confNode.changed = false;
15732
+ if (confNode.credentials) {
15733
+ delete confNode.credentials;
15734
+ }
15735
+ });
15736
+ RED.nodes.eachSubflow(function (subflow) {
15737
+ subflow.changed = false;
15738
+ });
15739
+ RED.nodes.eachWorkspace(function (ws) {
15740
+ ws.changed = false;
15741
+ });
15742
+ // Once deployed, cannot undo back to a clean state
15743
+ RED.history.markAllDirty();
15744
+ RED.view.redraw();
15745
+ RED.events.emit("deploy");
15746
+ }).fail(function (xhr, textStatus, err) {
15747
+ RED.nodes.dirty(true);
15748
+ $("#red-ui-header-button-deploy").removeClass("disabled");
15749
+ if (xhr.status === 401) {
15750
+ RED.notify(RED._("deploy.deployFailed", { message: RED._("user.notAuthorized") }), "error");
15751
+ } else if (xhr.status === 409) {
15752
+ resolveConflict(nns, true);
15753
+ } else if (xhr.responseText) {
15754
+ RED.notify(RED._("deploy.deployFailed", { message: xhr.responseText }), "error");
15755
+ } else {
15756
+ RED.notify(RED._("deploy.deployFailed", { message: RED._("deploy.errors.noResponse") }), "error");
15757
+ }
15758
+ }).always(function () {
15759
+ deployInflight = false;
15760
+ const delta = Math.max(0, 300 - (Date.now() - startTime));
15761
+ setTimeout(function () {
15762
+ $(".red-ui-deploy-button-content").css('opacity', 1);
15763
+ $(".red-ui-deploy-button-spinner").hide();
15764
+ $("#red-ui-header-shade").hide();
15765
+ $("#red-ui-editor-shade").hide();
15766
+ $("#red-ui-palette-shade").hide();
15767
+ $("#red-ui-sidebar-shade").hide();
15768
+ }, delta);
15769
+ });
15442
15770
  }
15443
15771
  return {
15444
15772
  init: init,
@@ -15448,6 +15776,67 @@ RED.deploy = (function() {
15448
15776
 
15449
15777
  }
15450
15778
  })();
15779
+ ;
15780
+ RED.diagnostics = (function () {
15781
+
15782
+ function init() {
15783
+ if (RED.settings.get('diagnostics.ui', true) === false) {
15784
+ return;
15785
+ }
15786
+ RED.actions.add("core:show-system-info", function () { show(); });
15787
+ }
15788
+
15789
+ function show() {
15790
+ $.ajax({
15791
+ headers: {
15792
+ "Accept": "application/json"
15793
+ },
15794
+ cache: false,
15795
+ url: 'diagnostics',
15796
+ success: function (data) {
15797
+ var json = JSON.stringify(data || {}, "", 4);
15798
+ if (json === "{}") {
15799
+ json = "{\n\n}";
15800
+ }
15801
+ RED.editor.editJSON({
15802
+ title: RED._('diagnostics.title'),
15803
+ value: json,
15804
+ requireValid: true,
15805
+ readOnly: true,
15806
+ toolbarButtons: [
15807
+ {
15808
+ text: RED._('clipboard.export.copy'),
15809
+ icon: 'fa fa-copy',
15810
+ click: function () {
15811
+ RED.clipboard.copyText(json, $(this), RED._('clipboard.copyMessageValue'))
15812
+ }
15813
+ },
15814
+ {
15815
+ text: RED._('clipboard.download'),
15816
+ icon: 'fa fa-download',
15817
+ click: function () {
15818
+ var element = document.createElement('a');
15819
+ element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(json));
15820
+ element.setAttribute('download', "system-info.json");
15821
+ element.style.display = 'none';
15822
+ document.body.appendChild(element);
15823
+ element.click();
15824
+ document.body.removeChild(element);
15825
+ }
15826
+ },
15827
+ ]
15828
+ });
15829
+ },
15830
+ error: function (jqXHR, textStatus, errorThrown) {
15831
+ console.log("Unexpected error loading system info:", jqXHR.status, textStatus, errorThrown);
15832
+ }
15833
+ });
15834
+ }
15835
+
15836
+ return {
15837
+ init: init,
15838
+ };
15839
+ })();
15451
15840
  ;RED.diff = (function() {
15452
15841
 
15453
15842
  var currentDiff = {};
@@ -18404,6 +18793,9 @@ RED.workspaces = (function() {
18404
18793
  var hideStack = [];
18405
18794
  var viewStackPos = 0;
18406
18795
 
18796
+ let flashingTab;
18797
+ let flashingTabTimer;
18798
+
18407
18799
  function addToViewStack(id) {
18408
18800
  if (viewStackPos !== viewStack.length) {
18409
18801
  viewStack.splice(viewStackPos);
@@ -18808,6 +19200,9 @@ RED.workspaces = (function() {
18808
19200
  }
18809
19201
  }
18810
19202
  })
19203
+ RED.actions.add("core:list-modified-nodes",function() {
19204
+ RED.actions.invoke("core:search","is:modified ");
19205
+ })
18811
19206
  RED.actions.add("core:list-hidden-flows",function() {
18812
19207
  RED.actions.invoke("core:search","is:hidden ");
18813
19208
  })
@@ -18908,6 +19303,31 @@ RED.workspaces = (function() {
18908
19303
  workspace_tabs.order(order);
18909
19304
  }
18910
19305
 
19306
+ function flashTab(tabId) {
19307
+ if(flashingTab && flashingTab.length) {
19308
+ //cancel current flashing node before flashing new node
19309
+ clearInterval(flashingTabTimer);
19310
+ flashingTabTimer = null;
19311
+ flashingTab.removeClass('highlighted');
19312
+ flashingTab = null;
19313
+ }
19314
+ let tab = $("#red-ui-tab-" + tabId);
19315
+ if(!tab || !tab.length) { return; }
19316
+
19317
+ flashingTabTimer = setInterval(function(flashEndTime) {
19318
+ if (flashEndTime >= Date.now()) {
19319
+ const highlighted = tab.hasClass("highlighted");
19320
+ tab.toggleClass('highlighted', !highlighted)
19321
+ } else {
19322
+ clearInterval(flashingTabTimer);
19323
+ flashingTabTimer = null;
19324
+ flashingTab = null;
19325
+ tab.removeClass('highlighted');
19326
+ }
19327
+ }, 100, Date.now() + 2200);
19328
+ flashingTab = tab;
19329
+ tab.addClass('highlighted');
19330
+ }
18911
19331
  return {
18912
19332
  init: init,
18913
19333
  add: addWorkspace,
@@ -18940,7 +19360,7 @@ RED.workspaces = (function() {
18940
19360
  isHidden: function(id) {
18941
19361
  return hideStack.includes(id)
18942
19362
  },
18943
- show: function(id,skipStack,unhideOnly) {
19363
+ show: function(id,skipStack,unhideOnly,flash) {
18944
19364
  if (!workspace_tabs.contains(id)) {
18945
19365
  var sf = RED.nodes.subflow(id);
18946
19366
  if (sf) {
@@ -18962,6 +19382,9 @@ RED.workspaces = (function() {
18962
19382
  }
18963
19383
  workspace_tabs.activateTab(id);
18964
19384
  }
19385
+ if(flash) {
19386
+ flashTab(id.replace(".","-"))
19387
+ }
18965
19388
  },
18966
19389
  refresh: function() {
18967
19390
  RED.nodes.eachWorkspace(function(ws) {
@@ -19015,6 +19438,7 @@ RED.statusBar = (function() {
19015
19438
  function addWidget(options) {
19016
19439
  widgets[options.id] = options;
19017
19440
  var el = $('<span class="red-ui-statusbar-widget"></span>');
19441
+ el.prop('id', options.id);
19018
19442
  options.element.appendTo(el);
19019
19443
  if (options.align === 'left') {
19020
19444
  leftBucket.append(el);
@@ -19058,6 +19482,7 @@ RED.statusBar = (function() {
19058
19482
  * |- <g> "groupLayer"
19059
19483
  * |- <g> "groupSelectLayer"
19060
19484
  * |- <g> "linkLayer"
19485
+ * |- <g> "junctionLayer"
19061
19486
  * |- <g> "dragGroupLayer"
19062
19487
  * |- <g> "nodeLayer"
19063
19488
  */
@@ -19090,6 +19515,7 @@ RED.view = (function() {
19090
19515
  var activeSubflow = null;
19091
19516
  var activeNodes = [];
19092
19517
  var activeLinks = [];
19518
+ var activeJunctions = [];
19093
19519
  var activeFlowLinks = [];
19094
19520
  var activeLinkNodes = {};
19095
19521
  var activeGroup = null;
@@ -19124,6 +19550,8 @@ RED.view = (function() {
19124
19550
  var lastClickPosition = [];
19125
19551
  var selectNodesOptions;
19126
19552
 
19553
+ let flashingNodeId;
19554
+
19127
19555
  var clipboard = "";
19128
19556
 
19129
19557
  // Note: these are the permitted status colour aliases. The actual RGB values
@@ -19145,6 +19573,7 @@ RED.view = (function() {
19145
19573
  var eventLayer;
19146
19574
  var gridLayer;
19147
19575
  var linkLayer;
19576
+ var junctionLayer;
19148
19577
  var dragGroupLayer;
19149
19578
  var groupSelectLayer;
19150
19579
  var nodeLayer;
@@ -19201,7 +19630,8 @@ RED.view = (function() {
19201
19630
  length: function() { return set.length},
19202
19631
  get: function(i) { return set[i] },
19203
19632
  forEach: function(func) { set.forEach(func) },
19204
- nodes: function() { return set.map(function(n) { return n.n })}
19633
+ nodes: function() { return set.map(function(n) { return n.n })},
19634
+ has: function(node) { return setIds.has(node.id) }
19205
19635
  }
19206
19636
  return api;
19207
19637
  })();
@@ -19407,6 +19837,7 @@ RED.view = (function() {
19407
19837
  groupSelectLayer = eventLayer.append("g");
19408
19838
  linkLayer = eventLayer.append("g");
19409
19839
  dragGroupLayer = eventLayer.append("g");
19840
+ junctionLayer = eventLayer.append("g");
19410
19841
  nodeLayer = eventLayer.append("g");
19411
19842
 
19412
19843
  drag_lines = [];
@@ -19477,13 +19908,40 @@ RED.view = (function() {
19477
19908
  }
19478
19909
  });
19479
19910
 
19911
+ //add search to status-toolbar
19912
+ RED.statusBar.add({
19913
+ id: "view-search-tools",
19914
+ align: "left",
19915
+ hidden: false,
19916
+ element: $('<span class="button-group">'+
19917
+ '<button class="red-ui-footer-button" id="red-ui-view-searchtools-search"><i class="fa fa-search"></i></button>' +
19918
+ '</span>' +
19919
+ '<span class="button-group search-counter">' +
19920
+ '<span class="red-ui-footer-button" id="red-ui-view-searchtools-counter">? of ?</span>' +
19921
+ '</span>' +
19922
+ '<span class="button-group">' +
19923
+ '<button class="red-ui-footer-button" id="red-ui-view-searchtools-prev"><i class="fa fa-chevron-left"></i></button>' +
19924
+ '<button class="red-ui-footer-button" id="red-ui-view-searchtools-next"><i class="fa fa-chevron-right"></i></button>' +
19925
+ '</span>' +
19926
+ '<span class="button-group">' +
19927
+ '<button class="red-ui-footer-button" id="red-ui-view-searchtools-close"><i class="fa fa-close"></i></button>' +
19928
+ '</span>')
19929
+ })
19930
+ $("#red-ui-view-searchtools-search").on("click", searchFlows);
19931
+ RED.popover.tooltip($("#red-ui-view-searchtools-search"),RED._('actions.search-flows'),'core:search');
19932
+ $("#red-ui-view-searchtools-prev").on("click", searchPrev);
19933
+ RED.popover.tooltip($("#red-ui-view-searchtools-prev"),RED._('actions.search-prev'),'core:search-previous');
19934
+ $("#red-ui-view-searchtools-next").on("click", searchNext);
19935
+ RED.popover.tooltip($("#red-ui-view-searchtools-next"),RED._('actions.search-next'),'core:search-next');
19936
+ RED.popover.tooltip($("#red-ui-view-searchtools-close"),RED._('common.label.close'));
19937
+
19480
19938
  // Handle nodes dragged from the palette
19481
19939
  chart.droppable({
19482
19940
  accept:".red-ui-palette-node",
19483
19941
  drop: function( event, ui ) {
19484
19942
  d3.event = event;
19485
19943
  var selected_tool = $(ui.draggable[0]).attr("data-palette-type");
19486
- var result = addNode(selected_tool);
19944
+ var result = createNode(selected_tool);
19487
19945
  if (!result) {
19488
19946
  return;
19489
19947
  }
@@ -19527,17 +19985,9 @@ RED.view = (function() {
19527
19985
  nn.y = mousePos[1];
19528
19986
 
19529
19987
  if (snapGrid) {
19530
- var gridOffset = [0,0];
19531
- var offsetLeft = nn.x-(gridSize*Math.round((nn.x-nn.w/2)/gridSize)+nn.w/2);
19532
- var offsetRight = nn.x-(gridSize*Math.round((nn.x+nn.w/2)/gridSize)-nn.w/2);
19533
- if (Math.abs(offsetLeft) < Math.abs(offsetRight)) {
19534
- gridOffset[0] = offsetLeft
19535
- } else {
19536
- gridOffset[0] = offsetRight
19537
- }
19538
- gridOffset[1] = nn.y-(gridSize*Math.round(nn.y/gridSize));
19539
- nn.x -= gridOffset[0];
19540
- nn.y -= gridOffset[1];
19988
+ var gridOffset = RED.view.tools.calculateGridSnapOffsets(nn);
19989
+ nn.x -= gridOffset.x;
19990
+ nn.y -= gridOffset.y;
19541
19991
  }
19542
19992
 
19543
19993
  var spliceLink = $(ui.helper).data("splice");
@@ -19607,7 +20057,7 @@ RED.view = (function() {
19607
20057
 
19608
20058
  RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection);
19609
20059
  RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection();deleteSelection();});
19610
- RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true});});
20060
+ RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true, generateDefaultNames: true});});
19611
20061
 
19612
20062
  RED.actions.add("core:detach-selected-nodes", function() { detachSelectedNodes() })
19613
20063
 
@@ -19827,7 +20277,7 @@ RED.view = (function() {
19827
20277
  source:{z:activeWorkspace},
19828
20278
  target:{z:activeWorkspace}
19829
20279
  });
19830
-
20280
+ activeJunctions = RED.nodes.junctions(activeWorkspace) || [];
19831
20281
  activeGroups = RED.nodes.groups(activeWorkspace)||[];
19832
20282
  activeGroups.forEach(function(g, i) {
19833
20283
  g._index = i;
@@ -19842,6 +20292,7 @@ RED.view = (function() {
19842
20292
  } else {
19843
20293
  activeNodes = [];
19844
20294
  activeLinks = [];
20295
+ activeJunctions = [];
19845
20296
  activeGroups = [];
19846
20297
  }
19847
20298
 
@@ -19961,84 +20412,10 @@ RED.view = (function() {
19961
20412
  }
19962
20413
  }
19963
20414
 
19964
- function addNode(type,x,y) {
19965
- var m = /^subflow:(.+)$/.exec(type);
19966
-
19967
- if (activeSubflow && m) {
19968
- var subflowId = m[1];
19969
- if (subflowId === activeSubflow.id) {
19970
- RED.notify(RED._("notification.error",{message: RED._("notification.errors.cannotAddSubflowToItself")}),"error");
19971
- return;
19972
- }
19973
- if (RED.nodes.subflowContains(m[1],activeSubflow.id)) {
19974
- RED.notify(RED._("notification.error",{message: RED._("notification.errors.cannotAddCircularReference")}),"error");
19975
- return;
19976
- }
19977
- }
19978
-
19979
- var nn = { id:RED.nodes.id(),z:RED.workspaces.active()};
19980
-
19981
- nn.type = type;
19982
- nn._def = RED.nodes.getType(nn.type);
19983
-
19984
- if (!m) {
19985
- nn.inputs = nn._def.inputs || 0;
19986
- nn.outputs = nn._def.outputs;
19987
-
19988
- for (var d in nn._def.defaults) {
19989
- if (nn._def.defaults.hasOwnProperty(d)) {
19990
- if (nn._def.defaults[d].value !== undefined) {
19991
- nn[d] = JSON.parse(JSON.stringify(nn._def.defaults[d].value));
19992
- }
19993
- }
19994
- }
19995
-
19996
- if (nn._def.onadd) {
19997
- try {
19998
- nn._def.onadd.call(nn);
19999
- } catch(err) {
20000
- console.log("Definition error: "+nn.type+".onadd:",err);
20001
- }
20002
- }
20003
- } else {
20004
- var subflow = RED.nodes.subflow(m[1]);
20005
- nn.name = "";
20006
- nn.inputs = subflow.in.length;
20007
- nn.outputs = subflow.out.length;
20008
- }
20009
-
20010
- nn.changed = true;
20011
- nn.moved = true;
20012
-
20013
- nn.w = node_width;
20014
- nn.h = Math.max(node_height,(nn.outputs||0) * 15);
20015
- nn.resize = true;
20016
-
20017
- var historyEvent = {
20018
- t:"add",
20019
- nodes:[nn.id],
20020
- dirty:RED.nodes.dirty()
20021
- }
20022
- if (activeSubflow) {
20023
- var subflowRefresh = RED.subflow.refresh(true);
20024
- if (subflowRefresh) {
20025
- historyEvent.subflow = {
20026
- id:activeSubflow.id,
20027
- changed: activeSubflow.changed,
20028
- instances: subflowRefresh.instances
20029
- }
20030
- }
20031
- }
20032
- return {
20033
- node: nn,
20034
- historyEvent: historyEvent
20035
- }
20036
-
20037
- }
20038
-
20039
20415
  function canvasMouseDown() {
20040
- if (RED.view.DEBUG) { console.warn("canvasMouseDown", mouse_mode); }
20041
- var point;
20416
+ if (RED.view.DEBUG) {
20417
+ console.warn("canvasMouseDown", { mouse_mode, point: d3.mouse(this), event: d3.event });
20418
+ }
20042
20419
  if (mouse_mode === RED.state.SELECTING_NODE) {
20043
20420
  d3.event.stopPropagation();
20044
20421
  return;
@@ -20055,45 +20432,49 @@ RED.view = (function() {
20055
20432
  selectedLinks.clear();
20056
20433
  updateSelection();
20057
20434
  }
20058
- if (mouse_mode === 0) {
20059
- if (lasso) {
20060
- lasso.remove();
20061
- lasso = null;
20062
- }
20435
+ if (mouse_mode === 0 && lasso) {
20436
+ lasso.remove();
20437
+ lasso = null;
20063
20438
  }
20064
- if ((mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) && (d3.event.touches || d3.event.button === 0) && (d3.event.metaKey || d3.event.ctrlKey)) {
20065
- // Trigger quick add dialog
20066
- d3.event.stopPropagation();
20067
- clearSelection();
20068
- point = d3.mouse(this);
20069
- var clickedGroup = getGroupAt(point[0],point[1]);
20070
- if (drag_lines.length > 0) {
20071
- clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g)
20072
- }
20073
- showQuickAddDialog({position:point, group:clickedGroup});
20074
- } else if (mouse_mode === 0 && (d3.event.touches || d3.event.button === 0) && !(d3.event.metaKey || d3.event.ctrlKey)) {
20075
- // Tigger lasso
20076
- if (!touchStartTime) {
20077
- point = d3.mouse(this);
20078
- lasso = eventLayer.append("rect")
20079
- .attr("ox",point[0])
20080
- .attr("oy",point[1])
20081
- .attr("rx",1)
20082
- .attr("ry",1)
20083
- .attr("x",point[0])
20084
- .attr("y",point[1])
20085
- .attr("width",0)
20086
- .attr("height",0)
20087
- .attr("class","nr-ui-view-lasso");
20088
- d3.event.preventDefault();
20439
+ if (d3.event.touches || d3.event.button === 0) {
20440
+ if ((mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) && (d3.event.metaKey || d3.event.ctrlKey) && !(d3.event.altKey || d3.event.shiftKey)) {
20441
+ // Trigger quick add dialog
20442
+ d3.event.stopPropagation();
20443
+ clearSelection();
20444
+ const point = d3.mouse(this);
20445
+ var clickedGroup = getGroupAt(point[0], point[1]);
20446
+ if (drag_lines.length > 0) {
20447
+ clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g)
20448
+ }
20449
+ showQuickAddDialog({ position: point, group: clickedGroup });
20450
+ } else if (mouse_mode === 0 && !(d3.event.metaKey || d3.event.ctrlKey)) {
20451
+ // CTRL not being held
20452
+ if (!d3.event.altKey) {
20453
+ // ALT not held (shift is allowed) Trigger lasso
20454
+ if (!touchStartTime) {
20455
+ const point = d3.mouse(this);
20456
+ lasso = eventLayer.append("rect")
20457
+ .attr("ox", point[0])
20458
+ .attr("oy", point[1])
20459
+ .attr("rx", 1)
20460
+ .attr("ry", 1)
20461
+ .attr("x", point[0])
20462
+ .attr("y", point[1])
20463
+ .attr("width", 0)
20464
+ .attr("height", 0)
20465
+ .attr("class", "nr-ui-view-lasso");
20466
+ d3.event.preventDefault();
20467
+ }
20468
+ } else if (d3.event.altKey) {
20469
+ //Alt [+shift] held - Begin slicing
20470
+ clearSelection();
20471
+ mouse_mode = (d3.event.shiftKey) ? RED.state.SLICING_JUNCTION : RED.state.SLICING;
20472
+ const point = d3.mouse(this);
20473
+ slicePath = eventLayer.append("path").attr("class", "nr-ui-view-slice").attr("d", `M${point[0]} ${point[1]}`)
20474
+ slicePathLast = point;
20475
+ RED.view.redraw();
20476
+ }
20089
20477
  }
20090
- } else if (mouse_mode === 0 && d3.event.button === 2 && (d3.event.metaKey || d3.event.ctrlKey)) {
20091
- clearSelection();
20092
- mouse_mode = RED.state.SLICING;
20093
- point = d3.mouse(this);
20094
- slicePath = eventLayer.append("path").attr("class","nr-ui-view-slice").attr("d",`M${point[0]} ${point[1]}`)
20095
- slicePathLast = point;
20096
- RED.view.redraw();
20097
20478
  }
20098
20479
  }
20099
20480
 
@@ -20224,16 +20605,38 @@ RED.view = (function() {
20224
20605
  keepAdding = false;
20225
20606
  resetMouseVars();
20226
20607
  }
20227
- var result = addNode(type);
20228
- if (!result) {
20229
- return;
20608
+
20609
+ var nn;
20610
+ var historyEvent;
20611
+ if (type === 'junction') {
20612
+ nn = {
20613
+ _def: {defaults:{}},
20614
+ type: 'junction',
20615
+ z: RED.workspaces.active(),
20616
+ id: RED.nodes.id(),
20617
+ x: 0,
20618
+ y: 0,
20619
+ w: 0, h: 0,
20620
+ outputs: 1,
20621
+ inputs: 1,
20622
+ dirty: true
20623
+ }
20624
+ historyEvent = {
20625
+ t:'add',
20626
+ junctions:[nn]
20627
+ }
20628
+ } else {
20629
+ var result = createNode(type);
20630
+ if (!result) {
20631
+ return;
20632
+ }
20633
+ nn = result.node;
20634
+ historyEvent = result.historyEvent;
20230
20635
  }
20231
20636
  if (keepAdding) {
20232
20637
  mouse_mode = RED.state.QUICK_JOINING;
20233
20638
  }
20234
20639
 
20235
- var nn = result.node;
20236
- var historyEvent = result.historyEvent;
20237
20640
  nn.x = point[0];
20238
20641
  nn.y = point[1];
20239
20642
  var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
@@ -20342,8 +20745,11 @@ RED.view = (function() {
20342
20745
  }
20343
20746
  }
20344
20747
  }
20345
-
20346
- RED.nodes.add(nn);
20748
+ if (nn.type === 'junction') {
20749
+ RED.nodes.addJunction(nn);
20750
+ } else {
20751
+ RED.nodes.add(nn);
20752
+ }
20347
20753
  RED.editor.validateNode(nn);
20348
20754
 
20349
20755
  if (targetGroup) {
@@ -20490,7 +20896,7 @@ RED.view = (function() {
20490
20896
  .attr("height",h)
20491
20897
  ;
20492
20898
  return;
20493
- } else if (mouse_mode === RED.state.SLICING) {
20899
+ } else if (mouse_mode === RED.state.SLICING || mouse_mode === RED.state.SLICING_JUNCTION) {
20494
20900
  if (slicePath) {
20495
20901
  var delta = Math.max(1,Math.abs(slicePathLast[0]-mouse_position[0]))*Math.max(1,Math.abs(slicePathLast[1]-mouse_position[1]))
20496
20902
  if (delta > 20) {
@@ -20677,16 +21083,9 @@ RED.view = (function() {
20677
21083
  gridOffset[0] = node.n.x-(gridSize*Math.floor(node.n.x/gridSize))-gridSize/2;
20678
21084
  gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize))-gridSize/2;
20679
21085
  } else {
20680
- var offsetLeft = node.n.x-(gridSize*Math.round((node.n.x-node.n.w/2)/gridSize)+node.n.w/2);
20681
- var offsetRight = node.n.x-(gridSize*Math.round((node.n.x+node.n.w/2)/gridSize)-node.n.w/2);
20682
- // gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2);
20683
- if (Math.abs(offsetLeft) < Math.abs(offsetRight)) {
20684
- gridOffset[0] = offsetLeft
20685
- } else {
20686
- gridOffset[0] = offsetRight
20687
- }
20688
- gridOffset[1] = node.n.y-(gridSize*Math.round(node.n.y/gridSize));
20689
- // console.log(offsetLeft, offsetRight);
21086
+ const snapOffsets = RED.view.tools.calculateGridSnapOffsets(node.n);
21087
+ gridOffset[0] = snapOffsets.x;
21088
+ gridOffset[1] = snapOffsets.y;
20690
21089
  }
20691
21090
  if (gridOffset[0] !== 0 || gridOffset[1] !== 0) {
20692
21091
  for (i = 0; i<movingSet.length(); i++) {
@@ -20778,7 +21177,9 @@ RED.view = (function() {
20778
21177
 
20779
21178
  function canvasMouseUp() {
20780
21179
  lastClickPosition = [d3.event.offsetX/scaleFactor,d3.event.offsetY/scaleFactor];
20781
- if (RED.view.DEBUG) { console.warn("canvasMouseUp", mouse_mode); }
21180
+ if (RED.view.DEBUG) {
21181
+ console.warn("canvasMouseUp", { mouse_mode, point: d3.mouse(this), event: d3.event });
21182
+ }
20782
21183
  var i;
20783
21184
  var historyEvent;
20784
21185
  if (mouse_mode === RED.state.PANNING) {
@@ -20863,6 +21264,15 @@ RED.view = (function() {
20863
21264
  }
20864
21265
  }
20865
21266
  });
21267
+ activeJunctions.forEach(function(n) {
21268
+ if (!n.selected) {
21269
+ if (n.x > x && n.x < x2 && n.y > y && n.y < y2) {
21270
+ n.selected = true;
21271
+ n.dirty = true;
21272
+ movingSet.add(n);
21273
+ }
21274
+ }
21275
+ })
20866
21276
 
20867
21277
 
20868
21278
 
@@ -20906,11 +21316,120 @@ RED.view = (function() {
20906
21316
  } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey && !d3.event.metaKey ) {
20907
21317
  clearSelection();
20908
21318
  updateSelection();
20909
- } else if (slicePath) {
21319
+ } else if (mouse_mode == RED.state.SLICING) {
20910
21320
  deleteSelection();
20911
21321
  slicePath.remove();
20912
21322
  slicePath = null;
20913
21323
  RED.view.redraw(true);
21324
+ } else if (mouse_mode == RED.state.SLICING_JUNCTION) {
21325
+ var removedLinks = new Set()
21326
+ var addedLinks = []
21327
+ var addedJunctions = []
21328
+
21329
+ var groupedLinks = {}
21330
+ selectedLinks.forEach(function(l) {
21331
+ var sourceId = l.source.id+":"+l.sourcePort
21332
+ groupedLinks[sourceId] = groupedLinks[sourceId] || []
21333
+ groupedLinks[sourceId].push(l)
21334
+
21335
+ groupedLinks[l.target.id] = groupedLinks[l.target.id] || []
21336
+ groupedLinks[l.target.id].push(l)
21337
+ });
21338
+ var linkGroups = Object.keys(groupedLinks)
21339
+ linkGroups.sort(function(A,B) {
21340
+ return groupedLinks[B].length - groupedLinks[A].length
21341
+ })
21342
+ linkGroups.forEach(function(gid) {
21343
+ var links = groupedLinks[gid]
21344
+ var junction = {
21345
+ _def: {defaults:{}},
21346
+ type: 'junction',
21347
+ z: RED.workspaces.active(),
21348
+ id: RED.nodes.id(),
21349
+ x: 0,
21350
+ y: 0,
21351
+ w: 0, h: 0,
21352
+ outputs: 1,
21353
+ inputs: 1,
21354
+ dirty: true
21355
+ }
21356
+ links = links.filter(function(l) { return !removedLinks.has(l) })
21357
+ if (links.length === 0) {
21358
+ return
21359
+ }
21360
+ links.forEach(function(l) {
21361
+ junction.x += l._sliceLocation.x
21362
+ junction.y += l._sliceLocation.y
21363
+ })
21364
+ junction.x = Math.round(junction.x/links.length)
21365
+ junction.y = Math.round(junction.y/links.length)
21366
+ if (snapGrid) {
21367
+ junction.x = (gridSize*Math.round(junction.x/gridSize));
21368
+ junction.y = (gridSize*Math.round(junction.y/gridSize));
21369
+ }
21370
+
21371
+ var nodeGroups = new Set()
21372
+
21373
+ RED.nodes.addJunction(junction)
21374
+ addedJunctions.push(junction)
21375
+ let newLink
21376
+ if (gid === links[0].source.id+":"+links[0].sourcePort) {
21377
+ newLink = {
21378
+ source: links[0].source,
21379
+ sourcePort: links[0].sourcePort,
21380
+ target: junction
21381
+ }
21382
+ } else {
21383
+ newLink = {
21384
+ source: junction,
21385
+ sourcePort: 0,
21386
+ target: links[0].target
21387
+ }
21388
+ }
21389
+ addedLinks.push(newLink)
21390
+ RED.nodes.addLink(newLink)
21391
+ links.forEach(function(l) {
21392
+ removedLinks.add(l)
21393
+ RED.nodes.removeLink(l)
21394
+ let newLink
21395
+ if (gid === l.target.id) {
21396
+ newLink = {
21397
+ source: l.source,
21398
+ sourcePort: l.sourcePort,
21399
+ target: junction
21400
+ }
21401
+ } else {
21402
+ newLink = {
21403
+ source: junction,
21404
+ sourcePort: 0,
21405
+ target: l.target
21406
+ }
21407
+ }
21408
+ addedLinks.push(newLink)
21409
+ RED.nodes.addLink(newLink)
21410
+ nodeGroups.add(l.source.g || "__NONE__")
21411
+ nodeGroups.add(l.target.g || "__NONE__")
21412
+ })
21413
+ if (nodeGroups.size === 1) {
21414
+ var group = nodeGroups.values().next().value
21415
+ if (group !== "__NONE__") {
21416
+ RED.group.addToGroup(RED.nodes.group(group), junction)
21417
+ }
21418
+ }
21419
+ })
21420
+ slicePath.remove();
21421
+ slicePath = null;
21422
+
21423
+ if (addedJunctions.length > 0) {
21424
+ RED.history.push({
21425
+ t: 'add',
21426
+ links: addedLinks,
21427
+ junctions: addedJunctions,
21428
+ removedLinks: Array.from(removedLinks)
21429
+ })
21430
+ RED.nodes.dirty(true)
21431
+ }
21432
+ RED.view.redraw(true);
20914
21433
  }
20915
21434
  if (mouse_mode == RED.state.MOVING_ACTIVE) {
20916
21435
  if (movingSet.length() > 0) {
@@ -21031,6 +21550,9 @@ RED.view = (function() {
21031
21550
  }
21032
21551
  }
21033
21552
  function zoomZero() { zoomView(1); }
21553
+ function searchFlows() { RED.actions.invoke("core:search", $(this).data("term")); }
21554
+ function searchPrev() { RED.actions.invoke("core:search-previous"); }
21555
+ function searchNext() { RED.actions.invoke("core:search-next"); }
21034
21556
 
21035
21557
 
21036
21558
  function zoomView(factor) {
@@ -21067,7 +21589,7 @@ RED.view = (function() {
21067
21589
  clearSelection();
21068
21590
  RED.history.pop();
21069
21591
  mouse_mode = 0;
21070
- } else if (mouse_mode === RED.state.SLICING) {
21592
+ } else if (mouse_mode === RED.state.SLICING || mouse_mode === RED.state.SLICING_JUNCTION) {
21071
21593
  if (slicePath) {
21072
21594
  slicePath.remove();
21073
21595
  slicePath = null;
@@ -21136,6 +21658,14 @@ RED.view = (function() {
21136
21658
  }
21137
21659
  });
21138
21660
 
21661
+ activeJunctions.forEach(function(n) {
21662
+ if (!n.selected) {
21663
+ n.selected = true;
21664
+ n.dirty = true;
21665
+ movingSet.add(n);
21666
+ }
21667
+ })
21668
+
21139
21669
  if (mouse_mode !== RED.state.SELECTING_NODE && activeSubflow) {
21140
21670
  activeSubflow.in.forEach(function(n) {
21141
21671
  if (!n.selected) {
@@ -21335,6 +21865,7 @@ RED.view = (function() {
21335
21865
  nodes: [],
21336
21866
  links: [],
21337
21867
  groups: [],
21868
+ junctions: [],
21338
21869
  workspaces: [],
21339
21870
  subflows: []
21340
21871
  }
@@ -21355,6 +21886,7 @@ RED.view = (function() {
21355
21886
  historyEvent.nodes = historyEvent.nodes.concat(subEvent.nodes);
21356
21887
  historyEvent.links = historyEvent.links.concat(subEvent.links);
21357
21888
  historyEvent.groups = historyEvent.groups.concat(subEvent.groups);
21889
+ historyEvent.junctions = historyEvent.junctions.concat(subEvent.junctions);
21358
21890
  }
21359
21891
  RED.history.push(historyEvent);
21360
21892
  RED.nodes.dirty(true);
@@ -21367,6 +21899,7 @@ RED.view = (function() {
21367
21899
  var removedNodes = [];
21368
21900
  var removedLinks = [];
21369
21901
  var removedGroups = [];
21902
+ var removedJunctions = [];
21370
21903
  var removedSubflowOutputs = [];
21371
21904
  var removedSubflowInputs = [];
21372
21905
  var removedSubflowStatus;
@@ -21411,14 +21944,14 @@ RED.view = (function() {
21411
21944
  for (var i=0;i<movingSet.length();i++) {
21412
21945
  node = movingSet.get(i).n;
21413
21946
  node.selected = false;
21414
- if (node.type !== "group" && node.type !== "subflow") {
21947
+ if (node.type !== "group" && node.type !== "subflow" && node.type !== 'junction') {
21415
21948
  if (node.x < 0) {
21416
21949
  node.x = 25
21417
21950
  }
21418
21951
  var removedEntities = RED.nodes.remove(node.id);
21419
21952
  removedNodes.push(node);
21420
21953
  removedNodes = removedNodes.concat(removedEntities.nodes);
21421
- addToRemovedLinks(removedNodes.removedLinks);
21954
+ addToRemovedLinks(removedEntities.links);
21422
21955
  if (node.g) {
21423
21956
  var group = RED.nodes.group(node.g);
21424
21957
  if (selectedGroups.indexOf(group) === -1) {
@@ -21429,6 +21962,10 @@ RED.view = (function() {
21429
21962
  RED.group.markDirty(group);
21430
21963
  }
21431
21964
  }
21965
+ } else if (node.type === 'junction') {
21966
+ var result = RED.nodes.removeJunction(node)
21967
+ removedJunctions.push(node);
21968
+ removedLinks = removedLinks.concat(result.links);
21432
21969
  } else {
21433
21970
  if (node.direction === "out") {
21434
21971
  removedSubflowOutputs.push(node);
@@ -21473,7 +22010,7 @@ RED.view = (function() {
21473
22010
  subflowInstances = instances.instances;
21474
22011
  }
21475
22012
  movingSet.clear();
21476
- if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus || removedGroups.length > 0) {
22013
+ if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus || removedGroups.length > 0 || removedJunctions.length > 0) {
21477
22014
  RED.nodes.dirty(true);
21478
22015
  }
21479
22016
  }
@@ -21520,6 +22057,7 @@ RED.view = (function() {
21520
22057
  nodes:removedNodes,
21521
22058
  links:removedLinks,
21522
22059
  groups: removedGroups,
22060
+ junctions: removedJunctions,
21523
22061
  subflowOutputs:removedSubflowOutputs,
21524
22062
  subflowInputs:removedSubflowInputs,
21525
22063
  subflow: {
@@ -21579,6 +22117,7 @@ RED.view = (function() {
21579
22117
  var nns = [];
21580
22118
  var nodeCount = 0;
21581
22119
  var groupCount = 0;
22120
+ var junctionCount = 0;
21582
22121
  var handled = {};
21583
22122
  for (var n=0;n<nodes.length;n++) {
21584
22123
  var node = nodes[n];
@@ -21591,6 +22130,8 @@ RED.view = (function() {
21591
22130
  if (node.type != "subflow") {
21592
22131
  if (node.type === "group") {
21593
22132
  groupCount++;
22133
+ } else if (node.type === 'junction') {
22134
+ junctionCount++;
21594
22135
  } else {
21595
22136
  nodeCount++;
21596
22137
  }
@@ -21773,6 +22314,7 @@ RED.view = (function() {
21773
22314
  evt.preventDefault();
21774
22315
  }
21775
22316
 
22317
+
21776
22318
  function portMouseUp(d,portType,portIndex,evt) {
21777
22319
  if (RED.view.DEBUG) { console.warn("portMouseUp", mouse_mode,d,portType,portIndex); }
21778
22320
  evt = evt || d3.event;
@@ -21864,7 +22406,7 @@ RED.view = (function() {
21864
22406
  } else if (drag_line.portType === PORT_TYPE_INPUT) {
21865
22407
  src = mouseup_node;
21866
22408
  dst = drag_line.node;
21867
- src_port = portIndex;
22409
+ src_port = portIndex || 0;
21868
22410
  }
21869
22411
  var link = {source: src, sourcePort:src_port, target: dst};
21870
22412
  if (drag_line.virtualLink) {
@@ -22155,6 +22697,16 @@ RED.view = (function() {
22155
22697
  port.classed("red-ui-flow-port-hovered",false);
22156
22698
  }
22157
22699
 
22700
+ function junctionMouseOver(junction, d, portType) {
22701
+ var active = (portType === undefined) ||
22702
+ (mouse_mode !== RED.state.JOINING && mouse_mode !== RED.state.QUICK_JOINING) ||
22703
+ (drag_lines.length > 0 && drag_lines[0].portType !== portType && !drag_lines[0].virtualLink)
22704
+ junction.classed("red-ui-flow-junction-hovered", active);
22705
+ }
22706
+ function junctionMouseOut(junction, d) {
22707
+ junction.classed("red-ui-flow-junction-hovered",false);
22708
+ }
22709
+
22158
22710
  function prepareDrag(mouse) {
22159
22711
  mouse_mode = RED.state.MOVING;
22160
22712
  // Called when movingSet should be prepared to be dragged
@@ -22314,6 +22866,9 @@ RED.view = (function() {
22314
22866
  return;
22315
22867
  } else if (mouse_mode === RED.state.SELECTING_NODE) {
22316
22868
  d3.event.stopPropagation();
22869
+ if (d.type === 'junction') {
22870
+ return
22871
+ }
22317
22872
  if (selectNodesOptions.single) {
22318
22873
  selectNodesOptions.done(d);
22319
22874
  return;
@@ -22340,14 +22895,12 @@ RED.view = (function() {
22340
22895
  var now = Date.now();
22341
22896
  clickElapsed = now-clickTime;
22342
22897
  clickTime = now;
22343
- dblClickPrimed = (lastClickNode == mousedown_node &&
22898
+ dblClickPrimed = lastClickNode == mousedown_node &&
22344
22899
  (d3.event.touches || d3.event.button === 0) &&
22345
22900
  !d3.event.shiftKey && !d3.event.altKey &&
22346
- clickElapsed < dblClickInterval
22347
- )
22348
- lastClickNode = mousedown_node;
22349
-
22350
- var i;
22901
+ clickElapsed < dblClickInterval &&
22902
+ d.type !== 'junction'
22903
+ lastClickNode = mousedown_node;
22351
22904
 
22352
22905
  if (!d.selected && d.g /*&& !RED.nodes.group(d.g).selected*/) {
22353
22906
  var nodeGroup = RED.nodes.group(d.g);
@@ -22382,7 +22935,6 @@ RED.view = (function() {
22382
22935
  enterActiveGroup(ag);
22383
22936
  activeGroup.selected = true;
22384
22937
  }
22385
- console.log(d3.event);
22386
22938
  var cnodes = RED.nodes.getAllFlowNodes(mousedown_node);
22387
22939
  for (var n=0;n<cnodes.length;n++) {
22388
22940
  if (!cnodes[n].selected) {
@@ -22474,9 +23026,9 @@ RED.view = (function() {
22474
23026
  clearSelection();
22475
23027
  }
22476
23028
  var clickPosition = (d3.event.offsetX/scaleFactor - mousedown_node.x)
22477
- var edgeDelta = (mousedown_node.w/2) - Math.abs(clickPosition);
23029
+ var edgeDelta = ((mousedown_node.w||10)/2) - Math.abs(clickPosition);
22478
23030
  var cnodes;
22479
- var targetEdgeDelta = mousedown_node.w > 30 ? 25 : 8;
23031
+ var targetEdgeDelta = mousedown_node.w > 30 ? 25 : (mousedown_node.w > 0 ? 8 : 3);
22480
23032
  if (edgeDelta < targetEdgeDelta) {
22481
23033
  if (clickPosition < 0) {
22482
23034
  cnodes = [mousedown_node].concat(RED.nodes.getAllUpstreamNodes(mousedown_node));
@@ -22621,7 +23173,13 @@ RED.view = (function() {
22621
23173
  function portMouseOverProxy(e) { portMouseOver(d3.select(this), this.__data__,this.__portType__,this.__portIndex__, e); }
22622
23174
  function portMouseOutProxy(e) { portMouseOut(d3.select(this), this.__data__,this.__portType__,this.__portIndex__, e); }
22623
23175
 
23176
+ function junctionMouseOverProxy(e) { junctionMouseOver(d3.select(this), this.__data__, this.__portType__) }
23177
+ function junctionMouseOutProxy(e) { junctionMouseOut(d3.select(this), this.__data__) }
23178
+
22624
23179
  function linkMouseDown(d) {
23180
+ if (RED.view.DEBUG) {
23181
+ console.warn("linkMouseDown", { mouse_mode, point: d3.mouse(this), event: d3.event });
23182
+ }
22625
23183
  if (mouse_mode === RED.state.SELECTING_NODE) {
22626
23184
  d3.event.stopPropagation();
22627
23185
  return;
@@ -23755,6 +24313,97 @@ RED.view = (function() {
23755
24313
  })
23756
24314
  }
23757
24315
 
24316
+ var junction = junctionLayer.selectAll(".red-ui-flow-junction").data(
24317
+ activeJunctions,
24318
+ d => d.id
24319
+ )
24320
+ var junctionEnter = junction.enter().insert("svg:g").attr("class","red-ui-flow-junction")
24321
+ junctionEnter.each(function(d,i) {
24322
+ var junction = d3.select(this);
24323
+ var contents = document.createDocumentFragment();
24324
+ // d.added = true;
24325
+ var junctionBack = document.createElementNS("http://www.w3.org/2000/svg","rect");
24326
+ junctionBack.setAttribute("class","red-ui-flow-junction-background");
24327
+ junctionBack.setAttribute("x",-5);
24328
+ junctionBack.setAttribute("y",-5);
24329
+ junctionBack.setAttribute("width",10);
24330
+ junctionBack.setAttribute("height",10);
24331
+ junctionBack.setAttribute("rx",3);
24332
+ junctionBack.setAttribute("ry",3);
24333
+ junctionBack.__data__ = d;
24334
+ this.__junctionBack__ = junctionBack;
24335
+ contents.appendChild(junctionBack);
24336
+
24337
+ var junctionInput = document.createElementNS("http://www.w3.org/2000/svg","rect");
24338
+ junctionInput.setAttribute("class","red-ui-flow-junction-port red-ui-flow-junction-port-input");
24339
+ junctionInput.setAttribute("x",-5);
24340
+ junctionInput.setAttribute("y",-5);
24341
+ junctionInput.setAttribute("width",10);
24342
+ junctionInput.setAttribute("height",10);
24343
+ junctionInput.setAttribute("rx",3);
24344
+ junctionInput.setAttribute("ry",3);
24345
+ junctionInput.__data__ = d;
24346
+ junctionInput.__portType__ = PORT_TYPE_INPUT;
24347
+ junctionInput.__portIndex__ = 0;
24348
+ this.__junctionInput__ = junctionOutput;
24349
+ contents.appendChild(junctionInput);
24350
+ junctionInput.addEventListener("mouseup", portMouseUpProxy);
24351
+ junctionInput.addEventListener("mousedown", portMouseDownProxy);
24352
+
24353
+
24354
+ this.__junctionInput__ = junctionInput;
24355
+ contents.appendChild(junctionInput);
24356
+ var junctionOutput = document.createElementNS("http://www.w3.org/2000/svg","rect");
24357
+ junctionOutput.setAttribute("class","red-ui-flow-junction-port red-ui-flow-junction-port-output");
24358
+ junctionOutput.setAttribute("x",-5);
24359
+ junctionOutput.setAttribute("y",-5);
24360
+ junctionOutput.setAttribute("width",10);
24361
+ junctionOutput.setAttribute("height",10);
24362
+ junctionOutput.setAttribute("rx",3);
24363
+ junctionOutput.setAttribute("ry",3);
24364
+ junctionOutput.__data__ = d;
24365
+ junctionOutput.__portType__ = PORT_TYPE_OUTPUT;
24366
+ junctionOutput.__portIndex__ = 0;
24367
+ this.__junctionOutput__ = junctionOutput;
24368
+ contents.appendChild(junctionOutput);
24369
+ junctionOutput.addEventListener("mouseup", portMouseUpProxy);
24370
+ junctionOutput.addEventListener("mousedown", portMouseDownProxy);
24371
+
24372
+ junctionOutput.addEventListener("mouseover", junctionMouseOverProxy);
24373
+ junctionOutput.addEventListener("mouseout", junctionMouseOutProxy);
24374
+ junctionInput.addEventListener("mouseover", junctionMouseOverProxy);
24375
+ junctionInput.addEventListener("mouseout", junctionMouseOutProxy);
24376
+ junctionBack.addEventListener("mouseover", junctionMouseOverProxy);
24377
+ junctionBack.addEventListener("mouseout", junctionMouseOutProxy);
24378
+
24379
+ // These handlers expect to be registered as d3 events
24380
+ d3.select(junctionBack).on("mousedown", nodeMouseDown).on("mouseup", nodeMouseUp);
24381
+
24382
+ junction[0][0].appendChild(contents);
24383
+ })
24384
+ junction.exit().remove();
24385
+ junction.each(function(d) {
24386
+ var junction = d3.select(this);
24387
+ this.setAttribute("transform", "translate(" + (d.x) + "," + (d.y) + ")");
24388
+ if (d.dirty) {
24389
+ junction.classed("red-ui-flow-junction-dragging", mouse_mode === RED.state.MOVING_ACTIVE && movingSet.has(d))
24390
+ junction.classed("selected", !!d.selected)
24391
+ dirtyNodes[d.id] = d;
24392
+
24393
+ if (d.g) {
24394
+ if (!dirtyGroups[d.g]) {
24395
+ var gg = d.g;
24396
+ while (gg && !dirtyGroups[gg]) {
24397
+ dirtyGroups[gg] = RED.nodes.group(gg);
24398
+ gg = dirtyGroups[gg].g;
24399
+ }
24400
+ }
24401
+ }
24402
+
24403
+ }
24404
+
24405
+ })
24406
+
23758
24407
  var link = linkLayer.selectAll(".red-ui-flow-link").data(
23759
24408
  activeLinks,
23760
24409
  function(d) {
@@ -23778,9 +24427,31 @@ RED.view = (function() {
23778
24427
  .on("touchstart",linkTouchStart)
23779
24428
  .on("mousemove", function(d) {
23780
24429
  if (mouse_mode === RED.state.SLICING) {
24430
+
23781
24431
  selectedLinks.add(d)
23782
24432
  l.classed("red-ui-flow-link-splice",true)
23783
24433
  redraw()
24434
+ } else if (mouse_mode === RED.state.SLICING_JUNCTION && !d.link) {
24435
+ if (!l.classed("red-ui-flow-link-splice")) {
24436
+ // Find intersection point
24437
+ var lineLength = pathLine.getTotalLength();
24438
+ var pos;
24439
+ var delta = Infinity;
24440
+ for (var i = 0; i < lineLength; i++) {
24441
+ var linePos = pathLine.getPointAtLength(i);
24442
+ var posDeltaX = Math.abs(linePos.x-d3.event.offsetX)
24443
+ var posDeltaY = Math.abs(linePos.y-d3.event.offsetY)
24444
+ var posDelta = posDeltaX*posDeltaX + posDeltaY*posDeltaY
24445
+ if (posDelta < delta) {
24446
+ pos = linePos
24447
+ delta = posDelta
24448
+ }
24449
+ }
24450
+ d._sliceLocation = pos
24451
+ selectedLinks.add(d)
24452
+ l.classed("red-ui-flow-link-splice",true)
24453
+ redraw()
24454
+ }
23784
24455
  }
23785
24456
  })
23786
24457
 
@@ -23807,9 +24478,9 @@ RED.view = (function() {
23807
24478
  var numOutputs = d.source.outputs || 1;
23808
24479
  var sourcePort = d.sourcePort || 0;
23809
24480
  var y = -((numOutputs-1)/2)*13 +13*sourcePort;
23810
- d.x1 = d.source.x+d.source.w/2;
24481
+ d.x1 = d.source.x+(d.source.w/2||0);
23811
24482
  d.y1 = d.source.y+y;
23812
- d.x2 = d.target.x-d.target.w/2;
24483
+ d.x2 = d.target.x-(d.target.w/2||0);
23813
24484
  d.y2 = d.target.y;
23814
24485
 
23815
24486
  // return "M "+d.x1+" "+d.y1+
@@ -24223,12 +24894,16 @@ RED.view = (function() {
24223
24894
  * - addFlow - whether to import nodes to a new tab
24224
24895
  * - touchImport - whether this is a touch import. If not, imported nodes are
24225
24896
  * attachedto mouse for placing - "IMPORT_DRAGGING" state
24897
+ * - generateIds - whether to automatically generate new ids for all imported nodes
24898
+ * - generateDefaultNames - whether to automatically update any nodes with clashing
24899
+ * default names
24226
24900
  */
24227
24901
  function importNodes(newNodesObj,options) {
24228
24902
  options = options || {
24229
24903
  addFlow: false,
24230
24904
  touchImport: false,
24231
- generateIds: false
24905
+ generateIds: false,
24906
+ generateDefaultNames: false
24232
24907
  }
24233
24908
  var addNewFlow = options.addFlow
24234
24909
  var touchImport = options.touchImport;
@@ -24256,7 +24931,13 @@ RED.view = (function() {
24256
24931
  if (!$.isArray(nodesToImport)) {
24257
24932
  nodesToImport = [nodesToImport];
24258
24933
  }
24259
-
24934
+ if (options.generateDefaultNames) {
24935
+ RED.actions.invoke("core:generate-node-names", nodesToImport, {
24936
+ renameBlank: false,
24937
+ renameClash: true,
24938
+ generateHistory: false
24939
+ })
24940
+ }
24260
24941
 
24261
24942
  try {
24262
24943
  var activeSubflowChanged;
@@ -24268,6 +24949,7 @@ RED.view = (function() {
24268
24949
  var new_nodes = result.nodes;
24269
24950
  var new_links = result.links;
24270
24951
  var new_groups = result.groups;
24952
+ var new_junctions = result.junctions;
24271
24953
  var new_workspaces = result.workspaces;
24272
24954
  var new_subflows = result.subflows;
24273
24955
  var removedNodes = result.removedNodes;
@@ -24277,6 +24959,7 @@ RED.view = (function() {
24277
24959
  }
24278
24960
  var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() });
24279
24961
  new_ms = new_ms.concat(new_groups.filter(function(g) { return g.z === RED.workspaces.active()}))
24962
+ new_ms = new_ms.concat(new_junctions.filter(function(j) { return j.z === RED.workspaces.active()}))
24280
24963
  var new_node_ids = new_nodes.map(function(n){ n.changed = true; return n.id; });
24281
24964
 
24282
24965
  clearSelection();
@@ -24310,9 +24993,11 @@ RED.view = (function() {
24310
24993
  node.n.moved = true;
24311
24994
  node.n.x -= dx - mouse_position[0];
24312
24995
  node.n.y -= dy - mouse_position[1];
24313
- node.n.w = node_width;
24314
- node.n.h = node_height;
24315
- node.n.resize = true;
24996
+ if (node.n.type !== 'junction') {
24997
+ node.n.w = node_width;
24998
+ node.n.h = node_height;
24999
+ node.n.resize = true;
25000
+ }
24316
25001
  node.dx = node.n.x - mouse_position[0];
24317
25002
  node.dy = node.n.y - mouse_position[1];
24318
25003
  if (node.n.type === "group") {
@@ -24359,6 +25044,7 @@ RED.view = (function() {
24359
25044
  nodes:new_node_ids,
24360
25045
  links:new_links,
24361
25046
  groups:new_groups,
25047
+ junctions: new_junctions,
24362
25048
  workspaces:new_workspaces,
24363
25049
  subflows:new_subflows,
24364
25050
  dirty:RED.nodes.dirty()
@@ -24406,6 +25092,7 @@ RED.view = (function() {
24406
25092
  }
24407
25093
  })
24408
25094
  var newGroupCount = new_groups.length;
25095
+ var newJunctionCount = new_junctions.length;
24409
25096
  if (new_workspaces.length > 0) {
24410
25097
  counts.push(RED._("clipboard.flow",{count:new_workspaces.length}));
24411
25098
  }
@@ -24542,6 +25229,93 @@ RED.view = (function() {
24542
25229
  return selection;
24543
25230
  }
24544
25231
 
25232
+ /**
25233
+ * Create a node from a type string.
25234
+ * **NOTE:** Can throw on error - use `try` `catch` block when calling
25235
+ * @param {string} type The node type to create
25236
+ * @param {number} [x] (optional) The horizontal position on the workspace
25237
+ * @param {number} [y] (optional)The vertical on the workspace
25238
+ * @param {string} [z] (optional) The flow tab this node will belong to. Defaults to active workspace.
25239
+ * @returns An object containing the `node` and a `historyEvent`
25240
+ * @private
25241
+ */
25242
+ function createNode(type, x, y, z) {
25243
+ var m = /^subflow:(.+)$/.exec(type);
25244
+ var activeSubflow = z ? RED.nodes.subflow(z) : null;
25245
+ if (activeSubflow && m) {
25246
+ var subflowId = m[1];
25247
+ if (subflowId === activeSubflow.id) {
25248
+ throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddSubflowToItself") }))
25249
+ }
25250
+ if (RED.nodes.subflowContains(m[1], activeSubflow.id)) {
25251
+ throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddCircularReference") }))
25252
+ }
25253
+ }
25254
+
25255
+ var nn = { id: RED.nodes.id(), z: z || RED.workspaces.active() };
25256
+
25257
+ nn.type = type;
25258
+ nn._def = RED.nodes.getType(nn.type);
25259
+
25260
+ if (!m) {
25261
+ nn.inputs = nn._def.inputs || 0;
25262
+ nn.outputs = nn._def.outputs;
25263
+
25264
+ for (var d in nn._def.defaults) {
25265
+ if (nn._def.defaults.hasOwnProperty(d)) {
25266
+ if (nn._def.defaults[d].value !== undefined) {
25267
+ nn[d] = JSON.parse(JSON.stringify(nn._def.defaults[d].value));
25268
+ }
25269
+ }
25270
+ }
25271
+
25272
+ if (nn._def.onadd) {
25273
+ try {
25274
+ nn._def.onadd.call(nn);
25275
+ } catch (err) {
25276
+ console.log("Definition error: " + nn.type + ".onadd:", err);
25277
+ }
25278
+ }
25279
+ } else {
25280
+ var subflow = RED.nodes.subflow(m[1]);
25281
+ nn.name = "";
25282
+ nn.inputs = subflow.in.length;
25283
+ nn.outputs = subflow.out.length;
25284
+ }
25285
+
25286
+ nn.changed = true;
25287
+ nn.moved = true;
25288
+
25289
+ nn.w = RED.view.node_width;
25290
+ nn.h = Math.max(RED.view.node_height, (nn.outputs || 0) * 15);
25291
+ nn.resize = true;
25292
+ if (x != null && typeof x == "number" && x >= 0) {
25293
+ nn.x = x;
25294
+ }
25295
+ if (y != null && typeof y == "number" && y >= 0) {
25296
+ nn.y = y;
25297
+ }
25298
+ var historyEvent = {
25299
+ t: "add",
25300
+ nodes: [nn.id],
25301
+ dirty: RED.nodes.dirty()
25302
+ }
25303
+ if (activeSubflow) {
25304
+ var subflowRefresh = RED.subflow.refresh(true);
25305
+ if (subflowRefresh) {
25306
+ historyEvent.subflow = {
25307
+ id: activeSubflow.id,
25308
+ changed: activeSubflow.changed,
25309
+ instances: subflowRefresh.instances
25310
+ }
25311
+ }
25312
+ }
25313
+ return {
25314
+ node: nn,
25315
+ historyEvent: historyEvent
25316
+ }
25317
+ }
25318
+
24545
25319
  function calculateNodeDimensions(node) {
24546
25320
  var result = [node_width,node_height];
24547
25321
  try {
@@ -24565,6 +25339,36 @@ RED.view = (function() {
24565
25339
  return result;
24566
25340
  }
24567
25341
 
25342
+
25343
+ function flashNode(n) {
25344
+ let node = n;
25345
+ if(typeof node === "string") { node = RED.nodes.node(n); }
25346
+ if(!node) { return; }
25347
+
25348
+ const flashingNode = flashingNodeId && RED.nodes.node(flashingNodeId);
25349
+ if(flashingNode) {
25350
+ //cancel current flashing node before flashing new node
25351
+ clearInterval(flashingNode.__flashTimer);
25352
+ delete flashingNode.__flashTimer;
25353
+ flashingNode.dirty = true;
25354
+ flashingNode.highlighted = false;
25355
+ }
25356
+ node.__flashTimer = setInterval(function(flashEndTime, n) {
25357
+ n.dirty = true;
25358
+ if (flashEndTime >= Date.now()) {
25359
+ n.highlighted = !n.highlighted;
25360
+ } else {
25361
+ clearInterval(n.__flashTimer);
25362
+ delete n.__flashTimer;
25363
+ flashingNodeId = null;
25364
+ n.highlighted = false;
25365
+ }
25366
+ RED.view.redraw();
25367
+ }, 100, Date.now() + 2200, node)
25368
+ flashingNodeId = node.id;
25369
+ node.highlighted = true;
25370
+ RED.view.redraw();
25371
+ }
24568
25372
  return {
24569
25373
  init: init,
24570
25374
  state:function(state) {
@@ -24625,7 +25429,21 @@ RED.view = (function() {
24625
25429
  redraw(true);
24626
25430
  },
24627
25431
  selection: getSelection,
24628
-
25432
+ clearSelection: clearSelection,
25433
+ createNode: createNode,
25434
+ /** default node width */
25435
+ get node_width() {
25436
+ return node_width;
25437
+ },
25438
+ /** default node height */
25439
+ get node_height() {
25440
+ return node_height;
25441
+ },
25442
+ /** snap to grid option state */
25443
+ get snapGrid() {
25444
+ return snapGrid;
25445
+ },
25446
+ /** gets the current scale factor */
24629
25447
  scale: function() {
24630
25448
  return scaleFactor;
24631
25449
  },
@@ -24647,7 +25465,7 @@ RED.view = (function() {
24647
25465
  getActiveGroup: function() { return activeGroup },
24648
25466
  reveal: function(id,triggerHighlight) {
24649
25467
  if (RED.nodes.workspace(id) || RED.nodes.subflow(id)) {
24650
- RED.workspaces.show(id);
25468
+ RED.workspaces.show(id, null, null, true);
24651
25469
  } else {
24652
25470
  var node = RED.nodes.node(id) || RED.nodes.group(id);
24653
25471
  if (node) {
@@ -24655,7 +25473,7 @@ RED.view = (function() {
24655
25473
  node.dirty = true;
24656
25474
  RED.workspaces.show(node.z);
24657
25475
 
24658
- var screenSize = [chart.width()/scaleFactor,chart.height()/scaleFactor];
25476
+ var screenSize = [chart[0].clientWidth/scaleFactor,chart[0].clientHeight/scaleFactor];
24659
25477
  var scrollPos = [chart.scrollLeft()/scaleFactor,chart.scrollTop()/scaleFactor];
24660
25478
  var cx = node.x;
24661
25479
  var cy = node.y;
@@ -24672,24 +25490,7 @@ RED.view = (function() {
24672
25490
  },200);
24673
25491
  }
24674
25492
  if (triggerHighlight !== false) {
24675
- node.highlighted = true;
24676
- if (!node._flashing) {
24677
- node._flashing = true;
24678
- var flash = 22;
24679
- var flashFunc = function() {
24680
- flash--;
24681
- node.dirty = true;
24682
- if (flash >= 0) {
24683
- node.highlighted = !node.highlighted;
24684
- setTimeout(flashFunc,100);
24685
- } else {
24686
- node.highlighted = false;
24687
- delete node._flashing;
24688
- }
24689
- RED.view.redraw();
24690
- }
24691
- flashFunc();
24692
- }
25493
+ flashNode(node);
24693
25494
  }
24694
25495
  } else if (node._def.category === 'config') {
24695
25496
  RED.sidebar.config.show(id);
@@ -25922,6 +26723,244 @@ RED.view.tools = (function() {
25922
26723
  }
25923
26724
  }
25924
26725
 
26726
+ /**
26727
+ * Splits selected wires and re-joins them with link-out+link-in
26728
+ * @param {Object || Object[]} wires The wire(s) to split and replace with link-out, link-in nodes.
26729
+ */
26730
+ function splitWiresWithLinkNodes(wires) {
26731
+ let wiresToSplit = wires || RED.view.selection().links;
26732
+ if (!Array.isArray(wiresToSplit)) {
26733
+ wiresToSplit = [wiresToSplit];
26734
+ }
26735
+ if (wiresToSplit.length < 1) {
26736
+ return; //nothing selected
26737
+ }
26738
+
26739
+ const history = {
26740
+ t: 'multi',
26741
+ events: [],
26742
+ dirty: RED.nodes.dirty()
26743
+ }
26744
+ const nodeSrcMap = {};
26745
+ const nodeTrgMap = {};
26746
+ const _gridSize = RED.view.gridSize();
26747
+
26748
+ for (let wireIdx = 0; wireIdx < wiresToSplit.length; wireIdx++) {
26749
+ const wire = wiresToSplit[wireIdx];
26750
+
26751
+ //get source and target nodes of this wire link
26752
+ const nSrc = wire.source;
26753
+ const nTrg = wire.target;
26754
+
26755
+ var updateNewNodePosXY = function (origNode, newNode, alignLeft, snap, yOffset) {
26756
+ const nnSize = RED.view.calculateNodeDimensions(newNode);
26757
+ newNode.w = nnSize[0];
26758
+ newNode.h = nnSize[1];
26759
+ const coords = { x: origNode.x || 0, y: origNode.y || 0, w: origNode.w || RED.view.node_width, h: origNode.h || RED.view.node_height };
26760
+ const x = coords.x - (coords.w/2.0);
26761
+ if (alignLeft) {
26762
+ coords.x = x - _gridSize - (newNode.w/2.0);
26763
+ } else {
26764
+ coords.x = x + coords.w + _gridSize + (newNode.w/2.0);
26765
+ }
26766
+ newNode.x = coords.x;
26767
+ newNode.y = coords.y;
26768
+ if (snap !== false) {
26769
+ const offsets = RED.view.tools.calculateGridSnapOffsets(newNode);
26770
+ newNode.x -= offsets.x;
26771
+ newNode.y -= offsets.y;
26772
+ }
26773
+ newNode.y += (yOffset || 0);
26774
+ }
26775
+ const srcPort = (wire.sourcePort || 0);
26776
+ let linkOutMapId = nSrc.id + ':' + srcPort;
26777
+ let nnLinkOut = nodeSrcMap[linkOutMapId];
26778
+ //Create a Link Out if one is not already present
26779
+ if(!nnLinkOut) {
26780
+ const nLinkOut = RED.view.createNode("link out"); //create link node
26781
+ nnLinkOut = nLinkOut.node;
26782
+ nodeSrcMap[linkOutMapId] = nnLinkOut;
26783
+ let yOffset = 0;
26784
+ if(nSrc.outputs > 1) {
26785
+
26786
+ const CENTER_PORT = (((nSrc.outputs-1) / 2) + 1);
26787
+ const offsetCount = Math.abs(CENTER_PORT - (srcPort + 1));
26788
+ yOffset = (_gridSize * 2 * offsetCount);
26789
+ if((srcPort + 1) < CENTER_PORT) {
26790
+ yOffset = -yOffset;
26791
+ }
26792
+ updateNewNodePosXY(nSrc, nnLinkOut, false, false, yOffset);
26793
+ } else {
26794
+ updateNewNodePosXY(nSrc, nnLinkOut, false, RED.view.snapGrid, yOffset);
26795
+ }
26796
+ //add created node
26797
+ RED.nodes.add(nnLinkOut);
26798
+ RED.editor.validateNode(nnLinkOut);
26799
+ history.events.push(nLinkOut.historyEvent);
26800
+ //connect node to link node
26801
+ const link = {
26802
+ source: nSrc,
26803
+ sourcePort: wire.sourcePort || 0,
26804
+ target: nnLinkOut
26805
+ };
26806
+ RED.nodes.addLink(link);
26807
+ history.events.push({
26808
+ t: 'add',
26809
+ links: [link],
26810
+ });
26811
+ }
26812
+
26813
+ let nnLinkIn = nodeTrgMap[nTrg.id];
26814
+ //Create a Link In if one is not already present
26815
+ if(!nnLinkIn) {
26816
+ const nLinkIn = RED.view.createNode("link in"); //create link node
26817
+ nnLinkIn = nLinkIn.node;
26818
+ nodeTrgMap[nTrg.id] = nnLinkIn;
26819
+ updateNewNodePosXY(nTrg, nnLinkIn, true, RED.view.snapGrid, 0);
26820
+ //add created node
26821
+ RED.nodes.add(nnLinkIn);
26822
+ RED.editor.validateNode(nnLinkIn);
26823
+ history.events.push(nLinkIn.historyEvent);
26824
+ //connect node to link node
26825
+ const link = {
26826
+ source: nnLinkIn,
26827
+ sourcePort: 0,
26828
+ target: nTrg
26829
+ };
26830
+ RED.nodes.addLink(link);
26831
+ history.events.push({
26832
+ t: 'add',
26833
+ links: [link],
26834
+ });
26835
+ }
26836
+
26837
+ //connect the link out/link in virtual wires
26838
+ if(nnLinkIn.links.indexOf(nnLinkOut.id) == -1) {
26839
+ nnLinkIn.links.push(nnLinkOut.id);
26840
+ }
26841
+ if(nnLinkOut.links.indexOf(nnLinkIn.id) == -1) {
26842
+ nnLinkOut.links.push(nnLinkIn.id);
26843
+ }
26844
+
26845
+ //delete the original wire
26846
+ RED.nodes.removeLink(wire);
26847
+ history.events.push({
26848
+ t: "delete",
26849
+ links: [wire]
26850
+ });
26851
+ }
26852
+ //add all history events to stack
26853
+ RED.history.push(history);
26854
+
26855
+ //select all downstream of new link-in nodes so user can drag to new location
26856
+ RED.view.clearSelection();
26857
+ RED.view.select({nodes: Object.values(nodeTrgMap) });
26858
+ selectConnected("down");
26859
+
26860
+ //update the view
26861
+ RED.nodes.dirty(true);
26862
+ RED.view.redraw(true);
26863
+ }
26864
+
26865
+ /**
26866
+ * Calculate the required offsets to snap a node
26867
+ * @param {Object} node The node to calculate grid snap offsets for
26868
+ * @param {Object} [options] Options: `align` can be "nearest", "left" or "right"
26869
+ * @returns `{x:number, y:number}` as the offsets to deduct from `x` and `y`
26870
+ */
26871
+ function calculateGridSnapOffsets(node, options) {
26872
+ options = options || { align: "nearest" };
26873
+ const gridOffset = { x: 0, y: 0 };
26874
+ const gridSize = RED.view.gridSize();
26875
+ const offsetLeft = node.x - (gridSize * Math.round((node.x - node.w / 2) / gridSize) + node.w / 2);
26876
+ const offsetRight = node.x - (gridSize * Math.round((node.x + node.w / 2) / gridSize) - node.w / 2);
26877
+ gridOffset.x = offsetRight;
26878
+ if (options.align === "right") {
26879
+ //skip - already set to right
26880
+ } else if (options.align === "left" || Math.abs(offsetLeft) < Math.abs(offsetRight)) {
26881
+ gridOffset.x = offsetLeft;
26882
+ }
26883
+ gridOffset.y = node.y - (gridSize * Math.round(node.y / gridSize));
26884
+ return gridOffset;
26885
+ }
26886
+
26887
+ /**
26888
+ * Generate names for the select nodes.
26889
+ * - it only sets the name if it is currently blank
26890
+ * - it uses `<paletteLabel> <N>` - where N is the next available integer that
26891
+ * doesn't clash with any existing nodes of that type
26892
+ * @param {Object} node The node to set the name of - if not provided, uses current selection
26893
+ */
26894
+ function generateNodeNames(node, options) {
26895
+ options = options || {
26896
+ renameBlank: true,
26897
+ renameClash: true,
26898
+ generateHistory: true
26899
+ }
26900
+ let nodes = node;
26901
+ if (node) {
26902
+ if (!Array.isArray(node)) {
26903
+ nodes = [ node ]
26904
+ }
26905
+ } else {
26906
+ nodes = RED.view.selection().nodes;
26907
+ }
26908
+ if (nodes && nodes.length > 0) {
26909
+ // Generate history event if using the workspace selection,
26910
+ // or if the provided node already exists
26911
+ const generateHistory = options.generateHistory && (!node || !!RED.nodes.node(node.id))
26912
+ const historyEvents = []
26913
+ const typeIndex = {}
26914
+ let changed = false;
26915
+ nodes.forEach(n => {
26916
+ const nodeDef = n._def || RED.nodes.getType(n.type)
26917
+ if (nodeDef && nodeDef.defaults && nodeDef.defaults.name) {
26918
+ const paletteLabel = RED.utils.getPaletteLabel(n.type, nodeDef)
26919
+ const defaultNodeNameRE = new RegExp('^'+paletteLabel+' (\\d+)$')
26920
+ if (!typeIndex.hasOwnProperty(n.type)) {
26921
+ const existingNodes = RED.nodes.filterNodes({type: n.type})
26922
+ let maxNameNumber = 0;
26923
+ existingNodes.forEach(n => {
26924
+ let match = defaultNodeNameRE.exec(n.name)
26925
+ if (match) {
26926
+ let nodeNumber = parseInt(match[1])
26927
+ if (nodeNumber > maxNameNumber) {
26928
+ maxNameNumber = nodeNumber
26929
+ }
26930
+ }
26931
+ })
26932
+ typeIndex[n.type] = maxNameNumber + 1
26933
+ }
26934
+ if ((options.renameBlank && n.name === '') || (options.renameClash && defaultNodeNameRE.test(n.name))) {
26935
+ if (generateHistory) {
26936
+ historyEvents.push({
26937
+ t:'edit',
26938
+ node: n,
26939
+ changes: { name: n.name },
26940
+ dirty: RED.nodes.dirty(),
26941
+ changed: n.changed
26942
+ })
26943
+ }
26944
+ n.name = paletteLabel+" "+typeIndex[n.type]
26945
+ n.dirty = true
26946
+ typeIndex[n.type]++
26947
+ changed = true
26948
+ }
26949
+ }
26950
+ })
26951
+ if (changed) {
26952
+ if (historyEvents.length > 0) {
26953
+ RED.history.push({
26954
+ t: 'multi',
26955
+ events: historyEvents
26956
+ })
26957
+ }
26958
+ RED.nodes.dirty(true)
26959
+ RED.view.redraw()
26960
+ }
26961
+ }
26962
+ }
26963
+
25925
26964
  return {
25926
26965
  init: function() {
25927
26966
  RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); })
@@ -25983,6 +27022,10 @@ RED.view.tools = (function() {
25983
27022
  RED.actions.add("core:wire-series-of-nodes", function() { wireSeriesOfNodes() })
25984
27023
  RED.actions.add("core:wire-node-to-multiple", function() { wireNodeToMultiple() })
25985
27024
 
27025
+ RED.actions.add("core:split-wire-with-link-nodes", function () { splitWiresWithLinkNodes() });
27026
+
27027
+ RED.actions.add("core:generate-node-names", generateNodeNames )
27028
+
25986
27029
  // RED.actions.add("core:add-node", function() { addNode() })
25987
27030
  },
25988
27031
  /**
@@ -25994,7 +27037,8 @@ RED.view.tools = (function() {
25994
27037
  * @param {Number} dx
25995
27038
  * @param {Number} dy
25996
27039
  */
25997
- moveSelection: moveSelection
27040
+ moveSelection: moveSelection,
27041
+ calculateGridSnapOffsets: calculateGridSnapOffsets
25998
27042
  }
25999
27043
 
26000
27044
  })();
@@ -26512,14 +27556,7 @@ RED.palette = (function() {
26512
27556
 
26513
27557
  var d = $('<div>',{class:"red-ui-palette-node"}).attr("data-palette-type",nt).data('category',rootCategory);
26514
27558
 
26515
- var label = nt;///^(.*?)([ -]in|[ -]out)?$/.exec(nt)[1];
26516
- if (typeof def.paletteLabel !== "undefined") {
26517
- try {
26518
- label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||"";
26519
- } catch(err) {
26520
- console.log("Definition error: "+nt+".paletteLabel",err);
26521
- }
26522
- }
27559
+ var label = RED.utils.getPaletteLabel(nt, def);///^(.*?)([ -]in|[ -]out)?$/.exec(nt)[1];
26523
27560
 
26524
27561
  $('<div/>', {
26525
27562
  class: "red-ui-palette-label"+(((!def.align && def.inputs !== 0 && def.outputs === 0) || "right" === def.align) ? " red-ui-palette-label-right" : "")
@@ -27149,6 +28186,7 @@ RED.sidebar.info = (function() {
27149
28186
  });
27150
28187
  return el;
27151
28188
  }
28189
+
27152
28190
  function refresh(node) {
27153
28191
  if (node === undefined) {
27154
28192
  refreshSelection();
@@ -27257,7 +28295,7 @@ RED.sidebar.info = (function() {
27257
28295
  objectType = "group";
27258
28296
  }
27259
28297
  $(propRow.children()[0]).text(RED._("sidebar.info."+objectType))
27260
- RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]);
28298
+ RED.utils.createObjectElement(node.id,{sourceId: node.id}).appendTo(propRow.children()[1]);
27261
28299
 
27262
28300
  if (node.type === "tab" || node.type === "subflow") {
27263
28301
  // If nothing is selected, but we're on a flow or subflow tab.
@@ -27287,8 +28325,8 @@ RED.sidebar.info = (function() {
27287
28325
  if (typeCounts.groups > 0) {
27288
28326
  $('<div>').text(RED._("clipboard.group",{count:typeCounts.groups})).appendTo(counts);
27289
28327
  }
27290
-
27291
-
28328
+ } else if (node.type === 'junction') {
28329
+ propertiesPanelHeaderHelp.hide();
27292
28330
  } else {
27293
28331
  propertiesPanelHeaderHelp.show();
27294
28332
 
@@ -27351,7 +28389,7 @@ RED.sidebar.info = (function() {
27351
28389
 
27352
28390
  }
27353
28391
  } else {
27354
- RED.utils.createObjectElement(val).appendTo(propRow.children()[1]);
28392
+ RED.utils.createObjectElement(val,{sourceId: node.id}).appendTo(propRow.children()[1]);
27355
28393
  }
27356
28394
  }
27357
28395
  }
@@ -27417,6 +28455,7 @@ RED.sidebar.info = (function() {
27417
28455
 
27418
28456
 
27419
28457
  }
28458
+
27420
28459
  function setInfoText(infoText,target) {
27421
28460
  var info = addTargetToExternalLinks($('<div class="red-ui-help"><span class="red-ui-text-bidi-aware" dir=\"'+RED.text.bidi.resolveBaseTextDir(infoText)+'">'+infoText+'</span></div>')).appendTo(target);
27422
28461
  info.find(".red-ui-text-bidi-aware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "<span></span>" );
@@ -27433,6 +28472,7 @@ RED.sidebar.info = (function() {
27433
28472
  $(this).toggleClass('expanded',!isExpanded);
27434
28473
  })
27435
28474
  }
28475
+
27436
28476
  var tips = (function() {
27437
28477
  var enabled = true;
27438
28478
  var startDelay = 1000;
@@ -27842,14 +28882,7 @@ RED.sidebar.info = (function() {
27842
28882
 
27843
28883
  }
27844
28884
  },
27845
- options: [
27846
- {label:RED._("sidebar.info.search.configNodes"), value:"is:config"},
27847
- {label:RED._("sidebar.info.search.unusedConfigNodes"), value:"is:config is:unused"},
27848
- {label:RED._("sidebar.info.search.invalidNodes"), value: "is:invalid"},
27849
- {label:RED._("sidebar.info.search.uknownNodes"), value: "type:unknown"},
27850
- {label:RED._("sidebar.info.search.unusedSubflows"), value:"is:subflow is:unused"},
27851
- {label:RED._("sidebar.info.search.hiddenFlows"), value:"is:hidden"},
27852
- ]
28885
+ options: RED.search.getSearchOptions()
27853
28886
  });
27854
28887
 
27855
28888
  projectInfo = $('<div class="red-ui-treeList-label red-ui-info-outline-project"><span class="red-ui-treeList-icon"><i class="fa fa-archive"></i></span></div>').hide().appendTo(container)
@@ -27861,15 +28894,18 @@ RED.sidebar.info = (function() {
27861
28894
  data:getFlowData()
27862
28895
  })
27863
28896
  treeList.on('treelistselect', function(e,item) {
27864
- var node = RED.nodes.node(item.id) || RED.nodes.group(item.id);
28897
+ var node = RED.nodes.node(item.id) || RED.nodes.group(item.id) || RED.nodes.workspace(item.id) || RED.nodes.subflow(item.id);
27865
28898
  if (node) {
27866
- if (node.type === 'group' || node._def.category !== "config") {
27867
- // RED.view.select({nodes:[node]})
27868
- } else if (node._def.category === "config") {
27869
- RED.sidebar.info.refresh(node);
27870
- } else {
27871
- // RED.view.select({nodes:[]})
27872
- }
28899
+ RED.sidebar.info.refresh(node);
28900
+ // if (node.type === 'group' || node._def.category !== "config") {
28901
+ // // RED.view.select({nodes:[node]})
28902
+ // } else if (node._def.category === "config") {
28903
+ // RED.sidebar.info.refresh(node);
28904
+ // } else {
28905
+ // // RED.view.select({nodes:[]})
28906
+ // }
28907
+ } else {
28908
+ RED.sidebar.info.refresh(null);
27873
28909
  }
27874
28910
  })
27875
28911
  treeList.on('treelistconfirm', function(e,item) {
@@ -28632,7 +29668,7 @@ RED.sidebar.help = (function() {
28632
29668
  var node = selection.nodes[0];
28633
29669
  if (node.type === "subflow" && node.direction) {
28634
29670
  // ignore subflow virtual ports
28635
- } else if (node.type !== 'group'){
29671
+ } else if (node.type !== 'group' && node.type !== 'junction'){
28636
29672
  showNodeTypeHelp(node.type);
28637
29673
  }
28638
29674
  }
@@ -28741,6 +29777,8 @@ RED.sidebar.help = (function() {
28741
29777
  **/
28742
29778
  RED.sidebar.config = (function() {
28743
29779
 
29780
+ let flashingConfigNode;
29781
+ let flashingConfigNodeTimer;
28744
29782
 
28745
29783
  var content = document.createElement("div");
28746
29784
  content.className = "red-ui-sidebar-node-config";
@@ -28871,6 +29909,7 @@ RED.sidebar.config = (function() {
28871
29909
  var entry = $('<li class="red-ui-palette-node_id_'+node.id.replace(/\./g,"-")+'"></li>').appendTo(list);
28872
29910
  var nodeDiv = $('<div class="red-ui-palette-node-config red-ui-palette-node"></div>').appendTo(entry);
28873
29911
  entry.data('node',node.id);
29912
+ nodeDiv.data('node',node.id);
28874
29913
  var label = $('<div class="red-ui-palette-label"></div>').text(label).appendTo(nodeDiv);
28875
29914
  if (node.d) {
28876
29915
  nodeDiv.addClass("red-ui-palette-node-config-disabled");
@@ -29072,10 +30111,36 @@ RED.sidebar.config = (function() {
29072
30111
  refreshConfigNodeList();
29073
30112
  }
29074
30113
  });
29075
- RED.popover.tooltip($('#red-ui-sidebar-config-filter-all'), RED._("sidebar.config.showAllUnusedConfigNodes"));
30114
+ RED.popover.tooltip($('#red-ui-sidebar-config-filter-all'), RED._("sidebar.config.showAllConfigNodes"));
29076
30115
  RED.popover.tooltip($('#red-ui-sidebar-config-filter-unused'), RED._("sidebar.config.showAllUnusedConfigNodes"));
29077
30116
 
29078
30117
  }
30118
+
30119
+ function flashConfigNode(el) {
30120
+ if(flashingConfigNode && flashingConfigNode.length) {
30121
+ //cancel current flashing node before flashing new node
30122
+ clearInterval(flashingConfigNodeTimer);
30123
+ flashingConfigNodeTimer = null;
30124
+ flashingConfigNode.children("div").removeClass('highlighted');
30125
+ flashingConfigNode = null;
30126
+ }
30127
+ if(!el || !el.children("div").length) { return; }
30128
+
30129
+ flashingConfigNodeTimer = setInterval(function(flashEndTime) {
30130
+ if (flashEndTime >= Date.now()) {
30131
+ const highlighted = el.children("div").hasClass("highlighted");
30132
+ el.children("div").toggleClass('highlighted', !highlighted)
30133
+ } else {
30134
+ clearInterval(flashingConfigNodeTimer);
30135
+ flashingConfigNodeTimer = null;
30136
+ flashingConfigNode = null;
30137
+ el.children("div").removeClass('highlighted');
30138
+ }
30139
+ }, 100, Date.now() + 2200);
30140
+ flashingConfigNode = el;
30141
+ el.children("div").addClass('highlighted');
30142
+ }
30143
+
29079
30144
  function show(id) {
29080
30145
  if (typeof id === 'boolean') {
29081
30146
  if (id) {
@@ -29100,19 +30165,7 @@ RED.sidebar.config = (function() {
29100
30165
  } else if (y<0) {
29101
30166
  scrollWindow.animate({scrollTop: '+='+(y-10)},150);
29102
30167
  }
29103
- var flash = 21;
29104
- var flashFunc = function() {
29105
- if ((flash%2)===0) {
29106
- node.removeClass('node_highlighted');
29107
- } else {
29108
- node.addClass('node_highlighted');
29109
- }
29110
- flash--;
29111
- if (flash >= 0) {
29112
- setTimeout(flashFunc,100);
29113
- }
29114
- }
29115
- flashFunc();
30168
+ flashConfigNode(node, id);
29116
30169
  },100);
29117
30170
  }
29118
30171
  RED.sidebar.show("config");
@@ -30797,7 +31850,11 @@ RED.editor = (function() {
30797
31850
  var result = [];
30798
31851
  for (var prop in definition) {
30799
31852
  if (definition.hasOwnProperty(prop)) {
30800
- if (!validateNodeProperty(node, definition, prop, properties[prop])) {
31853
+ var valid = validateNodeProperty(node, definition, prop, properties[prop]);
31854
+ if ((typeof valid) === "string") {
31855
+ result.push(valid);
31856
+ }
31857
+ else if(!valid) {
30801
31858
  result.push(prop);
30802
31859
  }
30803
31860
  }
@@ -30811,7 +31868,7 @@ RED.editor = (function() {
30811
31868
  * @param definition - the node property definitions (either def.defaults or def.creds)
30812
31869
  * @param property - the property name being validated
30813
31870
  * @param value - the property value being validated
30814
- * @returns {boolean} whether the node proprty is valid
31871
+ * @returns {boolean|string} whether the node proprty is valid. `true`: valid `false|String`: invalid
30815
31872
  */
30816
31873
  function validateNodeProperty(node,definition,property,value) {
30817
31874
  var valid = true;
@@ -30823,22 +31880,74 @@ RED.editor = (function() {
30823
31880
  if (/^\$\{[a-zA-Z_][a-zA-Z0-9_]*\}$/.test(value)) {
30824
31881
  return true;
30825
31882
  }
31883
+ var label = null;
31884
+ if (("label" in definition[property]) &&
31885
+ ((typeof definition[property].label) == "string")) {
31886
+ label = definition[property].label;
31887
+ }
30826
31888
  if ("required" in definition[property] && definition[property].required) {
30827
31889
  valid = value !== "";
31890
+ if (!valid && label) {
31891
+ return RED._("validator.errors.missing-required-prop", {
31892
+ prop: label
31893
+ });
31894
+ }
30828
31895
  }
30829
31896
  if (valid && "validate" in definition[property]) {
30830
31897
  try {
30831
- valid = definition[property].validate.call(node,value);
31898
+ var opt = {};
31899
+ if (label) {
31900
+ opt.label = label;
31901
+ }
31902
+ valid = definition[property].validate.call(node,value, opt);
31903
+ // If the validator takes two arguments, it is a 3.x validator that
31904
+ // can return a String to mean 'invalid' and provide a reason
31905
+ if ((definition[property].validate.length === 2) &&
31906
+ ((typeof valid) === "string")) {
31907
+ return valid;
31908
+ } else {
31909
+ // Otherwise, a 2.x returns a truth-like/false-like value that
31910
+ // we should cooerce to a boolean.
31911
+ valid = !!valid
31912
+ }
30832
31913
  } catch(err) {
30833
31914
  console.log("Validation error:",node.type,node.id,"property: "+property,"value:",value,err);
31915
+ return RED._("validator.errors.validation-error", {
31916
+ prop: property,
31917
+ node: node.type,
31918
+ id: node.id,
31919
+ error: err.message
31920
+ });
30834
31921
  }
30835
31922
  }
30836
31923
  if (valid && definition[property].type && RED.nodes.getType(definition[property].type) && !("validate" in definition[property])) {
30837
31924
  if (!value || value == "_ADD_") {
30838
31925
  valid = definition[property].hasOwnProperty("required") && !definition[property].required;
31926
+ if (!valid && label) {
31927
+ return RED._("validator.errors.missing-required-prop", {
31928
+ prop: label
31929
+ });
31930
+ }
30839
31931
  } else {
30840
31932
  var configNode = RED.nodes.node(value);
30841
- valid = (configNode && (configNode.valid == null || configNode.valid));
31933
+ if (configNode) {
31934
+ if ((configNode.valid == null) || configNode.valid) {
31935
+ return true;
31936
+ }
31937
+ if (label) {
31938
+ return RED._("validator.errors.invalid-config", {
31939
+ prop: label
31940
+ });
31941
+ }
31942
+ }
31943
+ else {
31944
+ if (label) {
31945
+ return RED._("validator.errors.missing-config", {
31946
+ prop: label
31947
+ });
31948
+ }
31949
+ }
31950
+ return false;
30842
31951
  }
30843
31952
  }
30844
31953
  return valid;
@@ -30866,10 +31975,26 @@ RED.editor = (function() {
30866
31975
  if (defaults[property].hasOwnProperty("format") && defaults[property].format !== "" && input[0].nodeName === "DIV") {
30867
31976
  value = input.text();
30868
31977
  }
30869
- if (!validateNodeProperty(node, defaults, property,value)) {
31978
+ var valid = validateNodeProperty(node, defaults, property,value);
31979
+ if (((typeof valid) === "string") || !valid) {
30870
31980
  input.addClass("input-error");
31981
+ if ((typeof valid) === "string") {
31982
+ var tooltip = input.data("tooltip");
31983
+ if (tooltip) {
31984
+ tooltip.setContent(valid);
31985
+ }
31986
+ else {
31987
+ tooltip = RED.popover.tooltip(input, valid);
31988
+ input.data("tooltip", tooltip);
31989
+ }
31990
+ }
30871
31991
  } else {
30872
31992
  input.removeClass("input-error");
31993
+ var tooltip = input.data("tooltip");
31994
+ if (tooltip) {
31995
+ input.data("tooltip", null);
31996
+ tooltip.delete();
31997
+ }
30873
31998
  }
30874
31999
  }
30875
32000
  }
@@ -31036,20 +32161,11 @@ RED.editor = (function() {
31036
32161
  * @param prefix - the prefix to use in the input element ids (node-input|node-config-input)
31037
32162
  */
31038
32163
  function attachPropertyChangeHandler(node,definition,property,prefix) {
31039
- var input = $("#"+prefix+"-"+property);
31040
- if (definition !== undefined && "format" in definition[property] && definition[property].format !== "" && input[0].nodeName === "DIV") {
31041
- $("#"+prefix+"-"+property).on('change keyup', function(event) {
31042
- if (!$(this).attr("skipValidation")) {
31043
- validateNodeEditor(node,prefix);
31044
- }
31045
- });
31046
- } else {
31047
- $("#"+prefix+"-"+property).on("change", function(event) {
31048
- if (!$(this).attr("skipValidation")) {
31049
- validateNodeEditor(node,prefix);
31050
- }
31051
- });
31052
- }
32164
+ $("#"+prefix+"-"+property).on("change keyup paste", function(event) {
32165
+ if (!$(this).attr("skipValidation")) {
32166
+ validateNodeEditor(node,prefix);
32167
+ }
32168
+ });
31053
32169
  }
31054
32170
 
31055
32171
  /**
@@ -31483,6 +32599,7 @@ RED.editor = (function() {
31483
32599
  if (buildingEditDialog) { return }
31484
32600
  buildingEditDialog = true;
31485
32601
  var editing_node = node;
32602
+ var removeInfoEditorOnClose = false;
31486
32603
  var skipInfoRefreshOnClose = false;
31487
32604
  var activeEditPanes = [];
31488
32605
 
@@ -31678,6 +32795,14 @@ RED.editor = (function() {
31678
32795
  }
31679
32796
  if (!node._def.defaults || !node._def.defaults.hasOwnProperty('info')) {
31680
32797
  nodeEditPanes.push('editor-tab-description');
32798
+ removeInfoEditorOnClose = true;
32799
+ if(node.infoEditor) {
32800
+ //As 'editor-tab-description' adds `node.infoEditor` store original & set a
32801
+ //flag to NOT remove this property
32802
+ node.infoEditor__orig = node.infoEditor;
32803
+ delete node.infoEditor;
32804
+ removeInfoEditorOnClose = false;
32805
+ }
31681
32806
  }
31682
32807
  nodeEditPanes.push("editor-tab-appearance");
31683
32808
 
@@ -31693,8 +32818,17 @@ RED.editor = (function() {
31693
32818
  if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
31694
32819
  RED.view.state(RED.state.DEFAULT);
31695
32820
  }
31696
- if (editing_node && !skipInfoRefreshOnClose) {
31697
- RED.sidebar.info.refresh(editing_node);
32821
+ if (editing_node) {
32822
+ if (editing_node.infoEditor__orig) {
32823
+ editing_node.infoEditor = editing_node.infoEditor__orig;
32824
+ delete editing_node.infoEditor__orig;
32825
+ }
32826
+ if (removeInfoEditorOnClose) {
32827
+ delete editing_node.infoEditor;
32828
+ }
32829
+ if (!skipInfoRefreshOnClose) {
32830
+ RED.sidebar.info.refresh(editing_node);
32831
+ }
31698
32832
  }
31699
32833
  RED.workspaces.refresh();
31700
32834
 
@@ -32554,6 +33688,48 @@ RED.editor = (function() {
32554
33688
  }
32555
33689
  }
32556
33690
 
33691
+ /** Genrate a consistent but unique ID for saving and restoring the code editors view state */
33692
+ function generateViewStateId(source, thing, suffix) {
33693
+ try {
33694
+ thing = thing || {};
33695
+ const thingOptions = typeof thing.options === "object" ? thing.options : {};
33696
+ let stateId;
33697
+ if (thing.hasOwnProperty("stateId")) {
33698
+ stateId = thing.stateId
33699
+ } else if (thingOptions.hasOwnProperty("stateId")) {
33700
+ stateId = thing.stateId
33701
+ }
33702
+ if (stateId === false) { return false; }
33703
+ if (!stateId) {
33704
+ let id;
33705
+ const selection = RED.view.selection();
33706
+ if (source === "node" && thing.id) {
33707
+ id = thing.id;
33708
+ } else if (selection.nodes && selection.nodes.length) {
33709
+ id = selection.nodes[0].id;
33710
+ } else {
33711
+ return false; //cant obtain Id.
33712
+ }
33713
+ //Use a string builder to build an ID
33714
+ const sb = [id];
33715
+ //get the index of the el - there may be more than one editor.
33716
+ const el = $(thing.element || thingOptions.element);
33717
+ if(el.length) {
33718
+ sb.push(el.closest(".form-row").index());
33719
+ sb.push(el.index());
33720
+ }
33721
+ if (source == "typedInput") {
33722
+ sb.push(el.closest("li").index());//for when embeded in editable list
33723
+ if (!suffix && thing.propertyType) { suffix = thing.propertyType }
33724
+ }
33725
+ stateId = sb.join("/");
33726
+ }
33727
+ if (stateId && suffix) { stateId += "/" + suffix; }
33728
+ return stateId;
33729
+ } catch (error) {
33730
+ return false;
33731
+ }
33732
+ }
32557
33733
  return {
32558
33734
  init: function() {
32559
33735
  if(window.ace) { window.ace.config.set('basePath', 'vendor/ace'); }
@@ -32570,6 +33746,7 @@ RED.editor = (function() {
32570
33746
  });
32571
33747
  RED.editor.codeEditor.init();
32572
33748
  },
33749
+ generateViewStateId: generateViewStateId,
32573
33750
  edit: showEditDialog,
32574
33751
  editConfig: showEditConfigNodeDialog,
32575
33752
  editFlow: showEditFlowDialog,
@@ -33174,7 +34351,6 @@ RED.editor = (function() {
33174
34351
 
33175
34352
  create: function(container) {
33176
34353
  this.editor = buildDescriptionForm(container,node);
33177
- RED.e = this.editor;
33178
34354
  },
33179
34355
  resize: function(size) {
33180
34356
  this.editor.resize();
@@ -33224,11 +34400,9 @@ RED.editor = (function() {
33224
34400
  var nodeInfoEditor = RED.editor.createEditor({
33225
34401
  id: editorId,
33226
34402
  mode: 'ace/mode/markdown',
33227
- value: ""
34403
+ stateId: RED.editor.generateViewStateId("node", node, "nodeinfo"),
34404
+ value: node.info || ""
33228
34405
  });
33229
- if (node.info) {
33230
- nodeInfoEditor.getSession().setValue(node.info, -1);
33231
- }
33232
34406
  node.infoEditor = nodeInfoEditor;
33233
34407
  return nodeInfoEditor;
33234
34408
  }
@@ -33478,7 +34652,7 @@ RED.editor = (function() {
33478
34652
  newValue = "";
33479
34653
  }
33480
34654
  }
33481
- if (node[d] != newValue) {
34655
+ if (!isEqual(node[d], newValue)) {
33482
34656
  if (node._def.defaults[d].type) {
33483
34657
  // Change to a related config node
33484
34658
  var configNode = RED.nodes.node(node[d]);
@@ -33510,6 +34684,23 @@ RED.editor = (function() {
33510
34684
  }
33511
34685
  });
33512
34686
 
34687
+ /**
34688
+ * Compares `newValue` with `originalValue` for equality.
34689
+ * @param {*} originalValue Original value
34690
+ * @param {*} newValue New value
34691
+ * @returns {boolean} true if originalValue equals newValue, otherwise false
34692
+ */
34693
+ function isEqual(originalValue, newValue) {
34694
+ try {
34695
+ if(originalValue == newValue) {
34696
+ return true;
34697
+ }
34698
+ return JSON.stringify(originalValue) === JSON.stringify(newValue);
34699
+ } catch (err) {
34700
+ return false;
34701
+ }
34702
+ }
34703
+
33513
34704
  /**
33514
34705
  * Update the node credentials from the edit form
33515
34706
  * @param node - the node containing the credentials
@@ -33778,6 +34969,7 @@ RED.editor = (function() {
33778
34969
  var definition = {
33779
34970
  show: function(options) {
33780
34971
  var value = options.value;
34972
+ var onCancel = options.cancel;
33781
34973
  var onComplete = options.complete;
33782
34974
  var type = "_buffer"
33783
34975
  if ($("script[data-template-name='"+type+"']").length === 0) {
@@ -33791,12 +34983,14 @@ RED.editor = (function() {
33791
34983
 
33792
34984
  var trayOptions = {
33793
34985
  title: options.title,
34986
+ focusElement: options.focusElement,
33794
34987
  width: "inherit",
33795
34988
  buttons: [
33796
34989
  {
33797
34990
  id: "node-dialog-cancel",
33798
34991
  text: RED._("common.label.cancel"),
33799
34992
  click: function() {
34993
+ if (onCancel) { onCancel(); }
33800
34994
  RED.tray.close();
33801
34995
  }
33802
34996
  },
@@ -33805,7 +34999,8 @@ RED.editor = (function() {
33805
34999
  text: RED._("common.label.done"),
33806
35000
  class: "primary",
33807
35001
  click: function() {
33808
- onComplete(JSON.stringify(bufferBinValue));
35002
+ bufferStringEditor.saveView();
35003
+ if (onComplete) { onComplete(JSON.stringify(bufferBinValue),null,bufferStringEditor); }
33809
35004
  RED.tray.close();
33810
35005
  }
33811
35006
  }
@@ -33817,19 +35012,20 @@ RED.editor = (function() {
33817
35012
  }
33818
35013
  },
33819
35014
  open: function(tray) {
33820
- var trayBody = tray.find('.red-ui-tray-body');
33821
35015
  var dialogForm = RED.editor.buildEditForm(tray.find('.red-ui-tray-body'),'dialog-form',type,'editor');
33822
-
33823
35016
  bufferStringEditor = RED.editor.createEditor({
33824
35017
  id: 'red-ui-editor-type-buffer-str',
33825
- value: "",
35018
+ value: value||"",
35019
+ stateId: RED.editor.generateViewStateId("buffer", options, ""),
35020
+ focus: true,
33826
35021
  mode:"ace/mode/text"
33827
35022
  });
33828
- bufferStringEditor.getSession().setValue(value||"",-1);
33829
35023
 
33830
35024
  bufferBinEditor = RED.editor.createEditor({
33831
35025
  id: 'red-ui-editor-type-buffer-bin',
33832
35026
  value: "",
35027
+ stateId: false,
35028
+ focus: false,
33833
35029
  mode:"ace/mode/text",
33834
35030
  readOnly: true
33835
35031
  });
@@ -34921,6 +36117,7 @@ RED.editor = (function() {
34921
36117
  show: function(options) {
34922
36118
  var expressionTestCacheId = options.parent||"_";
34923
36119
  var value = options.value;
36120
+ var onCancel = options.cancel;
34924
36121
  var onComplete = options.complete;
34925
36122
  var type = "_expression"
34926
36123
  if ($("script[data-template-name='"+type+"']").length === 0) {
@@ -34934,12 +36131,14 @@ RED.editor = (function() {
34934
36131
 
34935
36132
  var trayOptions = {
34936
36133
  title: options.title,
36134
+ focusElement: options.focusElement,
34937
36135
  width: "inherit",
34938
36136
  buttons: [
34939
36137
  {
34940
36138
  id: "node-dialog-cancel",
34941
36139
  text: RED._("common.label.cancel"),
34942
36140
  click: function() {
36141
+ if(onCancel) { onCancel(); }
34943
36142
  RED.tray.close();
34944
36143
  }
34945
36144
  },
@@ -34949,7 +36148,8 @@ RED.editor = (function() {
34949
36148
  class: "primary",
34950
36149
  click: function() {
34951
36150
  $("#red-ui-editor-type-expression-help").text("");
34952
- onComplete(expressionEditor.getValue());
36151
+ expressionEditor.saveView();
36152
+ if (onComplete) { onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition(),expressionEditor); }
34953
36153
  RED.tray.close();
34954
36154
  }
34955
36155
  }
@@ -34981,6 +36181,8 @@ RED.editor = (function() {
34981
36181
  id: 'red-ui-editor-type-expression',
34982
36182
  value: "",
34983
36183
  mode:"ace/mode/jsonata",
36184
+ stateId: options.stateId,
36185
+ focus: true,
34984
36186
  options: {
34985
36187
  enableBasicAutocompletion:true,
34986
36188
  enableSnippets:true,
@@ -35104,6 +36306,8 @@ RED.editor = (function() {
35104
36306
  testDataEditor = RED.editor.createEditor({
35105
36307
  id: 'red-ui-editor-type-expression-test-data',
35106
36308
  value: expressionTestCache[expressionTestCacheId] || '{\n "payload": "hello world"\n}',
36309
+ stateId: false,
36310
+ focus: false,
35107
36311
  mode:"ace/mode/json",
35108
36312
  lineNumbers: false
35109
36313
  });
@@ -35173,6 +36377,8 @@ RED.editor = (function() {
35173
36377
  testResultEditor = RED.editor.createEditor({
35174
36378
  id: 'red-ui-editor-type-expression-test-result',
35175
36379
  value: "",
36380
+ stateId: false,
36381
+ focus: false,
35176
36382
  mode:"ace/mode/json",
35177
36383
  lineNumbers: false,
35178
36384
  readOnly: true
@@ -35347,6 +36553,7 @@ RED.editor = (function() {
35347
36553
  var definition = {
35348
36554
  show: function(options) {
35349
36555
  var value = options.value;
36556
+ var onCancel = options.cancel;
35350
36557
  var onComplete = options.complete;
35351
36558
  var type = "_js"
35352
36559
  if ($("script[data-template-name='"+type+"']").length === 0) {
@@ -35354,16 +36561,16 @@ RED.editor = (function() {
35354
36561
  }
35355
36562
  RED.view.state(RED.state.EDITING);
35356
36563
  var expressionEditor;
35357
- var changeTimer;
35358
-
35359
36564
  var trayOptions = {
35360
36565
  title: options.title,
36566
+ focusElement: options.focusElement,
35361
36567
  width: options.width||"inherit",
35362
36568
  buttons: [
35363
36569
  {
35364
36570
  id: "node-dialog-cancel",
35365
36571
  text: RED._("common.label.cancel"),
35366
36572
  click: function() {
36573
+ if (onCancel) { onCancel(); }
35367
36574
  RED.tray.close();
35368
36575
  }
35369
36576
  },
@@ -35372,7 +36579,8 @@ RED.editor = (function() {
35372
36579
  text: RED._("common.label.done"),
35373
36580
  class: "primary",
35374
36581
  click: function() {
35375
- onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition());
36582
+ expressionEditor.saveView();
36583
+ if (onComplete) { onComplete(expressionEditor.getValue(), expressionEditor.getCursorPosition(), expressionEditor); }
35376
36584
  RED.tray.close();
35377
36585
  }
35378
36586
  }
@@ -35388,11 +36596,12 @@ RED.editor = (function() {
35388
36596
  expressionEditor.resize();
35389
36597
  },
35390
36598
  open: function(tray) {
35391
- var trayBody = tray.find('.red-ui-tray-body');
35392
36599
  var dialogForm = RED.editor.buildEditForm(tray.find('.red-ui-tray-body'),'dialog-form',type,'editor');
35393
36600
  expressionEditor = RED.editor.createEditor({
35394
36601
  id: 'node-input-js',
35395
36602
  mode: options.mode || 'ace/mode/javascript',
36603
+ stateId: options.stateId,
36604
+ focus: true,
35396
36605
  value: value,
35397
36606
  globals: {
35398
36607
  msg:true,
@@ -35410,19 +36619,16 @@ RED.editor = (function() {
35410
36619
  },
35411
36620
  extraLibs: options.extraLibs
35412
36621
  });
35413
- if (options.cursor) {
36622
+ if (options.cursor && !expressionEditor._initState) {
35414
36623
  expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
35415
36624
  }
35416
36625
  dialogForm.i18n();
35417
- setTimeout(function() {
35418
- expressionEditor.focus();
35419
- },300);
35420
36626
  },
35421
36627
  close: function() {
35422
- expressionEditor.destroy();
35423
36628
  if (options.onclose) {
35424
36629
  options.onclose();
35425
36630
  }
36631
+ expressionEditor.destroy();
35426
36632
  },
35427
36633
  show: function() {}
35428
36634
  }
@@ -35455,7 +36661,9 @@ RED.editor = (function() {
35455
36661
  '<ul id="red-ui-editor-type-json-tabs"></ul>'+
35456
36662
  '<div id="red-ui-editor-type-json-tab-raw" class="red-ui-editor-type-json-tab-content hide">'+
35457
36663
  '<div class="form-row" style="margin-bottom: 3px; text-align: right;">'+
35458
- '<button id="node-input-json-reformat" class="red-ui-button red-ui-button-small"><span data-i18n="jsonEditor.format"></span></button>'+
36664
+ '<span class="button-group">'+
36665
+ '<button id="node-input-json-reformat" class="red-ui-button red-ui-button-small"><span data-i18n="jsonEditor.format"></span></button>'+
36666
+ '<span class="button-group">'+
35459
36667
  '</div>'+
35460
36668
  '<div class="form-row node-text-editor-row">'+
35461
36669
  '<div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-json"></div>'+
@@ -35468,7 +36676,7 @@ RED.editor = (function() {
35468
36676
 
35469
36677
  var activeTab;
35470
36678
 
35471
- function insertNewItem(parent,index,copyIndex) {
36679
+ function insertNewItem(parent,index,copyIndex,readOnly) {
35472
36680
  var newValue = "";
35473
36681
 
35474
36682
  if (parent.children.length > 0) {
@@ -35494,26 +36702,26 @@ RED.editor = (function() {
35494
36702
  newKey = keyRoot+"-"+(keySuffix++);
35495
36703
  }
35496
36704
  }
35497
- var newItem = handleItem(newKey,newValue,parent.depth+1,parent);
36705
+ var newItem = handleItem(newKey,newValue,parent.depth+1,parent,readOnly);
35498
36706
  parent.treeList.insertChildAt(newItem, index, true);
35499
36707
  parent.treeList.expand();
35500
36708
  }
35501
- function showObjectMenu(button,item) {
36709
+ function showObjectMenu(button,item,readOnly) {
35502
36710
  var elementPos = button.offset();
35503
36711
  var options = [];
35504
36712
  if (item.parent) {
35505
36713
  options.push({id:"red-ui-editor-type-json-menu-insert-above", icon:"fa fa-toggle-up", label:RED._('jsonEditor.insertAbove'),onselect:function(){
35506
36714
  var index = item.parent.children.indexOf(item);
35507
- insertNewItem(item.parent,index,index);
36715
+ insertNewItem(item.parent,index,index,readOnly);
35508
36716
  }});
35509
36717
  options.push({id:"red-ui-editor-type-json-menu-insert-below", icon:"fa fa-toggle-down", label:RED._('jsonEditor.insertBelow'),onselect:function(){
35510
36718
  var index = item.parent.children.indexOf(item)+1;
35511
- insertNewItem(item.parent,index,index-1);
36719
+ insertNewItem(item.parent,index,index-1,readOnly);
35512
36720
  }});
35513
36721
  }
35514
36722
  if (item.type === 'array' || item.type === 'object') {
35515
36723
  options.push({id:"red-ui-editor-type-json-menu-add-child", icon:"fa fa-plus", label:RED._('jsonEditor.addItem'),onselect:function(){
35516
- insertNewItem(item,item.children.length,item.children.length-1);
36724
+ insertNewItem(item,item.children.length,item.children.length-1,readOnly);
35517
36725
  }});
35518
36726
  }
35519
36727
  if (item.parent) {
@@ -35555,7 +36763,7 @@ RED.editor = (function() {
35555
36763
  newKey = keyRoot+"-"+(keySuffix++);
35556
36764
  }
35557
36765
  }
35558
- var newItem = handleItem(newKey,convertToObject(item),item.parent.depth+1,item.parent);
36766
+ var newItem = handleItem(newKey,convertToObject(item),item.parent.depth+1,item.parent,readOnly);
35559
36767
  var index = item.parent.children.indexOf(item)+1;
35560
36768
 
35561
36769
  item.parent.treeList.insertChildAt(newItem, index, true);
@@ -35605,24 +36813,24 @@ RED.editor = (function() {
35605
36813
  menuOptionMenu.show();
35606
36814
  }
35607
36815
 
35608
- function parseObject(obj,depth,parent) {
36816
+ function parseObject(obj,depth,parent,readOnly) {
35609
36817
  var result = [];
35610
36818
  for (var prop in obj) {
35611
36819
  if (obj.hasOwnProperty(prop)) {
35612
- result.push(handleItem(prop,obj[prop],depth,parent));
36820
+ result.push(handleItem(prop,obj[prop],depth,parent,readOnly));
35613
36821
  }
35614
36822
  }
35615
36823
  return result;
35616
36824
  }
35617
- function parseArray(obj,depth,parent) {
36825
+ function parseArray(obj,depth,parent,readOnly) {
35618
36826
  var result = [];
35619
36827
  var l = obj.length;
35620
36828
  for (var i=0;i<l;i++) {
35621
- result.push(handleItem(i,obj[i],depth,parent));
36829
+ result.push(handleItem(i,obj[i],depth,parent,readOnly));
35622
36830
  }
35623
36831
  return result;
35624
36832
  }
35625
- function handleItem(key,val,depth,parent) {
36833
+ function handleItem(key,val,depth,parent,readOnly) {
35626
36834
  var item = {depth:depth, type: typeof val};
35627
36835
  var container = $('<span class="red-ui-editor-type-json-editor-label">');
35628
36836
  if (key != null) {
@@ -35638,11 +36846,14 @@ RED.editor = (function() {
35638
36846
  if (parent && parent.type === "array") {
35639
36847
  keyLabel.addClass("red-ui-editor-type-json-editor-label-array-key")
35640
36848
  }
35641
-
36849
+ if(readOnly) {
36850
+ keyLabel.addClass("readonly")
36851
+ }
35642
36852
  keyLabel.on("click", function(evt) {
35643
36853
  if (item.parent.type === 'array') {
35644
36854
  return;
35645
36855
  }
36856
+ if (readOnly) { return; }
35646
36857
  evt.preventDefault();
35647
36858
  evt.stopPropagation();
35648
36859
  var w = Math.max(150,keyLabel.width());
@@ -35687,10 +36898,10 @@ RED.editor = (function() {
35687
36898
  item.expanded = depth < 2;
35688
36899
  item.type = "array";
35689
36900
  item.deferBuild = depth >= 2;
35690
- item.children = parseArray(val,depth+1,item);
36901
+ item.children = parseArray(val,depth+1,item,readOnly);
35691
36902
  } else if (val !== null && item.type === "object") {
35692
36903
  item.expanded = depth < 2;
35693
- item.children = parseObject(val,depth+1,item);
36904
+ item.children = parseObject(val,depth+1,item,readOnly);
35694
36905
  item.deferBuild = depth >= 2;
35695
36906
  } else {
35696
36907
  item.value = val;
@@ -35721,7 +36932,11 @@ RED.editor = (function() {
35721
36932
  //
35722
36933
  var orphanedChildren;
35723
36934
  var valueLabel = $('<span class="red-ui-editor-type-json-editor-label-value">').addClass(valClass).text(valValue).appendTo(container);
36935
+ if (readOnly) {
36936
+ valueLabel.addClass("readonly")
36937
+ }
35724
36938
  valueLabel.on("click", function(evt) {
36939
+ if (readOnly) { return; }
35725
36940
  evt.preventDefault();
35726
36941
  evt.stopPropagation();
35727
36942
  if (valType === 'str') {
@@ -35736,8 +36951,8 @@ RED.editor = (function() {
35736
36951
  types:[
35737
36952
  'str','num','bool',
35738
36953
  {value:"null",label:RED._("common.type.null"),hasValue:false},
35739
- {value:"array",label:RED._("common.type.array"),hasValue:false,icon:"red/images/typedInput/json.png"},
35740
- {value:"object",label:RED._("common.type.object"),hasValue:false,icon:"red/images/typedInput/json.png"}
36954
+ {value:"array",label:RED._("common.type.array"),hasValue:false,icon:"red/images/typedInput/json.svg"},
36955
+ {value:"object",label:RED._("common.type.object"),hasValue:false,icon:"red/images/typedInput/json.svg"}
35741
36956
  ],
35742
36957
  default: valType
35743
36958
  });
@@ -35829,17 +37044,19 @@ RED.editor = (function() {
35829
37044
  valueLabel.hide();
35830
37045
  })
35831
37046
  item.gutter = $('<span class="red-ui-editor-type-json-editor-item-gutter"></span>');
35832
-
35833
- if (parent) {//red-ui-editor-type-json-editor-item-handle
35834
- $('<span class="red-ui-editor-type-json-editor-item-handle"><i class="fa fa-bars"></span>').appendTo(item.gutter);
35835
- } else {
35836
- $('<span></span>').appendTo(item.gutter);
37047
+ if(!readOnly) {
37048
+ if (parent) {
37049
+ $('<span class="red-ui-editor-type-json-editor-item-handle"><i class="fa fa-bars"></span>').appendTo(item.gutter);
37050
+ } else {
37051
+ $('<span></span>').appendTo(item.gutter);
37052
+ }
37053
+ $('<button type="button" class="editor-button editor-button-small"><i class="fa fa-caret-down"></button>').appendTo(item.gutter).on("click", function(evt) {
37054
+ evt.preventDefault();
37055
+ evt.stopPropagation();
37056
+ showObjectMenu($(this), item, readOnly);
37057
+ });
35837
37058
  }
35838
- $('<button type="button" class="editor-button editor-button-small"><i class="fa fa-caret-down"></button>').appendTo(item.gutter).on("click", function(evt) {
35839
- evt.preventDefault();
35840
- evt.stopPropagation();
35841
- showObjectMenu($(this), item);
35842
- });
37059
+
35843
37060
  item.element = container;
35844
37061
  return item;
35845
37062
  }
@@ -35868,6 +37085,7 @@ RED.editor = (function() {
35868
37085
  var definition = {
35869
37086
  show: function(options) {
35870
37087
  var value = options.value;
37088
+ var onCancel = options.cancel;
35871
37089
  var onComplete = options.complete;
35872
37090
  var type = "_json"
35873
37091
  if ($("script[data-template-name='"+type+"']").length === 0) {
@@ -35889,15 +37107,16 @@ RED.editor = (function() {
35889
37107
  }
35890
37108
  }
35891
37109
  var rootNode;
35892
-
35893
37110
  var trayOptions = {
35894
37111
  title: options.title,
37112
+ focusElement: options.focusElement,
35895
37113
  width: options.width||700,
35896
37114
  buttons: [
35897
37115
  {
35898
37116
  id: "node-dialog-cancel",
35899
37117
  text: RED._("common.label.cancel"),
35900
37118
  click: function() {
37119
+ if (onCancel) { onCancel(); }
35901
37120
  RED.tray.close();
35902
37121
  }
35903
37122
  },
@@ -35919,7 +37138,8 @@ RED.editor = (function() {
35919
37138
  } else if (activeTab === "json-raw") {
35920
37139
  result = expressionEditor.getValue();
35921
37140
  }
35922
- if (onComplete) { onComplete(result) }
37141
+ expressionEditor.saveView();
37142
+ if (onComplete) { onComplete(result,null,expressionEditor) }
35923
37143
  RED.tray.close();
35924
37144
  }
35925
37145
  }
@@ -35932,7 +37152,25 @@ RED.editor = (function() {
35932
37152
  open: function(tray) {
35933
37153
  var trayBody = tray.find('.red-ui-tray-body');
35934
37154
  var dialogForm = RED.editor.buildEditForm(tray.find('.red-ui-tray-body'),'dialog-form',type,'editor');
35935
-
37155
+ var toolbarButtons = options.toolbarButtons || [];
37156
+ if (toolbarButtons.length) {
37157
+ toolbarButtons.forEach(function (button) {
37158
+ var element = $('<button type="button" class="red-ui-button red-ui-button-small"> </button>')
37159
+ .insertBefore("#node-input-json-reformat")
37160
+ .on("click", function (evt) {
37161
+ evt.preventDefault();
37162
+ if (button.click !== undefined) {
37163
+ button.click.call(element, evt);
37164
+ }
37165
+ });
37166
+ if (button.id) { element.attr("id", button.id); }
37167
+ if (button.title) { element.attr("title", button.title); }
37168
+ if (button.icon) { element.append($("<i></i>").attr("class", button.icon)); }
37169
+ if (button.label || button.text) {
37170
+ element.append($("<span></span>").text(" " + (button.label || button.text)));
37171
+ }
37172
+ });
37173
+ }
35936
37174
  var container = $("#red-ui-editor-type-json-tab-ui-container").css({"height":"100%"});
35937
37175
  var filterDepth = Infinity;
35938
37176
  var list = $('<div class="red-ui-debug-msg-payload red-ui-editor-type-json-editor">').appendTo(container).treeList({
@@ -35962,13 +37200,15 @@ RED.editor = (function() {
35962
37200
  })
35963
37201
  });
35964
37202
 
35965
-
35966
37203
  expressionEditor = RED.editor.createEditor({
35967
37204
  id: 'node-input-json',
35968
- value: "",
35969
- mode:"ace/mode/json"
37205
+ value: value||"",
37206
+ mode:"ace/mode/json",
37207
+ readOnly: !!options.readOnly,
37208
+ stateId: options.stateId,
37209
+ focus: true
35970
37210
  });
35971
- expressionEditor.getSession().setValue(value||"",-1);
37211
+
35972
37212
  if (options.requireValid) {
35973
37213
  expressionEditor.getSession().on('change', function() {
35974
37214
  clearTimeout(changeTimer);
@@ -36005,7 +37245,7 @@ RED.editor = (function() {
36005
37245
  var raw = expressionEditor.getValue().trim() ||"{}";
36006
37246
  try {
36007
37247
  var parsed = JSON.parse(raw);
36008
- rootNode = handleItem(null,parsed,0,null);
37248
+ rootNode = handleItem(null,parsed,0,null,options.readOnly);
36009
37249
  rootNode.class = "red-ui-editor-type-json-root-node"
36010
37250
  list.treeList('data',[rootNode]);
36011
37251
  } catch(err) {
@@ -36023,17 +37263,15 @@ RED.editor = (function() {
36023
37263
 
36024
37264
  tabs.addTab({
36025
37265
  id: 'json-raw',
36026
- label: RED._('jsonEditor.rawMode'),
37266
+ label: options.readOnly ? RED._('jsonEditor.rawMode-readonly') : RED._('jsonEditor.rawMode'),
36027
37267
  content: $("#red-ui-editor-type-json-tab-raw")
36028
37268
  });
36029
37269
  tabs.addTab({
36030
37270
  id: 'json-ui',
36031
- label: RED._('jsonEditor.uiMode'),
37271
+ label: options.readOnly ? RED._('jsonEditor.uiMode-readonly') : RED._('jsonEditor.uiMode'),
36032
37272
  content: $("#red-ui-editor-type-json-tab-ui")
36033
37273
  });
36034
37274
  finishedBuild = true;
36035
-
36036
-
36037
37275
  },
36038
37276
  close: function() {
36039
37277
  if (options.onclose) {
@@ -36104,24 +37342,26 @@ RED.editor = (function() {
36104
37342
  var definition = {
36105
37343
  show: function(options) {
36106
37344
  var value = options.value;
37345
+ var onCancel = options.cancel;
36107
37346
  var onComplete = options.complete;
36108
37347
  var type = "_markdown"
36109
37348
  if ($("script[data-template-name='"+type+"']").length === 0) {
36110
37349
  $(template).appendTo("#red-ui-editor-node-configs");
36111
37350
  }
36112
37351
 
36113
-
36114
37352
  RED.view.state(RED.state.EDITING);
36115
37353
  var expressionEditor;
36116
37354
 
36117
37355
  var trayOptions = {
36118
37356
  title: options.title,
37357
+ focusElement: options.focusElement,
36119
37358
  width: options.width||Infinity,
36120
37359
  buttons: [
36121
37360
  {
36122
37361
  id: "node-dialog-cancel",
36123
37362
  text: RED._("common.label.cancel"),
36124
37363
  click: function() {
37364
+ if (onCancel) { onCancel(); }
36125
37365
  RED.tray.close();
36126
37366
  }
36127
37367
  },
@@ -36130,7 +37370,8 @@ RED.editor = (function() {
36130
37370
  text: RED._("common.label.done"),
36131
37371
  class: "primary",
36132
37372
  click: function() {
36133
- onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition());
37373
+ expressionEditor.saveView();
37374
+ if (onComplete) { onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition(), expressionEditor); }
36134
37375
  RED.tray.close();
36135
37376
  }
36136
37377
  }
@@ -36149,6 +37390,8 @@ RED.editor = (function() {
36149
37390
  expressionEditor = RED.editor.createEditor({
36150
37391
  id: 'red-ui-editor-type-markdown',
36151
37392
  value: value,
37393
+ stateId: options.stateId,
37394
+ focus: true,
36152
37395
  mode:"ace/mode/markdown",
36153
37396
  expandable: false
36154
37397
  });
@@ -36193,17 +37436,17 @@ RED.editor = (function() {
36193
37436
  });
36194
37437
  RED.popover.tooltip($("#node-btn-markdown-preview"), RED._("markdownEditor.toggle-preview"));
36195
37438
 
36196
- if (options.cursor) {
37439
+ if (options.cursor && !expressionEditor._initState) {
36197
37440
  expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
36198
37441
  }
36199
37442
 
36200
37443
  dialogForm.i18n();
36201
37444
  },
36202
37445
  close: function() {
36203
- expressionEditor.destroy();
36204
37446
  if (options.onclose) {
36205
37447
  options.onclose();
36206
37448
  }
37449
+ expressionEditor.destroy();
36207
37450
  },
36208
37451
  show: function() {}
36209
37452
  }
@@ -36218,7 +37461,7 @@ RED.editor = (function() {
36218
37461
  'b': { before:"**", after: "**", tooltip: RED._("markdownEditor.bold")},
36219
37462
  'i': { before:"_", after: "_", tooltip: RED._("markdownEditor.italic")},
36220
37463
  'code': { before:"`", after: "`", tooltip: RED._("markdownEditor.code")},
36221
- 'ol': { before:" * ", newline: true, tooltip: RED._("markdownEditor.ordered-list")},
37464
+ 'ol': { before:" 1. ", newline: true, tooltip: RED._("markdownEditor.ordered-list")},
36222
37465
  'ul': { before:" - ", newline: true, tooltip: RED._("markdownEditor.unordered-list")},
36223
37466
  'bq': { before:"> ", newline: true, tooltip: RED._("markdownEditor.quote")},
36224
37467
  'link': { before:"[", after: "]()", tooltip: RED._("markdownEditor.link")},
@@ -36287,6 +37530,7 @@ RED.editor = (function() {
36287
37530
  var definition = {
36288
37531
  show: function(options) {
36289
37532
  var value = options.value;
37533
+ var onCancel = options.cancel;
36290
37534
  var onComplete = options.complete;
36291
37535
  var type = "_text"
36292
37536
  if ($("script[data-template-name='"+type+"']").length === 0) {
@@ -36294,16 +37538,16 @@ RED.editor = (function() {
36294
37538
  }
36295
37539
  RED.view.state(RED.state.EDITING);
36296
37540
  var expressionEditor;
36297
- var changeTimer;
36298
-
36299
37541
  var trayOptions = {
36300
37542
  title: options.title,
37543
+ focusElement: options.focusElement,
36301
37544
  width: options.width||"inherit",
36302
37545
  buttons: [
36303
37546
  {
36304
37547
  id: "node-dialog-cancel",
36305
37548
  text: RED._("common.label.cancel"),
36306
37549
  click: function() {
37550
+ if(onCancel) { onCancel(); }
36307
37551
  RED.tray.close();
36308
37552
  }
36309
37553
  },
@@ -36312,7 +37556,8 @@ RED.editor = (function() {
36312
37556
  text: RED._("common.label.done"),
36313
37557
  class: "primary",
36314
37558
  click: function() {
36315
- onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition());
37559
+ expressionEditor.saveView();
37560
+ if (onComplete) { onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition(),expressionEditor);}
36316
37561
  RED.tray.close();
36317
37562
  }
36318
37563
  }
@@ -36321,31 +37566,27 @@ RED.editor = (function() {
36321
37566
  var rows = $("#dialog-form>div:not(.node-text-editor-row)");
36322
37567
  var editorRow = $("#dialog-form>div.node-text-editor-row");
36323
37568
  var height = $("#dialog-form").height();
36324
- // for (var i=0;i<rows.size();i++) {
36325
- // height -= $(rows[i]).outerHeight(true);
36326
- // }
36327
- // height -= (parseInt($("#dialog-form").css("marginTop"))+parseInt($("#dialog-form").css("marginBottom")));
36328
37569
  $(".node-text-editor").css("height",height+"px");
36329
37570
  expressionEditor.resize();
36330
37571
  },
36331
37572
  open: function(tray) {
36332
- var trayBody = tray.find('.red-ui-tray-body');
36333
37573
  var dialogForm = RED.editor.buildEditForm(tray.find('.red-ui-tray-body'),'dialog-form',type,'editor');
36334
37574
  expressionEditor = RED.editor.createEditor({
36335
37575
  id: 'node-input-text',
36336
- value: "",
36337
- mode:"ace/mode/"+(options.mode||"text")
37576
+ value: value||"",
37577
+ stateId: options.stateId,
37578
+ mode:"ace/mode/"+(options.mode||"text"),
37579
+ focus: true,
36338
37580
  });
36339
- expressionEditor.getSession().setValue(value||"",-1);
36340
- if (options.cursor) {
37581
+ if (options.cursor && !expressionEditor._initState) {
36341
37582
  expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
36342
37583
  }
36343
37584
  },
36344
37585
  close: function() {
36345
- expressionEditor.destroy();
36346
37586
  if (options.onclose) {
36347
37587
  options.onclose();
36348
37588
  }
37589
+ expressionEditor.destroy();
36349
37590
  },
36350
37591
  show: function() {}
36351
37592
  }
@@ -36436,6 +37677,9 @@ RED.editor.codeEditor.ace = (function() {
36436
37677
  }
36437
37678
  },100);
36438
37679
  }
37680
+ if (!options.stateId && options.stateId !== false) {
37681
+ options.stateId = RED.editor.generateViewStateId("ace", options, (options.mode || options.title).split("/").pop());
37682
+ }
36439
37683
  if (options.mode === 'ace/mode/markdown') {
36440
37684
  $(el).addClass("red-ui-editor-text-container-toolbar");
36441
37685
  editor.toolbar = RED.editor.customEditTypes['_markdown'].buildToolbar(toolbarRow,editor);
@@ -36448,11 +37692,15 @@ RED.editor.codeEditor.ace = (function() {
36448
37692
  RED.editor.editMarkdown({
36449
37693
  value: value,
36450
37694
  width: "Infinity",
36451
- cursor: editor.getCursorPosition(),
37695
+ stateId: options.stateId,
37696
+ focus: true,
37697
+ cancel: function () {
37698
+ editor.focus();
37699
+ },
36452
37700
  complete: function(v,cursor) {
36453
37701
  editor.setValue(v, -1);
36454
- editor.gotoLine(cursor.row+1,cursor.column,false);
36455
37702
  setTimeout(function() {
37703
+ editor.restoreView();
36456
37704
  editor.focus();
36457
37705
  },300);
36458
37706
  }
@@ -36473,11 +37721,56 @@ RED.editor.codeEditor.ace = (function() {
36473
37721
  editor._destroy = editor.destroy;
36474
37722
  editor.destroy = function() {
36475
37723
  try {
37724
+ editor.saveView();
37725
+ editor._initState = null;
36476
37726
  this._destroy();
36477
37727
  } catch (e) { }
36478
37728
  $(el).remove();
36479
37729
  $(toolbarRow).remove();
36480
37730
  }
37731
+ editor.on("blur", function () {
37732
+ editor.focusMemory = false;
37733
+ editor.saveView();
37734
+ })
37735
+ editor.on("focus", function () {
37736
+ if (editor._initState) {
37737
+ editor.restoreView(editor._initState);
37738
+ editor._initState = null;
37739
+ }
37740
+ })
37741
+ editor.getView = function () {
37742
+ var session = editor.getSession();
37743
+ return {
37744
+ selection: session.selection.toJSON(),
37745
+ scrollTop: session.getScrollTop(),
37746
+ scrollLeft: session.getScrollLeft(),
37747
+ options: session.getOptions()
37748
+ }
37749
+ }
37750
+ editor.saveView = function () {
37751
+ if (!options.stateId) { return; } //only possible if created with a unique stateId
37752
+ window._editorStateAce = window._editorStateAce || {};
37753
+ var state = editor.getView();
37754
+ window._editorStateAce[options.stateId] = state;
37755
+ return state;
37756
+ }
37757
+ editor.restoreView = function (state) {
37758
+ if (!options.stateId) { return; } //only possible if created with a unique stateId
37759
+ window._editorStateAce = window._editorStateAce || {};
37760
+ var _state = state || window._editorStateAce[options.stateId];
37761
+ if (!_state) { return; } //no view state available
37762
+ try {
37763
+ var session = editor.getSession();
37764
+ session.setOptions(_state.options);
37765
+ session.selection.fromJSON(_state.selection);
37766
+ session.setScrollTop(_state.scrollTop);
37767
+ session.setScrollLeft(_state.scrollLeft);
37768
+ editor._initState = _state;
37769
+ } catch (error) {
37770
+ delete window._editorStateMonaco[options.stateId];
37771
+ }
37772
+ };
37773
+ editor.restoreView();
36481
37774
  editor.type = type;
36482
37775
  return editor;
36483
37776
  }
@@ -36679,7 +37972,7 @@ RED.editor.codeEditor.monaco = (function() {
36679
37972
 
36680
37973
  options = options || {};
36681
37974
  window.MonacoEnvironment = window.MonacoEnvironment || {};
36682
- window.MonacoEnvironment.getWorkerUrl = function (moduleId, label) {
37975
+ window.MonacoEnvironment.getWorkerUrl = window.MonacoEnvironment.getWorkerUrl || function (moduleId, label) {
36683
37976
  if (label === 'json') { return './vendor/monaco/dist/json.worker.js'; }
36684
37977
  if (label === 'css' || label === 'scss') { return './vendor/monaco/dist/css.worker.js'; }
36685
37978
  if (label === 'html' || label === 'handlebars') { return './vendor/monaco/dist/html.worker.js'; }
@@ -37255,13 +38548,25 @@ RED.editor.codeEditor.monaco = (function() {
37255
38548
  mode = "html";
37256
38549
  break;
37257
38550
  case "appcache":
38551
+ case "sh":
38552
+ case "bash":
37258
38553
  mode = "shell";
37259
38554
  break;
38555
+ case "batchfile":
38556
+ mode = "bat";
38557
+ break;
38558
+ case "protobuf":
38559
+ mode = "proto";
38560
+ break;
37260
38561
  //TODO: add other compatability types.
37261
38562
  }
37262
38563
  return mode;
37263
38564
  }
37264
38565
 
38566
+
38567
+ if(!options.stateId && options.stateId !== false) {
38568
+ options.stateId = RED.editor.generateViewStateId("monaco", options, (options.mode || options.title).split("/").pop());
38569
+ }
37265
38570
  var el = options.element || $("#"+options.id)[0];
37266
38571
  var toolbarRow = $("<div>").appendTo(el);
37267
38572
  el = $("<div>").appendTo(el).addClass("red-ui-editor-text-container")[0];
@@ -37606,6 +38911,7 @@ RED.editor.codeEditor.monaco = (function() {
37606
38911
  try {
37607
38912
  var m = this.getModel();
37608
38913
  if(m && !m.isDisposed()) {
38914
+ ed._initState = null;
37609
38915
  m.dispose();
37610
38916
  }
37611
38917
  this.setModel(null);
@@ -37659,7 +38965,7 @@ RED.editor.codeEditor.monaco = (function() {
37659
38965
  try {
37660
38966
  var _model = ed.getModel();
37661
38967
  if (_model !== null) {
37662
- var id = _model.getModeId(); // e.g. javascript
38968
+ var id = _model._languageId; // e.g. javascript
37663
38969
  var ra = _model._associatedResource.authority; //e.g. model
37664
38970
  var rp = _model._associatedResource.path; //e.g. /18
37665
38971
  var rs = _model._associatedResource.scheme; //e.g. inmemory
@@ -37751,14 +39057,7 @@ RED.editor.codeEditor.monaco = (function() {
37751
39057
  //#endregion "ACE compatability"
37752
39058
 
37753
39059
  //final setup
37754
- if (options.cursor) {
37755
- var row = options.cursor.row || options.cursor.lineNumber;
37756
- var col = options.cursor.column || options.cursor.col;
37757
- ed.gotoLine(row, col);
37758
- }
37759
- if (options.focus) {
37760
- ed.focus();
37761
- }
39060
+ ed.focusMemory = options.focus;
37762
39061
  ed._mode = editorOptions.language;
37763
39062
 
37764
39063
  //as models are signleton, consts and let are avialable to other javascript instances
@@ -37770,11 +39069,12 @@ RED.editor.codeEditor.monaco = (function() {
37770
39069
  }
37771
39070
 
37772
39071
  ed.onDidBlurEditorWidget(function() {
39072
+ ed.focusMemory = false;
39073
+ ed.saveView();
37773
39074
  if(isVisible(el) == false) {
37774
39075
  onVisibilityChange(false, 0, el);
37775
39076
  }
37776
39077
  });
37777
-
37778
39078
  ed.onDidFocusEditorWidget(function() {
37779
39079
  onVisibilityChange(true, 10, el);
37780
39080
  });
@@ -37808,17 +39108,33 @@ RED.editor.codeEditor.monaco = (function() {
37808
39108
  }
37809
39109
 
37810
39110
  function onVisibilityChange(visible, delay, element) {
37811
- if(visible) {
37812
- if(ed._mode == "javascript" && ed._tempMode == "text") {
39111
+ delay = delay || 50;
39112
+ if (visible) {
39113
+ if (ed.focusMemory) {
39114
+ setTimeout(function () {
39115
+ if (element.parentElement) { //ensure el is still in DOM
39116
+ ed.focus();
39117
+ }
39118
+ }, 300)
39119
+ }
39120
+ if (ed._initState) {
39121
+ setTimeout(function () {
39122
+ if (element.parentElement) { //ensure el is still in DOM
39123
+ ed.restoreViewState(ed._initState);
39124
+ ed._initState = null;
39125
+ }
39126
+ }, delay);
39127
+ }
39128
+ if (ed._mode == "javascript" && ed._tempMode == "text") {
37813
39129
  ed._tempMode = "";
37814
- setTimeout(function() {
37815
- if(element.parentElement) { //ensure el is still in DOM
39130
+ setTimeout(function () {
39131
+ if (element.parentElement) { //ensure el is still in DOM
37816
39132
  ed.setMode('javascript', undefined, false);
37817
39133
  }
37818
- }, delay || 50);
39134
+ }, delay);
37819
39135
  }
37820
- } else if(ed._mode == "javascript" && ed._tempMode != "text") {
37821
- if(element.parentElement) { //ensure el is still in DOM
39136
+ } else if (ed._mode == "javascript" && ed._tempMode != "text") {
39137
+ if (element.parentElement) { //ensure el is still in DOM
37822
39138
  ed.setMode('text', undefined, false);
37823
39139
  ed._tempMode = "text";
37824
39140
  }
@@ -37837,15 +39153,19 @@ RED.editor.codeEditor.monaco = (function() {
37837
39153
  expandButton.on("click", function (e) {
37838
39154
  e.preventDefault();
37839
39155
  var value = ed.getValue();
39156
+ ed.saveView();
37840
39157
  RED.editor.editMarkdown({
37841
39158
  value: value,
37842
39159
  width: "Infinity",
37843
- cursor: ed.getCursorPosition(),
39160
+ stateId: options.stateId,
39161
+ cancel: function () {
39162
+ ed.focus();
39163
+ },
37844
39164
  complete: function (v, cursor) {
37845
39165
  ed.setValue(v, -1);
37846
- ed.gotoLine(cursor.row + 1, cursor.column, false);
37847
39166
  setTimeout(function () {
37848
39167
  ed.focus();
39168
+ ed.restoreView();
37849
39169
  }, 300);
37850
39170
  }
37851
39171
  })
@@ -37861,7 +39181,37 @@ RED.editor.codeEditor.monaco = (function() {
37861
39181
  autoClose: 50
37862
39182
  });
37863
39183
  }
37864
-
39184
+ ed.getView = function () {
39185
+ return ed.saveViewState();
39186
+ }
39187
+ ed.saveView = function (debuginfo) {
39188
+ if (!options.stateId) { return; } //only possible if created with a unique stateId
39189
+ window._editorStateMonaco = window._editorStateMonaco || {};
39190
+ var state = ed.getView();
39191
+ window._editorStateMonaco[options.stateId] = state;
39192
+ return state;
39193
+ }
39194
+ ed.restoreView = function (state) {
39195
+ if (!options.stateId) { return; } //only possible if created with a unique stateId
39196
+ window._editorStateMonaco = window._editorStateMonaco || {};
39197
+ var _state = state || window._editorStateMonaco[options.stateId];
39198
+ if (!_state) { return; } //no view state available
39199
+ try {
39200
+ if (ed.type) { //is editor already initialised?
39201
+ ed.restoreViewState(_state);
39202
+ } else {
39203
+ ed._initState = _state;
39204
+ }
39205
+ } catch (error) {
39206
+ delete window._editorStateMonaco[options.stateId];
39207
+ }
39208
+ };
39209
+ ed.restoreView();
39210
+ if (options.cursor && !ed._initState) {
39211
+ var row = options.cursor.row || options.cursor.lineNumber;
39212
+ var col = options.cursor.column || options.cursor.col;
39213
+ ed.gotoLine(row, col);
39214
+ }
37865
39215
  ed.type = type;
37866
39216
  return ed;
37867
39217
  }
@@ -38190,7 +39540,13 @@ RED.eventLog = (function() {
38190
39540
  raiseTrayZ();
38191
39541
  handleWindowResize();//cause call to monaco layout
38192
39542
  },200);
38193
- body.find(":focusable:first").trigger("focus");
39543
+ if(!options.hasOwnProperty("focusElement")) {
39544
+ //focusElement is not inside options - default to focusing 1st
39545
+ body.find(":focusable:first").trigger("focus");
39546
+ } else if(options.focusElement !== false) {
39547
+ //focusElement IS specified, focus that instead (if not false)
39548
+ $(options.focusElement).trigger("focus");
39549
+ }
38194
39550
 
38195
39551
  },150);
38196
39552
  el.css({right:0});
@@ -39343,6 +40699,7 @@ RED.clipboard = (function() {
39343
40699
  try {
39344
40700
  RED.view.importNodes(newNodes, importOptions);
39345
40701
  } catch(error) {
40702
+ console.log(error.importConfig)
39346
40703
  // Thrown for import_conflict
39347
40704
  confirmImport(error.importConfig, newNodes, importOptions);
39348
40705
  }
@@ -40891,6 +42248,8 @@ RED.search = (function() {
40891
42248
  var searchHistory = [];
40892
42249
  var index = {};
40893
42250
  var currentResults = [];
42251
+ var activeResults = [];
42252
+ var currentIndex = 0;
40894
42253
  var previousActiveElement;
40895
42254
 
40896
42255
  function indexProperty(node,label,property) {
@@ -40972,7 +42331,6 @@ RED.search = (function() {
40972
42331
 
40973
42332
  function search(val) {
40974
42333
  var results = [];
40975
- var keys = [];
40976
42334
  var typeFilter;
40977
42335
  var m = /(?:^| )type:([^ ]+)/.exec(val);
40978
42336
  if (m) {
@@ -40985,19 +42343,25 @@ RED.search = (function() {
40985
42343
  val = extractFlag(val,"config",flags);
40986
42344
  val = extractFlag(val,"subflow",flags);
40987
42345
  val = extractFlag(val,"hidden",flags);
40988
- // uses:<node-id>
40989
- val = extractValue(val,"uses",flags);
40990
-
40991
- var hasFlags = Object.keys(flags).length > 0;
40992
-
42346
+ val = extractFlag(val,"modified",flags);
42347
+ val = extractValue(val,"flow",flags);// flow:active or flow:<flow-id>
42348
+ val = extractValue(val,"uses",flags);// uses:<node-id>
40993
42349
  val = val.trim();
40994
-
42350
+ var hasFlags = Object.keys(flags).length > 0;
42351
+ if (flags.flow && flags.flow.indexOf("current") >= 0) {
42352
+ let idx = flags.flow.indexOf("current");
42353
+ flags.flow[idx] = RED.workspaces.active();//convert active to flow ID
42354
+ }
42355
+ if (flags.flow && flags.flow.length) {
42356
+ flags.flow = [ ...new Set(flags.flow) ]; //deduplicate
42357
+ }
40995
42358
  if (val.length > 0 || typeFilter || hasFlags) {
40996
42359
  val = val.toLowerCase();
40997
42360
  var i;
40998
42361
  var j;
40999
42362
  var list = [];
41000
42363
  var nodes = {};
42364
+ let keys = [];
41001
42365
  if (flags.uses) {
41002
42366
  keys = flags.uses;
41003
42367
  } else {
@@ -41007,7 +42371,7 @@ RED.search = (function() {
41007
42371
  var key = keys[i];
41008
42372
  var kpos = keys[i].indexOf(val);
41009
42373
  if (kpos > -1) {
41010
- var ids = Object.keys(index[key]);
42374
+ var ids = Object.keys(index[key]||{});
41011
42375
  for (j=0;j<ids.length;j++) {
41012
42376
  var node = index[key][ids[j]];
41013
42377
  var isConfigNode = node.node._def.category === "config" && node.node.type !== 'group';
@@ -41030,6 +42394,11 @@ RED.search = (function() {
41030
42394
  continue;
41031
42395
  }
41032
42396
  }
42397
+ if (flags.hasOwnProperty("modified")) {
42398
+ if (!node.node.changed && !node.node.moved) {
42399
+ continue;
42400
+ }
42401
+ }
41033
42402
  if (flags.hasOwnProperty("hidden")) {
41034
42403
  // Only tabs can be hidden
41035
42404
  if (node.node.type !== 'tab') {
@@ -41046,6 +42415,11 @@ RED.search = (function() {
41046
42415
  continue;
41047
42416
  }
41048
42417
  }
42418
+ if (flags.hasOwnProperty("flow")) {
42419
+ if (flags.flow.indexOf(node.node.z || node.node.id) < 0) {
42420
+ continue;
42421
+ }
42422
+ }
41049
42423
  if (!typeFilter || node.node.type === typeFilter) {
41050
42424
  nodes[node.node.id] = nodes[node.node.id] = {
41051
42425
  node: node.node,
@@ -41113,7 +42487,7 @@ RED.search = (function() {
41113
42487
  }
41114
42488
  currentResults = search(value);
41115
42489
  if (currentResults.length > 0) {
41116
- for (i=0;i<Math.min(currentResults.length,25);i++) {
42490
+ for (let i=0;i<Math.min(currentResults.length,25);i++) {
41117
42491
  searchResults.editableList('addItem',currentResults[i])
41118
42492
  }
41119
42493
  if (currentResults.length > 25) {
@@ -41127,9 +42501,8 @@ RED.search = (function() {
41127
42501
  } else {
41128
42502
  searchResults.editableList('addItem',{});
41129
42503
  }
41130
-
41131
-
41132
- }
42504
+ },
42505
+ options: getSearchOptions()
41133
42506
  });
41134
42507
  var copySearchContainer = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-caret-right"></button>').appendTo(searchDiv).on('click', function(evt) {
41135
42508
  evt.preventDefault();
@@ -41190,7 +42563,8 @@ RED.search = (function() {
41190
42563
  }
41191
42564
  } else if (!$(children[selected]).hasClass("red-ui-search-historyHeader")) {
41192
42565
  if (currentResults.length > 0) {
41193
- reveal(currentResults[Math.max(0,selected)].node);
42566
+ currentIndex = Math.max(0,selected);
42567
+ reveal(currentResults[currentIndex].node);
41194
42568
  }
41195
42569
  }
41196
42570
  }
@@ -41274,6 +42648,7 @@ RED.search = (function() {
41274
42648
 
41275
42649
  div.on("click", function(evt) {
41276
42650
  evt.preventDefault();
42651
+ currentIndex = i;
41277
42652
  reveal(node);
41278
42653
  });
41279
42654
  }
@@ -41289,13 +42664,59 @@ RED.search = (function() {
41289
42664
  if (existingIndex > -1) {
41290
42665
  searchHistory.splice(existingIndex,1);
41291
42666
  }
41292
- searchHistory.unshift(searchInput.val());
41293
- hide();
42667
+ searchHistory.unshift(searchVal);
42668
+ $("#red-ui-view-searchtools-search").data("term", searchVal);
42669
+ activeResults = Object.assign([], currentResults);
42670
+ hide(null, activeResults.length > 0);
41294
42671
  RED.view.reveal(node.id);
41295
42672
  }
41296
42673
 
42674
+ function revealPrev() {
42675
+ if (disabled) {
42676
+ updateSearchToolbar();
42677
+ return;
42678
+ }
42679
+ if (!searchResults || !activeResults.length) {
42680
+ show();
42681
+ return;
42682
+ }
42683
+ if (currentIndex > 0) {
42684
+ currentIndex--;
42685
+ } else {
42686
+ currentIndex = activeResults.length - 1;
42687
+ }
42688
+ const n = activeResults[currentIndex];
42689
+ if (n && n.node && n.node.id) {
42690
+ RED.view.reveal(n.node.id);
42691
+ $("#red-ui-view-searchtools-prev").trigger("focus");
42692
+ }
42693
+ updateSearchToolbar();
42694
+ }
42695
+ function revealNext() {
42696
+ if (disabled) {
42697
+ updateSearchToolbar();
42698
+ return;
42699
+ }
42700
+ if (!searchResults || !activeResults.length) {
42701
+ show();
42702
+ return;
42703
+ }
42704
+ if (currentIndex < activeResults.length - 1) {
42705
+ currentIndex++
42706
+ } else {
42707
+ currentIndex = 0;
42708
+ }
42709
+ const n = activeResults[currentIndex];
42710
+ if (n && n.node && n.node.id) {
42711
+ RED.view.reveal(n.node.id);
42712
+ $("#red-ui-view-searchtools-next").trigger("focus");
42713
+ }
42714
+ updateSearchToolbar();
42715
+ }
42716
+
41297
42717
  function show(v) {
41298
42718
  if (disabled) {
42719
+ updateSearchToolbar();
41299
42720
  return;
41300
42721
  }
41301
42722
  if (!visible) {
@@ -41322,7 +42743,7 @@ RED.search = (function() {
41322
42743
  searchInput.trigger("focus");
41323
42744
  }
41324
42745
 
41325
- function hide() {
42746
+ function hide(el, keepSearchToolbar) {
41326
42747
  if (visible) {
41327
42748
  visible = false;
41328
42749
  $("#red-ui-header-shade").hide();
@@ -41336,13 +42757,37 @@ RED.search = (function() {
41336
42757
  });
41337
42758
  }
41338
42759
  RED.events.emit("search:close");
41339
- if (previousActiveElement) {
42760
+ if (previousActiveElement && (!keepSearchToolbar || !activeResults.length)) {
41340
42761
  $(previousActiveElement).trigger("focus");
41341
- previousActiveElement = null;
41342
42762
  }
42763
+ previousActiveElement = null;
42764
+ }
42765
+ if(!keepSearchToolbar) {
42766
+ clearActiveSearch();
42767
+ }
42768
+ updateSearchToolbar();
42769
+ if(keepSearchToolbar && activeResults.length) {
42770
+ $("#red-ui-view-searchtools-next").trigger("focus");
42771
+ }
42772
+ }
42773
+ function updateSearchToolbar() {
42774
+ if (!disabled && currentIndex >= 0 && activeResults && activeResults.length) {
42775
+ let term = $("#red-ui-view-searchtools-search").data("term") || "";
42776
+ if (term.length > 16) {
42777
+ term = term.substring(0, 12) + "..."
42778
+ }
42779
+ const i18nSearchCounterData = {
42780
+ term: term,
42781
+ result: (currentIndex + 1),
42782
+ count: activeResults.length
42783
+ }
42784
+ $("#red-ui-view-searchtools-counter").text(RED._('actions.search-counter', i18nSearchCounterData));
42785
+ $("#view-search-tools > :not(:first-child)").show(); //show other tools
42786
+ } else {
42787
+ clearActiveSearch();
42788
+ $("#view-search-tools > :not(:first-child)").hide(); //hide all but search button
41343
42789
  }
41344
42790
  }
41345
-
41346
42791
  function clearIndex() {
41347
42792
  index = {};
41348
42793
  }
@@ -41364,9 +42809,29 @@ RED.search = (function() {
41364
42809
  addItemToIndex(item);
41365
42810
  }
41366
42811
 
42812
+ function clearActiveSearch() {
42813
+ activeResults = [];
42814
+ currentIndex = 0;
42815
+ $("#red-ui-view-searchtools-search").data("term", "");
42816
+ }
42817
+
42818
+ function getSearchOptions() {
42819
+ return [
42820
+ {label:RED._("search.options.configNodes"), value:"is:config"},
42821
+ {label:RED._("search.options.unusedConfigNodes"), value:"is:config is:unused"},
42822
+ {label:RED._("search.options.modifiedNodes"), value:"is:modified"},
42823
+ {label:RED._("search.options.invalidNodes"), value: "is:invalid"},
42824
+ {label:RED._("search.options.uknownNodes"), value: "type:unknown"},
42825
+ {label:RED._("search.options.unusedSubflows"), value:"is:subflow is:unused"},
42826
+ {label:RED._("search.options.hiddenFlows"), value:"is:hidden"},
42827
+ {label:RED._("search.options.thisFlow"), value:"flow:current"},
42828
+ ]
42829
+ }
41367
42830
 
41368
42831
  function init() {
41369
42832
  RED.actions.add("core:search",show);
42833
+ RED.actions.add("core:search-previous",revealPrev);
42834
+ RED.actions.add("core:search-next",revealNext);
41370
42835
 
41371
42836
  RED.events.on("editor:open",function() { disabled = true; });
41372
42837
  RED.events.on("editor:close",function() { disabled = false; });
@@ -41377,11 +42842,21 @@ RED.search = (function() {
41377
42842
 
41378
42843
  RED.keyboard.add("red-ui-search","escape",hide);
41379
42844
 
42845
+ RED.keyboard.add("view-search-tools","escape",function() {
42846
+ clearActiveSearch();
42847
+ updateSearchToolbar();
42848
+ });
42849
+
41380
42850
  $("#red-ui-header-shade").on('mousedown',hide);
41381
42851
  $("#red-ui-editor-shade").on('mousedown',hide);
41382
42852
  $("#red-ui-palette-shade").on('mousedown',hide);
41383
42853
  $("#red-ui-sidebar-shade").on('mousedown',hide);
41384
42854
 
42855
+ $("#red-ui-view-searchtools-close").on("click", function close() {
42856
+ clearActiveSearch();
42857
+ updateSearchToolbar();
42858
+ });
42859
+ $("#red-ui-view-searchtools-close").trigger("click");
41385
42860
 
41386
42861
  RED.events.on("workspace:clear", clearIndex);
41387
42862
 
@@ -41407,7 +42882,8 @@ RED.search = (function() {
41407
42882
  init: init,
41408
42883
  show: show,
41409
42884
  hide: hide,
41410
- search: search
42885
+ search: search,
42886
+ getSearchOptions: getSearchOptions
41411
42887
  };
41412
42888
 
41413
42889
  })();
@@ -41816,17 +43292,21 @@ RED.actionList = (function() {
41816
43292
  var div = $('<div>',{class:"red-ui-search-result"}).appendTo(container);
41817
43293
 
41818
43294
  var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(div);
41819
- var colour = RED.utils.getNodeColor(object.type,def);
43295
+ if (object.type === "junction") {
43296
+ nodeDiv.addClass("red-ui-palette-icon-junction");
43297
+ } else {
43298
+ var colour = RED.utils.getNodeColor(object.type,def);
43299
+ nodeDiv.css('backgroundColor',colour);
43300
+ }
41820
43301
  var icon_url = RED.utils.getNodeIcon(def);
41821
- nodeDiv.css('backgroundColor',colour);
41822
43302
 
41823
43303
  var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
41824
43304
  RED.utils.createIconElement(icon_url, iconContainer, false);
41825
43305
 
41826
- if (def.inputs > 0) {
43306
+ if (object.type !== "junction" && def.inputs > 0) {
41827
43307
  $('<div/>',{class:"red-ui-search-result-node-port"}).appendTo(nodeDiv);
41828
43308
  }
41829
- if (def.outputs > 0) {
43309
+ if (object.type !== "junction" && def.outputs > 0) {
41830
43310
  $('<div/>',{class:"red-ui-search-result-node-port red-ui-search-result-node-output"}).appendTo(nodeDiv);
41831
43311
  }
41832
43312
 
@@ -41958,8 +43438,8 @@ RED.actionList = (function() {
41958
43438
  return !filter ||
41959
43439
  (
41960
43440
  (!filter.type || type === filter.type) &&
41961
- (!filter.input || def.inputs > 0) &&
41962
- (!filter.output || def.outputs > 0)
43441
+ (!filter.input || type === 'junction' || def.inputs > 0) &&
43442
+ (!filter.output || type === 'junction' || def.outputs > 0)
41963
43443
  )
41964
43444
  }
41965
43445
  function refreshTypeList(opts) {
@@ -41968,7 +43448,7 @@ RED.actionList = (function() {
41968
43448
  searchInput.searchBox('value','').focus();
41969
43449
  selected = -1;
41970
43450
  var common = [
41971
- 'inject','debug','function','change','switch'
43451
+ 'inject','debug','function','change','switch','junction'
41972
43452
  ].filter(function(t) { return applyFilter(opts.filter,t,RED.nodes.getType(t)); });
41973
43453
 
41974
43454
  var recentlyUsed = Object.keys(typesUsed);
@@ -41993,6 +43473,9 @@ RED.actionList = (function() {
41993
43473
  var index = 0;
41994
43474
  for(i=0;i<common.length;i++) {
41995
43475
  var itemDef = RED.nodes.getType(common[i]);
43476
+ if (common[i] === 'junction') {
43477
+ itemDef = { inputs:1, outputs: 1, label: 'junction', type: 'junction'}
43478
+ }
41996
43479
  if (itemDef) {
41997
43480
  item = {
41998
43481
  type: common[i],
@@ -43680,9 +45163,6 @@ RED.group = (function() {
43680
45163
  groups: [ ],
43681
45164
  dirty: RED.nodes.dirty()
43682
45165
  }
43683
- RED.history.push(historyEvent);
43684
-
43685
-
43686
45166
  groups.forEach(function(g) {
43687
45167
  newSelection = newSelection.concat(ungroup(g))
43688
45168
  historyEvent.groups.push(g);
@@ -43709,8 +45189,10 @@ RED.group = (function() {
43709
45189
  }
43710
45190
  if (n.type === 'group') {
43711
45191
  RED.events.emit("groups:change",n)
43712
- } else {
45192
+ } else if (n.type !== 'junction') {
43713
45193
  RED.events.emit("nodes:change",n)
45194
+ } else {
45195
+ RED.events.emit("junctions:change",n)
43714
45196
  }
43715
45197
  })
43716
45198
  RED.nodes.removeGroup(g);
@@ -43904,8 +45386,10 @@ RED.group = (function() {
43904
45386
  group.h = Math.max(group.h,n.y+n.h/2+25-group.y);
43905
45387
  if (n.type === 'group') {
43906
45388
  RED.events.emit("groups:change",n)
43907
- } else {
45389
+ } else if (n.type !== 'junction') {
43908
45390
  RED.events.emit("nodes:change",n)
45391
+ } else {
45392
+ RED.events.emit("junctions:change",n)
43909
45393
  }
43910
45394
  }
43911
45395
  }
@@ -43940,8 +45424,10 @@ RED.group = (function() {
43940
45424
  }
43941
45425
  if (n.type === 'group') {
43942
45426
  RED.events.emit("groups:change",n)
43943
- } else {
45427
+ } else if (n.type !== 'junction') {
43944
45428
  RED.events.emit("nodes:change",n)
45429
+ } else {
45430
+ RED.events.emit("junctions:change",n)
43945
45431
  }
43946
45432
  }
43947
45433
  markDirty(group);
@@ -46953,6 +48439,7 @@ RED.projects.settings = (function() {
46953
48439
  title: RED._('sidebar.project.editDescription'),
46954
48440
  header: $('<span><i class="fa fa-book"></i> README.md</span>'),
46955
48441
  value: activeProject.description,
48442
+ stateId: "sidebar.project.editDescription",
46956
48443
  complete: function(v) {
46957
48444
  container.empty();
46958
48445
  var spinner = utils.addSpinnerOverlay(container);