@node-red/editor-client 2.2.2 → 3.0.0-beta.1

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 +44 -14
  3. package/locales/ja/editor.json +50 -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 +77 -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 +2060 -610
  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 +65 -99
  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,
@@ -6046,6 +6133,30 @@ RED.nodes = (function() {
6046
6133
  RED.events.emit("groups:remove",group);
6047
6134
  }
6048
6135
 
6136
+ function addJunction(junction) {
6137
+ junctionsByZ[junction.z] = junctionsByZ[junction.z] || []
6138
+ junctionsByZ[junction.z].push(junction)
6139
+ junctions[junction.id] = junction;
6140
+ if (!nodeLinks[junction.id]) {
6141
+ nodeLinks[junction.id] = {in:[],out:[]};
6142
+ }
6143
+ RED.events.emit("junctions:add", junction)
6144
+ }
6145
+ function removeJunction(junction) {
6146
+ var i = junctionsByZ[junction.z].indexOf(junction)
6147
+ junctionsByZ[junction.z].splice(i, 1)
6148
+ if (junctionsByZ[junction.z].length === 0) {
6149
+ delete junctionsByZ[junction.z]
6150
+ }
6151
+ delete junctions[junction.id]
6152
+ delete nodeLinks[junction.id];
6153
+ RED.events.emit("junctions:remove", junction)
6154
+
6155
+ var removedLinks = links.filter(function(l) { return (l.source === junction) || (l.target === junction); });
6156
+ removedLinks.forEach(removeLink);
6157
+ return { links: removedLinks }
6158
+ }
6159
+
6049
6160
  function getNodeHelp(type) {
6050
6161
  var helpContent = "";
6051
6162
  var helpElement = $("script[data-help-name='"+type+"']");
@@ -6274,7 +6385,6 @@ RED.nodes = (function() {
6274
6385
  getType: registry.getNodeType,
6275
6386
  getNodeHelp: getNodeHelp,
6276
6387
  convertNode: convertNode,
6277
-
6278
6388
  add: addNode,
6279
6389
  remove: removeNode,
6280
6390
  clear: clear,
@@ -6320,6 +6430,11 @@ RED.nodes = (function() {
6320
6430
  group: function(id) { return groups[id] },
6321
6431
  groups: function(z) { return groupsByZ[z]?groupsByZ[z].slice():[] },
6322
6432
 
6433
+ addJunction: addJunction,
6434
+ removeJunction: removeJunction,
6435
+ junction: function(id) { return junctions[id] },
6436
+ junctions: function(z) { return junctionsByZ[z]?junctionsByZ[z].slice():[] },
6437
+
6323
6438
  eachNode: function(cb) {
6324
6439
  allNodes.eachNode(cb);
6325
6440
  },
@@ -7227,6 +7342,11 @@ RED.nodes.fontAwesome = (function() {
7227
7342
  * See the License for the specific language governing permissions and
7228
7343
  * limitations under the License.
7229
7344
  **/
7345
+
7346
+ /**
7347
+ * An API for undo / redo history buffer
7348
+ * @namespace RED.history
7349
+ */
7230
7350
  RED.history = (function() {
7231
7351
  var undoHistory = [];
7232
7352
  var redoHistory = [];
@@ -7315,6 +7435,23 @@ RED.history = (function() {
7315
7435
  RED.nodes.removeLink(ev.links[i]);
7316
7436
  }
7317
7437
  }
7438
+ if (ev.junctions) {
7439
+ inverseEv.junctions = [];
7440
+ for (i=0;i<ev.junctions.length;i++) {
7441
+ inverseEv.junctions.push(ev.junctions[i]);
7442
+ RED.nodes.removeJunction(ev.junctions[i]);
7443
+ if (ev.junctions[i].g) {
7444
+ var group = RED.nodes.group(ev.junctions[i].g);
7445
+ var index = group.nodes.indexOf(ev.junctions[i]);
7446
+ if (index !== -1) {
7447
+ group.nodes.splice(index,1);
7448
+ RED.group.markDirty(group);
7449
+ }
7450
+ }
7451
+
7452
+
7453
+ }
7454
+ }
7318
7455
  if (ev.groups) {
7319
7456
  inverseEv.groups = [];
7320
7457
  for (i = ev.groups.length - 1;i>=0;i--) {
@@ -7481,6 +7618,21 @@ RED.history = (function() {
7481
7618
  }
7482
7619
  }
7483
7620
  }
7621
+ if (ev.junctions) {
7622
+ inverseEv.junctions = [];
7623
+ for (i=0;i<ev.junctions.length;i++) {
7624
+ inverseEv.junctions.push(ev.junctions[i]);
7625
+ RED.nodes.addJunction(ev.junctions[i]);
7626
+ if (ev.junctions[i].g) {
7627
+ group = RED.nodes.group(ev.junctions[i].g);
7628
+ if (group.nodes.indexOf(ev.junctions[i]) === -1) {
7629
+ group.nodes.push(ev.junctions[i]);
7630
+ }
7631
+ RED.group.markDirty(group)
7632
+ }
7633
+
7634
+ }
7635
+ }
7484
7636
  if (ev.links) {
7485
7637
  inverseEv.links = [];
7486
7638
  for (i=0;i<ev.links.length;i++) {
@@ -7941,24 +8093,74 @@ RED.history = (function() {
7941
8093
  * limitations under the License.
7942
8094
  **/
7943
8095
  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);
8096
+ number: function(blankAllowed,mopt){
8097
+ return function(v, opt) {
8098
+ if ((blankAllowed&&(v===''||v===undefined)) || (v!=='' && !isNaN(v))) {
7951
8099
  return true;
7952
- } catch(err) {
7953
- return false;
7954
8100
  }
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
- }}
8101
+ if (opt && opt.label) {
8102
+ return RED._("validator.errors.invalid-num-prop", {
8103
+ prop: opt.label
8104
+ });
8105
+ }
8106
+ return opt ? RED._("validator.errors.invalid-num") : false;
8107
+ };
8108
+ },
8109
+ regex: function(re, mopt) {
8110
+ return function(v, opt) {
8111
+ if (re.test(v)) {
8112
+ return true;
8113
+ }
8114
+ if (opt && opt.label) {
8115
+ return RED._("validator.errors.invalid-regex-prop", {
8116
+ prop: opt.label
8117
+ });
8118
+ }
8119
+ return opt ? RED._("validator.errors.invalid-regexp") : false;
8120
+ };
8121
+ },
8122
+ typedInput: function(ptypeName,isConfig,mopt) {
8123
+ return function(v, opt) {
8124
+ var ptype = $("#node-"+(isConfig?"config-":"")+"input-"+ptypeName).val() || this[ptypeName];
8125
+ if (ptype === 'json') {
8126
+ try {
8127
+ JSON.parse(v);
8128
+ return true;
8129
+ } catch(err) {
8130
+ if (opt && opt.label) {
8131
+ return RED._("validator.errors.invalid-json-prop", {
8132
+ error: err.message,
8133
+ prop: opt.label,
8134
+ });
8135
+ }
8136
+ return opt ? RED._("validator.errors.invalid-json", {
8137
+ error: err.message
8138
+ }) : false;
8139
+ }
8140
+ } else if (ptype === 'msg' || ptype === 'flow' || ptype === 'global' ) {
8141
+ if (RED.utils.validatePropertyExpression(v)) {
8142
+ return true;
8143
+ }
8144
+ if (opt && opt.label) {
8145
+ return RED._("validator.errors.invalid-prop-prop", {
8146
+ prop: opt.label
8147
+ });
8148
+ }
8149
+ return opt ? RED._("validator.errors.invalid-prop") : false;
8150
+ } else if (ptype === 'num') {
8151
+ if (/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v)) {
8152
+ return true;
8153
+ }
8154
+ if (opt && opt.label) {
8155
+ return RED._("validator.errors.invalid-num-prop", {
8156
+ prop: opt.label
8157
+ });
8158
+ }
8159
+ return opt ? RED._("validator.errors.invalid-num") : false;
8160
+ }
8161
+ return true;
8162
+ };
8163
+ }
7962
8164
  };
7963
8165
  ;/**
7964
8166
  * Copyright JS Foundation and other contributors, http://js.foundation
@@ -8327,7 +8529,16 @@ RED.utils = (function() {
8327
8529
  }
8328
8530
  }
8329
8531
 
8330
- function buildMessageElement(obj,options) {
8532
+ /**
8533
+ * Create a DOM element representation of obj - as used by Debug sidebar etc
8534
+ *
8535
+ * @params obj - the data to display
8536
+ * @params options - a bag of options
8537
+ *
8538
+ * - If you want the Copy Value button, then set `sourceId`
8539
+ * - If you want the Copy Path button, also set `path` to the value to be copied
8540
+ */
8541
+ function createObjectElement(obj,options) {
8331
8542
  options = options || {};
8332
8543
  var key = options.key;
8333
8544
  var typeHint = options.typeHint;
@@ -8517,7 +8728,7 @@ RED.utils = (function() {
8517
8728
  if (fullLength <= 10) {
8518
8729
  for (i=0;i<fullLength;i++) {
8519
8730
  row = $('<div class="red-ui-debug-msg-object-entry collapsed"></div>').appendTo(arrayRows);
8520
- subElements[path+"["+i+"]"] = buildMessageElement(
8731
+ subElements[path+"["+i+"]"] = createObjectElement(
8521
8732
  data[i],
8522
8733
  {
8523
8734
  key: ""+i,
@@ -8547,7 +8758,7 @@ RED.utils = (function() {
8547
8758
  return function() {
8548
8759
  for (var i=min;i<=max;i++) {
8549
8760
  var row = $('<div class="red-ui-debug-msg-object-entry collapsed"></div>').appendTo(parent);
8550
- subElements[path+"["+i+"]"] = buildMessageElement(
8761
+ subElements[path+"["+i+"]"] = createObjectElement(
8551
8762
  data[i],
8552
8763
  {
8553
8764
  key: ""+i,
@@ -8603,7 +8814,7 @@ RED.utils = (function() {
8603
8814
  newPath += "[\""+keys[i].replace(/"/,"\\\"")+"\"]"
8604
8815
  }
8605
8816
  }
8606
- subElements[newPath] = buildMessageElement(
8817
+ subElements[newPath] = createObjectElement(
8607
8818
  data[keys[i]],
8608
8819
  {
8609
8820
  key: keys[i],
@@ -8981,6 +9192,8 @@ RED.utils = (function() {
8981
9192
  return "font-awesome/fa-object-ungroup";
8982
9193
  } else if (node && node.type === 'group') {
8983
9194
  return "font-awesome/fa-object-group"
9195
+ } else if ((node && node.type === 'junction') || (def.type === "junction") ) {
9196
+ return "font-awesome/fa-circle-o"
8984
9197
  } else if (def.category === 'config') {
8985
9198
  return RED.settings.apiRootUrl+"icons/node-red/cog.svg"
8986
9199
  } else if (node && node.type === 'tab') {
@@ -9046,6 +9259,8 @@ RED.utils = (function() {
9046
9259
  l = node.label || defaultLabel
9047
9260
  } else if (node.type === 'group') {
9048
9261
  l = node.name || defaultLabel
9262
+ } else if (node.type === 'junction') {
9263
+ l = 'junction'
9049
9264
  } else {
9050
9265
  l = node._def.label;
9051
9266
  try {
@@ -9058,6 +9273,18 @@ RED.utils = (function() {
9058
9273
  return RED.text.bidi.enforceTextDirectionWithUCC(l);
9059
9274
  }
9060
9275
 
9276
+ function getPaletteLabel(nodeType, def) {
9277
+ var label = nodeType;
9278
+ if (typeof def.paletteLabel !== "undefined") {
9279
+ try {
9280
+ label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||"";
9281
+ } catch(err) {
9282
+ console.log("Definition error: "+nodeType+".paletteLabel",err);
9283
+ }
9284
+ }
9285
+ return label
9286
+ }
9287
+
9061
9288
  var nodeColorCache = {};
9062
9289
  function clearNodeColorCache() {
9063
9290
  nodeColorCache = {};
@@ -9200,6 +9427,8 @@ RED.utils = (function() {
9200
9427
  nodeDiv.addClass("red-ui-palette-icon-selection");
9201
9428
  } else if (node.type === "group") {
9202
9429
  nodeDiv.addClass("red-ui-palette-icon-group");
9430
+ } else if (node.type === "junction") {
9431
+ nodeDiv.addClass("red-ui-palette-icon-junction");
9203
9432
  } else if (node.type === 'tab') {
9204
9433
  nodeDiv.addClass("red-ui-palette-icon-flow");
9205
9434
  } else {
@@ -9331,7 +9560,7 @@ RED.utils = (function() {
9331
9560
  }
9332
9561
 
9333
9562
  return {
9334
- createObjectElement: buildMessageElement,
9563
+ createObjectElement: createObjectElement,
9335
9564
  getMessageProperty: getMessageProperty,
9336
9565
  setMessageProperty: setMessageProperty,
9337
9566
  normalisePropertyExpression: normalisePropertyExpression,
@@ -9341,6 +9570,7 @@ RED.utils = (function() {
9341
9570
  getNodeIcon: getNodeIcon,
9342
9571
  getNodeLabel: getNodeLabel,
9343
9572
  getNodeColor: getNodeColor,
9573
+ getPaletteLabel: getPaletteLabel,
9344
9574
  clearNodeColorCache: clearNodeColorCache,
9345
9575
  addSpinnerOverlay: addSpinnerOverlay,
9346
9576
  decodeObject: decodeObject,
@@ -11671,6 +11901,7 @@ RED.popover = (function() {
11671
11901
  setTimeout(closePopup,delay.hide);
11672
11902
  }
11673
11903
  });
11904
+
11674
11905
  if (trigger === 'hover') {
11675
11906
  target.on('mouseenter',function(e) {
11676
11907
  clearTimeout(timer);
@@ -11782,6 +12013,11 @@ RED.popover = (function() {
11782
12013
  popover.setAction = function(newAction) {
11783
12014
  action = newAction;
11784
12015
  }
12016
+ popover.delete = function() {
12017
+ popover.close(true)
12018
+ target.off("mouseenter");
12019
+ target.off("mouseleave");
12020
+ };
11785
12021
  return popover;
11786
12022
 
11787
12023
  },
@@ -12083,8 +12319,8 @@ RED.popover = (function() {
12083
12319
  }
12084
12320
  });
12085
12321
  this.element.on("keydown",function(e) {
12086
- if (!menuShown && e.keyCode === 40) {
12087
- //DOWN
12322
+ if (!menuShown && e.keyCode === 40 && $(this).val() === '') {
12323
+ //DOWN (only show menu if search field is emty)
12088
12324
  showMenu();
12089
12325
  }
12090
12326
  });
@@ -12829,7 +13065,7 @@ RED.tabs = (function() {
12829
13065
  }
12830
13066
  var link = $("<a/>",{href:"#"+tab.id, class:"red-ui-tab-label"}).appendTo(li);
12831
13067
  if (tab.icon) {
12832
- $('<img src="'+tab.icon+'" class="red-ui-tab-icon"/>').appendTo(link);
13068
+ $('<i>',{class:"red-ui-tab-icon", style:"mask-image: url("+tab.icon+"); -webkit-mask-image: url("+tab.icon+");"}).appendTo(link);
12833
13069
  } else if (tab.iconClass) {
12834
13070
  $('<i>',{class:"red-ui-tab-icon "+tab.iconClass}).appendTo(link);
12835
13071
  }
@@ -13408,34 +13644,46 @@ RED.stack = (function() {
13408
13644
  }
13409
13645
 
13410
13646
  var autoComplete = function(options) {
13647
+ function getMatch(value, searchValue) {
13648
+ const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
13649
+ const len = idx > -1 ? searchValue.length : 0;
13650
+ return {
13651
+ index: idx,
13652
+ found: idx > -1,
13653
+ pre: value.substring(0,idx),
13654
+ match: value.substring(idx,idx+len),
13655
+ post: value.substring(idx+len),
13656
+ }
13657
+ }
13658
+ function generateSpans(match) {
13659
+ const els = [];
13660
+ if(match.pre) { els.push($('<span/>').text(match.pre)); }
13661
+ if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
13662
+ if(match.post) { els.push($('<span/>').text(match.post)); }
13663
+ return els;
13664
+ }
13411
13665
  return function(val) {
13412
13666
  var matches = [];
13413
13667
  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
- })
13668
+ const optVal = opt.value;
13669
+ const optSrc = (opt.source||[]).join(",");
13670
+ const valMatch = getMatch(optVal, val);
13671
+ const srcMatch = getMatch(optSrc, val);
13672
+ if (valMatch.found || srcMatch.found) {
13673
+ const element = $('<div>',{style: "display: flex"});
13674
+ const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
13675
+ valEl.append(generateSpans(valMatch));
13676
+ valEl.appendTo(element);
13677
+ if (optSrc) {
13678
+ const optEl = $('<div>').css({ "font-size": "0.8em" });
13679
+ optEl.append(generateSpans(srcMatch));
13680
+ optEl.appendTo(element);
13681
+ }
13682
+ matches.push({
13683
+ value: optVal,
13684
+ label: element,
13685
+ i: (valMatch.found ? valMatch.index : srcMatch.index)
13686
+ });
13439
13687
  }
13440
13688
  })
13441
13689
  matches.sort(function(A,B){return A.i-B.i})
@@ -13446,45 +13694,65 @@ RED.stack = (function() {
13446
13694
  // This is a hand-generated list of completions for the core nodes (based on the node help html).
13447
13695
  var msgCompletions = [
13448
13696
  { 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"]},
13697
+ { value: "topic", source: ["mqtt","inject","rbe"] },
13698
+ { value: "action", source: ["mqtt"] },
13456
13699
  { value: "complete", source: ["join"] },
13457
13700
  { value: "contentType", source: ["mqtt"] },
13458
- { value: "cookies", source: ["http in","http request"] },
13701
+ { value: "cookies", source: ["http request","http response"] },
13459
13702
  { value: "correlationData", source: ["mqtt"] },
13460
13703
  { value: "delay", source: ["delay","trigger"] },
13461
13704
  { value: "encoding", source: ["file"] },
13462
13705
  { value: "error", source: ["catch"] },
13706
+ { value: "error.message", source: ["catch"] },
13707
+ { value: "error.source", source: ["catch"] },
13708
+ { value: "error.source.id", source: ["catch"] },
13709
+ { value: "error.source.type", source: ["catch"] },
13710
+ { value: "error.source.name", source: ["catch"] },
13463
13711
  { value: "filename", source: ["file","file in"] },
13464
13712
  { value: "flush", source: ["delay"] },
13465
13713
  { value: "followRedirects", source: ["http request"] },
13466
- { value: "headers", source: ["http in"," http request"] },
13714
+ { value: "headers", source: ["http response","http request"] },
13715
+ { value: "host", source: ["tcp request","http request"] },
13716
+ { value: "ip", source: ["udp out"] },
13467
13717
  { value: "kill", source: ["exec"] },
13468
13718
  { value: "messageExpiryInterval", source: ["mqtt"] },
13469
- { value: "method", source: ["http-request"] },
13719
+ { value: "method", source: ["http request"] },
13470
13720
  { value: "options", source: ["xml"] },
13471
- { value: "parts", source: ["split","join"] },
13721
+ { value: "parts", source: ["split","join","batch","sort"] },
13472
13722
  { value: "pid", source: ["exec"] },
13723
+ { value: "port", source: ["tcp request"," udp out"] },
13473
13724
  { value: "qos", source: ["mqtt"] },
13474
13725
  { value: "rate", source: ["delay"] },
13475
13726
  { value: "rejectUnauthorized", source: ["http request"] },
13727
+ { value: "req", source: ["http in"]},
13728
+ { value: "req.body", source: ["http in"]},
13729
+ { value: "req.headers", source: ["http in"]},
13730
+ { value: "req.query", source: ["http in"]},
13731
+ { value: "req.params", source: ["http in"]},
13732
+ { value: "req.cookies", source: ["http in"]},
13733
+ { value: "req.files", source: ["http in"]},
13476
13734
  { value: "requestTimeout", source: ["http request"] },
13477
13735
  { value: "reset", source: ["delay","trigger","join","rbe"] },
13736
+ { value: "responseCookies", source: ["http request"] },
13478
13737
  { value: "responseTopic", source: ["mqtt"] },
13738
+ { value: "responseURL", source: ["http request"] },
13479
13739
  { value: "restartTimeout", source: ["join"] },
13480
13740
  { value: "retain", source: ["mqtt"] },
13741
+ { value: "schema", source: ["json"] },
13481
13742
  { value: "select", source: ["html"] },
13482
- { value: "statusCode", source: ["http in"] },
13743
+ { value: "statusCode", source: ["http response","http request"] },
13744
+ { value: "status", source: ["status"] },
13745
+ { value: "status.text", source: ["status"] },
13746
+ { value: "status.source", source: ["status"] },
13747
+ { value: "status.source.type", source: ["status"] },
13748
+ { value: "status.source.id", source: ["status"] },
13749
+ { value: "status.source.name", source: ["status"] },
13750
+ { value: "target", source: ["link call"] },
13483
13751
  { value: "template", source: ["template"] },
13484
13752
  { value: "toFront", source: ["delay"] },
13485
- { value: "topic", source: ["inject","mqtt","rbe"] },
13486
13753
  { value: "url", source: ["http request"] },
13487
- { value: "userProperties", source: ["mqtt"] }
13754
+ { value: "userProperties", source: ["mqtt"] },
13755
+ { value: "_session", source: ["websocket out","tcp out"] },
13488
13756
  ]
13489
13757
  var allOptions = {
13490
13758
  msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: autoComplete(msgCompletions)},
@@ -13519,6 +13787,8 @@ RED.stack = (function() {
13519
13787
  }
13520
13788
  RED.editor.editJSON({
13521
13789
  value: value,
13790
+ stateId: RED.editor.generateViewStateId("typedInput", that, "json"),
13791
+ focus: true,
13522
13792
  complete: function(v) {
13523
13793
  var value = v;
13524
13794
  try {
@@ -13541,6 +13811,8 @@ RED.stack = (function() {
13541
13811
  var that = this;
13542
13812
  RED.editor.editExpression({
13543
13813
  value: this.value().replace(/\t/g,"\n"),
13814
+ stateId: RED.editor.generateViewStateId("typedInput", that, "jsonata"),
13815
+ focus: true,
13544
13816
  complete: function(v) {
13545
13817
  that.value(v.replace(/\n/g,"\t"));
13546
13818
  }
@@ -13555,6 +13827,8 @@ RED.stack = (function() {
13555
13827
  var that = this;
13556
13828
  RED.editor.editBuffer({
13557
13829
  value: this.value(),
13830
+ stateId: RED.editor.generateViewStateId("typedInput", that, "bin"),
13831
+ focus: true,
13558
13832
  complete: function(v) {
13559
13833
  that.value(v);
13560
13834
  }
@@ -13990,7 +14264,7 @@ RED.stack = (function() {
13990
14264
  if (opt.icon.indexOf("<") === 0) {
13991
14265
  $(opt.icon).prependTo(op);
13992
14266
  } else if (opt.icon.indexOf("/") !== -1) {
13993
- $('<img>',{src:mapDeprecatedIcon(opt.icon),style:"margin-right: 4px; height: 18px;"}).prependTo(op);
14267
+ $('<i>',{class:"red-ui-typedInput-icon", style:"mask-image: url("+opt.icon+"); -webkit-mask-image: url("+opt.icon+");"}).prependTo(op);
13994
14268
  } else {
13995
14269
  $('<i>',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(op);
13996
14270
  }
@@ -14500,7 +14774,8 @@ RED.stack = (function() {
14500
14774
  this.elementDiv.show();
14501
14775
  if (opt.autoComplete) {
14502
14776
  this.input.autoComplete({
14503
- search: opt.autoComplete
14777
+ search: opt.autoComplete,
14778
+ minLength: 0
14504
14779
  })
14505
14780
  }
14506
14781
  }
@@ -14730,12 +15005,14 @@ RED.stack = (function() {
14730
15005
  *
14731
15006
  * options:
14732
15007
  *
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
15008
+ * search: function(value, [done])
15009
+ * A function that is passed the current contents of the input whenever
15010
+ * it changes.
15011
+ * The function must either return auto-complete options, or pass them
15012
+ * to the optional 'done' parameter.
15013
+ * If the function signature includes 'done', it must be used
15014
+ * minLength: number
15015
+ * If `minLength` is 0, pressing down arrow will show the list
14739
15016
  *
14740
15017
  * The auto-complete options should be an array of objects in the form:
14741
15018
  * {
@@ -14747,10 +15024,11 @@ RED.stack = (function() {
14747
15024
 
14748
15025
  $.widget( "nodered.autoComplete", {
14749
15026
  _create: function() {
14750
- var that = this;
15027
+ const that = this;
14751
15028
  this.completionMenuShown = false;
14752
- this.options.search = this.options.search || function() { return [] }
14753
- this.element.addClass("red-ui-autoComplete")
15029
+ this.options.minLength = parseInteger(this.options.minLength, 1, 0);
15030
+ this.options.search = this.options.search || function() { return [] };
15031
+ this.element.addClass("red-ui-autoComplete");
14754
15032
  this.element.on("keydown.red-ui-autoComplete", function(evt) {
14755
15033
  if ((evt.keyCode === 13 || evt.keyCode === 9) && that.completionMenuShown) {
14756
15034
  var opts = that.menu.options();
@@ -14792,8 +15070,8 @@ RED.stack = (function() {
14792
15070
  this.completionMenuShown = true;
14793
15071
  },
14794
15072
  _updateCompletions: function(val) {
14795
- var that = this;
14796
- if (val.trim() === "") {
15073
+ const that = this;
15074
+ if (val.trim().length < this.options.minLength) {
14797
15075
  if (this.completionMenuShown) {
14798
15076
  this.menu.hide();
14799
15077
  }
@@ -14817,7 +15095,7 @@ RED.stack = (function() {
14817
15095
  }
14818
15096
  }
14819
15097
  if (this.options.search.length === 2) {
14820
- var requestId = 1+Math.floor(Math.random()*10000);
15098
+ const requestId = 1+Math.floor(Math.random()*10000);
14821
15099
  this.pendingRequest = requestId;
14822
15100
  this.options.search(val,function(completions) { displayResults(completions,requestId);})
14823
15101
  } else {
@@ -14833,6 +15111,14 @@ RED.stack = (function() {
14833
15111
  }
14834
15112
  }
14835
15113
  });
15114
+ function parseInteger(input, def, min, max) {
15115
+ if(input == null) { return (def || 0); }
15116
+ min = min == null ? Number.NEGATIVE_INFINITY : min;
15117
+ max = max == null ? Number.POSITIVE_INFINITY : max;
15118
+ let n = parseInt(input);
15119
+ if(isNaN(n) || n < min || n > max) { n = def || 0; }
15120
+ return n;
15121
+ }
14836
15122
  })(jQuery);
14837
15123
  ;RED.actions = (function() {
14838
15124
  var actions = {
@@ -15240,205 +15526,248 @@ RED.deploy = (function() {
15240
15526
  },delta);
15241
15527
  });
15242
15528
  }
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 = [];
15529
+ function save(skipValidation, force) {
15530
+ if ($("#red-ui-header-button-deploy").hasClass("disabled")) {
15531
+ return; //deploy is disabled
15532
+ }
15533
+ if ($("#red-ui-header-shade").is(":visible")) {
15534
+ return; //deploy is shaded
15535
+ }
15536
+ if (!RED.user.hasPermission("flows.write")) {
15537
+ RED.notify(RED._("user.errors.deploy"), "error");
15538
+ return;
15539
+ }
15540
+ let hasUnusedConfig = false;
15541
+ if (!skipValidation) {
15542
+ let hasUnknown = false;
15543
+ let hasInvalid = false;
15544
+ const unknownNodes = [];
15545
+ const invalidNodes = [];
15256
15546
 
15257
- RED.nodes.eachConfig(function(node) {
15258
- if (node.valid === undefined) {
15259
- RED.editor.validateNode(node);
15547
+ RED.nodes.eachConfig(function (node) {
15548
+ if (node.valid === undefined) {
15549
+ RED.editor.validateNode(node);
15550
+ }
15551
+ if (!node.valid && !node.d) {
15552
+ invalidNodes.push(getNodeInfo(node));
15553
+ }
15554
+ if (node.type === "unknown") {
15555
+ if (unknownNodes.indexOf(node.name) == -1) {
15556
+ unknownNodes.push(node.name);
15260
15557
  }
15261
- if (!node.valid && !node.d) {
15262
- invalidNodes.push(getNodeInfo(node));
15558
+ }
15559
+ });
15560
+ RED.nodes.eachNode(function (node) {
15561
+ if (!node.valid && !node.d) {
15562
+ invalidNodes.push(getNodeInfo(node));
15563
+ }
15564
+ if (node.type === "unknown") {
15565
+ if (unknownNodes.indexOf(node.name) == -1) {
15566
+ unknownNodes.push(node.name);
15263
15567
  }
15264
- if (node.type === "unknown") {
15265
- if (unknownNodes.indexOf(node.name) == -1) {
15266
- unknownNodes.push(node.name);
15568
+ }
15569
+ });
15570
+ hasUnknown = unknownNodes.length > 0;
15571
+ hasInvalid = invalidNodes.length > 0;
15572
+
15573
+ const unusedConfigNodes = [];
15574
+ RED.nodes.eachConfig(function (node) {
15575
+ if ((node._def.hasUsers !== false) && (node.users.length === 0)) {
15576
+ unusedConfigNodes.push(getNodeInfo(node));
15577
+ hasUnusedConfig = true;
15578
+ }
15579
+ });
15580
+
15581
+ let showWarning = false;
15582
+ let notificationMessage;
15583
+ let notificationButtons = [];
15584
+ let notification;
15585
+ if (hasUnknown && !ignoreDeployWarnings.unknown) {
15586
+ showWarning = true;
15587
+ notificationMessage = "<p>" + RED._('deploy.confirm.unknown') + "</p>" +
15588
+ '<ul class="red-ui-deploy-dialog-confirm-list"><li>' + cropList(unknownNodes).map(function (n) { return sanitize(n) }).join("</li><li>") + "</li></ul><p>" +
15589
+ RED._('deploy.confirm.confirm') +
15590
+ "</p>";
15591
+
15592
+ notificationButtons = [
15593
+ {
15594
+ text: RED._("deploy.unknownNodesButton"),
15595
+ class: "pull-left",
15596
+ click: function() {
15597
+ notification.close();
15598
+ RED.actions.invoke("core:search","type:unknown ");
15599
+ }
15600
+ },
15601
+ {
15602
+ id: "red-ui-deploy-dialog-confirm-deploy-deploy",
15603
+ text: RED._("deploy.confirm.button.confirm"),
15604
+ class: "primary",
15605
+ click: function () {
15606
+ save(true);
15607
+ notification.close();
15267
15608
  }
15268
- }
15269
- });
15270
- RED.nodes.eachNode(function(node) {
15271
- if (!node.valid && !node.d) {
15272
- invalidNodes.push(getNodeInfo(node));
15273
15609
  }
15274
- if (node.type === "unknown") {
15275
- if (unknownNodes.indexOf(node.name) == -1) {
15276
- unknownNodes.push(node.name);
15610
+ ];
15611
+ } else if (hasInvalid && !ignoreDeployWarnings.invalid) {
15612
+ showWarning = true;
15613
+ invalidNodes.sort(sortNodeInfo);
15614
+
15615
+ notificationMessage = "<p>" + RED._('deploy.confirm.improperlyConfigured') + "</p>" +
15616
+ '<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>" +
15617
+ RED._('deploy.confirm.confirm') +
15618
+ "</p>";
15619
+ notificationButtons = [
15620
+ {
15621
+ text: RED._("deploy.invalidNodesButton"),
15622
+ class: "pull-left",
15623
+ click: function() {
15624
+ notification.close();
15625
+ RED.actions.invoke("core:search","is:invalid ");
15626
+ }
15627
+ },
15628
+ {
15629
+ id: "red-ui-deploy-dialog-confirm-deploy-deploy",
15630
+ text: RED._("deploy.confirm.button.confirm"),
15631
+ class: "primary",
15632
+ click: function () {
15633
+ save(true);
15634
+ notification.close();
15277
15635
  }
15278
15636
  }
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;
15637
+ ];
15638
+ }
15639
+ if (showWarning) {
15640
+ notificationButtons.unshift(
15641
+ {
15642
+ text: RED._("common.label.cancel"),
15643
+ click: function () {
15644
+ notification.close();
15645
+ }
15288
15646
  }
15647
+ );
15648
+ notification = RED.notify(notificationMessage, {
15649
+ modal: true,
15650
+ fixed: true,
15651
+ buttons: notificationButtons
15289
15652
  });
15653
+ return;
15654
+ }
15655
+ }
15656
+
15657
+ const nns = RED.nodes.createCompleteNodeSet();
15658
+ const startTime = Date.now();
15659
+
15660
+ $(".red-ui-deploy-button-content").css('opacity', 0);
15661
+ $(".red-ui-deploy-button-spinner").show();
15662
+ $("#red-ui-header-button-deploy").addClass("disabled");
15290
15663
 
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= [
15664
+ const data = { flows: nns };
15665
+
15666
+ if (!force) {
15667
+ data.rev = RED.nodes.version();
15668
+ }
15669
+
15670
+ deployInflight = true;
15671
+ $("#red-ui-header-shade").show();
15672
+ $("#red-ui-editor-shade").show();
15673
+ $("#red-ui-palette-shade").show();
15674
+ $("#red-ui-sidebar-shade").show();
15675
+ $.ajax({
15676
+ url: "flows",
15677
+ type: "POST",
15678
+ data: JSON.stringify(data),
15679
+ contentType: "application/json; charset=utf-8",
15680
+ headers: {
15681
+ "Node-RED-Deployment-Type": deploymentType
15682
+ }
15683
+ }).done(function (data, textStatus, xhr) {
15684
+ RED.nodes.dirty(false);
15685
+ RED.nodes.version(data.rev);
15686
+ RED.nodes.originalFlow(nns);
15687
+ if (hasUnusedConfig) {
15688
+ let notification;
15689
+ const opts = {
15690
+ type: "success",
15691
+ fixed: false,
15692
+ timeout: 6000,
15693
+ buttons: [
15303
15694
  {
15304
- id: "red-ui-deploy-dialog-confirm-deploy-deploy",
15305
- text: RED._("deploy.confirm.button.confirm"),
15306
- class: "primary",
15695
+ text: RED._("deploy.unusedConfigNodesButton"),
15696
+ class: "pull-left",
15307
15697
  click: function() {
15308
- save(true);
15309
15698
  notification.close();
15699
+ RED.actions.invoke("core:search","is:config is:unused ");
15310
15700
  }
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= [
15701
+ },
15322
15702
  {
15323
- id: "red-ui-deploy-dialog-confirm-deploy-deploy",
15324
- text: RED._("deploy.confirm.button.confirm"),
15703
+ text: RED._("common.label.close"),
15325
15704
  class: "primary",
15326
- click: function() {
15705
+ click: function () {
15327
15706
  save(true);
15328
15707
  notification.close();
15329
15708
  }
15330
15709
  }
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;
15710
+ ]
15348
15711
  }
15712
+ notification = RED.notify(
15713
+ '<p>' + RED._("deploy.successfulDeploy") + '</p>' +
15714
+ '<p>' + RED._("deploy.unusedConfigNodes") + '</p>', opts);
15715
+ } else {
15716
+ RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success");
15349
15717
  }
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
15718
+ RED.nodes.eachNode(function (node) {
15719
+ if (node.changed) {
15720
+ node.dirty = true;
15721
+ node.changed = false;
15376
15722
  }
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");
15723
+ if (node.moved) {
15724
+ node.dirty = true;
15725
+ node.moved = false;
15387
15726
  }
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");
15727
+ if (node.credentials) {
15728
+ delete node.credentials;
15428
15729
  }
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
15730
  });
15441
- }
15731
+ RED.nodes.eachConfig(function (confNode) {
15732
+ confNode.changed = false;
15733
+ if (confNode.credentials) {
15734
+ delete confNode.credentials;
15735
+ }
15736
+ });
15737
+ RED.nodes.eachSubflow(function (subflow) {
15738
+ subflow.changed = false;
15739
+ });
15740
+ RED.nodes.eachWorkspace(function (ws) {
15741
+ ws.changed = false;
15742
+ });
15743
+ // Once deployed, cannot undo back to a clean state
15744
+ RED.history.markAllDirty();
15745
+ RED.view.redraw();
15746
+ RED.events.emit("deploy");
15747
+ }).fail(function (xhr, textStatus, err) {
15748
+ RED.nodes.dirty(true);
15749
+ $("#red-ui-header-button-deploy").removeClass("disabled");
15750
+ if (xhr.status === 401) {
15751
+ RED.notify(RED._("deploy.deployFailed", { message: RED._("user.notAuthorized") }), "error");
15752
+ } else if (xhr.status === 409) {
15753
+ resolveConflict(nns, true);
15754
+ } else if (xhr.responseText) {
15755
+ RED.notify(RED._("deploy.deployFailed", { message: xhr.responseText }), "error");
15756
+ } else {
15757
+ RED.notify(RED._("deploy.deployFailed", { message: RED._("deploy.errors.noResponse") }), "error");
15758
+ }
15759
+ }).always(function () {
15760
+ deployInflight = false;
15761
+ const delta = Math.max(0, 300 - (Date.now() - startTime));
15762
+ setTimeout(function () {
15763
+ $(".red-ui-deploy-button-content").css('opacity', 1);
15764
+ $(".red-ui-deploy-button-spinner").hide();
15765
+ $("#red-ui-header-shade").hide();
15766
+ $("#red-ui-editor-shade").hide();
15767
+ $("#red-ui-palette-shade").hide();
15768
+ $("#red-ui-sidebar-shade").hide();
15769
+ }, delta);
15770
+ });
15442
15771
  }
15443
15772
  return {
15444
15773
  init: init,
@@ -15448,6 +15777,67 @@ RED.deploy = (function() {
15448
15777
 
15449
15778
  }
15450
15779
  })();
15780
+ ;
15781
+ RED.diagnostics = (function () {
15782
+
15783
+ function init() {
15784
+ if (RED.settings.get('diagnostics.ui', true) === false) {
15785
+ return;
15786
+ }
15787
+ RED.actions.add("core:show-system-info", function () { show(); });
15788
+ }
15789
+
15790
+ function show() {
15791
+ $.ajax({
15792
+ headers: {
15793
+ "Accept": "application/json"
15794
+ },
15795
+ cache: false,
15796
+ url: 'diagnostics',
15797
+ success: function (data) {
15798
+ var json = JSON.stringify(data || {}, "", 4);
15799
+ if (json === "{}") {
15800
+ json = "{\n\n}";
15801
+ }
15802
+ RED.editor.editJSON({
15803
+ title: RED._('diagnostics.title'),
15804
+ value: json,
15805
+ requireValid: true,
15806
+ readOnly: true,
15807
+ toolbarButtons: [
15808
+ {
15809
+ text: RED._('clipboard.export.copy'),
15810
+ icon: 'fa fa-copy',
15811
+ click: function () {
15812
+ RED.clipboard.copyText(json, $(this), RED._('clipboard.copyMessageValue'))
15813
+ }
15814
+ },
15815
+ {
15816
+ text: RED._('clipboard.download'),
15817
+ icon: 'fa fa-download',
15818
+ click: function () {
15819
+ var element = document.createElement('a');
15820
+ element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(json));
15821
+ element.setAttribute('download', "system-info.json");
15822
+ element.style.display = 'none';
15823
+ document.body.appendChild(element);
15824
+ element.click();
15825
+ document.body.removeChild(element);
15826
+ }
15827
+ },
15828
+ ]
15829
+ });
15830
+ },
15831
+ error: function (jqXHR, textStatus, errorThrown) {
15832
+ console.log("Unexpected error loading system info:", jqXHR.status, textStatus, errorThrown);
15833
+ }
15834
+ });
15835
+ }
15836
+
15837
+ return {
15838
+ init: init,
15839
+ };
15840
+ })();
15451
15841
  ;RED.diff = (function() {
15452
15842
 
15453
15843
  var currentDiff = {};
@@ -18404,6 +18794,9 @@ RED.workspaces = (function() {
18404
18794
  var hideStack = [];
18405
18795
  var viewStackPos = 0;
18406
18796
 
18797
+ let flashingTab;
18798
+ let flashingTabTimer;
18799
+
18407
18800
  function addToViewStack(id) {
18408
18801
  if (viewStackPos !== viewStack.length) {
18409
18802
  viewStack.splice(viewStackPos);
@@ -18808,6 +19201,9 @@ RED.workspaces = (function() {
18808
19201
  }
18809
19202
  }
18810
19203
  })
19204
+ RED.actions.add("core:list-modified-nodes",function() {
19205
+ RED.actions.invoke("core:search","is:modified ");
19206
+ })
18811
19207
  RED.actions.add("core:list-hidden-flows",function() {
18812
19208
  RED.actions.invoke("core:search","is:hidden ");
18813
19209
  })
@@ -18908,6 +19304,31 @@ RED.workspaces = (function() {
18908
19304
  workspace_tabs.order(order);
18909
19305
  }
18910
19306
 
19307
+ function flashTab(tabId) {
19308
+ if(flashingTab && flashingTab.length) {
19309
+ //cancel current flashing node before flashing new node
19310
+ clearInterval(flashingTabTimer);
19311
+ flashingTabTimer = null;
19312
+ flashingTab.removeClass('highlighted');
19313
+ flashingTab = null;
19314
+ }
19315
+ let tab = $("#red-ui-tab-" + tabId);
19316
+ if(!tab || !tab.length) { return; }
19317
+
19318
+ flashingTabTimer = setInterval(function(flashEndTime) {
19319
+ if (flashEndTime >= Date.now()) {
19320
+ const highlighted = tab.hasClass("highlighted");
19321
+ tab.toggleClass('highlighted', !highlighted)
19322
+ } else {
19323
+ clearInterval(flashingTabTimer);
19324
+ flashingTabTimer = null;
19325
+ flashingTab = null;
19326
+ tab.removeClass('highlighted');
19327
+ }
19328
+ }, 100, Date.now() + 2200);
19329
+ flashingTab = tab;
19330
+ tab.addClass('highlighted');
19331
+ }
18911
19332
  return {
18912
19333
  init: init,
18913
19334
  add: addWorkspace,
@@ -18940,7 +19361,7 @@ RED.workspaces = (function() {
18940
19361
  isHidden: function(id) {
18941
19362
  return hideStack.includes(id)
18942
19363
  },
18943
- show: function(id,skipStack,unhideOnly) {
19364
+ show: function(id,skipStack,unhideOnly,flash) {
18944
19365
  if (!workspace_tabs.contains(id)) {
18945
19366
  var sf = RED.nodes.subflow(id);
18946
19367
  if (sf) {
@@ -18962,6 +19383,9 @@ RED.workspaces = (function() {
18962
19383
  }
18963
19384
  workspace_tabs.activateTab(id);
18964
19385
  }
19386
+ if(flash) {
19387
+ flashTab(id.replace(".","-"))
19388
+ }
18965
19389
  },
18966
19390
  refresh: function() {
18967
19391
  RED.nodes.eachWorkspace(function(ws) {
@@ -19015,6 +19439,7 @@ RED.statusBar = (function() {
19015
19439
  function addWidget(options) {
19016
19440
  widgets[options.id] = options;
19017
19441
  var el = $('<span class="red-ui-statusbar-widget"></span>');
19442
+ el.prop('id', options.id);
19018
19443
  options.element.appendTo(el);
19019
19444
  if (options.align === 'left') {
19020
19445
  leftBucket.append(el);
@@ -19058,6 +19483,7 @@ RED.statusBar = (function() {
19058
19483
  * |- <g> "groupLayer"
19059
19484
  * |- <g> "groupSelectLayer"
19060
19485
  * |- <g> "linkLayer"
19486
+ * |- <g> "junctionLayer"
19061
19487
  * |- <g> "dragGroupLayer"
19062
19488
  * |- <g> "nodeLayer"
19063
19489
  */
@@ -19090,6 +19516,7 @@ RED.view = (function() {
19090
19516
  var activeSubflow = null;
19091
19517
  var activeNodes = [];
19092
19518
  var activeLinks = [];
19519
+ var activeJunctions = [];
19093
19520
  var activeFlowLinks = [];
19094
19521
  var activeLinkNodes = {};
19095
19522
  var activeGroup = null;
@@ -19124,6 +19551,9 @@ RED.view = (function() {
19124
19551
  var lastClickPosition = [];
19125
19552
  var selectNodesOptions;
19126
19553
 
19554
+ let flashingNodeId;
19555
+ let flashingNodeTimer;
19556
+
19127
19557
  var clipboard = "";
19128
19558
 
19129
19559
  // Note: these are the permitted status colour aliases. The actual RGB values
@@ -19145,6 +19575,7 @@ RED.view = (function() {
19145
19575
  var eventLayer;
19146
19576
  var gridLayer;
19147
19577
  var linkLayer;
19578
+ var junctionLayer;
19148
19579
  var dragGroupLayer;
19149
19580
  var groupSelectLayer;
19150
19581
  var nodeLayer;
@@ -19233,6 +19664,11 @@ RED.view = (function() {
19233
19664
 
19234
19665
  function init() {
19235
19666
 
19667
+ // setTimeout(function() {
19668
+ // function snap(p) { return RED.view.gridSize() * Math.round(p/RED.view.gridSize())}; for (var i = 0;i<10;i++) {
19669
+ // RED.nodes.addJunction({_def:{defaults:{}}, type:'junction', z:"0ccdc1d81f2729cc",id:RED.nodes.id(),x:snap(Math.floor(Math.random()*600)),y:snap(Math.floor(Math.random()*600)), w:0,h:0})
19670
+ // } ; RED.view.redraw(true)
19671
+ // },2000)
19236
19672
  chart = $("#red-ui-workspace-chart");
19237
19673
 
19238
19674
  outer = d3.select("#red-ui-workspace-chart")
@@ -19407,6 +19843,7 @@ RED.view = (function() {
19407
19843
  groupSelectLayer = eventLayer.append("g");
19408
19844
  linkLayer = eventLayer.append("g");
19409
19845
  dragGroupLayer = eventLayer.append("g");
19846
+ junctionLayer = eventLayer.append("g");
19410
19847
  nodeLayer = eventLayer.append("g");
19411
19848
 
19412
19849
  drag_lines = [];
@@ -19477,13 +19914,40 @@ RED.view = (function() {
19477
19914
  }
19478
19915
  });
19479
19916
 
19917
+ //add search to status-toolbar
19918
+ RED.statusBar.add({
19919
+ id: "view-search-tools",
19920
+ align: "left",
19921
+ hidden: false,
19922
+ element: $('<span class="button-group">'+
19923
+ '<button class="red-ui-footer-button" id="red-ui-view-searchtools-search"><i class="fa fa-search"></i></button>' +
19924
+ '</span>' +
19925
+ '<span class="button-group search-counter">' +
19926
+ '<span class="red-ui-footer-button" id="red-ui-view-searchtools-counter">? of ?</span>' +
19927
+ '</span>' +
19928
+ '<span class="button-group">' +
19929
+ '<button class="red-ui-footer-button" id="red-ui-view-searchtools-prev"><i class="fa fa-chevron-left"></i></button>' +
19930
+ '<button class="red-ui-footer-button" id="red-ui-view-searchtools-next"><i class="fa fa-chevron-right"></i></button>' +
19931
+ '</span>' +
19932
+ '<span class="button-group">' +
19933
+ '<button class="red-ui-footer-button" id="red-ui-view-searchtools-close"><i class="fa fa-close"></i></button>' +
19934
+ '</span>')
19935
+ })
19936
+ $("#red-ui-view-searchtools-search").on("click", searchFlows);
19937
+ RED.popover.tooltip($("#red-ui-view-searchtools-search"),RED._('actions.search-flows'),'core:search');
19938
+ $("#red-ui-view-searchtools-prev").on("click", searchPrev);
19939
+ RED.popover.tooltip($("#red-ui-view-searchtools-prev"),RED._('actions.search-prev'),'core:search-previous');
19940
+ $("#red-ui-view-searchtools-next").on("click", searchNext);
19941
+ RED.popover.tooltip($("#red-ui-view-searchtools-next"),RED._('actions.search-next'),'core:search-next');
19942
+ RED.popover.tooltip($("#red-ui-view-searchtools-close"),RED._('common.label.close'));
19943
+
19480
19944
  // Handle nodes dragged from the palette
19481
19945
  chart.droppable({
19482
19946
  accept:".red-ui-palette-node",
19483
19947
  drop: function( event, ui ) {
19484
19948
  d3.event = event;
19485
19949
  var selected_tool = $(ui.draggable[0]).attr("data-palette-type");
19486
- var result = addNode(selected_tool);
19950
+ var result = createNode(selected_tool);
19487
19951
  if (!result) {
19488
19952
  return;
19489
19953
  }
@@ -19527,17 +19991,9 @@ RED.view = (function() {
19527
19991
  nn.y = mousePos[1];
19528
19992
 
19529
19993
  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];
19994
+ var gridOffset = RED.view.tools.calculateGridSnapOffsets(nn);
19995
+ nn.x -= gridOffset.x;
19996
+ nn.y -= gridOffset.y;
19541
19997
  }
19542
19998
 
19543
19999
  var spliceLink = $(ui.helper).data("splice");
@@ -19607,7 +20063,7 @@ RED.view = (function() {
19607
20063
 
19608
20064
  RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection);
19609
20065
  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});});
20066
+ RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true, generateDefaultNames: true});});
19611
20067
 
19612
20068
  RED.actions.add("core:detach-selected-nodes", function() { detachSelectedNodes() })
19613
20069
 
@@ -19827,7 +20283,7 @@ RED.view = (function() {
19827
20283
  source:{z:activeWorkspace},
19828
20284
  target:{z:activeWorkspace}
19829
20285
  });
19830
-
20286
+ activeJunctions = RED.nodes.junctions(activeWorkspace) || [];
19831
20287
  activeGroups = RED.nodes.groups(activeWorkspace)||[];
19832
20288
  activeGroups.forEach(function(g, i) {
19833
20289
  g._index = i;
@@ -19842,6 +20298,7 @@ RED.view = (function() {
19842
20298
  } else {
19843
20299
  activeNodes = [];
19844
20300
  activeLinks = [];
20301
+ activeJunctions = [];
19845
20302
  activeGroups = [];
19846
20303
  }
19847
20304
 
@@ -19961,81 +20418,6 @@ RED.view = (function() {
19961
20418
  }
19962
20419
  }
19963
20420
 
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
20421
  function canvasMouseDown() {
20040
20422
  if (RED.view.DEBUG) { console.warn("canvasMouseDown", mouse_mode); }
20041
20423
  var point;
@@ -20087,9 +20469,9 @@ RED.view = (function() {
20087
20469
  .attr("class","nr-ui-view-lasso");
20088
20470
  d3.event.preventDefault();
20089
20471
  }
20090
- } else if (mouse_mode === 0 && d3.event.button === 2 && (d3.event.metaKey || d3.event.ctrlKey)) {
20472
+ } else if (mouse_mode === 0 && d3.event.button === 2 && (d3.event.metaKey || d3.event.ctrlKey || d3.event.shiftKey)) {
20091
20473
  clearSelection();
20092
- mouse_mode = RED.state.SLICING;
20474
+ mouse_mode = (d3.event.metaKey || d3.event.ctrlKey)?RED.state.SLICING : RED.state.SLICING_JUNCTION;
20093
20475
  point = d3.mouse(this);
20094
20476
  slicePath = eventLayer.append("path").attr("class","nr-ui-view-slice").attr("d",`M${point[0]} ${point[1]}`)
20095
20477
  slicePathLast = point;
@@ -20224,16 +20606,38 @@ RED.view = (function() {
20224
20606
  keepAdding = false;
20225
20607
  resetMouseVars();
20226
20608
  }
20227
- var result = addNode(type);
20228
- if (!result) {
20229
- return;
20609
+
20610
+ var nn;
20611
+ var historyEvent;
20612
+ if (type === 'junction') {
20613
+ nn = {
20614
+ _def: {defaults:{}},
20615
+ type: 'junction',
20616
+ z: RED.workspaces.active(),
20617
+ id: RED.nodes.id(),
20618
+ x: 0,
20619
+ y: 0,
20620
+ w: 0, h: 0,
20621
+ outputs: 1,
20622
+ inputs: 1,
20623
+ dirty: true
20624
+ }
20625
+ historyEvent = {
20626
+ t:'add',
20627
+ junctions:[nn]
20628
+ }
20629
+ } else {
20630
+ var result = createNode(type);
20631
+ if (!result) {
20632
+ return;
20633
+ }
20634
+ nn = result.node;
20635
+ historyEvent = result.historyEvent;
20230
20636
  }
20231
20637
  if (keepAdding) {
20232
20638
  mouse_mode = RED.state.QUICK_JOINING;
20233
20639
  }
20234
20640
 
20235
- var nn = result.node;
20236
- var historyEvent = result.historyEvent;
20237
20641
  nn.x = point[0];
20238
20642
  nn.y = point[1];
20239
20643
  var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
@@ -20342,8 +20746,11 @@ RED.view = (function() {
20342
20746
  }
20343
20747
  }
20344
20748
  }
20345
-
20346
- RED.nodes.add(nn);
20749
+ if (nn.type === 'junction') {
20750
+ RED.nodes.addJunction(nn);
20751
+ } else {
20752
+ RED.nodes.add(nn);
20753
+ }
20347
20754
  RED.editor.validateNode(nn);
20348
20755
 
20349
20756
  if (targetGroup) {
@@ -20490,7 +20897,7 @@ RED.view = (function() {
20490
20897
  .attr("height",h)
20491
20898
  ;
20492
20899
  return;
20493
- } else if (mouse_mode === RED.state.SLICING) {
20900
+ } else if (mouse_mode === RED.state.SLICING || mouse_mode === RED.state.SLICING_JUNCTION) {
20494
20901
  if (slicePath) {
20495
20902
  var delta = Math.max(1,Math.abs(slicePathLast[0]-mouse_position[0]))*Math.max(1,Math.abs(slicePathLast[1]-mouse_position[1]))
20496
20903
  if (delta > 20) {
@@ -20677,16 +21084,9 @@ RED.view = (function() {
20677
21084
  gridOffset[0] = node.n.x-(gridSize*Math.floor(node.n.x/gridSize))-gridSize/2;
20678
21085
  gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize))-gridSize/2;
20679
21086
  } 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);
21087
+ const snapOffsets = RED.view.tools.calculateGridSnapOffsets(node.n);
21088
+ gridOffset[0] = snapOffsets.x;
21089
+ gridOffset[1] = snapOffsets.y;
20690
21090
  }
20691
21091
  if (gridOffset[0] !== 0 || gridOffset[1] !== 0) {
20692
21092
  for (i = 0; i<movingSet.length(); i++) {
@@ -20863,6 +21263,15 @@ RED.view = (function() {
20863
21263
  }
20864
21264
  }
20865
21265
  });
21266
+ activeJunctions.forEach(function(n) {
21267
+ if (!n.selected) {
21268
+ if (n.x > x && n.x < x2 && n.y > y && n.y < y2) {
21269
+ n.selected = true;
21270
+ n.dirty = true;
21271
+ movingSet.add(n);
21272
+ }
21273
+ }
21274
+ })
20866
21275
 
20867
21276
 
20868
21277
 
@@ -20906,11 +21315,92 @@ RED.view = (function() {
20906
21315
  } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey && !d3.event.metaKey ) {
20907
21316
  clearSelection();
20908
21317
  updateSelection();
20909
- } else if (slicePath) {
21318
+ } else if (mouse_mode == RED.state.SLICING) {
20910
21319
  deleteSelection();
20911
21320
  slicePath.remove();
20912
21321
  slicePath = null;
20913
21322
  RED.view.redraw(true);
21323
+ } else if (mouse_mode == RED.state.SLICING_JUNCTION) {
21324
+ var removedLinks = []
21325
+ var addedLinks = []
21326
+ var addedJunctions = []
21327
+
21328
+ var groupedLinks = {}
21329
+ selectedLinks.forEach(function(l) {
21330
+ var sourceId = l.source.id+":"+l.sourcePort
21331
+ groupedLinks[sourceId] = groupedLinks[sourceId] || []
21332
+ groupedLinks[sourceId].push(l)
21333
+ });
21334
+ var linkGroups = Object.keys(groupedLinks)
21335
+ linkGroups.forEach(function(gid) {
21336
+ var links = groupedLinks[gid]
21337
+ var junction = {
21338
+ _def: {defaults:{}},
21339
+ type: 'junction',
21340
+ z: RED.workspaces.active(),
21341
+ id: RED.nodes.id(),
21342
+ x: 0,
21343
+ y: 0,
21344
+ w: 0, h: 0,
21345
+ outputs: 1,
21346
+ inputs: 1,
21347
+ dirty: true
21348
+ }
21349
+ links.forEach(function(l) {
21350
+ junction.x += l._sliceLocation.x
21351
+ junction.y += l._sliceLocation.y
21352
+ })
21353
+ junction.x = Math.round(junction.x/links.length)
21354
+ junction.y = Math.round(junction.y/links.length)
21355
+ if (snapGrid) {
21356
+ junction.x = (gridSize*Math.round(junction.x/gridSize));
21357
+ junction.y = (gridSize*Math.round(junction.y/gridSize));
21358
+ }
21359
+
21360
+ var nodeGroups = new Set()
21361
+
21362
+ RED.nodes.addJunction(junction)
21363
+ addedJunctions.push(junction)
21364
+ var newLink = {
21365
+ source: links[0].source,
21366
+ sourcePort: links[0].sourcePort,
21367
+ target: junction
21368
+ }
21369
+ addedLinks.push(newLink)
21370
+ RED.nodes.addLink(newLink)
21371
+ links.forEach(function(l) {
21372
+ removedLinks.push(l)
21373
+ RED.nodes.removeLink(l)
21374
+ var newLink = {
21375
+ source: junction,
21376
+ sourcePort: 0,
21377
+ target: l.target
21378
+ }
21379
+ addedLinks.push(newLink)
21380
+ RED.nodes.addLink(newLink)
21381
+ nodeGroups.add(l.source.g || "__NONE__")
21382
+ nodeGroups.add(l.target.g || "__NONE__")
21383
+ })
21384
+ if (nodeGroups.size === 1) {
21385
+ var group = nodeGroups.values().next().value
21386
+ if (group !== "__NONE__") {
21387
+ RED.group.addToGroup(RED.nodes.group(group), junction)
21388
+ }
21389
+ }
21390
+ })
21391
+ slicePath.remove();
21392
+ slicePath = null;
21393
+
21394
+ if (addedJunctions.length > 0) {
21395
+ RED.history.push({
21396
+ t: 'add',
21397
+ links: addedLinks,
21398
+ junctions: addedJunctions,
21399
+ removedLinks: removedLinks
21400
+ })
21401
+ RED.nodes.dirty(true)
21402
+ }
21403
+ RED.view.redraw(true);
20914
21404
  }
20915
21405
  if (mouse_mode == RED.state.MOVING_ACTIVE) {
20916
21406
  if (movingSet.length() > 0) {
@@ -21031,6 +21521,9 @@ RED.view = (function() {
21031
21521
  }
21032
21522
  }
21033
21523
  function zoomZero() { zoomView(1); }
21524
+ function searchFlows() { RED.actions.invoke("core:search", $(this).data("term")); }
21525
+ function searchPrev() { RED.actions.invoke("core:search-previous"); }
21526
+ function searchNext() { RED.actions.invoke("core:search-next"); }
21034
21527
 
21035
21528
 
21036
21529
  function zoomView(factor) {
@@ -21067,7 +21560,7 @@ RED.view = (function() {
21067
21560
  clearSelection();
21068
21561
  RED.history.pop();
21069
21562
  mouse_mode = 0;
21070
- } else if (mouse_mode === RED.state.SLICING) {
21563
+ } else if (mouse_mode === RED.state.SLICING || mouse_mode === RED.state.SLICING_JUNCTION) {
21071
21564
  if (slicePath) {
21072
21565
  slicePath.remove();
21073
21566
  slicePath = null;
@@ -21136,6 +21629,14 @@ RED.view = (function() {
21136
21629
  }
21137
21630
  });
21138
21631
 
21632
+ activeJunctions.forEach(function(n) {
21633
+ if (!n.selected) {
21634
+ n.selected = true;
21635
+ n.dirty = true;
21636
+ movingSet.add(n);
21637
+ }
21638
+ })
21639
+
21139
21640
  if (mouse_mode !== RED.state.SELECTING_NODE && activeSubflow) {
21140
21641
  activeSubflow.in.forEach(function(n) {
21141
21642
  if (!n.selected) {
@@ -21335,6 +21836,7 @@ RED.view = (function() {
21335
21836
  nodes: [],
21336
21837
  links: [],
21337
21838
  groups: [],
21839
+ junctions: [],
21338
21840
  workspaces: [],
21339
21841
  subflows: []
21340
21842
  }
@@ -21355,6 +21857,7 @@ RED.view = (function() {
21355
21857
  historyEvent.nodes = historyEvent.nodes.concat(subEvent.nodes);
21356
21858
  historyEvent.links = historyEvent.links.concat(subEvent.links);
21357
21859
  historyEvent.groups = historyEvent.groups.concat(subEvent.groups);
21860
+ historyEvent.junctions = historyEvent.junctions.concat(subEvent.junctions);
21358
21861
  }
21359
21862
  RED.history.push(historyEvent);
21360
21863
  RED.nodes.dirty(true);
@@ -21367,6 +21870,7 @@ RED.view = (function() {
21367
21870
  var removedNodes = [];
21368
21871
  var removedLinks = [];
21369
21872
  var removedGroups = [];
21873
+ var removedJunctions = [];
21370
21874
  var removedSubflowOutputs = [];
21371
21875
  var removedSubflowInputs = [];
21372
21876
  var removedSubflowStatus;
@@ -21411,14 +21915,14 @@ RED.view = (function() {
21411
21915
  for (var i=0;i<movingSet.length();i++) {
21412
21916
  node = movingSet.get(i).n;
21413
21917
  node.selected = false;
21414
- if (node.type !== "group" && node.type !== "subflow") {
21918
+ if (node.type !== "group" && node.type !== "subflow" && node.type !== 'junction') {
21415
21919
  if (node.x < 0) {
21416
21920
  node.x = 25
21417
21921
  }
21418
21922
  var removedEntities = RED.nodes.remove(node.id);
21419
21923
  removedNodes.push(node);
21420
21924
  removedNodes = removedNodes.concat(removedEntities.nodes);
21421
- addToRemovedLinks(removedNodes.removedLinks);
21925
+ addToRemovedLinks(removedEntities.links);
21422
21926
  if (node.g) {
21423
21927
  var group = RED.nodes.group(node.g);
21424
21928
  if (selectedGroups.indexOf(group) === -1) {
@@ -21429,6 +21933,10 @@ RED.view = (function() {
21429
21933
  RED.group.markDirty(group);
21430
21934
  }
21431
21935
  }
21936
+ } else if (node.type === 'junction') {
21937
+ var result = RED.nodes.removeJunction(node)
21938
+ removedJunctions.push(node);
21939
+ removedLinks = removedLinks.concat(result.links);
21432
21940
  } else {
21433
21941
  if (node.direction === "out") {
21434
21942
  removedSubflowOutputs.push(node);
@@ -21473,7 +21981,7 @@ RED.view = (function() {
21473
21981
  subflowInstances = instances.instances;
21474
21982
  }
21475
21983
  movingSet.clear();
21476
- if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus || removedGroups.length > 0) {
21984
+ if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus || removedGroups.length > 0 || removedJunctions.length > 0) {
21477
21985
  RED.nodes.dirty(true);
21478
21986
  }
21479
21987
  }
@@ -21520,6 +22028,7 @@ RED.view = (function() {
21520
22028
  nodes:removedNodes,
21521
22029
  links:removedLinks,
21522
22030
  groups: removedGroups,
22031
+ junctions: removedJunctions,
21523
22032
  subflowOutputs:removedSubflowOutputs,
21524
22033
  subflowInputs:removedSubflowInputs,
21525
22034
  subflow: {
@@ -21579,6 +22088,7 @@ RED.view = (function() {
21579
22088
  var nns = [];
21580
22089
  var nodeCount = 0;
21581
22090
  var groupCount = 0;
22091
+ var junctionCount = 0;
21582
22092
  var handled = {};
21583
22093
  for (var n=0;n<nodes.length;n++) {
21584
22094
  var node = nodes[n];
@@ -21591,6 +22101,8 @@ RED.view = (function() {
21591
22101
  if (node.type != "subflow") {
21592
22102
  if (node.type === "group") {
21593
22103
  groupCount++;
22104
+ } else if (node.type === 'junction') {
22105
+ junctionCount++;
21594
22106
  } else {
21595
22107
  nodeCount++;
21596
22108
  }
@@ -21773,6 +22285,7 @@ RED.view = (function() {
21773
22285
  evt.preventDefault();
21774
22286
  }
21775
22287
 
22288
+
21776
22289
  function portMouseUp(d,portType,portIndex,evt) {
21777
22290
  if (RED.view.DEBUG) { console.warn("portMouseUp", mouse_mode,d,portType,portIndex); }
21778
22291
  evt = evt || d3.event;
@@ -22155,6 +22668,52 @@ RED.view = (function() {
22155
22668
  port.classed("red-ui-flow-port-hovered",false);
22156
22669
  }
22157
22670
 
22671
+ function junctionMouseOver(junction, d) {
22672
+ junction.classed("red-ui-flow-junction-hovered",true);
22673
+ }
22674
+ function junctionMouseOut(junction, d) {
22675
+ junction.classed("red-ui-flow-junction-hovered",false);
22676
+ }
22677
+
22678
+ function junctionMouseDown(junction, d, evt) {
22679
+ if (RED.view.DEBUG) { console.warn("junctionMouseDown", d); }
22680
+ evt = evt || d3.event;
22681
+ d3.event = evt
22682
+ if (evt === 1) {
22683
+ return;
22684
+ }
22685
+ if (mouse_mode === RED.state.SELECTING_NODE) {
22686
+ evt.stopPropagation();
22687
+ return;
22688
+ }
22689
+ if (mouse_mode == RED.state.QUICK_JOINING) {
22690
+ d3.event.stopPropagation();
22691
+ return;
22692
+ }
22693
+
22694
+ // mousedown_node = d;
22695
+ // mousedown_port_type = portType;
22696
+ // mousedown_port_index = portIndex || 0;
22697
+ if (mouse_mode !== RED.state.QUICK_JOINING && (evt.ctrlKey || evt.metaKey)) {
22698
+ mouse_mode = RED.state.QUICK_JOINING;
22699
+ document.body.style.cursor = "crosshair";
22700
+ showDragLines([{node:d,port:0,portType: PORT_TYPE_OUTPUT}]);
22701
+ $(window).on('keyup',disableQuickJoinEventHandler);
22702
+ } else if (event.button != 2) {
22703
+ nodeMouseDown.call(junction[0][0],d)
22704
+ // clearSelection();
22705
+ // movingSet.add(d);
22706
+ // mousedown_node = d;
22707
+ // mouse_mode = RED.state.MOVING;
22708
+ // var mouse = d3.touches(junction[0][0])[0]||d3.mouse(junction[0][0]);
22709
+ // mouse[0] += d.x-d.w/2;
22710
+ // mouse[1] += d.y-d.h/2;
22711
+ // prepareDrag(mouse);
22712
+ }
22713
+ evt.stopPropagation();
22714
+ evt.preventDefault();
22715
+ }
22716
+
22158
22717
  function prepareDrag(mouse) {
22159
22718
  mouse_mode = RED.state.MOVING;
22160
22719
  // Called when movingSet should be prepared to be dragged
@@ -22347,8 +22906,6 @@ RED.view = (function() {
22347
22906
  )
22348
22907
  lastClickNode = mousedown_node;
22349
22908
 
22350
- var i;
22351
-
22352
22909
  if (!d.selected && d.g /*&& !RED.nodes.group(d.g).selected*/) {
22353
22910
  var nodeGroup = RED.nodes.group(d.g);
22354
22911
 
@@ -22382,7 +22939,6 @@ RED.view = (function() {
22382
22939
  enterActiveGroup(ag);
22383
22940
  activeGroup.selected = true;
22384
22941
  }
22385
- console.log(d3.event);
22386
22942
  var cnodes = RED.nodes.getAllFlowNodes(mousedown_node);
22387
22943
  for (var n=0;n<cnodes.length;n++) {
22388
22944
  if (!cnodes[n].selected) {
@@ -22621,6 +23177,11 @@ RED.view = (function() {
22621
23177
  function portMouseOverProxy(e) { portMouseOver(d3.select(this), this.__data__,this.__portType__,this.__portIndex__, e); }
22622
23178
  function portMouseOutProxy(e) { portMouseOut(d3.select(this), this.__data__,this.__portType__,this.__portIndex__, e); }
22623
23179
 
23180
+ function junctionMouseOverProxy(e) { junctionMouseOver(d3.select(this), this.__data__) }
23181
+ function junctionMouseOutProxy(e) { junctionMouseOut(d3.select(this), this.__data__) }
23182
+ function junctionMouseDownProxy(e) { junctionMouseDown(d3.select(this), this.__data__, e) }
23183
+ function junctionMouseUpProxy(e) { junctionMouseUp(d3.select(this), this.__data__) }
23184
+
22624
23185
  function linkMouseDown(d) {
22625
23186
  if (mouse_mode === RED.state.SELECTING_NODE) {
22626
23187
  d3.event.stopPropagation();
@@ -23755,6 +24316,62 @@ RED.view = (function() {
23755
24316
  })
23756
24317
  }
23757
24318
 
24319
+ var junction = junctionLayer.selectAll(".red-ui-flow-junction").data(
24320
+ activeJunctions,
24321
+ d => d.id
24322
+ )
24323
+ var junctionEnter = junction.enter().insert("svg:g").attr("class","red-ui-flow-junction")
24324
+ junctionEnter.each(function(d,i) {
24325
+ var junction = d3.select(this);
24326
+ var contents = document.createDocumentFragment();
24327
+ // d.added = true;
24328
+ var junctionBack = document.createElementNS("http://www.w3.org/2000/svg","rect");
24329
+ junctionBack.setAttribute("class","red-ui-flow-junction-background");
24330
+ junctionBack.setAttribute("x",-5);
24331
+ junctionBack.setAttribute("y",-5);
24332
+ junctionBack.setAttribute("width",10);
24333
+ junctionBack.setAttribute("height",10);
24334
+ junctionBack.setAttribute("rx",5);
24335
+ junctionBack.setAttribute("ry",5);
24336
+ junctionBack.__data__ = d;
24337
+ this.__junctionBack__ = junctionBack;
24338
+ contents.appendChild(junctionBack);
24339
+
24340
+ junctionBack.addEventListener("mouseover", junctionMouseOverProxy);
24341
+ junctionBack.addEventListener("mouseout", junctionMouseOutProxy);
24342
+ junctionBack.addEventListener("mouseup", portMouseUpProxy);
24343
+ junctionBack.addEventListener("mousedown", junctionMouseDownProxy);
24344
+
24345
+ // d3.select(junctionBack).on("mousedown", nodeMouseDown);
24346
+
24347
+ this.__portType__ = PORT_TYPE_INPUT
24348
+ this.__portIndex__ = 0
24349
+ // function portMouseUpProxy(e) { portMouseUp(this.__data__,this.__portType__,this.__portIndex__, e); }
24350
+
24351
+ junction[0][0].appendChild(contents);
24352
+ })
24353
+ junction.exit().remove();
24354
+ junction.each(function(d) {
24355
+ var junction = d3.select(this);
24356
+ this.setAttribute("transform", "translate(" + (d.x) + "," + (d.y) + ")");
24357
+ if (d.dirty) {
24358
+ junction.classed("selected", !!d.selected)
24359
+ dirtyNodes[d.id] = d;
24360
+
24361
+ if (d.g) {
24362
+ if (!dirtyGroups[d.g]) {
24363
+ var gg = d.g;
24364
+ while (gg && !dirtyGroups[gg]) {
24365
+ dirtyGroups[gg] = RED.nodes.group(gg);
24366
+ gg = dirtyGroups[gg].g;
24367
+ }
24368
+ }
24369
+ }
24370
+
24371
+ }
24372
+
24373
+ })
24374
+
23758
24375
  var link = linkLayer.selectAll(".red-ui-flow-link").data(
23759
24376
  activeLinks,
23760
24377
  function(d) {
@@ -23781,6 +24398,27 @@ RED.view = (function() {
23781
24398
  selectedLinks.add(d)
23782
24399
  l.classed("red-ui-flow-link-splice",true)
23783
24400
  redraw()
24401
+ } else if (mouse_mode === RED.state.SLICING_JUNCTION) {
24402
+ if (!l.classed("red-ui-flow-link-splice")) {
24403
+ // Find intersection point
24404
+ var lineLength = pathLine.getTotalLength();
24405
+ var pos;
24406
+ var delta = Infinity;
24407
+ for (var i = 0; i < lineLength; i++) {
24408
+ var linePos = pathLine.getPointAtLength(i);
24409
+ var posDeltaX = Math.abs(linePos.x-d3.event.offsetX)
24410
+ var posDeltaY = Math.abs(linePos.y-d3.event.offsetY)
24411
+ var posDelta = posDeltaX*posDeltaX + posDeltaY*posDeltaY
24412
+ if (posDelta < delta) {
24413
+ pos = linePos
24414
+ delta = posDelta
24415
+ }
24416
+ }
24417
+ d._sliceLocation = pos
24418
+ selectedLinks.add(d)
24419
+ l.classed("red-ui-flow-link-splice",true)
24420
+ redraw()
24421
+ }
23784
24422
  }
23785
24423
  })
23786
24424
 
@@ -23807,9 +24445,9 @@ RED.view = (function() {
23807
24445
  var numOutputs = d.source.outputs || 1;
23808
24446
  var sourcePort = d.sourcePort || 0;
23809
24447
  var y = -((numOutputs-1)/2)*13 +13*sourcePort;
23810
- d.x1 = d.source.x+d.source.w/2;
24448
+ d.x1 = d.source.x+(d.source.w/2||0);
23811
24449
  d.y1 = d.source.y+y;
23812
- d.x2 = d.target.x-d.target.w/2;
24450
+ d.x2 = d.target.x-(d.target.w/2||0);
23813
24451
  d.y2 = d.target.y;
23814
24452
 
23815
24453
  // return "M "+d.x1+" "+d.y1+
@@ -24223,12 +24861,16 @@ RED.view = (function() {
24223
24861
  * - addFlow - whether to import nodes to a new tab
24224
24862
  * - touchImport - whether this is a touch import. If not, imported nodes are
24225
24863
  * attachedto mouse for placing - "IMPORT_DRAGGING" state
24864
+ * - generateIds - whether to automatically generate new ids for all imported nodes
24865
+ * - generateDefaultNames - whether to automatically update any nodes with clashing
24866
+ * default names
24226
24867
  */
24227
24868
  function importNodes(newNodesObj,options) {
24228
24869
  options = options || {
24229
24870
  addFlow: false,
24230
24871
  touchImport: false,
24231
- generateIds: false
24872
+ generateIds: false,
24873
+ generateDefaultNames: false
24232
24874
  }
24233
24875
  var addNewFlow = options.addFlow
24234
24876
  var touchImport = options.touchImport;
@@ -24256,7 +24898,13 @@ RED.view = (function() {
24256
24898
  if (!$.isArray(nodesToImport)) {
24257
24899
  nodesToImport = [nodesToImport];
24258
24900
  }
24259
-
24901
+ if (options.generateDefaultNames) {
24902
+ RED.actions.invoke("core:generate-node-names", nodesToImport, {
24903
+ renameBlank: false,
24904
+ renameClash: true,
24905
+ generateHistory: false
24906
+ })
24907
+ }
24260
24908
 
24261
24909
  try {
24262
24910
  var activeSubflowChanged;
@@ -24268,6 +24916,7 @@ RED.view = (function() {
24268
24916
  var new_nodes = result.nodes;
24269
24917
  var new_links = result.links;
24270
24918
  var new_groups = result.groups;
24919
+ var new_junctions = result.junctions;
24271
24920
  var new_workspaces = result.workspaces;
24272
24921
  var new_subflows = result.subflows;
24273
24922
  var removedNodes = result.removedNodes;
@@ -24277,6 +24926,7 @@ RED.view = (function() {
24277
24926
  }
24278
24927
  var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() });
24279
24928
  new_ms = new_ms.concat(new_groups.filter(function(g) { return g.z === RED.workspaces.active()}))
24929
+ new_ms = new_ms.concat(new_junctions.filter(function(j) { return j.z === RED.workspaces.active()}))
24280
24930
  var new_node_ids = new_nodes.map(function(n){ n.changed = true; return n.id; });
24281
24931
 
24282
24932
  clearSelection();
@@ -24310,9 +24960,11 @@ RED.view = (function() {
24310
24960
  node.n.moved = true;
24311
24961
  node.n.x -= dx - mouse_position[0];
24312
24962
  node.n.y -= dy - mouse_position[1];
24313
- node.n.w = node_width;
24314
- node.n.h = node_height;
24315
- node.n.resize = true;
24963
+ if (node.n.type !== 'junction') {
24964
+ node.n.w = node_width;
24965
+ node.n.h = node_height;
24966
+ node.n.resize = true;
24967
+ }
24316
24968
  node.dx = node.n.x - mouse_position[0];
24317
24969
  node.dy = node.n.y - mouse_position[1];
24318
24970
  if (node.n.type === "group") {
@@ -24359,6 +25011,7 @@ RED.view = (function() {
24359
25011
  nodes:new_node_ids,
24360
25012
  links:new_links,
24361
25013
  groups:new_groups,
25014
+ junctions: new_junctions,
24362
25015
  workspaces:new_workspaces,
24363
25016
  subflows:new_subflows,
24364
25017
  dirty:RED.nodes.dirty()
@@ -24406,6 +25059,7 @@ RED.view = (function() {
24406
25059
  }
24407
25060
  })
24408
25061
  var newGroupCount = new_groups.length;
25062
+ var newJunctionCount = new_junctions.length;
24409
25063
  if (new_workspaces.length > 0) {
24410
25064
  counts.push(RED._("clipboard.flow",{count:new_workspaces.length}));
24411
25065
  }
@@ -24542,6 +25196,93 @@ RED.view = (function() {
24542
25196
  return selection;
24543
25197
  }
24544
25198
 
25199
+ /**
25200
+ * Create a node from a type string.
25201
+ * **NOTE:** Can throw on error - use `try` `catch` block when calling
25202
+ * @param {string} type The node type to create
25203
+ * @param {number} [x] (optional) The horizontal position on the workspace
25204
+ * @param {number} [y] (optional)The vertical on the workspace
25205
+ * @param {string} [z] (optional) The flow tab this node will belong to. Defaults to active workspace.
25206
+ * @returns An object containing the `node` and a `historyEvent`
25207
+ * @private
25208
+ */
25209
+ function createNode(type, x, y, z) {
25210
+ var m = /^subflow:(.+)$/.exec(type);
25211
+ var activeSubflow = z ? RED.nodes.subflow(z) : null;
25212
+ if (activeSubflow && m) {
25213
+ var subflowId = m[1];
25214
+ if (subflowId === activeSubflow.id) {
25215
+ throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddSubflowToItself") }))
25216
+ }
25217
+ if (RED.nodes.subflowContains(m[1], activeSubflow.id)) {
25218
+ throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddCircularReference") }))
25219
+ }
25220
+ }
25221
+
25222
+ var nn = { id: RED.nodes.id(), z: z || RED.workspaces.active() };
25223
+
25224
+ nn.type = type;
25225
+ nn._def = RED.nodes.getType(nn.type);
25226
+
25227
+ if (!m) {
25228
+ nn.inputs = nn._def.inputs || 0;
25229
+ nn.outputs = nn._def.outputs;
25230
+
25231
+ for (var d in nn._def.defaults) {
25232
+ if (nn._def.defaults.hasOwnProperty(d)) {
25233
+ if (nn._def.defaults[d].value !== undefined) {
25234
+ nn[d] = JSON.parse(JSON.stringify(nn._def.defaults[d].value));
25235
+ }
25236
+ }
25237
+ }
25238
+
25239
+ if (nn._def.onadd) {
25240
+ try {
25241
+ nn._def.onadd.call(nn);
25242
+ } catch (err) {
25243
+ console.log("Definition error: " + nn.type + ".onadd:", err);
25244
+ }
25245
+ }
25246
+ } else {
25247
+ var subflow = RED.nodes.subflow(m[1]);
25248
+ nn.name = "";
25249
+ nn.inputs = subflow.in.length;
25250
+ nn.outputs = subflow.out.length;
25251
+ }
25252
+
25253
+ nn.changed = true;
25254
+ nn.moved = true;
25255
+
25256
+ nn.w = RED.view.node_width;
25257
+ nn.h = Math.max(RED.view.node_height, (nn.outputs || 0) * 15);
25258
+ nn.resize = true;
25259
+ if (x != null && typeof x == "number" && x >= 0) {
25260
+ nn.x = x;
25261
+ }
25262
+ if (y != null && typeof y == "number" && y >= 0) {
25263
+ nn.y = y;
25264
+ }
25265
+ var historyEvent = {
25266
+ t: "add",
25267
+ nodes: [nn.id],
25268
+ dirty: RED.nodes.dirty()
25269
+ }
25270
+ if (activeSubflow) {
25271
+ var subflowRefresh = RED.subflow.refresh(true);
25272
+ if (subflowRefresh) {
25273
+ historyEvent.subflow = {
25274
+ id: activeSubflow.id,
25275
+ changed: activeSubflow.changed,
25276
+ instances: subflowRefresh.instances
25277
+ }
25278
+ }
25279
+ }
25280
+ return {
25281
+ node: nn,
25282
+ historyEvent: historyEvent
25283
+ }
25284
+ }
25285
+
24545
25286
  function calculateNodeDimensions(node) {
24546
25287
  var result = [node_width,node_height];
24547
25288
  try {
@@ -24565,6 +25306,37 @@ RED.view = (function() {
24565
25306
  return result;
24566
25307
  }
24567
25308
 
25309
+
25310
+ function flashNode(n) {
25311
+ let node = n;
25312
+ if(typeof node === "string") { node = RED.nodes.node(n); }
25313
+ if(!node) { return; }
25314
+
25315
+ const flashingNode = flashingNodeTimer && flashingNodeId && RED.nodes.node(flashingNodeId);
25316
+ if(flashingNode) {
25317
+ //cancel current flashing node before flashing new node
25318
+ clearInterval(flashingNodeTimer);
25319
+ flashingNodeTimer = null;
25320
+ flashingNode.dirty = true;
25321
+ flashingNode.highlighted = false;
25322
+ }
25323
+
25324
+ flashingNodeTimer = setInterval(function(flashEndTime) {
25325
+ node.dirty = true;
25326
+ if (flashEndTime >= Date.now()) {
25327
+ node.highlighted = !node.highlighted;
25328
+ } else {
25329
+ clearInterval(flashingNodeTimer);
25330
+ flashingNodeTimer = null;
25331
+ node.highlighted = false;
25332
+ flashingNodeId = null;
25333
+ }
25334
+ RED.view.redraw();
25335
+ }, 100, Date.now() + 2200)
25336
+ flashingNodeId = node.id;
25337
+ node.highlighted = true;
25338
+ RED.view.redraw();
25339
+ }
24568
25340
  return {
24569
25341
  init: init,
24570
25342
  state:function(state) {
@@ -24625,7 +25397,21 @@ RED.view = (function() {
24625
25397
  redraw(true);
24626
25398
  },
24627
25399
  selection: getSelection,
24628
-
25400
+ clearSelection: clearSelection,
25401
+ createNode: createNode,
25402
+ /** default node width */
25403
+ get node_width() {
25404
+ return node_width;
25405
+ },
25406
+ /** default node height */
25407
+ get node_height() {
25408
+ return node_height;
25409
+ },
25410
+ /** snap to grid option state */
25411
+ get snapGrid() {
25412
+ return snapGrid;
25413
+ },
25414
+ /** gets the current scale factor */
24629
25415
  scale: function() {
24630
25416
  return scaleFactor;
24631
25417
  },
@@ -24647,7 +25433,7 @@ RED.view = (function() {
24647
25433
  getActiveGroup: function() { return activeGroup },
24648
25434
  reveal: function(id,triggerHighlight) {
24649
25435
  if (RED.nodes.workspace(id) || RED.nodes.subflow(id)) {
24650
- RED.workspaces.show(id);
25436
+ RED.workspaces.show(id, null, null, true);
24651
25437
  } else {
24652
25438
  var node = RED.nodes.node(id) || RED.nodes.group(id);
24653
25439
  if (node) {
@@ -24655,7 +25441,7 @@ RED.view = (function() {
24655
25441
  node.dirty = true;
24656
25442
  RED.workspaces.show(node.z);
24657
25443
 
24658
- var screenSize = [chart.width()/scaleFactor,chart.height()/scaleFactor];
25444
+ var screenSize = [chart[0].clientWidth/scaleFactor,chart[0].clientHeight/scaleFactor];
24659
25445
  var scrollPos = [chart.scrollLeft()/scaleFactor,chart.scrollTop()/scaleFactor];
24660
25446
  var cx = node.x;
24661
25447
  var cy = node.y;
@@ -24672,24 +25458,7 @@ RED.view = (function() {
24672
25458
  },200);
24673
25459
  }
24674
25460
  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
- }
25461
+ flashNode(node);
24693
25462
  }
24694
25463
  } else if (node._def.category === 'config') {
24695
25464
  RED.sidebar.config.show(id);
@@ -25922,6 +26691,244 @@ RED.view.tools = (function() {
25922
26691
  }
25923
26692
  }
25924
26693
 
26694
+ /**
26695
+ * Splits selected wires and re-joins them with link-out+link-in
26696
+ * @param {Object || Object[]} wires The wire(s) to split and replace with link-out, link-in nodes.
26697
+ */
26698
+ function splitWiresWithLinkNodes(wires) {
26699
+ let wiresToSplit = wires || RED.view.selection().links;
26700
+ if (!Array.isArray(wiresToSplit)) {
26701
+ wiresToSplit = [wiresToSplit];
26702
+ }
26703
+ if (wiresToSplit.length < 1) {
26704
+ return; //nothing selected
26705
+ }
26706
+
26707
+ const history = {
26708
+ t: 'multi',
26709
+ events: [],
26710
+ dirty: RED.nodes.dirty()
26711
+ }
26712
+ const nodeSrcMap = {};
26713
+ const nodeTrgMap = {};
26714
+ const _gridSize = RED.view.gridSize();
26715
+
26716
+ for (let wireIdx = 0; wireIdx < wiresToSplit.length; wireIdx++) {
26717
+ const wire = wiresToSplit[wireIdx];
26718
+
26719
+ //get source and target nodes of this wire link
26720
+ const nSrc = wire.source;
26721
+ const nTrg = wire.target;
26722
+
26723
+ var updateNewNodePosXY = function (origNode, newNode, alignLeft, snap, yOffset) {
26724
+ const nnSize = RED.view.calculateNodeDimensions(newNode);
26725
+ newNode.w = nnSize[0];
26726
+ newNode.h = nnSize[1];
26727
+ const coords = { x: origNode.x || 0, y: origNode.y || 0, w: origNode.w || RED.view.node_width, h: origNode.h || RED.view.node_height };
26728
+ const x = coords.x - (coords.w/2.0);
26729
+ if (alignLeft) {
26730
+ coords.x = x - _gridSize - (newNode.w/2.0);
26731
+ } else {
26732
+ coords.x = x + coords.w + _gridSize + (newNode.w/2.0);
26733
+ }
26734
+ newNode.x = coords.x;
26735
+ newNode.y = coords.y;
26736
+ if (snap !== false) {
26737
+ const offsets = RED.view.tools.calculateGridSnapOffsets(newNode);
26738
+ newNode.x -= offsets.x;
26739
+ newNode.y -= offsets.y;
26740
+ }
26741
+ newNode.y += (yOffset || 0);
26742
+ }
26743
+ const srcPort = (wire.sourcePort || 0);
26744
+ let linkOutMapId = nSrc.id + ':' + srcPort;
26745
+ let nnLinkOut = nodeSrcMap[linkOutMapId];
26746
+ //Create a Link Out if one is not already present
26747
+ if(!nnLinkOut) {
26748
+ const nLinkOut = RED.view.createNode("link out"); //create link node
26749
+ nnLinkOut = nLinkOut.node;
26750
+ nodeSrcMap[linkOutMapId] = nnLinkOut;
26751
+ let yOffset = 0;
26752
+ if(nSrc.outputs > 1) {
26753
+
26754
+ const CENTER_PORT = (((nSrc.outputs-1) / 2) + 1);
26755
+ const offsetCount = Math.abs(CENTER_PORT - (srcPort + 1));
26756
+ yOffset = (_gridSize * 2 * offsetCount);
26757
+ if((srcPort + 1) < CENTER_PORT) {
26758
+ yOffset = -yOffset;
26759
+ }
26760
+ updateNewNodePosXY(nSrc, nnLinkOut, false, false, yOffset);
26761
+ } else {
26762
+ updateNewNodePosXY(nSrc, nnLinkOut, false, RED.view.snapGrid, yOffset);
26763
+ }
26764
+ //add created node
26765
+ RED.nodes.add(nnLinkOut);
26766
+ RED.editor.validateNode(nnLinkOut);
26767
+ history.events.push(nLinkOut.historyEvent);
26768
+ //connect node to link node
26769
+ const link = {
26770
+ source: nSrc,
26771
+ sourcePort: wire.sourcePort || 0,
26772
+ target: nnLinkOut
26773
+ };
26774
+ RED.nodes.addLink(link);
26775
+ history.events.push({
26776
+ t: 'add',
26777
+ links: [link],
26778
+ });
26779
+ }
26780
+
26781
+ let nnLinkIn = nodeTrgMap[nTrg.id];
26782
+ //Create a Link In if one is not already present
26783
+ if(!nnLinkIn) {
26784
+ const nLinkIn = RED.view.createNode("link in"); //create link node
26785
+ nnLinkIn = nLinkIn.node;
26786
+ nodeTrgMap[nTrg.id] = nnLinkIn;
26787
+ updateNewNodePosXY(nTrg, nnLinkIn, true, RED.view.snapGrid, 0);
26788
+ //add created node
26789
+ RED.nodes.add(nnLinkIn);
26790
+ RED.editor.validateNode(nnLinkIn);
26791
+ history.events.push(nLinkIn.historyEvent);
26792
+ //connect node to link node
26793
+ const link = {
26794
+ source: nnLinkIn,
26795
+ sourcePort: 0,
26796
+ target: nTrg
26797
+ };
26798
+ RED.nodes.addLink(link);
26799
+ history.events.push({
26800
+ t: 'add',
26801
+ links: [link],
26802
+ });
26803
+ }
26804
+
26805
+ //connect the link out/link in virtual wires
26806
+ if(nnLinkIn.links.indexOf(nnLinkOut.id) == -1) {
26807
+ nnLinkIn.links.push(nnLinkOut.id);
26808
+ }
26809
+ if(nnLinkOut.links.indexOf(nnLinkIn.id) == -1) {
26810
+ nnLinkOut.links.push(nnLinkIn.id);
26811
+ }
26812
+
26813
+ //delete the original wire
26814
+ RED.nodes.removeLink(wire);
26815
+ history.events.push({
26816
+ t: "delete",
26817
+ links: [wire]
26818
+ });
26819
+ }
26820
+ //add all history events to stack
26821
+ RED.history.push(history);
26822
+
26823
+ //select all downstream of new link-in nodes so user can drag to new location
26824
+ RED.view.clearSelection();
26825
+ RED.view.select({nodes: Object.values(nodeTrgMap) });
26826
+ selectConnected("down");
26827
+
26828
+ //update the view
26829
+ RED.nodes.dirty(true);
26830
+ RED.view.redraw(true);
26831
+ }
26832
+
26833
+ /**
26834
+ * Calculate the required offsets to snap a node
26835
+ * @param {Object} node The node to calculate grid snap offsets for
26836
+ * @param {Object} [options] Options: `align` can be "nearest", "left" or "right"
26837
+ * @returns `{x:number, y:number}` as the offsets to deduct from `x` and `y`
26838
+ */
26839
+ function calculateGridSnapOffsets(node, options) {
26840
+ options = options || { align: "nearest" };
26841
+ const gridOffset = { x: 0, y: 0 };
26842
+ const gridSize = RED.view.gridSize();
26843
+ const offsetLeft = node.x - (gridSize * Math.round((node.x - node.w / 2) / gridSize) + node.w / 2);
26844
+ const offsetRight = node.x - (gridSize * Math.round((node.x + node.w / 2) / gridSize) - node.w / 2);
26845
+ gridOffset.x = offsetRight;
26846
+ if (options.align === "right") {
26847
+ //skip - already set to right
26848
+ } else if (options.align === "left" || Math.abs(offsetLeft) < Math.abs(offsetRight)) {
26849
+ gridOffset.x = offsetLeft;
26850
+ }
26851
+ gridOffset.y = node.y - (gridSize * Math.round(node.y / gridSize));
26852
+ return gridOffset;
26853
+ }
26854
+
26855
+ /**
26856
+ * Generate names for the select nodes.
26857
+ * - it only sets the name if it is currently blank
26858
+ * - it uses `<paletteLabel> <N>` - where N is the next available integer that
26859
+ * doesn't clash with any existing nodes of that type
26860
+ * @param {Object} node The node to set the name of - if not provided, uses current selection
26861
+ */
26862
+ function generateNodeNames(node, options) {
26863
+ options = options || {
26864
+ renameBlank: true,
26865
+ renameClash: true,
26866
+ generateHistory: true
26867
+ }
26868
+ let nodes = node;
26869
+ if (node) {
26870
+ if (!Array.isArray(node)) {
26871
+ nodes = [ node ]
26872
+ }
26873
+ } else {
26874
+ nodes = RED.view.selection().nodes;
26875
+ }
26876
+ if (nodes && nodes.length > 0) {
26877
+ // Generate history event if using the workspace selection,
26878
+ // or if the provided node already exists
26879
+ const generateHistory = options.generateHistory && (!node || !!RED.nodes.node(node.id))
26880
+ const historyEvents = []
26881
+ const typeIndex = {}
26882
+ let changed = false;
26883
+ nodes.forEach(n => {
26884
+ const nodeDef = n._def || RED.nodes.getType(n.type)
26885
+ if (nodeDef && nodeDef.defaults && nodeDef.defaults.name) {
26886
+ const paletteLabel = RED.utils.getPaletteLabel(n.type, nodeDef)
26887
+ const defaultNodeNameRE = new RegExp('^'+paletteLabel+' (\\d+)$')
26888
+ if (!typeIndex.hasOwnProperty(n.type)) {
26889
+ const existingNodes = RED.nodes.filterNodes({type: n.type})
26890
+ let maxNameNumber = 0;
26891
+ existingNodes.forEach(n => {
26892
+ let match = defaultNodeNameRE.exec(n.name)
26893
+ if (match) {
26894
+ let nodeNumber = parseInt(match[1])
26895
+ if (nodeNumber > maxNameNumber) {
26896
+ maxNameNumber = nodeNumber
26897
+ }
26898
+ }
26899
+ })
26900
+ typeIndex[n.type] = maxNameNumber + 1
26901
+ }
26902
+ if ((options.renameBlank && n.name === '') || (options.renameClash && defaultNodeNameRE.test(n.name))) {
26903
+ if (generateHistory) {
26904
+ historyEvents.push({
26905
+ t:'edit',
26906
+ node: n,
26907
+ changes: { name: n.name },
26908
+ dirty: RED.nodes.dirty(),
26909
+ changed: n.changed
26910
+ })
26911
+ }
26912
+ n.name = paletteLabel+" "+typeIndex[n.type]
26913
+ n.dirty = true
26914
+ typeIndex[n.type]++
26915
+ changed = true
26916
+ }
26917
+ }
26918
+ })
26919
+ if (changed) {
26920
+ if (historyEvents.length > 0) {
26921
+ RED.history.push({
26922
+ t: 'multi',
26923
+ events: historyEvents
26924
+ })
26925
+ }
26926
+ RED.nodes.dirty(true)
26927
+ RED.view.redraw()
26928
+ }
26929
+ }
26930
+ }
26931
+
25925
26932
  return {
25926
26933
  init: function() {
25927
26934
  RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); })
@@ -25983,6 +26990,10 @@ RED.view.tools = (function() {
25983
26990
  RED.actions.add("core:wire-series-of-nodes", function() { wireSeriesOfNodes() })
25984
26991
  RED.actions.add("core:wire-node-to-multiple", function() { wireNodeToMultiple() })
25985
26992
 
26993
+ RED.actions.add("core:split-wire-with-link-nodes", function () { splitWiresWithLinkNodes() });
26994
+
26995
+ RED.actions.add("core:generate-node-names", generateNodeNames )
26996
+
25986
26997
  // RED.actions.add("core:add-node", function() { addNode() })
25987
26998
  },
25988
26999
  /**
@@ -25994,7 +27005,8 @@ RED.view.tools = (function() {
25994
27005
  * @param {Number} dx
25995
27006
  * @param {Number} dy
25996
27007
  */
25997
- moveSelection: moveSelection
27008
+ moveSelection: moveSelection,
27009
+ calculateGridSnapOffsets: calculateGridSnapOffsets
25998
27010
  }
25999
27011
 
26000
27012
  })();
@@ -26512,14 +27524,7 @@ RED.palette = (function() {
26512
27524
 
26513
27525
  var d = $('<div>',{class:"red-ui-palette-node"}).attr("data-palette-type",nt).data('category',rootCategory);
26514
27526
 
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
- }
27527
+ var label = RED.utils.getPaletteLabel(nt, def);///^(.*?)([ -]in|[ -]out)?$/.exec(nt)[1];
26523
27528
 
26524
27529
  $('<div/>', {
26525
27530
  class: "red-ui-palette-label"+(((!def.align && def.inputs !== 0 && def.outputs === 0) || "right" === def.align) ? " red-ui-palette-label-right" : "")
@@ -27149,6 +28154,7 @@ RED.sidebar.info = (function() {
27149
28154
  });
27150
28155
  return el;
27151
28156
  }
28157
+
27152
28158
  function refresh(node) {
27153
28159
  if (node === undefined) {
27154
28160
  refreshSelection();
@@ -27257,7 +28263,7 @@ RED.sidebar.info = (function() {
27257
28263
  objectType = "group";
27258
28264
  }
27259
28265
  $(propRow.children()[0]).text(RED._("sidebar.info."+objectType))
27260
- RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]);
28266
+ RED.utils.createObjectElement(node.id,{sourceId: node.id}).appendTo(propRow.children()[1]);
27261
28267
 
27262
28268
  if (node.type === "tab" || node.type === "subflow") {
27263
28269
  // If nothing is selected, but we're on a flow or subflow tab.
@@ -27287,8 +28293,8 @@ RED.sidebar.info = (function() {
27287
28293
  if (typeCounts.groups > 0) {
27288
28294
  $('<div>').text(RED._("clipboard.group",{count:typeCounts.groups})).appendTo(counts);
27289
28295
  }
27290
-
27291
-
28296
+ } else if (node.type === 'junction') {
28297
+ propertiesPanelHeaderHelp.hide();
27292
28298
  } else {
27293
28299
  propertiesPanelHeaderHelp.show();
27294
28300
 
@@ -27351,7 +28357,7 @@ RED.sidebar.info = (function() {
27351
28357
 
27352
28358
  }
27353
28359
  } else {
27354
- RED.utils.createObjectElement(val).appendTo(propRow.children()[1]);
28360
+ RED.utils.createObjectElement(val,{sourceId: node.id}).appendTo(propRow.children()[1]);
27355
28361
  }
27356
28362
  }
27357
28363
  }
@@ -27417,6 +28423,7 @@ RED.sidebar.info = (function() {
27417
28423
 
27418
28424
 
27419
28425
  }
28426
+
27420
28427
  function setInfoText(infoText,target) {
27421
28428
  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
28429
  info.find(".red-ui-text-bidi-aware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "<span></span>" );
@@ -27433,6 +28440,7 @@ RED.sidebar.info = (function() {
27433
28440
  $(this).toggleClass('expanded',!isExpanded);
27434
28441
  })
27435
28442
  }
28443
+
27436
28444
  var tips = (function() {
27437
28445
  var enabled = true;
27438
28446
  var startDelay = 1000;
@@ -27842,14 +28850,7 @@ RED.sidebar.info = (function() {
27842
28850
 
27843
28851
  }
27844
28852
  },
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
- ]
28853
+ options: RED.search.getSearchOptions()
27853
28854
  });
27854
28855
 
27855
28856
  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 +28862,18 @@ RED.sidebar.info = (function() {
27861
28862
  data:getFlowData()
27862
28863
  })
27863
28864
  treeList.on('treelistselect', function(e,item) {
27864
- var node = RED.nodes.node(item.id) || RED.nodes.group(item.id);
28865
+ var node = RED.nodes.node(item.id) || RED.nodes.group(item.id) || RED.nodes.workspace(item.id) || RED.nodes.subflow(item.id);
27865
28866
  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
- }
28867
+ RED.sidebar.info.refresh(node);
28868
+ // if (node.type === 'group' || node._def.category !== "config") {
28869
+ // // RED.view.select({nodes:[node]})
28870
+ // } else if (node._def.category === "config") {
28871
+ // RED.sidebar.info.refresh(node);
28872
+ // } else {
28873
+ // // RED.view.select({nodes:[]})
28874
+ // }
28875
+ } else {
28876
+ RED.sidebar.info.refresh(null);
27873
28877
  }
27874
28878
  })
27875
28879
  treeList.on('treelistconfirm', function(e,item) {
@@ -28632,7 +29636,7 @@ RED.sidebar.help = (function() {
28632
29636
  var node = selection.nodes[0];
28633
29637
  if (node.type === "subflow" && node.direction) {
28634
29638
  // ignore subflow virtual ports
28635
- } else if (node.type !== 'group'){
29639
+ } else if (node.type !== 'group' && node.type !== 'junction'){
28636
29640
  showNodeTypeHelp(node.type);
28637
29641
  }
28638
29642
  }
@@ -28741,6 +29745,8 @@ RED.sidebar.help = (function() {
28741
29745
  **/
28742
29746
  RED.sidebar.config = (function() {
28743
29747
 
29748
+ let flashingConfigNode;
29749
+ let flashingConfigNodeTimer;
28744
29750
 
28745
29751
  var content = document.createElement("div");
28746
29752
  content.className = "red-ui-sidebar-node-config";
@@ -28871,6 +29877,7 @@ RED.sidebar.config = (function() {
28871
29877
  var entry = $('<li class="red-ui-palette-node_id_'+node.id.replace(/\./g,"-")+'"></li>').appendTo(list);
28872
29878
  var nodeDiv = $('<div class="red-ui-palette-node-config red-ui-palette-node"></div>').appendTo(entry);
28873
29879
  entry.data('node',node.id);
29880
+ nodeDiv.data('node',node.id);
28874
29881
  var label = $('<div class="red-ui-palette-label"></div>').text(label).appendTo(nodeDiv);
28875
29882
  if (node.d) {
28876
29883
  nodeDiv.addClass("red-ui-palette-node-config-disabled");
@@ -29072,10 +30079,36 @@ RED.sidebar.config = (function() {
29072
30079
  refreshConfigNodeList();
29073
30080
  }
29074
30081
  });
29075
- RED.popover.tooltip($('#red-ui-sidebar-config-filter-all'), RED._("sidebar.config.showAllUnusedConfigNodes"));
30082
+ RED.popover.tooltip($('#red-ui-sidebar-config-filter-all'), RED._("sidebar.config.showAllConfigNodes"));
29076
30083
  RED.popover.tooltip($('#red-ui-sidebar-config-filter-unused'), RED._("sidebar.config.showAllUnusedConfigNodes"));
29077
30084
 
29078
30085
  }
30086
+
30087
+ function flashConfigNode(el) {
30088
+ if(flashingConfigNode && flashingConfigNode.length) {
30089
+ //cancel current flashing node before flashing new node
30090
+ clearInterval(flashingConfigNodeTimer);
30091
+ flashingConfigNodeTimer = null;
30092
+ flashingConfigNode.children("div").removeClass('highlighted');
30093
+ flashingConfigNode = null;
30094
+ }
30095
+ if(!el || !el.children("div").length) { return; }
30096
+
30097
+ flashingConfigNodeTimer = setInterval(function(flashEndTime) {
30098
+ if (flashEndTime >= Date.now()) {
30099
+ const highlighted = el.children("div").hasClass("highlighted");
30100
+ el.children("div").toggleClass('highlighted', !highlighted)
30101
+ } else {
30102
+ clearInterval(flashingConfigNodeTimer);
30103
+ flashingConfigNodeTimer = null;
30104
+ flashingConfigNode = null;
30105
+ el.children("div").removeClass('highlighted');
30106
+ }
30107
+ }, 100, Date.now() + 2200);
30108
+ flashingConfigNode = el;
30109
+ el.children("div").addClass('highlighted');
30110
+ }
30111
+
29079
30112
  function show(id) {
29080
30113
  if (typeof id === 'boolean') {
29081
30114
  if (id) {
@@ -29100,19 +30133,7 @@ RED.sidebar.config = (function() {
29100
30133
  } else if (y<0) {
29101
30134
  scrollWindow.animate({scrollTop: '+='+(y-10)},150);
29102
30135
  }
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();
30136
+ flashConfigNode(node, id);
29116
30137
  },100);
29117
30138
  }
29118
30139
  RED.sidebar.show("config");
@@ -30797,7 +31818,11 @@ RED.editor = (function() {
30797
31818
  var result = [];
30798
31819
  for (var prop in definition) {
30799
31820
  if (definition.hasOwnProperty(prop)) {
30800
- if (!validateNodeProperty(node, definition, prop, properties[prop])) {
31821
+ var valid = validateNodeProperty(node, definition, prop, properties[prop]);
31822
+ if ((typeof valid) === "string") {
31823
+ result.push(valid);
31824
+ }
31825
+ else if(!valid) {
30801
31826
  result.push(prop);
30802
31827
  }
30803
31828
  }
@@ -30811,7 +31836,7 @@ RED.editor = (function() {
30811
31836
  * @param definition - the node property definitions (either def.defaults or def.creds)
30812
31837
  * @param property - the property name being validated
30813
31838
  * @param value - the property value being validated
30814
- * @returns {boolean} whether the node proprty is valid
31839
+ * @returns {boolean|string} whether the node proprty is valid. `true`: valid `false|String`: invalid
30815
31840
  */
30816
31841
  function validateNodeProperty(node,definition,property,value) {
30817
31842
  var valid = true;
@@ -30823,22 +31848,74 @@ RED.editor = (function() {
30823
31848
  if (/^\$\{[a-zA-Z_][a-zA-Z0-9_]*\}$/.test(value)) {
30824
31849
  return true;
30825
31850
  }
31851
+ var label = null;
31852
+ if (("label" in definition[property]) &&
31853
+ ((typeof definition[property].label) == "string")) {
31854
+ label = definition[property].label;
31855
+ }
30826
31856
  if ("required" in definition[property] && definition[property].required) {
30827
31857
  valid = value !== "";
31858
+ if (!valid && label) {
31859
+ return RED._("validator.errors.missing-required-prop", {
31860
+ prop: label
31861
+ });
31862
+ }
30828
31863
  }
30829
31864
  if (valid && "validate" in definition[property]) {
30830
31865
  try {
30831
- valid = definition[property].validate.call(node,value);
31866
+ var opt = {};
31867
+ if (label) {
31868
+ opt.label = label;
31869
+ }
31870
+ valid = definition[property].validate.call(node,value, opt);
31871
+ // If the validator takes two arguments, it is a 3.x validator that
31872
+ // can return a String to mean 'invalid' and provide a reason
31873
+ if ((definition[property].validate.length === 2) &&
31874
+ ((typeof valid) === "string")) {
31875
+ return valid;
31876
+ } else {
31877
+ // Otherwise, a 2.x returns a truth-like/false-like value that
31878
+ // we should cooerce to a boolean.
31879
+ valid = !!valid
31880
+ }
30832
31881
  } catch(err) {
30833
31882
  console.log("Validation error:",node.type,node.id,"property: "+property,"value:",value,err);
31883
+ return RED._("validator.errors.validation-error", {
31884
+ prop: property,
31885
+ node: node.type,
31886
+ id: node.id,
31887
+ error: err.message
31888
+ });
30834
31889
  }
30835
31890
  }
30836
31891
  if (valid && definition[property].type && RED.nodes.getType(definition[property].type) && !("validate" in definition[property])) {
30837
31892
  if (!value || value == "_ADD_") {
30838
31893
  valid = definition[property].hasOwnProperty("required") && !definition[property].required;
31894
+ if (!valid && label) {
31895
+ return RED._("validator.errors.missing-required-prop", {
31896
+ prop: label
31897
+ });
31898
+ }
30839
31899
  } else {
30840
31900
  var configNode = RED.nodes.node(value);
30841
- valid = (configNode && (configNode.valid == null || configNode.valid));
31901
+ if (configNode) {
31902
+ if ((configNode.valid == null) || configNode.valid) {
31903
+ return true;
31904
+ }
31905
+ if (label) {
31906
+ return RED._("validator.errors.invalid-config", {
31907
+ prop: label
31908
+ });
31909
+ }
31910
+ }
31911
+ else {
31912
+ if (label) {
31913
+ return RED._("validator.errors.missing-config", {
31914
+ prop: label
31915
+ });
31916
+ }
31917
+ }
31918
+ return false;
30842
31919
  }
30843
31920
  }
30844
31921
  return valid;
@@ -30866,10 +31943,26 @@ RED.editor = (function() {
30866
31943
  if (defaults[property].hasOwnProperty("format") && defaults[property].format !== "" && input[0].nodeName === "DIV") {
30867
31944
  value = input.text();
30868
31945
  }
30869
- if (!validateNodeProperty(node, defaults, property,value)) {
31946
+ var valid = validateNodeProperty(node, defaults, property,value);
31947
+ if (((typeof valid) === "string") || !valid) {
30870
31948
  input.addClass("input-error");
31949
+ if ((typeof valid) === "string") {
31950
+ var tooltip = input.data("tooltip");
31951
+ if (tooltip) {
31952
+ tooltip.setContent(valid);
31953
+ }
31954
+ else {
31955
+ tooltip = RED.popover.tooltip(input, valid);
31956
+ input.data("tooltip", tooltip);
31957
+ }
31958
+ }
30871
31959
  } else {
30872
31960
  input.removeClass("input-error");
31961
+ var tooltip = input.data("tooltip");
31962
+ if (tooltip) {
31963
+ input.data("tooltip", null);
31964
+ tooltip.delete();
31965
+ }
30873
31966
  }
30874
31967
  }
30875
31968
  }
@@ -31036,20 +32129,11 @@ RED.editor = (function() {
31036
32129
  * @param prefix - the prefix to use in the input element ids (node-input|node-config-input)
31037
32130
  */
31038
32131
  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
- }
32132
+ $("#"+prefix+"-"+property).on("change keyup paste", function(event) {
32133
+ if (!$(this).attr("skipValidation")) {
32134
+ validateNodeEditor(node,prefix);
32135
+ }
32136
+ });
31053
32137
  }
31054
32138
 
31055
32139
  /**
@@ -31483,6 +32567,7 @@ RED.editor = (function() {
31483
32567
  if (buildingEditDialog) { return }
31484
32568
  buildingEditDialog = true;
31485
32569
  var editing_node = node;
32570
+ var removeInfoEditorOnClose = false;
31486
32571
  var skipInfoRefreshOnClose = false;
31487
32572
  var activeEditPanes = [];
31488
32573
 
@@ -31678,6 +32763,14 @@ RED.editor = (function() {
31678
32763
  }
31679
32764
  if (!node._def.defaults || !node._def.defaults.hasOwnProperty('info')) {
31680
32765
  nodeEditPanes.push('editor-tab-description');
32766
+ removeInfoEditorOnClose = true;
32767
+ if(node.infoEditor) {
32768
+ //As 'editor-tab-description' adds `node.infoEditor` store original & set a
32769
+ //flag to NOT remove this property
32770
+ node.infoEditor__orig = node.infoEditor;
32771
+ delete node.infoEditor;
32772
+ removeInfoEditorOnClose = false;
32773
+ }
31681
32774
  }
31682
32775
  nodeEditPanes.push("editor-tab-appearance");
31683
32776
 
@@ -31693,8 +32786,17 @@ RED.editor = (function() {
31693
32786
  if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
31694
32787
  RED.view.state(RED.state.DEFAULT);
31695
32788
  }
31696
- if (editing_node && !skipInfoRefreshOnClose) {
31697
- RED.sidebar.info.refresh(editing_node);
32789
+ if (editing_node) {
32790
+ if (editing_node.infoEditor__orig) {
32791
+ editing_node.infoEditor = editing_node.infoEditor__orig;
32792
+ delete editing_node.infoEditor__orig;
32793
+ }
32794
+ if (removeInfoEditorOnClose) {
32795
+ delete editing_node.infoEditor;
32796
+ }
32797
+ if (!skipInfoRefreshOnClose) {
32798
+ RED.sidebar.info.refresh(editing_node);
32799
+ }
31698
32800
  }
31699
32801
  RED.workspaces.refresh();
31700
32802
 
@@ -32554,6 +33656,48 @@ RED.editor = (function() {
32554
33656
  }
32555
33657
  }
32556
33658
 
33659
+ /** Genrate a consistent but unique ID for saving and restoring the code editors view state */
33660
+ function generateViewStateId(source, thing, suffix) {
33661
+ try {
33662
+ thing = thing || {};
33663
+ const thingOptions = typeof thing.options === "object" ? thing.options : {};
33664
+ let stateId;
33665
+ if (thing.hasOwnProperty("stateId")) {
33666
+ stateId = thing.stateId
33667
+ } else if (thingOptions.hasOwnProperty("stateId")) {
33668
+ stateId = thing.stateId
33669
+ }
33670
+ if (stateId === false) { return false; }
33671
+ if (!stateId) {
33672
+ let id;
33673
+ const selection = RED.view.selection();
33674
+ if (source === "node" && thing.id) {
33675
+ id = thing.id;
33676
+ } else if (selection.nodes && selection.nodes.length) {
33677
+ id = selection.nodes[0].id;
33678
+ } else {
33679
+ return false; //cant obtain Id.
33680
+ }
33681
+ //Use a string builder to build an ID
33682
+ const sb = [id];
33683
+ //get the index of the el - there may be more than one editor.
33684
+ const el = $(thing.element || thingOptions.element);
33685
+ if(el.length) {
33686
+ sb.push(el.closest(".form-row").index());
33687
+ sb.push(el.index());
33688
+ }
33689
+ if (source == "typedInput") {
33690
+ sb.push(el.closest("li").index());//for when embeded in editable list
33691
+ if (!suffix && thing.propertyType) { suffix = thing.propertyType }
33692
+ }
33693
+ stateId = sb.join("/");
33694
+ }
33695
+ if (stateId && suffix) { stateId += "/" + suffix; }
33696
+ return stateId;
33697
+ } catch (error) {
33698
+ return false;
33699
+ }
33700
+ }
32557
33701
  return {
32558
33702
  init: function() {
32559
33703
  if(window.ace) { window.ace.config.set('basePath', 'vendor/ace'); }
@@ -32570,6 +33714,7 @@ RED.editor = (function() {
32570
33714
  });
32571
33715
  RED.editor.codeEditor.init();
32572
33716
  },
33717
+ generateViewStateId: generateViewStateId,
32573
33718
  edit: showEditDialog,
32574
33719
  editConfig: showEditConfigNodeDialog,
32575
33720
  editFlow: showEditFlowDialog,
@@ -33174,7 +34319,6 @@ RED.editor = (function() {
33174
34319
 
33175
34320
  create: function(container) {
33176
34321
  this.editor = buildDescriptionForm(container,node);
33177
- RED.e = this.editor;
33178
34322
  },
33179
34323
  resize: function(size) {
33180
34324
  this.editor.resize();
@@ -33224,11 +34368,9 @@ RED.editor = (function() {
33224
34368
  var nodeInfoEditor = RED.editor.createEditor({
33225
34369
  id: editorId,
33226
34370
  mode: 'ace/mode/markdown',
33227
- value: ""
34371
+ stateId: RED.editor.generateViewStateId("node", node, "nodeinfo"),
34372
+ value: node.info || ""
33228
34373
  });
33229
- if (node.info) {
33230
- nodeInfoEditor.getSession().setValue(node.info, -1);
33231
- }
33232
34374
  node.infoEditor = nodeInfoEditor;
33233
34375
  return nodeInfoEditor;
33234
34376
  }
@@ -33330,6 +34472,7 @@ RED.editor = (function() {
33330
34472
  this.tabflowEditor = RED.editor.createEditor({
33331
34473
  id: 'node-input-info',
33332
34474
  mode: 'ace/mode/markdown',
34475
+ stateId: options.stateId,
33333
34476
  value: ""
33334
34477
  });
33335
34478
 
@@ -33478,7 +34621,7 @@ RED.editor = (function() {
33478
34621
  newValue = "";
33479
34622
  }
33480
34623
  }
33481
- if (node[d] != newValue) {
34624
+ if (!isEqual(node[d], newValue)) {
33482
34625
  if (node._def.defaults[d].type) {
33483
34626
  // Change to a related config node
33484
34627
  var configNode = RED.nodes.node(node[d]);
@@ -33510,6 +34653,23 @@ RED.editor = (function() {
33510
34653
  }
33511
34654
  });
33512
34655
 
34656
+ /**
34657
+ * Compares `newValue` with `originalValue` for equality.
34658
+ * @param {*} originalValue Original value
34659
+ * @param {*} newValue New value
34660
+ * @returns {boolean} true if originalValue equals newValue, otherwise false
34661
+ */
34662
+ function isEqual(originalValue, newValue) {
34663
+ try {
34664
+ if(originalValue == newValue) {
34665
+ return true;
34666
+ }
34667
+ return JSON.stringify(originalValue) === JSON.stringify(newValue);
34668
+ } catch (err) {
34669
+ return false;
34670
+ }
34671
+ }
34672
+
33513
34673
  /**
33514
34674
  * Update the node credentials from the edit form
33515
34675
  * @param node - the node containing the credentials
@@ -33778,6 +34938,7 @@ RED.editor = (function() {
33778
34938
  var definition = {
33779
34939
  show: function(options) {
33780
34940
  var value = options.value;
34941
+ var onCancel = options.cancel;
33781
34942
  var onComplete = options.complete;
33782
34943
  var type = "_buffer"
33783
34944
  if ($("script[data-template-name='"+type+"']").length === 0) {
@@ -33791,12 +34952,14 @@ RED.editor = (function() {
33791
34952
 
33792
34953
  var trayOptions = {
33793
34954
  title: options.title,
34955
+ focusElement: options.focusElement,
33794
34956
  width: "inherit",
33795
34957
  buttons: [
33796
34958
  {
33797
34959
  id: "node-dialog-cancel",
33798
34960
  text: RED._("common.label.cancel"),
33799
34961
  click: function() {
34962
+ if (onCancel) { onCancel(); }
33800
34963
  RED.tray.close();
33801
34964
  }
33802
34965
  },
@@ -33805,7 +34968,8 @@ RED.editor = (function() {
33805
34968
  text: RED._("common.label.done"),
33806
34969
  class: "primary",
33807
34970
  click: function() {
33808
- onComplete(JSON.stringify(bufferBinValue));
34971
+ bufferStringEditor.saveView();
34972
+ if (onComplete) { onComplete(JSON.stringify(bufferBinValue),null,bufferStringEditor); }
33809
34973
  RED.tray.close();
33810
34974
  }
33811
34975
  }
@@ -33817,19 +34981,20 @@ RED.editor = (function() {
33817
34981
  }
33818
34982
  },
33819
34983
  open: function(tray) {
33820
- var trayBody = tray.find('.red-ui-tray-body');
33821
34984
  var dialogForm = RED.editor.buildEditForm(tray.find('.red-ui-tray-body'),'dialog-form',type,'editor');
33822
-
33823
34985
  bufferStringEditor = RED.editor.createEditor({
33824
34986
  id: 'red-ui-editor-type-buffer-str',
33825
- value: "",
34987
+ value: value||"",
34988
+ stateId: RED.editor.generateViewStateId("buffer", options, ""),
34989
+ focus: true,
33826
34990
  mode:"ace/mode/text"
33827
34991
  });
33828
- bufferStringEditor.getSession().setValue(value||"",-1);
33829
34992
 
33830
34993
  bufferBinEditor = RED.editor.createEditor({
33831
34994
  id: 'red-ui-editor-type-buffer-bin',
33832
34995
  value: "",
34996
+ stateId: false,
34997
+ focus: false,
33833
34998
  mode:"ace/mode/text",
33834
34999
  readOnly: true
33835
35000
  });
@@ -34921,6 +36086,7 @@ RED.editor = (function() {
34921
36086
  show: function(options) {
34922
36087
  var expressionTestCacheId = options.parent||"_";
34923
36088
  var value = options.value;
36089
+ var onCancel = options.cancel;
34924
36090
  var onComplete = options.complete;
34925
36091
  var type = "_expression"
34926
36092
  if ($("script[data-template-name='"+type+"']").length === 0) {
@@ -34934,12 +36100,14 @@ RED.editor = (function() {
34934
36100
 
34935
36101
  var trayOptions = {
34936
36102
  title: options.title,
36103
+ focusElement: options.focusElement,
34937
36104
  width: "inherit",
34938
36105
  buttons: [
34939
36106
  {
34940
36107
  id: "node-dialog-cancel",
34941
36108
  text: RED._("common.label.cancel"),
34942
36109
  click: function() {
36110
+ if(onCancel) { onCancel(); }
34943
36111
  RED.tray.close();
34944
36112
  }
34945
36113
  },
@@ -34949,7 +36117,8 @@ RED.editor = (function() {
34949
36117
  class: "primary",
34950
36118
  click: function() {
34951
36119
  $("#red-ui-editor-type-expression-help").text("");
34952
- onComplete(expressionEditor.getValue());
36120
+ expressionEditor.saveView();
36121
+ if (onComplete) { onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition(),expressionEditor); }
34953
36122
  RED.tray.close();
34954
36123
  }
34955
36124
  }
@@ -34981,6 +36150,8 @@ RED.editor = (function() {
34981
36150
  id: 'red-ui-editor-type-expression',
34982
36151
  value: "",
34983
36152
  mode:"ace/mode/jsonata",
36153
+ stateId: options.stateId,
36154
+ focus: true,
34984
36155
  options: {
34985
36156
  enableBasicAutocompletion:true,
34986
36157
  enableSnippets:true,
@@ -35104,6 +36275,8 @@ RED.editor = (function() {
35104
36275
  testDataEditor = RED.editor.createEditor({
35105
36276
  id: 'red-ui-editor-type-expression-test-data',
35106
36277
  value: expressionTestCache[expressionTestCacheId] || '{\n "payload": "hello world"\n}',
36278
+ stateId: false,
36279
+ focus: false,
35107
36280
  mode:"ace/mode/json",
35108
36281
  lineNumbers: false
35109
36282
  });
@@ -35173,6 +36346,8 @@ RED.editor = (function() {
35173
36346
  testResultEditor = RED.editor.createEditor({
35174
36347
  id: 'red-ui-editor-type-expression-test-result',
35175
36348
  value: "",
36349
+ stateId: false,
36350
+ focus: false,
35176
36351
  mode:"ace/mode/json",
35177
36352
  lineNumbers: false,
35178
36353
  readOnly: true
@@ -35347,6 +36522,7 @@ RED.editor = (function() {
35347
36522
  var definition = {
35348
36523
  show: function(options) {
35349
36524
  var value = options.value;
36525
+ var onCancel = options.cancel;
35350
36526
  var onComplete = options.complete;
35351
36527
  var type = "_js"
35352
36528
  if ($("script[data-template-name='"+type+"']").length === 0) {
@@ -35354,16 +36530,16 @@ RED.editor = (function() {
35354
36530
  }
35355
36531
  RED.view.state(RED.state.EDITING);
35356
36532
  var expressionEditor;
35357
- var changeTimer;
35358
-
35359
36533
  var trayOptions = {
35360
36534
  title: options.title,
36535
+ focusElement: options.focusElement,
35361
36536
  width: options.width||"inherit",
35362
36537
  buttons: [
35363
36538
  {
35364
36539
  id: "node-dialog-cancel",
35365
36540
  text: RED._("common.label.cancel"),
35366
36541
  click: function() {
36542
+ if (onCancel) { onCancel(); }
35367
36543
  RED.tray.close();
35368
36544
  }
35369
36545
  },
@@ -35372,7 +36548,8 @@ RED.editor = (function() {
35372
36548
  text: RED._("common.label.done"),
35373
36549
  class: "primary",
35374
36550
  click: function() {
35375
- onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition());
36551
+ expressionEditor.saveView();
36552
+ if (onComplete) { onComplete(expressionEditor.getValue(), expressionEditor.getCursorPosition(), expressionEditor); }
35376
36553
  RED.tray.close();
35377
36554
  }
35378
36555
  }
@@ -35388,11 +36565,12 @@ RED.editor = (function() {
35388
36565
  expressionEditor.resize();
35389
36566
  },
35390
36567
  open: function(tray) {
35391
- var trayBody = tray.find('.red-ui-tray-body');
35392
36568
  var dialogForm = RED.editor.buildEditForm(tray.find('.red-ui-tray-body'),'dialog-form',type,'editor');
35393
36569
  expressionEditor = RED.editor.createEditor({
35394
36570
  id: 'node-input-js',
35395
36571
  mode: options.mode || 'ace/mode/javascript',
36572
+ stateId: options.stateId,
36573
+ focus: true,
35396
36574
  value: value,
35397
36575
  globals: {
35398
36576
  msg:true,
@@ -35410,19 +36588,16 @@ RED.editor = (function() {
35410
36588
  },
35411
36589
  extraLibs: options.extraLibs
35412
36590
  });
35413
- if (options.cursor) {
36591
+ if (options.cursor && !expressionEditor._initState) {
35414
36592
  expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
35415
36593
  }
35416
36594
  dialogForm.i18n();
35417
- setTimeout(function() {
35418
- expressionEditor.focus();
35419
- },300);
35420
36595
  },
35421
36596
  close: function() {
35422
- expressionEditor.destroy();
35423
36597
  if (options.onclose) {
35424
36598
  options.onclose();
35425
36599
  }
36600
+ expressionEditor.destroy();
35426
36601
  },
35427
36602
  show: function() {}
35428
36603
  }
@@ -35455,7 +36630,9 @@ RED.editor = (function() {
35455
36630
  '<ul id="red-ui-editor-type-json-tabs"></ul>'+
35456
36631
  '<div id="red-ui-editor-type-json-tab-raw" class="red-ui-editor-type-json-tab-content hide">'+
35457
36632
  '<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>'+
36633
+ '<span class="button-group">'+
36634
+ '<button id="node-input-json-reformat" class="red-ui-button red-ui-button-small"><span data-i18n="jsonEditor.format"></span></button>'+
36635
+ '<span class="button-group">'+
35459
36636
  '</div>'+
35460
36637
  '<div class="form-row node-text-editor-row">'+
35461
36638
  '<div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-json"></div>'+
@@ -35468,7 +36645,7 @@ RED.editor = (function() {
35468
36645
 
35469
36646
  var activeTab;
35470
36647
 
35471
- function insertNewItem(parent,index,copyIndex) {
36648
+ function insertNewItem(parent,index,copyIndex,readOnly) {
35472
36649
  var newValue = "";
35473
36650
 
35474
36651
  if (parent.children.length > 0) {
@@ -35494,26 +36671,26 @@ RED.editor = (function() {
35494
36671
  newKey = keyRoot+"-"+(keySuffix++);
35495
36672
  }
35496
36673
  }
35497
- var newItem = handleItem(newKey,newValue,parent.depth+1,parent);
36674
+ var newItem = handleItem(newKey,newValue,parent.depth+1,parent,readOnly);
35498
36675
  parent.treeList.insertChildAt(newItem, index, true);
35499
36676
  parent.treeList.expand();
35500
36677
  }
35501
- function showObjectMenu(button,item) {
36678
+ function showObjectMenu(button,item,readOnly) {
35502
36679
  var elementPos = button.offset();
35503
36680
  var options = [];
35504
36681
  if (item.parent) {
35505
36682
  options.push({id:"red-ui-editor-type-json-menu-insert-above", icon:"fa fa-toggle-up", label:RED._('jsonEditor.insertAbove'),onselect:function(){
35506
36683
  var index = item.parent.children.indexOf(item);
35507
- insertNewItem(item.parent,index,index);
36684
+ insertNewItem(item.parent,index,index,readOnly);
35508
36685
  }});
35509
36686
  options.push({id:"red-ui-editor-type-json-menu-insert-below", icon:"fa fa-toggle-down", label:RED._('jsonEditor.insertBelow'),onselect:function(){
35510
36687
  var index = item.parent.children.indexOf(item)+1;
35511
- insertNewItem(item.parent,index,index-1);
36688
+ insertNewItem(item.parent,index,index-1,readOnly);
35512
36689
  }});
35513
36690
  }
35514
36691
  if (item.type === 'array' || item.type === 'object') {
35515
36692
  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);
36693
+ insertNewItem(item,item.children.length,item.children.length-1,readOnly);
35517
36694
  }});
35518
36695
  }
35519
36696
  if (item.parent) {
@@ -35555,7 +36732,7 @@ RED.editor = (function() {
35555
36732
  newKey = keyRoot+"-"+(keySuffix++);
35556
36733
  }
35557
36734
  }
35558
- var newItem = handleItem(newKey,convertToObject(item),item.parent.depth+1,item.parent);
36735
+ var newItem = handleItem(newKey,convertToObject(item),item.parent.depth+1,item.parent,readOnly);
35559
36736
  var index = item.parent.children.indexOf(item)+1;
35560
36737
 
35561
36738
  item.parent.treeList.insertChildAt(newItem, index, true);
@@ -35605,24 +36782,24 @@ RED.editor = (function() {
35605
36782
  menuOptionMenu.show();
35606
36783
  }
35607
36784
 
35608
- function parseObject(obj,depth,parent) {
36785
+ function parseObject(obj,depth,parent,readOnly) {
35609
36786
  var result = [];
35610
36787
  for (var prop in obj) {
35611
36788
  if (obj.hasOwnProperty(prop)) {
35612
- result.push(handleItem(prop,obj[prop],depth,parent));
36789
+ result.push(handleItem(prop,obj[prop],depth,parent,readOnly));
35613
36790
  }
35614
36791
  }
35615
36792
  return result;
35616
36793
  }
35617
- function parseArray(obj,depth,parent) {
36794
+ function parseArray(obj,depth,parent,readOnly) {
35618
36795
  var result = [];
35619
36796
  var l = obj.length;
35620
36797
  for (var i=0;i<l;i++) {
35621
- result.push(handleItem(i,obj[i],depth,parent));
36798
+ result.push(handleItem(i,obj[i],depth,parent,readOnly));
35622
36799
  }
35623
36800
  return result;
35624
36801
  }
35625
- function handleItem(key,val,depth,parent) {
36802
+ function handleItem(key,val,depth,parent,readOnly) {
35626
36803
  var item = {depth:depth, type: typeof val};
35627
36804
  var container = $('<span class="red-ui-editor-type-json-editor-label">');
35628
36805
  if (key != null) {
@@ -35638,11 +36815,14 @@ RED.editor = (function() {
35638
36815
  if (parent && parent.type === "array") {
35639
36816
  keyLabel.addClass("red-ui-editor-type-json-editor-label-array-key")
35640
36817
  }
35641
-
36818
+ if(readOnly) {
36819
+ keyLabel.addClass("readonly")
36820
+ }
35642
36821
  keyLabel.on("click", function(evt) {
35643
36822
  if (item.parent.type === 'array') {
35644
36823
  return;
35645
36824
  }
36825
+ if (readOnly) { return; }
35646
36826
  evt.preventDefault();
35647
36827
  evt.stopPropagation();
35648
36828
  var w = Math.max(150,keyLabel.width());
@@ -35687,10 +36867,10 @@ RED.editor = (function() {
35687
36867
  item.expanded = depth < 2;
35688
36868
  item.type = "array";
35689
36869
  item.deferBuild = depth >= 2;
35690
- item.children = parseArray(val,depth+1,item);
36870
+ item.children = parseArray(val,depth+1,item,readOnly);
35691
36871
  } else if (val !== null && item.type === "object") {
35692
36872
  item.expanded = depth < 2;
35693
- item.children = parseObject(val,depth+1,item);
36873
+ item.children = parseObject(val,depth+1,item,readOnly);
35694
36874
  item.deferBuild = depth >= 2;
35695
36875
  } else {
35696
36876
  item.value = val;
@@ -35721,7 +36901,11 @@ RED.editor = (function() {
35721
36901
  //
35722
36902
  var orphanedChildren;
35723
36903
  var valueLabel = $('<span class="red-ui-editor-type-json-editor-label-value">').addClass(valClass).text(valValue).appendTo(container);
36904
+ if (readOnly) {
36905
+ valueLabel.addClass("readonly")
36906
+ }
35724
36907
  valueLabel.on("click", function(evt) {
36908
+ if (readOnly) { return; }
35725
36909
  evt.preventDefault();
35726
36910
  evt.stopPropagation();
35727
36911
  if (valType === 'str') {
@@ -35829,17 +37013,19 @@ RED.editor = (function() {
35829
37013
  valueLabel.hide();
35830
37014
  })
35831
37015
  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);
37016
+ if(!readOnly) {
37017
+ if (parent) {
37018
+ $('<span class="red-ui-editor-type-json-editor-item-handle"><i class="fa fa-bars"></span>').appendTo(item.gutter);
37019
+ } else {
37020
+ $('<span></span>').appendTo(item.gutter);
37021
+ }
37022
+ $('<button type="button" class="editor-button editor-button-small"><i class="fa fa-caret-down"></button>').appendTo(item.gutter).on("click", function(evt) {
37023
+ evt.preventDefault();
37024
+ evt.stopPropagation();
37025
+ showObjectMenu($(this), item, readOnly);
37026
+ });
35837
37027
  }
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
- });
37028
+
35843
37029
  item.element = container;
35844
37030
  return item;
35845
37031
  }
@@ -35868,6 +37054,7 @@ RED.editor = (function() {
35868
37054
  var definition = {
35869
37055
  show: function(options) {
35870
37056
  var value = options.value;
37057
+ var onCancel = options.cancel;
35871
37058
  var onComplete = options.complete;
35872
37059
  var type = "_json"
35873
37060
  if ($("script[data-template-name='"+type+"']").length === 0) {
@@ -35889,15 +37076,16 @@ RED.editor = (function() {
35889
37076
  }
35890
37077
  }
35891
37078
  var rootNode;
35892
-
35893
37079
  var trayOptions = {
35894
37080
  title: options.title,
37081
+ focusElement: options.focusElement,
35895
37082
  width: options.width||700,
35896
37083
  buttons: [
35897
37084
  {
35898
37085
  id: "node-dialog-cancel",
35899
37086
  text: RED._("common.label.cancel"),
35900
37087
  click: function() {
37088
+ if (onCancel) { onCancel(); }
35901
37089
  RED.tray.close();
35902
37090
  }
35903
37091
  },
@@ -35919,7 +37107,8 @@ RED.editor = (function() {
35919
37107
  } else if (activeTab === "json-raw") {
35920
37108
  result = expressionEditor.getValue();
35921
37109
  }
35922
- if (onComplete) { onComplete(result) }
37110
+ expressionEditor.saveView();
37111
+ if (onComplete) { onComplete(result,null,expressionEditor) }
35923
37112
  RED.tray.close();
35924
37113
  }
35925
37114
  }
@@ -35932,7 +37121,25 @@ RED.editor = (function() {
35932
37121
  open: function(tray) {
35933
37122
  var trayBody = tray.find('.red-ui-tray-body');
35934
37123
  var dialogForm = RED.editor.buildEditForm(tray.find('.red-ui-tray-body'),'dialog-form',type,'editor');
35935
-
37124
+ var toolbarButtons = options.toolbarButtons || [];
37125
+ if (toolbarButtons.length) {
37126
+ toolbarButtons.forEach(function (button) {
37127
+ var element = $('<button type="button" class="red-ui-button red-ui-button-small"> </button>')
37128
+ .insertBefore("#node-input-json-reformat")
37129
+ .on("click", function (evt) {
37130
+ evt.preventDefault();
37131
+ if (button.click !== undefined) {
37132
+ button.click.call(element, evt);
37133
+ }
37134
+ });
37135
+ if (button.id) { element.attr("id", button.id); }
37136
+ if (button.title) { element.attr("title", button.title); }
37137
+ if (button.icon) { element.append($("<i></i>").attr("class", button.icon)); }
37138
+ if (button.label || button.text) {
37139
+ element.append($("<span></span>").text(" " + (button.label || button.text)));
37140
+ }
37141
+ });
37142
+ }
35936
37143
  var container = $("#red-ui-editor-type-json-tab-ui-container").css({"height":"100%"});
35937
37144
  var filterDepth = Infinity;
35938
37145
  var list = $('<div class="red-ui-debug-msg-payload red-ui-editor-type-json-editor">').appendTo(container).treeList({
@@ -35962,13 +37169,15 @@ RED.editor = (function() {
35962
37169
  })
35963
37170
  });
35964
37171
 
35965
-
35966
37172
  expressionEditor = RED.editor.createEditor({
35967
37173
  id: 'node-input-json',
35968
- value: "",
35969
- mode:"ace/mode/json"
37174
+ value: value||"",
37175
+ mode:"ace/mode/json",
37176
+ readOnly: !!options.readOnly,
37177
+ stateId: options.stateId,
37178
+ focus: true
35970
37179
  });
35971
- expressionEditor.getSession().setValue(value||"",-1);
37180
+
35972
37181
  if (options.requireValid) {
35973
37182
  expressionEditor.getSession().on('change', function() {
35974
37183
  clearTimeout(changeTimer);
@@ -36005,7 +37214,7 @@ RED.editor = (function() {
36005
37214
  var raw = expressionEditor.getValue().trim() ||"{}";
36006
37215
  try {
36007
37216
  var parsed = JSON.parse(raw);
36008
- rootNode = handleItem(null,parsed,0,null);
37217
+ rootNode = handleItem(null,parsed,0,null,options.readOnly);
36009
37218
  rootNode.class = "red-ui-editor-type-json-root-node"
36010
37219
  list.treeList('data',[rootNode]);
36011
37220
  } catch(err) {
@@ -36023,17 +37232,15 @@ RED.editor = (function() {
36023
37232
 
36024
37233
  tabs.addTab({
36025
37234
  id: 'json-raw',
36026
- label: RED._('jsonEditor.rawMode'),
37235
+ label: options.readOnly ? RED._('jsonEditor.rawMode-readonly') : RED._('jsonEditor.rawMode'),
36027
37236
  content: $("#red-ui-editor-type-json-tab-raw")
36028
37237
  });
36029
37238
  tabs.addTab({
36030
37239
  id: 'json-ui',
36031
- label: RED._('jsonEditor.uiMode'),
37240
+ label: options.readOnly ? RED._('jsonEditor.uiMode-readonly') : RED._('jsonEditor.uiMode'),
36032
37241
  content: $("#red-ui-editor-type-json-tab-ui")
36033
37242
  });
36034
37243
  finishedBuild = true;
36035
-
36036
-
36037
37244
  },
36038
37245
  close: function() {
36039
37246
  if (options.onclose) {
@@ -36104,24 +37311,26 @@ RED.editor = (function() {
36104
37311
  var definition = {
36105
37312
  show: function(options) {
36106
37313
  var value = options.value;
37314
+ var onCancel = options.cancel;
36107
37315
  var onComplete = options.complete;
36108
37316
  var type = "_markdown"
36109
37317
  if ($("script[data-template-name='"+type+"']").length === 0) {
36110
37318
  $(template).appendTo("#red-ui-editor-node-configs");
36111
37319
  }
36112
37320
 
36113
-
36114
37321
  RED.view.state(RED.state.EDITING);
36115
37322
  var expressionEditor;
36116
37323
 
36117
37324
  var trayOptions = {
36118
37325
  title: options.title,
37326
+ focusElement: options.focusElement,
36119
37327
  width: options.width||Infinity,
36120
37328
  buttons: [
36121
37329
  {
36122
37330
  id: "node-dialog-cancel",
36123
37331
  text: RED._("common.label.cancel"),
36124
37332
  click: function() {
37333
+ if (onCancel) { onCancel(); }
36125
37334
  RED.tray.close();
36126
37335
  }
36127
37336
  },
@@ -36130,7 +37339,8 @@ RED.editor = (function() {
36130
37339
  text: RED._("common.label.done"),
36131
37340
  class: "primary",
36132
37341
  click: function() {
36133
- onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition());
37342
+ expressionEditor.saveView();
37343
+ if (onComplete) { onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition(), expressionEditor); }
36134
37344
  RED.tray.close();
36135
37345
  }
36136
37346
  }
@@ -36149,6 +37359,8 @@ RED.editor = (function() {
36149
37359
  expressionEditor = RED.editor.createEditor({
36150
37360
  id: 'red-ui-editor-type-markdown',
36151
37361
  value: value,
37362
+ stateId: options.stateId,
37363
+ focus: true,
36152
37364
  mode:"ace/mode/markdown",
36153
37365
  expandable: false
36154
37366
  });
@@ -36193,17 +37405,17 @@ RED.editor = (function() {
36193
37405
  });
36194
37406
  RED.popover.tooltip($("#node-btn-markdown-preview"), RED._("markdownEditor.toggle-preview"));
36195
37407
 
36196
- if (options.cursor) {
37408
+ if (options.cursor && !expressionEditor._initState) {
36197
37409
  expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
36198
37410
  }
36199
37411
 
36200
37412
  dialogForm.i18n();
36201
37413
  },
36202
37414
  close: function() {
36203
- expressionEditor.destroy();
36204
37415
  if (options.onclose) {
36205
37416
  options.onclose();
36206
37417
  }
37418
+ expressionEditor.destroy();
36207
37419
  },
36208
37420
  show: function() {}
36209
37421
  }
@@ -36218,7 +37430,7 @@ RED.editor = (function() {
36218
37430
  'b': { before:"**", after: "**", tooltip: RED._("markdownEditor.bold")},
36219
37431
  'i': { before:"_", after: "_", tooltip: RED._("markdownEditor.italic")},
36220
37432
  'code': { before:"`", after: "`", tooltip: RED._("markdownEditor.code")},
36221
- 'ol': { before:" * ", newline: true, tooltip: RED._("markdownEditor.ordered-list")},
37433
+ 'ol': { before:" 1. ", newline: true, tooltip: RED._("markdownEditor.ordered-list")},
36222
37434
  'ul': { before:" - ", newline: true, tooltip: RED._("markdownEditor.unordered-list")},
36223
37435
  'bq': { before:"> ", newline: true, tooltip: RED._("markdownEditor.quote")},
36224
37436
  'link': { before:"[", after: "]()", tooltip: RED._("markdownEditor.link")},
@@ -36287,6 +37499,7 @@ RED.editor = (function() {
36287
37499
  var definition = {
36288
37500
  show: function(options) {
36289
37501
  var value = options.value;
37502
+ var onCancel = options.cancel;
36290
37503
  var onComplete = options.complete;
36291
37504
  var type = "_text"
36292
37505
  if ($("script[data-template-name='"+type+"']").length === 0) {
@@ -36294,16 +37507,16 @@ RED.editor = (function() {
36294
37507
  }
36295
37508
  RED.view.state(RED.state.EDITING);
36296
37509
  var expressionEditor;
36297
- var changeTimer;
36298
-
36299
37510
  var trayOptions = {
36300
37511
  title: options.title,
37512
+ focusElement: options.focusElement,
36301
37513
  width: options.width||"inherit",
36302
37514
  buttons: [
36303
37515
  {
36304
37516
  id: "node-dialog-cancel",
36305
37517
  text: RED._("common.label.cancel"),
36306
37518
  click: function() {
37519
+ if(onCancel) { onCancel(); }
36307
37520
  RED.tray.close();
36308
37521
  }
36309
37522
  },
@@ -36312,7 +37525,8 @@ RED.editor = (function() {
36312
37525
  text: RED._("common.label.done"),
36313
37526
  class: "primary",
36314
37527
  click: function() {
36315
- onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition());
37528
+ expressionEditor.saveView();
37529
+ if (onComplete) { onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition(),expressionEditor);}
36316
37530
  RED.tray.close();
36317
37531
  }
36318
37532
  }
@@ -36321,31 +37535,27 @@ RED.editor = (function() {
36321
37535
  var rows = $("#dialog-form>div:not(.node-text-editor-row)");
36322
37536
  var editorRow = $("#dialog-form>div.node-text-editor-row");
36323
37537
  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
37538
  $(".node-text-editor").css("height",height+"px");
36329
37539
  expressionEditor.resize();
36330
37540
  },
36331
37541
  open: function(tray) {
36332
- var trayBody = tray.find('.red-ui-tray-body');
36333
37542
  var dialogForm = RED.editor.buildEditForm(tray.find('.red-ui-tray-body'),'dialog-form',type,'editor');
36334
37543
  expressionEditor = RED.editor.createEditor({
36335
37544
  id: 'node-input-text',
36336
- value: "",
36337
- mode:"ace/mode/"+(options.mode||"text")
37545
+ value: value||"",
37546
+ stateId: options.stateId,
37547
+ mode:"ace/mode/"+(options.mode||"text"),
37548
+ focus: true,
36338
37549
  });
36339
- expressionEditor.getSession().setValue(value||"",-1);
36340
- if (options.cursor) {
37550
+ if (options.cursor && !expressionEditor._initState) {
36341
37551
  expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
36342
37552
  }
36343
37553
  },
36344
37554
  close: function() {
36345
- expressionEditor.destroy();
36346
37555
  if (options.onclose) {
36347
37556
  options.onclose();
36348
37557
  }
37558
+ expressionEditor.destroy();
36349
37559
  },
36350
37560
  show: function() {}
36351
37561
  }
@@ -36436,6 +37646,9 @@ RED.editor.codeEditor.ace = (function() {
36436
37646
  }
36437
37647
  },100);
36438
37648
  }
37649
+ if (!options.stateId && options.stateId !== false) {
37650
+ options.stateId = RED.editor.generateViewStateId("ace", options, (options.mode || options.title).split("/").pop());
37651
+ }
36439
37652
  if (options.mode === 'ace/mode/markdown') {
36440
37653
  $(el).addClass("red-ui-editor-text-container-toolbar");
36441
37654
  editor.toolbar = RED.editor.customEditTypes['_markdown'].buildToolbar(toolbarRow,editor);
@@ -36448,11 +37661,15 @@ RED.editor.codeEditor.ace = (function() {
36448
37661
  RED.editor.editMarkdown({
36449
37662
  value: value,
36450
37663
  width: "Infinity",
36451
- cursor: editor.getCursorPosition(),
37664
+ stateId: options.stateId,
37665
+ focus: true,
37666
+ cancel: function () {
37667
+ editor.focus();
37668
+ },
36452
37669
  complete: function(v,cursor) {
36453
37670
  editor.setValue(v, -1);
36454
- editor.gotoLine(cursor.row+1,cursor.column,false);
36455
37671
  setTimeout(function() {
37672
+ editor.restoreView();
36456
37673
  editor.focus();
36457
37674
  },300);
36458
37675
  }
@@ -36473,11 +37690,56 @@ RED.editor.codeEditor.ace = (function() {
36473
37690
  editor._destroy = editor.destroy;
36474
37691
  editor.destroy = function() {
36475
37692
  try {
37693
+ editor.saveView();
37694
+ editor._initState = null;
36476
37695
  this._destroy();
36477
37696
  } catch (e) { }
36478
37697
  $(el).remove();
36479
37698
  $(toolbarRow).remove();
36480
37699
  }
37700
+ editor.on("blur", function () {
37701
+ editor.focusMemory = false;
37702
+ editor.saveView();
37703
+ })
37704
+ editor.on("focus", function () {
37705
+ if (editor._initState) {
37706
+ editor.restoreView(editor._initState);
37707
+ editor._initState = null;
37708
+ }
37709
+ })
37710
+ editor.getView = function () {
37711
+ var session = editor.getSession();
37712
+ return {
37713
+ selection: session.selection.toJSON(),
37714
+ scrollTop: session.getScrollTop(),
37715
+ scrollLeft: session.getScrollLeft(),
37716
+ options: session.getOptions()
37717
+ }
37718
+ }
37719
+ editor.saveView = function () {
37720
+ if (!options.stateId) { return; } //only possible if created with a unique stateId
37721
+ window._editorStateAce = window._editorStateAce || {};
37722
+ var state = editor.getView();
37723
+ window._editorStateAce[options.stateId] = state;
37724
+ return state;
37725
+ }
37726
+ editor.restoreView = function (state) {
37727
+ if (!options.stateId) { return; } //only possible if created with a unique stateId
37728
+ window._editorStateAce = window._editorStateAce || {};
37729
+ var _state = state || window._editorStateAce[options.stateId];
37730
+ if (!_state) { return; } //no view state available
37731
+ try {
37732
+ var session = editor.getSession();
37733
+ session.setOptions(_state.options);
37734
+ session.selection.fromJSON(_state.selection);
37735
+ session.setScrollTop(_state.scrollTop);
37736
+ session.setScrollLeft(_state.scrollLeft);
37737
+ editor._initState = _state;
37738
+ } catch (error) {
37739
+ delete window._editorStateMonaco[options.stateId];
37740
+ }
37741
+ };
37742
+ editor.restoreView();
36481
37743
  editor.type = type;
36482
37744
  return editor;
36483
37745
  }
@@ -36679,7 +37941,7 @@ RED.editor.codeEditor.monaco = (function() {
36679
37941
 
36680
37942
  options = options || {};
36681
37943
  window.MonacoEnvironment = window.MonacoEnvironment || {};
36682
- window.MonacoEnvironment.getWorkerUrl = function (moduleId, label) {
37944
+ window.MonacoEnvironment.getWorkerUrl = window.MonacoEnvironment.getWorkerUrl || function (moduleId, label) {
36683
37945
  if (label === 'json') { return './vendor/monaco/dist/json.worker.js'; }
36684
37946
  if (label === 'css' || label === 'scss') { return './vendor/monaco/dist/css.worker.js'; }
36685
37947
  if (label === 'html' || label === 'handlebars') { return './vendor/monaco/dist/html.worker.js'; }
@@ -37255,13 +38517,25 @@ RED.editor.codeEditor.monaco = (function() {
37255
38517
  mode = "html";
37256
38518
  break;
37257
38519
  case "appcache":
38520
+ case "sh":
38521
+ case "bash":
37258
38522
  mode = "shell";
37259
38523
  break;
38524
+ case "batchfile":
38525
+ mode = "bat";
38526
+ break;
38527
+ case "protobuf":
38528
+ mode = "proto";
38529
+ break;
37260
38530
  //TODO: add other compatability types.
37261
38531
  }
37262
38532
  return mode;
37263
38533
  }
37264
38534
 
38535
+
38536
+ if(!options.stateId && options.stateId !== false) {
38537
+ options.stateId = RED.editor.generateViewStateId("monaco", options, (options.mode || options.title).split("/").pop());
38538
+ }
37265
38539
  var el = options.element || $("#"+options.id)[0];
37266
38540
  var toolbarRow = $("<div>").appendTo(el);
37267
38541
  el = $("<div>").appendTo(el).addClass("red-ui-editor-text-container")[0];
@@ -37606,6 +38880,7 @@ RED.editor.codeEditor.monaco = (function() {
37606
38880
  try {
37607
38881
  var m = this.getModel();
37608
38882
  if(m && !m.isDisposed()) {
38883
+ ed._initState = null;
37609
38884
  m.dispose();
37610
38885
  }
37611
38886
  this.setModel(null);
@@ -37659,7 +38934,7 @@ RED.editor.codeEditor.monaco = (function() {
37659
38934
  try {
37660
38935
  var _model = ed.getModel();
37661
38936
  if (_model !== null) {
37662
- var id = _model.getModeId(); // e.g. javascript
38937
+ var id = _model._languageId; // e.g. javascript
37663
38938
  var ra = _model._associatedResource.authority; //e.g. model
37664
38939
  var rp = _model._associatedResource.path; //e.g. /18
37665
38940
  var rs = _model._associatedResource.scheme; //e.g. inmemory
@@ -37751,14 +39026,7 @@ RED.editor.codeEditor.monaco = (function() {
37751
39026
  //#endregion "ACE compatability"
37752
39027
 
37753
39028
  //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
- }
39029
+ ed.focusMemory = options.focus;
37762
39030
  ed._mode = editorOptions.language;
37763
39031
 
37764
39032
  //as models are signleton, consts and let are avialable to other javascript instances
@@ -37770,11 +39038,12 @@ RED.editor.codeEditor.monaco = (function() {
37770
39038
  }
37771
39039
 
37772
39040
  ed.onDidBlurEditorWidget(function() {
39041
+ ed.focusMemory = false;
39042
+ ed.saveView();
37773
39043
  if(isVisible(el) == false) {
37774
39044
  onVisibilityChange(false, 0, el);
37775
39045
  }
37776
39046
  });
37777
-
37778
39047
  ed.onDidFocusEditorWidget(function() {
37779
39048
  onVisibilityChange(true, 10, el);
37780
39049
  });
@@ -37808,17 +39077,33 @@ RED.editor.codeEditor.monaco = (function() {
37808
39077
  }
37809
39078
 
37810
39079
  function onVisibilityChange(visible, delay, element) {
37811
- if(visible) {
37812
- if(ed._mode == "javascript" && ed._tempMode == "text") {
39080
+ delay = delay || 50;
39081
+ if (visible) {
39082
+ if (ed.focusMemory) {
39083
+ setTimeout(function () {
39084
+ if (element.parentElement) { //ensure el is still in DOM
39085
+ ed.focus();
39086
+ }
39087
+ }, 300)
39088
+ }
39089
+ if (ed._initState) {
39090
+ setTimeout(function () {
39091
+ if (element.parentElement) { //ensure el is still in DOM
39092
+ ed.restoreViewState(ed._initState);
39093
+ ed._initState = null;
39094
+ }
39095
+ }, delay);
39096
+ }
39097
+ if (ed._mode == "javascript" && ed._tempMode == "text") {
37813
39098
  ed._tempMode = "";
37814
- setTimeout(function() {
37815
- if(element.parentElement) { //ensure el is still in DOM
39099
+ setTimeout(function () {
39100
+ if (element.parentElement) { //ensure el is still in DOM
37816
39101
  ed.setMode('javascript', undefined, false);
37817
39102
  }
37818
- }, delay || 50);
39103
+ }, delay);
37819
39104
  }
37820
- } else if(ed._mode == "javascript" && ed._tempMode != "text") {
37821
- if(element.parentElement) { //ensure el is still in DOM
39105
+ } else if (ed._mode == "javascript" && ed._tempMode != "text") {
39106
+ if (element.parentElement) { //ensure el is still in DOM
37822
39107
  ed.setMode('text', undefined, false);
37823
39108
  ed._tempMode = "text";
37824
39109
  }
@@ -37837,15 +39122,19 @@ RED.editor.codeEditor.monaco = (function() {
37837
39122
  expandButton.on("click", function (e) {
37838
39123
  e.preventDefault();
37839
39124
  var value = ed.getValue();
39125
+ ed.saveView();
37840
39126
  RED.editor.editMarkdown({
37841
39127
  value: value,
37842
39128
  width: "Infinity",
37843
- cursor: ed.getCursorPosition(),
39129
+ stateId: options.stateId,
39130
+ cancel: function () {
39131
+ ed.focus();
39132
+ },
37844
39133
  complete: function (v, cursor) {
37845
39134
  ed.setValue(v, -1);
37846
- ed.gotoLine(cursor.row + 1, cursor.column, false);
37847
39135
  setTimeout(function () {
37848
39136
  ed.focus();
39137
+ ed.restoreView();
37849
39138
  }, 300);
37850
39139
  }
37851
39140
  })
@@ -37861,7 +39150,37 @@ RED.editor.codeEditor.monaco = (function() {
37861
39150
  autoClose: 50
37862
39151
  });
37863
39152
  }
37864
-
39153
+ ed.getView = function () {
39154
+ return ed.saveViewState();
39155
+ }
39156
+ ed.saveView = function (debuginfo) {
39157
+ if (!options.stateId) { return; } //only possible if created with a unique stateId
39158
+ window._editorStateMonaco = window._editorStateMonaco || {};
39159
+ var state = ed.getView();
39160
+ window._editorStateMonaco[options.stateId] = state;
39161
+ return state;
39162
+ }
39163
+ ed.restoreView = function (state) {
39164
+ if (!options.stateId) { return; } //only possible if created with a unique stateId
39165
+ window._editorStateMonaco = window._editorStateMonaco || {};
39166
+ var _state = state || window._editorStateMonaco[options.stateId];
39167
+ if (!_state) { return; } //no view state available
39168
+ try {
39169
+ if (ed.type) { //is editor already initialised?
39170
+ ed.restoreViewState(_state);
39171
+ } else {
39172
+ ed._initState = _state;
39173
+ }
39174
+ } catch (error) {
39175
+ delete window._editorStateMonaco[options.stateId];
39176
+ }
39177
+ };
39178
+ ed.restoreView();
39179
+ if (options.cursor && !ed._initState) {
39180
+ var row = options.cursor.row || options.cursor.lineNumber;
39181
+ var col = options.cursor.column || options.cursor.col;
39182
+ ed.gotoLine(row, col);
39183
+ }
37865
39184
  ed.type = type;
37866
39185
  return ed;
37867
39186
  }
@@ -38190,7 +39509,13 @@ RED.eventLog = (function() {
38190
39509
  raiseTrayZ();
38191
39510
  handleWindowResize();//cause call to monaco layout
38192
39511
  },200);
38193
- body.find(":focusable:first").trigger("focus");
39512
+ if(!options.hasOwnProperty("focusElement")) {
39513
+ //focusElement is not inside options - default to focusing 1st
39514
+ body.find(":focusable:first").trigger("focus");
39515
+ } else if(options.focusElement !== false) {
39516
+ //focusElement IS specified, focus that instead (if not false)
39517
+ $(options.focusElement).trigger("focus");
39518
+ }
38194
39519
 
38195
39520
  },150);
38196
39521
  el.css({right:0});
@@ -39343,6 +40668,7 @@ RED.clipboard = (function() {
39343
40668
  try {
39344
40669
  RED.view.importNodes(newNodes, importOptions);
39345
40670
  } catch(error) {
40671
+ console.log(error.importConfig)
39346
40672
  // Thrown for import_conflict
39347
40673
  confirmImport(error.importConfig, newNodes, importOptions);
39348
40674
  }
@@ -40891,6 +42217,8 @@ RED.search = (function() {
40891
42217
  var searchHistory = [];
40892
42218
  var index = {};
40893
42219
  var currentResults = [];
42220
+ var activeResults = [];
42221
+ var currentIndex = 0;
40894
42222
  var previousActiveElement;
40895
42223
 
40896
42224
  function indexProperty(node,label,property) {
@@ -40985,6 +42313,7 @@ RED.search = (function() {
40985
42313
  val = extractFlag(val,"config",flags);
40986
42314
  val = extractFlag(val,"subflow",flags);
40987
42315
  val = extractFlag(val,"hidden",flags);
42316
+ val = extractFlag(val,"modified",flags);
40988
42317
  // uses:<node-id>
40989
42318
  val = extractValue(val,"uses",flags);
40990
42319
 
@@ -41030,6 +42359,11 @@ RED.search = (function() {
41030
42359
  continue;
41031
42360
  }
41032
42361
  }
42362
+ if (flags.hasOwnProperty("modified")) {
42363
+ if (!node.node.changed && !node.node.moved) {
42364
+ continue;
42365
+ }
42366
+ }
41033
42367
  if (flags.hasOwnProperty("hidden")) {
41034
42368
  // Only tabs can be hidden
41035
42369
  if (node.node.type !== 'tab') {
@@ -41127,9 +42461,8 @@ RED.search = (function() {
41127
42461
  } else {
41128
42462
  searchResults.editableList('addItem',{});
41129
42463
  }
41130
-
41131
-
41132
- }
42464
+ },
42465
+ options: getSearchOptions()
41133
42466
  });
41134
42467
  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
42468
  evt.preventDefault();
@@ -41190,7 +42523,8 @@ RED.search = (function() {
41190
42523
  }
41191
42524
  } else if (!$(children[selected]).hasClass("red-ui-search-historyHeader")) {
41192
42525
  if (currentResults.length > 0) {
41193
- reveal(currentResults[Math.max(0,selected)].node);
42526
+ currentIndex = Math.max(0,selected);
42527
+ reveal(currentResults[currentIndex].node);
41194
42528
  }
41195
42529
  }
41196
42530
  }
@@ -41274,6 +42608,7 @@ RED.search = (function() {
41274
42608
 
41275
42609
  div.on("click", function(evt) {
41276
42610
  evt.preventDefault();
42611
+ currentIndex = i;
41277
42612
  reveal(node);
41278
42613
  });
41279
42614
  }
@@ -41289,13 +42624,59 @@ RED.search = (function() {
41289
42624
  if (existingIndex > -1) {
41290
42625
  searchHistory.splice(existingIndex,1);
41291
42626
  }
41292
- searchHistory.unshift(searchInput.val());
41293
- hide();
42627
+ searchHistory.unshift(searchVal);
42628
+ $("#red-ui-view-searchtools-search").data("term", searchVal);
42629
+ activeResults = Object.assign([], currentResults);
42630
+ hide(null, activeResults.length > 0);
41294
42631
  RED.view.reveal(node.id);
41295
42632
  }
41296
42633
 
42634
+ function revealPrev() {
42635
+ if (disabled) {
42636
+ updateSearchToolbar();
42637
+ return;
42638
+ }
42639
+ if (!searchResults || !activeResults.length) {
42640
+ show();
42641
+ return;
42642
+ }
42643
+ if (currentIndex > 0) {
42644
+ currentIndex--;
42645
+ } else {
42646
+ currentIndex = activeResults.length - 1;
42647
+ }
42648
+ const n = activeResults[currentIndex];
42649
+ if (n && n.node && n.node.id) {
42650
+ RED.view.reveal(n.node.id);
42651
+ $("#red-ui-view-searchtools-prev").trigger("focus");
42652
+ }
42653
+ updateSearchToolbar();
42654
+ }
42655
+ function revealNext() {
42656
+ if (disabled) {
42657
+ updateSearchToolbar();
42658
+ return;
42659
+ }
42660
+ if (!searchResults || !activeResults.length) {
42661
+ show();
42662
+ return;
42663
+ }
42664
+ if (currentIndex < activeResults.length - 1) {
42665
+ currentIndex++
42666
+ } else {
42667
+ currentIndex = 0;
42668
+ }
42669
+ const n = activeResults[currentIndex];
42670
+ if (n && n.node && n.node.id) {
42671
+ RED.view.reveal(n.node.id);
42672
+ $("#red-ui-view-searchtools-next").trigger("focus");
42673
+ }
42674
+ updateSearchToolbar();
42675
+ }
42676
+
41297
42677
  function show(v) {
41298
42678
  if (disabled) {
42679
+ updateSearchToolbar();
41299
42680
  return;
41300
42681
  }
41301
42682
  if (!visible) {
@@ -41322,7 +42703,7 @@ RED.search = (function() {
41322
42703
  searchInput.trigger("focus");
41323
42704
  }
41324
42705
 
41325
- function hide() {
42706
+ function hide(el, keepSearchToolbar) {
41326
42707
  if (visible) {
41327
42708
  visible = false;
41328
42709
  $("#red-ui-header-shade").hide();
@@ -41336,13 +42717,37 @@ RED.search = (function() {
41336
42717
  });
41337
42718
  }
41338
42719
  RED.events.emit("search:close");
41339
- if (previousActiveElement) {
42720
+ if (previousActiveElement && (!keepSearchToolbar || !activeResults.length)) {
41340
42721
  $(previousActiveElement).trigger("focus");
41341
- previousActiveElement = null;
41342
42722
  }
42723
+ previousActiveElement = null;
42724
+ }
42725
+ if(!keepSearchToolbar) {
42726
+ clearActiveSearch();
42727
+ }
42728
+ updateSearchToolbar();
42729
+ if(keepSearchToolbar && activeResults.length) {
42730
+ $("#red-ui-view-searchtools-next").trigger("focus");
42731
+ }
42732
+ }
42733
+ function updateSearchToolbar() {
42734
+ if (!disabled && currentIndex >= 0 && activeResults && activeResults.length) {
42735
+ let term = $("#red-ui-view-searchtools-search").data("term") || "";
42736
+ if (term.length > 16) {
42737
+ term = term.substring(0, 12) + "..."
42738
+ }
42739
+ const i18nSearchCounterData = {
42740
+ term: term,
42741
+ result: (currentIndex + 1),
42742
+ count: activeResults.length
42743
+ }
42744
+ $("#red-ui-view-searchtools-counter").text(RED._('actions.search-counter', i18nSearchCounterData));
42745
+ $("#view-search-tools > :not(:first-child)").show(); //show other tools
42746
+ } else {
42747
+ clearActiveSearch();
42748
+ $("#view-search-tools > :not(:first-child)").hide(); //hide all but search button
41343
42749
  }
41344
42750
  }
41345
-
41346
42751
  function clearIndex() {
41347
42752
  index = {};
41348
42753
  }
@@ -41364,9 +42769,29 @@ RED.search = (function() {
41364
42769
  addItemToIndex(item);
41365
42770
  }
41366
42771
 
42772
+ function clearActiveSearch() {
42773
+ activeResults = [];
42774
+ currentIndex = 0;
42775
+ $("#red-ui-view-searchtools-search").data("term", "");
42776
+ }
42777
+
42778
+ function getSearchOptions() {
42779
+ return [
42780
+ {label:RED._("search.options.configNodes"), value:"is:config"},
42781
+ {label:RED._("search.options.unusedConfigNodes"), value:"is:config is:unused"},
42782
+ {label:RED._("search.options.modifiedNodes"), value:"is:modified"},
42783
+ {label:RED._("search.options.invalidNodes"), value: "is:invalid"},
42784
+ {label:RED._("search.options.uknownNodes"), value: "type:unknown"},
42785
+ {label:RED._("search.options.unusedSubflows"), value:"is:subflow is:unused"},
42786
+ {label:RED._("search.options.hiddenFlows"), value:"is:hidden"},
42787
+ ]
42788
+
42789
+ }
41367
42790
 
41368
42791
  function init() {
41369
42792
  RED.actions.add("core:search",show);
42793
+ RED.actions.add("core:search-previous",revealPrev);
42794
+ RED.actions.add("core:search-next",revealNext);
41370
42795
 
41371
42796
  RED.events.on("editor:open",function() { disabled = true; });
41372
42797
  RED.events.on("editor:close",function() { disabled = false; });
@@ -41377,11 +42802,21 @@ RED.search = (function() {
41377
42802
 
41378
42803
  RED.keyboard.add("red-ui-search","escape",hide);
41379
42804
 
42805
+ RED.keyboard.add("view-search-tools","escape",function() {
42806
+ clearActiveSearch();
42807
+ updateSearchToolbar();
42808
+ });
42809
+
41380
42810
  $("#red-ui-header-shade").on('mousedown',hide);
41381
42811
  $("#red-ui-editor-shade").on('mousedown',hide);
41382
42812
  $("#red-ui-palette-shade").on('mousedown',hide);
41383
42813
  $("#red-ui-sidebar-shade").on('mousedown',hide);
41384
42814
 
42815
+ $("#red-ui-view-searchtools-close").on("click", function close() {
42816
+ clearActiveSearch();
42817
+ updateSearchToolbar();
42818
+ });
42819
+ $("#red-ui-view-searchtools-close").trigger("click");
41385
42820
 
41386
42821
  RED.events.on("workspace:clear", clearIndex);
41387
42822
 
@@ -41407,7 +42842,8 @@ RED.search = (function() {
41407
42842
  init: init,
41408
42843
  show: show,
41409
42844
  hide: hide,
41410
- search: search
42845
+ search: search,
42846
+ getSearchOptions: getSearchOptions
41411
42847
  };
41412
42848
 
41413
42849
  })();
@@ -41816,17 +43252,21 @@ RED.actionList = (function() {
41816
43252
  var div = $('<div>',{class:"red-ui-search-result"}).appendTo(container);
41817
43253
 
41818
43254
  var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(div);
41819
- var colour = RED.utils.getNodeColor(object.type,def);
43255
+ if (object.type === "junction") {
43256
+ nodeDiv.addClass("red-ui-palette-icon-junction");
43257
+ } else {
43258
+ var colour = RED.utils.getNodeColor(object.type,def);
43259
+ nodeDiv.css('backgroundColor',colour);
43260
+ }
41820
43261
  var icon_url = RED.utils.getNodeIcon(def);
41821
- nodeDiv.css('backgroundColor',colour);
41822
43262
 
41823
43263
  var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
41824
43264
  RED.utils.createIconElement(icon_url, iconContainer, false);
41825
43265
 
41826
- if (def.inputs > 0) {
43266
+ if (object.type !== "junction" && def.inputs > 0) {
41827
43267
  $('<div/>',{class:"red-ui-search-result-node-port"}).appendTo(nodeDiv);
41828
43268
  }
41829
- if (def.outputs > 0) {
43269
+ if (object.type !== "junction" && def.outputs > 0) {
41830
43270
  $('<div/>',{class:"red-ui-search-result-node-port red-ui-search-result-node-output"}).appendTo(nodeDiv);
41831
43271
  }
41832
43272
 
@@ -41958,8 +43398,8 @@ RED.actionList = (function() {
41958
43398
  return !filter ||
41959
43399
  (
41960
43400
  (!filter.type || type === filter.type) &&
41961
- (!filter.input || def.inputs > 0) &&
41962
- (!filter.output || def.outputs > 0)
43401
+ (!filter.input || type === 'junction' || def.inputs > 0) &&
43402
+ (!filter.output || type === 'junction' || def.outputs > 0)
41963
43403
  )
41964
43404
  }
41965
43405
  function refreshTypeList(opts) {
@@ -41968,7 +43408,7 @@ RED.actionList = (function() {
41968
43408
  searchInput.searchBox('value','').focus();
41969
43409
  selected = -1;
41970
43410
  var common = [
41971
- 'inject','debug','function','change','switch'
43411
+ 'inject','debug','function','change','switch','junction'
41972
43412
  ].filter(function(t) { return applyFilter(opts.filter,t,RED.nodes.getType(t)); });
41973
43413
 
41974
43414
  var recentlyUsed = Object.keys(typesUsed);
@@ -41993,6 +43433,9 @@ RED.actionList = (function() {
41993
43433
  var index = 0;
41994
43434
  for(i=0;i<common.length;i++) {
41995
43435
  var itemDef = RED.nodes.getType(common[i]);
43436
+ if (common[i] === 'junction') {
43437
+ itemDef = { inputs:1, outputs: 1, label: 'junction', type: 'junction'}
43438
+ }
41996
43439
  if (itemDef) {
41997
43440
  item = {
41998
43441
  type: common[i],
@@ -43709,8 +45152,10 @@ RED.group = (function() {
43709
45152
  }
43710
45153
  if (n.type === 'group') {
43711
45154
  RED.events.emit("groups:change",n)
43712
- } else {
45155
+ } else if (n.type !== 'junction') {
43713
45156
  RED.events.emit("nodes:change",n)
45157
+ } else {
45158
+ RED.events.emit("junctions:change",n)
43714
45159
  }
43715
45160
  })
43716
45161
  RED.nodes.removeGroup(g);
@@ -43904,8 +45349,10 @@ RED.group = (function() {
43904
45349
  group.h = Math.max(group.h,n.y+n.h/2+25-group.y);
43905
45350
  if (n.type === 'group') {
43906
45351
  RED.events.emit("groups:change",n)
43907
- } else {
45352
+ } else if (n.type !== 'junction') {
43908
45353
  RED.events.emit("nodes:change",n)
45354
+ } else {
45355
+ RED.events.emit("junctions:change",n)
43909
45356
  }
43910
45357
  }
43911
45358
  }
@@ -43940,8 +45387,10 @@ RED.group = (function() {
43940
45387
  }
43941
45388
  if (n.type === 'group') {
43942
45389
  RED.events.emit("groups:change",n)
43943
- } else {
45390
+ } else if (n.type !== 'junction') {
43944
45391
  RED.events.emit("nodes:change",n)
45392
+ } else {
45393
+ RED.events.emit("junctions:change",n)
43945
45394
  }
43946
45395
  }
43947
45396
  markDirty(group);
@@ -46953,6 +48402,7 @@ RED.projects.settings = (function() {
46953
48402
  title: RED._('sidebar.project.editDescription'),
46954
48403
  header: $('<span><i class="fa fa-book"></i> README.md</span>'),
46955
48404
  value: activeProject.description,
48405
+ stateId: "sidebar.project.editDescription",
46956
48406
  complete: function(v) {
46957
48407
  container.empty();
46958
48408
  var spinner = utils.addSpinnerOverlay(container);