1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Merge branch 'dev' into diagnostics

This commit is contained in:
Nick O'Leary 2022-04-27 22:30:13 +01:00 committed by GitHub
commit ff57de0753
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 496 additions and 105 deletions

View File

@ -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);
}

View File

@ -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,

View File

@ -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
});

View File

@ -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;
}

View File

@ -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 = $("<div>").appendTo(el);
el = $("<div>").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;
}

View File

@ -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

View File

@ -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() {}
}

View File

@ -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);

View File

@ -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() {}
}

View File

@ -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;
}

View File

@ -19,6 +19,7 @@
this.tabflowEditor = RED.editor.createEditor({
id: 'node-input-info',
mode: 'ace/mode/markdown',
stateId: options.stateId,
value: ""
});

View File

@ -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<rows.size();i++) {
// height -= $(rows[i]).outerHeight(true);
// }
// height -= (parseInt($("#dialog-form").css("marginTop"))+parseInt($("#dialog-form").css("marginBottom")));
$(".node-text-editor").css("height",height+"px");
expressionEditor.resize();
},
open: function(tray) {
var trayBody = tray.find('.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-text',
value: "",
mode:"ace/mode/"+(options.mode||"text")
value: value||"",
stateId: options.stateId,
mode:"ace/mode/"+(options.mode||"text"),
focus: true,
});
expressionEditor.getSession().setValue(value||"",-1);
if (options.cursor) {
if (options.cursor && !expressionEditor._initState) {
expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
}
},
close: function() {
expressionEditor.destroy();
if (options.onclose) {
options.onclose();
}
expressionEditor.destroy();
},
show: function() {}
}

View File

@ -120,6 +120,7 @@ RED.projects.settings = (function() {
title: RED._('sidebar.project.editDescription'),
header: $('<span><i class="fa fa-book"></i> README.md</span>'),
value: activeProject.description,
stateId: "sidebar.project.editDescription",
complete: function(v) {
container.empty();
var spinner = utils.addSpinnerOverlay(container);

View File

@ -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});

View File

@ -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));

View File

@ -18,7 +18,7 @@
<option value="handlebars">mustache</option>
<option value="html">HTML</option>
<option value="json">JSON</option>
<option value="javascript">Javascript</option>
<option value="javascript">JavaScript</option>
<option value="css">CSS</option>
<option value="markdown">Markdown</option>
<option value="python">Python</option>
@ -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);
}
})
})

View File

@ -3,6 +3,7 @@
<div class="form-row node-input-filename">
<label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="file.label.filename"></span></label>
<input id="node-input-filename" type="text">
<input type="hidden" id="node-input-filenameType">
</div>
<div class="form-row">
<label for="node-input-overwriteFile"><i class="fa fa-random"></i> <span data-i18n="file.label.action"></span></label>
@ -29,7 +30,7 @@
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
<input type="text" id="node-input-name">
</div>
<div class="form-tips"><span data-i18n="file.tip"></span></div>
</script>
@ -37,7 +38,8 @@
<script type="text/html" data-template-name="file in">
<div class="form-row">
<label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="file.label.filename"></span></label>
<input id="node-input-filename" type="text" data-i18n="[placeholder]file.label.filename">
<input id="node-input-filename" type="text">
<input type="hidden" id="node-input-filenameType">
</div>
<div class="form-row">
<label for="node-input-format"><i class="fa fa-sign-out"></i> <span data-i18n="file.label.outputas"></span></label>
@ -60,7 +62,7 @@
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
<input type="text" id="node-input-name">
</div>
<div class="form-tips"><span data-i18n="file.tip"></span></div>
</script>
@ -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 = $("<optgroup/>", {
@ -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 = $("<optgroup/>", {

View File

@ -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));
}

View File

@ -20,7 +20,9 @@
<h3>Inputs</h3>
<dl class="message-properties">
<dt class="optional">filename <span class="property-type">string</span></dt>
<dd>If not configured in the node, this optional property sets the name of the file to be updated.</dd>
<dd>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 <code>msg.filename</code> but this can be customised in the node.
</dd>
<dt class="optional">encoding <span class="property-type">string</span></dt>
<dd>If encoding is configured to be set by msg, then this optional property can set the encoding.</dt>
</dl>
@ -43,7 +45,9 @@
<h3>Inputs</h3>
<dl class="message-properties">
<dt class="optional">filename <span class="property-type">string</span></dt>
<dd>if not set in the node configuration, this property sets the filename to read.</dd>
<dd>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 <code>msg.filename</code> but this can be customised in the node.
</dd>
</dl>
<h3>Outputs</h3>
<dl class="message-properties">

View File

@ -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"]]},