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