a tool for shared writing and social publishing

set doc path properly

+39 -26
+5 -3
actions/publishToPublication.ts
··· 217 217 } 218 218 }); 219 219 220 + // Determine the rkey early since we need it for the path field 221 + const rkey = existingDocUri ? new AtUri(existingDocUri).rkey : TID.nextStr(); 222 + 220 223 // Create record based on the document type 221 224 let record: PubLeafletDocument.Record | SiteStandardDocument.Record; 222 225 223 226 if (documentType === "site.standard.document") { 224 227 // site.standard.document format 225 - // For standalone docs, use a constructed site URI; for publication docs, use the publication URI 228 + // For standalone docs, use HTTPS URL; for publication docs, use the publication AT-URI 226 229 const siteUri = publication_uri || `https://leaflet.pub/p/${credentialSession.did}`; 227 230 228 231 record = { 229 232 $type: "site.standard.document", 230 233 title: title || "Untitled", 231 234 site: siteUri, 235 + path: rkey, 232 236 publishedAt: existingRecord.publishedAt || new Date().toISOString(), 233 237 ...(description && { description }), 234 238 ...(tags !== undefined && { tags }), ··· 257 261 } satisfies PubLeafletDocument.Record; 258 262 } 259 263 260 - // Keep the same rkey if updating an existing document 261 - let rkey = existingDocUri ? new AtUri(existingDocUri).rkey : TID.nextStr(); 262 264 let { data: result } = await agent.com.atproto.repo.putRecord({ 263 265 rkey, 264 266 repo: credentialSession.did!,
+1 -1
app/(home-pages)/p/[didOrHandle]/getProfilePosts.ts
··· 63 63 64 64 for (let doc of docs || []) { 65 65 // Normalize records - filter out unrecognized formats 66 - const normalizedData = normalizeDocumentRecord(doc.data); 66 + const normalizedData = normalizeDocumentRecord(doc.data, doc.uri); 67 67 if (!normalizedData) continue; 68 68 69 69 let pubFromDoc = doc.documents_in_publications?.[0]?.publications;
+1 -1
app/(home-pages)/reader/getReaderFeed.ts
··· 55 55 let handle = await idResolver.did.resolve(uri.host); 56 56 57 57 // Normalize records - filter out unrecognized formats 58 - const normalizedData = normalizeDocumentRecord(post.data); 58 + const normalizedData = normalizeDocumentRecord(post.data, post.uri); 59 59 if (!normalizedData) return null; 60 60 61 61 const normalizedPubRecord = normalizePublicationRecord(pub?.record);
+1 -1
app/(home-pages)/tag/[tag]/getDocumentsByTag.ts
··· 41 41 } 42 42 43 43 // Normalize the document data - skip unrecognized formats 44 - const normalizedData = normalizeDocumentRecord(doc.data); 44 + const normalizedData = normalizeDocumentRecord(doc.data, doc.uri); 45 45 if (!normalizedData) { 46 46 return null; 47 47 }
+2 -2
app/api/inngest/functions/migrate_user_to_standard.ts
··· 171 171 } 172 172 173 173 const rkey = aturi.rkey; 174 - const normalized = normalizeDocumentRecord(doc.data); 174 + const normalized = normalizeDocumentRecord(doc.data, doc.uri); 175 175 176 176 if (!normalized) { 177 177 stats.errors.push(`Document ${doc.uri}: Failed to normalize document record`); ··· 197 197 $type: "site.standard.document", 198 198 title: normalized.title || "Untitled", 199 199 site: siteValue, 200 + path: rkey, 200 201 publishedAt: normalized.publishedAt || new Date().toISOString(), 201 202 description: normalized.description, 202 203 content: normalized.content, 203 - path: normalized.path, 204 204 tags: normalized.tags, 205 205 coverImage: normalized.coverImage, 206 206 bskyPostRef: normalized.bskyPostRef,
+1 -1
app/api/rpc/[command]/get_publication_data.ts
··· 70 70 const documents = (publication?.documents_in_publications || []) 71 71 .map((dip) => { 72 72 if (!dip.documents) return null; 73 - const normalized = normalizeDocumentRecord(dip.documents.data); 73 + const normalized = normalizeDocumentRecord(dip.documents.data, dip.documents.uri); 74 74 if (!normalized) return null; 75 75 return { 76 76 uri: dip.documents.uri,
+2 -2
app/lish/[did]/[publication]/[rkey]/getPostPageData.ts
··· 33 33 if (!document) return null; 34 34 35 35 // Normalize the document record - this is the primary way consumers should access document data 36 - const normalizedDocument = normalizeDocumentRecord(document.data); 36 + const normalizedDocument = normalizeDocumentRecord(document.data, document.uri); 37 37 if (!normalizedDocument) return null; 38 38 39 39 // Normalize the publication record - this is the primary way consumers should access publication data ··· 83 83 // Filter and sort documents by publishedAt 84 84 const sortedDocs = allDocs 85 85 .map((dip) => { 86 - const normalizedData = normalizeDocumentRecord(dip?.documents?.data); 86 + const normalizedData = normalizeDocumentRecord(dip?.documents?.data, dip?.documents?.uri); 87 87 return { 88 88 uri: dip?.documents?.uri, 89 89 title: normalizedData?.title,
+1 -1
app/lish/[did]/[publication]/generateFeed.ts
··· 54 54 await Promise.all( 55 55 publication.documents_in_publications.map(async (doc) => { 56 56 if (!doc.documents) return; 57 - const record = normalizeDocumentRecord(doc.documents?.data); 57 + const record = normalizeDocumentRecord(doc.documents?.data, doc.documents?.uri); 58 58 const uri = new AtUri(doc.documents?.uri); 59 59 const rkey = uri.rkey; 60 60 if (!record) return;
+1 -1
app/lish/feeds/[...path]/route.ts
··· 37 37 let posts = pub.publications?.documents_in_publications || []; 38 38 return posts.flatMap((p) => { 39 39 if (!p.documents?.data) return []; 40 - const normalizedDoc = normalizeDocumentRecord(p.documents.data); 40 + const normalizedDoc = normalizeDocumentRecord(p.documents.data, p.documents.uri); 41 41 if (!normalizedDoc?.bskyPostRef) return []; 42 42 return { post: normalizedDoc.bskyPostRef.uri }; 43 43 });
+1 -1
feeds/index.ts
··· 136 136 cursor: newCursor || cursor, 137 137 feed: posts.flatMap((p) => { 138 138 if (!p.data) return []; 139 - const normalizedDoc = normalizeDocumentRecord(p.data); 139 + const normalizedDoc = normalizeDocumentRecord(p.data, p.uri); 140 140 if (!normalizedDoc?.bskyPostRef) return []; 141 141 return { post: normalizedDoc.bskyPostRef.uri }; 142 142 }),
+7 -1
lexicons/src/normalize.ts
··· 21 21 import type * as SiteStandardThemeBasic from "../api/types/site/standard/theme/basic"; 22 22 import type * as PubLeafletThemeColor from "../api/types/pub/leaflet/theme/color"; 23 23 import type { $Typed } from "../api/util"; 24 + import { AtUri } from "@atproto/syntax"; 24 25 25 26 // Normalized document type - uses the generated site.standard.document type 26 27 // with an additional optional theme field for backwards compatibility ··· 143 144 * Normalizes a document record from either format to the standard format. 144 145 * 145 146 * @param record - The document record from the database (either pub.leaflet or site.standard) 147 + * @param uri - Optional document URI, used to extract the rkey for the path field when normalizing pub.leaflet records 146 148 * @returns A normalized document in site.standard format, or null if invalid/unrecognized 147 149 */ 148 - export function normalizeDocument(record: unknown): NormalizedDocument | null { 150 + export function normalizeDocument(record: unknown, uri?: string): NormalizedDocument | null { 149 151 if (!record || typeof record !== "object") return null; 150 152 151 153 // Pass through site.standard records directly (theme is already in correct format if present) ··· 168 170 // This matches the pattern used in publishToPublication.ts for new standalone docs 169 171 const site = record.publication || `https://leaflet.pub/p/${record.author}`; 170 172 173 + // Extract path from URI if available 174 + const path = uri ? new AtUri(uri).rkey : undefined; 175 + 171 176 // Wrap pages in pub.leaflet.content structure 172 177 const content: $Typed<PubLeafletContent.Main> | undefined = record.pages 173 178 ? { ··· 180 185 $type: "site.standard.document", 181 186 title: record.title, 182 187 site, 188 + path, 183 189 publishedAt, 184 190 description: record.description, 185 191 tags: record.tags,
+6 -6
src/notifications.ts
··· 105 105 ? comments?.find((c) => c.uri === notification.data.parent_uri) 106 106 : undefined, 107 107 commentData, 108 - normalizedDocument: normalizeDocumentRecord(commentData.documents?.data), 108 + normalizedDocument: normalizeDocumentRecord(commentData.documents?.data, commentData.documents?.uri), 109 109 normalizedPublication: normalizePublicationRecord( 110 110 commentData.documents?.documents_in_publications[0]?.publications?.record, 111 111 ), ··· 198 198 document_uri: notification.data.document_uri, 199 199 bskyPost, 200 200 document, 201 - normalizedDocument: normalizeDocumentRecord(document.data), 201 + normalizedDocument: normalizeDocumentRecord(document.data, document.uri), 202 202 normalizedPublication: normalizePublicationRecord( 203 203 document.documents_in_publications[0]?.publications?.record, 204 204 ), ··· 299 299 documentCreatorHandle, 300 300 mentionedPublication, 301 301 mentionedDocument: mentionedDoc, 302 - normalizedDocument: normalizeDocumentRecord(document.data), 302 + normalizedDocument: normalizeDocumentRecord(document.data, document.uri), 303 303 normalizedPublication: normalizePublicationRecord( 304 304 document.documents_in_publications[0]?.publications?.record, 305 305 ), 306 306 normalizedMentionedPublication: normalizePublicationRecord(mentionedPublication?.record), 307 - normalizedMentionedDocument: normalizeDocumentRecord(mentionedDoc?.data), 307 + normalizedMentionedDocument: normalizeDocumentRecord(mentionedDoc?.data, mentionedDoc?.uri), 308 308 }; 309 309 }) 310 310 .filter((n) => n !== null); ··· 404 404 commenterHandle, 405 405 mentionedPublication, 406 406 mentionedDocument: mentionedDoc, 407 - normalizedDocument: normalizeDocumentRecord(commentData.documents?.data), 407 + normalizedDocument: normalizeDocumentRecord(commentData.documents?.data, commentData.documents?.uri), 408 408 normalizedPublication: normalizePublicationRecord( 409 409 commentData.documents?.documents_in_publications[0]?.publications?.record, 410 410 ), 411 411 normalizedMentionedPublication: normalizePublicationRecord(mentionedPublication?.record), 412 - normalizedMentionedDocument: normalizeDocumentRecord(mentionedDoc?.data), 412 + normalizedMentionedDocument: normalizeDocumentRecord(mentionedDoc?.data, mentionedDoc?.uri), 413 413 }; 414 414 }) 415 415 .filter((n) => n !== null);
+10 -5
src/utils/normalizeRecords.ts
··· 17 17 * Normalizes a document record from a database query result. 18 18 * Returns the normalized document or null if the record is invalid/unrecognized. 19 19 * 20 + * @param data - The document record data from the database 21 + * @param uri - Optional document URI, used to extract the rkey for the path field when normalizing pub.leaflet records 22 + * 20 23 * @example 21 - * const doc = normalizeDocumentRecord(dbResult.data); 24 + * const doc = normalizeDocumentRecord(dbResult.data, dbResult.uri); 22 25 * if (doc) { 23 26 * // doc is NormalizedDocument with proper typing 24 27 * console.log(doc.title, doc.site, doc.publishedAt); 25 28 * } 26 29 */ 27 30 export function normalizeDocumentRecord( 28 - data: Json | unknown 31 + data: Json | unknown, 32 + uri?: string 29 33 ): NormalizedDocument | null { 30 - return normalizeDocument(data); 34 + return normalizeDocument(data, uri); 31 35 } 32 36 33 37 /** ··· 69 73 70 74 /** 71 75 * Normalizes a document row in place, returning a properly typed row. 76 + * If the row has a `uri` field, it will be used to extract the path. 72 77 */ 73 - export function normalizeDocumentRow<T extends { data: Json | unknown }>( 78 + export function normalizeDocumentRow<T extends { data: Json | unknown; uri?: string }>( 74 79 row: T 75 80 ): DocumentRowWithNormalizedData<T> { 76 81 return { 77 82 ...row, 78 - data: normalizeDocumentRecord(row.data), 83 + data: normalizeDocumentRecord(row.data, row.uri), 79 84 }; 80 85 } 81 86