a tool for shared writing and social publishing

add themes for standalone docs, etc

+56 -19
+2
actions/publishToPublication.ts
··· 233 233 ...(description && { description }), 234 234 ...(tags !== undefined && { tags }), 235 235 ...(coverImageBlob && { coverImage: coverImageBlob }), 236 + // Include theme for standalone documents (not for publication documents) 237 + ...(!publication_uri && theme && { theme }), 236 238 content: { 237 239 $type: "pub.leaflet.content" as const, 238 240 pages: pagesArray,
+2 -2
app/lish/[did]/[publication]/[rkey]/getPostPageData.ts
··· 6 6 type NormalizedDocument, 7 7 type NormalizedPublication, 8 8 } from "src/utils/normalizeRecords"; 9 - import { PubLeafletPublication } from "lexicons/api"; 9 + import { PubLeafletPublication, SiteStandardPublication } from "lexicons/api"; 10 10 import { documentUriFilter } from "src/utils/uriHelpers"; 11 11 12 12 export async function getPostPageData(did: string, rkey: string) { ··· 126 126 uri: rawPub.uri, 127 127 name: rawPub.name, 128 128 identity_did: rawPub.identity_did, 129 - record: rawPub.record as PubLeafletPublication.Record | null, 129 + record: rawPub.record as PubLeafletPublication.Record | SiteStandardPublication.Record | null, 130 130 publication_subscriptions: rawPub.publication_subscriptions || [], 131 131 } : null; 132 132
+17 -9
app/lish/createPub/updatePublication.ts
··· 118 118 } 119 119 120 120 /** 121 - * Helper to build preferences object with correct $type based on publication type 121 + * Helper to build preferences object with correct $type based on publication type. 122 + * site.standard.publication preferences is a simple object type, no $type needed. 123 + * pub.leaflet.publication preferences requires $type. 122 124 */ 123 125 function buildPreferences( 124 126 preferencesData: NormalizedPublication["preferences"] | undefined, 125 127 publicationType: PublicationType, 126 - ) { 128 + ): SiteStandardPublication.Preferences | PubLeafletPublication.Preferences | undefined { 127 129 if (!preferencesData) return undefined; 128 130 129 - const $type = 130 - publicationType === "site.standard.publication" 131 - ? ("site.standard.publication#preferences" as const) 132 - : ("pub.leaflet.publication#preferences" as const); 133 - 134 - return { 135 - $type, 131 + const basePreferences = { 136 132 showInDiscover: preferencesData.showInDiscover, 137 133 showComments: preferencesData.showComments, 138 134 showMentions: preferencesData.showMentions, 139 135 showPrevNext: preferencesData.showPrevNext, 140 136 }; 137 + 138 + if (publicationType === "site.standard.publication") { 139 + return basePreferences; 140 + } 141 + 142 + return { 143 + $type: "pub.leaflet.publication#preferences" as const, 144 + ...basePreferences, 145 + }; 141 146 } 142 147 143 148 /** ··· 152 157 description?: string; 153 158 icon?: any; 154 159 theme?: any; 160 + basicTheme?: NormalizedPublication["basicTheme"]; 155 161 preferences?: NormalizedPublication["preferences"]; 156 162 basePath?: string; 157 163 }, ··· 162 168 : normalizedPub?.description; 163 169 const icon = overrides.icon !== undefined ? overrides.icon : normalizedPub?.icon; 164 170 const theme = overrides.theme !== undefined ? overrides.theme : normalizedPub?.theme; 171 + const basicTheme = overrides.basicTheme !== undefined ? overrides.basicTheme : normalizedPub?.basicTheme; 165 172 const preferencesData = overrides.preferences ?? normalizedPub?.preferences; 166 173 const basePath = overrides.basePath ?? existingBasePath; 167 174 ··· 172 179 description, 173 180 icon, 174 181 theme, 182 + basicTheme, 175 183 preferences: buildPreferences(preferencesData, publicationType), 176 184 url: basePath ? `https://${basePath}` : normalizedPub?.url || "", 177 185 } as SiteStandardPublication.Record;
+7 -1
lexicons/api/lexicons.ts
··· 2143 2143 }, 2144 2144 site: { 2145 2145 description: 2146 - 'URI to the site or publication this document belongs to (https or at-uri)', 2146 + 'URI to the site or publication this document belongs to. Supports both AT-URIs (at://did/collection/rkey) for publication references and HTTPS URLs (https://example.com) for standalone documents or external sites.', 2147 2147 format: 'uri', 2148 2148 type: 'string', 2149 2149 }, ··· 2157 2157 }, 2158 2158 textContent: { 2159 2159 type: 'string', 2160 + }, 2161 + theme: { 2162 + description: 2163 + 'Theme for standalone documents. For documents in publications, theme is inherited from the publication.', 2164 + ref: 'lex:pub.leaflet.publication#theme', 2165 + type: 'ref', 2160 2166 }, 2161 2167 title: { 2162 2168 maxGraphemes: 128,
+3 -1
lexicons/api/types/site/standard/document.ts
··· 7 7 import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' 8 8 import type * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef' 9 9 import type * as PubLeafletContent from '../../pub/leaflet/content' 10 + import type * as PubLeafletPublication from '../../pub/leaflet/publication' 10 11 11 12 const is$typed = _is$typed, 12 13 validate = _validate ··· 21 22 /** combine with the publication url or the document site to construct a full url to the document */ 22 23 path?: string 23 24 publishedAt: string 24 - /** URI to the site or publication this document belongs to (https or at-uri) */ 25 + /** URI to the site or publication this document belongs to. Supports both AT-URIs (at://did/collection/rkey) for publication references and HTTPS URLs (https://example.com) for standalone documents or external sites. */ 25 26 site: string 26 27 tags?: string[] 27 28 textContent?: string 29 + theme?: PubLeafletPublication.Theme 28 30 title: string 29 31 updatedAt?: string 30 32 [k: string]: unknown
+6 -1
lexicons/site/standard/document.json
··· 32 32 "type": "string" 33 33 }, 34 34 "site": { 35 - "description": "URI to the site or publication this document belongs to (https or at-uri)", 35 + "description": "URI to the site or publication this document belongs to. Supports both AT-URIs (at://did/collection/rkey) for publication references and HTTPS URLs (https://example.com) for standalone documents or external sites.", 36 36 "format": "uri", 37 37 "type": "string" 38 38 }, ··· 46 46 }, 47 47 "textContent": { 48 48 "type": "string" 49 + }, 50 + "theme": { 51 + "description": "Theme for standalone documents. For documents in publications, theme is inherited from the publication.", 52 + "ref": "pub.leaflet.publication#theme", 53 + "type": "ref" 49 54 }, 50 55 "title": { 51 56 "maxGraphemes": 128,
+18 -4
lexicons/src/normalize.ts
··· 3 3 * 4 4 * The standard format (site.standard.*) is used as the canonical representation for 5 5 * reading data from the database, while both formats are accepted for storage. 6 + * 7 + * ## Site Field Format 8 + * 9 + * The `site` field in site.standard.document supports two URI formats: 10 + * - AT-URIs (at://did/collection/rkey) - Used when document belongs to an AT Protocol publication 11 + * - HTTPS URLs (https://example.com) - Used for standalone documents or external sites 12 + * 13 + * Both formats are valid and should be handled by consumers. 6 14 */ 7 15 8 16 import type * as PubLeafletDocument from "../api/types/pub/leaflet/document"; ··· 140 148 export function normalizeDocument(record: unknown): NormalizedDocument | null { 141 149 if (!record || typeof record !== "object") return null; 142 150 143 - // Pass through site.standard records directly 151 + // Pass through site.standard records directly (theme is already in correct format if present) 144 152 if (isStandardDocument(record)) { 145 - return record as NormalizedDocument; 153 + return { 154 + ...record, 155 + theme: record.theme, 156 + } as NormalizedDocument; 146 157 } 147 158 148 159 if (isLeafletDocument(record)) { 149 160 // Convert from pub.leaflet to site.standard 150 - const site = record.publication; 151 161 const publishedAt = record.publishedAt; 152 162 153 - if (!site || !publishedAt) { 163 + if (!publishedAt) { 154 164 return null; 155 165 } 166 + 167 + // For standalone documents (no publication), construct a site URL from the author 168 + // This matches the pattern used in publishToPublication.ts for new standalone docs 169 + const site = record.publication || `https://leaflet.pub/p/${record.author}`; 156 170 157 171 // Wrap pages in pub.leaflet.content structure 158 172 const content: $Typed<PubLeafletContent.Main> | undefined = record.pages
+1 -1
src/utils/uriHelpers.ts
··· 30 30 export function publicationNameOrUriFilter(did: string, nameOrRkey: string): string { 31 31 const standard = AtUri.make(did, ids.SiteStandardPublication, nameOrRkey).toString(); 32 32 const legacy = AtUri.make(did, ids.PubLeafletPublication, nameOrRkey).toString(); 33 - return `name.eq."${nameOrRkey}",uri.eq.${standard},uri.eq.${legacy}`; 33 + return `name.eq.${nameOrRkey},uri.eq.${standard},uri.eq.${legacy}`; 34 34 }