From 26f5305593861b7bf416312400d43ca6b1d6e184 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 15 Nov 2016 23:22:25 +0000 Subject: [PATCH] Add jsonata function help --- Gruntfile.js | 6 +- editor/js/i18n.js | 2 +- editor/js/ui/common/typedInput.js | 5 +- editor/js/ui/editor.js | 23 ++++- editor/templates/index.mst | 8 +- editor/vendor/jsonata/formatter.js | 109 +++++++++++++++++++++ editor/vendor/jsonata/mode-jsonata.js | 14 ++- editor/vendor/jsonata/worker-jsonata.js | 121 ++++++++++++------------ red/api/index.js | 2 + red/api/locales/en-US/editor.json | 3 + red/api/locales/en-US/jsonata.json | 98 +++++++++++++++++++ 11 files changed, 318 insertions(+), 73 deletions(-) create mode 100644 editor/vendor/jsonata/formatter.js create mode 100644 red/api/locales/en-US/jsonata.json 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