Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
158243b69c Bump peter-evans/create-pull-request in the github-actions group
Bumps the github-actions group with 1 update: [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request).


Updates `peter-evans/create-pull-request` from 6 to 7
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v6...v7)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-01 07:14:17 +00:00
37 changed files with 287 additions and 811 deletions

View File

@@ -32,7 +32,7 @@ jobs:
node-version: '16'
- run: node ./node-red/.github/scripts/update-node-red-docker.js
- name: Create Docker Pull Request
uses: peter-evans/create-pull-request@v6
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.NR_REPO_TOKEN }}
committer: GitHub <noreply@github.com>
@@ -48,7 +48,7 @@ jobs:
This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary
- run: node ./node-red/.github/scripts/update-node-red-website.js
- name: Create Website Pull Request
uses: peter-evans/create-pull-request@v6
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.NR_REPO_TOKEN }}
committer: GitHub <noreply@github.com>

4
.nodemonignore Normal file
View File

@@ -0,0 +1,4 @@
/Gruntfile.js
/.git/*
*.backup
/public/*

View File

@@ -1,50 +1,3 @@
#### 4.0.8: Maintenance Release
Editor
- Fix config node sort order when importing (#5000) @knolleary
#### 4.0.7: Maintenance Release
Editor
- Fix def can be undefined if the type is missing (#4997) @GogoVega
- Fix the user list of nested config node (#4995) @GogoVega
- Support custom login message and button (#4993) @knolleary
#### 4.0.6: Maintenance Release
Editor
- Roll up various fixes on config node change history (#4975) @knolleary
- Add quotes when installing local tgz to fix spacing in the file path (#4949) @AGhorab-upland
- Validate json dropped into editor to avoid unhelpful error messages (#4964) @knolleary
- Fix junction insert position via context menu (#4974) @knolleary
- Apply zoom scale when calculating annotation positions (#4981) @knolleary
- Handle the import of an incomplete Subflow (#4811) @GogoVega
- Fix updating the Subflow name during a copy (#4809) @GogoVega
- Rename variable to avoid confusion in view.js (#4963) @knolleary
- Change groups.length to groups.size (#4959) @hungtcs
- Remove disabled node types from QuickAddDialog list (#4946) @GogoVega
- Fix `setModulePendingUpdated` with plugins (#4939) @GogoVega
- Missing getSubscriptions in the docs while its implemented (#4934) @ersinpw
- Apply `envVarExcludes` setting to `util.getSetting` into the function node (#4925) @GogoVega
- Fix `envVar` editable list should be sortable (#4932) @GogoVega
- Improve the node name auto-generated with the first available number (#4912) @GogoVega
Runtime
- Get the env config node from the parent subflow (#4960) @GogoVega
- Update dependencies (#4987) @knolleary
Nodes
- Performance : make reading single buffer / string file faster by not re-allocating and handling huge buffers (#4980) @Fadoli
- Make delay node rate limit reset consistent - not send on reset. (#4940) @dceejay
- Fix trigger node date handling for latest time type input (#4915) @dceejay
- Fix delay node not dropping when nodeMessageBufferMaxLength is set (#4973)
- Ensure node.sep is honoured when generating CSV (#4982) @knolleary
#### 4.0.5: Maintenance Release
Editor

View File

@@ -1,16 +0,0 @@
{
"ignoreRoot": [
".git",
".nyc_output",
".sass-cache",
"bower-components",
"coverage"
],
"ignore": [
"/Gruntfile.js",
"/.git/*",
"*.backup",
"/public/*"
]
}

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "4.0.8",
"version": "4.0.5",
"description": "Low-code programming for event-driven applications",
"homepage": "https://nodered.org",
"license": "Apache-2.0",
@@ -41,7 +41,7 @@
"cors": "2.8.5",
"cronosjs": "1.7.1",
"denque": "2.1.0",
"express": "4.21.2",
"express": "4.21.1",
"express-session": "1.18.1",
"form-data": "4.0.0",
"fs-extra": "11.2.0",

View File

@@ -126,14 +126,6 @@ async function login(req,res) {
if (themeContext.login && themeContext.login.image) {
response.image = themeContext.login.image;
}
if (themeContext.login?.message) {
response.loginMessage = themeContext.login?.message
}
if (themeContext.login?.button) {
response.prompts = [
{ type: "button", ...themeContext.login.button }
]
}
}
res.json(response);
}

View File

@@ -206,26 +206,14 @@ module.exports = {
}
if (theme.login) {
let themeContextLogin = {}
let hasLoginTheme = false
if (theme.login.image) {
url = serveFile(themeApp,"/login/",theme.login.image);
if (url) {
themeContextLogin.image = url
hasLoginTheme = true
themeContext.login = {
image: url
}
}
}
if (theme.login.message) {
themeContextLogin.message = theme.login.message
hasLoginTheme = true
}
if (theme.login.button) {
themeContextLogin.button = theme.login.button
hasLoginTheme = true
}
if (hasLoginTheme) {
themeContext.login = themeContextLogin
}
}
themeApp.get("/", async function(req,res) {
const themePluginList = await runtimeAPI.plugins.getPluginsByType({type:"node-red-theme"});

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/editor-api",
"version": "4.0.8",
"version": "4.0.5",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,14 +16,14 @@
}
],
"dependencies": {
"@node-red/util": "4.0.8",
"@node-red/editor-client": "4.0.8",
"@node-red/util": "4.0.5",
"@node-red/editor-client": "4.0.5",
"bcryptjs": "2.4.3",
"body-parser": "1.20.3",
"clone": "2.1.2",
"cors": "2.8.5",
"express-session": "1.18.1",
"express": "4.21.2",
"express": "4.21.1",
"memorystore": "1.6.7",
"mime": "3.0.0",
"multer": "1.4.5-lts.1",

View File

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

View File

@@ -453,61 +453,10 @@ RED.history = (function() {
RED.events.emit("nodes:change",newConfigNode);
}
});
} else if (i === "env" && ev.node.type.indexOf("subflow:") === 0) {
// Subflow can have config node in node.env
let nodeList = ev.node.env || [];
nodeList = nodeList.reduce((list, prop) => {
if (prop.type === "conf-type" && prop.value) {
list.push(prop.value);
}
return list;
}, []);
nodeList.forEach(function(id) {
const configNode = RED.nodes.node(id);
if (configNode) {
if (configNode.users.indexOf(ev.node) !== -1) {
configNode.users.splice(configNode.users.indexOf(ev.node), 1);
RED.events.emit("nodes:change", configNode);
}
}
});
nodeList = ev.changes.env || [];
nodeList = nodeList.reduce((list, prop) => {
if (prop.type === "conf-type" && prop.value) {
list.push(prop.value);
}
return list;
}, []);
nodeList.forEach(function(id) {
const configNode = RED.nodes.node(id);
if (configNode) {
if (configNode.users.indexOf(ev.node) === -1) {
configNode.users.push(ev.node);
RED.events.emit("nodes:change", configNode);
}
}
});
}
if (i === "credentials" && ev.changes[i]) {
// Reset - Only want to keep the changes
inverseEv.changes[i] = {};
for (const [key, value] of Object.entries(ev.changes[i])) {
// Edge case: node.credentials is cleared after a deploy, so we can't
// capture values for the inverse event when undoing past a deploy
if (ev.node.credentials) {
inverseEv.changes[i][key] = ev.node.credentials[key];
}
ev.node.credentials[key] = value;
}
} else {
ev.node[i] = ev.changes[i];
}
ev.node[i] = ev.changes[i];
}
}
ev.node.dirty = true;
ev.node.changed = ev.changed;
@@ -587,24 +536,6 @@ RED.history = (function() {
RED.editor.updateNodeProperties(ev.node,outputMap);
RED.editor.validateNode(ev.node);
}
// If it's a Config Node, validate user nodes too.
// NOTE: The Config Node must be validated before validating users.
if (ev.node.users) {
const validatedNodes = new Set();
const userStack = ev.node.users.slice();
validatedNodes.add(ev.node.id);
while (userStack.length) {
const node = userStack.pop();
if (!validatedNodes.has(node.id)) {
validatedNodes.add(node.id);
if (node.users) {
userStack.push(...node.users);
}
RED.editor.validateNode(node);
}
}
}
if (ev.links) {
inverseEv.createdLinks = [];
for (i=0;i<ev.links.length;i++) {

View File

@@ -707,15 +707,12 @@ RED.nodes = (function() {
}
n["_"] = RED._;
}
// Both node and config node can use a config node
updateConfigNodeUsers(newNode, { action: "add" });
if (n._def.category == "config") {
configNodes[n.id] = newNode;
configNodes[n.id] = n;
} else {
if (n.wires && (n.wires.length > n.outputs)) { n.outputs = n.wires.length; }
n.dirty = true;
updateConfigNodeUsers(n);
if (n._def.category == "subflows" && typeof n.i === "undefined") {
var nextId = 0;
RED.nodes.eachNode(function(node) {
@@ -777,11 +774,9 @@ RED.nodes = (function() {
var removedLinks = [];
var removedNodes = [];
var node;
if (id in configNodes) {
node = configNodes[id];
delete configNodes[id];
updateConfigNodeUsers(node, { action: "remove" });
RED.events.emit('nodes:remove',node);
RED.workspaces.refresh();
} else if (allNodes.hasNode(id)) {
@@ -790,9 +785,6 @@ RED.nodes = (function() {
delete nodeLinks[id];
removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
removedLinks.forEach(removeLink);
updateConfigNodeUsers(node, { action: "remove" });
// TODO: Legacy code for exclusive config node
var updatedConfigNode = false;
for (var d in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d)) {
@@ -806,6 +798,10 @@ RED.nodes = (function() {
if (configNode._def.exclusive) {
removeNode(node[d]);
removedNodes.push(configNode);
} else {
var users = configNode.users;
users.splice(users.indexOf(node),1);
RED.events.emit('nodes:change',configNode)
}
}
}
@@ -1802,20 +1798,9 @@ RED.nodes = (function() {
// Replace config nodes
//
configNodeIds.forEach(function(id) {
const configNode = getNode(id);
const currentUserCount = configNode.users;
// Add a snapshot of the Config Node
removedNodes = removedNodes.concat(convertNode(configNode));
// Remove the Config Node instance
removedNodes = removedNodes.concat(convertNode(getNode(id)));
removeNode(id);
// Import the new one
importNodes([newConfigNodes[id]]);
// Re-attributes the user count
getNode(id).users = currentUserCount;
importNodes([newConfigNodes[id]])
});
return {
@@ -2099,8 +2084,6 @@ RED.nodes = (function() {
activeWorkspace = RED.workspaces.active();
}
const pendingConfigNodes = []
const pendingConfigNodeIds = new Set()
// Find all config nodes and add them
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
@@ -2160,8 +2143,7 @@ RED.nodes = (function() {
type:n.type,
info: n.info,
users:[],
_config:{},
_configNodeReferences: new Set()
_config:{}
};
if (!n.z) {
delete configNode.z;
@@ -2176,9 +2158,6 @@ RED.nodes = (function() {
if (def.defaults.hasOwnProperty(d)) {
configNode[d] = n[d];
configNode._config[d] = JSON.stringify(n[d]);
if (def.defaults[d].type) {
configNode._configNodeReferences.add(n[d])
}
}
}
if (def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
@@ -2195,55 +2174,11 @@ RED.nodes = (function() {
configNode.id = getID();
}
node_map[n.id] = configNode;
pendingConfigNodes.push(configNode);
pendingConfigNodeIds.add(configNode.id)
new_nodes.push(configNode);
}
}
}
// We need to sort new_nodes (which only contains config nodes at this point)
// to ensure they get added in the right order. If NodeA depends on NodeB, then
// NodeB must be added first.
// Limit us to 5 full iterations of the list - this should be more than
// enough to process the list as config->config node relationships are
// not very common
let iterationLimit = pendingConfigNodes.length * 5
const handledConfigNodes = new Set()
while (pendingConfigNodes.length > 0 && iterationLimit > 0) {
const node = pendingConfigNodes.shift()
let hasPending = false
// Loop through the nodes referenced by this node to see if anything
// is pending
node._configNodeReferences.forEach(id => {
if (pendingConfigNodeIds.has(id) && !handledConfigNodes.has(id)) {
// This reference is for a node we know is in this import, but
// it isn't added yet - flag as pending
hasPending = true
}
})
if (!hasPending) {
// This node has no pending config node references - safe to add
delete node._configNodeReferences
new_nodes.push(node)
handledConfigNodes.add(node.id)
} else {
// This node has pending config node references
// Put to the back of the queue
pendingConfigNodes.push(node)
}
iterationLimit--
}
if (pendingConfigNodes.length > 0) {
// We exceeded the iteration count. Could be due to reference loops
// between the config nodes. At this point, just add the remaining
// nodes as-is
pendingConfigNodes.forEach(node => {
delete node._configNodeReferences
new_nodes.push(node)
})
}
// Find regular flow nodes and subflow instances
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
@@ -2414,31 +2349,29 @@ RED.nodes = (function() {
node.type = "unknown";
}
if (node._def.category != "config") {
if (n.hasOwnProperty('inputs') && node._def.defaults.hasOwnProperty("inputs")) {
node.inputs = parseInt(n.inputs, 10);
if (n.hasOwnProperty('inputs')) {
node.inputs = n.inputs;
node._config.inputs = JSON.stringify(n.inputs);
} else {
node.inputs = node._def.inputs;
}
if (n.hasOwnProperty('outputs') && node._def.defaults.hasOwnProperty("outputs")) {
node.outputs = parseInt(n.outputs, 10);
if (n.hasOwnProperty('outputs')) {
node.outputs = n.outputs;
node._config.outputs = JSON.stringify(n.outputs);
} else {
node.outputs = node._def.outputs;
}
// The node declares outputs in its defaults, but has not got a valid value
// Defer to the length of the wires array
if (node.hasOwnProperty('wires')) {
if (isNaN(node.outputs)) {
node.outputs = node.wires.length;
} else if (node.wires.length > node.outputs) {
if (node.hasOwnProperty('wires') && node.wires.length > node.outputs) {
if (!node._def.defaults.hasOwnProperty("outputs") || !isNaN(parseInt(n.outputs))) {
// If 'wires' is longer than outputs, clip wires
console.log("Warning: node.wires longer than node.outputs - trimming wires:", node.id, " wires:", node.wires.length, " outputs:", node.outputs);
node.wires = node.wires.slice(0, node.outputs);
console.log("Warning: node.wires longer than node.outputs - trimming wires:",node.id," wires:",node.wires.length," outputs:",node.outputs);
node.wires = node.wires.slice(0,node.outputs);
} else {
// The node declares outputs in its defaults, but has not got a valid value
// Defer to the length of the wires array
node.outputs = node.wires.length;
}
}
for (d in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d) && d !== 'inputs' && d !== 'outputs') {
node[d] = n[d];
@@ -2535,6 +2468,11 @@ RED.nodes = (function() {
nodeList = nodeList.map(function(id) {
var node = node_map[id];
if (node) {
if (node._def.category === 'config') {
if (node.users.indexOf(n) === -1) {
node.users.push(n);
}
}
return node.id;
}
return id;
@@ -2761,79 +2699,25 @@ RED.nodes = (function() {
return result;
}
/**
* Update any config nodes referenced by the provided node to ensure
* their 'users' list is correct.
*
* @param {object} node The node in which to check if it contains references
* @param {object} options Options to apply.
* @param {"add" | "remove"} [options.action] Add or remove the node from
* the Config Node users list. Default `add`.
* @param {boolean} [options.emitEvent] Emit the `nodes:changes` event.
* Default true.
*/
function updateConfigNodeUsers(node, options) {
const defaultOptions = { action: "add", emitEvent: true };
options = Object.assign({}, defaultOptions, options);
for (var d in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d)) {
var property = node._def.defaults[d];
// Update any config nodes referenced by the provided node to ensure their 'users' list is correct
function updateConfigNodeUsers(n) {
for (var d in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d)) {
var property = n._def.defaults[d];
if (property.type) {
var type = registry.getNodeType(property.type);
// Need to ensure the type is a config node to not treat links nodes
if (type && type.category == "config") {
var configNode = configNodes[node[d]];
var configNode = configNodes[n[d]];
if (configNode) {
if (options.action === "add") {
if (configNode.users.indexOf(node) === -1) {
configNode.users.push(node);
if (options.emitEvent) {
RED.events.emit('nodes:change', configNode);
}
}
} else if (options.action === "remove") {
if (configNode.users.indexOf(node) !== -1) {
const users = configNode.users;
users.splice(users.indexOf(node), 1);
if (options.emitEvent) {
RED.events.emit('nodes:change', configNode);
}
}
if (configNode.users.indexOf(n) === -1) {
configNode.users.push(n);
RED.events.emit('nodes:change',configNode)
}
}
}
}
}
}
// Subflows can have config node env
if (node.type.indexOf("subflow:") === 0) {
node.env?.forEach((prop) => {
if (prop.type === "conf-type" && prop.value) {
// Add the node to the config node users
const configNode = getNode(prop.value);
if (configNode) {
if (options.action === "add") {
if (configNode.users.indexOf(node) === -1) {
configNode.users.push(node);
if (options.emitEvent) {
RED.events.emit('nodes:change', configNode);
}
}
} else if (options.action === "remove") {
if (configNode.users.indexOf(node) !== -1) {
const users = configNode.users;
users.splice(users.indexOf(node), 1);
if (options.emitEvent) {
RED.events.emit('nodes:change', configNode);
}
}
}
}
}
});
}
}
function flowVersion(version) {

View File

@@ -334,30 +334,6 @@ RED.clipboard = (function() {
},100);
}
/**
* Validates if the provided string looks like valid flow json
* @param {string} flowString the string to validate
* @returns If valid, returns the node array
*/
function validateFlowString(flowString) {
const res = JSON.parse(flowString)
if (!Array.isArray(res)) {
throw new Error(RED._("clipboard.import.errors.notArray"));
}
for (let i = 0; i < res.length; i++) {
if (typeof res[i] !== "object") {
throw new Error(RED._("clipboard.import.errors.itemNotObject",{index:i}));
}
if (!Object.hasOwn(res[i], 'id')) {
throw new Error(RED._("clipboard.import.errors.missingId",{index:i}));
}
if (!Object.hasOwn(res[i], 'type')) {
throw new Error(RED._("clipboard.import.errors.missingType",{index:i}));
}
}
return res
}
var validateImportTimeout;
function validateImport() {
if (activeTab === "red-ui-clipboard-dialog-import-tab-clipboard") {
@@ -375,7 +351,21 @@ RED.clipboard = (function() {
return;
}
try {
validateFlowString(v)
if (!/^\[[\s\S]*\]$/m.test(v)) {
throw new Error(RED._("clipboard.import.errors.notArray"));
}
var res = JSON.parse(v);
for (var i=0;i<res.length;i++) {
if (typeof res[i] !== "object") {
throw new Error(RED._("clipboard.import.errors.itemNotObject",{index:i}));
}
if (!res[i].hasOwnProperty('id')) {
throw new Error(RED._("clipboard.import.errors.missingId",{index:i}));
}
if (!res[i].hasOwnProperty('type')) {
throw new Error(RED._("clipboard.import.errors.missingType",{index:i}));
}
}
currentPopoverError = null;
popover.close(true);
importInput.removeClass("input-error");
@@ -1008,16 +998,16 @@ RED.clipboard = (function() {
}
function importNodes(nodesStr,addFlow) {
let newNodes = nodesStr;
var newNodes = nodesStr;
if (typeof nodesStr === 'string') {
try {
nodesStr = nodesStr.trim();
if (nodesStr.length === 0) {
return;
}
newNodes = validateFlowString(nodesStr)
newNodes = JSON.parse(nodesStr);
} catch(err) {
const e = new Error(RED._("clipboard.invalidFlow",{message:err.message}));
var e = new Error(RED._("clipboard.invalidFlow",{message:err.message}));
e.code = "NODE_RED";
throw e;
}
@@ -1352,7 +1342,6 @@ RED.clipboard = (function() {
}
}
} catch(err) {
console.warn('Import failed: ', err)
// Ensure any errors throw above doesn't stop the drop target from
// being hidden.
}

View File

@@ -54,15 +54,15 @@ RED.contextMenu = (function () {
}
}
const scale = RED.view.scale()
const offset = $("#red-ui-workspace-chart").offset()
let addX = (options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft()) / scale
let addY = (options.y - offset.top + $("#red-ui-workspace-chart").scrollTop()) / scale
let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft()
let addY = options.y - offset.top + $("#red-ui-workspace-chart").scrollTop()
if (RED.view.snapGrid) {
const gridSize = RED.view.gridSize()
addX = gridSize * Math.round(addX / gridSize)
addY = gridSize * Math.round(addY / gridSize)
addX = gridSize * Math.floor(addX / gridSize)
addY = gridSize * Math.floor(addY / gridSize)
}
if (RED.settings.theme("menu.menu-item-action-list", true)) {
@@ -87,9 +87,7 @@ RED.contextMenu = (function () {
},
(hasLinks) ? { // has least 1 wire selected
label: RED._("contextMenu.junction"),
onselect: function () {
RED.actions.invoke('core:split-wires-with-junctions', { x: addX, y: addY })
},
onselect: 'core:split-wires-with-junctions',
disabled: !canEdit || !hasLinks
} : {
label: RED._("contextMenu.junction"),

View File

@@ -808,20 +808,6 @@ RED.editor = (function() {
}
}
const oldCreds = {};
if (editing_node._def.credentials) {
for (const prop in editing_node._def.credentials) {
if (Object.prototype.hasOwnProperty.call(editing_node._def.credentials, prop)) {
if (editing_node._def.credentials[prop].type === 'password') {
oldCreds['has_' + prop] = editing_node.credentials['has_' + prop];
}
if (prop in editing_node.credentials) {
oldCreds[prop] = editing_node.credentials[prop];
}
}
}
}
try {
const rc = editing_node._def.oneditsave.call(editing_node);
if (rc === true) {
@@ -853,25 +839,6 @@ RED.editor = (function() {
}
}
}
if (editing_node._def.credentials) {
for (const prop in editing_node._def.credentials) {
if (Object.prototype.hasOwnProperty.call(editing_node._def.credentials, prop)) {
if (oldCreds[prop] !== editing_node.credentials[prop]) {
if (editing_node.credentials[prop] === '__PWRD__') {
// The password may not exist in oldCreds
// The value '__PWRD__' means the password exists,
// so ignore this change
continue;
}
editState.changes.credentials = editState.changes.credentials || {};
editState.changes.credentials['has_' + prop] = oldCreds['has_' + prop];
editState.changes.credentials[prop] = oldCreds[prop];
editState.changed = true;
}
}
}
}
}
}
@@ -1514,181 +1481,134 @@ RED.editor = (function() {
},
{
id: "node-config-dialog-ok",
text: adding ? RED._("editor.configAdd") : RED._("editor.configUpdate"),
text: adding?RED._("editor.configAdd"):RED._("editor.configUpdate"),
class: "primary",
click: function() {
// TODO: Already defined
const configProperty = name;
const configType = type;
const configTypeDef = RED.nodes.getType(configType);
const wasChanged = editing_config_node.changed;
const editState = {
var editState = {
changes: {},
changed: false,
outputMap: null
};
// Call `oneditsave` and search for changes
handleEditSave(editing_config_node, editState);
var configProperty = name;
var configId = editing_config_node.id;
var configType = type;
var configAdding = adding;
var configTypeDef = RED.nodes.getType(configType);
var d;
var input;
// Search for changes in the edit box (panes)
activeEditPanes.forEach(function (pane) {
if (configTypeDef.oneditsave) {
try {
configTypeDef.oneditsave.call(editing_config_node);
} catch(err) {
console.warn("oneditsave",editing_config_node.id,editing_config_node.type,err.toString());
}
}
for (d in configTypeDef.defaults) {
if (configTypeDef.defaults.hasOwnProperty(d)) {
var newValue;
input = $("#node-config-input-"+d);
if (input.attr('type') === "checkbox") {
newValue = input.prop('checked');
} else if ("format" in configTypeDef.defaults[d] && configTypeDef.defaults[d].format !== "" && input[0].nodeName === "DIV") {
newValue = input.text();
} else {
newValue = input.val();
}
if (newValue != null && newValue !== editing_config_node[d]) {
if (editing_config_node._def.defaults[d].type) {
if (newValue == "_ADD_") {
newValue = "";
}
// Change to a related config node
var configNode = RED.nodes.node(editing_config_node[d]);
if (configNode) {
var users = configNode.users;
users.splice(users.indexOf(editing_config_node),1);
RED.events.emit("nodes:change",configNode);
}
configNode = RED.nodes.node(newValue);
if (configNode) {
configNode.users.push(editing_config_node);
RED.events.emit("nodes:change",configNode);
}
}
editing_config_node[d] = newValue;
}
}
}
activeEditPanes.forEach(function(pane) {
if (pane.apply) {
pane.apply.call(pane, editState);
}
});
})
// TODO: Why?
editing_config_node.label = configTypeDef.label
editing_config_node.label = configTypeDef.label;
var scope = $("#red-ui-editor-config-scope").val();
editing_config_node.z = scope;
// Check if disabled has changed
if ($("#node-config-input-node-disabled").prop('checked')) {
if (editing_config_node.d !== true) {
editState.changes.d = editing_config_node.d;
editState.changed = true;
editing_config_node.d = true;
}
} else {
if (editing_config_node.d === true) {
editState.changes.d = editing_config_node.d;
editState.changed = true;
delete editing_config_node.d;
}
}
// NOTE: must be undefined if no scope used
const scope = $("#red-ui-editor-config-scope").val() || undefined;
// Check if the scope has changed
if (editing_config_node.z !== scope) {
editState.changes.z = editing_config_node.z;
editState.changed = true;
editing_config_node.z = scope;
}
// Search for nodes that use this config node that are no longer
// in scope, so must be removed
const historyEvents = [];
if (scope) {
const newUsers = editing_config_node.users.filter(function (node) {
let keepNode = false;
let nodeModified = null;
for (const d in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d)) {
if (node._def.defaults[d].type === editing_config_node.type) {
if (node[d] === editing_config_node.id) {
if (node.z === editing_config_node.z) {
// The node is kept only if at least one property uses
// this config node in the correct scope.
keepNode = true;
} else {
if (!nodeModified) {
nodeModified = {
t: "edit",
node: node,
changes: { [d]: node[d] },
changed: node.changed,
dirty: node.dirty
};
} else {
nodeModified.changes[d] = node[d];
}
// Remove the reference to the config node
node[d] = "";
}
}
// Search for nodes that use this one that are no longer
// in scope, so must be removed
editing_config_node.users = editing_config_node.users.filter(function(n) {
var keep = true;
for (var d in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d)) {
if (n._def.defaults[d].type === editing_config_node.type &&
n[d] === editing_config_node.id &&
n.z !== scope) {
keep = false;
// Remove the reference to this node
// and revalidate
n[d] = null;
n.dirty = true;
n.changed = true;
validateNode(n);
}
}
}
// Add the node modified to the history
if (nodeModified) {
historyEvents.push(nodeModified);
}
// Mark as changed and revalidate this node
if (!keepNode) {
node.changed = true;
node.dirty = true;
validateNode(node);
RED.events.emit("nodes:change", node);
}
return keepNode;
return keep;
});
// Check if users are changed
if (editing_config_node.users.length !== newUsers.length) {
editState.changes.users = editing_config_node.users;
editState.changed = true;
editing_config_node.users = newUsers;
}
}
if (editState.changed) {
// Set the congig node as changed
editing_config_node.changed = true;
if (configAdding) {
RED.nodes.add(editing_config_node);
}
// Now, validate the config node
validateNode(editing_config_node);
var validatedNodes = {};
validatedNodes[editing_config_node.id] = true;
// And validate nodes using this config node too
const validatedNodes = new Set();
const userStack = editing_config_node.users.slice();
validatedNodes.add(editing_config_node.id);
while (userStack.length) {
const node = userStack.pop();
if (!validatedNodes.has(node.id)) {
validatedNodes.add(node.id);
if (node.users) {
userStack.push(...node.users);
var userStack = editing_config_node.users.slice();
while(userStack.length > 0) {
var user = userStack.pop();
if (!validatedNodes[user.id]) {
validatedNodes[user.id] = true;
if (user.users) {
userStack = userStack.concat(user.users);
}
validateNode(node);
validateNode(user);
}
}
let historyEvent = {
t: "edit",
node: editing_config_node,
changes: editState.changes,
changed: wasChanged,
dirty: RED.nodes.dirty()
};
if (historyEvents.length) {
// Need a multi events
historyEvent = {
t: "multi",
events: [historyEvent].concat(historyEvents),
dirty: historyEvent.dirty
};
RED.nodes.dirty(true);
RED.view.redraw(true);
if (!configAdding) {
RED.events.emit("editor:save",editing_config_node);
RED.events.emit("nodes:change",editing_config_node);
}
if (!adding) {
// This event is triggered when the edit box is saved,
// regardless of whether there are any modifications.
RED.events.emit("editor:save", editing_config_node);
}
if (editState.changed) {
if (adding) {
RED.history.push({ t: "add", nodes: [editing_config_node.id], dirty: RED.nodes.dirty() });
// Add the new config node and trigger the `nodes:add` event
RED.nodes.add(editing_config_node);
} else {
RED.history.push(historyEvent);
RED.events.emit("nodes:change", editing_config_node);
}
RED.nodes.dirty(true);
RED.view.redraw(true);
}
RED.tray.close(function() {
var filter = null;
// when editing a config via subflow edit panel, the `configProperty` will not

View File

@@ -20,31 +20,10 @@
apply: function(editState) {
var old_env = node.env;
var new_env = [];
if (/^subflow:/.test(node.type)) {
// Get the list of environment variables from the node properties
new_env = RED.subflow.exportSubflowInstanceEnv(node);
}
if (old_env && old_env.length) {
old_env.forEach(function (prop) {
if (prop.type === "conf-type" && prop.value) {
const stillInUse = new_env?.some((p) => p.type === "conf-type" && p.name === prop.name && p.value === prop.value);
if (!stillInUse) {
// Remove the node from the config node users
// Only for empty value or modified
const configNode = RED.nodes.node(prop.value);
if (configNode) {
if (configNode.users.indexOf(node) !== -1) {
configNode.users.splice(configNode.users.indexOf(node), 1);
RED.events.emit('nodes:change', configNode)
}
}
}
}
});
}
// Get the values from the Properties table tab
var items = this.list.editableList('items');
items.each(function (i,el) {
@@ -62,6 +41,7 @@
}
});
if (new_env && new_env.length > 0) {
new_env.forEach(function(prop) {
if (prop.type === "cred") {
@@ -72,15 +52,6 @@
editState.changed = true;
}
delete prop.value;
} else if (prop.type === "conf-type" && prop.value) {
const configNode = RED.nodes.node(prop.value);
if (configNode) {
if (configNode.users.indexOf(node) === -1) {
// Add the node to the config node users
configNode.users.push(node);
RED.events.emit('nodes:change', configNode);
}
}
}
});
}

View File

@@ -44,7 +44,6 @@
apply: function(editState) {
var newValue;
var d;
// If the node is a subflow, the node's properties (exepts name) are saved by `envProperties`
if (node._def.defaults) {
for (d in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d)) {
@@ -132,16 +131,9 @@
}
}
if (node._def.credentials) {
const credDefinition = node._def.credentials;
const credChanges = updateNodeCredentials(node, credDefinition, this.inputClass);
if (Object.keys(credChanges).length) {
editState.changed = true;
editState.changes.credentials = {
...(editState.changes.credentials || {}),
...credChanges
};
}
var credDefinition = node._def.credentials;
var credsChanged = updateNodeCredentials(node,credDefinition,this.inputClass);
editState.changed = editState.changed || credsChanged;
}
}
}
@@ -169,11 +161,10 @@
* @param node - the node containing the credentials
* @param credDefinition - definition of the credentials
* @param prefix - prefix of the input fields
* @return {object} an object containing the modified properties
* @return {boolean} whether anything has changed
*/
function updateNodeCredentials(node, credDefinition, prefix) {
const changes = {};
var changed = false;
if (!node.credentials) {
node.credentials = {_:{}};
} else if (!node.credentials._) {
@@ -186,33 +177,22 @@
if (input.length > 0) {
var value = input.val();
if (credDefinition[cred].type == 'password') {
if (value === '__PWRD__') {
// A cred value exists - no changes
} else if (value === '' && node.credentials['has_' + cred] === false) {
// Empty cred value exists - no changes
} else if (value === node.credentials[cred]) {
// A cred value exists locally in the editor - no changes
// Like the user sets a value, saves the config,
// reopens the config and save the config again
} else {
changes['has_' + cred] = node.credentials['has_' + cred];
changes[cred] = node.credentials[cred];
node.credentials[cred] = value;
node.credentials['has_' + cred] = (value !== "");
if (value == '__PWRD__') {
continue;
}
changed = true;
node.credentials['has_' + cred] = (value !== '');
} else {
// Since these creds are loaded by the editor,
// values can be directly compared
if (value !== node.credentials[cred]) {
changes[cred] = node.credentials[cred];
node.credentials[cred] = value;
}
}
node.credentials[cred] = value;
if (value != node.credentials._[cred]) {
changed = true;
}
}
}
}
return changes;
return changed;
}
})();

View File

@@ -245,15 +245,10 @@ RED.library = (function() {
if (lib.types && lib.types.indexOf(options.url) === -1) {
return;
}
let icon = 'fa fa-hdd-o';
if (lib.icon) {
const fullIcon = RED.utils.separateIconPath(lib.icon);
icon = (fullIcon.module==="font-awesome"?"fa ":"")+fullIcon.file;
}
listing.push({
library: lib.id,
type: options.url,
icon,
icon: lib.icon || 'fa fa-hdd-o',
label: RED._(lib.label||lib.id),
path: "",
expanded: true,
@@ -308,15 +303,10 @@ RED.library = (function() {
if (lib.types && lib.types.indexOf(options.url) === -1) {
return;
}
let icon = 'fa fa-hdd-o';
if (lib.icon) {
const fullIcon = RED.utils.separateIconPath(lib.icon);
icon = (fullIcon.module==="font-awesome"?"fa ":"")+fullIcon.file;
}
listing.push({
library: lib.id,
type: options.url,
icon,
icon: lib.icon || 'fa fa-hdd-o',
label: RED._(lib.label||lib.id),
path: "",
expanded: true,

View File

@@ -1362,7 +1362,7 @@ RED.subflow = (function() {
item.value = ""+input.prop("checked");
break;
case "conf-types":
item.value = input.val() === "_ADD_" ? "" : input.val();
item.value = input.val()
item.type = "conf-type"
}
if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) {

View File

@@ -11,7 +11,7 @@ RED.view.annotations = (function() {
}
let badgeRDX = 0;
let badgeLDX = 0;
const scale = RED.view.scale()
for (let i=0,l=evt.el.__annotations__.length;i<l;i++) {
const annotation = evt.el.__annotations__[i];
if (annotations.hasOwnProperty(annotation.id)) {
@@ -42,17 +42,15 @@ RED.view.annotations = (function() {
}
if (isBadge) {
if (showAnnotation) {
// getBoundingClientRect is in real-world scale so needs to be adjusted according to
// the current scale factor
const rectWidth = annotation.element.getBoundingClientRect().width / scale;
const rect = annotation.element.getBoundingClientRect();
let annotationX
if (!opts.align || opts.align === 'right') {
annotationX = evt.node.w - 3 - badgeRDX - rectWidth
badgeRDX += rectWidth + 4;
annotationX = evt.node.w - 3 - badgeRDX - rect.width
badgeRDX += rect.width + 4;
} else if (opts.align === 'left') {
annotationX = 3 + badgeLDX
badgeLDX += rectWidth + 4;
badgeLDX += rect.width + 4;
}
annotation.element.setAttribute("transform", "translate("+annotationX+", -8)");
}

View File

@@ -1154,11 +1154,11 @@ RED.view.tools = (function() {
}
}
function addJunctionsToWires(options = {}) {
function addJunctionsToWires(wires) {
if (RED.workspaces.isLocked()) {
return
}
let wiresToSplit = options.wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
if (!wiresToSplit) {
return
}
@@ -1206,26 +1206,21 @@ RED.view.tools = (function() {
if (links.length === 0) {
return
}
if (addedJunctions.length === 0 && Object.hasOwn(options, 'x') && Object.hasOwn(options, 'y')) {
junction.x = options.x
junction.y = options.y
} else {
let pointCount = 0
links.forEach(function(l) {
if (l._sliceLocation) {
junction.x += l._sliceLocation.x
junction.y += l._sliceLocation.y
delete l._sliceLocation
pointCount++
} else {
junction.x += l.source.x + l.source.w/2 + l.target.x - l.target.w/2
junction.y += l.source.y + l.target.y
pointCount += 2
}
})
junction.x = Math.round(junction.x/pointCount)
junction.y = Math.round(junction.y/pointCount)
}
let pointCount = 0
links.forEach(function(l) {
if (l._sliceLocation) {
junction.x += l._sliceLocation.x
junction.y += l._sliceLocation.y
delete l._sliceLocation
pointCount++
} else {
junction.x += l.source.x + l.source.w/2 + l.target.x - l.target.w/2
junction.y += l.source.y + l.target.y
pointCount += 2
}
})
junction.x = Math.round(junction.x/pointCount)
junction.y = Math.round(junction.y/pointCount)
if (RED.view.snapGrid) {
let gridSize = RED.view.gridSize()
junction.x = (gridSize*Math.round(junction.x/gridSize));
@@ -1415,7 +1410,7 @@ RED.view.tools = (function() {
RED.actions.add("core:wire-multiple-to-node", function() { wireMultipleToNode() })
RED.actions.add("core:split-wire-with-link-nodes", function () { splitWiresWithLinkNodes() });
RED.actions.add("core:split-wires-with-junctions", function (options) { addJunctionsToWires(options) });
RED.actions.add("core:split-wires-with-junctions", function () { addJunctionsToWires() });
RED.actions.add("core:generate-node-names", generateNodeNames )

View File

@@ -321,8 +321,8 @@ RED.view = (function() {
evt.stopPropagation()
RED.contextMenu.show({
type: 'workspace',
x: evt.clientX,
y: evt.clientY
x:evt.clientX-5,
y:evt.clientY-5
})
return false
})
@@ -5174,8 +5174,8 @@ RED.view = (function() {
var delta = Infinity;
for (var i = 0; i < lineLength; i++) {
var linePos = pathLine.getPointAtLength(i);
var posDeltaX = Math.abs(linePos.x-(d3.event.offsetX / scaleFactor))
var posDeltaY = Math.abs(linePos.y-(d3.event.offsetY / scaleFactor))
var posDeltaX = Math.abs(linePos.x-d3.event.offsetX)
var posDeltaY = Math.abs(linePos.y-d3.event.offsetY)
var posDelta = posDeltaX*posDeltaX + posDeltaY*posDeltaY
if (posDelta < delta) {
pos = linePos

View File

@@ -168,37 +168,6 @@ RED.user = (function() {
}
} else {
if (data.prompts) {
if (data.loginMessage) {
const sessionMessages = $("<div/>",{class:"form-row",style:"text-align: center"}).appendTo("#node-dialog-login-fields");
$('<div>').text(data.loginMessage).appendTo(sessionMessages);
}
i = 0;
for (;i<data.prompts.length;i++) {
var field = data.prompts[i];
var row = $("<div/>",{class:"form-row",style:"text-align: center"}).appendTo("#node-dialog-login-fields");
var loginButton = $('<a href="#" class="red-ui-button"></a>',{style: "padding: 10px"}).appendTo(row).on("click", function() {
document.location = field.url;
});
if (field.image) {
$("<img>",{src:field.image}).appendTo(loginButton);
} else if (field.label) {
var label = $('<span></span>').text(field.label);
if (field.icon) {
$('<i></i>',{class: "fa fa-2x "+field.icon, style:"vertical-align: middle"}).appendTo(loginButton);
label.css({
"verticalAlign":"middle",
"marginLeft":"8px"
});
}
label.appendTo(loginButton);
}
loginButton.button();
}
}
}
if (opts.cancelable) {
$("#node-dialog-login-cancel").button().on("click", function( event ) {

View File

@@ -352,9 +352,7 @@ module.exports = function(RED) {
if (msgs.length === 0) {
done()
} else {
setImmediate(() => {
drainMessageGroup(msgs,count,done);
})
drainMessageGroup(msgs,count,done);
}
}
})
@@ -507,9 +505,7 @@ module.exports = function(RED) {
if (err) {
node.error(err,nextMsg);
}
setImmediate(() => {
processMessageQueue()
})
processMessageQueue()
});
}

View File

@@ -291,23 +291,43 @@ module.exports = function(RED) {
}
}
else if (!msg.hasOwnProperty("reset")) {
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate))) {
node.rate = msg.rate;
if (maxKeptMsgsCount(node) > 0) {
if (node.intervalID === -1) {
node.send(msg);
node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
} else {
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate)) && node.rate !== msg.rate) {
node.rate = msg.rate;
clearInterval(node.intervalID);
node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
}
if (node.buffer.length < _maxKeptMsgsCount) {
var m = RED.util.cloneMessage(msg);
node.buffer.push({msg: m, send: send, done: done});
} else {
node.trace("dropped due to buffer overflow. msg._msgid = " + msg._msgid);
node.droppedMsgs++;
}
}
}
var timeSinceLast;
if (node.lastSent) {
timeSinceLast = process.hrtime(node.lastSent);
}
if (!node.lastSent) { // ensuring that we always send the first message
node.lastSent = process.hrtime();
send(msg);
}
else if ( ( (timeSinceLast[0] * SECONDS_TO_NANOS) + timeSinceLast[1] ) > (node.rate * MILLIS_TO_NANOS) ) {
node.lastSent = process.hrtime();
send(msg);
}
else if (node.outputs === 2) {
send([null,msg])
else {
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate))) {
node.rate = msg.rate;
}
var timeSinceLast;
if (node.lastSent) {
timeSinceLast = process.hrtime(node.lastSent);
}
if (!node.lastSent) { // ensuring that we always send the first message
node.lastSent = process.hrtime();
send(msg);
}
else if ( ( (timeSinceLast[0] * SECONDS_TO_NANOS) + timeSinceLast[1] ) > (node.rate * MILLIS_TO_NANOS) ) {
node.lastSent = process.hrtime();
send(msg);
} else if (node.outputs === 2) {
send([null,msg])
}
}
done();
}

View File

@@ -367,21 +367,20 @@ module.exports = function(RED) {
const sendHeadersAlways = node.hdrout === "all"
const sendHeaders = !dontSendHeaders && (sendHeadersOnce || sendHeadersAlways)
const quoteables = [node.sep, node.quo, "\n", "\r"]
const templateQuoteables = [node.sep, node.quo, "\n", "\r"]
const templateQuoteablesStrict = [',', '"', "\n", "\r"]
const templateQuoteables = [',', '"', "\n", "\r"]
let badTemplateWarnOnce = true
const columnStringToTemplateArray = function (col, sep) {
// NOTE: enforce strict column template parsing in RFC4180 mode
const parsed = csv.parse(col, { separator: sep, quote: node.quo, outputStyle: 'array', strict: true })
if (parsed.data?.length === 1) { node.goodtmpl = true } else { node.goodtmpl = false }
return node.goodtmpl ? parsed.data[0] : null
if (parsed.headers.length > 0) { node.goodtmpl = true } else { node.goodtmpl = false }
return parsed.headers.length ? parsed.headers : null
}
const templateArrayToColumnString = function (template, keepEmptyColumns, separator = ',', quotables = templateQuoteablesStrict) {
// NOTE: defaults to strict column template parsing (commas and double quotes)
const parsed = csv.parse('', {headers: template, headersOnly:true, separator, quote: node.quo, outputStyle: 'array', strict: true })
const templateArrayToColumnString = function (template, keepEmptyColumns) {
// NOTE: enforce strict column template parsing in RFC4180 mode
const parsed = csv.parse('', {headers: template, headersOnly:true, separator: ',', quote: node.quo, outputStyle: 'array', strict: true })
return keepEmptyColumns
? parsed.headers.map(e => addQuotes(e || '', { separator, quoteables: quotables })).join(separator)
? parsed.headers.map(e => addQuotes(e || '', { separator: ',', quoteables: templateQuoteables}))
: parsed.header // exclues empty columns
// TODO: resolve inconsistency between CSV->JSON and JSON->CSV
// CSV->JSON: empty columns are excluded
@@ -448,7 +447,7 @@ module.exports = function(RED) {
template = Object.keys(inputData[0]) || ['']
}
}
stringBuilder.push(templateArrayToColumnString(template, true, node.sep, templateQuoteables)) // use user set separator for output data.
stringBuilder.push(templateArrayToColumnString(template, true))
if (sendHeadersOnce) { node.hdrSent = true }
}
@@ -484,7 +483,6 @@ module.exports = function(RED) {
node.warn(RED._("csv.errors.obj_csv"))
badTemplateWarnOnce = false
}
template = Object.keys(row) || ['']
const rowData = []
for (let header in inputData[0]) {
if (row.hasOwnProperty(header)) {
@@ -520,7 +518,7 @@ module.exports = function(RED) {
// join lines, don't forget to add the last new line
msg.payload = stringBuilder.join(node.ret) + node.ret
msg.columns = templateArrayToColumnString(template) // always strict commas + double quotes for
msg.columns = templateArrayToColumnString(template)
if (msg.payload !== '') { send(msg) }
done()
}
@@ -617,15 +615,16 @@ module.exports = function(RED) {
}
if (msg.parts.index + 1 === msg.parts.count) {
msg.payload = node.store
// msg.columns = csvParseResult.header
msg.columns = templateArrayToColumnString(csvParseResult.headers) // always strict commas + double quotes for msg.columns
msg.columns = csvParseResult.header
// msg._mode = 'RFC4180 mode'
delete msg.parts
send(msg)
node.store = []
}
}
else {
msg.columns = templateArrayToColumnString(csvParseResult.headers) // always strict commas + double quotes for msg.columns
msg.columns = csvParseResult.header
// msg._mode = 'RFC4180 mode'
msg.payload = data
send(msg); // finally send the array
}
@@ -634,8 +633,7 @@ module.exports = function(RED) {
const len = data.length
for (let row = 0; row < len; row++) {
const newMessage = RED.util.cloneMessage(msg)
// newMessage.columns = csvParseResult.header
newMessage.columns = templateArrayToColumnString(csvParseResult.headers) // always strict commas + double quotes for msg.columns
newMessage.columns = csvParseResult.header
newMessage.payload = data[row]
if (!has_parts) {
newMessage.parts = {

View File

@@ -339,7 +339,7 @@ module.exports = function(RED) {
}
else {
msg.filename = filename;
const bufferArray = [];
var lines = Buffer.from([]);
var spare = "";
var count = 0;
var type = "buffer";
@@ -397,7 +397,7 @@ module.exports = function(RED) {
}
}
else {
bufferArray.push(chunk);
lines = Buffer.concat([lines,chunk]);
}
}
})
@@ -413,11 +413,10 @@ module.exports = function(RED) {
})
.on('end', function() {
if (node.chunk === false) {
const buffer = Buffer.concat(bufferArray);
if (node.format === "utf8") {
msg.payload = decode(buffer, node.encoding);
msg.payload = decode(lines, node.encoding);
}
else { msg.payload = buffer; }
else { msg.payload = lines; }
nodeSend(msg);
}
else if (node.format === "lines") {

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/nodes",
"version": "4.0.8",
"version": "4.0.5",
"license": "Apache-2.0",
"repository": {
"type": "git",

View File

@@ -144,7 +144,7 @@ async function installModule(module,version,url) {
if (url) {
if (pkgurlRe.test(url) || localtgzRe.test(url)) {
// Git remote url or Tarball url - check the valid package url
installName = localtgzRe.test(url) && slashRe.test(url) ? `"${url}"` : url;
installName = url;
isRegistryPackage = false;
} else {
log.warn(log._("server.install.install-failed-url",{name:module,url:url}));

View File

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

View File

@@ -719,14 +719,6 @@ class Flow {
});
}
getContext(scope) {
if (scope === 'flow') {
return this.context
} else if (scope === 'global') {
return context.get('global')
}
}
dump() {
console.log("==================")
console.log(this.TYPE, this.id);

View File

@@ -49,14 +49,6 @@ class Group {
}
return this.parent.getSetting(key);
}
error(msg) {
this.parent.error(msg);
}
getContext(scope) {
return this.parent.getContext(scope);
}
}
module.exports = {

View File

@@ -100,24 +100,7 @@ async function evaluateEnvProperties(flow, env, credentials) {
}
} else if (type ==='jsonata') {
pendingEvaluations.push(new Promise((resolve, _) => {
redUtil.evaluateNodeProperty(value, 'jsonata',{
// Fake a node object to provide access to _flow and context
_flow: flow,
context: () => {
return {
flow: {
get: (value, store, callback) => {
return flow.getContext('flow').get(value, store, callback)
}
},
global: {
get: (value, store, callback) => {
return flow.getContext('global').get(value, store, callback)
}
}
}
}
}, null, (err, result) => {
redUtil.evaluateNodeProperty(value, 'jsonata', {_flow: flow}, null, (err, result) => {
if (!err) {
if (typeof result === 'object') {
result = { value: result, __clone__: true}

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/runtime",
"version": "4.0.8",
"version": "4.0.5",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,11 +16,11 @@
}
],
"dependencies": {
"@node-red/registry": "4.0.8",
"@node-red/util": "4.0.8",
"@node-red/registry": "4.0.5",
"@node-red/util": "4.0.5",
"async-mutex": "0.5.0",
"clone": "2.1.2",
"express": "4.21.2",
"express": "4.21.1",
"fs-extra": "11.2.0",
"json-stringify-safe": "5.0.1",
"rfdc": "^1.3.1"

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "4.0.8",
"version": "4.0.5",
"description": "Low-code programming for event-driven applications",
"homepage": "https://nodered.org",
"license": "Apache-2.0",
@@ -31,14 +31,14 @@
"flow"
],
"dependencies": {
"@node-red/editor-api": "4.0.8",
"@node-red/runtime": "4.0.8",
"@node-red/util": "4.0.8",
"@node-red/nodes": "4.0.8",
"@node-red/editor-api": "4.0.5",
"@node-red/runtime": "4.0.5",
"@node-red/util": "4.0.5",
"@node-red/nodes": "4.0.5",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"cors": "2.8.5",
"express": "4.21.2",
"express": "4.21.1",
"fs-extra": "11.2.0",
"node-red-admin": "^4.0.1",
"nopt": "5.0.0",

View File

@@ -2067,27 +2067,6 @@ describe('CSV node (RFC Mode)', function () {
n2.on("input", function (msg) {
try {
msg.should.have.property('payload', '1\tfoo\t"ba""r"\tdi,ng\n');
msg.should.have.property('columns', 'd,b,c,a'); // Strict RFC columns
done();
} catch (e) {
done(e);
}
});
const testJson = { d: 1, b: "foo", c: "ba\"r", a: "di,ng" };
n1.emit("input", { payload: testJson });
});
});
it('should convert a simple object back to a tsv with headers using a tab as a separator', function (done) {
const flow = [{ id: "n1", type: "csv", spec: "rfc", temp: "", sep: "\t", ret: '\n', hdrout: "all", wires: [["n2"]] }, // RFC-vs-Legacy difference - use line separator \n to satisfy original test
{ id: "n2", type: "helper" }];
helper.load(csvNode, flow, function () {
const n1 = helper.getNode("n1");
const n2 = helper.getNode("n2");
n2.on("input", function (msg) {
try {
msg.should.have.property('payload', 'd\tb\tc\ta\n1\tfoo\t"ba""r"\tdi,ng\n');
msg.should.have.property('columns', 'd,b,c,a'); // Strict RFC columns
done();
} catch (e) {
done(e);
@@ -2107,7 +2086,6 @@ describe('CSV node (RFC Mode)', function () {
n2.on("input", function (msg) {
try {
msg.should.have.property('payload', '4,foo,true,,0\n');
msg.should.have.property('columns', 'a,b o,c p,e'); // Strict RFC columns
done();
} catch (e) {
done(e);
@@ -2128,7 +2106,6 @@ describe('CSV node (RFC Mode)', function () {
try {
// 'payload', 'a"a,b\'b\nA1,B1\nA2,B2\n'); // Legacy
msg.should.have.property('payload', '"a""a",b\'b\nA1,B1\nA2,B2\n'); // RFC-vs-Legacy difference - RFC4180 Section 2.6, 2.7 quote handling
msg.should.have.property('columns', '"a""a",b\'b'); // RCF compliant column names
done();
} catch (e) {
done(e);
@@ -2194,7 +2171,6 @@ describe('CSV node (RFC Mode)', function () {
n2.on("input", function (msg) {
try {
msg.should.have.property('payload', '1,3,2,4\n4,2,3,1\n');
msg.should.have.property('columns', 'd,b,c,a'); // Strict RFC columns
done();
}
catch (e) { done(e); }
@@ -2213,7 +2189,6 @@ describe('CSV node (RFC Mode)', function () {
n2.on("input", function (msg) {
try {
msg.should.have.property('payload', 'd,b,c,a\n1,3,2,4\n4,"f\ng",3,1\n');
msg.should.have.property('columns', 'd,b,c,a'); // Strict RFC columns
done();
}
catch (e) { done(e); }
@@ -2233,7 +2208,6 @@ describe('CSV node (RFC Mode)', function () {
try {
// 'payload', ',0,1,foo,"ba""r","di,ng","fa\nba"\n');
msg.should.have.property('payload', ',0,1,foo\n'); // RFC-vs-Legacy difference - respect that user has specified a template with 4 columns
msg.should.have.property('columns', 'a,b,c,d');
done();
}
catch (e) { done(e); }
@@ -2353,7 +2327,6 @@ describe('CSV node (RFC Mode)', function () {
n2.on("input", function (msg) {
try {
msg.should.have.property('payload', '{},"text,with,commas","This ""is"" a banana","{""sub"":""object""}"\n');
msg.should.have.property('columns', 'a,b,c,d');
done();
}
catch (e) { done(e); }

View File

@@ -258,29 +258,6 @@ describe('nodes/registry/installer', function() {
}).catch(done);
});
it("succeeds when file path is valid node-red module", function(done) {
var nodeInfo = {nodes:{module:"foo",types:["a"]}};
var res = {
code: 0,
stdout:"",
stderr:""
}
var p = Promise.resolve(res);
p.catch((err)=>{});
execResponse = p;
var addModule = sinon.stub(registry,"addModule").callsFake(function(md) {
return Promise.resolve(nodeInfo);
});
installer.installModule("foo",null,"/example path/foo-0.1.1.tgz").then(function(info) {
exec.run.lastCall.args[1].should.eql([ 'install', '--no-audit', '--no-update-notifier', '--no-fund', '--save', '--save-prefix=~', '--omit=dev', '--engine-strict', '"/example path/foo-0.1.1.tgz"' ]);
info.should.eql(nodeInfo);
done();
}).catch(done);
});
it("triggers preInstall and postInstall hooks", function(done) {
let receivedPreEvent,receivedPostEvent;
hooks.add("preInstall", function(event) { event.args = ["a"]; receivedPreEvent = event; })