Compare commits

..

4 Commits

Author SHA1 Message Date
28f232571d Refactor site templates to include footer partial and update page paths
- Added a footer partial to improve code reusability across templates.
- Updated paths for the 'about' and 'badges' pages to 'om-oss.html' and 'om-markning.html', respectively.
- Removed redundant footer code from individual templates, replacing it with a dynamic footer inclusion.
2026-01-26 00:39:41 +01:00
cfb35a3c38 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.
2026-01-25 23:37:20 +01:00
2519520e1b Added Jeansverket 2026-01-25 23:22:26 +01:00
6d43b3c844 Update package dependencies and enhance development setup
- Added `browser-sync` and `concurrently` as devDependencies for improved development workflow.
- Updated the `dev` script to run the build process and start a live server concurrently.
- Enhanced the about page with a link to the source code repository hosted on a self-managed Gitea instance.
2026-01-25 23:18:32 +01:00
13 changed files with 1984 additions and 54 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)
@@ -198,7 +199,8 @@ function generateEntryPage(entry, template) {
title: `${entry.title} | Ursprung Sverige`, title: `${entry.title} | Ursprung Sverige`,
subtitle: entry.title, subtitle: entry.title,
content: entryHtml, content: entryHtml,
year: new Date().getFullYear() year: new Date().getFullYear(),
footer: getFooter('../')
}); });
} }
@@ -207,8 +209,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 +233,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 +253,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">
@@ -257,6 +276,19 @@ function generateFilterHTML(filters) {
`; `;
} }
// Load footer partial
function getFooter(rootPath = '') {
const footerPath = path.join(TEMPLATE_DIR, 'partials', 'footer.html');
if (!fs.existsSync(footerPath)) {
return '';
}
const footerTemplate = fs.readFileSync(footerPath, 'utf-8');
return renderTemplate(footerTemplate, {
year: new Date().getFullYear(),
rootPath
});
}
// Main build function // Main build function
function build() { function build() {
console.log('Building site...'); console.log('Building site...');
@@ -295,28 +327,31 @@ function build() {
filters: generateFilterHTML(filters), filters: generateFilterHTML(filters),
entries: generateEntryCards(entries), entries: generateEntryCards(entries),
entryCount: entries.length, entryCount: entries.length,
year: new Date().getFullYear() year: new Date().getFullYear(),
footer: getFooter('')
}); });
// Write index.html // Write index.html
fs.writeFileSync(path.join(DIST_DIR, 'index.html'), indexHtml); fs.writeFileSync(path.join(DIST_DIR, 'index.html'), indexHtml);
// Generate badges info page // Generate badges info page
const badgesTemplatePath = path.join(TEMPLATE_DIR, 'badges.html'); const badgesTemplatePath = path.join(TEMPLATE_DIR, 'om-markning.html');
if (fs.existsSync(badgesTemplatePath)) { if (fs.existsSync(badgesTemplatePath)) {
const badgesTemplate = fs.readFileSync(badgesTemplatePath, 'utf-8'); const badgesTemplate = fs.readFileSync(badgesTemplatePath, 'utf-8');
const badgesHtml = renderTemplate(badgesTemplate, { const badgesHtml = renderTemplate(badgesTemplate, {
year: new Date().getFullYear() year: new Date().getFullYear(),
footer: getFooter('')
}); });
fs.writeFileSync(path.join(DIST_DIR, 'om-markning.html'), badgesHtml); fs.writeFileSync(path.join(DIST_DIR, 'om-markning.html'), badgesHtml);
} }
// Generate about page // Generate about page
const aboutTemplatePath = path.join(TEMPLATE_DIR, 'about.html'); const aboutTemplatePath = path.join(TEMPLATE_DIR, 'om-oss.html');
if (fs.existsSync(aboutTemplatePath)) { if (fs.existsSync(aboutTemplatePath)) {
const aboutTemplate = fs.readFileSync(aboutTemplatePath, 'utf-8'); const aboutTemplate = fs.readFileSync(aboutTemplatePath, 'utf-8');
const aboutHtml = renderTemplate(aboutTemplate, { const aboutHtml = renderTemplate(aboutTemplate, {
year: new Date().getFullYear() year: new Date().getFullYear(),
footer: getFooter('')
}); });
fs.writeFileSync(path.join(DIST_DIR, 'om-oss.html'), aboutHtml); fs.writeFileSync(path.join(DIST_DIR, 'om-oss.html'), aboutHtml);
} }

View File

@@ -0,0 +1,9 @@
---
title: Jeansverket
region: Borås
tags: [jeans, kläder, denim, hantverk]
website: https://www.jeansverket.se
badge: tillverkat-i-sverige
---
Jeansverket är ett svenskt jeansföretag som tillverkar jeans i Borås. Med fokus på kvalitet och hållbarhet erbjuder de jeans som är skapade för att hålla länge.

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.

1883
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,11 +5,15 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "node build.js", "build": "node build.js",
"dev": "node build.js --watch", "dev": "concurrently \"node build.js --watch\" \"browser-sync start --server dist --files dist --no-open\"",
"serve": "npx --yes serve dist -p 3000" "serve": "npx --yes serve dist -p 3000"
}, },
"dependencies": { "dependencies": {
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"marked": "^12.0.0" "marked": "^12.0.0"
},
"devDependencies": {
"browser-sync": "^3.0.2",
"concurrently": "^8.2.2"
} }
} }

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

View File

@@ -75,15 +75,6 @@
</div> </div>
</main> </main>
<footer class="site-footer"> {{ footer }}
<div class="container">
<p>Ursprung Sverige &copy; {{ year }}</p>
<p class="footer-tagline">Stöd svensk ekonomi och kultur</p>
<nav class="footer-nav">
<a href="om-oss.html">Om oss</a>
<a href="om-markning.html">Om märkning</a>
</nav>
</div>
</footer>
</body> </body>
</html> </html>

View File

@@ -51,6 +51,7 @@
<section class="about-section"> <section class="about-section">
<h2>Öppen Källkod</h2> <h2>Öppen Källkod</h2>
<p>Webbplatsen är byggd med enkel teknik ren HTML, CSS och minimal JavaScript. Ingen spårningskod, inga tunga ramverk, inga dolda skript.</p> <p>Webbplatsen är byggd med enkel teknik ren HTML, CSS och minimal JavaScript. Ingen spårningskod, inga tunga ramverk, inga dolda skript.</p>
<p>All källkod finns tillgänglig på <a href="https://git.4z.nu/jonas/ursprungsverige" target="_blank" rel="noopener">git.4z.nu</a> en egenhostad Gitea-instans som också körs på servrar i Lerum.</p>
</section> </section>
<section class="about-section"> <section class="about-section">
@@ -64,15 +65,6 @@
</div> </div>
</main> </main>
<footer class="site-footer"> {{ footer }}
<div class="container">
<p>Ursprung Sverige &copy; {{ year }}</p>
<p class="footer-tagline">Stöd svensk ekonomi och kultur</p>
<nav class="footer-nav">
<a href="om-oss.html">Om oss</a>
<a href="om-markning.html">Om märkning</a>
</nav>
</div>
</footer>
</body> </body>
</html> </html>

View File

@@ -21,15 +21,6 @@
</div> </div>
</main> </main>
<footer class="site-footer"> {{ footer }}
<div class="container">
<p>Ursprung Sverige &copy; {{ year }}</p>
<p class="footer-tagline">Stöd svensk ekonomi och kultur</p>
<nav class="footer-nav">
<a href="../om-oss.html">Om oss</a>
<a href="../om-markning.html">Om märkning</a>
</nav>
</div>
</footer>
</body> </body>
</html> </html>

View File

@@ -43,16 +43,7 @@
</div> </div>
</main> </main>
<footer class="site-footer"> {{ footer }}
<div class="container">
<p>Ursprung Sverige &copy; {{ year }}</p>
<p class="footer-tagline">Stöd svensk ekonomi och kultur</p>
<nav class="footer-nav">
<a href="om-oss.html">Om oss</a>
<a href="om-markning.html">Om märkning</a>
</nav>
</div>
</footer>
<script src="filter.js"></script> <script src="filter.js"></script>
</body> </body>