From 194eb4e266fdd226bbee9446b7401d728826a2ae Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 27 Apr 2022 11:23:13 +0100 Subject: [PATCH] code editor ux improvements * Save and restore editor selection(s), cursor(s), scroll pos etc * Improve focusing of editor at appropriate times * Works with both ace and monaco * Backwards compatible and (almost) fully functional with existing nodes --- .../src/js/ui/common/typedInput.js | 6 ++ .../editor-client/src/js/ui/editor.js | 65 ++++++++++++- .../editor-client/src/js/ui/editors/buffer.js | 15 ++- .../src/js/ui/editors/code-editors/ace.js | 56 ++++++++++- .../src/js/ui/editors/code-editors/monaco.js | 97 +++++++++++++++---- .../src/js/ui/editors/expression.js | 15 ++- .../editor-client/src/js/ui/editors/js.js | 19 ++-- .../editor-client/src/js/ui/editors/json.js | 18 ++-- .../src/js/ui/editors/markdown.js | 14 ++- .../src/js/ui/editors/panes/description.js | 7 +- .../src/js/ui/editors/panes/flowProperties.js | 1 + .../editor-client/src/js/ui/editors/text.js | 25 +++-- .../src/js/ui/projects/projectSettings.js | 1 + .../@node-red/editor-client/src/js/ui/tray.js | 8 +- .../nodes/core/function/10-function.html | 46 ++++++--- .../nodes/core/function/80-template.html | 24 ++--- 16 files changed, 322 insertions(+), 95 deletions(-) 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 1d649d6e0..59199b9ec 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 6017fb687..cc7443175 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 @@ -796,6 +796,7 @@ RED.editor = (function() { if (buildingEditDialog) { return } buildingEditDialog = true; var editing_node = node; + var removeInfoEditorOnClose = false; var skipInfoRefreshOnClose = false; var activeEditPanes = []; @@ -991,6 +992,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"); @@ -1006,8 +1015,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(); @@ -1867,6 +1885,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'); } @@ -1883,6 +1943,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..9a9b582d1 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..80a212a72 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 @@ -346,6 +356,9 @@ expressionEditor.destroy(); testDataEditor.destroy(); testResultEditor.destroy(); + delete expressionEditor; + delete testDataEditor; + delete testResultEditor; }, show: function() {} } 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..5ab25177f 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,17 @@ }, 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(); + delete expressionEditor; }, 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 52ab820ac..400e001a1 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 @@ -434,6 +434,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) { @@ -455,15 +456,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(); } }, @@ -485,7 +487,8 @@ } else if (activeTab === "json-raw") { result = expressionEditor.getValue(); } - if (onComplete) { onComplete(result) } + expressionEditor.saveView(); + if (onComplete) { onComplete(result,null,expressionEditor) }; RED.tray.close(); } } @@ -531,10 +534,12 @@ expressionEditor = RED.editor.createEditor({ id: 'node-input-json', - value: "", - mode:"ace/mode/json" + value: value||"", + mode:"ace/mode/json", + stateId: options.stateId, + focus: true }); - expressionEditor.getSession().setValue(value||"",-1); + if (options.requireValid) { expressionEditor.getSession().on('change', function() { clearTimeout(changeTimer); @@ -598,14 +603,13 @@ content: $("#red-ui-editor-type-json-tab-ui") }); finishedBuild = true; - - }, close: function() { if (options.onclose) { options.onclose(); } expressionEditor.destroy(); + delete expressionEditor; }, show: function() {} } 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..68f99d07b 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,18 @@ }); 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(); + delete expressionEditor; }, 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..35c4b16d0 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..e64e5e902 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,28 @@ 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..6d15394bb 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 8d4145a68..83904c16a 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 @@ -399,11 +399,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(); + } } } }); @@ -438,11 +446,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, @@ -462,11 +472,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 @@ -505,28 +516,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 98d4a0d37..a560782bb 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 @@ - + @@ -73,7 +73,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"); @@ -90,10 +91,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({ @@ -103,7 +104,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(); @@ -113,20 +113,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); } }) })