Compare commits

..

No commits in common. "master" and "4.0.7" have entirely different histories.

37 changed files with 162 additions and 494 deletions

1
.gitignore vendored
View File

@ -28,4 +28,3 @@ docs
.nyc_output .nyc_output
sync.ffs_db sync.ffs_db
package-lock.json package-lock.json
.editorconfig

4
.nodemonignore Normal file
View File

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

View File

@ -1,35 +1,3 @@
#### 4.0.9: Maintenance Release
Editor
- Add details for the dynamic subscription to match the English docs (#5050) @aikitori
- Fix tooltip snapping based on `typedInput` type (#5051) @GogoVega
- Prevent symbol usage warning in monaco (#5049) @Steve-Mcl
- Show subflow flow context under node section of sidebar (#5025) @knolleary
- feat: Add custom label for default deploy button in settings.editorTheme (#5030) @matiseni51
- Handle long auto-complete suggests (#5042) @knolleary
- Handle undefined username when generating user icon (#5043) @knolleary
- Handle dragging node into group and splicing link at same time (#5027) @knolleary
- Remember context sidebar tree state when refreshing (#5021) @knolleary
- Update sf instance env vars when removed from template (#5023) @knolleary
- Do not select group when triggering quick-add within it (#5022) @knolleary
- Fix library icon handling within library browser component (#5017) @knolleary
Runtime
- Allow env var access to context (#5016) @knolleary
- fix debug status reporting if null (#5018) @dceejay
- Fix grunt dev via better ndoemon ignore rules (#5015) @knolleary
- Fix typo in CHANGELOG (4.0.7-->4.0.8) (#5007) @natcl
Nodes
- Switch: Avoid exceeding call stack when draining message group in Switch (#5014) @knolleary
#### 4.0.8: Maintenance Release
Editor
- Fix config node sort order when importing (#5000) @knolleary
#### 4.0.7: Maintenance Release #### 4.0.7: Maintenance Release
Editor 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", "name": "node-red",
"version": "4.0.9", "version": "4.0.7",
"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",

View File

@ -185,12 +185,13 @@ module.exports = {
} }
if (theme.deployButton) { if (theme.deployButton) {
themeSettings.deployButton = {};
if (theme.deployButton.label) {
themeSettings.deployButton.label = theme.deployButton.label;
}
if (theme.deployButton.type == "simple") { if (theme.deployButton.type == "simple") {
themeSettings.deployButton.type = theme.deployButton.type; themeSettings.deployButton = {
type: "simple"
}
if (theme.deployButton.label) {
themeSettings.deployButton.label = theme.deployButton.label;
}
if (theme.deployButton.icon) { if (theme.deployButton.icon) {
url = serveFile(themeApp,"/deploy/",theme.deployButton.icon); url = serveFile(themeApp,"/deploy/",theme.deployButton.icon);
if (url) { if (url) {

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/editor-api", "name": "@node-red/editor-api",
"version": "4.0.9", "version": "4.0.7",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@ -16,8 +16,8 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/util": "4.0.9", "@node-red/util": "4.0.7",
"@node-red/editor-client": "4.0.9", "@node-red/editor-client": "4.0.7",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"body-parser": "1.20.3", "body-parser": "1.20.3",
"clone": "2.1.2", "clone": "2.1.2",

View File

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

View File

@ -398,13 +398,14 @@ RED.multiplayer = (function () {
anonIconBody.setAttribute("d",`M ${radius/2} ${radius/2 + 5} h -2.5 c -2 1 -2 -5 0.5 -4.5 c 2 1 2 1 4 0 c 2.5 -0.5 2.5 5.5 0 4.5 z`); anonIconBody.setAttribute("d",`M ${radius/2} ${radius/2 + 5} h -2.5 c -2 1 -2 -5 0.5 -4.5 c 2 1 2 1 4 0 c 2.5 -0.5 2.5 5.5 0 4.5 z`);
group.appendChild(anonIconBody) group.appendChild(anonIconBody)
} else { } else {
const labelText = user.username ? user.username.substring(0,2) : user
const label = document.createElementNS("http://www.w3.org/2000/svg","text"); const label = document.createElementNS("http://www.w3.org/2000/svg","text");
if (user.username || user.email) { if (user.username) {
label.setAttribute("class","red-ui-multiplayer-annotation-label"); label.setAttribute("class","red-ui-multiplayer-annotation-label");
label.textContent = (user.username || user.email).substring(0,2) label.textContent = user.username.substring(0,2)
} else { } else {
label.setAttribute("class","red-ui-multiplayer-annotation-label red-ui-multiplayer-user-count") label.setAttribute("class","red-ui-multiplayer-annotation-label red-ui-multiplayer-user-count")
label.textContent = 'nr' label.textContent = user
} }
label.setAttribute("text-anchor", "middle") label.setAttribute("text-anchor", "middle")
label.setAttribute("x",radius/2); label.setAttribute("x",radius/2);

View File

@ -2099,8 +2099,6 @@ RED.nodes = (function() {
activeWorkspace = RED.workspaces.active(); activeWorkspace = RED.workspaces.active();
} }
const pendingConfigNodes = []
const pendingConfigNodeIds = new Set()
// Find all config nodes and add them // Find all config nodes and add them
for (i=0;i<newNodes.length;i++) { for (i=0;i<newNodes.length;i++) {
n = newNodes[i]; n = newNodes[i];
@ -2160,8 +2158,7 @@ RED.nodes = (function() {
type:n.type, type:n.type,
info: n.info, info: n.info,
users:[], users:[],
_config:{}, _config:{}
_configNodeReferences: new Set()
}; };
if (!n.z) { if (!n.z) {
delete configNode.z; delete configNode.z;
@ -2176,9 +2173,6 @@ RED.nodes = (function() {
if (def.defaults.hasOwnProperty(d)) { if (def.defaults.hasOwnProperty(d)) {
configNode[d] = n[d]; configNode[d] = n[d];
configNode._config[d] = JSON.stringify(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')) { if (def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
@ -2195,54 +2189,25 @@ RED.nodes = (function() {
configNode.id = getID(); configNode.id = getID();
} }
node_map[n.id] = configNode; node_map[n.id] = configNode;
pendingConfigNodes.push(configNode); new_nodes.push(configNode);
pendingConfigNodeIds.add(configNode.id)
} }
} }
} }
// We need to sort new_nodes (which only contains config nodes at this point) // Config node can use another config node, must ensure that this other
// to ensure they get added in the right order. If NodeA depends on NodeB, then // config node is added before to exists when updating the user list
// NodeB must be added first. const configNodeFilter = function (node) {
let count = 0;
// Limit us to 5 full iterations of the list - this should be more than if (node._def?.defaults) {
// enough to process the list as config->config node relationships are for (const def of Object.values(node._def.defaults)) {
// not very common if (def.type) {
let iterationLimit = pendingConfigNodes.length * 5 count++;
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-- return count;
} };
if (pendingConfigNodes.length > 0) { new_nodes.sort((a, b) => configNodeFilter(a) - configNodeFilter(b));
// 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 // Find regular flow nodes and subflow instances
for (i=0;i<newNodes.length;i++) { for (i=0;i<newNodes.length;i++) {

View File

@ -61,7 +61,7 @@
} }
this.menu = RED.popover.menu({ this.menu = RED.popover.menu({
tabSelect: true, tabSelect: true,
width: Math.max(300, this.element.width()), width: 300,
maxHeight: 200, maxHeight: 200,
class: "red-ui-autoComplete-container", class: "red-ui-autoComplete-container",
options: completions, options: completions,

View File

@ -63,7 +63,6 @@
pre: value.substring(0,idx), pre: value.substring(0,idx),
match: value.substring(idx,idx+len), match: value.substring(idx,idx+len),
post: value.substring(idx+len), post: value.substring(idx+len),
exact: idx === 0 && value.length === searchValue.length
} }
} }
function generateSpans(match) { function generateSpans(match) {
@ -84,7 +83,7 @@
const srcMatch = getMatch(optSrc, val); const srcMatch = getMatch(optSrc, val);
if (valMatch.found || srcMatch.found) { if (valMatch.found || srcMatch.found) {
const element = $('<div>',{style: "display: flex"}); const element = $('<div>',{style: "display: flex"});
const valEl = $('<div/>',{ class: "red-ui-autoComplete-completion" }); const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
valEl.append(generateSpans(valMatch)); valEl.append(generateSpans(valMatch));
valEl.appendTo(element); valEl.appendTo(element);
if (optSrc) { if (optSrc) {
@ -160,7 +159,7 @@
if (valMatch.found) { if (valMatch.found) {
const optSrc = envVarsMap[v] const optSrc = envVarsMap[v]
const element = $('<div>',{style: "display: flex"}); const element = $('<div>',{style: "display: flex"});
const valEl = $('<div/>',{ class: "red-ui-autoComplete-completion" }); const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
valEl.append(generateSpans(valMatch)) valEl.append(generateSpans(valMatch))
valEl.appendTo(element) valEl.appendTo(element)
@ -202,7 +201,7 @@
const that = this const that = this
const getContextKeysFromRuntime = function(scope, store, searchKey, done) { const getContextKeysFromRuntime = function(scope, store, searchKey, done) {
contextKnownKeys[scope] = contextKnownKeys[scope] || {} contextKnownKeys[scope] = contextKnownKeys[scope] || {}
contextKnownKeys[scope][store] = contextKnownKeys[scope][store] || new Map() contextKnownKeys[scope][store] = contextKnownKeys[scope][store] || new Set()
if (searchKey.length > 0) { if (searchKey.length > 0) {
try { try {
RED.utils.normalisePropertyExpression(searchKey) RED.utils.normalisePropertyExpression(searchKey)
@ -224,12 +223,11 @@
const result = data[store] || {} const result = data[store] || {}
const keys = result.keys || [] const keys = result.keys || []
const keyPrefix = searchKey + (searchKey.length > 0 ? '.' : '') const keyPrefix = searchKey + (searchKey.length > 0 ? '.' : '')
keys.forEach(keyInfo => { keys.forEach(key => {
const key = keyInfo.key
if (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(key)) { if (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(key)) {
contextKnownKeys[scope][store].set(keyPrefix + key, keyInfo) contextKnownKeys[scope][store].add(keyPrefix + key)
} else { } else {
contextKnownKeys[scope][store].set(searchKey + "[\""+key.replace(/"/,"\\\"")+"\"]", keyInfo) contextKnownKeys[scope][store].add(searchKey + "[\""+key.replace(/"/,"\\\"")+"\"]")
} }
}) })
done() done()
@ -244,14 +242,14 @@
// Get the flow id of the node we're editing // Get the flow id of the node we're editing
const editStack = RED.editor.getEditStack() const editStack = RED.editor.getEditStack()
if (editStack.length === 0) { if (editStack.length === 0) {
done(new Map()) done([])
return return
} }
const editingNode = editStack.pop() const editingNode = editStack.pop()
if (editingNode.z) { if (editingNode.z) {
scope = `${scope}/${editingNode.z}` scope = `${scope}/${editingNode.z}`
} else { } else {
done(new Map()) done([])
return return
} }
} }
@ -271,29 +269,17 @@
return function(val, done) { return function(val, done) {
getContextKeys(val, function (keys) { getContextKeys(val, function (keys) {
const matches = [] const matches = []
keys.forEach((keyInfo, v) => { keys.forEach(v => {
let optVal = v let optVal = v
let valMatch = getMatch(optVal, val); let valMatch = getMatch(optVal, val);
if (!valMatch.found && val.length > 0) { if (!valMatch.found && val.length > 0 && val.endsWith('.')) {
if (val.endsWith('.')) { // Search key ends in '.' - but doesn't match. Check again
// Search key ends in '.' - but doesn't match. Check again // with [" at the end instead so we match bracket notation
// with [" at the end instead so we match bracket notation valMatch = getMatch(optVal, val.substring(0, val.length - 1) + '["')
valMatch = getMatch(optVal, val.substring(0, val.length - 1) + '["')
// } else if (val.endsWith('[') && /^array/.test(keyInfo.format)) {
// console.log('this case')
}
} }
if (valMatch.found) { if (valMatch.found) {
const element = $('<div>',{style: "display: flex"}); const element = $('<div>',{style: "display: flex"});
const valEl = $('<div/>',{ class: "red-ui-autoComplete-completion" }); const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
// if (keyInfo.format) {
// valMatch.post += ' ' + keyInfo.format
// }
if (valMatch.exact && /^array/.test(keyInfo.format)) {
valMatch.post += `[0-${keyInfo.length}]`
optVal += '['
}
valEl.append(generateSpans(valMatch)) valEl.append(generateSpans(valMatch))
valEl.appendTo(element) valEl.appendTo(element)
matches.push({ matches.push({
@ -1581,8 +1567,7 @@
if (tooltip) { if (tooltip) {
tooltip.setContent(valid); tooltip.setContent(valid);
} else { } else {
const target = this.typeMap[type]?.options ? this.optionSelectLabel : this.elementDiv; tooltip = RED.popover.tooltip(this.elementDiv, valid);
tooltip = RED.popover.tooltip(target, valid);
this.element.data("tooltip", tooltip); this.element.data("tooltip", tooltip);
} }
} }

View File

@ -44,7 +44,6 @@ RED.deploy = (function() {
/** /**
* options: * options:
* type: "default" - Button with drop-down options - no further customisation available * type: "default" - Button with drop-down options - no further customisation available
* label: the text to display - default: "Deploy"
* type: "simple" - Button without dropdown. Customisations: * type: "simple" - Button without dropdown. Customisations:
* label: the text to display - default: "Deploy" * label: the text to display - default: "Deploy"
* icon : the icon to use. Null removes the icon. default: "red/images/deploy-full-o.svg" * icon : the icon to use. Null removes the icon. default: "red/images/deploy-full-o.svg"
@ -52,14 +51,13 @@ RED.deploy = (function() {
function init(options) { function init(options) {
options = options || {}; options = options || {};
var type = options.type || "default"; var type = options.type || "default";
var label = options.label || RED._("deploy.deploy");
if (type == "default") { if (type == "default") {
$('<li><span class="red-ui-deploy-button-group button-group">'+ $('<li><span class="red-ui-deploy-button-group button-group">'+
'<a id="red-ui-header-button-deploy" class="red-ui-deploy-button disabled" href="#">'+ '<a id="red-ui-header-button-deploy" class="red-ui-deploy-button disabled" href="#">'+
'<span class="red-ui-deploy-button-content">'+ '<span class="red-ui-deploy-button-content">'+
'<img id="red-ui-header-button-deploy-icon" src="red/images/deploy-full-o.svg"> '+ '<img id="red-ui-header-button-deploy-icon" src="red/images/deploy-full-o.svg"> '+
'<span>'+label+'</span>'+ '<span>'+RED._("deploy.deploy")+'</span>'+
'</span>'+ '</span>'+
'<span class="red-ui-deploy-button-spinner hide">'+ '<span class="red-ui-deploy-button-spinner hide">'+
'<img src="red/images/spin.svg"/>'+ '<img src="red/images/spin.svg"/>'+
@ -80,6 +78,7 @@ RED.deploy = (function() {
mainMenuItems.push({id:"deploymenu-item-reload", icon:"red/images/deploy-reload.svg",label:RED._("deploy.restartFlows"),sublabel:RED._("deploy.restartFlowsDesc"),onselect:"core:restart-flows"}) mainMenuItems.push({id:"deploymenu-item-reload", icon:"red/images/deploy-reload.svg",label:RED._("deploy.restartFlows"),sublabel:RED._("deploy.restartFlowsDesc"),onselect:"core:restart-flows"})
RED.menu.init({id:"red-ui-header-button-deploy-options", options: mainMenuItems }); RED.menu.init({id:"red-ui-header-button-deploy-options", options: mainMenuItems });
} else if (type == "simple") { } else if (type == "simple") {
var label = options.label || RED._("deploy.deploy");
var icon = 'red/images/deploy-full-o.svg'; var icon = 'red/images/deploy-full-o.svg';
if (options.hasOwnProperty('icon')) { if (options.hasOwnProperty('icon')) {
icon = options.icon; icon = options.icon;
@ -425,15 +424,11 @@ RED.deploy = (function() {
const unknownNodes = []; const unknownNodes = [];
const invalidNodes = []; const invalidNodes = [];
const isDisabled = function (node) {
return (node.d || RED.nodes.workspace(node.z)?.disabled);
};
RED.nodes.eachConfig(function (node) { RED.nodes.eachConfig(function (node) {
if (node.valid === undefined) { if (node.valid === undefined) {
RED.editor.validateNode(node); RED.editor.validateNode(node);
} }
if (!node.valid && !isDisabled(node)) { if (!node.valid && !node.d) {
invalidNodes.push(getNodeInfo(node)); invalidNodes.push(getNodeInfo(node));
} }
if (node.type === "unknown") { if (node.type === "unknown") {
@ -443,7 +438,7 @@ RED.deploy = (function() {
} }
}); });
RED.nodes.eachNode(function (node) { RED.nodes.eachNode(function (node) {
if (!node.valid && !isDisabled(node)) { if (!node.valid && !node.d) {
invalidNodes.push(getNodeInfo(node)); invalidNodes.push(getNodeInfo(node));
} }
if (node.type === "unknown") { if (node.type === "unknown") {
@ -457,7 +452,7 @@ RED.deploy = (function() {
const unusedConfigNodes = []; const unusedConfigNodes = [];
RED.nodes.eachConfig(function (node) { RED.nodes.eachConfig(function (node) {
if ((node._def.hasUsers !== false) && (node.users.length === 0) && !isDisabled(node)) { if ((node._def.hasUsers !== false) && (node.users.length === 0)) {
unusedConfigNodes.push(getNodeInfo(node)); unusedConfigNodes.push(getNodeInfo(node));
hasUnusedConfig = true; hasUnusedConfig = true;
} }

View File

@ -1836,18 +1836,8 @@ RED.editor = (function() {
} }
}); });
} }
let envToRemove = new Set()
if (!isSameObj(old_env, new_env)) { if (!isSameObj(old_env, new_env)) {
// Get a list of env properties that have been removed
// by comparing old_env and new_env
if (old_env) {
old_env.forEach(env => { envToRemove.add(env.name) })
}
if (new_env) {
new_env.forEach(env => {
envToRemove.delete(env.name)
})
}
editState.changes.env = editing_node.env; editState.changes.env = editing_node.env;
editing_node.env = new_env; editing_node.env = new_env;
editState.changed = true; editState.changed = true;
@ -1856,11 +1846,10 @@ RED.editor = (function() {
if (editState.changed) { if (editState.changed) {
let wasChanged = editing_node.changed; var wasChanged = editing_node.changed;
editing_node.changed = true; editing_node.changed = true;
validateNode(editing_node); validateNode(editing_node);
let subflowInstances = []; var subflowInstances = [];
let instanceHistoryEvents = []
RED.nodes.eachNode(function(n) { RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+editing_node.id) { if (n.type == "subflow:"+editing_node.id) {
subflowInstances.push({ subflowInstances.push({
@ -1870,35 +1859,13 @@ RED.editor = (function() {
n._def.color = editing_node.color; n._def.color = editing_node.color;
n.changed = true; n.changed = true;
n.dirty = true; n.dirty = true;
if (n.env) {
const oldEnv = n.env
const newEnv = []
let envChanged = false
n.env.forEach((env, index) => {
if (envToRemove.has(env.name)) {
envChanged = true
} else {
newEnv.push(env)
}
})
if (envChanged) {
instanceHistoryEvents.push({
t: 'edit',
node: n,
changes: { env: oldEnv },
dirty: n.dirty,
changed: n.changed
})
n.env = newEnv
}
}
updateNodeProperties(n); updateNodeProperties(n);
validateNode(n); validateNode(n);
} }
}); });
RED.events.emit("subflows:change",editing_node); RED.events.emit("subflows:change",editing_node);
RED.nodes.dirty(true); RED.nodes.dirty(true);
let historyEvent = { var historyEvent = {
t:'edit', t:'edit',
node:editing_node, node:editing_node,
changes:editState.changes, changes:editState.changes,
@ -1908,13 +1875,7 @@ RED.editor = (function() {
instances:subflowInstances instances:subflowInstances
} }
}; };
if (instanceHistoryEvents.length > 0) {
historyEvent = {
t: 'multi',
events: [ historyEvent, ...instanceHistoryEvents ],
dirty: wasDirty
}
}
RED.history.push(historyEvent); RED.history.push(historyEvent);
} }
editing_node.dirty = true; editing_node.dirty = true;

View File

@ -691,7 +691,6 @@ RED.editor.codeEditor.monaco = (function() {
2322, //Type 'unknown' is not assignable to type 'string' 2322, //Type 'unknown' is not assignable to type 'string'
2339, //property does not exist on 2339, //property does not exist on
2345, //Argument of type xxx is not assignable to parameter of type 'DateTimeFormatOptions' 2345, //Argument of type xxx is not assignable to parameter of type 'DateTimeFormatOptions'
2538, //Ignore symbols as index property error.
7043, //i forget what this one is, 7043, //i forget what this one is,
80001, //Convert to ES6 module 80001, //Convert to ES6 module
80004, //JSDoc types may be moved to TypeScript types. 80004, //JSDoc types may be moved to TypeScript types.

View File

@ -27,12 +27,6 @@
reader.readAsDataURL(file); reader.readAsDataURL(file);
} }
function file2Text(file,cb) {
file.arrayBuffer().then(d => {
cb( new TextDecoder().decode(d) )
}).catch(ex => { cb(`error: ${ex}`) })
}
var initialized = false; var initialized = false;
var currentEditor = null; var currentEditor = null;
/** /**
@ -58,7 +52,6 @@
if (files.length === 1) { if (files.length === 1) {
var file = files[0]; var file = files[0];
var name = file.name.toLowerCase(); var name = file.name.toLowerCase();
var fileType = file.type.toLowerCase();
if (name.match(/\.(apng|avif|gif|jpeg|png|svg|webp)$/)) { if (name.match(/\.(apng|avif|gif|jpeg|png|svg|webp)$/)) {
file2base64Image(file, function (image) { file2base64Image(file, function (image) {
@ -70,29 +63,6 @@
}); });
return; return;
} }
if ( fileType.startsWith("text/") ) {
file2Text(file, function (txt) {
var session = currentEditor.getSession();
var pos = session.getCursorPosition();
session.insert(pos, txt);
$("#red-ui-image-drop-target").hide();
});
return;
}
}
} else if ($.inArray("text/plain", ev.originalEvent.dataTransfer.types) != -1) {
let item = Object.values(ev.originalEvent.dataTransfer.items).filter(d => d.type == "text/plain")[0]
if (item) {
item.getAsString(txt => {
var session = currentEditor.getSession();
var pos = session.getCursorPosition();
session.insert(pos, txt);
$("#red-ui-image-drop-target").hide();
})
return
} }
} }
$("#red-ui-image-drop-target").hide(); $("#red-ui-image-drop-target").hide();

View File

@ -245,15 +245,10 @@ RED.library = (function() {
if (lib.types && lib.types.indexOf(options.url) === -1) { if (lib.types && lib.types.indexOf(options.url) === -1) {
return; 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({ listing.push({
library: lib.id, library: lib.id,
type: options.url, type: options.url,
icon, icon: lib.icon || 'fa fa-hdd-o',
label: RED._(lib.label||lib.id), label: RED._(lib.label||lib.id),
path: "", path: "",
expanded: true, expanded: true,
@ -308,15 +303,10 @@ RED.library = (function() {
if (lib.types && lib.types.indexOf(options.url) === -1) { if (lib.types && lib.types.indexOf(options.url) === -1) {
return; 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({ listing.push({
library: lib.id, library: lib.id,
type: options.url, type: options.url,
icon, icon: lib.icon || 'fa fa-hdd-o',
label: RED._(lib.label||lib.id), label: RED._(lib.label||lib.id),
path: "", path: "",
expanded: true, expanded: true,

View File

@ -56,16 +56,7 @@ RED.sidebar.config = (function() {
} else { } else {
$('<span class="red-ui-palette-node-config-label" data-i18n="sidebar.config.'+name+'">').appendTo(header); $('<span class="red-ui-palette-node-config-label" data-i18n="sidebar.config.'+name+'">').appendTo(header);
} }
$('<span class="red-ui-sidebar-node-config-filter-info"></span>').appendTo(header); $('<span class="red-ui-sidebar-node-config-filter-info"></span>').appendTo(header);
const changeBadgeContainer = $('<svg class="red-ui-sidebar-config-category-changed red-ui-flow-node-changed" width="10" height="10" viewBox="-1 -1 12 12"></svg>').appendTo(header);
const changeBadge = document.createElementNS("http://www.w3.org/2000/svg", "circle");
changeBadge.setAttribute("cx", "5");
changeBadge.setAttribute("cy", "5");
changeBadge.setAttribute("r", "5");
changeBadgeContainer.append(changeBadge);
category = $('<ul class="red-ui-palette-content red-ui-sidebar-node-config-list"></ul>').appendTo(container); category = $('<ul class="red-ui-palette-content red-ui-sidebar-node-config-list"></ul>').appendTo(container);
category.on("click", function(e) { category.on("click", function(e) {
$(content).find(".red-ui-palette-node").removeClass("selected"); $(content).find(".red-ui-palette-node").removeClass("selected");
@ -159,6 +150,9 @@ RED.sidebar.config = (function() {
$('<li class="red-ui-palette-node-config-type">'+node.type+'</li>').appendTo(list); $('<li class="red-ui-palette-node-config-type">'+node.type+'</li>').appendTo(list);
currentType = node.type; currentType = node.type;
} }
if (node.changed) {
labelText += "!!"
}
var entry = $('<li class="red-ui-palette-node_id_'+node.id.replace(/\./g,"-")+'"></li>').appendTo(list); var entry = $('<li class="red-ui-palette-node_id_'+node.id.replace(/\./g,"-")+'"></li>').appendTo(list);
var nodeDiv = $('<div class="red-ui-palette-node-config red-ui-palette-node"></div>').appendTo(entry); var nodeDiv = $('<div class="red-ui-palette-node-config red-ui-palette-node"></div>').appendTo(entry);
entry.data('node',node.id); entry.data('node',node.id);
@ -187,29 +181,15 @@ RED.sidebar.config = (function() {
} }
} }
if (node.changed) {
const nodeDivAnnotations = $('<svg class="red-ui-palette-node-annotations red-ui-flow-node-changed" width="10" height="10" viewBox="-1 -1 12 12"></svg>').appendTo(nodeDiv);
const changeBadge = document.createElementNS("http://www.w3.org/2000/svg", "circle");
changeBadge.setAttribute("cx", "5");
changeBadge.setAttribute("cy", "5");
changeBadge.setAttribute("r", "5");
nodeDivAnnotations.append($(changeBadge));
const categoryHeader = list.parent().find(".red-ui-sidebar-config-tray-header.red-ui-palette-header");
categoryHeader.addClass("red-ui-sidebar-config-changed");
nodeDiv.addClass("red-ui-palette-node-config-changed");
}
if (!node.valid) { if (!node.valid) {
const nodeDivAnnotations = $('<svg class="red-ui-palette-node-annotations red-ui-flow-node-error" width="10" height="10"></svg>').appendTo(nodeDiv); nodeDiv.addClass("red-ui-palette-node-config-invalid")
const errorBadge = document.createElementNS("http://www.w3.org/2000/svg", "path"); const nodeDivAnnotations = $('<svg class="red-ui-palette-node-annotations red-ui-flow-node-error" width="10" height="10"></svg>').appendTo(nodeDiv)
errorBadge.setAttribute("d", "M 0,9 l 10,0 -5,-8 z"); const errorBadge = document.createElementNS("http://www.w3.org/2000/svg","path");
nodeDivAnnotations.append($(errorBadge)); errorBadge.setAttribute("d","M 0,9 l 10,0 -5,-8 z");
nodeDivAnnotations.append($(errorBadge))
nodeDiv.addClass("red-ui-palette-node-config-invalid");
RED.popover.tooltip(nodeDivAnnotations, function () { RED.popover.tooltip(nodeDivAnnotations, function () {
if (node.validationErrors && node.validationErrors.length > 0) { if (node.validationErrors && node.validationErrors.length > 0) {
return RED._("editor.errors.invalidProperties") + "<br> - " + node.validationErrors.join("<br> - "); return RED._("editor.errors.invalidProperties")+"<br> - "+node.validationErrors.join("<br> - ")
} }
}) })
} }
@ -272,10 +252,6 @@ RED.sidebar.config = (function() {
$(this).remove(); $(this).remove();
delete categories[id]; delete categories[id];
} }
// Remove the `changed` badge from the category header
const categoryHeader = $(this).find(".red-ui-sidebar-config-tray-header.red-ui-palette-header");
categoryHeader.removeClass("red-ui-sidebar-config-changed");
}) })
var globalConfigNodes = []; var globalConfigNodes = [];
var configList = {}; var configList = {};

View File

@ -18,6 +18,8 @@ RED.sidebar.context = (function() {
var content; var content;
var sections; var sections;
var localCache = {};
var flowAutoRefresh; var flowAutoRefresh;
var nodeAutoRefresh; var nodeAutoRefresh;
var nodeSection; var nodeSection;
@ -25,8 +27,6 @@ RED.sidebar.context = (function() {
var flowSection; var flowSection;
var globalSection; var globalSection;
const expandedPaths = {}
var currentNode; var currentNode;
var currentFlow; var currentFlow;
@ -212,41 +212,14 @@ RED.sidebar.context = (function() {
var l = keys.length; var l = keys.length;
for (var i = 0; i < l; i++) { for (var i = 0; i < l; i++) {
sortedData[keys[i]].forEach(function(v) { sortedData[keys[i]].forEach(function(v) {
const k = keys[i]; var k = keys[i];
let payload = v.msg; var l2 = sortedData[k].length;
let format = v.format; var propRow = $('<tr class="red-ui-help-info-row"><td class="red-ui-sidebar-context-property"></td><td></td></tr>').appendTo(container);
const tools = $('<span class="button-group"></span>'); var obj = $(propRow.children()[0]);
expandedPaths[id + "." + k] = expandedPaths[id + "." + k] || new Set()
const objectElementOptions = {
typeHint: format,
sourceId: id + "." + k,
tools,
path: k,
rootPath: k,
exposeApi: true,
ontoggle: function(path,state) {
path = path.substring(k.length+1)
if (state) {
expandedPaths[id+"."+k].add(path)
} else {
// if 'a' has been collapsed, we want to remove 'a.b' and 'a[0]...' from the set
// of collapsed paths
for (let expandedPath of expandedPaths[id+"."+k]) {
if (expandedPath.startsWith(path+".") || expandedPath.startsWith(path+"[")) {
expandedPaths[id+"."+k].delete(expandedPath)
}
}
expandedPaths[id+"."+k].delete(path)
}
},
expandPaths: [ ...expandedPaths[id+"."+k] ].sort(),
expandLeafNodes: true
}
const propRow = $('<tr class="red-ui-help-info-row"><td class="red-ui-sidebar-context-property"></td><td></td></tr>').appendTo(container);
const obj = $(propRow.children()[0]);
obj.text(k); obj.text(k);
var tools = $('<span class="button-group"></span>');
const urlSafeK = encodeURIComponent(k) const urlSafeK = encodeURIComponent(k)
const refreshItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-refresh"></i></button>').appendTo(tools).on("click", function(e) { var refreshItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-refresh"></i></button>').appendTo(tools).on("click", function(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
$.getJSON(baseUrl+"/"+urlSafeK+"?store="+v.store, function(data) { $.getJSON(baseUrl+"/"+urlSafeK+"?store="+v.store, function(data) {
@ -256,14 +229,16 @@ RED.sidebar.context = (function() {
tools.detach(); tools.detach();
$(propRow.children()[1]).empty(); $(propRow.children()[1]).empty();
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), { RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
...objectElementOptions,
typeHint: data.format, typeHint: data.format,
sourceId: id+"."+k,
tools: tools,
path: k
}).appendTo(propRow.children()[1]); }).appendTo(propRow.children()[1]);
} }
}) })
}); });
RED.popover.tooltip(refreshItem,RED._("sidebar.context.refrsh")); RED.popover.tooltip(refreshItem,RED._("sidebar.context.refrsh"));
const deleteItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-trash"></i></button>').appendTo(tools).on("click", function(e) { var deleteItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-trash"></i></button>').appendTo(tools).on("click", function(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
var popover = RED.popover.create({ var popover = RED.popover.create({
@ -271,7 +246,7 @@ RED.sidebar.context = (function() {
target: propRow, target: propRow,
direction: "left", direction: "left",
content: function() { content: function() {
const content = $('<div>'); var content = $('<div>');
$('<p data-i18n="sidebar.context.deleteConfirm"></p>').appendTo(content); $('<p data-i18n="sidebar.context.deleteConfirm"></p>').appendTo(content);
var row = $('<p>').appendTo(content); var row = $('<p>').appendTo(content);
var bg = $('<span class="button-group"></span>').appendTo(row); var bg = $('<span class="button-group"></span>').appendTo(row);
@ -294,15 +269,16 @@ RED.sidebar.context = (function() {
if (container.children().length === 0) { if (container.children().length === 0) {
$('<tr class="red-ui-help-info-row red-ui-search-empty blank" colspan="2"><td data-i18n="sidebar.context.empty"></td></tr>').appendTo(container).i18n(); $('<tr class="red-ui-help-info-row red-ui-search-empty blank" colspan="2"><td data-i18n="sidebar.context.empty"></td></tr>').appendTo(container).i18n();
} }
delete expandedPaths[id + "." + k]
} else { } else {
payload = data.msg; payload = data.msg;
format = data.format; format = data.format;
tools.detach(); tools.detach();
$(propRow.children()[1]).empty(); $(propRow.children()[1]).empty();
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), { RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
...objectElementOptions, typeHint: data.format,
typeHint: data.format sourceId: id+"."+k,
tools: tools,
path: k
}).appendTo(propRow.children()[1]); }).appendTo(propRow.children()[1]);
} }
}); });
@ -317,7 +293,14 @@ RED.sidebar.context = (function() {
}); });
RED.popover.tooltip(deleteItem,RED._("sidebar.context.delete")); RED.popover.tooltip(deleteItem,RED._("sidebar.context.delete"));
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), objectElementOptions).appendTo(propRow.children()[1]); var payload = v.msg;
var format = v.format;
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
typeHint: v.format,
sourceId: id+"."+k,
tools: tools,
path: k
}).appendTo(propRow.children()[1]);
if (contextStores.length > 1) { if (contextStores.length > 1) {
$("<span>",{class:"red-ui-sidebar-context-property-storename"}).text(v.store).appendTo($(propRow.children()[0])) $("<span>",{class:"red-ui-sidebar-context-property-storename"}).text(v.store).appendTo($(propRow.children()[0]))
} }

View File

@ -230,7 +230,7 @@ RED.utils = (function() {
var pinnedPaths = {}; var pinnedPaths = {};
var formattedPaths = {}; var formattedPaths = {};
function addMessageControls(obj,sourceId,key,msg,rootPath,strippedKey,extraTools,enablePinning) { function addMessageControls(obj,sourceId,key,msg,rootPath,strippedKey,extraTools) {
if (!pinnedPaths.hasOwnProperty(sourceId)) { if (!pinnedPaths.hasOwnProperty(sourceId)) {
pinnedPaths[sourceId] = {} pinnedPaths[sourceId] = {}
} }
@ -250,7 +250,7 @@ RED.utils = (function() {
RED.clipboard.copyText(msg,copyPayload,"clipboard.copyMessageValue"); RED.clipboard.copyText(msg,copyPayload,"clipboard.copyMessageValue");
}) })
RED.popover.tooltip(copyPayload,RED._("node-red:debug.sidebar.copyPayload")); RED.popover.tooltip(copyPayload,RED._("node-red:debug.sidebar.copyPayload"));
if (enablePinning && strippedKey !== undefined && strippedKey !== '') { if (strippedKey !== undefined && strippedKey !== '') {
var isPinned = pinnedPaths[sourceId].hasOwnProperty(strippedKey); var isPinned = pinnedPaths[sourceId].hasOwnProperty(strippedKey);
var pinPath = $('<button class="red-ui-button red-ui-button-small red-ui-debug-msg-tools-pin"><i class="fa fa-map-pin"></i></button>').appendTo(tools).on("click", function(e) { var pinPath = $('<button class="red-ui-button red-ui-button-small red-ui-debug-msg-tools-pin"><i class="fa fa-map-pin"></i></button>').appendTo(tools).on("click", function(e) {
@ -281,16 +281,13 @@ RED.utils = (function() {
} }
} }
} }
function checkExpanded(strippedKey, expandPaths, { minRange, maxRange, expandLeafNodes }) { function checkExpanded(strippedKey,expandPaths,minRange,maxRange) {
if (expandPaths && expandPaths.length > 0) { if (expandPaths && expandPaths.length > 0) {
if (strippedKey === '' && minRange === undefined) { if (strippedKey === '' && minRange === undefined) {
return true; return true;
} }
for (var i=0;i<expandPaths.length;i++) { for (var i=0;i<expandPaths.length;i++) {
var p = expandPaths[i]; var p = expandPaths[i];
if (expandLeafNodes && p === strippedKey) {
return true
}
if (p.indexOf(strippedKey) === 0 && (p[strippedKey.length] === "." || p[strippedKey.length] === "[") ) { if (p.indexOf(strippedKey) === 0 && (p[strippedKey.length] === "." || p[strippedKey.length] === "[") ) {
if (minRange !== undefined && p[strippedKey.length] === "[") { if (minRange !== undefined && p[strippedKey.length] === "[") {
@ -397,8 +394,6 @@ RED.utils = (function() {
var sourceId = options.sourceId; var sourceId = options.sourceId;
var rootPath = options.rootPath; var rootPath = options.rootPath;
var expandPaths = options.expandPaths; var expandPaths = options.expandPaths;
const enablePinning = options.enablePinning
const expandLeafNodes = options.expandLeafNodes;
var ontoggle = options.ontoggle; var ontoggle = options.ontoggle;
var exposeApi = options.exposeApi; var exposeApi = options.exposeApi;
var tools = options.tools; var tools = options.tools;
@ -421,11 +416,11 @@ RED.utils = (function() {
} }
header = $('<span class="red-ui-debug-msg-row"></span>').appendTo(element); header = $('<span class="red-ui-debug-msg-row"></span>').appendTo(element);
if (sourceId) { if (sourceId) {
addMessageControls(header,sourceId,path,obj,rootPath,strippedKey,tools, enablePinning); addMessageControls(header,sourceId,path,obj,rootPath,strippedKey,tools);
} }
if (!key) { if (!key) {
element.addClass("red-ui-debug-msg-top-level"); element.addClass("red-ui-debug-msg-top-level");
if (sourceId && !expandPaths) { if (sourceId) {
var pinned = pinnedPaths[sourceId]; var pinned = pinnedPaths[sourceId];
expandPaths = []; expandPaths = [];
if (pinned) { if (pinned) {
@ -481,7 +476,7 @@ RED.utils = (function() {
$('<span class="red-ui-debug-msg-type-meta red-ui-debug-msg-object-type-header"></span>').text(typeHint||'string').appendTo(header); $('<span class="red-ui-debug-msg-type-meta red-ui-debug-msg-object-type-header"></span>').text(typeHint||'string').appendTo(header);
var row = $('<div class="red-ui-debug-msg-object-entry collapsed"></div>').appendTo(element); var row = $('<div class="red-ui-debug-msg-object-entry collapsed"></div>').appendTo(element);
$('<pre class="red-ui-debug-msg-type-string"></pre>').text(obj).appendTo(row); $('<pre class="red-ui-debug-msg-type-string"></pre>').text(obj).appendTo(row);
},function(state) {if (ontoggle) { ontoggle(path,state);}}, checkExpanded(strippedKey, expandPaths, { expandLeafNodes })); },function(state) {if (ontoggle) { ontoggle(path,state);}}, checkExpanded(strippedKey,expandPaths));
} }
e = $('<span class="red-ui-debug-msg-type-string red-ui-debug-msg-object-header"></span>').html('"'+formatString(sanitize(obj))+'"').appendTo(entryObj); e = $('<span class="red-ui-debug-msg-type-string red-ui-debug-msg-object-header"></span>').html('"'+formatString(sanitize(obj))+'"').appendTo(entryObj);
if (/^#[0-9a-f]{6}$/i.test(obj)) { if (/^#[0-9a-f]{6}$/i.test(obj)) {
@ -597,16 +592,14 @@ RED.utils = (function() {
typeHint: type==='buffer'?'hex':false, typeHint: type==='buffer'?'hex':false,
hideKey: false, hideKey: false,
path: path+"["+i+"]", path: path+"["+i+"]",
sourceId, sourceId: sourceId,
rootPath, rootPath: rootPath,
expandPaths, expandPaths: expandPaths,
expandLeafNodes, ontoggle: ontoggle,
ontoggle, exposeApi: exposeApi,
exposeApi,
// tools: tools // Do not pass tools down as we // tools: tools // Do not pass tools down as we
// keep them attached to the top-level header // keep them attached to the top-level header
nodeSelector: options.nodeSelector, nodeSelector: options.nodeSelector,
enablePinning
} }
).appendTo(row); ).appendTo(row);
} }
@ -630,23 +623,21 @@ RED.utils = (function() {
typeHint: type==='buffer'?'hex':false, typeHint: type==='buffer'?'hex':false,
hideKey: false, hideKey: false,
path: path+"["+i+"]", path: path+"["+i+"]",
sourceId, sourceId: sourceId,
rootPath, rootPath: rootPath,
expandPaths, expandPaths: expandPaths,
expandLeafNodes, ontoggle: ontoggle,
ontoggle, exposeApi: exposeApi,
exposeApi,
// tools: tools // Do not pass tools down as we // tools: tools // Do not pass tools down as we
// keep them attached to the top-level header // keep them attached to the top-level header
nodeSelector: options.nodeSelector, nodeSelector: options.nodeSelector,
enablePinning
} }
).appendTo(row); ).appendTo(row);
} }
} }
})(), })(),
(function() { var path = path+"["+i+"]"; return function(state) {if (ontoggle) { ontoggle(path,state);}}})(), (function() { var path = path+"["+i+"]"; return function(state) {if (ontoggle) { ontoggle(path,state);}}})(),
checkExpanded(strippedKey,expandPaths,{ minRange, maxRange: Math.min(fullLength-1,(minRange+9)), expandLeafNodes})); checkExpanded(strippedKey,expandPaths,minRange,Math.min(fullLength-1,(minRange+9))));
$('<span class="red-ui-debug-msg-object-key"></span>').html("["+minRange+" &hellip; "+Math.min(fullLength-1,(minRange+9))+"]").appendTo(header); $('<span class="red-ui-debug-msg-object-key"></span>').html("["+minRange+" &hellip; "+Math.min(fullLength-1,(minRange+9))+"]").appendTo(header);
} }
if (fullLength < originalLength) { if (fullLength < originalLength) {
@ -655,7 +646,7 @@ RED.utils = (function() {
} }
}, },
function(state) {if (ontoggle) { ontoggle(path,state);}}, function(state) {if (ontoggle) { ontoggle(path,state);}},
checkExpanded(strippedKey, expandPaths, { expandLeafNodes })); checkExpanded(strippedKey,expandPaths));
} }
} else if (typeof obj === 'object') { } else if (typeof obj === 'object') {
element.addClass('collapsed'); element.addClass('collapsed');
@ -689,16 +680,14 @@ RED.utils = (function() {
typeHint: false, typeHint: false,
hideKey: false, hideKey: false,
path: newPath, path: newPath,
sourceId, sourceId: sourceId,
rootPath, rootPath: rootPath,
expandPaths, expandPaths: expandPaths,
expandLeafNodes, ontoggle: ontoggle,
ontoggle, exposeApi: exposeApi,
exposeApi,
// tools: tools // Do not pass tools down as we // tools: tools // Do not pass tools down as we
// keep them attached to the top-level header // keep them attached to the top-level header
nodeSelector: options.nodeSelector, nodeSelector: options.nodeSelector,
enablePinning
} }
).appendTo(row); ).appendTo(row);
} }
@ -707,7 +696,7 @@ RED.utils = (function() {
} }
}, },
function(state) {if (ontoggle) { ontoggle(path,state);}}, function(state) {if (ontoggle) { ontoggle(path,state);}},
checkExpanded(strippedKey, expandPaths, { expandLeafNodes })); checkExpanded(strippedKey,expandPaths));
} }
if (key) { if (key) {
$('<span class="red-ui-debug-msg-type-meta"></span>').text(type).appendTo(entryObj); $('<span class="red-ui-debug-msg-type-meta"></span>').text(type).appendTo(entryObj);

View File

@ -1265,6 +1265,11 @@ RED.view = (function() {
var targetGroup = options.group; var targetGroup = options.group;
var touchTrigger = options.touchTrigger; var touchTrigger = options.touchTrigger;
if (targetGroup) {
selectedGroups.add(targetGroup,false);
RED.view.redraw();
}
// `point` is the place in the workspace the mouse has clicked. // `point` is the place in the workspace the mouse has clicked.
// This takes into account scrolling and scaling of the workspace. // This takes into account scrolling and scaling of the workspace.
var ox = point[0]; var ox = point[0];
@ -1586,6 +1591,9 @@ RED.view = (function() {
// auto select dropped node - so info shows (if visible) // auto select dropped node - so info shows (if visible)
clearSelection(); clearSelection();
nn.selected = true; nn.selected = true;
if (targetGroup) {
selectedGroups.add(targetGroup,false);
}
movingSet.add(nn); movingSet.add(nn);
updateActiveNodes(); updateActiveNodes();
updateSelection(); updateSelection();
@ -2170,22 +2178,17 @@ RED.view = (function() {
n.n.moved = true; n.n.moved = true;
} }
} }
// If a node has moved and ends up being spliced into a link, keep
// track of which historyEvent to add the splice info to // Check to see if we need to splice a link
let targetSpliceEvent = null
if (moveEvent.nodes.length > 0) { if (moveEvent.nodes.length > 0) {
historyEvent.events.push(moveEvent) historyEvent.events.push(moveEvent)
targetSpliceEvent = moveEvent if (activeSpliceLink) {
var linkToSplice = d3.select(activeSpliceLink).data()[0];
spliceLink(linkToSplice, movingSet.get(0).n, moveEvent)
}
} }
if (moveAndChangedGroupEvent.nodes.length > 0) { if (moveAndChangedGroupEvent.nodes.length > 0) {
historyEvent.events.push(moveAndChangedGroupEvent) historyEvent.events.push(moveAndChangedGroupEvent)
targetSpliceEvent = moveAndChangedGroupEvent
}
// activeSpliceLink will only be set if the movingSet has a single
// node that is able to splice.
if (targetSpliceEvent && activeSpliceLink) {
var linkToSplice = d3.select(activeSpliceLink).data()[0];
spliceLink(linkToSplice, movingSet.get(0).n, targetSpliceEvent)
} }
// Only continue if something has moved // Only continue if something has moved

View File

@ -351,10 +351,10 @@ RED.user = (function() {
userIcon.css({ userIcon.css({
backgroundImage: "url("+user.image+")", backgroundImage: "url("+user.image+")",
}) })
} else if (user.anonymous || (!user.username && !user.email)) { } else if (user.anonymous) {
$('<i class="fa fa-user"></i>').appendTo(userIcon); $('<i class="fa fa-user"></i>').appendTo(userIcon);
} else { } else {
$('<span>').text((user.username || user.email).substring(0,2)).appendTo(userIcon); $('<span>').text(user.username.substring(0,2)).appendTo(userIcon);
} }
if (user.profileColor !== undefined) { if (user.profileColor !== undefined) {
userIcon.addClass('red-ui-user-profile-color-' + user.profileColor) userIcon.addClass('red-ui-user-profile-color-' + user.profileColor)

View File

@ -84,11 +84,6 @@ ul.red-ui-sidebar-node-config-list {
background: var(--red-ui-node-config-background); background: var(--red-ui-node-config-background);
color: var(--red-ui-primary-text-color); color: var(--red-ui-primary-text-color);
cursor: pointer; cursor: pointer;
&.red-ui-palette-node-config-invalid.red-ui-palette-node-config-changed {
.red-ui-palette-node-annotations.red-ui-flow-node-error {
left: calc(100% - 28px);
}
}
} }
ul.red-ui-sidebar-node-config-list li.red-ui-palette-node-config-type { ul.red-ui-sidebar-node-config-list li.red-ui-palette-node-config-type {
color: var(--red-ui-secondary-text-color); color: var(--red-ui-secondary-text-color);
@ -120,15 +115,6 @@ ul.red-ui-sidebar-node-config-list li.red-ui-palette-node-config-type {
.red-ui-palette-node-config-invalid { .red-ui-palette-node-config-invalid {
border-color: var(--red-ui-form-input-border-error-color) border-color: var(--red-ui-form-input-border-error-color)
} }
.red-ui-sidebar-config-tray-header.red-ui-palette-header:not(.red-ui-sidebar-config-changed) .red-ui-flow-node-changed {
display: none;
}
.red-ui-sidebar-config-tray-header.red-ui-palette-header.red-ui-sidebar-config-changed .red-ui-flow-node-changed {
display: inline-block;
position: absolute;
top: 1px;
right: 1px;
}
.red-ui-palette-node-annotations { .red-ui-palette-node-annotations {
position: absolute; position: absolute;
left: calc(100% - 15px); left: calc(100% - 15px);

View File

@ -2,15 +2,4 @@
&.red-ui-popover-panel { &.red-ui-popover-panel {
border-top: none; border-top: none;
} }
}
.red-ui-autoComplete-completion {
font-family: var(--red-ui-monospace-font);
white-space: nowrap;
overflow: hidden;
flex-grow: 1;
text-overflow: ellipsis;
direction: rtl;
text-align: left;
} }

View File

@ -148,7 +148,7 @@ module.exports = function(RED) {
var st = (typeof output === 'string') ? output : util.inspect(output); var st = (typeof output === 'string') ? output : util.inspect(output);
var fill = "grey"; var fill = "grey";
var shape = "dot"; var shape = "dot";
if (typeof output === 'object' && output?.fill && output?.shape && output?.text) { if (typeof output === 'object' && hasOwnProperty.call(output, "fill") && hasOwnProperty.call(output, "shape") && hasOwnProperty.call(output, "text")) {
fill = output.fill; fill = output.fill;
shape = output.shape; shape = output.shape;
st = output.text; st = output.text;

View File

@ -511,10 +511,9 @@ RED.debug = (function() {
typeHint: format, typeHint: format,
hideKey: false, hideKey: false,
path: path, path: path,
sourceId: sourceNode && sourceNode.id, sourceId: sourceNode&&sourceNode.id,
rootPath: path, rootPath: path,
nodeSelector: config.messageSourceClick, nodeSelector: config.messageSourceClick,
enablePinning: true
}); });
// Do this in a separate step so the element functions aren't stripped // Do this in a separate step so the element functions aren't stripped
debugMessage.appendTo(el); debugMessage.appendTo(el);

View File

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

View File

@ -39,36 +39,10 @@
<dd><b>MQTTv5</b>: Ablaufzeit der Nachricht in Sekunden.</dd> <dd><b>MQTTv5</b>: Ablaufzeit der Nachricht in Sekunden.</dd>
</dl> </dl>
<h3>Details</h3> <h3>Details</h3>
<p>Das abonnierte Topic darf MQTT-Platzhalterzeichen (wildcards) enthalten (+ für eine Ebene und # für mehrere Ebenen).</p> <p>Das abonnierte Topic darf MQTT-Platzhalterzeichen (wildcards) enthalten (+ für eine Ebene und # für mehrere Ebenen).</p>
<p>Diese Node erfordert eine Verbindung zu einem MQTT-Broker, der über die Auswahlliste selektiert werden kann. Eine neue Verbindung wird durch Klicken auf das Stiftsymbol erstellt.</p> <p>Dieser Node erfordert eine Verbindung zu einem MQTT-Broker, der über die Auswahlliste selektiert werden kann.
Eine neue Verbindung wird durch Klicken auf das Stiftsymbol erstellt.</p>
<p>Mehrere MQTT-Nodes (in oder out) können bei Bedarf dieselbe Broker-Verbindung nutzen.</p> <p>Mehrere MQTT-Nodes (in oder out) können bei Bedarf dieselbe Broker-Verbindung nutzen.</p>
<h4>Dynamische Steuerung</h4>
Die von der Node genutzte Verbindung kann dynamisch gesteuert werden, wenn die MQTT-Node eine der folgenden Nachrichten erhält. Die Payload dieser Nachrichten werden nicht veröffentlicht.
<h4>Eingangsdaten</h4>
<p>Nur Verfügbar, wenn die Node für dynamische Abonnements konfiguriert wurde.</p>
<dl class="message-properties">
<dt>action <span class="property-type">string</span></dt>
<dd>Der Name der Aktion, die die MQTT-Node ausführen soll. Verfügbare Aktionen sind: <code>"connect"</code>, <code>"disconnect"</code>, <code>"getSubscriptions"</code>, <code>"subscribe"</code> und <code>"unsubscribe"</code>.</dd>
<dt class="optional">topic <span class="property-type">string|object|array</span></dt>
<dd>Bei den Aktionen <code>"subscribe"</code> und <code>"unsubscribe"</code> gibt diese Eigenschaft die MQTT-Topic an. Dabei kann es sich um Folgendes handeln:
<ul>
<li>eine Zeichenfolge, die den Topic-Filter enthält</li>
<li>ein Objekt mit den Eigenschaften <code>topic</code> und <code>qos</code></li>
<li>ein Array aus Zeichenfolgen oder Objekten, um mehrere Topics gleichzeitig zu verwalten</li>
</ul>
</dd>
<dt class="optional">broker <span class="property-type">broker</span> </dt>
<dd>Für die Aktion <code>"connect"</code> kann diese Eigenschaft jede der einzelnen Broker-Konfigurationseinstellungen überschreiben, einschließlich: <ul>
<li><code>broker</code></li>
<li><code>port</code></li>
<li><code>url</code> - überschreibt Broker/Port, um eine vollständige Verbindungs-URL bereitzustellen</li>
<li><code>username</code></li>
<li><code>password</code></li>
</ul>
<p>Wenn diese Eigenschaft gesetzt ist und der Broker bereits verbunden ist, wird ein Fehler protokolliert, es sei denn, die Eigenschaft <code>force</code> gesetzt - in diesem Fall wird die Verbindung zum Broker getrennt, die neuen Einstellungen angewendet und erneut verbunden.</p>
</dd>
</dl>
</script> </script>
<script type="text/html" data-help-name="mqtt out"> <script type="text/html" data-help-name="mqtt out">

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/registry", "name": "@node-red/registry",
"version": "4.0.9", "version": "4.0.7",
"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": "4.0.9", "@node-red/util": "4.0.7",
"clone": "2.1.2", "clone": "2.1.2",
"fs-extra": "11.2.0", "fs-extra": "11.2.0",
"semver": "7.6.3", "semver": "7.6.3",

View File

@ -96,11 +96,7 @@ var api = module.exports = {
} else if (scope === 'node') { } else if (scope === 'node') {
var node = runtime.nodes.getNode(id); var node = runtime.nodes.getNode(id);
if (node) { if (node) {
if (/^subflow:/.test(node.type)) { ctx = node.context();
ctx = runtime.nodes.getContext(node.id);
} else {
ctx = node.context();
}
} }
} }
if (ctx) { if (ctx) {
@ -108,25 +104,13 @@ var api = module.exports = {
store = store || availableStores.default; store = store || availableStores.default;
ctx.get(key,store,function(err, v) { ctx.get(key,store,function(err, v) {
if (opts.keysOnly) { if (opts.keysOnly) {
const result = {}
if (Array.isArray(v)) { if (Array.isArray(v)) {
result.format = `array[${v.length}]` resolve({ [store]: { format: `array[${v.length}]`}})
} else if (typeof v === 'object') { } else if (typeof v === 'object') {
result.keys = Object.keys(v).map(k => { resolve({ [store]: { keys: Object.keys(v), format: 'Object' } })
if (Array.isArray(v[k])) {
return { key: k, format: `array[${v[k].length}]`, length: v[k].length }
} else if (typeof v[k] === 'object') {
return { key: k, format: 'object' }
} else {
return { key: k }
}
})
result.format = 'object'
} else { } else {
result.keys = [] resolve({ [store]: { keys: [] }})
} }
resolve({ [store]: result })
return
} }
var encoded = util.encodeObject({msg:v}); var encoded = util.encodeObject({msg:v});
if (store !== availableStores.default) { if (store !== availableStores.default) {
@ -163,7 +147,7 @@ var api = module.exports = {
} }
return return
} }
result[store] = { keys: keys.map(key => { return { key }}) } result[store] = { keys }
c--; c--;
if (c === 0) { if (c === 0) {
if (!errorReported) { if (!errorReported) {

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() { dump() {
console.log("==================") console.log("==================")
console.log(this.TYPE, this.id); console.log(this.TYPE, this.id);

View File

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

View File

@ -100,24 +100,7 @@ async function evaluateEnvProperties(flow, env, credentials) {
} }
} else if (type ==='jsonata') { } else if (type ==='jsonata') {
pendingEvaluations.push(new Promise((resolve, _) => { pendingEvaluations.push(new Promise((resolve, _) => {
redUtil.evaluateNodeProperty(value, 'jsonata',{ redUtil.evaluateNodeProperty(value, 'jsonata', {_flow: flow}, null, (err, result) => {
// 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) => {
if (!err) { if (!err) {
if (typeof result === 'object') { if (typeof result === 'object') {
result = { value: result, __clone__: true} result = { value: result, __clone__: true}

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/runtime", "name": "@node-red/runtime",
"version": "4.0.9", "version": "4.0.7",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@ -16,8 +16,8 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/registry": "4.0.9", "@node-red/registry": "4.0.7",
"@node-red/util": "4.0.9", "@node-red/util": "4.0.7",
"async-mutex": "0.5.0", "async-mutex": "0.5.0",
"clone": "2.1.2", "clone": "2.1.2",
"express": "4.21.2", "express": "4.21.2",

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/util", "name": "@node-red/util",
"version": "4.0.9", "version": "4.0.7",
"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": "4.0.9", "version": "4.0.7",
"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,10 +31,10 @@
"flow" "flow"
], ],
"dependencies": { "dependencies": {
"@node-red/editor-api": "4.0.9", "@node-red/editor-api": "4.0.7",
"@node-red/runtime": "4.0.9", "@node-red/runtime": "4.0.7",
"@node-red/util": "4.0.9", "@node-red/util": "4.0.7",
"@node-red/nodes": "4.0.9", "@node-red/nodes": "4.0.7",
"basic-auth": "2.0.1", "basic-auth": "2.0.1",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"cors": "2.8.5", "cors": "2.8.5",