diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js index 89337e7c9e..655178aa5b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -39,6 +39,14 @@ RED.palette = (function() { let filterRefreshTimeout + // Catalog search variables + let catalogues = []; + let loadedCatalogs = []; + let catalogLoadedList = []; + let catalogLoadedIndex = {}; + let catalogSearchTimeout; + let availableNodesContainer; + function createCategory(originalCategory,rootCategory,category,ns) { if ($("#red-ui-palette-base-category-"+rootCategory).length === 0) { createCategoryContainer(originalCategory,rootCategory, ns+":palette.label."+rootCategory); @@ -603,6 +611,175 @@ RED.palette = (function() { } } } + + // Trigger catalog search with debouncing + clearTimeout(catalogSearchTimeout); + if (val && val.trim() !== "") { + catalogSearchTimeout = setTimeout(function() { + searchCatalog(val); + }, 300); + } else { + hideAvailableNodes(); + } + } + + // Load node catalogs for search + function loadNodeCatalogs(done) { + catalogLoadedList = []; + catalogLoadedIndex = {}; + loadedCatalogs.length = 0; + let handled = 0; + const catalogueCount = catalogues.length; + + 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) { + loadedCatalogs.sort((a, b) => a.index - b.index); + if (done) { + done(); + } + } + }); + } + } + + function handleCatalogResponse(catalog, index, v) { + if (v.modules) { + v.modules.forEach(function(m) { + if (RED.utils.checkModuleAllowed(m.id, m.version, null, null)) { + catalogLoadedIndex[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; + catalogLoadedList.push(m); + } + }); + } + } + + // Search available nodes from catalog + function searchCatalog(searchTerm) { + if (!searchTerm || searchTerm.trim() === "") { + hideAvailableNodes(); + return; + } + + var re = new RegExp(searchTerm.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'i'); + var results = []; + + catalogLoadedList.forEach(function(m) { + if (re.test(m.index) || re.test(m.id)) { + // Check if not already installed + if (!RED.nodes.registry.getModule(m.id)) { + results.push(m); + } + } + }); + + if (results.length > 0) { + showAvailableNodes(results.slice(0, 10)); // Limit to first 10 results + } else { + hideAvailableNodes(); + } + } + + function showAvailableNodes(modules) { + if (!availableNodesContainer) { + createAvailableNodesContainer(); + } + + availableNodesContainer.empty(); + availableNodesContainer.show(); + + modules.forEach(function(m) { + var nodeDiv = $('
') + .appendTo(availableNodesContainer); + + var labelDiv = $('') + .text(m.id.replace('node-red-contrib-', '').replace('node-red-node-', '')) + .appendTo(nodeDiv); + + var installBtn = $('') + .appendTo(nodeDiv) + .on('click', function(e) { + e.stopPropagation(); + e.preventDefault(); + installNodeModule(m.id); + }); + + if (m.description) { + nodeDiv.attr('title', m.description); + } + }); + } + + function hideAvailableNodes() { + if (availableNodesContainer) { + availableNodesContainer.hide(); + } + } + + function createAvailableNodesContainer() { + availableNodesContainer = $('