Skip to content
File

Blob: src/client/components/page-mention/provider.tsx

typescript105 lines
1import { useEffect, useEffectEvent, useMemo, useState, useSyncExternalStore, type ReactNode } from "react";
2import { PageMentionContext } from "./context";
3import { createPageMentionResolver, type PageMentionResolver, type PageMentionResolverEnvironment } from "./resolver";
4import type { PageMentionCacheMode, PageMentionCachedPage } from "./types";
5 
6interface PageMentionProviderProps {
7 children: ReactNode;
8 workspaceId: string | undefined;
9 scopeKey: string | null;
10 shareToken?: string;
11 cacheMode: PageMentionCacheMode;
12 networkEnabled: boolean;
13 lookupCachedPage?: (pageId: string) => PageMentionCachedPage | null;
14 navigate: (pageId: string) => void;
15}
16 
17type ResolverSlotListener = () => void;
18 
19function createResolverSlot() {
20 let current: PageMentionResolver | null = null;
21 const listeners = new Set<ResolverSlotListener>();
22 
23 const notify = () => {
24 for (const listener of listeners) listener();
25 };
26 
27 return {
28 subscribe(listener: ResolverSlotListener) {
29 listeners.add(listener);
30 return () => {
31 listeners.delete(listener);
32 };
33 },
34 getSnapshot() {
35 return current;
36 },
37 getServerSnapshot() {
38 return null;
39 },
40 set(next: PageMentionResolver | null) {
41 if (current === next) return;
42 current = next;
43 notify();
44 },
45 clear(expected: PageMentionResolver) {
46 if (current !== expected) return;
47 current = null;
48 notify();
49 },
50 };
51}
52 
53export function PageMentionProvider({
54 children,
55 workspaceId,
56 scopeKey,
57 shareToken,
58 cacheMode,
59 networkEnabled,
60 lookupCachedPage,
61 navigate,
62}: PageMentionProviderProps) {
63 const environment = useMemo<PageMentionResolverEnvironment>(
64 () => ({
65 cacheMode,
66 networkEnabled,
67 lookupCachedPage,
68 }),
69 [cacheMode, lookupCachedPage, networkEnabled],
70 );
71 const readEnvironment = useEffectEvent(() => environment);
72 const [resolverSlot] = useState(createResolverSlot);
73 const resolver = useSyncExternalStore(
74 resolverSlot.subscribe,
75 resolverSlot.getSnapshot,
76 resolverSlot.getServerSnapshot,
77 );
78 
79 useEffect(() => {
80 if (!workspaceId || !scopeKey) {
81 resolverSlot.set(null);
82 return;
83 }
84 
85 const nextResolver = createPageMentionResolver({
86 workspaceId,
87 shareToken,
88 environment: readEnvironment(),
89 });
90 resolverSlot.set(nextResolver);
91 return () => {
92 resolverSlot.clear(nextResolver);
93 nextResolver.dispose();
94 };
95 }, [resolverSlot, scopeKey, shareToken, workspaceId]);
96 
97 useEffect(() => {
98 resolver?.setEnvironment(environment);
99 }, [environment, resolver]);
100 
101 const value = useMemo(() => ({ resolver, navigate }), [navigate, resolver]);
102 
103 return <PageMentionContext.Provider value={value}>{children}</PageMentionContext.Provider>;
104}