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 {
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;