1import {
2 CleanStyles,
3 ComponentUtils,
4 DismissSuggestionsOnOutsideClick,
5 HandleExcelPaste,
6 PreserveWhitespace,
7} from "@opendesk/shared/tiptap";
8import {
9 AlignCenter,
10 AlignLeft,
11 AlignRight,
12 Blockquote,
13 Bold,
14 BulletList,
15 FontColor,
16 FontHighlight,
17 HeadingGroup,
18 HorizontalRule,
19 InlineCode,
20 InsertImage,
21 InsertLink,
22 InsertTable,
23 InsertVideo,
24 Italic,
25 OrderedList,
26 Paragraph,
27 RichTextKit,
28 Separator,
29 Strike,
30 commentToolbar,
31 type CommandMenuItem,
32 type MentionSuggestionItem,
33 type MenuItem,
34} from "frappe-ui/editor";
35import type { MaybeRefOrGetter } from "vue";
36
37/**
38 * Build the extension list for a Opendesk rich-text editor.
39 *
40 * Mentions are passed as a reactive getter so the `@` list stays in sync as
41 * agents load (the v0 `:mentions` snapshot prop is what broke suggestions).
42 */
43export function buildEditorExtensions(
44 options: {
45 mentions?: MaybeRefOrGetter<MentionSuggestionItem[]>;
46 extra?: unknown[];
47 } = {}
48) {
49 const kit = RichTextKit.configure({
50 heading: { levels: [2, 3, 4, 5, 6] },
51 // rc.1 ships the table-of-contents node off; opendesk editors had it.
52 toc: {},
53 ...(options.mentions ? { mention: { items: options.mentions } } : {}),
54 });
55 return [
56 kit,
57 ComponentUtils,
58 HandleExcelPaste,
59 CleanStyles,
60 DismissSuggestionsOnOutsideClick,
61 PreserveWhitespace,
62 ...(options.extra ?? []),
63 ];
64}
65
66/** Clear-formatting toolbar button (ports the v0 `ClearFormattingUtility`). */
67export const ClearFormatting: CommandMenuItem = {
68 label: "Clear formatting",
69 icon: "lucide-brush-cleaning",
70 action: (editor) =>
71 editor.chain().focus().unsetAllMarks().clearNodes().cleanStyles().run(),
72};
73
74/** Code block button; frappe-ui ships the node but no toolbar item for it. */
75export const InsertCodeBlock: CommandMenuItem = {
76 label: "Code block",
77 icon: "lucide-square-code",
78 isActive: (editor) => editor.isActive("codeBlock"),
79 action: (editor) => editor.chain().focus().toggleCodeBlock().run(),
80};
81
82/** Full toolbar mirroring the v0 `textEditorMenuButtons`. */
83export const fullToolbar: MenuItem[] = [
84 HeadingGroup,
85 Separator,
86 Bold,
87 Italic,
88 Strike,
89 FontColor,
90 FontHighlight,
91 Separator,
92 AlignLeft,
93 AlignCenter,
94 AlignRight,
95 Separator,
96 BulletList,
97 OrderedList,
98 Blockquote,
99 InlineCode,
100 InsertCodeBlock,
101 Separator,
102 InsertLink,
103 InsertImage,
104 InsertVideo,
105 InsertTable,
106 HorizontalRule,
107 Separator,
108 ClearFormatting,
109];
110
111/**
112 * Curated toolbar for the new-ticket / base editor (ports the v0 `fixedMenu`).
113 * Kept compact so the toolbar + Discard/Submit fit on one row.
114 */
115export const ticketToolbar: MenuItem[] = [
116 Paragraph,
117 HeadingGroup,
118 Separator,
119 Bold,
120 Italic,
121 Separator,
122 BulletList,
123 OrderedList,
124 Separator,
125 InsertImage,
126 InsertVideo,
127 InsertLink,
128 Blockquote,
129 InlineCode,
130 ClearFormatting,
131];
132
133/** Compact bubble/inline toolbar for comment display + inline editing. */
134export { commentToolbar };
135