mirror of
https://github.com/node-red/node-red.git
synced 2025-12-27 23:34:38 +01:00
Merge branch 'dev' into major-version-and-link
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -128,10 +128,25 @@ RED.utils = (function() {
|
||||
function formatString(str) {
|
||||
return str.replace(/\r?\n/g,"↵").replace(/\t/g,"→");
|
||||
}
|
||||
|
||||
function sanitize(m) {
|
||||
return m.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user