// تگ‌های سئوی سمت مرورگر — عنوان، توضیح متا، OG، توییتر، canonical و JSON-LD. // محتوا از دیتابیس می‌آید؛ برای خزنده‌های بدون جاوااسکریپت، بک‌اند همان تگ‌ها را // سمت سرور داخل index.html تزریق می‌کند (app/seo_pages.py). export function setMetaDescription(content: string): void { // بدون توضیح از دیتابیس، پیش‌فرض استاتیک index.html دست‌نخورده می‌ماند if (!content.trim()) return let el = document.querySelector('meta[name="description"]') if (!el) { el = document.createElement('meta') el.setAttribute('name', 'description') document.head.appendChild(el) } el.setAttribute('content', content.trim()) } /** کلید سئوی مسیر جاری — فقط صفحه‌های فهرست دقیق؛ صفحه‌های جزئیات خودشان عنوان می‌گذارند */ export function seoKeyForPath(pathname: string): string | null { if (pathname === '/') return 'home' const seg = pathname.split('/')[1] ?? '' const known = ['about', 'services', 'projects', 'investment', 'blog', 'faq', 'contact'] // مسیرهای جزئی مثل /blog/{slug} شامل نمی‌شوند تا اثر خود صفحه جزئیات برسانند return known.includes(seg) && pathname === `/${seg}` ? seg : null } function upsertMeta(attr: 'name' | 'property', key: string, content: string): void { let el = document.querySelector(`meta[${attr}="${key}"]`) if (!el) { el = document.createElement('meta') el.setAttribute(attr, key) document.head.appendChild(el) } el.setAttribute('content', content) } function upsertCanonical(path: string): void { let el = document.querySelector('link[rel="canonical"]') if (!el) { el = document.createElement('link') el.setAttribute('rel', 'canonical') document.head.appendChild(el) } el.setAttribute('href', new URL(path, window.location.origin).href) } function setJsonLd(data: Record | null): void { const id = 'seo-jsonld' let el = document.getElementById(id) if (!data) { el?.remove() return } if (!el) { el = document.createElement('script') el.setAttribute('type', 'application/ld+json') el.id = id document.head.appendChild(el) } el.textContent = JSON.stringify(data) } export type SeoInput = { title: string description?: string /** مسیر canonical — پیش‌فرض مسیر جاری مرورگر */ path?: string /** تصویر OG — مسیر نسبی مثل /media/... به آدرس مطلق تبدیل می‌شود */ image?: string type?: 'website' | 'article' jsonLd?: Record | null } /** تنظیم همه تگ‌های سئوی صفحه در یک فراخوانی */ export function applySeo(input: SeoInput): void { const title = input.title.trim() const description = (input.description ?? '').trim() const path = input.path ?? window.location.pathname const image = (input.image ?? '').trim() document.title = title setMetaDescription(description) upsertCanonical(path) upsertMeta('property', 'og:title', title) upsertMeta('property', 'og:description', description) upsertMeta('property', 'og:type', input.type ?? 'website') upsertMeta('property', 'og:url', new URL(path, window.location.origin).href) upsertMeta('name', 'twitter:card', 'summary_large_image') if (image) { upsertMeta('property', 'og:image', new URL(image, window.location.origin).href) } else { document.querySelector('meta[property="og:image"]')?.remove() } setJsonLd(input.jsonLd ?? null) }