This commit is contained in:
@@ -11,6 +11,13 @@ export const fetchPreview = action({
|
||||
description: v.optional(v.string()),
|
||||
image: v.optional(v.string()),
|
||||
siteName: v.optional(v.string()),
|
||||
// Image dimensions sourced from og:image:width/height or
|
||||
// twitter:image:width/height when the page emits them. The client
|
||||
// uses these to reserve the preview card's image slot *before* the
|
||||
// image decodes, eliminating the height shift that used to expand
|
||||
// the card on first paint.
|
||||
imageWidth: v.optional(v.number()),
|
||||
imageHeight: v.optional(v.number()),
|
||||
}),
|
||||
v.null(),
|
||||
),
|
||||
@@ -89,6 +96,26 @@ export const fetchPreview = action({
|
||||
const siteName =
|
||||
pick(/<meta[^>]+property=["']og:site_name["'][^>]+content=["']([^"']+)["']/i);
|
||||
|
||||
const pickNum = (re: RegExp): number | undefined => {
|
||||
const raw = pick(re);
|
||||
if (!raw) return undefined;
|
||||
const n = Number(raw);
|
||||
return Number.isFinite(n) && n > 0 ? Math.round(n) : undefined;
|
||||
};
|
||||
// og: and twitter: tags both publish the intended image dimensions
|
||||
// in their own namespaces — use whichever is available. The attr
|
||||
// order in HTML varies (some sites emit `content="w"` before
|
||||
// `property=...`), so fall back to a second regex with the
|
||||
// attributes swapped.
|
||||
const imageWidth =
|
||||
pickNum(/<meta[^>]+property=["']og:image:width["'][^>]+content=["']([^"']+)["']/i) ??
|
||||
pickNum(/<meta[^>]+content=["']([^"']+)["'][^>]+property=["']og:image:width["']/i) ??
|
||||
pickNum(/<meta[^>]+name=["']twitter:image:width["'][^>]+content=["']([^"']+)["']/i);
|
||||
const imageHeight =
|
||||
pickNum(/<meta[^>]+property=["']og:image:height["'][^>]+content=["']([^"']+)["']/i) ??
|
||||
pickNum(/<meta[^>]+content=["']([^"']+)["'][^>]+property=["']og:image:height["']/i) ??
|
||||
pickNum(/<meta[^>]+name=["']twitter:image:height["'][^>]+content=["']([^"']+)["']/i);
|
||||
|
||||
// Resolve relative image URLs
|
||||
if (image) {
|
||||
try {
|
||||
@@ -97,7 +124,15 @@ export const fetchPreview = action({
|
||||
}
|
||||
|
||||
if (!title && !description && !image) return null;
|
||||
return { url: u.toString(), title, description, image, siteName };
|
||||
return {
|
||||
url: u.toString(),
|
||||
title,
|
||||
description,
|
||||
image,
|
||||
siteName,
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user