Skip to content
File

Blob: src/client/components/editor/extensions/page-mention/node-view.tsx

typescript89 lines
1import type { NodeViewProps } from "@tiptap/react";
2import { NodeViewWrapper } from "@tiptap/react";
3import { Loader2 } from "lucide-react";
4import { PageMentionContent } from "@/shared/editor/presentation/page-mention";
5import { getPageMentionViewState } from "../../lib/page-mention/view-state";
6import { usePageMentionEntry, usePageMentionNavigate } from "@/client/components/page-mention/context";
7 
8export function PageMentionView({ node }: NodeViewProps) {
9 const pageId = node.attrs.pageId as string | null;
10 const entry = usePageMentionEntry(pageId);
11 const navigate = usePageMentionNavigate();
12 
13 if (!pageId) {
14 return (
15 <NodeViewWrapper
16 as="span"
17 className="tiptap-page-mention tiptap-page-mention--restricted"
18 aria-disabled="true"
19 contentEditable={false}
20 >
21 <PageMentionContent label="Restricted" restricted />
22 </NodeViewWrapper>
23 );
24 }
25 
26 const viewState = getPageMentionViewState(entry);
27 const canNavigate = viewState.kind === "accessible";
28 
29 const handleClick = (e: React.MouseEvent) => {
30 if (viewState.kind !== "accessible") return;
31 e.preventDefault();
32 navigate(pageId);
33 };
34 
35 const handleKeyDown = (e: React.KeyboardEvent) => {
36 if (viewState.kind !== "accessible") return;
37 if (e.key === "Enter" || e.key === " ") {
38 e.preventDefault();
39 navigate(pageId);
40 }
41 };
42 
43 if (viewState.kind === "restricted") {
44 return (
45 <NodeViewWrapper
46 as="span"
47 className="tiptap-page-mention tiptap-page-mention--restricted"
48 aria-disabled="true"
49 data-page-id={pageId}
50 contentEditable={false}
51 >
52 <PageMentionContent label={viewState.label} restricted />
53 </NodeViewWrapper>
54 );
55 }
56 
57 if (viewState.kind === "pending") {
58 return (
59 <NodeViewWrapper
60 as="span"
61 className="tiptap-page-mention tiptap-page-mention--pending"
62 data-page-id={pageId}
63 aria-busy="true"
64 aria-label={viewState.ariaLabel}
65 contentEditable={false}
66 >
67 <PageMentionContent icon={<Loader2 size={12} className="animate-spin" />} label={viewState.label} />
68 </NodeViewWrapper>
69 );
70 }
71 
72 return (
73 <NodeViewWrapper
74 as="span"
75 className="tiptap-page-mention"
76 data-page-id={pageId}
77 role={canNavigate ? "link" : undefined}
78 tabIndex={canNavigate ? 0 : undefined}
79 onClick={handleClick}
80 onKeyDown={handleKeyDown}
81 aria-label={viewState.ariaLabel}
82 aria-disabled={canNavigate ? undefined : "true"}
83 contentEditable={false}
84 >
85 <PageMentionContent icon={viewState.icon} label={viewState.label} />
86 </NodeViewWrapper>
87 );
88}