diff --git a/mindspace-og-tags.mjs b/mindspace-og-tags.mjs
index 3faba0f..677c46e 100644
--- a/mindspace-og-tags.mjs
+++ b/mindspace-og-tags.mjs
@@ -5,6 +5,7 @@
// / hero ), reused via extractCoverSignals. Authors who
// already wrote their own og:image are left untouched; missing site_name / description /
// brand icon are still backfilled.
+import path from 'node:path';
import { extractCoverSignals } from './mindspace-thumbnails.mjs';
export const PLATFORM_SITE_NAME = 'TKMind 智趣';
@@ -40,6 +41,15 @@ function resolveImageUrl(image, { origin, pageDirUrl }) {
return `${pageDirUrl}${trimmed}`;
}
+/** Relative cover paths must exist beside the HTML file; remote/absolute refs are trusted. */
+export function coverImageRefExists(imageRef, htmlFilePath, fileExists = () => false) {
+ const ref = String(imageRef ?? '').trim();
+ if (!ref || ref.startsWith('data:')) return false;
+ if (/^https?:\/\//i.test(ref) || ref.startsWith('//') || ref.startsWith('/')) return true;
+ if (!htmlFilePath) return true;
+ return fileExists(path.join(path.dirname(htmlFilePath), ref));
+}
+
function rawMetaContent(html, attr, name) {
const pattern = new RegExp(
`]+${attr}=["']${name}["'][^>]+content=["']([^"']*)["']|]+content=["']([^"']*)["'][^>]+${attr}=["']${name}["']`,
@@ -82,11 +92,23 @@ function resolveShareDescription(html, { siteName = PLATFORM_SITE_NAME, meta = {
);
}
-function resolveShareImageUrl(html, { origin, pageDirUrl, fallbackImageUrl = '', meta = {} } = {}) {
+function resolveShareImageUrl(
+ html,
+ { origin, pageDirUrl, fallbackImageUrl = '', meta = {}, htmlFilePath = '', fileExists = null } = {},
+) {
const existing = rawMetaContent(html, 'property', 'og:image');
if (existing) return existing;
const signals = extractCoverSignals(html, meta);
- return resolveImageUrl(signals.image, { origin, pageDirUrl }) || fallbackImageUrl || '';
+ const imageRef = signals.image;
+ if (imageRef) {
+ const exists =
+ typeof fileExists === 'function' ? coverImageRefExists(imageRef, htmlFilePath, fileExists) : true;
+ if (exists) {
+ const resolved = resolveImageUrl(imageRef, { origin, pageDirUrl });
+ if (resolved) return resolved;
+ }
+ }
+ return fallbackImageUrl || '';
}
/**
@@ -102,12 +124,21 @@ export function extractSharePreviewMeta(
siteName = PLATFORM_SITE_NAME,
brandIconUrl = '',
meta = {},
+ htmlFilePath = '',
+ fileExists = null,
} = {},
) {
const source = String(html ?? '');
const title = rawMetaContent(source, 'property', 'og:title') || rawTitleFromHtml(source) || 'MindSpace 页面';
const description = resolveShareDescription(source, { siteName, meta });
- const imageUrl = resolveShareImageUrl(source, { origin, pageDirUrl, fallbackImageUrl, meta });
+ const imageUrl = resolveShareImageUrl(source, {
+ origin,
+ pageDirUrl,
+ fallbackImageUrl,
+ meta,
+ htmlFilePath,
+ fileExists,
+ });
const resolvedSiteName = rawMetaContent(source, 'property', 'og:site_name') || siteName;
const iconUrl = brandIconUrl || (origin ? `${origin}${PLATFORM_BRAND_ICON_PATH}` : PLATFORM_BRAND_ICON_PATH);
return {
@@ -136,6 +167,8 @@ export function injectOgTags(
siteName = PLATFORM_SITE_NAME,
brandIconUrl = '',
meta = {},
+ htmlFilePath = '',
+ fileExists = null,
} = {},
) {
const source = String(html);
@@ -147,6 +180,8 @@ export function injectOgTags(
siteName,
brandIconUrl,
meta,
+ htmlFilePath,
+ fileExists,
});
const tags = [];
diff --git a/mindspace-og-tags.test.mjs b/mindspace-og-tags.test.mjs
index 12758d1..d26c3aa 100644
--- a/mindspace-og-tags.test.mjs
+++ b/mindspace-og-tags.test.mjs
@@ -39,13 +39,30 @@ test('falls back to the thumbnail png when the page has no cover of its own', ()
assert.match(out, //);
});
-test('prefers the page cover over the thumbnail fallback', () => {
+test('prefers the page cover over the thumbnail fallback when the cover file exists', () => {
const html = `