Fix package.json conflict

This commit is contained in:
Nick O'Leary 2016-11-24 20:52:48 +00:00
commit f2797a4153
23 changed files with 4989 additions and 76 deletions

View File

@ -152,6 +152,10 @@ module.exports = function(grunt) {
"public/vendor/vendor.css": [ "public/vendor/vendor.css": [
// TODO: resolve relative resource paths in // TODO: resolve relative resource paths in
// bootstrap/FA/jquery // bootstrap/FA/jquery
],
"public/vendor/jsonata/jsonata.js": [
"node_modules/jsonata/jsonata.js",
"editor/vendor/jsonata/formatter.js"
] ]
} }
} }
@ -160,7 +164,11 @@ module.exports = function(grunt) {
build: { build: {
files: { files: {
'public/red/red.min.js': 'public/red/red.js', 'public/red/red.min.js': 'public/red/red.js',
'public/red/main.min.js': 'public/red/main.js' 'public/red/main.min.js': 'public/red/main.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',
'public/vendor/ace/snippets/jsonata.js': 'editor/vendor/jsonata/snippets-jsonata.js'
} }
} }
}, },

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

View File

@ -23,7 +23,7 @@ RED.i18n = (function() {
dynamicLoad: false, dynamicLoad: false,
load:'current', load:'current',
ns: { ns: {
namespaces: ["editor","node-red"], namespaces: ["editor","node-red","jsonata"],
defaultNs: "editor" defaultNs: "editor"
}, },
fallbackLng: ['en-US'], fallbackLng: ['en-US'],

View File

@ -85,6 +85,7 @@
} }
return true; return true;
} }
var allOptions = { var allOptions = {
msg: {value:"msg",label:"msg.",validate:validateExpression}, msg: {value:"msg",label:"msg.",validate:validateExpression},
flow: {value:"flow",label:"flow.",validate:validateExpression}, flow: {value:"flow",label:"flow.",validate:validateExpression},
@ -94,7 +95,22 @@
bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.png",options:["true","false"]}, bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.png",options:["true","false"]},
json: {value:"json",label:"JSON",icon:"red/images/typedInput/json.png", validate: function(v) { try{JSON.parse(v);return true;}catch(e){return false;}}}, json: {value:"json",label:"JSON",icon:"red/images/typedInput/json.png", validate: function(v) { try{JSON.parse(v);return true;}catch(e){return false;}}},
re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.png"}, re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.png"},
date: {value:"date",label:"timestamp",hasValue:false} date: {value:"date",label:"timestamp",hasValue:false},
jsonata: {
value: "jsonata",
label: "expression",
icon: "red/images/typedInput/expr.png",
validate: function(v) { try{jsonata(v);return true;}catch(e){return false;}},
expand:function() {
var that = this;
RED.editor.editExpression({
value: this.value().replace(/\t/g,"\n"),
complete: function(v) {
that.value(v.replace(/\n/g,"\t"));
}
})
}
}
}; };
var nlsd = false; var nlsd = false;
@ -188,7 +204,7 @@
that.uiSelect.addClass('red-ui-typedInput-focus'); that.uiSelect.addClass('red-ui-typedInput-focus');
}); });
this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"><i class="fa fa-ellipsis-h"></i></button>').appendTo(this.uiSelect);
this.type(this.options.default||this.typeList[0].value); this.type(this.options.default||this.typeList[0].value);
@ -322,11 +338,16 @@
this.uiSelect.width(this.uiWidth); this.uiSelect.width(this.uiWidth);
} }
if (this.typeMap[this.propertyType] && this.typeMap[this.propertyType].hasValue === false) { if (this.typeMap[this.propertyType] && this.typeMap[this.propertyType].hasValue === false) {
this.selectTrigger.css('width',"100%"); this.selectTrigger.addClass("red-ui-typedInput-full-width");
} else { } else {
this.selectTrigger.width('auto'); this.selectTrigger.removeClass("red-ui-typedInput-full-width");
var labelWidth = this._getLabelWidth(this.selectTrigger); var labelWidth = this._getLabelWidth(this.selectTrigger);
this.elementDiv.css('left',labelWidth+"px"); this.elementDiv.css('left',labelWidth+"px");
if (this.optionExpandButton.is(":visible")) {
this.elementDiv.css('right',"22px");
} else {
this.elementDiv.css('right','0');
}
if (this.optionSelectTrigger) { if (this.optionSelectTrigger) {
this.optionSelectTrigger.css({'left':(labelWidth)+"px",'width':'calc( 100% - '+labelWidth+'px )'}); this.optionSelectTrigger.css({'left':(labelWidth)+"px",'width':'calc( 100% - '+labelWidth+'px )'});
} }
@ -396,6 +417,9 @@
this.selectLabel.text(opt.label); this.selectLabel.text(opt.label);
} }
if (opt.options) { if (opt.options) {
if (this.optionExpandButton) {
this.optionExpandButton.hide();
}
if (this.optionSelectTrigger) { if (this.optionSelectTrigger) {
this.optionSelectTrigger.show(); this.optionSelectTrigger.show();
this.elementDiv.hide(); this.elementDiv.hide();
@ -429,6 +453,16 @@
} }
this.elementDiv.show(); this.elementDiv.show();
} }
if (opt.expand && typeof opt.expand === 'function') {
this.optionExpandButton.show();
this.optionExpandButton.off('click');
this.optionExpandButton.on('click',function(evt) {
evt.preventDefault();
opt.expand.call(that);
})
} else {
this.optionExpandButton.hide();
}
this.element.trigger('change',this.propertyType,this.value()); this.element.trigger('change',this.propertyType,this.value());
} }
if (image) { if (image) {

View File

@ -494,12 +494,13 @@ RED.editor = (function() {
} }
function getEditStackTitle() { function getEditStackTitle() {
var title = '<ul class="editor-tray-breadcrumbs">'; var title = '<ul class="editor-tray-breadcrumbs">';
for (var i=0;i<editStack.length;i++) { for (var i=0;i<editStack.length;i++) {
var node = editStack[i]; var node = editStack[i];
var label = node.type; var label = node.type;
if (node.type === 'subflow') { if (node.type === '_expression') {
label = "Expression editor";
} else if (node.type === 'subflow') {
label = RED._("subflow.editSubflow",{name:node.name}) label = RED._("subflow.editSubflow",{name:node.name})
} else if (node.type.indexOf("subflow:")===0) { } else if (node.type.indexOf("subflow:")===0) {
var subflow = RED.nodes.subflow(node.type.substring(8)); var subflow = RED.nodes.subflow(node.type.substring(8));
@ -526,6 +527,33 @@ RED.editor = (function() {
return title; return title;
} }
function buildEditForm(tray,formId,type,ns) {
var trayBody = tray.find('.editor-tray-body');
var dialogForm = $('<form id="'+formId+'" class="form-horizontal"></form>').appendTo(trayBody);
dialogForm.html($("script[data-template-name='"+type+"']").html());
ns = ns||"node-red";
dialogForm.find('[data-i18n]').each(function() {
var current = $(this).attr("data-i18n");
var keys = current.split(";");
for (var i=0;i<keys.length;i++) {
var key = keys[i];
if (key.indexOf(":") === -1) {
var prefix = "";
if (key.indexOf("[")===0) {
var parts = key.split("]");
prefix = parts[0]+"]";
key = parts[1];
}
keys[i] = prefix+ns+":"+key;
}
}
$(this).attr("data-i18n",keys.join(";"));
});
$('<input type="text" style="display: none;" />').prependTo(dialogForm);
dialogForm.submit(function(e) { e.preventDefault();});
return dialogForm;
}
function showEditDialog(node) { function showEditDialog(node) {
var editing_node = node; var editing_node = node;
editStack.push(node); editStack.push(node);
@ -763,33 +791,13 @@ RED.editor = (function() {
if (editing_node) { if (editing_node) {
RED.sidebar.info.refresh(editing_node); RED.sidebar.info.refresh(editing_node);
} }
var trayBody = tray.find('.editor-tray-body');
var dialogForm = $('<form id="dialog-form" class="form-horizontal"></form>').appendTo(trayBody);
dialogForm.html($("script[data-template-name='"+type+"']").html());
var ns; var ns;
if (node._def.set.module === "node-red") { if (node._def.set.module === "node-red") {
ns = "node-red"; ns = "node-red";
} else { } else {
ns = node._def.set.id; ns = node._def.set.id;
} }
dialogForm.find('[data-i18n]').each(function() { var dialogForm = buildEditForm(tray,"dialog-form",type,ns);
var current = $(this).attr("data-i18n");
var keys = current.split(";");
for (var i=0;i<keys.length;i++) {
var key = keys[i];
if (key.indexOf(":") === -1) {
var prefix = "";
if (key.indexOf("[")===0) {
var parts = key.split("]");
prefix = parts[0]+"]";
key = parts[1];
}
keys[i] = prefix+ns+":"+key;
}
}
$(this).attr("data-i18n",keys.join(";"));
});
$('<input type="text" style="display: none;" />').prependTo(dialogForm);
prepareEditDialog(node,node._def,"node-input"); prepareEditDialog(node,node._def,"node-input");
dialogForm.i18n(); dialogForm.i18n();
}, },
@ -882,7 +890,6 @@ RED.editor = (function() {
}, },
open: function(tray) { open: function(tray) {
var trayHeader = tray.find(".editor-tray-header"); var trayHeader = tray.find(".editor-tray-header");
var trayBody = tray.find(".editor-tray-body");
var trayFooter = tray.find(".editor-tray-footer"); var trayFooter = tray.find(".editor-tray-footer");
if (node_def.hasUsers !== false) { if (node_def.hasUsers !== false) {
@ -890,21 +897,8 @@ RED.editor = (function() {
} }
trayFooter.append('<span id="node-config-dialog-scope-container"><span id="node-config-dialog-scope-warning" data-i18n="[title]editor.errors.scopeChange"><i class="fa fa-warning"></i></span><select id="node-config-dialog-scope"></select></span>'); trayFooter.append('<span id="node-config-dialog-scope-container"><span id="node-config-dialog-scope-warning" data-i18n="[title]editor.errors.scopeChange"><i class="fa fa-warning"></i></span><select id="node-config-dialog-scope"></select></span>');
var dialogForm = $('<form id="node-config-dialog-edit-form" class="form-horizontal"></form>').appendTo(trayBody); var dialogForm = buildEditForm(tray,"node-config-dialog-edit-form",type,ns);
dialogForm.html($("script[data-template-name='"+type+"']").html());
dialogForm.find('[data-i18n]').each(function() {
var current = $(this).attr("data-i18n");
if (current.indexOf(":") === -1) {
var prefix = "";
if (current.indexOf("[")===0) {
var parts = current.split("]");
prefix = parts[0]+"]";
current = parts[1];
}
$(this).attr("data-i18n",prefix+ns+":"+current);
}
});
$('<input type="text" style="display: none;" />').prependTo(dialogForm);
prepareEditDialog(editing_config_node,node_def,"node-config-input"); prepareEditDialog(editing_config_node,node_def,"node-config-input");
if (editing_config_node._def.exclusive) { if (editing_config_node._def.exclusive) {
$("#node-config-dialog-scope").hide(); $("#node-config-dialog-scope").hide();
@ -1338,30 +1332,7 @@ RED.editor = (function() {
if (editing_node) { if (editing_node) {
RED.sidebar.info.refresh(editing_node); RED.sidebar.info.refresh(editing_node);
} }
var trayBody = tray.find('.editor-tray-body'); var dialogForm = buildEditForm(tray,"dialog-form","subflow-template");
var dialogForm = $('<form id="dialog-form" class="form-horizontal"></form>').appendTo(trayBody);
dialogForm.html($("script[data-template-name='subflow-template']").html());
var ns = "node-red";
dialogForm.find('[data-i18n]').each(function() {
var current = $(this).attr("data-i18n");
var keys = current.split(";");
for (var i=0;i<keys.length;i++) {
var key = keys[i];
if (key.indexOf(":") === -1) {
var prefix = "";
if (key.indexOf("[")===0) {
var parts = key.split("]");
prefix = parts[0]+"]";
key = parts[1];
}
keys[i] = prefix+ns+":"+key;
}
}
$(this).attr("data-i18n",keys.join(";"));
});
$('<input type="text" style="display: none;" />').prependTo(dialogForm);
dialogForm.submit(function(e) { e.preventDefault();});
subflowEditor = RED.editor.createEditor({ subflowEditor = RED.editor.createEditor({
id: 'subflow-input-info-editor', id: 'subflow-input-info-editor',
mode: 'ace/mode/markdown', mode: 'ace/mode/markdown',
@ -1397,6 +1368,167 @@ RED.editor = (function() {
RED.tray.show(trayOptions); RED.tray.show(trayOptions);
} }
function editExpression(options) {
var value = options.value;
var onComplete = options.complete;
var type = "_expression"
editStack.push({type:type});
RED.view.state(RED.state.EDITING);
var expressionEditor;
var trayOptions = {
title: getEditStackTitle(),
buttons: [
{
id: "node-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
RED.tray.close();
}
},
{
id: "node-dialog-ok",
text: RED._("common.label.done"),
class: "primary",
click: function() {
$("#node-input-expression-help").html("");
onComplete(expressionEditor.getValue());
RED.tray.close();
}
}
],
resize: function(dimensions) {
editTrayWidthCache[type] = dimensions.width;
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
var editorRow = $("#dialog-form>div.node-text-editor-row");
var height = $("#dialog-form").height();
for (var i=0;i<rows.size();i++) {
height -= $(rows[i]).outerHeight(true);
}
height -= (parseInt($("#dialog-form").css("marginTop"))+parseInt($("#dialog-form").css("marginBottom")));
$(".node-text-editor").css("height",height+"px");
expressionEditor.resize();
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
var dialogForm = buildEditForm(tray,'dialog-form','_expression','editor');
var funcSelect = $("#node-input-expression-func");
Object.keys(jsonata.functions).forEach(function(f) {
funcSelect.append($("<option></option>").val(f).text(f));
})
funcSelect.change(function(e) {
var f = $(this).val();
var args = RED._('jsonata:'+f+".args",{defaultValue:''});
var title = "<h5>"+f+"("+args+")</h5>";
var body = marked(RED._('jsonata:'+f+'.desc',{defaultValue:''}));
$("#node-input-expression-help").html(title+"<p>"+body+"</p>");
})
expressionEditor = RED.editor.createEditor({
id: 'node-input-expression',
value: "",
mode:"ace/mode/jsonata",
options: {
enableBasicAutocompletion:true,
enableSnippets:true,
enableLiveAutocompletion: true
}
});
var currentToken = null;
var currentTokenPos = -1;
var currentFunctionMarker = null;
expressionEditor.getSession().setValue(value||"",-1);
expressionEditor.on("changeSelection", function() {
var c = expressionEditor.getCursorPosition();
var token = expressionEditor.getSession().getTokenAt(c.row,c.column);
if (token !== currentToken || (token && /paren/.test(token.type) && c.column !== currentTokenPos)) {
currentToken = token;
var r,p;
var scopedFunction = null;
if (token && token.type === 'keyword') {
r = c.row;
scopedFunction = token;
} else {
var depth = 0;
var next = false;
if (token) {
if (token.type === 'paren.rparen') {
// If this is a block of parens ')))', set
// depth to offset against the cursor position
// within the block
currentTokenPos = c.column;
depth = c.column - (token.start + token.value.length);
}
r = c.row;
p = token.index;
} else {
r = c.row-1;
p = -1;
}
while ( scopedFunction === null && r > -1) {
var rowTokens = expressionEditor.getSession().getTokens(r);
if (p === -1) {
p = rowTokens.length-1;
}
while (p > -1) {
var type = rowTokens[p].type;
if (next) {
if (type === 'keyword') {
scopedFunction = rowTokens[p];
// console.log("HIT",scopedFunction);
break;
}
next = false;
}
if (type === 'paren.lparen') {
depth-=rowTokens[p].value.length;
} else if (type === 'paren.rparen') {
depth+=rowTokens[p].value.length;
}
if (depth < 0) {
next = true;
depth = 0;
}
// console.log(r,p,depth,next,rowTokens[p]);
p--;
}
if (!scopedFunction) {
r--;
}
}
}
expressionEditor.session.removeMarker(currentFunctionMarker);
if (scopedFunction) {
//console.log(token,.map(function(t) { return t.type}));
funcSelect.val(scopedFunction.value).change();
}
}
});
dialogForm.i18n();
$("#node-input-expression-func-insert").click(function(e) {
e.preventDefault();
var pos = expressionEditor.getCursorPosition();
var f = funcSelect.val();
var snippet = jsonata.getFunctionSnippet(f);
expressionEditor.insertSnippet(snippet);
expressionEditor.focus();
})
},
close: function() {
editStack.pop();
},
show: function() {}
}
if (editTrayWidthCache.hasOwnProperty(type)) {
trayOptions.width = editTrayWidthCache[type];
}
RED.tray.show(trayOptions);
}
return { return {
init: function() { init: function() {
RED.tray.init(); RED.tray.init();
@ -1413,6 +1545,7 @@ RED.editor = (function() {
edit: showEditDialog, edit: showEditDialog,
editConfig: showEditConfigNodeDialog, editConfig: showEditConfigNodeDialog,
editSubflow: showEditSubflowDialog, editSubflow: showEditSubflowDialog,
editExpression: editExpression,
validateNode: validateNode, validateNode: validateNode,
updateNodeProperties: updateNodeProperties, // TODO: only exposed for edit-undo updateNodeProperties: updateNodeProperties, // TODO: only exposed for edit-undo
@ -1447,7 +1580,6 @@ RED.editor = (function() {
} }
},100); },100);
} }
return editor; return editor;
} }
} }

8
editor/sass/ace.scss Normal file
View File

@ -0,0 +1,8 @@
.ace_gutter {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.ace_scroller {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}

View File

@ -21,6 +21,7 @@
@import "jquery"; @import "jquery";
@import "bootstrap"; @import "bootstrap";
@import "ace";
@import "dropdownMenu"; @import "dropdownMenu";

View File

@ -69,6 +69,9 @@
margin-right:4px; margin-right:4px;
margin-top: 1px; margin-top: 1px;
vertical-align: middle; vertical-align: middle;
&.fa-ellipsis-h {
top: -1px;
}
} }
&.disabled { &.disabled {
cursor: default; cursor: default;
@ -95,6 +98,19 @@
background: $typedInput-button-background-active; background: $typedInput-button-background-active;
text-decoration: none; text-decoration: none;
} }
&.red-ui-typedInput-full-width {
width: 100%;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
}
button.red-ui-typedInput-option-expand {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
right: 0;
} }
button.red-ui-typedInput-option-trigger { button.red-ui-typedInput-option-trigger {

View File

@ -159,7 +159,21 @@
<div class="form-row form-tips" id="subflow-dialog-user-count"></div> <div class="form-row form-tips" id="subflow-dialog-user-count"></div>
</script> </script>
<script type="text/x-red" data-template-name="_expression">
<div class="form-row node-text-editor-row">
<div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-expression"></div>
</div>
<div class="form-row">
<label for="node-input-expression-func" data-i18n="expressionEditor.functions"></label>
<select id="node-input-expression-func"></select>
<button id="node-input-expression-func-insert" class="editor-button" data-i18n="expressionEditor.insert"></button>
<div style="min-height: 200px;" id="node-input-expression-help"></div>
</div>
</script>
<script src="vendor/vendor.js"></script> <script src="vendor/vendor.js"></script>
<script src="vendor/jsonata/jsonata.min.js"></script>
<script src="vendor/ace/ace.js"></script> <script src="vendor/ace/ace.js"></script>
<script src="vendor/ace/ext-language_tools.js"></script> <script src="vendor/ace/ext-language_tools.js"></script>
<script src="{{ asset.red }}"></script> <script src="{{ asset.red }}"></script>

147
editor/vendor/jsonata/formatter.js vendored Normal file
View File

@ -0,0 +1,147 @@
(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<length;i++) {
var c = str[i];
if (!inString) {
if (c === "'" || c === '"') {
inString = true;
quoteChar = c;
frame = {type:"string",pos:i};
list.push(frame);
stack.push(frame);
} else if (c === ";") {
frame = {type:";",pos:i};
list.push(frame);
} else if (c === ",") {
frame = {type:",",pos:i};
list.push(frame);
} else if (/[\(\[\{]/.test(c)) {
frame = {type:"open-block",char:c,pos:i};
list.push(frame);
stack.push(frame);
} else if (/[\}\)\]]/.test(c)) {
var oldFrame = stack.pop();
if (matchingBrackets[oldFrame.char] !== c) {
//console.log("Stack frame mismatch",c,"at",i,"expected",matchingBrackets[oldFrame.char],"from",oldFrame.pos);
return str;
}
//console.log("Closing",c,"at",i,"compare",oldFrame.type,oldFrame.pos);
oldFrame.width = i-oldFrame.pos;
frame = {type:"close-block",pos:i,char:c,width:oldFrame.width}
list.push(frame);
}
} else {
if (c === quoteChar) {
// Next char must be a ]
inString = false;
stack.pop();
}
}
}
// console.log(stack);
var result = str;
var indent = 0;
var offset = 0;
var pre,post,indented;
var longStack = [];
list.forEach(function(f) {
if (f.type === ";" || f.type === ",") {
if (longStack[longStack.length-1]) {
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 if (f.type === "open-block") {
if (f.width > 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 =
{
'$append':{ args:['array','array'] },
'$average':{ args:['value'] },
'$boolean':{ args:['value'] },
'$count':{ args:['array'] },
'$exists':{ args:['value'] },
'$join':{ args:['array','separator'] },
'$keys':{ args:['object'] },
'$length':{ args:['string'] },
'$lookup':{ args:['object','key'] },
'$lowercase':{ args:['string'] },
'$map':{ args:[] },
'$max':{ args:['array'] },
'$min':{ args:['array'] },
'$not':{ args:['value'] },
'$number':{ args:['value'] },
'$reduce':{ args:[] },
'$split':{ args:['string','separator','limit'] },
'$spread':{ args:['object'] },
'$string':{ args:['value'] },
'$substring':{ args:['string','start','length'] },
'$substringAfter':{ args:['string','chars'] },
'$substringBefore':{ args:['string','chars'] },
'$sum':{ args:['array'] },
'$uppercase':{ args:['string'] }
}
jsonata.getFunctionSnippet = function(fn) {
var snippetText = "";
if (jsonata.functions.hasOwnProperty(fn)) {
var def = jsonata.functions[fn];
snippetText = "\\"+fn+"(";
if (def.args) {
snippetText += def.args.map(function(a,i) { return "${"+(i+1)+":"+a+"}"}).join(", ");
}
snippetText += ")\n"
}
return snippetText;
}
})();

134
editor/vendor/jsonata/mode-jsonata.js vendored Normal file
View File

@ -0,0 +1,134 @@
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 jsonataFunctions = Object.keys(jsonata.functions);
// sort in length order (long->short) otherwise substringAfter gets matched
// as substring etc.
jsonataFunctions.sort(function(A,B) {
return B.length-A.length;
});
jsonataFunctions = jsonataFunctions.join("|").replace(/\$/g,"\\$");
var JSONataHighlightRules = function() {
var keywordMapper = this.createKeywordMapper({
"keyword.operator":
"and|or|in",
"constant.language":
"null|Infinity|NaN|undefined",
"constant.language.boolean":
"true|false",
"storage.type":
"function"
}, "identifier");
this.$rules = {
"start" : [
{
token : "string",
regex : "'(?=.)",
next : "qstring"
},
{
token : "string",
regex : '"(?=.)',
next : "qqstring"
},
{
token : "constant.numeric", // hex
regex : /0(?:[xX][0-9a-fA-F]+|[bB][01]+)\b/
},
{
token : "constant.numeric", // float
regex : /[+-]?\d[\d_]*(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/
},
{ token: "keyword",
regex: /λ/
},
{
token: "keyword",
regex: jsonataFunctions
},
{
token : keywordMapper,
regex : "[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*"
},
{
token : "punctuation.operator",
regex : /[.](?![.])/
},
{
token : "keyword.operator",
regex : /\|\||<=|>=|\.\.|\*\*|!=|:=|[=<>`!$%&*+\-~\/^]/,
next : "start"
},
{
token : "punctuation.operator",
regex : /[?:,;.]/,
next : "start"
},
{
token : "paren.lparen",
regex : /[\[({]/,
next : "start"
},
{
token : "paren.rparen",
regex : /[\])}]/
}
],
"qqstring" : [
{
token : "string",
regex : '"|$',
next : "start"
}, {
defaultToken: "string"
}
],
"qstring" : [
{
token : "string",
regex : "'|$",
next : "start"
}, {
defaultToken: "string"
}
]
};
};
oop.inherits(JSONataHighlightRules, TextHighlightRules);
var TextMode = require("./text").Mode;
var Mode = function() {
this.HighlightRules = JSONataHighlightRules;
};
oop.inherits(Mode, TextMode);
(function() {
this.createWorker = function(session) {
var worker = new WorkerClient(["ace"], "ace/mode/jsonata_worker", "JSONataWorker");
worker.attachToDocument(session.getDocument());
worker.on("annotate", function(e) {
session.setAnnotations(e.data);
});
worker.on("terminate", function() {
session.clearAnnotations();
});
return worker;
};
this.$id = "ace/mode/jsonata";
}).call(Mode.prototype);
exports.Mode = Mode;
});

View File

@ -0,0 +1,11 @@
define("ace/snippets/jsonata",["require","exports","module"], function(require, exports, module) {
"use strict";
var snippetText = "";
for (var fn in jsonata.functions) {
if (jsonata.functions.hasOwnProperty(fn)) {
snippetText += "# "+fn+"\nsnippet "+fn+"\n\t"+jsonata.getFunctionSnippet(fn)+"\n"
}
}
exports.snippetText = snippetText;
exports.scope = "jsonata";
});

4236
editor/vendor/jsonata/worker-jsonata.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -502,6 +502,9 @@
"null":"is null", "null":"is null",
"nnull":"is not null", "nnull":"is not null",
"else":"otherwise" "else":"otherwise"
},
"errors": {
"invalid-expr": "Invalid expression: __error__"
} }
}, },
"change": { "change": {

View File

@ -64,7 +64,7 @@
var node = this; var node = this;
var previousValueType = {value:"prev",label:this._("inject.previous"),hasValue:false}; var previousValueType = {value:"prev",label:this._("inject.previous"),hasValue:false};
$("#node-input-property").typedInput({default:this.propertyType||'msg',types:['msg','flow','global']}); $("#node-input-property").typedInput({default:this.propertyType||'msg',types:['msg','flow','global','jsonata']});
var operators = [ var operators = [
{v:"eq",t:"=="}, {v:"eq",t:"=="},
{v:"neq",t:"!="}, {v:"neq",t:"!="},
@ -129,10 +129,10 @@
for (var d in operators) { for (var d in operators) {
selectField.append($("<option></option>").val(operators[d].v).text(operators[d].t)); selectField.append($("<option></option>").val(operators[d].v).text(operators[d].t));
} }
var valueField = $('<input/>',{class:"node-input-rule-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'str',types:['msg','flow','global','str','num',previousValueType]}); var valueField = $('<input/>',{class:"node-input-rule-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'str',types:['msg','flow','global','str','num','jsonata',previousValueType]});
var btwnValueField = $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'num',types:['msg','flow','global','str','num',previousValueType]}); var btwnValueField = $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata',previousValueType]});
var btwnAndLabel = $('<span/>',{class:"node-input-rule-btwn-label"}).text(" "+andLabel+" ").appendTo(row3); var btwnAndLabel = $('<span/>',{class:"node-input-rule-btwn-label"}).text(" "+andLabel+" ").appendTo(row3);
var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"margin-left:2px;"}).appendTo(row3).typedInput({default:'num',types:['msg','flow','global','str','num',previousValueType]}); var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"margin-left:2px;"}).appendTo(row3).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata',previousValueType]});
var finalspan = $('<span/>',{style:"float: right;margin-top: 6px;"}).appendTo(row); var finalspan = $('<span/>',{style:"float: right;margin-top: 6px;"}).appendTo(row);
finalspan.append(' &#8594; <span class="node-input-rule-index">'+(i+1)+'</span> '); finalspan.append(' &#8594; <span class="node-input-rule-index">'+(i+1)+'</span> ');
var caseSensitive = $('<input/>',{id:"node-input-rule-case-"+i,class:"node-input-rule-case",type:"checkbox",style:"width:auto;vertical-align:top"}).appendTo(row2); var caseSensitive = $('<input/>',{id:"node-input-rule-case-"+i,class:"node-input-rule-case",type:"checkbox",style:"width:auto;vertical-align:top"}).appendTo(row2);

View File

@ -16,6 +16,9 @@
module.exports = function(RED) { module.exports = function(RED) {
"use strict"; "use strict";
var jsonata = require('jsonata');
var operators = { var operators = {
'eq': function(a, b) { return a == b; }, 'eq': function(a, b) { return a == b; },
'neq': function(a, b) { return a != b; }, 'neq': function(a, b) { return a != b; },
@ -38,9 +41,20 @@ module.exports = function(RED) {
this.rules = n.rules || []; this.rules = n.rules || [];
this.property = n.property; this.property = n.property;
this.propertyType = n.propertyType || "msg"; this.propertyType = n.propertyType || "msg";
if (this.propertyType === 'jsonata') {
try {
this.property = jsonata(this.property);
} catch(err) {
this.error(RED._("switch.errors.invalid-expr",{error:err.message}));
return;
}
}
this.checkall = n.checkall || "true"; this.checkall = n.checkall || "true";
this.previousValue = null; this.previousValue = null;
var node = this; var node = this;
var valid = true;
for (var i=0; i<this.rules.length; i+=1) { for (var i=0; i<this.rules.length; i+=1) {
var rule = this.rules[i]; var rule = this.rules[i];
if (!rule.vt) { if (!rule.vt) {
@ -54,6 +68,13 @@ module.exports = function(RED) {
if (!isNaN(Number(rule.v))) { if (!isNaN(Number(rule.v))) {
rule.v = Number(rule.v); rule.v = Number(rule.v);
} }
} else if (rule.vt === "jsonata") {
try {
rule.v = jsonata(rule.v);
} catch(err) {
this.error(RED._("switch.errors.invalid-expr",{error:err.message}));
valid = false;
}
} }
if (typeof rule.v2 !== 'undefined') { if (typeof rule.v2 !== 'undefined') {
if (!rule.v2t) { if (!rule.v2t) {
@ -65,14 +86,30 @@ module.exports = function(RED) {
} }
if (rule.v2t === 'num') { if (rule.v2t === 'num') {
rule.v2 = Number(rule.v2); rule.v2 = Number(rule.v2);
} else if (rule.v2t === 'jsonata') {
try {
rule.v2 = jsonata(rule.v2);
} catch(err) {
this.error(RED._("switch.errors.invalid-expr",{error:err.message}));
valid = false;
}
} }
} }
} }
if (!valid) {
return;
}
this.on('input', function (msg) { this.on('input', function (msg) {
var onward = []; var onward = [];
try { try {
var prop = RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg); var prop;
if (node.propertyType === 'jsonata') {
prop = node.property.evaluate({msg:msg});
} else {
prop = RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg);
}
var elseflag = true; var elseflag = true;
for (var i=0; i<node.rules.length; i+=1) { for (var i=0; i<node.rules.length; i+=1) {
var rule = node.rules[i]; var rule = node.rules[i];
@ -80,12 +117,26 @@ module.exports = function(RED) {
var v1,v2; var v1,v2;
if (rule.vt === 'prev') { if (rule.vt === 'prev') {
v1 = node.previousValue; v1 = node.previousValue;
} else if (rule.vt === 'jsonata') {
try {
v1 = rule.v.evaluate({msg:msg});
} catch(err) {
node.error(RED._("switch.errors.invalid-expr",{error:err.message}));
return;
}
} else { } else {
v1 = RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg); v1 = RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg);
} }
v2 = rule.v2; v2 = rule.v2;
if (rule.v2t === 'prev') { if (rule.v2t === 'prev') {
v2 = node.previousValue; v2 = node.previousValue;
} else if (rule.v2t === 'jsonata') {
try {
v2 = rule.v2.evaluate({msg:msg});
} catch(err) {
node.error(RED._("switch.errors.invalid-expr",{error:err.message}));
return;
}
} else if (typeof v2 !== 'undefined') { } else if (typeof v2 !== 'undefined') {
v2 = RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg); v2 = RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg);
} }

View File

@ -150,7 +150,7 @@
.appendTo(row2); .appendTo(row2);
var propertyValue = $('<input/>',{class:"node-input-rule-property-value",type:"text"}) var propertyValue = $('<input/>',{class:"node-input-rule-property-value",type:"text"})
.appendTo(row2) .appendTo(row2)
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','date']}); .typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','date','jsonata']});
var row3_1 = $('<div/>').appendTo(row3); var row3_1 = $('<div/>').appendTo(row3);
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"}) $('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})

View File

@ -16,6 +16,7 @@
module.exports = function(RED) { module.exports = function(RED) {
"use strict"; "use strict";
var jsonata = require("jsonata");
function ChangeNode(n) { function ChangeNode(n) {
RED.nodes.createNode(this, n); RED.nodes.createNode(this, n);
@ -85,6 +86,13 @@ module.exports = function(RED) {
} }
} else if (rule.tot === 'bool') { } else if (rule.tot === 'bool') {
rule.to = /^true$/i.test(rule.to); rule.to = /^true$/i.test(rule.to);
} else if (rule.tot === 'jsonata') {
try {
rule.to = jsonata(rule.to);
} catch(e) {
valid = false;
this.error(RED._("change.errors.invalid-from",{error:e.message}));
}
} }
} }
@ -107,6 +115,8 @@ module.exports = function(RED) {
value = node.context().global.get(rule.to); value = node.context().global.get(rule.to);
} else if (rule.tot === 'date') { } else if (rule.tot === 'date') {
value = Date.now(); value = Date.now();
} else if (rule.tot === 'jsonata') {
value = rule.to.evaluate({msg:msg});
} }
if (rule.t === 'change') { if (rule.t === 'change') {
if (rule.fromt === 'msg' || rule.fromt === 'flow' || rule.fromt === 'global') { if (rule.fromt === 'msg' || rule.fromt === 'flow' || rule.fromt === 'global') {

View File

@ -41,6 +41,7 @@
"i18next":"1.10.6", "i18next":"1.10.6",
"is-utf8":"0.2.1", "is-utf8":"0.2.1",
"js-yaml": "3.6.1", "js-yaml": "3.6.1",
"jsonata":"1.0.10",
"media-typer": "0.3.0", "media-typer": "0.3.0",
"mqtt": "1.14.1", "mqtt": "1.14.1",
"mustache": "2.2.1", "mustache": "2.2.1",

View File

@ -155,6 +155,8 @@ function init(_server,_runtime) {
} }
} }
function start() { function start() {
i18n.registerMessageCatalog("jsonata",path.resolve(path.join(__dirname,"locales")),"jsonata.json")
return i18n.registerMessageCatalog("editor",path.resolve(path.join(__dirname,"locales")),"editor.json").then(function(){ return i18n.registerMessageCatalog("editor",path.resolve(path.join(__dirname,"locales")),"editor.json").then(function(){
comms.start(); comms.start();
}); });

View File

@ -318,5 +318,9 @@
}, },
"search": { "search": {
"empty": "No matches found" "empty": "No matches found"
},
"expressionEditor": {
"functions": "Functions",
"insert": "Insert"
} }
} }

View File

@ -0,0 +1,98 @@
{
"$string": {
"args": "arg",
"desc": "Casts the *arg* parameter to a string using the following casting rules:\n\n - Strings are unchanged\n - Functions are converted to an empty string\n - Numeric infinity and NaN throw an error because they cannot be represented as a JSON number\n - All other values are converted to a JSON string using the `JSON.stringify` function"
},
"$length": {
"args": "str",
"desc": "Returns the number of characters in the string `str`. An error is thrown if `str` is not a string."
},
"$substring": {
"args": "str, start[, length]",
"desc": "Returns a string containing the characters in the first parameter `str` starting at position `start` (zero-offset). If `length` is specified, then the substring will contain maximum `length` characters. If `start` is negative then it indicates the number of characters from the end of `str`."
},
"$substringBefore": {
"args": "str, chars",
"desc": "Returns the substring before the first occurrence of the character sequence chars in `str`. If `str` does not contain `chars`, then it returns `str`."
},
"$substringAfter": {
"args": "str, chars",
"desc": "Returns the substring after the first occurrence of the character sequence `chars` in `str`. If `str` does not contain `chars`, then it returns `str`."
},
"$uppercase": {
"args": "str",
"desc": "Returns a string with all the characters of `str` converted to uppercase."
},
"$lowercase": {
"args": "str",
"desc": "Returns a string with all the characters of `str` converted to lowercase."
},
"$split": {
"args": "str[, separator][, limit]",
"desc": "Splits the `str` parameter into an array of substrings. It is an error if `str` is not a string. The optional `separator` parameter specifies the characters within the `str` about which it should be split. If `separator` is not specified, then the empty string is assumed, and `str` will be split into an array of single characters. It is an error if `separator` is not a string. The optional `limit` parameter is a number that specifies the maximum number of substrings to include in the resultant array. Any additional substrings are discarded. If `limit` is not specified, then `str` is fully split with no limit to the size of the resultant array. It is an error if `limit` is not a non-negative number."
},
"$join": {
"args": "array[, separator]",
"desc": "Joins an array of component strings into a single concatenated string with each component string separated by the optional `separator` parameter. It is an error if the input `array` contains an item which isn't a string. If `separator` is not specified, then it is assumed to be the empty string, i.e. no `separator` between the component strings. It is an error if `separator` is not a string."
},
"$number": {
"args": "arg",
"desc": "Casts the `arg` parameter to a number using the following casting rules:\n\n - Numbers are unchanged\n - Strings that contain a sequence of characters that represent a legal JSON number are converted to that number\n - All other values cause an error to be thrown."
},
"$sum": {
"args": "array",
"desc": "Returns the arithmetic sum of an `array` of numbers. It is an error if the input `array` contains an item which isn't a number."
},
"$max": {
"args": "array",
"desc": "Returns the maximum number in an `array` of numbers. It is an error if the input `array` contains an item which isn't a number."
},
"$min": {
"args": "array",
"desc": "Returns the minimum number in an `array` of numbers. It is an error if the input `array` contains an item which isn't a number."
},
"$average": {
"args": "array",
"desc": "Returns the mean value of an `array` of numbers. It is an error if the input `array` contains an item which isn't a number."
},
"$boolean": {
"args": "arg",
"desc": "Casts the argument to a Boolean using the following rules:\n\n - `Boolean` : unchanged\n - `string`: empty : `false`\n - `string`: non-empty : `true`\n - `number`: `0` : `false`\n - `number`: non-zero : `true`\n - `null` : `false`\n - `array`: empty : `false`\n - `array`: contains a member that casts to `true` : `true`\n - `array`: all members cast to `false` : `false`\n - `object`: empty : `false`\n - `object`: non-empty : `true`\n - `function` : `false`"
},
"$not": {
"args": "arg",
"desc": "Returns Boolean NOT on the argument. `arg` is first cast to a boolean"
},
"$exists": {
"args": "arg",
"desc": "Returns Boolean `true` if the `arg` expression evaluates to a value, or `false` if the expression does not match anything (e.g. a path to a non-existent field reference)."
},
"$count": {
"args": "array",
"desc": "Returns the number of items in the array"
},
"$append": {
"args": "array, array",
"desc": "Appends two arrays"
},
"$keys": {
"args": "object",
"desc": "Returns an array containing the keys in the object. If the argument is an array of objects, then the array returned contains a de-duplicated list of all the keys in all of the objects."
},
"$lookup": {
"args": "object, key",
"desc": "Returns the value associated with key in object. If the first argument is an array of objects, then all of the objects in the array are searched, and the values associated with all occurrences of key are returned."
},
"$spread": {
"args": "object",
"desc": "Splits an object containing key/value pairs into an array of objects, each of which has a single key/value pair from the input object. If the parameter is an array of objects, then the resultant array contains an object for every key/value pair in every object in the supplied array."
}
}

View File

@ -15,6 +15,7 @@
**/ **/
var clone = require("clone"); var clone = require("clone");
var jsonata = require("jsonata");
function generateId() { function generateId() {
return (1+Math.random()*4294967295).toString(16); return (1+Math.random()*4294967295).toString(16);
@ -310,6 +311,8 @@ function evaluateNodeProperty(value, type, node, msg) {
return node.context().global.get(value); return node.context().global.get(value);
} else if (type === 'bool') { } else if (type === 'bool') {
return /^true$/i.test(value); return /^true$/i.test(value);
} else if (type === 'jsonata') {
return jsonata(value).evaluate({msg:msg});
} }
return value; return value;
} }