diff --git a/.travis.yml b/.travis.yml index 3d064cb65..04d4ab3ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,6 @@ node_js: - "7" - "6" - "4" - - "0.10" script: - istanbul cover ./node_modules/.bin/grunt --report lcovonly && istanbul report text && ( cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js || true ) && rm -rf coverage before_script: diff --git a/Gruntfile.js b/Gruntfile.js index 2c5da4ab1..fc881ddee 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -98,7 +98,7 @@ module.exports = function(grunt) { src: [ // Ensure editor source files are concatenated in // the right order - "editor/js/main.js", + "editor/js/red.js", "editor/js/events.js", "editor/js/i18n.js", "editor/js/settings.js", @@ -110,13 +110,16 @@ module.exports = function(grunt) { "editor/js/nodes.js", "editor/js/history.js", "editor/js/validators.js", + "editor/js/ui/utils.js", "editor/js/ui/common/editableList.js", "editor/js/ui/common/menu.js", "editor/js/ui/common/popover.js", "editor/js/ui/common/searchBox.js", "editor/js/ui/common/tabs.js", "editor/js/ui/common/typedInput.js", + "editor/js/ui/actions.js", "editor/js/ui/deploy.js", + "editor/js/ui/diff.js", "editor/js/ui/keyboard.js", "editor/js/ui/workspaces.js", "editor/js/ui/view.js", @@ -131,6 +134,7 @@ module.exports = function(grunt) { "editor/js/ui/library.js", "editor/js/ui/notifications.js", "editor/js/ui/search.js", + "editor/js/ui/typeSearch.js", "editor/js/ui/subflow.js", "editor/js/ui/touch/radialMenu.js" ], @@ -150,6 +154,10 @@ module.exports = function(grunt) { "public/vendor/vendor.css": [ // TODO: resolve relative resource paths in // bootstrap/FA/jquery + ], + "public/vendor/jsonata/jsonata.js": [ + "node_modules/jsonata/jsonata.js", + "editor/vendor/jsonata/formatter.js" ] } } @@ -157,7 +165,12 @@ module.exports = function(grunt) { uglify: { build: { 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/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' } } }, @@ -183,12 +196,18 @@ module.exports = function(grunt) { 'red/api/locales/en-US/editor.json', 'red/runtime/locales/en-US/runtime.json' ] + }, + keymaps: { + src: [ + 'editor/js/keymap.json' + ] } }, attachCopyright: { js: { src: [ - 'public/red/red.min.js' + 'public/red/red.min.js', + 'public/red/main.min.js' ] }, css: { @@ -218,7 +237,7 @@ module.exports = function(grunt) { files: [ 'editor/js/**/*.js' ], - tasks: ['concat','uglify','attachCopyright:js'] + tasks: ['copy:build','concat','uglify','attachCopyright:js'] }, sass: { files: [ @@ -234,6 +253,12 @@ module.exports = function(grunt) { ], tasks: ['jsonlint:messages'] }, + keymaps: { + files: [ + 'editor/js/keymap.json' + ], + tasks: ['jsonlint:keymaps','copy:build'] + }, misc: { files: [ 'CHANGELOG.md' @@ -267,40 +292,49 @@ module.exports = function(grunt) { copy: { build: { - files:[{ - cwd: 'editor/images', - src: '**', - expand: true, - dest: 'public/red/images/' - }, - { - cwd: 'editor/vendor', - src: [ - 'ace/**', - //'bootstrap/css/**', - 'bootstrap/img/**', - 'jquery/css/**', - 'font-awesome/**' - ], - expand: true, - dest: 'public/vendor/' - }, - { - cwd: 'editor/icons', - src: '**', - expand: true, - dest: 'public/icons/' - }, - { - expand: true, - src: ['editor/index.html','editor/favicon.ico'], - dest: 'public/', - flatten: true - }, - { - src: 'CHANGELOG.md', - dest: 'public/red/about' - } + files:[ + { + src: 'editor/js/main.js', + dest: 'public/red/main.js' + }, + { + src: 'editor/js/keymap.json', + dest: 'public/red/keymap.json' + }, + { + cwd: 'editor/images', + src: '**', + expand: true, + dest: 'public/red/images/' + }, + { + cwd: 'editor/vendor', + src: [ + 'ace/**', + //'bootstrap/css/**', + 'bootstrap/img/**', + 'jquery/css/**', + 'font-awesome/**' + ], + expand: true, + dest: 'public/vendor/' + }, + { + cwd: 'editor/icons', + src: '**', + expand: true, + dest: 'public/icons/' + }, + { + expand: true, + src: ['editor/index.html','editor/favicon.ico'], + dest: 'public/', + flatten: true + }, + { + src: 'CHANGELOG.md', + dest: 'public/red/about' + } ] }, release: { @@ -426,7 +460,7 @@ module.exports = function(grunt) { grunt.registerTask('build', 'Builds editor content', - ['clean:build','concat:build','concat:vendor','uglify:build','sass:build','jsonlint:messages','copy:build','attachCopyright']); + ['clean:build','jsonlint','concat:build','concat:vendor','copy:build','uglify:build','sass:build','attachCopyright']); grunt.registerTask('dev', 'Developer mode: run node-red, watch for source changes and build/restart', diff --git a/editor/icons/parser-yaml.png b/editor/icons/parser-yaml.png new file mode 100644 index 000000000..e4cb2f20b Binary files /dev/null and b/editor/icons/parser-yaml.png differ diff --git a/editor/images/typedInput/expr.png b/editor/images/typedInput/expr.png new file mode 100644 index 000000000..704105ce5 Binary files /dev/null and b/editor/images/typedInput/expr.png differ diff --git a/editor/js/history.js b/editor/js/history.js index 215de3835..169d05b3f 100644 --- a/editor/js/history.js +++ b/editor/js/history.js @@ -16,6 +16,289 @@ RED.history = (function() { var undo_history = []; + function undoEvent(ev) { + var i; + var len; + var node; + var subflow; + var modifiedTabs = {}; + if (ev) { + if (ev.t == 'multi') { + len = ev.events.length; + for (i=len-1;i>=0;i--) { + undoEvent(ev.events[i]); + } + } else if (ev.t == 'replace') { + RED.nodes.clear(); + var imported = RED.nodes.import(ev.config); + imported[0].forEach(function(n) { + if (ev.changed[n.id]) { + n.changed = true; + } + }) + + RED.nodes.version(ev.rev); + } else if (ev.t == 'add') { + if (ev.nodes) { + for (i=0;i 0) { + subflow = RED.nodes.subflow(ev.subflowInputs[0].z); + subflow.in.push(ev.subflowInputs[0]); + subflow.in[0].dirty = true; + } + if (ev.subflowOutputs && ev.subflowOutputs.length > 0) { + subflow = RED.nodes.subflow(ev.subflowOutputs[0].z); + ev.subflowOutputs.sort(function(a,b) { return a.i-b.i}); + for (i=0;i= output.i) { + l.sourcePort++; + } + } + }); + } + } + if (ev.subflow && ev.subflow.hasOwnProperty('instances')) { + ev.subflow.instances.forEach(function(n) { + var node = RED.nodes.node(n.id); + if (node) { + node.changed = n.changed; + node.dirty = true; + } + }); + } + if (subflow) { + RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) { + n.inputs = subflow.in.length; + n.outputs = subflow.out.length; + while (n.outputs > n.ports.length) { + n.ports.push(n.ports.length); + } + n.resize = true; + n.dirty = true; + }); + } + if (ev.nodes) { + for (i=0;i ev.subflow.inputCount) { + ev.node.in.splice(ev.subflow.inputCount); + } else if (ev.subflow.inputs.length > 0) { + ev.node.in = ev.node.in.concat(ev.subflow.inputs); + } + } + if (ev.subflow.hasOwnProperty('outputCount')) { + if (ev.node.out.length > ev.subflow.outputCount) { + ev.node.out.splice(ev.subflow.outputCount); + } else if (ev.subflow.outputs.length > 0) { + ev.node.out = ev.node.out.concat(ev.subflow.outputs); + } + } + if (ev.subflow.hasOwnProperty('instances')) { + ev.subflow.instances.forEach(function(n) { + var node = RED.nodes.node(n.id); + if (node) { + node.changed = n.changed; + node.dirty = true; + } + }); + } + RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) { + n.inputs = ev.node.in.length; + n.outputs = ev.node.out.length; + RED.editor.updateNodeProperties(n); + }); + } else { + var outputMap; + if (ev.outputMap) { + outputMap = {}; + for (var port in ev.outputMap) { + if (ev.outputMap.hasOwnProperty(port) && ev.outputMap[port] !== -1) { + outputMap[ev.outputMap[port]] = port; + } + } + } + RED.editor.updateNodeProperties(ev.node,outputMap); + RED.editor.validateNode(ev.node); + } + if (ev.links) { + for (i=0;i 0) { - subflow = RED.nodes.subflow(ev.subflowInputs[0].z); - subflow.in.push(ev.subflowInputs[0]); - subflow.in[0].dirty = true; - } - if (ev.subflowOutputs && ev.subflowOutputs.length > 0) { - subflow = RED.nodes.subflow(ev.subflowOutputs[0].z); - ev.subflowOutputs.sort(function(a,b) { return a.i-b.i}); - for (i=0;i= output.i) { - l.sourcePort++; - } - } - }); - } - } - if (ev.subflow && ev.subflow.hasOwnProperty('instances')) { - ev.subflow.instances.forEach(function(n) { - var node = RED.nodes.node(n.id); - if (node) { - node.changed = n.changed; - node.dirty = true; - } - }); - } - if (subflow) { - RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) { - n.inputs = subflow.in.length; - n.outputs = subflow.out.length; - while (n.outputs > n.ports.length) { - n.ports.push(n.ports.length); - } - n.resize = true; - n.dirty = true; - }); - } - if (ev.nodes) { - for (i=0;i ev.subflow.inputCount) { - ev.node.in.splice(ev.subflow.inputCount); - } else if (ev.subflow.inputs.length > 0) { - ev.node.in = ev.node.in.concat(ev.subflow.inputs); - } - } - if (ev.subflow.hasOwnProperty('outputCount')) { - if (ev.node.out.length > ev.subflow.outputCount) { - ev.node.out.splice(ev.subflow.outputCount); - } else if (ev.subflow.outputs.length > 0) { - ev.node.out = ev.node.out.concat(ev.subflow.outputs); - } - } - if (ev.subflow.hasOwnProperty('instances')) { - ev.subflow.instances.forEach(function(n) { - var node = RED.nodes.node(n.id); - if (node) { - node.changed = n.changed; - node.dirty = true; - } - }); - } - RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) { - n.inputs = ev.node.in.length; - n.outputs = ev.node.out.length; - RED.editor.updateNodeProperties(n); - }); - } else { - RED.editor.updateNodeProperties(ev.node); - RED.editor.validateNode(ev.node); - } - if (ev.links) { - for (i=0;i
  • ")+"
  • "; RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success"); @@ -172,12 +186,11 @@ var RED = (function() { } function loadEditor() { - var menuOptions = []; menuOptions.push({id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[ - {id:"menu-item-view-show-grid",label:RED._("menu.label.view.showGrid"),toggle:true,onselect:RED.view.toggleShowGrid}, - {id:"menu-item-view-snap-grid",label:RED._("menu.label.view.snapGrid"),toggle:true,onselect:RED.view.toggleSnapGrid}, - {id:"menu-item-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:toggleStatus, selected: true}, + {id:"menu-item-view-show-grid",label:RED._("menu.label.view.showGrid"),toggle:true,onselect:"core:toggle-show-grid"}, + {id:"menu-item-view-snap-grid",label:RED._("menu.label.view.snapGrid"),toggle:true,onselect:"core:toggle-snap-grid"}, + {id:"menu-item-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:"core:toggle-status", selected: true}, null, // {id:"menu-item-bidi",label:RED._("menu.label.view.textDir"),options:[ // {id:"menu-item-bidi-default",toggle:"text-direction",label:RED._("menu.label.view.defaultDir"),selected: true, onselect:function(s) { if(s){RED.text.bidi.setTextDirection("")}}}, @@ -186,48 +199,47 @@ var RED = (function() { // {id:"menu-item-bidi-auto",toggle:"text-direction",label:RED._("menu.label.view.auto"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("auto")}}} // ]}, // null, - {id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true} + {id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:"core:toggle-sidebar", selected: true} ]}); menuOptions.push(null); menuOptions.push({id:"menu-item-import",label:RED._("menu.label.import"),options:[ - {id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:RED.clipboard.import}, + {id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:"core:import"}, {id:"menu-item-import-library",label:RED._("menu.label.library"),options:[]} ]}); menuOptions.push({id:"menu-item-export",label:RED._("menu.label.export"),disabled:true,options:[ - {id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),disabled:true,onselect:RED.clipboard.export}, - {id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:RED.library.export} + {id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),disabled:true,onselect:"core:export"}, + {id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:"core:library-export"} ]}); menuOptions.push(null); - menuOptions.push({id:"menu-item-search",label:RED._("menu.label.search"),onselect:RED.search.show}); + menuOptions.push({id:"menu-item-search",label:RED._("menu.label.search"),onselect:"core:search"}); menuOptions.push(null); - menuOptions.push({id:"menu-item-config-nodes",label:RED._("menu.label.displayConfig"),onselect:function() {}}); + menuOptions.push({id:"menu-item-config-nodes",label:RED._("menu.label.displayConfig"),onselect:"core:show-config-tab"}); menuOptions.push({id:"menu-item-workspace",label:RED._("menu.label.flows"),options:[ - {id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:RED.workspaces.add}, - {id:"menu-item-workspace-edit",label:RED._("menu.label.rename"),onselect:RED.workspaces.edit}, - {id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:RED.workspaces.remove} + {id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:"core:add-flow"}, + {id:"menu-item-workspace-edit",label:RED._("menu.label.rename"),onselect:"core:edit-flow"}, + {id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:"core:remove-flow"} ]}); menuOptions.push({id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [ - {id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:RED.subflow.createSubflow}, - {id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:RED.subflow.convertToSubflow}, + {id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:"core:create-subflow"}, + {id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:"core:convert-to-subflow"}, ]}); menuOptions.push(null); if (RED.settings.theme('palette.editable') !== false) { RED.palette.editor.init(); - menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:RED.palette.editor.show}); + menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"}); menuOptions.push(null); } - menuOptions.push({id:"menu-item-keyboard-shortcuts",label:RED._("menu.label.keyboardShortcuts"),onselect:RED.keyboard.showHelp}); + menuOptions.push({id:"menu-item-keyboard-shortcuts",label:RED._("menu.label.keyboardShortcuts"),onselect:"core:show-help"}); + menuOptions.push({id:"menu-item-show-tips",label:RED._("menu.label.showTips"),toggle:true,selected:true,onselect:"core:toggle-show-tips"}); menuOptions.push({id:"menu-item-help", label: RED.settings.theme("menu.menu-item-help.label","Node-RED website"), href: RED.settings.theme("menu.menu-item-help.url","http://nodered.org/docs") }); - menuOptions.push({id:"menu-item-node-red-version", label:"v"+RED.settings.version, onselect: showAbout }); + menuOptions.push({id:"menu-item-node-red-version", label:"v"+RED.settings.version, onselect: "core:show-about" }); - RED.menu.init({id:"btn-sidemenu",options: menuOptions}); RED.user.init(); - RED.library.init(); RED.palette.init(); RED.sidebar.init(); @@ -237,15 +249,21 @@ var RED = (function() { RED.search.init(); RED.view.init(); RED.editor.init(); + RED.keyboard.init(); + RED.diff.init(); + + RED.menu.init({id:"btn-sidemenu",options: menuOptions}); RED.deploy.init(RED.settings.theme("deployButton",null)); - RED.keyboard.add("workspace", /* ? */ 191,{shift:true},function() {RED.keyboard.showHelp();d3.event.preventDefault();}); + RED.actions.add("core:show-about", showAbout); + RED.comms.connect(); $("#main-container").show(); $(".header-toolbar").show(); + loadNodeList(); } @@ -261,8 +279,4 @@ var RED = (function() { RED.settings.init(loadEditor); }) }); - - - return { - }; })(); diff --git a/editor/js/nodes.js b/editor/js/nodes.js index 732c339de..38c5bcb77 100644 --- a/editor/js/nodes.js +++ b/editor/js/nodes.js @@ -24,21 +24,13 @@ RED.nodes = (function() { var workspacesOrder =[]; var subflows = {}; var loadedFlowVersion = null; - var pending = { - deleted: {}, - added: {} - }; + + var initialLoad; var dirty = false; function setDirty(d) { dirty = d; - if (!d) { - pending = { - deleted: {}, - added: {} - }; - } RED.events.emit("nodes:change",{dirty:dirty}); } @@ -62,6 +54,9 @@ RED.nodes = (function() { getNodeList: function() { return nodeList; }, + getNodeTypes: function() { + return Object.keys(nodeDefinitions); + }, setNodeList: function(list) { nodeList = []; for(var i=0;i n.outputs)) { n.outputs = n.wires.length; } if (n.outputs) { for (var i=0;i',{href:"#",class:"red-ui-editableList-item-remove editor-button editor-button-small"}).appendTo(li); $('',{class:"fa fa-remove"}).appendTo(deleteButton); li.addClass("red-ui-editableList-item-removable"); - deleteButton.click(function() { + deleteButton.click(function(evt) { + evt.preventDefault(); var data = row.data('data'); li.addClass("red-ui-editableList-item-deleting") li.fadeOut(300, function() { diff --git a/editor/js/ui/common/menu.js b/editor/js/ui/common/menu.js index b2a7ee6c6..79fc3a286 100644 --- a/editor/js/ui/common/menu.js +++ b/editor/js/ui/common/menu.js @@ -13,9 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ - - - RED.menu = (function() { var menuItems = {}; @@ -34,17 +31,17 @@ RED.menu = (function() { var savedStateActive = isSavedStateActive(opt.id); if (savedStateActive) { link.addClass("active"); - opt.onselect.call(opt, true); + triggerAction(opt.id,true); } else if (savedStateActive === false) { link.removeClass("active"); - opt.onselect.call(opt, false); + triggerAction(opt.id,false); } else if (opt.hasOwnProperty("selected")) { if (opt.selected) { link.addClass("active"); } else { link.removeClass("active"); } - opt.onselect.call(opt, opt.selected); + triggerAction(opt.id,opt.selected); } } @@ -107,10 +104,12 @@ RED.menu = (function() { setSelected(opt.id, !selected); } } else { - opt.onselect.call(opt); + triggerAction(opt.id); } }); - setInitialState(); + if (opt.toggle) { + setInitialState(); + } } else if (opt.href) { link.attr("target","_blank").attr("href",opt.href); } else if (!opt.options) { @@ -164,6 +163,19 @@ RED.menu = (function() { } } + function triggerAction(id, args) { + var opt = menuItems[id]; + var callback = opt.onselect; + if (typeof opt.onselect === 'string') { + callback = RED.actions.get(opt.onselect); + } + if (callback) { + callback.call(opt,args); + } else { + console.log("No callback for",id,opt.onselect); + } + } + function isSavedStateActive(id) { return RED.settings.get("menu-" + id); } @@ -187,11 +199,15 @@ RED.menu = (function() { $("#"+id).removeClass("active"); } if (opt && opt.onselect) { - opt.onselect.call(opt,state); + triggerAction(opt.id,state); } setSavedState(id, state); } + function toggleSelected(id) { + setSelected(id,!isSelected(id)); + } + function setDisabled(id,state) { if (state) { $("#"+id).parent().addClass("disabled"); @@ -231,16 +247,6 @@ RED.menu = (function() { var opt = menuItems[id]; if (opt) { opt.onselect = action; - // $("#"+id).click(function() { - // if ($(this).parent().hasClass("disabled")) { - // return; - // } - // if (menuItems[id].toggle) { - // setSelected(id,!isSelected(id)); - // } else { - // menuItems[id].onselect.call(menuItems[id]); - // } - // }); } } @@ -248,6 +254,7 @@ RED.menu = (function() { init: createMenu, setSelected: setSelected, isSelected: isSelected, + toggleSelected: toggleSelected, setDisabled: setDisabled, addItem: addItem, removeItem: removeItem, diff --git a/editor/js/ui/common/tabs.js b/editor/js/ui/common/tabs.js index 783db3d41..57991f58e 100644 --- a/editor/js/ui/common/tabs.js +++ b/editor/js/ui/common/tabs.js @@ -105,6 +105,9 @@ RED.tabs = (function() { if (typeof link === "string") { link = ul.find("a[href='#"+link+"']"); } + if (link.length === 0) { + return; + } if (!link.parent().hasClass("active")) { ul.children().removeClass("active"); ul.children().css({"transition": "width 100ms"}); @@ -126,6 +129,18 @@ RED.tabs = (function() { },100); } } + function activatePreviousTab() { + var previous = ul.find("li.active").prev(); + if (previous.length > 0) { + activateTab(previous.find("a")); + } + } + function activateNextTab() { + var next = ul.find("li.active").next(); + if (next.length > 0) { + activateTab(next.find("a")); + } + } function updateTabWidths() { var tabs = ul.find("li.red-ui-tab"); @@ -303,6 +318,8 @@ RED.tabs = (function() { }, removeTab: removeTab, activateTab: activateTab, + nextTab: activateNextTab, + previousTab: activatePreviousTab, resize: updateTabWidths, count: function() { return ul.find("li.red-ui-tab").size(); diff --git a/editor/js/ui/common/typedInput.js b/editor/js/ui/common/typedInput.js index 2bfea5e18..67fb03eed 100644 --- a/editor/js/ui/common/typedInput.js +++ b/editor/js/ui/common/typedInput.js @@ -14,87 +14,31 @@ * limitations under the License. **/ (function($) { - function validateExpression(str) { - var length = str.length; - var start = 0; - var inString = false; - var inBox = false; - var quoteChar; - var v; - for (var i=0;i" ).parent(); var attrStyle = this.element.attr('style'); var m; - if ((m = /width\s*:\s*(\d+%)/i.exec(attrStyle)) !== null) { + if ((m = /width\s*:\s*(\d+(%|px))/i.exec(attrStyle)) !== null) { this.element.css('width','100%'); this.uiSelect.width(m[1]); this.uiWidth = null; @@ -133,7 +77,7 @@ this.options.types = this.options.types||Object.keys(allOptions); - this.selectTrigger = $('').prependTo(this.uiSelect); + this.selectTrigger = $('').prependTo(this.uiSelect); $('').appendTo(this.selectTrigger); this.selectLabel = $('').appendTo(this.selectTrigger); @@ -160,32 +104,72 @@ }) this.selectTrigger.click(function(event) { event.preventDefault(); - if (that.typeList.length > 1) { - that._showMenu(that.menu,that.selectTrigger); - } else { - that.element.focus(); - } + that._showTypeMenu(); }); + this.selectTrigger.on('keydown',function(evt) { + if (evt.keyCode === 40) { + // Down + that._showTypeMenu(); + } + }).on('focus', function() { + that.uiSelect.addClass('red-ui-typedInput-focus'); + }) // explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline' - this.optionSelectTrigger = $('').appendTo(this.uiSelect); - this.optionSelectLabel = $('').prependTo(this.optionSelectTrigger); + this.optionSelectTrigger = $('').appendTo(this.uiSelect); + this.optionSelectLabel = $('').prependTo(this.optionSelectTrigger); this.optionSelectTrigger.click(function(event) { event.preventDefault(); - if (that.optionMenu) { - that.optionMenu.css({ - minWidth:that.optionSelectLabel.width() - }); - - that._showMenu(that.optionMenu,that.optionSelectLabel) + that._showOptionSelectMenu(); + }).on('keydown', function(evt) { + if (evt.keyCode === 40) { + // Down + that._showOptionSelectMenu(); } + }).on('blur', function() { + that.uiSelect.removeClass('red-ui-typedInput-focus'); + }).on('focus', function() { + that.uiSelect.addClass('red-ui-typedInput-focus'); }); + + this.optionExpandButton = $('').appendTo(this.uiSelect); + + this.type(this.options.default||this.typeList[0].value); }, + _showTypeMenu: function() { + if (this.typeList.length > 1) { + this._showMenu(this.menu,this.selectTrigger); + this.menu.find("[value='"+this.propertyType+"']").focus(); + } else { + this.element.focus(); + } + }, + _showOptionSelectMenu: function() { + if (this.optionMenu) { + this.optionMenu.css({ + minWidth:this.optionSelectLabel.width() + }); + + this._showMenu(this.optionMenu,this.optionSelectLabel); + var selectedOption = this.optionMenu.find("[value='"+this.value()+"']"); + if (selectedOption.length === 0) { + selectedOption = this.optionMenu.children(":first"); + } + selectedOption.focus(); + + } + }, _hideMenu: function(menu) { $(document).off("mousedown.close-property-select"); menu.hide(); - this.element.focus(); + if (this.elementDiv.is(":visible")) { + this.element.focus(); + } else if (this.optionSelectTrigger.is(":visible")){ + this.optionSelectTrigger.focus(); + } else { + this.selectTrigger.focus(); + } }, _createMenu: function(opts,callback) { var that = this; @@ -194,7 +178,7 @@ if (typeof opt === 'string') { opt = {value:opt,label:opt}; } - var op = $('').attr("value",opt.value).appendTo(menu); + var op = $('').attr("value",opt.value).appendTo(menu); if (opt.label) { op.text(opt.label); } @@ -214,6 +198,21 @@ display: "none", }); menu.appendTo(document.body); + + menu.on('keydown', function(evt) { + if (evt.keyCode === 40) { + // DOWN + $(this).children(":focus").next().focus(); + } else if (evt.keyCode === 38) { + // UP + $(this).children(":focus").prev().focus(); + } else if (evt.keyCode === 27) { + that._hideMenu(menu); + } + }) + + + return menu; }, @@ -267,13 +266,18 @@ this.uiSelect.width(this.uiWidth); } 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 { - this.selectTrigger.width('auto'); + this.selectTrigger.removeClass("red-ui-typedInput-full-width"); var labelWidth = this._getLabelWidth(this.selectTrigger); 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) { - this.optionSelectTrigger.css('left',(labelWidth+5)+"px"); + this.optionSelectTrigger.css({'left':(labelWidth)+"px",'width':'calc( 100% - '+labelWidth+'px )'}); } } }, @@ -341,6 +345,9 @@ this.selectLabel.text(opt.label); } if (opt.options) { + if (this.optionExpandButton) { + this.optionExpandButton.hide(); + } if (this.optionSelectTrigger) { this.optionSelectTrigger.show(); this.elementDiv.hide(); @@ -374,6 +381,16 @@ } 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()); } if (image) { diff --git a/editor/js/ui/deploy.js b/editor/js/ui/deploy.js index 55f3d8a7a..7e9ee350d 100644 --- a/editor/js/ui/deploy.js +++ b/editor/js/ui/deploy.js @@ -30,13 +30,13 @@ RED.deploy = (function() { var deploymentType = "full"; + var currentDiff = null; + function changeDeploymentType(type) { deploymentType = type; $("#btn-deploy-icon").attr("src",deploymentTypes[type].img); } - var currentDiff = null; - /** * options: * type: "default" - Button with drop-down options - no further customisation available @@ -98,21 +98,33 @@ RED.deploy = (function() { height: "auto", buttons: [ { - text: RED._("deploy.confirm.button.cancel"), + text: RED._("common.label.cancel"), click: function() { $( this ).dialog( "close" ); } }, - // { - // id: "node-dialog-confirm-deploy-review", - // text: RED._("deploy.confirm.button.review"), - // class: "primary", - // click: function() { - // showDiff(); - // $( this ).dialog( "close" ); - // } - // }, { + id: "node-dialog-confirm-deploy-review", + text: RED._("deploy.confirm.button.review"), + class: "primary disabled", + click: function() { + if (!$("#node-dialog-confirm-deploy-review").hasClass('disabled')) { + RED.diff.showRemoteDiff(); + $( this ).dialog( "close" ); + } + } + }, + { + id: "node-dialog-confirm-deploy-merge", + text: RED._("deploy.confirm.button.merge"), + class: "primary disabled", + click: function() { + RED.diff.mergeDiff(currentDiff); + $( this ).dialog( "close" ); + } + }, + { + id: "node-dialog-confirm-deploy-deploy", text: RED._("deploy.confirm.button.confirm"), class: "primary", click: function() { @@ -136,10 +148,37 @@ RED.deploy = (function() { }, open: function() { if ($( "#node-dialog-confirm-deploy-type" ).val() === "conflict") { - // $("#node-dialog-confirm-deploy-review").show(); + $("#node-dialog-confirm-deploy-deploy").hide(); + $("#node-dialog-confirm-deploy-review").addClass('disabled').show(); + $("#node-dialog-confirm-deploy-merge").addClass('disabled').show(); + currentDiff = null; + $("#node-dialog-confirm-deploy-conflict-checking").show(); + $("#node-dialog-confirm-deploy-conflict-auto-merge").hide(); + $("#node-dialog-confirm-deploy-conflict-manual-merge").hide(); + + var now = Date.now(); + RED.diff.getRemoteDiff(function(diff) { + var ellapsed = Math.max(1000 - (Date.now()-now), 0); + currentDiff = diff; + setTimeout(function() { + $("#node-dialog-confirm-deploy-conflict-checking").hide(); + var d = Object.keys(diff.conflicts); + if (d.length === 0) { + $("#node-dialog-confirm-deploy-conflict-auto-merge").show(); + $("#node-dialog-confirm-deploy-merge").removeClass('disabled') + } else { + $("#node-dialog-confirm-deploy-conflict-manual-merge").show(); + } + $("#node-dialog-confirm-deploy-review").removeClass('disabled') + },ellapsed); + }) + + $("#node-dialog-confirm-deploy-hide").parent().hide(); } else { - // $("#node-dialog-confirm-deploy-review").hide(); + $("#node-dialog-confirm-deploy-deploy").show(); + $("#node-dialog-confirm-deploy-review").hide(); + $("#node-dialog-confirm-deploy-merge").hide(); $("#node-dialog-confirm-deploy-hide").parent().show(); } } @@ -157,198 +196,7 @@ RED.deploy = (function() { } }); - // $("#node-dialog-view-diff").dialog({ - // title: RED._('deploy.confirm.button.review'), - // modal: true, - // autoOpen: false, - // buttons: [ - // { - // text: RED._("deploy.confirm.button.cancel"), - // click: function() { - // $( this ).dialog( "close" ); - // } - // }, - // { - // text: RED._("deploy.confirm.button.merge"), - // class: "primary", - // click: function() { - // $( this ).dialog( "close" ); - // } - // } - // ], - // open: function() { - // $(this).dialog({width:Math.min($(window).width(),900),height:Math.min($(window).height(),600)}); - // } - // }); - // $("#node-dialog-view-diff-diff").editableList({ - // addButton: false, - // scrollOnAdd: false, - // addItem: function(container,i,object) { - // var tab = object.tab.n; - // var tabDiv = $('
    ',{class:"node-diff-tab collapsed"}).appendTo(container); - // - // var titleRow = $('
    ',{class:"node-diff-tab-title"}).appendTo(tabDiv); - // titleRow.click(function(evt) { - // evt.preventDefault(); - // titleRow.parent().toggleClass('collapsed'); - // }) - // var chevron = $('').appendTo(titleRow); - // var title = $('').html(tab.label||tab.id).appendTo(titleRow); - // - // var stats = $('',{class:"node-diff-tab-stats"}).appendTo(titleRow); - // - // var addedCount = 0; - // var deletedCount = 0; - // var changedCount = 0; - // var conflictedCount = 0; - // - // object.tab.nodes.forEach(function(node) { - // var realNode = RED.nodes.node(node.id); - // var hasChanges = false; - // if (currentDiff.added[node.id]) { - // addedCount++; - // hasChanges = true; - // } - // if (currentDiff.deleted[node.id]) { - // deletedCount++; - // hasChanges = true; - // } - // if (currentDiff.changed[node.id]) { - // changedCount++; - // hasChanges = true; - // } - // if (currentDiff.conflicted[node.id]) { - // conflictedCount++; - // hasChanges = true; - // } - // - // if (hasChanges) { - // var def = RED.nodes.getType(node.type)||{}; - // var div = $("
    ",{class:"node-diff-node-entry collapsed"}).appendTo(tabDiv); - // var nodeTitleDiv = $("
    ",{class:"node-diff-node-entry-title"}).appendTo(div); - // nodeTitleDiv.click(function(evt) { - // evt.preventDefault(); - // $(this).parent().toggleClass('collapsed'); - // }) - // var newNode = currentDiff.newConfig.all[node.id]; - // var nodePropertiesDiv = $("
    ",{class:"node-diff-node-entry-properties"}).appendTo(div); - // - // var nodePropertiesTable = $("").appendTo(nodePropertiesDiv); - // - // if (node.hasOwnProperty('x')) { - // if (newNode.x !== node.x || newNode.y !== node.y) { - // var currentPosition = node.x+", "+node.y - // var newPosition = newNode.x+", "+newNode.y; - // $("").appendTo(nodePropertiesTable); - // } - // } - // var properties = Object.keys(node).filter(function(p) { return p!='z'&&p!='wires'&&p!=='x'&&p!=='y'&&p!=='id'&&p!=='type'&&(!def.defaults||!def.defaults.hasOwnProperty(p))}); - // if (def.defaults) { - // properties = properties.concat(Object.keys(def.defaults)); - // } - // properties.forEach(function(d) { - // var localValue = JSON.stringify(node[d]); - // var remoteValue = JSON.stringify(newNode[d]); - // var originalValue = realNode._config[d]; - // - // if (remoteValue !== originalValue) { - // var formattedProperty = formatNodeProperty(node[d]); - // var newFormattedProperty = formatNodeProperty(newNode[d]); - // if (localValue === originalValue) { - // // no conflict change - // } else { - // // conflicting change - // } - // $("").appendTo(nodePropertiesTable); - // } - // - // }) - // var nodeChevron = $('').appendTo(nodeTitleDiv); - // - // - // // var leftColumn = $('
    ',{class:"node-diff-column"}).appendTo(div); - // // var rightColumn = $('
    ',{class:"node-diff-column"}).appendTo(div); - // // rightColumn.html(" "); - // - // - // - // var nodeDiv = $("
    ",{class:"node-diff-node-entry-node"}).appendTo(nodeTitleDiv); - // var colour = def.color; - // var icon_url = "arrow-in.png"; - // if (node.type === 'tab') { - // colour = "#C0DEED"; - // icon_url = "subflow.png"; - // } else if (def.category === 'config') { - // icon_url = "cog.png"; - // } else if (node.type === 'unknown') { - // icon_url = "alert.png"; - // } else { - // icon_url = def.icon; - // } - // nodeDiv.css('backgroundColor',colour); - // - // var iconContainer = $('
    ',{class:"palette_icon_container"}).appendTo(nodeDiv); - // $('
    ',{class:"palette_icon",style:"background-image: url(icons/"+icon_url+")"}).appendTo(iconContainer); - // - // - // - // var contentDiv = $('
    ',{class:"node-diff-node-description"}).appendTo(nodeTitleDiv); - // - // $('',{class:"node-diff-node-label"}).html(node.label || node.name || node.id).appendTo(contentDiv); - // //$('
    ',{class:"red-ui-search-result-node-type"}).html(node.type).appendTo(contentDiv); - // //$('
    ',{class:"red-ui-search-result-node-id"}).html(node.id).appendTo(contentDiv); - // } - // - // }); - // - // var statsInfo = ''+object.tab.nodes.length+" nodes"+ - // (addedCount+deletedCount+changedCount+conflictedCount > 0 ? " : ":"")+ - // " "+ - // ((addedCount > 0)?''+addedCount+' added ':'')+ - // ((deletedCount > 0)?''+deletedCount+' deleted ':'')+ - // ((changedCount > 0)?''+changedCount+' changed ':'')+ - // ((conflictedCount > 0)?''+conflictedCount+' conflicts':''); - // stats.html(statsInfo); - // - // - // - // // - // // - // // - // // var node = object.node; - // // var realNode = RED.nodes.node(node.id); - // // var def = RED.nodes.getType(object.node.type)||{}; - // // var l = ""; - // // if (def && def.label && realNode) { - // // l = def.label; - // // try { - // // l = (typeof l === "function" ? l.call(realNode) : l); - // // } catch(err) { - // // console.log("Definition error: "+node.type+".label",err); - // // } - // // } - // // l = l||node.label||node.name||node.id||""; - // // console.log(node); - // // var div = $('
    ').appendTo(container); - // // div.html(l); - // } - // }); - } - - function formatNodeProperty(prop) { - var formattedProperty = prop; - if (formattedProperty === null) { - formattedProperty = 'null'; - } else if (formattedProperty === undefined) { - formattedProperty = 'undefined'; - } else if (typeof formattedProperty === 'object') { - formattedProperty = JSON.stringify(formattedProperty); - } - if (/\n/.test(formattedProperty)) { - formattedProperty = "
    "+formattedProperty+"
    " - } - return formattedProperty; } function getNodeInfo(node) { @@ -393,145 +241,8 @@ RED.deploy = (function() { $( "#node-dialog-confirm-deploy-conflict" ).show(); $( "#node-dialog-confirm-deploy-type" ).val("conflict"); $( "#node-dialog-confirm-deploy" ).dialog( "open" ); - - // $("#node-dialog-confirm-deploy-review").append($('')); - // $("#node-dialog-confirm-deploy-review .ui-button-text").css("opacity",0.4); - // $("#node-dialog-confirm-deploy-review").attr("disabled",true).addClass("disabled"); - // $.ajax({ - // headers: { - // "Accept":"application/json", - // }, - // cache: false, - // url: 'flows', - // success: function(nodes) { - // var newNodes = nodes.flows; - // var newRevision = nodes.rev; - // generateDiff(currentNodes,newNodes); - // $("#node-dialog-confirm-deploy-review").attr("disabled",false).removeClass("disabled"); - // $("#node-dialog-confirm-deploy-review img").remove(); - // $("#node-dialog-confirm-deploy-review .ui-button-text").css("opacity",1); - // } - // }); } - // function parseNodes(nodeList) { - // var tabOrder = []; - // var tabs = {}; - // var subflows = {}; - // var globals = []; - // var all = {}; - // - // nodeList.forEach(function(node) { - // all[node.id] = node; - // if (node.type === 'tab') { - // tabOrder.push(node.id); - // tabs[node.id] = {n:node,nodes:[]}; - // } else if (node.type === 'subflow') { - // subflows[node.id] = {n:node,nodes:[]}; - // } - // }); - // - // nodeList.forEach(function(node) { - // if (node.type !== 'tab' && node.type !== 'subflow') { - // if (tabs[node.z]) { - // tabs[node.z].nodes.push(node); - // } else if (subflows[node.z]) { - // subflows[node.z].nodes.push(node); - // } else { - // globals.push(node); - // } - // } - // }); - // - // return { - // all: all, - // tabOrder: tabOrder, - // tabs: tabs, - // subflows: subflows, - // globals: globals - // } - // } - - // function generateDiff(currentNodes,newNodes) { - // var currentConfig = parseNodes(currentNodes); - // var newConfig = parseNodes(newNodes); - // var pending = RED.nodes.pending(); - // var added = {}; - // var deleted = {}; - // var changed = {}; - // var conflicted = {}; - // - // - // Object.keys(currentConfig.all).forEach(function(id) { - // var node = RED.nodes.workspace(id)||RED.nodes.subflow(id)||RED.nodes.node(id); - // if (!newConfig.all.hasOwnProperty(id)) { - // if (!pending.added.hasOwnProperty(id)) { - // deleted[id] = true; - // conflicted[id] = node.changed; - // } - // } else if (JSON.stringify(currentConfig.all[id]) !== JSON.stringify(newConfig.all[id])) { - // changed[id] = true; - // conflicted[id] = node.changed; - // } - // }); - // Object.keys(newConfig.all).forEach(function(id) { - // if (!currentConfig.all.hasOwnProperty(id) && !pending.deleted.hasOwnProperty(id)) { - // added[id] = true; - // } - // }); - // - // // console.log("Added",added); - // // console.log("Deleted",deleted); - // // console.log("Changed",changed); - // // console.log("Conflicted",conflicted); - // - // var formatString = function(id) { - // return conflicted[id]?"!":(added[id]?"+":(deleted[id]?"-":(changed[id]?"~":" "))); - // } - // newConfig.tabOrder.forEach(function(tabId) { - // var tab = newConfig.tabs[tabId]; - // console.log(formatString(tabId),"Flow:",tab.n.label, "("+tab.n.id+")"); - // tab.nodes.forEach(function(node) { - // console.log(" ",formatString(node.id),node.type,node.name || node.id); - // }) - // if (currentConfig.tabs[tabId]) { - // currentConfig.tabs[tabId].nodes.forEach(function(node) { - // if (deleted[node.id]) { - // console.log(" ",formatString(node.id),node.type,node.name || node.id); - // } - // }) - // } - // }); - // currentConfig.tabOrder.forEach(function(tabId) { - // if (deleted[tabId]) { - // console.log(formatString(tabId),"Flow:",tab.n.label, "("+tab.n.id+")"); - // } - // }); - // - // currentDiff = { - // currentConfig: currentConfig, - // newConfig: newConfig, - // added: added, - // deleted: deleted, - // changed: changed, - // conflicted: conflicted - // } - // } - - // function showDiff() { - // if (currentDiff) { - // var list = $("#node-dialog-view-diff-diff"); - // list.editableList('empty'); - // var currentConfig = currentDiff.currentConfig; - // currentConfig.tabOrder.forEach(function(tabId) { - // var tab = currentConfig.tabs[tabId]; - // list.editableList('addItem',{tab:tab}) - // }); - // } - // $("#node-dialog-view-diff").dialog("open"); - // } - - function save(skipValidation,force) { if (!$("#btn-deploy").hasClass("disabled")) { if (!skipValidation) { @@ -624,6 +335,7 @@ RED.deploy = (function() { }).done(function(data,textStatus,xhr) { RED.nodes.dirty(false); RED.nodes.version(data.rev); + RED.nodes.originalFlow(nns); if (hasUnusedConfig) { RED.notify( '

    '+RED._("deploy.successfulDeploy")+'

    '+ diff --git a/editor/js/ui/diff.js b/editor/js/ui/diff.js new file mode 100644 index 000000000..553e73cd0 --- /dev/null +++ b/editor/js/ui/diff.js @@ -0,0 +1,1278 @@ +RED.diff = (function() { + + var currentDiff = {}; + + function init() { + + // RED.actions.add("core:show-current-diff",showLocalDiff); + RED.actions.add("core:show-remote-diff",showRemoteDiff); + + // RED.keyboard.add("*","ctrl-shift-l","core:show-current-diff"); + RED.keyboard.add("*","ctrl-shift-r","core:show-remote-diff"); + + + var dialog = $('
      ').appendTo(document.body); + + var toolbar = $('
      '+ + ' '+ + '
      ').prependTo(dialog); + + $("#node-dialog-view-diff").dialog({ + title: RED._('deploy.confirm.button.review'), + modal: true, + autoOpen: false, + buttons: [ + { + text: RED._("common.label.cancel"), + click: function() { + $( this ).dialog( "close" ); + } + }, + { + id: "node-diff-view-diff-merge", + text: RED._("deploy.confirm.button.merge"), + class: "primary disabled", + click: function() { + if (!$("#node-diff-view-diff-merge").hasClass('disabled')) { + refreshConflictHeader(); + mergeDiff(currentDiff); + $( this ).dialog( "close" ); + } + } + } + ], + open: function() { + $(this).dialog({width:Math.min($(window).width(),900),height:Math.min($(window).height(),600)}); + } + }); + + var diffList = $("#node-dialog-view-diff-diff").editableList({ + addButton: false, + scrollOnAdd: false, + addItem: function(container,i,object) { + var localDiff = object.diff; + var remoteDiff = object.remoteDiff; + var tab = object.tab.n; + var def = object.def; + var conflicts = currentDiff.conflicts; + + var tabDiv = $('
      ',{class:"node-diff-tab"}).appendTo(container); + tabDiv.addClass('collapsed'); + var titleRow = $('
      ',{class:"node-diff-tab-title"}).appendTo(tabDiv); + var nodesDiv = $('
      ').appendTo(tabDiv); + var originalCell = $('
      ',{class:"node-diff-node-entry-cell"}).appendTo(titleRow); + var localCell = $('
      ',{class:"node-diff-node-entry-cell node-diff-node-local"}).appendTo(titleRow); + var remoteCell; + var selectState; + + if (remoteDiff) { + remoteCell = $('
      ',{class:"node-diff-node-entry-cell node-diff-node-remote"}).appendTo(titleRow); + } + $('').appendTo(originalCell); + createNodeIcon(tab,def).appendTo(originalCell); + var tabForLabel = (object.newTab || object.tab).n; + var titleSpan = $('',{class:"node-diff-tab-title-meta"}).appendTo(originalCell); + if (tabForLabel.type === 'tab') { + titleSpan.html(tabForLabel.label||tabForLabel.id); + } else if (tab.type === 'subflow') { + titleSpan.html((tabForLabel.name||tabForLabel.id)); + } else { + titleSpan.html("Global nodes"); + } + var flowStats = { + local: { + addedCount:0, + deletedCount:0, + changedCount:0, + unchangedCount: 0 + }, + remote: { + addedCount:0, + deletedCount:0, + changedCount:0, + unchangedCount: 0 + }, + conflicts: 0 + } + if (object.newTab || object.remoteTab) { + var localTabNode = { + node: localDiff.newConfig.all[tab.id], + all: localDiff.newConfig.all, + diff: localDiff + } + var remoteTabNode; + if (remoteDiff) { + remoteTabNode = { + node:remoteDiff.newConfig.all[tab.id]||null, + all: remoteDiff.newConfig.all, + diff: remoteDiff + } + } + if (tab.type !== undefined) { + var div = $("
      ",{class:"node-diff-node-entry node-diff-node-props collapsed"}).appendTo(nodesDiv); + var row = $("
      ",{class:"node-diff-node-entry-header"}).appendTo(div); + var originalNodeDiv = $("
      ",{class:"node-diff-node-entry-cell"}).appendTo(row); + var localNodeDiv = $("
      ",{class:"node-diff-node-entry-cell node-diff-node-local"}).appendTo(row); + var localChanged = false; + var remoteChanged = false; + + if (!localDiff.newConfig.all[tab.id]) { + localNodeDiv.addClass("node-diff-empty"); + } else if (localDiff.added[tab.id]) { + localNodeDiv.addClass("node-diff-node-added"); + localChanged = true; + $(' ').appendTo(localNodeDiv); + } else if (localDiff.changed[tab.id]) { + localNodeDiv.addClass("node-diff-node-changed"); + localChanged = true; + $(' ').appendTo(localNodeDiv); + } else { + localNodeDiv.addClass("node-diff-node-unchanged"); + $(' ').appendTo(localNodeDiv); + } + + var remoteNodeDiv; + if (remoteDiff) { + remoteNodeDiv = $("
      ",{class:"node-diff-node-entry-cell node-diff-node-remote"}).appendTo(row); + if (!remoteDiff.newConfig.all[tab.id]) { + remoteNodeDiv.addClass("node-diff-empty"); + if (remoteDiff.deleted[tab.id]) { + remoteChanged = true; + } + } else if (remoteDiff.added[tab.id]) { + remoteNodeDiv.addClass("node-diff-node-added"); + remoteChanged = true; + $(' ').appendTo(remoteNodeDiv); + } else if (remoteDiff.changed[tab.id]) { + remoteNodeDiv.addClass("node-diff-node-changed"); + remoteChanged = true; + $(' ').appendTo(remoteNodeDiv); + } else { + remoteNodeDiv.addClass("node-diff-node-unchanged"); + $(' ').appendTo(remoteNodeDiv); + } + } + $('').appendTo(originalNodeDiv); + $('').html("Flow Properties").appendTo(originalNodeDiv); + + row.click(function(evt) { + evt.preventDefault(); + $(this).parent().toggleClass('collapsed'); + }); + + createNodePropertiesTable(def,tab,localTabNode,remoteTabNode,conflicts).appendTo(div); + selectState = ""; + if (conflicts[tab.id]) { + flowStats.conflicts++; + + if (!localNodeDiv.hasClass("node-diff-empty")) { + $('').prependTo(localNodeDiv); + } + if (!remoteNodeDiv.hasClass("node-diff-empty")) { + $('').prependTo(remoteNodeDiv); + } + div.addClass("node-diff-node-entry-conflict"); + } else { + selectState = currentDiff.resolutions[tab.id]; + } + // Tab properties row + createNodeConflictRadioBoxes(tab,div,localNodeDiv,remoteNodeDiv,true,!conflicts[tab.id],selectState); + } + } + // var stats = $('',{class:"node-diff-tab-stats"}).appendTo(titleRow); + var localNodeCount = 0; + var remoteNodeCount = 0; + var seen = {}; + object.tab.nodes.forEach(function(node) { + seen[node.id] = true; + createNodeDiffRow(node,flowStats).appendTo(nodesDiv) + }); + if (object.newTab) { + localNodeCount = object.newTab.nodes.length; + object.newTab.nodes.forEach(function(node) { + if (!seen[node.id]) { + seen[node.id] = true; + createNodeDiffRow(node,flowStats).appendTo(nodesDiv) + } + }); + } + if (object.remoteTab) { + remoteNodeCount = object.remoteTab.nodes.length; + object.remoteTab.nodes.forEach(function(node) { + if (!seen[node.id]) { + createNodeDiffRow(node,flowStats).appendTo(nodesDiv) + } + }); + } + titleRow.click(function(evt) { + // if (titleRow.parent().find(".node-diff-node-entry:not(.hide)").length > 0) { + titleRow.parent().toggleClass('collapsed'); + if ($(this).parent().hasClass('collapsed')) { + $(this).parent().find('.node-diff-node-entry').addClass('collapsed'); + $(this).parent().find('.debug-message-element').addClass('collapsed'); + } + // } + }) + + if (localDiff.deleted[tab.id]) { + $(' ').appendTo(localCell); + } else if (object.newTab) { + if (localDiff.added[tab.id]) { + $(' ').appendTo(localCell); + } else { + if (tab.id) { + if (localDiff.changed[tab.id]) { + flowStats.local.changedCount++; + } else { + flowStats.local.unchangedCount++; + } + } + var localStats = $('',{class:"node-diff-tab-stats"}).appendTo(localCell); + $('').html(RED._('diff.nodeCount',{count:localNodeCount})).appendTo(localStats); + + if (flowStats.conflicts + flowStats.local.addedCount + flowStats.local.changedCount + flowStats.local.deletedCount > 0) { + $(' [ ').appendTo(localStats); + if (flowStats.conflicts > 0) { + $(' '+flowStats.conflicts+'').appendTo(localStats); + } + if (flowStats.local.addedCount > 0) { + $(' '+flowStats.local.addedCount+'').appendTo(localStats); + } + if (flowStats.local.changedCount > 0) { + $(' '+flowStats.local.changedCount+'').appendTo(localStats); + } + if (flowStats.local.deletedCount > 0) { + $(' '+flowStats.local.deletedCount+'').appendTo(localStats); + } + $(' ] ').appendTo(localStats); + } + + } + } else { + localCell.addClass("node-diff-empty"); + } + + if (remoteDiff) { + if (remoteDiff.deleted[tab.id]) { + $(' ').appendTo(remoteCell); + } else if (object.remoteTab) { + if (remoteDiff.added[tab.id]) { + $(' ').appendTo(remoteCell); + } else { + if (tab.id) { + if (remoteDiff.changed[tab.id]) { + flowStats.remote.changedCount++; + } else { + flowStats.remote.unchangedCount++; + } + } + var remoteStats = $('',{class:"node-diff-tab-stats"}).appendTo(remoteCell); + $('').html(RED._('diff.nodeCount',{count:remoteNodeCount})).appendTo(remoteStats); + if (flowStats.conflicts + flowStats.remote.addedCount + flowStats.remote.changedCount + flowStats.remote.deletedCount > 0) { + $(' [ ').appendTo(remoteStats); + if (flowStats.conflicts > 0) { + $(' '+flowStats.conflicts+'').appendTo(remoteStats); + } + if (flowStats.remote.addedCount > 0) { + $(' '+flowStats.remote.addedCount+'').appendTo(remoteStats); + } + if (flowStats.remote.changedCount > 0) { + $(' '+flowStats.remote.changedCount+'').appendTo(remoteStats); + } + if (flowStats.remote.deletedCount > 0) { + $(' '+flowStats.remote.deletedCount+'').appendTo(remoteStats); + } + $(' ] ').appendTo(remoteStats); + } + } + } else { + remoteCell.addClass("node-diff-empty"); + } + selectState = ""; + if (flowStats.conflicts > 0) { + titleRow.addClass("node-diff-node-entry-conflict"); + } else { + selectState = currentDiff.resolutions[tab.id]; + } + if (tab.id) { + var hide = !(flowStats.conflicts > 0 &&(localDiff.deleted[tab.id] || remoteDiff.deleted[tab.id])); + // Tab parent row + createNodeConflictRadioBoxes(tab,titleRow,localCell,remoteCell, false, hide, selectState); + } + } + + if (tabDiv.find(".node-diff-node-entry").length === 0) { + tabDiv.addClass("node-diff-tab-empty"); + } + container.i18n(); + } + }); + } + function formatWireProperty(wires,allNodes) { + var result = $("
      ",{class:"node-diff-property-wires"}) + var list = $("
        "); + var c = 0; + wires.forEach(function(p,i) { + var port = $("
      1. ").appendTo(list); + if (p && p.length > 0) { + $("").html(i+1).appendTo(port); + var links = $("
          ").appendTo(port); + p.forEach(function(d) { + c++; + var entry = $("
        • ").appendTo(links); + var node = allNodes[d]; + if (node) { + var def = RED.nodes.getType(node.type)||{}; + createNode(node,def).appendTo(entry); + } else { + entry.html(d); + } + }) + } else { + port.html('none'); + } + }) + if (c === 0) { + result.html("none"); + } else { + list.appendTo(result); + } + return result; + } + function createNodeIcon(node,def) { + var nodeDiv = $("
          ",{class:"node-diff-node-entry-node"}); + var colour = def.color; + var icon_url = "arrow-in.png"; + if (node.type === 'tab') { + colour = "#C0DEED"; + icon_url = "subflow.png"; + } else if (def.category === 'config') { + icon_url = "cog.png"; + } else if (node.type === 'unknown') { + icon_url = "alert.png"; + } else { + icon_url = def.icon; + } + nodeDiv.css('backgroundColor',colour); + + var iconContainer = $('
          ',{class:"palette_icon_container"}).appendTo(nodeDiv); + $('
          ',{class:"palette_icon",style:"background-image: url(icons/"+icon_url+")"}).appendTo(iconContainer); + + return nodeDiv; + } + function createNode(node,def) { + var nodeTitleDiv = $("
          ",{class:"node-diff-node-entry-title"}) + createNodeIcon(node,def).appendTo(nodeTitleDiv); + var contentDiv = $('
          ',{class:"node-diff-node-description"}).appendTo(nodeTitleDiv); + var nodeLabel = node.label || node.name || node.id; + $('',{class:"node-diff-node-label"}).html(nodeLabel).appendTo(contentDiv); + return nodeTitleDiv; + } + function createNodeDiffRow(node,stats) { + var localDiff = currentDiff.localDiff; + var remoteDiff = currentDiff.remoteDiff; + var conflicted = currentDiff.conflicts[node.id]; + + var hasChanges = false; // exists in original and local/remote but with changes + var unChanged = true; // existing in original,local,remote unchanged + var localChanged = false; + + if (localDiff.added[node.id]) { + stats.local.addedCount++; + unChanged = false; + } + if (remoteDiff && remoteDiff.added[node.id]) { + stats.remote.addedCount++; + unChanged = false; + } + if (localDiff.deleted[node.id]) { + stats.local.deletedCount++; + unChanged = false; + } + if (remoteDiff && remoteDiff.deleted[node.id]) { + stats.remote.deletedCount++; + unChanged = false; + } + if (localDiff.changed[node.id]) { + stats.local.changedCount++; + hasChanges = true; + unChanged = false; + } + if (remoteDiff && remoteDiff.changed[node.id]) { + stats.remote.changedCount++; + hasChanges = true; + unChanged = false; + } + // console.log(node.id,localDiff.added[node.id],remoteDiff.added[node.id],localDiff.deleted[node.id],remoteDiff.deleted[node.id],localDiff.changed[node.id],remoteDiff.changed[node.id]) + var def = RED.nodes.getType(node.type); + if (def === undefined) { + if (/^subflow:/.test(node.type)) { + def = { + icon:"subflow.png", + category: "subflows", + color: "#da9", + defaults:{name:{value:""}} + } + } else { + def = {}; + } + } + var div = $("
          ",{class:"node-diff-node-entry collapsed"}); + var row = $("
          ",{class:"node-diff-node-entry-header"}).appendTo(div); + + var originalNodeDiv = $("
          ",{class:"node-diff-node-entry-cell"}).appendTo(row); + var localNodeDiv = $("
          ",{class:"node-diff-node-entry-cell node-diff-node-local"}).appendTo(row); + var remoteNodeDiv; + var chevron; + if (remoteDiff) { + remoteNodeDiv = $("
          ",{class:"node-diff-node-entry-cell node-diff-node-remote"}).appendTo(row); + } + $('').appendTo(originalNodeDiv); + + if (unChanged) { + stats.local.unchangedCount++; + createNode(node,def).appendTo(originalNodeDiv); + localNodeDiv.addClass("node-diff-node-unchanged"); + $(' ').appendTo(localNodeDiv); + if (remoteDiff) { + stats.remote.unchangedCount++; + remoteNodeDiv.addClass("node-diff-node-unchanged"); + $(' ').appendTo(remoteNodeDiv); + } + div.addClass("node-diff-node-unchanged"); + } else if (localDiff.added[node.id]) { + localNodeDiv.addClass("node-diff-node-added"); + if (remoteNodeDiv) { + remoteNodeDiv.addClass("node-diff-empty"); + } + $(' ').appendTo(localNodeDiv); + createNode(node,def).appendTo(originalNodeDiv); + } else if (remoteDiff && remoteDiff.added[node.id]) { + localNodeDiv.addClass("node-diff-empty"); + remoteNodeDiv.addClass("node-diff-node-added"); + $(' ').appendTo(remoteNodeDiv); + createNode(node,def).appendTo(originalNodeDiv); + } else { + createNode(node,def).appendTo(originalNodeDiv); + if (localDiff.moved[node.id]) { + var localN = localDiff.newConfig.all[node.id]; + if (!localDiff.deleted[node.z] && node.z !== localN.z && node.z !== "" && !localDiff.newConfig.all[node.z]) { + localNodeDiv.addClass("node-diff-empty"); + } else { + localNodeDiv.addClass("node-diff-node-moved"); + var localMovedMessage = ""; + if (node.z === localN.z) { + localMovedMessage = RED._("diff.type.movedFrom",{id:(localDiff.currentConfig.all[node.id].z||'global')}); + } else { + localMovedMessage = RED._("diff.type.movedTo",{id:(localN.z||'global')}); + } + $(' '+localMovedMessage+'').appendTo(localNodeDiv); + } + localChanged = true; + } else if (localDiff.deleted[node.z]) { + localNodeDiv.addClass("node-diff-empty"); + localChanged = true; + } else if (localDiff.deleted[node.id]) { + localNodeDiv.addClass("node-diff-node-deleted"); + $(' ').appendTo(localNodeDiv); + localChanged = true; + } else if (localDiff.changed[node.id]) { + if (localDiff.newConfig.all[node.id].z !== node.z) { + localNodeDiv.addClass("node-diff-empty"); + } else { + localNodeDiv.addClass("node-diff-node-changed"); + $(' ').appendTo(localNodeDiv); + localChanged = true; + } + } else { + if (localDiff.newConfig.all[node.id].z !== node.z) { + localNodeDiv.addClass("node-diff-empty"); + } else { + stats.local.unchangedCount++; + localNodeDiv.addClass("node-diff-node-unchanged"); + $(' ').appendTo(localNodeDiv); + } + } + + if (remoteDiff) { + if (remoteDiff.moved[node.id]) { + var remoteN = remoteDiff.newConfig.all[node.id]; + if (!remoteDiff.deleted[node.z] && node.z !== remoteN.z && node.z !== "" && !remoteDiff.newConfig.all[node.z]) { + remoteNodeDiv.addClass("node-diff-empty"); + } else { + remoteNodeDiv.addClass("node-diff-node-moved"); + var remoteMovedMessage = ""; + if (node.z === remoteN.z) { + remoteMovedMessage = RED._("diff.type.movedFrom",{id:(remoteDiff.currentConfig.all[node.id].z||'global')}); + } else { + remoteMovedMessage = RED._("diff.type.movedTo",{id:(remoteN.z||'global')}); + } + $(' '+remoteMovedMessage+'').appendTo(remoteNodeDiv); + } + } else if (remoteDiff.deleted[node.z]) { + remoteNodeDiv.addClass("node-diff-empty"); + } else if (remoteDiff.deleted[node.id]) { + remoteNodeDiv.addClass("node-diff-node-deleted"); + $(' ').appendTo(remoteNodeDiv); + } else if (remoteDiff.changed[node.id]) { + if (remoteDiff.newConfig.all[node.id].z !== node.z) { + remoteNodeDiv.addClass("node-diff-empty"); + } else { + remoteNodeDiv.addClass("node-diff-node-changed"); + $(' ').appendTo(remoteNodeDiv); + } + } else { + if (remoteDiff.newConfig.all[node.id].z !== node.z) { + remoteNodeDiv.addClass("node-diff-empty"); + } else { + stats.remote.unchangedCount++; + remoteNodeDiv.addClass("node-diff-node-unchanged"); + $(' ').appendTo(remoteNodeDiv); + } + } + } + } + var localNode = { + node: localDiff.newConfig.all[node.id], + all: localDiff.newConfig.all, + diff: localDiff + }; + var remoteNode; + if (remoteDiff) { + remoteNode = { + node:remoteDiff.newConfig.all[node.id]||null, + all: remoteDiff.newConfig.all, + diff: remoteDiff + } + } + createNodePropertiesTable(def,node,localNode,remoteNode).appendTo(div); + + var selectState = ""; + + if (conflicted) { + stats.conflicts++; + if (!localNodeDiv.hasClass("node-diff-empty")) { + $('').prependTo(localNodeDiv); + } + if (!remoteNodeDiv.hasClass("node-diff-empty")) { + $('').prependTo(remoteNodeDiv); + } + div.addClass("node-diff-node-entry-conflict"); + } else { + selectState = currentDiff.resolutions[node.id]; + } + // Node row + createNodeConflictRadioBoxes(node,div,localNodeDiv,remoteNodeDiv,false,!conflicted,selectState); + row.click(function(evt) { + $(this).parent().toggleClass('collapsed'); + }); + + return div; + } + function createNodePropertiesTable(def,node,localNodeObj,remoteNodeObj) { + var localNode = localNodeObj.node; + var remoteNode; + if (remoteNodeObj) { + remoteNode = remoteNodeObj.node; + } + + var nodePropertiesDiv = $("
          ",{class:"node-diff-node-entry-properties"}); + var nodePropertiesTable = $("
      2. position"+currentPosition+""+newPosition+"
        "+d+''+formattedProperty+''+newFormattedProperty+"
        ").appendTo(nodePropertiesDiv); + var row; + var localCell, remoteCell; + var currentValue, localValue, remoteValue; + var localChanged = false; + var remoteChanged = false; + var localChanges = 0; + var remoteChanges = 0; + var conflict = false; + var status; + + row = $("").appendTo(nodePropertiesTable); + $("").appendTo(nodePropertiesTable); + $("").appendTo(nodePropertiesTable); + $("").appendTo(nodePropertiesTable); + $("'; + tips.stop(); + $(content).empty(); + var table = $('
        ",{class:"node-diff-property-cell-label"}).html("id").appendTo(row); + localCell = $("",{class:"node-diff-property-cell node-diff-node-local"}).appendTo(row); + if (localNode) { + localCell.addClass("node-diff-node-unchanged"); + $('').appendTo(localCell); + RED.utils.createObjectElement(localNode.id).appendTo(localCell); + } else { + localCell.addClass("node-diff-empty"); + } + if (remoteNode !== undefined) { + remoteCell = $("",{class:"node-diff-property-cell node-diff-node-remote"}).appendTo(row); + remoteCell.addClass("node-diff-node-unchanged"); + if (remoteNode) { + $('').appendTo(remoteCell); + RED.utils.createObjectElement(remoteNode.id).appendTo(remoteCell); + } else { + remoteCell.addClass("node-diff-empty"); + } + } + + + if (node.hasOwnProperty('x')) { + if (localNode) { + if (localNode.x !== node.x || localNode.y !== node.y) { + localChanged = true; + localChanges++; + } + } + if (remoteNode) { + if (remoteNode.x !== node.x || remoteNode.y !== node.y) { + remoteChanged = true; + remoteChanges++; + } + } + if ( (remoteChanged && localChanged && (localNode.x !== remoteNode.x || localNode.y !== remoteNode.y)) || + (!localChanged && remoteChanged && localNodeObj.diff.deleted[node.id]) || + (localChanged && !remoteChanged && remoteNodeObj.diff.deleted[node.id]) + ) { + conflict = true; + } + row = $("
        ",{class:"node-diff-property-cell-label"}).html("position").appendTo(row); + localCell = $("",{class:"node-diff-property-cell node-diff-node-local"}).appendTo(row); + if (localNode) { + localCell.addClass("node-diff-node-"+(localChanged?"changed":"unchanged")); + $(''+(localChanged?'':'')+'').appendTo(localCell); + RED.utils.createObjectElement({x:localNode.x,y:localNode.y}).appendTo(localCell); + } else { + localCell.addClass("node-diff-empty"); + } + + if (remoteNode !== undefined) { + remoteCell = $("",{class:"node-diff-property-cell node-diff-node-remote"}).appendTo(row); + remoteCell.addClass("node-diff-node-"+(remoteChanged?"changed":"unchanged")); + if (remoteNode) { + $(''+(remoteChanged?'':'')+'').appendTo(remoteCell); + RED.utils.createObjectElement({x:remoteNode.x,y:remoteNode.y}).appendTo(remoteCell); + } else { + remoteCell.addClass("node-diff-empty"); + } + } + } + // + localChanged = remoteChanged = conflict = false; + if (node.hasOwnProperty('wires')) { + currentValue = JSON.stringify(node.wires); + if (localNode) { + localValue = JSON.stringify(localNode.wires); + if (currentValue !== localValue) { + localChanged = true; + localChanges++; + } + } + if (remoteNode) { + remoteValue = JSON.stringify(remoteNode.wires); + if (currentValue !== remoteValue) { + remoteChanged = true; + remoteChanges++; + } + } + if ( (remoteChanged && localChanged && (localValue !== remoteValue)) || + (!localChanged && remoteChanged && localNodeObj.diff.deleted[node.id]) || + (localChanged && !remoteChanged && remoteNodeObj.diff.deleted[node.id]) + ){ + conflict = true; + } + row = $("
        ",{class:"node-diff-property-cell-label"}).html("wires").appendTo(row); + localCell = $("",{class:"node-diff-property-cell node-diff-node-local"}).appendTo(row); + if (localNode) { + if (!conflict) { + localCell.addClass("node-diff-node-"+(localChanged?"changed":"unchanged")); + $(''+(localChanged?'':'')+'').appendTo(localCell); + } else { + localCell.addClass("node-diff-node-conflict"); + $('').appendTo(localCell); + } + formatWireProperty(localNode.wires,localNodeObj.all).appendTo(localCell); + } else { + localCell.addClass("node-diff-empty"); + } + + if (remoteNode !== undefined) { + remoteCell = $("",{class:"node-diff-property-cell node-diff-node-remote"}).appendTo(row); + if (remoteNode) { + if (!conflict) { + remoteCell.addClass("node-diff-node-"+(remoteChanged?"changed":"unchanged")); + $(''+(remoteChanged?'':'')+'').appendTo(remoteCell); + } else { + remoteCell.addClass("node-diff-node-conflict"); + $('').appendTo(remoteCell); + } + formatWireProperty(remoteNode.wires,remoteNodeObj.all).appendTo(remoteCell); + } else { + remoteCell.addClass("node-diff-empty"); + } + } + } + var properties = Object.keys(node).filter(function(p) { return p!='z'&&p!='wires'&&p!=='x'&&p!=='y'&&p!=='id'&&p!=='type'&&(!def.defaults||!def.defaults.hasOwnProperty(p))}); + if (def.defaults) { + properties = properties.concat(Object.keys(def.defaults)); + } + properties.forEach(function(d) { + localChanged = false; + remoteChanged = false; + conflict = false; + currentValue = JSON.stringify(node[d]); + if (localNode) { + localValue = JSON.stringify(localNode[d]); + if (currentValue !== localValue) { + localChanged = true; + localChanges++; + } + } + if (remoteNode) { + remoteValue = JSON.stringify(remoteNode[d]); + if (currentValue !== remoteValue) { + remoteChanged = true; + remoteChanges++; + } + } + + if ( (remoteChanged && localChanged && (localValue !== remoteValue)) || + (!localChanged && remoteChanged && localNodeObj.diff.deleted[node.id]) || + (localChanged && !remoteChanged && remoteNodeObj.diff.deleted[node.id]) + ){ + conflict = true; + } + + row = $("
        ",{class:"node-diff-property-cell-label"}).html(d).appendTo(row); + localCell = $("",{class:"node-diff-property-cell node-diff-node-local"}).appendTo(row); + if (localNode) { + if (!conflict) { + localCell.addClass("node-diff-node-"+(localChanged?"changed":"unchanged")); + $(''+(localChanged?'':'')+'').appendTo(localCell); + } else { + localCell.addClass("node-diff-node-conflict"); + $('').appendTo(localCell); + } + RED.utils.createObjectElement(localNode[d]).appendTo(localCell); + } else { + localCell.addClass("node-diff-empty"); + } + if (remoteNode !== undefined) { + remoteCell = $("",{class:"node-diff-property-cell node-diff-node-remote"}).appendTo(row); + if (remoteNode) { + if (!conflict) { + remoteCell.addClass("node-diff-node-"+(remoteChanged?"changed":"unchanged")); + $(''+(remoteChanged?'':'')+'').appendTo(remoteCell); + } else { + remoteCell.addClass("node-diff-node-conflict"); + $('').appendTo(remoteCell); + } + RED.utils.createObjectElement(remoteNode[d]).appendTo(remoteCell); + } else { + remoteCell.addClass("node-diff-empty"); + } + } + }); + return nodePropertiesDiv; + } + function createNodeConflictRadioBoxes(node,row,localDiv,remoteDiv,propertiesTable,hide,state) { + var safeNodeId = "node-diff-selectbox-"+node.id.replace(/\./g,'-')+(propertiesTable?"-props":""); + var className = ""; + if (node.z||propertiesTable) { + className = "node-diff-selectbox-tab-"+(propertiesTable?node.id:node.z).replace(/\./g,'-'); + } + var titleRow = !propertiesTable && (node.type === 'tab' || node.type === 'subflow'); + var changeHandler = function(evt) { + var className; + if (node.type === undefined) { + // TODO: handle globals + } else if (titleRow) { + className = "node-diff-selectbox-tab-"+node.id.replace(/\./g,'-'); + $("."+className+"-"+this.value).prop('checked',true); + if (this.value === 'local') { + $("."+className+"-"+this.value).closest(".node-diff-node-entry").addClass("node-diff-select-local"); + $("."+className+"-"+this.value).closest(".node-diff-node-entry").removeClass("node-diff-select-remote"); + } else { + $("."+className+"-"+this.value).closest(".node-diff-node-entry").removeClass("node-diff-select-local"); + $("."+className+"-"+this.value).closest(".node-diff-node-entry").addClass("node-diff-select-remote"); + } + } else { + // Individual node or properties table + var parentId = "node-diff-selectbox-"+(propertiesTable?node.id:node.z).replace(/\./g,'-'); + $('#'+parentId+"-local").prop('checked',false); + $('#'+parentId+"-remote").prop('checked',false); + var titleRowDiv = $('#'+parentId+"-local").closest(".node-diff-tab").find(".node-diff-tab-title"); + titleRowDiv.removeClass("node-diff-select-local"); + titleRowDiv.removeClass("node-diff-select-remote"); + } + if (this.value === 'local') { + row.removeClass("node-diff-select-remote"); + row.addClass("node-diff-select-local"); + } else if (this.value === 'remote') { + row.addClass("node-diff-select-remote"); + row.removeClass("node-diff-select-local"); + } + refreshConflictHeader(); + } + + var localSelectDiv = $('
        '); + var tableBody = $('').appendTo(table); + $(''+RED._("sidebar.info.node")+'').appendTo(tableBody); if (node.type != "subflow" && node.name) { - table += ''+RED._("common.label.name")+' '+node.name+''; + $(''+RED._("common.label.name")+' '+node.name+'').appendTo(tableBody); } - table += ""+RED._("sidebar.info.type")+" "+node.type+""; - table += ""+RED._("sidebar.info.id")+" "+node.id+""; + $(""+RED._("sidebar.info.type")+" "+node.type+"").appendTo(tableBody); + $(""+RED._("sidebar.info.id")+" "+node.id+"").appendTo(tableBody); var m = /^subflow(:(.+))?$/.exec(node.type); var subflowNode; @@ -84,7 +87,7 @@ RED.sidebar.info = (function() { subflowNode = node; } - table += ''+RED._("sidebar.info.subflow")+''; + $(''+RED._("sidebar.info.subflow")+'').appendTo(tableBody); var userCount = 0; var subflowType = "subflow:"+subflowNode.id; @@ -93,66 +96,38 @@ RED.sidebar.info = (function() { userCount++; } }); - table += ''+RED._("common.label.name")+''+subflowNode.name+''; - table += ""+RED._("sidebar.info.instances")+""+userCount+""; + $(''+RED._("common.label.name")+''+subflowNode.name+'').appendTo(tableBody); + $(""+RED._("sidebar.info.instances")+""+userCount+"").appendTo(tableBody); } if (!m && node.type != "subflow" && node.type != "comment") { - table += ' '+RED._("sidebar.info.properties")+''; + $(' '+RED._("sidebar.info.properties")+'').appendTo(tableBody); if (node._def) { for (var n in node._def.defaults) { if (n != "name" && node._def.defaults.hasOwnProperty(n)) { var val = node[n]; var type = typeof val; - if (val === null || val === undefined) { - val = ''+RED._("sidebar.info.null")+''; - } else if (type === "string") { - if (val.length === 0) { - val = ''+RED._("sidebar.info.blank")+''; - } else { - if (val.length > 30) { - val = val.substring(0,30)+" ..."; - } - val = val.replace(/&/g,"&").replace(//g,">"); - } - } else if (type === "number") { - val = val.toString(); - } else if ($.isArray(val)) { - val = "[
        "; - for (var i=0;i/g,">"); - val += " "+i+": "+vv+"
        "; - } - if (node[n].length > 10) { - val += " ... "+RED._("sidebar.info.arrayItems",{count:node[n].length})+"
        "; - } - val += "]"; - } else { - val = JSON.stringify(val,jsonFilter," "); - val = val.replace(/&/g,"&").replace(//g,">"); - } - - table += ''+n+""+val+""; + var propRow = $(''+n+"").appendTo(tableBody); + RED.utils.createObjectElement(val).appendTo(propRow.children()[1]); } } } } - table += "
        "; + $(table).appendTo(content); + $("
        ").appendTo(content); if (!subflowNode && node.type != "comment") { var helpText = $("script[data-help-name$='"+node.type+"']").html()||""; - table += '
        '+helpText+'
        '; + $('
        '+helpText+'
        ').appendTo(content); } if (subflowNode) { - table += '
        '+marked(subflowNode.info||"")+'
        '; + $('
        '+marked(subflowNode.info||"")+'
        ').appendTo(content); } else if (node._def && node._def.info) { var info = node._def.info; var textInfo = (typeof info === "function" ? info.call(node) : info); - table += '
        '+marked(textInfo)+'
        '; - //table += '
        '+(typeof info === "function" ? info.call(node) : info)+'
        '; + $('
        '+marked(textInfo)+'
        ').appendTo(content); + //$('
        '+(typeof info === "function" ? info.call(node) : info)+'
        '; } - $(content).html(table); - $(".node-info-property-header").click(function(e) { var icon = $(this).find("i"); if (icon.hasClass("fa-caret-right")) { @@ -171,14 +146,98 @@ RED.sidebar.info = (function() { }); } + + var tips = (function() { + var started = false; + var enabled = true; + var startDelay = 1000; + var cycleDelay = 10000; + var startTimeout; + var refreshTimeout; + var tipCount = -1; + + RED.actions.add("core:toggle-show-tips",function(state) { + if (state === undefined) { + RED.menu.toggleSelected("menu-item-show-tips"); + } else { + enabled = state; + if (enabled) { + if (started) { + startTips(); + } + } else { + stopTips(); + } + } + }); + + function setTip() { + var r = Math.floor(Math.random() * tipCount); + var tip = RED._("infotips:info.tip"+r); + + var m; + while ((m=/({{(.*?)}})/.exec(tip))) { + var shortcut = RED.keyboard.getShortcut(m[2]); + if (shortcut) { + tip = tip.replace(m[1],RED.keyboard.formatKey(shortcut.key)); + } else { + return; + } + } + while ((m=/(\[(.*?)\])/.exec(tip))) { + tip = tip.replace(m[1],RED.keyboard.formatKey(m[2])); + } + $('
        '+tip+'
        ').appendTo(content).fadeIn(200); + if (startTimeout) { + startTimeout = null; + refreshTimeout = setInterval(cycleTips,cycleDelay); + } + } + function cycleTips() { + $(".node-info-tip").fadeOut(300,function() { + $(this).remove(); + setTip(); + }) + } + function startTips() { + started = true; + if (enabled) { + if (!startTimeout && !refreshTimeout) { + $(content).html(""); + if (tipCount === -1) { + do { + tipCount++; + } while(RED._("infotips:info.tip"+tipCount)!=="infotips:info.tip"+tipCount); + } + startTimeout = setTimeout(setTip,startDelay); + } + } + } + function stopTips() { + started = false; + clearInterval(refreshTimeout); + clearTimeout(startTimeout); + refreshTimeout = null; + startTimeout = null; + $(".node-info-tip").remove(); + } + return { + start: startTips, + stop: stopTips + } + })(); + function clear() { - $(content).html(""); + tips.start(); } function set(html) { + tips.stop(); $(content).html(html); } + + RED.events.on("view:selection-changed",function(selection) { if (selection.nodes) { if (selection.nodes.length == 1) { @@ -202,7 +261,7 @@ RED.sidebar.info = (function() { return { init: init, show: show, - refresh:refresh, + refresh: refresh, clear: clear, set: set } diff --git a/editor/js/ui/tray.js b/editor/js/ui/tray.js index 747600b48..362ffe5b0 100644 --- a/editor/js/ui/tray.js +++ b/editor/js/ui/tray.js @@ -155,6 +155,8 @@ RED.tray = (function() { // Delay resetting the flag, so we don't close prematurely openingTray = false; },200); + body.find(":focusable:first").focus(); + },150); el.css({right:0}); },0); diff --git a/editor/js/ui/typeSearch.js b/editor/js/ui/typeSearch.js new file mode 100644 index 000000000..f949890b8 --- /dev/null +++ b/editor/js/ui/typeSearch.js @@ -0,0 +1,304 @@ +RED.typeSearch = (function() { + + var shade; + + var disabled = false; + var dialog = null; + var searchInput; + var searchResults; + var searchResultsDiv; + var selected = -1; + var visible = false; + + var activeFilter = ""; + var addCallback; + + var typesUsed = {}; + + function search(val) { + activeFilter = val.toLowerCase(); + var visible = searchResults.editableList('filter'); + setTimeout(function() { + selected = 0; + searchResults.children().removeClass('selected'); + searchResults.children(":visible:first").addClass('selected'); + },100); + + } + + function ensureSelectedIsVisible() { + var selectedEntry = searchResults.find("li.selected"); + if (selectedEntry.length === 1) { + var scrollWindow = searchResults.parent(); + var scrollHeight = scrollWindow.height(); + var scrollOffset = scrollWindow.scrollTop(); + var y = selectedEntry.position().top; + var h = selectedEntry.height(); + if (y+h > scrollHeight) { + scrollWindow.animate({scrollTop: '-='+(scrollHeight-(y+h)-10)},50); + } else if (y<0) { + scrollWindow.animate({scrollTop: '+='+(y-10)},50); + } + } + } + + function createDialog() { + //shade = $('
        ',{class:"red-ui-type-search-shade"}).appendTo("#main-container"); + dialog = $("
        ",{id:"red-ui-type-search",class:"red-ui-search red-ui-type-search"}).appendTo("#main-container"); + var searchDiv = $("
        ",{class:"red-ui-search-container"}).appendTo(dialog); + searchInput = $('').attr("placeholder",RED._("search.addNode")).appendTo(searchDiv).searchBox({ + delay: 50, + change: function() { + search($(this).val()); + } + }); + searchInput.on('keydown',function(evt) { + var children = searchResults.children(":visible"); + if (children.length > 0) { + if (evt.keyCode === 40) { + // Down + if (selected < children.length-1) { + if (selected > -1) { + $(children[selected]).removeClass('selected'); + } + selected++; + } + $(children[selected]).addClass('selected'); + ensureSelectedIsVisible(); + evt.preventDefault(); + } else if (evt.keyCode === 38) { + // Up + if (selected > 0) { + if (selected < children.length) { + $(children[selected]).removeClass('selected'); + } + selected--; + } + $(children[selected]).addClass('selected'); + ensureSelectedIsVisible(); + evt.preventDefault(); + } else if (evt.keyCode === 13) { + // Enter + var index = Math.max(0,selected); + if (index < children.length) { + // TODO: dips into editableList impl details + confirm($(children[index]).find(".red-ui-editableList-item-content").data('data')); + } + } + } + }); + + searchResultsDiv = $("
        ",{class:"red-ui-search-results-container"}).appendTo(dialog); + searchResults = $('
          ',{id:"search-result-list", style:"position: absolute;top: 0;bottom: 0;left: 0;right: 0;"}).appendTo(searchResultsDiv).editableList({ + addButton: false, + filter: function(data) { + if (activeFilter === "" ) { + return true; + } + if (data.recent || data.common) { + return false; + } + return (activeFilter==="")||(data.index.indexOf(activeFilter) > -1); + }, + addItem: function(container,i,object) { + var def = object.def; + object.index = object.type.toLowerCase(); + if (object.separator) { + container.addClass("red-ui-search-result-separator") + } + var div = $('',{href:'#',class:"red-ui-search-result"}).appendTo(container); + + var nodeDiv = $('
          ',{class:"red-ui-search-result-node"}).appendTo(div); + var colour = def.color; + var icon_url = "arrow-in.png"; + if (def.category === 'config') { + icon_url = "cog.png"; + } else { + try { + icon_url = (typeof def.icon === "function" ? def.icon.call({}) : def.icon); + } catch(err) { + console.log("Definition error: "+object.type+".icon",err); + } + } + nodeDiv.css('backgroundColor',colour); + + var iconContainer = $('
          ',{class:"palette_icon_container"}).appendTo(nodeDiv); + $('
          ',{class:"palette_icon",style:"background-image: url(icons/"+icon_url+")"}).appendTo(iconContainer); + + if (def.inputs > 0) { + $('
          ',{class:"red-ui-search-result-node-port"}).appendTo(nodeDiv); + } + if (def.outputs > 0) { + $('
          ',{class:"red-ui-search-result-node-port red-ui-search-result-node-output"}).appendTo(nodeDiv); + } + + var contentDiv = $('
          ',{class:"red-ui-search-result-description"}).appendTo(div); + + var label = object.label; + object.index += "|"+label.toLowerCase(); + + $('
          ',{class:"red-ui-search-result-node-label"}).html(label).appendTo(contentDiv); + + div.click(function(evt) { + evt.preventDefault(); + confirm(object); + }); + }, + scrollOnAdd: false + }); + + } + function confirm(def) { + hide(); + typesUsed[def.type] = Date.now(); + addCallback(def.type); + } + + function handleMouseActivity(evt) { + if (visible) { + var t = $(evt.target); + while (t.prop('nodeName').toLowerCase() !== 'body') { + if (t.attr('id') === 'red-ui-type-search') { + return; + } + t = t.parent(); + } + hide(true); + } + } + function show(opts) { + if (!visible) { + RED.keyboard.add("*","escape",function(){hide()}); + if (dialog === null) { + createDialog(); + } + visible = true; + setTimeout(function() { + $(document).on('mousedown.type-search',handleMouseActivity); + $(document).on('mouseup.type-search',handleMouseActivity); + $(document).on('click.type-search',handleMouseActivity); + },200); + } else { + dialog.hide(); + searchResultsDiv.hide(); + } + refreshTypeList(); + addCallback = opts.add; + RED.events.emit("type-search:open"); + //shade.show(); + dialog.css({left:opts.x+"px",top:opts.y+"px"}).show(); + searchResultsDiv.slideDown(); + setTimeout(function() { + searchResultsDiv.find(".red-ui-editableList-container").scrollTop(0); + searchInput.focus(); + },100); + } + function hide(fast) { + if (visible) { + RED.keyboard.remove("escape"); + visible = false; + if (dialog !== null) { + searchResultsDiv.slideUp(fast?50:200,function() { + dialog.hide(); + searchInput.searchBox('value',''); + }); + //shade.hide(); + } + RED.events.emit("type-search:close"); + RED.view.focus(); + $(document).off('mousedown.type-search'); + $(document).off('mouseup.type-search'); + $(document).off('click.type-search'); + } + } + + function getTypeLabel(type, def) { + var label = type; + if (typeof def.paletteLabel !== "undefined") { + try { + label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||""; + label += " ("+type+")"; + } catch(err) { + console.log("Definition error: "+type+".paletteLabel",err); + } + } + return label; + } + + function refreshTypeList() { + var i; + searchResults.editableList('empty'); + searchInput.searchBox('value',''); + selected = -1; + var common = [ + 'debug','inject','function','change','switch' + ]; + + var recentlyUsed = Object.keys(typesUsed); + recentlyUsed.sort(function(a,b) { + return typesUsed[b]-typesUsed[a]; + }); + recentlyUsed = recentlyUsed.filter(function(t) { + return common.indexOf(t) === -1; + }); + + var items = []; + RED.nodes.registry.getNodeTypes().forEach(function(t) { + var def = RED.nodes.getType(t); + if (def.category !== 'config' && t !== 'unknown') { + items.push({type:t,def: def, label:getTypeLabel(t,def)}); + } + }); + items.sort(function(a,b) { + var al = a.label.toLowerCase(); + var bl = b.label.toLowerCase(); + if (al < bl) { + return -1; + } else if (al === bl) { + return 0; + } else { + return 1; + } + }) + + var commonCount = 0; + var item; + for(i=0;i/g,">"); + } + + function buildMessageSummaryValue(value) { + var result; + if (Array.isArray(value)) { + result = $('').html('array['+value.length+']'); + } else if (value === null) { + result = $('null'); + } else if (typeof value === 'object') { + if (value.hasOwnProperty('type') && value.type === 'Buffer' && value.hasOwnProperty('data')) { + result = $('').html('buffer['+value.data.length+']'); + } else if (value.hasOwnProperty('type') && value.type === 'array' && value.hasOwnProperty('data')) { + result = $('').html('array['+value.length+']'); + } else { + result = $('object'); + } + } else if (typeof value === 'string') { + var subvalue; + if (value.length > 30) { + subvalue = sanitize(value.substring(0,30))+"…"; + } else { + subvalue = sanitize(value); + } + result = $('').html('"'+formatString(subvalue)+'"'); + } else { + result = $('').text(""+value); + } + return result; + } + function makeExpandable(el,onexpand) { + el.addClass("debug-message-expandable"); + el.click(function(e) { + var parent = $(this).parent(); + if (parent.hasClass('collapsed')) { + if (onexpand && !parent.hasClass('built')) { + onexpand(); + parent.addClass('built'); + } + parent.removeClass('collapsed'); + } else { + parent.addClass('collapsed'); + } + e.preventDefault(); + }); + } + + function buildMessageElement(obj,key,typeHint,hideKey) { + var i; + var e; + var entryObj; + var header; + var headerHead; + var value; + var element = $(''); + if (!key) { + element.addClass("debug-message-top-level"); + } + + header = $('').appendTo(element); + + if (key && !hideKey) { + $('').text(key).appendTo(header); + $(': ').appendTo(header); + } + entryObj = $('').appendTo(header); + + var isArray = Array.isArray(obj); + var isArrayObject = false; + if (obj && typeof obj === 'object' && obj.hasOwnProperty('type') && obj.hasOwnProperty('data') && ((obj.__encoded__ && obj.type === 'array') || obj.type === 'Buffer')) { + isArray = true; + isArrayObject = true; + } + + if (obj === null || obj === undefined) { + $(''+obj+'').appendTo(entryObj); + } else if (typeof obj === 'string') { + if (/[\t\n\r]/.test(obj)) { + element.addClass('collapsed'); + $(' ').prependTo(header); + makeExpandable(header, function() { + $('').html(typeHint||'string').appendTo(header); + var row = $('').appendTo(element); + $('
          ').text(obj).appendTo(row);
          +                });
          +            }
          +            $('').html('"'+formatString(sanitize(obj))+'"').appendTo(entryObj);
          +
          +
          +        } else if (typeof obj === 'number') {
          +            e = $('').text(""+obj).appendTo(entryObj);
          +            if (Number.isInteger(obj) && (obj >= 0)) { // if it's a +ve integer
          +                e.addClass("debug-message-type-number-toggle");
          +                e.click(function(evt) {
          +                    var format = $(this).data('format') || "date";
          +                    if (format === 'dec') {
          +                        $(this).text(""+obj).data('format','date');
          +                    } else if ((format === 'date') && (obj.toString().length===13) && (obj<=2147483647000)) {
          +                        $(this).text((new Date(obj)).toISOString()).data('format','hex');
          +                    } else if ((format === 'date') && (obj.toString().length===10) && (obj<=2147483647)) {
          +                        $(this).text((new Date(obj*1000)).toISOString()).data('format','hex');
          +                    } else {
          +                        $(this).text("0x"+(obj).toString(16)).data('format','dec');
          +                    }
          +                    evt.preventDefault();
          +                });
          +            }
          +        } else if (isArray) {
          +            element.addClass('collapsed');
          +
          +            var originalLength = obj.length;
          +            if (typeHint) {
          +                var m = /\[(\d+)\]/.exec(typeHint);
          +                if (m) {
          +                    originalLength = parseInt(m[1]);
          +                }
          +            }
          +            var data = obj;
          +            var type = 'array';
          +            if (isArrayObject) {
          +                data = obj.data;
          +                if (originalLength === undefined) {
          +                    originalLength = data.length;
          +                }
          +                type = obj.type.toLowerCase();
          +            } else if (/buffer/.test(typeHint)) {
          +                type = 'buffer';
          +            }
          +            var fullLength = data.length;
          +
          +            if (originalLength > 0) {
          +                $(' ').prependTo(header);
          +                var arrayRows = $('
          ').appendTo(element); + element.addClass('debug-message-buffer-raw'); + makeExpandable(header,function() { + if (!key) { + headerHead = $('').html(typeHint||(type+'['+originalLength+']')).appendTo(header); + } + if (type === 'buffer') { + var stringRow = $('
          ').appendTo(element); + var sr = $('').appendTo(stringRow); + var stringEncoding = ""; + try { + stringEncoding = String.fromCharCode.apply(null, new Uint16Array(data)) + } catch(err) { + console.log(err); + } + $('
          ').text(stringEncoding).appendTo(sr);
          +                        var bufferOpts = $('').appendTo(headerHead);
          +                        $('
          ').addClass('selected').html('raw').appendTo(bufferOpts).click(function(e) { + if ($(this).text() === 'raw') { + $(this).text('string'); + element.addClass('debug-message-buffer-string').removeClass('debug-message-buffer-raw'); + } else { + $(this).text('raw'); + element.removeClass('debug-message-buffer-string').addClass('debug-message-buffer-raw'); + } + e.preventDefault(); + e.stopPropagation(); + }) + } + var row; + if (fullLength <= 10) { + for (i=0;i
          ').appendTo(arrayRows); + buildMessageElement(data[i],""+i,false).appendTo(row); + } + } else { + for (i=0;i
          ').appendTo(arrayRows); + header = $('').appendTo(row); + $(' ').appendTo(header); + makeExpandable(header, (function() { + var min = minRange; + var max = Math.min(fullLength-1,(minRange+9)); + var parent = row; + return function() { + for (var i=min;i<=max;i++) { + var row = $('').appendTo(parent); + buildMessageElement(data[i],""+i,false).appendTo(row); + } + } + })()); + $('').html("["+minRange+" … "+Math.min(fullLength-1,(minRange+9))+"]").appendTo(header); + } + if (fullLength < originalLength) { + $('').appendTo(arrayRows); + } + } + }); + } + if (key) { + headerHead = $('').html(typeHint||(type+'['+originalLength+']')).appendTo(entryObj); + } else { + headerHead = $('').appendTo(entryObj); + $('[ ').appendTo(headerHead); + var arrayLength = Math.min(originalLength,10); + for (i=0;i, ').appendTo(headerHead); + } + } + if (originalLength > arrayLength) { + $('').appendTo(headerHead); + } + if (arrayLength === 0) { + $('empty').appendTo(headerHead); + } + $(' ]').appendTo(headerHead); + } + + } else if (typeof obj === 'object') { + element.addClass('collapsed'); + var keys = Object.keys(obj); + if (key || keys.length > 0) { + $(' ').prependTo(header); + makeExpandable(header, function() { + if (!key) { + $('').html('object').appendTo(header); + } + for (i=0;i
          ').appendTo(element); + buildMessageElement(obj[keys[i]],keys[i],false).appendTo(row); + } + if (keys.length === 0) { + $('').text("empty").appendTo(element); + } + }); + } + if (key) { + $('').html('object').appendTo(entryObj); + } else { + headerHead = $('').appendTo(entryObj); + $('{ ').appendTo(headerHead); + var keysLength = Math.min(keys.length,5); + for (i=0;i').text(keys[i]).appendTo(headerHead); + $(': ').appendTo(headerHead); + buildMessageSummaryValue(obj[keys[i]]).appendTo(headerHead); + if (i < keysLength-1) { + $(', ').appendTo(headerHead); + } + } + if (keys.length > keysLength) { + $('').appendTo(headerHead); + } + if (keysLength === 0) { + $('empty').appendTo(headerHead); + } + $(' }').appendTo(headerHead); + } + } else { + $('').text(""+obj).appendTo(entryObj); + } + return element; + } + + function validatePropertyExpression(str) { + // This must be kept in sync with normalisePropertyExpression + // in red/runtime/util.js + + var length = str.length; + var start = 0; + var inString = false; + var inBox = false; + var quoteChar; + var v; + for (var i=0;i 0) { + var drag_line = drag_lines[0]; + var src = null,dst,src_port; + + if (drag_line.portType === 0 && nn.inputs > 0) { + src = drag_line.node; + src_port = drag_line.port; + dst = nn; + } else if (drag_line.portType === 1 && nn.outputs > 0) { + src = nn; + dst = drag_line.node; + src_port = 0; + } + if (src !== null) { + var link = {source: src, sourcePort:src_port, target: dst}; + RED.nodes.addLink(link); + historyEvent.links = [link]; + hideDragLines(); + if (drag_line.portType === 0 && nn.outputs > 0) { + showDragLines([{node:nn,port:0,portType:0}]); + } else if (drag_line.portType === 1 && nn.inputs > 0) { + showDragLines([{node:nn,port:0,portType:1}]); + } else { + resetMouseVars(); + } + } else { + hideDragLines(); + resetMouseVars(); + } + } else { + if (nn.outputs > 0) { + showDragLines([{node:nn,port:0,portType:0}]); + } else if (nn.inputs > 0) { + showDragLines([{node:nn,port:0,portType:1}]); + } else { + resetMouseVars(); + } + } + } + + + RED.history.push(historyEvent); + RED.nodes.add(nn); + RED.editor.validateNode(nn); + RED.nodes.dirty(true); + // auto select dropped node - so info shows (if visible) + clearSelection(); + nn.selected = true; + moving_set.push({n:nn}); + updateActiveNodes(); + updateSelection(); + redraw(); + } + }); + + updateActiveNodes(); + updateSelection(); + redraw(); + } + } + if (mouse_mode === 0 && !(d3.event.metaKey || d3.event.ctrlKey)) { if (!touchStartTime) { - var point = d3.mouse(this); + point = d3.mouse(this); lasso = vis.append("rect") - .attr("ox",point[0]) - .attr("oy",point[1]) - .attr("rx",1) - .attr("ry",1) - .attr("x",point[0]) - .attr("y",point[1]) - .attr("width",0) - .attr("height",0) - .attr("class","lasso"); + .attr("ox",point[0]) + .attr("oy",point[1]) + .attr("rx",1) + .attr("ry",1) + .attr("x",point[0]) + .attr("y",point[1]) + .attr("width",0) + .attr("height",0) + .attr("class","lasso"); d3.event.preventDefault(); } } @@ -530,12 +658,12 @@ RED.view = (function() { return; } - if (mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && selected_link == null) { + if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && selected_link == null) { return; } var mousePos; - if (mouse_mode == RED.state.JOINING) { + if (mouse_mode == RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { // update drag line if (drag_lines.length === 0) { if (d3.event.shiftKey) { @@ -576,12 +704,17 @@ RED.view = (function() { portType: (mousedown_port_type===0)?1:0 }) } - showDragLines(links); - mouse_mode = 0; - updateActiveNodes(); - redraw(); - mouse_mode = RED.state.JOINING; - } else { + if (links.length === 0) { + resetMouseVars(); + redraw(); + } else { + showDragLines(links); + mouse_mode = 0; + updateActiveNodes(); + redraw(); + mouse_mode = RED.state.JOINING; + } + } else if (mousedown_node) { showDragLines([{node:mousedown_node,port:mousedown_port_index,portType:mousedown_port_type}]); } selected_link = null; @@ -748,6 +881,9 @@ RED.view = (function() { function canvasMouseUp() { var i; var historyEvent; + if (mouse_mode === RED.state.QUICK_JOINING) { + return; + } if (mousedown_node && mouse_mode == RED.state.JOINING) { var removedLinks = []; for (i=0;i 0) { var ns = []; for (var i=0;i 0) { + if (!endMoveSet) { + $(document).one('keyup',endKeyboardMove); + endMoveSet = true; + } var minX = 0; var minY = 0; var node; for (var i=0;i 0) { + var node = moving_set[0].n; + if (node.type === "subflow") { + RED.editor.editSubflow(activeSubflow); + } else { + RED.editor.edit(node); + } + } + } function deleteSelection() { if (moving_set.length > 0 || selected_link != null) { var result; @@ -1165,22 +1319,45 @@ RED.view = (function() { } } + function disableQuickJoinEventHandler(evt) { + // Check for ctrl (all browsers), "Meta" (Chrome/FF), keyCode 91 (Safari) + if (evt.keyCode === 17 || evt.key === "Meta" || evt.keyCode === 91) { + resetMouseVars(); + hideDragLines(); + redraw(); + $(window).off('keyup',disableQuickJoinEventHandler); + } + } + function portMouseDown(d,portType,portIndex) { //console.log(d,portType,portIndex); // disable zoom //vis.call(d3.behavior.zoom().on("zoom"), null); mousedown_node = d; - mouse_mode = RED.state.JOINING; mousedown_port_type = portType; mousedown_port_index = portIndex || 0; - document.body.style.cursor = "crosshair"; + if (mouse_mode !== RED.state.QUICK_JOINING) { + mouse_mode = RED.state.JOINING; + document.body.style.cursor = "crosshair"; + if (d3.event.ctrlKey || d3.event.metaKey) { + mouse_mode = RED.state.QUICK_JOINING; + showDragLines([{node:mousedown_node,port:mousedown_port_index,portType:mousedown_port_type}]); + $(window).on('keyup',disableQuickJoinEventHandler); + } + } + d3.event.stopPropagation(); d3.event.preventDefault(); } function portMouseUp(d,portType,portIndex) { var i; + if (mouse_mode === RED.state.QUICK_JOINING) { + if (drag_lines[0].node===d) { + return + } + } document.body.style.cursor = ""; - if (mouse_mode == RED.state.JOINING && drag_lines.length > 0) { + if (mouse_mode == RED.state.JOINING || mouse_mode == RED.state.QUICK_JOINING) { if (typeof TouchEvent != "undefined" && d3.event instanceof TouchEvent) { RED.nodes.eachNode(function(n) { if (n.z == RED.workspaces.active()) { @@ -1247,6 +1424,21 @@ RED.view = (function() { updateActiveNodes(); RED.nodes.dirty(true); } + if (mouse_mode === RED.state.QUICK_JOINING) { + if (addedLinks.length > 0) { + hideDragLines(); + if (portType === 1 && d.outputs > 0) { + showDragLines([{node:d,port:0,portType:0}]); + } else if (portType === 0 && d.inputs > 0) { + showDragLines([{node:d,port:0,portType:1}]); + } else { + resetMouseVars(); + } + } + redraw(); + return; + } + resetMouseVars(); hideDragLines(); selected_link = null; @@ -1276,7 +1468,7 @@ RED.view = (function() { //var pos = [touch0.pageX,touch0.pageY]; //RED.touch.radialMenu.show(d3.select(this),pos); if (mouse_mode == RED.state.IMPORT_DRAGGING) { - RED.keyboard.remove(/* ESCAPE */ 27); + RED.keyboard.remove("escape"); if (activeSpliceLink) { // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp @@ -1306,6 +1498,9 @@ RED.view = (function() { resetMouseVars(); d3.event.stopPropagation(); return; + } else if (mouse_mode == RED.state.QUICK_JOINING) { + d3.event.stopPropagation(); + return; } mousedown_node = d; var now = Date.now(); @@ -1623,14 +1818,14 @@ RED.view = (function() { nodeMouseUp.call(this,d); }) .on("mouseover",function(d) { - if (mouse_mode === 0) { - var node = d3.select(this); - node.classed("node_hovered",true); - } + if (mouse_mode === 0) { + var node = d3.select(this); + node.classed("node_hovered",true); + } }) .on("mouseout",function(d) { - var node = d3.select(this); - node.classed("node_hovered",false); + var node = d3.select(this); + node.classed("node_hovered",false); }); //node.append("rect").attr("class", "node-gradient-top").attr("rx", 6).attr("ry", 6).attr("height",30).attr("stroke","none").attr("fill","url(#gradient-top)").style("pointer-events","none"); @@ -2270,8 +2465,8 @@ RED.view = (function() { node.n._def.outputs > 0; } } - RED.keyboard.add("*",/* ESCAPE */ 27,function(){ - RED.keyboard.remove(/* ESCAPE */ 27); + RED.keyboard.add("*","escape",function(){ + RED.keyboard.remove("escape"); clearSelection(); RED.history.pop(); mouse_mode = 0; @@ -2316,6 +2511,24 @@ RED.view = (function() { } } + function toggleShowGrid(state) { + if (state) { + grid.style("visibility","visible"); + } else { + grid.style("visibility","hidden"); + } + } + function toggleSnapGrid(state) { + snapGrid = state; + redraw(); + } + function toggleStatus(s) { + showStatus = s; + RED.nodes.eachNode(function(n) { n.dirty = true;}); + //TODO: subscribe/unsubscribe here + redraw(); + } + return { init: init, state:function(state) { @@ -2335,16 +2548,6 @@ RED.view = (function() { }, focus: focusView, importNodes: importNodes, - status: function(s) { - if (s == null) { - return showStatus; - } else { - showStatus = s; - RED.nodes.eachNode(function(n) { n.dirty = true;}); - //TODO: subscribe/unsubscribe here - redraw(); - } - }, calculateTextWidth: calculateTextWidth, select: function(selection) { if (typeof selection !== "undefined") { @@ -2371,17 +2574,6 @@ RED.view = (function() { } return selection; }, - toggleShowGrid: function(state) { - if (state) { - grid.style("visibility","visible"); - } else { - grid.style("visibility","hidden"); - } - }, - toggleSnapGrid: function(state) { - snapGrid = state; - redraw(); - }, scale: function() { return scaleFactor; }, @@ -2405,7 +2597,6 @@ RED.view = (function() { node.highlighted = true; node.dirty = true; RED.workspaces.show(node.z); - RED.view.redraw(); var screenSize = [$("#chart").width(),$("#chart").height()]; var scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()]; @@ -2419,17 +2610,23 @@ RED.view = (function() { },200); } - var flash = 22; - var flashFunc = function() { - flash--; - node.highlighted = !node.highlighted; - node.dirty = true; - RED.view.redraw(); - if (flash >= 0) { - setTimeout(flashFunc,100); + if (!node._flashing) { + node._flashing = true; + var flash = 22; + var flashFunc = function() { + flash--; + node.dirty = true; + if (flash >= 0) { + node.highlighted = !node.highlighted; + setTimeout(flashFunc,100); + } else { + node.highlighted = false; + delete node._flashing; + } + RED.view.redraw(); } + flashFunc(); } - flashFunc(); } else if (node._def.category === 'config') { RED.sidebar.config.show(id); } diff --git a/editor/js/ui/workspaces.js b/editor/js/ui/workspaces.js index f19e49543..5bfe7f4ca 100644 --- a/editor/js/ui/workspaces.js +++ b/editor/js/ui/workspaces.js @@ -170,6 +170,9 @@ RED.workspaces = (function() { createWorkspaceTabs(); RED.events.on("sidebar:resize",workspace_tabs.resize); + RED.actions.add("core:show-next-tab",workspace_tabs.nextTab); + RED.actions.add("core:show-previous-tab",workspace_tabs.previousTab); + RED.menu.setAction('menu-item-workspace-delete',function() { deleteWorkspace(RED.nodes.workspace(activeWorkspace)); }); @@ -177,6 +180,14 @@ RED.workspaces = (function() { $(window).resize(function() { workspace_tabs.resize(); }); + + RED.actions.add("core:add-flow",addWorkspace); + RED.actions.add("core:edit-flow",editWorkspace); + RED.actions.add("core:remove-flow",removeWorkspace); + } + + function editWorkspace(id) { + showRenameWorkspaceDialog(id||activeWorkspace); } function removeWorkspace(ws) { @@ -201,9 +212,7 @@ RED.workspaces = (function() { add: addWorkspace, remove: removeWorkspace, order: setWorkspaceOrder, - edit: function(id) { - showRenameWorkspaceDialog(id||activeWorkspace); - }, + edit: editWorkspace, contains: function(id) { return workspace_tabs.contains(id); }, diff --git a/editor/sass/ace.scss b/editor/sass/ace.scss new file mode 100644 index 000000000..9425c356c --- /dev/null +++ b/editor/sass/ace.scss @@ -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; +} diff --git a/editor/sass/colors.scss b/editor/sass/colors.scss index 6a2f04b1c..702ba0385 100644 --- a/editor/sass/colors.scss +++ b/editor/sass/colors.scss @@ -58,7 +58,7 @@ $workspace-button-color-focus-outline: rgba(85,150,230,0.2); $typedInput-button-background: #efefef; $typedInput-button-background-hover: #ddd; -$typedInput-button-background-active: #e3e3e3; +$typedInput-button-background-active: #ddd; $editor-button-color-primary: #eee; $editor-button-background-primary: #AD1625; diff --git a/editor/sass/debug.scss b/editor/sass/debug.scss new file mode 100644 index 000000000..7e3db9d10 --- /dev/null +++ b/editor/sass/debug.scss @@ -0,0 +1,185 @@ +/** + * Copyright 2016 IBM Corp. + * + * 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. + **/ + +.debug-window { + padding:0; + margin:0; + background: #fff; + line-height: 20px; +} +.debug-window .debug-message-payload { + font-size: 14px; +} +.debug-content { + position: absolute; + top: 43px; + bottom: 0px; + left:0px; + right: 0px; + overflow-y: scroll; +} +.debug-filter-box { + position:absolute; + top: 42px; + left: 0px; + right: 0px; + background: #f9f9f9; + padding: 10px; + border-bottom: 1px solid #ddd; + box-shadow: 0 2px 6px rgba(0,0,0,0.1); +} +.debug-filter-row { + text-align: right; +} + +.debug-message { + border-bottom: 1px solid #eee; + border-left: 8px solid #eee; + border-right: 8px solid #eee; + padding: 2px; +} +.debug-message-meta { + background: #fff; + font-size: 10px; + color: #777; +} +.debug-message-date { + padding: 1px 5px 1px 1px; +} +.debug-message-topic { + display: block; + color: #a66; +} +.debug-message-name { + padding: 1px 5px; + color: #777; +} +.debug-message-payload { + display: block; + padding: 2px; + background: #fff; +} +.debug-message-level-log { + border-left-color: #eee; + border-right-color: #eee; +} +.debug-message-level-30 { + border-left-color: #ffdf9d; + border-right-color: #ffdf9d; +} +.debug-message-level-20 { + border-left-color: #f99; + border-right-color: #f99; +} +.debug-message-object-entry { + padding-left: 15px; + padding-bottom: 3px; + padding-top: 3px; +} +.debug-message-element { + color: #333; + font-family: Menlo, monospace; + font-size: 12px !important; + line-height: 1.3em; +} +.debug-message-object-key { + color: #792e90; +} +.debug-message-object-value { + +} +.debug-message-object-handle { + color: #666; + font-size: 1em; + width: 1em; + text-align: center; + transition: transform 0.1s ease-in-out; + transform: rotate(90deg); +} +.debug-message-element:not(.debug-message-top-level)>.debug-message-expandable>.debug-message-object-handle { + margin-left: -1em; +} +.debug-message-object-entry>.debug-message-expandable>.debug-message-object-handle { + margin-left: -1em; +} +.debug-message-object-entry.collapsed>span>.debug-message-object-handle { + transform: rotate(0deg); +} +.debug-message-element.collapsed>span>.debug-message-object-handle { + transform: rotate(0deg); +} + +.debug-message-object-entry.collapsed > .debug-message-object-entry { + display:none; +} + +.debug-message-element.collapsed .debug-message-object-entry { + display:none; +} +.debug-message-element:not(.collapsed)>.debug-message-expandable>.debug-message-object-value>.debug-message-object-header { + display:none; +} +.debug-message-element.collapsed .debug-message-buffer-opts { + display: none; +} + +.debug-message-element.collapsed .debug-message-object-type-header { + display:none; +} +.debug-message-object-entry pre { + font-family: Menlo, monospace; + font-size: 12px; + line-height: 1.4em; + margin: 0 0 0 -1em; +} + +.debug-message-type-other { color: #2033d6; } +.debug-message-type-string { color: #b72828; } +.debug-message-type-null { color: #666; font-style: italic;} +.debug-message-type-meta { color: #666; font-style: italic;} +.debug-message-type-number { color: #2033d6; }; +.debug-message-type-number-toggle { cursor: pointer;} + +.debug-message-expandable { + cursor: pointer; +} +.debug-message-expandable:hover { + background: #fefefe; +} +.debug-message-expandable:hover .debug-message-object-handle { + color: #b72828 !important; +} + +.debug-message-buffer-opts a { + font-size: 9px; + color: #bbb; + border: 1px solid #bbb; + border-radius: 2px; + padding: 2px 5px; + margin-left: 5px; +} +.debug-message-buffer-opts a:hover { + text-decoration: none; + color: #999; + border: 1px solid #999; + background: #f3f3f3; +} +.debug-message-buffer-raw > .debug-message-string-rows { + display: none; +} +.debug-message-buffer-string > .debug-message-array-rows { + display: none; +} diff --git a/editor/sass/diff.scss b/editor/sass/diff.scss index 8c27f4ad4..57be425cc 100644 --- a/editor/sass/diff.scss +++ b/editor/sass/diff.scss @@ -21,30 +21,67 @@ .red-ui-editableList-container { border-radius:1px; padding:0; + background: #f9f9f9; } - ol { + #node-dialog-view-diff-diff { position: absolute; - top:10px; + top:80px; bottom:10px; left:10px; right:10px; li { + background: #f9f9f9; padding: 0px; border: none; + min-height: 0; } } .red-ui-editableList-item-content { padding: 5px; + // padding-bottom: 5px; } +} +#node-dialog-view-diff-headers { + position: absolute; + left:17px; + right:32px; + top: 55px; + height: 25px; + .node-diff-node-entry-cell:not(:first-child) { + background: #f9f9f9; + text-align: center; + border-top: 1px solid $secondary-border-color; + border-color:$secondary-border-color; + } + .node-diff-node-entry-cell:last-child { + border-right: 1px solid $secondary-border-color; + } +} + +.node-diff-toolbar { + position:absolute; + top:0; + left:0; + right:0; + height: 43px; + box-sizing: border-box; + color: #666; + text-align: right; + padding: 8px 10px; + background: #f3f3f3; + border-bottom: 1px solid $secondary-border-color; + white-space: nowrap; } .node-diff-tab { - border: 1px solid $secondary-border-color; - border-radius: 3px; + background: #fff; + border: 1px solid #ddd; + border-radius: 1px; + overflow: hidden; &.collapsed { - .node-diff-tab-title > .node-diff-chevron { + .node-diff-tab-title .node-diff-chevron { transform: rotate(-90deg); } .node-diff-node-entry { @@ -53,21 +90,26 @@ } } .node-diff-tab-stats { - position: absolute; - left: 50%; + font-size: 0.9em; } .node-diff-chevron { + display: inline-block; width: 15px; text-align: center; - margin: 3px 5px 3px 5px; + margin-left: 3px; transition: transform 0.1s ease-in-out; } .node-diff-node-entry { - padding: 0 0 0 5px; + margin-left: 20px; + font-size: 0.9em; + + &:first-child { + border-top: 1px solid #eee; + } &:not(:last-child) { - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid #eee; } &.collapsed { @@ -78,25 +120,80 @@ display: none; } } + &:not(.collapsed) { + .node-diff-node-entry-cell:not(:first-child) { + //display: none; + } + .node-diff-node-entry-cell:first-child { + //width: 100% + } + } table { - border-collapse: collapse; - width: 100%; - table-layout:fixed; + border-collapse: collapse; + table-layout:fixed; + + // Fix for table-layout: fixed on safari: + max-width: none; + width: auto; + min-width: 100%; } td, th { border: 1px solid $secondary-border-color; - padding: 3px 5px; + padding: 0 0 0 3px; text-align: left; + overflow-x: auto; } - - td:nth-child(1) { - width: 150px; + tr { + vertical-align: top; + &:first-child td { + white-space:nowrap; + overflow:hidden; + } + } + td:first-child { + width: 140px; } td:not(:first-child) { - width: calc(50% - 150px); + width: calc( 100% - 140px); + } + td { + .node-diff-status { + margin-left: 0; + } + } + tr:not(.node-diff-property-header) { + .node-diff-status { + width: 12px; + margin-left: 0; + margin-top: 0; + margin-bottom: 0; + margin-right: 5px; + } } } +.node-diff-three-way { + .node-diff-node-entry-cell { + width: calc((100% - 220px) / 2); + &:first-child { + width: 220px; + } + } + td:not(:first-child) { + width: calc( (100% - 140px) / 2); + } + + .node-diff-node-entry { + .node-diff-node-entry-cell { + width: calc((100% + 20px - 220px) / 2); + &:first-child { + width: 200px; + } + + } + } +} + .node-diff-column { display:inline-block; height:100%; @@ -108,18 +205,26 @@ border-right: 1px solid $secondary-border-color } } + .node-diff-tab-title { - padding: 3px 3px 3px 0; - background: #f6f6f6; + cursor: pointer; + padding: 0; + // background: #f6f6f6; +} +.node-diff-tab-title-meta { + vertical-align: middle; + display: inline-block; + padding-top: 2px; +} +.node-diff-node-entry-header { cursor: pointer; } - .node-diff-node-entry-node { vertical-align: middle; display: inline-block; margin: 5px; - width: 24px; - height: 20px; + width: 18px; + height: 15px; background: #ddd; border-radius: 2px; border: 1px solid #999; @@ -128,25 +233,89 @@ background-size: contain; position: relative; - .palette-icon { - width: 16px; + .palette_icon { + background-position: 49% 50%; + width: 15px; } .palette_icon_container { - width: 24px; + width: 18px; + } +} +.node-diff-tab-empty { + .node-diff-chevron i { + display: none; + } + .node-diff-tab-title { + cursor: default; + } +} +.node-diff-node-deleted { + //background: #fadddd; + cursor: default !important; + .node-diff-status { + color: #f80000; + } + .node-diff-node-entry-node { + opacity: 0.5; + } + .node-diff-node-description { + opacity: 0.5; + text-decoration: line-through; + } +} +.node-diff-node-added { + //background: #eefaee; + cursor: default !important; + .node-diff-status { + color: #009900; + } +} +.node-diff-node-moved { + //background: #eefaee; + .node-diff-status { + color: #3f81b3; + } +} + +.node-diff-node-changed { + //background: #fff2ca; + .node-diff-status { + color: #f89406; + } +} +.node-diff-node-unchanged { + //background: #fff2ca; + .node-diff-status { + color: #bbb; + } +} +.node-diff-node-conflict { + .node-diff-status { + color: #9b45ce; } } .node-diff-node-entry-title { - cursor: pointer; + display: inline-block; + .node-diff-status { + margin-left: 15px; + } } .node-diff-node-entry-properties { - margin-left: 30px; - margin-right: 8px; - margin-bottom:8px; + margin: 5px ; color: #666; } +.node-diff-status { + display: inline-block; + height: 20px; + margin-left: 5px; + vertical-align: top; + margin-top: 6px; + margin-bottom: 6px; + text-align: center; +} + .node-diff-node-description { color: $form-text-color; - margin-left: 5px; margin-right: 5px; padding-top: 5px; display: inline-block; @@ -156,10 +325,192 @@ clear: both; } } - +.node-diff-node-meta { + float: right; + //font-size: 0.9em; + color: #999; + margin-top: 7px; + margin-right: 10px; +} .node-diff-count { color: #999} .node-diff-added { color: #009900} .node-diff-deleted { color: #f80000} .node-diff-changed { color: #f89406} .node-diff-conflicted { color: purple} + + +.node-diff-node-entry-cell { + display: inline-block; + vertical-align: top; + box-sizing: border-box; + width: calc( (100% - 20px) / 2); + height: 32px; + border-left: 1px solid #eee; + padding-top: 2px; + white-space: nowrap; + overflow: hidden; + position: relative; +} +.node-diff-empty { + background: #f3f3f3; + background: repeating-linear-gradient( + 20deg, + #fff, #fff 5px, + #f6f6f6 5px, + #f6f6f6 10px + ); +} +.node-diff-node-entry-cell:first-child { + border-left: none; +} +.node-diff-property-cell-label { + margin-left: 20px; + vertical-align: top; + box-sizing: border-box; + padding-left: 8px; + width: 120px; +} +.node-diff-property-wires { + display: inline-block; + .node-diff-node-entry-node { + width: 18px; + height: 15px; + } + .palette_icon_container { + width: 18px; + } + .palette_icon { + width: 15px; + } + ul,li,ol { + background: none !important; + } + ul { + vertical-align: middle; + display: inline-block; + margin-left: 5px; + } + li { + list-style-type: none !important; + } + ol { + font-size: 0.9em; + margin: 0; + & > span { + vertical-align: middle; + display: inline-block; + width: 30px; + text-align: center; + } + & > li:not(:last-child) { + border-bottom: 1px solid #999; + } + } + +} +.node-diff-node-props .node-diff-node-entry-cell:first-child { + padding: 6px 0px; + span:not(.node-diff-chevron) { + margin-left: 5px; + } + +} +.node-diff-property-cell { + // vertical-align: top; + // display:inline-block; + // + // box-sizing: border-box; + // padding: 1px 5px; + //min-height: 30px; + + &.node-diff-node-changed { + background: #fff2e1 !important; + } + &.node-diff-node-conflict { + background: #ffdad4 !important; + } +} + +.node-diff-selectbox { + position: absolute; + top:0; + right:0; + bottom:0; + width: 35px; + text-align: center; + border-left: 1px solid #eee; + margin:0; + input { + margin-top: 8px; + } + + &:hover { + background: #f3f3f3; + } +} + +.node-diff-node-entry-conflict.node-diff-select-remote { + .node-diff-node-remote { + background: #e7ffe3; + label { + border-left-color: #b8daad; + } + } + .node-diff-node-local { + background: #ffe1e1; + label { + border-left-color: #e4bcbc; + } + } +} +.node-diff-node-entry-conflict.node-diff-select-local { + .node-diff-node-local { + background: #e7ffe3; + label { + border-left-color: #b8daad; + } + } + .node-diff-node-remote { + background: #ffe1e1; + label { + border-left-color: #e4bcbc; + } + } +} + + +#node-dialog-confirm-deploy { + .node-dialog-confirm-row { + text-align: left; padding-top: 10px; + } + ul { + font-size: 0.9em; + width: 400px; + margin: 10px auto; + text-align: left; + } + .node-dialog-confirm-conflict-row { + img { + vertical-align:middle; + height: 30px; + margin-right: 10px; + } + i { + vertical-align:middle; + text-align: center; + font-size: 30px; + width: 30px; + margin-right: 10px; + } + div { + vertical-align: middle; + width: calc(100% - 60px); + display:inline-block; + } + } +} + +#node-diff-toolbar-resolved-conflicts .node-diff-status { + margin:0; +} diff --git a/editor/sass/editor.scss b/editor/sass/editor.scss index fe0b18dac..5e8ecdd47 100644 --- a/editor/sass/editor.scss +++ b/editor/sass/editor.scss @@ -168,12 +168,7 @@ color: $workspace-button-color; } #palette-shade, #editor-shade, #header-shade, #sidebar-shade { - position: absolute; - top:0; - bottom:0; - left:0; - right:0; - background: $shade-color; + @include shade; z-index: 2; } #sidebar-shade { diff --git a/editor/sass/flow.scss b/editor/sass/flow.scss index 88dad4622..8bd3f7e6c 100644 --- a/editor/sass/flow.scss +++ b/editor/sass/flow.scss @@ -170,6 +170,11 @@ stroke-dasharray:8, 3; } + +.node_quickadd * { + stroke-dasharray: 12,3; +} + .node_status_label { @include disable-selection; stroke-width: 0; @@ -183,6 +188,13 @@ stroke: $port-selected-color; fill: $port-selected-color; } + +.port_quick_link { + stroke: $port-selected-color; + fill: $port-selected-color; +} + + .subflowport { stroke-dasharray: 5,5; fill: #eee; diff --git a/editor/sass/keyboard.scss b/editor/sass/keyboard.scss index 6c05e5046..e355223f6 100644 --- a/editor/sass/keyboard.scss +++ b/editor/sass/keyboard.scss @@ -37,4 +37,6 @@ font-family: Courier, monospace; box-shadow: #999 1px 1px 1px; } - +.help-key-block { + white-space: nowrap; +} diff --git a/editor/sass/mixins.scss b/editor/sass/mixins.scss index 4042b111e..e4ad7660f 100644 --- a/editor/sass/mixins.scss +++ b/editor/sass/mixins.scss @@ -70,7 +70,6 @@ &.selected:not(.disabled) { color: $workspace-button-color-selected !important; background: $workspace-button-background-active; - cursor: default; } .button-group &:not(:first-child) { border-left: none; @@ -86,6 +85,12 @@ outline: 1px solid $workspace-button-color-focus-outline; } } + +.button-group:not(:last-child) { + margin-right: 10px; +} + + @mixin workspace-button-toggle { @include workspace-button; color: $workspace-button-toggle-color !important; @@ -97,6 +102,7 @@ border-bottom-width: 2px; border-bottom-color: $form-input-border-selected-color; margin-bottom: 0; + cursor: default; } &.disabled { color: $workspace-button-toggle-color-disabled !important; @@ -115,6 +121,12 @@ height: 25px; line-height: 23px; padding: 0 10px; + + + .button-group:not(:last-child) { + margin-right: 5px; + } + } @mixin component-footer-button { @@ -128,9 +140,28 @@ padding: 0 5px; } } +@mixin component-footer-button-toggle { + @include workspace-button-toggle; + font-size: 11px; + line-height: 17px; + height: 18px; + &.text-button { + width: auto; + padding: 0 5px; + } +} @mixin component-shadow { border: 1px solid $secondary-border-color; box-shadow: 1px 1px 4px rgba(0,0,0,0.2); } + +@mixin shade { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: $shade-color; +} diff --git a/editor/sass/palette-editor.scss b/editor/sass/palette-editor.scss index 8565d91b3..6b67a8a85 100644 --- a/editor/sass/palette-editor.scss +++ b/editor/sass/palette-editor.scss @@ -72,10 +72,6 @@ padding: 8px 10px; border-bottom: 1px solid $primary-border-color; text-align: right; - - .button-group { - margin-right: 10px; - } } .palette-module-button-group { position: absolute; @@ -86,12 +82,7 @@ } } .palette-module-shade { - position: absolute; - top: 0; - bottom:0; - left:0; - right:0; - background: $shade-color; + @include shade; text-align: center; padding-top: 20px; } diff --git a/editor/sass/search.scss b/editor/sass/search.scss index 7ad8cbe5c..bc391698a 100644 --- a/editor/sass/search.scss +++ b/editor/sass/search.scss @@ -29,6 +29,77 @@ ol { } } +.red-ui-type-search-shade { + @include shade; + z-index: 20; + position: fixed; + background: rgba(255,255,255,0.05); +} +.red-ui-type-search { + box-shadow: 0 1px 6px -3px black; + background: none; + width: 300px; + margin-left: 0px; + //height: 75px; + border: none; + .red-ui-search-container { + border-top-left-radius: 5px; + border-top-right-radius: 5px; + border: 1px dashed #aaa; + border-bottom: none; + padding: 0; + } + .red-ui-search-results-container { + display: none; + height: 150px; + .red-ui-editableList-container { + border: 1px dashed #aaa; + border-top: 1px solid #ccc; + } + } + .red-ui-search-result { + padding: 2px 2px 2px 5px; + font-size: 13px; + border-left-width: 3px; + border-right-width: 3px; + } + .red-ui-search-result-separator { + border-bottom: 3px solid #ddd; + } + .red-ui-search-result-node { + position: relative; + width: 18px; + height: 15px; + margin-top: 1px; + } + .red-ui-search-result-node-port { + position: absolute; + border-radius: 2px; + border: 1px solid #999; + width: 6px; + height: 7px; + top:4px; + left:-4px; + background: #eee; + box-sizing: border-box; + } + .red-ui-search-result-node-output{ + left: 16px; + } + .palette_icon_container { + width: 18px; + } + .palette_icon { + width: 15px; + } + .red-ui-search-result-description { + margin-left:28px; + } + .red-ui-search-result-node-label { + color: #999; + } +} + .red-ui-search-container { padding: 3px; border-bottom: 1px solid $secondary-border-color; @@ -37,19 +108,21 @@ position:relative; height: 300px; padding: 5px; - background: $background-color; + background: #f9f9f9; + .red-ui-editableList-container { background: white; - border-radius: 2px; padding: 0; - background: $background-color; + background: #f9f9f9; li { padding: 0; - - &.selected .red-ui-search-result { - border-color: $primary-border-color; + &.selected { + background: #efefef; + .red-ui-search-result { + border-left-color:#999; + border-right-color:#999; + } } - } } } @@ -58,11 +131,19 @@ display: block; cursor: pointer; color: $form-text-color; - border: 2px solid white; + border-left: 3px solid #fff; + border-right: 3px solid #fff; &:hover { text-decoration: none; - border-color: $primary-border-color; color: $form-text-color; + background: #efefef; + border-left-color:#efefef; + border-right-color:#efefef; + } + &:after { + content: ""; + display: table; + clear: both; } } @@ -78,6 +159,11 @@ background-repeat: no-repeat; background-size: contain; position: relative; + + .palette_icon_container { + border-right: none; + } + } .red-ui-search-result-description { margin-left: 40px; diff --git a/editor/sass/sidebar.scss b/editor/sass/sidebar.scss index 0f782e773..95d58e2c8 100644 --- a/editor/sass/sidebar.scss +++ b/editor/sass/sidebar.scss @@ -71,6 +71,7 @@ padding: 8px 10px; background: #f3f3f3; border-bottom: 1px solid $secondary-border-color; + white-space: nowrap; } #sidebar-footer { @@ -80,6 +81,9 @@ .sidebar-footer-button { @include component-footer-button; } +.sidebar-footer-button-toggle { + @include component-footer-button-toggle; +} .sidebar-header-button { @include workspace-button; font-size: 13px; @@ -97,10 +101,5 @@ } .sidebar-shade { - position: absolute; - top:0; - bottom:0; - left:0; - right:0; - background: $shade-color; + @include shade; } diff --git a/editor/sass/style.scss b/editor/sass/style.scss index c09db2817..46ad05c82 100644 --- a/editor/sass/style.scss +++ b/editor/sass/style.scss @@ -21,6 +21,7 @@ @import "jquery"; @import "bootstrap"; +@import "ace"; @import "dropdownMenu"; @@ -53,6 +54,7 @@ @import "keyboard"; +@import "debug"; body { font-size: 14px; @@ -119,3 +121,7 @@ pre code { background-color: transparent; border: 0; } + +.hide { + display: none; +} diff --git a/editor/sass/tab-info.scss b/editor/sass/tab-info.scss index b74b791c4..eda9098c3 100644 --- a/editor/sass/tab-info.scss +++ b/editor/sass/tab-info.scss @@ -35,7 +35,7 @@ table.node-info tr.blank td { padding-left: 0px; } table.node-info td:first-child{ - color: #000; + color: #666; vertical-align: top; width: 90px; padding: 3px; @@ -81,3 +81,18 @@ div.node-info { margin: 8px auto; } } +.node-info-tip { + position: absolute; + top: 40%; + left:0; + right:0; + padding: 20px; + font-size: 16px; + text-align: center; + line-height: 1.9em; + color : #bbb; + background-color: #fff; + @include disable-selection; + cursor: default; + +} diff --git a/editor/sass/ui/common/typedInput.scss b/editor/sass/ui/common/typedInput.scss index b2cbcdfde..95d8c4dfa 100644 --- a/editor/sass/ui/common/typedInput.scss +++ b/editor/sass/ui/common/typedInput.scss @@ -49,7 +49,10 @@ border-color: $form-input-focus-color !important; } - a { + button { + text-align: left; + border: none; + position: absolute; box-sizing: border-box; border-top-left-radius: 4px; border-bottom-left-radius: 4px; @@ -66,6 +69,9 @@ margin-right:4px; margin-top: 1px; vertical-align: middle; + &.fa-ellipsis-h { + top: -1px; + } } &.disabled { cursor: default; @@ -82,44 +88,65 @@ &:not(.disabled):hover { text-decoration: none; background: $typedInput-button-background-hover; - - span { - background: $typedInput-button-background-hover; - } } &:focus { text-decoration: none; + outline: none; + box-shadow: inset 0 0 0 1px $form-input-focus-color; } &:not(.disabled):active { background: $typedInput-button-background-active; text-decoration: none; } + &.red-ui-typedInput-full-width { + width: 100%; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + } } - a.red-ui-typedInput-option-trigger { + 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 { border-top-left-radius: 0px; border-bottom-left-radius: 0px; border-top-right-radius: 4px; border-bottom-right-radius: 4px; - padding: 0 5px 0 0; + padding: 0 0 0 0; position:absolute; - left:0; - top:0; - bottom:0; - right:0; + width: calc( 100% ); i { position:absolute; right: 4px; top: 7px; } - span { + .red-ui-typedInput-option-label { background:#fff; position:absolute; left:0; right:23px; + top: 0; padding: 0 5px 0 5px; } + .red-ui-typedInput-option-caret { + top: 0; + position: absolute; + right: 0; + width: 17px; + } + &:focus { + box-shadow: none; + } + &:focus .red-ui-typedInput-option-caret { + box-shadow: inset 0 0 0 1px $form-input-focus-color; + } } } .red-ui-typedInput-options { @@ -139,12 +166,12 @@ } &:focus { text-decoration: none; + background: $typedInput-button-background-active; + outline: none; } &:active { - background: $typedInput-button-background-active; text-decoration: none; + background: $typedInput-button-background-active; } - - } } diff --git a/editor/templates/index.mst b/editor/templates/index.mst index 6fa473c93..c3d2cf1c8 100644 --- a/editor/templates/index.mst +++ b/editor/templates/index.mst @@ -87,19 +87,28 @@
          -
          -
            +
            +
              -
              -
                +
                +
                  -
                  +
                  +
                  + +
                  +
                  +
                  +
                  +
                  +
                  +
                  +
                  +
                  +
                  -
                  -
                    -
                    @@ -159,10 +168,25 @@
                    + + + + + diff --git a/editor/vendor/font-awesome/css/font-awesome.min.css b/editor/vendor/font-awesome/css/font-awesome.min.css index 9b27f8ea8..540440ce8 100755 --- a/editor/vendor/font-awesome/css/font-awesome.min.css +++ b/editor/vendor/font-awesome/css/font-awesome.min.css @@ -1,4 +1,4 @@ /*! - * Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.6.3');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.6.3') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.6.3') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.6.3') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.6.3') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.6.3#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/editor/vendor/font-awesome/fonts/FontAwesome.otf b/editor/vendor/font-awesome/fonts/FontAwesome.otf index d4de13e83..401ec0f36 100755 Binary files a/editor/vendor/font-awesome/fonts/FontAwesome.otf and b/editor/vendor/font-awesome/fonts/FontAwesome.otf differ diff --git a/editor/vendor/font-awesome/fonts/fontawesome-webfont.eot b/editor/vendor/font-awesome/fonts/fontawesome-webfont.eot index c7b00d2ba..e9f60ca95 100755 Binary files a/editor/vendor/font-awesome/fonts/fontawesome-webfont.eot and b/editor/vendor/font-awesome/fonts/fontawesome-webfont.eot differ diff --git a/editor/vendor/font-awesome/fonts/fontawesome-webfont.svg b/editor/vendor/font-awesome/fonts/fontawesome-webfont.svg index 8b66187fe..855c845e5 100755 --- a/editor/vendor/font-awesome/fonts/fontawesome-webfont.svg +++ b/editor/vendor/font-awesome/fonts/fontawesome-webfont.svg @@ -1,685 +1,2671 @@ - - + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/editor/vendor/font-awesome/fonts/fontawesome-webfont.ttf b/editor/vendor/font-awesome/fonts/fontawesome-webfont.ttf index f221e50a2..35acda2fa 100755 Binary files a/editor/vendor/font-awesome/fonts/fontawesome-webfont.ttf and b/editor/vendor/font-awesome/fonts/fontawesome-webfont.ttf differ diff --git a/editor/vendor/font-awesome/fonts/fontawesome-webfont.woff b/editor/vendor/font-awesome/fonts/fontawesome-webfont.woff index 6e7483cf6..400014a4b 100755 Binary files a/editor/vendor/font-awesome/fonts/fontawesome-webfont.woff and b/editor/vendor/font-awesome/fonts/fontawesome-webfont.woff differ diff --git a/editor/vendor/font-awesome/fonts/fontawesome-webfont.woff2 b/editor/vendor/font-awesome/fonts/fontawesome-webfont.woff2 index 7eb74fd12..4d13fc604 100755 Binary files a/editor/vendor/font-awesome/fonts/fontawesome-webfont.woff2 and b/editor/vendor/font-awesome/fonts/fontawesome-webfont.woff2 differ diff --git a/editor/vendor/jsonata/formatter.js b/editor/vendor/jsonata/formatter.js new file mode 100644 index 000000000..f0d231a79 --- /dev/null +++ b/editor/vendor/jsonata/formatter.js @@ -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 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; + } +})(); diff --git a/editor/vendor/jsonata/mode-jsonata.js b/editor/vendor/jsonata/mode-jsonata.js new file mode 100644 index 000000000..fb5883db7 --- /dev/null +++ b/editor/vendor/jsonata/mode-jsonata.js @@ -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; + +}); diff --git a/editor/vendor/jsonata/snippets-jsonata.js b/editor/vendor/jsonata/snippets-jsonata.js new file mode 100644 index 000000000..3fa8b7e82 --- /dev/null +++ b/editor/vendor/jsonata/snippets-jsonata.js @@ -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"; +}); diff --git a/editor/vendor/jsonata/worker-jsonata.js b/editor/vendor/jsonata/worker-jsonata.js new file mode 100644 index 000000000..da292ea6d --- /dev/null +++ b/editor/vendor/jsonata/worker-jsonata.js @@ -0,0 +1,4236 @@ +"no use strict"; +;(function(window) { +if (typeof window.window != "undefined" && window.document) + return; +if (window.require && window.define) + return; + +if (!window.console) { + window.console = function() { + var msgs = Array.prototype.slice.call(arguments, 0); + postMessage({type: "log", data: msgs}); + }; + window.console.error = + window.console.warn = + window.console.log = + window.console.trace = window.console; +} +window.window = window; +window.ace = window; + +window.onerror = function(message, file, line, col, err) { + postMessage({type: "error", data: { + message: message, + data: err.data, + file: file, + line: line, + col: col, + stack: err.stack + }}); +}; + +window.normalizeModule = function(parentId, moduleName) { + // normalize plugin requires + if (moduleName.indexOf("!") !== -1) { + var chunks = moduleName.split("!"); + return window.normalizeModule(parentId, chunks[0]) + "!" + window.normalizeModule(parentId, chunks[1]); + } + // normalize relative requires + 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; +}; + +window.require = function require(parentId, id) { + if (!id) { + id = parentId; + parentId = null; + } + if (!id.charAt) + throw new Error("worker.js require() accepts only (parentId, id) as arguments"); + + id = window.normalizeModule(parentId, id); + + var module = window.require.modules[id]; + if (module) { + if (!module.initialized) { + module.initialized = true; + module.exports = module.factory().exports; + } + 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); + return window.require(parentId, id); +}; +function resolveModuleId(id, paths) { + var testPath = id, tail = ""; + while (testPath) { + var alias = paths[testPath]; + if (typeof alias == "string") { + return alias + tail; + } else if (alias) { + return alias.location.replace(/\/*$/, "/") + (tail || alias.main || alias.name); + } else if (alias === false) { + return ""; + } + var i = testPath.lastIndexOf("/"); + if (i === -1) break; + tail = testPath.substr(i) + tail; + testPath = testPath.slice(0, i); + } + return id; +} +window.require.modules = {}; +window.require.tlns = {}; + +window.define = function(id, deps, factory) { + if (arguments.length == 2) { + factory = deps; + if (typeof id != "string") { + deps = id; + id = window.require.id; + } + } else if (arguments.length == 1) { + factory = id; + deps = []; + id = window.require.id; + } + + if (typeof factory != "function") { + window.require.modules[id] = { + exports: factory, + initialized: true + }; + return; + } + + if (!deps.length) + // If there is no dependencies, we inject "require", "exports" and + // "module" as dependencies, to provide CommonJS compatibility. + deps = ["require", "exports", "module"]; + + var req = function(childId) { + return window.require(id, childId); + }; + + window.require.modules[id] = { + exports: {}, + factory: function() { + var module = this; + var returnExports = factory.apply(this, deps.map(function(dep) { + switch (dep) { + // Because "require", "exports" and "module" aren't actual + // dependencies, we must handle them seperately. + case "require": return req; + case "exports": return module.exports; + case "module": return module; + // But for all other dependencies, we can just go ahead and + // require them. + default: return req(dep); + } + })); + if (returnExports) + module.exports = returnExports; + return module; + } + }; +}; +window.define.amd = {}; +require.tlns = {}; +window.initBaseUrls = function initBaseUrls(topLevelNamespaces) { + for (var i in topLevelNamespaces) + require.tlns[i] = topLevelNamespaces[i]; +}; + +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", + id: callbackId, + data: data + }); + }; + + this.emit = function(name, data) { + postMessage({ + type: "event", + name: name, + data: data + }); + }; + + }).call(Sender.prototype); + + return new Sender(); +}; + +var main = window.main = null; +var sender = window.sender = null; + +window.onmessage = function(e) { + var msg = e.data; + if (msg.event && sender) { + sender._signal(msg.event, msg.data); + } + else if (msg.command) { + if (main[msg.command]) + main[msg.command].apply(main, msg.args); + else if (window[msg.command]) + window[msg.command].apply(window, msg.args); + else + throw new Error("Unknown command:" + msg.command); + } + else if (msg.init) { + window.initBaseUrls(msg.tlns); + require("ace/lib/es5-shim"); + sender = window.sender = window.initSender(); + var clazz = require(msg.module)[msg.classname]; + main = window.main = new clazz(sender); + } +}; +})(this); + +define("ace/lib/oop",["require","exports","module"], function(require, exports, module) { +"use strict"; + +exports.inherits = function(ctor, superCtor) { + ctor.super_ = superCtor; + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); +}; + +exports.mixin = function(obj, mixin) { + for (var key in mixin) { + obj[key] = mixin[key]; + } + return obj; +}; + +exports.implement = function(proto, mixin) { + exports.mixin(proto, mixin); +}; + +}); + +define("ace/range",["require","exports","module"], function(require, exports, module) { +"use strict"; +var comparePoints = function(p1, p2) { + return p1.row - p2.row || p1.column - p2.column; +}; +var Range = function(startRow, startColumn, endRow, endColumn) { + this.start = { + row: startRow, + column: startColumn + }; + + this.end = { + row: endRow, + column: endColumn + }; +}; + +(function() { + this.isEqual = function(range) { + return this.start.row === range.start.row && + this.end.row === range.end.row && + this.start.column === range.start.column && + this.end.column === range.end.column; + }; + this.toString = function() { + return ("Range: [" + this.start.row + "/" + this.start.column + + "] -> [" + this.end.row + "/" + this.end.column + "]"); + }; + + this.contains = function(row, column) { + return this.compare(row, column) == 0; + }; + this.compareRange = function(range) { + var cmp, + end = range.end, + start = range.start; + + cmp = this.compare(end.row, end.column); + if (cmp == 1) { + cmp = this.compare(start.row, start.column); + if (cmp == 1) { + return 2; + } else if (cmp == 0) { + return 1; + } else { + return 0; + } + } else if (cmp == -1) { + return -2; + } else { + cmp = this.compare(start.row, start.column); + if (cmp == -1) { + return -1; + } else if (cmp == 1) { + return 42; + } else { + return 0; + } + } + }; + this.comparePoint = function(p) { + return this.compare(p.row, p.column); + }; + this.containsRange = function(range) { + return this.comparePoint(range.start) == 0 && this.comparePoint(range.end) == 0; + }; + this.intersects = function(range) { + var cmp = this.compareRange(range); + return (cmp == -1 || cmp == 0 || cmp == 1); + }; + this.isEnd = function(row, column) { + return this.end.row == row && this.end.column == column; + }; + this.isStart = function(row, column) { + return this.start.row == row && this.start.column == column; + }; + this.setStart = function(row, column) { + if (typeof row == "object") { + this.start.column = row.column; + this.start.row = row.row; + } else { + this.start.row = row; + this.start.column = column; + } + }; + this.setEnd = function(row, column) { + if (typeof row == "object") { + this.end.column = row.column; + this.end.row = row.row; + } else { + this.end.row = row; + this.end.column = column; + } + }; + this.inside = function(row, column) { + if (this.compare(row, column) == 0) { + if (this.isEnd(row, column) || this.isStart(row, column)) { + return false; + } else { + return true; + } + } + return false; + }; + this.insideStart = function(row, column) { + if (this.compare(row, column) == 0) { + if (this.isEnd(row, column)) { + return false; + } else { + return true; + } + } + return false; + }; + this.insideEnd = function(row, column) { + if (this.compare(row, column) == 0) { + if (this.isStart(row, column)) { + return false; + } else { + return true; + } + } + return false; + }; + this.compare = function(row, column) { + if (!this.isMultiLine()) { + if (row === this.start.row) { + return column < this.start.column ? -1 : (column > this.end.column ? 1 : 0); + } + } + + if (row < this.start.row) + return -1; + + if (row > this.end.row) + return 1; + + if (this.start.row === row) + return column >= this.start.column ? 0 : -1; + + if (this.end.row === row) + return column <= this.end.column ? 0 : 1; + + return 0; + }; + this.compareStart = function(row, column) { + if (this.start.row == row && this.start.column == column) { + return -1; + } else { + return this.compare(row, column); + } + }; + this.compareEnd = function(row, column) { + if (this.end.row == row && this.end.column == column) { + return 1; + } else { + return this.compare(row, column); + } + }; + this.compareInside = function(row, column) { + if (this.end.row == row && this.end.column == column) { + return 1; + } else if (this.start.row == row && this.start.column == column) { + return -1; + } else { + return this.compare(row, column); + } + }; + this.clipRows = function(firstRow, lastRow) { + if (this.end.row > lastRow) + var end = {row: lastRow + 1, column: 0}; + else if (this.end.row < firstRow) + var end = {row: firstRow, column: 0}; + + if (this.start.row > lastRow) + var start = {row: lastRow + 1, column: 0}; + else if (this.start.row < firstRow) + var start = {row: firstRow, column: 0}; + + return Range.fromPoints(start || this.start, end || this.end); + }; + this.extend = function(row, column) { + var cmp = this.compare(row, column); + + if (cmp == 0) + return this; + else if (cmp == -1) + var start = {row: row, column: column}; + else + var end = {row: row, column: column}; + + return Range.fromPoints(start || this.start, end || this.end); + }; + + this.isEmpty = function() { + return (this.start.row === this.end.row && this.start.column === this.end.column); + }; + this.isMultiLine = function() { + return (this.start.row !== this.end.row); + }; + this.clone = function() { + return Range.fromPoints(this.start, this.end); + }; + this.collapseRows = function() { + if (this.end.column == 0) + return new Range(this.start.row, 0, Math.max(this.start.row, this.end.row-1), 0) + else + return new Range(this.start.row, 0, this.end.row, 0) + }; + this.toScreenRange = function(session) { + var screenPosStart = session.documentToScreenPosition(this.start); + var screenPosEnd = session.documentToScreenPosition(this.end); + + return new Range( + screenPosStart.row, screenPosStart.column, + screenPosEnd.row, screenPosEnd.column + ); + }; + this.moveBy = function(row, column) { + this.start.row += row; + this.start.column += column; + this.end.row += row; + this.end.column += column; + }; + +}).call(Range.prototype); +Range.fromPoints = function(start, end) { + return new Range(start.row, start.column, end.row, end.column); +}; +Range.comparePoints = comparePoints; + +Range.comparePoints = function(p1, p2) { + return p1.row - p2.row || p1.column - p2.column; +}; + + +exports.Range = Range; +}); + +define("ace/apply_delta",["require","exports","module"], function(require, exports, module) { +"use strict"; + +function throwDeltaError(delta, errorText){ + console.log("Invalid Delta:", delta); + throw "Invalid Delta: " + errorText; +} + +function positionInDocument(docLines, position) { + return position.row >= 0 && position.row < docLines.length && + position.column >= 0 && position.column <= docLines[position.row].length; +} + +function validateDelta(docLines, delta) { + if (delta.action != "insert" && delta.action != "remove") + throwDeltaError(delta, "delta.action must be 'insert' or 'remove'"); + if (!(delta.lines instanceof Array)) + throwDeltaError(delta, "delta.lines must be an Array"); + if (!delta.start || !delta.end) + throwDeltaError(delta, "delta.start/end must be an present"); + var start = delta.start; + if (!positionInDocument(docLines, delta.start)) + throwDeltaError(delta, "delta.start must be contained in document"); + var end = delta.end; + if (delta.action == "remove" && !positionInDocument(docLines, end)) + throwDeltaError(delta, "delta.end must contained in document for 'remove' actions"); + var numRangeRows = end.row - start.row; + var numRangeLastLineChars = (end.column - (numRangeRows == 0 ? start.column : 0)); + if (numRangeRows != delta.lines.length - 1 || delta.lines[numRangeRows].length != numRangeLastLineChars) + throwDeltaError(delta, "delta.range must match delta lines"); +} + +exports.applyDelta = function(docLines, delta, doNotValidate) { + + var row = delta.start.row; + var startColumn = delta.start.column; + var line = docLines[row] || ""; + switch (delta.action) { + case "insert": + var lines = delta.lines; + if (lines.length === 1) { + docLines[row] = line.substring(0, startColumn) + delta.lines[0] + line.substring(startColumn); + } else { + var args = [row, 1].concat(delta.lines); + docLines.splice.apply(docLines, args); + docLines[row] = line.substring(0, startColumn) + docLines[row]; + docLines[row + delta.lines.length - 1] += line.substring(startColumn); + } + break; + case "remove": + var endColumn = delta.end.column; + var endRow = delta.end.row; + if (row === endRow) { + docLines[row] = line.substring(0, startColumn) + line.substring(endColumn); + } else { + docLines.splice( + row, endRow - row + 1, + line.substring(0, startColumn) + docLines[endRow].substring(endColumn) + ); + } + break; + } +} +}); + +define("ace/lib/event_emitter",["require","exports","module"], function(require, exports, module) { +"use strict"; + +var EventEmitter = {}; +var stopPropagation = function() { this.propagationStopped = true; }; +var preventDefault = function() { this.defaultPrevented = true; }; + +EventEmitter._emit = +EventEmitter._dispatchEvent = function(eventName, e) { + this._eventRegistry || (this._eventRegistry = {}); + this._defaultHandlers || (this._defaultHandlers = {}); + + var listeners = this._eventRegistry[eventName] || []; + var defaultHandler = this._defaultHandlers[eventName]; + if (!listeners.length && !defaultHandler) + return; + + if (typeof e != "object" || !e) + e = {}; + + if (!e.type) + e.type = eventName; + if (!e.stopPropagation) + e.stopPropagation = stopPropagation; + if (!e.preventDefault) + e.preventDefault = preventDefault; + + listeners = listeners.slice(); + for (var i=0; i 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); + var deltaColShift = (deltaIsInsert ? 1 : -1) * (delta.end.column - delta.start.column); + var deltaStart = delta.start; + var deltaEnd = deltaIsInsert ? deltaStart : delta.end; // Collapse insert range. + if ($pointsInOrder(point, deltaStart, moveIfEqual)) { + return { + row: point.row, + column: point.column + }; + } + if ($pointsInOrder(deltaEnd, point, !moveIfEqual)) { + return { + row: point.row + deltaRowShift, + column: point.column + (point.row == deltaEnd.row ? deltaColShift : 0) + }; + } + + return { + row: deltaStart.row, + column: deltaStart.column + }; + } + this.setPosition = function(row, column, noClip) { + var pos; + if (noClip) { + pos = { + row: row, + column: column + }; + } else { + pos = this.$clipPositionToDocument(row, column); + } + + if (this.row == pos.row && this.column == pos.column) + return; + + var old = { + row: this.row, + column: this.column + }; + + this.row = pos.row; + this.column = pos.column; + this._signal("change", { + old: old, + value: pos + }); + }; + this.detach = function() { + this.document.removeEventListener("change", this.$onChange); + }; + this.attach = function(doc) { + this.document = doc || this.document; + this.document.on("change", this.$onChange); + }; + this.$clipPositionToDocument = function(row, column) { + var pos = {}; + + if (row >= this.document.getLength()) { + pos.row = Math.max(0, this.document.getLength() - 1); + pos.column = this.document.getLine(pos.row).length; + } + else if (row < 0) { + pos.row = 0; + pos.column = 0; + } + else { + pos.row = row; + pos.column = Math.min(this.document.getLine(pos.row).length, Math.max(0, column)); + } + + if (column < 0) + pos.column = 0; + + return pos; + }; + +}).call(Anchor.prototype); + +}); + +define("ace/document",["require","exports","module","ace/lib/oop","ace/apply_delta","ace/lib/event_emitter","ace/range","ace/anchor"], function(require, exports, module) { +"use strict"; + +var oop = require("./lib/oop"); +var applyDelta = require("./apply_delta").applyDelta; +var EventEmitter = require("./lib/event_emitter").EventEmitter; +var Range = require("./range").Range; +var Anchor = require("./anchor").Anchor; + +var Document = function(textOrLines) { + this.$lines = [""]; + if (textOrLines.length === 0) { + this.$lines = [""]; + } else if (Array.isArray(textOrLines)) { + this.insertMergedLines({row: 0, column: 0}, textOrLines); + } else { + this.insert({row: 0, column:0}, textOrLines); + } +}; + +(function() { + + oop.implement(this, EventEmitter); + this.setValue = function(text) { + var len = this.getLength() - 1; + this.remove(new Range(0, 0, len, this.getLine(len).length)); + this.insert({row: 0, column: 0}, text); + }; + this.getValue = function() { + return this.getAllLines().join(this.getNewLineCharacter()); + }; + this.createAnchor = function(row, column) { + return new Anchor(this, row, column); + }; + if ("aaa".split(/a/).length === 0) { + this.$split = function(text) { + return text.replace(/\r\n|\r/g, "\n").split("\n"); + }; + } else { + this.$split = function(text) { + return text.split(/\r\n|\r|\n/); + }; + } + + + this.$detectNewLine = function(text) { + var match = text.match(/^.*?(\r\n|\r|\n)/m); + this.$autoNewLine = match ? match[1] : "\n"; + this._signal("changeNewLineMode"); + }; + this.getNewLineCharacter = function() { + switch (this.$newLineMode) { + case "windows": + return "\r\n"; + case "unix": + return "\n"; + default: + return this.$autoNewLine || "\n"; + } + }; + + this.$autoNewLine = ""; + this.$newLineMode = "auto"; + this.setNewLineMode = function(newLineMode) { + if (this.$newLineMode === newLineMode) + return; + + this.$newLineMode = newLineMode; + this._signal("changeNewLineMode"); + }; + this.getNewLineMode = function() { + return this.$newLineMode; + }; + this.isNewLine = function(text) { + return (text == "\r\n" || text == "\r" || text == "\n"); + }; + this.getLine = function(row) { + return this.$lines[row] || ""; + }; + this.getLines = function(firstRow, lastRow) { + return this.$lines.slice(firstRow, lastRow + 1); + }; + this.getAllLines = function() { + return this.getLines(0, this.getLength()); + }; + this.getLength = function() { + return this.$lines.length; + }; + this.getTextRange = function(range) { + return this.getLinesForRange(range).join(this.getNewLineCharacter()); + }; + this.getLinesForRange = function(range) { + var lines; + if (range.start.row === range.end.row) { + lines = [this.getLine(range.start.row).substring(range.start.column, range.end.column)]; + } else { + lines = this.getLines(range.start.row, range.end.row); + lines[0] = (lines[0] || "").substring(range.start.column); + var l = lines.length - 1; + if (range.end.row - range.start.row == l) + lines[l] = lines[l].substring(0, range.end.column); + } + return lines; + }; + this.insertLines = function(row, lines) { + console.warn("Use of document.insertLines is deprecated. Use the insertFullLines method instead."); + return this.insertFullLines(row, lines); + }; + this.removeLines = function(firstRow, lastRow) { + console.warn("Use of document.removeLines is deprecated. Use the removeFullLines method instead."); + return this.removeFullLines(firstRow, lastRow); + }; + this.insertNewLine = function(position) { + console.warn("Use of document.insertNewLine is deprecated. Use insertMergedLines(position, ['', '']) instead."); + return this.insertMergedLines(position, ["", ""]); + }; + 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) { + row = length; + } else if (row < 0) { + row = 0; + } else if (row >= length) { + row = length - 1; + column = undefined; + } + var line = this.getLine(row); + if (column == undefined) + column = line.length; + 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) { + position.row = Math.max(0, length - 1); + position.column = this.getLine(length - 1).length; + } else { + position.row = Math.max(0, position.row); + position.column = Math.min(Math.max(position.column, 0), this.getLine(position.row).length); + } + return position; + }; + this.insertFullLines = function(row, lines) { + row = Math.min(Math.max(row, 0), this.getLength()); + var column = 0; + if (row < this.getLength()) { + lines = lines.concat([""]); + column = 0; + } else { + lines = [""].concat(lines); + row--; + 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) { + var start = this.clippedPos(range.start.row, range.start.column); + var end = this.clippedPos(range.end.row, range.end.column); + this.applyDelta({ + start: start, + end: end, + action: "remove", + lines: this.getLinesForRange({start: start, end: end}) + }); + return this.clonePos(start); + }; + 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) { + firstRow = Math.min(Math.max(0, firstRow), this.getLength() - 1); + lastRow = Math.min(Math.max(0, lastRow ), this.getLength() - 1); + var deleteFirstNewLine = lastRow == this.getLength() - 1 && firstRow > 0; + var deleteLastNewLine = lastRow < this.getLength() - 1; + 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 range = new Range(startRow, startCol, endRow, endCol); + var deletedLines = this.$lines.slice(firstRow, lastRow + 1); + + this.applyDelta({ + start: range.start, + end: range.end, + action: "remove", + lines: this.getLinesForRange(range) + }); + return deletedLines; + }; + this.removeNewLine = function(row) { + if (row < this.getLength() - 1 && row >= 0) { + this.applyDelta({ + start: this.pos(row, this.getLine(row).length), + end: this.pos(row + 1, 0), + action: "remove", + lines: ["", ""] + }); + } + }; + this.replace = function(range, text) { + if (!(range instanceof Range)) + range = Range.fromPoints(range.start, range.end); + if (text.length === 0 && range.isEmpty()) + return range.start; + if (text == this.getTextRange(range)) + return range.end; + + this.remove(range); + var end; + if (text) { + end = this.insert(range.start, text); + } + else { + end = range.start; + } + + return end; + }; + this.applyDeltas = function(deltas) { + for (var i=0; i=0; i--) { + this.revertDelta(deltas[i]); + } + }; + this.applyDelta = function(delta, doNotValidate) { + var isInsert = delta.action == "insert"; + if (isInsert ? delta.lines.length <= 1 && !delta.lines[0] + : !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 column = delta.start.column; + var from = 0, to = 0; + do { + from = to; + to += MAX - 1; + var chunk = lines.slice(from, to); + if (to > l) { + delta.lines = chunk; + delta.start.row = row + from; + delta.start.column = column; + break; + } + chunk.push(""); + this.applyDelta({ + start: this.pos(row + from, column), + end: this.pos(row + to, column = 0), + action: delta.action, + lines: chunk + }, true); + } while(true); + }; + this.revertDelta = function(delta) { + this.applyDelta({ + start: this.clonePos(delta.start), + end: this.clonePos(delta.end), + action: (delta.action == "insert" ? "remove" : "insert"), + lines: delta.lines.slice() + }); + }; + this.indexToPosition = function(index, startRow) { + var lines = this.$lines || this.getAllLines(); + var newlineLength = this.getNewLineCharacter().length; + for (var i = startRow || 0, l = lines.length; i < l; i++) { + index -= lines[i].length + newlineLength; + if (index < 0) + return {row: i, column: index + lines[i].length + newlineLength}; + } + return {row: l-1, column: lines[l-1].length}; + }; + this.positionToIndex = function(pos, startRow) { + var lines = this.$lines || this.getAllLines(); + var newlineLength = this.getNewLineCharacter().length; + var index = 0; + var row = Math.min(pos.row, lines.length); + for (var i = startRow || 0; i < row; ++i) + index += lines[i].length + newlineLength; + + return index + pos.column; + }; + +}).call(Document.prototype); + +exports.Document = Document; +}); + +define("ace/lib/lang",["require","exports","module"], function(require, exports, module) { +"use strict"; + +exports.last = function(a) { + return a[a.length - 1]; +}; + +exports.stringReverse = function(string) { + return string.split("").reverse().join(""); +}; + +exports.stringRepeat = function (string, count) { + var result = ''; + while (count > 0) { + if (count & 1) + result += string; + + if (count >>= 1) + string += string; + } + return result; +}; + +var trimBeginRegexp = /^\s\s*/; +var trimEndRegexp = /\s\s*$/; + +exports.stringTrimLeft = function (string) { + return string.replace(trimBeginRegexp, ''); +}; + +exports.stringTrimRight = function (string) { + return string.replace(trimEndRegexp, ''); +}; + +exports.copyObject = function(obj) { + var copy = {}; + for (var key in obj) { + copy[key] = obj[key]; + } + return copy; +}; + +exports.copyArray = function(array){ + var copy = []; + for (var i=0, l=array.length; i': 40, + '`': 80, + '**': 60, + '..': 20, + ':=': 10, + '!=': 40, + '<=': 40, + '>=': 40, + 'and': 30, + 'or': 25, + 'in': 40, + '&': 50, + '!': 0 // not an operator, but needed as a stop character for name tokens + }; + + var escapes = { // JSON string escape sequences - see json.org + '"': '"', + '\\': '\\', + '/': '/', + 'b': '\b', + 'f': '\f', + 'n': '\n', + 'r': '\r', + 't': '\t' + }; + var tokenizer = function (path) { + var position = 0; + var length = path.length; + + var create = function (type, value) { + var obj = {type: type, value: value, position: position}; + return obj; + }; + + var next = function () { + if (position >= length) return null; + var currentChar = path.charAt(position); + while (position < length && ' \t\n\r\v'.indexOf(currentChar) > -1) { + position++; + currentChar = path.charAt(position); + } + if (currentChar === '.' && path.charAt(position + 1) === '.') { + position += 2; + return create('operator', '..'); + } + if (currentChar === ':' && path.charAt(position + 1) === '=') { + position += 2; + return create('operator', ':='); + } + if (currentChar === '!' && path.charAt(position + 1) === '=') { + position += 2; + return create('operator', '!='); + } + if (currentChar === '>' && path.charAt(position + 1) === '=') { + position += 2; + return create('operator', '>='); + } + if (currentChar === '<' && path.charAt(position + 1) === '=') { + position += 2; + return create('operator', '<='); + } + if (currentChar === '*' && path.charAt(position + 1) === '*') { + position += 2; + return create('operator', '**'); + } + if (operators.hasOwnProperty(currentChar)) { + position++; + return create('operator', currentChar); + } + if (currentChar === '"' || currentChar === "'") { + var quoteType = currentChar; + position++; + var qstr = ""; + while (position < length) { + currentChar = path.charAt(position); + if (currentChar === '\\') { // escape sequence + position++; + currentChar = path.charAt(position); + if (escapes.hasOwnProperty(currentChar)) { + qstr += escapes[currentChar]; + } else if (currentChar === 'u') { + var octets = path.substr(position + 1, 4); + if (/^[0-9a-fA-F]+$/.test(octets)) { + var codepoint = parseInt(octets, 16); + qstr += String.fromCharCode(codepoint); + position += 4; + } else { + throw { + message: "The escape sequence \\u must be followed by 4 hex digits at column " + position, + stack: (new Error()).stack, + position: position + }; + } + } else { + throw { + message: 'unsupported escape sequence: \\' + currentChar + ' at column ' + position, + stack: (new Error()).stack, + position: position, + token: currentChar + }; + + } + } else if (currentChar === quoteType) { + position++; + return create('string', qstr); + } else { + qstr += currentChar; + } + position++; + } + throw { + message: 'no terminating quote found in string literal at column ' + position, + stack: (new Error()).stack, + position: position + }; + } + var numregex = /^-?(0|([1-9][0-9]*))(\.[0-9]+)?([Ee][-+]?[0-9]+)?/; + var match = numregex.exec(path.substring(position)); + if (match !== null) { + var num = parseFloat(match[0]); + if (!isNaN(num) && isFinite(num)) { + position += match[0].length; + return create('number', num); + } else { + throw { + message: 'Number out of range: ' + match[0] + ' at column ' + position, + stack: (new Error()).stack, + position: position, + token: match[0] + }; + } + } + var i = position; + var ch; + var name; + for (;;) { + ch = path.charAt(i); + if (i == length || ' \t\n\r\v'.indexOf(ch) > -1 || operators.hasOwnProperty(ch)) { + if (path.charAt(position) === '$') { + name = path.substring(position + 1, i); + position = i; + return create('variable', name); + } else { + name = path.substring(position, i); + position = i; + switch (name) { + case 'and': + case 'or': + case 'in': + return create('operator', name); + case 'true': + return create('value', true); + case 'false': + return create('value', false); + case 'null': + return create('value', null); + default: + if (position == length && name === '') { + return null; + } + return create('name', name); + } + } + } else { + i++; + } + } + }; + + return next; + }; + + var parser = function (source) { + var node; + var lexer; + + var symbol_table = {}; + + var base_symbol = { + nud: function () { + return this; + } + }; + + var symbol = function (id, bp) { + var s = symbol_table[id]; + bp = bp || 0; + if (s) { + if (bp >= s.lbp) { + s.lbp = bp; + } + } else { + s = Object.create(base_symbol); + s.id = s.value = id; + s.lbp = bp; + symbol_table[id] = s; + } + return s; + }; + + var advance = function (id) { + if (id && node.id !== id) { + var msg; + if(node.id === '(end)') { + msg = "Syntax error: expected '" + id + "' before end of expression"; + } else { + msg = "Syntax error: expected '" + id + "', got '" + node.id + "' at column " + node.position; + } + throw { + message: msg , + stack: (new Error()).stack, + position: node.position, + token: node.id, + value: id + }; + } + var next_token = lexer(); + if (next_token === null) { + node = symbol_table["(end)"]; + return node; + } + var value = next_token.value; + var type = next_token.type; + var symbol; + switch (type) { + case 'name': + case 'variable': + symbol = symbol_table["(name)"]; + break; + case 'operator': + symbol = symbol_table[value]; + if (!symbol) { + throw { + message: "Unknown operator: " + value + " at column " + next_token.position, + stack: (new Error()).stack, + position: next_token.position, + token: value + }; + } + break; + case 'string': + case 'number': + case 'value': + type = "literal"; + symbol = symbol_table["(literal)"]; + break; + default: + throw { + message: "Unexpected token:" + value + " at column " + next_token.position, + stack: (new Error()).stack, + position: next_token.position, + token: value + }; + } + + node = Object.create(symbol); + node.value = value; + node.type = type; + node.position = next_token.position; + return node; + }; + var expression = function (rbp) { + var left; + var t = node; + advance(); + left = t.nud(); + while (rbp < node.lbp) { + t = node; + advance(); + left = t.led(left); + } + return left; + }; + var infix = function (id, bp, led) { + var bindingPower = bp || operators[id]; + var s = symbol(id, bindingPower); + s.led = led || function (left) { + this.lhs = left; + this.rhs = expression(bindingPower); + this.type = "binary"; + return this; + }; + return s; + }; + var infixr = function (id, bp, led) { + var bindingPower = bp || operators[id]; + var s = symbol(id, bindingPower); + s.led = led || function (left) { + this.lhs = left; + this.rhs = expression(bindingPower - 1); // subtract 1 from bindingPower for right associative operators + this.type = "binary"; + return this; + }; + return s; + }; + var prefix = function (id, nud) { + var s = symbol(id); + s.nud = nud || function () { + this.expression = expression(70); + this.type = "unary"; + return this; + }; + return s; + }; + + symbol("(end)"); + symbol("(name)"); + symbol("(literal)"); + symbol(":"); + symbol(";"); + symbol(","); + symbol(")"); + symbol("]"); + symbol("}"); + symbol(".."); // range operator + infix("."); // field reference + infix("+"); // numeric addition + infix("-"); // numeric subtraction + infix("*"); // numeric multiplication + infix("/"); // numeric division + infix("%"); // numeric modulus + infix("="); // equality + infix("<"); // less than + infix(">"); // greater than + infix("!="); // not equal to + infix("<="); // less than or equal + infix(">="); // greater than or equal + infix("&"); // string concatenation + infix("and"); // Boolean AND + infix("or"); // Boolean OR + infix("in"); // is member of array + infixr(":="); // bind variable + prefix("-"); // unary numeric negation + prefix('*', function () { + this.type = "wildcard"; + return this; + }); + prefix('**', function () { + this.type = "descendant"; + return this; + }); + infix("(", operators['('], function (left) { + this.procedure = left; + this.type = 'function'; + this.arguments = []; + if (node.id !== ')') { + for (;;) { + if (node.type === 'operator' && node.id === '?') { + this.type = 'partial'; + this.arguments.push(node); + advance('?'); + } else { + this.arguments.push(expression(0)); + } + if (node.id !== ',') break; + advance(','); + } + } + advance(")"); + if (left.type === 'name' && (left.value === 'function' || left.value === '\u03BB')) { + this.arguments.forEach(function (arg, index) { + if (arg.type !== 'variable') { + throw { + message: 'Parameter ' + (index + 1) + ' of function definition must be a variable name (start with $)', + stack: (new Error()).stack, + position: arg.position, + token: arg.value + }; + } + }); + this.type = 'lambda'; + advance('{'); + this.body = expression(0); + advance('}'); + } + return this; + }); + prefix("(", function () { + var expressions = []; + while (node.id !== ")") { + expressions.push(expression(0)); + if (node.id !== ";") { + break; + } + advance(";"); + } + advance(")"); + this.type = 'block'; + this.expressions = expressions; + return this; + }); + prefix("{", function () { + var a = []; + if (node.id !== "}") { + for (;;) { + var n = expression(0); + advance(":"); + var v = expression(0); + a.push([n, v]); // holds an array of name/value expression pairs + if (node.id !== ",") { + break; + } + advance(","); + } + } + advance("}"); + this.lhs = a; + this.type = "unary"; + return this; + }); + prefix("[", function () { + var a = []; + if (node.id !== "]") { + for (;;) { + var item = expression(0); + if (node.id === "..") { + var range = {type: "binary", value: "..", position: node.position, lhs: item}; + advance(".."); + range.rhs = expression(0); + item = range; + } + a.push(item); + if (node.id !== ",") { + break; + } + advance(","); + } + } + advance("]"); + this.lhs = a; + this.type = "unary"; + return this; + }); + infix("[", operators['['], function (left) { + this.lhs = left; + this.rhs = expression(operators[']']); + this.type = 'binary'; + advance("]"); + return this; + }); + infix("{", operators['{'], function (left) { + this.lhs = left; + this.rhs = expression(operators['}']); + this.type = 'binary'; + advance("}"); + return this; + }); + infix("?", operators['?'], function (left) { + this.type = 'condition'; + this.condition = left; + this.then = expression(0); + if (node.id === ':') { + advance(":"); + this.else = expression(0); + } + return this; + }); + var tail_call_optimize = function(expr) { + var result; + if(expr.type === 'function') { + var thunk = {type: 'lambda', thunk: true, arguments: [], position: expr.position}; + thunk.body = expr; + result = thunk; + } else if(expr.type === 'condition') { + expr.then = tail_call_optimize(expr.then); + expr.else = tail_call_optimize(expr.else); + result = expr; + } else if(expr.type === 'block') { + var length = expr.expressions.length; + if(length > 0) { + expr.expressions[length - 1] = tail_call_optimize(expr.expressions[length - 1]); + } + result = expr; + } else { + result = expr; + } + return result; + }; + var post_parse = function (expr) { + var result = []; + switch (expr.type) { + case 'binary': + switch (expr.value) { + case '.': + var step = post_parse(expr.lhs); + if (Array.isArray(step)) { + Array.prototype.push.apply(result, step); + } else { + result.push(step); + } + var rest = [post_parse(expr.rhs)]; + Array.prototype.push.apply(result, rest); + result.type = 'path'; + break; + case '[': + result = post_parse(expr.lhs); + if (typeof result.aggregate !== 'undefined') { + throw { + message: 'A predicate cannot follow an aggregate in a step. Error at column: ' + expr.position, + stack: (new Error()).stack, + position: expr.position + }; + } + if (typeof result.predicate === 'undefined') { + result.predicate = []; + } + result.predicate.push(post_parse(expr.rhs)); + break; + case '{': + result = post_parse(expr.lhs); + if (typeof result.aggregate !== 'undefined') { + throw { + message: 'Each step can only have one aggregator. Error at column: ' + expr.position, + stack: (new Error()).stack, + position: expr.position + }; + } + result.aggregate = post_parse(expr.rhs); + break; + default: + result = {type: expr.type, value: expr.value, position: expr.position}; + result.lhs = post_parse(expr.lhs); + result.rhs = post_parse(expr.rhs); + } + break; + case 'unary': + result = {type: expr.type, value: expr.value, position: expr.position}; + if (expr.value === '[') { + result.lhs = expr.lhs.map(function (item) { + return post_parse(item); + }); + } else if (expr.value === '{') { + result.lhs = expr.lhs.map(function (pair) { + return [post_parse(pair[0]), post_parse(pair[1])]; + }); + } else { + result.expression = post_parse(expr.expression); + if (expr.value === '-' && result.expression.type === 'literal' && isNumeric(result.expression.value)) { + result = result.expression; + result.value = -result.value; + } + } + break; + case 'function': + case 'partial': + result = {type: expr.type, name: expr.name, value: expr.value, position: expr.position}; + result.arguments = expr.arguments.map(function (arg) { + return post_parse(arg); + }); + result.procedure = post_parse(expr.procedure); + break; + case 'lambda': + result = {type: expr.type, arguments: expr.arguments, position: expr.position}; + var body = post_parse(expr.body); + result.body = tail_call_optimize(body); + break; + case 'condition': + result = {type: expr.type, position: expr.position}; + result.condition = post_parse(expr.condition); + result.then = post_parse(expr.then); + if (typeof expr.else !== 'undefined') { + result.else = post_parse(expr.else); + } + break; + case 'block': + result = {type: expr.type, position: expr.position}; + result.expressions = expr.expressions.map(function (item) { + return post_parse(item); + }); + break; + case 'name': + case 'literal': + case 'wildcard': + case 'descendant': + case 'variable': + result = expr; + break; + case 'operator': + if (expr.value === 'and' || expr.value === 'or' || expr.value === 'in') { + expr.type = 'name'; + result = post_parse(expr); + } else if (expr.value === '?') { + result = expr; + } else { + throw { + message: "Syntax error: " + expr.value + " at column " + expr.position, + stack: (new Error()).stack, + position: expr.position, + token: expr.value + }; + } + break; + default: + var reason = "Unknown expression type: " + expr.value + " at column " + expr.position; + if (expr.id === '(end)') { + reason = "Syntax error: unexpected end of expression"; + } + throw { + message: reason, + stack: (new Error()).stack, + position: expr.position, + token: expr.value + }; + } + return result; + }; + + lexer = tokenizer(source); + advance(); + var expr = expression(0); + if (node.id !== '(end)') { + throw { + message: "Syntax error: " + node.value + " at column " + node.position, + stack: (new Error()).stack, + position: node.position, + token: node.value + }; + } + expr = post_parse(expr); + if (expr.type === 'name') { + expr = [expr]; + expr.type = 'path'; + } + + return expr; + }; + function isNumeric(n) { + var isNum = false; + if(typeof n === 'number') { + var num = parseFloat(n); + isNum = !isNaN(num); + if (isNum && !isFinite(num)) { + throw { + message: "Number out of range", + value: n, + stack: (new Error()).stack + }; + } + } + return isNum; + } + function isArrayOfNumbers(arg) { + var result = false; + if(Array.isArray(arg)) { + result = (arg.filter(function(item){return !isNumeric(item);}).length == 0); + } + return result; + } + Number.isInteger = Number.isInteger || function(value) { + return typeof value === "number" && + isFinite(value) && + Math.floor(value) === value; + }; + function evaluate(expr, input, environment) { + var result; + + var entryCallback = environment.lookup('__evaluate_entry'); + if(entryCallback) { + entryCallback(expr, input, environment); + } + + switch (expr.type) { + case 'path': + result = evaluatePath(expr, input, environment); + break; + case 'binary': + result = evaluateBinary(expr, input, environment); + break; + case 'unary': + result = evaluateUnary(expr, input, environment); + break; + case 'name': + result = evaluateName(expr, input, environment); + break; + case 'literal': + result = evaluateLiteral(expr, input, environment); + break; + case 'wildcard': + result = evaluateWildcard(expr, input, environment); + break; + case 'descendant': + result = evaluateDescendants(expr, input, environment); + break; + case 'condition': + result = evaluateCondition(expr, input, environment); + break; + case 'block': + result = evaluateBlock(expr, input, environment); + break; + case 'function': + result = evaluateFunction(expr, input, environment); + break; + case 'variable': + result = evaluateVariable(expr, input, environment); + break; + case 'lambda': + result = evaluateLambda(expr, input, environment); + break; + case 'partial': + result = evaluatePartialApplication(expr, input, environment); + break; + } + if (expr.hasOwnProperty('predicate')) { + result = applyPredicates(expr.predicate, result, environment); + } + if (expr.hasOwnProperty('aggregate')) { + result = applyAggregate(expr.aggregate, result, environment); + } + + var exitCallback = environment.lookup('__evaluate_exit'); + if(exitCallback) { + exitCallback(expr, input, environment, result); + } + + return result; + } + function evaluatePath(expr, input, environment) { + var result; + var inputSequence; + if (expr[0].type === 'variable') { + expr[0].absolute = true; + } else if(expr[0].type === 'unary' && expr[0].value === '[') { + input = [null];// dummy singleton sequence for first step + } + expr.forEach(function (step) { + var resultSequence = []; + result = undefined; + if (step.absolute === true) { + inputSequence = [input]; // dummy singleton sequence for first (absolute) step + } else if (Array.isArray(input)) { + inputSequence = input; + } else { + inputSequence = [input]; + } + if (expr.length > 1 && step.type === 'literal') { + step.type = 'name'; + } + if (step.value === '{') { + if(typeof input !== 'undefined') { + result = evaluateGroupExpression(step, inputSequence, environment); + } + } else { + inputSequence.forEach(function (item) { + var res = evaluate(step, item, environment); + if (typeof res !== 'undefined') { + if (Array.isArray(res)) { + res.forEach(function (innerRes) { + if (typeof innerRes !== 'undefined') { + resultSequence.push(innerRes); + } + }); + } else { + resultSequence.push(res); + } + } + }); + if (resultSequence.length == 1) { + result = resultSequence[0]; + } else if (resultSequence.length > 1) { + result = resultSequence; + } + } + + input = result; + }); + return result; + } + function applyPredicates(predicates, input, environment) { + var result; + var inputSequence = input; + + var results = []; + predicates.forEach(function (predicate) { + if (!Array.isArray(inputSequence)) { + inputSequence = [inputSequence]; + } + results = []; + result = undefined; + if (predicate.type === 'literal' && isNumeric(predicate.value)) { + var index = predicate.value; + if (!Number.isInteger(index)) { + index = Math.floor(index); + } + if (index < 0) { + index = inputSequence.length + index; + } + result = inputSequence[index]; + } else { + inputSequence.forEach(function (item, index) { + var res = evaluate(predicate, item, environment); + if (isNumeric(res)) { + res = [res]; + } + if(isArrayOfNumbers(res)) { + res.forEach(function(ires) { + if (!Number.isInteger(ires)) { + ires = Math.floor(ires); + } + if (ires < 0) { + ires = inputSequence.length + ires; + } + if (ires == index) { + results.push(item); + } + }); + } else if (functionBoolean(res)) { // truthy + results.push(item); + } + }); + } + if (results.length == 1) { + result = results[0]; + } else if (results.length > 1) { + result = results; + } + inputSequence = result; + }); + return result; + } + function applyAggregate(expr, input, environment) { + var result = {}; + if (Array.isArray(input)) { + var aggEnv = createFrame(environment); + aggEnv.bind('_', input[0]); + for (var index = 1; index < input.length; index++) { + var reduce = evaluate(expr, input[index], aggEnv); + aggEnv.bind('_', reduce); + } + result = aggEnv.lookup('_'); + } else { + result = input; + } + return result; + } + function evaluateBinary(expr, input, environment) { + var result; + + switch (expr.value) { + case '+': + case '-': + case '*': + case '/': + case '%': + result = evaluateNumericExpression(expr, input, environment); + break; + case '=': + case '!=': + case '<': + case '<=': + case '>': + case '>=': + result = evaluateComparisonExpression(expr, input, environment); + break; + case '&': + result = evaluateStringConcat(expr, input, environment); + break; + case 'and': + case 'or': + result = evaluateBooleanExpression(expr, input, environment); + break; + case '..': + result = evaluateRangeExpression(expr, input, environment); + break; + case ':=': + result = evaluateBindExpression(expr, input, environment); + break; + case 'in': + result = evaluateIncludesExpression(expr, input, environment); + break; + } + return result; + } + function evaluateUnary(expr, input, environment) { + var result; + + switch (expr.value) { + case '-': + result = evaluate(expr.expression, input, environment); + if (isNumeric(result)) { + result = -result; + } else { + throw { + message: "Cannot negate a non-numeric value: " + result + " at column " + expr.position, + stack: (new Error()).stack, + position: expr.position, + token: expr.value, + value: result + }; + } + break; + case '[': + result = []; + expr.lhs.forEach(function (item) { + var value = evaluate(item, input, environment); + if (typeof value !== 'undefined') { + if (item.value === '..') { + result = functionAppend(result, value); + } else { + result.push(value); + } + } + }); + break; + case '{': + result = evaluateGroupExpression(expr, input, environment); + break; + + } + return result; + } + function evaluateName(expr, input, environment) { + var result; + if (Array.isArray(input)) { + var results = []; + input.forEach(function (item) { + var res = evaluateName(expr, item, environment); + if (typeof res !== 'undefined') { + results.push(res); + } + }); + if (results.length == 1) { + result = results[0]; + } else if (results.length > 1) { + result = results; + } + } else if (input !== null && typeof input === 'object') { + result = input[expr.value]; + } + return result; + } + function evaluateLiteral(expr) { + return expr.value; + } + function evaluateWildcard(expr, input) { + var result; + var results = []; + if (input !== null && typeof input === 'object') { + Object.keys(input).forEach(function (key) { + var value = input[key]; + if(Array.isArray(value)) { + value = flatten(value); + results = functionAppend(results, value); + } else { + results.push(value); + } + }); + } + + if (results.length == 1) { + result = results[0]; + } else if (results.length > 1) { + result = results; + } + return result; + } + function flatten(arg, flattened) { + if(typeof flattened === 'undefined') { + flattened = []; + } + if(Array.isArray(arg)) { + arg.forEach(function (item) { + flatten(item, flattened); + }); + } else { + flattened.push(arg); + } + return flattened; + } + function evaluateDescendants(expr, input) { + var result; + var resultSequence = []; + if (typeof input !== 'undefined') { + recurseDescendants(input, resultSequence); + if (resultSequence.length == 1) { + result = resultSequence[0]; + } else { + result = resultSequence; + } + } + return result; + } + function recurseDescendants(input, results) { + if (!Array.isArray(input)) { + results.push(input); + } + if (Array.isArray(input)) { + input.forEach(function (member) { + recurseDescendants(member, results); + }); + } else if (input !== null && typeof input === 'object') { + Object.keys(input).forEach(function (key) { + recurseDescendants(input[key], results); + }); + } + } + function evaluateNumericExpression(expr, input, environment) { + var result; + + var lhs = evaluate(expr.lhs, input, environment); + var rhs = evaluate(expr.rhs, input, environment); + + if (typeof lhs === 'undefined' || typeof rhs === 'undefined') { + return result; + } + + if (!isNumeric(lhs)) { + throw { + message: 'LHS of ' + expr.value + ' operator must evaluate to a number at column ' + expr.position, + stack: (new Error()).stack, + position: expr.position, + token: expr.value, + value: lhs + }; + } + if (!isNumeric(rhs)) { + throw { + message: 'RHS of ' + expr.value + ' operator must evaluate to a number at column ' + expr.position, + stack: (new Error()).stack, + position: expr.position, + token: expr.value, + value: rhs + }; + } + + switch (expr.value) { + case '+': + result = lhs + rhs; + break; + case '-': + result = lhs - rhs; + break; + case '*': + result = lhs * rhs; + break; + case '/': + result = lhs / rhs; + break; + case '%': + result = lhs % rhs; + break; + } + return result; + } + function evaluateComparisonExpression(expr, input, environment) { + var result; + + var lhs = evaluate(expr.lhs, input, environment); + var rhs = evaluate(expr.rhs, input, environment); + + if (typeof lhs === 'undefined' || typeof rhs === 'undefined') { + return false; + } + + switch (expr.value) { + case '=': + result = lhs == rhs; + break; + case '!=': + result = (lhs != rhs); + break; + case '<': + result = lhs < rhs; + break; + case '<=': + result = lhs <= rhs; + break; + case '>': + result = lhs > rhs; + break; + case '>=': + result = lhs >= rhs; + break; + } + return result; + } + function evaluateIncludesExpression(expr, input, environment) { + var result = false; + + var lhs = evaluate(expr.lhs, input, environment); + var rhs = evaluate(expr.rhs, input, environment); + + if (typeof lhs === 'undefined' || typeof rhs === 'undefined') { + return false; + } + + if(!Array.isArray(rhs)) { + rhs = [rhs]; + } + + for(var i = 0; i < rhs.length; i++) { + if(rhs[i] === lhs) { + result = true; + break; + } + } + + return result; + } + function evaluateBooleanExpression(expr, input, environment) { + var result; + + switch (expr.value) { + case 'and': + result = functionBoolean(evaluate(expr.lhs, input, environment)) && + functionBoolean(evaluate(expr.rhs, input, environment)); + break; + case 'or': + result = functionBoolean(evaluate(expr.lhs, input, environment)) || + functionBoolean(evaluate(expr.rhs, input, environment)); + break; + } + return result; + } + function evaluateStringConcat(expr, input, environment) { + var result; + var lhs = evaluate(expr.lhs, input, environment); + var rhs = evaluate(expr.rhs, input, environment); + + var lstr = ''; + var rstr = ''; + if (typeof lhs !== 'undefined') { + lstr = functionString(lhs); + } + if (typeof rhs !== 'undefined') { + rstr = functionString(rhs); + } + + result = lstr.concat(rstr); + return result; + } + function evaluateGroupExpression(expr, input, environment) { + var result = {}; + var groups = {}; + if (!Array.isArray(input)) { + input = [input]; + } + input.forEach(function (item) { + expr.lhs.forEach(function (pair) { + var key = evaluate(pair[0], item, environment); + if (typeof key !== 'string') { + throw { + message: 'Key in object structure must evaluate to a string. Got: ' + key + ' at column ' + expr.position, + stack: (new Error()).stack, + position: expr.position, + value: key + }; + } + var entry = {data: item, expr: pair[1]}; + if (groups.hasOwnProperty(key)) { + groups[key].data = functionAppend(groups[key].data, item); + } else { + groups[key] = entry; + } + }); + }); + for (var key in groups) { + var entry = groups[key]; + var value = evaluate(entry.expr, entry.data, environment); + result[key] = value; + } + return result; + } + function evaluateRangeExpression(expr, input, environment) { + var result; + + var lhs = evaluate(expr.lhs, input, environment); + var rhs = evaluate(expr.rhs, input, environment); + + if (typeof lhs === 'undefined' || typeof rhs === 'undefined') { + return result; + } + + if (lhs > rhs) { + return result; + } + + if (!Number.isInteger(lhs)) { + throw { + message: 'LHS of range operator (..) must evaluate to an integer at column ' + expr.position, + stack: (new Error()).stack, + position: expr.position, + token: expr.value, + value: lhs + }; + } + if (!Number.isInteger(rhs)) { + throw { + message: 'RHS of range operator (..) must evaluate to an integer at column ' + expr.position, + stack: (new Error()).stack, + position: expr.position, + token: expr.value, + value: rhs + }; + } + + result = new Array(rhs - lhs + 1); + for (var item = lhs, index = 0; item <= rhs; item++, index++) { + result[index] = item; + } + return result; + } + function evaluateBindExpression(expr, input, environment) { + var value = evaluate(expr.rhs, input, environment); + if (expr.lhs.type !== 'variable') { + throw { + message: "Left hand side of := must be a variable name (start with $) at column " + expr.position, + stack: (new Error()).stack, + position: expr.position, + token: expr.value, + value: expr.lhs.value + }; + } + environment.bind(expr.lhs.value, value); + return value; + } + function evaluateCondition(expr, input, environment) { + var result; + var condition = evaluate(expr.condition, input, environment); + if (functionBoolean(condition)) { + result = evaluate(expr.then, input, environment); + } else if (typeof expr.else !== 'undefined') { + result = evaluate(expr.else, input, environment); + } + return result; + } + function evaluateBlock(expr, input, environment) { + var result; + var frame = createFrame(environment); + expr.expressions.forEach(function (expression) { + result = evaluate(expression, input, frame); + }); + + return result; + } + function evaluateVariable(expr, input, environment) { + var result; + if (expr.value === '') { + result = input; + } else { + result = environment.lookup(expr.value); + } + return result; + } + function evaluateFunction(expr, input, environment) { + var result; + var evaluatedArgs = []; + expr.arguments.forEach(function (arg) { + evaluatedArgs.push(evaluate(arg, input, environment)); + }); + var proc = evaluate(expr.procedure, input, environment); + + if (typeof proc === 'undefined' && expr.procedure.type === 'name' && environment.lookup(expr.procedure.value)) { + throw { + message: 'Attempted to invoke a non-function at column ' + expr.position + '. Did you mean \'$' + expr.procedure.value + '\'?', + stack: (new Error()).stack, + position: expr.position, + token: expr.procedure.value + }; + } + try { + result = apply(proc, evaluatedArgs, environment, input); + while(typeof result === 'object' && result.lambda == true && result.thunk == true) { + var next = evaluate(result.body.procedure, result.input, result.environment); + evaluatedArgs = []; + result.body.arguments.forEach(function (arg) { + evaluatedArgs.push(evaluate(arg, result.input, result.environment)); + }); + + result = apply(next, evaluatedArgs); + } + } catch (err) { + err.position = expr.position; + err.token = expr.procedure.value; + throw err; + } + return result; + } + function apply(proc, args, environment, self) { + var result; + if (proc && proc.lambda) { + result = applyProcedure(proc, args); + } else if (typeof proc === 'function') { + result = proc.apply(self, args); + } else { + throw { + message: 'Attempted to invoke a non-function', + stack: (new Error()).stack + }; + } + return result; + } + function evaluateLambda(expr, input, environment) { + var procedure = { + lambda: true, + input: input, + environment: environment, + arguments: expr.arguments, + body: expr.body + }; + if(expr.thunk == true) { + procedure.thunk = true; + } + return procedure; + } + function evaluatePartialApplication(expr, input, environment) { + var result; + var evaluatedArgs = []; + expr.arguments.forEach(function (arg) { + if (arg.type === 'operator' && arg.value === '?') { + evaluatedArgs.push(arg); + } else { + evaluatedArgs.push(evaluate(arg, input, environment)); + } + }); + var proc = evaluate(expr.procedure, input, environment); + if (typeof proc === 'undefined' && expr.procedure.type === 'name' && environment.lookup(expr.procedure.value)) { + throw { + message: 'Attempted to partially apply a non-function at column ' + expr.position + '. Did you mean \'$' + expr.procedure.value + '\'?', + stack: (new Error()).stack, + position: expr.position, + token: expr.procedure.value + }; + } + if (proc && proc.lambda) { + result = partialApplyProcedure(proc, evaluatedArgs); + } else if (typeof proc === 'function') { + result = partialApplyNativeFunction(proc, evaluatedArgs); + } else { + throw { + message: 'Attempted to partially apply a non-function at column ' + expr.position, + stack: (new Error()).stack, + position: expr.position, + token: expr.procedure.value + }; + } + return result; + } + function applyProcedure(proc, args) { + var result; + var env = createFrame(proc.environment); + proc.arguments.forEach(function (param, index) { + env.bind(param.value, args[index]); + }); + if (typeof proc.body === 'function') { + result = applyNativeFunction(proc.body, env); + } else { + result = evaluate(proc.body, proc.input, env); + } + return result; + } + function partialApplyProcedure(proc, args) { + var env = createFrame(proc.environment); + var unboundArgs = []; + proc.arguments.forEach(function (param, index) { + var arg = args[index]; + if (arg && arg.type === 'operator' && arg.value === '?') { + unboundArgs.push(param); + } else { + env.bind(param.value, arg); + } + }); + var procedure = { + lambda: true, + input: proc.input, + environment: env, + arguments: unboundArgs, + body: proc.body + }; + return procedure; + } + function partialApplyNativeFunction(native, args) { + var sigArgs = getNativeFunctionArguments(native); + sigArgs = sigArgs.map(function (sigArg) { + return '$' + sigArg.trim(); + }); + var body = 'function(' + sigArgs.join(', ') + '){ _ }'; + + var bodyAST = parser(body); + bodyAST.body = native; + + var partial = partialApplyProcedure(bodyAST, args); + return partial; + } + function applyNativeFunction(proc, env) { + var sigArgs = getNativeFunctionArguments(proc); + var args = sigArgs.map(function (sigArg) { + return env.lookup(sigArg.trim()); + }); + + var result = proc.apply(null, args); + return result; + } + function getNativeFunctionArguments(func) { + var signature = func.toString(); + var sigParens = /\(([^\)]*)\)/.exec(signature)[1]; // the contents of the parens + var sigArgs = sigParens.split(','); + return sigArgs; + } + function isLambda(arg) { + var result = false; + if(arg && typeof arg === 'object' && + arg.lambda === true && + arg.hasOwnProperty('input') && + arg.hasOwnProperty('arguments') && + arg.hasOwnProperty('environment') && + arg.hasOwnProperty('body')) { + result = true; + } + + return result; + } + function functionSum(args) { + var total = 0; + + if (arguments.length != 1) { + throw { + message: 'The sum function expects one argument', + stack: (new Error()).stack + }; + } + if(typeof args === 'undefined') { + return undefined; + } + + if(!Array.isArray(args)) { + args = [args]; + } + var nonNumerics = args.filter(function(val) {return (typeof val !== 'number');}); + if(nonNumerics.length > 0) { + throw { + message: 'Type error: argument of sum function must be an array of numbers', + stack: (new Error()).stack, + value: nonNumerics + }; + } + args.forEach(function(num){total += num;}); + return total; + } + function functionCount(args) { + if (arguments.length != 1) { + throw { + message: 'The count function expects one argument', + stack: (new Error()).stack + }; + } + if(typeof args === 'undefined') { + return 0; + } + + if(!Array.isArray(args)) { + args = [args]; + } + + return args.length; + } + function functionMax(args) { + var max; + + if (arguments.length != 1) { + throw { + message: 'The max function expects one argument', + stack: (new Error()).stack + }; + } + if(typeof args === 'undefined') { + return undefined; + } + + if(!Array.isArray(args)) { + args = [args]; + } + var nonNumerics = args.filter(function(val) {return (typeof val !== 'number');}); + if(nonNumerics.length > 0) { + throw { + message: 'Type error: argument of max function must be an array of numbers', + stack: (new Error()).stack, + value: nonNumerics + }; + } + max = Math.max.apply(Math, args); + return max; + } + function functionMin(args) { + var min; + + if (arguments.length != 1) { + throw { + message: 'The min function expects one argument', + stack: (new Error()).stack + }; + } + if(typeof args === 'undefined') { + return undefined; + } + + if(!Array.isArray(args)) { + args = [args]; + } + var nonNumerics = args.filter(function(val) {return (typeof val !== 'number');}); + if(nonNumerics.length > 0) { + throw { + message: 'Type error: argument of min function must be an array of numbers', + stack: (new Error()).stack, + value: nonNumerics + }; + } + min = Math.min.apply(Math, args); + return min; + } + function functionAverage(args) { + var total = 0; + + if (arguments.length != 1) { + throw { + message: 'The average function expects one argument', + stack: (new Error()).stack + }; + } + if(typeof args === 'undefined') { + return undefined; + } + + if(!Array.isArray(args)) { + args = [args]; + } + var nonNumerics = args.filter(function(val) {return (typeof val !== 'number');}); + if(nonNumerics.length > 0) { + throw { + message: 'Type error: argument of average function must be an array of numbers', + stack: (new Error()).stack, + value: nonNumerics + }; + } + args.forEach(function(num){total += num;}); + return total/args.length; + } + function functionString(arg) { + var str; + + if(arguments.length != 1) { + throw { + message: 'The string function expects one argument', + stack: (new Error()).stack + }; + } + if(typeof arg === 'undefined') { + return undefined; + } + + if (typeof arg === 'string') { + str = arg; + } else if(typeof arg === 'function' || isLambda(arg)) { + str = ''; + } else if (typeof arg === 'number' && !isFinite(arg)) { + throw { + message: "Attempting to invoke string function on Infinity or NaN", + value: arg, + stack: (new Error()).stack + }; + } else + str = JSON.stringify(arg, function (key, val) { + return (typeof val !== 'undefined' && val !== null && val.toPrecision && isNumeric(val)) ? Number(val.toPrecision(13)) : + (val && isLambda(val)) ? '' : + (typeof val === 'function') ? '' : val; + }); + return str; + } + function functionSubstring(str, start, length) { + if(arguments.length != 2 && arguments.length != 3) { + throw { + message: 'The substring function expects two or three arguments', + stack: (new Error()).stack + }; + } + if(typeof str === 'undefined') { + return undefined; + } + if(typeof str !== 'string') { + throw { + message: 'Type error: first argument of substring function must evaluate to a string', + stack: (new Error()).stack, + value: str + }; + } + + if(typeof start !== 'number') { + throw { + message: 'Type error: second argument of substring function must evaluate to a number', + stack: (new Error()).stack, + value: start + }; + } + + if(typeof length !== 'undefined' && typeof length !== 'number') { + throw { + message: 'Type error: third argument of substring function must evaluate to a number', + stack: (new Error()).stack, + value: length + }; + } + + return str.substr(start, length); + } + function functionSubstringBefore(str, chars) { + if(arguments.length != 2) { + throw { + message: 'The substringBefore function expects two arguments', + stack: (new Error()).stack + }; + } + if(typeof str === 'undefined') { + return undefined; + } + if(typeof str !== 'string') { + throw { + message: 'Type error: first argument of substringBefore function must evaluate to a string', + stack: (new Error()).stack, + value: str + }; + } + if(typeof chars !== 'string') { + throw { + message: 'Type error: second argument of substringBefore function must evaluate to a string', + stack: (new Error()).stack, + value: chars + }; + } + + var pos = str.indexOf(chars); + if (pos > -1) { + return str.substr(0, pos); + } else { + return str; + } + } + function functionSubstringAfter(str, chars) { + if(arguments.length != 2) { + throw { + message: 'The substringAfter function expects two arguments', + stack: (new Error()).stack + }; + } + if(typeof str === 'undefined') { + return undefined; + } + if(typeof str !== 'string') { + throw { + message: 'Type error: first argument of substringAfter function must evaluate to a string', + stack: (new Error()).stack, + value: str + }; + } + if(typeof chars !== 'string') { + throw { + message: 'Type error: second argument of substringAfter function must evaluate to a string', + stack: (new Error()).stack, + value: chars + }; + } + + var pos = str.indexOf(chars); + if (pos > -1) { + return str.substr(pos + chars.length); + } else { + return str; + } + } + function functionLowercase(str) { + if(arguments.length != 1) { + throw { + message: 'The lowercase function expects one argument', + stack: (new Error()).stack + }; + } + if(typeof str === 'undefined') { + return undefined; + } + if(typeof str !== 'string') { + throw { + message: 'Type error: argument of lowercase function must evaluate to a string', + stack: (new Error()).stack, + value: str + }; + } + + return str.toLowerCase(); + } + function functionUppercase(str) { + if(arguments.length != 1) { + throw { + message: 'The uppercase function expects one argument', + stack: (new Error()).stack + }; + } + if(typeof str === 'undefined') { + return undefined; + } + if(typeof str !== 'string') { + throw { + message: 'Type error: argument of uppercase function must evaluate to a string', + stack: (new Error()).stack, + value: str + }; + } + + return str.toUpperCase(); + } + function functionLength(str) { + if(arguments.length != 1) { + throw { + message: 'The length function expects one argument', + stack: (new Error()).stack + }; + } + if(typeof str === 'undefined') { + return undefined; + } + if(typeof str !== 'string') { + throw { + message: 'Type error: argument of length function must evaluate to a string', + stack: (new Error()).stack, + value: str + }; + } + + return str.length; + } + function functionSplit(str, separator, limit) { + if(arguments.length != 2 && arguments.length != 3) { + throw { + message: 'The split function expects two or three arguments', + stack: (new Error()).stack + }; + } + if(typeof str === 'undefined') { + return undefined; + } + if(typeof str !== 'string') { + throw { + message: 'Type error: first argument of split function must evaluate to a string', + stack: (new Error()).stack, + value: str + }; + } + if(typeof separator !== 'string') { + throw { + message: 'Type error: second argument of split function must evaluate to a string', + stack: (new Error()).stack, + value: separator + }; + } + if(typeof limit !== 'undefined' && (typeof limit !== 'number' || limit < 0)) { + throw { + message: 'Type error: third argument of split function must evaluate to a positive number', + stack: (new Error()).stack, + value: limit + }; + } + + return str.split(separator, limit); + } + function functionJoin(strs, separator) { + if(arguments.length != 1 && arguments.length != 2) { + throw { + message: 'The join function expects one or two arguments', + stack: (new Error()).stack + }; + } + if(typeof strs === 'undefined') { + return undefined; + } + + if(!Array.isArray(strs)) { + strs = [strs]; + } + var nonStrings = strs.filter(function(val) {return (typeof val !== 'string');}); + if(nonStrings.length > 0) { + throw { + message: 'Type error: first argument of join function must be an array of strings', + stack: (new Error()).stack, + value: nonStrings + }; + } + if(typeof separator === 'undefined') { + separator = ""; + } + if(typeof separator !== 'string') { + throw { + message: 'Type error: second argument of split function must evaluate to a string', + stack: (new Error()).stack, + value: separator + }; + } + + return strs.join(separator); + } + function functionNumber(arg) { + var result; + + if(arguments.length != 1) { + throw { + message: 'The number function expects one argument', + stack: (new Error()).stack + }; + } + if(typeof arg === 'undefined') { + return undefined; + } + + if (typeof arg === 'number') { + result = arg; + } else if(typeof arg === 'string' && /^-?(0|([1-9][0-9]*))(\.[0-9]+)?([Ee][-+]?[0-9]+)?$/.test(arg) && !isNaN(parseFloat(arg)) && isFinite(arg)) { + result = parseFloat(arg); + } else { + throw { + message: "Unable to cast value to a number", + value: arg, + stack: (new Error()).stack + }; + } + return result; + } + function functionBoolean(arg) { + + if(arguments.length != 1) { + throw { + message: 'The boolean function expects one argument', + stack: (new Error()).stack + }; + } + if(typeof arg === 'undefined') { + return undefined; + } + + var result = false; + if (Array.isArray(arg)) { + if (arg.length == 1) { + result = functionBoolean(arg[0]); + } else if (arg.length > 1) { + var trues = arg.filter(function(val) {return functionBoolean(val);}); + result = trues.length > 0; + } + } else if (typeof arg === 'string') { + if (arg.length > 0) { + result = true; + } + } else if (isNumeric(arg)) { + if (arg != 0) { + result = true; + } + } else if (arg != null && typeof arg === 'object') { + if (Object.keys(arg).length > 0) { + if (!(isLambda(arg))) { + result = true; + } + } + } else if (typeof arg === 'boolean' && arg == true) { + result = true; + } + return result; + } + function functionNot(arg) { + return !functionBoolean(arg); + } + function functionMap(func) { + var varargs = arguments; + var result = []; + var args = []; + for (var ii = 1; ii < varargs.length; ii++) { + if (Array.isArray(varargs[ii])) { + args.push(varargs[ii]); + } else { + args.push([varargs[ii]]); + } + + } + if (args.length > 0) { + for (var i = 0; i < args[0].length; i++) { + var func_args = []; + for (var j = 0; j < func.arguments.length; j++) { + func_args.push(args[j][i]); + } + result.push(apply(func, func_args, null, null)); + } + } + return result; + } + function functionFoldLeft(func, sequence, init) { + var result; + + if (!(func.length == 2 || func.arguments.length == 2)) { + throw { + message: 'The first argument of the reduce function must be a function of arity 2', + stack: (new Error()).stack + }; + } + + if (!Array.isArray(sequence)) { + sequence = [sequence]; + } + + var index; + if (typeof init === 'undefined' && sequence.length > 0) { + result = sequence[0]; + index = 1; + } else { + result = init; + index = 0; + } + + while (index < sequence.length) { + result = apply(func, [result, sequence[index]], null, null); + index++; + } + + return result; + } + function functionKeys(arg) { + var result = []; + + if(Array.isArray(arg)) { + var merge = {}; + arg.forEach(function(item) { + var keys = functionKeys(item); + if(Array.isArray(keys)) { + keys.forEach(function(key) { + merge[key] = true; + }); + } + }); + result = functionKeys(merge); + } else if(arg != null && typeof arg === 'object' && !(isLambda(arg))) { + result = Object.keys(arg); + if(result.length == 0) { + result = undefined; + } + } else { + result = undefined; + } + return result; + } + function functionLookup(object, key) { + var result = evaluateName({value: key}, object); + return result; + } + function functionAppend(arg1, arg2) { + if (typeof arg1 === 'undefined') { + return arg2; + } + if (typeof arg2 === 'undefined') { + return arg1; + } + if (!Array.isArray(arg1)) { + arg1 = [arg1]; + } + if (!Array.isArray(arg2)) { + arg2 = [arg2]; + } + Array.prototype.push.apply(arg1, arg2); + return arg1; + } + function functionExists(arg){ + if (arguments.length != 1) { + throw { + message: 'The exists function expects one argument', + stack: (new Error()).stack + }; + } + + if (typeof arg === 'undefined') { + return false; + } else { + return true; + } + } + function functionSpread(arg) { + var result = []; + + if(Array.isArray(arg)) { + arg.forEach(function(item) { + result = functionAppend(result, functionSpread(item)); + }); + } else if(arg != null && typeof arg === 'object' && !isLambda(arg)) { + for(var key in arg) { + var obj = {}; + obj[key] = arg[key]; + result.push(obj); + } + } else { + result = arg; + } + return result; + } + function createFrame(enclosingEnvironment) { + var bindings = {}; + return { + bind: function (name, value) { + bindings[name] = value; + }, + lookup: function (name) { + var value = bindings[name]; + if (typeof value === 'undefined' && enclosingEnvironment) { + value = enclosingEnvironment.lookup(name); + } + return value; + } + }; + } + + var staticFrame = createFrame(null); + + staticFrame.bind('sum', functionSum); + staticFrame.bind('count', functionCount); + staticFrame.bind('max', functionMax); + staticFrame.bind('min', functionMin); + staticFrame.bind('average', functionAverage); + staticFrame.bind('string', functionString); + staticFrame.bind('substring', functionSubstring); + staticFrame.bind('substringBefore', functionSubstringBefore); + staticFrame.bind('substringAfter', functionSubstringAfter); + staticFrame.bind('lowercase', functionLowercase); + staticFrame.bind('uppercase', functionUppercase); + staticFrame.bind('length', functionLength); + staticFrame.bind('split', functionSplit); + staticFrame.bind('join', functionJoin); + staticFrame.bind('number', functionNumber); + staticFrame.bind('boolean', functionBoolean); + staticFrame.bind('not', functionNot); + staticFrame.bind('map', functionMap); + staticFrame.bind('reduce', functionFoldLeft); + staticFrame.bind('keys', functionKeys); + staticFrame.bind('lookup', functionLookup); + staticFrame.bind('append', functionAppend); + staticFrame.bind('exists', functionExists); + staticFrame.bind('spread', functionSpread); + + function jsonata(expr) { + var ast = parser(expr); + var environment = createFrame(staticFrame); + return { + evaluate: function (input, bindings) { + if (typeof bindings !== 'undefined') { + var exec_env; + exec_env = createFrame(environment); + for (var v in bindings) { + exec_env.bind(v, bindings[v]); + } + } else { + exec_env = environment; + } + exec_env.bind('$', input); + return evaluate(ast, input, exec_env); + }, + assign: function (name, value) { + environment.bind(name, value); + } + }; + } + + jsonata.parser = parser; + + return jsonata; + + })(); + + + + + var oop = require("../lib/oop"); + var Mirror = require("../worker/mirror").Mirror; + + var JSONataWorker = exports.JSONataWorker = function(sender) { + Mirror.call(this, sender); + this.setTimeout(200); + }; + + oop.inherits(JSONataWorker, Mirror); + + (function() { + + this.onUpdate = function() { + var value = this.doc.getValue(); + var errors = []; + try { + if (value) + jsonata(value); + } catch (e) { + var pos = this.doc.indexToPosition(e.position-1); + var msg = e.message; + msg = msg.replace(/ at column \d+/,""); + errors.push({ + row: pos.row, + column: pos.column, + text: msg, + type: "error" + }); + } + this.sender.emit("annotate", errors); + }; + + }).call(JSONataWorker.prototype); + +}); + +define("ace/lib/es5-shim",["require","exports","module"], function(require, exports, module) { + +function Empty() {} + +if (!Function.prototype.bind) { + Function.prototype.bind = function bind(that) { // .length is 1 + var target = this; + if (typeof target != "function") { + throw new TypeError("Function.prototype.bind called on incompatible " + target); + } + var args = slice.call(arguments, 1); // for normal call + var bound = function () { + + if (this instanceof bound) { + + var result = target.apply( + this, + args.concat(slice.call(arguments)) + ); + if (Object(result) === result) { + return result; + } + return this; + + } else { + return target.apply( + that, + args.concat(slice.call(arguments)) + ); + + } + + }; + if(target.prototype) { + Empty.prototype = target.prototype; + bound.prototype = new Empty(); + Empty.prototype = null; + } + return bound; + }; +} +var call = Function.prototype.call; +var prototypeOfArray = Array.prototype; +var prototypeOfObject = Object.prototype; +var slice = prototypeOfArray.slice; +var _toString = call.bind(prototypeOfObject.toString); +var owns = call.bind(prototypeOfObject.hasOwnProperty); +var defineGetter; +var defineSetter; +var lookupGetter; +var lookupSetter; +var supportsAccessors; +if ((supportsAccessors = owns(prototypeOfObject, "__defineGetter__"))) { + defineGetter = call.bind(prototypeOfObject.__defineGetter__); + defineSetter = call.bind(prototypeOfObject.__defineSetter__); + lookupGetter = call.bind(prototypeOfObject.__lookupGetter__); + lookupSetter = call.bind(prototypeOfObject.__lookupSetter__); +} +if ([1,2].splice(0).length != 2) { + if(function() { // test IE < 9 to splice bug - see issue #138 + function makeArray(l) { + var a = new Array(l+2); + a[0] = a[1] = 0; + return a; + } + var array = [], lengthBefore; + + array.splice.apply(array, makeArray(20)); + array.splice.apply(array, makeArray(26)); + + lengthBefore = array.length; //46 + array.splice(5, 0, "XXX"); // add one element + + lengthBefore + 1 == array.length + + if (lengthBefore + 1 == array.length) { + return true;// has right splice implementation without bugs + } + }()) {//IE 6/7 + var array_splice = Array.prototype.splice; + Array.prototype.splice = function(start, deleteCount) { + if (!arguments.length) { + return []; + } else { + return array_splice.apply(this, [ + start === void 0 ? 0 : start, + deleteCount === void 0 ? (this.length - start) : deleteCount + ].concat(slice.call(arguments, 2))) + } + }; + } else {//IE8 + Array.prototype.splice = function(pos, removeCount){ + var length = this.length; + if (pos > 0) { + if (pos > length) + pos = length; + } else if (pos == void 0) { + pos = 0; + } else if (pos < 0) { + pos = Math.max(length + pos, 0); + } + + if (!(pos+removeCount < length)) + removeCount = length - pos; + + var removed = this.slice(pos, pos+removeCount); + var insert = slice.call(arguments, 2); + var add = insert.length; + if (pos === length) { + if (add) { + this.push.apply(this, insert); + } + } else { + var remove = Math.min(removeCount, length - pos); + var tailOldPos = pos + remove; + var tailNewPos = tailOldPos + add - remove; + var tailCount = length - tailOldPos; + var lengthAfterRemove = length - remove; + + if (tailNewPos < tailOldPos) { // case A + for (var i = 0; i < tailCount; ++i) { + this[tailNewPos+i] = this[tailOldPos+i]; + } + } else if (tailNewPos > tailOldPos) { // case B + for (i = tailCount; i--; ) { + this[tailNewPos+i] = this[tailOldPos+i]; + } + } // else, add == remove (nothing to do) + + if (add && pos === lengthAfterRemove) { + this.length = lengthAfterRemove; // truncate array + this.push.apply(this, insert); + } else { + this.length = lengthAfterRemove + add; // reserves space + for (i = 0; i < add; ++i) { + this[pos+i] = insert[i]; + } + } + } + return removed; + }; + } +} +if (!Array.isArray) { + Array.isArray = function isArray(obj) { + return _toString(obj) == "[object Array]"; + }; +} +var boxedString = Object("a"), + splitString = boxedString[0] != "a" || !(0 in boxedString); + +if (!Array.prototype.forEach) { + Array.prototype.forEach = function forEach(fun /*, thisp*/) { + var object = toObject(this), + self = splitString && _toString(this) == "[object String]" ? + this.split("") : + object, + thisp = arguments[1], + i = -1, + length = self.length >>> 0; + if (_toString(fun) != "[object Function]") { + throw new TypeError(); // TODO message + } + + while (++i < length) { + if (i in self) { + fun.call(thisp, self[i], i, object); + } + } + }; +} +if (!Array.prototype.map) { + Array.prototype.map = function map(fun /*, thisp*/) { + var object = toObject(this), + self = splitString && _toString(this) == "[object String]" ? + this.split("") : + object, + length = self.length >>> 0, + result = Array(length), + thisp = arguments[1]; + if (_toString(fun) != "[object Function]") { + throw new TypeError(fun + " is not a function"); + } + + for (var i = 0; i < length; i++) { + if (i in self) + result[i] = fun.call(thisp, self[i], i, object); + } + return result; + }; +} +if (!Array.prototype.filter) { + Array.prototype.filter = function filter(fun /*, thisp */) { + var object = toObject(this), + self = splitString && _toString(this) == "[object String]" ? + this.split("") : + object, + length = self.length >>> 0, + result = [], + value, + thisp = arguments[1]; + if (_toString(fun) != "[object Function]") { + throw new TypeError(fun + " is not a function"); + } + + for (var i = 0; i < length; i++) { + if (i in self) { + value = self[i]; + if (fun.call(thisp, value, i, object)) { + result.push(value); + } + } + } + return result; + }; +} +if (!Array.prototype.every) { + Array.prototype.every = function every(fun /*, thisp */) { + var object = toObject(this), + self = splitString && _toString(this) == "[object String]" ? + this.split("") : + object, + length = self.length >>> 0, + thisp = arguments[1]; + if (_toString(fun) != "[object Function]") { + throw new TypeError(fun + " is not a function"); + } + + for (var i = 0; i < length; i++) { + if (i in self && !fun.call(thisp, self[i], i, object)) { + return false; + } + } + return true; + }; +} +if (!Array.prototype.some) { + Array.prototype.some = function some(fun /*, thisp */) { + var object = toObject(this), + self = splitString && _toString(this) == "[object String]" ? + this.split("") : + object, + length = self.length >>> 0, + thisp = arguments[1]; + if (_toString(fun) != "[object Function]") { + throw new TypeError(fun + " is not a function"); + } + + for (var i = 0; i < length; i++) { + if (i in self && fun.call(thisp, self[i], i, object)) { + return true; + } + } + return false; + }; +} +if (!Array.prototype.reduce) { + Array.prototype.reduce = function reduce(fun /*, initial*/) { + var object = toObject(this), + self = splitString && _toString(this) == "[object String]" ? + this.split("") : + object, + length = self.length >>> 0; + if (_toString(fun) != "[object Function]") { + throw new TypeError(fun + " is not a function"); + } + if (!length && arguments.length == 1) { + throw new TypeError("reduce of empty array with no initial value"); + } + + var i = 0; + var result; + if (arguments.length >= 2) { + result = arguments[1]; + } else { + do { + if (i in self) { + result = self[i++]; + break; + } + if (++i >= length) { + throw new TypeError("reduce of empty array with no initial value"); + } + } while (true); + } + + for (; i < length; i++) { + if (i in self) { + result = fun.call(void 0, result, self[i], i, object); + } + } + + return result; + }; +} +if (!Array.prototype.reduceRight) { + Array.prototype.reduceRight = function reduceRight(fun /*, initial*/) { + var object = toObject(this), + self = splitString && _toString(this) == "[object String]" ? + this.split("") : + object, + length = self.length >>> 0; + if (_toString(fun) != "[object Function]") { + throw new TypeError(fun + " is not a function"); + } + if (!length && arguments.length == 1) { + throw new TypeError("reduceRight of empty array with no initial value"); + } + + var result, i = length - 1; + if (arguments.length >= 2) { + result = arguments[1]; + } else { + do { + if (i in self) { + result = self[i--]; + break; + } + if (--i < 0) { + throw new TypeError("reduceRight of empty array with no initial value"); + } + } while (true); + } + + do { + if (i in this) { + result = fun.call(void 0, result, self[i], i, object); + } + } while (i--); + + return result; + }; +} +if (!Array.prototype.indexOf || ([0, 1].indexOf(1, 2) != -1)) { + Array.prototype.indexOf = function indexOf(sought /*, fromIndex */ ) { + var self = splitString && _toString(this) == "[object String]" ? + this.split("") : + toObject(this), + length = self.length >>> 0; + + if (!length) { + return -1; + } + + var i = 0; + if (arguments.length > 1) { + i = toInteger(arguments[1]); + } + i = i >= 0 ? i : Math.max(0, length + i); + for (; i < length; i++) { + if (i in self && self[i] === sought) { + return i; + } + } + return -1; + }; +} +if (!Array.prototype.lastIndexOf || ([0, 1].lastIndexOf(0, -3) != -1)) { + Array.prototype.lastIndexOf = function lastIndexOf(sought /*, fromIndex */) { + var self = splitString && _toString(this) == "[object String]" ? + this.split("") : + toObject(this), + length = self.length >>> 0; + + if (!length) { + return -1; + } + var i = length - 1; + if (arguments.length > 1) { + i = Math.min(i, toInteger(arguments[1])); + } + i = i >= 0 ? i : length - Math.abs(i); + for (; i >= 0; i--) { + if (i in self && sought === self[i]) { + return i; + } + } + return -1; + }; +} +if (!Object.getPrototypeOf) { + Object.getPrototypeOf = function getPrototypeOf(object) { + return object.__proto__ || ( + object.constructor ? + object.constructor.prototype : + prototypeOfObject + ); + }; +} +if (!Object.getOwnPropertyDescriptor) { + var ERR_NON_OBJECT = "Object.getOwnPropertyDescriptor called on a " + + "non-object: "; + Object.getOwnPropertyDescriptor = function getOwnPropertyDescriptor(object, property) { + if ((typeof object != "object" && typeof object != "function") || object === null) + throw new TypeError(ERR_NON_OBJECT + object); + if (!owns(object, property)) + return; + + var descriptor, getter, setter; + descriptor = { enumerable: true, configurable: true }; + if (supportsAccessors) { + var prototype = object.__proto__; + object.__proto__ = prototypeOfObject; + + var getter = lookupGetter(object, property); + var setter = lookupSetter(object, property); + object.__proto__ = prototype; + + if (getter || setter) { + if (getter) descriptor.get = getter; + if (setter) descriptor.set = setter; + return descriptor; + } + } + descriptor.value = object[property]; + return descriptor; + }; +} +if (!Object.getOwnPropertyNames) { + Object.getOwnPropertyNames = function getOwnPropertyNames(object) { + return Object.keys(object); + }; +} +if (!Object.create) { + var createEmpty; + if (Object.prototype.__proto__ === null) { + createEmpty = function () { + return { "__proto__": null }; + }; + } else { + createEmpty = function () { + var empty = {}; + for (var i in empty) + empty[i] = null; + empty.constructor = + empty.hasOwnProperty = + empty.propertyIsEnumerable = + empty.isPrototypeOf = + empty.toLocaleString = + empty.toString = + empty.valueOf = + empty.__proto__ = null; + return empty; + } + } + + Object.create = function create(prototype, properties) { + var object; + if (prototype === null) { + object = createEmpty(); + } else { + if (typeof prototype != "object") + throw new TypeError("typeof prototype["+(typeof prototype)+"] != 'object'"); + var Type = function () {}; + Type.prototype = prototype; + object = new Type(); + object.__proto__ = prototype; + } + if (properties !== void 0) + Object.defineProperties(object, properties); + return object; + }; +} + +function doesDefinePropertyWork(object) { + try { + Object.defineProperty(object, "sentinel", {}); + return "sentinel" in object; + } catch (exception) { + } +} +if (Object.defineProperty) { + var definePropertyWorksOnObject = doesDefinePropertyWork({}); + var definePropertyWorksOnDom = typeof document == "undefined" || + doesDefinePropertyWork(document.createElement("div")); + if (!definePropertyWorksOnObject || !definePropertyWorksOnDom) { + var definePropertyFallback = Object.defineProperty; + } +} + +if (!Object.defineProperty || definePropertyFallback) { + var ERR_NON_OBJECT_DESCRIPTOR = "Property description must be an object: "; + var ERR_NON_OBJECT_TARGET = "Object.defineProperty called on non-object: " + var ERR_ACCESSORS_NOT_SUPPORTED = "getters & setters can not be defined " + + "on this javascript engine"; + + Object.defineProperty = function defineProperty(object, property, descriptor) { + if ((typeof object != "object" && typeof object != "function") || object === null) + throw new TypeError(ERR_NON_OBJECT_TARGET + object); + if ((typeof descriptor != "object" && typeof descriptor != "function") || descriptor === null) + throw new TypeError(ERR_NON_OBJECT_DESCRIPTOR + descriptor); + if (definePropertyFallback) { + try { + return definePropertyFallback.call(Object, object, property, descriptor); + } catch (exception) { + } + } + if (owns(descriptor, "value")) { + + if (supportsAccessors && (lookupGetter(object, property) || + lookupSetter(object, property))) + { + var prototype = object.__proto__; + object.__proto__ = prototypeOfObject; + delete object[property]; + object[property] = descriptor.value; + object.__proto__ = prototype; + } else { + object[property] = descriptor.value; + } + } else { + if (!supportsAccessors) + throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED); + if (owns(descriptor, "get")) + defineGetter(object, property, descriptor.get); + if (owns(descriptor, "set")) + defineSetter(object, property, descriptor.set); + } + + return object; + }; +} +if (!Object.defineProperties) { + Object.defineProperties = function defineProperties(object, properties) { + for (var property in properties) { + if (owns(properties, property)) + Object.defineProperty(object, property, properties[property]); + } + return object; + }; +} +if (!Object.seal) { + Object.seal = function seal(object) { + return object; + }; +} +if (!Object.freeze) { + Object.freeze = function freeze(object) { + return object; + }; +} +try { + Object.freeze(function () {}); +} catch (exception) { + Object.freeze = (function freeze(freezeObject) { + return function freeze(object) { + if (typeof object == "function") { + return object; + } else { + return freezeObject(object); + } + }; + })(Object.freeze); +} +if (!Object.preventExtensions) { + Object.preventExtensions = function preventExtensions(object) { + return object; + }; +} +if (!Object.isSealed) { + Object.isSealed = function isSealed(object) { + return false; + }; +} +if (!Object.isFrozen) { + Object.isFrozen = function isFrozen(object) { + return false; + }; +} +if (!Object.isExtensible) { + Object.isExtensible = function isExtensible(object) { + if (Object(object) === object) { + throw new TypeError(); // TODO message + } + var name = ''; + while (owns(object, name)) { + name += '?'; + } + object[name] = true; + var returnValue = owns(object, name); + delete object[name]; + return returnValue; + }; +} +if (!Object.keys) { + var hasDontEnumBug = true, + dontEnums = [ + "toString", + "toLocaleString", + "valueOf", + "hasOwnProperty", + "isPrototypeOf", + "propertyIsEnumerable", + "constructor" + ], + dontEnumsLength = dontEnums.length; + + for (var key in {"toString": null}) { + hasDontEnumBug = false; + } + + Object.keys = function keys(object) { + + if ( + (typeof object != "object" && typeof object != "function") || + object === null + ) { + throw new TypeError("Object.keys called on a non-object"); + } + + var keys = []; + for (var name in object) { + if (owns(object, name)) { + keys.push(name); + } + } + + if (hasDontEnumBug) { + for (var i = 0, ii = dontEnumsLength; i < ii; i++) { + var dontEnum = dontEnums[i]; + if (owns(object, dontEnum)) { + keys.push(dontEnum); + } + } + } + return keys; + }; + +} +if (!Date.now) { + Date.now = function now() { + return new Date().getTime(); + }; +} +var ws = "\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003" + + "\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028" + + "\u2029\uFEFF"; +if (!String.prototype.trim || ws.trim()) { + ws = "[" + ws + "]"; + var trimBeginRegexp = new RegExp("^" + ws + ws + "*"), + trimEndRegexp = new RegExp(ws + ws + "*$"); + String.prototype.trim = function trim() { + return String(this).replace(trimBeginRegexp, "").replace(trimEndRegexp, ""); + }; +} + +function toInteger(n) { + n = +n; + if (n !== n) { // isNaN + n = 0; + } else if (n !== 0 && n !== (1/0) && n !== -(1/0)) { + n = (n > 0 || -1) * Math.floor(Math.abs(n)); + } + return n; +} + +function isPrimitive(input) { + var type = typeof input; + return ( + input === null || + type === "undefined" || + type === "boolean" || + type === "number" || + type === "string" + ); +} + +function toPrimitive(input) { + var val, valueOf, toString; + if (isPrimitive(input)) { + return input; + } + valueOf = input.valueOf; + if (typeof valueOf === "function") { + val = valueOf.call(input); + if (isPrimitive(val)) { + return val; + } + } + toString = input.toString; + if (typeof toString === "function") { + val = toString.call(input); + if (isPrimitive(val)) { + return val; + } + } + throw new TypeError(); +} +var toObject = function (o) { + if (o == null) { // this matches both null and undefined + throw new TypeError("can't convert "+o+" to object"); + } + return Object(o); +}; + +}); diff --git a/nodes/core/core/20-inject.html b/nodes/core/core/20-inject.html index 1c8629bec..b7e0f996c 100644 --- a/nodes/core/core/20-inject.html +++ b/nodes/core/core/20-inject.html @@ -185,7 +185,7 @@ return false; } } else if (ptype === 'flow' || ptype === 'global' ) { - return /^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]+)*/i.test(v); + return RED.utils.validatePropertyExpression(v); } else if (ptype === 'num') { return /^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v); } diff --git a/nodes/core/core/58-debug.html b/nodes/core/core/58-debug.html index 845c0ed72..6d00fe837 100644 --- a/nodes/core/core/58-debug.html +++ b/nodes/core/core/58-debug.html @@ -41,12 +41,14 @@

                    The button to the right of the node will toggle its output on and off so you can de-clutter the debug window.

                    If the payload is an object or buffer it will be stringified first for display and indicate that by saying "(Object)" or "(Buffer)".

                    Selecting any particular message will highlight (in red) the debug node that reported it. This is useful if you wire up multiple debug nodes.

                    -

                    Optionally can show the complete msg object, and send messages to the console log.

                    +

                    Optionally can show the complete msg object, and send messages to the console log (⇶).

                    In addition any calls to node.warn or node.error will appear here.

                    + - - diff --git a/nodes/core/core/58-debug.js b/nodes/core/core/58-debug.js index bf9b6610f..d0b93e95a 100644 --- a/nodes/core/core/58-debug.js +++ b/nodes/core/core/58-debug.js @@ -18,6 +18,8 @@ module.exports = function(RED) { "use strict"; var util = require("util"); var events = require("events"); + var path = require("path"); + var safeJSONStringify = require("json-stringify-safe"); var debuglength = RED.settings.debugMaxLength||1000; var useColors = false; // util.inspect.styles.boolean = "red"; @@ -79,30 +81,62 @@ module.exports = function(RED) { msg.format = "error"; msg.msg = msg.msg.toString(); } else if (msg.msg instanceof Buffer) { - msg.format = "buffer ["+msg.msg.length+"]"; + msg.format = "buffer["+msg.msg.length+"]"; msg.msg = msg.msg.toString('hex'); + if (msg.msg.length > debuglength) { + msg.msg = msg.msg.substring(0,debuglength); + } } else if (msg.msg && typeof msg.msg === 'object') { var seen = []; + var seenAts = []; try { msg.format = msg.msg.constructor.name || "Object"; } catch(err) { msg.format = "Object"; } - var isArray = util.isArray(msg.msg); - if (isArray) { - msg.format = "array ["+msg.msg.length+"]"; - } - if (isArray || (msg.format === "Object")) { - msg.msg = JSON.stringify(msg.msg, function(key, value) { - if (typeof value === 'object' && value !== null) { - if (seen.indexOf(value) !== -1) { return "[circular]"; } - seen.push(value); - } - return value; - }," "); + if (/error/i.test(msg.format)) { + msg.msg = JSON.stringify({ + name: msg.msg.name, + message: msg.msg.message + }); } else { - try { msg.msg = msg.msg.toString(); } - catch(e) { msg.msg = "[Type not printable]"; } + var isArray = util.isArray(msg.msg); + if (isArray) { + msg.format = "array["+msg.msg.length+"]"; + if (msg.msg.length > debuglength) { + msg.msg = msg.msg.slice(0,debuglength); + } + } + if (isArray || (msg.format === "Object")) { + msg.msg = safeJSONStringify(msg.msg, function(key, value) { + if (key[0] === '_' && key !== "_msgid") { + return undefined; + } + if (key === '_req' || key === '_res') { + return "[internal]" + } + if (value instanceof Error) { + return value.toString() + } + if (util.isArray(value) && value.length > debuglength) { + value = { + __encoded__: true, + type: "array", + data: value.slice(0,debuglength), + length: value.length + } + } + if (typeof value === 'string') { + if (value.length > debuglength) { + return value.substring(0,debuglength)+"..."; + } + } + return value; + }," "); + } else { + try { msg.msg = msg.msg.toString(); } + catch(e) { msg.msg = "[Type not printable]"; } + } } seen = null; } else if (typeof msg.msg === "boolean") { @@ -118,13 +152,14 @@ module.exports = function(RED) { msg.format = (msg.msg === null)?"null":"undefined"; msg.msg = "(undefined)"; } else { - msg.format = "string ["+msg.msg.length+"]"; - msg.msg = msg.msg; - } - - if (msg.msg.length > debuglength) { - msg.msg = msg.msg.substr(0,debuglength) +" ...."; + msg.format = "string["+msg.msg.length+"]"; + if (msg.msg.length > debuglength) { + msg.msg = msg.msg.substring(0,debuglength)+"..."; + } } + // if (msg.msg.length > debuglength) { + // msg.msg = msg.msg.substr(0,debuglength) +" ...."; + // } RED.comms.publish("debug",msg); } @@ -153,4 +188,12 @@ module.exports = function(RED) { res.sendStatus(404); } }); + + RED.httpAdmin.get("/debug/view/*",RED.auth.needsPermission("debug.read"),function(req,res) { + var options = { + root: __dirname + '/lib/debug/', + dotfiles: 'deny' + }; + res.sendFile(req.params[0], options); + }); }; diff --git a/nodes/core/core/75-exec.html b/nodes/core/core/75-exec.html index a6c7fe339..f354db965 100644 --- a/nodes/core/core/75-exec.html +++ b/nodes/core/core/75-exec.html @@ -53,7 +53,7 @@ (on the 3rd output).

                    The optional append gets added to the command after msg.payload - so you can do things like pipe the result to another command.

                    -

                    Parameters with spaces should be enclosed in quotes - "This is a single parameter"

                    +

                    Commands or parameters with spaces should be enclosed in quotes - "This is a single parameter"

                    If stdout is binary a buffer is returned - otherwise returns a string.

                    The blue status icon will be visible while the node is active.

                    If running a Python app you may need to use the -u parameter to stop the output being buffered.

                    diff --git a/nodes/core/core/75-exec.js b/nodes/core/core/75-exec.js index f4caa7e5b..931dbb616 100644 --- a/nodes/core/core/75-exec.js +++ b/nodes/core/core/75-exec.js @@ -49,6 +49,7 @@ module.exports = function(RED) { // slice whole line by spaces (trying to honour quotes); arg = arg.match(/(?:[^\s"]+|"[^"]*")+/g); var cmd = arg.shift(); + if (/^".*"$/.test(cmd)) { cmd = cmd.slice(1,-1); } /* istanbul ignore else */ if (RED.settings.verbose) { node.log(cmd+" ["+arg+"]"); } child = spawn(cmd,arg); diff --git a/nodes/core/core/80-template.html b/nodes/core/core/80-template.html index fc6455ac6..0bbf4da42 100644 --- a/nodes/core/core/80-template.html +++ b/nodes/core/core/80-template.html @@ -36,6 +36,7 @@ +
                    @@ -66,6 +67,7 @@ }

                    The resulting property will be:

                    Hello Fred. Today is Monday
                    +

                    It is possible to use property from flow context or global context. Just use {{flow.name}} or {{global.name}}.

                    By default, mustache will escape any HTML entities in the values it substitutes. To prevent this, use {{{triple}}} braces. diff --git a/nodes/core/core/80-template.js b/nodes/core/core/80-template.js index 8e93b1530..ba571d458 100644 --- a/nodes/core/core/80-template.js +++ b/nodes/core/core/80-template.js @@ -18,6 +18,39 @@ module.exports = function(RED) { "use strict"; var mustache = require("mustache"); + /** + * Custom Mustache Context capable to resolve message property and node + * flow and global context + */ + function NodeContext(msg, nodeContext) { + this.msgContext = new mustache.Context(msg); + this.nodeContext = nodeContext; + } + + NodeContext.prototype = new mustache.Context(); + + NodeContext.prototype.lookup = function (name) { + // try message first: + var value = this.msgContext.lookup(name); + if (value !== undefined) { + return value; + } + + // try node context: + var dot = name.indexOf("."); + if (dot > 0) { + var contextName = name.substr(0, dot); + var variableName = name.substr(dot + 1); + + if (contextName === "flow" && this.nodeContext.flow) { + return this.nodeContext.flow.get(variableName); + } + else if (contextName === "global" && this.nodeContext.global) { + return this.nodeContext.global.get(variableName); + } + } + } + function TemplateNode(n) { RED.nodes.createNode(this,n); this.name = n.name; @@ -31,7 +64,7 @@ module.exports = function(RED) { try { var value; if (node.syntax === "mustache") { - value = mustache.render(node.template,msg); + value = mustache.render(node.template, new NodeContext(msg, node.context())); } else { value = node.template; } diff --git a/nodes/core/core/lib/debug/debug-utils.js b/nodes/core/core/lib/debug/debug-utils.js new file mode 100644 index 000000000..5322266a5 --- /dev/null +++ b/nodes/core/core/lib/debug/debug-utils.js @@ -0,0 +1,285 @@ +/** + * Copyright 2016 IBM Corp. + * + * 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. + **/ + +if (!RED) { + var RED = {} +} +RED.debug = (function() { + var config; + var messageList; + var messageTable; + var filter = false; + var view = 'list'; + var messages = []; + var messagesByNode = {}; + var sbc; + var activeWorkspace; + + function init(_config) { + config = _config; + + var content = $("

                    ").css({"position":"relative","height":"100%"}); + var toolbar = $('').appendTo(content); + + + var footerToolbar = $('
                    '+ + // ''+ + // 'list'+ + // 'table '+ + // ''+ + ' ' + + '
                    '); + + messageList = $('
                    ').appendTo(content); + sbc = messageList[0]; + messageTable = $('
                    ').appendTo(content); + + var filterDialog = $('
                    '+ + '
                    '+ + ''+ + 'all flows'+ + 'current flow '+ + ''+ + '
                    '+ + '
                    ').appendTo(content); + + try { + content.i18n(); + } catch(err) { + console.log("TODO: i18n library support"); + } + + + filterDialog.find('#debug-tab-filter-all').on("click",function(e) { + e.preventDefault(); + if (filter) { + $(this).addClass('selected'); + $('#debug-tab-filter-current').removeClass('selected'); + filter = !filter; + refreshMessageList(); + } + }); + filterDialog.find('#debug-tab-filter-current').on("click",function(e) { + e.preventDefault(); + if (!filter) { + $(this).addClass('selected'); + $('#debug-tab-filter-all').removeClass('selected'); + filter = !filter; + refreshMessageList(); + } + }); + // $('#debug-tab-view-list').on("click",function(e) { + // e.preventDefault(); + // if (!$(this).hasClass('selected')) { + // $(this).addClass('selected'); + // $('#debug-tab-view-table').removeClass('selected'); + // showMessageList(); + // } + // }); + // $('#debug-tab-view-table').on("click",function(e) { + // e.preventDefault(); + // if (!$(this).hasClass('selected')) { + // $(this).addClass('selected'); + // $('#debug-tab-view-list').removeClass('selected'); + // showMessageTable(); + // } + // }); + + + toolbar.find('#debug-tab-filter').on("click",function(e) { + e.preventDefault(); + if ($(this).hasClass('selected')) { + $(this).removeClass('selected'); + filterDialog.slideUp(200); + } else { + $(this).addClass('selected'); + filterDialog.slideDown(200); + } + }) + + toolbar.find("#debug-tab-clear").click(function(e) { + e.preventDefault(); + $(".debug-message").remove(); + messageCount = 0; + config.clear(); + }); + + + return { + content: content, + footer: footerToolbar + } + + } + + function getTimestamp() { + var d = new Date(); + return d.toLocaleString(); + } + + function sanitize(m) { + return m.replace(/&/g,"&").replace(//g,">"); + } + + function refreshMessageList(_activeWorkspace) { + if (_activeWorkspace) { + activeWorkspace = _activeWorkspace; + } + $(".debug-message").each(function() { + $(this).toggleClass('hide',filter&&!$(this).hasClass('debug-message-flow-'+activeWorkspace)); + }); + } + function refreshMessageTable() { + + } + + function showMessageList() { + view = 'list'; + messageTable.hide(); + messageTable.empty(); + + messages.forEach(function(m) { + messageList.append(m.el); + }) + messageList.show(); + } + function showMessageTable() { + view = 'table'; + messageList.hide(); + messageList.empty(); + + Object.keys(messagesByNode).forEach(function(id) { + var m = messagesByNode[id]; + var msg = m.el; + var sourceNode = m.source; + if (sourceNode) { + var wrapper = $("
                    ",{id:"debug-message-source-"+sourceNode.id.replace(/\./g,"_")}).appendTo(messageTable); + wrapper.append(msg); + } + }); + messageTable.show(); + } + function formatString(str) { + return str.replace(/\n/g,"↵").replace(/\t/g,"→"); + } + + function handleDebugMessage(o) { + var msg = document.createElement("div"); + + var sourceNode = o._source; + + msg.onmouseenter = function() { + msg.style.borderRightColor = "#999"; + if (o._source) { + config.messageMouseEnter(o._source.id); + } + }; + msg.onmouseleave = function() { + msg.style.borderRightColor = ""; + if (o._source) { + config.messageMouseLeave(o._source.id); + } + }; + var name = sanitize(((o.name?o.name:o.id)||"").toString()); + var topic = sanitize((o.topic||"").toString()); + var property = sanitize(o.property?o.property:''); + var payload = o.msg; + var format = sanitize((o.format||"").toString()); + msg.className = 'debug-message'+(o.level?(' debug-message-level-'+o.level):'') + + ((sourceNode&&sourceNode.z)?((" debug-message-flow-"+sourceNode.z+((filter&&(activeWorkspace!==sourceNode.z))?" hide":""))):""); + var metaRow = $('
                    ').appendTo(msg); + $(''+ getTimestamp()+'').appendTo(metaRow); + if (sourceNode) { + $('',{href:"#",class:"debug-message-name"}).html('node: '+sourceNode.id) + .appendTo(metaRow) + .click(function(evt) { + evt.preventDefault(); + config.messageSourceClick(sourceNode.id); + }); + } else if (name) { + $(''+name+'').appendTo(metaRow); + } + // NOTE: relying on function error to have a "type" that all other msgs don't + if (o.hasOwnProperty("type") && (o.type === "function")) { + var errorLvlType = 'error'; + var errorLvl = 20; + if (o.hasOwnProperty("level") && o.level === 30) { + errorLvl = 30; + errorLvlType = 'warn'; + } + $(msg).addClass('debug-message-level-' + errorLvl); + $('function : (' + errorLvlType + ')').appendTo(metaRow); + } else { + $(''+ + (o.topic?topic+' : ':'')+ + (o.property?'msg.'+property:'msg')+" : "+format+ + '').appendTo(metaRow); + } + if (format === 'Object' || /^array/.test(format) || format === 'boolean' || format === 'number'||/error/i.test(format) ) { + payload = JSON.parse(payload); + } else if (format === 'null') { + payload = null; + } else if (format === 'undefined') { + payload = undefined; + } else if (/^buffer/.test(format)) { + var buffer = payload; + payload = []; + for (var c = 0; c < buffer.length; c += 2) { + payload.push(parseInt(buffer.substr(c, 2), 16)); + } + } + var el = $('').appendTo(msg); + RED.utils.createObjectElement(payload,/*true*/null,format).appendTo(el); + var atBottom = (sbc.scrollHeight-messageList.height()-sbc.scrollTop) < 5; + var m = { + el: msg + }; + messages.push(m); + if (sourceNode) { + m.source = sourceNode; + messagesByNode[sourceNode.id] = m; + } + if (view == "list") { + messageList.append(msg); + } else { + if (sourceNode) { + var wrapper = $("#debug-message-source-"+sourceNode.id.replace(/\./g,"_")); + if (wrapper.length === 0 ) { + wrapper = $("
                    ",{id:"debug-message-source-"+sourceNode.id.replace(/\./g,"_")}).appendTo(messageTable); + } + wrapper.empty(); + wrapper.append(msg); + } + } + + if (messages.length === 100) { + m = messages.shift(); + if (view === "list") { + m.el.remove(); + } + } + if (atBottom) { + messageList.scrollTop(sbc.scrollHeight); + } + } + return { + init: init, + refreshMessageList:refreshMessageList, + handleDebugMessage: handleDebugMessage + } +})(); diff --git a/nodes/core/core/lib/debug/debug.js b/nodes/core/core/lib/debug/debug.js new file mode 100644 index 000000000..ac02543e3 --- /dev/null +++ b/nodes/core/core/lib/debug/debug.js @@ -0,0 +1,28 @@ +$(function() { + var options = { + messageMouseEnter: function(sourceId) { + window.opener.postMessage({event:"mouseEnter",id:sourceId},'*'); + }, + messageMouseLeave: function(sourceId) { + window.opener.postMessage({event:"mouseLeave",id:sourceId},'*'); + }, + messageSourceClick: function(sourceId) { + window.opener.postMessage({event:"mouseClick",id:sourceId},'*'); + }, + clear: function() { + window.opener.postMessage({event:"clear"},'*'); + } + } + + var uiComponents = RED.debug.init(options); + + $(".debug-window").append(uiComponents.content); + + window.addEventListener('message',function(evt) { + if (evt.data.event === "message") { + RED.debug.handleDebugMessage(evt.data.msg); + } else if (evt.data.event === "workspaceChange") { + RED.debug.refreshMessageList(evt.data.activeWorkspace); + } + },false); +}); diff --git a/nodes/core/core/lib/debug/view.html b/nodes/core/core/lib/debug/view.html new file mode 100644 index 000000000..964f633f5 --- /dev/null +++ b/nodes/core/core/lib/debug/view.html @@ -0,0 +1,13 @@ + + + + + Node-RED Debug Tools + + + + + + + + diff --git a/nodes/core/io/21-httprequest.html b/nodes/core/io/21-httprequest.html index 19d5f5a2a..891f4d0d6 100644 --- a/nodes/core/io/21-httprequest.html +++ b/nodes/core/io/21-httprequest.html @@ -90,6 +90,7 @@
                  1. payload is the body of the response
                  2. statusCode is the status code of the response, or the error code if the request could not be completed
                  3. headers is an object containing the response headers
                  4. +
                  5. responseUrl is the url of the server that responds
                  6. Note: If you need to configure a proxy please add http_proxy=... to your environment variables and restart Node-RED.

                    diff --git a/nodes/core/io/21-httprequest.js b/nodes/core/io/21-httprequest.js index 3fa8097d0..603342497 100644 --- a/nodes/core/io/21-httprequest.js +++ b/nodes/core/io/21-httprequest.js @@ -159,6 +159,7 @@ module.exports = function(RED) { (node.ret === "bin") ? res.setEncoding('binary') : res.setEncoding('utf8'); msg.statusCode = res.statusCode; msg.headers = res.headers; + msg.responseUrl = res.responseUrl; msg.payload = ""; // msg.url = url; // revert when warning above finally removed res.on('data',function(chunk) { diff --git a/nodes/core/locales/en-US/messages.json b/nodes/core/locales/en-US/messages.json index ab733c45d..d36139f58 100644 --- a/nodes/core/locales/en-US/messages.json +++ b/nodes/core/locales/en-US/messages.json @@ -502,6 +502,9 @@ "null":"is null", "nnull":"is not null", "else":"otherwise" + }, + "errors": { + "invalid-expr": "Invalid expression: __error__" } }, "change": { @@ -614,6 +617,13 @@ "dropped-error": "Failed to convert payload" } }, + "yaml": { + "errors": { + "dropped-object": "Ignored non-object payload", + "dropped": "Ignored unsupported payload type", + "dropped-error": "Failed to convert payload" + } + }, "xml": { "label": { "represent": "Represent XML tag attributes as a property named", diff --git a/nodes/core/logic/10-switch.html b/nodes/core/logic/10-switch.html index 0d666f514..3ce53c89f 100644 --- a/nodes/core/logic/10-switch.html +++ b/nodes/core/logic/10-switch.html @@ -64,7 +64,7 @@ var node = this; 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 = [ {v:"eq",t:"=="}, {v:"neq",t:"!="}, @@ -115,7 +115,10 @@ $("#node-input-rule-container").css('min-height','250px').css('min-width','450px').editableList({ addItem: function(container,i,opt) { - var rule = opt; + if (!opt.hasOwnProperty('r')) { + opt.r = {}; + } + var rule = opt.r; if (!rule.hasOwnProperty('t')) { rule.t = 'eq'; } @@ -126,10 +129,10 @@ for (var d in operators) { selectField.append($("").val(operators[d].v).text(operators[d].t)); } - var valueField = $('',{class:"node-input-rule-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'str',types:['msg','flow','global','str','num',previousValueType]}); - var btwnValueField = $('',{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 valueField = $('',{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 = $('',{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 = $('',{class:"node-input-rule-btwn-label"}).text(" "+andLabel+" ").appendTo(row3); - var btwnValue2Field = $('',{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 = $('',{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 = $('',{style:"float: right;margin-top: 6px;"}).appendTo(row); finalspan.append(' → '+(i+1)+' '); var caseSensitive = $('',{id:"node-input-rule-case-"+i,class:"node-input-rule-case",type:"checkbox",style:"width:auto;vertical-align:top"}).appendTo(row2); @@ -177,6 +180,12 @@ selectField.change(); }, removeItem: function(opt) { + if (opt.hasOwnProperty('i')) { + var removedList = $("#node-input-rule-container").data('removedList')||[]; + removedList.push(opt.i); + $("#node-input-rule-container").data('removedList',removedList); + } + var rules = $("#node-input-rule-container").editableList('items'); rules.each(function(i) { $(this).find(".node-input-rule-index").html(i+1); }); }, @@ -191,15 +200,21 @@ for (var i=0;i").val(selectOptions[i].v).text(selectOptions[i].l)); } - var propertyName = $('',{style:"width:250px",class:"node-input-rule-property-name",type:"text"}) + var propertyName = $('',{class:"node-input-rule-property-name",type:"text"}) .appendTo(row1) .typedInput({types:['msg','flow','global']}); $('
                    ',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"}) .text(to) .appendTo(row2); - var propertyValue = $('',{style:"width:250px",class:"node-input-rule-property-value",type:"text"}) + var propertyValue = $('',{class:"node-input-rule-property-value",type:"text"}) .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 = $('
                    ').appendTo(row3); $('
                    ',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"}) .text(search) .appendTo(row3_1); - var fromValue = $('',{style:"width:250px",class:"node-input-rule-property-search-value",type:"text"}) + var fromValue = $('',{class:"node-input-rule-property-search-value",type:"text"}) .appendTo(row3_1) .typedInput({default:'str',types:['msg','flow','global','str','re','num','bool']}); @@ -164,14 +164,14 @@ $('
                    ',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"}) .text(replace) .appendTo(row3_2); - var toValue = $('',{style:"width:250px",class:"node-input-rule-property-replace-value",type:"text"}) + var toValue = $('',{class:"node-input-rule-property-replace-value",type:"text"}) .appendTo(row3_2) .typedInput({default:'str',types:['msg','flow','global','str','num','bool','json']}); $('
                    ',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"}) .text(to) .appendTo(row4); - var moveValue = $('',{style:"width:250px",class:"node-input-rule-property-move-value",type:"text"}) + var moveValue = $('',{class:"node-input-rule-property-move-value",type:"text"}) .appendTo(row4) .typedInput({default:'msg',types:['msg','flow','global']}); diff --git a/nodes/core/logic/15-change.js b/nodes/core/logic/15-change.js index 67f30f12f..805b708ed 100644 --- a/nodes/core/logic/15-change.js +++ b/nodes/core/logic/15-change.js @@ -16,6 +16,7 @@ module.exports = function(RED) { "use strict"; + var jsonata = require("jsonata"); function ChangeNode(n) { RED.nodes.createNode(this, n); @@ -85,6 +86,13 @@ module.exports = function(RED) { } } else if (rule.tot === 'bool') { 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); } else if (rule.tot === 'date') { value = Date.now(); + } else if (rule.tot === 'jsonata') { + value = rule.to.evaluate({msg:msg}); } if (rule.t === 'change') { if (rule.fromt === 'msg' || rule.fromt === 'flow' || rule.fromt === 'global') { diff --git a/nodes/core/parsers/70-JSON.js b/nodes/core/parsers/70-JSON.js index 1bacb16ff..cafe26cf7 100644 --- a/nodes/core/parsers/70-JSON.js +++ b/nodes/core/parsers/70-JSON.js @@ -16,7 +16,6 @@ module.exports = function(RED) { "use strict"; - var util = require("util"); function JSONNode(n) { RED.nodes.createNode(this,n); @@ -36,9 +35,7 @@ module.exports = function(RED) { msg.payload = JSON.stringify(msg.payload); node.send(msg); } - catch(e) { - node.error(RED._("json.errors.dropped-error")); - } + catch(e) { node.error(RED._("json.errors.dropped-error")); } } else { node.warn(RED._("json.errors.dropped-object")); } } diff --git a/nodes/core/parsers/70-YAML.html b/nodes/core/parsers/70-YAML.html new file mode 100644 index 000000000..0245da328 --- /dev/null +++ b/nodes/core/parsers/70-YAML.html @@ -0,0 +1,32 @@ + + + + + + diff --git a/nodes/core/parsers/70-YAML.js b/nodes/core/parsers/70-YAML.js new file mode 100644 index 000000000..ae4ca1a8c --- /dev/null +++ b/nodes/core/parsers/70-YAML.js @@ -0,0 +1,35 @@ + +module.exports = function(RED) { + "use strict"; + var yaml = require('js-yaml'); + function YAMLNode(n) { + RED.nodes.createNode(this,n); + var node = this; + this.on("input", function(msg) { + if (msg.hasOwnProperty("payload")) { + if (typeof msg.payload === "string") { + try { + msg.payload = yaml.load(msg.payload); + node.send(msg); + } + catch(e) { node.error(e.message,msg); } + } + else if (typeof msg.payload === "object") { + if (!Buffer.isBuffer(msg.payload)) { + try { + msg.payload = yaml.dump(msg.payload); + node.send(msg); + } + catch(e) { + node.error(RED._("yaml.errors.dropped-error")); + } + } + else { node.warn(RED._("yaml.errors.dropped-object")); } + } + else { node.warn(RED._("yaml.errors.dropped")); } + } + else { node.send(msg); } // If no payload - just pass it on. + }); + } + RED.nodes.registerType("yaml",YAMLNode); +}; diff --git a/nodes/core/storage/50-file.js b/nodes/core/storage/50-file.js index 3eed20736..e65b12b93 100644 --- a/nodes/core/storage/50-file.js +++ b/nodes/core/storage/50-file.js @@ -45,8 +45,8 @@ module.exports = function(RED) { data = new Buffer(data); if (this.overwriteFile === "true") { // using "binary" not {encoding:"binary"} to be 0.8 compatible for a while - fs.writeFile(filename, data, "binary", function (err) { - //fs.writeFile(filename, data, {encoding:"binary"}, function (err) { + //fs.writeFile(filename, data, "binary", function (err) { + fs.writeFile(filename, data, {encoding:"binary"}, function (err) { if (err) { if ((err.code === "ENOENT") && node.createDir) { fs.ensureFile(filename, function (err) { @@ -71,8 +71,8 @@ module.exports = function(RED) { } else { // using "binary" not {encoding:"binary"} to be 0.8 compatible for a while longer - fs.appendFile(filename, data, "binary", function (err) { - //fs.appendFile(filename, data, {encoding:"binary"}, function (err) { + //fs.appendFile(filename, data, "binary", function (err) { + fs.appendFile(filename, data, {encoding:"binary"}, function (err) { if (err) { if ((err.code === "ENOENT") && node.createDir) { fs.ensureFile(filename, function (err) { diff --git a/package.json b/package.json index 4924ffbbb..91bbcb3f8 100644 --- a/package.json +++ b/package.json @@ -26,35 +26,38 @@ "editor", "messaging", "iot", "ibm", "flow" ], "dependencies": { - "basic-auth": "1.0.4", - "bcryptjs": "2.3.0", + "basic-auth": "1.1.0", + "bcryptjs": "2.4.0", "body-parser": "1.15.2", "cheerio":"0.22.0", - "clone": "2.0.0", + "clone": "2.1.0", "cookie-parser": "1.4.3", "cors":"2.8.1", - "cron":"1.1.1", + "cron":"1.2.1", "express": "4.14.0", - "follow-redirects":"0.2.0", - "fs-extra": "0.30.0", + "follow-redirects":"1.2.1", + "fs-extra": "1.0.0", "fs.notify":"0.0.4", "i18next":"1.10.6", "is-utf8":"0.2.1", + "js-yaml": "3.7.0", + "json-stringify-safe":"5.0.1", + "jsonata":"1.0.10", "media-typer": "0.3.0", - "mqtt": "1.14.1", - "mustache": "2.2.1", + "mqtt": "1.*", + "mustache": "2.3.0", "nopt": "3.0.6", - "oauth2orize":"1.5.0", + "oauth2orize":"1.6.0", "on-headers":"1.0.1", "passport":"0.3.2", "passport-http-bearer":"1.0.1", "passport-oauth2-client-password":"0.1.2", "raw-body":"2.1.7", "semver": "5.3.0", - "sentiment":"1.0.6", - "uglify-js":"2.7.3", + "sentiment":"2.1.0", + "uglify-js":"2.7.5", "when": "3.7.7", - "ws": "0.8.1", + "ws": "1.1.1", "xml2js":"0.4.17", "node-red-node-feedparser":"0.1.*", "node-red-node-email":"0.1.*", @@ -62,31 +65,30 @@ "node-red-node-rbe":"0.1.*" }, "optionalDependencies": { - "node-red-node-serialport":"0.4.*", - "bcrypt":"0.8.7" + "bcrypt":"~1.0.1" }, "devDependencies": { - "grunt": "1.0.1", - "grunt-chmod": "1.1.1", - "grunt-cli": "1.2.0", - "grunt-concurrent":"2.3.1", - "grunt-contrib-clean":"1.0.0", - "grunt-contrib-compress": "1.3.0", - "grunt-contrib-concat":"1.0.1", - "grunt-contrib-copy": "1.0.0", - "grunt-contrib-jshint": "1.0.0", - "grunt-contrib-uglify": "2.0.0", - "grunt-contrib-watch":"1.0.0", - "grunt-jsonlint":"1.1.0", - "grunt-nodemon":"0.4.2", - "grunt-sass":"1.2.1", - "grunt-simple-mocha": "0.4.1", - "mocha": "3.1.1", - "should": "8.4.0", - "sinon": "1.17.6", - "supertest": "2.0.0" + "grunt": "~1.0.1", + "grunt-chmod": "~1.1.1", + "grunt-cli": "~1.2.0", + "grunt-concurrent":"~2.3.1", + "grunt-contrib-clean":"~1.0.0", + "grunt-contrib-compress": "~1.3.0", + "grunt-contrib-concat":"~1.0.1", + "grunt-contrib-copy": "~1.0.0", + "grunt-contrib-jshint": "~1.1.0", + "grunt-contrib-uglify": "~2.0.0", + "grunt-contrib-watch":"~1.0.0", + "grunt-jsonlint":"~1.1.0", + "grunt-nodemon":"~0.4.2", + "grunt-sass":"~1.2.1", + "grunt-simple-mocha": "~0.4.1", + "mocha": "~3.2.0", + "should": "^8.4.0", + "sinon": "^1.17.6", + "supertest": "^2.0.0" }, "engines": { - "node": ">=0.10" + "node": ">=4" } } diff --git a/red/api/comms.js b/red/api/comms.js index 1a51bfa1e..49fb669c2 100644 --- a/red/api/comms.js +++ b/red/api/comms.js @@ -32,7 +32,9 @@ var lastSentTime; function handleStatus(event) { publish("status/"+event.id,event.status,true); } - +function handleRuntimeEvent(event) { + publish("notification/"+event.id,event,true); +} function init(_server,runtime) { server = _server; settings = runtime.settings; @@ -40,6 +42,9 @@ function init(_server,runtime) { runtime.events.removeListener("node-status",handleStatus); runtime.events.on("node-status",handleStatus); + + runtime.events.removeListener("runtime-event",handleRuntimeEvent); + runtime.events.on("runtime-event",handleRuntimeEvent); } function start() { diff --git a/red/api/index.js b/red/api/index.js index 5ee778124..e4f01fcbd 100644 --- a/red/api/index.js +++ b/red/api/index.js @@ -165,7 +165,12 @@ function init(_server,_runtime) { } } function start() { - return i18n.registerMessageCatalog("editor",path.resolve(path.join(__dirname,"locales")),"editor.json").then(function(){ + var catalogPath = path.resolve(path.join(__dirname,"locales")); + return i18n.registerMessageCatalogs([ + {namespace: "editor", dir: catalogPath, file:"editor.json"}, + {namespace: "jsonata", dir: catalogPath, file:"jsonata.json"}, + {namespace: "infotips", dir: catalogPath, file:"infotips.json"} + ]).then(function(){ comms.start(); }); } diff --git a/red/api/info.js b/red/api/info.js index 9504713b0..5bb4c7ba8 100644 --- a/red/api/info.js +++ b/red/api/info.js @@ -42,13 +42,13 @@ module.exports = { if (settings.flowFilePretty) { safeSettings.flowFilePretty = settings.flowFilePretty; } + if (!runtime.nodes.paletteEditorEnabled()) { safeSettings.editorTheme = safeSettings.editorTheme || {}; safeSettings.editorTheme.palette = safeSettings.editorTheme.palette || {}; safeSettings.editorTheme.palette.editable = false; } - res.json(safeSettings); } } diff --git a/red/api/locales/en-US/editor.json b/red/api/locales/en-US/editor.json index 0f63fa6f8..5ac8aaad5 100644 --- a/red/api/locales/en-US/editor.json +++ b/red/api/locales/en-US/editor.json @@ -53,7 +53,8 @@ "keyboardShortcuts": "Keyboard shortcuts", "login": "Login", "logout": "Logout", - "editPalette":"Manage palette" + "editPalette":"Manage palette", + "showTips": "Show tips" } }, "user": { @@ -66,7 +67,8 @@ "warning": "Warning: __message__", "warnings": { "undeployedChanges": "node has undeployed changes", - "nodeActionDisabled": "node actions disabled within subflow" + "nodeActionDisabled": "node actions disabled within subflow", + "missing-types": "Flows stopped due to missing node types. Check logs for details." }, "error": "Error: __message__", @@ -129,9 +131,31 @@ "improperlyConfigured": "The workspace contains some nodes that are not properly configured:", "unknown": "The workspace contains some unknown node types:", "confirm": "Are you sure you want to deploy?", - "conflict": "The server is running a more recent set of flows." + "conflict": "The server is running a more recent set of flows.", + "conflictChecking": "Checking to see if the changes can be merged automatically", + "conflictAutoMerge": "The changes include no conflicts and can be merged automatically.", + "conflictManualMerge": "The changes include conflicts that must be resolved before they can be deployed." } }, + "diff": { + "unresolvedCount": "__count__ unresolved conflict", + "unresolvedCount_plural": "__count__ unresolved conflicts", + "type": { + "added": "added", + "changed": "changed", + "unchanged": "unchanged", + "deleted": "deleted", + "flowDeleted": "flow deleted", + "flowAdded": "flow added", + "movedTo": "moved to __id__", + "movedFrom": "moved from __id__" + }, + "nodeCount": "__count__ node", + "nodeCount_plural": "__count__ nodes", + "local":"Local", + "remote":"Remote" + + }, "subflow": { "editSubflow": "Edit flow template: __name__", "edit": "Edit flow template", @@ -167,11 +191,12 @@ "selectAll": "Select all nodes", "selectAllConnected": "Select all connected nodes", "addRemoveNode": "Add/remove node from selection", + "editSelected": "Edit selected node", "deleteSelected": "Delete selected nodes or link", "importNode": "Import nodes", "exportNode": "Export nodes", - "nudgeNode": "Move selected node(s) by a small amount", - "moveNode": "Move selected node(s) by a large amount", + "nudgeNode": "Move selected nodes (1px)", + "moveNode": "Move selected nodes (20px)", "toggleSidebar": "Toggle sidebar", "deleteNode": "Delete selected nodes or link", "copyNode": "Copy selected nodes", @@ -316,6 +341,11 @@ "add": "add" }, "search": { - "empty": "No matches found" + "empty": "No matches found", + "addNode": "add a node..." + }, + "expressionEditor": { + "functions": "Functions", + "insert": "Insert" } } diff --git a/red/api/locales/en-US/infotips.json b/red/api/locales/en-US/infotips.json new file mode 100644 index 000000000..ca4fdf630 --- /dev/null +++ b/red/api/locales/en-US/infotips.json @@ -0,0 +1,32 @@ +{ + "info": { + "tip0" : "You can remove the selected nodes or links with {{core:delete}}", + "tip1" : "Search for nodes using {{core:search}}", + "tip2" : "{{core:toggle-sidebar}} will toggle the view of this sidebar", + "tip3" : "You can manage your palette of nodes with {{core:manage-palette}}", + "tip4" : "Your flow configuration nodes are listed in the sidebar panel. It can been accessed from the menu or with {{core:show-config-tab}}", + "tip5" : "Enable or disable these tips from the option in the menu", + "tip6" : "Move the selected nodes using the [left] [up] [down] and [right] keys. Hold [shift] to nudge them further", + "tip7" : "Dragging a node onto a wire will splice it into the link", + "tip8" : "Export the selected nodes, or the current tab with {{core:export}}", + "tip9" : "Import a flow by dragging its JSON into the editor, or with {{core:import}}", + "tip10" : "[shift] [click] and drag on a node port to move all of the attached wires or just the selected one", + "tip11" : "Show the Info tab with {{core:show-info-tab}} or the Debug tab with {{core:show-debug-tab}}", + "tip12" : "[ctrl] [click] in the workspace to open the quick-add dialog", + "tip13" : "Hold down [ctrl] when you [click] on a node port to enable quick-wiring", + "tip14" : "Hold down [shift] when you [click] on a node to also select all of its connected nodes", + "tip15" : "Hold down [ctrl] when you [click] on a node to add or remove it from the current selection", + "tip16" : "Switch flow tabs with {{core:show-previous-tab}} and {{core:show-next-tab}}", + "tip17" : "You can confirm your changes in the node edit tray with {{core:confirm-edit-tray}} or cancel them with {{core:cancel-edit-tray}}" + }, + "info-tbd": { + "tip1" : "Press the Deploy button above to start the flow running. You can choose to deploy the whole flow or just the changes.", + "tip2" : "Options like Show grid and Snap to grid are under the menu icon
                    View", + "tip4" : " Manage palette can be used to find, add and remove extra nodes.", + "tip5" : "Nodes may install examples under
                    Import Examples", + "tip6" : "Lots of example flows can be found on
                    flows.nodered.org
                    They can then be imported by drag and drop to the workspace.", + "tip7" : "Shift-click and drag on a connector to move all the attached wires in one go.", + "tip8" : "The Node-RED Dashboard package can be used to create simple User Interfaces.", + "tip10": "Got a question?
                    Join us on Slack
                    or the
                    Node-RED Google group" + } +} diff --git a/red/api/locales/en-US/jsonata.json b/red/api/locales/en-US/jsonata.json new file mode 100644 index 000000000..d91a12c96 --- /dev/null +++ b/red/api/locales/en-US/jsonata.json @@ -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." + } + + +} diff --git a/red/api/theme.js b/red/api/theme.js index 74c765abb..22acc313b 100644 --- a/red/api/theme.js +++ b/red/api/theme.js @@ -31,7 +31,9 @@ var defaultContext = { image: "red/images/node-red.png" }, asset: { - red: (process.env.NODE_ENV == "development")? "red/red.js":"red/red.min.js" + red: (process.env.NODE_ENV == "development")? "red/red.js":"red/red.min.js", + main: (process.env.NODE_ENV == "development")? "red/main.js":"red/main.min.js", + } }; diff --git a/red/runtime/i18n.js b/red/runtime/i18n.js index 44cdf4b44..f555cb5fa 100644 --- a/red/runtime/i18n.js +++ b/red/runtime/i18n.js @@ -24,6 +24,13 @@ var defaultLang = "en-US"; var resourceMap = {}; var resourceCache = {}; +function registerMessageCatalogs(catalogs) { + var promises = catalogs.map(function(catalog) { + return registerMessageCatalog(catalog.namespace,catalog.dir,catalog.file); + }); + return when.settle(promises); +} + function registerMessageCatalog(namespace,dir,file) { return when.promise(function(resolve,reject) { resourceMap[namespace] = { basedir:dir, file:file}; @@ -109,9 +116,10 @@ function getCatalog(namespace,lang) { var obj = module.exports = { init: init, registerMessageCatalog: registerMessageCatalog, + registerMessageCatalogs: registerMessageCatalogs, catalog: getCatalog, i: i18n, - defaultLang:defaultLang + defaultLang: defaultLang } obj['_'] = function() { diff --git a/red/runtime/index.js b/red/runtime/index.js index f0ea0fab3..838cafaff 100644 --- a/red/runtime/index.js +++ b/red/runtime/index.js @@ -92,7 +92,7 @@ function start() { reportMetrics(); }, settings.runtimeMetricInterval||15000); } - console.log("\n\n"+log._("runtime.welcome")+"\n===================\n"); + log.info("\n\n"+log._("runtime.welcome")+"\n===================\n"); if (settings.version) { log.info(log._("runtime.version",{component:"Node-RED",version:"v"+settings.version})); } diff --git a/red/runtime/nodes/flows/index.js b/red/runtime/nodes/flows/index.js index 8aa980e06..b0358235e 100644 --- a/red/runtime/nodes/flows/index.js +++ b/red/runtime/nodes/flows/index.js @@ -58,6 +58,7 @@ function init(runtime) { log.info(log._("nodes.flows.registered-missing", {type:type})); activeFlowConfig.missingTypes.splice(i,1); if (activeFlowConfig.missingTypes.length === 0 && started) { + events.emit("runtime-event",{id:"runtime-state"}); start(); } } @@ -238,6 +239,7 @@ function start(type,diff,muteLog) { log.info(log._("nodes.flows.missing-type-install-2")); log.info(" "+settings.userDir); } + events.emit("runtime-event",{id:"runtime-state",type:"warning",text:"notification.warnings.missing-types"}); return when.resolve(); } if (!muteLog) { @@ -287,6 +289,8 @@ function start(type,diff,muteLog) { } } events.emit("nodes-started"); + events.emit("runtime-event",{id:"runtime-state"}); + if (!muteLog) { if (diff) { log.info(log._("nodes.flows.started-modified-"+type)); diff --git a/red/runtime/util.js b/red/runtime/util.js index cae03a4be..4ed1cd4d3 100644 --- a/red/runtime/util.js +++ b/red/runtime/util.js @@ -15,6 +15,7 @@ **/ var clone = require("clone"); +var jsonata = require("jsonata"); function generateId() { return (1+Math.random()*4294967295).toString(16); @@ -128,6 +129,9 @@ function compareObjects(obj1,obj2) { } function normalisePropertyExpression(str) { + // This must be kept in sync with validatePropertyExpression + // in editor/js/ui/utils.js + var length = str.length; var parts = []; var start = 0; @@ -139,7 +143,7 @@ function normalisePropertyExpression(str) { var c = str[i]; if (!inString) { if (c === "'" || c === '"') { - if (!inBox) { + if (i != start) { throw new Error("Invalid property expression: unexpected "+c+" at position "+i); } inString = true; @@ -200,10 +204,15 @@ function normalisePropertyExpression(str) { } } else { if (c === quoteChar) { + if (i-start === 0) { + throw new Error("Invalid property expression: zero-length string at position "+start); + } parts.push(str.substring(start,i)); - // Next char must be a ] - if (!/\]/.test(str[i+1])) { + // If inBox, next char must be a ]. Otherwise it may be [ or . + if (inBox && !/\]/.test(str[i+1])) { throw new Error("Invalid property expression: unexpected array expression at position "+start); + } else if (!inBox && i+1!==length && !/[\[\.]/.test(str[i+1])) { + throw new Error("Invalid property expression: unexpected "+str[i+1]+" expression at position "+(i+1)); } start = i+1; inString = false; @@ -310,6 +319,8 @@ function evaluateNodeProperty(value, type, node, msg) { return node.context().global.get(value); } else if (type === 'bool') { return /^true$/i.test(value); + } else if (type === 'jsonata') { + return jsonata(value).evaluate({msg:msg}); } return value; } diff --git a/test/nodes/core/core/58-debug_spec.js b/test/nodes/core/core/58-debug_spec.js index 8170efc05..5ab0f0b57 100644 --- a/test/nodes/core/core/58-debug_spec.js +++ b/test/nodes/core/core/58-debug_spec.js @@ -49,7 +49,7 @@ describe('debug node', function() { }, function(msg) { JSON.parse(msg).should.eql({ topic:"debug",data:{id:"n1",name:"Debug",msg:"test", - format:"string [4]",property:"payload"} + format:"string[4]",property:"payload"} }); }, done); }); @@ -64,7 +64,7 @@ describe('debug node', function() { n1.emit("input", {payload:"test"}); }, function(msg) { JSON.parse(msg).should.eql({ - topic:"debug",data:{id:"n1",msg:"test",property:"payload",format:"string [4]"} + topic:"debug",data:{id:"n1",msg:"test",property:"payload",format:"string[4]"} }); count++; }, function() { @@ -108,7 +108,7 @@ describe('debug node', function() { n1.emit("input", {payload:"test", foo:"bar"}); }, function(msg) { JSON.parse(msg).should.eql({ - topic:"debug",data:{id:"n1",msg:"bar",property:"foo",format:"string [3]"} + topic:"debug",data:{id:"n1",msg:"bar",property:"foo",format:"string[3]"} }); }, done); }); @@ -122,7 +122,7 @@ describe('debug node', function() { n1.emit("input", {payload:"test", foo: {bar: "bar"}}); }, function(msg) { JSON.parse(msg).should.eql({ - topic:"debug",data:{id:"n1",msg:"bar",property:"foo.bar",format:"string [3]"} + topic:"debug",data:{id:"n1",msg:"bar",property:"foo.bar",format:"string[3]"} }); }, done); }); @@ -194,7 +194,7 @@ describe('debug node', function() { }, function(msg) { JSON.parse(msg).should.eql({ topic:"debug", - data:{id:"n1",msg: '[\n 0,\n 1,\n 2,\n 3\n]',format:"array [4]", + data:{id:"n1",msg: '[\n 0,\n 1,\n 2,\n 3\n]',format:"array[4]", property:"payload"} }); }, done); @@ -214,7 +214,7 @@ describe('debug node', function() { topic:"debug", data:{ id:"n1", - msg:'{\n "name": "bar",\n "o": "[circular]"\n}', + msg:'{\n "name": "bar",\n "o": "[Circular ~]"\n}', property:"payload",format:"Object" } }); @@ -222,7 +222,7 @@ describe('debug node', function() { }); }); - it('should truncated a long message', function(done) { + it('should truncate a long message', function(done) { var flow = [{id:"n1", type:"debug" }]; helper.load(debugNode, flow, function() { var n1 = helper.getNode("n1"); @@ -234,9 +234,9 @@ describe('debug node', function() { topic:"debug", data:{ id:"n1", - msg: Array(1001).join("X")+' ....', + msg: Array(1001).join("X")+'...', property:"payload", - format:"string [1001]" + format:"string[1001]" } }); }, done); @@ -256,7 +256,7 @@ describe('debug node', function() { id:"n1", msg: '48454c4c4f', property:"payload", - format: "buffer [5]" + format: "buffer[5]" } }); }, done); @@ -277,7 +277,7 @@ describe('debug node', function() { }); }, function(msg) { JSON.parse(msg).should.eql({ - topic:"debug",data:{id:"n1",msg:"message 2",property:"payload",format:"string [9]"} + topic:"debug",data:{id:"n1",msg:"message 2",property:"payload",format:"string[9]"} }); }, done); }); diff --git a/test/nodes/core/core/80-template_spec.js b/test/nodes/core/core/80-template_spec.js index b93601989..686f77ede 100644 --- a/test/nodes/core/core/80-template_spec.js +++ b/test/nodes/core/core/80-template_spec.js @@ -43,6 +43,52 @@ describe('template node', function() { }); }); + it('should modify payload from flow context', function(done) { + var flow = [{id:"n1",z:"t1", type:"template", field:"payload", template:"payload={{flow.value}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}]; + helper.load(templateNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n1.context().flow.set("value","foo"); + n2.on("input", function(msg) { + msg.should.have.property('topic', 'bar'); + msg.should.have.property('payload', 'payload=foo'); + done(); + }); + n1.receive({payload:"foo",topic: "bar"}); + }); + }); + + it('should modify payload from global context', function(done) { + var flow = [{id:"n1",z:"t1", type:"template", field:"payload", template:"payload={{global.value}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}]; + helper.load(templateNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n1.context().global.set("value","foo"); + n2.on("input", function(msg) { + msg.should.have.property('topic', 'bar'); + msg.should.have.property('payload', 'payload=foo'); + done(); + }); + n1.receive({payload:"foo",topic: "bar"}); + }); + }); + + it('should handle missing node context', function(done) { + // this is artificial test because in flow there is missing z property (probably never happen in real usage) + var flow = [{id:"n1",type:"template", field:"payload", template:"payload={{flow.value}},{{global.value}}",wires:[["n2"]]},{id:"n2",type:"helper"}]; + helper.load(templateNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property('topic', 'bar'); + msg.should.have.property('payload', 'payload=,'); + done(); + }); + n1.receive({payload:"foo",topic: "bar"}); + }); + }); + + it('should modify payload in plain text mode', function(done) { var flow = [{id:"n1", type:"template", field:"payload", syntax:"plain", template:"payload={{payload}}",wires:[["n2"]]},{id:"n2",type:"helper"}]; helper.load(templateNode, flow, function() { @@ -57,32 +103,36 @@ describe('template node', function() { }); }); - xit('should modify flow context', function(done) { - var flow = [{id:"n1", type:"template", field:"payload", fieldType:"flow", template:"payload={{payload}}",wires:[["n2"]]},{id:"n2",type:"helper"}]; + it('should modify flow context', function(done) { + var flow = [{id:"n1",z:"t1", type:"template", field:"payload", fieldType:"flow", template:"payload={{payload}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}]; helper.load(templateNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); - setTimeout( function() { - console.log(n2); - console.log(n2.context().global.get("payload")); - //c.should.equal(1); // should only have had one output. + n2.on("input", function(msg) { + // mesage is intact + msg.should.have.property('topic', 'bar'); + msg.should.have.property('payload', 'foo'); + // result is in flow context + n2.context().flow.get("payload").should.equal("payload=foo"); done(); - },50); + }); n1.receive({payload:"foo",topic: "bar"}); }); }); - xit('should modify global context', function(done) { - var flow = [{id:"n1", type:"template", field:"payload", fieldType:"global", template:"payload={{payload}}",wires:[["n2"]]},{id:"n2",type:"helper"}]; + it('should modify global context', function(done) { + var flow = [{id:"n1",z:"t1", type:"template", field:"payload", fieldType:"global", template:"payload={{payload}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}]; helper.load(templateNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); - setTimeout( function() { - console.log(n2); - console.log(n2.context().global.get("payload")); - //c.should.equal(1); // should only have had one output. + n2.on("input", function(msg) { + // mesage is intact + msg.should.have.property('topic', 'bar'); + msg.should.have.property('payload', 'foo'); + // result is in global context + n2.context().global.get("payload").should.equal("payload=foo"); done(); - },50); + }); n1.receive({payload:"foo",topic: "bar"}); }); }); diff --git a/test/nodes/core/parsers/70-XML_spec.js b/test/nodes/core/parsers/70-XML_spec.js index 3c96eec4b..d9b83701e 100644 --- a/test/nodes/core/parsers/70-XML_spec.js +++ b/test/nodes/core/parsers/70-XML_spec.js @@ -27,7 +27,7 @@ describe('XML node', function() { afterEach(function() { helper.unload(); }); - + it('should be loaded', function(done) { var flow = [{id:"xmlNode1", type:"xml", name: "xmlNode" }]; helper.load(xmlNode, flow, function() { @@ -56,7 +56,27 @@ describe('XML node', function() { n1.receive({payload:string,topic: "bar"}); }); }); - + + it('should convert a valid xml string to a javascript object with options', function(done) { + var flow = [{id:"n1",type:"xml",wires:[["n2"]],func:"return msg;"}, + {id:"n2", type:"helper"}]; + helper.load(xmlNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property('topic', 'bar'); + msg.payload.should.have.property('employees'); + msg.payload.employees.should.have.property('firstName'); + should.equal(msg.payload.employees.firstName[0], 'John'); + msg.payload.employees.should.have.property('lastName'); + should.equal(msg.payload.employees.lastName[0], 'Smith'); + done(); + }); + var string = 'JohnSmith'; + n1.receive({payload:string, topic:"bar", options:{trim:true}}); + }); + }); + it('should convert a javascript object to an xml string', function(done) { var flow = [{id:"n1",type:"xml",wires:[["n2"]],func:"return msg;"}, {id:"n2", type:"helper"}]; @@ -73,7 +93,24 @@ describe('XML node', function() { n1.receive({payload:obj,topic: "bar"}); }); }); - + + it('should convert a javascript object to an xml string with options', function(done) { + var flow = [{id:"n1",type:"xml",wires:[["n2"]],func:"return msg;"}, + {id:"n2", type:"helper"}]; + helper.load(xmlNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property('topic', 'bar'); + var index = msg.payload.indexOf('\n John\n Smith\n'); + index.should.be.above(-1); + done(); + }); + var obj = {"employees":{"firstName":["John"],"lastName":["Smith"] }}; + n1.receive({payload:obj, topic:"bar", options:{headless:true}}); + }); + }); + it('should log an error if asked to parse an invalid xml string', function(done) { var flow = [{id:"n1",type:"xml",wires:[["n2"]],func:"return msg;"}, {id:"n2", type:"helper"}]; @@ -89,7 +126,6 @@ describe('XML node', function() { logEvents.should.have.length(1); logEvents[0][0].should.have.a.property('msg'); logEvents[0][0].msg.toString().should.startWith("Error: Attribute without value"); - done(); } catch(err) { done(err); diff --git a/test/nodes/core/parsers/70-YAML_spec.js b/test/nodes/core/parsers/70-YAML_spec.js new file mode 100644 index 000000000..3e0dd9bdc --- /dev/null +++ b/test/nodes/core/parsers/70-YAML_spec.js @@ -0,0 +1,154 @@ +/** + * Copyright 2014 IBM Corp. + * + * 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. + **/ + +var should = require("should"); +var yamlNode = require("../../../../nodes/core/parsers/70-YAML.js"); +var helper = require("../../helper.js"); + +describe('YAML node', function() { + + before(function(done) { + helper.startServer(done); + }); + + afterEach(function() { + helper.unload(); + }); + + it('should be loaded', function(done) { + var flow = [{id:"yamlNode1", type:"yaml", name: "yamlNode" }]; + helper.load(yamlNode, flow, function() { + var yamlNode1 = helper.getNode("yamlNode1"); + yamlNode1.should.have.property('name', 'yamlNode'); + done(); + }); + }); + + it('should convert a valid yaml string to a javascript object', function(done) { + var flow = [{id:"yn1",type:"yaml",wires:[["yn2"]],func:"return msg;"}, + {id:"yn2", type:"helper"}]; + helper.load(yamlNode, flow, function() { + var yn1 = helper.getNode("yn1"); + var yn2 = helper.getNode("yn2"); + yn2.on("input", function(msg) { + msg.should.have.property('topic', 'bar'); + msg.payload.should.have.property('employees'); + msg.payload.employees[0].should.have.property('firstName', 'John'); + msg.payload.employees[0].should.have.property('lastName', 'Smith'); + done(); + }); + var yamlString = "employees:\n - firstName: John\n lastName: Smith\n"; + yn1.receive({payload:yamlString,topic: "bar"}); + }); + }); + + it('should convert a javascript object to a yaml string', function(done) { + var flow = [{id:"yn1",type:"yaml",wires:[["yn2"]],func:"return msg;"}, + {id:"yn2", type:"helper"}]; + helper.load(yamlNode, flow, function() { + var yn1 = helper.getNode("yn1"); + var yn2 = helper.getNode("yn2"); + yn2.on("input", function(msg) { + should.equal(msg.payload, "employees:\n - firstName: John\n lastName: Smith\n"); + done(); + }); + var obj = {employees:[{firstName:"John", lastName:"Smith"}]}; + yn1.receive({payload:obj}); + }); + }); + + it('should convert an array to a yaml string', function(done) { + var flow = [{id:"yn1",type:"yaml",wires:[["yn2"]],func:"return msg;"}, + {id:"yn2", type:"helper"}]; + helper.load(yamlNode, flow, function() { + var yn1 = helper.getNode("yn1"); + var yn2 = helper.getNode("yn2"); + yn2.on("input", function(msg) { + should.equal(msg.payload, "- 1\n- 2\n- 3\n"); + done(); + }); + var obj = [1,2,3]; + yn1.receive({payload:obj}); + }); + }); + + it('should log an error if asked to parse an invalid yaml string', function(done) { + var flow = [{id:"yn1",type:"yaml",wires:[["yn2"]],func:"return msg;"}, + {id:"yn2", type:"helper"}]; + helper.load(yamlNode, flow, function() { + try { + var yn1 = helper.getNode("yn1"); + var yn2 = helper.getNode("yn2"); + yn1.receive({payload:'employees:\n-firstName: John\n- lastName: Smith\n',topic: "bar"}); + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "yaml"; + }); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.should.startWith("end of the stream"); + logEvents[0][0].should.have.a.property('level',helper.log().ERROR); + done(); + } catch(err) { + done(err); + } + }); + }); + + it('should log an error if asked to parse something thats not yaml or js', function(done) { + var flow = [{id:"yn1",type:"yaml",wires:[["yn2"]],func:"return msg;"}, + {id:"yn2", type:"helper"}]; + helper.load(yamlNode, flow, function() { + var yn1 = helper.getNode("yn1"); + var yn2 = helper.getNode("yn2"); + setTimeout(function() { + try { + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "yaml"; + }); + logEvents.should.have.length(3); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.toString().should.eql('yaml.errors.dropped'); + logEvents[1][0].should.have.a.property('msg'); + logEvents[1][0].msg.toString().should.eql('yaml.errors.dropped'); + logEvents[2][0].should.have.a.property('msg'); + logEvents[2][0].msg.toString().should.eql('yaml.errors.dropped-object'); + done(); + } catch(err) { + done(err); + } + },150); + yn1.receive({payload:true}); + yn1.receive({payload:1}); + yn1.receive({payload:new Buffer("a")}); + }); + }); + + it('should pass straight through if no payload set', function(done) { + var flow = [{id:"yn1",type:"yaml",wires:[["yn2"]],func:"return msg;"}, + {id:"yn2", type:"helper"}]; + helper.load(yamlNode, flow, function() { + var yn1 = helper.getNode("yn1"); + var yn2 = helper.getNode("yn2"); + yn2.on("input", function(msg) { + msg.should.have.property('topic', 'bar'); + msg.should.not.have.property('payload'); + done(); + }); + yn1.receive({topic: "bar"}); + }); + }); + +}); diff --git a/test/red/runtime/util_spec.js b/test/red/runtime/util_spec.js index 9a4dbe37c..c5772d38f 100644 --- a/test/red/runtime/util_spec.js +++ b/test/red/runtime/util_spec.js @@ -332,6 +332,10 @@ describe("red/util", function() { it("pass a.0.c",function() { testABC("a.0.c",['a',0,'c']); }) it("pass a['a.b[0]'].c",function() { testABC("a['a.b[0]'].c",['a','a.b[0]','c']); }) it("pass a[0][0][0]",function() { testABC("a[0][0][0]",['a',0,0,0]); }) + it("pass '1.2.3.4'",function() { testABC("'1.2.3.4'",['1.2.3.4']); }) + it("pass 'a.b'[1]",function() { testABC("'a.b'[1]",['a.b',1]); }) + it("pass 'a.b'.c",function() { testABC("'a.b'.c",['a.b','c']); }) + it('pass a.$b.c',function() { testABC('a.$b.c',['a','$b','c']); }) it('pass a["$b"].c',function() { testABC('a["$b"].c',['a','$b','c']); }) @@ -355,5 +359,8 @@ describe("red/util", function() { it("fail a. b",function() { testInvalid("a. b"); }) it("fail a.b",function() { testInvalid(" a.b"); }) it("fail a[0].[1]",function() { testInvalid("a[0].[1]"); }) + it("fail a['']",function() { testInvalid("a['']"); }) + it("fail 'a.b'c",function() { testInvalid("'a.b'c"); }) + }); });