2 Commits

Author SHA1 Message Date
Chuck
763df0fee4 feat(plugins): add sorting to installed plugins section
Add A-Z, Z-A, and Enabled First sort options for installed plugins
with localStorage persistence. Both installed and store sections
now default to A-Z sorting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 14:51:34 -05:00
Chuck
2447b131b5 fix: bump cache-buster versions for JS and CSS 2026-02-16 14:43:49 -05:00
3 changed files with 53 additions and 16 deletions

View File

@@ -897,6 +897,9 @@ window.currentPluginConfig = null;
}
};
// Installed plugins sort state
let installedSort = localStorage.getItem('installedSort') || 'a-z';
// Shared on-demand status store (mirrors Alpine store when available)
window.__onDemandStore = window.__onDemandStore || {
loading: true,
@@ -1010,7 +1013,7 @@ window.initPluginsPage = function() {
// If we fetched data before the DOM existed, render it now
if (window.__pendingInstalledPlugins) {
console.log('[RENDER] Applying pending installed plugins data');
renderInstalledPlugins(window.__pendingInstalledPlugins);
sortAndRenderInstalledPlugins(window.__pendingInstalledPlugins);
window.__pendingInstalledPlugins = null;
}
if (window.__pendingStorePlugins) {
@@ -1266,7 +1269,7 @@ function loadInstalledPlugins(forceRefresh = false) {
}));
pluginLog('[CACHE] Dispatched pluginsUpdated event from cache');
// Still render to ensure UI is updated
renderInstalledPlugins(pluginLoadCache.data);
sortAndRenderInstalledPlugins(pluginLoadCache.data);
return Promise.resolve(pluginLoadCache.data);
}
@@ -1325,7 +1328,7 @@ function loadInstalledPlugins(forceRefresh = false) {
});
}
renderInstalledPlugins(installedPlugins);
sortAndRenderInstalledPlugins(installedPlugins);
// Update count
const countEl = document.getElementById('installed-count');
@@ -1366,6 +1369,24 @@ function refreshInstalledPlugins() {
window.pluginManager.loadInstalledPlugins = loadInstalledPlugins;
// Note: searchPluginStore will be exposed after its definition (see below)
function sortAndRenderInstalledPlugins(plugins) {
const sorted = [...plugins].sort((a, b) => {
const nameA = (a.name || a.id || '').toLowerCase();
const nameB = (b.name || b.id || '').toLowerCase();
switch (installedSort) {
case 'z-a':
return nameB.localeCompare(nameA);
case 'enabled':
if (a.enabled !== b.enabled) return a.enabled ? -1 : 1;
return nameA.localeCompare(nameB);
case 'a-z':
default:
return nameA.localeCompare(nameB);
}
});
renderInstalledPlugins(sorted);
}
function renderInstalledPlugins(plugins) {
const container = document.getElementById('installed-plugins-grid');
if (!container) {
@@ -5085,7 +5106,7 @@ function handleUninstallSuccess(pluginId) {
if (typeof installedPlugins !== 'undefined') {
installedPlugins = updatedPlugins;
}
renderInstalledPlugins(updatedPlugins);
sortAndRenderInstalledPlugins(updatedPlugins);
showNotification(`Plugin uninstalled successfully`, 'success');
// Also refresh from server to ensure consistency
@@ -5127,20 +5148,16 @@ function restartDisplay() {
// --- Store Filter/Sort Functions ---
function setupStoreFilterListeners() {
console.log('[FILTER SETUP] setupStoreFilterListeners() called');
// Sort dropdown
const sortSelect = document.getElementById('store-sort');
console.log('[FILTER SETUP] store-sort element:', !!sortSelect, 'listenerSetup:', sortSelect?._listenerSetup);
if (sortSelect && !sortSelect._listenerSetup) {
sortSelect._listenerSetup = true;
sortSelect.value = storeFilterState.sort;
sortSelect.addEventListener('change', () => {
console.log('[FILTER] Sort changed to:', sortSelect.value, 'cache:', !!pluginStoreCache);
storeFilterState.sort = sortSelect.value;
storeFilterState.persist();
applyStoreFiltersAndSort();
});
console.log('[FILTER SETUP] Sort listener attached');
}
// Verified filter toggle
@@ -5197,11 +5214,9 @@ function setupStoreFilterListeners() {
// Tag pills (event delegation on container)
// Category pills (event delegation on container)
const catsPills = document.getElementById('filter-categories-pills');
console.log('[FILTER SETUP] filter-categories-pills element:', catsPills, 'listenerSetup:', catsPills?._listenerSetup);
if (catsPills && !catsPills._listenerSetup) {
catsPills._listenerSetup = true;
catsPills.addEventListener('click', (e) => {
console.log('[FILTER] Category pill click, target:', e.target, 'closest:', e.target.closest('.category-filter-pill'));
const pill = e.target.closest('.category-filter-pill');
if (!pill) return;
const cat = pill.dataset.category;
@@ -5213,10 +5228,8 @@ function setupStoreFilterListeners() {
storeFilterState.filterCategories.push(cat);
pill.dataset.active = 'true';
}
console.log('[FILTER] Categories now:', storeFilterState.filterCategories, 'cache size:', pluginStoreCache?.length);
applyStoreFiltersAndSort();
});
console.log('[FILTER SETUP] Category pill listener attached');
}
// Clear filters button
@@ -5248,11 +5261,25 @@ function setupStoreFilterListeners() {
applyStoreFiltersAndSort();
});
}
// Installed plugins sort dropdown
const installedSortSelect = document.getElementById('installed-sort');
if (installedSortSelect && !installedSortSelect._listenerSetup) {
installedSortSelect._listenerSetup = true;
installedSortSelect.value = installedSort;
installedSortSelect.addEventListener('change', () => {
installedSort = installedSortSelect.value;
localStorage.setItem('installedSort', installedSort);
const plugins = window.installedPlugins || [];
if (plugins.length > 0) {
sortAndRenderInstalledPlugins(plugins);
}
});
}
}
function applyStoreFiltersAndSort() {
console.log('[FILTER] applyStoreFiltersAndSort called, cache:', pluginStoreCache?.length, 'state:', JSON.stringify(storeFilterState));
if (!pluginStoreCache) { console.log('[FILTER] No cache, returning'); return; }
if (!pluginStoreCache) return;
let plugins = [...pluginStoreCache];
const installedIds = new Set(

View File

@@ -1375,7 +1375,7 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Custom v3 styles -->
<link rel="stylesheet" href="{{ url_for('static', filename='v3/app.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='v3/app.css') }}?v=20260216b">
</head>
<body x-data="app()" class="bg-gray-50 min-h-screen">
<!-- Header -->
@@ -5013,7 +5013,7 @@
<script src="{{ url_for('static', filename='v3/js/widgets/plugin-loader.js') }}" defer></script>
<!-- Legacy plugins_manager.js (for backward compatibility during migration) -->
<script src="{{ url_for('static', filename='v3/plugins_manager.js') }}?v=20250116a" defer></script>
<script src="{{ url_for('static', filename='v3/plugins_manager.js') }}?v=20260216b" defer></script>
<!-- Custom feeds table helper functions -->
<script>

View File

@@ -28,6 +28,16 @@
<h3 class="text-lg font-bold text-gray-900">Installed Plugins</h3>
<span id="installed-count" class="text-sm text-gray-500 font-medium">0 installed</span>
</div>
<div class="flex items-center gap-2">
<label for="installed-sort" class="text-sm font-medium text-gray-700 whitespace-nowrap">
<i class="fas fa-sort mr-1"></i>Sort:
</label>
<select id="installed-sort" class="text-sm px-3 py-1.5 border border-gray-300 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500">
<option value="a-z">A &rarr; Z</option>
<option value="z-a">Z &rarr; A</option>
<option value="enabled">Enabled First</option>
</select>
</div>
</div>
<div id="installed-plugins-content" class="block">
<div id="installed-plugins-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-6">