Skip to content
File

Blob: src/client/components/active-page/shared.tsx

typescript71 lines
1import { useEffect, type ReactNode } from "react";
2import { SharedPageMentionSurface } from "@/client/components/page-mention/shared-surface";
3import { ActivePageProvider } from "@/client/components/active-page/provider";
4import { useActivePageActions, useActivePageSync } from "@/client/components/active-page/use-active-page";
5import { useReadyShareView } from "@/client/components/share/use-share-view";
6import type { ActivePageInitialSnapshot } from "@/client/lib/active-page-model";
7import { parseDocMessage } from "@/shared/doc-messages";
8 
9/**
10 * Share boundary: keeps share-link resolution token-scoped in ShareViewProvider
11 * and mounts active-page state only for the active shared page. The
12 * `key={activePageId}` forces a remount on subpage↔root transitions so the
13 * `initialSnapshot` seed for the root page lands via the useState initializer
14 * rather than an effect — no intermediate paint with empty metadata.
15 */
16export function SharedActivePageBoundary({ children }: { children: ReactNode }) {
17 const { token, workspaceId, rootPageId, rootPage, activePageId } = useReadyShareView();
18 
19 const initialSnapshot: ActivePageInitialSnapshot | null =
20 activePageId === rootPageId
21 ? {
22 snapshot: {
23 id: rootPage.id,
24 workspaceId,
25 kind: rootPage.kind,
26 title: rootPage.title,
27 icon: rootPage.icon,
28 coverUrl: rootPage.cover_url,
29 },
30 access: { mode: rootPage.permission },
31 }
32 : null;
33 
34 return (
35 <ActivePageProvider
36 key={activePageId}
37 surface="shared"
38 workspaceId={workspaceId}
39 pageId={activePageId}
40 accessMode="shared"
41 role={null}
42 cachedPageMeta={null}
43 cachedAccess={null}
44 shareToken={token}
45 initialSnapshot={initialSnapshot}
46 >
47 <SharedMetadataListener />
48 <SharedPageMentionSurface>{children}</SharedPageMentionSurface>
49 </ActivePageProvider>
50 );
51}
52 
53function SharedMetadataListener() {
54 const { syncProvider } = useActivePageSync();
55 const { patchPage } = useActivePageActions();
56 
57 useEffect(() => {
58 if (!syncProvider) return;
59 const handler = (message: string) => {
60 const msg = parseDocMessage(message);
61 if (msg?.type === "page-metadata-updated") {
62 patchPage({ icon: msg.icon, coverUrl: msg.cover_url });
63 }
64 };
65 syncProvider.on("custom-message", handler);
66 return () => syncProvider.off("custom-message", handler);
67 }, [syncProvider, patchPage]);
68 
69 return null;
70}