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

@@ -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;