import { extractCoverSignals } from './mindspace-thumbnails.mjs'; import { extractSharePreviewMeta, PLATFORM_SITE_NAME } from './mindspace-og-tags.mjs'; import { extractSeoDescription } from './mindspace-seo-tags.mjs'; function escapeAttr(value) { return String(value ?? '') .replaceAll('&', '&') .replaceAll('"', '"') .replaceAll('<', '<') .replaceAll('>', '>'); } function rawTitleFromHtml(html) { return String(html ?? '').match(/]*>([^<]*)<\/title>/i)?.[1]?.trim() ?? ''; } function rawH1FromHtml(html) { const match = String(html ?? '').match(/]*>([\s\S]*?)<\/h1>/i); if (!match) return ''; return match[1].replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim(); } function hasJsonLd(html) { return /]+type=["']application\/ld\+json["']/i.test(String(html ?? '')); } function appendBeforeHeadClose(html, block) { const source = String(html ?? ''); if (!block) return source; if (/<\/head>/i.test(source)) { return source.replace(/<\/head>/i, `${block}\n`); } if (/]*>/i.test(source)) { return source.replace(/(]*>)/i, `$1${block}`); } return `${block}\n${source}`; } function parseMindspaceGeoMeta(html) { const tag = String(html ?? '').match(/]*name=["']mindspace-geo["'][^>]*>/i)?.[0]; if (!tag) return {}; const contentMatch = tag.match(/content=(["'])([\s\S]*?)\1/i) ?? tag.match(/content=["']([^"']+)["']/i); const raw = contentMatch?.[2] ?? contentMatch?.[1]; if (!raw) return {}; try { return JSON.parse(raw.replaceAll('"', '"')); } catch { return {}; } } function buildArticleJsonLd({ title, description, imageUrl, pageUrl, siteName = PLATFORM_SITE_NAME, faq = [], }) { const graph = { '@context': 'https://schema.org', '@type': 'Article', headline: title, description, inLanguage: 'zh-CN', isAccessibleForFree: true, publisher: { '@type': 'Organization', name: siteName, }, }; if (pageUrl) graph.mainEntityOfPage = pageUrl; if (imageUrl) graph.image = [imageUrl]; if (Array.isArray(faq) && faq.length > 0) { graph.hasPart = { '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faq .map((item) => ({ '@type': 'Question', name: String(item?.q ?? item?.question ?? '').trim(), acceptedAnswer: { '@type': 'Answer', text: String(item?.a ?? item?.answer ?? '').trim(), }, })) .filter((item) => item.name && item.acceptedAnswer.text), }; } return graph; } export function buildGeoStructuredData( html, { origin = '', pageUrl = '', pageDirUrl = '', publication = null, siteName = PLATFORM_SITE_NAME, htmlFilePath = '', fileExists = null, meta = {}, } = {}, ) { const source = String(html ?? ''); const authorMeta = parseMindspaceGeoMeta(source); const preview = extractSharePreviewMeta(source, { origin, pageUrl, pageDirUrl, siteName, meta, htmlFilePath, fileExists, }); const title = rawMetaOgTitle(source) || rawTitleFromHtml(source) || rawH1FromHtml(source) || 'MindSpace 页面'; const description = String(authorMeta.summary ?? '').trim() || extractSeoDescription(source) || preview.description || ''; const canonical = String(publication?.publicUrl ?? pageUrl ?? '').trim() || pageUrl; const absoluteUrl = canonical.startsWith('http') ? canonical : origin && canonical.startsWith('/') ? `${origin}${canonical}` : pageUrl; return buildArticleJsonLd({ title, description, imageUrl: preview.imageUrl, pageUrl: absoluteUrl, siteName: preview.siteName || siteName, faq: authorMeta.faq, }); } function rawMetaOgTitle(html) { return ( String(html ?? '').match( /]+property=["']og:title["'][^>]+content=["']([^"']+)["']/i, )?.[1] || String(html ?? '').match( /]+content=["']([^"']+)["'][^>]+property=["']og:title["']/i, )?.[1] || '' ); } export function injectGeoTags( html, ctx = {}, ) { const sourceHtml = String(ctx.sourceHtml ?? html ?? ''); const targetHtml = String(html ?? ''); if (hasJsonLd(targetHtml)) return targetHtml; const payload = buildGeoStructuredData(sourceHtml, ctx); const serialized = JSON.stringify(payload).replace(/${serialized}\n`; return appendBeforeHeadClose(targetHtml, block); }