chore: removed regular posts and opted for only standard.site
cf0bb21b
2 file(s) · +53 −122
| 13 | 13 | interface Record { |
|
| 14 | 14 | uri: string; |
|
| 15 | 15 | value: any; |
|
| 16 | - | type: "document" | "post"; |
|
| 17 | 16 | } |
|
| 18 | 17 | ||
| 19 | 18 | export default function NowUpdates() { |
|
| 22 | 21 | useEffect(() => { |
|
| 23 | 22 | async function fetchPosts() { |
|
| 24 | 23 | try { |
|
| 25 | - | // Fetch new site.standard.document records |
|
| 26 | - | const documentsPromise = fetch( |
|
| 24 | + | // Fetch site.standard.document records only |
|
| 25 | + | const documentsData = await fetch( |
|
| 27 | 26 | `${PDS_URL}/xrpc/com.atproto.repo.listRecords?` + |
|
| 28 | 27 | new URLSearchParams({ |
|
| 29 | 28 | repo: DID, |
|
| 34 | 33 | .then((res) => (res.ok ? res.json() : { records: [] })) |
|
| 35 | 34 | .catch(() => ({ records: [] })); |
|
| 36 | 35 | ||
| 37 | - | // Fetch old app.bsky.feed.post records for backward compatibility |
|
| 38 | - | const postsPromise = fetch( |
|
| 39 | - | `${PDS_URL}/xrpc/com.atproto.repo.listRecords?` + |
|
| 40 | - | new URLSearchParams({ |
|
| 41 | - | repo: DID, |
|
| 42 | - | collection: "app.bsky.feed.post", |
|
| 43 | - | limit: "20", |
|
| 44 | - | filter: "posts_no_replies", |
|
| 45 | - | }), |
|
| 46 | - | ) |
|
| 47 | - | .then((res) => (res.ok ? res.json() : { records: [] })) |
|
| 48 | - | .catch(() => ({ records: [] })); |
|
| 49 | - | ||
| 50 | - | const [documentsData, postsData] = await Promise.all([ |
|
| 51 | - | documentsPromise, |
|
| 52 | - | postsPromise, |
|
| 53 | - | ]); |
|
| 54 | - | ||
| 55 | - | // Combine and normalize records |
|
| 56 | - | const documents = (documentsData.records || []).map((record: any) => ({ |
|
| 57 | - | ...record, |
|
| 58 | - | type: "document", |
|
| 59 | - | })); |
|
| 60 | - | ||
| 61 | - | const posts = (postsData.records || []) |
|
| 62 | - | .filter((record: any) => !record.value.reply) |
|
| 63 | - | .map((record: any) => ({ |
|
| 64 | - | ...record, |
|
| 65 | - | type: "post", |
|
| 66 | - | })); |
|
| 67 | - | ||
| 68 | - | // Combine all records and sort by date |
|
| 69 | - | const allRecords: Record[] = [...documents, ...posts].sort((a, b) => { |
|
| 70 | - | const dateA = new Date(a.value.publishedAt || a.value.createdAt); |
|
| 71 | - | const dateB = new Date(b.value.publishedAt || b.value.createdAt); |
|
| 72 | - | return dateB.getTime() - dateA.getTime(); // Most recent first |
|
| 73 | - | }); |
|
| 36 | + | const documents: Record[] = (documentsData.records || []).sort( |
|
| 37 | + | (a: Record, b: Record) => { |
|
| 38 | + | const dateA = new Date(a.value.publishedAt); |
|
| 39 | + | const dateB = new Date(b.value.publishedAt); |
|
| 40 | + | return dateB.getTime() - dateA.getTime(); // Most recent first |
|
| 41 | + | }, |
|
| 42 | + | ); |
|
| 74 | 43 | ||
| 75 | - | if (allRecords.length === 0) { |
|
| 44 | + | if (documents.length === 0) { |
|
| 76 | 45 | setContent("<p>No recent updates found.</p>"); |
|
| 77 | 46 | return; |
|
| 78 | 47 | } |
|
| 79 | 48 | ||
| 80 | - | const postsHTML = allRecords |
|
| 49 | + | const postsHTML = documents |
|
| 81 | 50 | .map((record) => { |
|
| 82 | 51 | const value = record.value; |
|
| 83 | 52 | const rkey = record.uri.split("/").pop(); |
|
| 53 | + | const publishedAt = new Date( |
|
| 54 | + | value.publishedAt, |
|
| 55 | + | ).toLocaleDateString(); |
|
| 84 | 56 | ||
| 85 | - | // Render based on record type |
|
| 86 | - | if (record.type === "document") { |
|
| 87 | - | // site.standard.document |
|
| 88 | - | const publishedAt = new Date( |
|
| 89 | - | value.publishedAt, |
|
| 90 | - | ).toLocaleDateString(); |
|
| 57 | + | // Extract markdown content |
|
| 58 | + | let contentHTML = ""; |
|
| 59 | + | if (value.content && value.content.markdown) { |
|
| 60 | + | contentHTML = md.render(value.content.markdown); |
|
| 61 | + | } else if (value.textContent) { |
|
| 62 | + | contentHTML = `<p>${value.textContent}</p>`; |
|
| 63 | + | } |
|
| 91 | 64 | ||
| 92 | - | // Extract markdown content |
|
| 93 | - | let contentHTML = ""; |
|
| 94 | - | if (value.content && value.content.markdown) { |
|
| 95 | - | contentHTML = md.render(value.content.markdown); |
|
| 96 | - | } else if (value.textContent) { |
|
| 97 | - | contentHTML = `<p>${value.textContent}</p>`; |
|
| 98 | - | } |
|
| 99 | - | ||
| 100 | - | return ` |
|
| 65 | + | return ` |
|
| 101 | 66 | <a href="/pds?rkey=${rkey}" class="block border-b pb-6 mb-6 last:border-b-0"> |
|
| 102 | 67 | <article> |
|
| 103 | 68 | <h3 class="text-lg font-semibold mb-3">${value.title}</h3> |
|
| 110 | 75 | </article> |
|
| 111 | 76 | </a> |
|
| 112 | 77 | `; |
|
| 113 | - | } else { |
|
| 114 | - | // app.bsky.feed.post (backward compatibility) |
|
| 115 | - | const createdAt = new Date(value.createdAt).toLocaleDateString(); |
|
| 116 | - | ||
| 117 | - | // Handle images |
|
| 118 | - | let imagesHTML = ""; |
|
| 119 | - | if ( |
|
| 120 | - | value.embed && |
|
| 121 | - | value.embed.$type === "app.bsky.embed.images" && |
|
| 122 | - | value.embed.images |
|
| 123 | - | ) { |
|
| 124 | - | const imageElements = value.embed.images |
|
| 125 | - | .map((image: any) => { |
|
| 126 | - | const blobUrl = |
|
| 127 | - | `${PDS_URL}/xrpc/com.atproto.sync.getBlob?` + |
|
| 128 | - | new URLSearchParams({ |
|
| 129 | - | did: DID, |
|
| 130 | - | cid: image.image.ref.$link, |
|
| 131 | - | }); |
|
| 132 | - | ||
| 133 | - | return ` |
|
| 134 | - | <img |
|
| 135 | - | src="${blobUrl}" |
|
| 136 | - | alt="${image.alt || "Image from post"}" |
|
| 137 | - | class="max-w-full h-auto" |
|
| 138 | - | loading="lazy" |
|
| 139 | - | /> |
|
| 140 | - | `; |
|
| 141 | - | }) |
|
| 142 | - | .join(""); |
|
| 143 | - | ||
| 144 | - | imagesHTML = ` |
|
| 145 | - | <div class="mt-3 grid gap-2 ${value.embed.images.length === 1 ? "grid-cols-1" : "grid-cols-2"}"> |
|
| 146 | - | ${imageElements} |
|
| 147 | - | </div> |
|
| 148 | - | `; |
|
| 149 | - | } |
|
| 150 | - | ||
| 151 | - | return ` |
|
| 152 | - | <a href="/pds?rkey=${rkey}" class="block border-b pb-6 mb-6 last:border-b-0"> |
|
| 153 | - | <article> |
|
| 154 | - | <p class="mb-2">${value.text}</p> |
|
| 155 | - | ${imagesHTML} |
|
| 156 | - | <time class="text-sm text-gray-500 mt-2 block">${createdAt}</time> |
|
| 157 | - | </article> |
|
| 158 | - | </a> |
|
| 159 | - | `; |
|
| 160 | - | } |
|
| 161 | 78 | }) |
|
| 162 | 79 | .join(""); |
|
| 163 | 80 | ||
| 1 | - | export interface BlobRef { |
|
| 2 | - | $link: string; |
|
| 1 | + | // Standard.site lexicon types |
|
| 2 | + | export interface StandardSiteMarkdownContent { |
|
| 3 | + | $type: "site.standard.content.markdown"; |
|
| 4 | + | markdown: string; |
|
| 3 | 5 | } |
|
| 4 | 6 | ||
| 5 | - | export interface ImageEmbed { |
|
| 6 | - | image: { |
|
| 7 | - | ref: BlobRef; |
|
| 7 | + | export interface StandardSiteBlobRef { |
|
| 8 | + | $type: "blob"; |
|
| 9 | + | ref: { |
|
| 10 | + | $link: string; |
|
| 8 | 11 | }; |
|
| 9 | - | alt?: string; |
|
| 12 | + | mimeType: string; |
|
| 13 | + | size: number; |
|
| 10 | 14 | } |
|
| 11 | 15 | ||
| 12 | - | export interface PostEmbed { |
|
| 13 | - | $type: string; |
|
| 14 | - | images?: ImageEmbed[]; |
|
| 16 | + | export interface StandardSiteStrongRef { |
|
| 17 | + | uri: string; |
|
| 18 | + | cid: string; |
|
| 15 | 19 | } |
|
| 16 | 20 | ||
| 17 | - | export interface PostValue { |
|
| 18 | - | text: string; |
|
| 19 | - | createdAt: string; |
|
| 20 | - | embed?: PostEmbed; |
|
| 21 | - | reply?: PostRecord; |
|
| 21 | + | export interface StandardSiteDocument { |
|
| 22 | + | $type: "site.standard.document"; |
|
| 23 | + | site: string; // URI or HTTPS URL |
|
| 24 | + | path?: string; // Path to combine with site |
|
| 25 | + | title: string; // Max 128 graphemes |
|
| 26 | + | description?: string; // Max 300 graphemes |
|
| 27 | + | coverImage?: StandardSiteBlobRef; // Max 1MB |
|
| 28 | + | content?: StandardSiteMarkdownContent; // Union type for content |
|
| 29 | + | textContent?: string; // Plaintext without formatting |
|
| 30 | + | bskyPostRef?: StandardSiteStrongRef; // Reference to Bluesky post |
|
| 31 | + | tags?: string[]; // Max 50 graphemes per tag |
|
| 32 | + | publishedAt: string; // ISO datetime string |
|
| 33 | + | updatedAt?: string; // ISO datetime string |
|
| 22 | 34 | } |
|
| 23 | 35 | ||
| 24 | - | export interface PostRecord { |
|
| 36 | + | export interface StandardSiteDocumentRecord { |
|
| 25 | 37 | uri: string; |
|
| 26 | - | value: PostValue; |
|
| 38 | + | cid: string; |
|
| 39 | + | value: StandardSiteDocument; |
|
| 27 | 40 | } |
|
| 28 | 41 | ||
| 29 | 42 | export interface ListRecordsResponse { |
|
| 30 | - | records: PostRecord[]; |
|
| 43 | + | records: StandardSiteDocumentRecord[]; |
|
| 44 | + | cursor?: string; |
|
| 31 | 45 | } |