Compare commits

...

50 Commits

Author SHA1 Message Date
Nick O'Leary
2feb290ae3
Merge pull request #5060 from gorenje/patch-3
Add .editorconfig to .gitignore
2025-02-25 09:57:04 +00:00
Nick O'Leary
45bcb74dae
Merge pull request #5062 from GogoVega/tab-config-changed-badge
Add the `changed` badge to the config node
2025-02-25 09:55:45 +00:00
GogoVega
6da890bf88
Manage badge locations with pure CSS + cleanup 2025-02-24 21:09:44 +01:00
GogoVega
b8eeef182c
Add changed badge for category header + fix cropped badge 2025-02-24 21:09:11 +01:00
GogoVega
54e0de64a7
Add the changed badge for the config node 2025-02-24 21:08:43 +01:00
Gerrit Riessen
3bd1b58217
Add .editorconfig to .gitignore
I use [EditorConfig](https://editorconfig.org/) to adjust my editor settings for indentation.

My settings should probably be best ignored, unless there is a desire to have a project wide .editorconfig
2025-02-24 19:01:17 +01:00
Nick O'Leary
71f8de94b0
Merge pull request #5056 from gorenje/master
Support text drag & drop into markdown editor
2025-02-24 16:26:19 +00:00
Nick O'Leary
4723378f2f
Merge pull request #5054 from GogoVega/deployment-ignore-disabled
Ignore state of disabled nodes/flows during deployment
2025-02-24 16:11:55 +00:00
Nick O'Leary
8837597ff5
Merge pull request #5053 from matiseni51/fix-include-label-themeSettings-config
fix: set label in themeSettings.deployButton despite type attribute
2025-02-24 16:08:47 +00:00
Gerrit Riessen
473b93f497
Support text drag & drop into markdown editor
Along with image drop, this change adds text drop into the editor. 

This is useful for highlighting text and then drag that text into the description of a node. Similar creating a mermaid diagram using a third-party tool and then drag the text into the description.

Also drop text files is also supported for longer texts.
2025-02-20 09:30:50 +01:00
GogoVega
d9c5144fe2
Ignore state of disabled nodes/flows during deployment 2025-02-17 14:05:51 +01:00
matiseni51
cbc91a9ac8
Merge branch 'master' into fix-include-label-themeSettings-config 2025-02-16 14:03:24 +01:00
matiseni51
88c946d401 fix: set label in themeSettings.deployButton despite type attribute 2025-02-16 13:44:22 +01:00
Nick O'Leary
ff565bacb4
Merge pull request #5052 from node-red/rel409
Update for 4.0.9
2025-02-14 17:07:27 +00:00
Nick O'Leary
e55301c073
Update for 4.0.9 2025-02-14 17:03:13 +00:00
Nick O'Leary
dc69226944
Merge pull request #5050 from aikitori/mqtt-update-translation
Add details for the dynamic subscription to match the English docs
2025-02-14 16:27:12 +00:00
Nick O'Leary
bf6b18b8a6
Merge pull request #5051 from GogoVega/typedInput-tooltip
Fix tooltip snapping based on `typedInput` type
2025-02-14 16:26:52 +00:00
GogoVega
e1b591d761
Fix tooltip snapping based on input type 2025-02-13 13:59:06 +01:00
aikitori
27463197cd
Add details for the dynamic subscription to match the English documentation 2025-02-13 08:55:10 +01:00
Nick O'Leary
854460db56
Merge pull request #5049 from node-red/Steve-Mcl-patch-1
Prevent symbol usage warning in monaco
2025-02-12 17:02:49 +00:00
Stephen McLaughlin
4984af48f1
Update monaco.js for symbol error 2025-02-12 09:42:55 +00:00
Nick O'Leary
e9dab46de8
Merge pull request #5025 from node-red/browse-subflow-flow-context
Show subflow flow context under node section of sidebar
2025-02-10 16:51:55 +00:00
Nick O'Leary
4605f01c5d
Merge pull request #5030 from matiseni51/add-editorTheme-custom-label-default-deploy-button
feat: Add custom label for default deploy button in settings.editorTheme
2025-02-10 16:51:45 +00:00
Nick O'Leary
a0ddf96e03
Merge pull request #5042 from node-red/5028-improve-auto-complete-long-keys
Handle long auto-complete suggests
2025-02-10 16:51:31 +00:00
Nick O'Leary
16d25b9d41
Merge pull request #5043 from node-red/5036-fix-undefined-username
Handle undefined username when generating user icon
2025-02-10 16:51:20 +00:00
Nick O'Leary
77c4ccf8fb
Handle undefined username when generating user icon
Fixes #5036
2025-02-10 16:41:18 +00:00
Nick O'Leary
7d9e09f5a7
Handle long auto-complete suggests
Fixes #5028
2025-02-10 16:23:13 +00:00
matiseni51
f4c184af4d
Merge branch 'master' into add-editorTheme-custom-label-default-deploy-button 2025-02-03 20:52:23 +01:00
Nick O'Leary
9694c8bdfa
Merge pull request #5027 from node-red/5026-fix-splice-group-add
Handle dragging node into group and splicing link at same time
2025-02-03 16:04:33 +00:00
matiseni51
ca61efc986 feat: Add custom label for default deploy button in settings.editorTheme 2025-02-01 11:44:57 +01:00
Nick O'Leary
ffdbd94927
Handle dragging node into group and splicing link at same time
Fixes #5026
2025-01-29 14:22:44 +00:00
Nick O'Leary
43df2318d4
Show subflow flow context under node section of sidebar 2025-01-27 16:36:57 +00:00
Nick O'Leary
21612a5215
Merge pull request #5021 from node-red/5008-remember-context-tree-state
Remember context sidebar tree state when refreshing
2025-01-27 15:58:43 +00:00
Nick O'Leary
756485e308
Merge pull request #5023 from node-red/5010-update-instance-envvars
Update sf instance env vars when removed from template
2025-01-27 15:58:30 +00:00
Nick O'Leary
efbe38f509
Merge pull request #5022 from node-red/5020-quick-add-group-select
Do not select group when triggering quick-add within it
2025-01-27 15:58:17 +00:00
Nick O'Leary
daa76e6e5f
Update sf instance env vars when removed from template 2025-01-22 10:25:24 +00:00
Nick O'Leary
48d2d269a5
Do not select group when triggering quick-add within it 2025-01-21 16:15:13 +00:00
Nick O'Leary
13cac1b5ef
Remember context sidebar tree state when refreshing
Closes #5008
2025-01-21 13:56:44 +00:00
Nick O'Leary
479b7e756d
Merge pull request #5016 from node-red/5009-fix-env-context-access
Allow env var access to context
2025-01-20 11:32:55 +00:00
Nick O'Leary
503ef62cf5
Merge pull request #5018 from node-red/fix-debug-status-if-null
fix debug status reporting if null
2025-01-20 11:32:43 +00:00
Nick O'Leary
b355a37378
Merge pull request #5017 from node-red/5004-icon-fix-2
Fix library icon handling within library browser component
2025-01-20 11:32:05 +00:00
Dave Conway-Jones
1acc16c9ef
fix debug status reporting if null 2025-01-19 10:57:58 +00:00
Nick O'Leary
4cbf672b26
Fix library icon handling within library browser component
Closes #5004
2025-01-17 17:07:58 +00:00
Nick O'Leary
272355a48e
Merge pull request #5014 from node-red/5013-switch-call-stack-fix
Avoid exceeding call stack when draining message group in Switch
2025-01-17 16:48:17 +00:00
Nick O'Leary
e2981f2970
Allow env var access to context 2025-01-17 16:45:44 +00:00
Nick O'Leary
804551000a
Merge pull request #5015 from node-red/fix-nodemon
Fix grunt dev via better ndoemon ignore rules
2025-01-15 14:43:08 +00:00
Nick O'Leary
254b6a1e23
Fix grunt dev via better ndoemon ignore rules 2025-01-15 10:32:10 +00:00
Nick O'Leary
3838e4e605
Merge pull request #5007 from natcl/patch-7
Fix typo in CHANGELOG (4.0.7-->4.0.8)
2025-01-13 16:44:29 +00:00
Nick O'Leary
953b7584a3
Avoid exceeding call stack when draining message group in Switch
Fixes #5013
2025-01-13 16:37:35 +00:00
Nathanaël Lécaudé
3da22882e9
Fix typo in CHANGELOG (4.0.7-->4.0.8) 2025-01-07 11:30:35 -05:00
36 changed files with 440 additions and 149 deletions

1
.gitignore vendored
View File

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

View File

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

View File

@ -1,4 +1,30 @@
#### 4.0.7: Maintenance Release
#### 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

16
nodemon.json Normal file
View File

@ -0,0 +1,16 @@
{
"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.9",
"description": "Low-code programming for event-driven applications",
"homepage": "https://nodered.org",
"license": "Apache-2.0",

View File

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

View File

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

View File

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

View File

@ -398,14 +398,13 @@ 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`);
group.appendChild(anonIconBody)
} else {
const labelText = user.username ? user.username.substring(0,2) : user
const label = document.createElementNS("http://www.w3.org/2000/svg","text");
if (user.username) {
if (user.username || user.email) {
label.setAttribute("class","red-ui-multiplayer-annotation-label");
label.textContent = user.username.substring(0,2)
label.textContent = (user.username || user.email).substring(0,2)
} else {
label.setAttribute("class","red-ui-multiplayer-annotation-label red-ui-multiplayer-user-count")
label.textContent = user
label.textContent = 'nr'
}
label.setAttribute("text-anchor", "middle")
label.setAttribute("x",radius/2);

View File

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

View File

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

View File

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

View File

@ -1836,8 +1836,18 @@ RED.editor = (function() {
}
});
}
let envToRemove = new Set()
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;
editing_node.env = new_env;
editState.changed = true;
@ -1846,10 +1856,11 @@ RED.editor = (function() {
if (editState.changed) {
var wasChanged = editing_node.changed;
let wasChanged = editing_node.changed;
editing_node.changed = true;
validateNode(editing_node);
var subflowInstances = [];
let subflowInstances = [];
let instanceHistoryEvents = []
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+editing_node.id) {
subflowInstances.push({
@ -1859,13 +1870,35 @@ RED.editor = (function() {
n._def.color = editing_node.color;
n.changed = 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);
validateNode(n);
}
});
RED.events.emit("subflows:change",editing_node);
RED.nodes.dirty(true);
var historyEvent = {
let historyEvent = {
t:'edit',
node:editing_node,
changes:editState.changes,
@ -1875,7 +1908,13 @@ RED.editor = (function() {
instances:subflowInstances
}
};
if (instanceHistoryEvents.length > 0) {
historyEvent = {
t: 'multi',
events: [ historyEvent, ...instanceHistoryEvents ],
dirty: wasDirty
}
}
RED.history.push(historyEvent);
}
editing_node.dirty = true;

View File

@ -691,6 +691,7 @@ RED.editor.codeEditor.monaco = (function() {
2322, //Type 'unknown' is not assignable to type 'string'
2339, //property does not exist on
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,
80001, //Convert to ES6 module
80004, //JSDoc types may be moved to TypeScript types.

View File

@ -27,6 +27,12 @@
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 currentEditor = null;
/**
@ -52,6 +58,7 @@
if (files.length === 1) {
var file = files[0];
var name = file.name.toLowerCase();
var fileType = file.type.toLowerCase();
if (name.match(/\.(apng|avif|gif|jpeg|png|svg|webp)$/)) {
file2base64Image(file, function (image) {
@ -63,6 +70,29 @@
});
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();

View File

@ -245,10 +245,15 @@ 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: lib.icon || 'fa fa-hdd-o',
icon,
label: RED._(lib.label||lib.id),
path: "",
expanded: true,
@ -303,10 +308,15 @@ 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: lib.icon || 'fa fa-hdd-o',
icon,
label: RED._(lib.label||lib.id),
path: "",
expanded: true,

View File

@ -56,7 +56,16 @@ RED.sidebar.config = (function() {
} else {
$('<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);
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.on("click", function(e) {
$(content).find(".red-ui-palette-node").removeClass("selected");
@ -150,9 +159,6 @@ RED.sidebar.config = (function() {
$('<li class="red-ui-palette-node-config-type">'+node.type+'</li>').appendTo(list);
currentType = node.type;
}
if (node.changed) {
labelText += "!!"
}
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);
entry.data('node',node.id);
@ -181,15 +187,29 @@ 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) {
nodeDiv.addClass("red-ui-palette-node-config-invalid")
const nodeDivAnnotations = $('<svg class="red-ui-palette-node-annotations red-ui-flow-node-error" width="10" height="10"></svg>').appendTo(nodeDiv)
const errorBadge = document.createElementNS("http://www.w3.org/2000/svg","path");
errorBadge.setAttribute("d","M 0,9 l 10,0 -5,-8 z");
nodeDivAnnotations.append($(errorBadge))
const nodeDivAnnotations = $('<svg class="red-ui-palette-node-annotations red-ui-flow-node-error" width="10" height="10"></svg>').appendTo(nodeDiv);
const errorBadge = document.createElementNS("http://www.w3.org/2000/svg", "path");
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 () {
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> - ");
}
})
}
@ -252,6 +272,10 @@ RED.sidebar.config = (function() {
$(this).remove();
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 configList = {};

View File

@ -18,8 +18,6 @@ RED.sidebar.context = (function() {
var content;
var sections;
var localCache = {};
var flowAutoRefresh;
var nodeAutoRefresh;
var nodeSection;
@ -27,6 +25,8 @@ RED.sidebar.context = (function() {
var flowSection;
var globalSection;
const expandedPaths = {}
var currentNode;
var currentFlow;
@ -212,14 +212,41 @@ RED.sidebar.context = (function() {
var l = keys.length;
for (var i = 0; i < l; i++) {
sortedData[keys[i]].forEach(function(v) {
var k = keys[i];
var l2 = sortedData[k].length;
var propRow = $('<tr class="red-ui-help-info-row"><td class="red-ui-sidebar-context-property"></td><td></td></tr>').appendTo(container);
var obj = $(propRow.children()[0]);
const k = keys[i];
let payload = v.msg;
let format = v.format;
const tools = $('<span class="button-group"></span>');
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);
var tools = $('<span class="button-group"></span>');
const urlSafeK = encodeURIComponent(k)
var refreshItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-refresh"></i></button>').appendTo(tools).on("click", function(e) {
const 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.stopPropagation();
$.getJSON(baseUrl+"/"+urlSafeK+"?store="+v.store, function(data) {
@ -229,16 +256,14 @@ RED.sidebar.context = (function() {
tools.detach();
$(propRow.children()[1]).empty();
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
...objectElementOptions,
typeHint: data.format,
sourceId: id+"."+k,
tools: tools,
path: k
}).appendTo(propRow.children()[1]);
}
})
});
RED.popover.tooltip(refreshItem,RED._("sidebar.context.refrsh"));
var deleteItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-trash"></i></button>').appendTo(tools).on("click", function(e) {
const 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.stopPropagation();
var popover = RED.popover.create({
@ -246,7 +271,7 @@ RED.sidebar.context = (function() {
target: propRow,
direction: "left",
content: function() {
var content = $('<div>');
const content = $('<div>');
$('<p data-i18n="sidebar.context.deleteConfirm"></p>').appendTo(content);
var row = $('<p>').appendTo(content);
var bg = $('<span class="button-group"></span>').appendTo(row);
@ -269,16 +294,15 @@ RED.sidebar.context = (function() {
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();
}
delete expandedPaths[id + "." + k]
} else {
payload = data.msg;
format = data.format;
tools.detach();
$(propRow.children()[1]).empty();
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
typeHint: data.format,
sourceId: id+"."+k,
tools: tools,
path: k
...objectElementOptions,
typeHint: data.format
}).appendTo(propRow.children()[1]);
}
});
@ -293,14 +317,7 @@ RED.sidebar.context = (function() {
});
RED.popover.tooltip(deleteItem,RED._("sidebar.context.delete"));
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]);
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), objectElementOptions).appendTo(propRow.children()[1]);
if (contextStores.length > 1) {
$("<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 formattedPaths = {};
function addMessageControls(obj,sourceId,key,msg,rootPath,strippedKey,extraTools) {
function addMessageControls(obj,sourceId,key,msg,rootPath,strippedKey,extraTools,enablePinning) {
if (!pinnedPaths.hasOwnProperty(sourceId)) {
pinnedPaths[sourceId] = {}
}
@ -250,7 +250,7 @@ RED.utils = (function() {
RED.clipboard.copyText(msg,copyPayload,"clipboard.copyMessageValue");
})
RED.popover.tooltip(copyPayload,RED._("node-red:debug.sidebar.copyPayload"));
if (strippedKey !== undefined && strippedKey !== '') {
if (enablePinning && strippedKey !== undefined && 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) {
@ -281,13 +281,16 @@ RED.utils = (function() {
}
}
}
function checkExpanded(strippedKey,expandPaths,minRange,maxRange) {
function checkExpanded(strippedKey, expandPaths, { minRange, maxRange, expandLeafNodes }) {
if (expandPaths && expandPaths.length > 0) {
if (strippedKey === '' && minRange === undefined) {
return true;
}
for (var i=0;i<expandPaths.length;i++) {
var p = expandPaths[i];
if (expandLeafNodes && p === strippedKey) {
return true
}
if (p.indexOf(strippedKey) === 0 && (p[strippedKey.length] === "." || p[strippedKey.length] === "[") ) {
if (minRange !== undefined && p[strippedKey.length] === "[") {
@ -394,6 +397,8 @@ RED.utils = (function() {
var sourceId = options.sourceId;
var rootPath = options.rootPath;
var expandPaths = options.expandPaths;
const enablePinning = options.enablePinning
const expandLeafNodes = options.expandLeafNodes;
var ontoggle = options.ontoggle;
var exposeApi = options.exposeApi;
var tools = options.tools;
@ -416,11 +421,11 @@ RED.utils = (function() {
}
header = $('<span class="red-ui-debug-msg-row"></span>').appendTo(element);
if (sourceId) {
addMessageControls(header,sourceId,path,obj,rootPath,strippedKey,tools);
addMessageControls(header,sourceId,path,obj,rootPath,strippedKey,tools, enablePinning);
}
if (!key) {
element.addClass("red-ui-debug-msg-top-level");
if (sourceId) {
if (sourceId && !expandPaths) {
var pinned = pinnedPaths[sourceId];
expandPaths = [];
if (pinned) {
@ -476,7 +481,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);
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);
},function(state) {if (ontoggle) { ontoggle(path,state);}}, checkExpanded(strippedKey,expandPaths));
},function(state) {if (ontoggle) { ontoggle(path,state);}}, checkExpanded(strippedKey, expandPaths, { expandLeafNodes }));
}
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)) {
@ -592,14 +597,16 @@ RED.utils = (function() {
typeHint: type==='buffer'?'hex':false,
hideKey: false,
path: path+"["+i+"]",
sourceId: sourceId,
rootPath: rootPath,
expandPaths: expandPaths,
ontoggle: ontoggle,
exposeApi: exposeApi,
sourceId,
rootPath,
expandPaths,
expandLeafNodes,
ontoggle,
exposeApi,
// tools: tools // Do not pass tools down as we
// keep them attached to the top-level header
nodeSelector: options.nodeSelector,
enablePinning
}
).appendTo(row);
}
@ -623,21 +630,23 @@ RED.utils = (function() {
typeHint: type==='buffer'?'hex':false,
hideKey: false,
path: path+"["+i+"]",
sourceId: sourceId,
rootPath: rootPath,
expandPaths: expandPaths,
ontoggle: ontoggle,
exposeApi: exposeApi,
sourceId,
rootPath,
expandPaths,
expandLeafNodes,
ontoggle,
exposeApi,
// tools: tools // Do not pass tools down as we
// keep them attached to the top-level header
nodeSelector: options.nodeSelector,
enablePinning
}
).appendTo(row);
}
}
})(),
(function() { var path = path+"["+i+"]"; return function(state) {if (ontoggle) { ontoggle(path,state);}}})(),
checkExpanded(strippedKey,expandPaths,minRange,Math.min(fullLength-1,(minRange+9))));
checkExpanded(strippedKey,expandPaths,{ minRange, maxRange: Math.min(fullLength-1,(minRange+9)), expandLeafNodes}));
$('<span class="red-ui-debug-msg-object-key"></span>').html("["+minRange+" &hellip; "+Math.min(fullLength-1,(minRange+9))+"]").appendTo(header);
}
if (fullLength < originalLength) {
@ -646,7 +655,7 @@ RED.utils = (function() {
}
},
function(state) {if (ontoggle) { ontoggle(path,state);}},
checkExpanded(strippedKey,expandPaths));
checkExpanded(strippedKey, expandPaths, { expandLeafNodes }));
}
} else if (typeof obj === 'object') {
element.addClass('collapsed');
@ -680,14 +689,16 @@ RED.utils = (function() {
typeHint: false,
hideKey: false,
path: newPath,
sourceId: sourceId,
rootPath: rootPath,
expandPaths: expandPaths,
ontoggle: ontoggle,
exposeApi: exposeApi,
sourceId,
rootPath,
expandPaths,
expandLeafNodes,
ontoggle,
exposeApi,
// tools: tools // Do not pass tools down as we
// keep them attached to the top-level header
nodeSelector: options.nodeSelector,
enablePinning
}
).appendTo(row);
}
@ -696,7 +707,7 @@ RED.utils = (function() {
}
},
function(state) {if (ontoggle) { ontoggle(path,state);}},
checkExpanded(strippedKey,expandPaths));
checkExpanded(strippedKey, expandPaths, { expandLeafNodes }));
}
if (key) {
$('<span class="red-ui-debug-msg-type-meta"></span>').text(type).appendTo(entryObj);

View File

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

View File

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

View File

@ -84,6 +84,11 @@ ul.red-ui-sidebar-node-config-list {
background: var(--red-ui-node-config-background);
color: var(--red-ui-primary-text-color);
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 {
color: var(--red-ui-secondary-text-color);
@ -115,6 +120,15 @@ ul.red-ui-sidebar-node-config-list li.red-ui-palette-node-config-type {
.red-ui-palette-node-config-invalid {
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 {
position: absolute;
left: calc(100% - 15px);

View File

@ -2,4 +2,15 @@
&.red-ui-popover-panel {
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 fill = "grey";
var shape = "dot";
if (typeof output === 'object' && hasOwnProperty.call(output, "fill") && hasOwnProperty.call(output, "shape") && hasOwnProperty.call(output, "text")) {
if (typeof output === 'object' && output?.fill && output?.shape && output?.text) {
fill = output.fill;
shape = output.shape;
st = output.text;

View File

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

View File

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

View File

@ -39,10 +39,36 @@
<dd><b>MQTTv5</b>: Ablaufzeit der Nachricht in Sekunden.</dd>
</dl>
<h3>Details</h3>
<p>Das abonnierte Topic darf MQTT-Platzhalterzeichen (wildcards) enthalten (+ für eine Ebene und # für mehrere Ebenen).</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>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>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 type="text/html" data-help-name="mqtt out">

View File

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

View File

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

View File

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

View File

@ -719,6 +719,14 @@ 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,6 +49,14 @@ 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,7 +100,24 @@ async function evaluateEnvProperties(flow, env, credentials) {
}
} else if (type ==='jsonata') {
pendingEvaluations.push(new Promise((resolve, _) => {
redUtil.evaluateNodeProperty(value, 'jsonata', {_flow: flow}, null, (err, result) => {
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) => {
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.9",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@ -16,8 +16,8 @@
}
],
"dependencies": {
"@node-red/registry": "4.0.8",
"@node-red/util": "4.0.8",
"@node-red/registry": "4.0.9",
"@node-red/util": "4.0.9",
"async-mutex": "0.5.0",
"clone": "2.1.2",
"express": "4.21.2",

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "4.0.8",
"version": "4.0.9",
"description": "Low-code programming for event-driven applications",
"homepage": "https://nodered.org",
"license": "Apache-2.0",
@ -31,10 +31,10 @@
"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.9",
"@node-red/runtime": "4.0.9",
"@node-red/util": "4.0.9",
"@node-red/nodes": "4.0.9",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"cors": "2.8.5",