Split out expandable editors and add JS editor

This commit is contained in:
Nick O'Leary 2018-07-15 21:06:51 +01:00
parent 7fca04404e
commit 529b358c9b
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
9 changed files with 892 additions and 651 deletions

View File

@ -156,6 +156,7 @@ module.exports = function(grunt) {
"editor/js/ui/tab-context.js",
"editor/js/ui/palette-editor.js",
"editor/js/ui/editor.js",
"editor/js/ui/editors/*.js",
"editor/js/ui/tray.js",
"editor/js/ui/clipboard.js",
"editor/js/ui/library.js",

View File

@ -496,6 +496,8 @@ RED.editor = (function() {
label = node.type;
if (node.type === '_expression') {
label = RED._("expressionEditor.title");
} else if (node.type === '_js') {
label = RED._("jsEditor.title");
} else if (node.type === '_json') {
label = RED._("jsonEditor.title");
} else if (node.type === '_markdown') {
@ -1867,658 +1869,22 @@ RED.editor = (function() {
RED.tray.show(trayOptions);
}
var expressionTestCache = {};
function editExpression(options) {
var expressionTestCacheId = "_";
if (editStack.length > 0) {
expressionTestCacheId = editStack[editStack.length-1].id;
}
var value = options.value;
var onComplete = options.complete;
var type = "_expression"
editStack.push({type:type});
RED.view.state(RED.state.EDITING);
var expressionEditor;
var testDataEditor;
var testResultEditor
var panels;
var trayOptions = {
title: getEditStackTitle(),
width: "inherit",
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").text("");
onComplete(expressionEditor.getValue());
RED.tray.close();
}
}
],
resize: function(dimensions) {
if (dimensions) {
editTrayWidthCache[type] = dimensions.width;
}
var height = $("#dialog-form").height();
if (panels) {
panels.resize(height);
}
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
trayBody.addClass("node-input-expression-editor")
var dialogForm = buildEditForm(tray.find('.editor-tray-body'),'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();
});
$("#node-input-expression-reformat").click(function(evt) {
evt.preventDefault();
var v = expressionEditor.getValue()||"";
try {
v = jsonata.format(v);
} catch(err) {
// TODO: do an optimistic auto-format
}
expressionEditor.getSession().setValue(v||"",-1);
});
var tabs = RED.tabs.create({
element: $("#node-input-expression-tabs"),
onchange:function(tab) {
$(".node-input-expression-tab-content").hide();
tab.content.show();
trayOptions.resize();
}
})
tabs.addTab({
id: 'expression-help',
label: RED._('expressionEditor.functionReference'),
content: $("#node-input-expression-tab-help")
});
tabs.addTab({
id: 'expression-tests',
label: RED._('expressionEditor.test'),
content: $("#node-input-expression-tab-test")
});
testDataEditor = RED.editor.createEditor({
id: 'node-input-expression-test-data',
value: expressionTestCache[expressionTestCacheId] || '{\n "payload": "hello world"\n}',
mode:"ace/mode/json",
lineNumbers: false
});
var changeTimer;
$(".node-input-expression-legacy").click(function(e) {
e.preventDefault();
RED.sidebar.info.set(RED._("expressionEditor.compatModeDesc"));
RED.sidebar.info.show();
})
var testExpression = function() {
var value = testDataEditor.getValue();
var parsedData;
var currentExpression = expressionEditor.getValue();
var expr;
var usesContext = false;
var legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(currentExpression);
$(".node-input-expression-legacy").toggle(legacyMode);
try {
expr = jsonata(currentExpression);
expr.assign('flowContext',function(val) {
usesContext = true;
return null;
});
expr.assign('globalContext',function(val) {
usesContext = true;
return null;
});
} catch(err) {
testResultEditor.setValue(RED._("expressionEditor.errors.invalid-expr",{message:err.message}),-1);
return;
}
try {
parsedData = JSON.parse(value);
} catch(err) {
testResultEditor.setValue(RED._("expressionEditor.errors.invalid-msg",{message:err.toString()}))
return;
}
try {
var result = expr.evaluate(legacyMode?{msg:parsedData}:parsedData);
if (usesContext) {
testResultEditor.setValue(RED._("expressionEditor.errors.context-unsupported"),-1);
return;
}
var formattedResult;
if (result !== undefined) {
formattedResult = JSON.stringify(result,null,4);
} else {
formattedResult = RED._("expressionEditor.noMatch");
}
testResultEditor.setValue(formattedResult,-1);
} catch(err) {
testResultEditor.setValue(RED._("expressionEditor.errors.eval",{message:err.message}),-1);
}
}
testDataEditor.getSession().on('change', function() {
clearTimeout(changeTimer);
changeTimer = setTimeout(testExpression,200);
expressionTestCache[expressionTestCacheId] = testDataEditor.getValue();
});
expressionEditor.getSession().on('change', function() {
clearTimeout(changeTimer);
changeTimer = setTimeout(testExpression,200);
});
testResultEditor = RED.editor.createEditor({
id: 'node-input-expression-test-result',
value: "",
mode:"ace/mode/json",
lineNumbers: false,
readOnly: true
});
panels = RED.panels.create({
id:"node-input-expression-panels",
resize: function(p1Height,p2Height) {
var p1 = $("#node-input-expression-panel-expr");
p1Height -= $(p1.children()[0]).outerHeight(true);
var editorRow = $(p1.children()[1]);
p1Height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-expression").css("height",(p1Height-5)+"px");
expressionEditor.resize();
var p2 = $("#node-input-expression-panel-info > .form-row > div:first-child");
p2Height -= p2.outerHeight(true) + 20;
$(".node-input-expression-tab-content").height(p2Height);
$("#node-input-expression-test-data").css("height",(p2Height-5)+"px");
testDataEditor.resize();
$("#node-input-expression-test-result").css("height",(p2Height-5)+"px");
testResultEditor.resize();
}
});
$("#node-input-example-reformat").click(function(evt) {
evt.preventDefault();
var v = testDataEditor.getValue()||"";
try {
v = JSON.stringify(JSON.parse(v),null,4);
} catch(err) {
// TODO: do an optimistic auto-format
}
testDataEditor.getSession().setValue(v||"",-1);
});
testExpression();
},
close: function() {
editStack.pop();
expressionEditor.destroy();
testDataEditor.destroy();
},
show: function() {}
}
RED.tray.show(trayOptions);
}
function editJSON(options) {
var value = options.value;
var onComplete = options.complete;
var type = "_json"
editStack.push({type:type});
RED.view.state(RED.state.EDITING);
var expressionEditor;
var changeTimer;
var checkValid = function() {
var v = expressionEditor.getValue();
try {
JSON.parse(v);
$("#node-dialog-ok").removeClass('disabled');
return true;
} catch(err) {
$("#node-dialog-ok").addClass('disabled');
return false;
function showTypeEditor(type, options) {
if (RED.editor.types.hasOwnProperty(type)) {
if (editStack.length > 0) {
options.parent = editStack[editStack.length-1].id;
}
}
var trayOptions = {
title: options.title || getEditStackTitle(),
width: "inherit",
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() {
if (options.requireValid && !checkValid()) {
return;
}
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.find('.editor-tray-body'),'dialog-form',type,'editor');
expressionEditor = RED.editor.createEditor({
id: 'node-input-json',
value: "",
mode:"ace/mode/json"
});
expressionEditor.getSession().setValue(value||"",-1);
if (options.requireValid) {
expressionEditor.getSession().on('change', function() {
clearTimeout(changeTimer);
changeTimer = setTimeout(checkValid,200);
});
checkValid();
}
$("#node-input-json-reformat").click(function(evt) {
evt.preventDefault();
var v = expressionEditor.getValue()||"";
try {
v = JSON.stringify(JSON.parse(v),null,4);
} catch(err) {
// TODO: do an optimistic auto-format
}
expressionEditor.getSession().setValue(v||"",-1);
});
dialogForm.i18n();
},
close: function() {
editStack.push({type:type});
options.title = options.title || getEditStackTitle();
options.onclose = function() {
editStack.pop();
expressionEditor.destroy();
},
show: function() {}
}
RED.tray.show(trayOptions);
}
function editMarkdown(options) {
var value = options.value;
var onComplete = options.complete;
var type = "_markdown"
editStack.push({type:type});
RED.view.state(RED.state.EDITING);
var expressionEditor;
var trayOptions = {
title: options.title || getEditStackTitle(),
width: "inherit",
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() {
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.find('.editor-tray-body'),'dialog-form',type,'editor');
expressionEditor = RED.editor.createEditor({
id: 'node-input-markdown',
value: value,
mode:"ace/mode/markdown"
});
if (options.header) {
options.header.appendTo(tray.find('#node-input-markdown-title'));
}
dialogForm.i18n();
},
close: function() {
editStack.pop();
expressionEditor.destroy();
},
show: function() {}
}
RED.tray.show(trayOptions);
}
function stringToUTF8Array(str) {
var data = [];
var i=0, l = str.length;
for (i=0; i<l; i++) {
var char = str.charCodeAt(i);
if (char < 0x80) {
data.push(char);
} else if (char < 0x800) {
data.push(0xc0 | (char >> 6));
data.push(0x80 | (char & 0x3f));
} else if (char < 0xd800 || char >= 0xe000) {
data.push(0xe0 | (char >> 12));
data.push(0x80 | ((char>>6) & 0x3f));
data.push(0x80 | (char & 0x3f));
} else {
i++;
char = 0x10000 + (((char & 0x3ff)<<10) | (str.charAt(i) & 0x3ff));
data.push(0xf0 | (char >>18));
data.push(0x80 | ((char>>12) & 0x3f));
data.push(0x80 | ((char>>6) & 0x3f));
data.push(0x80 | (char & 0x3f));
}
RED.editor.types[type].show(options);
} else {
console.log("Unknown type editor:",type);
}
return data;
}
function editBuffer(options) {
var value = options.value;
var onComplete = options.complete;
var type = "_buffer"
editStack.push({type:type});
RED.view.state(RED.state.EDITING);
var bufferStringEditor = [];
var bufferBinValue;
var panels;
var trayOptions = {
title: getEditStackTitle(),
width: "inherit",
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() {
onComplete(JSON.stringify(bufferBinValue));
RED.tray.close();
}
}
],
resize: function(dimensions) {
if (dimensions) {
editTrayWidthCache[type] = dimensions.width;
}
var height = $("#dialog-form").height();
if (panels) {
panels.resize(height);
}
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
var dialogForm = buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor');
bufferStringEditor = RED.editor.createEditor({
id: 'node-input-buffer-str',
value: "",
mode:"ace/mode/text"
});
bufferStringEditor.getSession().setValue(value||"",-1);
bufferBinEditor = RED.editor.createEditor({
id: 'node-input-buffer-bin',
value: "",
mode:"ace/mode/text",
readOnly: true
});
var changeTimer;
var buildBuffer = function(data) {
var valid = true;
var isString = typeof data === 'string';
var binBuffer = [];
if (isString) {
bufferBinValue = stringToUTF8Array(data);
} else {
bufferBinValue = data;
}
var i=0,l=bufferBinValue.length;
var c = 0;
for(i=0;i<l;i++) {
var d = parseInt(bufferBinValue[i]);
if (!isString && (isNaN(d) || d < 0 || d > 255)) {
valid = false;
break;
}
if (i>0) {
if (i%8 === 0) {
if (i%16 === 0) {
binBuffer.push("\n");
} else {
binBuffer.push(" ");
}
} else {
binBuffer.push(" ");
}
}
binBuffer.push((d<16?"0":"")+d.toString(16).toUpperCase());
}
if (valid) {
$("#node-input-buffer-type-string").toggle(isString);
$("#node-input-buffer-type-array").toggle(!isString);
bufferBinEditor.setValue(binBuffer.join(""),1);
}
return valid;
}
var bufferStringUpdate = function() {
var value = bufferStringEditor.getValue();
var isValidArray = false;
if (/^[\s]*\[[\s\S]*\][\s]*$/.test(value)) {
isValidArray = true;
try {
var data = JSON.parse(value);
isValidArray = buildBuffer(data);
} catch(err) {
isValidArray = false;
}
}
if (!isValidArray) {
buildBuffer(value);
}
}
bufferStringEditor.getSession().on('change', function() {
clearTimeout(changeTimer);
changeTimer = setTimeout(bufferStringUpdate,200);
});
bufferStringUpdate();
dialogForm.i18n();
panels = RED.panels.create({
id:"node-input-buffer-panels",
resize: function(p1Height,p2Height) {
var p1 = $("#node-input-buffer-panel-str");
p1Height -= $(p1.children()[0]).outerHeight(true);
var editorRow = $(p1.children()[1]);
p1Height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-buffer-str").css("height",(p1Height-5)+"px");
bufferStringEditor.resize();
var p2 = $("#node-input-buffer-panel-bin");
editorRow = $(p2.children()[0]);
p2Height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-buffer-bin").css("height",(p2Height-5)+"px");
bufferBinEditor.resize();
}
});
$(".node-input-buffer-type").click(function(e) {
e.preventDefault();
RED.sidebar.info.set(RED._("bufferEditor.modeDesc"));
RED.sidebar.info.show();
})
},
close: function() {
editStack.pop();
bufferStringEditor.destroy();
bufferBinEditor.destroy();
},
show: function() {}
}
RED.tray.show(trayOptions);
}
return {
init: function() {
@ -2531,14 +1897,23 @@ RED.editor = (function() {
$("#node-dialog-cancel").click();
$("#node-config-dialog-cancel").click();
});
for (var type in RED.editor.types) {
if (RED.editor.types.hasOwnProperty(type)) {
RED.editor.types[type].init();
}
}
},
types: {},
edit: showEditDialog,
editConfig: showEditConfigNodeDialog,
editSubflow: showEditSubflowDialog,
editExpression: editExpression,
editJSON: editJSON,
editMarkdown: editMarkdown,
editBuffer: editBuffer,
editJavaScript: function(options) { showTypeEditor("_js",options) },
editExpression: function(options) { showTypeEditor("_expression", options) },
editJSON: function(options) { showTypeEditor("_json", options) },
editMarkdown: function(options) { showTypeEditor("_markdown", options) },
editBuffer: function(options) { showTypeEditor("_buffer", options) },
buildEditForm: buildEditForm,
validateNode: validateNode,
updateNodeProperties: updateNodeProperties, // TODO: only exposed for edit-undo

View File

@ -0,0 +1,209 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._buffer = (function() {
var template = '<script type="text/x-red" data-template-name="_buffer"><div id="node-input-buffer-panels"><div id="node-input-buffer-panel-str" class="red-ui-panel"><div class="form-row" style="margin-bottom: 3px; text-align: right;"><span class="node-input-buffer-type"><i class="fa fa-exclamation-circle"></i> <span id="node-input-buffer-type-string" data-i18n="bufferEditor.modeString"></span><span id="node-input-buffer-type-array" data-i18n="bufferEditor.modeArray"></span></span></div><div class="form-row node-text-editor-row"><div class="node-text-editor" id="node-input-buffer-str"></div></div></div><div id="node-input-buffer-panel-bin" class="red-ui-panel"><div class="form-row node-text-editor-row" style="margin-top: 10px"><div class="node-text-editor" id="node-input-buffer-bin"></div></div></div></div></script>';
function stringToUTF8Array(str) {
var data = [];
var i=0, l = str.length;
for (i=0; i<l; i++) {
var char = str.charCodeAt(i);
if (char < 0x80) {
data.push(char);
} else if (char < 0x800) {
data.push(0xc0 | (char >> 6));
data.push(0x80 | (char & 0x3f));
} else if (char < 0xd800 || char >= 0xe000) {
data.push(0xe0 | (char >> 12));
data.push(0x80 | ((char>>6) & 0x3f));
data.push(0x80 | (char & 0x3f));
} else {
i++;
char = 0x10000 + (((char & 0x3ff)<<10) | (str.charAt(i) & 0x3ff));
data.push(0xf0 | (char >>18));
data.push(0x80 | ((char>>12) & 0x3f));
data.push(0x80 | ((char>>6) & 0x3f));
data.push(0x80 | (char & 0x3f));
}
}
return data;
}
return {
init: function() {
$(template).appendTo(document.body);
},
show: function(options) {
var value = options.value;
var onComplete = options.complete;
var type = "_buffer"
RED.view.state(RED.state.EDITING);
var bufferStringEditor = [];
var bufferBinValue;
var panels;
var trayOptions = {
title: options.title,
width: "inherit",
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() {
onComplete(JSON.stringify(bufferBinValue));
RED.tray.close();
}
}
],
resize: function(dimensions) {
var height = $("#dialog-form").height();
if (panels) {
panels.resize(height);
}
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
var dialogForm = RED.editor.buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor');
bufferStringEditor = RED.editor.createEditor({
id: 'node-input-buffer-str',
value: "",
mode:"ace/mode/text"
});
bufferStringEditor.getSession().setValue(value||"",-1);
bufferBinEditor = RED.editor.createEditor({
id: 'node-input-buffer-bin',
value: "",
mode:"ace/mode/text",
readOnly: true
});
var changeTimer;
var buildBuffer = function(data) {
var valid = true;
var isString = typeof data === 'string';
var binBuffer = [];
if (isString) {
bufferBinValue = stringToUTF8Array(data);
} else {
bufferBinValue = data;
}
var i=0,l=bufferBinValue.length;
var c = 0;
for(i=0;i<l;i++) {
var d = parseInt(bufferBinValue[i]);
if (!isString && (isNaN(d) || d < 0 || d > 255)) {
valid = false;
break;
}
if (i>0) {
if (i%8 === 0) {
if (i%16 === 0) {
binBuffer.push("\n");
} else {
binBuffer.push(" ");
}
} else {
binBuffer.push(" ");
}
}
binBuffer.push((d<16?"0":"")+d.toString(16).toUpperCase());
}
if (valid) {
$("#node-input-buffer-type-string").toggle(isString);
$("#node-input-buffer-type-array").toggle(!isString);
bufferBinEditor.setValue(binBuffer.join(""),1);
}
return valid;
}
var bufferStringUpdate = function() {
var value = bufferStringEditor.getValue();
var isValidArray = false;
if (/^[\s]*\[[\s\S]*\][\s]*$/.test(value)) {
isValidArray = true;
try {
var data = JSON.parse(value);
isValidArray = buildBuffer(data);
} catch(err) {
isValidArray = false;
}
}
if (!isValidArray) {
buildBuffer(value);
}
}
bufferStringEditor.getSession().on('change', function() {
clearTimeout(changeTimer);
changeTimer = setTimeout(bufferStringUpdate,200);
});
bufferStringUpdate();
dialogForm.i18n();
panels = RED.panels.create({
id:"node-input-buffer-panels",
resize: function(p1Height,p2Height) {
var p1 = $("#node-input-buffer-panel-str");
p1Height -= $(p1.children()[0]).outerHeight(true);
var editorRow = $(p1.children()[1]);
p1Height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-buffer-str").css("height",(p1Height-5)+"px");
bufferStringEditor.resize();
var p2 = $("#node-input-buffer-panel-bin");
editorRow = $(p2.children()[0]);
p2Height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-buffer-bin").css("height",(p2Height-5)+"px");
bufferBinEditor.resize();
}
});
$(".node-input-buffer-type").click(function(e) {
e.preventDefault();
RED.sidebar.info.set(RED._("bufferEditor.modeDesc"));
RED.sidebar.info.show();
})
},
close: function() {
if (options.onclose) {
options.onclose();
}
bufferStringEditor.destroy();
bufferBinEditor.destroy();
},
show: function() {}
}
RED.tray.show(trayOptions);
}
}
})();

View File

@ -0,0 +1,325 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._expression = (function() {
var template = '<script type="text/x-red" data-template-name="_expression"><div id="node-input-expression-panels"><div id="node-input-expression-panel-expr" class="red-ui-panel"><div class="form-row" style="margin-bottom: 3px; text-align: right;"><span class="node-input-expression-legacy"><i class="fa fa-exclamation-circle"></i> <span data-i18n="expressionEditor.compatMode"></span></span><button id="node-input-expression-reformat" class="editor-button editor-button-small"><span data-i18n="expressionEditor.format"></span></button></div><div class="form-row node-text-editor-row"><div class="node-text-editor" id="node-input-expression"></div></div></div><div id="node-input-expression-panel-info" class="red-ui-panel"><div class="form-row"><ul id="node-input-expression-tabs"></ul><div id="node-input-expression-tab-help" class="node-input-expression-tab-content hide"><div><select id="node-input-expression-func"></select><button id="node-input-expression-func-insert" class="editor-button" data-i18n="expressionEditor.insert"></button></div><div id="node-input-expression-help"></div></div><div id="node-input-expression-tab-test" class="node-input-expression-tab-content hide"><div><span style="display: inline-block; width: calc(50% - 5px);"><span data-i18n="expressionEditor.data"></span><button style="float: right; margin-right: 5px;" id="node-input-example-reformat" class="editor-button editor-button-small"><span data-i18n="jsonEditor.format"></span></button></span><span style="display: inline-block; width: calc(50% - 5px);" data-i18n="expressionEditor.result"></span></div><div style="display: inline-block; width: calc(50% - 5px);" class="node-text-editor" id="node-input-expression-test-data"></div><div style="display: inline-block; width: calc(50% - 5px);" class="node-text-editor" id="node-input-expression-test-result"></div></div></div></div></div></script>';
var expressionTestCache = {};
return {
init: function() {
$(template).appendTo(document.body);
},
show: function(options) {
var expressionTestCacheId = options.parent||"_";
var value = options.value;
var onComplete = options.complete;
var type = "_expression"
RED.view.state(RED.state.EDITING);
var expressionEditor;
var testDataEditor;
var testResultEditor
var panels;
var trayOptions = {
title: options.title,
width: "inherit",
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").text("");
onComplete(expressionEditor.getValue());
RED.tray.close();
}
}
],
resize: function(dimensions) {
var height = $("#dialog-form").height();
if (panels) {
panels.resize(height);
}
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
trayBody.addClass("node-input-expression-editor")
var dialogForm = RED.editor.buildEditForm(tray.find('.editor-tray-body'),'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();
});
$("#node-input-expression-reformat").click(function(evt) {
evt.preventDefault();
var v = expressionEditor.getValue()||"";
try {
v = jsonata.format(v);
} catch(err) {
// TODO: do an optimistic auto-format
}
expressionEditor.getSession().setValue(v||"",-1);
});
var tabs = RED.tabs.create({
element: $("#node-input-expression-tabs"),
onchange:function(tab) {
$(".node-input-expression-tab-content").hide();
tab.content.show();
trayOptions.resize();
}
})
tabs.addTab({
id: 'expression-help',
label: RED._('expressionEditor.functionReference'),
content: $("#node-input-expression-tab-help")
});
tabs.addTab({
id: 'expression-tests',
label: RED._('expressionEditor.test'),
content: $("#node-input-expression-tab-test")
});
testDataEditor = RED.editor.createEditor({
id: 'node-input-expression-test-data',
value: expressionTestCache[expressionTestCacheId] || '{\n "payload": "hello world"\n}',
mode:"ace/mode/json",
lineNumbers: false
});
var changeTimer;
$(".node-input-expression-legacy").click(function(e) {
e.preventDefault();
RED.sidebar.info.set(RED._("expressionEditor.compatModeDesc"));
RED.sidebar.info.show();
})
var testExpression = function() {
var value = testDataEditor.getValue();
var parsedData;
var currentExpression = expressionEditor.getValue();
var expr;
var usesContext = false;
var legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(currentExpression);
$(".node-input-expression-legacy").toggle(legacyMode);
try {
expr = jsonata(currentExpression);
expr.assign('flowContext',function(val) {
usesContext = true;
return null;
});
expr.assign('globalContext',function(val) {
usesContext = true;
return null;
});
} catch(err) {
testResultEditor.setValue(RED._("expressionEditor.errors.invalid-expr",{message:err.message}),-1);
return;
}
try {
parsedData = JSON.parse(value);
} catch(err) {
testResultEditor.setValue(RED._("expressionEditor.errors.invalid-msg",{message:err.toString()}))
return;
}
try {
var result = expr.evaluate(legacyMode?{msg:parsedData}:parsedData);
if (usesContext) {
testResultEditor.setValue(RED._("expressionEditor.errors.context-unsupported"),-1);
return;
}
var formattedResult;
if (result !== undefined) {
formattedResult = JSON.stringify(result,null,4);
} else {
formattedResult = RED._("expressionEditor.noMatch");
}
testResultEditor.setValue(formattedResult,-1);
} catch(err) {
testResultEditor.setValue(RED._("expressionEditor.errors.eval",{message:err.message}),-1);
}
}
testDataEditor.getSession().on('change', function() {
clearTimeout(changeTimer);
changeTimer = setTimeout(testExpression,200);
expressionTestCache[expressionTestCacheId] = testDataEditor.getValue();
});
expressionEditor.getSession().on('change', function() {
clearTimeout(changeTimer);
changeTimer = setTimeout(testExpression,200);
});
testResultEditor = RED.editor.createEditor({
id: 'node-input-expression-test-result',
value: "",
mode:"ace/mode/json",
lineNumbers: false,
readOnly: true
});
panels = RED.panels.create({
id:"node-input-expression-panels",
resize: function(p1Height,p2Height) {
var p1 = $("#node-input-expression-panel-expr");
p1Height -= $(p1.children()[0]).outerHeight(true);
var editorRow = $(p1.children()[1]);
p1Height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-expression").css("height",(p1Height-5)+"px");
expressionEditor.resize();
var p2 = $("#node-input-expression-panel-info > .form-row > div:first-child");
p2Height -= p2.outerHeight(true) + 20;
$(".node-input-expression-tab-content").height(p2Height);
$("#node-input-expression-test-data").css("height",(p2Height-5)+"px");
testDataEditor.resize();
$("#node-input-expression-test-result").css("height",(p2Height-5)+"px");
testResultEditor.resize();
}
});
$("#node-input-example-reformat").click(function(evt) {
evt.preventDefault();
var v = testDataEditor.getValue()||"";
try {
v = JSON.stringify(JSON.parse(v),null,4);
} catch(err) {
// TODO: do an optimistic auto-format
}
testDataEditor.getSession().setValue(v||"",-1);
});
testExpression();
},
close: function() {
if (options.onclose) {
options.onclose();
}
expressionEditor.destroy();
testDataEditor.destroy();
},
show: function() {}
}
RED.tray.show(trayOptions);
}
}
})();

103
editor/js/ui/editors/js.js Normal file
View File

@ -0,0 +1,103 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._js = (function() {
var template = '<script type="text/x-red" data-template-name="_js"><div class="form-row node-text-editor-row" style="width: 700px"><div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-js"></div></div></script>';
return {
init: function() {
$(template).appendTo(document.body);
},
show: function(options) {
var value = options.value;
var onComplete = options.complete;
var type = "_js"
RED.view.state(RED.state.EDITING);
var expressionEditor;
var changeTimer;
var trayOptions = {
title: options.title,
width: "inherit",
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() {
onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition());
RED.tray.close();
}
}
],
resize: function(dimensions) {
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 = RED.editor.buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor');
expressionEditor = RED.editor.createEditor({
id: 'node-input-js',
mode: 'ace/mode/javascript',
value: value,
globals: {
msg:true,
context:true,
RED: true,
util: true,
flow: true,
global: true,
console: true,
Buffer: true,
setTimeout: true,
clearTimeout: true,
setInterval: true,
clearInterval: true
}
});
if (options.cursor) {
expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
}
dialogForm.i18n();
},
close: function() {
expressionEditor.destroy();
if (options.onclose) {
options.onclose();
}
},
show: function() {}
}
RED.tray.show(trayOptions);
}
}
})();

View File

@ -0,0 +1,118 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._json = (function() {
var template = '<script type="text/x-red" data-template-name="_json"><div class="form-row" style="margin-bottom: 3px; text-align: right;"><button id="node-input-json-reformat" class="editor-button editor-button-small"><span data-i18n="jsonEditor.format"></span></button></div><div class="form-row node-text-editor-row"><div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-json"></div></div></script>';
return {
init: function() {
$(template).appendTo(document.body);
},
show: function(options) {
var value = options.value;
var onComplete = options.complete;
var type = "_json"
RED.view.state(RED.state.EDITING);
var expressionEditor;
var changeTimer;
var checkValid = function() {
var v = expressionEditor.getValue();
try {
JSON.parse(v);
$("#node-dialog-ok").removeClass('disabled');
return true;
} catch(err) {
$("#node-dialog-ok").addClass('disabled');
return false;
}
}
var trayOptions = {
title: options.title,
width: "inherit",
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() {
if (options.requireValid && !checkValid()) {
return;
}
onComplete(expressionEditor.getValue());
RED.tray.close();
}
}
],
resize: function(dimensions) {
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 = RED.editor.buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor');
expressionEditor = RED.editor.createEditor({
id: 'node-input-json',
value: "",
mode:"ace/mode/json"
});
expressionEditor.getSession().setValue(value||"",-1);
if (options.requireValid) {
expressionEditor.getSession().on('change', function() {
clearTimeout(changeTimer);
changeTimer = setTimeout(checkValid,200);
});
checkValid();
}
$("#node-input-json-reformat").click(function(evt) {
evt.preventDefault();
var v = expressionEditor.getValue()||"";
try {
v = JSON.stringify(JSON.parse(v),null,4);
} catch(err) {
// TODO: do an optimistic auto-format
}
expressionEditor.getSession().setValue(v||"",-1);
});
dialogForm.i18n();
},
close: function() {
expressionEditor.destroy();
if (options.onclose) {
options.onclose();
}
},
show: function() {}
}
RED.tray.show(trayOptions);
}
}
})();

View File

@ -0,0 +1,90 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._markdown = (function() {
var template = '<script type="text/x-red" data-template-name="_markdown"><div class="form-row" id="node-input-markdown-title" style="margin-bottom: 3px; text-align: right;"></div><div class="form-row node-text-editor-row"><div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-markdown"></div></div></script>';
return {
init: function() {
$(template).appendTo(document.body);
},
show: function(options) {
var value = options.value;
var onComplete = options.complete;
var type = "_markdown"
RED.view.state(RED.state.EDITING);
var expressionEditor;
var trayOptions = {
title: options.title,
width: "inherit",
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() {
onComplete(expressionEditor.getValue());
RED.tray.close();
}
}
],
resize: function(dimensions) {
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 = RED.editor.buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor');
expressionEditor = RED.editor.createEditor({
id: 'node-input-markdown',
value: value,
mode:"ace/mode/markdown"
});
if (options.header) {
options.header.appendTo(tray.find('#node-input-markdown-title'));
}
dialogForm.i18n();
},
close: function() {
expressionEditor.destroy();
if (options.onclose) {
options.onclose();
}
},
show: function() {}
}
RED.tray.show(trayOptions);
}
}
})();

View File

@ -9,7 +9,8 @@
<input type="hidden" id="node-input-func" autofocus="autofocus">
<input type="hidden" id="node-input-noerr">
</div>
<div class="form-row node-text-editor-row">
<div class="form-row node-text-editor-row" style="position:relative">
<div style="position: absolute; right:0; bottom:calc(100% + 3px);"><button id="node-function-expand-js" class="editor-button editor-button-small"><i class="fa fa-expand"></i></button></div>
<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-func-editor" ></div>
</div>
<div class="form-row">
@ -119,6 +120,22 @@
fields:['name','outputs']
});
this.editor.focus();
$("#node-function-expand-js").click(function(e) {
e.preventDefault();
var value = that.editor.getValue();
RED.editor.editJavaScript({
value: value,
cursor: that.editor.getCursorPosition(),
complete: function(v,cursor) {
that.editor.setValue(v, -1);
that.editor.gotoLine(cursor.row+1,cursor.column,false);
setTimeout(function() {
that.editor.focus();
},300);
}
})
})
},
oneditsave: function() {
var annot = this.editor.getSession().getAnnotations();

View File

@ -639,6 +639,9 @@
"eval": "Error evaluating expression:\n __message__"
}
},
"jsEditor": {
"title": "JavaScript editor"
},
"jsonEditor": {
"title": "JSON editor",
"format": "format JSON"