import fs from 'node:fs'; import path from 'node:path'; import { injectBeforeDocumentClosingHead } from '../../html-document-injection.mjs'; import { normalizeCoverMetaSuggestion, upsertMindspaceCoverMeta } from '../../mindspace-cover-meta.mjs'; import { preparePublishedPlatformBrand } from '../../mindspace-page-tag.mjs'; import { scheduleWorkspaceHtmlThumbnailSidecars } from '../../mindspace-workspace-thumbnails.mjs'; import { artifactFileExists, resolveArtifactLocalPath, verifyPageArtifactContent, } from './page-artifact.mjs'; import { hasMindspaceCoverMeta, hasPlatformBrandMarker, hasShareDescription, verifyArtifactSharePreview, verifySharePreviewMeta, } from './share-preview.mjs'; function escapeMetaAttribute(value) { return String(value ?? '') .replaceAll('&', '&') .replaceAll('"', '"') .replaceAll('<', '<'); } export { resolveArtifactLocalPath } from './page-artifact.mjs'; export function extractPageTitle(html) { const match = String(html ?? '').match(/]*>([\s\S]*?)<\/title>/i); return match?.[1]?.replace(/<[^>]+>/g, '').trim() ?? ''; } export function inferCoverMeta({ title = '', topic = '' } = {}) { const source = `${title} ${topic}`.trim(); let tag = '报告'; let emoji = '📄'; let accent = '#667eea'; let accent2 = '#764ba2'; if (/攻略|旅行|游记|半日|地图|景点|路线/.test(source)) { tag = '旅行'; emoji = '🏞️'; accent = '#2e7d32'; accent2 = '#1565c0'; } else if (/新闻|早报|热点|资讯|日报/.test(source)) { tag = '资讯'; emoji = '📰'; accent = '#2563eb'; accent2 = '#0f172a'; } else if (/诗|诗词|散文|文学/.test(source)) { tag = '文艺'; emoji = '🌿'; accent = '#2d5016'; accent2 = '#1a2a3a'; } const subtitle = (title || topic || 'MindSpace 页面').trim().slice(0, 80); return normalizeCoverMetaSuggestion({ tag, emoji, accent, accent2, subtitle, cover: '' }); } export function repairSharePreviewHtml(html, { title = '', topic = '' } = {}) { let next = String(html ?? ''); const resolvedTitle = String(title || extractPageTitle(next) || '').trim(); const summary = (topic || resolvedTitle || 'MindSpace 页面').trim().slice(0, 160); if (!hasShareDescription(next)) { const meta = ``; next = injectBeforeDocumentClosingHead(next, `${meta}\n`); } if (!hasMindspaceCoverMeta(next)) { next = upsertMindspaceCoverMeta(next, inferCoverMeta({ title: resolvedTitle, topic })); } if (!hasPlatformBrandMarker(next)) { next = preparePublishedPlatformBrand(next); } return next; } function syncArtifactSharePreview(artifact, localPath) { if (!artifact || !localPath) return artifact; try { const content = fs.readFileSync(localPath, 'utf8'); artifact.sharePreview = verifySharePreviewMeta(content); artifact.exists = true; artifact.isHtmlDocument = /<(?:html|body|main|article)\b/i.test(content); } catch { // Keep the caller's existing artifact metadata when disk read fails. } return artifact; } export function repairArtifactSharePreview(artifact, { topic = '', publishDir = '' } = {}) { const localPath = resolveArtifactLocalPath(artifact, publishDir); const resolvedArtifact = localPath ? { ...artifact, localPath } : artifact; if (!artifactFileExists(resolvedArtifact)) { return { ok: false, reason: 'missing_file', changes: [] }; } if (!verifyPageArtifactContent(resolvedArtifact).ok) { return { ok: false, reason: 'not_repairable_artifact', changes: [] }; } if (!localPath) { return { ok: false, reason: 'missing_local_path', changes: [] }; } const before = fs.readFileSync(localPath, 'utf8'); if (verifySharePreviewMeta(before).ok) { syncArtifactSharePreview(artifact, localPath); return { ok: true, reason: null, changes: [], alreadyReady: true }; } const title = extractPageTitle(before); const after = repairSharePreviewHtml(before, { title, topic }); if (after === before) { return { ok: false, reason: 'repair_noop', changes: [] }; } const changes = []; if (!hasShareDescription(before) && hasShareDescription(after)) changes.push('description'); if (!hasMindspaceCoverMeta(before) && hasMindspaceCoverMeta(after)) changes.push('mindspace_cover'); if (!hasPlatformBrandMarker(before) && hasPlatformBrandMarker(after)) changes.push('platform_brand'); fs.writeFileSync(localPath, after, 'utf8'); syncArtifactSharePreview(artifact, localPath); const verified = verifyArtifactSharePreview(artifact, { publishDir }); if (!verified.ok) { return { ok: false, reason: verified.reason ?? 'repair_incomplete', changes }; } if (publishDir && artifact?.relativePath) { scheduleWorkspaceHtmlThumbnailSidecars( publishDir, String(artifact.relativePath).replace(/\\/g, '/'), ); } return { ok: true, reason: null, changes }; } export function ensureArtifactsSharePreviewReady( artifacts = [], { topic = '', publishDir = '' } = {}, ) { const next = []; for (const artifact of artifacts) { if (!artifact) continue; if (artifact.auxiliary || artifact.isStub) { next.push(artifact); continue; } if (artifact.sharePreview?.ok === true) { next.push(artifact); continue; } repairArtifactSharePreview(artifact, { topic, publishDir }); next.push(artifact); } return next; }