Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions assets/scss/custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2045,6 +2045,34 @@ h1.clusters-site-heading.clusters-hub-page__title {
.fr-specificity-toggle input {
margin: 0;
}
.fr-clear-btn {
margin-left: auto;
align-self: center;
font-family: inherit;
font-size: 0.62rem;
font-weight: 600;
padding: 0.18rem 0.5rem;
border-radius: 3px;
border: 1px solid rgba(0,64,85,0.25);
background: transparent;
color: #004055;
cursor: pointer;
white-space: nowrap;
transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.fr-clear-btn::before {
content: "\00d7";
margin-right: 0.3rem;
font-weight: 700;
}
.fr-clear-btn:hover {
background: #004055;
color: #fff;
border-color: #004055;
}
.fr-clear-btn[hidden] {
display: none;
}

/* ---- Search input ---- */
.fr-search-wrap {
Expand Down
2 changes: 2 additions & 0 deletions layouts/partials/clusters/clusters_controls.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
<span>Show specialist resources</span>
</label>
</div>
<button type="button" class="fr-clear-btn" id="fr-global-clear" hidden
aria-label="Clear all filters and show everything">Clear filters</button>
</div>

{{/* Inline search results panel (kept for cluster/subcluster name search) */}}
Expand Down
75 changes: 59 additions & 16 deletions static/js/featured-resources.js
Original file line number Diff line number Diff line change
Expand Up @@ -705,33 +705,45 @@
var typeGroup = document.getElementById('fr-global-type-tags');
var specCheckbox = document.getElementById('fr-global-specificity-checkbox');
var searchInput = document.getElementById('clusters-inline-search-input');
var clearBtn = document.getElementById('fr-global-clear');

// --- Pre-cache card data so filtering never queries the DOM for text ---
var sectionCache = [];
document.querySelectorAll('.acc-section').forEach(function (section) {
var header = section.querySelector('.acc-header');
var body = section.querySelector('.acc-body');

/* Own text (heading + description) - a query matching only this, not any
card, still means the section is relevant and shouldn't disable/collapse. */
var labelEl = header ? header.querySelector('.acc-label') : null;
var descEl = section.querySelector('.sc-description');
var ownText = ((labelEl ? labelEl.textContent : '') + ' ' + (descEl ? descEl.textContent : '')).toLowerCase();

var container = section.querySelector('.fr-cards-list');
if (!container) return;
var cardEls = container.querySelectorAll('.fc-card, .fr-card');
var cards = [];
cardEls.forEach(function (card) {
var title = (card.querySelector('.fc-title') || card.querySelector('.fr-title') || {}).textContent || '';
var summary = (card.querySelector('.fc-summary') || card.querySelector('.fr-summary') || {}).textContent || '';
cards.push({
el: card,
focus: card.getAttribute('data-focus'),
type: card.getAttribute('data-type'),
spec: card.getAttribute('data-specificity'),
text: (title + ' ' + summary).toLowerCase()
if (container) {
container.querySelectorAll('.fc-card, .fr-card').forEach(function (card) {
var title = (card.querySelector('.fc-title') || card.querySelector('.fr-title') || {}).textContent || '';
var summary = (card.querySelector('.fc-summary') || card.querySelector('.fr-summary') || {}).textContent || '';
cards.push({
el: card,
focus: card.getAttribute('data-focus'),
type: card.getAttribute('data-type'),
spec: card.getAttribute('data-specificity'),
text: (title + ' ' + summary).toLowerCase()
});
});
});
var header = section.querySelector('.acc-header');
var body = section.querySelector('.acc-body');
}

if (!container && !ownText.trim()) return;

sectionCache.push({
el: section,
countEl: section.querySelector('.acc-count-match'),
header: header,
body: body,
chevron: header ? header.querySelector('.acc-chevron') : null,
ownText: ownText,
cards: cards
});
});
Expand All @@ -750,6 +762,24 @@

if (specCheckbox) specCheckbox.addEventListener('change', applyGlobalFilters);

if (clearBtn) {
clearBtn.addEventListener('click', function () {
if (searchInput) searchInput.value = '';
resetGroupToAll(focusGroup);
resetGroupToAll(typeGroup);
if (specCheckbox) specCheckbox.checked = false;
applyGlobalFilters();
if (searchInput) searchInput.focus();
});
}

function resetGroupToAll(group) {
if (!group) return;
group.querySelectorAll('.fr-tag-btn').forEach(function (b) {
b.classList.toggle('active', b.getAttribute('data-value') === 'all');
});
}

if (searchInput) {
var debounce;
searchInput.addEventListener('input', function () {
Expand All @@ -773,6 +803,7 @@
var query = searchInput ? searchInput.value.toLowerCase().trim() : '';
var tokens = query ? query.split(/\s+/) : [];
var isFiltered = activeFocus !== 'all' || activeType !== 'all' || tokens.length > 0;
if (clearBtn) clearBtn.hidden = !isFiltered;

// --- Pass 1: compute visibility from cached data (no DOM reads) ---
var sectionResults = [];
Expand All @@ -793,7 +824,19 @@
cardVis[c] = show;
if (show) matchCount++;
}
sectionResults.push({ matchCount: matchCount, cardVis: cardVis });

/* A query matching only the section's own heading/description (no card)
still means it's relevant - keep it open rather than disabling it.
Only applies with no active facet, since facets are card-specific. */
var ownTextMatches = false;
if (tokens.length > 0 && activeFocus === 'all' && activeType === 'all' && sec.ownText) {
ownTextMatches = true;
for (var t2 = 0; t2 < tokens.length; t2++) {
if (sec.ownText.indexOf(tokens[t2]) === -1) { ownTextMatches = false; break; }
}
}

sectionResults.push({ matchCount: matchCount, cardVis: cardVis, ownTextMatches: ownTextMatches });
}

// --- Pass 2: batch all DOM writes ---
Expand All @@ -812,7 +855,7 @@
var chevron = sec.chevron;
if (!header || !body) continue;

if (res.matchCount === 0) {
if (res.matchCount === 0 && !res.ownTextMatches) {
if (!header.classList.contains('acc-disabled')) {
header.dataset.wasOpen = body.classList.contains('acc-collapsed') ? '0' : '1';
}
Expand Down
Loading