@node-red/nodes 2.2.2 → 3.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/99-sample.html.demo +1 -1
  2. package/core/common/05-junction.html +5 -0
  3. package/core/common/05-junction.js +12 -0
  4. package/core/common/20-inject.html +25 -13
  5. package/core/common/20-inject.js +3 -2
  6. package/core/common/21-debug.html +58 -5
  7. package/core/common/21-debug.js +57 -27
  8. package/core/common/60-link.html +65 -29
  9. package/core/common/60-link.js +169 -20
  10. package/core/common/lib/debug/debug-utils.js +34 -1
  11. package/core/function/10-function.html +58 -22
  12. package/core/function/10-switch.html +19 -12
  13. package/core/function/10-switch.js +1 -0
  14. package/core/function/15-change.html +40 -12
  15. package/core/function/16-range.html +14 -5
  16. package/core/function/80-template.html +16 -12
  17. package/core/function/89-delay.html +46 -6
  18. package/core/function/89-trigger.html +12 -4
  19. package/core/function/rbe.html +7 -3
  20. package/core/network/05-tls.html +10 -4
  21. package/core/network/06-httpproxy.html +10 -1
  22. package/core/network/10-mqtt.html +73 -17
  23. package/core/network/10-mqtt.js +239 -91
  24. package/core/network/21-httpin.html +10 -6
  25. package/core/network/21-httprequest.html +219 -12
  26. package/core/network/21-httprequest.js +98 -17
  27. package/core/network/22-websocket.html +19 -5
  28. package/core/network/22-websocket.js +16 -13
  29. package/core/network/31-tcpin.html +47 -10
  30. package/core/network/31-tcpin.js +23 -20
  31. package/core/network/32-udp.html +14 -2
  32. package/core/parsers/70-CSV.html +4 -1
  33. package/core/parsers/70-JSON.html +3 -2
  34. package/core/parsers/70-XML.html +2 -1
  35. package/core/parsers/70-YAML.html +2 -1
  36. package/core/sequence/17-split.html +6 -2
  37. package/core/sequence/19-batch.html +28 -4
  38. package/core/storage/10-file.html +66 -6
  39. package/core/storage/10-file.js +46 -3
  40. package/core/storage/23-watch.html +2 -1
  41. package/core/storage/23-watch.js +21 -43
  42. package/locales/de/messages.json +1 -0
  43. package/locales/en-US/common/60-link.html +19 -3
  44. package/locales/en-US/messages.json +136 -83
  45. package/locales/en-US/network/21-httprequest.html +1 -1
  46. package/locales/en-US/storage/10-file.html +6 -2
  47. package/locales/ja/common/60-link.html +13 -0
  48. package/locales/ja/messages.json +85 -32
  49. package/locales/ja/network/21-httprequest.html +1 -1
  50. package/locales/ja/storage/10-file.html +8 -6
  51. package/locales/ko/messages.json +1 -0
  52. package/locales/ru/messages.json +1 -0
  53. package/locales/zh-CN/messages.json +1 -0
  54. package/locales/zh-TW/messages.json +1 -0
  55. package/package.json +13 -13
@@ -69,7 +69,7 @@
69
69
  outputs:1, // set the number of outputs - 0 to n
70
70
  color: "#ddd", // set icon color
71
71
  // set the icon (held in icons dir below where you save the node)
72
- icon: "myicon.png", // saved in icons/myicon.png
72
+ icon: "myicon.svg", // saved in icons/myicon.svg
73
73
  label: function() { // sets the default label contents
74
74
  return this.name||this.topic||"sample";
75
75
  },
@@ -0,0 +1,5 @@
1
+ <!--
2
+ 05-junction.html
3
+ This file exists so that the runtime loads the Junction node into the registry,
4
+ but it is empty so it doesn't appear in the editor palette
5
+ -->
@@ -0,0 +1,12 @@
1
+ module.exports = function(RED) {
2
+ "use strict";
3
+ function JunctionNode(n) {
4
+ RED.nodes.createNode(this,n);
5
+ this.on("input",function(msg, send, done) {
6
+ send(msg);
7
+ done();
8
+ });
9
+ }
10
+
11
+ RED.nodes.registerType("junction",JunctionNode);
12
+ }
@@ -225,28 +225,47 @@
225
225
  color:"#a6bbcf",
226
226
  defaults: {
227
227
  name: {value:""},
228
- props:{value:[{p:"payload"},{p:"topic",vt:"str"}], validate:function(v) {
228
+ props:{value:[{p:"payload"},{p:"topic",vt:"str"}], validate:function(v, opt) {
229
229
  if (!v || v.length === 0) { return true }
230
230
  for (var i=0;i<v.length;i++) {
231
231
  if (/msg|flow|global/.test(v[i].vt)) {
232
232
  if (!RED.utils.validatePropertyExpression(v[i].v)) {
233
- return false;
233
+ return RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v });
234
234
  }
235
235
  } else if (v[i].vt === "jsonata") {
236
- try{jsonata(v[i].v);}catch(e){return false;}
236
+ try{ jsonata(v[i].v); }
237
+ catch(e){
238
+ return RED._("node-red:inject.errors.invalid-jsonata", { prop: 'msg.'+v[i].p, error: e.message });
239
+ }
237
240
  } else if (v[i].vt === "json") {
238
- try{JSON.parse(v[i].v);}catch(e){return false;}
241
+ try{ JSON.parse(v[i].v); }
242
+ catch(e){
243
+ return RED._("node-red:inject.errors.invalid-json", { prop: 'msg.'+v[i].p, error: e.message });
244
+ }
245
+ } else if (v[i].vt === "num"){
246
+ if (!/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v[i].v)) {
247
+ return RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v });
248
+ }
239
249
  }
240
250
  }
241
251
  return true;
242
252
  }
243
253
  },
244
- repeat: {value:"", validate:function(v) { return ((v === "") || (RED.validators.number(v) && (v >= 0) && (v <= 2147483))) }},
254
+ repeat: {
255
+ value:"", validate: function(v, opt) {
256
+ if ((v === "") ||
257
+ (RED.validators.number(v) &&
258
+ (v >= 0) && (v <= 2147483))) {
259
+ return true;
260
+ }
261
+ return RED._("node-red:inject.errors.invalid-repeat");
262
+ }
263
+ },
245
264
  crontab: {value:""},
246
265
  once: {value:false},
247
266
  onceDelay: {value:0.1},
248
267
  topic: {value:""},
249
- payload: {value:"", validate: RED.validators.typedInput("payloadType")},
268
+ payload: {value:"", validate: RED.validators.typedInput("payloadType", false) },
250
269
  payloadType: {value:"date"},
251
270
  },
252
271
  icon: "inject.svg",
@@ -370,13 +389,6 @@
370
389
  var id = $("#inject-time-type-select").val();
371
390
  $(".inject-time-row").hide();
372
391
  $("#inject-time-row-"+id).show();
373
- if ((id == "none") || (id == "interval") || (id == "interval-time")) {
374
- $("#node-once").show();
375
- }
376
- else {
377
- $("#node-once").hide();
378
- $("#node-input-once").prop('checked', false);
379
- }
380
392
 
381
393
  // Scroll down
382
394
  var scrollDiv = $("#dialog-form").parent();
@@ -109,9 +109,10 @@ module.exports = function(RED) {
109
109
  if (!property) return;
110
110
 
111
111
  if (valueType === "jsonata") {
112
- if (p.exp) {
112
+ if (p.v) {
113
113
  try {
114
- var val = RED.util.evaluateJSONataExpression(p.exp, msg);
114
+ var exp = RED.util.prepareJSONataExpression(p.v, node);
115
+ var val = RED.util.evaluateJSONataExpression(exp, msg);
115
116
  RED.util.setMessageProperty(msg, property, val, true);
116
117
  }
117
118
  catch (err) {
@@ -74,7 +74,7 @@
74
74
  RED.nodes.registerType('debug',{
75
75
  category: 'common',
76
76
  defaults: {
77
- name: {value:""},
77
+ name: {value:"_DEFAULT_"},
78
78
  active: {value:true},
79
79
  tosidebar: {value:true},
80
80
  console: {value:false},
@@ -160,6 +160,10 @@
160
160
  },
161
161
  messageSourceClick: function(sourceId, aliasId, path) {
162
162
  // Get all of the nodes that could have logged this message
163
+ if (RED.nodes.workspace(sourceId)) {
164
+ RED.view.reveal(sourceId);
165
+ return
166
+ }
163
167
  var candidateNodes = [RED.nodes.node(sourceId)]
164
168
  if (path) {
165
169
  for (var i=2;i<path.length;i++) {
@@ -235,10 +239,11 @@
235
239
  // sourceNode should be the top-level node - one that is on a flow.
236
240
  var sourceNode;
237
241
  var pathParts;
242
+ var pathHierarchy;
238
243
  if (o.path) {
239
244
  // Path is a `/`-separated list of ids that identifies the
240
245
  // complete parentage of the node that generated this message.
241
- // flow-id/subflow-A-instance/subflow-A-type/subflow-B-instance/subflow-B-type/node-id
246
+ // flow-id/subflow-A-instance/subflow-B-instance
242
247
 
243
248
  // If it has one id, that is a top level flow
244
249
  // each subsequent id is the instance id of a subflow node
@@ -251,11 +256,41 @@
251
256
  // Highlight the subflow instance node.
252
257
  sourceNode = RED.nodes.node(pathParts[1]);
253
258
  }
259
+ pathHierarchy = pathParts.map((id,index) => {
260
+ if (index === 0) {
261
+ return {
262
+ id: id,
263
+ label: RED.nodes.workspace(id).label
264
+ }
265
+ } else {
266
+ var instanceNode = RED.nodes.node(id)
267
+ return {
268
+ id: id,
269
+ label: (instanceNode.name || RED.nodes.subflow(instanceNode.type.substring(8)).name)
270
+ }
271
+ }
272
+ })
273
+ if (pathParts.length === 1) {
274
+ pathHierarchy.push({
275
+ id: o.id,
276
+ label: sourceNode.name || sourceNode.type+":"+sourceNode.id
277
+ })
278
+ }
279
+ if (o._alias) {
280
+ let aliasNode = RED.nodes.node(o._alias)
281
+ if (aliasNode) {
282
+ pathHierarchy.push({
283
+ id: o._alias,
284
+ label: aliasNode.name || aliasNode.type+":"+aliasNode.id
285
+ })
286
+ }
287
+ }
254
288
  } else {
255
289
  // This is probably redundant...
256
290
  sourceNode = RED.nodes.node(o.id) || RED.nodes.node(o.z);
257
291
  }
258
292
  if (sourceNode) {
293
+ var sourceFlow = RED.nodes.workspace(sourceNode.z)
259
294
  o._source = {
260
295
  id:sourceNode.id,
261
296
  z:sourceNode.z,
@@ -266,7 +301,9 @@
266
301
  // the top-level subflow instance node.
267
302
  // This means the node's name is displayed in the sidebar.
268
303
  _alias:o._alias,
269
- path: pathParts
304
+ flowName: sourceFlow?(sourceFlow.label||sourceNode.z):sourceNode.z,
305
+ path: pathParts,
306
+ pathHierarchy: pathHierarchy
270
307
  };
271
308
  }
272
309
  RED.debug.handleDebugMessage(o);
@@ -416,9 +453,16 @@
416
453
  label: RED._("node-red:debug.autostatus"),
417
454
  hasValue: false
418
455
  };
456
+
457
+ var counter = {
458
+ value: "counter",
459
+ label: RED._("node-red:debug.messageCount"),
460
+ hasValue: false
461
+ };
462
+
419
463
  $("#node-input-typed-status").typedInput({
420
464
  default: "auto",
421
- types:[autoType, "msg", "jsonata"],
465
+ types:[autoType, "msg", "jsonata", counter],
422
466
  typeField: $("#node-input-statusType")
423
467
  });
424
468
  var that = this;
@@ -477,7 +521,10 @@
477
521
 
478
522
  $("#node-input-tostatus").on('change',function() {
479
523
  if ($(this).is(":checked")) {
480
- if (!that.hasOwnProperty("statusVal") || that.statusVal === "") {
524
+ if (that.statusType === "counter") {
525
+ that.statusVal = "";
526
+ }
527
+ else if (!that.hasOwnProperty("statusVal") || that.statusVal === "") {
481
528
  var type = $("#node-input-typed-complete").typedInput('type');
482
529
  var comp = "payload";
483
530
  if (type !== 'full') {
@@ -507,6 +554,12 @@
507
554
  $("#node-input-complete").val($("#node-input-typed-complete").typedInput('value'));
508
555
  }
509
556
  $("#node-input-statusVal").val($("#node-input-typed-status").typedInput('value'));
557
+ },
558
+ onadd: function() {
559
+ if (this.name === '_DEFAULT_') {
560
+ this.name = ''
561
+ RED.actions.invoke("core:generate-node-names", this)
562
+ }
510
563
  }
511
564
  });
512
565
  })();
@@ -21,6 +21,9 @@ module.exports = function(RED) {
21
21
  this.statusType = n.statusType || "auto";
22
22
  this.statusVal = n.statusVal || this.complete;
23
23
  this.tosidebar = n.tosidebar;
24
+ this.counter = 0;
25
+ this.lastTime = new Date().getTime();
26
+ this.timeout = null;
24
27
  if (this.tosidebar === undefined) { this.tosidebar = true; }
25
28
  this.active = (n.active === null || typeof n.active === "undefined") || n.active;
26
29
  if (this.tostatus) {
@@ -32,6 +35,12 @@ module.exports = function(RED) {
32
35
  var statExpression = hasStatExpression ? n.statusVal : null;
33
36
 
34
37
  var node = this;
38
+ if ( node.statusType === "counter" ){
39
+ node.status({fill:"blue", shape:"ring", text: node.counter});
40
+ }
41
+ else {
42
+ node.status({fill:"", shape:"", text: ""});
43
+ }
35
44
  var preparedEditExpression = null;
36
45
  var preparedStatExpression = null;
37
46
  if (editExpression) {
@@ -106,6 +115,9 @@ module.exports = function(RED) {
106
115
  if (this.oldState) {
107
116
  this.status({});
108
117
  }
118
+ if (this.timeout) {
119
+ clearTimeout(this.timeout)
120
+ }
109
121
  })
110
122
  this.on("input", function(msg, send, done) {
111
123
  if (hasOwnProperty.call(msg, "status") && hasOwnProperty.call(msg.status, "source") && hasOwnProperty.call(msg.status.source, "id") && (msg.status.source.id === node.id)) {
@@ -113,36 +125,54 @@ module.exports = function(RED) {
113
125
  return;
114
126
  }
115
127
  if (node.tostatus === true) {
116
- prepareStatus(msg, function(err,debugMsg) {
117
- if (err) { node.error(err); return; }
118
- var output = debugMsg.msg;
119
- var st = (typeof output === 'string') ? output : util.inspect(output);
120
- var fill = "grey";
121
- var shape = "dot";
122
- if (typeof output === 'object' && hasOwnProperty.call(output, "fill") && hasOwnProperty.call(output, "shape") && hasOwnProperty.call(output, "text")) {
123
- fill = output.fill;
124
- shape = output.shape;
125
- st = output.text;
128
+ if ( node.statusType === "counter" ){
129
+ const differenceOfTime = (new Date().getTime() - node.lastTime);
130
+ node.lastTime = new Date().getTime();
131
+ node.counter++;
132
+ if ( differenceOfTime > 100 ){
133
+ node.status({fill:"blue", shape:"ring", text: node.counter});
126
134
  }
127
- if (node.statusType === "auto") {
128
- if (hasOwnProperty.call(msg, "error")) {
129
- fill = "red";
130
- st = msg.error.message;
131
- }
132
- if (hasOwnProperty.call(msg, "status")) {
133
- fill = msg.status.fill || "grey";
134
- shape = msg.status.shape || "ring";
135
- st = msg.status.text || "";
135
+ else {
136
+ if (node.timeout) {
137
+ clearTimeout(node.timeout)
136
138
  }
139
+ node.timeout = setTimeout(() => {
140
+ node.status({fill:"blue", shape:"ring", text: node.counter})
141
+ }, 200)
137
142
  }
143
+ } else {
144
+ prepareStatus(msg, function(err,debugMsg) {
145
+ if (err) { node.error(err); return; }
146
+ var output = debugMsg.msg;
147
+ var st = (typeof output === 'string') ? output : util.inspect(output);
148
+ var fill = "grey";
149
+ var shape = "dot";
150
+ if (typeof output === 'object' && hasOwnProperty.call(output, "fill") && hasOwnProperty.call(output, "shape") && hasOwnProperty.call(output, "text")) {
151
+ fill = output.fill;
152
+ shape = output.shape;
153
+ st = output.text;
154
+ }
155
+ if (node.statusType === "auto") {
156
+ if (hasOwnProperty.call(msg, "error")) {
157
+ fill = "red";
158
+ st = msg.error.message;
159
+ }
160
+ if (hasOwnProperty.call(msg, "status")) {
161
+ fill = msg.status.fill || "grey";
162
+ shape = msg.status.shape || "ring";
163
+ st = msg.status.text || "";
164
+ }
165
+ }
138
166
 
139
- if (st.length > 32) { st = st.substr(0,32) + "..."; }
140
- var newStatus = {fill:fill, shape:shape, text:st};
141
- if (JSON.stringify(newStatus) !== node.oldState) { // only send if we have to
142
- node.status(newStatus);
143
- node.oldState = JSON.stringify(newStatus);
144
- }
145
- });
167
+ if (st.length > 32) { st = st.substr(0,32) + "..."; }
168
+
169
+ var newStatus = {fill:fill, shape:shape, text:st};
170
+ if (JSON.stringify(newStatus) !== node.oldState) { // only send if we have to
171
+ node.status(newStatus);
172
+ node.oldState = JSON.stringify(newStatus);
173
+ }
174
+ });
175
+ }
146
176
  }
147
177
 
148
178
  if (this.complete === "true") {
@@ -285,7 +315,7 @@ module.exports = function(RED) {
285
315
  res.sendFile(
286
316
  req.params[0],
287
317
  options,
288
- err => {
318
+ err => {
289
319
  if (err) {
290
320
  res.sendStatus(404);
291
321
  }
@@ -32,14 +32,23 @@
32
32
  <label for="node-input-timeout"><span data-i18n="exec.label.timeout"></span></label>
33
33
  <input type="text" id="node-input-timeout" placeholder="30" style="width: 70px; margin-right: 5px;"><span data-i18n="inject.seconds"></span>
34
34
  </div>
35
- <div style="position:relative; height: 30px; text-align: right;"><div style="display:inline-block"><input type="text" id="node-input-link-target-filter"></div></div>
36
- <div class="form-row node-input-link-row"></div>
35
+ <div class="form-row">
36
+ <label for="node-input-linkType" data-i18n="link.linkCallType"></label>
37
+ <select id="node-input-linkType" style="width: 70%">
38
+ <option value="static" data-i18n="link.staticLinkCall"></option>
39
+ <option value="dynamic" data-i18n="link.dynamicLinkCall"></option>
40
+ </select>
41
+ </div>
42
+ <div class="link-call-target-tree" style="position:relative; height: 30px; text-align: right;">
43
+ <div style="display:inline-block"><input type="text" id="node-input-link-target-filter"></div>
44
+ </div>
45
+ <div class="form-row node-input-link-row link-call-target-tree"></div>
37
46
  </script>
38
47
 
39
48
  <script type="text/javascript">
40
49
  (function() {
41
50
 
42
- var treeList;
51
+ let treeList;
43
52
 
44
53
  function onEditPrepare(node,targetType) {
45
54
  if (!node.links) {
@@ -47,7 +56,7 @@
47
56
  }
48
57
  node.oldLinks = [];
49
58
 
50
- var activeSubflow = RED.nodes.subflow(node.z);
59
+ const activeSubflow = RED.nodes.subflow(node.z);
51
60
 
52
61
  treeList = $("<div>")
53
62
  .css({width: "100%", height: "100%"})
@@ -67,10 +76,10 @@
67
76
  RED.view.redraw();
68
77
  }
69
78
  });
70
- var candidateNodes = RED.nodes.filterNodes({type:targetType});
71
- var candidateNodesCount = 0;
79
+ const candidateNodes = RED.nodes.filterNodes({type:targetType});
80
+ let candidateNodesCount = 0;
72
81
 
73
- var search = $("#node-input-link-target-filter").searchBox({
82
+ const search = $("#node-input-link-target-filter").searchBox({
74
83
  style: "compact",
75
84
  delay: 300,
76
85
  change: function() {
@@ -79,7 +88,7 @@
79
88
  treeList.treeList("filter", null);
80
89
  search.searchBox("count","");
81
90
  } else {
82
- var count = treeList.treeList("filter", function(item) {
91
+ const count = treeList.treeList("filter", function(item) {
83
92
  return item.label.toLowerCase().indexOf(val) > -1 || (item.node && item.node.type.toLowerCase().indexOf(val) > -1)
84
93
  });
85
94
  search.searchBox("count",count+" / "+candidateNodesCount);
@@ -87,25 +96,27 @@
87
96
  }
88
97
  });
89
98
 
90
-
91
- var flows = [];
92
- var flowMap = {};
99
+ const flows = [];
100
+ const flowMap = {};
93
101
 
94
102
  if (activeSubflow) {
95
103
  flowMap[activeSubflow.id] = {
96
104
  id: activeSubflow.id,
97
105
  class: 'red-ui-palette-header',
98
- label: "Subflow : "+(activeSubflow.name || activeSubflow.id),
106
+ label: "Subflow : " + (activeSubflow.name || activeSubflow.id),
99
107
  expanded: true,
100
108
  children: []
101
109
  };
102
110
  flows.push(flowMap[activeSubflow.id])
103
- } else {
104
- RED.nodes.eachWorkspace(function(ws) {
111
+ }
112
+ if (!activeSubflow || node.type === "link call") {
113
+ // Only "Link Call" can look outside of its own subflow
114
+ // Link In and Link Out nodes outside of a subflow should be ignored
115
+ RED.nodes.eachWorkspace(function (ws) {
105
116
  flowMap[ws.id] = {
106
117
  id: ws.id,
107
118
  class: 'red-ui-palette-header',
108
- label: (ws.label || ws.id)+(node.z===ws.id ? " *":""),
119
+ label: (ws.label || ws.id) + (node.z === ws.id ? " *" : ""),
109
120
  expanded: true,
110
121
  children: []
111
122
  }
@@ -113,22 +124,21 @@
113
124
  })
114
125
  }
115
126
 
116
- candidateNodes.forEach(function(n) {
127
+ candidateNodes.forEach(function (n) {
117
128
  if (flowMap[n.z]) {
118
129
  if (targetType === "link out" && n.mode === 'return') {
119
130
  // Link In nodes looking for Link Out nodes should not
120
131
  // include return-mode nodes.
121
- return
132
+ return;
122
133
  }
123
- var isChecked = false;
124
- isChecked = (node.links.indexOf(n.id) !== -1) || (n.links||[]).indexOf(node.id) !== -1;
134
+ const isChecked = (node.links.indexOf(n.id) !== -1) || (n.links || []).indexOf(node.id) !== -1;
125
135
  if (isChecked) {
126
136
  node.oldLinks.push(n.id);
127
137
  }
128
138
  flowMap[n.z].children.push({
129
139
  id: n.id,
130
140
  node: n,
131
- label: n.name||n.id,
141
+ label: n.name || n.id,
132
142
  selected: isChecked,
133
143
  checkbox: node.type !== "link call",
134
144
  radio: node.type === "link call"
@@ -136,8 +146,8 @@
136
146
  candidateNodesCount++;
137
147
  }
138
148
  });
139
- flows = flows.filter(function(f) { return f.children.length > 0 })
140
- treeList.treeList('data',flows);
149
+ const flowsFiltered = flows.filter(function(f) { return f.children.length > 0 })
150
+ treeList.treeList('data',flowsFiltered);
141
151
  setTimeout(function() {
142
152
  treeList.treeList('show',node.z);
143
153
  },100);
@@ -209,6 +219,10 @@
209
219
  }
210
220
 
211
221
  function onAdd() {
222
+ if (this.name === '_DEFAULT_') {
223
+ this.name = ''
224
+ RED.actions.invoke("core:generate-node-names", this)
225
+ }
212
226
  for (var i=0;i<this.links.length;i++) {
213
227
  var n = RED.nodes.node(this.links[i]);
214
228
  if (n && n.links.indexOf(this.id) === -1) {
@@ -221,7 +235,7 @@
221
235
  category: 'common',
222
236
  color:"#ddd",//"#87D8CF",
223
237
  defaults: {
224
- name: {value:""},
238
+ name: { value: "_DEFAULT_" },
225
239
  links: { value: [], type:"link out[]" }
226
240
  },
227
241
  inputs:0,
@@ -257,9 +271,14 @@
257
271
  category: 'common',
258
272
  color:"#ddd",//"#87D8CF",
259
273
  defaults: {
260
- name: {value:""},
261
- links: { value: [], type:"link in[]"},
262
- timeout: { value: "30", validate:RED.validators.number(true) }
274
+ name: { value: "" },
275
+ links: { value: [], type:"link in[]" },
276
+ linkType: { value:"static" },
277
+ timeout: {
278
+ value: "30",
279
+ label: RED._("node-red:link.timeout"),
280
+ validate:RED.validators.number(true)
281
+ }
263
282
  },
264
283
  inputs: 1,
265
284
  outputs: 1,
@@ -271,7 +290,9 @@
271
290
  if (this.name) {
272
291
  return this.name;
273
292
  }
274
- if (this.links.length > 0) {
293
+ if (this.linkType === "dynamic") {
294
+ return this._("link.dynamicLinkLabel");
295
+ } else if (this.links.length > 0) {
275
296
  var targetNode = RED.nodes.node(this.links[0]);
276
297
  return targetNode && (targetNode.name || this._("link.linkCall"));
277
298
  }
@@ -281,6 +302,21 @@
281
302
  return this.name?"node_label_italic":"";
282
303
  },
283
304
  oneditprepare: function() {
305
+ const updateVisibility = function() {
306
+ const static = $('#node-input-linkType').val() !== "dynamic";
307
+ if(static) {
308
+ $("div.link-call-target-tree").show();
309
+ } else {
310
+ $("div.link-call-target-tree").hide();
311
+ }
312
+ }
313
+ $("#node-input-linkType").on("change",function(d){
314
+ updateVisibility();
315
+ });
316
+ if (["static","dynamic"].indexOf(this.linkType) < 0) {
317
+ $("#node-input-linkType").val('static');
318
+ }
319
+ updateVisibility();
284
320
  onEditPrepare(this,"link in");
285
321
  },
286
322
  oneditsave: function() {
@@ -293,9 +329,9 @@
293
329
  category: 'common',
294
330
  color:"#ddd",//"#87D8CF",
295
331
  defaults: {
296
- name: {value:""},
332
+ name: { value:"_DEFAULT_" },
297
333
  mode: { value: "link" },// link || return
298
- links: { value: [], type:"link in[]"}
334
+ links: { value: [], type:"link in[]" }
299
335
  },
300
336
  align:"right",
301
337
  inputs:1,