Compare commits

..

1 Commits

Author SHA1 Message Date
Nick O'Leary
c294532152 Fix undo history of moves and post-deploy handling 2024-04-23 22:29:42 +02:00
33 changed files with 186 additions and 274 deletions

View File

@@ -1,47 +1,3 @@
#### 3.1.15: Maintenance Release
- Bump express 3.1.x (#4988) @hardillb
#### 3.1.14: Maintenance Release
- Update mermaid version
#### 3.1.13: Maintenance Release
- Update cookie/DOMPurify dependencies
#### 3.1.12: Maintenance Release
- Update express/body-parser dependencies
#### 3.1.11: Maintenance Release
- Add/Update German Translations for delay node (#4762) @dceejay
- Update ws dependency
#### 3.1.10: Maintenance Release
- Include rewired nodes when calculating Modified Flows stop list (#4754) @knolleary
- Fix clone of group env var properties (#4753) @knolleary
- Fix losing links when importing a copy of links into a subflow (#4750) @GogoVega
- Ensure all CSS variables are in the output file (#3743) @bonanitech
- Fix the Sidebar Config is not refreshed after a deploy (#4734) @GogoVega
- Fix checkboxes are not updated when calling `typedInput("value", "")` (#4729) @GogoVega
- Fix panning with middle mouse button on windows 10/11 (#4716) @corentin-sodebo-voile
- Add Japanese translation for sidebar tooltip (#4727) @kazuhitoyokoi
- Translate the number of items selected in the options list (#4730) @GogoVega
- Fix a checkbox should return a Boolean value and not the string `on` (#4715) @GogoVega
- Deleting a grouped node should update the group (#4714) @GogoVega
- Change the Config Node cursor to `pointer` (#4711) @GogoVega
- Add missing tooltips to Sidebar (#4713) @GogoVega
- Allow nodes to return additional history entries in onEditSave (#4710) @knolleary
- Pass full error object in Function node and copy over cause property (#4685) @knolleary
- Replacing vm.createScript in favour of vm.Script (#4534) @patlux
- Avoid login loops when autoLogin enabled but login fails (#4684) @knolleary
- Fix undo of subflow env property edits (#4667) @knolleary
- Fix three error typos in monaco.js (#4660) @JoshuaCWebDeveloper
- docs: Add closing paragraph tag (#4664) @ZJvandeWeg
#### 3.1.9: Maintenance Release #### 3.1.9: Maintenance Release
- Prevent subflow being added to itself (#4654) @knolleary - Prevent subflow being added to itself (#4654) @knolleary

View File

@@ -1,6 +1,6 @@
{ {
"name": "node-red", "name": "node-red",
"version": "3.1.15", "version": "3.1.9",
"description": "Low-code programming for event-driven applications", "description": "Low-code programming for event-driven applications",
"homepage": "https://nodered.org", "homepage": "https://nodered.org",
"license": "Apache-2.0", "license": "Apache-2.0",
@@ -32,17 +32,17 @@
"async-mutex": "0.4.0", "async-mutex": "0.4.0",
"basic-auth": "2.0.1", "basic-auth": "2.0.1",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"body-parser": "1.20.3", "body-parser": "1.20.2",
"cheerio": "1.0.0-rc.10", "cheerio": "1.0.0-rc.10",
"clone": "2.1.2", "clone": "2.1.2",
"content-type": "1.0.5", "content-type": "1.0.5",
"cookie": "0.7.2", "cookie": "0.5.0",
"cookie-parser": "1.4.7", "cookie-parser": "1.4.6",
"cors": "2.8.5", "cors": "2.8.5",
"cronosjs": "1.7.1", "cronosjs": "1.7.1",
"denque": "2.1.0", "denque": "2.1.0",
"express": "4.21.2", "express": "4.19.2",
"express-session": "1.18.1", "express-session": "1.17.3",
"form-data": "4.0.0", "form-data": "4.0.0",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"got": "12.6.0", "got": "12.6.0",
@@ -78,14 +78,14 @@
"tough-cookie": "4.1.3", "tough-cookie": "4.1.3",
"uglify-js": "3.17.4", "uglify-js": "3.17.4",
"uuid": "9.0.0", "uuid": "9.0.0",
"ws": "7.5.10", "ws": "7.5.6",
"xml2js": "0.6.2" "xml2js": "0.6.2"
}, },
"optionalDependencies": { "optionalDependencies": {
"bcrypt": "5.1.1" "bcrypt": "5.1.1"
}, },
"devDependencies": { "devDependencies": {
"dompurify": "2.5.7", "dompurify": "2.4.1",
"grunt": "1.6.1", "grunt": "1.6.1",
"grunt-chmod": "~1.1.1", "grunt-chmod": "~1.1.1",
"grunt-cli": "~1.4.3", "grunt-cli": "~1.4.3",
@@ -109,7 +109,7 @@
"jquery-i18next": "1.2.1", "jquery-i18next": "1.2.1",
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template", "jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
"marked": "4.3.0", "marked": "4.3.0",
"mermaid": "11.3.0", "mermaid": "^10.4.0",
"minami": "1.2.3", "minami": "1.2.3",
"mocha": "9.2.2", "mocha": "9.2.2",
"node-red-node-test-helper": "^0.3.3", "node-red-node-test-helper": "^0.3.3",

View File

@@ -205,10 +205,9 @@ function genericStrategy(adminApp,strategy) {
passport.use(new strategy.strategy(options, verify)); passport.use(new strategy.strategy(options, verify));
adminApp.get('/auth/strategy', adminApp.get('/auth/strategy',
passport.authenticate(strategy.name, { passport.authenticate(strategy.name, {session:false,
session:false,
failureMessage: true, failureMessage: true,
failureRedirect: settings.httpAdminRoot + '?session_message=Login Failed' failureRedirect: settings.httpAdminRoot
}), }),
completeGenerateStrategyAuth, completeGenerateStrategyAuth,
handleStrategyError handleStrategyError
@@ -222,7 +221,7 @@ function genericStrategy(adminApp,strategy) {
passport.authenticate(strategy.name, { passport.authenticate(strategy.name, {
session:false, session:false,
failureMessage: true, failureMessage: true,
failureRedirect: settings.httpAdminRoot + '?session_message=Login Failed' failureRedirect: settings.httpAdminRoot
}), }),
completeGenerateStrategyAuth, completeGenerateStrategyAuth,
handleStrategyError handleStrategyError

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/editor-api", "name": "@node-red/editor-api",
"version": "3.1.15", "version": "3.1.9",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@@ -16,14 +16,14 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/util": "3.1.15", "@node-red/util": "3.1.9",
"@node-red/editor-client": "3.1.15", "@node-red/editor-client": "3.1.9",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"body-parser": "1.20.3", "body-parser": "1.20.2",
"clone": "2.1.2", "clone": "2.1.2",
"cors": "2.8.5", "cors": "2.8.5",
"express-session": "1.18.1", "express-session": "1.17.3",
"express": "4.21.2", "express": "4.19.2",
"memorystore": "1.6.7", "memorystore": "1.6.7",
"mime": "3.0.0", "mime": "3.0.0",
"multer": "1.4.5-lts.1", "multer": "1.4.5-lts.1",
@@ -32,7 +32,7 @@
"passport-http-bearer": "1.0.1", "passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2", "passport-oauth2-client-password": "0.1.2",
"passport": "0.6.0", "passport": "0.6.0",
"ws": "7.5.10" "ws": "7.5.6"
}, },
"optionalDependencies": { "optionalDependencies": {
"bcrypt": "5.1.0" "bcrypt": "5.1.0"

View File

@@ -719,7 +719,6 @@
"nodeHelp": "Node Help", "nodeHelp": "Node Help",
"showHelp": "Show help", "showHelp": "Show help",
"showInOutline": "Show in outline", "showInOutline": "Show in outline",
"hideTopics": "Hide topics",
"showTopics": "Show topics", "showTopics": "Show topics",
"noHelp": "No help topic selected", "noHelp": "No help topic selected",
"changeLog": "Change Log" "changeLog": "Change Log"
@@ -915,8 +914,6 @@
} }
}, },
"typedInput": { "typedInput": {
"selected": "__count__ selected",
"selected_plural": "__count__ selected",
"type": { "type": {
"str": "string", "str": "string",
"num": "number", "num": "number",

View File

@@ -719,7 +719,6 @@
"nodeHelp": "Aide sur les noeuds", "nodeHelp": "Aide sur les noeuds",
"showHelp": "Afficher l'aide", "showHelp": "Afficher l'aide",
"showInOutline": "Afficher dans les grandes lignes", "showInOutline": "Afficher dans les grandes lignes",
"hideTopics": "Masquer les sujets",
"showTopics": "Afficher les sujets", "showTopics": "Afficher les sujets",
"noHelp": "Aucune rubrique d'aide sélectionnée", "noHelp": "Aucune rubrique d'aide sélectionnée",
"changeLog": "Journal des modifications" "changeLog": "Journal des modifications"
@@ -915,8 +914,6 @@
} }
}, },
"typedInput": { "typedInput": {
"selected": "__count__ sélectionnée",
"selected_plural": "__count__ sélectionnées",
"type": { "type": {
"str": "chaîne de caractères", "str": "chaîne de caractères",
"num": "nombre", "num": "nombre",

View File

@@ -719,7 +719,6 @@
"nodeHelp": "ノードヘルプ", "nodeHelp": "ノードヘルプ",
"showHelp": "ヘルプを表示", "showHelp": "ヘルプを表示",
"showInOutline": "アウトラインに表示", "showInOutline": "アウトラインに表示",
"hideTopics": "トピックを非表示",
"showTopics": "トピックを表示", "showTopics": "トピックを表示",
"noHelp": "ヘルプのトピックが未選択", "noHelp": "ヘルプのトピックが未選択",
"changeLog": "更新履歴" "changeLog": "更新履歴"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/editor-client", "name": "@node-red/editor-client",
"version": "3.1.15", "version": "3.1.9",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -706,11 +706,36 @@ RED.history = (function() {
} }
function markEventDirty (evt) {
// This isn't 100% thorough - just covers the main move/edit/delete cases
evt.dirty = true
if (evt.multi) {
for (let i = 0; i < evt.events.length-1; i++) {
markEventDirty(evt.events[i])
}
} else if (evt.t === 'move') {
for (let i=0;i<evt.nodes.length;i++) {
evt.nodes[i].moved = true
}
} else if (evt.t === 'edit') {
evt.changed = true
} else if (evt.t === 'delete') {
if (evt.nodes) {
for (let i=0;i<evt.nodes.length;i++) {
evt.nodes[i].changed = true
}
}
}
}
return { return {
//TODO: this function is a placeholder until there is a 'save' event that can be listened to
markAllDirty: function() { markAllDirty: function() {
for (var i=0;i<undoHistory.length;i++) { // A deploy has happened meaning any undo into the history will represent
// an undeployed change - regardless of what it was when the event was recorded.
// This goes back through the history any marks them all as being dirty events
// and also ensures individual node states are marked dirty
for (let i=0;i<undoHistory.length;i++) {
undoHistory[i].dirty = true; undoHistory[i].dirty = true;
markEventDirty(undoHistory[i])
} }
}, },
list: function() { list: function() {

View File

@@ -2379,13 +2379,6 @@ RED.nodes = (function() {
} else { } else {
delete n.g delete n.g
} }
// If importing into a subflow, ensure an outbound-link doesn't get added
if (activeSubflow && /^link /.test(n.type) && n.links) {
n.links = n.links.filter(function(id) {
const otherNode = node_map[id] || RED.nodes.node(id);
return (otherNode && otherNode.z === activeWorkspace);
});
}
for (var d3 in n._def.defaults) { for (var d3 in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d3)) { if (n._def.defaults.hasOwnProperty(d3)) {
if (n._def.defaults[d3].type) { if (n._def.defaults[d3].type) {
@@ -2409,6 +2402,14 @@ RED.nodes = (function() {
} }
} }
} }
// If importing into a subflow, ensure an outbound-link doesn't
// get added
if (activeSubflow && /^link /.test(n.type) && n.links) {
n.links = n.links.filter(function(id) {
const otherNode = node_map[id] || RED.nodes.node(id);
return (otherNode && otherNode.z === activeWorkspace)
});
}
} }
for (i=0;i<new_subflows.length;i++) { for (i=0;i<new_subflows.length;i++) {
n = new_subflows[i]; n = new_subflows[i];

View File

@@ -734,12 +734,12 @@
} }
if (menu.opts.multiple) { if (menu.opts.multiple) {
var selected = {}; var selected = {};
this.value().split(",").forEach(function(f) { this.value().split(",").forEach(function(f) {
selected[f] = true; selected[f] = true;
}); })
menu.find('input[type="checkbox"]').each(function() { menu.find('input[type="checkbox"]').each(function() {
$(this).prop("checked", selected[$(this).data('value')] || false); $(this).prop("checked",selected[$(this).data('value')])
}); })
} }
@@ -830,7 +830,7 @@
this.input.trigger('change',[this.propertyType,this.value()]); this.input.trigger('change',[this.propertyType,this.value()]);
} }
} else { } else {
this.optionSelectLabel.text(RED._("typedInput.selected", { count: o.length })); this.optionSelectLabel.text(o.length+" selected");
} }
} }
}, },

View File

@@ -631,7 +631,6 @@ RED.deploy = (function() {
// Once deployed, cannot undo back to a clean state // Once deployed, cannot undo back to a clean state
RED.history.markAllDirty(); RED.history.markAllDirty();
RED.view.redraw(); RED.view.redraw();
RED.sidebar.config.refresh();
RED.events.emit("deploy"); RED.events.emit("deploy");
}).fail(function (xhr, textStatus, err) { }).fail(function (xhr, textStatus, err) {
RED.nodes.dirty(true); RED.nodes.dirty(true);

View File

@@ -248,8 +248,6 @@ RED.editor = (function() {
var value = input.val(); var value = input.val();
if (defaults[property].hasOwnProperty("format") && defaults[property].format !== "" && input[0].nodeName === "DIV") { if (defaults[property].hasOwnProperty("format") && defaults[property].format !== "" && input[0].nodeName === "DIV") {
value = input.text(); value = input.text();
} else if (input.attr("type") === "checkbox") {
value = input.prop("checked");
} }
var valid = validateNodeProperty(node, defaults, property,value); var valid = validateNodeProperty(node, defaults, property,value);
if (((typeof valid) === "string") || !valid) { if (((typeof valid) === "string") || !valid) {
@@ -743,16 +741,9 @@ RED.editor = (function() {
} }
try { try {
const rc = editing_node._def.oneditsave.call(editing_node); var rc = editing_node._def.oneditsave.call(editing_node);
if (rc === true) { if (rc === true) {
editState.changed = true; editState.changed = true;
} else if (typeof rc === 'object' && rc !== null ) {
if (rc.changed === true) {
editState.changed = true
}
if (Array.isArray(rc.history) && rc.history.length > 0) {
editState.history = rc.history
}
} }
} catch(err) { } catch(err) {
console.warn("oneditsave",editing_node.id,editing_node.type,err.toString()); console.warn("oneditsave",editing_node.id,editing_node.type,err.toString());
@@ -923,17 +914,6 @@ RED.editor = (function() {
dirty: startDirty dirty: startDirty
} }
if (editing_node.g) {
const group = RED.nodes.group(editing_node.g);
// Don't use RED.group.removeFromGroup as that emits
// a change event on the node - but we're deleting it
const index = group?.nodes.indexOf(editing_node) ?? -1;
if (index > -1) {
group.nodes.splice(index, 1);
RED.group.markDirty(group);
}
}
RED.nodes.dirty(true); RED.nodes.dirty(true);
RED.view.redraw(true); RED.view.redraw(true);
RED.history.push(historyEvent); RED.history.push(historyEvent);
@@ -1035,7 +1015,7 @@ RED.editor = (function() {
} }
}); });
} }
let historyEvent = { var historyEvent = {
t:'edit', t:'edit',
node:editing_node, node:editing_node,
changes:editState.changes, changes:editState.changes,
@@ -1051,15 +1031,6 @@ RED.editor = (function() {
instances:subflowInstances instances:subflowInstances
} }
} }
if (editState.history) {
historyEvent = {
t: 'multi',
events: [ historyEvent, ...editState.history ],
dirty: wasDirty
}
}
RED.history.push(historyEvent); RED.history.push(historyEvent);
} }
editing_node.dirty = true; editing_node.dirty = true;

View File

@@ -514,7 +514,7 @@ RED.editor.codeEditor.monaco = (function() {
_monaco.languages.json.jsonDefaults.setDiagnosticsOptions(diagnosticOptions); _monaco.languages.json.jsonDefaults.setDiagnosticsOptions(diagnosticOptions);
if(modeConfiguration) { _monaco.languages.json.jsonDefaults.setModeConfiguration(modeConfiguration); } if(modeConfiguration) { _monaco.languages.json.jsonDefaults.setModeConfiguration(modeConfiguration); }
} catch (error) { } catch (error) {
console.warn("monaco - Error setting up json options", error) console.warn("monaco - Error setting up json options", err)
} }
} }
@@ -526,7 +526,7 @@ RED.editor.codeEditor.monaco = (function() {
if(htmlDefaults) { _monaco.languages.html.htmlDefaults.setOptions(htmlDefaults); } if(htmlDefaults) { _monaco.languages.html.htmlDefaults.setOptions(htmlDefaults); }
if(handlebarDefaults) { _monaco.languages.html.handlebarDefaults.setOptions(handlebarDefaults); } if(handlebarDefaults) { _monaco.languages.html.handlebarDefaults.setOptions(handlebarDefaults); }
} catch (error) { } catch (error) {
console.warn("monaco - Error setting up html options", error) console.warn("monaco - Error setting up html options", err)
} }
} }
@@ -546,7 +546,7 @@ RED.editor.codeEditor.monaco = (function() {
if(lessDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(lessDefaults_modeConfiguration); } if(lessDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(lessDefaults_modeConfiguration); }
if(scssDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(scssDefaults_modeConfiguration); } if(scssDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(scssDefaults_modeConfiguration); }
} catch (error) { } catch (error) {
console.warn("monaco - Error setting up CSS/SCSS/LESS options", error) console.warn("monaco - Error setting up CSS/SCSS/LESS options", err)
} }
} }

View File

@@ -382,11 +382,9 @@ RED.sidebar.config = (function() {
refreshConfigNodeList(); refreshConfigNodeList();
} }
}); });
RED.popover.tooltip($('#red-ui-sidebar-config-filter-all'), RED._("sidebar.config.showAllConfigNodes")); RED.popover.tooltip($('#red-ui-sidebar-config-filter-all'), RED._("sidebar.config.showAllConfigNodes"));
RED.popover.tooltip($('#red-ui-sidebar-config-filter-unused'), RED._("sidebar.config.showAllUnusedConfigNodes")); RED.popover.tooltip($('#red-ui-sidebar-config-filter-unused'), RED._("sidebar.config.showAllUnusedConfigNodes"));
RED.popover.tooltip($('#red-ui-sidebar-config-collapse-all'), RED._("palette.actions.collapse-all"));
RED.popover.tooltip($('#red-ui-sidebar-config-expand-all'), RED._("palette.actions.expand-all"));
} }
function flashConfigNode(el) { function flashConfigNode(el) {

View File

@@ -36,13 +36,7 @@ RED.sidebar.help = (function() {
toolbar = $("<div>", {class:"red-ui-sidebar-header red-ui-info-toolbar"}).appendTo(content); toolbar = $("<div>", {class:"red-ui-sidebar-header red-ui-info-toolbar"}).appendTo(content);
$('<span class="button-group"><a id="red-ui-sidebar-help-show-toc" class="red-ui-button red-ui-button-small selected" href="#"><i class="fa fa-list-ul"></i></a></span>').appendTo(toolbar) $('<span class="button-group"><a id="red-ui-sidebar-help-show-toc" class="red-ui-button red-ui-button-small selected" href="#"><i class="fa fa-list-ul"></i></a></span>').appendTo(toolbar)
var showTOCButton = toolbar.find('#red-ui-sidebar-help-show-toc') var showTOCButton = toolbar.find('#red-ui-sidebar-help-show-toc')
RED.popover.tooltip(showTOCButton, function () { RED.popover.tooltip(showTOCButton,RED._("sidebar.help.showTopics"));
if ($(showTOCButton).hasClass('selected')) {
return RED._("sidebar.help.hideTopics");
} else {
return RED._("sidebar.help.showTopics");
}
});
showTOCButton.on("click",function(e) { showTOCButton.on("click",function(e) {
e.preventDefault(); e.preventDefault();
if ($(this).hasClass('selected')) { if ($(this).hasClass('selected')) {

View File

@@ -1190,7 +1190,6 @@ RED.view = (function() {
if (d3.event.button === 1) { if (d3.event.button === 1) {
// Middle Click pan // Middle Click pan
d3.event.preventDefault();
mouse_mode = RED.state.PANNING; mouse_mode = RED.state.PANNING;
mouse_position = [d3.event.pageX,d3.event.pageY] mouse_position = [d3.event.pageX,d3.event.pageY]
scroll_position = [chart.scrollLeft(),chart.scrollTop()]; scroll_position = [chart.scrollLeft(),chart.scrollTop()];
@@ -2168,9 +2167,9 @@ RED.view = (function() {
if (n.ox !== n.n.x || n.oy !== n.n.y || addedToGroup) { if (n.ox !== n.n.x || n.oy !== n.n.y || addedToGroup) {
// This node has moved or added to a group // This node has moved or added to a group
if (rehomedNodes.has(n)) { if (rehomedNodes.has(n)) {
moveAndChangedGroupEvent.nodes.push({...n}) moveAndChangedGroupEvent.nodes.push({...n, moved: n.n.moved})
} else { } else {
moveEvent.nodes.push({...n}) moveEvent.nodes.push({...n, moved: n.n.moved})
} }
n.n.dirty = true; n.n.dirty = true;
n.n.moved = true; n.n.moved = true;

View File

@@ -37,6 +37,7 @@ ul.red-ui-sidebar-node-config-list {
} }
.red-ui-palette-node { .red-ui-palette-node {
// overflow: hidden; // overflow: hidden;
cursor: default;
&.selected { &.selected {
border-color: transparent; border-color: transparent;
box-shadow: 0 0 0 2px var(--red-ui-node-selected-color); box-shadow: 0 0 0 2px var(--red-ui-node-selected-color);

View File

@@ -194,46 +194,27 @@
nodeMap[node.links[i]].new = true; nodeMap[node.links[i]].new = true;
} }
} }
var n;
let editHistories = [] for (var id in nodeMap) {
let n;
for (let id in nodeMap) {
if (nodeMap.hasOwnProperty(id)) { if (nodeMap.hasOwnProperty(id)) {
n = RED.nodes.node(id); n = RED.nodes.node(id);
if (n) { if (n) {
editHistories.push({
t:'edit',
node: n,
changes: {
links: [...n.links]
},
changed: n.changed
})
if (nodeMap[id].old && !nodeMap[id].new) { if (nodeMap[id].old && !nodeMap[id].new) {
// Removed id // Removed id
i = n.links.indexOf(node.id); i = n.links.indexOf(node.id);
if (i > -1) { if (i > -1) {
n.links.splice(i,1); n.links.splice(i,1);
n.changed = true
n.dirty = true
} }
} else if (!nodeMap[id].old && nodeMap[id].new) { } else if (!nodeMap[id].old && nodeMap[id].new) {
// Added id // Added id
i = n.links.indexOf(id); i = n.links.indexOf(id);
if (i === -1) { if (i === -1) {
n.links.push(node.id); n.links.push(node.id);
n.changed = true
n.dirty = true
} }
} }
} }
} }
} }
if (editHistories.length > 0) {
return {
history: editHistories
}
}
} }
function onAdd() { function onAdd() {
@@ -273,14 +254,13 @@
onEditPrepare(this,"link out"); onEditPrepare(this,"link out");
}, },
oneditsave: function() { oneditsave: function() {
const result = onEditSave(this); onEditSave(this);
// In case the name has changed, ensure any link call nodes on this // In case the name has changed, ensure any link call nodes on this
// tab are redrawn with the updated name // tab are redrawn with the updated name
var localCallNodes = RED.nodes.filterNodes({z:RED.workspaces.active(), type:"link call"}); var localCallNodes = RED.nodes.filterNodes({z:RED.workspaces.active(), type:"link call"});
localCallNodes.forEach(function(node) { localCallNodes.forEach(function(node) {
node.dirty = true; node.dirty = true;
}); });
return result
}, },
onadd: onAdd, onadd: onAdd,
oneditresize: resizeNodeList oneditresize: resizeNodeList
@@ -349,7 +329,7 @@
onEditPrepare(this,"link in"); onEditPrepare(this,"link in");
}, },
oneditsave: function() { oneditsave: function() {
return onEditSave(this); onEditSave(this);
}, },
oneditresize: resizeNodeList oneditresize: resizeNodeList
}); });
@@ -393,7 +373,7 @@
}, },
oneditsave: function() { oneditsave: function() {
return onEditSave(this); onEditSave(this);
}, },
onadd: onAdd, onadd: onAdd,
oneditresize: resizeNodeList oneditresize: resizeNodeList

View File

@@ -374,7 +374,7 @@ module.exports = function(RED) {
iniOpt.breakOnSigint = true; iniOpt.breakOnSigint = true;
} }
} }
node.script = new vm.Script(functionText, createVMOpt(node, "")); node.script = vm.createScript(functionText, createVMOpt(node, ""));
if (node.fin && (node.fin !== "")) { if (node.fin && (node.fin !== "")) {
var finText = `(function () { var finText = `(function () {
var node = { var node = {
@@ -438,9 +438,10 @@ module.exports = function(RED) {
//store the error in msg to be used in flows //store the error in msg to be used in flows
msg.error = err; msg.error = err;
var line = 0;
var errorMessage;
if (stack.length > 0) { if (stack.length > 0) {
let line = 0;
let errorMessage;
while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) { while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) {
line++; line++;
} }
@@ -454,13 +455,11 @@ module.exports = function(RED) {
errorMessage += " (line "+lineno+", col "+cha+")"; errorMessage += " (line "+lineno+", col "+cha+")";
} }
} }
if (errorMessage) {
err.message = errorMessage
}
} }
// Pass the whole error object so any additional properties if (!errorMessage) {
// (such as cause) are preserved errorMessage = err.toString();
done(err); }
done(errorMessage);
} }
else if (typeof err === "string") { else if (typeof err === "string") {
done(err); done(err);

View File

@@ -20,26 +20,12 @@
<dt class="optional">delay <span class="property-type">number</span></dt> <dt class="optional">delay <span class="property-type">number</span></dt>
<dd>Legt die Verzögerung in Millisekunden fest, die auf die Nachricht angewendet werden soll. <dd>Legt die Verzögerung in Millisekunden fest, die auf die Nachricht angewendet werden soll.
Zur Nutzung dieser Option muss <i>Verzög. mit msg.delay überschreibbar</i> aktiviert sein.</dd> Zur Nutzung dieser Option muss <i>Verzög. mit msg.delay überschreibbar</i> aktiviert sein.</dd>
<dt class="optional">rate <span class="property-type">number</span></dt>
<dd>Setzt die Verzögerung in Millisekunden zwischen den Nachrichten. Diese Node überschreibt die
bestehende Verzögerung die in der Node konfiguration, wenn die empfangende Nachricht <code>msg.rate</code>
in Millisekunden enthält. Dies trifft nur zu, wenn in der Node konfiguriert ist, das empfangene
Nachrichten den konfigurierten Wert überschreiben können.</dd>
<dt class="optional">reset</dt> <dt class="optional">reset</dt>
<dd>Wenn bei der empfangenen Nachricht diese Eigenschaft auf einen beliebigen Wert gesetzt ist, <dd>Wenn bei der empfangenen Nachricht diese Eigenschaft auf einen beliebigen Wert gesetzt ist,
werden alle im Node gepufferten Nachrichten gelöscht.</dd> werden alle im Node gepufferten Nachrichten gelöscht.</dd>
<dt class="optional">flush</dt> <dt class="optional">flush</dt>
<dd>Wenn bei der empfangenen Nachricht diese Eigenschaft auf einen beliebigen Wert gesetzt ist, <dd>Wenn bei der empfangenen Nachricht diese Eigenschaft auf einen beliebigen Wert gesetzt ist,
werden alle im Node gepufferten Nachrichten sofort gesendet.</dd> werden alle im Node gepufferten Nachrichten sofort gesendet.</dd>
<dt class="optional">flush</dt>
<dd>Wenn bei der empfangenen Nachricht diese Eigenschaft auf einen numerischen Wert gesetzt ist,
wird diese Anzahl an Nachrichten sofort gesendet. Wenn ein anderer Typ gesetzt ist (z.B. Boolean),
werden alle in der Node gepufferten Nachrichten gesendet.</dd>
<dt class="optional">toFront</dt>
<dd>Wenn diese Eigenschaft im Ratenbegrenzungsmodus für die empfangene Nachricht auf den booleschen Wert
<code>true</code> gesetzt ist, Anschließend wird die Nachricht an den Anfang der Warteschlange verschoben
und als nächstes freigegeben. Dies kann in Kombination mit <code>msg.flush=1</code> verwendet werden, um sofort erneut zu senden.
</dd>
</dl> </dl>
<h3>Details</h3> <h3>Details</h3>
<p>Wenn Verzögerung als Nachrichtenaktion eingestellt ist, kann die Verzögerungszeit ein fixer Wert, <p>Wenn Verzögerung als Nachrichtenaktion eingestellt ist, kann die Verzögerungszeit ein fixer Wert,

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/nodes", "name": "@node-red/nodes",
"version": "3.1.15", "version": "3.1.9",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -18,11 +18,11 @@
"acorn": "8.8.2", "acorn": "8.8.2",
"acorn-walk": "8.2.0", "acorn-walk": "8.2.0",
"ajv": "8.12.0", "ajv": "8.12.0",
"body-parser": "1.20.3", "body-parser": "1.20.2",
"cheerio": "1.0.0-rc.10", "cheerio": "1.0.0-rc.10",
"content-type": "1.0.5", "content-type": "1.0.5",
"cookie-parser": "1.4.7", "cookie-parser": "1.4.6",
"cookie": "0.7.2", "cookie": "0.5.0",
"cors": "2.8.5", "cors": "2.8.5",
"cronosjs": "1.7.1", "cronosjs": "1.7.1",
"denque": "2.1.0", "denque": "2.1.0",
@@ -43,7 +43,7 @@
"raw-body": "2.5.2", "raw-body": "2.5.2",
"tough-cookie": "4.1.3", "tough-cookie": "4.1.3",
"uuid": "9.0.0", "uuid": "9.0.0",
"ws": "7.5.10", "ws": "7.5.6",
"xml2js": "0.6.2", "xml2js": "0.6.2",
"iconv-lite": "0.6.3" "iconv-lite": "0.6.3"
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/registry", "name": "@node-red/registry",
"version": "3.1.15", "version": "3.1.9",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@@ -16,7 +16,7 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/util": "3.1.15", "@node-red/util": "3.1.9",
"clone": "2.1.2", "clone": "2.1.2",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"semver": "7.5.4", "semver": "7.5.4",

View File

@@ -678,9 +678,6 @@ class Flow {
if (logMessage.hasOwnProperty('stack')) { if (logMessage.hasOwnProperty('stack')) {
errorMessage.error.stack = logMessage.stack; errorMessage.error.stack = logMessage.stack;
} }
if (logMessage.hasOwnProperty('cause')) {
errorMessage.error.cause = logMessage.cause;
}
targetCatchNode.receive(errorMessage); targetCatchNode.receive(errorMessage);
handled = true; handled = true;
}); });

View File

@@ -1,6 +1,5 @@
const flowUtil = require("./util"); const flowUtil = require("./util");
const credentials = require("../nodes/credentials"); const credentials = require("../nodes/credentials");
const clone = require("clone");
/** /**
* This class represents a group within the runtime. * This class represents a group within the runtime.

View File

@@ -462,8 +462,9 @@ function stop(type,diff,muteLog,isDeploy) {
if (type === 'nodes') { if (type === 'nodes') {
stopList = diff.changed.concat(diff.removed); stopList = diff.changed.concat(diff.removed);
} else if (type === 'flows') { } else if (type === 'flows') {
stopList = diff.changed.concat(diff.removed).concat(diff.linked).concat(diff.rewired); stopList = diff.changed.concat(diff.removed).concat(diff.linked);
} }
events.emit("flows:stopping",{config: activeConfig, type: type, diff: diff}) events.emit("flows:stopping",{config: activeConfig, type: type, diff: diff})
// Stop the global flow object last // Stop the global flow object last

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/runtime", "name": "@node-red/runtime",
"version": "3.1.15", "version": "3.1.9",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@@ -16,11 +16,11 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/registry": "3.1.15", "@node-red/registry": "3.1.9",
"@node-red/util": "3.1.15", "@node-red/util": "3.1.9",
"async-mutex": "0.4.0", "async-mutex": "0.4.0",
"clone": "2.1.2", "clone": "2.1.2",
"express": "4.21.2", "express": "4.19.2",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"json-stringify-safe": "5.0.1" "json-stringify-safe": "5.0.1"
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/util", "name": "@node-red/util",
"version": "3.1.15", "version": "3.1.9",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -1,6 +1,6 @@
{ {
"name": "node-red", "name": "node-red",
"version": "3.1.15", "version": "3.1.9",
"description": "Low-code programming for event-driven applications", "description": "Low-code programming for event-driven applications",
"homepage": "https://nodered.org", "homepage": "https://nodered.org",
"license": "Apache-2.0", "license": "Apache-2.0",
@@ -31,13 +31,13 @@
"flow" "flow"
], ],
"dependencies": { "dependencies": {
"@node-red/editor-api": "3.1.15", "@node-red/editor-api": "3.1.9",
"@node-red/runtime": "3.1.15", "@node-red/runtime": "3.1.9",
"@node-red/util": "3.1.15", "@node-red/util": "3.1.9",
"@node-red/nodes": "3.1.15", "@node-red/nodes": "3.1.9",
"basic-auth": "2.0.1", "basic-auth": "2.0.1",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"express": "4.21.2", "express": "4.19.2",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"node-red-admin": "^3.1.3", "node-red-admin": "^3.1.3",
"nopt": "5.0.0", "nopt": "5.0.0",

View File

@@ -13,7 +13,7 @@
// 4. Edit your settings file to set the theme: // 4. Edit your settings file to set the theme:
// editorTheme: { // editorTheme: {
// page: { // page: {
// css: '/path/to/file/generated/by/this/script' // css: "/path/to/file/generated/by/this/script"
// } // }
// } // }
// //
@@ -22,69 +22,110 @@
const os = require('os'); const os = require("os");
const nopt = require('nopt'); const nopt = require("nopt");
const path = require('path'); const path = require("path");
const fs = require('fs-extra'); const fs = require("fs-extra");
const sass = require('sass'); const sass = require("sass");
const knownOpts = { const knownOpts = {
'help': Boolean, "help": Boolean,
'long': Boolean, "long": Boolean,
'in': [path], "in": [path],
'out': [path] "out": [path]
}; };
const shortHands = { const shortHands = {
'?':['--help'] "?":["--help"]
}; };
nopt.invalidHandler = function(k,v,t) {} nopt.invalidHandler = function(k,v,t) {}
const parsedArgs = nopt(knownOpts,shortHands,process.argv,2) const parsedArgs = nopt(knownOpts,shortHands,process.argv,2)
if (parsedArgs.help) { if (parsedArgs.help) {
showUsageAndExit(0) console.log("Usage: build-custom-theme [-?] [--in FILE] [--out FILE]");
console.log("");
console.log("Options:");
console.log(" --in FILE Custom colors sass file");
console.log(" --out FILE Where you write the result");
console.log(" --long Do not compress the output");
console.log(" -?, --help Show this help");
console.log("");
process.exit();
} }
if (!parsedArgs.in) {
console.warn('Missing argument: in') const ruleRegex = /(\$.*?) *: *(\S[\S\s]*?);/g;
showUsageAndExit(1) var match;
const customColors = {};
if (parsedArgs.in && fs.existsSync(parsedArgs.in)) {
let customColorsFile = fs.readFileSync(parsedArgs.in,"utf-8");
while((match = ruleRegex.exec(customColorsFile)) !== null) {
customColors[match[1]] = match[2];
}
} }
// Load base colours
let colorsFile = fs.readFileSync(path.join(__dirname,"../packages/node_modules/@node-red/editor-client/src/sass/colors.scss"),"utf-8")
let updatedColors = [];
while((match = ruleRegex.exec(colorsFile)) !== null) {
updatedColors.push(match[1]+": "+(customColors[match[1]]||match[2])+";")
}
(async function() { (async function() {
const tmpDir = os.tmpdir(); const tmpDir = os.tmpdir();
const workingDir = await fs.mkdtemp(`${tmpDir}${path.sep}`); const workingDir = await fs.mkdtemp(`${tmpDir}${path.sep}`);
await fs.copy(path.join(__dirname,"../packages/node_modules/@node-red/editor-client/src/sass/"),workingDir)
await fs.writeFile(path.join(workingDir,"colors.scss"),updatedColors.join("\n"))
await fs.copy(path.join(__dirname, '../packages/node_modules/@node-red/editor-client/src/sass/'), workingDir); const result = sass.renderSync({
await fs.copyFile(parsedArgs.in, path.join(workingDir,'colors.scss')); outputStyle: "expanded",
file: path.join(workingDir,"style-custom-theme.scss"),
});
const output = sass.compile( const css = result.css.toString()
path.join(workingDir, 'style-custom-theme.scss'), const lines = css.split("\n");
{style: parsedArgs.long === true ? 'expanded' : 'compressed'} const colorCSS = []
); const nonColorCSS = [];
const nrPkg = require('../package.json'); let inKeyFrameBlock = false;
lines.forEach(l => {
if (inKeyFrameBlock) {
nonColorCSS.push(l);
if (/^}/.test(l)) {
inKeyFrameBlock = false;
}
} else if (/^@keyframes/.test(l)) {
nonColorCSS.push(l);
inKeyFrameBlock = true;
} else if (!/^ /.test(l)) {
colorCSS.push(l);
nonColorCSS.push(l);
} else if (/color|border|background|fill|stroke|outline|box-shadow/.test(l)) {
colorCSS.push(l);
} else {
nonColorCSS.push(l);
}
});
const nrPkg = require("../package.json");
const now = new Date().toISOString(); const now = new Date().toISOString();
const header = `/*\n* Theme generated with Node-RED ${nrPkg.version} on ${now}\n*/`;
const header = `/*
* Theme generated with Node-RED ${nrPkg.version} on ${now}
*/`;
var output = sass.renderSync({outputStyle: parsedArgs.long?"expanded":"compressed",data:colorCSS.join("\n")});
if (parsedArgs.out) { if (parsedArgs.out) {
await fs.writeFile(parsedArgs.out, header+'\n'+output.css);
await fs.writeFile(parsedArgs.out,header+"\n"+output.css);
} else { } else {
console.log(header); console.log(header);
console.log(output.css.toString()); console.log(output.css.toString());
} }
await fs.remove(workingDir); await fs.remove(workingDir);
})() })()
function showUsageAndExit (exitCode) {
console.log('');
console.log('Usage: build-custom-theme [-?] [--in FILE] [--out FILE]');
console.log('');
console.log('Options:');
console.log(' --in FILE Custom colors sass file');
console.log(' --out FILE Where you write the result');
console.log(' --long Do not compress the output');
console.log(' -?, --help Show this help');
console.log('');
process.exit(exitCode);
}

View File

@@ -4,7 +4,7 @@ const path = require("path");
const fs = require("fs-extra"); const fs = require("fs-extra");
const should = require("should"); const should = require("should");
const LATEST = "4"; const LATEST = "3";
function generateScript() { function generateScript() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View File

@@ -390,8 +390,7 @@ describe('function node', function() {
msg.should.have.property('level', helper.log().ERROR); msg.should.have.property('level', helper.log().ERROR);
msg.should.have.property('id', 'n1'); msg.should.have.property('id', 'n1');
msg.should.have.property('type', 'function'); msg.should.have.property('type', 'function');
msg.should.have.property('msg') msg.should.have.property('msg', 'ReferenceError: retunr is not defined (line 2, col 1)');
msg.msg.message.should.equal('ReferenceError: retunr is not defined (line 2, col 1)');
done(); done();
} catch(err) { } catch(err) {
done(err); done(err);
@@ -660,8 +659,7 @@ describe('function node', function() {
msg.should.have.property('level', helper.log().ERROR); msg.should.have.property('level', helper.log().ERROR);
msg.should.have.property('id', name); msg.should.have.property('id', name);
msg.should.have.property('type', 'function'); msg.should.have.property('type', 'function');
msg.should.have.property('msg') msg.should.have.property('msg', 'Error: Callback must be a function');
msg.msg.message.should.equal('Callback must be a function');
done(); done();
} }
catch (e) { catch (e) {

View File

@@ -16,31 +16,6 @@ describe('Group', function () {
group.getSetting("NR_GROUP_NAME").should.equal("g1") group.getSetting("NR_GROUP_NAME").should.equal("g1")
group.getSetting("NR_GROUP_ID").should.equal("group1") group.getSetting("NR_GROUP_ID").should.equal("group1")
}) })
it("returns cloned env var property", async function () {
const group = new Group({
getSetting: v => v+v
}, {
name: "g1",
id: "group1",
env: [
{
name: 'jsonEnvVar',
type: 'json',
value: '{"a":1}'
}
]
})
await group.start()
const result = group.getSetting('jsonEnvVar')
result.should.have.property('a', 1)
result.a = 2
result.b = 'hello'
const result2 = group.getSetting('jsonEnvVar')
result2.should.have.property('a', 1)
result2.should.not.have.property('b')
})
it("delegates to parent if not found", async function () { it("delegates to parent if not found", async function () {
const group = new Group({ const group = new Group({
getSetting: v => v+v getSetting: v => v+v