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 * Optional custom renderer for advanced layouts. 34 */ 35 renderer?: React.ComponentType<LeafletDocumentRendererInjectedProps>; 36 /** 37 * React node rendered before data begins loading. 38 */ 39 fallback?: React.ReactNode; 40 /** 41 * Indicator rendered while data is being fetched from the PDS. 42 */ 43 loadingIndicator?: React.ReactNode; 44 /** 45 * Preferred color scheme to forward to the renderer. 46 */ 47 colorScheme?: ColorSchemePreference; 48} 49 50/** 51 * Props provided to renderer overrides for Leaflet documents. 52 */ 53export type LeafletDocumentRendererInjectedProps = LeafletDocumentRendererProps; 54 55/** NSID for Leaflet document records. */ 56export const LEAFLET_DOCUMENT_COLLECTION = "pub.leaflet.document"; 57 58/** 59 * Loads a Leaflet document along with its associated publication record and renders it 60 * using the provided or default renderer. 61 * 62 * @param did - DID of the Leaflet publisher. 63 * @param rkey - Record key of the Leaflet document. 64 * @param renderer - Optional renderer override used in place of the default. 65 * @param fallback - Node rendered before loading begins. 66 * @param loadingIndicator - Node rendered while the document or publication records are loading. 67 * @param colorScheme - Preferred color scheme forwarded to the renderer. 68 * @returns A JSX subtree that renders a Leaflet document with contextual metadata. 69 */ 70export const LeafletDocument: React.FC<LeafletDocumentProps> = ({ 71 did, 72 rkey, 73 renderer, 74 fallback, 75 loadingIndicator, 76 colorScheme, 77}) => { 78 const Comp: React.ComponentType<LeafletDocumentRendererInjectedProps> = 79 renderer ?? ((props) => <LeafletDocumentRenderer {...props} />); 80 81 const Wrapped: React.FC<{ 82 record: LeafletDocumentRecord; 83 loading: boolean; 84 error?: Error; 85 }> = (props) => { 86 const publicationUri = useMemo( 87 () => parseAtUri(props.record.publication), 88 [props.record.publication], 89 ); 90 const { record: publicationRecord } = 91 useAtProtoRecord<LeafletPublicationRecord>({ 92 did: publicationUri?.did, 93 collection: 94 publicationUri?.collection ?? "pub.leaflet.publication", 95 rkey: publicationUri?.rkey ?? "", 96 }); 97 const publicationBaseUrl = normalizeLeafletBasePath( 98 publicationRecord?.base_path, 99 ); 100 const canonicalUrl = resolveCanonicalUrl( 101 props.record, 102 did, 103 rkey, 104 publicationRecord?.base_path, 105 ); 106 return ( 107 <Comp 108 {...props} 109 colorScheme={colorScheme} 110 did={did} 111 rkey={rkey} 112 canonicalUrl={canonicalUrl} 113 publicationBaseUrl={publicationBaseUrl} 114 publicationRecord={publicationRecord} 115 /> 116 ); 117 }; 118 119 return ( 120 <AtProtoRecord<LeafletDocumentRecord> 121 did={did} 122 collection={LEAFLET_DOCUMENT_COLLECTION} 123 rkey={rkey} 124 renderer={Wrapped} 125 fallback={fallback} 126 loadingIndicator={loadingIndicator} 127 /> 128 ); 129}; 130 131/** 132 * Determines the best canonical URL to expose for a Leaflet document. 133 * 134 * @param record - Leaflet document record under review. 135 * @param did - Publisher DID. 136 * @param rkey - Record key for the document. 137 * @param publicationBasePath - Optional base path configured by the publication. 138 * @returns A URL to use for canonical links. 139 */ 140function resolveCanonicalUrl( 141 record: LeafletDocumentRecord, 142 did: string, 143 rkey: string, 144 publicationBasePath?: string, 145): string { 146 const publicationUrl = leafletRkeyUrl(publicationBasePath, rkey); 147 if (publicationUrl) return publicationUrl; 148 const postUri = record.postRef?.uri; 149 if (postUri) { 150 const parsed = parseAtUri(postUri); 151 const href = parsed ? toBlueskyPostUrl(parsed) : undefined; 152 if (href) return href; 153 } 154 return `at://${encodeURIComponent(did)}/${LEAFLET_DOCUMENT_COLLECTION}/${encodeURIComponent(rkey)}`; 155} 156 157export default LeafletDocument;