From 34a55b5a55bb340375ba3e6bd9ca155594b42a7b Mon Sep 17 00:00:00 2001 From: Chuck Date: Mon, 16 Feb 2026 09:56:00 -0500 Subject: [PATCH 1/7] feat(store): add sorting, filtering, and fix Update All button Add client-side sorting and filtering to the Plugin Store: - Sort by A-Z, Z-A, Verified First, Recently Updated, Category - Filter by verified, new, installed status, author, and tags - Installed/Update Available badges on store cards - Active filter count badge with clear-all button - Sort preference persisted to localStorage Fix three bugs causing button unresponsiveness: - pluginsInitialized never reset on HTMX tab navigation (root cause of Update All silently doing nothing on second visit) - htmx:afterSwap condition too broad (fired on unrelated swaps) - data-running guard tied to DOM element replaced by cloneNode Co-Authored-By: Claude Opus 4.6 --- web_interface/static/v3/app.css | 36 ++ web_interface/static/v3/plugins_manager.js | 360 ++++++++++++++++-- .../templates/v3/partials/plugins.html | 62 +++ 3 files changed, 425 insertions(+), 33 deletions(-) diff --git a/web_interface/static/v3/app.css b/web_interface/static/v3/app.css index 7bbedd37e..5ac1446d1 100644 --- a/web_interface/static/v3/app.css +++ b/web_interface/static/v3/app.css @@ -83,6 +83,9 @@ [data-theme="dark"] .hover\:bg-gray-200:hover { background-color: #4b5563; } [data-theme="dark"] .hover\:text-gray-700:hover { color: #e5e7eb; } [data-theme="dark"] .hover\:border-gray-300:hover { border-color: #6b7280; } +[data-theme="dark"] .bg-red-100 { background-color: #450a0a; } +[data-theme="dark"] .text-red-700 { color: #fca5a5; } +[data-theme="dark"] .hover\:bg-red-200:hover { background-color: #7f1d1d; } /* Base styles */ * { @@ -663,6 +666,39 @@ button.bg-white { color: var(--color-purple-text); } +/* Filter Pill Toggle States */ +.filter-pill { + cursor: pointer; + user-select: none; + transition: all 0.15s ease; +} + +.filter-pill[data-active="true"] { + background-color: var(--color-info-bg); + border-color: var(--color-info); + color: var(--color-info); + font-weight: 600; +} + +.filter-pill[data-active="true"]:hover { + opacity: 0.85; +} + +/* Tag Filter Pills */ +.tag-filter-pill { + cursor: pointer; + user-select: none; + transition: all 0.15s ease; +} + +.tag-filter-pill[data-active="true"] { + background-color: var(--color-info-bg); + border-color: var(--color-info); + color: var(--color-info); + font-weight: 600; + box-shadow: 0 0 0 1px var(--color-info); +} + /* Section Headers with Subtle Gradients */ .section-header { background: linear-gradient(135deg, rgb(255 255 255 / 90%) 0%, rgb(249 250 251 / 90%) 100%); diff --git a/web_interface/static/v3/plugins_manager.js b/web_interface/static/v3/plugins_manager.js index c5233fed7..ab2ec5af0 100644 --- a/web_interface/static/v3/plugins_manager.js +++ b/web_interface/static/v3/plugins_manager.js @@ -863,6 +863,40 @@ window.currentPluginConfig = null; let currentOnDemandPluginId = null; let hasLoadedOnDemandStatus = false; + // Store filter/sort state + const storeFilterState = { + sort: localStorage.getItem('storeSort') || 'a-z', + filterVerified: false, + filterNew: false, + filterInstalled: null, // null = all, true = installed only, false = not installed only + filterAuthors: [], + filterTags: [], + + persist() { + localStorage.setItem('storeSort', this.sort); + }, + + reset() { + this.sort = 'a-z'; + this.filterVerified = false; + this.filterNew = false; + this.filterInstalled = null; + this.filterAuthors = []; + this.filterTags = []; + this.persist(); + }, + + activeCount() { + let count = 0; + if (this.filterVerified) count++; + if (this.filterNew) count++; + if (this.filterInstalled !== null) count++; + count += this.filterAuthors.length; + count += this.filterTags.length; + return count; + } + }; + // Shared on-demand status store (mirrors Alpine store when available) window.__onDemandStore = window.__onDemandStore || { loading: true, @@ -1105,13 +1139,15 @@ function initializePluginPageWhenReady() { document.body.addEventListener('htmx:afterSwap', function(event) { const target = event.detail.target; // Check if plugins content was swapped in - if (target.id === 'plugins-content' || - target.querySelector('#installed-plugins-grid') || - document.getElementById('installed-plugins-grid')) { + if (target.id === 'plugins-content' || + target.querySelector('[data-plugins-loaded]')) { console.log('HTMX swap detected for plugins, initializing...'); - // Reset initialization flag to allow re-initialization after HTMX swap + // Reset initialization flags to allow re-initialization after HTMX swap window.pluginManager.initialized = false; window.pluginManager.initializing = false; + pluginsInitialized = false; + pluginLoadCache.data = null; + pluginLoadCache.promise = null; initTimer = setTimeout(attemptInit, 100); } }, { once: false }); // Allow multiple swaps @@ -1172,7 +1208,10 @@ function initializePlugins() { categorySelect._listenerSetup = true; categorySelect.addEventListener('change', searchPluginStore); } - + + // Setup store sort/filter controls + setupStoreFilterListeners(); + // Setup GitHub installation handlers console.log('[initializePlugins] About to call setupGitHubInstallHandlers...'); if (typeof setupGitHubInstallHandlers === 'function') { @@ -1682,6 +1721,8 @@ function startOnDemandStatusPolling() { window.loadOnDemandStatus = loadOnDemandStatus; +let updateAllRunning = false; + async function runUpdateAllPlugins() { console.log('[runUpdateAllPlugins] Button clicked, checking for updates...'); const button = document.getElementById('update-all-plugins-btn'); @@ -1691,7 +1732,7 @@ async function runUpdateAllPlugins() { return; } - if (button.dataset.running === 'true') { + if (updateAllRunning) { return; } @@ -1702,7 +1743,7 @@ async function runUpdateAllPlugins() { } const originalContent = button.innerHTML; - button.dataset.running = 'true'; + updateAllRunning = true; button.disabled = true; button.classList.add('opacity-60', 'cursor-wait'); @@ -1712,7 +1753,11 @@ async function runUpdateAllPlugins() { for (let i = 0; i < plugins.length; i++) { const plugin = plugins[i]; const pluginId = plugin.id; - button.innerHTML = `Updating ${i + 1}/${plugins.length}...`; + // Re-fetch button in case DOM was replaced by HTMX swap + const btn = document.getElementById('update-all-plugins-btn'); + if (btn) { + btn.innerHTML = `Updating ${i + 1}/${plugins.length}...`; + } try { const response = await fetch('/api/v3/plugins/update', { @@ -1752,10 +1797,13 @@ async function runUpdateAllPlugins() { console.error('Bulk plugin update failed:', error); showNotification('Failed to update all plugins: ' + error.message, 'error'); } finally { - button.innerHTML = originalContent; - button.disabled = false; - button.classList.remove('opacity-60', 'cursor-wait'); - button.dataset.running = 'false'; + updateAllRunning = false; + const btn = document.getElementById('update-all-plugins-btn'); + if (btn) { + btn.innerHTML = originalContent; + btn.disabled = false; + btn.classList.remove('opacity-60', 'cursor-wait'); + } } } @@ -5076,6 +5124,243 @@ function restartDisplay() { }); } +// --- Store Filter/Sort Functions --- + +function setupStoreFilterListeners() { + // Sort dropdown + const sortSelect = document.getElementById('store-sort'); + if (sortSelect && !sortSelect._listenerSetup) { + sortSelect._listenerSetup = true; + sortSelect.value = storeFilterState.sort; + sortSelect.addEventListener('change', () => { + storeFilterState.sort = sortSelect.value; + storeFilterState.persist(); + applyStoreFiltersAndSort(); + }); + } + + // Verified filter toggle + const verifiedBtn = document.getElementById('filter-verified'); + if (verifiedBtn && !verifiedBtn._listenerSetup) { + verifiedBtn._listenerSetup = true; + verifiedBtn.addEventListener('click', () => { + storeFilterState.filterVerified = !storeFilterState.filterVerified; + verifiedBtn.dataset.active = storeFilterState.filterVerified; + applyStoreFiltersAndSort(); + }); + } + + // New filter toggle + const newBtn = document.getElementById('filter-new'); + if (newBtn && !newBtn._listenerSetup) { + newBtn._listenerSetup = true; + newBtn.addEventListener('click', () => { + storeFilterState.filterNew = !storeFilterState.filterNew; + newBtn.dataset.active = storeFilterState.filterNew; + applyStoreFiltersAndSort(); + }); + } + + // Installed filter (cycles: All -> Installed -> Not Installed -> All) + const installedBtn = document.getElementById('filter-installed'); + if (installedBtn && !installedBtn._listenerSetup) { + installedBtn._listenerSetup = true; + installedBtn.addEventListener('click', () => { + const states = [null, true, false]; + const labels = ['All', 'Installed', 'Not Installed']; + const icons = ['fa-download', 'fa-check', 'fa-times']; + const current = states.indexOf(storeFilterState.filterInstalled); + const next = (current + 1) % states.length; + storeFilterState.filterInstalled = states[next]; + installedBtn.querySelector('span').textContent = labels[next]; + installedBtn.querySelector('i').className = `fas ${icons[next]} mr-1`; + installedBtn.dataset.active = String(states[next] !== null); + applyStoreFiltersAndSort(); + }); + } + + // Author dropdown + const authorSelect = document.getElementById('filter-author'); + if (authorSelect && !authorSelect._listenerSetup) { + authorSelect._listenerSetup = true; + authorSelect.addEventListener('change', () => { + storeFilterState.filterAuthors = authorSelect.value + ? [authorSelect.value] : []; + applyStoreFiltersAndSort(); + }); + } + + // Tag pills (event delegation on container) + const tagsPills = document.getElementById('filter-tags-pills'); + if (tagsPills && !tagsPills._listenerSetup) { + tagsPills._listenerSetup = true; + tagsPills.addEventListener('click', (e) => { + const pill = e.target.closest('.tag-filter-pill'); + if (!pill) return; + const tag = pill.dataset.tag; + const idx = storeFilterState.filterTags.indexOf(tag); + if (idx >= 0) { + storeFilterState.filterTags.splice(idx, 1); + pill.dataset.active = 'false'; + } else { + storeFilterState.filterTags.push(tag); + pill.dataset.active = 'true'; + } + applyStoreFiltersAndSort(); + }); + } + + // Clear filters button + const clearBtn = document.getElementById('clear-filters-btn'); + if (clearBtn && !clearBtn._listenerSetup) { + clearBtn._listenerSetup = true; + clearBtn.addEventListener('click', () => { + storeFilterState.reset(); + // Reset all UI elements + const sort = document.getElementById('store-sort'); + if (sort) sort.value = 'a-z'; + const vBtn = document.getElementById('filter-verified'); + if (vBtn) vBtn.dataset.active = 'false'; + const nBtn = document.getElementById('filter-new'); + if (nBtn) nBtn.dataset.active = 'false'; + const iBtn = document.getElementById('filter-installed'); + if (iBtn) { + iBtn.dataset.active = 'false'; + const span = iBtn.querySelector('span'); + if (span) span.textContent = 'All'; + const icon = iBtn.querySelector('i'); + if (icon) icon.className = 'fas fa-download mr-1'; + } + const auth = document.getElementById('filter-author'); + if (auth) auth.value = ''; + document.querySelectorAll('.tag-filter-pill').forEach(p => { + p.dataset.active = 'false'; + }); + applyStoreFiltersAndSort(); + }); + } +} + +function applyStoreFiltersAndSort() { + if (!pluginStoreCache) return; + + let plugins = [...pluginStoreCache]; + const installedIds = new Set( + (window.installedPlugins || []).map(p => p.id) + ); + + // Apply filters + if (storeFilterState.filterVerified) { + plugins = plugins.filter(p => p.verified); + } + if (storeFilterState.filterNew) { + plugins = plugins.filter(p => isNewPlugin(p.last_updated)); + } + if (storeFilterState.filterInstalled === true) { + plugins = plugins.filter(p => installedIds.has(p.id)); + } else if (storeFilterState.filterInstalled === false) { + plugins = plugins.filter(p => !installedIds.has(p.id)); + } + if (storeFilterState.filterAuthors.length > 0) { + const authorSet = new Set(storeFilterState.filterAuthors); + plugins = plugins.filter(p => authorSet.has(p.author)); + } + if (storeFilterState.filterTags.length > 0) { + const tagSet = new Set(storeFilterState.filterTags); + plugins = plugins.filter(p => + p.tags && p.tags.some(t => tagSet.has(t)) + ); + } + + // Apply sort + switch (storeFilterState.sort) { + case 'a-z': + plugins.sort((a, b) => + (a.name || a.id).localeCompare(b.name || b.id)); + break; + case 'z-a': + plugins.sort((a, b) => + (b.name || b.id).localeCompare(a.name || a.id)); + break; + case 'verified': + plugins.sort((a, b) => + (b.verified ? 1 : 0) - (a.verified ? 1 : 0) || + (a.name || a.id).localeCompare(b.name || b.id)); + break; + case 'newest': + plugins.sort((a, b) => { + const dateA = a.last_updated_iso || a.last_updated || ''; + const dateB = b.last_updated_iso || b.last_updated || ''; + return dateB.localeCompare(dateA); + }); + break; + case 'category': + plugins.sort((a, b) => + (a.category || '').localeCompare(b.category || '') || + (a.name || a.id).localeCompare(b.name || b.id)); + break; + } + + renderPluginStore(plugins); + + // Update result count + const countEl = document.getElementById('store-count'); + if (countEl) { + const total = pluginStoreCache.length; + const shown = plugins.length; + countEl.innerHTML = shown < total + ? `${shown} of ${total} shown` + : `${total} available`; + } + + updateFilterCountBadge(); +} + +function populateFilterControls() { + if (!pluginStoreCache) return; + + // Collect unique authors + const authors = [...new Set( + pluginStoreCache.map(p => p.author).filter(Boolean) + )].sort(); + + const authorSelect = document.getElementById('filter-author'); + if (authorSelect) { + const currentVal = authorSelect.value; + authorSelect.innerHTML = '' + + authors.map(a => ``).join(''); + authorSelect.value = currentVal; + } + + // Collect unique tags sorted by frequency (most common first) + const tagCounts = {}; + pluginStoreCache.forEach(p => { + (p.tags || []).forEach(t => { tagCounts[t] = (tagCounts[t] || 0) + 1; }); + }); + const tags = Object.keys(tagCounts).sort((a, b) => tagCounts[b] - tagCounts[a]); + + const tagsContainer = document.getElementById('filter-tags-container'); + const tagsPills = document.getElementById('filter-tags-pills'); + if (tagsContainer && tagsPills && tags.length > 0) { + tagsContainer.classList.remove('hidden'); + tagsPills.innerHTML = tags.map(tag => + `` + ).join(''); + } +} + +function updateFilterCountBadge() { + const count = storeFilterState.activeCount(); + const clearBtn = document.getElementById('clear-filters-btn'); + const badge = document.getElementById('filter-count-badge'); + if (clearBtn) { + clearBtn.classList.toggle('hidden', count === 0); + } + if (badge) { + badge.textContent = count; + } +} + function searchPluginStore(fetchCommitInfo = true) { pluginLog('[STORE] Searching plugin store...', { fetchCommitInfo }); @@ -5100,15 +5385,7 @@ function searchPluginStore(fetchCommitInfo = true) { console.error('plugin-store-grid element not found, cannot render cached plugins'); // Don't return, let it fetch fresh data } else { - renderPluginStore(pluginStoreCache); - try { - const countEl = document.getElementById('store-count'); - if (countEl) { - countEl.innerHTML = `${pluginStoreCache.length} available`; - } - } catch (e) { - console.warn('Could not update store count:', e); - } + applyStoreFiltersAndSort(); return; } } @@ -5158,8 +5435,9 @@ function searchPluginStore(fetchCommitInfo = true) { pluginStoreCache = plugins; cacheTimestamp = Date.now(); console.log('Cached plugin store data'); + populateFilterControls(); } - + // Ensure plugin store grid exists before rendering const storeGrid = document.getElementById('plugin-store-grid'); if (!storeGrid) { @@ -5168,17 +5446,20 @@ function searchPluginStore(fetchCommitInfo = true) { window.__pendingStorePlugins = plugins; return; } - - renderPluginStore(plugins); - // Update count - safely check element exists - try { - const countEl = document.getElementById('store-count'); - if (countEl) { - countEl.innerHTML = `${plugins.length} available`; + // Route through filter/sort pipeline if cache is available, otherwise render directly + if (pluginStoreCache) { + applyStoreFiltersAndSort(); + } else { + renderPluginStore(plugins); + try { + const countEl = document.getElementById('store-count'); + if (countEl) { + countEl.innerHTML = `${plugins.length} available`; + } + } catch (e) { + console.warn('Could not update store count:', e); } - } catch (e) { - console.warn('Could not update store count:', e); } // Ensure GitHub token collapse handler is attached after store is rendered @@ -5279,7 +5560,17 @@ function renderPluginStore(plugins) { return JSON.stringify(text || ''); }; - container.innerHTML = plugins.map(plugin => ` + // Build installed lookup for badges + const installedMap = new Map(); + (window.installedPlugins || []).forEach(p => { + installedMap.set(p.id, p.version || ''); + }); + + container.innerHTML = plugins.map(plugin => { + const isInstalled = installedMap.has(plugin.id); + const installedVersion = installedMap.get(plugin.id); + const hasUpdate = isInstalled && plugin.version && installedVersion && plugin.version !== installedVersion; + return `
@@ -5287,6 +5578,8 @@ function renderPluginStore(plugins) {

${escapeHtml(plugin.name || plugin.id)}

${plugin.verified ? 'Verified' : ''} ${isNewPlugin(plugin.last_updated) ? 'New' : ''} + ${isInstalled ? 'Installed' : ''} + ${hasUpdate ? 'Update' : ''} ${plugin._source === 'custom_repository' ? `Custom` : ''}
@@ -5325,7 +5618,8 @@ function renderPluginStore(plugins) {
- `).join(''); + `; + }).join(''); } // Expose functions to window for onclick handlers diff --git a/web_interface/templates/v3/partials/plugins.html b/web_interface/templates/v3/partials/plugins.html index 024716ca3..c57e4eea5 100644 --- a/web_interface/templates/v3/partials/plugins.html +++ b/web_interface/templates/v3/partials/plugins.html @@ -165,6 +165,68 @@

GitHub API Configuration

+ + +
+ +
+ +
+ + +
+ + +
+ + +
+ Filter: + + + +
+ + +
+ + + + + + +
+ + + +
+
From 4c2c7c0d1706fb0019bae0b7be737f7d67aaf820 Mon Sep 17 00:00:00 2001 From: Chuck Date: Mon, 16 Feb 2026 10:38:27 -0500 Subject: [PATCH 2/7] refactor(store): replace tag pills with category pills, fix sort dates - Replace tag filter pills with category filter pills (less duplication) - Prefer per-plugin last_updated over repo-wide pushed_at for sort Co-Authored-By: Claude Opus 4.6 --- web_interface/static/v3/app.css | 6 +- web_interface/static/v3/plugins_manager.js | 64 +++++++++---------- .../templates/v3/partials/plugins.html | 10 +-- 3 files changed, 39 insertions(+), 41 deletions(-) diff --git a/web_interface/static/v3/app.css b/web_interface/static/v3/app.css index 5ac1446d1..23f627d5b 100644 --- a/web_interface/static/v3/app.css +++ b/web_interface/static/v3/app.css @@ -684,14 +684,14 @@ button.bg-white { opacity: 0.85; } -/* Tag Filter Pills */ -.tag-filter-pill { +/* Category Filter Pills */ +.category-filter-pill { cursor: pointer; user-select: none; transition: all 0.15s ease; } -.tag-filter-pill[data-active="true"] { +.category-filter-pill[data-active="true"] { background-color: var(--color-info-bg); border-color: var(--color-info); color: var(--color-info); diff --git a/web_interface/static/v3/plugins_manager.js b/web_interface/static/v3/plugins_manager.js index ab2ec5af0..4a212b832 100644 --- a/web_interface/static/v3/plugins_manager.js +++ b/web_interface/static/v3/plugins_manager.js @@ -870,7 +870,7 @@ window.currentPluginConfig = null; filterNew: false, filterInstalled: null, // null = all, true = installed only, false = not installed only filterAuthors: [], - filterTags: [], + filterCategories: [], persist() { localStorage.setItem('storeSort', this.sort); @@ -882,7 +882,7 @@ window.currentPluginConfig = null; this.filterNew = false; this.filterInstalled = null; this.filterAuthors = []; - this.filterTags = []; + this.filterCategories = []; this.persist(); }, @@ -892,7 +892,7 @@ window.currentPluginConfig = null; if (this.filterNew) count++; if (this.filterInstalled !== null) count++; count += this.filterAuthors.length; - count += this.filterTags.length; + count += this.filterCategories.length; return count; } }; @@ -5191,19 +5191,20 @@ function setupStoreFilterListeners() { } // Tag pills (event delegation on container) - const tagsPills = document.getElementById('filter-tags-pills'); - if (tagsPills && !tagsPills._listenerSetup) { - tagsPills._listenerSetup = true; - tagsPills.addEventListener('click', (e) => { - const pill = e.target.closest('.tag-filter-pill'); + // Category pills (event delegation on container) + const catsPills = document.getElementById('filter-categories-pills'); + if (catsPills && !catsPills._listenerSetup) { + catsPills._listenerSetup = true; + catsPills.addEventListener('click', (e) => { + const pill = e.target.closest('.category-filter-pill'); if (!pill) return; - const tag = pill.dataset.tag; - const idx = storeFilterState.filterTags.indexOf(tag); + const cat = pill.dataset.category; + const idx = storeFilterState.filterCategories.indexOf(cat); if (idx >= 0) { - storeFilterState.filterTags.splice(idx, 1); + storeFilterState.filterCategories.splice(idx, 1); pill.dataset.active = 'false'; } else { - storeFilterState.filterTags.push(tag); + storeFilterState.filterCategories.push(cat); pill.dataset.active = 'true'; } applyStoreFiltersAndSort(); @@ -5233,7 +5234,7 @@ function setupStoreFilterListeners() { } const auth = document.getElementById('filter-author'); if (auth) auth.value = ''; - document.querySelectorAll('.tag-filter-pill').forEach(p => { + document.querySelectorAll('.category-filter-pill').forEach(p => { p.dataset.active = 'false'; }); applyStoreFiltersAndSort(); @@ -5265,11 +5266,9 @@ function applyStoreFiltersAndSort() { const authorSet = new Set(storeFilterState.filterAuthors); plugins = plugins.filter(p => authorSet.has(p.author)); } - if (storeFilterState.filterTags.length > 0) { - const tagSet = new Set(storeFilterState.filterTags); - plugins = plugins.filter(p => - p.tags && p.tags.some(t => tagSet.has(t)) - ); + if (storeFilterState.filterCategories.length > 0) { + const catSet = new Set(storeFilterState.filterCategories); + plugins = plugins.filter(p => catSet.has(p.category)); } // Apply sort @@ -5289,8 +5288,9 @@ function applyStoreFiltersAndSort() { break; case 'newest': plugins.sort((a, b) => { - const dateA = a.last_updated_iso || a.last_updated || ''; - const dateB = b.last_updated_iso || b.last_updated || ''; + // Prefer static per-plugin last_updated over GitHub pushed_at (which is repo-wide) + const dateA = a.last_updated || a.last_updated_iso || ''; + const dateB = b.last_updated || b.last_updated_iso || ''; return dateB.localeCompare(dateA); }); break; @@ -5332,19 +5332,17 @@ function populateFilterControls() { authorSelect.value = currentVal; } - // Collect unique tags sorted by frequency (most common first) - const tagCounts = {}; - pluginStoreCache.forEach(p => { - (p.tags || []).forEach(t => { tagCounts[t] = (tagCounts[t] || 0) + 1; }); - }); - const tags = Object.keys(tagCounts).sort((a, b) => tagCounts[b] - tagCounts[a]); - - const tagsContainer = document.getElementById('filter-tags-container'); - const tagsPills = document.getElementById('filter-tags-pills'); - if (tagsContainer && tagsPills && tags.length > 0) { - tagsContainer.classList.remove('hidden'); - tagsPills.innerHTML = tags.map(tag => - `` + // Collect unique categories sorted alphabetically + const categories = [...new Set( + pluginStoreCache.map(p => p.category).filter(Boolean) + )].sort(); + + const catsContainer = document.getElementById('filter-categories-container'); + const catsPills = document.getElementById('filter-categories-pills'); + if (catsContainer && catsPills && categories.length > 0) { + catsContainer.classList.remove('hidden'); + catsPills.innerHTML = categories.map(cat => + `` ).join(''); } } diff --git a/web_interface/templates/v3/partials/plugins.html b/web_interface/templates/v3/partials/plugins.html index c57e4eea5..9639af43e 100644 --- a/web_interface/templates/v3/partials/plugins.html +++ b/web_interface/templates/v3/partials/plugins.html @@ -216,12 +216,12 @@

GitHub API Configuration

- -