From 23d06a3db53c541f0788bd7d165e07657ac849b6 Mon Sep 17 00:00:00 2001 From: gosangam Date: Sun, 14 Jun 2026 01:49:44 +0530 Subject: [PATCH] fix: og image fix for collections --- app/api/og/collection/[id]/route.tsx | 119 ++++++++++++---------- app/api/og/post/[id]/route.tsx | 15 ++- components/social/post/post-card-menu.tsx | 75 ++++++++++---- 3 files changed, 132 insertions(+), 77 deletions(-) diff --git a/app/api/og/collection/[id]/route.tsx b/app/api/og/collection/[id]/route.tsx index 66160d14..59c944bc 100644 --- a/app/api/og/collection/[id]/route.tsx +++ b/app/api/og/collection/[id]/route.tsx @@ -76,6 +76,10 @@ export async function GET( 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.
{hasImage ? ( @@ -119,72 +122,80 @@ export async function GET( ) : null} - {/* Eyebrow */} + {/* Content layer — holds the padding so the bg image stays full-bleed. */}
- Collection -
- - {/* Title — the centrepiece */} -
48 ? 60 : 76, - fontWeight: 700, - color: textPrimary, - lineHeight: 1.15, - marginTop: 24, - maxHeight: 320, - overflow: "hidden", - zIndex: 1, - }} - > - {title} -
- - {description ? ( + {/* Eyebrow */}
- {description} + Collection
- ) : null} -
- - {/* Footer: count + branding */} -
-
- {count} + {/* Title — the centrepiece */} +
48 ? 60 : 76, + fontWeight: 700, + color: textPrimary, + lineHeight: 1.15, + marginTop: 24, + maxHeight: 320, + overflow: "hidden", + }} + > + {title}
-
- prabhupadasays.org + + {description ? ( +
+ {description} +
+ ) : null} + +
+ + {/* Footer: count + branding */} +
+
+ {count} +
+
+ prabhupadasays.org +
diff --git a/app/api/og/post/[id]/route.tsx b/app/api/og/post/[id]/route.tsx index 6fbae81e..d1d0d82d 100644 --- a/app/api/og/post/[id]/route.tsx +++ b/app/api/og/post/[id]/route.tsx @@ -102,7 +102,6 @@ export async function GET( backgroundColor: hasImage ? "#1a1410" : "#faf5f0", fontFamily: "Noto Sans, sans-serif", position: "relative", - padding: 80, }} > {/* Background image + dimming overlay */} @@ -138,6 +137,19 @@ export async function GET( ) : 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). */} +
{/* Caption — the centrepiece */}
+
), { diff --git a/components/social/post/post-card-menu.tsx b/components/social/post/post-card-menu.tsx index e488b04a..34e7b39b 100644 --- a/components/social/post/post-card-menu.tsx +++ b/components/social/post/post-card-menu.tsx @@ -152,29 +152,60 @@ export function PostCardMenu({ // Copy the post's caption + source citation + a (short) link to the post — // the same payload the share sheet / footer produce. Available to every // viewer. - async function copyCaption() { - setOpen(false); - try { - const origin = window.location.origin; - let url = ""; - if (authorHandle) { - const longPath = `/u/${encodeURIComponent(authorHandle)}/posts#post-${post.id}`; - const code = await createShortLink(longPath); - url = code - ? new URL(`/share/${code}`, origin).toString() - : new URL(longPath, origin).toString(); - } - 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); - await navigator.clipboard.writeText(lines.join("\n")); - toast.success("Caption copied"); - } catch { - toast.error("Could not copy"); + // Build the copy payload — caption + citation + a short link to the post. + // The short link is a network round-trip, so we must NOT await it before + // touching the clipboard or we lose the click's transient user activation + // (the write then fails on the first click and only lands on the second, + // once the link is cached). See `useCopyFeedback` for the same fix. + async function buildCaptionText(): Promise { + const origin = window.location.origin; + let url = ""; + if (authorHandle) { + const longPath = `/u/${encodeURIComponent(authorHandle)}/posts#post-${post.id}`; + const code = await createShortLink(longPath); + url = code + ? new URL(`/share/${code}`, origin).toString() + : new URL(longPath, origin).toString(); } + 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