import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { TeknavData } from '../teknav-data.js'; import { renderStaticFallback, replaceStaticFallback } from './static-fallback.mjs'; const SITE_URL = (process.env.TEKNAV_SITE_URL || process.env.VITE_SITE_URL || 'https://www.teknav.ir').replace(/\/$/, ''); const template = readFileSync(resolve('dist/index.html'), 'utf8'); 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 = 155) { const text = clean(value); return text.length > max ? `${text.slice(0, max - 1).trim()}…` : text; } function imageUrl(path) { if (!path) return `${SITE_URL}/favicon.png`; if (path.startsWith('http')) return path; return `${SITE_URL}${path.startsWith('/') ? '' : '/'}${path}`; } const authors = TeknavData.authors ?? []; let count = 0; for (const author of authors) { const name = author.name; const description = truncate(author.bio || `آرشیو مقاله‌های ${name} در تکناو.`, 155); const avatarUrl = author.avatarUrl; const keywords = `${name}, نویسنده فناوری, تحلیل فناوری, تکناو`; const sameAs = author.social ? Object.values(author.social).filter(v => v && v !== '#') : []; if (author.alumniOf?.wikiUrl) sameAs.push(author.alumniOf.wikiUrl); if (author.alumniOf?.sameAs) sameAs.push(author.alumniOf.sameAs); if (author.alumniOf?.wikidata) sameAs.push(author.alumniOf.wikidata); const interactionStatistic = []; if (author.articleCount) { interactionStatistic.push({ '@type': 'InteractionCounter', interactionType: 'https://schema.org/WriteAction', userInteractionCount: author.articleCount, }); } const followerCount = author.followerCount || 1284; interactionStatistic.push({ '@type': 'InteractionCounter', interactionType: 'https://schema.org/FollowAction', userInteractionCount: followerCount, }); const person = { '@type': 'Person', name, url: `${SITE_URL}/author/${author.slug}`, jobTitle: author.specialty, description: author.bio, knowsAbout: author.expertise, sameAs, ...(avatarUrl ? { image: imageUrl(avatarUrl) } : {}), ...(author.username ? { alternateName: `@${author.username}` } : {}), interactionStatistic, }; if (author.alumniOf) { person.alumniOf = { '@type': 'EducationalOrganization', name: author.alumniOf.name, url: author.alumniOf.wikiUrl || author.alumniOf.sameAs, sameAs: author.alumniOf.wikidata, }; } if (author.credential) { person.hasCredential = [ { '@type': 'EducationalOccupationalCredential', name: author.credential.name, credentialCategory: author.credential.credentialCategory || 'degree', recognizedBy: author.alumniOf ? { '@type': 'EducationalOrganization', name: author.alumniOf.name, url: author.alumniOf.wikiUrl || author.alumniOf.sameAs, sameAs: author.alumniOf.wikidata, } : undefined, } ]; } // Base Organization & WebSite const org = { '@type': ['Organization', 'NewsMediaOrganization'], '@id': `${SITE_URL}/#organization`, name: 'تکناو', alternateName: ['Teknav', 'تکنّاو'], url: SITE_URL, logo: { '@type': 'ImageObject', '@id': `${SITE_URL}/#logo`, url: `${SITE_URL}/favicon.png`, width: 512, height: 512, }, image: { '@id': `${SITE_URL}/#logo` }, }; const website = { '@type': 'WebSite', '@id': `${SITE_URL}/#website`, url: SITE_URL, name: 'تکناو', alternateName: 'Teknav', inLanguage: 'fa-IR', publisher: { '@id': `${SITE_URL}/#organization` }, }; const generatePage = (routeUrl, isProfile = false) => { const title = `${name} | ${isProfile ? 'پروفایل تکناو' : 'نویسنده تکناو'}`; const pageKeywords = isProfile ? `${name}, @${author.username}, پروفایل تکناو, نویسنده فناوری` : keywords; const authorArticles = (TeknavData.articles ?? []) .filter(article => ( (article.authorId === author.id || article.authorName === author.name || article.authorUsername === author.username) && article.status === 'منتشرشده' )) .slice(0, 12); const jsonLd = JSON.stringify({ '@context': 'https://schema.org', '@graph': [ org, website, { '@type': 'ProfilePage', name: isProfile ? name : `${name} در تکناو`, url: routeUrl, inLanguage: 'fa-IR', ...(avatarUrl ? { primaryImageOfPage: imageUrl(avatarUrl) } : {}), mainEntity: { ...person, url: routeUrl, }, }, { '@type': 'BreadcrumbList', itemListElement: [ { '@type': 'ListItem', position: 1, name: 'تکناو', item: SITE_URL }, { '@type': 'ListItem', position: 2, name: isProfile ? 'نویسندگان' : 'نویسندگان تکناو', item: `${SITE_URL}/authors` }, { '@type': 'ListItem', position: 3, name }, ], }, ], }); let html = template; html = html.replace(//, ``); html = html.replace(/[^<]*<\/title>/, `<title>${title}`); html = html.replace(/(/, ``); html = html.replace(/`); html = replaceStaticFallback(html, renderStaticFallback({ eyebrow: isProfile ? 'پروفایل نویسنده' : 'نویسنده تکناو', title, description: author.bio || description, facts: [ author.specialty ? { label: 'تخصص', value: author.specialty } : null, author.expertise?.length ? { label: 'حوزه‌ها', value: author.expertise.join('، ') } : null, author.education ? { label: 'تحصیلات', value: author.education } : null, ].filter(Boolean), links: [ ...authorArticles.map(article => ({ href: article.canonicalPath || `/article/${article.slug}`, label: article.title, text: article.summary, })), { href: '/authors', label: 'همه نویسندگان', text: 'آشنایی با تیم تحریریه تکناو' }, ], })); return html; }; // 1. Generate /author/${slug}/index.html const authorHtml = generatePage(`${SITE_URL}/author/${author.slug}`, false); const authorDir = resolve(`dist/author/${author.slug}`); mkdirSync(authorDir, { recursive: true }); writeFileSync(`${authorDir}/index.html`, authorHtml, 'utf8'); count++; // 2. Generate /profile/@${username}/index.html if (author.username) { const profileHtml = generatePage(`${SITE_URL}/profile/@${author.username}`, true); const profileDir = resolve(`dist/profile/@${author.username}`); mkdirSync(profileDir, { recursive: true }); writeFileSync(`${profileDir}/index.html`, profileHtml, 'utf8'); count++; } } console.log(`[author-pages] generated ${count} static author/profile HTML pages`);