import fs from 'fs'; import path from 'path'; import matter from 'gray-matter'; import { marked } from 'marked'; const CONTENT_DIR = './content'; const TEMPLATE_DIR = './templates'; const PUBLIC_DIR = './public'; const STATIC_DIR = './static'; const DIST_DIR = './dist'; // Swedish translations for categories const CATEGORY_LABELS = { manufacturers: 'Tillverkare', products: 'Produkter', services: 'Tjänster', experiences: 'Upplevelser', shops: 'Butiker' }; // Badge tiers (highest to lowest) const BADGE_TIERS = { // Tier 1 - Gold (universal) 'akta-svenskt': { label: 'Äkta Svenskt', tier: 1, description: 'Helt svenskt: företag, produktion/drift och leverantörer' }, // Tier 2 - Silver (physical or digital) 'tillverkat-i-sverige': { label: 'Tillverkat i Sverige', tier: 2, description: 'Svenskt företag, tillverkat i Sverige' }, 'svenskt-moln': { label: 'Svenskt Moln', tier: 2, description: 'Svenskt företag med svenska servrar och hosting' }, // Tier 3 - Bronze (physical or digital) 'designat-i-sverige': { label: 'Designat i Sverige', tier: 3, description: 'Svensk design, produktion kan ske utomlands' }, 'utvecklat-i-sverige': { label: 'Utvecklat i Sverige', tier: 3, description: 'Svensk utveckling, hosting kan ske utomlands' }, // Tier 4 - Blue (universal) 'svenskt-foretag': { label: 'Svenskt Företag', tier: 4, description: 'Svenskregistrerat eller svenskgrundat företag' } }; function translateCategory(category) { return CATEGORY_LABELS[category] || category; } function getBadgeHTML(badgeKey) { if (!badgeKey || !BADGE_TIERS[badgeKey]) return ''; const badge = BADGE_TIERS[badgeKey]; return `${badge.label}`; } // Ensure directory exists function ensureDir(dir) { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } } // Copy directory recursively function copyDirRecursive(src, dest) { const items = fs.readdirSync(src, { withFileTypes: true }); for (const item of items) { const srcPath = path.join(src, item.name); const destPath = path.join(dest, item.name); if (item.isDirectory()) { ensureDir(destPath); copyDirRecursive(srcPath, destPath); } else { fs.copyFileSync(srcPath, destPath); } } } // Read all markdown files from a directory recursively function readContentFiles(dir) { const entries = []; if (!fs.existsSync(dir)) { return entries; } const items = fs.readdirSync(dir, { withFileTypes: true }); for (const item of items) { const fullPath = path.join(dir, item.name); if (item.isDirectory()) { entries.push(...readContentFiles(fullPath)); } else if (item.name.endsWith('.md')) { const content = fs.readFileSync(fullPath, 'utf-8'); const { data, content: body } = matter(content); const category = path.basename(path.dirname(fullPath)); const slug = path.basename(item.name, '.md'); entries.push({ ...data, category: data.category || category, body: marked(body), bodyRaw: body, slug, url: `${category}/${slug}.html`, sourcePath: fullPath }); } } return entries; } // Simple template replacement function renderTemplate(template, data) { let result = template; for (const [key, value] of Object.entries(data)) { const regex = new RegExp(`{{\\s*${key}\\s*}}`, 'g'); result = result.replace(regex, value ?? ''); } return result; } // Generate entry cards HTML function generateEntryCards(entries) { return entries.map(entry => { const imageHtml = entry.image ? `
${entry.title}
` : ''; const logoHtml = entry.logo ? `` : ''; const badgeHtml = getBadgeHTML(entry.badge); return `
${imageHtml}
${logoHtml}

${entry.title}

${badgeHtml}
${entry.body}
${entry.tags?.length ? `
${entry.tags.map(tag => ``).join('')}
` : ''}
`}).join('\n'); } // Generate single entry page HTML function generateEntryPage(entry, template) { const imageHtml = entry.image ? `
${entry.title}
` : ''; const logoHtml = entry.logo ? `` : ''; const badgeHtml = getBadgeHTML(entry.badge); const entryHtml = `
${imageHtml}
${logoHtml}

${entry.title}

${badgeHtml}
${translateCategory(entry.category) || ''} ${entry.region ? `${entry.region}` : ''}
${entry.body}
${entry.website ? `Besök webbplats →` : ''} ${entry.tags?.length ? `
${entry.tags.map(tag => `${tag}`).join('')}
` : ''} ← Tillbaka till alla poster
`; return renderTemplate(template, { title: `${entry.title} | Ursprung Sverige`, subtitle: entry.title, content: entryHtml, year: new Date().getFullYear(), footer: getFooter('../') }); } // Extract unique values for filters function extractFilters(entries) { 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 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, badges }; } // Generate filter HTML function generateFilterHTML(filters) { const categoryOptions = filters.categories.map(c => `` ).join(''); const regionOptions = filters.regions.map(r => `` ).join(''); const tagOptions = filters.tags.map(t => `` ).join(''); const badgeOptions = filters.badges.map(b => `` ).join(''); return `
`; } // 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 function build() { console.log('Building site...'); // Clean and create dist directory if (fs.existsSync(DIST_DIR)) { fs.rmSync(DIST_DIR, { recursive: true }); } ensureDir(DIST_DIR); // Read content const entries = readContentFiles(CONTENT_DIR); console.log(`Found ${entries.length} entries`); // Extract filters const filters = extractFilters(entries); // Read templates const indexTemplatePath = path.join(TEMPLATE_DIR, 'template.html'); const singleTemplatePath = path.join(TEMPLATE_DIR, 'single.html'); if (!fs.existsSync(indexTemplatePath)) { console.error('Template not found:', indexTemplatePath); process.exit(1); } const indexTemplate = fs.readFileSync(indexTemplatePath, 'utf-8'); const singleTemplate = fs.existsSync(singleTemplatePath) ? fs.readFileSync(singleTemplatePath, 'utf-8') : indexTemplate; // Fallback to index template // Generate index HTML const indexHtml = renderTemplate(indexTemplate, { title: 'Ursprung Sverige', subtitle: 'En kurerad samling av svenska produkter, tjänster, upplevelser och tillverkare', filters: generateFilterHTML(filters), entries: generateEntryCards(entries), entryCount: entries.length, year: new Date().getFullYear(), footer: getFooter('') }); // Write index.html fs.writeFileSync(path.join(DIST_DIR, 'index.html'), indexHtml); // Generate badges info page const badgesTemplatePath = path.join(TEMPLATE_DIR, 'om-markning.html'); if (fs.existsSync(badgesTemplatePath)) { const badgesTemplate = fs.readFileSync(badgesTemplatePath, 'utf-8'); const badgesHtml = renderTemplate(badgesTemplate, { year: new Date().getFullYear(), footer: getFooter('') }); fs.writeFileSync(path.join(DIST_DIR, 'om-markning.html'), badgesHtml); } // Generate about page const aboutTemplatePath = path.join(TEMPLATE_DIR, 'om-oss.html'); if (fs.existsSync(aboutTemplatePath)) { const aboutTemplate = fs.readFileSync(aboutTemplatePath, 'utf-8'); const aboutHtml = renderTemplate(aboutTemplate, { year: new Date().getFullYear(), footer: getFooter('') }); fs.writeFileSync(path.join(DIST_DIR, 'om-oss.html'), aboutHtml); } // Generate individual entry pages for (const entry of entries) { const categoryDir = path.join(DIST_DIR, entry.category); ensureDir(categoryDir); const entryHtml = generateEntryPage(entry, singleTemplate); fs.writeFileSync(path.join(categoryDir, `${entry.slug}.html`), entryHtml); } console.log(`Generated ${entries.length} entry pages`); // Copy public assets (CSS, JS - small files) if (fs.existsSync(PUBLIC_DIR)) { copyDirRecursive(PUBLIC_DIR, DIST_DIR); } // Symlink static folder (images, large assets - avoids copying) const staticDest = path.join(DIST_DIR, 'static'); if (fs.existsSync(STATIC_DIR) && !fs.existsSync(staticDest)) { const absoluteStatic = path.resolve(STATIC_DIR); fs.symlinkSync(absoluteStatic, staticDest); } console.log('Build complete! Output in', DIST_DIR); } // Watch mode if (process.argv.includes('--watch')) { console.log('Watching for changes...'); build(); const watchDirs = [CONTENT_DIR, TEMPLATE_DIR, PUBLIC_DIR]; for (const dir of watchDirs) { if (fs.existsSync(dir)) { fs.watch(dir, { recursive: true }, (event, filename) => { console.log(`\nChange detected: ${filename}`); build(); }); } } } else { build(); }