Merge branch 'dev' into major-version-and-link

This commit is contained in:
Gauthier Dandele
2025-06-03 18:02:17 +02:00
committed by GitHub
91 changed files with 2118 additions and 942 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/editor-api",
"version": "4.0.9",
"version": "4.1.0-beta.0",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,8 +16,8 @@
}
],
"dependencies": {
"@node-red/util": "4.0.9",
"@node-red/editor-client": "4.0.9",
"@node-red/util": "4.1.0-beta.0",
"@node-red/editor-client": "4.1.0-beta.0",
"bcryptjs": "3.0.2",
"body-parser": "1.20.3",
"clone": "2.1.2",
@@ -26,7 +26,7 @@
"express": "4.21.2",
"memorystore": "1.6.7",
"mime": "3.0.0",
"multer": "1.4.5-lts.2",
"multer": "2.0.0",
"mustache": "4.2.0",
"oauth2orize": "1.12.0",
"passport-http-bearer": "1.0.1",

View File

@@ -111,6 +111,7 @@
"userSettings": "User Settings",
"nodes": "Nodes",
"displayStatus": "Show node status",
"displayInfoIcon": "Show node information icon",
"displayConfig": "Configuration nodes",
"import": "Import",
"importExample": "Import example flow",
@@ -264,6 +265,8 @@
"download": "Download",
"importUnrecognised": "Imported unrecognised type:",
"importUnrecognised_plural": "Imported unrecognised types:",
"importWithModuleInfo": "Required dependencies missing",
"importWithModuleInfoDesc": "These nodes are not currently installed in your palette and are required for the imported flow:",
"importDuplicate": "Imported duplicate node:",
"importDuplicate_plural": "Imported duplicate nodes:",
"nodesExported": "Nodes exported to clipboard",
@@ -629,6 +632,8 @@
"pluginCount_plural": "__count__ plugins",
"moduleCount": "__count__ module available",
"moduleCount_plural": "__count__ modules available",
"updateCount": "__count__ update available",
"updateCount_plural": "__count__ updates available",
"inuse": "in use",
"enableall": "enable all",
"disableall": "disable all",
@@ -652,6 +657,8 @@
"more": "+ __count__ more",
"upload": "Upload module tgz file",
"refresh": "Refresh module list",
"deprecated": "deprecated",
"deprecatedTip": "This module has been deprecated",
"errors": {
"catalogLoadFailed": "<p>Failed to load node catalogue.</p><p>Check the browser console for more information</p>",
"installFailed": "<p>Failed to install: __module__</p><p>__message__</p><p>Check the log for more information</p>",
@@ -1274,5 +1281,15 @@
"environment": "Environment",
"header": "Global Environment Variables",
"revert": "Revert"
},
"telemetry": {
"label": "Update Notifications",
"settingsTitle": "Enable Update Notifications",
"settingsDescription": "<p>Node-RED can notify you when there is a new version available. This ensures you keep up to date with the latest features and fixes.</p><p>This requires sending anonymised data back to the Node-RED team. It does not include any details of your flows or users.</p><p>For full information on what information is collected and how it is used, please see the <a href=\"https://nodered.org/docs/telemetry\" target=\"_blank\">documentation</a>.</p>",
"settingsDescription2": "<p>You can change this setting at any time in the editor settings.</p>",
"enableLabel": "Yes, enable notifications",
"disableLabel": "No, do not enable notifications",
"updateAvailable": "Update available",
"updateAvailableDesc": "Node-RED __version__ is now available"
}
}

View File

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

View File

@@ -1 +1,20 @@
<svg width="46.994" height="18.006" xmlns="http://www.w3.org/2000/svg"><g stroke="#d6d6d6"><g fill="#9e3131" stroke-linejoin="round" stroke-width="3.847" transform="matrix(.25848 0 0 .2614 -63.87 -108.483)"><rect x="249.04" y="435.92" width="50.294" height="22.953" ry="6.608"/><rect x="345.63" y="416.93" width="50.294" height="22.953" ry="6.608"/><rect x="376.71" y="459.01" width="50.294" height="22.953" ry="6.608"/></g><path d="M301.04 447.43c24.406.184 7.107-18.84 42.708-19.03M374.82 470.48c-46.966.538-28.989-22.664-73.619-22.944" fill="none" stroke-width="5.771" transform="matrix(.25848 0 0 .2614 -63.87 -108.483)"/></g></svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" xmlns:osb="http://www.openswatchbook.org/uri/2009/osb" height="647" width="560" version="1.1" xmlns:cc="http://creativecommons.org/ns#" viewBox="0 0 560.00001 647.00001" xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<g transform="translate(0 -405.36)">
<g transform="translate(.000014172 .000022107)">
<path opacity="0.98" style="color-rendering:auto;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;image-rendering:auto" d="m280 405.36 280 162.03v323.32l-280 161.69-280-161.7-0.0000055092-323.41z" fill="#8f0000"/>
<path d="m278.57 1019.5-28.122-14.758-219.32-239.19 3.8673-12.478h107.86l19.715-65.534 17.337 24.866 57.948-65.047 47.857-12.857 2.1429 33.571 33.571 3.5714 33.571 0.7143 87.143 2.1428 14.96-70.328 74.709 87.705-2.0935 65.238-115.11 0.0298-1.9844 60.416 120.23 12.653 1.4251 42.694z" fill-opacity=".19898" fill-rule="evenodd"/>
<path opacity="0.98" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m21.75 766.85v108.15c-0.000146 1.7862 0.95284 3.4368 2.5 4.3301l253.25 145.97c1.5471 0.8927 3.4529 0.8927 5 0l252.86-145.98c1.5472-0.8933 2.6427-2.5439 2.6426-4.3301v-35.361-22.285-50.504-22.281-161.46c0.00015-1.7862-1.0954-3.4368-2.6426-4.3301l-252.86-145.95c-0.72594-0.41802-1.5453-0.64704-2.3828-0.66602-0.91698-0.0221-1.8224 0.20827-2.6172 0.66602l-253.25 145.95c-1.5472 0.8933-2.5001 2.5439-2.5 4.3301v88.68 20.795 51.99zm258.25-323.93l248 143.06v158.38h-99.357c-17.583 0-32.643 14.683-32.643 32.267v4.3203c-59.713-0.44167-77.52-15.896-99.729-35.316-18.46-16.144-41.584-33.808-88.092-41.188 8.6712-8.6968 13.887-18.575 18.533-27.002 4.9936-9.0548 9.5102-16.227 16.734-21.184 5.6262-3.8616 15.231-6.1887 27.666-6.9277v4.0606c0 17.583 13.655 31.97 31.238 31.97h127.3c17.583 0 33.352-14.386 33.352-31.97v-31.279c0-17.583-15.706-31.75-33.289-31.75l-127.3-0.002c-17.583 0-31.301 14.167-31.301 31.75v5.7031c-16.445 0.81071-30.442 4.0316-39.949 10.557-11.762 8.0728-18.195 19.038-23.461 28.586-5.2657 9.5484-9.5828 17.764-15.855 23.518-5.3491 4.9052-12.841 8.2718-25.018 9.8086-1.5749-16.163-15.629-27.827-32.172-27.921h-102.66v-86.38zm22.414 169.44h127.3c5.9367 0 10.289 3.8124 10.289 9.7492v31.279c0 5.9367-4.415 9.9717-10.352 9.9716h-127.3c-5.9367 0-10.766-4.035-10.766-9.9716v-31.279c0-5.9367 4.8916-9.7492 10.828-9.7492zm-270.41 80h102.65c5.9367 0 10.348 4.9199 10.348 10.857v31.281c0 5.9367-4.411 9.8623-10.348 9.8622h-102.65zm135.44 30.243c71.712 1.1287 91.494 19.195 114.55 39.359 22.126 19.349 49.351 39.661 114.01 40.516v5.4297c0 17.583 15.059 31.452 32.643 31.452h99.357v32.747l-248 143.09-248-143.09v-105.75h102.66c17.583 0 32.777-14.277 32.777-31.86zm261.21 43.757h99.355v51h-99.355c-5.9367 0-10.645-4.0661-10.645-10.003v-30.73c0-5.9367 4.7078-10.267 10.645-10.267z" fill="#fff"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 636 B

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -92,7 +92,6 @@
"ctrl-+": "core:zoom-in",
"ctrl--": "core:zoom-out",
"ctrl-0": "core:zoom-reset"
},
"red-ui-editor-stack": {
"ctrl-enter": "core:confirm-edit-tray",

View File

@@ -1495,7 +1495,12 @@ RED.nodes = (function() {
/**
* Converts the current node selection to an exportable JSON Object
**/
function createExportableNodeSet(set, exportedIds, exportedSubflows, exportedConfigNodes) {
function createExportableNodeSet(set, {
exportedIds,
exportedSubflows,
exportedConfigNodes,
includeModuleConfig = false
} = {}) {
var nns = [];
exportedIds = exportedIds || {};
@@ -1529,7 +1534,7 @@ RED.nodes = (function() {
subflowSet = subflowSet.concat(RED.nodes.junctions(subflowId))
subflowSet = subflowSet.concat(RED.nodes.groups(subflowId))
var exportableSubflow = createExportableNodeSet(subflowSet, exportedIds, exportedSubflows, exportedConfigNodes);
var exportableSubflow = createExportableNodeSet(subflowSet, { exportedIds, exportedSubflows, exportedConfigNodes });
nns = exportableSubflow.concat(nns);
}
}
@@ -1564,19 +1569,23 @@ RED.nodes = (function() {
}
nns.push(convertedNode);
if (node.type === "group") {
nns = nns.concat(createExportableNodeSet(node.nodes, exportedIds, exportedSubflows, exportedConfigNodes));
nns = nns.concat(createExportableNodeSet(node.nodes, { exportedIds, exportedSubflows, exportedConfigNodes }));
}
} else {
var convertedSubflow = convertSubflow(node, { credentials: false });
nns.push(convertedSubflow);
}
}
if (includeModuleConfig) {
updateGlobalConfigModuleList(nns)
}
return nns;
}
// Create the Flow JSON for the current configuration
// opts.credentials (whether to include (known) credentials) - default: true
// opts.dimensions (whether to include node dimensions) - default: false
// opts.includeModuleConfig (whether to include modules) - default: false
function createCompleteNodeSet(opts) {
var nns = [];
var i;
@@ -1608,6 +1617,9 @@ RED.nodes = (function() {
RED.nodes.eachNode(function(n) {
nns.push(convertNode(n, opts));
})
if (opts?.includeModuleConfig) {
updateGlobalConfigModuleList(nns);
}
return nns;
}
@@ -1835,6 +1847,7 @@ RED.nodes = (function() {
* - id:import - import as-is
* - id:copy - import with new id
* - id:replace - import over the top of existing
* - modules: map of module:version - hints for unknown nodes
*/
function importNodes(newNodesObj,options) { // createNewIds,createMissingWorkspace) {
const defOpts = { generateIds: false, addFlow: false, markChanged: false, reimport: false, importMap: {} }
@@ -1970,12 +1983,58 @@ RED.nodes = (function() {
}
if (!isInitialLoad && unknownTypes.length > 0) {
var typeList = $("<ul>");
unknownTypes.forEach(function(t) {
$("<li>").text(t).appendTo(typeList);
})
typeList = typeList[0].outerHTML;
RED.notify("<p>"+RED._("clipboard.importUnrecognised",{count:unknownTypes.length})+"</p>"+typeList,"error",false,10000);
const notificationOptions = {
type: "error",
fixed: false,
timeout: 10000,
}
let unknownNotification
let missingModules = []
if (options.modules) {
missingModules = Object.keys(options.modules).filter(module => !RED.nodes.registry.getModule(module))
}
if (missingModules.length > 0) {
notificationOptions.fixed = true
delete notificationOptions.timeout
// We have module hint list from imported global-config
// Provide option to install missing modules
notificationOptions.buttons = [
{
text: "Manage dependencies",
class:"primary",
click: function(e) {
unknownNotification.close();
RED.actions.invoke('core:manage-palette', {
view: 'install',
filter: '"' + missingModules.join('", "') + '"'
})
}
}
]
let moduleList = $("<ul>");
missingModules.forEach(function(t) {
$("<li>").text(t).appendTo(moduleList);
})
moduleList = moduleList[0].outerHTML;
unknownNotification = RED.notify(
"<p>"+RED._("clipboard.importWithModuleInfo")+"</p>"+
"<p>"+RED._("clipboard.importWithModuleInfoDesc")+"</p>"+
moduleList,
notificationOptions
);
} else {
var typeList = $("<ul>");
unknownTypes.forEach(function(t) {
$("<li>").text(t).appendTo(typeList);
})
typeList = typeList[0].outerHTML;
unknownNotification = RED.notify(
"<p>"+RED._("clipboard.importUnrecognised",{count:unknownTypes.length})+"</p>"+typeList,
notificationOptions
);
}
}
var activeWorkspace = RED.workspaces.active();
@@ -2403,6 +2462,9 @@ RED.nodes = (function() {
delete node.z;
}
}
const unknownTypeDef = RED.nodes.getType('unknown')
node._def.oneditprepare = unknownTypeDef.oneditprepare
var orig = {};
for (var p in n) {
if (n.hasOwnProperty(p) && p!="x" && p!="y" && p!="z" && p!="id" && p!="wires") {
@@ -2412,6 +2474,10 @@ RED.nodes = (function() {
node._orig = orig;
node.name = n.type;
node.type = "unknown";
if (options.modules) {
// We have a module hint list. Attach to the unknown node so we can reference it later
node.modules = Object.keys(options.modules)
}
}
if (node._def.category != "config") {
if (n.hasOwnProperty('inputs') && node._def.defaults.hasOwnProperty("inputs")) {
@@ -3098,7 +3164,33 @@ RED.nodes = (function() {
}
}
}
function getModuleListForNodes(nodes) {
const modules = {}
nodes.forEach(n => {
const nodeSet = RED.nodes.registry.getNodeSetForType(n.type)
if (nodeSet) {
modules[nodeSet.module] = nodeSet.version
}
})
return modules
}
function updateGlobalConfigModuleList(nodes) {
const modules = getModuleListForNodes(nodes)
delete modules['node-red']
const hasModules = (Object.keys(modules).length > 0)
let globalConfigNode = nodes.find(n => n.type === 'global-config')
if (!globalConfigNode && hasModules) {
globalConfigNode = {
id: RED.nodes.id(),
type: 'global-config',
env: [],
modules
}
nodes.push(globalConfigNode)
} else if (globalConfigNode) {
globalConfigNode.modules = modules
}
}
return {
init: function() {
RED.events.on("registry:node-type-added",function(type) {
@@ -3180,7 +3272,12 @@ RED.nodes = (function() {
});
RED.events.on('deploy', function () {
allNodes.clearState()
})
});
RED.actions.add("core:trigger-selected-nodes-action", function () {
const selectedNodes = RED.view.selection().nodes || [];
// Triggers the button action of the selected nodes
selectedNodes.forEach((node) => RED.view.clickNodeButton(node));
});
},
registry:registry,
setNodeList: registry.setNodeList,

View File

@@ -1,56 +0,0 @@
(function() {
var isIE11 = !!window.MSInputMethodContext && !!document.documentMode;
if (isIE11) {
// IE11 DOMTokenList.toggle does not support the two-argument variety
window.DOMTokenList.prototype.toggle = function(cl,bo) {
if (arguments.length === 1) {
bo = !this.contains(cl);
}
this[!!bo?"add":"remove"](cl);
}
// IE11 does not provide classList on SVGElements
if (! ("classList" in SVGElement.prototype)) {
Object.defineProperty(SVGElement.prototype, 'classList', Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'classList'));
}
// IE11 does not provide children on SVGElements
if (! ("children" in SVGElement.prototype)) {
Object.defineProperty(SVGElement.prototype, 'children', Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'children'));
}
Array.from = function() {
if (arguments.length > 1) {
throw new Error("Node-RED's IE11 Array.from polyfill doesn't support multiple arguments");
}
var arrayLike = arguments[0]
var result = [];
if (arrayLike.forEach) {
arrayLike.forEach(function(i) {
result.push(i);
})
} else {
for (var i=0;i<arrayLike.length;i++) {
result.push(arrayList[i]);
}
}
return result;
}
if (new Set([0]).size === 0) {
// IE does not support passing an iterable to Set constructor
var _Set = Set;
/*global Set:true */
Set = function Set(iterable) {
var set = new _Set();
if (iterable) {
iterable.forEach(set.add, set);
}
return set;
};
Set.prototype = _Set.prototype;
Set.prototype.constructor = Set;
}
}
})();

View File

@@ -358,7 +358,10 @@ var RED = (function() {
});
return;
}
if (notificationId === "update-available") {
// re-emit as an event to be handled in editor-client/src/js/ui/palette-editor.js
RED.events.emit("notification/update-available", msg)
}
if (msg.text) {
msg.default = msg.text;
var text = RED._(msg.text,msg);
@@ -672,14 +675,48 @@ var RED = (function() {
setTimeout(function() {
loader.end();
checkFirstRun(function() {
if (showProjectWelcome) {
RED.projects.showStartup();
}
});
checkTelemetry(function () {
checkFirstRun(function() {
if (showProjectWelcome) {
RED.projects.showStartup();
}
});
})
},100);
}
function checkTelemetry(done) {
const telemetrySettings = RED.settings.telemetryEnabled;
// Can only get telemetry permission from a user with permission to modify settings
if (RED.user.hasPermission("settings.write") && telemetrySettings === undefined) {
const dialog = RED.popover.dialog({
title: RED._("telemetry.settingsTitle"),
content: `${RED._("telemetry.settingsDescription")}${RED._("telemetry.settingsDescription2")}`,
closeButton: false,
buttons: [
{
text: RED._("telemetry.enableLabel"),
click: () => {
RED.settings.set("telemetryEnabled", true)
dialog.close()
done()
}
},
{
text: RED._("telemetry.disableLabel"),
click: () => {
RED.settings.set("telemetryEnabled", false)
dialog.close()
done()
}
}
]
})
} else {
done()
}
}
function checkFirstRun(done) {
if (RED.settings.theme("tours") === false) {
done();

View File

@@ -34,23 +34,13 @@ RED.clipboard = (function() {
function downloadData(file, data) {
if (window.navigator.msSaveBlob) {
// IE11 workaround
// IE does not support data uri scheme for downloading data
var blob = new Blob([data], {
type: "data:application/json;charset=utf-8"
});
navigator.msSaveBlob(blob, file);
}
else {
var element = document.createElement('a');
element.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(data));
element.setAttribute('download', file);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
const element = document.createElement('a');
element.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(data));
element.setAttribute('download', file);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
function setupDialogs() {
@@ -740,7 +730,7 @@ RED.clipboard = (function() {
nodes = RED.view.selection().nodes||[];
}
// Don't include the subflow meta-port nodes in the exported selection
nodes = RED.nodes.createExportableNodeSet(nodes.filter(function(n) { return n.type !== 'subflow'}));
nodes = RED.nodes.createExportableNodeSet(nodes.filter(function(n) { return n.type !== 'subflow'}), { includeModuleConfig: true });
} else if (type === 'flow') {
var activeWorkspace = RED.workspaces.active();
nodes = RED.nodes.groups(activeWorkspace);
@@ -755,9 +745,9 @@ RED.clipboard = (function() {
});
var parentNode = RED.nodes.workspace(activeWorkspace)||RED.nodes.subflow(activeWorkspace);
nodes.unshift(parentNode);
nodes = RED.nodes.createExportableNodeSet(nodes);
nodes = RED.nodes.createExportableNodeSet(nodes, { includeModuleConfig: true });
} else if (type === 'full') {
nodes = RED.nodes.createCompleteNodeSet({ credentials: false });
nodes = RED.nodes.createCompleteNodeSet({ credentials: false, includeModuleConfig: true });
}
if (nodes !== null) {
if (format === "red-ui-clipboard-dialog-export-fmt-full") {
@@ -858,7 +848,7 @@ RED.clipboard = (function() {
children: []
};
treeSubflows.push(subflows[node.id])
} else {
} else if (node.type !== 'global-config') {
nodes.push(node);
}
});

View File

@@ -163,13 +163,18 @@ RED.popover = (function() {
}
var timer = null;
let isOpen = false
var active;
var div;
var contentDiv;
var currentStyle;
var openPopup = function(instant) {
if (isOpen) {
return
}
if (active) {
isOpen = true
var existingPopover = target.data("red-ui-popover");
if (options.tooltip && existingPopover) {
active = false;
@@ -334,6 +339,7 @@ RED.popover = (function() {
}
var closePopup = function(instant) {
isOpen = false
$(document).off('mousedown.red-ui-popover');
if (!active) {
if (div) {
@@ -673,6 +679,74 @@ RED.popover = (function() {
show:show,
hide:hide
}
},
dialog: function(options) {
const dialogContent = $('<div style="position:relative"></div>');
if (options.closeButton !== false) {
$('<button type="button" class="red-ui-button red-ui-button-small" style="float: right; margin-top: -4px; margin-right: -4px;"><i class="fa fa-times"></i></button>').appendTo(dialogContent).click(function(evt) {
evt.preventDefault();
close();
})
}
const dialogBody = $('<div class="red-ui-dialog-body"></div>').appendTo(dialogContent);
if (options.title) {
$('<h2>').text(options.title).appendTo(dialogBody);
}
$('<div>').css("text-align","left").html(options.content).appendTo(dialogBody);
const stepToolbar = $('<div>',{class:"red-ui-dialog-toolbar"}).appendTo(dialogContent);
if (options.buttons) {
options.buttons.forEach(button => {
const btn = $('<button type="button" class="red-ui-button"></button>').text(button.text).appendTo(stepToolbar);
if (button.class) {
btn.addClass(button.class);
}
if (button.click) {
btn.on('click', function(evt) {
evt.preventDefault();
button.click();
})
}
})
}
const width = 500;
const maxWidth = Math.min($(window).width()-10,Math.max(width || 0, 300));
let shade = $('<div class="red-ui-shade" style="z-index: 2000"></div>').appendTo(document.body);
shade.fadeIn()
let popover = RED.popover.create({
target: $(".red-ui-editor"),
width: width || "auto",
maxWidth: maxWidth+"px",
direction: "inset",
class: "red-ui-dialog",
trigger: "manual",
content: dialogContent
}).open()
function close() {
if (shade) {
shade.fadeOut(() => {
shade.remove()
shade = null
})
}
if (popover) {
popover.close()
popover = null
}
}
return {
close
}
}
}

View File

@@ -33,8 +33,7 @@ RED.envVar = (function() {
id: RED.nodes.id(),
type: "global-config",
env: [],
name: "global-config",
label: "",
modules: {},
hasUsers: false,
users: [],
credentials: cred,

View File

@@ -15,26 +15,35 @@
**/
RED.palette.editor = (function() {
var disabled = false;
let catalogues = []
const loadedCatalogs = []
var editorTabs;
let filterInput;
let searchInput;
let nodeList;
let packageList;
let fullList = []
// Loaded modules
let loadedList = [];
let filteredList = [];
let loadedIndex = {};
var typesInUse = {};
var nodeEntries = {};
var eventTimers = {};
var activeFilter = "";
// Module list
let fullList = [];
let filteredList = [];
var semverre = /^(\d+)(\.(\d+))?(\.(\d+))?(-([0-9A-Za-z-]+))?(\.([0-9A-Za-z-.]+))?$/;
var NUMBERS_ONLY = /^\d+$/;
// Modules installed
let nodeEntries = {};
// EditableList
let nodeList;
let packageList;
// Nodes tab - filter
let activeFilterTerms = [];
// Nodes tab - search input
let filterInput;
// Install tab - search input
let searchInput;
const typesInUse = {};
const semverre = /^(\d+)(\.(\d+))?(\.(\d+))?(-([0-9A-Za-z-]+))?(\.([0-9A-Za-z-.]+))?$/;
const NUMBERS_ONLY = /^\d+$/;
function SemVerPart(part) {
this.number = 0;
@@ -115,8 +124,65 @@ RED.palette.editor = (function() {
});
})
}
function installNodeModule(id,version,url,callback) {
var requestBody = {
const moduleQueue = [];
const processQueue = function () {
if (moduleQueue.length === 0) {
return;
}
const { type, body, callback } = moduleQueue[0];
if (type === "install") {
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install") + ` : ${body.module} ${body.version}`);
$.ajax({
url: "nodes",
type: "POST",
data: JSON.stringify(body),
contentType: "application/json; charset=utf-8"
}).done(function(_data, _textStatus, _xhr) {
callback();
}).fail(function(xhr, textStatus, err) {
callback(xhr, textStatus, err);
}).always(function () {
// Remove the task from the list
moduleQueue.shift();
// Process the next task
processQueue();
});
} else if (type === "remove") {
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.remove") + ` : ${body.id}`);
$.ajax({
url: "nodes/" + body.id,
type: "DELETE"
}).done(function(_data, _textStatus, _xhr) {
callback();
}).fail(function(xhr, _textStatus, _err) {
callback(xhr);
}).always(function () {
// Remove the task from the list
moduleQueue.shift();
// Process the next task
processQueue();
});
}
};
/**
* Adds a module to the processing queue to install or remove it
* @param {string} type the type of request to apply to the module
* @param {Object} body an object with module info
* @param {(xhr?: JQuery.jqXHR, textStatus?: JQuery.Ajax.ErrorTextStatus, err?: string) => void} callback a callback function called when the request is done
*/
function addModuleToQueue(type, body, callback) {
moduleQueue.push({ type, body, callback });
if (moduleQueue.length === 1) {
processQueue();
}
}
function installNodeModule(id, version, url, callback) {
const requestBody = {
module: id
};
if (version) {
@@ -125,26 +191,10 @@ RED.palette.editor = (function() {
if (url) {
requestBody.url = url;
}
$.ajax({
url:"nodes",
type: "POST",
data: JSON.stringify(requestBody),
contentType: "application/json; charset=utf-8"
}).done(function(data,textStatus,xhr) {
callback();
}).fail(function(xhr,textStatus,err) {
callback(xhr,textStatus,err);
});
addModuleToQueue("install", requestBody, callback);
}
function removeNodeModule(id,callback) {
$.ajax({
url:"nodes/"+id,
type: "DELETE"
}).done(function(data,textStatus,xhr) {
callback();
}).fail(function(xhr,textStatus,err) {
callback(xhr);
})
function removeNodeModule(id, callback) {
addModuleToQueue("remove", { id: id }, callback);
}
function refreshNodeModuleList() {
@@ -155,6 +205,7 @@ RED.palette.editor = (function() {
}
}
const eventTimers = {};
function refreshNodeModule(module) {
if (!eventTimers.hasOwnProperty(module)) {
eventTimers[module] = setTimeout(function() {
@@ -419,7 +470,19 @@ RED.palette.editor = (function() {
}
function filterChange(val) {
activeFilter = val.toLowerCase();
const activeFilter = val.toLowerCase();
activeFilterTerms = []
activeFilter.split(',').forEach(term => {
term = term.trim()
if (term) {
const isExact = term[0] === '"' && term[term.length-1] === '"'
activeFilterTerms.push({
exact: isExact,
term: isExact ? term.substring(1,term.length-1) : term
})
}
})
var visible = nodeList.editableList('filter');
var size = nodeList.editableList('length');
if (val === "") {
@@ -429,58 +492,60 @@ RED.palette.editor = (function() {
}
}
let activeSort = sortModulesRelevance;
var catalogueCount;
var catalogueLoadStatus = [];
var catalogueLoadStart;
var catalogueLoadErrors = false;
var activeSort = sortModulesRelevance;
function handleCatalogResponse(err,catalog,index,v) {
const url = catalog.url
catalogueLoadStatus.push(err||v);
if (!err) {
if (v.modules) {
v.modules = v.modules.filter(function(m) {
if (RED.utils.checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) {
loadedIndex[m.id] = m;
m.index = [m.id];
if (m.keywords) {
m.index = m.index.concat(m.keywords);
}
if (m.types) {
m.index = m.index.concat(m.types);
}
if (m.updated_at) {
m.timestamp = new Date(m.updated_at).getTime();
} else {
m.timestamp = 0;
}
m.index = m.index.join(",").toLowerCase();
m.catalog = catalog;
m.catalogIndex = index;
return true;
function refreshCatalogues (done) {
const catalogueCount = catalogues.length;
loadedList = []
loadedIndex = {}
loadedCatalogs.length = 0
let handled = 0
for (let index = 0; index < catalogues.length; index++) {
const url = catalogues[index];
$.getJSON(url, {_: new Date().getTime()},function(v) {
loadedCatalogs.push({ index: index, url: url, name: v.name, updated_at: v.updated_at, modules_count: (v.modules || []).length })
handleCatalogResponse({ url: url, name: v.name},index,v);
}).fail(function(_jqxhr, _textStatus, error) {
console.warn("Error loading catalog",url,":",error);
}).always(function() {
handled++;
if (handled === catalogueCount) {
//sort loadedCatalogs by e.index ascending
loadedCatalogs.sort((a, b) => a.index - b.index)
refreshUpdateStatus();
if (done) {
done()
}
return false;
})
loadedList = loadedList.concat(v.modules);
}
} else {
catalogueLoadErrors = true;
}
})
}
if (catalogueCount > 1) {
$(".red-ui-palette-module-shade-status").html(RED._('palette.editor.loading')+"<br>"+catalogueLoadStatus.length+"/"+catalogueCount);
}
if (catalogueLoadStatus.length === catalogueCount) {
if (catalogueLoadErrors) {
RED.notify(RED._('palette.editor.errors.catalogLoadFailed',{url: url}),"error",false,8000);
}
var delta = 250-(Date.now() - catalogueLoadStart);
setTimeout(function() {
$("#red-ui-palette-module-install-shade").hide();
},Math.max(delta,0));
}
function handleCatalogResponse(catalog,index,v) {
if (v.modules) {
v.modules = v.modules.filter(function(m) {
if (RED.utils.checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) {
loadedIndex[m.id] = m;
m.index = [m.id];
if (m.keywords) {
m.index = m.index.concat(m.keywords);
}
if (m.types) {
m.index = m.index.concat(m.types);
}
if (m.updated_at) {
m.timestamp = new Date(m.updated_at).getTime();
} else {
m.timestamp = 0;
}
m.index = m.index.join(",").toLowerCase();
m.catalog = catalog;
m.catalogIndex = index;
return true;
}
return false;
})
loadedList = loadedList.concat(v.modules);
}
}
@@ -492,35 +557,19 @@ RED.palette.editor = (function() {
packageList.editableList('empty');
$(".red-ui-palette-module-shade-status").text(RED._('palette.editor.loading'));
catalogueLoadStatus = [];
catalogueLoadErrors = false;
catalogueCount = catalogues.length;
if (catalogues.length > 1) {
$(".red-ui-palette-module-shade-status").html(RED._('palette.editor.loading')+"<br>0/"+catalogues.length);
}
$("#red-ui-palette-module-install-shade").show();
catalogueLoadStart = Date.now();
var handled = 0;
loadedCatalogs.length = 0; // clear the loadedCatalogs array
for (let index = 0; index < catalogues.length; index++) {
const url = catalogues[index];
$.getJSON(url, {_: new Date().getTime()},function(v) {
loadedCatalogs.push({ index: index, url: url, name: v.name, updated_at: v.updated_at, modules_count: (v.modules || []).length })
handleCatalogResponse(null,{ url: url, name: v.name},index,v);
refreshNodeModuleList();
}).fail(function(jqxhr, textStatus, error) {
console.warn("Error loading catalog",url,":",error);
handleCatalogResponse(jqxhr,url,index);
}).always(function() {
handled++;
if (handled === catalogueCount) {
//sort loadedCatalogs by e.index ascending
loadedCatalogs.sort((a, b) => a.index - b.index)
updateCatalogFilter(loadedCatalogs)
}
})
}
const catalogueLoadStart = Date.now()
refreshCatalogues(function () {
refreshNodeModuleList();
updateCatalogFilter(loadedCatalogs)
const delta = 250-(Date.now() - catalogueLoadStart);
setTimeout(function() {
$("#red-ui-palette-module-install-shade").hide();
},Math.max(delta,0));
})
} else {
refreshNodeModuleList();
updateCatalogFilter(loadedCatalogs)
}
}
@@ -534,7 +583,6 @@ RED.palette.editor = (function() {
if (catalogSelection.length === 0) {
// sidebar not yet loaded (red-catalogue-filter-select is not in dom)
if (maxRetry > 0) {
// console.log("updateCatalogFilter: sidebar not yet loaded, retrying in 100ms")
// try again in 100ms
setTimeout(() => {
updateCatalogFilter(catalogEntries, maxRetry - 1)
@@ -596,7 +644,9 @@ RED.palette.editor = (function() {
packageList.editableList('addItem',{count:loadedList.length})
return;
}
// sort the filtered modules
filteredList.sort(activeSort);
// render the items in the package list
for (var i=0;i<Math.min(10,filteredList.length);i++) {
packageList.editableList('addItem',filteredList[i]);
}
@@ -608,15 +658,26 @@ RED.palette.editor = (function() {
}
}
function sortModulesRelevance(A,B) {
var currentFilter = searchInput.searchBox('value').trim();
if (currentFilter === "") {
return sortModulesAZ(A,B);
const defaultSort = sortModulesDownloads(A,B);
const currentFilter = searchInput.searchBox('value').trim();
if (defaultSort === 0) {
// same number of downloads
if (currentFilter === "") {
return sortModulesAZ(A,B);
}
var i = A.info.index.indexOf(currentFilter) - B.info.index.indexOf(currentFilter);
if (i === 0) {
return sortModulesAZ(A,B);
}
return i;
}
var i = A.info.index.indexOf(currentFilter) - B.info.index.indexOf(currentFilter);
if (i === 0) {
return sortModulesAZ(A,B);
}
return i;
return defaultSort;
}
function sortModulesDownloads(A,B) {
// check A has the info.downloads property - if not exising, prioritise it (likely a custom user catalogue)
const a = A.info && typeof A.info.downloads !== 'undefined' ? A.info.downloads.week : Number.MAX_SAFE_INTEGER;
const b = B.info && typeof B.info.downloads !== 'undefined' ? B.info.downloads.week : Number.MAX_SAFE_INTEGER;
return b - a;
}
function sortModulesAZ(A,B) {
return A.info.id.localeCompare(B.info.id);
@@ -625,36 +686,42 @@ RED.palette.editor = (function() {
return -1 * (A.info.timestamp-B.info.timestamp);
}
var installAllowList = ['*'];
var installDenyList = [];
var updateAllowed = true;
var updateAllowList = ['*'];
var updateDenyList = [];
let installAllowList = ['*'];
let installDenyList = [];
let updateAllowed = true;
let updateAllowList = ['*'];
let updateDenyList = [];
let editorTabs;
let settingsPane;
function init() {
catalogues = RED.settings.theme('palette.catalogues')||['https://catalogue.nodered.org/catalogue.json']
if (RED.settings.get('externalModules.palette.allowInstall', true) === false) {
return;
}
var settingsAllowList = RED.settings.get("externalModules.palette.allowList")
var settingsDenyList = RED.settings.get("externalModules.palette.denyList")
const settingsAllowList = RED.settings.get("externalModules.palette.allowList")
const settingsDenyList = RED.settings.get("externalModules.palette.denyList")
const settingsUpdateAllowList = RED.settings.get("externalModules.palette.allowUpdateList")
const settingsUpdateDenyList = RED.settings.get("externalModules.palette.denyUpdateList")
if (settingsAllowList || settingsDenyList) {
installAllowList = settingsAllowList;
installDenyList = settingsDenyList
installDenyList = settingsDenyList;
}
installAllowList = RED.utils.parseModuleList(installAllowList);
installDenyList = RED.utils.parseModuleList(installDenyList);
var settingsUpdateAllowList = RED.settings.get("externalModules.palette.allowUpdateList")
var settingsUpdateDenyList = RED.settings.get("externalModules.palette.denyUpdateList")
if (settingsUpdateAllowList || settingsUpdateDenyList) {
updateAllowList = settingsUpdateAllowList;
updateDenyList = settingsUpdateDenyList;
}
installAllowList = RED.utils.parseModuleList(installAllowList);
installDenyList = RED.utils.parseModuleList(installDenyList);
updateAllowed = RED.settings.get("externalModules.palette.allowUpdate", true);
updateAllowList = RED.utils.parseModuleList(updateAllowList);
updateDenyList = RED.utils.parseModuleList(updateDenyList);
updateAllowed = RED.settings.get("externalModules.palette.allowUpdate",true);
catalogues = RED.settings.theme('palette.catalogues') || ['https://catalogue.nodered.org/catalogue.json']
createSettingsPane();
@@ -663,6 +730,9 @@ RED.palette.editor = (function() {
title: RED._("palette.editor.palette"),
get: getSettingsPane,
close: function() {
if (filterInput) {
filterInput.searchBox('value',"");
}
settingsPane.detach();
},
focus: function() {
@@ -673,12 +743,30 @@ RED.palette.editor = (function() {
}
})
RED.actions.add("core:manage-palette",function() {
// Add the update status to the status bar
addUpdateInfoToStatusBar();
// Load catalogues
refreshCatalogues()
RED.actions.add("core:manage-palette", function(opts) {
RED.userSettings.show('palette');
if (opts) {
if (opts.view) {
editorTabs.activateTab(opts.view);
}
if (opts.filter) {
if (opts.view === 'install') {
searchInput.searchBox('value', opts.filter)
} else if (opts.view === 'nodes') {
filterInput.searchBox('value', opts.filter)
}
}
}
});
RED.events.on('registry:module-updated', function(ns) {
refreshNodeModule(ns.module);
refreshUpdateStatus();
});
RED.events.on('registry:node-set-enabled', function(ns) {
refreshNodeModule(ns.module);
@@ -690,12 +778,14 @@ RED.palette.editor = (function() {
if (!/^subflow:/.test(nodeType)) {
var ns = RED.nodes.registry.getNodeSetForType(nodeType);
refreshNodeModule(ns.module);
refreshUpdateStatus();
}
});
RED.events.on('registry:node-type-removed', function(nodeType) {
if (!/^subflow:/.test(nodeType)) {
var ns = RED.nodes.registry.getNodeSetForType(nodeType);
refreshNodeModule(ns.module);
refreshUpdateStatus();
}
});
RED.events.on('registry:node-set-added', function(ns) {
@@ -786,6 +876,7 @@ RED.palette.editor = (function() {
_refreshNodeModule(module);
}
refreshUpdateStatus();
for (var i=0;i<filteredList.length;i++) {
if (filteredList[i].info.id === module) {
var installButton = filteredList[i].elements.installButton;
@@ -795,9 +886,15 @@ RED.palette.editor = (function() {
}
}
});
}
var settingsPane;
RED.events.on("notification/update-available", function (msg) {
const updateKnownAbout = updateStatusState.version === msg.version
updateStatusState.version = msg.version
if (updateStatusWidgetPopover && !updateKnownAbout) {
setTimeout(() => { updateStatusWidgetPopover.open(); setTimeout(() => updateStatusWidgetPopover.close(), 20000) }, 1000)
}
})
}
function getSettingsPane() {
initInstallTab();
@@ -867,11 +964,26 @@ RED.palette.editor = (function() {
return A.info.name.localeCompare(B.info.name);
},
filter: function(data) {
if (activeFilter === "" ) {
if (activeFilterTerms.length === 0) {
return true;
}
return (activeFilter==="")||(data.index.indexOf(activeFilter) > -1);
for (let i = 0; i < activeFilterTerms.length; i++) {
const searchTerm = activeFilterTerms[i]
const location = data.index.indexOf(searchTerm.term)
if (
(
searchTerm.exact &&
(
location === 0 && (
data.index.length === searchTerm.term.length ||
data.index[searchTerm.term.length] === ','
)
)
) ||
(!searchTerm.exact && location > -1)) {
return true
}
}
},
addItem: function(container,i,object) {
var entry = object.info;
@@ -1074,8 +1186,35 @@ RED.palette.editor = (function() {
change: function() {
var searchTerm = $(this).val().trim().toLowerCase();
if (searchTerm.length > 0 || loadedList.length < 20) {
const searchTerms = []
searchTerm.split(',').forEach(term => {
term = term.trim()
if (term) {
const isExact = term[0] === '"' && term[term.length-1] === '"'
searchTerms.push({
exact: isExact,
term: isExact ? term.substring(1,term.length-1) : term
})
}
})
filteredList = loadedList.filter(function(m) {
return (m.index.indexOf(searchTerm) > -1);
for (let i = 0; i < searchTerms.length; i++) {
const location = m.index.indexOf(searchTerms[i].term)
if (
(
searchTerms[i].exact &&
(
location === 0 && (
m.index.length === searchTerms[i].term.length ||
m.index[searchTerms[i].term.length] === ','
)
)
) ||
(!searchTerms[i].exact && location > -1)) {
return true
}
}
return false
}).map(function(f) { return {info:f}});
refreshFilteredItems();
searchInput.searchBox('count',filteredList.length+" / "+loadedList.length);
@@ -1160,12 +1299,25 @@ RED.palette.editor = (function() {
var headerRow = $('<div>',{class:"red-ui-palette-module-header"}).appendTo(container);
var titleRow = $('<div class="red-ui-palette-module-meta red-ui-palette-module-name"><i class="fa fa-cube"></i></div>').appendTo(headerRow);
$('<span>').text(entry.name||entry.id).appendTo(titleRow);
$('<a target="_blank" class="red-ui-palette-module-link"><i class="fa fa-external-link"></i></a>').attr('href',entry.url).appendTo(titleRow);
if (entry.url) {
$('<a target="_blank" class="red-ui-palette-module-link"><i class="fa fa-external-link"></i></a>').attr('href',entry.url).appendTo(titleRow);
}
if (entry.deprecated) {
const deprecatedWarning = $('<span class="red-ui-palette-module-deprecated"></span>').text(RED._('palette.editor.deprecated')).appendTo(titleRow);
let message = $('<span>').text(RED._('palette.editor.deprecatedTip'))
if (typeof entry.deprecated === 'string') {
$('<p>').text(entry.deprecated).appendTo(message)
}
RED.popover.tooltip(deprecatedWarning, message);
}
var descRow = $('<div class="red-ui-palette-module-meta"></div>').appendTo(headerRow);
$('<div>',{class:"red-ui-palette-module-description"}).text(entry.description).appendTo(descRow);
var metaRow = $('<div class="red-ui-palette-module-meta"></div>').appendTo(headerRow);
$('<span class="red-ui-palette-module-version"><i class="fa fa-tag"></i> '+entry.version+'</span>').appendTo(metaRow);
$('<span class="red-ui-palette-module-updated"><i class="fa fa-calendar"></i> '+formatUpdatedAt(entry.updated_at)+'</span>').appendTo(metaRow);
$('<span class="red-ui-palette-module-version" title="Latest Version"><i class="fa fa-tag"></i> '+entry.version+'</span>').appendTo(metaRow);
$('<span class="red-ui-palette-module-updated" title="Last Updated"><i class="fa fa-calendar"></i> '+formatUpdatedAt(entry.updated_at)+'</span>').appendTo(metaRow);
if (entry.downloads?.week !== undefined) {
$('<span class="red-ui-palette-module-downloads" title="Downloads - Last 7 Days"><i class="fa fa-download"></i> '+(new Intl.NumberFormat().format(entry.downloads.week))+'</span>').appendTo(metaRow);
}
if (loadedCatalogs.length > 1) {
$('<span class="red-ui-palette-module-updated"><i class="fa fa-cubes"></i>' + (entry.catalog.name || entry.catalog.url) + '</span>').appendTo(metaRow);
}
@@ -1406,7 +1558,6 @@ RED.palette.editor = (function() {
evt.preventDefault();
RED.actions.invoke("core:show-event-log");
});
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.remove")+" : "+entry.name);
removeNodeModule(entry.name, function(xhr) {
spinner.remove();
if (xhr) {
@@ -1439,6 +1590,7 @@ RED.palette.editor = (function() {
if (e) {
nodeList.editableList('removeItem', e);
delete nodeEntries[entry.name];
refreshUpdateStatus();
}
// We assume that a plugin that implements onremove
@@ -1520,7 +1672,6 @@ RED.palette.editor = (function() {
evt.preventDefault();
RED.actions.invoke("core:show-event-log");
});
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.id+" "+entry.version);
installNodeModule(entry.id,entry.version,entry.pkg_url,function(xhr, textStatus,err) {
spinner.remove();
if (err && xhr.status === 504) {
@@ -1578,6 +1729,85 @@ RED.palette.editor = (function() {
})
}
const updateStatusWidget = $('<button type="button" class="red-ui-footer-button red-ui-update-status"></button>');
let updateStatusWidgetPopover;
const updateStatusState = { moduleCount: 0 }
let updateAvailable = [];
function addUpdateInfoToStatusBar() {
updateStatusWidgetPopover = RED.popover.create({
target: updateStatusWidget,
trigger: "click",
interactive: true,
direction: "bottom",
content: function () {
const count = updateAvailable.length || 0;
const content = $('<div style="display: flex; flex-direction: column; gap: 5px;"></div>');
if (updateStatusState.version) {
$(`<a class='red-ui-button' href="https://github.com/node-red/node-red/releases/tag/${updateStatusState.version}" target="_blank">${RED._("telemetry.updateAvailableDesc", updateStatusState)}</a>`).appendTo(content)
}
if (count > 0) {
$(`<button type="button" class="red-ui-button"><i class="fa fa-cube"></i> ${RED._("palette.editor.updateCount", { count: count })}</button>`).on("click", function (evt) {
updateStatusWidgetPopover.close()
RED.actions.invoke("core:manage-palette", {
view: "nodes",
filter: '"' + updateAvailable.join('", "') + '"'
});
}).appendTo(content)
}
return content
},
delay: { show: 750, hide: 250 }
});
RED.statusBar.add({
id: "red-ui-status-package-update",
align: "right",
element: updateStatusWidget
});
updateStatus();
}
let pendingRefreshTimeout
function refreshUpdateStatus() {
clearTimeout(pendingRefreshTimeout)
pendingRefreshTimeout = setTimeout(() => {
updateAvailable = [];
for (const module of Object.keys(nodeEntries)) {
if (loadedIndex.hasOwnProperty(module)) {
const moduleInfo = nodeEntries[module].info;
if (moduleInfo.pending_version) {
// Module updated
continue;
}
if (updateAllowed &&
semVerCompare(loadedIndex[module].version, moduleInfo.version) > 0 &&
RED.utils.checkModuleAllowed(module, null, updateAllowList, updateDenyList)
) {
updateAvailable.push(module);
}
}
}
updateStatusState.moduleCount = updateAvailable.length;
updateStatus();
}, 200)
}
function updateStatus() {
if (updateStatusState.moduleCount || updateStatusState.version) {
updateStatusWidget.empty();
let count = updateStatusState.moduleCount || 0;
if (updateStatusState.version) {
count ++
}
$(`<span><i class="fa fa-cube"></i> ${RED._("telemetry.updateAvailable", { count: count })}</span>`).appendTo(updateStatusWidget);
RED.statusBar.show("red-ui-status-package-update");
} else {
RED.statusBar.hide("red-ui-status-package-update");
}
}
return {
init: init,
install: install

View File

@@ -33,6 +33,7 @@ RED.statusBar = (function() {
var el = $('<span class="red-ui-statusbar-widget"></span>');
el.prop('id', options.id);
options.element.appendTo(el);
options.elementDiv = el;
if (options.align === 'left') {
leftBucket.append(el);
} else if (options.align === 'right') {
@@ -40,12 +41,30 @@ RED.statusBar = (function() {
}
}
function hideWidget(id) {
const widget = widgets[id];
if (widget && widget.elementDiv) {
widget.elementDiv.hide();
}
}
function showWidget(id) {
const widget = widgets[id];
if (widget && widget.elementDiv) {
widget.elementDiv.show();
}
}
return {
init: function() {
leftBucket = $('<span class="red-ui-statusbar-bucket red-ui-statusbar-bucket-left">').appendTo("#red-ui-workspace-footer");
rightBucket = $('<span class="red-ui-statusbar-bucket red-ui-statusbar-bucket-right">').appendTo("#red-ui-workspace-footer");
},
add: addWidget
add: addWidget,
hide: hideWidget,
show: showWidget
}
})();

View File

@@ -435,10 +435,15 @@ RED.tourGuide = (function() {
function listTour() {
return [
{
id: "4_1",
label: "4.1",
path: "./tours/welcome.js"
},
{
id: "4_0",
label: "4.0",
path: "./tours/welcome.js"
path: "./tours/4.0/welcome.js"
},
{
id: "3_1",

View File

@@ -140,9 +140,22 @@ RED.userSettings = (function() {
title: "menu.label.nodes",
options: [
{setting:"view-node-status",oldSetting:"menu-menu-item-status",label:"menu.label.displayStatus",default: true, toggle:true,onchange:"core:toggle-status"},
{setting:"view-node-info-icon", label:"menu.label.displayInfoIcon", default: true, toggle:true,onchange:"core:toggle-node-info-icon"},
{setting:"view-node-show-label",label:"menu.label.showNodeLabelDefault",default: true, toggle:true}
]
},
{
title: "telemetry.label",
options: [
{
global: true,
setting: "telemetryEnabled",
label: "telemetry.settingsTitle",
description: "telemetry.settingsDescription",
toggle: true
},
]
},
{
title: "menu.label.other",
options: [
@@ -169,13 +182,20 @@ RED.userSettings = (function() {
var initialState;
if (opt.local) {
initialState = localStorage.getItem(opt.setting);
} else if (opt.global) {
initialState = RED.settings.get(opt.setting);
} else {
initialState = currentEditorSettings.view[opt.setting];
}
var row = $('<div class="red-ui-settings-row"></div>').appendTo(pane);
var input;
if (opt.toggle) {
input = $('<label for="user-settings-'+opt.setting+'"><input id="user-settings-'+opt.setting+'" type="checkbox"> '+RED._(opt.label)+'</label>').appendTo(row).find("input");
let label = RED._(opt.label)
if (opt.description) {
label = `<p>${label}</p>${RED._(opt.description)}`;
}
input = $('<input id="user-settings-'+opt.setting+'" type="checkbox">').appendTo(row)
$('<label for="user-settings-'+opt.setting+'">'+label+'</label>').appendTo(row)
input.prop('checked',initialState);
} else if (opt.options) {
$('<label for="user-settings-'+opt.setting+'">'+RED._(opt.label)+'</label>').appendTo(row);
@@ -209,6 +229,8 @@ RED.userSettings = (function() {
var opt = allSettings[id];
if (opt.local) {
localStorage.setItem(opt.setting,value);
} else if (opt.global) {
RED.settings.set(opt.setting, value)
} else {
var currentEditorSettings = RED.settings.get('editor') || {};
currentEditorSettings.view = currentEditorSettings.view || {};
@@ -237,7 +259,7 @@ RED.userSettings = (function() {
addPane({
id:'view',
title: RED._("menu.label.view.view"),
title: RED._("menu.label.settings"),
get: createViewPane,
close: function() {
viewSettings.forEach(function(section) {

View File

@@ -128,10 +128,25 @@ RED.utils = (function() {
function formatString(str) {
return str.replace(/\r?\n/g,"&crarr;").replace(/\t/g,"&rarr;");
}
function sanitize(m) {
return m.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
}
/**
* Truncates a string to a specified maximum length, adding ellipsis
* if truncated.
*
* @param {string} str The string to be truncated.
* @param {number} [maxLength = 120] The maximum length of the truncated
* string. Default `120`.
* @returns {string} The truncated string with ellipsis if it exceeds the
* maximum length.
*/
function truncateString(str, maxLength = 120) {
return str.length > maxLength ? str.slice(0, maxLength) + "..." : str;
}
function buildMessageSummaryValue(value) {
var result;
if (Array.isArray(value)) {
@@ -379,6 +394,9 @@ RED.utils = (function() {
}
}
// Max string length before truncating
const MAX_STRING_LENGTH = 80;
/**
* Create a DOM element representation of obj - as used by Debug sidebar etc
*
@@ -474,7 +492,7 @@ RED.utils = (function() {
} else if (typeHint === "internal" || (obj.__enc__ && obj.type === 'internal')) {
e = $('<span class="red-ui-debug-msg-type-meta red-ui-debug-msg-object-header"></span>').text("[internal]").appendTo(entryObj);
} else if (typeof obj === 'string') {
if (/[\t\n\r]/.test(obj)) {
if (/[\t\n\r]/.test(obj) || obj.length > MAX_STRING_LENGTH) {
element.addClass('collapsed');
$('<i class="fa fa-caret-right red-ui-debug-msg-object-handle"></i> ').prependTo(header);
makeExpandable(header, function() {
@@ -483,7 +501,7 @@ RED.utils = (function() {
$('<pre class="red-ui-debug-msg-type-string"></pre>').text(obj).appendTo(row);
},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);
e = $('<span class="red-ui-debug-msg-type-string red-ui-debug-msg-object-header"></span>').html('"'+formatString(sanitize(truncateString(obj, MAX_STRING_LENGTH)))+'"').appendTo(entryObj);
if (/^#[0-9a-f]{6}$/i.test(obj)) {
$('<span class="red-ui-debug-msg-type-string-swatch"></span>').css('backgroundColor',obj).appendTo(e);
}
@@ -1513,6 +1531,7 @@ RED.utils = (function() {
parseContextKey: parseContextKey,
createIconElement: createIconElement,
sanitize: sanitize,
truncateString: truncateString,
renderMarkdown: renderMarkdown,
createNodeIcon: createNodeIcon,
getDarkerColor: getDarkerColor,

View File

@@ -82,6 +82,7 @@ RED.view = (function() {
var slicePathLast = null;
var ghostNode = null;
var showStatus = false;
let showNodeInfo = true;
var lastClickNode = null;
var dblClickPrimed = null;
var clickTime = 0;
@@ -860,11 +861,51 @@ RED.view = (function() {
toggleStatus(state);
}
});
RED.actions.add("core:toggle-node-info-icon", function (state) {
if (state === undefined) {
RED.userSettings.toggle("view-node-info-icon");
} else {
toggleNodeInfo(state)
}
})
RED.view.annotations.init();
RED.view.navigator.init();
RED.view.tools.init();
RED.view.annotations.register("red-ui-flow-node-docs",{
type: "badge",
class: "red-ui-flow-node-docs",
element: function(node) {
const docsBadge = document.createElementNS("http://www.w3.org/2000/svg","g")
const pageOutline = document.createElementNS("http://www.w3.org/2000/svg","rect");
pageOutline.setAttribute("x",0);
pageOutline.setAttribute("y",0);
pageOutline.setAttribute("rx",2);
pageOutline.setAttribute("width",7);
pageOutline.setAttribute("height",10);
docsBadge.appendChild(pageOutline)
const pageLines = document.createElementNS("http://www.w3.org/2000/svg","path");
pageLines.setAttribute("d", "M 7 3 h -3 v -3 M 2 8 h 3 M 2 6 h 3 M 2 4 h 2")
docsBadge.appendChild(pageLines)
$(docsBadge).on('click', function (evt) {
if (node.type === "subflow") {
RED.editor.editSubflow(activeSubflow, 'editor-tab-description');
} else if (node.type === "group") {
RED.editor.editGroup(node, 'editor-tab-description');
} else {
RED.editor.edit(node, 'editor-tab-description');
}
})
return docsBadge;
},
show: function(n) { return showNodeInfo && !!n.info }
})
RED.view.annotations.register("red-ui-flow-node-changed",{
type: "badge",
@@ -5695,37 +5736,62 @@ RED.view = (function() {
activeSubflowChanged = activeSubflow.changed;
}
var filteredNodesToImport = nodesToImport;
var globalConfig = null;
var gconf = null;
var importedGlobalConfig = null;
var existingGlobalConfig = null;
RED.nodes.eachConfig(function (conf) {
if (conf.type === "global-config") {
gconf = conf;
existingGlobalConfig = conf;
}
});
if (gconf) {
if (existingGlobalConfig) {
filteredNodesToImport = nodesToImport.filter(function (n) {
return (n.type !== "global-config");
if (n.type === "global-config") {
importedGlobalConfig = n
// Do not import it if one exists
// The properties of this one will be merged after import
return false
}
return true
});
globalConfig = nodesToImport.find(function (n) {
return (n.type === "global-config");
} else {
filteredNodesToImport = nodesToImport.filter(function (n) {
if (n.type === "global-config") {
importedGlobalConfig = n
if (n.env && n.env.length) {
// No existing global-config
// Contains env and maybe modules, so import it
return true
}
// Contains modules only - do not import it
return false
}
return true
});
}
var result = RED.nodes.import(filteredNodesToImport,{
const modules = importedGlobalConfig?.modules || {}
// Ensure do not import modules - since it can contain it
if (importedGlobalConfig?.modules) {
delete importedGlobalConfig.modules;
}
const importResult = RED.nodes.import(filteredNodesToImport,{
generateIds: options.generateIds,
addFlow: addNewFlow,
importMap: options.importMap,
markChanged: true
markChanged: true,
modules: modules
});
if (result) {
var new_nodes = result.nodes;
var new_links = result.links;
var new_groups = result.groups;
var new_junctions = result.junctions;
var new_workspaces = result.workspaces;
var new_subflows = result.subflows;
var removedNodes = result.removedNodes;
var new_default_workspace = result.missingWorkspace;
if (importResult) {
var new_nodes = importResult.nodes;
var new_links = importResult.links;
var new_groups = importResult.groups;
var new_junctions = importResult.junctions;
var new_workspaces = importResult.workspaces;
var new_subflows = importResult.subflows;
var removedNodes = importResult.removedNodes;
var new_default_workspace = importResult.missingWorkspace;
if (addNewFlow && new_default_workspace) {
RED.workspaces.show(new_default_workspace.id);
}
@@ -5839,38 +5905,38 @@ RED.view = (function() {
}
}
if (globalConfig) {
if (importedGlobalConfig && existingGlobalConfig) {
// merge global env to existing global-config
var env0 = gconf.env;
var env1 = globalConfig.env;
var newEnv = Array.from(env0);
var existingEnv = existingGlobalConfig.env || [];
var importedEnv = importedGlobalConfig.env || []
var newEnv = Array.from(existingEnv);
var changed = false;
env1.forEach(function (item1) {
var index = newEnv.findIndex(function (item0) {
return (item0.name === item1.name);
importedEnv.forEach(function (importedItem) {
var index = newEnv.findIndex(function (existingItem) {
return (existingItem.name === importedItem.name);
});
if (index >= 0) {
var item0 = newEnv[index];
if ((item0.type !== item1.type) ||
(item0.value !== item1.value)) {
newEnv[index] = item1;
const existingItem = newEnv[index];
if ((existingItem.type !== importedItem.type) ||
(existingItem.value !== importedItem.value)) {
newEnv[index] = importedItem;
changed = true;
}
}
else {
newEnv.push(item1);
newEnv.push(importedItem);
changed = true;
}
});
if(changed) {
gconf.env = newEnv;
if (changed) {
existingGlobalConfig.env = newEnv;
var replaceEvent = {
t: "edit",
node: gconf,
node: existingGlobalConfig,
changed: true,
changes: {
env: env0
env: existingEnv
}
};
historyEvent = {
@@ -6007,6 +6073,11 @@ RED.view = (function() {
//TODO: subscribe/unsubscribe here
redraw();
}
function toggleNodeInfo(s) {
showNodeInfo = s
RED.nodes.eachNode(function(n) { n.dirty = true;});
redraw();
}
function setSelectedNodeState(isDisabled) {
if (mouse_mode === RED.state.SELECTING_NODE) {
return;

View File

@@ -1,3 +1,5 @@
@use "mixins";
.red-ui-editor, .red-ui-editor-dialog {
.ace_read-only {
@@ -42,7 +44,7 @@
background: var(--red-ui-popover-background);
color: var(--red-ui-popover-color);
border-radius: 4px;
@include component-shadow;
@include mixins.component-shadow;
border-color: var(--red-ui-popover-background);
}
.ace_content {

View File

@@ -1,3 +1,5 @@
@use "mixins";
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
@@ -43,7 +45,7 @@ body {
}
#red-ui-palette-shade, #red-ui-editor-shade, #red-ui-header-shade, #red-ui-sidebar-shade {
@include shade;
@include mixins.shade;
z-index: 5;
}
#red-ui-sidebar-shade {
@@ -52,7 +54,7 @@ body {
bottom: -1px;
}
#red-ui-full-shade {
@include shade;
@include mixins.shade;
z-index: 15;
}
.red-ui-editor,
@@ -206,12 +208,10 @@ body {
}
img {
width: auto\9;
height: auto;
max-width: 100%;
vertical-align: middle;
border: 0;
-ms-interpolation-mode: bicubic;
}
blockquote {

View File

@@ -1,3 +1,5 @@
@use "mixins";
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
@@ -63,7 +65,7 @@
}
}
.red-ui-tray-header {
@include disable-selection;
@include mixins.disable-selection;
position: relative;
box-sizing: border-box;
font-weight: bold;
@@ -77,7 +79,7 @@
}
.red-ui-tray-footer {
@include component-footer;
@include mixins.component-footer;
height: 35px;
font-size: 14px !important;
line-height: 35px;
@@ -110,9 +112,9 @@
padding: 6px;
button {
@include editor-button;
@include mixins.editor-button;
&.toggle {
@include workspace-button-toggle;
@include mixins.workspace-button-toggle;
}
}
}
@@ -179,7 +181,7 @@
}
a.red-ui-tray-resize-button,
button.red-ui-tray-resize-button {
@include workspace-button;
@include mixins.workspace-button;
display: block;
height: 37px;
line-height: 35px;
@@ -274,7 +276,7 @@ button.editor-button, // Deprecated: use .red-ui-button
a.red-ui-button,
button.red-ui-button
{
@include workspace-button;
@include mixins.workspace-button;
height: 34px;
line-height: 32px;
font-size: 13px;
@@ -283,7 +285,7 @@ button.red-ui-button
white-space: nowrap;
text-overflow: ellipsis;
&.toggle {
@include workspace-button-toggle;
@include mixins.workspace-button-toggle;
}
}

View File

@@ -1,3 +1,5 @@
@use "mixins";
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
@@ -41,7 +43,7 @@
font-size: 14px;
pointer-events: none;
-webkit-touch-callout: none;
@include disable-selection;
@include mixins.disable-selection;
.red-ui-flow-node-label-text {
dominant-baseline: middle;
@@ -60,7 +62,7 @@
text-anchor: middle;
pointer-events: none;
-webkit-touch-callout: none;
@include disable-selection;
@include mixins.disable-selection;
}
@@ -155,7 +157,7 @@ svg:not(.red-ui-workspace-lasso-active) {
stroke-opacity: var(--red-ui-group-default-stroke-opacity);
}
.red-ui-flow-group-label {
@include disable-selection;
@include mixins.disable-selection;
fill: var(--red-ui-group-default-label-color);
}
@@ -174,10 +176,10 @@ svg:not(.red-ui-workspace-lasso-active) {
}
.red-ui-flow-node-icon-group {
text {
@include disable-selection;
@include mixins.disable-selection;
}
.fa-lg {
@include disable-selection;
@include mixins.disable-selection;
stroke: none;
fill: var(--red-ui-node-icon-color);
text-anchor: middle;
@@ -242,6 +244,18 @@ svg:not(.red-ui-workspace-lasso-active) {
stroke-linecap: round;
}
.red-ui-flow-node-docs {
stroke-width: 1px;
stroke-linejoin: round;
stroke-linecap: round;
stroke: var(--red-ui-node-border);
fill: none;
cursor: pointer;
rect {
fill: white;
}
}
g.red-ui-flow-node-selected {
.red-ui-workspace-select-mode & {
opacity: 1;
@@ -311,7 +325,7 @@ g.red-ui-flow-node-selected {
fill-opacity: 0.9;
}
.red-ui-flow-node-status-label {
@include disable-selection;
@include mixins.disable-selection;
stroke-width: 0;
fill: var(--red-ui-secondary-text-color);
font-size:9pt;
@@ -417,7 +431,7 @@ g.red-ui-flow-link-unknown path.red-ui-flow-link-line {
pointer-events: none;
-webkit-touch-callout: none;
white-space: pre;
@include disable-selection;
@include mixins.disable-selection;
}
.red-ui-flow-junction-dragging {
.red-ui-flow-junction-background {

View File

@@ -216,14 +216,11 @@
.uneditable-input:focus {
border-color: var(--red-ui-form-input-focus-color);
outline: 0;
outline: thin dotted \9;
}
input[type="radio"],
input[type="checkbox"] {
margin: 4px 0 0;
margin-top: 1px \9;
*margin-top: 0;
line-height: normal;
}
@@ -285,12 +282,6 @@
color: var(--red-ui-form-placeholder-color);
}
input:-ms-input-placeholder,
div[contenteditable="true"]:-ms-input-placeholder,
textarea:-ms-input-placeholder {
color: var(--red-ui-form-placeholder-color);
}
input::-webkit-input-placeholder,
div[contenteditable="true"]::-webkit-input-placeholder,
textarea::-webkit-input-placeholder {
@@ -568,11 +559,7 @@
input.search-query {
padding-right: 14px;
padding-right: 4px \9;
padding-left: 14px;
padding-left: 4px \9;
/* IE7-8 doesn't have border-radius, so don't indent the padding */
margin-bottom: 0;
border-radius: 15px;
}

View File

@@ -1,3 +1,5 @@
@use "mixins";
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
@@ -15,7 +17,7 @@
**/
.button {
@include disable-selection;
@include mixins.disable-selection;
}
#red-ui-header {
@@ -50,7 +52,7 @@
}
}
img {
height: 18px;
height: 32px;
}
a {

View File

@@ -1,3 +1,5 @@
@use "mixins";
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
@@ -54,7 +56,7 @@
border-radius: 1px;
background: var(--red-ui-secondary-background);
padding: 0;
@include component-shadow;
@include mixins.component-shadow;
}
.ui-dialog .ui-dialog-content {
padding: 25px 25px 10px 25px;
@@ -92,7 +94,7 @@
}
.ui-dialog-buttonset button {
@include workspace-button;
@include mixins.workspace-button;
font-size: 14px;
padding: 6px 14px;
margin-right: 8px;
@@ -160,7 +162,7 @@
.ui-widget-overlay {
@include shade;
@include mixins.shade;
z-index: 100;
opacity: 1;
}

View File

@@ -1,3 +1,5 @@
@use "mixins";
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
@@ -254,7 +256,7 @@
display: flex;
flex-direction: row;
span:not(:nth-child(2)) {
@include disable-selection;
@include mixins.disable-selection;
}
span {
padding: 8px 0;

View File

@@ -18,7 +18,6 @@
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@@ -26,7 +25,6 @@
-webkit-user-select: auto;
-khtml-user-select: auto;
-moz-user-select: auto;
-ms-user-select: auto;
user-select: auto;
}

View File

@@ -1,3 +1,5 @@
@use "mixins";
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
@@ -117,19 +119,27 @@
.red-ui-palette-module-updated {
margin-left: 10px;
}
.red-ui-palette-module-downloads {
margin-left: 10px;
}
.red-ui-palette-module-link {
margin-left: 5px;
}
.red-ui-palette-module-deprecated {
cursor: pointer;
color: var(--red-ui-text-color-error);
font-size: 0.7em;
border: 1px solid var(--red-ui-text-color-error);
border-radius: 30px;
padding: 2px 5px;
}
.red-ui-palette-module-description {
margin-left: 20px;
font-size: 0.9em;
color: var(--red-ui-secondary-text-color);
}
.red-ui-palette-module-link {
}
.red-ui-palette-module-set-button-group {
}
.red-ui-palette-module-content {
display: none;
padding: 10px 3px;
@@ -167,7 +177,7 @@
color: var(--red-ui-secondary-text-color);
padding-left: 5px;
font-size: 0.9em;
@include enable-selection;
@include mixins.enable-selection;
}
.red-ui-palette-module-type-swatch {
display: inline-block;
@@ -226,12 +236,12 @@
.red-ui-palette-module-name {
color: var(--red-ui-primary-text-color);
white-space: nowrap;
@include enable-selection;
@include mixins.enable-selection;
}
.red-ui-palette-module-version, .red-ui-palette-module-updated, .red-ui-palette-module-link {
.red-ui-palette-module-version, .red-ui-palette-module-updated, .red-ui-palette-module-link, .red-ui-palette-module-downloads {
font-style:italic;
font-size: 0.8em;
@include enable-selection;
@include mixins.enable-selection;
}
.red-ui-palette-module-button-group {
position: absolute;
@@ -256,7 +266,7 @@ ul.red-ui-palette-module-error-list {
}
.red-ui-palette-module-shade {
@include shade;
@include mixins.shade;
text-align: center;
padding-top: 20px;
}
@@ -305,3 +315,8 @@ button.red-ui-palette-editor-upload-button {
margin-left: 10px;
}
}
button.red-ui-update-status {
width: auto;
padding: 0 3px;
}

View File

@@ -1,3 +1,5 @@
@use "mixins";
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
@@ -23,9 +25,29 @@
background: var(--red-ui-primary-background);
width: 180px;
text-align: center;
@include disable-selection;
@include component-border;
@include mixins.disable-selection;
@include mixins.component-border;
transition: width 0.2s ease-in-out;
&:before {
content: '';
top: 0px;
bottom: 0px;
right: 0px;
width: 7px;
height: 100%;
z-index: 4;
position: absolute;
-webkit-mask-image: url(images/grip.svg);
mask-image: url(images/grip.svg);
-webkit-mask-size: auto;
mask-size: auto;
-webkit-mask-position: 50% 50%;
mask-position: 50% 50%;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
background-color: var(--red-ui-grip-color);
}
}
.red-ui-palette-closed {
@@ -112,7 +134,7 @@
line-height: 20px;
overflow: hidden;
text-align: center;
@include disable-selection;
@include mixins.disable-selection;
}
.red-ui-palette-label-right {
margin: 4px 32px 4px 0;

View File

@@ -1,3 +1,5 @@
@use "mixins";
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
@@ -28,7 +30,7 @@
font-family: var(--red-ui-primary-font);
font-size: 14px;
line-height: 1.4em;
@include component-shadow;
@include mixins.component-shadow;
border-color: var(--red-ui-popover-border);
}
.red-ui-popover-content {
@@ -194,7 +196,7 @@
.red-ui-popover-panel {
@include component-shadow;
@include mixins.component-shadow;
font-family: var(--red-ui-primary-font);
font-size: var(--red-ui-primary-font-size);
position: absolute;
@@ -203,3 +205,39 @@
background: var(--red-ui-secondary-background);
z-index: 2000;
}
.red-ui-popover.red-ui-dialog {
z-index: 2003;
--red-ui-popover-background: var(--red-ui-secondary-background);
--red-ui-popover-border: var(--red-ui-tourGuide-border);
--red-ui-popover-color: var(--red-ui-primary-text-color);
.red-ui-popover-content {
h2 {
text-align: center;
margin-top: 0px;
line-height: 1.2em;
color: var(--red-ui-tourGuide-heading-color);
i.fa {
font-size: 1.5em
}
}
}
}
.red-ui-dialog-toolbar {
min-height: 36px;
position: relative;
display: flex;
justify-content: flex-end;
gap: 10px;
}
.red-ui-dialog-body {
padding: 20px 40px 10px;
a {
color: var(--red-ui-text-color-link) !important;
text-decoration: none;
}
}

View File

@@ -1,3 +1,5 @@
@use "mixins";
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
@@ -23,7 +25,7 @@
background: var(--red-ui-primary-background);
box-sizing: border-box;
z-index: 10;
@include component-border;
@include mixins.component-border;
}
#red-ui-sidebar.closing {
@@ -73,7 +75,7 @@
.red-ui-sidebar-closed > #red-ui-editor-stack { right: 8px !important; }
#red-ui-sidebar .button {
@include workspace-button;
@include mixins.workspace-button;
line-height: 18px;
font-size: 12px;
margin-right: 5px;
@@ -92,23 +94,23 @@
/* Deprecated -> red-ui-footer-button */
.sidebar-footer-button {
@include component-footer-button;
@include mixins.component-footer-button;
}
/* Deprecated -> red-ui-footer-button-toggle */
.sidebar-footer-button-toggle {
@include component-footer-button-toggle;
@include mixins.component-footer-button-toggle;
}
a.sidebar-header-button,
button.sidebar-header-button, /* Deprecated -> red-ui-sidebar-header-button */
a.red-ui-sidebar-header-button,
button.red-ui-sidebar-header-button {
@include workspace-button;
@include mixins.workspace-button;
font-size: 13px;
line-height: 13px;
padding: 5px 8px;
&.toggle {
@include workspace-button-toggle;
@include mixins.workspace-button-toggle;
}
}
@@ -116,7 +118,7 @@ a.sidebar-header-button-toggle, /* Deprecated -> red-ui-sidebar-header-button-to
button.sidebar-header-button-toggle, /* Deprecated -> red-ui-sidebar-header-button-toggle */
a.red-ui-sidebar-header-button-toggle,
button.red-ui-sidebar-header-button-toggle {
@include workspace-button-toggle;
@include mixins.workspace-button-toggle;
font-size: 13px;
line-height: 13px;
padding: 5px 8px;
@@ -128,7 +130,7 @@ button.red-ui-sidebar-header-button-toggle {
}
.red-ui-sidebar-shade {
@include shade;
@include mixins.shade;
}

View File

@@ -14,6 +14,6 @@
* limitations under the License.
**/
@import "colors";
@import "sizes";
@import "variables";
@use "colors";
@use "sizes";
@use "variables";

View File

@@ -14,64 +14,64 @@
* limitations under the License.
**/
@import "colors";
@import "sizes";
@import "variables";
@import "mixins";
@use "colors";
@use "sizes";
@use "variables";
@use "mixins";
@import "base";
@import "forms";
@use "base";
@use "forms";
@import "jquery";
@import "ace";
@use "jquery";
@use "ace";
@import "dropdownMenu";
@use "dropdownMenu";
@import "header";
@import "palette";
@import "sidebar";
@import "workspace";
@import "workspaceToolbar";
@use "header";
@use "palette";
@use "sidebar";
@use "workspace";
@use "workspaceToolbar";
@import "notifications";
@use "notifications";
@import "editor";
@import "library";
@import "search";
@use "editor";
@use "library";
@use "search";
@import "panels";
@import "tabs";
@import "tab-config";
@import "tab-context";
@import "tab-info";
@import "tab-help";
@import "popover";
@import "flow";
@import "palette-editor";
@import "diff";
@use "panels";
@use "tabs";
@use "tab-config";
@use "tab-context";
@use "tab-info";
@use "tab-help";
@use "popover";
@use "flow";
@use "palette-editor";
@use "diff";
@import "userSettings";
@use "userSettings";
@import "projects";
@use "projects";
@import "ui/common/editableList";
@import "ui/common/searchBox";
@import "ui/common/typedInput";
@import "ui/common/nodeList";
@import "ui/common/checkboxSet";
@import "ui/common/stack";
@import "ui/common/treeList";
@import "ui/common/autoComplete";
@use "ui/common/editableList";
@use "ui/common/searchBox";
@use "ui/common/typedInput";
@use "ui/common/nodeList";
@use "ui/common/checkboxSet";
@use "ui/common/stack";
@use "ui/common/treeList";
@use "ui/common/autoComplete";
@import "dragdrop";
@use "dragdrop";
@import "keyboard";
@use "keyboard";
@import "debug";
@use "debug";
@import "radialMenu";
@use "radialMenu";
@import "tourGuide";
@use "tourGuide";
@import "multiplayer";
@use "multiplayer";

View File

@@ -1,3 +1,5 @@
@use "mixins";
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
@@ -38,7 +40,7 @@
background: var(--red-ui-secondary-background);
height: 100%;
overflow-y:auto;
@include disable-selection;
@include mixins.disable-selection;
&:focus {
outline: none;

View File

@@ -1,3 +1,5 @@
@use "mixins";
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
@@ -25,6 +27,7 @@
padding-left: 9px;
line-height: 21px;
cursor: default;
border-bottom: 1px solid var(--red-ui-secondary-border-color);
> * {
vertical-align: middle
}
@@ -33,7 +36,6 @@
margin-left: 5px;
overflow-wrap: anywhere;
}
border-bottom: 1px solid var(--red-ui-secondary-border-color);
}
table.red-ui-info-table {
font-size: 14px;
@@ -307,7 +309,7 @@ div.red-ui-info-table {
text-align: center;
line-height: 1.9em;
color : var(--red-ui-tertiary-text-color);
@include disable-selection;
@include mixins.disable-selection;
cursor: default;
}
.red-ui-help-tips-buttons {

View File

@@ -1,3 +1,5 @@
@use "mixins";
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
@@ -41,7 +43,7 @@
box-sizing:border-box;
border-bottom: 1px solid var(--red-ui-primary-border-color);
white-space: nowrap;
@include disable-selection;
@include mixins.disable-selection;
li {
box-sizing: border-box;
@@ -244,7 +246,7 @@
z-index: 2;
a {
@include workspace-button;
@include mixins.workspace-button;
line-height: 30px;
height: 28px;
width: 28px;
@@ -266,7 +268,7 @@
border-bottom: 1px solid var(--red-ui-primary-border-color);
z-index: 2;
a {
@include workspace-button-toggle;
@include mixins.workspace-button-toggle;
line-height: 26px;
height: 28px;
width: 28px;

View File

@@ -1,3 +1,5 @@
@use "../../mixins";
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
@@ -67,7 +69,7 @@
}
}
.red-ui-treeList-label {
@include disable-selection;
@include mixins.disable-selection;
padding: 6px 0;
display: flex;
align-items: center;

View File

@@ -1,3 +1,5 @@
@use "../../mixins";
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
@@ -67,7 +69,7 @@
}
}
.red-ui-typedInput-options {
@include component-shadow;
@include mixins.component-shadow;
font-family: var(--red-ui-primary-font);
font-size: var(--red-ui-primary-font-size);

View File

@@ -70,8 +70,14 @@
overflow-y: auto;
}
.red-ui-settings-row {
display: flex;
gap: 10px;
align-items:flex-start;
padding: 5px 10px 2px;
}
.red-ui-settings-row input[type="checkbox"] {
margin-top: 8px;
}
.red-ui-settings-section {
position: relative;
&:after {

View File

@@ -1,305 +1,309 @@
@use "sass:map";
@use "colors";
@use "sizes";
:root {
--red-ui-primary-font: #{$primary-font};
--red-ui-primary-font-size: #{$primary-font-size};
--red-ui-monospace-font: #{$monospace-font};
--red-ui-primary-font: #{colors.$primary-font};
--red-ui-primary-font-size: #{colors.$primary-font-size};
--red-ui-monospace-font: #{colors.$monospace-font};
--red-ui-primary-background: #{$primary-background};
--red-ui-primary-background: #{colors.$primary-background};
--red-ui-secondary-background: #{$secondary-background};
--red-ui-secondary-background-selected: #{$secondary-background-selected};
--red-ui-secondary-background-inactive: #{$secondary-background-inactive};
--red-ui-secondary-background-hover: #{$secondary-background-hover};
--red-ui-secondary-background-disabled: #{$secondary-background-disabled};
--red-ui-secondary-background: #{colors.$secondary-background};
--red-ui-secondary-background-selected: #{colors.$secondary-background-selected};
--red-ui-secondary-background-inactive: #{colors.$secondary-background-inactive};
--red-ui-secondary-background-hover: #{colors.$secondary-background-hover};
--red-ui-secondary-background-disabled: #{colors.$secondary-background-disabled};
--red-ui-tertiary-background: #{$tertiary-background};
--red-ui-tertiary-background: #{colors.$tertiary-background};
--red-ui-shadow: #{$shadow};
--red-ui-shadow: #{colors.$shadow};
// Header Height
--red-ui-header-height: #{$header-height};
--red-ui-header-height: #{sizes.$header-height};
// Main body text
--red-ui-primary-text-color: #{$primary-text-color};
--red-ui-primary-text-color: #{colors.$primary-text-color};
// UI control label text
--red-ui-secondary-text-color: #{$secondary-text-color};
--red-ui-secondary-text-color-focus: #{$secondary-text-color-focus};
--red-ui-secondary-text-color-hover: #{$secondary-text-color-hover};
--red-ui-secondary-text-color-active: #{$secondary-text-color-active};
--red-ui-secondary-text-color-selected: #{$secondary-text-color-selected};
--red-ui-secondary-text-color-inactive: #{$secondary-text-color-inactive};
--red-ui-secondary-text-color-disabled: #{$secondary-text-color-disabled};
--red-ui-secondary-text-color-disabled-active: #{$secondary-text-color-disabled-active};
--red-ui-secondary-text-color-disabled-inactive: #{$secondary-text-color-disabled-inactive};
--red-ui-secondary-text-color: #{colors.$secondary-text-color};
--red-ui-secondary-text-color-focus: #{colors.$secondary-text-color-focus};
--red-ui-secondary-text-color-hover: #{colors.$secondary-text-color-hover};
--red-ui-secondary-text-color-active: #{colors.$secondary-text-color-active};
--red-ui-secondary-text-color-selected: #{colors.$secondary-text-color-selected};
--red-ui-secondary-text-color-inactive: #{colors.$secondary-text-color-inactive};
--red-ui-secondary-text-color-disabled: #{colors.$secondary-text-color-disabled};
--red-ui-secondary-text-color-disabled-active: #{colors.$secondary-text-color-disabled-active};
--red-ui-secondary-text-color-disabled-inactive: #{colors.$secondary-text-color-disabled-inactive};
// Sub label text
--red-ui-tertiary-text-color: #{$tertiary-text-color};
--red-ui-tertiary-text-color: #{colors.$tertiary-text-color};
// Heading text
--red-ui-header-text-color: #{$header-text-color};
--red-ui-header-text-color: #{colors.$header-text-color};
--red-ui-text-color-error: #{$text-color-error};
--red-ui-text-color-warning: #{$text-color-warning};
--red-ui-text-color-success: #{$text-color-success};
--red-ui-text-color-code: #{$text-color-code};
--red-ui-text-color-link: #{$text-color-link};
--red-ui-text-color-error: #{colors.$text-color-error};
--red-ui-text-color-warning: #{colors.$text-color-warning};
--red-ui-text-color-success: #{colors.$text-color-success};
--red-ui-text-color-code: #{colors.$text-color-code};
--red-ui-text-color-link: #{colors.$text-color-link};
--red-ui-primary-border-color: #{$primary-border-color};
--red-ui-secondary-border-color: #{$secondary-border-color};
--red-ui-tertiary-border-color: #{$tertiary-border-color};
--red-ui-primary-border-color: #{colors.$primary-border-color};
--red-ui-secondary-border-color: #{colors.$secondary-border-color};
--red-ui-tertiary-border-color: #{colors.$tertiary-border-color};
--red-ui-border-color-error: #{$border-color-error};
--red-ui-border-color-warning: #{$border-color-warning};
--red-ui-border-color-success: #{$border-color-success};
--red-ui-border-color-error: #{colors.$border-color-error};
--red-ui-border-color-warning: #{colors.$border-color-warning};
--red-ui-border-color-success: #{colors.$border-color-success};
--red-ui-form-background: #{$form-background};
--red-ui-form-placeholder-color: #{$form-placeholder-color};
--red-ui-form-text-color: #{$form-text-color};
--red-ui-form-text-color-disabled: #{$form-text-color-disabled};
--red-ui-form-input-focus-color: #{$form-input-focus-color};
--red-ui-form-input-border-color: #{$form-input-border-color};
--red-ui-form-input-border-selected-color: #{$form-input-border-selected-color};
--red-ui-form-input-border-error-color: #{$form-input-border-error-color};
--red-ui-form-input-background: #{$form-input-background};
--red-ui-form-input-background-disabled: #{$form-input-background-disabled};
--red-ui-form-button-background: #{$form-button-background};
--red-ui-form-background: #{colors.$form-background};
--red-ui-form-placeholder-color: #{colors.$form-placeholder-color};
--red-ui-form-text-color: #{colors.$form-text-color};
--red-ui-form-text-color-disabled: #{colors.$form-text-color-disabled};
--red-ui-form-input-focus-color: #{colors.$form-input-focus-color};
--red-ui-form-input-border-color: #{colors.$form-input-border-color};
--red-ui-form-input-border-selected-color: #{colors.$form-input-border-selected-color};
--red-ui-form-input-border-error-color: #{colors.$form-input-border-error-color};
--red-ui-form-input-background: #{colors.$form-input-background};
--red-ui-form-input-background-disabled: #{colors.$form-input-background-disabled};
--red-ui-form-button-background: #{colors.$form-button-background};
--red-ui-form-tips-background: #{$form-tips-background};
--red-ui-form-tips-background: #{colors.$form-tips-background};
--red-ui-text-editor-color: #{$text-editor-color};
--red-ui-text-editor-background: #{$text-editor-background};
--red-ui-text-editor-color-disabled: #{$text-editor-color-disabled};
--red-ui-text-editor-background-disabled: #{$text-editor-background-disabled};
--red-ui-text-editor-gutter-background: #{$text-editor-gutter-background};
--red-ui-text-editor-gutter-color: #{$text-editor-gutter-color};
--red-ui-text-editor-gutter-active-line-background: #{$text-editor-gutter-active-line-background};
--red-ui-text-editor-active-line-background: #{$text-editor-active-line-background};
--red-ui-text-editor-selection-background: #{$text-editor-selection-background};
--red-ui-text-editor-color: #{colors.$text-editor-color};
--red-ui-text-editor-background: #{colors.$text-editor-background};
--red-ui-text-editor-color-disabled: #{colors.$text-editor-color-disabled};
--red-ui-text-editor-background-disabled: #{colors.$text-editor-background-disabled};
--red-ui-text-editor-gutter-background: #{colors.$text-editor-gutter-background};
--red-ui-text-editor-gutter-color: #{colors.$text-editor-gutter-color};
--red-ui-text-editor-gutter-active-line-background: #{colors.$text-editor-gutter-active-line-background};
--red-ui-text-editor-active-line-background: #{colors.$text-editor-active-line-background};
--red-ui-text-editor-selection-background: #{colors.$text-editor-selection-background};
--red-ui-event-log-background: #{$event-log-background};
--red-ui-event-log-color: #{$event-log-color};
--red-ui-event-log-active-line-background: #{$event-log-active-line-background};
--red-ui-event-log-selection-background: #{$event-log-selection-background};
--red-ui-event-log-background: #{colors.$event-log-background};
--red-ui-event-log-color: #{colors.$event-log-color};
--red-ui-event-log-active-line-background: #{colors.$event-log-active-line-background};
--red-ui-event-log-selection-background: #{colors.$event-log-selection-background};
--red-ui-list-item-color: #{$list-item-color};
--red-ui-list-item-secondary-color: #{$list-item-secondary-color};
--red-ui-list-item-background: #{$list-item-background};
--red-ui-list-item-background-disabled: #{$list-item-background-disabled};
--red-ui-list-item-background-hover: #{$list-item-background-hover};
--red-ui-list-item-background-selected: #{$list-item-background-selected};
--red-ui-list-item-border-selected: #{$list-item-border-selected};
--red-ui-list-item-color: #{colors.$list-item-color};
--red-ui-list-item-secondary-color: #{colors.$list-item-secondary-color};
--red-ui-list-item-background: #{colors.$list-item-background};
--red-ui-list-item-background-disabled: #{colors.$list-item-background-disabled};
--red-ui-list-item-background-hover: #{colors.$list-item-background-hover};
--red-ui-list-item-background-selected: #{colors.$list-item-background-selected};
--red-ui-list-item-border-selected: #{colors.$list-item-border-selected};
--red-ui-tab-text-color-active: #{$tab-text-color-active};
--red-ui-tab-text-color-inactive: #{$tab-text-color-inactive};
--red-ui-tab-text-color-disabled-active: #{$tab-text-color-disabled-active};
--red-ui-tab-text-color-disabled-inactive: #{$tab-text-color-disabled-inactive};
--red-ui-tab-text-color-active: #{colors.$tab-text-color-active};
--red-ui-tab-text-color-inactive: #{colors.$tab-text-color-inactive};
--red-ui-tab-text-color-disabled-active: #{colors.$tab-text-color-disabled-active};
--red-ui-tab-text-color-disabled-inactive: #{colors.$tab-text-color-disabled-inactive};
--red-ui-tab-badge-color: #{$tab-badge-color};
--red-ui-tab-background: #{$tab-background};
--red-ui-tab-background-active: #{$tab-background-active};
--red-ui-tab-background-active-alpha: #{$tab-background-active-alpha};
--red-ui-tab-background-selected: #{$tab-background-selected};
--red-ui-tab-background-selected-alpha: #{$tab-background-selected-alpha};
--red-ui-tab-background-inactive: #{$tab-background-inactive};
--red-ui-tab-background-inactive-alpha: #{$tab-background-inactive-alpha};
--red-ui-tab-background-hover: #{$tab-background-hover};
--red-ui-tab-background-hover-alpha: #{$tab-background-hover-alpha};
--red-ui-tab-badge-color: #{colors.$tab-badge-color};
--red-ui-tab-background: #{colors.$tab-background};
--red-ui-tab-background-active: #{colors.$tab-background-active};
--red-ui-tab-background-active-alpha: #{colors.$tab-background-active-alpha};
--red-ui-tab-background-selected: #{colors.$tab-background-selected};
--red-ui-tab-background-selected-alpha: #{colors.$tab-background-selected-alpha};
--red-ui-tab-background-inactive: #{colors.$tab-background-inactive};
--red-ui-tab-background-inactive-alpha: #{colors.$tab-background-inactive-alpha};
--red-ui-tab-background-hover: #{colors.$tab-background-hover};
--red-ui-tab-background-hover-alpha: #{colors.$tab-background-hover-alpha};
--red-ui-palette-header-background: #{$palette-header-background};
--red-ui-palette-header-color: #{$palette-header-color};
--red-ui-palette-content-background: #{$palette-content-background};
--red-ui-palette-header-background: #{colors.$palette-header-background};
--red-ui-palette-header-color: #{colors.$palette-header-color};
--red-ui-palette-content-background: #{colors.$palette-content-background};
--red-ui-workspace-button-background: #{$workspace-button-background};
--red-ui-workspace-button-background-hover: #{$workspace-button-background-hover};
--red-ui-workspace-button-background-active: #{$workspace-button-background-active};
--red-ui-workspace-button-background: #{colors.$workspace-button-background};
--red-ui-workspace-button-background-hover: #{colors.$workspace-button-background-hover};
--red-ui-workspace-button-background-active: #{colors.$workspace-button-background-active};
--red-ui-workspace-button-color: #{$workspace-button-color};
--red-ui-workspace-button-color-disabled: #{$workspace-button-color-disabled};
--red-ui-workspace-button-color-focus: #{$workspace-button-color-focus};
--red-ui-workspace-button-color-hover: #{$workspace-button-color-hover};
--red-ui-workspace-button-color-active: #{$workspace-button-color-active};
--red-ui-workspace-button-color-selected: #{$workspace-button-color-selected};
--red-ui-workspace-button-color: #{colors.$workspace-button-color};
--red-ui-workspace-button-color-disabled: #{colors.$workspace-button-color-disabled};
--red-ui-workspace-button-color-focus: #{colors.$workspace-button-color-focus};
--red-ui-workspace-button-color-hover: #{colors.$workspace-button-color-hover};
--red-ui-workspace-button-color-active: #{colors.$workspace-button-color-active};
--red-ui-workspace-button-color-selected: #{colors.$workspace-button-color-selected};
--red-ui-workspace-button-color-primary: #{$workspace-button-color-primary};
--red-ui-workspace-button-background-primary: #{$workspace-button-background-primary};
--red-ui-workspace-button-background-primary-hover: #{$workspace-button-background-primary-hover};
--red-ui-workspace-button-color-primary: #{colors.$workspace-button-color-primary};
--red-ui-workspace-button-background-primary: #{colors.$workspace-button-background-primary};
--red-ui-workspace-button-background-primary-hover: #{colors.$workspace-button-background-primary-hover};
--red-ui-workspace-button-color-focus-outline: #{$workspace-button-color-focus-outline};
--red-ui-workspace-button-color-focus-outline: #{colors.$workspace-button-color-focus-outline};
--red-ui-shade-color: #{$shade-color};
--red-ui-shade-color: #{colors.$shade-color};
--red-ui-popover-background: #{$popover-background};
--red-ui-popover-border: #{$popover-border};
--red-ui-popover-color: #{$popover-color};
--red-ui-popover-button-border-color: #{$popover-button-border-color};
--red-ui-popover-button-border-color-hover: #{$popover-button-border-color-hover};
--red-ui-popover-background: #{colors.$popover-background};
--red-ui-popover-border: #{colors.$popover-border};
--red-ui-popover-color: #{colors.$popover-color};
--red-ui-popover-button-border-color: #{colors.$popover-button-border-color};
--red-ui-popover-button-border-color-hover: #{colors.$popover-button-border-color-hover};
--red-ui-diff-text-header-color: #{$diff-text-header-color};
--red-ui-diff-text-header-background: #{$diff-text-header-background};
--red-ui-diff-state-color: #{$diff-state-color};
--red-ui-diff-state-prefix-color: #{$diff-state-prefix-color};
--red-ui-diff-state-added: #{$diff-state-added};
--red-ui-diff-state-deleted: #{$diff-state-deleted};
--red-ui-diff-state-changed: #{$diff-state-changed};
--red-ui-diff-state-changed-background: #{$diff-state-changed-background};
--red-ui-diff-state-unchanged: #{$diff-state-unchanged};
--red-ui-diff-state-unchanged-background: #{$diff-state-unchanged-background};
--red-ui-diff-text-header-color: #{colors.$diff-text-header-color};
--red-ui-diff-text-header-background: #{colors.$diff-text-header-background};
--red-ui-diff-state-color: #{colors.$diff-state-color};
--red-ui-diff-state-prefix-color: #{colors.$diff-state-prefix-color};
--red-ui-diff-state-added: #{colors.$diff-state-added};
--red-ui-diff-state-deleted: #{colors.$diff-state-deleted};
--red-ui-diff-state-changed: #{colors.$diff-state-changed};
--red-ui-diff-state-changed-background: #{colors.$diff-state-changed-background};
--red-ui-diff-state-unchanged: #{colors.$diff-state-unchanged};
--red-ui-diff-state-unchanged-background: #{colors.$diff-state-unchanged-background};
--red-ui-diff-state-conflicted: #{$diff-state-conflicted};
--red-ui-diff-state-moved: #{$diff-state-moved};
--red-ui-diff-state-conflict: #{$diff-state-conflict};
--red-ui-diff-state-conflict-background: #{$diff-state-conflict-background};
--red-ui-diff-state-conflicted: #{colors.$diff-state-conflicted};
--red-ui-diff-state-moved: #{colors.$diff-state-moved};
--red-ui-diff-state-conflict: #{colors.$diff-state-conflict};
--red-ui-diff-state-conflict-background: #{colors.$diff-state-conflict-background};
--red-ui-diff-state-added-background: #{$diff-state-added-background};
--red-ui-diff-state-added-border: #{$diff-state-added-border};
--red-ui-diff-state-added-header-background: #{$diff-state-added-header-background};
--red-ui-diff-state-added-header-border: #{$diff-state-added-header-border};
--red-ui-diff-state-added-background: #{colors.$diff-state-added-background};
--red-ui-diff-state-added-border: #{colors.$diff-state-added-border};
--red-ui-diff-state-added-header-background: #{colors.$diff-state-added-header-background};
--red-ui-diff-state-added-header-border: #{colors.$diff-state-added-header-border};
--red-ui-diff-state-deleted-background: #{$diff-state-deleted-background};
--red-ui-diff-state-deleted-border: #{$diff-state-deleted-border};
--red-ui-diff-state-deleted-header-background: #{$diff-state-deleted-header-background};
--red-ui-diff-state-deleted-header-border: #{$diff-state-deleted-header-border};
--red-ui-diff-state-deleted-background: #{colors.$diff-state-deleted-background};
--red-ui-diff-state-deleted-border: #{colors.$diff-state-deleted-border};
--red-ui-diff-state-deleted-header-background: #{colors.$diff-state-deleted-header-background};
--red-ui-diff-state-deleted-header-border: #{colors.$diff-state-deleted-header-border};
--red-ui-diff-merge-header-color: #{$diff-merge-header-color};
--red-ui-diff-merge-header-background: #{$diff-merge-header-background};
--red-ui-diff-merge-header-border-color: #{$diff-merge-header-border-color};
--red-ui-diff-merge-header-color: #{colors.$diff-merge-header-color};
--red-ui-diff-merge-header-background: #{colors.$diff-merge-header-background};
--red-ui-diff-merge-header-border-color: #{colors.$diff-merge-header-border-color};
--red-ui-menuBackground: #{$menuBackground};
--red-ui-menuDivider: #{$menuDivider};
--red-ui-menuColor: #{$menuColor};
--red-ui-menuActiveColor: #{$menuActiveColor};
--red-ui-menuActiveBackground: #{$menuActiveBackground};
--red-ui-menuDisabledColor: #{$menuDisabledColor};
--red-ui-menuHoverColor: #{$menuHoverColor};
--red-ui-menuHoverBackground: #{$menuHoverBackground};
--red-ui-menuCaret: #{$menuCaret};
--red-ui-menuBackground: #{colors.$menuBackground};
--red-ui-menuDivider: #{colors.$menuDivider};
--red-ui-menuColor: #{colors.$menuColor};
--red-ui-menuActiveColor: #{colors.$menuActiveColor};
--red-ui-menuActiveBackground: #{colors.$menuActiveBackground};
--red-ui-menuDisabledColor: #{colors.$menuDisabledColor};
--red-ui-menuHoverColor: #{colors.$menuHoverColor};
--red-ui-menuHoverBackground: #{colors.$menuHoverBackground};
--red-ui-menuCaret: #{colors.$menuCaret};
--red-ui-view-navigator-background: #{$view-navigator-background};
--red-ui-view-navigator-background: #{colors.$view-navigator-background};
--red-ui-view-lasso-stroke: #{$view-lasso-stroke};
--red-ui-view-lasso-fill: #{$view-lasso-fill};
--red-ui-view-lasso-stroke: #{colors.$view-lasso-stroke};
--red-ui-view-lasso-fill: #{colors.$view-lasso-fill};
--red-ui-view-background: #{$view-background};
--red-ui-view-grid-color: #{$view-grid-color};
--red-ui-view-background: #{colors.$view-background};
--red-ui-view-grid-color: #{colors.$view-grid-color};
--red-ui-node-label-color: #{$node-label-color};
--red-ui-node-port-label-color: #{$node-port-label-color};
--red-ui-node-border: #{$node-border};
--red-ui-node-border-unknown: #{$node-border-unknown};
--red-ui-node-border-placeholder: #{$node-border-placeholder};
--red-ui-node-background-placeholder: #{$node-background-placeholder};
--red-ui-node-label-color: #{colors.$node-label-color};
--red-ui-node-port-label-color: #{colors.$node-port-label-color};
--red-ui-node-border: #{colors.$node-border};
--red-ui-node-border-unknown: #{colors.$node-border-unknown};
--red-ui-node-border-placeholder: #{colors.$node-border-placeholder};
--red-ui-node-background-placeholder: #{colors.$node-background-placeholder};
--red-ui-node-port-background: #{$node-port-background};
--red-ui-node-port-background-hover: #{$node-port-background-hover};
--red-ui-node-icon-color: #{$node-icon-color};
--red-ui-node-icon-background-color: #{$node-icon-background-color};
--red-ui-node-icon-background-color-fill: #{$node-icon-background-color-fill};
--red-ui-node-icon-background-color-opacity: #{$node-icon-background-color-opacity};
--red-ui-node-icon-border-color: #{$node-icon-border-color};
--red-ui-node-icon-border-color-opacity: #{$node-icon-border-color-opacity};
--red-ui-node-port-background: #{colors.$node-port-background};
--red-ui-node-port-background-hover: #{colors.$node-port-background-hover};
--red-ui-node-icon-color: #{colors.$node-icon-color};
--red-ui-node-icon-background-color: #{colors.$node-icon-background-color};
--red-ui-node-icon-background-color-fill: #{colors.$node-icon-background-color-fill};
--red-ui-node-icon-background-color-opacity: #{colors.$node-icon-background-color-opacity};
--red-ui-node-icon-border-color: #{colors.$node-icon-border-color};
--red-ui-node-icon-border-color-opacity: #{colors.$node-icon-border-color-opacity};
--red-ui-node-config-background: #{$node-config-background};
--red-ui-node-config-icon-container-disabled: #{$node-config-icon-container-disabled};
--red-ui-node-config-background: #{colors.$node-config-background};
--red-ui-node-config-icon-container-disabled: #{colors.$node-config-icon-container-disabled};
--red-ui-node-link-port-background: #{$node-link-port-background};
--red-ui-node-link-port-background: #{colors.$node-link-port-background};
--red-ui-node-status-error-border: #{$node-status-error-border};
--red-ui-node-status-error-background: #{$node-status-error-background};
--red-ui-node-status-changed-border: #{$node-status-changed-border};
--red-ui-node-status-changed-background: #{$node-status-changed-background};
--red-ui-node-status-error-border: #{colors.$node-status-error-border};
--red-ui-node-status-error-background: #{colors.$node-status-error-background};
--red-ui-node-status-changed-border: #{colors.$node-status-changed-border};
--red-ui-node-status-changed-background: #{colors.$node-status-changed-background};
@each $current-color in red green yellow blue grey gray {
--red-ui-node-status-colors-#{"" + $current-color}: #{map-get($node-status-colors, $current-color)};
--red-ui-node-status-colors-#{"" + $current-color}: #{map.get(colors.$node-status-colors, $current-color)};
}
--red-ui-node-selected-color: #{$node-selected-color};
--red-ui-port-selected-color: #{$port-selected-color};
--red-ui-node-selected-color: #{colors.$node-selected-color};
--red-ui-port-selected-color: #{colors.$port-selected-color};
--red-ui-link-color: #{$link-color};
--red-ui-link-link-color: #{$link-link-color};
--red-ui-link-disabled-color: #{$link-disabled-color};
--red-ui-link-link-active-color: #{$link-link-active-color};
--red-ui-link-unknown-color: #{$link-unknown-color};
--red-ui-link-color: #{colors.$link-color};
--red-ui-link-link-color: #{colors.$link-link-color};
--red-ui-link-disabled-color: #{colors.$link-disabled-color};
--red-ui-link-link-active-color: #{colors.$link-link-active-color};
--red-ui-link-unknown-color: #{colors.$link-unknown-color};
--red-ui-clipboard-textarea-background: #{$clipboard-textarea-background};
--red-ui-clipboard-textarea-background: #{colors.$clipboard-textarea-background};
--red-ui-deploy-button-color: #{$deploy-button-color};
--red-ui-deploy-button-color-active: #{$deploy-button-color-active};
--red-ui-deploy-button-color-disabled: #{$deploy-button-color-disabled};
--red-ui-deploy-button-background: #{$deploy-button-background};
--red-ui-deploy-button-background-hover: #{$deploy-button-background-hover};
--red-ui-deploy-button-background-active: #{$deploy-button-background-active};
--red-ui-deploy-button-background-disabled: #{$deploy-button-background-disabled};
--red-ui-deploy-button-background-disabled-hover: #{$deploy-button-background-disabled-hover};
--red-ui-deploy-button-color: #{colors.$deploy-button-color};
--red-ui-deploy-button-color-active: #{colors.$deploy-button-color-active};
--red-ui-deploy-button-color-disabled: #{colors.$deploy-button-color-disabled};
--red-ui-deploy-button-background: #{colors.$deploy-button-background};
--red-ui-deploy-button-background-hover: #{colors.$deploy-button-background-hover};
--red-ui-deploy-button-background-active: #{colors.$deploy-button-background-active};
--red-ui-deploy-button-background-disabled: #{colors.$deploy-button-background-disabled};
--red-ui-deploy-button-background-disabled-hover: #{colors.$deploy-button-background-disabled-hover};
--red-ui-header-background: #{$header-background};
--red-ui-header-accent: #{$header-accent};
--red-ui-header-button-background-active: #{$header-button-background-active};
--red-ui-header-menu-color: #{$header-menu-color};
--red-ui-header-menu-color-disabled: #{$header-menu-color-disabled};
--red-ui-header-menu-heading-color: #{$header-menu-heading-color};
--red-ui-header-menu-sublabel-color: #{$header-menu-sublabel-color};
--red-ui-header-menu-background: #{$header-menu-background};
--red-ui-header-menu-item-hover: #{$header-menu-item-hover};
--red-ui-header-menu-item-border-active: #{$header-menu-item-border-active};
--red-ui-headerMenuItemDivider: #{$headerMenuItemDivider};
--red-ui-headerMenuCaret: #{$headerMenuCaret};
--red-ui-header-background: #{colors.$header-background};
--red-ui-header-accent: #{colors.$header-accent};
--red-ui-header-button-background-active: #{colors.$header-button-background-active};
--red-ui-header-menu-color: #{colors.$header-menu-color};
--red-ui-header-menu-color-disabled: #{colors.$header-menu-color-disabled};
--red-ui-header-menu-heading-color: #{colors.$header-menu-heading-color};
--red-ui-header-menu-sublabel-color: #{colors.$header-menu-sublabel-color};
--red-ui-header-menu-background: #{colors.$header-menu-background};
--red-ui-header-menu-item-hover: #{colors.$header-menu-item-hover};
--red-ui-header-menu-item-border-active: #{colors.$header-menu-item-border-active};
--red-ui-headerMenuItemDivider: #{colors.$headerMenuItemDivider};
--red-ui-headerMenuCaret: #{colors.$headerMenuCaret};
--red-ui-vcCommitShaColor: #{$vcCommitShaColor};
--red-ui-vcCommitShaColor: #{colors.$vcCommitShaColor};
--red-ui-dnd-background: #{$dnd-background};
--red-ui-dnd-color: #{$dnd-color};
--red-ui-dnd-background: #{colors.$dnd-background};
--red-ui-dnd-color: #{colors.$dnd-color};
--red-ui-notification-border-default: #{$notification-border-default};
--red-ui-notification-border-success: #{$notification-border-success};
--red-ui-notification-border-warning: #{$notification-border-warning};
--red-ui-notification-border-error: #{$notification-border-error};
--red-ui-notification-border-default: #{colors.$notification-border-default};
--red-ui-notification-border-success: #{colors.$notification-border-success};
--red-ui-notification-border-warning: #{colors.$notification-border-warning};
--red-ui-notification-border-error: #{colors.$notification-border-error};
--red-ui-debug-message-background: #{$debug-message-background};
--red-ui-debug-message-background-hover: #{$debug-message-background-hover};
--red-ui-debug-message-background: #{colors.$debug-message-background};
--red-ui-debug-message-background-hover: #{colors.$debug-message-background-hover};
--red-ui-debug-message-text-color: #{$debug-message-text-color};
--red-ui-debug-message-text-color-meta: #{$debug-message-text-color-meta};
--red-ui-debug-message-text-color-object-key: #{$debug-message-text-color-object-key};
--red-ui-debug-message-text-color-msg-type-other: #{$debug-message-text-color-msg-type-other};
--red-ui-debug-message-text-color-msg-type-string: #{$debug-message-text-color-msg-type-string};
--red-ui-debug-message-text-color-msg-type-null: #{$debug-message-text-color-msg-type-null};
--red-ui-debug-message-text-color-msg-type-meta: #{$debug-message-text-color-msg-type-meta};
--red-ui-debug-message-text-color-msg-type-number: #{$debug-message-text-color-msg-type-number};
--red-ui-debug-message-text-color: #{colors.$debug-message-text-color};
--red-ui-debug-message-text-color-meta: #{colors.$debug-message-text-color-meta};
--red-ui-debug-message-text-color-object-key: #{colors.$debug-message-text-color-object-key};
--red-ui-debug-message-text-color-msg-type-other: #{colors.$debug-message-text-color-msg-type-other};
--red-ui-debug-message-text-color-msg-type-string: #{colors.$debug-message-text-color-msg-type-string};
--red-ui-debug-message-text-color-msg-type-null: #{colors.$debug-message-text-color-msg-type-null};
--red-ui-debug-message-text-color-msg-type-meta: #{colors.$debug-message-text-color-msg-type-meta};
--red-ui-debug-message-text-color-msg-type-number: #{colors.$debug-message-text-color-msg-type-number};
--red-ui-debug-message-border: #{$debug-message-border};
--red-ui-debug-message-border-hover: #{$debug-message-border-hover};
--red-ui-debug-message-border-warning: #{$debug-message-border-warning};
--red-ui-debug-message-border-error: #{$debug-message-border-error};
--red-ui-debug-message-border: #{colors.$debug-message-border};
--red-ui-debug-message-border-hover: #{colors.$debug-message-border-hover};
--red-ui-debug-message-border-warning: #{colors.$debug-message-border-warning};
--red-ui-debug-message-border-error: #{colors.$debug-message-border-error};
--red-ui-group-default-fill: #{$group-default-fill};
--red-ui-group-default-fill-opacity: #{$group-default-fill-opacity};
--red-ui-group-default-stroke: #{$group-default-stroke};
--red-ui-group-default-stroke-opacity: #{$group-default-stroke-opacity};
--red-ui-group-default-label-color: #{$group-default-label-color};
--red-ui-group-default-fill: #{colors.$group-default-fill};
--red-ui-group-default-fill-opacity: #{colors.$group-default-fill-opacity};
--red-ui-group-default-stroke: #{colors.$group-default-stroke};
--red-ui-group-default-stroke-opacity: #{colors.$group-default-stroke-opacity};
--red-ui-group-default-label-color: #{colors.$group-default-label-color};
--red-ui-tourGuide-border: #{$tourGuide-border};
--red-ui-tourGuide-heading-color: #{$tourGuide-heading-color};
--red-ui-tourGuide-border: #{colors.$tourGuide-border};
--red-ui-tourGuide-heading-color: #{colors.$tourGuide-heading-color};
--red-ui-grip-color: #{$grip-color};
--red-ui-grip-color: #{colors.$grip-color};
--red-ui-icons-flow-color: #{$icons-flow-color};
--red-ui-icons-flow-color: #{colors.$icons-flow-color};
--red-ui-spinner-color: #{$spinner-color};
--red-ui-spinner-color: #{colors.$spinner-color};
--red-ui-tab-icon-color: #{$tab-icon-color};
--red-ui-tab-icon-color: #{colors.$tab-icon-color};
@each $current-color in 1 2 3 4 5 {
--red-ui-user-profile-colors-#{"" + $current-color}: #{map-get($user-profile-colors, $current-color)};
--red-ui-user-profile-colors-#{"" + $current-color}: #{map.get(colors.$user-profile-colors, $current-color)};
}
}

View File

@@ -1,3 +1,5 @@
@use "mixins";
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
@@ -22,7 +24,7 @@
bottom: 0px;
right: 322px;
overflow: hidden;
@include component-border;
@include mixins.component-border;
transition: left 0.1s ease-in-out;
}
@@ -40,7 +42,7 @@
}
}
#red-ui-workspace-tabs-shade {
@include shade;
@include mixins.shade;
z-index: 2;
bottom: auto;
height: 35px;
@@ -157,7 +159,7 @@
}
.red-ui-component-footer {
@include component-footer;
@include mixins.component-footer;
> .button-group {
display: inline-block;
@@ -176,12 +178,12 @@
a.red-ui-footer-button,
button.red-ui-footer-button {
@include component-footer-button;
@include mixins.component-footer-button;
}
a.red-ui-footer-button-toggle,
button.red-ui-footer-button-toggle {
@include component-footer-button-toggle;
@include mixins.component-footer-button-toggle;
}
.red-ui-statusbar-widget {

View File

@@ -1,3 +1,5 @@
@use "mixins";
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
@@ -32,7 +34,7 @@
white-space: nowrap;
transition: right 0.2s ease;
overflow: hidden;
@include disable-selection;
@include mixins.disable-selection;
label {
padding: 1px 8px;
@@ -44,12 +46,12 @@
padding: 0;
}
.button {
@include workspace-button;
@include mixins.workspace-button;
margin-right: 10px;
padding: 2px 8px;
}
.button-group {
@include disable-selection;
@include mixins.disable-selection;
.button:first-child {
margin-right: 0;

View File

@@ -0,0 +1,231 @@
export default {
version: "4.0.0",
steps: [
{
titleIcon: "fa fa-map-o",
title: {
"en-US": "Welcome to Node-RED 4.0!",
"ja": "Node-RED 4.0 へようこそ!",
"fr": "Bienvenue dans Node-RED 4.0!"
},
description: {
"en-US": "<p>Let's take a moment to discover the new features in this release.</p>",
"ja": "<p>本リリースの新機能を見つけてみましょう。</p>",
"fr": "<p>Prenons un moment pour découvrir les nouvelles fonctionnalités de cette version.</p>"
}
},
{
title: {
"en-US": "Multiplayer Mode",
"ja": "複数ユーザ同時利用モード",
"fr": "Mode Multi-utilisateur"
},
image: 'images/nr4-multiplayer-location.png',
description: {
"en-US": `<p>This release includes the first small steps towards making Node-RED easier
to work with when you have multiple people editing flows at the same time.</p>
<p>When this feature is enabled, you will now see who else has the editor open and some
basic information on where they are in the editor.</p>
<p>Check the release post for details on how to enable this feature in your settings file.</p>`,
"ja": `<p>本リリースには、複数ユーザが同時にフローを編集する時に、Node-REDをより使いやすくするのための最初の微修正が入っています。</p>
<p>本機能を有効にすると、誰がエディタを開いているか、その人がエディタ上のどこにいるかの基本的な情報が表示されます。</p>
<p>設定ファイルで本機能を有効化する方法の詳細は、リリースの投稿を確認してください。</p>`,
"fr": `<p>Cette version inclut les premières étapes visant à rendre Node-RED plus facile à utiliser
lorsque plusieurs personnes modifient des flux en même temps.</p>
<p>Lorsque cette fonctionnalité est activée, vous pourrez désormais voir si dautres utilisateurs ont
ouvert l'éditeur. Vous pourrez également savoir où ces utilisateurs se trouvent dans l'éditeur.</p>
<p>Consultez la note de publication pour plus de détails sur la façon d'activer cette fonctionnalité
dans votre fichier de paramètres.</p>`
}
},
{
title: {
"en-US": "Better background deploy handling",
"ja": "バックグラウンドのデプロイ処理の改善",
"fr": "Meilleure gestion du déploiement en arrière-plan"
},
image: 'images/nr4-background-deploy.png',
description: {
"en-US": `<p>If another user deploys changes whilst you are editing, we now use a more discrete notification
that doesn't stop you continuing your work - especially if they are being very productive and deploying lots
of changes.</p>`,
"ja": `他のユーザが変更をデプロイした時に、特に変更が多い生産的な編集作業を妨げないように通知するようになりました。`,
"fr": `<p>Si un autre utilisateur déploie des modifications pendant que vous êtes en train de modifier, vous recevrez
une notification plus discrète qu'auparavant qui ne vous empêche pas de continuer votre travail.</p>`
}
},
{
title: {
"en-US": "Improved flow diffs",
"ja": "フローの差分表示の改善",
"fr": "Amélioration des différences de flux"
},
image: 'images/nr4-diff-update.png',
description: {
"en-US": `<p>When viewing changes made to a flow, Node-RED now distinguishes between nodes that have had configuration
changes and those that have only been moved.<p>
<p>When faced with a long list of changes to look at, this makes it much easier to focus on more significant items.</p>`,
"ja": `<p>フローの変更内容を表示する時に、Node-REDは設定が変更されたードと、移動されただけのードを区別するようになりました。<p>
<p>これによって、多くの変更内容を確認する際に、重要な項目に焦点を当てることができます。</p>`,
"fr": `<p>Lors de l'affichage des modifications apportées à un flux, Node-RED fait désormais la distinction entre les
noeuds qui ont changé de configuration et ceux qui ont seulement été déplacés.<p>
<p>Face à une longue liste de changements à examiner, il est beaucoup plus facile de se concentrer sur les éléments les
plus importants.</p>`
}
},
{
title: {
"en-US": "Better Configuration Node UX",
"ja": "設定ードのUXが向上",
"fr": "Meilleure expérience utilisateur du noeud de configuration"
},
image: 'images/nr4-config-select.png',
description: {
"en-US": `<p>The Configuration node selection UI has had a small update to have a dedicated 'add' button
next to the select box.</p>
<p>It's a small change, but should make it easier to work with your config nodes.</p>`,
"ja": `<p>設定ードを選択するUIが修正され、選択ボックスの隣に専用の「追加」ボタンが追加されました。</p>
<p>微修正ですが設定ノードの操作が容易になります。</p>`,
"fr": `<p>L'interface utilisateur de la sélection du noeud de configuration a fait l'objet d'une petite
mise à jour afin de disposer d'un bouton « Ajouter » à côté de la zone de sélection.</p>
<p>C'est un petit changement, mais cela devrait faciliter le travail avec vos noeuds de configuration.</p>`
}
},
{
title: {
"en-US": "Timestamp formatting options",
"ja": "タイムスタンプの形式の項目",
"fr": "Options de formatage de l'horodatage"
},
image: 'images/nr4-timestamp-formatting.png',
description: {
"en-US": `<p>Nodes that let you set a timestamp now have options on what format that timestamp should be in.</p>
<p>We're keeping it simple to begin with by providing three options:<p>
<ul>
<li>Milliseconds since epoch - this is existing behaviour of the timestamp option</li>
<li>ISO 8601 - a common format used by many systems</li>
<li>JavaScript Date Object</li>
</ul>`,
"ja": `<p>タイムスタンプを設定するノードに、タイムスタンプの形式を指定できる項目が追加されました。</p>
<p>次の3つの項目を追加したことで、簡単に選択できるようになりました:<p>
<ul>
<li>エポックからのミリ秒 - 従来動作と同じになるタイムスタンプの項目</li>
<li>ISO 8601 - 多くのシステムで使用されている共通の形式</li>
<li>JavaScript日付オブジェクト</li>
</ul>`,
"fr": `<p>Les noeuds qui vous permettent de définir un horodatage disposent désormais d'options sur le format dans lequel cet horodatage peut être défini.</p>
<p>Nous gardons les choses simples en proposant trois options :<p>
<ul>
<li>Millisecondes depuis l'époque : il s'agit du comportement existant de l'option d'horodatage</li>
<li>ISO 8601 : un format commun utilisé par de nombreux systèmes</li>
<li>Objet Date JavaScript</li>
</ul>`
}
},
{
title: {
"en-US": "Auto-complete of flow/global and env types",
"ja": "フロー/グローバル、環境変数の型の自動補完",
"fr": "Saisie automatique des types de flux/global et env"
},
image: 'images/nr4-auto-complete.png',
description: {
"en-US": `<p>The <code>flow</code>/<code>global</code> context inputs and the <code>env</code> input
now all include auto-complete suggestions based on the live state of your flows.</p>
`,
"ja": `<p><code>flow</code>/<code>global</code>コンテキストや<code>env</code>の入力を、現在のフローの状態をもとに自動補完で提案するようになりました。</p>
`,
"fr": `<p>Les entrées contextuelles <code>flow</code>/<code>global</code> et l'entrée <code>env</code>
incluent désormais des suggestions de saisie semi-automatique basées sur l'état actuel de vos flux.</p>
`,
}
},
{
title: {
"en-US": "Config node customisation in Subflows",
"ja": "サブフローでの設定ノードのカスタマイズ",
"fr": "Personnalisation du noeud de configuration dans les sous-flux"
},
image: 'images/nr4-sf-config.png',
description: {
"en-US": `<p>Subflows can now be customised to allow each instance to use a different
config node of a selected type.</p>
<p>For example, each instance of a subflow that connects to an MQTT Broker and does some post-processing
of the messages received can be pointed at a different broker.</p>
`,
"ja": `<p>サブフローをカスタマイズして、選択した型の異なる設定ノードを各インスタンスが使用できるようになりました。</p>
<p>例えば、MQTTブローカへ接続し、メッセージ受信と後処理を行うサブフローの各インスタンスに異なるブローカを指定することも可能です。</p>
`,
"fr": `<p>Les sous-flux peuvent désormais être personnalisés pour permettre à chaque instance d'utiliser un
noeud de configuration d'un type sélectionné.</p>
<p>Par exemple, chaque instance d'un sous-flux qui se connecte à un courtier MQTT et effectue un post-traitement
des messages reçus peut être pointée vers un autre courtier.</p>
`
}
},
{
title: {
"en-US": "Remembering palette state",
"ja": "パレットの状態を維持",
"fr": "Mémorisation de l'état de la palette"
},
description: {
"en-US": `<p>The palette now remembers what categories you have hidden between reloads - as well as any
filter you have applied.</p>`,
"ja": `<p>パレット上で非表示にしたカテゴリや適用したフィルタが、リロードしても記憶されるようになりました。</p>`,
"fr": `<p>La palette se souvient désormais des catégories que vous avez masquées entre les rechargements,
ainsi que le filtre que vous avez appliqué.</p>`
}
},
{
title: {
"en-US": "Plugins shown in the Palette Manager",
"ja": "パレット管理にプラグインを表示",
"fr": "Affichage des Plugins dans le gestionnaire de palettes"
},
image: 'images/nr4-plugins.png',
description: {
"en-US": `<p>The palette manager now shows any plugin modules you have installed, such as
<code>node-red-debugger</code>. Previously they would only be shown if the plugins include
nodes for the palette.</p>`,
"ja": `<p>パレットの管理に <code>node-red-debugger</code> の様なインストールしたプラグインが表示されます。以前はプラグインにパレット向けのノードが含まれている時のみ表示されていました。</p>`,
"fr": `<p>Le gestionnaire de palettes affiche désormais tous les plugins que vous avez installés,
tels que <code>node-red-debugger</code>. Auparavant, ils n'étaient affichés que s'ils contenaient
des noeuds pour la palette.</p>`
}
},
{
title: {
"en-US": "Node Updates",
"ja": "ノードの更新",
"fr": "Mises à jour des noeuds"
},
// image: "images/",
description: {
"en-US": `<p>The core nodes have received lots of minor fixes, documentation updates and
small enhancements. Check the full changelog in the Help sidebar for a full list.</p>
<ul>
<li>A fully RFC4180 compliant CSV mode</li>
<li>Customisable headers on the WebSocket node</li>
<li>Split node now can operate on any message property</li>
<li>and lots more...</li>
</ul>`,
"ja": `<p>コアノードには沢山の軽微な修正、ドキュメント更新、小さな機能拡張が入っています。全リストはヘルプサイドバーにある変更履歴を参照してください。</p>
<ul>
<li>RFC4180に完全に準拠したCSVモード</li>
<li>WebSocketードのカスタマイズ可能なヘッダ</li>
<li>Splitードは、メッセージプロパティで操作できるようになりました</li>
<li>他にも沢山あります...</li>
</ul>`,
"fr": `<p>Les noeuds principaux ont reçu de nombreux correctifs mineurs ainsi que des améliorations. La documentation a été mise à jour.
Consultez le journal des modifications dans la barre latérale d'aide pour une liste complète. Ci-dessous, les changements les plus importants :</p>
<ul>
<li>Un mode CSV entièrement conforme à la norme RFC4180</li>
<li>En-têtes personnalisables pour le noeud WebSocket</li>
<li>Le noeud Split peut désormais fonctionner sur n'importe quelle propriété de message</li>
<li>Et bien plus encore...</li>
</ul>`
}
}
]
}

View File

@@ -1,12 +1,12 @@
export default {
version: "4.0.0",
version: "4.1.0",
steps: [
{
titleIcon: "fa fa-map-o",
title: {
"en-US": "Welcome to Node-RED 4.0!",
"ja": "Node-RED 4.0 へようこそ!",
"fr": "Bienvenue dans Node-RED 4.0!"
"en-US": "Welcome to Node-RED 4.1!",
"ja": "Node-RED 4.1 へようこそ!",
"fr": "Bienvenue dans Node-RED 4.1!"
},
description: {
"en-US": "<p>Let's take a moment to discover the new features in this release.</p>",
@@ -16,216 +16,45 @@ export default {
},
{
title: {
"en-US": "Multiplayer Mode",
"ja": "複数ユーザ同時利用モード",
"fr": "Mode Multi-utilisateur"
"en-US": "Something new",
},
image: 'images/nr4-multiplayer-location.png',
// image: 'images/nr4-multiplayer-location.png',
description: {
"en-US": `<p>This release includes the first small steps towards making Node-RED easier
to work with when you have multiple people editing flows at the same time.</p>
<p>When this feature is enabled, you will now see who else has the editor open and some
basic information on where they are in the editor.</p>
<p>Check the release post for details on how to enable this feature in your settings file.</p>`,
"ja": `<p>本リリースには、複数ユーザが同時にフローを編集する時に、Node-REDをより使いやすくするのための最初の微修正が入っています。</p>
<p>本機能を有効にすると、誰がエディタを開いているか、その人がエディタ上のどこにいるかの基本的な情報が表示されます。</p>
<p>設定ファイルで本機能を有効化する方法の詳細は、リリースの投稿を確認してください。</p>`,
"fr": `<p>Cette version inclut les premières étapes visant à rendre Node-RED plus facile à utiliser
lorsque plusieurs personnes modifient des flux en même temps.</p>
<p>Lorsque cette fonctionnalité est activée, vous pourrez désormais voir si dautres utilisateurs ont
ouvert l'éditeur. Vous pourrez également savoir où ces utilisateurs se trouvent dans l'éditeur.</p>
<p>Consultez la note de publication pour plus de détails sur la façon d'activer cette fonctionnalité
dans votre fichier de paramètres.</p>`
"en-US": `<p>Something new</p>`
}
},
{
title: {
"en-US": "Better background deploy handling",
"ja": "バックグラウンドのデプロイ処理の改善",
"fr": "Meilleure gestion du déploiement en arrière-plan"
},
image: 'images/nr4-background-deploy.png',
description: {
"en-US": `<p>If another user deploys changes whilst you are editing, we now use a more discrete notification
that doesn't stop you continuing your work - especially if they are being very productive and deploying lots
of changes.</p>`,
"ja": `他のユーザが変更をデプロイした時に、特に変更が多い生産的な編集作業を妨げないように通知するようになりました。`,
"fr": `<p>Si un autre utilisateur déploie des modifications pendant que vous êtes en train de modifier, vous recevrez
une notification plus discrète qu'auparavant qui ne vous empêche pas de continuer votre travail.</p>`
}
},
{
title: {
"en-US": "Improved flow diffs",
"ja": "フローの差分表示の改善",
"fr": "Amélioration des différences de flux"
},
image: 'images/nr4-diff-update.png',
description: {
"en-US": `<p>When viewing changes made to a flow, Node-RED now distinguishes between nodes that have had configuration
changes and those that have only been moved.<p>
<p>When faced with a long list of changes to look at, this makes it much easier to focus on more significant items.</p>`,
"ja": `<p>フローの変更内容を表示する時に、Node-REDは設定が変更されたードと、移動されただけのードを区別するようになりました。<p>
<p>これによって、多くの変更内容を確認する際に、重要な項目に焦点を当てることができます。</p>`,
"fr": `<p>Lors de l'affichage des modifications apportées à un flux, Node-RED fait désormais la distinction entre les
noeuds qui ont changé de configuration et ceux qui ont seulement été déplacés.<p>
<p>Face à une longue liste de changements à examiner, il est beaucoup plus facile de se concentrer sur les éléments les
plus importants.</p>`
}
},
{
title: {
"en-US": "Better Configuration Node UX",
"ja": "設定ードのUXが向上",
"fr": "Meilleure expérience utilisateur du noeud de configuration"
},
image: 'images/nr4-config-select.png',
description: {
"en-US": `<p>The Configuration node selection UI has had a small update to have a dedicated 'add' button
next to the select box.</p>
<p>It's a small change, but should make it easier to work with your config nodes.</p>`,
"ja": `<p>設定ードを選択するUIが修正され、選択ボックスの隣に専用の「追加」ボタンが追加されました。</p>
<p>微修正ですが設定ノードの操作が容易になります。</p>`,
"fr": `<p>L'interface utilisateur de la sélection du noeud de configuration a fait l'objet d'une petite
mise à jour afin de disposer d'un bouton « Ajouter » à côté de la zone de sélection.</p>
<p>C'est un petit changement, mais cela devrait faciliter le travail avec vos noeuds de configuration.</p>`
}
},
{
title: {
"en-US": "Timestamp formatting options",
"ja": "タイムスタンプの形式の項目",
"fr": "Options de formatage de l'horodatage"
},
image: 'images/nr4-timestamp-formatting.png',
description: {
"en-US": `<p>Nodes that let you set a timestamp now have options on what format that timestamp should be in.</p>
<p>We're keeping it simple to begin with by providing three options:<p>
<ul>
<li>Milliseconds since epoch - this is existing behaviour of the timestamp option</li>
<li>ISO 8601 - a common format used by many systems</li>
<li>JavaScript Date Object</li>
</ul>`,
"ja": `<p>タイムスタンプを設定するノードに、タイムスタンプの形式を指定できる項目が追加されました。</p>
<p>次の3つの項目を追加したことで、簡単に選択できるようになりました:<p>
<ul>
<li>エポックからのミリ秒 - 従来動作と同じになるタイムスタンプの項目</li>
<li>ISO 8601 - 多くのシステムで使用されている共通の形式</li>
<li>JavaScript日付オブジェクト</li>
</ul>`,
"fr": `<p>Les noeuds qui vous permettent de définir un horodatage disposent désormais d'options sur le format dans lequel cet horodatage peut être défini.</p>
<p>Nous gardons les choses simples en proposant trois options :<p>
<ul>
<li>Millisecondes depuis l'époque : il s'agit du comportement existant de l'option d'horodatage</li>
<li>ISO 8601 : un format commun utilisé par de nombreux systèmes</li>
<li>Objet Date JavaScript</li>
</ul>`
}
},
{
title: {
"en-US": "Auto-complete of flow/global and env types",
"ja": "フロー/グローバル、環境変数の型の自動補完",
"fr": "Saisie automatique des types de flux/global et env"
},
image: 'images/nr4-auto-complete.png',
description: {
"en-US": `<p>The <code>flow</code>/<code>global</code> context inputs and the <code>env</code> input
now all include auto-complete suggestions based on the live state of your flows.</p>
`,
"ja": `<p><code>flow</code>/<code>global</code>コンテキストや<code>env</code>の入力を、現在のフローの状態をもとに自動補完で提案するようになりました。</p>
`,
"fr": `<p>Les entrées contextuelles <code>flow</code>/<code>global</code> et l'entrée <code>env</code>
incluent désormais des suggestions de saisie semi-automatique basées sur l'état actuel de vos flux.</p>
`,
}
},
{
title: {
"en-US": "Config node customisation in Subflows",
"ja": "サブフローでの設定ノードのカスタマイズ",
"fr": "Personnalisation du noeud de configuration dans les sous-flux"
},
image: 'images/nr4-sf-config.png',
description: {
"en-US": `<p>Subflows can now be customised to allow each instance to use a different
config node of a selected type.</p>
<p>For example, each instance of a subflow that connects to an MQTT Broker and does some post-processing
of the messages received can be pointed at a different broker.</p>
`,
"ja": `<p>サブフローをカスタマイズして、選択した型の異なる設定ノードを各インスタンスが使用できるようになりました。</p>
<p>例えば、MQTTブローカへ接続し、メッセージ受信と後処理を行うサブフローの各インスタンスに異なるブローカを指定することも可能です。</p>
`,
"fr": `<p>Les sous-flux peuvent désormais être personnalisés pour permettre à chaque instance d'utiliser un
noeud de configuration d'un type sélectionné.</p>
<p>Par exemple, chaque instance d'un sous-flux qui se connecte à un courtier MQTT et effectue un post-traitement
des messages reçus peut être pointée vers un autre courtier.</p>
`
}
},
{
title: {
"en-US": "Remembering palette state",
"ja": "パレットの状態を維持",
"fr": "Mémorisation de l'état de la palette"
},
description: {
"en-US": `<p>The palette now remembers what categories you have hidden between reloads - as well as any
filter you have applied.</p>`,
"ja": `<p>パレット上で非表示にしたカテゴリや適用したフィルタが、リロードしても記憶されるようになりました。</p>`,
"fr": `<p>La palette se souvient désormais des catégories que vous avez masquées entre les rechargements,
ainsi que le filtre que vous avez appliqué.</p>`
}
},
{
title: {
"en-US": "Plugins shown in the Palette Manager",
"ja": "パレット管理にプラグインを表示",
"fr": "Affichage des Plugins dans le gestionnaire de palettes"
},
image: 'images/nr4-plugins.png',
description: {
"en-US": `<p>The palette manager now shows any plugin modules you have installed, such as
<code>node-red-debugger</code>. Previously they would only be shown if the plugins include
nodes for the palette.</p>`,
"ja": `<p>パレットの管理に <code>node-red-debugger</code> の様なインストールしたプラグインが表示されます。以前はプラグインにパレット向けのノードが含まれている時のみ表示されていました。</p>`,
"fr": `<p>Le gestionnaire de palettes affiche désormais tous les plugins que vous avez installés,
tels que <code>node-red-debugger</code>. Auparavant, ils n'étaient affichés que s'ils contenaient
des noeuds pour la palette.</p>`
}
},
{
title: {
"en-US": "Node Updates",
"ja": "ノードの更新",
"fr": "Mises à jour des noeuds"
},
// image: "images/",
description: {
"en-US": `<p>The core nodes have received lots of minor fixes, documentation updates and
small enhancements. Check the full changelog in the Help sidebar for a full list.</p>
<ul>
<li>A fully RFC4180 compliant CSV mode</li>
<li>Customisable headers on the WebSocket node</li>
<li>Split node now can operate on any message property</li>
<li>and lots more...</li>
</ul>`,
"ja": `<p>コアノードには沢山の軽微な修正、ドキュメント更新、小さな機能拡張が入っています。全リストはヘルプサイドバーにある変更履歴を参照してください。</p>
<ul>
<li>RFC4180に完全に準拠したCSVモード</li>
<li>WebSocketードのカスタマイズ可能なヘッダ</li>
<li>Splitードは、メッセージプロパティで操作できるようになりました</li>
<li>他にも沢山あります...</li>
</ul>`,
"fr": `<p>Les noeuds principaux ont reçu de nombreux correctifs mineurs ainsi que des améliorations. La documentation a été mise à jour.
Consultez le journal des modifications dans la barre latérale d'aide pour une liste complète. Ci-dessous, les changements les plus importants :</p>
<ul>
<li>Un mode CSV entièrement conforme à la norme RFC4180</li>
<li>En-têtes personnalisables pour le noeud WebSocket</li>
<li>Le noeud Split peut désormais fonctionner sur n'importe quelle propriété de message</li>
<li>Et bien plus encore...</li>
</ul>`
}
}
// {
// title: {
// "en-US": "Node Updates",
// "ja": "ノードの更新",
// "fr": "Mises à jour des noeuds"
// },
// // image: "images/",
// description: {
// "en-US": `<p>The core nodes have received lots of minor fixes, documentation updates and
// small enhancements. Check the full changelog in the Help sidebar for a full list.</p>
// <ul>
// <li>A fully RFC4180 compliant CSV mode</li>
// <li>Customisable headers on the WebSocket node</li>
// <li>Split node now can operate on any message property</li>
// <li>and lots more...</li>
// </ul>`,
// "ja": `<p>コアノードには沢山の軽微な修正、ドキュメント更新、小さな機能拡張が入っています。全リストはヘルプサイドバーにある変更履歴を参照してください。</p>
// <ul>
// <li>RFC4180に完全に準拠したCSVモード</li>
// <li>WebSocketードのカスタマイズ可能なヘッダ</li>
// <li>Splitードは、メッセージプロパティで操作できるようになりました</li>
// <li>他にも沢山あります...</li>
// </ul>`,
// "fr": `<p>Les noeuds principaux ont reçu de nombreux correctifs mineurs ainsi que des améliorations. La documentation a été mise à jour.
// Consultez le journal des modifications dans la barre latérale d'aide pour une liste complète. Ci-dessous, les changements les plus importants :</p>
// <ul>
// <li>Un mode CSV entièrement conforme à la norme RFC4180</li>
// <li>En-têtes personnalisables pour le noeud WebSocket</li>
// <li>Le noeud Split peut désormais fonctionner sur n'importe quelle propriété de message</li>
// <li>Et bien plus encore...</li>
// </ul>`
// }
// }
]
}

View File

@@ -46,12 +46,12 @@
<div class="form-row inject-time-row hidden" id="inject-time-row-interval">
<span data-i18n="inject.every"></span>
<input id="inject-time-interval-count" class="inject-time-count" value="1"></input>
<input id="inject-time-interval-count" class="inject-time-count" value="1">
<select style="width:100px" id="inject-time-interval-units">
<option value="s" data-i18n="inject.seconds"></option>
<option value="m" data-i18n="inject.minutes"></option>
<option value="h" data-i18n="inject.hours"></option>
</select><br/>
</select><br>
</div>
<div class="form-row inject-time-row hidden" id="inject-time-row-interval-time">
@@ -68,46 +68,46 @@
<option value="20">20</option>
<option value="30">30</option>
<option value="0">60</option>
</select> <span data-i18n="inject.minutes"></span><br/>
</select> <span data-i18n="inject.minutes"></span><br>
<span data-i18n="inject.between"></span> <select id="inject-time-interval-time-start" class="inject-time-times"></select>
<span data-i18n="inject.and"></span> <select id="inject-time-interval-time-end" class="inject-time-times"></select><br/>
<span data-i18n="inject.and"></span> <select id="inject-time-interval-time-end" class="inject-time-times"></select><br>
<div id="inject-time-interval-time-days" class="inject-time-days" style="margin-top:5px">
<div style="display:inline-block; vertical-align:top; margin-right:5px;" data-i18n="inject.on">on</div>
<div style="display:inline-block;">
<div>
<label><input type='checkbox' checked value='1'/> <span data-i18n="inject.days.0"></span></label>
<label><input type='checkbox' checked value='2'/> <span data-i18n="inject.days.1"></span></label>
<label><input type='checkbox' checked value='3'/> <span data-i18n="inject.days.2"></span></label>
<label><input type='checkbox' checked value='1'> <span data-i18n="inject.days.0"></span></label>
<label><input type='checkbox' checked value='2'> <span data-i18n="inject.days.1"></span></label>
<label><input type='checkbox' checked value='3'> <span data-i18n="inject.days.2"></span></label>
</div>
<div>
<label><input type='checkbox' checked value='4'/> <span data-i18n="inject.days.3"></span></label>
<label><input type='checkbox' checked value='5'/> <span data-i18n="inject.days.4"></span></label>
<label><input type='checkbox' checked value='6'/> <span data-i18n="inject.days.5"></span></label>
<label><input type='checkbox' checked value='4'> <span data-i18n="inject.days.3"></span></label>
<label><input type='checkbox' checked value='5'> <span data-i18n="inject.days.4"></span></label>
<label><input type='checkbox' checked value='6'> <span data-i18n="inject.days.5"></span></label>
</div>
<div>
<label><input type='checkbox' checked value='0'/> <span data-i18n="inject.days.6"></span></label>
<label><input type='checkbox' checked value='0'> <span data-i18n="inject.days.6"></span></label>
</div>
</div>
</div>
</div>
<div class="form-row inject-time-row hidden" id="inject-time-row-time">
<span data-i18n="inject.at"></span> <input type="text" id="inject-time-time" value="12:00"></input><br/>
<span data-i18n="inject.at"></span> <input type="text" id="inject-time-time" value="12:00"><br>
<div id="inject-time-time-days" class="inject-time-days">
<div style="display:inline-block; vertical-align:top; margin-right:5px;" data-i18n="inject.on"></div>
<div style="display:inline-block;">
<div>
<label><input type='checkbox' checked value='1'/> <span data-i18n="inject.days.0"></span></label>
<label><input type='checkbox' checked value='2'/> <span data-i18n="inject.days.1"></span></label>
<label><input type='checkbox' checked value='3'/> <span data-i18n="inject.days.2"></span></label>
<label><input type='checkbox' checked value='1'> <span data-i18n="inject.days.0"></span></label>
<label><input type='checkbox' checked value='2'> <span data-i18n="inject.days.1"></span></label>
<label><input type='checkbox' checked value='3'> <span data-i18n="inject.days.2"></span></label>
</div>
<div>
<label><input type='checkbox' checked value='4'/> <span data-i18n="inject.days.3"></span></label>
<label><input type='checkbox' checked value='5'/> <span data-i18n="inject.days.4"></span></label>
<label><input type='checkbox' checked value='6'/> <span data-i18n="inject.days.5"></span></label>
<label><input type='checkbox' checked value='4'> <span data-i18n="inject.days.3"></span></label>
<label><input type='checkbox' checked value='5'> <span data-i18n="inject.days.4"></span></label>
<label><input type='checkbox' checked value='6'> <span data-i18n="inject.days.5"></span></label>
</div>
<div>
<label><input type='checkbox' checked value='0'/> <span data-i18n="inject.days.6"></span></label>
<label><input type='checkbox' checked value='0'> <span data-i18n="inject.days.6"></span></label>
</div>
</div>
</div>

View File

@@ -11,12 +11,13 @@
RED.nodes.registerType('global-config',{
category: 'config',
defaults: {
name: { value: "" },
env: { value: [] },
modules: { value: {} }
},
credentials: {
map: { type: "map" }
},
label: 'global-config',
oneditprepare: function() {
$('#node-input-edit-env-var').on('click', function(evt) {
RED.actions.invoke('core:show-user-settings', 'envvar')

View File

@@ -1,14 +1,22 @@
<script type="text/html" data-template-name="unknown">
<div class="form-tips"><span data-i18n="[html]unknown.tip"></span></div>
<div class="form-tips">
<span data-i18n="[html]unknown.tip"></span>
<p id="unknown-module-known">
<button id="unknown-manage-dependencies" class="red-ui-button">Manage dependencies</button>
</p>
</div>
</div>
</script>
<script type="text/javascript">
RED.nodes.registerType('unknown',{
RED.nodes.registerType('unknown', {
category: 'unknown',
color:"#fff0f0",
color:"#fff000",
defaults: {
name: {value:""}
name: {value:""},
modules: { value: [] }
},
inputs:1,
outputs:1,
@@ -18,6 +26,20 @@
},
labelStyle: function() {
return "node_label_unknown";
},
oneditprepare: function () {
const node = this
if (this.modules && this.modules.length > 0) {
$('#unknown-manage-dependencies').on('click', function () {
RED.actions.invoke('core:cancel-edit-tray')
RED.actions.invoke('core:manage-palette', {
view: 'install',
filter: '"' + node.modules.join('", "') + '"'
})
})
} else {
$('#unknown-module-known').hide()
}
}
});
</script>

View File

@@ -21,8 +21,8 @@
</div>
<div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="switch.label.property"></span></label>
<input type="text" id="node-input-property" style="width: calc(100% - 105px)"/>
<input type="hidden" id="node-input-outputs"/>
<input type="text" id="node-input-property" style="width: calc(100% - 105px)">
<input type="hidden" id="node-input-outputs">
</div>
<div class="form-row node-input-rule-container-row">
<ol id="node-input-rule-container"></ol>
@@ -35,7 +35,7 @@
</div>
<div class="form-row">
<input type="checkbox" id="node-input-repair" style="display: inline-block; width: auto; vertical-align: top;">
<label style="width: auto;" for="node-input-repair"><span data-i18n="switch.label.repair"></span></label></input>
<label style="width: auto;" for="node-input-repair"><span data-i18n="switch.label.repair"></span></label>
</div>
</script>

View File

@@ -2,7 +2,7 @@
<script type="text/html" data-template-name="range">
<div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label>
<input type="text" id="node-input-property" style="width:calc(70% - 1px)"/>
<input type="text" id="node-input-property" style="width:calc(70% - 1px)">
</div>
<div class="form-row">
<label for="node-input-action"><i class="fa fa-dot-circle-o"></i> <span data-i18n="range.label.action"></span></label>
@@ -13,23 +13,23 @@
<option value="drop" data-i18n="range.scale.drop"></option>
</select>
</div>
<br/>
<br>
<div class="form-row"><i class="fa fa-sign-in"></i> <span data-i18n="range.label.inputrange"></span>:</div>
<div class="form-row"><label></label>
<span data-i18n="range.label.from"></span>: <input type="text" id="node-input-minin" data-i18n="[placeholder]range.placeholder.min" style="width:100px;"/>
&nbsp;&nbsp;<span data-i18n="range.label.to"></span>: <input type="text" id="node-input-maxin" data-i18n="[placeholder]range.placeholder.maxin" style="width:100px;"/>
<span data-i18n="range.label.from"></span>: <input type="text" id="node-input-minin" data-i18n="[placeholder]range.placeholder.min" style="width:100px;">
&nbsp;&nbsp;<span data-i18n="range.label.to"></span>: <input type="text" id="node-input-maxin" data-i18n="[placeholder]range.placeholder.maxin" style="width:100px;">
</div>
<div class="form-row"><i class="fa fa-sign-out"></i> <span data-i18n="range.label.resultrange"></span>:</div>
<div class="form-row"><label></label>
<span data-i18n="range.label.from"></span>: <input type="text" id="node-input-minout" data-i18n="[placeholder]range.placeholder.min" style="width:100px;"/>
&nbsp;&nbsp;<span data-i18n="range.label.to"></span>: <input type="text" id="node-input-maxout" data-i18n="[placeholder]range.placeholder.maxout" style="width:100px;"/>
<span data-i18n="range.label.from"></span>: <input type="text" id="node-input-minout" data-i18n="[placeholder]range.placeholder.min" style="width:100px;">
&nbsp;&nbsp;<span data-i18n="range.label.to"></span>: <input type="text" id="node-input-maxout" data-i18n="[placeholder]range.placeholder.maxout" style="width:100px;">
</div>
<br/>
<br>
<div class="form-row"><label></label>
<input type="checkbox" id="node-input-round" style="display: inline-block; width: auto; vertical-align: top;">
<label style="width: auto;" for="node-input-round"><span data-i18n="range.label.roundresult"></span></label></input>
<label style="width: auto;" for="node-input-round"><span data-i18n="range.label.roundresult"></span></label>
</div>
<br/>
<br>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">

View File

@@ -63,7 +63,7 @@
<li><span data-i18n="trigger.label.resetPayload"></span> <input type="text" id="node-input-reset" style="width:150px" data-i18n="[placeholder]trigger.label.resetprompt"></li>
</ul>
</div>
<br/>
<br>
<div class="form-row">
<label data-i18n="trigger.for" for="node-input-bytopic"></label>
<select id="node-input-bytopic" style="width:120px;">
@@ -71,12 +71,12 @@
<option value="topic" data-i18n="trigger.bytopics"></option>
</select>
<span class="form-row" id="node-stream-topic">
<input type="text" id="node-input-topic" style="width:46%;"/>
<input type="text" id="node-input-topic" style="width:46%;">
</span>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></input>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
<input type="hidden" id="node-input-outputs" value="1">
</div>
</script>

View File

@@ -25,7 +25,7 @@
</div>
<div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="node-red:common.label.property"></span></label>
<input type="text" id="node-input-property" style="width:70%;"/>
<input type="text" id="node-input-property" style="width:70%;">
</div>
<div class="form-row" style="margin-bottom: 0px;">
<label> </label>
@@ -34,7 +34,7 @@
</div>
<div class="form-row">
<label> </label>
<input type="text" id="node-input-topi" style="width:70%;"/>
<input type="text" id="node-input-topi" style="width:70%;">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="rbe.label.name"></span></label>

View File

@@ -29,7 +29,7 @@
<div class="form-row">
<label for="node-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
<input id="node-input-url" type="text" placeholder="http://">
<input id="node-input-url" type="text" placeholder="https://">
</div>
<div class="form-row node-input-paytoqs-row">

View File

@@ -50,7 +50,7 @@
</div>
<div id="node-row-newline" class="form-row hidden" style="padding-left:110px;">
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional"><br/>
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional"><br>
<input type="checkbox" id="node-input-trim" style="display:inline-block; width:auto; vertical-align:top;"> <span data-i18n="tcpin.label.reattach"></span>
</div>
@@ -317,7 +317,7 @@
<span id="node-units"></span>
</div>
<div id="node-row-newline" class="form-row hidden" style="padding-left:162px;">
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional"><br/>
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional"><br>
<input type="checkbox" id="node-input-trim" style="display:inline-block; width:auto; vertical-align:top;"> <span data-i18n="tcpin.label.reattach"></span>
</div>
<div class="form-row">

View File

@@ -35,21 +35,21 @@
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<hr align="middle"/>
<hr align="middle">
<div class="form-row">
<label style="width:100%;"><span data-i18n="csv.label.c2o"></span></label>
</div>
<div class="form-row" style="padding-left:20px;">
<label><i class="fa fa-sign-in"></i> <span data-i18n="csv.label.input"></span></label>
<span data-i18n="csv.label.skip-s"></span>&nbsp;<input type="text" id="node-input-skip" style="width:40px; height:25px;"/>&nbsp;<span data-i18n="csv.label.skip-e"></span><br/>
<span data-i18n="csv.label.skip-s"></span>&nbsp;<input type="text" id="node-input-skip" style="width:40px; height:25px;">&nbsp;<span data-i18n="csv.label.skip-e"></span><br>
<label>&nbsp;</label>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-hdrin"><label style="width:auto; margin-top:7px;" for="node-input-hdrin"><span data-i18n="csv.label.firstrow"></span></label><br/>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-hdrin"><label style="width:auto; margin-top:7px;" for="node-input-hdrin"><span data-i18n="csv.label.firstrow"></span></label><br>
<label>&nbsp;</label>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-strings"><label style="width:auto; margin-top:7px;" for="node-input-strings"><span data-i18n="csv.label.usestrings"></span></label><br/>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-strings"><label style="width:auto; margin-top:7px;" for="node-input-strings"><span data-i18n="csv.label.usestrings"></span></label><br>
<label>&nbsp;</label>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-include_empty_strings"><label style="width:auto; margin-top:7px;" for="node-input-include_empty_strings"><span data-i18n="csv.label.include_empty_strings"></span></label><br/>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-include_empty_strings"><label style="width:auto; margin-top:7px;" for="node-input-include_empty_strings"><span data-i18n="csv.label.include_empty_strings"></span></label><br>
<label>&nbsp;</label>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-include_null_values"><label style="width:auto; margin-top:7px;" for="node-input-include_null_values"><span data-i18n="csv.label.include_null_values"></span></label><br/>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-include_null_values"><label style="width:auto; margin-top:7px;" for="node-input-include_null_values"><span data-i18n="csv.label.include_null_values"></span></label><br>
</div>
<div class="form-row" style="padding-left:20px;">
<label><i class="fa fa-sign-out"></i> <span data-i18n="csv.label.output"></span></label>

View File

@@ -33,7 +33,7 @@
<label for="node-input-chr" style="width: 230px;"><i class="fa fa-tag"></i> <span data-i18n="html.label.prefix"></span></label>
<input type="text" id="node-input-chr" style="text-align:center; width: 40px;" placeholder="_">
</div>
<br/>
<br>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" style="width:70%" data-i18n="[placeholder]common.label.name">

View File

@@ -10,13 +10,13 @@
</div>
<div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="json.label.property"></span></label>
<input type="text" id="node-input-property" style="width:70%;"/>
<input type="text" id="node-input-property" style="width:70%;">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<hr align="middle"/>
<hr align="middle">
<div class="form-row node-json-to-json-options">
<label style="width:100%;"><span data-i18n="json.label.o2j"></span></label>
</div>

View File

@@ -2,13 +2,13 @@
<script type="text/html" data-template-name="xml">
<div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label>
<input type="text" id="node-input-property" style="width:70%;"/>
<input type="text" id="node-input-property" style="width:70%;">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<hr align="middle"/>
<hr align="middle">
<div class="form-row">
<label style="width:100%;"><span data-i18n="xml.label.x2o"></span></label>
</div>

View File

@@ -2,7 +2,7 @@
<script type="text/html" data-template-name="yaml">
<div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label>
<input type="text" id="node-input-property" style="width:70%;"/>
<input type="text" id="node-input-property" style="width:70%;">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>

View File

@@ -21,7 +21,7 @@
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
</div>
<div class="form-row">
<label for="node-input-property"><i class="fa fa-forward"></i> <span data-i18n="split.splitThe"></span></label>
<label for="node-input-property" style="padding-left:10px; margin-right:-10px;" data-i18n="split.splitThe"></label>
<input type="text" id="node-input-property" style="width:70%;"/>
</div>
<div class="form-row"><span data-i18n="[html]split.strBuff"></span></div>
@@ -202,7 +202,7 @@
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-reduceRight" style="display:inline-block; width:auto; vertical-align:top; margin-left:10px;">
<label for="node-input-reduceRight" style="width:70%;" data-i18n="join.reduce.right" style="margin-left:10px;"/>
<label for="node-input-reduceRight" data-i18n="join.reduce.right" style="width:70%; margin-left:10px;"></label>
</div>
</div>
<div class="form-tips form-tips-auto hide" data-i18n="[html]join.tip"></div>

View File

@@ -1017,7 +1017,7 @@
"objectSend": "Send a message for each key/value pair",
"strBuff": "<b>String</b> / <b>Buffer</b>",
"array": "<b>Array</b>",
"splitThe": "Split the",
"splitThe": "Split property",
"splitUsing": "Split using",
"splitLength": "Fixed length of",
"stream": "Handle as a stream of messages",

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/nodes",
"version": "4.0.9",
"version": "4.1.0-beta.0",
"license": "Apache-2.0",
"repository": {
"type": "git",
@@ -36,7 +36,7 @@
"js-yaml": "4.1.0",
"media-typer": "1.1.0",
"mqtt": "5.11.0",
"multer": "1.4.5-lts.2",
"multer": "2.0.0",
"mustache": "4.2.0",
"node-watch": "0.7.4",
"on-headers": "1.0.2",

View File

@@ -406,6 +406,7 @@ async function loadPlugin(plugin) {
}
try {
var r = require(plugin.file);
r = r.__esModule ? r.default : r
if (typeof r === "function") {
var red = registryUtil.createNodeApi(plugin);

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/registry",
"version": "4.0.9",
"version": "4.1.0-beta.0",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,7 +16,7 @@
}
],
"dependencies": {
"@node-red/util": "4.0.9",
"@node-red/util": "4.1.0-beta.0",
"clone": "2.1.2",
"fs-extra": "11.3.0",
"semver": "7.7.1",

View File

@@ -161,6 +161,8 @@ var api = module.exports = {
safeSettings.diagnostics.ui = false; // cannot have UI without endpoint
}
safeSettings.telemetryEnabled = runtime.telemetry.isEnabled()
safeSettings.runtimeState = {
//unless runtimeState.ui and runtimeState.enabled are explicitly true, they will default to false.
enabled: !!runtime.settings.runtimeState && runtime.settings.runtimeState.enabled === true,
@@ -213,7 +215,19 @@ var api = module.exports = {
}
var currentSettings = runtime.settings.getUserSettings(username)||{};
currentSettings = extend(currentSettings, opts.settings);
try {
if (currentSettings.hasOwnProperty("telemetryEnabled")) {
// This is a global setting that is being set by the user. It should
// not be stored per-user as it applies to the whole runtime.
const telemetryEnabled = currentSettings.telemetryEnabled;
delete currentSettings.telemetryEnabled;
if (telemetryEnabled) {
runtime.telemetry.enable()
} else {
runtime.telemetry.disable()
}
}
return runtime.settings.setUserSettings(username, currentSettings).then(function() {
runtime.log.audit({event: "settings.update",username:username}, opts.req);
return;

View File

@@ -23,6 +23,7 @@ var library = require("./library");
var plugins = require("./plugins");
var settings = require("./settings");
const multiplayer = require("./multiplayer");
const telemetry = require("./telemetry");
var express = require("express");
var path = require('path');
@@ -135,6 +136,7 @@ function start() {
return i18n.registerMessageCatalog("runtime",path.resolve(path.join(__dirname,"..","locales")),"runtime.json")
.then(function() { return storage.init(runtime)})
.then(function() { return settings.load(storage)})
.then(function() { return telemetry.init(runtime)})
.then(function() { return library.init(runtime)})
.then(function() { return multiplayer.init(runtime)})
.then(function() {
@@ -337,6 +339,7 @@ var runtime = {
library: library,
exec: exec,
util: util,
telemetry: telemetry,
get adminApi() { return adminApi },
get adminApp() { return adminApp },
get nodeApp() { return nodeApp },

View File

@@ -51,6 +51,8 @@ function runGitCommand(args,cwd,env,emit) {
err.code = "git_auth_failed";
} else if(/Authentication failed/i.test(stderr)) {
err.code = "git_auth_failed";
} else if (/The requested URL returned error: 403/i.test(stderr)) {
err.code = "git_auth_failed";
} else if (/commit your changes or stash/i.test(stderr)) {
err.code = "git_local_overwrite";
} else if (/CONFLICT/.test(err.stdout)) {

View File

@@ -0,0 +1,208 @@
const path = require('path')
const fs = require('fs/promises')
const semver = require('semver')
const cronosjs = require('cronosjs')
const METRICS_DIR = path.join(__dirname, 'metrics')
const INITIAL_PING_DELAY = 1000 * 60 * 30 // 30 minutes from startup
let runtime
let scheduleTask
async function gather () {
let metricFiles = await fs.readdir(METRICS_DIR)
metricFiles = metricFiles.filter(name => /^\d+-.*\.js$/.test(name))
metricFiles.sort()
const metrics = {}
for (let i = 0, l = metricFiles.length; i < l; i++) {
const metricModule = require(path.join(METRICS_DIR, metricFiles[i]))
let result = metricModule(runtime)
if (!!result && (typeof result === 'object' || typeof result === 'function') && typeof result.then === 'function') {
result = await result
}
const keys = Object.keys(result)
keys.forEach(key => {
const keyParts = key.split('.')
let p = metrics
keyParts.forEach((part, index) => {
if (index < keyParts.length - 1) {
if (!p[part]) {
p[part] = {}
}
p = p[part]
} else {
p[part] = result[key]
}
})
})
}
return metrics
}
async function report () {
if (!isTelemetryEnabled()) {
return
}
// If enabled, gather metrics
const metrics = await gather()
console.log(JSON.stringify(metrics, null, 2))
// Post metrics to endpoint - handle any error silently
const { got } = await import('got')
runtime.log.debug('Sending telemetry')
const response = await got.post('https://telemetry.nodered.org/ping', {
json: metrics,
responseType: 'json',
headers: {
'User-Agent': `Node-RED/${runtime.settings.version}`
}
}).json().catch(err => {
// swallow errors
runtime.log.debug('Failed to send telemetry: ' + err.toString())
})
// Example response:
// { 'node-red': { latest: '4.0.9', next: '4.1.0-beta.1.9' } }
runtime.log.debug(`Telemetry response: ${JSON.stringify(response)}`)
// Get response from endpoint
if (response?.['node-red']) {
const currentVersion = metrics.env['node-red']
if (semver.valid(currentVersion)) {
const latest = response['node-red'].latest
const next = response['node-red'].next
let updatePayload
if (semver.lt(currentVersion, latest)) {
// Case one: current < latest
runtime.log.info(`A new version of Node-RED is available: ${latest}`)
updatePayload = { version: latest }
} else if (semver.gt(currentVersion, latest) && semver.lt(currentVersion, next)) {
// Case two: current > latest && current < next
runtime.log.info(`A new beta version of Node-RED is available: ${next}`)
updatePayload = { version: next }
}
if (updatePayload && isUpdateNotificationEnabled()) {
runtime.events.emit("runtime-event",{id:"update-available", payload: updatePayload, retain: true});
}
}
}
}
function isTelemetryEnabled () {
// If NODE_RED_DISABLE_TELEMETRY was set, or --no-telemetry was specified,
// the settings object will have been updated to disable telemetry explicitly
// If there are no telemetry settings then the user has not had a chance
// to opt out yet - so keep it disabled until they do
let telemetrySettings
try {
telemetrySettings = runtime.settings.get('telemetry')
} catch (err) {
// Settings not available
}
let runtimeTelemetryEnabled
try {
runtimeTelemetryEnabled = runtime.settings.get('telemetryEnabled')
} catch (err) {
// Settings not available
}
if (telemetrySettings === undefined && runtimeTelemetryEnabled === undefined) {
// No telemetry settings - so keep it disabled
return undefined
}
// User has made a choice; defer to that
if (runtimeTelemetryEnabled !== undefined) {
return runtimeTelemetryEnabled
}
// If there are telemetry settings, use what it says
if (telemetrySettings && telemetrySettings.enabled !== undefined) {
return telemetrySettings.enabled
}
// At this point, we have no sign the user has consented to telemetry, so
// keep disabled - but return undefined as a false-like value to distinguish
// it from the explicit disable above
return undefined
}
function isUpdateNotificationEnabled () {
const telemetrySettings = runtime.settings.get('telemetry') || {}
return telemetrySettings.updateNotification !== false
}
/**
* Start the telemetry schedule
*/
function startTelemetry () {
if (scheduleTask) {
// Already scheduled - nothing left to do
return
}
const pingTime = new Date(Date.now() + INITIAL_PING_DELAY)
const pingMinutes = pingTime.getMinutes()
const pingHours = pingTime.getHours()
const pingSchedule = `${pingMinutes} ${pingHours} * * *`
runtime.log.debug(`Telemetry enabled. Schedule: ${pingSchedule}`)
scheduleTask = cronosjs.scheduleTask(pingSchedule, () => {
report()
})
}
function stopTelemetry () {
if (scheduleTask) {
runtime.log.debug(`Telemetry disabled`)
scheduleTask.stop()
scheduleTask = null
}
}
module.exports = {
init: (_runtime) => {
runtime = _runtime
if (isTelemetryEnabled()) {
startTelemetry()
}
},
/**
* Enable telemetry via user opt-in in the editor
*/
enable: () => {
if (runtime.settings.available()) {
runtime.settings.set('telemetryEnabled', true)
}
startTelemetry()
},
/**
* Disable telemetry via user opt-in in the editor
*/
disable: () => {
if (runtime.settings.available()) {
runtime.settings.set('telemetryEnabled', false)
}
stopTelemetry()
},
/**
* Get telemetry enabled status
* @returns {boolean} true if telemetry is enabled, false if disabled, undefined if not set
*/
isEnabled: isTelemetryEnabled,
stop: () => {
if (scheduleTask) {
scheduleTask.stop()
scheduleTask = null
}
}
}

View File

@@ -0,0 +1,5 @@
module.exports = (runtime) => {
return {
instanceId: runtime.settings.get('instanceId')
}
}

View File

@@ -0,0 +1,9 @@
const os = require('os')
module.exports = (_) => {
return {
'os.type': os.type(),
'os.release': os.release(),
'os.arch': os.arch()
}
}

View File

@@ -0,0 +1,8 @@
const process = require('process')
module.exports = (runtime) => {
return {
'env.nodejs': process.version.replace(/^v/, ''),
'env.node-red': runtime.settings.version
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/runtime",
"version": "4.0.9",
"version": "4.1.0-beta.0",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,13 +16,15 @@
}
],
"dependencies": {
"@node-red/registry": "4.0.9",
"@node-red/util": "4.0.9",
"@node-red/registry": "4.1.0-beta.0",
"@node-red/util": "4.1.0-beta.0",
"async-mutex": "0.5.0",
"clone": "2.1.2",
"cronosjs": "1.7.1",
"express": "4.21.2",
"fs-extra": "11.3.0",
"json-stringify-safe": "5.0.1",
"rfdc": "^1.3.1"
"rfdc": "^1.3.1",
"semver": "7.7.1"
}
}

View File

@@ -23,6 +23,7 @@ var util = require("util");
var EventEmitter = require("events").EventEmitter;
var i18n = require("./i18n");
const chalk = require("chalk");
var levels = {
off: 1,
@@ -47,6 +48,17 @@ var levelNames = {
99: "metric"
};
var levelColours = {
10: 'red',
20: 'red',
30: 'yellow',
40: '',
50: 'cyan',
60: 'gray',
98: '',
99: ''
};
var logHandlers = [];
var verbose;
@@ -75,28 +87,32 @@ LogHandler.prototype.shouldReportMessage = function(msglevel) {
msglevel <= this.logLevel;
}
// Older versions of Node-RED used the deprecated util.log function.
// With Node.js 22, use of that function causes warnings. So here we
// are replicating the same format output to ensure we don't break any
// log parsing that happens in the real world.
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const utilLog = function (msg) {
const utilLog = function (msg, level) {
const d = new Date();
const time = [
d.getHours().toString().padStart(2, '0'),
d.getMinutes().toString().padStart(2, '0'),
d.getSeconds().toString().padStart(2, '0')
].join(':');
console.log(`${d.getDate()} ${months[d.getMonth()]} ${time} - ${msg}`)
const logLine = `${d.getDate()} ${months[d.getMonth()]} ${time} - ${msg}`
if (levelColours[level]) {
console.log(chalk[levelColours[level]](logLine))
} else {
console.log(logLine)
}
}
var consoleLogger = function(msg) {
if (msg.level == log.METRIC || msg.level == log.AUDIT) {
utilLog("["+levelNames[msg.level]+"] "+JSON.stringify(msg));
utilLog("["+levelNames[msg.level]+"] "+JSON.stringify(msg), msg.level);
} else {
if (verbose && msg.msg && msg.msg.stack) {
utilLog("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+msg.msg.stack);
utilLog("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+msg.msg.stack, msg.level);
} else {
var message = msg.msg;
try {
@@ -107,7 +123,7 @@ var consoleLogger = function(msg) {
message = 'Exception trying to log: '+util.inspect(message);
}
utilLog("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+message);
utilLog("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+message, msg.level);
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/util",
"version": "4.0.9",
"version": "4.1.0-beta.0",
"license": "Apache-2.0",
"repository": {
"type": "git",
@@ -15,6 +15,7 @@
}
],
"dependencies": {
"chalk": "^4.1.2",
"fs-extra": "11.3.0",
"i18next": "24.2.3",
"json-stringify-safe": "5.0.1",

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "4.0.9",
"version": "4.1.0-beta.0",
"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.9",
"@node-red/runtime": "4.0.9",
"@node-red/util": "4.0.9",
"@node-red/nodes": "4.0.9",
"@node-red/editor-api": "4.1.0-beta.0",
"@node-red/runtime": "4.1.0-beta.0",
"@node-red/util": "4.1.0-beta.0",
"@node-red/nodes": "4.1.0-beta.0",
"basic-auth": "2.0.1",
"bcryptjs": "3.0.2",
"cors": "2.8.5",

View File

@@ -63,7 +63,8 @@ var knownOpts = {
"verbose": Boolean,
"safe": Boolean,
"version": Boolean,
"define": [String, Array]
"define": [String, Array],
"no-telemetry": Boolean
};
var shortHands = {
"?":["--help"],
@@ -97,6 +98,7 @@ if (parsedArgs.help) {
console.log(" --safe enable safe mode");
console.log(" -D, --define X=Y overwrite value in settings file");
console.log(" --version show version information");
console.log(" --no-telemetry do not share usage data with the Node-RED project");
console.log(" -?, --help show this help");
console.log(" admin <command> run an admin command");
console.log("");
@@ -222,6 +224,10 @@ if (process.env.NODE_RED_ENABLE_TOURS) {
settings.editorTheme.tours = !/^false$/i.test(process.env.NODE_RED_ENABLE_TOURS);
}
if (parsedArgs.telemetry === false || process.env.NODE_RED_DISABLE_TELEMETRY) {
settings.telemetry = settings.telemetry || {};
settings.telemetry.enabled = false;
}
var defaultServerSettings = {
"x-powered-by": false

View File

@@ -273,6 +273,7 @@ module.exports = {
* Runtime Settings
* - lang
* - runtimeState
* - telemetry
* - diagnostics
* - logging
* - contextStorage
@@ -311,6 +312,22 @@ module.exports = {
/** show or hide runtime stop/start options in the node-red editor. Must be set to `false` to hide */
ui: false,
},
telemetry: {
/**
* By default, telemetry is disabled until the user provides consent the first
* time they open the editor.
*
* The following property can be uncommented and set to true/false to enable/disable
* telemetry without seeking further consent in the editor.
* The user can override this setting via the user settings dialog within the editor
*/
// enabled: true,
/**
* If telemetry is enabled, the editor will notify the user if a new version of Node-RED
* is available. Set the following property to false to disable this notification.
*/
// updateNotification: true
},
/** Configure the logging output */
logging: {
/** Only console logging is currently supported */