fix: og image fix for collections
This commit is contained in:
@@ -76,6 +76,10 @@ export async function GET(
|
|||||||
|
|
||||||
return new ImageResponse(
|
return new ImageResponse(
|
||||||
(
|
(
|
||||||
|
// Root carries NO padding — absolutely-positioned children measure
|
||||||
|
// from the padding box, so any root padding would inset the full-bleed
|
||||||
|
// cover image and leave the bg colour showing as a strip. Padding lives
|
||||||
|
// on the inner content layer instead.
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
width: 1200,
|
width: 1200,
|
||||||
@@ -85,7 +89,6 @@ export async function GET(
|
|||||||
backgroundColor: hasImage ? "#1a1410" : "#faf5f0",
|
backgroundColor: hasImage ? "#1a1410" : "#faf5f0",
|
||||||
fontFamily: "Noto Sans, sans-serif",
|
fontFamily: "Noto Sans, sans-serif",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
padding: 80,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{hasImage ? (
|
{hasImage ? (
|
||||||
@@ -119,72 +122,80 @@ export async function GET(
|
|||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{/* Eyebrow */}
|
{/* Content layer — holds the padding so the bg image stays full-bleed. */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
position: "relative",
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: 700,
|
|
||||||
letterSpacing: 6,
|
|
||||||
textTransform: "uppercase",
|
|
||||||
color: accent,
|
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
|
flex: 1,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
padding: 80,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Collection
|
{/* Eyebrow */}
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Title — the centrepiece */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
fontSize: title.length > 48 ? 60 : 76,
|
|
||||||
fontWeight: 700,
|
|
||||||
color: textPrimary,
|
|
||||||
lineHeight: 1.15,
|
|
||||||
marginTop: 24,
|
|
||||||
maxHeight: 320,
|
|
||||||
overflow: "hidden",
|
|
||||||
zIndex: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{description ? (
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
fontSize: 28,
|
fontSize: 24,
|
||||||
color: textSecondary,
|
fontWeight: 700,
|
||||||
lineHeight: 1.4,
|
letterSpacing: 6,
|
||||||
marginTop: 24,
|
textTransform: "uppercase",
|
||||||
maxHeight: 120,
|
color: accent,
|
||||||
overflow: "hidden",
|
|
||||||
zIndex: 1,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{description}
|
Collection
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
|
||||||
|
|
||||||
<div style={{ flex: 1, display: "flex" }} />
|
{/* Title — the centrepiece */}
|
||||||
|
<div
|
||||||
{/* Footer: count + branding */}
|
style={{
|
||||||
<div
|
display: "flex",
|
||||||
style={{
|
fontSize: title.length > 48 ? 60 : 76,
|
||||||
display: "flex",
|
fontWeight: 700,
|
||||||
flexDirection: "row",
|
color: textPrimary,
|
||||||
alignItems: "center",
|
lineHeight: 1.15,
|
||||||
justifyContent: "space-between",
|
marginTop: 24,
|
||||||
zIndex: 1,
|
maxHeight: 320,
|
||||||
}}
|
overflow: "hidden",
|
||||||
>
|
}}
|
||||||
<div style={{ fontSize: 24, color: textSecondary, display: "flex" }}>
|
>
|
||||||
{count}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 22, color: accent, fontWeight: 700 }}>
|
|
||||||
prabhupadasays.org
|
{description ? (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
fontSize: 28,
|
||||||
|
color: textSecondary,
|
||||||
|
lineHeight: 1.4,
|
||||||
|
marginTop: 24,
|
||||||
|
maxHeight: 120,
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div style={{ flex: 1, display: "flex" }} />
|
||||||
|
|
||||||
|
{/* Footer: count + branding */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontSize: 24, color: textSecondary, display: "flex" }}>
|
||||||
|
{count}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 22, color: accent, fontWeight: 700 }}>
|
||||||
|
prabhupadasays.org
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -102,7 +102,6 @@ export async function GET(
|
|||||||
backgroundColor: hasImage ? "#1a1410" : "#faf5f0",
|
backgroundColor: hasImage ? "#1a1410" : "#faf5f0",
|
||||||
fontFamily: "Noto Sans, sans-serif",
|
fontFamily: "Noto Sans, sans-serif",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
padding: 80,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Background image + dimming overlay */}
|
{/* Background image + dimming overlay */}
|
||||||
@@ -138,6 +137,19 @@ export async function GET(
|
|||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{/* Content layer — holds the padding so the bg image stays
|
||||||
|
full-bleed (absolute children measure from the padding box, so
|
||||||
|
padding on the root would inset the image and show a strip). */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
zIndex: 1,
|
||||||
|
flex: 1,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
padding: 80,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{/* Caption — the centrepiece */}
|
{/* Caption — the centrepiece */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -254,6 +266,7 @@ export async function GET(
|
|||||||
prabhupadasays.org
|
prabhupadasays.org
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -152,29 +152,60 @@ export function PostCardMenu({
|
|||||||
// Copy the post's caption + source citation + a (short) link to the post —
|
// Copy the post's caption + source citation + a (short) link to the post —
|
||||||
// the same payload the share sheet / footer produce. Available to every
|
// the same payload the share sheet / footer produce. Available to every
|
||||||
// viewer.
|
// viewer.
|
||||||
async function copyCaption() {
|
// Build the copy payload — caption + citation + a short link to the post.
|
||||||
setOpen(false);
|
// The short link is a network round-trip, so we must NOT await it before
|
||||||
try {
|
// touching the clipboard or we lose the click's transient user activation
|
||||||
const origin = window.location.origin;
|
// (the write then fails on the first click and only lands on the second,
|
||||||
let url = "";
|
// once the link is cached). See `useCopyFeedback` for the same fix.
|
||||||
if (authorHandle) {
|
async function buildCaptionText(): Promise<string> {
|
||||||
const longPath = `/u/${encodeURIComponent(authorHandle)}/posts#post-${post.id}`;
|
const origin = window.location.origin;
|
||||||
const code = await createShortLink(longPath);
|
let url = "";
|
||||||
url = code
|
if (authorHandle) {
|
||||||
? new URL(`/share/${code}`, origin).toString()
|
const longPath = `/u/${encodeURIComponent(authorHandle)}/posts#post-${post.id}`;
|
||||||
: new URL(longPath, origin).toString();
|
const code = await createShortLink(longPath);
|
||||||
}
|
url = code
|
||||||
const ref = structuredReferenceForPost(post.source_url, post.source_title);
|
? new URL(`/share/${code}`, origin).toString()
|
||||||
const lines: string[] = [];
|
: new URL(longPath, origin).toString();
|
||||||
if (post.caption) lines.push(`“${post.caption}”`, "");
|
|
||||||
lines.push("Source:", ref.title);
|
|
||||||
if (ref.detail) lines.push(ref.detail);
|
|
||||||
if (url) lines.push(url);
|
|
||||||
await navigator.clipboard.writeText(lines.join("\n"));
|
|
||||||
toast.success("Caption copied");
|
|
||||||
} catch {
|
|
||||||
toast.error("Could not copy");
|
|
||||||
}
|
}
|
||||||
|
const ref = structuredReferenceForPost(post.source_url, post.source_title);
|
||||||
|
const lines: string[] = [];
|
||||||
|
if (post.caption) lines.push(`“${post.caption}”`, "");
|
||||||
|
lines.push("Source:", ref.title);
|
||||||
|
if (ref.detail) lines.push(ref.detail);
|
||||||
|
if (url) lines.push(url);
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyCaption() {
|
||||||
|
setOpen(false);
|
||||||
|
// Hand the clipboard a *Promise* of the text via ClipboardItem so the
|
||||||
|
// browser keeps the gesture alive while the short link resolves — one
|
||||||
|
// click copies cleanly.
|
||||||
|
if (typeof ClipboardItem !== "undefined" && navigator.clipboard?.write) {
|
||||||
|
try {
|
||||||
|
const item = new ClipboardItem({
|
||||||
|
"text/plain": buildCaptionText().then(
|
||||||
|
(s) => new Blob([s], { type: "text/plain" }),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
navigator.clipboard.write([item]).then(
|
||||||
|
() => toast.success("Caption copied"),
|
||||||
|
() => toast.error("Could not copy"),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
} catch {
|
||||||
|
// ClipboardItem-with-promise unsupported → fall through to the
|
||||||
|
// await + writeText path below.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void buildCaptionText().then(async (text) => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
toast.success("Caption copied");
|
||||||
|
} catch {
|
||||||
|
toast.error("Could not copy");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the items list AFTER all hooks have run so a conditional
|
// Build the items list AFTER all hooks have run so a conditional
|
||||||
|
|||||||
Reference in New Issue
Block a user