Skip to content
File

Blob: src/client/components/editor/extensions/page-mention/suggestion.ts

typescript89 lines
1import { Extension, type Editor, type Range } from "@tiptap/core";
2import { PluginKey } from "@tiptap/pm/state";
3import Suggestion from "@tiptap/suggestion";
4import type { SuggestionProps, SuggestionKeyDownProps } from "@tiptap/suggestion";
5import { isChangeOrigin } from "@tiptap/extension-collaboration";
6import type { PageMentionCandidate } from "@/client/components/page-mention/types";
7import { filterPageMentionItems } from "../../lib/page-mention/open-picker";
8import { canInsertPageMentionAtRange } from "../../lib/page-mention/can-insert";
9import { mountPageMentionPicker, type PageMentionPickerHandle } from "../../controllers/page-mention/picker-overlay";
10import type { PageMentionItem } from "../../controllers/page-mention/picker-panel";
11 
12const pageMentionSuggestionKey = new PluginKey("pageMentionSuggestion");
13 
14interface PageMentionSuggestionOptions {
15 getCurrentPageId: () => string;
16 isAvailable: (editor: Editor) => boolean;
17 getCandidates: () => PageMentionCandidate[];
18}
19 
20export const PageMentionSuggestion = Extension.create<PageMentionSuggestionOptions>({
21 name: "pageMentionSuggestion",
22 
23 addOptions() {
24 return {
25 getCurrentPageId: () => "",
26 isAvailable: () => false,
27 getCandidates: () => [],
28 };
29 },
30 
31 addProseMirrorPlugins() {
32 return [
33 Suggestion<PageMentionItem, PageMentionItem>({
34 pluginKey: pageMentionSuggestionKey,
35 editor: this.editor,
36 char: "[[",
37 allowSpaces: true,
38 allow: ({ editor, range }) => this.options.isAvailable(editor) && canInsertPageMentionAtRange(editor, range),
39 shouldShow: ({ editor, transaction }) => this.options.isAvailable(editor) && !isChangeOrigin(transaction),
40 items: ({ query }) => {
41 if (!this.options.isAvailable(this.editor)) return [];
42 return filterPageMentionItems(this.options.getCandidates(), query);
43 },
44 command: ({ editor, range, props: item }) => {
45 if (!this.options.isAvailable(editor)) return;
46 editor.commands.insertPageMention({ pageId: item.pageId, range: range as Range });
47 },
48 render: () => {
49 let handle: PageMentionPickerHandle | null = null;
50 
51 return {
52 onStart: (props: SuggestionProps<PageMentionItem>) => {
53 handle = mountPageMentionPicker(props.editor, {
54 items: props.items,
55 filterMode: "external",
56 command: (item) => props.command(item),
57 clientRect: props.clientRect ?? null,
58 });
59 },
60 
61 onUpdate: (props: SuggestionProps<PageMentionItem>) => {
62 handle?.updateProps({
63 items: props.items,
64 filterMode: "external",
65 command: (item) => props.command(item),
66 clientRect: props.clientRect ?? null,
67 });
68 },
69 
70 onKeyDown: (props: SuggestionKeyDownProps) => {
71 if (props.event.key === "Escape") {
72 handle?.destroy();
73 handle = null;
74 return true;
75 }
76 return handle?.onKeyDown(props.event) ?? false;
77 },
78 
79 onExit: () => {
80 handle?.destroy();
81 handle = null;
82 },
83 };
84 },
85 }),
86 ];
87 },
88});