From a82eec7a06a1ff58e01ef4e63c15a62dc2d1130b Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Wed, 25 Feb 2026 02:06:21 +0300 Subject: [PATCH] Enhance card section filter: multi-term OR/AND, filtered count badge Space-separated terms use OR logic, comma-separated use AND. Count badge shows visible/total when filter is active. Co-Authored-By: Claude Opus 4.6 --- .../static/js/core/card-sections.js | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/server/src/wled_controller/static/js/core/card-sections.js b/server/src/wled_controller/static/js/core/card-sections.js index 62f6ff5..404a707 100644 --- a/server/src/wled_controller/static/js/core/card-sections.js +++ b/server/src/wled_controller/static/js/core/card-sections.js @@ -143,9 +143,9 @@ export class CardSection { this._lastItems = items; - // Update count badge + // Update count badge (will be refined by _applyFilter if a filter is active) const countEl = document.querySelector(`[data-cs-toggle="${this.sectionKey}"] .cs-count`); - if (countEl) countEl.textContent = items.length; + if (countEl && !this._filterValue) countEl.textContent = items.length; const newMap = new Map(items.map(i => [i.key, i.html])); const addCard = content.querySelector('.cs-add-card'); @@ -229,15 +229,32 @@ export class CardSection { } _applyFilter(content, query) { - const lower = query.toLowerCase(); const cards = content.querySelectorAll('.card, .template-card:not(.add-template-card)'); const addCard = content.querySelector('.cs-add-card'); + const countEl = document.querySelector(`[data-cs-toggle="${this.sectionKey}"] .cs-count`); + const total = cards.length; + + if (!query) { + cards.forEach(card => { card.style.display = ''; }); + if (addCard) addCard.style.display = ''; + if (countEl) countEl.textContent = total; + return; + } + + const lower = query.toLowerCase(); + // Comma-separated segments → AND; spaces within a segment → OR + const groups = lower.split(',').map(g => g.trim().split(/\s+/).filter(Boolean)).filter(g => g.length); + let visible = 0; cards.forEach(card => { const text = card.textContent.toLowerCase(); - card.style.display = (!lower || text.includes(lower)) ? '' : 'none'; + // Each group must have at least one matching term (AND of ORs) + const match = groups.every(orTerms => orTerms.some(term => text.includes(term))); + card.style.display = match ? '' : 'none'; + if (match) visible++; }); - if (addCard) addCard.style.display = lower ? 'none' : ''; + if (addCard) addCard.style.display = 'none'; + if (countEl) countEl.textContent = `${visible}/${total}`; } }