A React component library for rendering common AT Protocol records for applications such as Bluesky and Leaflet.
1import React, { useMemo } from "react"; 2import { AtProtoRecord } from "../core/AtProtoRecord"; 3import { 4 LeafletDocumentRenderer, 5 type LeafletDocumentRendererProps, 6} from "../renderers/LeafletDocumentRenderer"; 7import type { 8 LeafletDocumentRecord, 9 LeafletPublicationRecord, 10} from "../types/leaflet"; 11import type { ColorSchemePreference } from "../hooks/useColorScheme"; 12import { 13 parseAtUri, 14 toBlueskyPostUrl, 15 leafletRkeyUrl, 16 normalizeLeafletBasePath, 17} from "../utils/at-uri"; 18import { useAtProtoRecord } from "../hooks/useAtProtoRecord"; 19 20/** 21 * Props for rendering a Leaflet document record. 22 */ 23export interface LeafletDocumentProps { 24 /** 25 * DID of the Leaflet publisher. 26 */ 27 did: string; 28 /** 29 * Record key of the document within the Leaflet collection. 30 */ 31 rkey: string; 32 /** 33 * Prefetched Leaflet document record. When provided, skips fetching from the network. 34 */ 35 record?: LeafletDocumentRecord; 36 /** 37 * Optional custom renderer for advanced layouts. 38 */ 39 renderer?: React.ComponentType<LeafletDocumentRendererInjectedProps>; 40 /** 41 * React node rendered before data begins loading. 42 */ 43 fallback?: React.ReactNode; 44 /** 45 * Indicator rendered while data is being fetched from the PDS. 46 */ 47 loadingIndicator?: React.ReactNode; 48 /** 49 * Preferred color scheme to forward to the renderer. 50 */ 51 colorScheme?: ColorSchemePreference; 52} 53 54/** 55 * Props provided to renderer overrides for Leaflet documents. 56 */ 57export type LeafletDocumentRendererInjectedProps = LeafletDocumentRendererProps; 58 59/** NSID for Leaflet document records. */ 60export const LEAFLET_DOCUMENT_COLLECTION = "pub.leaflet.document"; 61 62/** 63 * Loads a Leaflet document along with its associated publication record and renders it 64 * using the provided or default renderer. 65 * 66 * @param did - DID of the Leaflet publisher. 67 * @param rkey - Record key of the Leaflet document. 68 * @param renderer - Optional renderer override used in place of the default. 69 * @param fallback - Node rendered before loading begins. 70 * @param loadingIndicator - Node rendered while the document or publication records are loading. 71 * @param colorScheme - Preferred color scheme forwarded to the renderer. 72 * @returns A JSX subtree that renders a Leaflet document with contextual metadata. 73 */ 74export const LeafletDocument: React.FC<LeafletDocumentProps> = ({ 75 did, 76 rkey, 77 record, 78 renderer, 79 fallback, 80 loadingIndicator, 81 colorScheme, 82}) => { 83 const Comp: React.ComponentType<LeafletDocumentRendererInjectedProps> = 84 renderer ?? ((props) => <LeafletDocumentRenderer {...props} />); 85 86 const Wrapped: React.FC<{ 87 record: LeafletDocumentRecord; 88 loading: boolean; 89 error?: Error; 90 }> = (props) => { 91 const publicationUri = useMemo( 92 () => parseAtUri(props.record.publication), 93 [props.record.publication], 94 ); 95 const { record: publicationRecord } = 96 useAtProtoRecord<LeafletPublicationRecord>({ 97 did: publicationUri?.did, 98 collection: 99 publicationUri?.collection ?? "pub.leaflet.publication", 100 rkey: publicationUri?.rkey ?? "", 101 }); 102 const publicationBaseUrl = normalizeLeafletBasePath( 103 publicationRecord?.base_path, 104 ); 105 const canonicalUrl = resolveCanonicalUrl( 106 props.record, 107 did, 108 rkey, 109 publicationRecord?.base_path, 110 ); 111 return ( 112 <Comp 113 {...props} 114 colorScheme={colorScheme} 115 did={did} 116 rkey={rkey} 117 canonicalUrl={canonicalUrl} 118 publicationBaseUrl={publicationBaseUrl} 119 publicationRecord={publicationRecord} 120 /> 121 ); 122 }; 123 124 if (record !== undefined) { 125 return ( 126 <AtProtoRecord<LeafletDocumentRecord> 127 record={record} 128 renderer={Wrapped} 129 fallback={fallback} 130 loadingIndicator={loadingIndicator} 131 /> 132 ); 133 } 134 135 return ( 136 <AtProtoRecord<LeafletDocumentRecord> 137 did={did} 138 collection={LEAFLET_DOCUMENT_COLLECTION} 139 rkey={rkey} 140 renderer={Wrapped} 141 fallback={fallback} 142 loadingIndicator={loadingIndicator} 143 /> 144 ); 145}; 146 147/** 148 * Determines the best canonical URL to expose for a Leaflet document. 149 * 150 * @param record - Leaflet document record under review. 151 * @param did - Publisher DID. 152 * @param rkey - Record key for the document. 153 * @param publicationBasePath - Optional base path configured by the publication. 154 * @returns A URL to use for canonical links. 155 */ 156function resolveCanonicalUrl( 157 record: LeafletDocumentRecord, 158 did: string, 159 rkey: string, 160 publicationBasePath?: string, 161): string { 162 const publicationUrl = leafletRkeyUrl(publicationBasePath, rkey); 163 if (publicationUrl) return publicationUrl; 164 const postUri = record.postRef?.uri; 165 if (postUri) { 166 const parsed = parseAtUri(postUri); 167 const href = parsed ? toBlueskyPostUrl(parsed) : undefined; 168 if (href) return href; 169 } 170 return `at://${encodeURIComponent(did)}/${LEAFLET_DOCUMENT_COLLECTION}/${encodeURIComponent(rkey)}`; 171} 172 173export default LeafletDocument;