| 1 | "use client"; |
| 2 | |
| 3 | import { |
| 4 | BaseSuggestionPlugin, |
| 5 | type BaseSuggestionConfig, |
| 6 | } from "@platejs/suggestion"; |
| 7 | import { |
| 8 | isSlateEditor, |
| 9 | isSlateString, |
| 10 | type ExtendConfig, |
| 11 | type Path, |
| 12 | } from "platejs"; |
| 13 | import { toTPlatePlugin } from "platejs/react"; |
| 14 | |
| 15 | import { |
| 16 | SuggestionLeaf, |
| 17 | SuggestionLineBreak, |
| 18 | } from "@/components/plate/ui/suggestion-node"; |
| 19 | import { discussionPlugin } from "./discussion-kit"; |
| 20 | |
| 21 | export type SuggestionConfig = ExtendConfig< |
| 22 | BaseSuggestionConfig, |
| 23 | { |
| 24 | activeId: string | null; |
| 25 | hoverId: string | null; |
| 26 | uniquePathMap: Map<string, Path>; |
| 27 | } |
| 28 | >; |
| 29 | |
| 30 | export const suggestionPlugin = toTPlatePlugin<SuggestionConfig>( |
| 31 | BaseSuggestionPlugin, |
| 32 | ({ editor }) => ({ |
| 33 | options: { |
| 34 | activeId: null, |
| 35 | currentUserId: editor.getOption(discussionPlugin, "currentUserId"), |
| 36 | hoverId: null, |
| 37 | uniquePathMap: new Map(), |
| 38 | }, |
| 39 | }), |
| 40 | ).configure({ |
| 41 | handlers: { |
| 42 | // unset active suggestion when clicking outside of suggestion |
| 43 | onClick: ({ api, event, setOption, type }) => { |
| 44 | let leaf = event.target as HTMLElement; |
| 45 | let isSet = false; |
| 46 | |
| 47 | const isBlockLeaf = leaf.dataset.blockSuggestion === "true"; |
| 48 | |
| 49 | const unsetActiveSuggestion = () => { |
| 50 | setOption("activeId", null); |
| 51 | isSet = true; |
| 52 | }; |
| 53 | |
| 54 | if (!isSlateString(leaf) && !isBlockLeaf) { |
| 55 | unsetActiveSuggestion(); |
| 56 | } |
| 57 | |
| 58 | while (leaf.parentElement && !isSlateEditor(leaf.parentElement)) { |
| 59 | const isBlockSuggestion = leaf.dataset.blockSuggestion === "true"; |
| 60 | |
| 61 | if (leaf.classList.contains(`slate-${type}`) || isBlockSuggestion) { |
| 62 | const suggestionEntry = api.suggestion!.node({ |
| 63 | isText: !isBlockSuggestion, |
| 64 | }); |
| 65 | |
| 66 | if (!suggestionEntry) { |
| 67 | unsetActiveSuggestion(); |
| 68 | |
| 69 | break; |
| 70 | } |
| 71 | |
| 72 | const id = api.suggestion!.nodeId(suggestionEntry[0]); |
| 73 | setOption("activeId", id ?? null); |
| 74 | |
| 75 | isSet = true; |
| 76 | |
| 77 | break; |
| 78 | } |
| 79 | |
| 80 | leaf = leaf.parentElement; |
| 81 | } |
| 82 | |
| 83 | if (!isSet) unsetActiveSuggestion(); |
| 84 | }, |
| 85 | }, |
| 86 | render: { |
| 87 | belowNodes: (props) => |
| 88 | SuggestionLineBreak(props as Parameters<typeof SuggestionLineBreak>[0]), |
| 89 | node: SuggestionLeaf, |
| 90 | }, |
| 91 | }); |
| 92 | |
| 93 | export const SuggestionKit = [suggestionPlugin]; |
| 94 |