import { extractCoverSignals } from './mindspace-thumbnails.mjs'; import { extractSharePreviewMeta } from './mindspace-og-tags.mjs'; function escapeAttr(value) { return String(value ?? '') .replaceAll('&', '&') .replaceAll('"', '"') .replaceAll('<', '<') .replaceAll('>', '>'); } function hasMeta(html, attr, name) { return new RegExp(`]+${attr}=["']${name}["']`, 'i').test(String(html)); } function hasLinkRel(html, rel) { return new RegExp(`]+rel=["'][^"']*\\b${rel}\\b[^"']*["']`, '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 rawMetaContent(html, attr, name) { const pattern = new RegExp( `]+${attr}=["']${name}["'][^>]+content=["']([^"']*)["']|]+content=["']([^"']*)["'][^>]+${attr}=["']${name}["']`, 'i', ); const match = String(html ?? '').match(pattern); return match?.[1] || match?.[2] || ''; } export function injectRobotsNoindex(html) { const source = String(html ?? ''); if (hasMeta(source, 'name', 'robots')) return source; const tags = [ '', '', ] .map((tag) => ` ${tag}`) .join('\n'); return appendBeforeHeadClose(source, `\n${tags}\n`); } export function resolveCanonicalUrl({ publication = null, pageUrl = '', origin = '', } = {}) { const fromPublication = String(publication?.publicUrl ?? '').trim(); if (fromPublication) { if (/^https?:\/\//i.test(fromPublication)) return fromPublication.split('#')[0]; if (fromPublication.startsWith('/') && origin) { return `${origin}${fromPublication}`.split('#')[0]; } return fromPublication.split('#')[0]; } return String(pageUrl ?? '').split('#')[0]; } export function injectSeoTags( html, { origin = '', pageUrl = '', pageDirUrl = '', publication = null, canonicalEnabled = true, htmlFilePath = '', fileExists = null, meta = {}, } = {}, ) { const source = String(html ?? ''); const preview = extractSharePreviewMeta(source, { origin, pageUrl, pageDirUrl, meta, htmlFilePath, fileExists, }); const signals = extractCoverSignals(source, meta); const tags = []; const links = []; if (!hasMeta(source, 'name', 'description') && preview.description) { tags.push( ``, ); } const keywords = [ signals.tag, ...(Array.isArray(meta.keywords) ? meta.keywords : []), ] .map((item) => String(item ?? '').trim()) .filter(Boolean); if (keywords.length > 0 && !hasMeta(source, 'name', 'keywords')) { tags.push(``); } if (canonicalEnabled) { const canonicalUrl = resolveCanonicalUrl({ publication, pageUrl, origin }); if (canonicalUrl && !hasLinkRel(source, 'canonical')) { links.push(``); } } const existingLang = source.match(/]*\blang=["']([^"']+)["']/i)?.[1]; if (!existingLang) { // Leave html lang to author; only add content-language when absent. if (!hasMeta(source, 'http-equiv', 'content-language')) { tags.push(''); } } const block = [...tags, ...links].map((item) => ` ${item}`).join('\n'); if (!block) return source; return appendBeforeHeadClose(source, `\n${block}\n`); } export function extractSeoDescription(html) { return ( rawMetaContent(html, 'property', 'og:description') || rawMetaContent(html, 'name', 'description') || extractCoverSignals(String(html ?? '')).subtitle || '' ); }