import React, { useMemo } from "react"; import { AtProtoRecord } from "../core/AtProtoRecord"; import { LeafletDocumentRenderer, type LeafletDocumentRendererProps, } from "../renderers/LeafletDocumentRenderer"; import type { LeafletDocumentRecord, LeafletPublicationRecord, } from "../types/leaflet"; import { parseAtUri, toBlueskyPostUrl, leafletRkeyUrl, normalizeLeafletBasePath, } from "../utils/at-uri"; import { useAtProtoRecord } from "../hooks/useAtProtoRecord"; /** * Props for rendering a Leaflet document record. */ export interface LeafletDocumentProps { /** * DID of the Leaflet publisher. */ did: string; /** * Record key of the document within the Leaflet collection. */ rkey: string; /** * Prefetched Leaflet document record. When provided, skips fetching from the network. */ record?: LeafletDocumentRecord; /** * Optional custom renderer for advanced layouts. */ renderer?: React.ComponentType; /** * React node rendered before data begins loading. */ fallback?: React.ReactNode; /** * Indicator rendered while data is being fetched from the PDS. */ loadingIndicator?: React.ReactNode; } /** * Props provided to renderer overrides for Leaflet documents. */ export type LeafletDocumentRendererInjectedProps = LeafletDocumentRendererProps; /** NSID for Leaflet document records. */ export const LEAFLET_DOCUMENT_COLLECTION = "pub.leaflet.document"; /** * Loads a Leaflet document along with its associated publication record and renders it * using the provided or default renderer. * * @param did - DID of the Leaflet publisher. * @param rkey - Record key of the Leaflet document. * @param renderer - Optional renderer override used in place of the default. * @param fallback - Node rendered before loading begins. * @param loadingIndicator - Node rendered while the document or publication records are loading. * @param colorScheme - Preferred color scheme forwarded to the renderer. * @returns A JSX subtree that renders a Leaflet document with contextual metadata. */ export const LeafletDocument: React.FC = React.memo(({ did, rkey, record, renderer, fallback, loadingIndicator, }) => { const Comp: React.ComponentType = renderer ?? ((props) => ); const Wrapped: React.FC<{ record: LeafletDocumentRecord; loading: boolean; error?: Error; }> = (props) => { const publicationUri = useMemo( () => parseAtUri(props.record.publication), [props.record.publication], ); const { record: publicationRecord } = useAtProtoRecord({ did: publicationUri?.did, collection: publicationUri?.collection ?? "pub.leaflet.publication", rkey: publicationUri?.rkey ?? "", }); const publicationBaseUrl = normalizeLeafletBasePath( publicationRecord?.base_path, ); const canonicalUrl = resolveCanonicalUrl( props.record, did, rkey, publicationRecord?.base_path, ); return ( ); }; if (record !== undefined) { return ( record={record} renderer={Wrapped} fallback={fallback} loadingIndicator={loadingIndicator} /> ); } return ( did={did} collection={LEAFLET_DOCUMENT_COLLECTION} rkey={rkey} renderer={Wrapped} fallback={fallback} loadingIndicator={loadingIndicator} /> ); }); /** * Determines the best canonical URL to expose for a Leaflet document. * * @param record - Leaflet document record under review. * @param did - Publisher DID. * @param rkey - Record key for the document. * @param publicationBasePath - Optional base path configured by the publication. * @returns A URL to use for canonical links. */ function resolveCanonicalUrl( record: LeafletDocumentRecord, did: string, rkey: string, publicationBasePath?: string, ): string { const publicationUrl = leafletRkeyUrl(publicationBasePath, rkey); if (publicationUrl) return publicationUrl; const postUri = record.postRef?.uri; if (postUri) { const parsed = parseAtUri(postUri); const href = parsed ? toBlueskyPostUrl(parsed) : undefined; if (href) return href; } return `at://${encodeURIComponent(did)}/${LEAFLET_DOCUMENT_COLLECTION}/${encodeURIComponent(rkey)}`; } export default LeafletDocument;