#!/usr/bin/env node // Generates CSS scroll-snap Web Stories for each published article. // Output: dist/stories/{slug}/index.html // Zero-CDN: inline CSS only, uses local OG images and local fonts. import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { TeknavData } from '../teknav-data.js'; const SITE_URL = (process.env.TEKNAV_SITE_URL || process.env.VITE_SITE_URL || 'https://www.teknav.ir').replace(/\/$/, ''); const today = new Date().toISOString().slice(0, 10); function esc(value) { return String(value ?? '') .replace(/&/g, '&') .replace(/"/g, '"') .replace(//g, '>'); } function clean(value) { return String(value ?? '').replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim(); } function truncate(value, max) { const text = clean(value); return text.length > max ? `${text.slice(0, max - 1).trim()}…` : text; } function isPastOrTodayDate(date) { const value = String(date ?? '').slice(0, 10); return /^\d{4}-\d{2}-\d{2}$/.test(value) && value <= today; } function isPublishedArticle(article) { if (article.status === 'منتشرشده') return true; if (article.status === 'پیش‌نویس' || article.status === 'در انتظار بررسی') return false; if (['rust-systems', 'venture-capital-ai'].includes(article.slug)) return false; return !!article.dateEn; } function extractSlides(article) { const content = String(article.content || ''); const slides = []; // Lead paragraph const leadMatch = content.match(/]*class="lead"[^>]*>([\s\S]*?)<\/p>/i) || content.match(/

([\s\S]*?)<\/p>/i); const lead = leadMatch ? truncate(clean(leadMatch[1]), 220) : truncate(article.summary || '', 220); if (lead) slides.push({ type: 'lead', text: lead }); // h2 headings + the text right after them const h2Pattern = /]*>([\s\S]*?)<\/h2>([\s\S]*?)(?=]*>([\s\S]*?)<\/p>/i); const body = bodyMatch ? truncate(clean(bodyMatch[1]), 200) : ''; if (heading) slides.push({ type: 'section', heading, body }); } return slides; } function slide(id, bgImage, content, slideNum, totalSlides) { const progress = Math.round((slideNum / totalSlides) * 100); return `

${content}
`; } function buildStory(article) { const slug = article.slug; const title = esc(article.title); const subtitle = esc(article.subtitle || article.summary || ''); const canonical = `${SITE_URL}/article/${slug}`; const storyUrl = `${SITE_URL}/stories/${slug}`; const slugImg = `/images/og/${slug}.jpg`; const hasSlugImg = existsSync(resolve(`dist${slugImg}`)); const ogImage = hasSlugImg ? slugImg : (article.ogImage || '/images/og/default.jpg'); const category = esc(article.categoryName || ''); const author = esc(article.authorName || ''); const date = esc(article.date || ''); const readTime = article.readTime || 5; const contentSlides = extractSlides(article); const totalSlides = 2 + contentSlides.length + 1; // cover + content + CTA const slides = []; let slideNum = 0; // Cover slide slides.push(slide('cover', ogImage, `
${category}

${title}

${author} · ${date} · ${readTime} دقیقه

← بکشید
`, slideNum++, totalSlides)); // Content slides for (const s of contentSlides) { if (s.type === 'lead') { slides.push(slide(`slide-${slideNum}`, ogImage, `

${esc(s.text)}

`, slideNum++, totalSlides)); } else { slides.push(slide(`slide-${slideNum}`, ogImage, `

${esc(s.heading)}

${s.body ? `

${esc(s.body)}

` : ''}`, slideNum++, totalSlides)); } } // CTA slide slides.push(slide('cta', ogImage, `

تحلیل کامل را در تکناو بخوانید

${subtitle}

خواندن مقاله →
`, slideNum++, totalSlides)); const jsonLd = JSON.stringify({ '@context': 'https://schema.org', '@graph': [ { '@type': ['Organization', 'NewsMediaOrganization'], '@id': `${SITE_URL}/#organization`, name: 'تکناو', url: SITE_URL, logo: { '@type': 'ImageObject', url: `${SITE_URL}/favicon.png` }, }, { '@type': ['NewsArticle', 'TechArticle'], headline: clean(article.title).slice(0, 110), description: clean(article.metaDescription || article.summary || '').slice(0, 160), image: `${SITE_URL}${ogImage}`, datePublished: article.dateEn, author: { '@type': 'Person', name: article.authorName || 'تکناو' }, publisher: { '@id': `${SITE_URL}/#organization` }, mainEntityOfPage: { '@type': 'WebPage', '@id': canonical }, url: storyUrl, }, ], }); return ` ${title} | تکناو
${slides.join('\n')}
`; } const publishedArticles = TeknavData.articles.filter( (a) => isPublishedArticle(a) && isPastOrTodayDate(a.dateEn), ); let count = 0; for (const article of publishedArticles) { const dir = resolve(`dist/stories/${article.slug}`); mkdirSync(dir, { recursive: true }); writeFileSync(`${dir}/index.html`, buildStory(article), 'utf8'); count++; } console.log(`[web-stories] generated ${count} story pages in dist/stories/`);