Add badge filtering and shop category to the site

- Introduced a new 'shops' category in the build script and README.
- Implemented badge filtering in the filter functionality, allowing users to filter entries by badge.
This commit is contained in:
2026-01-25 23:37:20 +01:00
parent 2519520e1b
commit cfb35a3c38
6 changed files with 59 additions and 7 deletions

View File

@@ -33,10 +33,13 @@ The generated site will be in the `dist/` folder.
Create a Markdown file in the appropriate `content/` subfolder: Create a Markdown file in the appropriate `content/` subfolder:
- `content/products/` - Swedish products | Folder | Category | Description |
- `content/services/` - Swedish services |--------|----------|-------------|
- `content/experiences/` - Swedish experiences | `content/products/` | Produkter | Physical Swedish products (e.g., Dalahäst, Orrefors glass) |
- `content/manufacturers/` - Swedish manufacturers | `content/manufacturers/` | Tillverkare | Companies that design/manufacture products (e.g., Fjällräven, Hasselblad) |
| `content/shops/` | Butiker | Retail stores selling products (e.g., Göteborg Manufaktur) |
| `content/services/` | Tjänster | Service providers, both physical and digital (e.g., SJ, Mediaflow) |
| `content/experiences/` | Upplevelser | Experiences, events, and cultural activities (e.g., Icehotel, Midsommar) |
### Content Format ### Content Format

View File

@@ -14,7 +14,8 @@ const CATEGORY_LABELS = {
manufacturers: 'Tillverkare', manufacturers: 'Tillverkare',
products: 'Produkter', products: 'Produkter',
services: 'Tjänster', services: 'Tjänster',
experiences: 'Upplevelser' experiences: 'Upplevelser',
shops: 'Butiker'
}; };
// Badge tiers (highest to lowest) // Badge tiers (highest to lowest)
@@ -207,8 +208,14 @@ function extractFilters(entries) {
const categories = [...new Set(entries.map(e => e.category).filter(Boolean))].sort(); const categories = [...new Set(entries.map(e => e.category).filter(Boolean))].sort();
const regions = [...new Set(entries.map(e => e.region).filter(Boolean))].sort(); const regions = [...new Set(entries.map(e => e.region).filter(Boolean))].sort();
const tags = [...new Set(entries.flatMap(e => e.tags || []))].sort(); const tags = [...new Set(entries.flatMap(e => e.tags || []))].sort();
const badges = [...new Set(entries.map(e => e.badge).filter(Boolean))].sort((a, b) => {
// Sort by tier (highest first)
const tierA = BADGE_TIERS[a]?.tier || 99;
const tierB = BADGE_TIERS[b]?.tier || 99;
return tierA - tierB;
});
return { categories, regions, tags }; return { categories, regions, tags, badges };
} }
// Generate filter HTML // Generate filter HTML
@@ -225,6 +232,10 @@ function generateFilterHTML(filters) {
`<option value="${t}">${t}</option>` `<option value="${t}">${t}</option>`
).join(''); ).join('');
const badgeOptions = filters.badges.map(b =>
`<option value="${b}">${BADGE_TIERS[b]?.label || b}</option>`
).join('');
return ` return `
<div class="filters"> <div class="filters">
<div class="filter-group"> <div class="filter-group">
@@ -241,6 +252,13 @@ function generateFilterHTML(filters) {
${regionOptions} ${regionOptions}
</select> </select>
</div> </div>
<div class="filter-group">
<label for="badge-filter">Märkning</label>
<select id="badge-filter">
<option value="">Alla märkningar</option>
${badgeOptions}
</select>
</div>
<div class="filter-group"> <div class="filter-group">
<label for="tag-filter">Tagg</label> <label for="tag-filter">Tagg</label>
<select id="tag-filter"> <select id="tag-filter">

View File

@@ -0,0 +1,13 @@
---
title: Göteborg Manufaktur
region: Västra Götaland
tags: [jeans, denim, kläder, reparation, second-hand, hållbarhet]
website: https://goteborgmanufaktur.se
badge: svenskt-foretag
image: /static/images/goteborg-manufaktur-cover.jpeg
logo: /static/images/goteborg-manufaktur.avif
---
Göteborg Manufaktur är en denimbutik på Tredje Långgatan i Göteborg, grundad 2016 av Jonas Melin, Olof Norrman och Erik Davis. Butiken erbjuder ett noggrant utvalt sortiment av premiumjeans och kläder med fokus på japansk denim och workwear heritage.
Det som började som en liten jeansreparationsverkstad 2014 har vuxit till en fullskalig butik med etisk produktion i centrum. Alla jeansreparationer görs på plats och är gratis för produkter köpta i butiken. Genom "Worn Not Wasted" erbjuds även second-hand på kommission en cirkulär modell för hållbart mode.

View File

@@ -5,6 +5,7 @@
const categoryFilter = document.getElementById('category-filter'); const categoryFilter = document.getElementById('category-filter');
const regionFilter = document.getElementById('region-filter'); const regionFilter = document.getElementById('region-filter');
const badgeFilter = document.getElementById('badge-filter');
const tagFilter = document.getElementById('tag-filter'); const tagFilter = document.getElementById('tag-filter');
const searchInput = document.getElementById('search-input'); const searchInput = document.getElementById('search-input');
const clearFiltersBtn = document.getElementById('clear-filters'); const clearFiltersBtn = document.getElementById('clear-filters');
@@ -26,6 +27,9 @@
if (regionFilter?.value) { if (regionFilter?.value) {
params.set('region', regionFilter.value); params.set('region', regionFilter.value);
} }
if (badgeFilter?.value) {
params.set('badge', badgeFilter.value);
}
if (tagFilter?.value) { if (tagFilter?.value) {
params.set('tag', tagFilter.value); params.set('tag', tagFilter.value);
} }
@@ -51,6 +55,9 @@
if (params.has('region') && regionFilter) { if (params.has('region') && regionFilter) {
regionFilter.value = params.get('region'); regionFilter.value = params.get('region');
} }
if (params.has('badge') && badgeFilter) {
badgeFilter.value = params.get('badge');
}
if (params.has('tag') && tagFilter) { if (params.has('tag') && tagFilter) {
tagFilter.value = params.get('tag'); tagFilter.value = params.get('tag');
} }
@@ -64,6 +71,7 @@
function filterEntries(shouldUpdateURL = true) { function filterEntries(shouldUpdateURL = true) {
const categoryValue = categoryFilter?.value.toLowerCase() || ''; const categoryValue = categoryFilter?.value.toLowerCase() || '';
const regionValue = regionFilter?.value.toLowerCase() || ''; const regionValue = regionFilter?.value.toLowerCase() || '';
const badgeValue = badgeFilter?.value.toLowerCase() || '';
const tagValue = tagFilter?.value.toLowerCase() || ''; const tagValue = tagFilter?.value.toLowerCase() || '';
const searchValue = searchInput?.value.toLowerCase().trim() || ''; const searchValue = searchInput?.value.toLowerCase().trim() || '';
@@ -72,15 +80,17 @@
entries.forEach(entry => { entries.forEach(entry => {
const entryCategory = (entry.dataset.category || '').toLowerCase(); const entryCategory = (entry.dataset.category || '').toLowerCase();
const entryRegion = (entry.dataset.region || '').toLowerCase(); const entryRegion = (entry.dataset.region || '').toLowerCase();
const entryBadge = (entry.dataset.badge || '').toLowerCase();
const entryTags = (entry.dataset.tags || '').toLowerCase().split(','); const entryTags = (entry.dataset.tags || '').toLowerCase().split(',');
const entryText = entry.textContent.toLowerCase(); const entryText = entry.textContent.toLowerCase();
const matchesCategory = !categoryValue || entryCategory === categoryValue; const matchesCategory = !categoryValue || entryCategory === categoryValue;
const matchesRegion = !regionValue || entryRegion === regionValue; const matchesRegion = !regionValue || entryRegion === regionValue;
const matchesBadge = !badgeValue || entryBadge === badgeValue;
const matchesTag = !tagValue || entryTags.includes(tagValue); const matchesTag = !tagValue || entryTags.includes(tagValue);
const matchesSearch = !searchValue || entryText.includes(searchValue); const matchesSearch = !searchValue || entryText.includes(searchValue);
const isVisible = matchesCategory && matchesRegion && matchesTag && matchesSearch; const isVisible = matchesCategory && matchesRegion && matchesBadge && matchesTag && matchesSearch;
entry.hidden = !isVisible; entry.hidden = !isVisible;
if (isVisible) visibleEntries++; if (isVisible) visibleEntries++;
@@ -111,6 +121,7 @@
const hasFilters = const hasFilters =
(categoryFilter?.value || '') !== '' || (categoryFilter?.value || '') !== '' ||
(regionFilter?.value || '') !== '' || (regionFilter?.value || '') !== '' ||
(badgeFilter?.value || '') !== '' ||
(tagFilter?.value || '') !== '' || (tagFilter?.value || '') !== '' ||
(searchInput?.value || '') !== ''; (searchInput?.value || '') !== '';
@@ -120,6 +131,7 @@
function clearFilters() { function clearFilters() {
if (categoryFilter) categoryFilter.value = ''; if (categoryFilter) categoryFilter.value = '';
if (regionFilter) regionFilter.value = ''; if (regionFilter) regionFilter.value = '';
if (badgeFilter) badgeFilter.value = '';
if (tagFilter) tagFilter.value = ''; if (tagFilter) tagFilter.value = '';
if (searchInput) searchInput.value = ''; if (searchInput) searchInput.value = '';
@@ -138,6 +150,8 @@
categoryFilter.value = filterValue; categoryFilter.value = filterValue;
} else if (filterType === 'region' && regionFilter) { } else if (filterType === 'region' && regionFilter) {
regionFilter.value = filterValue; regionFilter.value = filterValue;
} else if (filterType === 'badge' && badgeFilter) {
badgeFilter.value = filterValue;
} else if (filterType === 'tag' && tagFilter) { } else if (filterType === 'tag' && tagFilter) {
tagFilter.value = filterValue; tagFilter.value = filterValue;
} }
@@ -171,6 +185,10 @@
regionFilter.addEventListener('change', () => filterEntries()); regionFilter.addEventListener('change', () => filterEntries());
} }
if (badgeFilter) {
badgeFilter.addEventListener('change', () => filterEntries());
}
if (tagFilter) { if (tagFilter) {
tagFilter.addEventListener('change', () => filterEntries()); tagFilter.addEventListener('change', () => filterEntries());
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB