@node-red/nodes 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 (51) hide show
  1. package/core/common/05-junction.html +5 -0
  2. package/core/common/05-junction.js +12 -0
  3. package/core/common/20-inject.html +25 -13
  4. package/core/common/21-debug.html +60 -6
  5. package/core/common/21-debug.js +57 -27
  6. package/core/common/60-link.html +66 -29
  7. package/core/common/60-link.js +169 -20
  8. package/core/common/lib/debug/debug-utils.js +34 -1
  9. package/core/function/10-function.html +57 -21
  10. package/core/function/10-switch.html +3 -1
  11. package/core/function/10-switch.js +1 -0
  12. package/core/function/15-change.html +40 -12
  13. package/core/function/16-range.html +14 -5
  14. package/core/function/80-template.html +16 -12
  15. package/core/function/89-delay.html +46 -6
  16. package/core/function/89-trigger.html +12 -4
  17. package/core/function/rbe.html +7 -3
  18. package/core/network/05-tls.html +10 -4
  19. package/core/network/06-httpproxy.html +10 -1
  20. package/core/network/10-mqtt.html +73 -17
  21. package/core/network/10-mqtt.js +191 -80
  22. package/core/network/21-httpin.html +6 -2
  23. package/core/network/21-httprequest.html +217 -12
  24. package/core/network/21-httprequest.js +98 -17
  25. package/core/network/22-websocket.html +19 -5
  26. package/core/network/22-websocket.js +16 -13
  27. package/core/network/31-tcpin.html +47 -10
  28. package/core/network/31-tcpin.js +8 -3
  29. package/core/network/32-udp.html +14 -2
  30. package/core/parsers/70-CSV.html +4 -1
  31. package/core/parsers/70-JSON.html +3 -2
  32. package/core/parsers/70-XML.html +2 -1
  33. package/core/parsers/70-YAML.html +2 -1
  34. package/core/sequence/17-split.html +5 -1
  35. package/core/sequence/19-batch.html +28 -4
  36. package/core/storage/10-file.html +68 -8
  37. package/core/storage/10-file.js +46 -3
  38. package/core/storage/23-watch.html +2 -1
  39. package/core/storage/23-watch.js +21 -43
  40. package/locales/de/messages.json +1 -0
  41. package/locales/en-US/common/60-link.html +18 -3
  42. package/locales/en-US/messages.json +68 -17
  43. package/locales/en-US/network/21-httprequest.html +1 -1
  44. package/locales/en-US/storage/10-file.html +6 -2
  45. package/locales/ja/common/60-link.html +12 -0
  46. package/locales/ja/messages.json +63 -16
  47. package/locales/ko/messages.json +1 -0
  48. package/locales/ru/messages.json +1 -0
  49. package/locales/zh-CN/messages.json +1 -0
  50. package/locales/zh-TW/messages.json +1 -0
  51. package/package.json +12 -12
@@ -14,10 +14,119 @@
14
14
  * limitations under the License.
15
15
  **/
16
16
 
17
+ /**
18
+ * @typedef LinkTarget
19
+ * @type {object}
20
+ * @property {string} id - ID of the target node.
21
+ * @property {string} name - Name of target Node
22
+ * @property {number} flowId - ID of flow where the target node exists
23
+ * @property {string} flowName - Name of flow where the target node exists
24
+ * @property {boolean} isSubFlow - True if the link-in node exists in a subflow instance
25
+ */
26
+
27
+
17
28
  module.exports = function(RED) {
18
29
  "use strict";
19
-
20
30
  const crypto = require("crypto");
31
+ const targetCache = (function () {
32
+ const registry = { id: {}, name: {} };
33
+ function getIndex(/** @type {[LinkTarget]}*/ targets, id) {
34
+ for (let index = 0; index < (targets || []).length; index++) {
35
+ const element = targets[index];
36
+ if (element.id === id) {
37
+ return index;
38
+ }
39
+ }
40
+ return -1;
41
+ }
42
+ /**
43
+ * Generate a target object from a node
44
+ * @param {LinkInNode} node
45
+ * @returns {LinkTarget} a link target object
46
+ */
47
+ function generateTarget(node) {
48
+ const isSubFlow = node._flow.TYPE === "subflow";
49
+ return {
50
+ id: node.id,
51
+ name: node.name || node.id,
52
+ flowId: node._flow.flow.id,
53
+ flowName: isSubFlow ? node._flow.subflowDef.name : node._flow.flow.label,
54
+ isSubFlow: isSubFlow
55
+ }
56
+ }
57
+ return {
58
+ /**
59
+ * Get a list of targets registerd to this name
60
+ * @param {string} name Name of the target
61
+ * @param {boolean} [excludeSubflows] set `true` to exclude
62
+ * @returns {[LinkTarget]} Targets registerd to this name.
63
+ */
64
+ getTargets(name, excludeSubflows) {
65
+ const targets = registry.name[name] || [];
66
+ if (excludeSubflows) {
67
+ return targets.filter(e => e.isSubFlow != true);
68
+ }
69
+ return targets;
70
+ },
71
+ /**
72
+ * Get a single target by registered name.
73
+ * To restrict to a single flow, include the `flowId`
74
+ * If there is no targets OR more than one target, null is returned
75
+ * @param {string} name Name of the node
76
+ * @param {string} [flowId]
77
+ * @returns {LinkTarget} target
78
+ */
79
+ getTarget(name, flowId) {
80
+ /** @type {[LinkTarget]}*/
81
+ let possibleTargets = this.getTargets(name);
82
+ /** @type {LinkTarget}*/
83
+ let target;
84
+ if (possibleTargets.length && flowId) {
85
+ possibleTargets = possibleTargets.filter(e => e.flowId == flowId);
86
+ }
87
+ if (possibleTargets.length === 1) {
88
+ target = possibleTargets[0];
89
+ }
90
+ return target;
91
+ },
92
+ /**
93
+ * Get a target by node ID
94
+ * @param {string} nodeId ID of the node
95
+ * @returns {LinkTarget} target
96
+ */
97
+ getTargetById(nodeId) {
98
+ return registry.id[nodeId];
99
+ },
100
+ register(/** @type {LinkInNode} */ node) {
101
+ const target = generateTarget(node);
102
+ const tByName = this.getTarget(target.name, target.flowId);
103
+ if (!tByName || tByName.id !== target.id) {
104
+ registry.name[target.name] = registry.name[target.name] || [];
105
+ registry.name[target.name].push(target)
106
+ }
107
+ registry.id[target.id] = target;
108
+ return target;
109
+ },
110
+ remove(node) {
111
+ const target = generateTarget(node);
112
+ const tn = this.getTarget(target.name, target.flowId);
113
+ if (tn) {
114
+ const targs = this.getTargets(tn.name);
115
+ const idx = getIndex(targs, tn.id);
116
+ if (idx > -1) {
117
+ targs.splice(idx, 1);
118
+ }
119
+ if (targs.length === 0) {
120
+ delete registry.name[tn.name];
121
+ }
122
+ }
123
+ delete registry.id[target.id];
124
+ },
125
+ clear() {
126
+ registry = { id: {}, name: {} };
127
+ }
128
+ }
129
+ })();
21
130
 
22
131
  function LinkInNode(n) {
23
132
  RED.nodes.createNode(this,n);
@@ -27,12 +136,14 @@ module.exports = function(RED) {
27
136
  msg._event = n.event;
28
137
  node.receive(msg);
29
138
  }
139
+ targetCache.register(node);
30
140
  RED.events.on(event,handler);
31
141
  this.on("input", function(msg, send, done) {
32
142
  send(msg);
33
143
  done();
34
144
  });
35
145
  this.on("close",function() {
146
+ targetCache.remove(node);
36
147
  RED.events.removeListener(event,handler);
37
148
  });
38
149
  }
@@ -74,31 +185,69 @@ module.exports = function(RED) {
74
185
  function LinkCallNode(n) {
75
186
  RED.nodes.createNode(this,n);
76
187
  const node = this;
77
- const target = n.links[0];
188
+ const staticTarget = typeof n.links === "string" ? n.links : n.links[0];
189
+ const linkType = n.linkType;
78
190
  const messageEvents = {};
79
- let timeout = parseFloat(n.timeout || "30")*1000;
191
+
192
+ let timeout = parseFloat(n.timeout || "30") * 1000;
80
193
  if (isNaN(timeout)) {
81
194
  timeout = 30000;
82
195
  }
196
+ function getTargetNode(msg) {
197
+ const dynamicMode = linkType === "dynamic";
198
+ const target = dynamicMode ? msg.target : staticTarget
83
199
 
84
- this.on("input", function(msg, send, done) {
85
- msg._linkSource = msg._linkSource || [];
86
- const messageEvent = {
87
- id: crypto.randomBytes(14).toString('hex'),
88
- node: node.id,
200
+ ////1st see if the target is a direct node id
201
+ let foundNode;
202
+ if (targetCache.getTargetById(target)) {
203
+ foundNode = RED.nodes.getNode(target)
204
+ }
205
+ if (target && !foundNode && dynamicMode) {
206
+ //next, look in **this flow only** for the node
207
+ let cachedTarget = targetCache.getTarget(target, node._flow.flow.id);
208
+ if (!cachedTarget) {
209
+ //single target node not found in registry!
210
+ //get all possible targets from regular flows (exclude subflow instances)
211
+ const possibleTargets = targetCache.getTargets(target, true);
212
+ if (possibleTargets.length === 1) {
213
+ //only 1 link-in found with this name - good, lets use it
214
+ cachedTarget = possibleTargets[0];
215
+ } else if (possibleTargets.length > 1) {
216
+ //more than 1 link-in has this name, raise an error
217
+ throw new Error(`Multiple link-in nodes named '${target}' found`);
218
+ }
219
+ }
220
+ if (cachedTarget) {
221
+ foundNode = RED.nodes.getNode(cachedTarget.id);
222
+ }
223
+ }
224
+ if (foundNode instanceof LinkInNode) {
225
+ return foundNode;
89
226
  }
90
- messageEvents[messageEvent.id] = {
91
- msg: RED.util.cloneMessage(msg),
92
- send,
93
- done,
94
- ts: setTimeout(function() {
95
- timeoutMessage(messageEvent.id)
96
- }, timeout )
97
- };
98
- msg._linkSource.push(messageEvent);
99
- var targetNode = RED.nodes.getNode(target);
100
- if (targetNode) {
101
- targetNode.receive(msg);
227
+ throw new Error(`target link-in node '${target || ""}' not found`);
228
+ }
229
+ this.on("input", function (msg, send, done) {
230
+ try {
231
+ const targetNode = getTargetNode(msg);
232
+ if (targetNode instanceof LinkInNode) {
233
+ msg._linkSource = msg._linkSource || [];
234
+ const messageEvent = {
235
+ id: crypto.randomBytes(14).toString('hex'),
236
+ node: node.id,
237
+ }
238
+ messageEvents[messageEvent.id] = {
239
+ msg: RED.util.cloneMessage(msg),
240
+ send,
241
+ done,
242
+ ts: setTimeout(function () {
243
+ timeoutMessage(messageEvent.id)
244
+ }, timeout)
245
+ };
246
+ msg._linkSource.push(messageEvent);
247
+ targetNode.receive(msg);
248
+ }
249
+ } catch (error) {
250
+ node.error(error, msg);
102
251
  }
103
252
  });
104
253
 
@@ -581,12 +581,45 @@ RED.debug = (function() {
581
581
  var metaRow = $('<div class="red-ui-debug-msg-meta"></div>').appendTo(msg);
582
582
  $('<span class="red-ui-debug-msg-date">'+ getTimestamp()+'</span>').appendTo(metaRow);
583
583
  if (sourceNode) {
584
- $('<a>',{href:"#",class:"red-ui-debug-msg-name"}).text('node: '+(o.name||sourceNode.name||sourceNode.id))
584
+
585
+ var nodeLink = $('<a>',{href:"#",class:"red-ui-debug-msg-name"}).text("node: "+(o.name||sourceNode.name||sourceNode.id))
585
586
  .appendTo(metaRow)
586
587
  .on("click", function(evt) {
587
588
  evt.preventDefault();
588
589
  config.messageSourceClick(sourceNode.id, sourceNode._alias, sourceNode.path);
589
590
  });
591
+
592
+ if (sourceNode.pathHierarchy) {
593
+ RED.popover.create({
594
+ tooltip: true,
595
+ target:nodeLink,
596
+ trigger: "hover",
597
+ size: "small",
598
+ direction: "bottom",
599
+ interactive: true,
600
+ content: function() {
601
+ const content = $("<div>")
602
+ sourceNode.pathHierarchy.forEach((pathPart,idx) => {
603
+ const link = $("<a>", {href:"#" ,style:'display: block'})
604
+ .css({
605
+ paddingLeft:((idx*10)+((idx === sourceNode.pathHierarchy.length - 1)?10:0))+"px",
606
+ paddingRight:'2px'
607
+ })
608
+ .text(pathPart.label)
609
+ .appendTo(content)
610
+ .on("click", function(evt) {
611
+ evt.preventDefault();
612
+ config.messageSourceClick(pathPart.id);
613
+ })
614
+ if (idx < sourceNode.pathHierarchy.length - 1) {
615
+ $('<i class="fa fa-angle-down" style="margin-right: 3px"></i>').prependTo(link)
616
+ }
617
+ })
618
+ return content
619
+ },
620
+ delay: { show: 50, hide: 150 }
621
+ });
622
+ }
590
623
  } else if (name) {
591
624
  $('<span class="red-ui-debug-msg-name">'+name+'</span>').appendTo(metaRow);
592
625
  }
@@ -355,27 +355,41 @@
355
355
  color:"#fdd0a2",
356
356
  category: 'function',
357
357
  defaults: {
358
- name: {value:""},
358
+ name: {value:"_DEFAULT_"},
359
359
  func: {value:"\nreturn msg;"},
360
360
  outputs: {value:1},
361
- noerr: {value:0,required:true,validate:function(v) { return !v; }},
361
+ noerr: {value:0,required:true,
362
+ validate: function(v, opt) {
363
+ if (!v) {
364
+ return true;
365
+ }
366
+ return RED._("node-red:function.error.invalid-js");
367
+ }},
362
368
  initialize: {value:""},
363
369
  finalize: {value:""},
364
- libs: {value: [], validate: function(v) {
370
+ libs: {value: [], validate: function(v, opt) {
365
371
  if (!v) { return true; }
366
372
  for (var i=0,l=v.length;i<l;i++) {
367
373
  var m = v[i];
368
374
  if (!RED.utils.checkModuleAllowed(m.module,null,installAllowList,installDenyList)) {
369
- return false
375
+ return RED._("node-red:function.error.moduleNotAllowed", {
376
+ module: m.module
377
+ });
370
378
  }
371
379
  if (m.var === "" || / /.test(m.var)) {
372
- return false;
380
+ return RED._("node-red:function.error.moduleNameError", {
381
+ name: m.var
382
+ });
373
383
  }
374
384
  if (missingModules.indexOf(m.module) > -1) {
375
- return false;
385
+ return RED._("node-red:function.error.missing-module", {
386
+ module: m.module
387
+ });
376
388
  }
377
389
  if (invalidModuleVNames.indexOf(m.var) !== -1){
378
- return false;
390
+ return RED._("node-red:function.error.moduleNameError", {
391
+ name: m.var
392
+ });
379
393
  }
380
394
  }
381
395
  return true;
@@ -399,11 +413,19 @@
399
413
  $("#func-tabs-content").children().hide();
400
414
  $("#" + tab.id).show();
401
415
  let editor = $("#" + tab.id).find('.monaco-editor').first();
402
- if(editor.length) {
416
+ if(editor.length) {
403
417
  if(that.editor.nodered && that.editor.type == "monaco") {
404
418
  that.editor.nodered.refreshModuleLibs(getLibsList());
405
419
  }
406
420
  RED.tray.resize();
421
+ //auto focus editor on tab switch
422
+ if (that.initEditor.getDomNode() == editor[0]) {
423
+ that.initEditor.focus();
424
+ } else if (that.editor.getDomNode() == editor[0]) {
425
+ that.editor.focus();
426
+ } else if (that.finalizeEditor.getDomNode() == editor[0]) {
427
+ that.finalizeEditor.focus();
428
+ }
407
429
  }
408
430
  }
409
431
  });
@@ -438,11 +460,13 @@
438
460
  }
439
461
  });
440
462
 
441
- var buildEditor = function(id, value, defaultValue, extraLibs) {
463
+ var buildEditor = function(id, stateId, focus, value, defaultValue, extraLibs) {
442
464
  var editor = RED.editor.createEditor({
443
465
  id: id,
444
466
  mode: 'ace/mode/nrjavascript',
445
467
  value: value || defaultValue || "",
468
+ stateId: stateId,
469
+ focus: true,
446
470
  globals: {
447
471
  msg:true,
448
472
  context:true,
@@ -462,11 +486,12 @@
462
486
  if (defaultValue && value === "") {
463
487
  editor.moveCursorTo(defaultValue.split("\n").length - 1, 0);
464
488
  }
489
+ editor.__stateId = stateId;
465
490
  return editor;
466
491
  }
467
- this.initEditor = buildEditor('node-input-init-editor',$("#node-input-initialize").val(),RED._("node-red:function.text.initialize"))
468
- this.editor = buildEditor('node-input-func-editor',$("#node-input-func").val(), undefined, that.libs || [])
469
- this.finalizeEditor = buildEditor('node-input-finalize-editor',$("#node-input-finalize").val(),RED._("node-red:function.text.finalize"))
492
+ this.initEditor = buildEditor('node-input-init-editor', this.id + "/" + "initEditor", false, $("#node-input-initialize").val(), RED._("node-red:function.text.initialize"))
493
+ this.editor = buildEditor('node-input-func-editor', this.id + "/" + "editor", true, $("#node-input-func").val(), undefined, that.libs || [])
494
+ this.finalizeEditor = buildEditor('node-input-finalize-editor', this.id + "/" + "finalizeEditor", false, $("#node-input-finalize").val(), RED._("node-red:function.text.finalize"))
470
495
 
471
496
  RED.library.create({
472
497
  url:"functions", // where to get the data from
@@ -505,28 +530,33 @@
505
530
  ],
506
531
  ext:"js"
507
532
  });
508
- this.editor.focus();
509
-
510
533
 
511
534
  var expandButtonClickHandler = function(editor) {
512
- return function(e) {
535
+ return function (e) {
513
536
  e.preventDefault();
514
537
  var value = editor.getValue();
538
+ editor.saveView(`inside function-expandButtonClickHandler ${editor.__stateId}`);
515
539
  var extraLibs = that.libs || [];
516
540
  RED.editor.editJavaScript({
517
541
  value: value,
518
542
  width: "Infinity",
519
- cursor: editor.getCursorPosition(),
543
+ stateId: editor.__stateId,
520
544
  mode: "ace/mode/nrjavascript",
521
- complete: function(v,cursor) {
545
+ focus: true,
546
+ cancel: function () {
547
+ setTimeout(function () {
548
+ editor.focus();
549
+ }, 250);
550
+ },
551
+ complete: function (v, cursor) {
522
552
  editor.setValue(v, -1);
523
- editor.gotoLine(cursor.row+1,cursor.column,false);
524
- setTimeout(function() {
553
+ setTimeout(function () {
554
+ editor.restoreView();
525
555
  editor.focus();
526
- },300);
556
+ }, 250);
527
557
  },
528
558
  extraLibs: extraLibs
529
- })
559
+ });
530
560
  }
531
561
  }
532
562
  $("#node-init-expand-js").on("click", expandButtonClickHandler(this.initEditor));
@@ -605,6 +635,12 @@
605
635
  this.finalizeEditor.resize();
606
636
 
607
637
  $("#node-input-libs-container").css("height", (height - 192)+"px");
638
+ },
639
+ onadd: function() {
640
+ if (this.name === '_DEFAULT_') {
641
+ this.name = ''
642
+ RED.actions.invoke("core:generate-node-names", this)
643
+ }
608
644
  }
609
645
  });
610
646
  })();
@@ -163,7 +163,9 @@
163
163
  category: 'function',
164
164
  defaults: {
165
165
  name: {value:""},
166
- property: {value:"payload", required:true, validate: RED.validators.typedInput("propertyType")},
166
+ property: {value:"payload", required:true,
167
+ label:RED._("node-red:common.label.payload"),
168
+ validate: RED.validators.typedInput("propertyType", false)},
167
169
  propertyType: { value:"msg" },
168
170
  rules: {value:[{t:"eq", v:"", vt:"str"}]},
169
171
  checkall: {value:"true", required:true},
@@ -55,6 +55,7 @@ module.exports = function(RED) {
55
55
  catch(e) { return false;}
56
56
  }
57
57
  else if (b === "null") { return a === null; }
58
+ else if (b === "number") { return typeof a === b && !isNaN(a) }
58
59
  else { return typeof a === b && !Array.isArray(a) && !Buffer.isBuffer(a) && a !== null; }
59
60
  },
60
61
  'head': function(a, b, c, d, parts) {
@@ -19,38 +19,66 @@
19
19
 
20
20
  <script type="text/javascript">
21
21
  (function() {
22
- function validateProperty(v,vt) {
22
+ function isInvalidProperty(v,vt) {
23
23
  if (/msg|flow|global/.test(vt)) {
24
24
  if (!RED.utils.validatePropertyExpression(v)) {
25
- return false;
25
+ return RED._("node-red:change.errors.invalid-prop", {
26
+ property: v
27
+ });
26
28
  }
27
29
  } else if (vt === "jsonata") {
28
- try{jsonata(v);}catch(e){return false;}
30
+ try{ jsonata(v); } catch(e) {
31
+ return RED._("node-red:change.errors.invalid-expr", {
32
+ error: e.message
33
+ });
34
+ }
29
35
  } else if (vt === "json") {
30
- try{JSON.parse(v);}catch(e){return false;}
36
+ try{ JSON.parse(v); } catch(e) {
37
+ return RED._("node-red:change.errors.invalid-json-data", {
38
+ error: e.message
39
+ });
40
+ }
31
41
  }
32
- return true;
42
+ return false;
33
43
  }
44
+
34
45
  RED.nodes.registerType('change', {
35
46
  color: "#E2D96E",
36
47
  category: 'function',
37
48
  defaults: {
38
49
  name: {value:""},
39
- rules:{value:[{t:"set",p:"payload",pt:"msg",to:"",tot:"str"}],validate: function(rules) {
50
+ rules:{value:[{t:"set",p:"payload",pt:"msg",to:"",tot:"str"}],validate: function(rules, opt) {
51
+ var msg;
40
52
  if (!rules || rules.length === 0) { return true }
41
53
  for (var i=0;i<rules.length;i++) {
42
54
  var r = rules[i];
43
55
  if (r.t === 'set') {
44
- if (!validateProperty(r.p,r.pt) || !validateProperty(r.to,r.tot)) {
45
- return false;
56
+ if (msg = isInvalidProperty(r.p,r.pt)) {
57
+ return msg;
58
+ }
59
+ if (msg = isInvalidProperty(r.to,r.tot)) {
60
+ return msg;
46
61
  }
47
62
  } else if (r.t === 'change') {
48
- if (!validateProperty(r.p,r.pt) || !validateProperty(r.from,r.fromt) || !validateProperty(r.to,r.tot)) {
49
- return false;
63
+ if (msg = isInvalidProperty(r.p,r.pt)) {
64
+ return msg;
65
+ }
66
+ if(msg = isInvalidProperty(r.from,r.fromt)) {
67
+ return msg;
68
+ }
69
+ if(msg = isInvalidProperty(r.to,r.tot)) {
70
+ return msg;
71
+ }
72
+ } else if (r.t === 'delete') {
73
+ if (msg = isInvalidProperty(r.p,r.pt)) {
74
+ return msg;
50
75
  }
51
76
  } else if (r.t === 'move') {
52
- if (!validateProperty(r.p,r.pt)) {
53
- return false;
77
+ if (msg = isInvalidProperty(r.p,r.pt)) {
78
+ return msg;
79
+ }
80
+ if (msg = isInvalidProperty(r.to,r.tot)) {
81
+ return msg;
54
82
  }
55
83
  }
56
84
  }
@@ -41,13 +41,22 @@
41
41
  color: "#E2D96E",
42
42
  category: 'function',
43
43
  defaults: {
44
- minin: {value:"",required:true,validate:RED.validators.number()},
45
- maxin: {value:"",required:true,validate:RED.validators.number()},
46
- minout: {value:"",required:true,validate:RED.validators.number()},
47
- maxout: {value:"",required:true,validate:RED.validators.number()},
44
+ minin: {value:"", required: true,
45
+ label:RED._("node-red:range.label.minin"),
46
+ validate:RED.validators.number(false)},
47
+ maxin: {value:"", required: true,
48
+ label:RED._("node-red:range.label.maxin"),
49
+ validate:RED.validators.number(false)},
50
+ minout: {value:"", required:true,
51
+ label:RED._("node-red:range.label.minout"),
52
+ validate:RED.validators.number(false)},
53
+ maxout: {value:"", required:true,
54
+ label:RED._("node-red:range.label.maxout"),
55
+ validate:RED.validators.number(false)},
48
56
  action: {value:"scale"},
49
57
  round: {value:false},
50
- property: {value:"payload",required:true},
58
+ property: {value:"payload",required:true,
59
+ label:RED._("node-red:common.label.property")},
51
60
  name: {value:""}
52
61
  },
53
62
  inputs: 1,
@@ -18,7 +18,7 @@
18
18
  <option value="handlebars">mustache</option>
19
19
  <option value="html">HTML</option>
20
20
  <option value="json">JSON</option>
21
- <option value="javascript">Javascript</option>
21
+ <option value="javascript">JavaScript</option>
22
22
  <option value="css">CSS</option>
23
23
  <option value="markdown">Markdown</option>
24
24
  <option value="python">Python</option>
@@ -56,7 +56,9 @@
56
56
  category: 'function',
57
57
  defaults: {
58
58
  name: {value:""},
59
- field: {value:"payload", validate:RED.validators.typedInput("fieldType")},
59
+ field: {value:"payload",
60
+ label:"payload",
61
+ validate:RED.validators.typedInput("fieldType", false)},
60
62
  fieldType: {value:"msg"},
61
63
  format: {value:"handlebars"},
62
64
  syntax: {value:"mustache"},
@@ -73,7 +75,8 @@
73
75
  return this.name?"node_label_italic":"";
74
76
  },
75
77
  oneditprepare: function() {
76
- var that = this;
78
+ const that = this;
79
+ const stateId = RED.editor.generateViewStateId("node", this, "");
77
80
  if (!this.field) {
78
81
  this.field = 'payload';
79
82
  $("#node-input-field").val("payload");
@@ -90,10 +93,10 @@
90
93
  types: ['msg','flow','global'],
91
94
  typeField: $("#node-input-fieldType")
92
95
  });
93
-
94
96
  this.editor = RED.editor.createEditor({
95
97
  id: 'node-input-template-editor',
96
98
  mode: 'ace/mode/html',
99
+ stateId: stateId,
97
100
  value: $("#node-input-template").val()
98
101
  });
99
102
  RED.library.create({
@@ -103,7 +106,6 @@
103
106
  fields:['name','format','output','syntax'],
104
107
  ext: "txt"
105
108
  });
106
- this.editor.focus();
107
109
 
108
110
  $("#node-input-format").on("change", function() {
109
111
  var mod = "ace/mode/"+$("#node-input-format").val();
@@ -113,20 +115,22 @@
113
115
  });
114
116
  });
115
117
  RED.popover.tooltip($("#node-template-expand-editor"), RED._("node-red:common.label.expand"));
116
- $("#node-template-expand-editor").on("click", function(e) {
118
+ $("#node-template-expand-editor").on("click", function (e) {
117
119
  e.preventDefault();
118
- var value = that.editor.getValue();
120
+ const value = that.editor.getValue();
121
+ that.editor.saveView();
119
122
  RED.editor.editText({
120
123
  mode: $("#node-input-format").val(),
121
124
  value: value,
125
+ stateId: stateId,
122
126
  width: "Infinity",
123
- cursor: that.editor.getCursorPosition(),
124
- complete: function(v,cursor) {
127
+ focus: true,
128
+ complete: function (v, cursor) {
125
129
  that.editor.setValue(v, -1);
126
- that.editor.gotoLine(cursor.row+1,cursor.column,false);
127
- setTimeout(function() {
130
+ setTimeout(function () {
131
+ that.editor.restoreView();
128
132
  that.editor.focus();
129
- },300);
133
+ }, 250);
130
134
  }
131
135
  })
132
136
  })