this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

unfurl

alice 103d04e9 1d8f8290

+79 -9
+65 -2
src/app/leaflet/[rkey]/page.tsx
··· 6 6 import { Footer } from "#/components/footer"; 7 7 import { PostInfo } from "#/components/post-info"; 8 8 import { Title } from "#/components/typography"; 9 + import { leafletBlobToImageSrc } from "#/lib/leaflet/images"; 9 10 import { getLeafletPost, getLeafletPosts } from "#/lib/leaflet/api"; 10 11 import { extractLeafletPlaintext } from "#/lib/leaflet/plaintext"; 11 - import type { LeafletDocumentRecord } from "#/lib/leaflet/types"; 12 + import type { 13 + LeafletBlock, 14 + LeafletDocumentRecord, 15 + LeafletImageBlock, 16 + LeafletListItem, 17 + } from "#/lib/leaflet/types"; 12 18 import { AUTHOR_NAME, HOSTNAME } from "#/lib/config"; 13 19 14 20 export const dynamic = "force-static"; ··· 35 41 const post = await getLeafletPost(rkey); 36 42 const record = post.value as LeafletDocumentRecord; 37 43 const description = record.description || extractLeafletPlaintext(record).slice(0, 160); 44 + const canonical = `https://${HOSTNAME}/leaflet/${rkey}`; 45 + const firstImage = findFirstLeafletImage(record); 46 + const imageUrl = firstImage ? leafletBlobToImageSrc(firstImage.image, record.author) : null; 38 47 39 48 return { 40 49 title: `${record.title} — ${HOSTNAME}`, 41 50 description, 42 51 alternates: { 43 - canonical: `https://${HOSTNAME}/leaflet/${rkey}`, 52 + canonical, 44 53 }, 45 54 authors: [{ name: AUTHOR_NAME, url: `https://bsky.app/profile/${record.author}` }], 55 + openGraph: { 56 + type: "article", 57 + url: canonical, 58 + title: record.title, 59 + description, 60 + publishedTime: record.publishedAt, 61 + authors: [AUTHOR_NAME], 62 + images: imageUrl ? [{ url: imageUrl, alt: firstImage?.alt ?? record.title }] : undefined, 63 + }, 64 + twitter: { 65 + card: imageUrl ? "summary_large_image" : "summary", 66 + title: record.title, 67 + description, 68 + images: imageUrl ? [imageUrl] : undefined, 69 + }, 46 70 }; 47 71 } 48 72 ··· 80 104 </div> 81 105 ); 82 106 } 107 + 108 + function findFirstLeafletImage(record: LeafletDocumentRecord): LeafletImageBlock | null { 109 + for (const page of record.pages) { 110 + if (page.$type !== "pub.leaflet.pages.linearDocument") continue; 111 + for (const block of page.blocks) { 112 + const found = findImageInBlock(block.block); 113 + if (found) return found; 114 + } 115 + } 116 + return null; 117 + } 118 + 119 + function findImageInBlock(block: LeafletBlock): LeafletImageBlock | null { 120 + switch (block.$type) { 121 + case "pub.leaflet.blocks.image": 122 + return block; 123 + case "pub.leaflet.blocks.unorderedList": 124 + case "pub.leaflet.blocks.orderedList": 125 + for (const child of block.children) { 126 + const found = findImageInListItem(child); 127 + if (found) return found; 128 + } 129 + return null; 130 + default: 131 + return null; 132 + } 133 + } 134 + 135 + function findImageInListItem(item: LeafletListItem): LeafletImageBlock | null { 136 + const direct = findImageInBlock(item.content); 137 + if (direct) return direct; 138 + if (item.children) { 139 + for (const child of item.children) { 140 + const nested = findImageInListItem(child); 141 + if (nested) return nested; 142 + } 143 + } 144 + return null; 145 + }
+2 -7
src/components/leaflet/leaflet-content.tsx
··· 13 13 LeafletOrderedListBlock, 14 14 LeafletUnorderedListBlock, 15 15 } from "#/lib/leaflet/types"; 16 + import { leafletBlobToImageSrc } from "#/lib/leaflet/images"; 16 17 17 18 const alignmentClasses: Record<string, string> = { 18 19 "lex:pub.leaflet.pages.linearDocument#textAlignLeft": "items-start text-left", ··· 90 91 case "pub.leaflet.blocks.horizontalRule": 91 92 return <hr className="border-slate-800/10 dark:border-slate-100/10" />; 92 93 case "pub.leaflet.blocks.image": { 93 - const src = blobRefToUrl(block.image, authorDid); 94 + const src = leafletBlobToImageSrc(block.image, authorDid); 94 95 if (!src) return null; 95 96 const { width, height } = block.aspectRatio; 96 97 const safeHeight = height || 1; ··· 251 252 ) : null} 252 253 </li> 253 254 ); 254 - } 255 - 256 - function blobRefToUrl(blob: { ref?: { $link?: string } } | undefined, did: string) { 257 - const cid = blob?.ref?.$link; 258 - if (!cid) return null; 259 - return `https://cdn.bsky.app/img/feed_fullsize/plain/${encodeURIComponent(did)}/${cid}@jpeg`; 260 255 } 261 256 262 257 function BskyPostPreview({ block }: { block: LeafletBskyPostBlock }) {
+12
src/lib/leaflet/images.ts
··· 1 + import type { LeafletBlobRef } from "#/lib/leaflet/types"; 2 + 3 + export function leafletBlobToImageSrc( 4 + blob: LeafletBlobRef | undefined, 5 + did: string, 6 + variant: "feed_fullsize" | "feed_thumbnail" = "feed_fullsize", 7 + ) { 8 + const cid = blob?.ref?.$link; 9 + if (!cid) return null; 10 + const encodedDid = encodeURIComponent(did); 11 + return `https://cdn.bsky.app/img/${variant}/plain/${encodedDid}/${cid}@jpeg`; 12 + }