diff --git a/Gruntfile.js b/Gruntfile.js
index 0e15c8dcd..dac1329a6 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -152,6 +152,10 @@ module.exports = function(grunt) {
"public/vendor/vendor.css": [
// TODO: resolve relative resource paths in
// bootstrap/FA/jquery
+ ],
+ "public/vendor/jsonata/jsonata.js": [
+ "editor/vendor/jsonata/jsonata.js",
+ "editor/vendor/jsonata/formatter.js"
]
}
}
@@ -161,7 +165,7 @@ module.exports = function(grunt) {
files: {
'public/red/red.min.js': 'public/red/red.js',
'public/red/main.min.js': 'public/red/main.js',
- 'public/vendor/jsonata/jsonata.min.js': 'editor/vendor/jsonata/jsonata.js',
+ 'public/vendor/jsonata/jsonata.min.js': 'public/vendor/jsonata/jsonata.js',
'public/vendor/ace/mode-jsonata.js': 'editor/vendor/jsonata/mode-jsonata.js',
'public/vendor/ace/worker-jsonata.js': 'editor/vendor/jsonata/worker-jsonata.js'
}
diff --git a/editor/js/i18n.js b/editor/js/i18n.js
index cf29a6b43..f03b89a7b 100644
--- a/editor/js/i18n.js
+++ b/editor/js/i18n.js
@@ -23,7 +23,7 @@ RED.i18n = (function() {
dynamicLoad: false,
load:'current',
ns: {
- namespaces: ["editor","node-red"],
+ namespaces: ["editor","node-red","jsonata"],
defaultNs: "editor"
},
fallbackLng: ['en-US'],
diff --git a/editor/js/ui/common/typedInput.js b/editor/js/ui/common/typedInput.js
index c433c0366..6f18a4884 100644
--- a/editor/js/ui/common/typedInput.js
+++ b/editor/js/ui/common/typedInput.js
@@ -85,6 +85,7 @@
}
return true;
}
+
var allOptions = {
msg: {value:"msg",label:"msg.",validate:validateExpression},
flow: {value:"flow",label:"flow.",validate:validateExpression},
@@ -103,9 +104,9 @@
expand:function() {
var that = this;
RED.editor.editExpression({
- value: this.value(),
+ value: jsonata.format(this.value()),
complete: function(v) {
- that.value(v);
+ that.value(v.replace(/\s*\n\s*/g," "));
}
})
}
diff --git a/editor/js/ui/editor.js b/editor/js/ui/editor.js
index cb821d731..23947b9fb 100644
--- a/editor/js/ui/editor.js
+++ b/editor/js/ui/editor.js
@@ -1392,6 +1392,7 @@ RED.editor = (function() {
text: RED._("common.label.done"),
class: "primary",
click: function() {
+ $("#node-input-expression-help").html("");
onComplete(expressionEditor.getValue());
RED.tray.close();
}
@@ -1412,8 +1413,19 @@ RED.editor = (function() {
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
- var dialogForm = buildEditForm(tray,'dialog-form','_expression');
+ var dialogForm = buildEditForm(tray,'dialog-form','_expression','editor');
+ var funcSelect = $("#node-input-expression-func");
+ jsonata.functions.forEach(function(f) {
+ funcSelect.append($("").val(f).text(f));
+ })
+ funcSelect.change(function(e) {
+ var f = $(this).val();
+ var args = RED._('jsonata:'+f+".args",{defaultValue:''});
+ var title = "
"+f+"("+args+")
";
+ var body = marked(RED._('jsonata:'+f+'.desc',{defaultValue:''}));
+ $("#node-input-expression-help").html(title+""+body+"
");
+ })
expressionEditor = RED.editor.createEditor({
id: 'node-input-expression',
value: "",
@@ -1426,6 +1438,15 @@ RED.editor = (function() {
});
expressionEditor.getSession().setValue(value||"",-1);
+ expressionEditor.on("changeSelection", function() {
+ var c = expressionEditor.getCursorPosition();
+ var token = expressionEditor.getSession().getTokenAt(c.row,c.column);
+ //console.log(token);
+ if (token && token.type === 'keyword') {
+ funcSelect.val(token.value).change();
+ }
+ });
+
dialogForm.i18n();
},
diff --git a/editor/templates/index.mst b/editor/templates/index.mst
index 8407774a3..7dc5fda50 100644
--- a/editor/templates/index.mst
+++ b/editor/templates/index.mst
@@ -161,7 +161,13 @@
diff --git a/editor/vendor/jsonata/formatter.js b/editor/vendor/jsonata/formatter.js
new file mode 100644
index 000000000..036de6852
--- /dev/null
+++ b/editor/vendor/jsonata/formatter.js
@@ -0,0 +1,109 @@
+(function() {
+ function indentLine(str,length) {
+ if (length <= 0) {
+ return str;
+ }
+ var i = (new Array(length)).join(" ");
+ str = str.replace(/^\s*/,i);
+ return str;
+ }
+ function formatExpression(str) {
+ var length = str.length;
+ var start = 0;
+ var inString = false;
+ var inBox = false;
+ var quoteChar;
+ var list = [];
+ var stack = [];
+ var frame;
+ var v;
+ var matchingBrackets = {
+ "(":")",
+ "[":"]",
+ "{":"}"
+ }
+ for (var i=0;i 30) {
+ longStack.push(true);
+ indent += 4;
+ pre = result.substring(0,offset+f.pos+1);
+ post = result.substring(offset+f.pos+1);
+ indented = indentLine(post,indent);
+ result = pre+"\n"+indented;
+ offset += indented.length-post.length+1;
+ } else {
+ longStack.push(false);
+ }
+ } else if (f.type === "close-block") {
+ if (f.width > 30) {
+ indent -= 4;
+ pre = result.substring(0,offset+f.pos);
+ post = result.substring(offset+f.pos);
+ indented = indentLine(post,indent);
+ result = pre+"\n"+indented;
+ offset += indented.length-post.length+1;
+ }
+ longStack.pop();
+ }
+ })
+ //console.log(result);
+ return result;
+ }
+
+ jsonata.format = formatExpression;
+ jsonata.functions = ["$sum", "$count", "$max", "$min", "$average", "$string", "$substring", "$substringBefore", "$substringAfter", "$lowercase", "$uppercase", "$length", "$split", "$join", "$number", "$boolean", "$not", "$map", "$reduce", "$keys", "$lookup", "$append", "$exists", "$spread"]
+})();
diff --git a/editor/vendor/jsonata/mode-jsonata.js b/editor/vendor/jsonata/mode-jsonata.js
index 24562c536..50190e7b3 100644
--- a/editor/vendor/jsonata/mode-jsonata.js
+++ b/editor/vendor/jsonata/mode-jsonata.js
@@ -1,8 +1,10 @@
define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules","ace/worker/worker_client","ace/mode/text"], function(require, exports, module) {
+
"use strict";
var oop = require("../lib/oop");
var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
+
var WorkerClient = require("../worker/worker_client").WorkerClient;
var JSONataHighlightRules = function() {
@@ -12,6 +14,8 @@ define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/mode/
"and|or|in",
"constant.language":
"null|Infinity|NaN|undefined",
+ "constant.language.boolean":
+ "true|false",
"storage.type":
"function",
"keyword":
@@ -39,16 +43,12 @@ define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/mode/
token : "constant.numeric", // float
regex : /[+-]?\d[\d_]*(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/
},
- {
- token : keywordMapper,
- regex : "[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*"
- },
{ token: "keyword",
regex: /λ/
},
{
- token: "constant.language.boolean",
- regex: "true|false"
+ token : keywordMapper,
+ regex : "[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*"
},
{
token : "punctuation.operator",
@@ -119,8 +119,6 @@ define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/mode/
return worker;
};
-
-
this.$id = "ace/mode/jsonata";
}).call(Mode.prototype);
diff --git a/editor/vendor/jsonata/worker-jsonata.js b/editor/vendor/jsonata/worker-jsonata.js
index b6803d3af..da292ea6d 100644
--- a/editor/vendor/jsonata/worker-jsonata.js
+++ b/editor/vendor/jsonata/worker-jsonata.js
@@ -11,7 +11,7 @@ if (!window.console) {
postMessage({type: "log", data: msgs});
};
window.console.error =
- window.console.warn =
+ window.console.warn =
window.console.log =
window.console.trace = window.console;
}
@@ -23,7 +23,7 @@ window.onerror = function(message, file, line, col, err) {
message: message,
data: err.data,
file: file,
- line: line,
+ line: line,
col: col,
stack: err.stack
}});
@@ -39,13 +39,13 @@ window.normalizeModule = function(parentId, moduleName) {
if (moduleName.charAt(0) == ".") {
var base = parentId.split("/").slice(0, -1).join("/");
moduleName = (base ? base + "/" : "") + moduleName;
-
+
while (moduleName.indexOf(".") !== -1 && previous != moduleName) {
var previous = moduleName;
moduleName = moduleName.replace(/^\.\//, "").replace(/\/\.\//, "/").replace(/[^\/]+\/\.\.\//, "");
}
}
-
+
return moduleName;
};
@@ -67,13 +67,13 @@ window.require = function require(parentId, id) {
}
return module.exports;
}
-
+
if (!window.require.tlns)
return console.log("unable to load " + id);
-
+
var path = resolveModuleId(id, window.require.tlns);
if (path.slice(-3) != ".js") path += ".js";
-
+
window.require.id = id;
window.require.modules[id] = {}; // prevent infinite loop on broken modules
importScripts(path);
@@ -112,7 +112,7 @@ window.define = function(id, deps, factory) {
deps = [];
id = window.require.id;
}
-
+
if (typeof factory != "function") {
window.require.modules[id] = {
exports: factory,
@@ -163,13 +163,13 @@ window.initSender = function initSender() {
var EventEmitter = window.require("ace/lib/event_emitter").EventEmitter;
var oop = window.require("ace/lib/oop");
-
+
var Sender = function() {};
-
+
(function() {
-
+
oop.implement(this, EventEmitter);
-
+
this.callback = function(data, callbackId) {
postMessage({
type: "call",
@@ -177,7 +177,7 @@ window.initSender = function initSender() {
data: data
});
};
-
+
this.emit = function(name, data) {
postMessage({
type: "event",
@@ -185,9 +185,9 @@ window.initSender = function initSender() {
data: data
});
};
-
+
}).call(Sender.prototype);
-
+
return new Sender();
};
@@ -517,7 +517,7 @@ function validateDelta(docLines, delta) {
}
exports.applyDelta = function(docLines, delta, doNotValidate) {
-
+
var row = delta.start.row;
var startColumn = delta.start.column;
var line = docLines[row] || "";
@@ -582,7 +582,7 @@ EventEmitter._dispatchEvent = function(eventName, e) {
if (e.propagationStopped)
break;
}
-
+
if (defaultHandler && !e.defaultPrevented)
return defaultHandler(e, this);
};
@@ -610,7 +610,7 @@ EventEmitter.setDefaultHandler = function(eventName, callback) {
var handlers = this._defaultHandlers
if (!handlers)
handlers = this._defaultHandlers = {_disabled_: {}};
-
+
if (handlers[eventName]) {
var old = handlers[eventName];
var disabled = handlers._disabled_[eventName];
@@ -618,7 +618,7 @@ EventEmitter.setDefaultHandler = function(eventName, callback) {
handlers._disabled_[eventName] = disabled = [];
disabled.push(old);
var i = disabled.indexOf(callback);
- if (i != -1)
+ if (i != -1)
disabled.splice(i, 1);
}
handlers[eventName] = callback;
@@ -628,7 +628,7 @@ EventEmitter.removeDefaultHandler = function(eventName, callback) {
if (!handlers)
return;
var disabled = handlers._disabled_[eventName];
-
+
if (handlers[eventName] == callback) {
var old = handlers[eventName];
if (disabled)
@@ -684,7 +684,7 @@ var EventEmitter = require("./lib/event_emitter").EventEmitter;
var Anchor = exports.Anchor = function(doc, row, column) {
this.$onChange = this.onChange.bind(this);
this.attach(doc);
-
+
if (typeof column == "undefined")
this.setPosition(row.row, row.column);
else
@@ -707,16 +707,16 @@ var Anchor = exports.Anchor = function(doc, row, column) {
if (delta.start.row > this.row)
return;
-
+
var point = $getTransformedPoint(delta, {row: this.row, column: this.column}, this.$insertRight);
this.setPosition(point.row, point.column, true);
};
-
+
function $pointsInOrder(point1, point2, equalPointsInOrder) {
var bColIsAfter = equalPointsInOrder ? point1.column <= point2.column : point1.column < point2.column;
return (point1.row < point2.row) || (point1.row == point2.row && bColIsAfter);
}
-
+
function $getTransformedPoint(delta, point, moveIfEqual) {
var deltaIsInsert = delta.action == "insert";
var deltaRowShift = (deltaIsInsert ? 1 : -1) * (delta.end.row - delta.start.row);
@@ -735,7 +735,7 @@ var Anchor = exports.Anchor = function(doc, row, column) {
column: point.column + (point.row == deltaEnd.row ? deltaColShift : 0)
};
}
-
+
return {
row: deltaStart.row,
column: deltaStart.column
@@ -919,23 +919,23 @@ var Document = function(textOrLines) {
this.insert = function(position, text) {
if (this.getLength() <= 1)
this.$detectNewLine(text);
-
+
return this.insertMergedLines(position, this.$split(text));
};
this.insertInLine = function(position, text) {
var start = this.clippedPos(position.row, position.column);
var end = this.pos(position.row, position.column + text.length);
-
+
this.applyDelta({
start: start,
end: end,
action: "insert",
lines: [text]
}, true);
-
+
return this.clonePos(end);
};
-
+
this.clippedPos = function(row, column) {
var length = this.getLength();
if (row === undefined) {
@@ -952,15 +952,15 @@ var Document = function(textOrLines) {
column = Math.min(Math.max(column, 0), line.length);
return {row: row, column: column};
};
-
+
this.clonePos = function(pos) {
return {row: pos.row, column: pos.column};
};
-
+
this.pos = function(row, column) {
return {row: row, column: column};
};
-
+
this.$clipPosition = function(position) {
var length = this.getLength();
if (position.row >= length) {
@@ -984,21 +984,21 @@ var Document = function(textOrLines) {
column = this.$lines[row].length;
}
this.insertMergedLines({row: row, column: column}, lines);
- };
+ };
this.insertMergedLines = function(position, lines) {
var start = this.clippedPos(position.row, position.column);
var end = {
row: start.row + lines.length - 1,
column: (lines.length == 1 ? start.column : 0) + lines[lines.length - 1].length
};
-
+
this.applyDelta({
start: start,
end: end,
action: "insert",
lines: lines
});
-
+
return this.clonePos(end);
};
this.remove = function(range) {
@@ -1015,14 +1015,14 @@ var Document = function(textOrLines) {
this.removeInLine = function(row, startColumn, endColumn) {
var start = this.clippedPos(row, startColumn);
var end = this.clippedPos(row, endColumn);
-
+
this.applyDelta({
start: start,
end: end,
action: "remove",
lines: this.getLinesForRange({start: start, end: end})
}, true);
-
+
return this.clonePos(start);
};
this.removeFullLines = function(firstRow, lastRow) {
@@ -1033,10 +1033,10 @@ var Document = function(textOrLines) {
var startRow = ( deleteFirstNewLine ? firstRow - 1 : firstRow );
var startCol = ( deleteFirstNewLine ? this.getLine(startRow).length : 0 );
var endRow = ( deleteLastNewLine ? lastRow + 1 : lastRow );
- var endCol = ( deleteLastNewLine ? 0 : this.getLine(endRow).length );
+ var endCol = ( deleteLastNewLine ? 0 : this.getLine(endRow).length );
var range = new Range(startRow, startCol, endRow, endCol);
var deletedLines = this.$lines.slice(firstRow, lastRow + 1);
-
+
this.applyDelta({
start: range.start,
end: range.end,
@@ -1071,7 +1071,7 @@ var Document = function(textOrLines) {
else {
end = range.start;
}
-
+
return end;
};
this.applyDeltas = function(deltas) {
@@ -1090,17 +1090,17 @@ var Document = function(textOrLines) {
: !Range.comparePoints(delta.start, delta.end)) {
return;
}
-
+
if (isInsert && delta.lines.length > 20000)
this.$splitAndapplyLargeDelta(delta, 20000);
applyDelta(this.$lines, delta, doNotValidate);
this._signal("change", delta);
};
-
+
this.$splitAndapplyLargeDelta = function(delta, MAX) {
var lines = delta.lines;
var l = lines.length;
- var row = delta.start.row;
+ var row = delta.start.row;
var column = delta.start.column;
var from = 0, to = 0;
do {
@@ -1203,7 +1203,7 @@ exports.copyArray = function(array){
for (var i=0, l=array.length; i