fix(search): filters + metadata across collection layouts

Fixes https://github.com/HugoBlox/hugo-blox-builder/discussions/3292
This commit is contained in:
George Cushen committed 2025-12-31 01:18:09 +00:00
1 parent 1c1d54bdc3
commit d95cd5fac8
7 files changed
+160 -123

No files matched your search

@@ -267,6 +267,7 @@
selectedIndex: -1,
activeFilter: null,
availableFilters: [],
allFilters: [],
trendingSearches: {{ site.Params.hugoblox.search.suggestions | default (slice "Search term 1" "Search term 2") | jsonify }},
pagefind: null,
pagefindModulePath: window.hbb?.assetPaths?.pagefind ?? '/pagefind/pagefind.js',
@@ -336,6 +337,62 @@
}
});
},
buildFilters(filterIndex) {
const filters = [];
if (filterIndex?.type) {
Object.entries(filterIndex.type).forEach(([value, count]) => {
filters.push({
category: 'type',
value,
label: this.typeLabels[value] || value.charAt(0).toUpperCase() + value.slice(1),
count,
filterKey: `type:${value}`
});
});
}
if (filterIndex?.category) {
Object.entries(filterIndex.category).forEach(([value, count]) => {
filters.push({
category: 'category',
value,
label: value,
count,
filterKey: `category:${value}`
});
});
}
if (filterIndex?.difficulty) {
Object.entries(filterIndex.difficulty).forEach(([value, count]) => {
filters.push({
category: 'difficulty',
value,
label: value,
count,
filterKey: `difficulty:${value}`
});
});
}
return filters;
},
mergeFilterCounts(filterCounts) {
if (!this.allFilters.length) {
return [];
}
return this.allFilters.map((filter) => {
const count = filterCounts?.[filter.category]?.[filter.value];
return {
...filter,
count: typeof count === 'number' ? count : 0
};
});
},
async search() {
if (!this.query.trim()) {
@@ -374,72 +431,14 @@
};
})
);
// Extract unique filters from results themselves
const filterCounts = { type: {}, category: {}, difficulty: {} };
this.results.forEach(result => {
if (result.filters) {
// Count type filters
if (result.filters.type) {
result.filters.type.forEach(val => {
filterCounts.type[val] = (filterCounts.type[val] || 0) + 1;
});
}
// Count category filters
if (result.filters.category) {
result.filters.category.forEach(val => {
filterCounts.category[val] = (filterCounts.category[val] || 0) + 1;
});
}
// Count difficulty filters
if (result.filters.difficulty) {
result.filters.difficulty.forEach(val => {
filterCounts.difficulty[val] = (filterCounts.difficulty[val] || 0) + 1;
});
}
}
});
console.log('Extracted filter counts from results:', filterCounts);
// Build filter list
const filters = [];
// Add type filters with i18n labels
Object.entries(filterCounts.type).forEach(([value, count]) => {
filters.push({
category: 'type',
value,
label: this.typeLabels[value] || value.charAt(0).toUpperCase() + value.slice(1),
count,
filterKey: `type:${value}`
});
});
// Add category filters
Object.entries(filterCounts.category).forEach(([value, count]) => {
filters.push({
category: 'category',
value,
label: value,
count,
filterKey: `category:${value}`
});
});
// Add difficulty filters
Object.entries(filterCounts.difficulty).forEach(([value, count]) => {
filters.push({
category: 'difficulty',
value,
label: value,
count,
filterKey: `difficulty:${value}`
});
});
this.availableFilters = filters;
const filterCounts = search.totalFilters || search.filters || {};
if (this.allFilters.length) {
this.availableFilters = this.mergeFilterCounts(filterCounts);
} else {
this.availableFilters = this.buildFilters(filterCounts);
}
console.log('Final availableFilters array:', this.availableFilters);
// Set first result as selected for keyboard navigation
@@ -456,52 +455,13 @@
},
async loadFilters() {
// Pre-load available filters by doing an empty search
// Pre-load available filters from the full Pagefind index
try {
const search = await this.pagefind.search('');
console.log('Available Pagefind filters:', search.filters);
const filters = [];
// Load type filters (questions, faq, docs)
if (search.filters && search.filters.type) {
Object.entries(search.filters.type).forEach(([value, count]) => {
filters.push({
category: 'type',
value,
label: value.charAt(0).toUpperCase() + value.slice(1),
count,
filterKey: `type:${value}`
});
});
}
// Load category filters
if (search.filters && search.filters.category) {
Object.entries(search.filters.category).forEach(([value, count]) => {
filters.push({
category: 'category',
value,
label: value,
count,
filterKey: `category:${value}`
});
});
}
// Load difficulty filters (for questions)
if (search.filters && search.filters.difficulty) {
Object.entries(search.filters.difficulty).forEach(([value, count]) => {
filters.push({
category: 'difficulty',
value,
label: value,
count,
filterKey: `difficulty:${value}`
});
});
}
const filterIndex = await this.pagefind.filters();
console.log('Available Pagefind filters:', filterIndex);
const filters = this.buildFilters(filterIndex);
this.allFilters = filters;
this.availableFilters = filters;
console.log('Processed filters:', this.availableFilters);
} catch (error) {
@@ -10,8 +10,8 @@
{{ end }}
{{/* Pagefind metadata for search filtering */}}
<div class="content" data-pagefind-body data-pagefind-meta="title:{{ .Title }}">
<h1>{{ .Title }}</h1>
<div class="content" data-pagefind-body>
<h1 data-pagefind-meta="title">{{ .Title }}</h1>
{{ .Content }}
{{/* Use hidden spans for filter values - best practice per Pagefind docs */}}
@@ -28,7 +28,7 @@
{{ end }}
{{ end }}
<h1 class="{{ if $has_overlap_icon }}mt-12{{ else }}mt-2{{ end }} text-4xl font-bold tracking-tight text-slate-900 dark:text-slate-100">
<h1 class="{{ if $has_overlap_icon }}mt-12{{ else }}mt-2{{ end }} text-4xl font-bold tracking-tight text-slate-900 dark:text-slate-100" data-pagefind-meta="title" data-pagefind-body>
{{/* Inline icon before title if specified */}}
{{ if $cover }}
{{ $icon_config := .Params.cover.icon | default (dict) }}
@@ -375,7 +375,13 @@
</div>
{{ end }}
<div class="prose prose-slate lg:prose-xl dark:prose-invert">
<div class="prose prose-slate lg:prose-xl dark:prose-invert" data-pagefind-body>
{{ with .Section }}
<span data-pagefind-filter="type" style="display:none;">{{ . }}</span>
{{ end }}
{{ with .Params.categories }}
<span data-pagefind-filter="category" style="display:none;">{{ index . 0 }}</span>
{{ end }}
{{ .Content }}
</div>
@@ -4,7 +4,7 @@
{{ .Page.Store.Set "has_alpine" true }}
{{/* Pagefind metadata for search filtering */}}
<div data-pagefind-body data-pagefind-meta="title:{{ .Title }}">
<div data-pagefind-body>
{{/* Use hidden spans for filter values - best practice per Pagefind docs */}}
<span data-pagefind-filter="type" style="display:none;">faq</span>
{{ with .Params.categories }}
@@ -23,7 +23,7 @@
</div>
{{ end }}
<h1>{{ .Title }}</h1>
<h1 data-pagefind-meta="title">{{ .Title }}</h1>
{{ with .Content }}
<div class="lead mb-8">
@@ -138,4 +138,4 @@
</div>{{/* End Pagefind metadata wrapper */}}
{{- end -}}
{{- end -}}
@@ -1,8 +1,7 @@
{{- define "main" -}}
{{/* Pagefind metadata for search filtering */}}
<div data-pagefind-body
{{ with .Params.question }}data-pagefind-meta="title:{{ . }}"{{ end }}>
<div data-pagefind-body>
{{/* Use hidden spans for filter values - best practice per Pagefind docs */}}
<span data-pagefind-filter="type" style="display:none;">questions</span>
{{ with .Params.categories }}
@@ -26,7 +25,7 @@
{{/* Question as title */}}
<div class="qa-question mb-6">
<h1 class="text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">
<h1 class="text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4" data-pagefind-meta="title">
{{ .Params.question | default .Title }}
</h1>
+10 -2
View File
@@ -37,7 +37,7 @@
{{ end }}
{{ end }}
<h1 class="{{ if $has_overlap_icon }}mt-12{{ else }}mt-2{{ end }} text-4xl font-bold tracking-tight text-slate-900 dark:text-slate-100">
<h1 class="{{ if $has_overlap_icon }}mt-12{{ else }}mt-2{{ end }} text-4xl font-bold tracking-tight text-slate-900 dark:text-slate-100" data-pagefind-meta="title" data-pagefind-body>
{{/* Inline icon before title if specified */}}
{{ if $cover }}
{{ $icon_config := .Params.cover.icon | default (dict) }}
@@ -201,6 +201,7 @@
<img srcset="{{ $responsive.srcset }}"
sizes="{{ $sizes_attr }}"
src="{{ $responsive.fallback.RelPermalink }}"
data-pagefind-meta="image[src]"
width="{{ $fitted_image.Width }}"
height="{{ $fitted_image.Height }}"
alt="{{ with $image_params.alt_text }}{{.}}{{ end }}"
@@ -222,6 +223,7 @@
<div class="article-header {{$image_container}} featured-image-wrapper mt-4 mb-16" style="max-width: {{$image.Width}}px; max-height: {{$image.Height}}px;">
<div style="position: relative">
<img src="{{ $image.RelPermalink }}"
data-pagefind-meta="image[src]"
width="{{ $image.Width }}"
height="{{ $image.Height }}"
alt="{{ with $image_params.alt_text }}{{.}}{{ end }}"
@@ -288,7 +290,13 @@
</div>
{{ end }}
<div class="prose prose-slate lg:prose-xl dark:prose-invert">
<div class="prose prose-slate lg:prose-xl dark:prose-invert" data-pagefind-body>
{{ with .Section }}
<span data-pagefind-filter="type" style="display:none;">{{ . }}</span>
{{ end }}
{{ with .Params.categories }}
<span data-pagefind-filter="category" style="display:none;">{{ index . 0 }}</span>
{{ end }}
{{ .Content }}
</div>