chore: removed regular posts and opted for only standard.site cf0bb21b
Steve · 2026-01-08 19:23 2 file(s) · +53 −122
packages/client/src/components/NowUpdates.tsx +22 −105
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
packages/server/src/types.ts +31 −17
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
}