返回 presentation-ai
block-context-menu.tsx
根目录 / src / components / plate / ui / block-context-menu.tsx
1 "use client";
2
3 import * as React from "react";
4
5 import { AIChatPlugin } from "@platejs/ai/react";
6 import {
7 BLOCK_CONTEXT_MENU_ID,
8 BlockMenuPlugin,
9 BlockSelectionPlugin,
10 } from "@platejs/selection/react";
11 import { KEYS } from "platejs";
12 import { useEditorPlugin, usePlateState } from "platejs/react";
13
14 import { useIsTouchDevice } from "@/components/plate/hooks/use-is-touch-device";
15 import {
16 ContextMenu,
17 ContextMenuContent,
18 ContextMenuGroup,
19 ContextMenuItem,
20 ContextMenuSub,
21 ContextMenuSubContent,
22 ContextMenuSubTrigger,
23 ContextMenuTrigger,
24 } from "@/components/plate/ui/context-menu";
25
26 type Value = "askAI" | null;
27
28 export function BlockContextMenu({ children }: { children: React.ReactNode }) {
29 const { api, editor } = useEditorPlugin(BlockMenuPlugin);
30 const [value, setValue] = React.useState<Value>(null);
31 const isTouch = useIsTouchDevice();
32 const [readOnly] = usePlateState("readOnly");
33
34 const handleTurnInto = React.useCallback(
35 (type: string) => {
36 editor
37 .getApi(BlockSelectionPlugin)
38 .blockSelection.getNodes()
39 .forEach(([node, path]) => {
40 if (node[KEYS.listType]) {
41 editor.tf.unsetNodes([KEYS.listType, "indent"], {
42 at: path,
43 });
44 }
45
46 editor.tf.toggleBlock(type, { at: path });
47 });
48 },
49 [editor],
50 );
51
52 const handleAlign = React.useCallback(
53 (align: "center" | "left" | "right") => {
54 editor
55 .getTransforms(BlockSelectionPlugin)
56 .blockSelection.setNodes({ align });
57 },
58 [editor],
59 );
60
61 if (isTouch) {
62 return children;
63 }
64
65 return (
66 <ContextMenu
67 onOpenChange={(open) => {
68 if (!open) {
69 // prevent unselect the block selection
70 setTimeout(() => {
71 api.blockMenu.hide();
72 }, 0);
73 }
74 }}
75 modal={false}
76 >
77 <ContextMenuTrigger
78 asChild
79 onContextMenu={(event) => {
80 const dataset = (event.target as HTMLElement).dataset;
81
82 const disabled = dataset?.slateEditor === "true" || readOnly;
83
84 if (disabled) return event.preventDefault();
85
86 api.blockMenu.show(BLOCK_CONTEXT_MENU_ID, {
87 x: event.clientX,
88 y: event.clientY,
89 });
90 }}
91 >
92 {children}
93 </ContextMenuTrigger>
94 <ContextMenuContent
95 className="w-64"
96 onCloseAutoFocus={(e) => {
97 e.preventDefault();
98 editor.getApi(BlockSelectionPlugin).blockSelection.focus();
99
100 if (value === "askAI") {
101 editor.getApi(AIChatPlugin).aiChat.show();
102 }
103
104 setValue(null);
105 }}
106 >
107 <ContextMenuGroup>
108 <ContextMenuItem
109 onClick={() => {
110 setValue("askAI");
111 }}
112 >
113 Ask AI
114 </ContextMenuItem>
115 <ContextMenuItem
116 onClick={() => {
117 editor
118 .getTransforms(BlockSelectionPlugin)
119 .blockSelection.removeNodes();
120 editor.tf.focus();
121 }}
122 >
123 Delete
124 </ContextMenuItem>
125 <ContextMenuItem
126 onClick={() => {
127 editor
128 .getTransforms(BlockSelectionPlugin)
129 .blockSelection.duplicate();
130 }}
131 >
132 Duplicate
133 {/* <ContextMenuShortcut>⌘ + D</ContextMenuShortcut> */}
134 </ContextMenuItem>
135 <ContextMenuSub>
136 <ContextMenuSubTrigger>Turn into</ContextMenuSubTrigger>
137 <ContextMenuSubContent className="w-48">
138 <ContextMenuItem onClick={() => handleTurnInto(KEYS.p)}>
139 Paragraph
140 </ContextMenuItem>
141
142 <ContextMenuItem onClick={() => handleTurnInto(KEYS.h1)}>
143 Heading 1
144 </ContextMenuItem>
145 <ContextMenuItem onClick={() => handleTurnInto(KEYS.h2)}>
146 Heading 2
147 </ContextMenuItem>
148 <ContextMenuItem onClick={() => handleTurnInto(KEYS.h3)}>
149 Heading 3
150 </ContextMenuItem>
151 <ContextMenuItem onClick={() => handleTurnInto(KEYS.blockquote)}>
152 Blockquote
153 </ContextMenuItem>
154 </ContextMenuSubContent>
155 </ContextMenuSub>
156 </ContextMenuGroup>
157
158 <ContextMenuGroup>
159 <ContextMenuItem
160 onClick={() =>
161 editor
162 .getTransforms(BlockSelectionPlugin)
163 .blockSelection.setIndent(1)
164 }
165 >
166 Indent
167 </ContextMenuItem>
168 <ContextMenuItem
169 onClick={() =>
170 editor
171 .getTransforms(BlockSelectionPlugin)
172 .blockSelection.setIndent(-1)
173 }
174 >
175 Outdent
176 </ContextMenuItem>
177 <ContextMenuSub>
178 <ContextMenuSubTrigger>Align</ContextMenuSubTrigger>
179 <ContextMenuSubContent className="w-48">
180 <ContextMenuItem onClick={() => handleAlign("left")}>
181 Left
182 </ContextMenuItem>
183 <ContextMenuItem onClick={() => handleAlign("center")}>
184 Center
185 </ContextMenuItem>
186 <ContextMenuItem onClick={() => handleAlign("right")}>
187 Right
188 </ContextMenuItem>
189 </ContextMenuSubContent>
190 </ContextMenuSub>
191 </ContextMenuGroup>
192 </ContextMenuContent>
193 </ContextMenu>
194 );
195 }
196
196 lines Plain Text