diff --git a/app/scripts/generate-web-stories.mjs b/app/scripts/generate-web-stories.mjs new file mode 100644 index 0000000..9bd6262 --- /dev/null +++ b/app/scripts/generate-web-stories.mjs @@ -0,0 +1,255 @@ +#!/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 = /
${esc(s.text)}
`, slideNum++, totalSlides)); + } else { + slides.push(slide(`slide-${slideNum}`, ogImage, ` +${esc(s.body)}
` : ''}`, slideNum++, totalSlides)); + } + } + + // CTA slide + slides.push(slide('cta', ogImage, ` + `, 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 ` + + + + + +