diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js index f6b93f840..aa05ad695 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js @@ -198,6 +198,8 @@ } RED.editor.editJSON({ value: value, + stateId: RED.editor.generateViewStateId("typedInput", that, "json"), + focus: true, complete: function(v) { var value = v; try { @@ -220,6 +222,8 @@ var that = this; RED.editor.editExpression({ value: this.value().replace(/\t/g,"\n"), + stateId: RED.editor.generateViewStateId("typedInput", that, "jsonata"), + focus: true, complete: function(v) { that.value(v.replace(/\n/g,"\t")); } @@ -234,6 +238,8 @@ var that = this; RED.editor.editBuffer({ value: this.value(), + stateId: RED.editor.generateViewStateId("typedInput", that, "bin"), + focus: true, complete: function(v) { that.value(v); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index f0f3a073b..8aa86cbea 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -868,6 +868,7 @@ RED.editor = (function() { if (buildingEditDialog) { return } buildingEditDialog = true; var editing_node = node; + var removeInfoEditorOnClose = false; var skipInfoRefreshOnClose = false; var activeEditPanes = []; @@ -1063,6 +1064,14 @@ RED.editor = (function() { } if (!node._def.defaults || !node._def.defaults.hasOwnProperty('info')) { nodeEditPanes.push('editor-tab-description'); + removeInfoEditorOnClose = true; + if(node.infoEditor) { + //As 'editor-tab-description' adds `node.infoEditor` store original & set a + //flag to NOT remove this property + node.infoEditor__orig = node.infoEditor; + delete node.infoEditor; + removeInfoEditorOnClose = false; + } } nodeEditPanes.push("editor-tab-appearance"); @@ -1078,8 +1087,17 @@ RED.editor = (function() { if (RED.view.state() != RED.state.IMPORT_DRAGGING) { RED.view.state(RED.state.DEFAULT); } - if (editing_node && !skipInfoRefreshOnClose) { - RED.sidebar.info.refresh(editing_node); + if (editing_node) { + if (editing_node.infoEditor__orig) { + editing_node.infoEditor = editing_node.infoEditor__orig; + delete editing_node.infoEditor__orig; + } + if (removeInfoEditorOnClose) { + delete editing_node.infoEditor; + } + if (!skipInfoRefreshOnClose) { + RED.sidebar.info.refresh(editing_node); + } } RED.workspaces.refresh(); @@ -1939,6 +1957,48 @@ RED.editor = (function() { } } + /** Genrate a consistent but unique ID for saving and restoring the code editors view state */ + function generateViewStateId(source, thing, suffix) { + try { + thing = thing || {}; + const thingOptions = typeof thing.options === "object" ? thing.options : {}; + let stateId; + if (thing.hasOwnProperty("stateId")) { + stateId = thing.stateId + } else if (thingOptions.hasOwnProperty("stateId")) { + stateId = thing.stateId + } + if (stateId === false) { return false; } + if (!stateId) { + let id; + const selection = RED.view.selection(); + if (source === "node" && thing.id) { + id = thing.id; + } else if (selection.nodes && selection.nodes.length) { + id = selection.nodes[0].id; + } else { + return false; //cant obtain Id. + } + //Use a string builder to build an ID + const sb = [id]; + //get the index of the el - there may be more than one editor. + const el = $(thing.element || thingOptions.element); + if(el.length) { + sb.push(el.closest(".form-row").index()); + sb.push(el.index()); + } + if (source == "typedInput") { + sb.push(el.closest("li").index());//for when embeded in editable list + if (!suffix && thing.propertyType) { suffix = thing.propertyType } + } + stateId = sb.join("/"); + } + if (stateId && suffix) { stateId += "/" + suffix; } + return stateId; + } catch (error) { + return false; + } + } return { init: function() { if(window.ace) { window.ace.config.set('basePath', 'vendor/ace'); } @@ -1955,6 +2015,7 @@ RED.editor = (function() { }); RED.editor.codeEditor.init(); }, + generateViewStateId: generateViewStateId, edit: showEditDialog, editConfig: showEditConfigNodeDialog, editFlow: showEditFlowDialog, diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/buffer.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/buffer.js index a1e244290..f47ec508b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/buffer.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/buffer.js @@ -47,6 +47,7 @@ var definition = { show: function(options) { var value = options.value; + var onCancel = options.cancel; var onComplete = options.complete; var type = "_buffer" if ($("script[data-template-name='"+type+"']").length === 0) { @@ -60,12 +61,14 @@ var trayOptions = { title: options.title, + focusElement: options.focusElement, width: "inherit", buttons: [ { id: "node-dialog-cancel", text: RED._("common.label.cancel"), click: function() { + if (onCancel) { onCancel(); } RED.tray.close(); } }, @@ -74,7 +77,8 @@ text: RED._("common.label.done"), class: "primary", click: function() { - onComplete(JSON.stringify(bufferBinValue)); + bufferStringEditor.saveView(); + if (onComplete) { onComplete(JSON.stringify(bufferBinValue),null,bufferStringEditor); } RED.tray.close(); } } @@ -86,19 +90,20 @@ } }, open: function(tray) { - var trayBody = tray.find('.red-ui-tray-body'); var dialogForm = RED.editor.buildEditForm(tray.find('.red-ui-tray-body'),'dialog-form',type,'editor'); - bufferStringEditor = RED.editor.createEditor({ id: 'red-ui-editor-type-buffer-str', - value: "", + value: value||"", + stateId: RED.editor.generateViewStateId("buffer", options, ""), + focus: true, mode:"ace/mode/text" }); - bufferStringEditor.getSession().setValue(value||"",-1); bufferBinEditor = RED.editor.createEditor({ id: 'red-ui-editor-type-buffer-bin', value: "", + stateId: false, + focus: false, mode:"ace/mode/text", readOnly: true }); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/ace.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/ace.js index caec3006c..02b65528d 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/ace.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/ace.js @@ -80,6 +80,9 @@ RED.editor.codeEditor.ace = (function() { } },100); } + if (!options.stateId && options.stateId !== false) { + options.stateId = RED.editor.generateViewStateId("ace", options, (options.mode || options.title).split("/").pop()); + } if (options.mode === 'ace/mode/markdown') { $(el).addClass("red-ui-editor-text-container-toolbar"); editor.toolbar = RED.editor.customEditTypes['_markdown'].buildToolbar(toolbarRow,editor); @@ -92,11 +95,15 @@ RED.editor.codeEditor.ace = (function() { RED.editor.editMarkdown({ value: value, width: "Infinity", - cursor: editor.getCursorPosition(), + stateId: options.stateId, + focus: true, + cancel: function () { + editor.focus(); + }, complete: function(v,cursor) { editor.setValue(v, -1); - editor.gotoLine(cursor.row+1,cursor.column,false); setTimeout(function() { + editor.restoreView(); editor.focus(); },300); } @@ -117,11 +124,56 @@ RED.editor.codeEditor.ace = (function() { editor._destroy = editor.destroy; editor.destroy = function() { try { + editor.saveView(); + editor._initState = null; this._destroy(); } catch (e) { } $(el).remove(); $(toolbarRow).remove(); } + editor.on("blur", function () { + editor.focusMemory = false; + editor.saveView(); + }) + editor.on("focus", function () { + if (editor._initState) { + editor.restoreView(editor._initState); + editor._initState = null; + } + }) + editor.getView = function () { + var session = editor.getSession(); + return { + selection: session.selection.toJSON(), + scrollTop: session.getScrollTop(), + scrollLeft: session.getScrollLeft(), + options: session.getOptions() + } + } + editor.saveView = function () { + if (!options.stateId) { return; } //only possible if created with a unique stateId + window._editorStateAce = window._editorStateAce || {}; + var state = editor.getView(); + window._editorStateAce[options.stateId] = state; + return state; + } + editor.restoreView = function (state) { + if (!options.stateId) { return; } //only possible if created with a unique stateId + window._editorStateAce = window._editorStateAce || {}; + var _state = state || window._editorStateAce[options.stateId]; + if (!_state) { return; } //no view state available + try { + var session = editor.getSession(); + session.setOptions(_state.options); + session.selection.fromJSON(_state.selection); + session.setScrollTop(_state.scrollTop); + session.setScrollLeft(_state.scrollLeft); + editor._initState = _state; + } catch (error) { + delete window._editorStateMonaco[options.stateId]; + } + }; + editor.restoreView(); editor.type = type; return editor; } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js index d1a674dd5..701e3da44 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js @@ -171,7 +171,7 @@ RED.editor.codeEditor.monaco = (function() { options = options || {}; window.MonacoEnvironment = window.MonacoEnvironment || {}; - window.MonacoEnvironment.getWorkerUrl = function (moduleId, label) { + window.MonacoEnvironment.getWorkerUrl = window.MonacoEnvironment.getWorkerUrl || function (moduleId, label) { if (label === 'json') { return './vendor/monaco/dist/json.worker.js'; } if (label === 'css' || label === 'scss') { return './vendor/monaco/dist/css.worker.js'; } if (label === 'html' || label === 'handlebars') { return './vendor/monaco/dist/html.worker.js'; } @@ -747,13 +747,25 @@ RED.editor.codeEditor.monaco = (function() { mode = "html"; break; case "appcache": + case "sh": + case "bash": mode = "shell"; break; + case "batchfile": + mode = "bat"; + break; + case "protobuf": + mode = "proto"; + break; //TODO: add other compatability types. } return mode; } + + if(!options.stateId && options.stateId !== false) { + options.stateId = RED.editor.generateViewStateId("monaco", options, (options.mode || options.title).split("/").pop()); + } var el = options.element || $("#"+options.id)[0]; var toolbarRow = $("
").appendTo(el); el = $("
").appendTo(el).addClass("red-ui-editor-text-container")[0]; @@ -1098,6 +1110,7 @@ RED.editor.codeEditor.monaco = (function() { try { var m = this.getModel(); if(m && !m.isDisposed()) { + ed._initState = null; m.dispose(); } this.setModel(null); @@ -1243,14 +1256,7 @@ RED.editor.codeEditor.monaco = (function() { //#endregion "ACE compatability" //final setup - if (options.cursor) { - var row = options.cursor.row || options.cursor.lineNumber; - var col = options.cursor.column || options.cursor.col; - ed.gotoLine(row, col); - } - if (options.focus) { - ed.focus(); - } + ed.focusMemory = options.focus; ed._mode = editorOptions.language; //as models are signleton, consts and let are avialable to other javascript instances @@ -1262,11 +1268,12 @@ RED.editor.codeEditor.monaco = (function() { } ed.onDidBlurEditorWidget(function() { + ed.focusMemory = false; + ed.saveView(); if(isVisible(el) == false) { onVisibilityChange(false, 0, el); } }); - ed.onDidFocusEditorWidget(function() { onVisibilityChange(true, 10, el); }); @@ -1300,17 +1307,33 @@ RED.editor.codeEditor.monaco = (function() { } function onVisibilityChange(visible, delay, element) { - if(visible) { - if(ed._mode == "javascript" && ed._tempMode == "text") { + delay = delay || 50; + if (visible) { + if (ed.focusMemory) { + setTimeout(function () { + if (element.parentElement) { //ensure el is still in DOM + ed.focus(); + } + }, 300) + } + if (ed._initState) { + setTimeout(function () { + if (element.parentElement) { //ensure el is still in DOM + ed.restoreViewState(ed._initState); + ed._initState = null; + } + }, delay); + } + if (ed._mode == "javascript" && ed._tempMode == "text") { ed._tempMode = ""; - setTimeout(function() { - if(element.parentElement) { //ensure el is still in DOM + setTimeout(function () { + if (element.parentElement) { //ensure el is still in DOM ed.setMode('javascript', undefined, false); } - }, delay || 50); + }, delay); } - } else if(ed._mode == "javascript" && ed._tempMode != "text") { - if(element.parentElement) { //ensure el is still in DOM + } else if (ed._mode == "javascript" && ed._tempMode != "text") { + if (element.parentElement) { //ensure el is still in DOM ed.setMode('text', undefined, false); ed._tempMode = "text"; } @@ -1329,15 +1352,19 @@ RED.editor.codeEditor.monaco = (function() { expandButton.on("click", function (e) { e.preventDefault(); var value = ed.getValue(); + ed.saveView(); RED.editor.editMarkdown({ value: value, width: "Infinity", - cursor: ed.getCursorPosition(), + stateId: options.stateId, + cancel: function () { + ed.focus(); + }, complete: function (v, cursor) { ed.setValue(v, -1); - ed.gotoLine(cursor.row + 1, cursor.column, false); setTimeout(function () { ed.focus(); + ed.restoreView(); }, 300); } }) @@ -1353,7 +1380,37 @@ RED.editor.codeEditor.monaco = (function() { autoClose: 50 }); } - + ed.getView = function () { + return ed.saveViewState(); + } + ed.saveView = function (debuginfo) { + if (!options.stateId) { return; } //only possible if created with a unique stateId + window._editorStateMonaco = window._editorStateMonaco || {}; + var state = ed.getView(); + window._editorStateMonaco[options.stateId] = state; + return state; + } + ed.restoreView = function (state) { + if (!options.stateId) { return; } //only possible if created with a unique stateId + window._editorStateMonaco = window._editorStateMonaco || {}; + var _state = state || window._editorStateMonaco[options.stateId]; + if (!_state) { return; } //no view state available + try { + if (ed.type) { //is editor already initialised? + ed.restoreViewState(_state); + } else { + ed._initState = _state; + } + } catch (error) { + delete window._editorStateMonaco[options.stateId]; + } + }; + ed.restoreView(); + if (options.cursor && !ed._initState) { + var row = options.cursor.row || options.cursor.lineNumber; + var col = options.cursor.column || options.cursor.col; + ed.gotoLine(row, col); + } ed.type = type; return ed; } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/expression.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/expression.js index 7f8eb4265..b3c4c3848 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/expression.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/expression.js @@ -50,6 +50,7 @@ show: function(options) { var expressionTestCacheId = options.parent||"_"; var value = options.value; + var onCancel = options.cancel; var onComplete = options.complete; var type = "_expression" if ($("script[data-template-name='"+type+"']").length === 0) { @@ -63,12 +64,14 @@ var trayOptions = { title: options.title, + focusElement: options.focusElement, width: "inherit", buttons: [ { id: "node-dialog-cancel", text: RED._("common.label.cancel"), click: function() { + if(onCancel) { onCancel(); } RED.tray.close(); } }, @@ -78,7 +81,8 @@ class: "primary", click: function() { $("#red-ui-editor-type-expression-help").text(""); - onComplete(expressionEditor.getValue()); + expressionEditor.saveView(); + if (onComplete) { onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition(),expressionEditor); } RED.tray.close(); } } @@ -110,6 +114,8 @@ id: 'red-ui-editor-type-expression', value: "", mode:"ace/mode/jsonata", + stateId: options.stateId, + focus: true, options: { enableBasicAutocompletion:true, enableSnippets:true, @@ -233,6 +239,8 @@ testDataEditor = RED.editor.createEditor({ id: 'red-ui-editor-type-expression-test-data', value: expressionTestCache[expressionTestCacheId] || '{\n "payload": "hello world"\n}', + stateId: false, + focus: false, mode:"ace/mode/json", lineNumbers: false }); @@ -302,6 +310,8 @@ testResultEditor = RED.editor.createEditor({ id: 'red-ui-editor-type-expression-test-result', value: "", + stateId: false, + focus: false, mode:"ace/mode/json", lineNumbers: false, readOnly: true diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/js.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/js.js index 8619455f2..2073c6050 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/js.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/js.js @@ -21,6 +21,7 @@ var definition = { show: function(options) { var value = options.value; + var onCancel = options.cancel; var onComplete = options.complete; var type = "_js" if ($("script[data-template-name='"+type+"']").length === 0) { @@ -28,16 +29,16 @@ } RED.view.state(RED.state.EDITING); var expressionEditor; - var changeTimer; - var trayOptions = { title: options.title, + focusElement: options.focusElement, width: options.width||"inherit", buttons: [ { id: "node-dialog-cancel", text: RED._("common.label.cancel"), click: function() { + if (onCancel) { onCancel(); } RED.tray.close(); } }, @@ -46,7 +47,8 @@ text: RED._("common.label.done"), class: "primary", click: function() { - onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition()); + expressionEditor.saveView(); + if (onComplete) { onComplete(expressionEditor.getValue(), expressionEditor.getCursorPosition(), expressionEditor); } RED.tray.close(); } } @@ -62,11 +64,12 @@ expressionEditor.resize(); }, open: function(tray) { - var trayBody = tray.find('.red-ui-tray-body'); var dialogForm = RED.editor.buildEditForm(tray.find('.red-ui-tray-body'),'dialog-form',type,'editor'); expressionEditor = RED.editor.createEditor({ id: 'node-input-js', mode: options.mode || 'ace/mode/javascript', + stateId: options.stateId, + focus: true, value: value, globals: { msg:true, @@ -84,19 +87,16 @@ }, extraLibs: options.extraLibs }); - if (options.cursor) { + if (options.cursor && !expressionEditor._initState) { expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false); } dialogForm.i18n(); - setTimeout(function() { - expressionEditor.focus(); - },300); }, close: function() { - expressionEditor.destroy(); if (options.onclose) { options.onclose(); } + expressionEditor.destroy(); }, show: function() {} } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/json.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/json.js index c2b7c4905..afb0698a1 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/json.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/json.js @@ -445,6 +445,7 @@ var definition = { show: function(options) { var value = options.value; + var onCancel = options.cancel; var onComplete = options.complete; var type = "_json" if ($("script[data-template-name='"+type+"']").length === 0) { @@ -466,15 +467,16 @@ } } var rootNode; - var trayOptions = { title: options.title, + focusElement: options.focusElement, width: options.width||700, buttons: [ { id: "node-dialog-cancel", text: RED._("common.label.cancel"), click: function() { + if (onCancel) { onCancel(); } RED.tray.close(); } }, @@ -496,7 +498,8 @@ } else if (activeTab === "json-raw") { result = expressionEditor.getValue(); } - if (onComplete) { onComplete(result) } + expressionEditor.saveView(); + if (onComplete) { onComplete(result,null,expressionEditor) } RED.tray.close(); } } @@ -561,9 +564,13 @@ id: 'node-input-json', value: "", mode:"ace/mode/json", - readOnly: !!options.readOnly + value: value||"", + mode:"ace/mode/json", + readOnly: !!options.readOnly, + stateId: options.stateId, + focus: true }); - expressionEditor.getSession().setValue(value||"",-1); + if (options.requireValid) { expressionEditor.getSession().on('change', function() { clearTimeout(changeTimer); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/markdown.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/markdown.js index e0f670c59..eeb8519e6 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/markdown.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/markdown.js @@ -54,24 +54,26 @@ var definition = { show: function(options) { var value = options.value; + var onCancel = options.cancel; var onComplete = options.complete; var type = "_markdown" if ($("script[data-template-name='"+type+"']").length === 0) { $(template).appendTo("#red-ui-editor-node-configs"); } - RED.view.state(RED.state.EDITING); var expressionEditor; var trayOptions = { title: options.title, + focusElement: options.focusElement, width: options.width||Infinity, buttons: [ { id: "node-dialog-cancel", text: RED._("common.label.cancel"), click: function() { + if (onCancel) { onCancel(); } RED.tray.close(); } }, @@ -80,7 +82,8 @@ text: RED._("common.label.done"), class: "primary", click: function() { - onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition()); + expressionEditor.saveView(); + if (onComplete) { onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition(), expressionEditor); } RED.tray.close(); } } @@ -99,6 +102,8 @@ expressionEditor = RED.editor.createEditor({ id: 'red-ui-editor-type-markdown', value: value, + stateId: options.stateId, + focus: true, mode:"ace/mode/markdown", expandable: false }); @@ -143,17 +148,17 @@ }); RED.popover.tooltip($("#node-btn-markdown-preview"), RED._("markdownEditor.toggle-preview")); - if (options.cursor) { + if (options.cursor && !expressionEditor._initState) { expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false); } dialogForm.i18n(); }, close: function() { - expressionEditor.destroy(); if (options.onclose) { options.onclose(); } + expressionEditor.destroy(); }, show: function() {} } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/description.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/description.js index b7be32596..2106ae53b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/description.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/description.js @@ -8,7 +8,6 @@ create: function(container) { this.editor = buildDescriptionForm(container,node); - RED.e = this.editor; }, resize: function(size) { this.editor.resize(); @@ -58,11 +57,9 @@ var nodeInfoEditor = RED.editor.createEditor({ id: editorId, mode: 'ace/mode/markdown', - value: "" + stateId: RED.editor.generateViewStateId("node", node, "nodeinfo"), + value: node.info || "" }); - if (node.info) { - nodeInfoEditor.getSession().setValue(node.info, -1); - } node.infoEditor = nodeInfoEditor; return nodeInfoEditor; } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/flowProperties.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/flowProperties.js index 2db4d0c85..826814bab 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/flowProperties.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/flowProperties.js @@ -19,6 +19,7 @@ this.tabflowEditor = RED.editor.createEditor({ id: 'node-input-info', mode: 'ace/mode/markdown', + stateId: options.stateId, value: "" }); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/text.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/text.js index 1824091fe..ee6a3f11c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/text.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/text.js @@ -21,6 +21,7 @@ var definition = { show: function(options) { var value = options.value; + var onCancel = options.cancel; var onComplete = options.complete; var type = "_text" if ($("script[data-template-name='"+type+"']").length === 0) { @@ -28,16 +29,16 @@ } RED.view.state(RED.state.EDITING); var expressionEditor; - var changeTimer; - var trayOptions = { title: options.title, + focusElement: options.focusElement, width: options.width||"inherit", buttons: [ { id: "node-dialog-cancel", text: RED._("common.label.cancel"), click: function() { + if(onCancel) { onCancel(); } RED.tray.close(); } }, @@ -46,7 +47,8 @@ text: RED._("common.label.done"), class: "primary", click: function() { - onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition()); + expressionEditor.saveView(); + if (onComplete) { onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition(),expressionEditor);} RED.tray.close(); } } @@ -55,31 +57,27 @@ var rows = $("#dialog-form>div:not(.node-text-editor-row)"); var editorRow = $("#dialog-form>div.node-text-editor-row"); var height = $("#dialog-form").height(); - // for (var i=0;i README.md'), value: activeProject.description, + stateId: "sidebar.project.editDescription", complete: function(v) { container.empty(); var spinner = utils.addSpinnerOverlay(container); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tray.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tray.js index 1b331fb9a..0ea6d6044 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tray.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tray.js @@ -169,7 +169,13 @@ raiseTrayZ(); handleWindowResize();//cause call to monaco layout },200); - body.find(":focusable:first").trigger("focus"); + if(!options.hasOwnProperty("focusElement")) { + //focusElement is not inside options - default to focusing 1st + body.find(":focusable:first").trigger("focus"); + } else if(options.focusElement !== false) { + //focusElement IS specified, focus that instead (if not false) + $(options.focusElement).trigger("focus"); + } },150); el.css({right:0}); diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.html b/packages/node_modules/@node-red/nodes/core/function/10-function.html index 4091eb22d..6309ae9ad 100644 --- a/packages/node_modules/@node-red/nodes/core/function/10-function.html +++ b/packages/node_modules/@node-red/nodes/core/function/10-function.html @@ -413,11 +413,19 @@ $("#func-tabs-content").children().hide(); $("#" + tab.id).show(); let editor = $("#" + tab.id).find('.monaco-editor').first(); - if(editor.length) { + if(editor.length) { if(that.editor.nodered && that.editor.type == "monaco") { that.editor.nodered.refreshModuleLibs(getLibsList()); } RED.tray.resize(); + //auto focus editor on tab switch + if (that.initEditor.getDomNode() == editor[0]) { + that.initEditor.focus(); + } else if (that.editor.getDomNode() == editor[0]) { + that.editor.focus(); + } else if (that.finalizeEditor.getDomNode() == editor[0]) { + that.finalizeEditor.focus(); + } } } }); @@ -452,11 +460,13 @@ } }); - var buildEditor = function(id, value, defaultValue, extraLibs) { + var buildEditor = function(id, stateId, focus, value, defaultValue, extraLibs) { var editor = RED.editor.createEditor({ id: id, mode: 'ace/mode/nrjavascript', value: value || defaultValue || "", + stateId: stateId, + focus: true, globals: { msg:true, context:true, @@ -476,11 +486,12 @@ if (defaultValue && value === "") { editor.moveCursorTo(defaultValue.split("\n").length - 1, 0); } + editor.__stateId = stateId; return editor; } - this.initEditor = buildEditor('node-input-init-editor',$("#node-input-initialize").val(),RED._("node-red:function.text.initialize")) - this.editor = buildEditor('node-input-func-editor',$("#node-input-func").val(), undefined, that.libs || []) - this.finalizeEditor = buildEditor('node-input-finalize-editor',$("#node-input-finalize").val(),RED._("node-red:function.text.finalize")) + this.initEditor = buildEditor('node-input-init-editor', this.id + "/" + "initEditor", false, $("#node-input-initialize").val(), RED._("node-red:function.text.initialize")) + this.editor = buildEditor('node-input-func-editor', this.id + "/" + "editor", true, $("#node-input-func").val(), undefined, that.libs || []) + this.finalizeEditor = buildEditor('node-input-finalize-editor', this.id + "/" + "finalizeEditor", false, $("#node-input-finalize").val(), RED._("node-red:function.text.finalize")) RED.library.create({ url:"functions", // where to get the data from @@ -519,28 +530,33 @@ ], ext:"js" }); - this.editor.focus(); - var expandButtonClickHandler = function(editor) { - return function(e) { + return function (e) { e.preventDefault(); var value = editor.getValue(); + editor.saveView(`inside function-expandButtonClickHandler ${editor.__stateId}`); var extraLibs = that.libs || []; RED.editor.editJavaScript({ value: value, width: "Infinity", - cursor: editor.getCursorPosition(), + stateId: editor.__stateId, mode: "ace/mode/nrjavascript", - complete: function(v,cursor) { - editor.setValue(v, -1); - editor.gotoLine(cursor.row+1,cursor.column,false); - setTimeout(function() { + focus: true, + cancel: function () { + setTimeout(function () { editor.focus(); - },300); + }, 250); + }, + complete: function (v, cursor) { + editor.setValue(v, -1); + setTimeout(function () { + editor.restoreView(); + editor.focus(); + }, 250); }, extraLibs: extraLibs - }) + }); } } $("#node-init-expand-js").on("click", expandButtonClickHandler(this.initEditor)); diff --git a/packages/node_modules/@node-red/nodes/core/function/80-template.html b/packages/node_modules/@node-red/nodes/core/function/80-template.html index 9d60d5a94..940487001 100644 --- a/packages/node_modules/@node-red/nodes/core/function/80-template.html +++ b/packages/node_modules/@node-red/nodes/core/function/80-template.html @@ -18,7 +18,7 @@ - + @@ -75,7 +75,8 @@ return this.name?"node_label_italic":""; }, oneditprepare: function() { - var that = this; + const that = this; + const stateId = RED.editor.generateViewStateId("node", this, ""); if (!this.field) { this.field = 'payload'; $("#node-input-field").val("payload"); @@ -92,10 +93,10 @@ types: ['msg','flow','global'], typeField: $("#node-input-fieldType") }); - this.editor = RED.editor.createEditor({ id: 'node-input-template-editor', mode: 'ace/mode/html', + stateId: stateId, value: $("#node-input-template").val() }); RED.library.create({ @@ -105,7 +106,6 @@ fields:['name','format','output','syntax'], ext: "txt" }); - this.editor.focus(); $("#node-input-format").on("change", function() { var mod = "ace/mode/"+$("#node-input-format").val(); @@ -115,20 +115,22 @@ }); }); RED.popover.tooltip($("#node-template-expand-editor"), RED._("node-red:common.label.expand")); - $("#node-template-expand-editor").on("click", function(e) { + $("#node-template-expand-editor").on("click", function (e) { e.preventDefault(); - var value = that.editor.getValue(); + const value = that.editor.getValue(); + that.editor.saveView(); RED.editor.editText({ mode: $("#node-input-format").val(), value: value, + stateId: stateId, width: "Infinity", - cursor: that.editor.getCursorPosition(), - complete: function(v,cursor) { + focus: true, + complete: function (v, cursor) { that.editor.setValue(v, -1); - that.editor.gotoLine(cursor.row+1,cursor.column,false); - setTimeout(function() { + setTimeout(function () { + that.editor.restoreView(); that.editor.focus(); - },300); + }, 250); } }) }) diff --git a/packages/node_modules/@node-red/nodes/core/storage/10-file.html b/packages/node_modules/@node-red/nodes/core/storage/10-file.html index b76a01615..ddddcb687 100755 --- a/packages/node_modules/@node-red/nodes/core/storage/10-file.html +++ b/packages/node_modules/@node-red/nodes/core/storage/10-file.html @@ -3,6 +3,7 @@
+
@@ -29,7 +30,7 @@
- +
@@ -37,7 +38,8 @@ @@ -196,7 +198,8 @@ category: 'storage', defaults: { name: {value:""}, - filename: {value:""}, + filename: {value:"filename"}, + filenameType: {value:"msg"}, appendNewline: {value:true}, createDir: {value:false}, overwriteFile: {value:"false"}, @@ -207,10 +210,13 @@ outputs:1, icon: "file-out.svg", label: function() { + var fn = this.filename; + if(this.filenameType != "str" && this.filenameType != "env" ) { fn = ""; } + if(this.filenameType === "env") { fn = "env."+fn; } if (this.overwriteFile === "delete") { - return this.name||this._("file.label.deletelabel",{file:this.filename}); + return this.name||this._("file.label.deletelabel",{file:fn}); } else { - return this.name||this.filename||this._("file.label.write"); + return this.name||fn||this._("file.label.write"); } }, paletteLabel: RED._("node-red:file.label.write"), @@ -229,6 +235,31 @@ value: "setbymsg", label: node._("file.encoding.setbymsg") }).text(label).appendTo(encSel); + $("#node-input-filename").typedInput({ + default: "msg", + types:[{ value: "str", label:"", icon:"red/images/typedInput/az.svg"}, "msg", "jsonata", "env"], + typeField: $("#node-input-filenameType") + }); + if(typeof node.filenameType == 'undefined') { + //existing node AND filenameType is not set - inplace (compatible) upgrade to new typedInput + if(node.filename == "") { //was using empty value to denote msg.filename - set typedInput to match + node.filename = "filename"; + node.filenameType = "msg"; + $("#node-input-filename").typedInput("type", node.filenameType); + $("#node-input-filename").typedInput("value", node.filename); + } else if(/^\${[^}]+}$/.test(node.filename)) { //was using an ${ENV_VAR} + node.filenameType = "env"; + node.filename = node.filename.replace(/\${([^}]+)}/g, function(match, name) { + return (name === undefined)?"":name; + }); + $("#node-input-filename").typedInput("type", node.filenameType); + $("#node-input-filename").typedInput("value", node.filename); + } else { //was using a static filename - set typedInput type to str + node.filenameType = "str"; + $("#node-input-filename").typedInput("type", node.filenameType); + $("#node-input-filename").typedInput("value", node.filename); + } + } encodings.forEach(function(item) { if(Array.isArray(item)) { var group = $("", { @@ -266,7 +297,8 @@ category: 'storage', defaults: { name: {value:""}, - filename: {value:""}, + filename: {value:"filename"}, + filenameType: {value:"msg"}, format: {value:"utf8"}, chunk: {value:false}, sendError: {value: false}, @@ -291,7 +323,10 @@ }, icon: "file-in.svg", label: function() { - return this.name||this.filename||this._("file.label.read"); + var fn = this.filename; + if(this.filenameType != "str" && this.filenameType != "env" ) { fn = ""; } + if(this.filenameType === "env") { fn = "env."+fn; } + return this.name||fn||this._("file.label.read"); }, paletteLabel: RED._("node-red:file.label.read"), labelStyle: function() { @@ -305,6 +340,31 @@ value: "none", label: label }).text(label).appendTo(encSel); + $("#node-input-filename").typedInput({ + default: "msg", + types:[{ value: "str", label:"", icon:"red/images/typedInput/az.svg"}, "msg", "jsonata", "env"], + typeField: $("#node-input-filenameType") + }); + if(typeof node.filenameType == 'undefined') { + //existing node AND filenameType is not set - inplace (compatible) upgrade to new typedInput + if(node.filename == "") { //was using empty value to denote msg.filename - set typedInput to match + node.filename = "filename"; + node.filenameType = "msg"; + $("#node-input-filename").typedInput("type", node.filenameType); + $("#node-input-filename").typedInput("value", node.filename); + } else if(/^\${[^}]+}$/.test(node.filename)) { //was using an ${ENV_VAR} + node.filenameType = "env"; + node.filename = node.filename.replace(/\${([^}]+)}/g, function(match, name) { + return (name === undefined)?"":name; + }); + $("#node-input-filename").typedInput("type", node.filenameType); + $("#node-input-filename").typedInput("value", node.filename); + } else { //was using a static filename - set typedInput type to str + node.filenameType = "str"; + $("#node-input-filename").typedInput("type", node.filenameType); + $("#node-input-filename").typedInput("value", node.filename); + } + } encodings.forEach(function(item) { if(Array.isArray(item)) { var group = $("", { diff --git a/packages/node_modules/@node-red/nodes/core/storage/10-file.js b/packages/node_modules/@node-red/nodes/core/storage/10-file.js index ba81125fe..d0db89d01 100644 --- a/packages/node_modules/@node-red/nodes/core/storage/10-file.js +++ b/packages/node_modules/@node-red/nodes/core/storage/10-file.js @@ -39,6 +39,7 @@ module.exports = function(RED) { // Write/delete a file RED.nodes.createNode(this,n); this.filename = n.filename; + this.filenameType = n.filenameType; this.appendNewline = n.appendNewline; this.overwriteFile = n.overwriteFile.toString(); this.createDir = n.createDir || false; @@ -50,7 +51,28 @@ module.exports = function(RED) { node.closeCallback = null; function processMsg(msg,nodeSend, done) { - var filename = node.filename || msg.filename || ""; + var filename = node.filename || ""; + //Pre V3 compatibility - if filenameType is empty, do in place upgrade + if(typeof node.filenameType == 'undefined' || node.filenameType == "") { + //existing node AND filenameType is not set - inplace (compatible) upgrade + if(filename == "") { //was using empty value to denote msg.filename + node.filename = "filename"; + node.filenameType = "msg"; + } else { //was using a static filename - set typedInput type to str + node.filenameType = "str"; + } + } + + RED.util.evaluateNodeProperty(node.filename,node.filenameType,node,msg,(err,value) => { + if (err) { + node.error(err,msg); + return done(); + } else { + filename = value; + } + }); + filename = filename || ""; + msg.filename = filename; var fullFilename = filename; if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) { fullFilename = path.resolve(path.join(RED.settings.fileWorkingDirectory,filename)); @@ -158,7 +180,7 @@ module.exports = function(RED) { done(); }); } - if (node.filename) { + if (node.filenameType === "str" || node.filenameType === "env") { // Static filename - write and reuse the stream next time node.wstream.write(buf, function() { nodeSend(msg); @@ -256,6 +278,7 @@ module.exports = function(RED) { // Read a file RED.nodes.createNode(this,n); this.filename = n.filename; + this.filenameType = n.filenameType; this.format = n.format; this.chunk = false; this.encoding = n.encoding || "none"; @@ -270,8 +293,28 @@ module.exports = function(RED) { var node = this; this.on("input",function(msg, nodeSend, nodeDone) { - var filename = (node.filename || msg.filename || "").replace(/\t|\r|\n/g,''); + var filename = node.filename || ""; + //Pre V3 compatibility - if filenameType is empty, do in place upgrade + if(typeof node.filenameType == 'undefined' || node.filenameType == "") { + //existing node AND filenameType is not set - inplace (compatible) upgrade + if(filename == "") { //was using empty value to denote msg.filename + node.filename = "filename"; + node.filenameType = "msg"; + } else { //was using a static filename - set typedInput type to str + node.filenameType = "str"; + } + } + RED.util.evaluateNodeProperty(node.filename,node.filenameType,node,msg,(err,value) => { + if (err) { + node.error(err,msg); + return done(); + } else { + filename = (value || "").replace(/\t|\r|\n/g,''); + } + }); + filename = filename || ""; var fullFilename = filename; + var filePath = ""; if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) { fullFilename = path.resolve(path.join(RED.settings.fileWorkingDirectory,filename)); } diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html index 70400f676..d4a1fd39d 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html @@ -20,7 +20,9 @@

Inputs

filename string
-
If not configured in the node, this optional property sets the name of the file to be updated.
+
The name of the file to be updated can be provided in the node configuration, or as a message property. + By default it will use msg.filename but this can be customised in the node. +
encoding string
If encoding is configured to be set by msg, then this optional property can set the encoding.
@@ -43,7 +45,9 @@

Inputs

filename string
-
if not set in the node configuration, this property sets the filename to read.
+
The name of the file to be read can be provided in the node configuration, or as a message property. + By default it will use msg.filename but this can be customised in the node. +

Outputs

diff --git a/test/nodes/core/storage/10-file_spec.js b/test/nodes/core/storage/10-file_spec.js index 99ed23978..9d5aa033f 100644 --- a/test/nodes/core/storage/10-file_spec.js +++ b/test/nodes/core/storage/10-file_spec.js @@ -48,6 +48,7 @@ describe('file Nodes', function() { beforeEach(function(done) { //fs.writeFileSync(fileToTest, "File message line 1\File message line 2\n"); + process.env.TEST_FILE = fileToTest; helper.startServer(done); }); @@ -58,6 +59,7 @@ describe('file Nodes', function() { //fs.unlinkSync(fileToTest); helper.stopServer(done); }); + delete process.env.TEST_FILE }); it('should be loaded', function(done) { @@ -343,6 +345,64 @@ describe('file Nodes', function() { n1.receive({payload:"fine", filename:fileToTest}); }); }); + it('should use msg._user_specified_filename set in nodes typedInput', function(done) { + var flow = [{id:"fileNode1", type:"file", filename:"_user_specified_filename", filenameType: "msg", name: "fileNode", "appendNewline":true, "overwriteFile":true, wires: [["helperNode1"]]}, + {id:"helperNode1", type:"helper"}]; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileNode1"); + var n2 = helper.getNode("helperNode1"); + + n2.on("input", function (msg) { + try { + msg.should.have.property("payload", "typedInput"); + msg.should.have.property("filename", fileToTest); + + var f = fs.readFileSync(fileToTest).toString(); + if (os.type() !== "Windows_NT") { + f.should.equal("typedInput\n"); + } + else { + f.should.equal("typedInput\r\n"); + } + done(); + } + catch (e) { + done(e); + } + }); + + n1.receive({payload:"typedInput", _user_specified_filename:fileToTest}); + }); + }); + it('should use env.TEST_FILE set in nodes typedInput', function(done) { + var flow = [{id:"fileNode1", type:"file", filename:"TEST_FILE", filenameType: "env", name: "fileNode", "appendNewline":true, "overwriteFile":true, wires: [["helperNode1"]]}, + {id:"helperNode1", type:"helper"}]; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileNode1"); + var n2 = helper.getNode("helperNode1"); + + n2.on("input", function (msg) { + try { + msg.should.have.property("payload", "envTest"); + msg.should.have.property("filename", fileToTest); + + var f = fs.readFileSync(fileToTest).toString(); + if (os.type() !== "Windows_NT") { + f.should.equal("envTest\n"); + } + else { + f.should.equal("envTest\r\n"); + } + done(); + } + catch (e) { + done(e); + } + }); + + n1.receive({payload:"envTest"}); + }); + }); it('should be able to delete the file', function(done) { var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":"delete", wires: [["helperNode1"]]},