import { useEffect, useMemo, useState } from 'react'; import { TeknavData } from '../../teknav-data.js'; import { api } from './api.js'; import { contentApi } from './content-api.js'; import { useNav } from '../../teknav-ui.jsx'; const SITE_URL = (import.meta.env.VITE_SITE_URL || 'https://www.teknav.ir').replace(/\/$/, ''); const SITE_NAME = 'تکناو'; const DEFAULT_TITLE = 'تکناو | تحلیل فناوری، هوش مصنوعی و امنیت سایبری'; const DEFAULT_DESCRIPTION = 'تکناو رسانه فارسی تحلیل فناوری، هوش مصنوعی، علم داده، امنیت سایبری، نرم‌افزار، سخت‌افزار و آینده تکنولوژی است.'; const DEFAULT_KEYWORDS = 'تکناو, فناوری, هوش مصنوعی, علم داده, امنیت سایبری, نرم افزار, سخت افزار, استارتاپ, آینده فناوری, تحلیل تکنولوژی'; const DEFAULT_IMAGE = `${SITE_URL}/images/og/default.jpg`; function cleanText(value, fallback = '') { return String(value ?? fallback) .replace(/<[^>]+>/g, ' ') .replace(/\s+/g, ' ') .trim(); } function truncate(value, max = 155) { const text = cleanText(value); return text.length > max ? `${text.slice(0, max - 1).trim()}…` : text; } function absoluteUrl(path = '/') { if (/^https?:\/\//i.test(path)) return path; const normalized = path.startsWith('/') ? path : `/${path}`; return `${SITE_URL}${normalized}`; } function imageUrl(value) { return value ? absoluteUrl(value) : DEFAULT_IMAGE; } function uniqueList(values, limit = 18) { const seen = new Set(); return values .map((value) => cleanText(value)) .filter(Boolean) .filter((value) => { const key = value.toLocaleLowerCase('fa-IR'); if (seen.has(key)) return false; seen.add(key); return true; }) .slice(0, limit); } function articleKeywords(article, category, author) { return uniqueList([ ...(article.keywords || []), ...(article.tags || []), article.title, article.subtitle, article.categoryName || category?.name, article.type, author?.specialty, 'تحلیل فارسی فناوری', 'تکناو', ]); } const ENTITY_WIKIDATA_MAP = { 'هوش مصنوعی': 'https://www.wikidata.org/wiki/Q11660', 'ai': 'https://www.wikidata.org/wiki/Q11660', 'artificial intelligence': 'https://www.wikidata.org/wiki/Q11660', 'rag': 'https://www.wikidata.org/wiki/Q108745802', 'retrieval-augmented generation': 'https://www.wikidata.org/wiki/Q108745802', 'امنیت سایبری': 'https://www.wikidata.org/wiki/Q3510502', 'cybersecurity': 'https://www.wikidata.org/wiki/Q3510502', 'علم داده': 'https://www.wikidata.org/wiki/Q19847', 'data science': 'https://www.wikidata.org/wiki/Q19847', 'رایانش کوانتومی': 'https://www.wikidata.org/wiki/Q11190', 'quantum computing': 'https://www.wikidata.org/wiki/Q11190', 'نرم‌افزار': 'https://www.wikidata.org/wiki/Q7397', 'software': 'https://www.wikidata.org/wiki/Q7397', 'سخت‌افزار': 'https://www.wikidata.org/wiki/Q11641', 'hardware': 'https://www.wikidata.org/wiki/Q11641', 'استارتاپ': 'https://www.wikidata.org/wiki/Q300627', 'startup': 'https://www.wikidata.org/wiki/Q300627', 'پایگاه داده': 'https://www.wikidata.org/wiki/Q8513', 'database': 'https://www.wikidata.org/wiki/Q8513', 'امنیت': 'https://www.wikidata.org/wiki/Q2526255', 'security': 'https://www.wikidata.org/wiki/Q2526255', 'شبکه': 'https://www.wikidata.org/wiki/Q1301371', 'network': 'https://www.wikidata.org/wiki/Q1301371', 'اینترنت': 'https://www.wikidata.org/wiki/Q75', 'internet': 'https://www.wikidata.org/wiki/Q75', 'وب': 'https://www.wikidata.org/wiki/Q466', 'web': 'https://www.wikidata.org/wiki/Q466', 'فناوری': 'https://www.wikidata.org/wiki/Q11016', 'technology': 'https://www.wikidata.org/wiki/Q11016', }; const GLOSSARY_SAMEAS_MAP = { ai: [ 'https://www.wikidata.org/wiki/Q11660', 'https://fa.wikipedia.org/wiki/%D9%87%D9%88%D8%B4_%D9%85%D8%B5%D9%86%D9%88%D8%B5%DB%8C' ], llm: [ 'https://www.wikidata.org/wiki/Q115305900', 'https://fa.wikipedia.org/wiki/%D9%85%D8%AF%D9%84_%D8%B2%D8%A8%D8%A7%D9%86%DB%8C_%D8%A8%D8%B2%D8%B1%DA%AF' ], rag: [ 'https://www.wikidata.org/wiki/Q124378129', 'https://en.wikipedia.org/wiki/Retrieval-augmented_generation' ], 'prompt-injection': [ 'https://www.wikidata.org/wiki/Q117409023', 'https://en.wikipedia.org/wiki/Prompt_injection' ], 'vector-database': [ 'https://www.wikidata.org/wiki/Q119790697', 'https://en.wikipedia.org/wiki/Vector_database' ], 'agentic-ai': [ 'https://www.wikidata.org/wiki/Q127415174' ] }; function renderAuthorPerson(author, defaultName, defaultJob, defaultBio, defaultExpertise, defaultUrl, defaultImage) { if (!author) { const avatar = defaultImage ? imageUrl(defaultImage) : undefined; return { '@type': 'Person', name: defaultName, jobTitle: defaultJob, description: defaultBio, knowsAbout: defaultExpertise, url: defaultUrl, ...(avatar ? { image: avatar } : {}), }; } const name = author.name || defaultName; const url = defaultUrl || (author.slug ? absoluteUrl(`/author/${author.slug}`) : undefined); 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 avatarUrl = author.avatarUrl || defaultImage; const image = avatarUrl ? imageUrl(avatarUrl) : undefined; const alternateName = author.username ? `@${author.username}` : undefined; 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, jobTitle: author.specialty || defaultJob, description: author.bio || defaultBio, knowsAbout: author.expertise || defaultExpertise, sameAs, ...(image ? { image } : {}), ...(alternateName ? { alternateName } : {}), 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 || author.credential.category || 'degree', }; } return person; } function articleEntities(article, category) { return uniqueList([ article.categoryName || category?.name, ...(article.tags || []), ...(article.keywords || []), ], 24).map((name) => { const key = name.toLocaleLowerCase('fa-IR').trim(); const sameAs = ENTITY_WIKIDATA_MAP[key]; return { '@type': 'Thing', name, ...(sameAs ? { sameAs } : {}), }; }); } function articleSections(content) { return [...String(content || '').matchAll(/]*>(.*?)<\/h2>/g)] .map((match) => cleanText(match[1])) .filter(Boolean) .slice(0, 12); } function articleCitationUrls(article) { return uniqueList(article?.citationUrls || [], 20); } function articleFaqJsonLd(article, path) { if (!article?.faqs?.length) return null; return { '@type': 'FAQPage', '@id': `${absoluteUrl(path)}#faq`, mainEntity: article.faqs.map(([question, answer]) => ({ '@type': 'Question', name: cleanText(question), acceptedAnswer: { '@type': 'Answer', text: cleanText(answer), }, })), }; } function articleBenchmarkJsonLd(article, author, path) { if (!article?.identifier && !article?.version && !article?.researchQuestions?.length) return null; return { '@type': 'CreativeWork', '@id': `${absoluteUrl(path)}#benchmark`, name: `${cleanText(article.title)} benchmark protocol`, alternateName: cleanText(article.slug), identifier: article.identifier, version: article.version, url: absoluteUrl(path), inLanguage: article.language || article.inLanguage || 'fa-IR', creator: { '@type': 'Person', name: article.authorName || author?.name, url: author ? absoluteUrl(`/author/${author.slug}`) : undefined, }, about: (article.researchQuestions || []).map((question) => ({ '@type': 'Question', name: cleanText(question), })), conditionsOfAccess: 'Protocol article; empirical dataset release depends on copyright and licensing clearance.', }; } function articleAcademicMeta(article, author, canonical) { const published = article.publishedAt || article.dateEn; const modified = article.dateModified || article.updatedAt || published; return { citation_title: cleanText(article.title), citation_author: cleanText(article.authorName || author?.name || ''), citation_publication_date: published, citation_online_date: published, citation_lastdate: modified, citation_journal_title: 'Teknav', citation_language: article.language || article.inLanguage || 'fa-IR', citation_public_url: canonical, citation_keywords: articleKeywords(article, findCategory(article), author).join('; '), 'DC.title': cleanText(article.title), 'DC.creator': cleanText(article.authorName || author?.name || ''), 'DC.date': published, 'DC.language': article.language || article.inLanguage || 'fa-IR', 'DC.identifier': article.identifier || canonical, }; } function setMeta(attr, key, content) { if (!content) return; let el = document.head.querySelector(`meta[${attr}="${key}"]`); if (!el) { el = document.createElement('meta'); el.setAttribute(attr, key); document.head.appendChild(el); } el.setAttribute('content', content); } function setNamedMetaList(name, values) { document.head.querySelectorAll(`meta[name="${name}"][data-teknav-seo-list="true"]`).forEach((el) => el.remove()); values.filter(Boolean).forEach((value) => { const el = document.createElement('meta'); el.setAttribute('name', name); el.dataset.teknavSeoList = 'true'; el.setAttribute('content', value); document.head.appendChild(el); }); } function setLink(rel, href, extra = {}) { let el = document.head.querySelector(`link[rel="${rel}"]${extra.hreflang ? `[hreflang="${extra.hreflang}"]` : ''}`); if (!el) { el = document.createElement('link'); el.setAttribute('rel', rel); if (extra.hreflang) el.setAttribute('hreflang', extra.hreflang); document.head.appendChild(el); } el.setAttribute('href', href); } function setJsonLd(items) { document.head.querySelectorAll('script[data-teknav-seo="jsonld"]').forEach((el) => el.remove()); const script = document.createElement('script'); script.type = 'application/ld+json'; script.dataset.teknavSeo = 'jsonld'; script.textContent = JSON.stringify({ '@context': 'https://schema.org', '@graph': items.filter(Boolean), }); document.head.appendChild(script); } function baseGraph() { return [ { '@type': ['Organization', 'NewsMediaOrganization'], '@id': `${SITE_URL}/#organization`, name: SITE_NAME, alternateName: ['Teknav', 'تکنّاو'], url: SITE_URL, description: DEFAULT_DESCRIPTION, logo: { '@type': 'ImageObject', '@id': `${SITE_URL}/#logo`, url: `${SITE_URL}/favicon.png`, width: { '@type': 'QuantitativeValue', value: 512 }, height: { '@type': 'QuantitativeValue', value: 512 }, caption: 'تکناو - رسانه فارسی فناوری', }, image: { '@id': `${SITE_URL}/#logo` }, publishingPrinciples: `${SITE_URL}/about`, masthead: `${SITE_URL}/authors`, areaServed: { '@type': 'Country', name: 'Iran' }, audience: { '@type': 'Audience', audienceType: 'فارسی‌زبانان علاقه‌مند به فناوری' }, sameAs: [ 'https://twitter.com/teknavir', 'https://x.com/teknavir', 'https://linkedin.com/company/teknav', ], contactPoint: { '@type': 'ContactPoint', email: 'info@teknav.ir', contactType: 'customer service', availableLanguage: { '@type': 'Language', name: 'Persian' }, }, }, { '@type': 'WebSite', '@id': `${SITE_URL}/#website`, url: SITE_URL, name: SITE_NAME, alternateName: 'Teknav', description: DEFAULT_DESCRIPTION, inLanguage: 'fa-IR', publisher: { '@id': `${SITE_URL}/#organization` }, potentialAction: { '@type': 'SearchAction', target: { '@type': 'EntryPoint', urlTemplate: `${SITE_URL}/search?q={search_term_string}`, }, 'query-input': 'required name=search_term_string', }, }, ]; } function breadcrumb(items) { return { '@type': 'BreadcrumbList', itemListElement: items.map((item, index) => ({ '@type': 'ListItem', position: index + 1, name: item.name, item: item.path ? absoluteUrl(item.path) : undefined, })), }; } function itemList(items, path) { return { '@type': 'ItemList', url: absoluteUrl(path), itemListElement: items.slice(0, 15).map((item, index) => ({ '@type': 'ListItem', position: index + 1, url: absoluteUrl(item.canonicalPath || `/article/${item.slug}`), name: item.title, })), }; } function findCategory(article) { return TeknavData.categories.find((c) => ( c.id === article?.category || c.slug === article?.category || c.slug === article?.categorySlug || c.name === article?.categoryName )); } function findAuthor(article) { return TeknavData.authors.find((a) => ( a.id === article?.authorId || a.slug === article?.authorSlug || a.name === article?.authorName )); } function articleSeo(article) { const category = findCategory(article); const author = findAuthor(article); const path = article.canonicalPath || `/article/${article.slug}`; const title = article.seoTitle || article.ogTitle || `${article.title} | تکناو`; const description = truncate(article.metaDescription || article.ogDescription || article.summary || article.subtitle || article.content, 160); const datePublished = article.publishedAt || article.dateEn; const dateModified = article.dateModified || article.updatedAt || article.publishedAt || article.dateEn; const image = imageUrl(article.ogImage || article.imageUrl || article.coverUrl); const keywords = articleKeywords(article, category, author); const sections = articleSections(article.content); const language = article.language || article.inLanguage || 'fa-IR'; const canonical = absoluteUrl(path); const citations = articleCitationUrls(article); const articleTypes = article.identifier || citations.length ? ['NewsArticle', 'TechArticle', 'ScholarlyArticle'] : ['NewsArticle', 'TechArticle']; const about = [ category ? { '@type': 'Thing', name: category.name, url: absoluteUrl(`/topics/${category.slug}`), sameAs: ENTITY_WIKIDATA_MAP[category.slug.toLocaleLowerCase('fa-IR')] || ENTITY_WIKIDATA_MAP[category.name.toLocaleLowerCase('fa-IR')], } : null, ...articleEntities(article, category).slice(0, 5), ].filter(Boolean); const mentions = articleEntities(article, category); return { title, description, keywords: keywords.join(', '), language, canonical, type: 'article', image, academicMeta: articleAcademicMeta(article, author, canonical), citations, jsonLd: [ ...baseGraph(), { '@type': articleTypes, '@id': `${canonical}#article`, mainEntityOfPage: { '@type': 'WebPage', '@id': canonical, }, headline: cleanText(article.title).slice(0, 110), alternativeHeadline: cleanText(article.subtitle || article.summary).slice(0, 110) || undefined, description, abstract: cleanText(article.summary || article.subtitle || description), articleBody: truncate(article.content, 5000), image: { '@type': 'ImageObject', url: image, width: 1200, height: 630 }, datePublished, dateModified, inLanguage: language, articleSection: article.categoryName || category?.name, keywords, wordCount: cleanText(article.content).split(/\s+/).filter(Boolean).length || undefined, timeRequired: article.readTime ? `PT${article.readTime}M` : undefined, genre: article.type, teaches: sections, identifier: article.identifier, version: article.version, citation: citations, author: renderAuthorPerson(author, article.authorName, author?.specialty, author?.bio, author?.expertise), publisher: { '@id': `${SITE_URL}/#organization` }, about, mentions, isPartOf: [ { '@id': `${SITE_URL}/#website` }, category ? { '@type': 'CollectionPage', '@id': absoluteUrl(`/topics/${category.slug}`) } : null, ].filter(Boolean), speakable: { '@type': 'SpeakableSpecification', xpath: ['/html/head/title', '/html/head/meta[@name="description"]/@content'] }, isAccessibleForFree: true, educationalLevel: 'Intermediate', ...(article.factCheckedAt ? { reviewedBy: { '@id': `${SITE_URL}/#organization` }, lastReviewed: article.factCheckedAt } : {}) }, articleBenchmarkJsonLd(article, author, path), articleFaqJsonLd(article, path), breadcrumb([ { name: SITE_NAME, path: '/' }, { name: 'مقاله‌ها', path: '/articles' }, category ? { name: category.name, path: `/category/${category.slug}` } : null, { name: article.title }, ].filter(Boolean)), ], }; } function pageSeo(page, data) { if (page.startsWith('/article/') && data.article) return articleSeo(data.article); if (page.startsWith('/category/')) { const slug = page.replace('/category/', ''); const category = data.category || TeknavData.categories.find((c) => c.slug === slug || c.id === slug); const name = category?.name || 'دسته‌بندی فناوری'; const description = truncate(category?.description || `مقاله‌ها و تحلیل‌های فارسی تکناو درباره ${name}.`, 155); return { title: `${name} | مقاله‌ها و تحلیل‌های تکناو`, description, keywords: `${name}, مقاله ${name}, تحلیل ${name}, فناوری, تکناو`, canonical: absoluteUrl(`/category/${slug}`), type: 'website', image: DEFAULT_IMAGE, jsonLd: [ ...baseGraph(), { '@type': 'CollectionPage', name, description, url: absoluteUrl(`/category/${slug}`), inLanguage: 'fa-IR' }, breadcrumb([{ name: SITE_NAME, path: '/' }, { name: name }]), ], }; } if (page.startsWith('/tag/')) { const tag = decodeURIComponent(page.replace('/tag/', '')); const articles = TeknavData.articles .filter((article) => article.tags?.includes(tag) && article.status !== 'پیش‌نویس' && article.status !== 'در انتظار بررسی') .sort((a, b) => String(b.dateEn || '').localeCompare(String(a.dateEn || ''))); return { title: `برچسب ${tag} | مقاله‌های تکنـاو`, description: truncate(`همه مقاله‌های تکنـاو درباره ${tag}، همراه با تحلیل فارسی، داده و مسیرهای مرتبط برای مطالعه بیشتر.`, 155), keywords: `${tag}, تکنـاو, مقاله فناوری, تحلیل فناوری`, canonical: absoluteUrl(`/tag/${encodeURIComponent(tag)}`), type: 'website', image: DEFAULT_IMAGE, jsonLd: [ ...baseGraph(), { '@type': 'CollectionPage', name: `برچسب ${tag}`, url: absoluteUrl(`/tag/${encodeURIComponent(tag)}`), inLanguage: 'fa-IR', mainEntity: itemList(articles, `/tag/${encodeURIComponent(tag)}`), }, breadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'برچسب‌ها', path: '/articles' }, { name: tag }]), ], }; } if (page.startsWith('/glossary/')) { const slug = page.replace('/glossary/', ''); const term = TeknavData.glossary?.find((item) => item.slug === slug); const path = `/glossary/${slug}`; const title = term ? `${term.term} چیست؟ | فرهنگ واژگان تکنّاو` : 'فرهنگ واژگان فناوری | تکنّاو'; const description = truncate(term?.summary || 'تعریف دقیق و فارسی واژه‌های فنی فناوری، هوش مصنوعی، داده، امنیت و نرم‌افزار در تکنّاو.', 160); return { title, description, keywords: [term?.term, term?.english, 'فرهنگ واژگان فناوری', 'تکنّاو'].filter(Boolean).join(', '), canonical: absoluteUrl(path), type: 'website', image: DEFAULT_IMAGE, jsonLd: [ ...baseGraph(), { '@type': 'DefinedTerm', name: term?.term, alternateName: term?.english, description, inDefinedTermSet: absoluteUrl('/glossary'), url: absoluteUrl(path), sameAs: GLOSSARY_SAMEAS_MAP[slug] || [], }, term?.faqs?.length ? { '@type': 'FAQPage', mainEntity: term.faqs.map(([question, answer]) => ({ '@type': 'Question', name: question, acceptedAnswer: { '@type': 'Answer', text: answer }, })), } : null, breadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'فرهنگ واژگان', path: '/articles' }, { name: term?.term || slug }]), ].filter(Boolean), }; } if (page.startsWith('/topics/')) { const slug = page.replace('/topics/', ''); const topic = TeknavData.topicHubs.find((item) => item.slug === slug); const category = TeknavData.categories.find((item) => item.slug === topic?.categorySlug); const articles = TeknavData.articles .filter((article) => article.category === topic?.categorySlug && article.status !== 'پیش‌نویس' && article.status !== 'در انتظار بررسی') .sort((a, b) => String(b.dateEn || '').localeCompare(String(a.dateEn || ''))); const name = topic?.title || category?.name || 'موضوع فناوری'; const description = truncate(topic?.description || category?.description || `مقاله‌ها و تحلیل‌های تکناو درباره ${name}.`, 160); const path = `/topics/${slug}`; return { title: topic?.seoTitle || `${name} | خوشه موضوعی تکناو`, description, keywords: (topic?.keywords ?? [name, 'تکناو', 'تحلیل فناوری']).join(', '), canonical: absoluteUrl(path), type: 'website', image: DEFAULT_IMAGE, jsonLd: [ ...baseGraph(), { '@type': 'CollectionPage', name, description, url: absoluteUrl(path), inLanguage: 'fa-IR', isPartOf: { '@id': `${SITE_URL}/#website` }, mainEntity: itemList(articles, path), }, topic?.faqs?.length ? { '@type': 'FAQPage', mainEntity: topic.faqs.map(([question, answer]) => ({ '@type': 'Question', name: question, acceptedAnswer: { '@type': 'Answer', text: answer }, })), } : null, breadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'موضوعات', path: '/articles' }, { name }]), ], }; } if (page.startsWith('/series/')) { const slug = page.replace('/series/', ''); const series = data.series; const path = `/series/${slug}`; const name = series?.title || 'سری مقاله تکناو'; const description = truncate(series?.description || 'سری مقاله‌های تکناو برای دنبال کردن یک موضوع فناوری به‌ترتیب تحریریه.', 160); const articles = (series?.articles ?? []).map((item) => item.article).filter(Boolean); return { title: `${name} | سری مقاله‌های تکناو`, description, keywords: `${name}, سری مقاله, تکناو, تحلیل فناوری`, canonical: absoluteUrl(path), type: 'website', image: imageUrl(series?.coverImage), jsonLd: [ ...baseGraph(), { '@type': 'CollectionPage', name, description, url: absoluteUrl(path), inLanguage: 'fa-IR', mainEntity: itemList(articles, path), }, breadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'سری مقاله‌ها', path: '/articles' }, { name }]), ], }; } if (page.startsWith('/author/')) { const slug = page.replace('/author/', ''); const author = data.author || TeknavData.authors.find((a) => a.slug === slug); const name = author?.name || 'نویسنده تکناو'; const description = truncate(author?.bio || `آرشیو مقاله‌های ${name} در تکناو.`, 155); const avatarUrl = author?.avatarUrl; return { title: `${name} | نویسنده تکناو`, description, keywords: `${name}, نویسنده فناوری, تحلیل فناوری, تکناو`, canonical: absoluteUrl(`/author/${slug}`), type: 'profile', image: imageUrl(avatarUrl), jsonLd: [ ...baseGraph(), { '@type': 'ProfilePage', name: `${name} در تکناو`, url: absoluteUrl(`/author/${slug}`), inLanguage: 'fa-IR', ...(avatarUrl ? { primaryImageOfPage: imageUrl(avatarUrl) } : {}), mainEntity: renderAuthorPerson(author, name, author?.specialty, description, author?.expertise, absoluteUrl(`/author/${slug}`)), }, breadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'نویسندگان', path: '/authors' }, { name }]), ], }; } if (page.startsWith('/profile/@')) { const username = page.replace('/profile/@', ''); const author = TeknavData.authors.find((a) => a.username === username || a.slug === username); const name = data.profile?.name || author?.name || `@${username}`; const description = truncate(data.profile?.bio || author?.bio || `پروفایل عمومی ${name} در تکناو.`, 155); const avatarUrl = data.profile?.avatarUrl || author?.avatarUrl; const mainEntity = author ? renderAuthorPerson(author, name, author.specialty, description, author.expertise, absoluteUrl(`/profile/@${username}`), data.profile?.avatarUrl) : { '@type': 'Person', name, alternateName: `@${username}`, description, image: avatarUrl ? imageUrl(avatarUrl) : undefined, }; return { title: `${name} | پروفایل تکناو`, description, keywords: `${name}, ${username}, پروفایل تکناو, نویسنده فناوری`, canonical: absoluteUrl(`/profile/@${username}`), type: 'profile', image: imageUrl(avatarUrl), jsonLd: [ ...baseGraph(), { '@type': 'ProfilePage', name, description, url: absoluteUrl(`/profile/@${username}`), inLanguage: 'fa-IR', ...(avatarUrl ? { primaryImageOfPage: imageUrl(avatarUrl) } : {}), mainEntity, }, ], }; } if (page === '/articles') { return { title: 'مقاله‌های فناوری | تکناو', description: 'آرشیو مقاله‌های فارسی تکناو درباره هوش مصنوعی، علم داده، امنیت سایبری، نرم‌افزار، سخت‌افزار و آینده فناوری.', keywords: DEFAULT_KEYWORDS, canonical: absoluteUrl('/articles'), type: 'website', image: DEFAULT_IMAGE, jsonLd: [...baseGraph(), { '@type': 'CollectionPage', name: 'مقاله‌های فناوری تکناو', url: absoluteUrl('/articles'), inLanguage: 'fa-IR' }], }; } if (page === '/authors') { return { title: 'نویسندگان تکناو | تحلیلگران فناوری', description: 'آشنایی با نویسندگان و تحلیلگران تکناو در حوزه هوش مصنوعی، امنیت سایبری، نرم‌افزار، سخت‌افزار و استارتاپ.', keywords: 'نویسندگان تکناو, تحلیلگر فناوری, نویسنده هوش مصنوعی, نویسنده امنیت سایبری', canonical: absoluteUrl('/authors'), type: 'website', image: DEFAULT_IMAGE, jsonLd: [...baseGraph(), { '@type': 'CollectionPage', name: 'نویسندگان تکناو', url: absoluteUrl('/authors'), inLanguage: 'fa-IR' }], }; } if (page === '/newsletter') { return { title: 'آرشیو خبرنامه تکنـاو | تحلیل هفتگی فناوری', description: 'آرشیو خبرنامه‌های ارسال‌شده تکنـاو درباره هوش مصنوعی، امنیت سایبری، نرم‌افزار، سخت‌افزار، استارتاپ و داده.', keywords: 'خبرنامه تکنـاو, آرشیو خبرنامه فناوری, تحلیل هفتگی فناوری, هوش مصنوعی, امنیت سایبری', canonical: absoluteUrl('/newsletter'), type: 'website', image: DEFAULT_IMAGE, jsonLd: [ ...baseGraph(), { '@type': 'CollectionPage', name: 'آرشیو خبرنامه تکنـاو', url: absoluteUrl('/newsletter'), inLanguage: 'fa-IR' }, breadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'خبرنامه' }]), ], }; } if (page === '/jobs' || page === '/courses' || page === '/membership') { const meta = { '/jobs': ['فرصت‌های شغلی فناوری | تکناو', 'آگهی‌های شغلی منتخب فناوری و امکان ثبت موقعیت شغلی شرکت‌ها در تکناو.', 'شغل فناوری, استخدام برنامه نویس, استخدام هوش مصنوعی, تکناو جابز'], '/courses': ['دوره‌های تخصصی فناوری | تکناو', 'دوره‌های فارسی تکناو برای هوش مصنوعی، امنیت، نرم‌افزار و مهارت‌های فنی محصول‌محور.', 'دوره فناوری, آموزش هوش مصنوعی, آموزش امنیت, دوره فارسی برنامه نویسی'], '/membership': ['عضویت پریمیوم تکناو | محتوای اختصاصی فناوری', 'عضویت پریمیوم تکناو برای تجربه بدون تبلیغ، محتوای اختصاصی و دسترسی بهتر به دوره‌ها و گزارش‌های تخصصی.', 'عضویت تکناو, محتوای پریمیوم فناوری, تحلیل اختصاصی فناوری'], }[page]; return { title: meta[0], description: meta[1], keywords: meta[2], canonical: absoluteUrl(page), type: 'website', image: DEFAULT_IMAGE, jsonLd: [ ...baseGraph(), { '@type': 'CollectionPage', name: meta[0], url: absoluteUrl(page), inLanguage: 'fa-IR', description: meta[1] }, breadcrumb([{ name: SITE_NAME, path: '/' }, { name: meta[0] }]), ], }; } if (page.startsWith('/newsletter/')) { const slug = page.replace('/newsletter/', ''); const issue = data.newsletterIssue; const name = issue?.subject || 'خبرنامه تکنـاو'; const description = truncate(issue?.bodyHtml || 'نسخه‌ای از خبرنامه تکنـاو درباره روندهای مهم فناوری.', 155); return { title: `${name} | خبرنامه تکنـاو`, description, keywords: `${name}, خبرنامه تکنـاو, فناوری, هوش مصنوعی, امنیت سایبری`, canonical: absoluteUrl(`/newsletter/${slug}`), type: 'article', image: DEFAULT_IMAGE, jsonLd: [ ...baseGraph(), { '@type': 'Article', headline: cleanText(name).slice(0, 110), description, datePublished: issue?.sentAt, dateModified: issue?.sentAt || issue?.createdAt, inLanguage: 'fa-IR', mainEntityOfPage: absoluteUrl(`/newsletter/${slug}`), publisher: { '@id': `${SITE_URL}/#organization` }, }, breadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'خبرنامه', path: '/newsletter' }, { name }]), ], }; } if (page.startsWith('/search')) { const q = new URLSearchParams(page.split('?')[1] || '').get('q') || ''; return { title: q ? `جستجو برای ${q} | تکناو` : 'جستجو در تکناو', description: q ? `نتایج جستجوی تکناو برای ${q} در مقاله‌های فارسی فناوری.` : 'جستجو در مقاله‌های فارسی تکناو درباره فناوری و تکنولوژی.', keywords: q ? `${q}, جستجوی فناوری, تکناو` : DEFAULT_KEYWORDS, canonical: absoluteUrl(q ? `/search?q=${encodeURIComponent(q)}` : '/search'), type: 'website', image: DEFAULT_IMAGE, jsonLd: baseGraph(), }; } return { title: DEFAULT_TITLE, description: DEFAULT_DESCRIPTION, keywords: DEFAULT_KEYWORDS, canonical: absoluteUrl('/'), type: 'website', image: DEFAULT_IMAGE, jsonLd: [ ...baseGraph(), { '@type': 'WebPage', '@id': `${SITE_URL}/#webpage`, name: DEFAULT_TITLE, description: DEFAULT_DESCRIPTION, url: absoluteUrl('/'), inLanguage: 'fa-IR', isPartOf: { '@id': `${SITE_URL}/#website` }, about: { '@id': `${SITE_URL}/#organization` }, speakable: { '@type': 'SpeakableSpecification', xpath: ['/html/head/title', '/html/head/meta[@name="description"]/@content'], }, }, ], }; } export function SeoManager() { const { page } = useNav(); const [data, setData] = useState({}); useEffect(() => { let cancelled = false; setData({}); if (page.startsWith('/article/')) { const rawSlug = page.replace('/article/', ''); const slug = rawSlug === 'ai-agents-2026' ? 'agentic-ai-production' : rawSlug; const fallback = contentApi.fallbackArticles().find((a) => a.slug === slug); if (fallback) setData({ article: fallback }); contentApi.getArticle(slug).then((res) => { if (!cancelled && res?.article) setData({ article: res.article }); }).catch(() => {}); } else if (page.startsWith('/category/')) { const slug = page.replace('/category/', ''); const fallback = TeknavData.categories.find((c) => c.slug === slug || c.id === slug); if (fallback) setData({ category: fallback }); contentApi.getCategory(slug).then((category) => { if (!cancelled && category) setData({ category }); }).catch(() => {}); } else if (page.startsWith('/author/')) { const slug = page.replace('/author/', ''); const fallback = TeknavData.authors.find((a) => a.slug === slug); if (fallback) setData({ author: fallback }); contentApi.getAuthor(slug).then((author) => { if (!cancelled && author) setData({ author }); }).catch(() => {}); } else if (page.startsWith('/profile/@')) { const username = page.replace('/profile/@', ''); api.get(`/api/profile/${username}`) .then((res) => { if (!cancelled && res?.profile) setData({ profile: res.profile }); }) .catch(() => {}); } else if (page.startsWith('/series/')) { const slug = page.replace('/series/', ''); contentApi.getSeries(slug) .then((series) => { if (!cancelled && series) setData({ series }); }) .catch(() => {}); } else if (page.startsWith('/newsletter/')) { const slug = page.replace('/newsletter/', ''); contentApi.getNewsletterIssue(slug) .then((newsletterIssue) => { if (!cancelled && newsletterIssue) setData({ newsletterIssue }); }) .catch(() => {}); } return () => { cancelled = true; }; }, [page]); const seo = useMemo(() => pageSeo(page, data), [page, data]); useEffect(() => { const isEn = typeof window !== 'undefined' && window.location.search.includes('lang=en'); const language = seo.language || (isEn ? 'en' : 'fa-IR'); const htmlLang = language.toLocaleLowerCase('en-US').startsWith('en') ? 'en' : 'fa'; const ogLocale = htmlLang === 'en' ? 'en_US' : 'fa_IR'; const canonical = seo.canonical.split('?')[0]; document.documentElement.lang = htmlLang; document.documentElement.dir = htmlLang === 'en' ? 'ltr' : 'rtl'; document.title = seo.title; setMeta('name', 'description', seo.description); setMeta('name', 'keywords', seo.keywords); setMeta('name', 'robots', 'index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1'); setMeta('name', 'googlebot', 'index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1'); setMeta('name', 'bingbot', 'index,follow,max-snippet:-1,max-image-preview:large'); setMeta('name', 'author', SITE_NAME); setMeta('name', 'language', language); if (seo.academicMeta) { Object.entries(seo.academicMeta).forEach(([name, value]) => setMeta('name', name, value)); setNamedMetaList('citation_reference', seo.citations || []); } else { document.head.querySelectorAll('meta[name="citation_reference"][data-teknav-seo-list="true"]').forEach((el) => el.remove()); } setMeta('property', 'og:locale', ogLocale); setMeta('property', 'og:site_name', SITE_NAME); setMeta('property', 'og:type', seo.type); setMeta('property', 'og:title', seo.title); setMeta('property', 'og:description', seo.description); setMeta('property', 'og:url', canonical); setMeta('property', 'og:image', seo.image); setMeta('property', 'og:image:width', '1200'); setMeta('property', 'og:image:height', '630'); setMeta('name', 'twitter:card', 'summary_large_image'); setMeta('name', 'twitter:title', seo.title); setMeta('name', 'twitter:description', seo.description); setMeta('name', 'twitter:image', seo.image); setLink('canonical', canonical); document.head.querySelectorAll('link[rel="alternate"][hreflang]').forEach((el) => el.remove()); setLink('alternate', canonical, { hreflang: htmlLang }); setJsonLd(seo.jsonLd); }, [seo]); return null; }