@meta-1/editor 0.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +458 -0
  2. package/package.json +100 -0
  3. package/src/editor/constants.tsx +66 -0
  4. package/src/editor/container.css +46 -0
  5. package/src/editor/control/character-count/index.tsx +39 -0
  6. package/src/editor/control/drag-handle/index.tsx +85 -0
  7. package/src/editor/control/drag-handle/use.content.actions.ts +71 -0
  8. package/src/editor/control/drag-handle/use.data.ts +29 -0
  9. package/src/editor/control/drag-handle/use.handle.id.ts +6 -0
  10. package/src/editor/control/index.tsx +35 -0
  11. package/src/editor/editor.css +626 -0
  12. package/src/editor/extension/block-quote-figure/BlockquoteFigure.ts +73 -0
  13. package/src/editor/extension/block-quote-figure/Quote/Quote.ts +31 -0
  14. package/src/editor/extension/block-quote-figure/Quote/index.ts +1 -0
  15. package/src/editor/extension/block-quote-figure/QuoteCaption/QuoteCaption.ts +54 -0
  16. package/src/editor/extension/block-quote-figure/QuoteCaption/index.ts +1 -0
  17. package/src/editor/extension/block-quote-figure/index.ts +1 -0
  18. package/src/editor/extension/document/index.ts +5 -0
  19. package/src/editor/extension/figcaption/Figcaption.ts +90 -0
  20. package/src/editor/extension/figcaption/index.ts +1 -0
  21. package/src/editor/extension/figure/Figure.ts +62 -0
  22. package/src/editor/extension/figure/index.ts +1 -0
  23. package/src/editor/extension/font-size/FontSize.ts +64 -0
  24. package/src/editor/extension/font-size/index.ts +1 -0
  25. package/src/editor/extension/global-drag-handle/clipboard-serializer.ts +28 -0
  26. package/src/editor/extension/global-drag-handle/index.ts +377 -0
  27. package/src/editor/extension/heading/index.ts +13 -0
  28. package/src/editor/extension/horizontal-rule/HorizontalRule.ts +10 -0
  29. package/src/editor/extension/horizontal-rule/index.ts +1 -0
  30. package/src/editor/extension/image/index.ts +5 -0
  31. package/src/editor/extension/image-block/ImageBlock.ts +103 -0
  32. package/src/editor/extension/image-block/components/ImageBlockMenu.tsx +100 -0
  33. package/src/editor/extension/image-block/components/ImageBlockView.tsx +47 -0
  34. package/src/editor/extension/image-block/components/ImageBlockWidth.tsx +40 -0
  35. package/src/editor/extension/image-block/index.ts +1 -0
  36. package/src/editor/extension/image-upload/ImageUpload.ts +58 -0
  37. package/src/editor/extension/image-upload/index.ts +1 -0
  38. package/src/editor/extension/image-upload/view/ImageUpload.tsx +27 -0
  39. package/src/editor/extension/image-upload/view/ImageUploader.tsx +64 -0
  40. package/src/editor/extension/image-upload/view/hooks.ts +109 -0
  41. package/src/editor/extension/image-upload/view/index.tsx +1 -0
  42. package/src/editor/extension/index.ts +30 -0
  43. package/src/editor/extension/link/Link.ts +39 -0
  44. package/src/editor/extension/link/index.ts +1 -0
  45. package/src/editor/extension/multi-column/Column.ts +33 -0
  46. package/src/editor/extension/multi-column/Columns.ts +65 -0
  47. package/src/editor/extension/multi-column/index.ts +2 -0
  48. package/src/editor/extension/multi-column/menus/ColumnsMenu.tsx +82 -0
  49. package/src/editor/extension/multi-column/menus/index.ts +1 -0
  50. package/src/editor/extension/selection/Selection.ts +36 -0
  51. package/src/editor/extension/selection/index.ts +1 -0
  52. package/src/editor/extension/slash-command/MenuList.tsx +145 -0
  53. package/src/editor/extension/slash-command/groups.ts +153 -0
  54. package/src/editor/extension/slash-command/index.ts +277 -0
  55. package/src/editor/extension/slash-command/types.ts +25 -0
  56. package/src/editor/extension/table/Cell.ts +126 -0
  57. package/src/editor/extension/table/Header.ts +89 -0
  58. package/src/editor/extension/table/Row.ts +8 -0
  59. package/src/editor/extension/table/Table.ts +9 -0
  60. package/src/editor/extension/table/index.ts +4 -0
  61. package/src/editor/extension/table/menus/TableColumn/index.tsx +73 -0
  62. package/src/editor/extension/table/menus/TableColumn/utils.ts +38 -0
  63. package/src/editor/extension/table/menus/TableRow/index.tsx +74 -0
  64. package/src/editor/extension/table/menus/TableRow/utils.ts +38 -0
  65. package/src/editor/extension/table/menus/index.tsx +2 -0
  66. package/src/editor/extension/table/utils.ts +258 -0
  67. package/src/editor/extension/task-item/index.ts +1 -0
  68. package/src/editor/extension/task-item/task-item.ts +225 -0
  69. package/src/editor/extension/task-list/index.ts +1 -0
  70. package/src/editor/extension/task-list/task-list.ts +81 -0
  71. package/src/editor/extension/trailing-node/index.ts +1 -0
  72. package/src/editor/extension/trailing-node/trailing-node.ts +70 -0
  73. package/src/editor/extension/unique-id/index.ts +1 -0
  74. package/src/editor/extension/unique-id/uniqueId.ts +123 -0
  75. package/src/editor/hooks.ts +264 -0
  76. package/src/editor/index.tsx +53 -0
  77. package/src/editor/menus/LinkMenu/LinkMenu.tsx +75 -0
  78. package/src/editor/menus/LinkMenu/index.tsx +1 -0
  79. package/src/editor/menus/TextMenu/TextMenu.tsx +193 -0
  80. package/src/editor/menus/TextMenu/components/AIDropdown.tsx +140 -0
  81. package/src/editor/menus/TextMenu/components/ContentTypePicker.tsx +76 -0
  82. package/src/editor/menus/TextMenu/components/EditLinkPopover.tsx +25 -0
  83. package/src/editor/menus/TextMenu/components/FontFamilyPicker.tsx +84 -0
  84. package/src/editor/menus/TextMenu/components/FontSizePicker.tsx +56 -0
  85. package/src/editor/menus/TextMenu/hooks/useTextmenuCommands.ts +96 -0
  86. package/src/editor/menus/TextMenu/hooks/useTextmenuContentTypes.ts +86 -0
  87. package/src/editor/menus/TextMenu/hooks/useTextmenuStates.ts +50 -0
  88. package/src/editor/menus/TextMenu/index.tsx +2 -0
  89. package/src/editor/menus/types.ts +21 -0
  90. package/src/editor/panels/Colorpicker/ColorButton.tsx +35 -0
  91. package/src/editor/panels/Colorpicker/Colorpicker.tsx +67 -0
  92. package/src/editor/panels/Colorpicker/index.tsx +2 -0
  93. package/src/editor/panels/LinkEditorPanel/LinkEditorPanel.tsx +76 -0
  94. package/src/editor/panels/LinkEditorPanel/index.tsx +1 -0
  95. package/src/editor/panels/LinkPreviewPanel/LinkPreviewPanel.tsx +32 -0
  96. package/src/editor/panels/LinkPreviewPanel/index.tsx +1 -0
  97. package/src/editor/panels/index.tsx +3 -0
  98. package/src/editor/types.tsx +38 -0
  99. package/src/editor/ui/Button/Button.tsx +70 -0
  100. package/src/editor/ui/Button/index.tsx +2 -0
  101. package/src/editor/ui/Dropdown/Dropdown.tsx +39 -0
  102. package/src/editor/ui/Dropdown/index.tsx +1 -0
  103. package/src/editor/ui/Icon.tsx +21 -0
  104. package/src/editor/ui/Loader/Loader.tsx +39 -0
  105. package/src/editor/ui/Loader/index.ts +1 -0
  106. package/src/editor/ui/Loader/types.ts +7 -0
  107. package/src/editor/ui/Panel/index.tsx +109 -0
  108. package/src/editor/ui/PopoverMenu.tsx +127 -0
  109. package/src/editor/ui/Spinner/Spinner.tsx +10 -0
  110. package/src/editor/ui/Spinner/index.tsx +1 -0
  111. package/src/editor/ui/Surface.tsx +27 -0
  112. package/src/editor/ui/Textarea/Textarea.tsx +20 -0
  113. package/src/editor/ui/Textarea/index.tsx +1 -0
  114. package/src/editor/ui/Toggle/Toggle.tsx +39 -0
  115. package/src/editor/ui/Toggle/index.tsx +1 -0
  116. package/src/editor/ui/Toolbar.tsx +107 -0
  117. package/src/editor/ui/Tooltip/index.tsx +77 -0
  118. package/src/editor/ui/Tooltip/types.ts +17 -0
  119. package/src/editor/utils/cssVar.ts +14 -0
  120. package/src/editor/utils/getRenderContainer.ts +39 -0
  121. package/src/editor/utils/index.ts +16 -0
  122. package/src/editor/utils/isCustomNodeSelected.ts +47 -0
  123. package/src/editor/utils/isTextSelected.ts +25 -0
  124. package/src/editor/utils/locale.ts +5 -0
  125. package/src/editor/viewer/index.tsx +26 -0
  126. package/src/globals.css +1 -0
  127. package/src/index.ts +7 -0
  128. package/src/locales/en-us.ts +133 -0
  129. package/src/locales/zh-cn.ts +133 -0
  130. package/src/locales/zh-tw.ts +133 -0
@@ -0,0 +1,193 @@
1
+ import { memo } from "react";
2
+ import { Content as PopoverContent, Root as PopoverRoot, Trigger as PopoverTrigger } from "@radix-ui/react-popover";
3
+ import { BubbleMenu, type Editor } from "@tiptap/react";
4
+
5
+ import { ColorPicker } from "../../panels";
6
+ import { Icon } from "../../ui/Icon";
7
+ import { Surface } from "../../ui/Surface";
8
+ import { Toolbar } from "../../ui/Toolbar";
9
+ import { i18n } from "../../utils/locale";
10
+ import { ContentTypePicker } from "./components/ContentTypePicker";
11
+ import { EditLinkPopover } from "./components/EditLinkPopover";
12
+ import { FontFamilyPicker } from "./components/FontFamilyPicker";
13
+ import { FontSizePicker } from "./components/FontSizePicker";
14
+ import { useTextmenuCommands } from "./hooks/useTextmenuCommands";
15
+ import { useTextmenuContentTypes } from "./hooks/useTextmenuContentTypes";
16
+ import { useTextMenuStates } from "./hooks/useTextmenuStates";
17
+
18
+ // We memorize the button so each button is not rerendered
19
+ // on every editor state change
20
+ const MemoButton = memo(Toolbar.Button);
21
+ const MemoColorPicker = memo(ColorPicker);
22
+ const MemoFontFamilyPicker = memo(FontFamilyPicker);
23
+ const MemoFontSizePicker = memo(FontSizePicker);
24
+ const MemoContentTypePicker = memo(ContentTypePicker);
25
+
26
+ export type TextMenuProps = {
27
+ editor: Editor;
28
+ };
29
+
30
+ export const TextMenu = ({ editor }: TextMenuProps) => {
31
+ const commands = useTextmenuCommands(editor);
32
+ const states = useTextMenuStates(editor);
33
+ const blockOptions = useTextmenuContentTypes(editor);
34
+
35
+ return (
36
+ <BubbleMenu
37
+ editor={editor}
38
+ pluginKey="textMenu"
39
+ shouldShow={states.shouldShow}
40
+ tippyOptions={{
41
+ zIndex: 40,
42
+ maxWidth: "none",
43
+ popperOptions: {
44
+ placement: "auto",
45
+ },
46
+ }}
47
+ updateDelay={100}
48
+ >
49
+ <Toolbar.Wrapper>
50
+ <MemoContentTypePicker options={blockOptions} />
51
+ <MemoFontFamilyPicker onChange={commands.onSetFont} value={states.currentFont || ""} />
52
+ <MemoFontSizePicker onChange={commands.onSetFontSize} value={states.currentSize || ""} />
53
+ <Toolbar.Divider />
54
+ <MemoButton
55
+ active={states.isBold}
56
+ onClick={commands.onBold}
57
+ tooltip={i18n("textMenu.bold")}
58
+ tooltipShortcut={["Mod", "B"]}
59
+ >
60
+ <Icon name="Bold" />
61
+ </MemoButton>
62
+ <MemoButton
63
+ active={states.isItalic}
64
+ onClick={commands.onItalic}
65
+ tooltip={i18n("textMenu.italic")}
66
+ tooltipShortcut={["Mod", "I"]}
67
+ >
68
+ <Icon name="Italic" />
69
+ </MemoButton>
70
+ <MemoButton
71
+ active={states.isUnderline}
72
+ onClick={commands.onUnderline}
73
+ tooltip={i18n("textMenu.underline")}
74
+ tooltipShortcut={["Mod", "U"]}
75
+ >
76
+ <Icon name="Underline" />
77
+ </MemoButton>
78
+ <MemoButton
79
+ active={states.isStrike}
80
+ onClick={commands.onStrike}
81
+ tooltip={i18n("textMenu.strikethrough")}
82
+ tooltipShortcut={["Mod", "Shift", "S"]}
83
+ >
84
+ <Icon name="Strikethrough" />
85
+ </MemoButton>
86
+ <MemoButton
87
+ active={states.isCode}
88
+ onClick={commands.onCode}
89
+ tooltip={i18n("textMenu.code")}
90
+ tooltipShortcut={["Mod", "E"]}
91
+ >
92
+ <Icon name="Code" />
93
+ </MemoButton>
94
+ <MemoButton onClick={commands.onCodeBlock} tooltip={i18n("textMenu.codeBlock")}>
95
+ <Icon name="Code" />
96
+ </MemoButton>
97
+ <EditLinkPopover onSetLink={commands.onLink} />
98
+ <PopoverRoot>
99
+ <PopoverTrigger asChild>
100
+ <MemoButton active={!!states.currentHighlight} tooltip={i18n("textMenu.highlighter")}>
101
+ <Icon name="Highlighter" />
102
+ </MemoButton>
103
+ </PopoverTrigger>
104
+ <PopoverContent asChild side="top" sideOffset={8}>
105
+ <Surface className="p-1">
106
+ <MemoColorPicker
107
+ color={states.currentHighlight}
108
+ onChange={commands.onChangeHighlight}
109
+ onClear={commands.onClearHighlight}
110
+ />
111
+ </Surface>
112
+ </PopoverContent>
113
+ </PopoverRoot>
114
+ <PopoverRoot>
115
+ <PopoverTrigger asChild>
116
+ <MemoButton active={!!states.currentColor} tooltip={i18n("textMenu.color")}>
117
+ <Icon name="Palette" />
118
+ </MemoButton>
119
+ </PopoverTrigger>
120
+ <PopoverContent asChild side="top" sideOffset={8}>
121
+ <Surface className="p-1">
122
+ <MemoColorPicker
123
+ color={states.currentColor}
124
+ onChange={commands.onChangeColor}
125
+ onClear={commands.onClearColor}
126
+ />
127
+ </Surface>
128
+ </PopoverContent>
129
+ </PopoverRoot>
130
+ <PopoverRoot>
131
+ <PopoverTrigger asChild>
132
+ <MemoButton tooltip={i18n("textMenu.more")}>
133
+ <Icon name="MoveVertical" />
134
+ </MemoButton>
135
+ </PopoverTrigger>
136
+ <PopoverContent asChild side="top">
137
+ <Toolbar.Wrapper>
138
+ <MemoButton
139
+ active={states.isSubscript}
140
+ onClick={commands.onSubscript}
141
+ tooltip={i18n("textMenu.subscript")}
142
+ tooltipShortcut={["Mod", "."]}
143
+ >
144
+ <Icon name="Subscript" />
145
+ </MemoButton>
146
+ <MemoButton
147
+ active={states.isSuperscript}
148
+ onClick={commands.onSuperscript}
149
+ tooltip={i18n("textMenu.superscript")}
150
+ tooltipShortcut={["Mod", ","]}
151
+ >
152
+ <Icon name="Superscript" />
153
+ </MemoButton>
154
+ <Toolbar.Divider />
155
+ <MemoButton
156
+ active={states.isAlignLeft}
157
+ onClick={commands.onAlignLeft}
158
+ tooltip={i18n("textMenu.alignLeft")}
159
+ tooltipShortcut={["Shift", "Mod", "L"]}
160
+ >
161
+ <Icon name="AlignLeft" />
162
+ </MemoButton>
163
+ <MemoButton
164
+ active={states.isAlignCenter}
165
+ onClick={commands.onAlignCenter}
166
+ tooltip={i18n("textMenu.alignCenter")}
167
+ tooltipShortcut={["Shift", "Mod", "E"]}
168
+ >
169
+ <Icon name="AlignCenter" />
170
+ </MemoButton>
171
+ <MemoButton
172
+ active={states.isAlignRight}
173
+ onClick={commands.onAlignRight}
174
+ tooltip={i18n("textMenu.alignRight")}
175
+ tooltipShortcut={["Shift", "Mod", "R"]}
176
+ >
177
+ <Icon name="AlignRight" />
178
+ </MemoButton>
179
+ <MemoButton
180
+ active={states.isAlignJustify}
181
+ onClick={commands.onAlignJustify}
182
+ tooltip={i18n("textMenu.alignJustify")}
183
+ tooltipShortcut={["Shift", "Mod", "J"]}
184
+ >
185
+ <Icon name="AlignJustify" />
186
+ </MemoButton>
187
+ </Toolbar.Wrapper>
188
+ </PopoverContent>
189
+ </PopoverRoot>
190
+ </Toolbar.Wrapper>
191
+ </BubbleMenu>
192
+ );
193
+ };
@@ -0,0 +1,140 @@
1
+ import { useCallback } from "react";
2
+ import {
3
+ Content as DropdownContent,
4
+ Item as DropdownItem,
5
+ Root as DropdownRoot,
6
+ Sub as DropdownSub,
7
+ SubContent as DropdownSubContent,
8
+ SubTrigger as DropdownSubTrigger,
9
+ Trigger as DropdownTrigger,
10
+ } from "@radix-ui/react-dropdown-menu";
11
+
12
+ import { languages, tones } from "../../../constants";
13
+ import { DropdownButton } from "../../../ui/Dropdown";
14
+ import { Icon } from "../../../ui/Icon";
15
+ import { Surface } from "../../../ui/Surface";
16
+ import { Toolbar } from "../../../ui/Toolbar";
17
+
18
+ export type AIDropdownProps = {
19
+ onSimplify: () => void;
20
+ onFixSpelling: () => void;
21
+ onMakeShorter: () => void;
22
+ onMakeLonger: () => void;
23
+ onEmojify: () => void;
24
+ onTldr: () => void;
25
+ onTranslate: (language: string) => void;
26
+ onTone: (tone: string) => void;
27
+ onCompleteSentence: () => void;
28
+ };
29
+
30
+ export const AIDropdown = ({
31
+ onCompleteSentence,
32
+ onEmojify,
33
+ onFixSpelling,
34
+ onMakeLonger,
35
+ onMakeShorter,
36
+ onSimplify,
37
+ onTldr,
38
+ onTone,
39
+ onTranslate,
40
+ }: AIDropdownProps) => {
41
+ const handleTone = useCallback((tone: string) => () => onTone(tone), [onTone]);
42
+ const handleTranslate = useCallback((language: string) => () => onTranslate(language), [onTranslate]);
43
+
44
+ return (
45
+ <DropdownRoot>
46
+ <DropdownTrigger asChild>
47
+ <Toolbar.Button
48
+ activeClassname="text-purple-600 hover:text-purple-600 dark:text-purple-400 dark:hover:text-purple-200"
49
+ className="text-purple-500 hover:text-purple-600 active:text-purple-600 dark:text-purple-400 dark:active:text-purple-400 dark:hover:text-purple-300"
50
+ >
51
+ <Icon className="mr-1" name="Sparkles" />
52
+ AI Tools
53
+ <Icon className="ml-1 h-2 w-2" name="ChevronDown" />
54
+ </Toolbar.Button>
55
+ </DropdownTrigger>
56
+ <DropdownContent asChild>
57
+ <Surface className="min-w-[10rem] p-2">
58
+ <DropdownItem onClick={onSimplify}>
59
+ <DropdownButton>
60
+ <Icon name="CircleSlash" />
61
+ Simplify
62
+ </DropdownButton>
63
+ </DropdownItem>
64
+ <DropdownItem onClick={onFixSpelling}>
65
+ <DropdownButton>
66
+ <Icon name="Eraser" />
67
+ Fix spelling & grammar
68
+ </DropdownButton>
69
+ </DropdownItem>
70
+ <DropdownItem onClick={onMakeShorter}>
71
+ <DropdownButton>
72
+ <Icon name="ArrowLeftToLine" />
73
+ Make shorter
74
+ </DropdownButton>
75
+ </DropdownItem>
76
+ <DropdownItem onClick={onMakeLonger}>
77
+ <DropdownButton>
78
+ <Icon name="ArrowRightToLine" />
79
+ Make longer
80
+ </DropdownButton>
81
+ </DropdownItem>
82
+ <DropdownSub>
83
+ <DropdownSubTrigger>
84
+ <DropdownButton>
85
+ <Icon name="Mic" />
86
+ Change tone
87
+ <Icon className="ml-auto h-4 w-4" name="ChevronRight" />
88
+ </DropdownButton>
89
+ </DropdownSubTrigger>
90
+ <DropdownSubContent>
91
+ <Surface className="flex max-h-[20rem] min-w-[15rem] flex-col overflow-auto p-2">
92
+ {tones.map((tone) => (
93
+ <DropdownItem key={tone.value} onClick={handleTone(tone.value)}>
94
+ <DropdownButton>{tone.label}</DropdownButton>
95
+ </DropdownItem>
96
+ ))}
97
+ </Surface>
98
+ </DropdownSubContent>
99
+ </DropdownSub>
100
+ <DropdownItem onClick={onTldr}>
101
+ <DropdownButton>
102
+ <Icon name="MoveHorizontal" />
103
+ Tl;dr:
104
+ </DropdownButton>
105
+ </DropdownItem>
106
+ <DropdownItem onClick={onEmojify}>
107
+ <DropdownButton>
108
+ <Icon name="SmilePlus" />
109
+ Emojify
110
+ </DropdownButton>
111
+ </DropdownItem>
112
+ <DropdownSub>
113
+ <DropdownSubTrigger>
114
+ <DropdownButton>
115
+ <Icon name="Languages" />
116
+ Translate
117
+ <Icon className="ml-auto h-4 w-4" name="ChevronRight" />
118
+ </DropdownButton>
119
+ </DropdownSubTrigger>
120
+ <DropdownSubContent>
121
+ <Surface className="flex max-h-[20rem] min-w-[15rem] flex-col overflow-auto p-2">
122
+ {languages.map((lang) => (
123
+ <DropdownItem key={lang.value} onClick={handleTranslate(lang.value)}>
124
+ <DropdownButton>{lang.label}</DropdownButton>
125
+ </DropdownItem>
126
+ ))}
127
+ </Surface>
128
+ </DropdownSubContent>
129
+ </DropdownSub>
130
+ <DropdownItem onClick={onCompleteSentence}>
131
+ <DropdownButton>
132
+ <Icon name="PenLine" />
133
+ Complete sentence
134
+ </DropdownButton>
135
+ </DropdownItem>
136
+ </Surface>
137
+ </DropdownContent>
138
+ </DropdownRoot>
139
+ );
140
+ };
@@ -0,0 +1,76 @@
1
+ import { useMemo } from "react";
2
+ import {
3
+ Content as DropdownContent,
4
+ Root as DropdownRoot,
5
+ Trigger as DropdownTrigger,
6
+ } from "@radix-ui/react-dropdown-menu";
7
+ import type { icons } from "lucide-react";
8
+
9
+ import { DropdownButton, DropdownCategoryTitle } from "../../../ui/Dropdown";
10
+ import { Icon } from "../../../ui/Icon";
11
+ import { Surface } from "../../../ui/Surface";
12
+ import { Toolbar } from "../../../ui/Toolbar";
13
+
14
+ export type ContentTypePickerOption = {
15
+ label: string;
16
+ id: string;
17
+ type: "option";
18
+ disabled: () => boolean;
19
+ isActive: () => boolean;
20
+ onClick: () => void;
21
+ icon: keyof typeof icons;
22
+ };
23
+
24
+ export type ContentTypePickerCategory = {
25
+ label: string;
26
+ id: string;
27
+ type: "category";
28
+ };
29
+
30
+ export type ContentPickerOptions = Array<ContentTypePickerOption | ContentTypePickerCategory>;
31
+
32
+ export type ContentTypePickerProps = {
33
+ options: ContentPickerOptions;
34
+ };
35
+
36
+ const isOption = (option: ContentTypePickerOption | ContentTypePickerCategory): option is ContentTypePickerOption =>
37
+ option.type === "option";
38
+ const isCategory = (option: ContentTypePickerOption | ContentTypePickerCategory): option is ContentTypePickerCategory =>
39
+ option.type === "category";
40
+
41
+ export const ContentTypePicker = ({ options }: ContentTypePickerProps) => {
42
+ const activeItem = useMemo(() => options.find((option) => option.type === "option" && option.isActive()), [options]);
43
+
44
+ return (
45
+ <DropdownRoot>
46
+ <DropdownTrigger asChild>
47
+ <Toolbar.Button active={activeItem?.id !== "paragraph" && !!activeItem?.type}>
48
+ <Icon name={(activeItem?.type === "option" && activeItem.icon) || "Pilcrow"} />
49
+ <Icon className="h-2 w-2" name="ChevronDown" />
50
+ </Toolbar.Button>
51
+ </DropdownTrigger>
52
+ <DropdownContent asChild>
53
+ <Surface className="flex flex-col gap-1 px-2 py-4">
54
+ {options.map((option) => {
55
+ if (isOption(option)) {
56
+ return (
57
+ <DropdownButton isActive={option.isActive()} key={option.id} onClick={option.onClick}>
58
+ <Icon className="mr-1 h-4 w-4" name={option.icon} />
59
+ {option.label}
60
+ </DropdownButton>
61
+ );
62
+ }
63
+ if (isCategory(option)) {
64
+ return (
65
+ <div className="mt-2 first:mt-0" key={option.id}>
66
+ <DropdownCategoryTitle key={option.id}>{option.label}</DropdownCategoryTitle>
67
+ </div>
68
+ );
69
+ }
70
+ return null;
71
+ })}
72
+ </Surface>
73
+ </DropdownContent>
74
+ </DropdownRoot>
75
+ );
76
+ };
@@ -0,0 +1,25 @@
1
+ import { Content as PopoverContent, Root as PopoverRoot, Trigger as PopoverTrigger } from "@radix-ui/react-popover";
2
+
3
+ import { LinkEditorPanel } from "../../../panels";
4
+ import { Icon } from "../../../ui/Icon";
5
+ import { Toolbar } from "../../../ui/Toolbar";
6
+ import { i18n } from "../../../utils/locale";
7
+
8
+ export type EditLinkPopoverProps = {
9
+ onSetLink: (link: string, openInNewTab?: boolean) => void;
10
+ };
11
+
12
+ export const EditLinkPopover = ({ onSetLink }: EditLinkPopoverProps) => {
13
+ return (
14
+ <PopoverRoot>
15
+ <PopoverTrigger asChild>
16
+ <Toolbar.Button tooltip={i18n("textMenu.editLink")}>
17
+ <Icon name="Link" />
18
+ </Toolbar.Button>
19
+ </PopoverTrigger>
20
+ <PopoverContent>
21
+ <LinkEditorPanel onSetLink={onSetLink} />
22
+ </PopoverContent>
23
+ </PopoverRoot>
24
+ );
25
+ };
@@ -0,0 +1,84 @@
1
+ import { useCallback } from "react";
2
+ import {
3
+ Content as DropdownContent,
4
+ Root as DropdownRoot,
5
+ Trigger as DropdownTrigger,
6
+ } from "@radix-ui/react-dropdown-menu";
7
+
8
+ import { DropdownButton, DropdownCategoryTitle } from "../../../ui/Dropdown";
9
+ import { Icon } from "../../../ui/Icon";
10
+ import { Surface } from "../../../ui/Surface";
11
+ import { Toolbar } from "../../../ui/Toolbar";
12
+ import { i18n } from "../../../utils/locale";
13
+
14
+ const getFontFamilyGroups = () => [
15
+ {
16
+ label: "Sans Serif",
17
+ options: [
18
+ { label: i18n("default"), value: "" },
19
+ { label: "Arial", value: "Arial" },
20
+ { label: "Helvetica", value: "Helvetica" },
21
+ ],
22
+ },
23
+ {
24
+ label: "Serif",
25
+ options: [
26
+ { label: "Times New Roman", value: "Times" },
27
+ { label: "Garamond", value: "Garamond" },
28
+ { label: "Georgia", value: "Georgia" },
29
+ ],
30
+ },
31
+ {
32
+ label: "Monospace",
33
+ options: [
34
+ { label: "Courier", value: "Courier" },
35
+ { label: "Courier New", value: "Courier New" },
36
+ ],
37
+ },
38
+ ];
39
+
40
+ const getFontFamilies = () =>
41
+ getFontFamilyGroups()
42
+ .flatMap((group) => [group.options])
43
+ .flat();
44
+
45
+ export type FontFamilyPickerProps = {
46
+ onChange: (value: string) => void;
47
+ value: string;
48
+ };
49
+
50
+ export const FontFamilyPicker = ({ onChange, value }: FontFamilyPickerProps) => {
51
+ const currentValue = getFontFamilies().find((size) => size.value === value);
52
+ const currentFontLabel = currentValue?.label?.split(" ")[0] || i18n("default");
53
+
54
+ const selectFont = useCallback((font: string) => () => onChange(font), [onChange]);
55
+
56
+ return (
57
+ <DropdownRoot>
58
+ <DropdownTrigger asChild>
59
+ <Toolbar.Button active={!!currentValue?.value}>
60
+ {currentFontLabel}
61
+ <Icon className="h-2 w-2" name="ChevronDown" />
62
+ </Toolbar.Button>
63
+ </DropdownTrigger>
64
+ <DropdownContent asChild>
65
+ <Surface className="flex flex-col gap-1 px-2 py-4">
66
+ {getFontFamilyGroups().map((group) => (
67
+ <div className="mt-2.5 flex flex-col gap-0.5 first:mt-0" key={group.label}>
68
+ <DropdownCategoryTitle>{group.label}</DropdownCategoryTitle>
69
+ {group.options.map((font) => (
70
+ <DropdownButton
71
+ isActive={value === font.value}
72
+ key={`${font.label}_${font.value}`}
73
+ onClick={selectFont(font.value)}
74
+ >
75
+ <span style={{ fontFamily: font.value }}>{font.label}</span>
76
+ </DropdownButton>
77
+ ))}
78
+ </div>
79
+ ))}
80
+ </Surface>
81
+ </DropdownContent>
82
+ </DropdownRoot>
83
+ );
84
+ };
@@ -0,0 +1,56 @@
1
+ import { useCallback } from "react";
2
+ import {
3
+ Content as DropdownContent,
4
+ Root as DropdownRoot,
5
+ Trigger as DropdownTrigger,
6
+ } from "@radix-ui/react-dropdown-menu";
7
+
8
+ import { DropdownButton } from "../../../ui/Dropdown";
9
+ import { Icon } from "../../../ui/Icon";
10
+ import { Surface } from "../../../ui/Surface";
11
+ import { Toolbar } from "../../../ui/Toolbar";
12
+ import { i18n } from "../../../utils/locale";
13
+
14
+ const getFontSizes = () => [
15
+ { label: i18n("fontSize.mini"), value: "12px" },
16
+ { label: i18n("fontSize.small"), value: "14px" },
17
+ { label: i18n("fontSize.normal"), value: "" },
18
+ { label: i18n("fontSize.large"), value: "18px" },
19
+ { label: i18n("fontSize.huge"), value: "24px" },
20
+ ];
21
+
22
+ export type FontSizePickerProps = {
23
+ onChange: (value: string) => void;
24
+ value: string;
25
+ };
26
+
27
+ export const FontSizePicker = ({ onChange, value }: FontSizePickerProps) => {
28
+ const currentValue = getFontSizes().find((size) => size.value === value);
29
+ const currentSizeLabel = currentValue?.label?.split(" ")[0] || i18n("default");
30
+
31
+ const selectSize = useCallback((size: string) => () => onChange(size), [onChange]);
32
+
33
+ return (
34
+ <DropdownRoot>
35
+ <DropdownTrigger asChild>
36
+ <Toolbar.Button active={!!currentValue?.value}>
37
+ {currentSizeLabel}
38
+ <Icon className="h-2 w-2" name="ChevronDown" />
39
+ </Toolbar.Button>
40
+ </DropdownTrigger>
41
+ <DropdownContent asChild>
42
+ <Surface className="flex flex-col gap-1 px-2 py-4">
43
+ {getFontSizes().map((size) => (
44
+ <DropdownButton
45
+ isActive={value === size.value}
46
+ key={`${size.label}_${size.value}`}
47
+ onClick={selectSize(size.value)}
48
+ >
49
+ <span style={{ fontSize: size.value }}>{size.label}</span>
50
+ </DropdownButton>
51
+ ))}
52
+ </Surface>
53
+ </DropdownContent>
54
+ </DropdownRoot>
55
+ );
56
+ };
@@ -0,0 +1,96 @@
1
+ // import { Language } from '@tiptap-pro/extension-ai'
2
+
3
+ import { useCallback } from "react";
4
+ import type { Editor } from "@tiptap/react";
5
+
6
+ export const useTextmenuCommands = (editor: Editor) => {
7
+ const onBold = useCallback(() => editor.chain().focus().toggleBold().run(), [editor]);
8
+ const onItalic = useCallback(() => editor.chain().focus().toggleItalic().run(), [editor]);
9
+ const onStrike = useCallback(() => editor.chain().focus().toggleStrike().run(), [editor]);
10
+ const onUnderline = useCallback(() => editor.chain().focus().toggleUnderline().run(), [editor]);
11
+ const onCode = useCallback(() => editor.chain().focus().toggleCode().run(), [editor]);
12
+ const onCodeBlock = useCallback(() => editor.chain().focus().toggleCodeBlock().run(), [editor]);
13
+
14
+ const onSubscript = useCallback(() => editor.chain().focus().toggleSubscript().run(), [editor]);
15
+ const onSuperscript = useCallback(() => editor.chain().focus().toggleSuperscript().run(), [editor]);
16
+ const onAlignLeft = useCallback(() => editor.chain().focus().setTextAlign("left").run(), [editor]);
17
+ const onAlignCenter = useCallback(() => editor.chain().focus().setTextAlign("center").run(), [editor]);
18
+ const onAlignRight = useCallback(() => editor.chain().focus().setTextAlign("right").run(), [editor]);
19
+ const onAlignJustify = useCallback(() => editor.chain().focus().setTextAlign("justify").run(), [editor]);
20
+
21
+ const onChangeColor = useCallback((color: string) => editor.chain().setColor(color).run(), [editor]);
22
+ const onClearColor = useCallback(() => editor.chain().focus().unsetColor().run(), [editor]);
23
+
24
+ const onChangeHighlight = useCallback((color: string) => editor.chain().setHighlight({ color }).run(), [editor]);
25
+ const onClearHighlight = useCallback(() => editor.chain().focus().unsetHighlight().run(), [editor]);
26
+
27
+ // const onSimplify = useCallback(() => editor.chain().focus().aiSimplify().run(), [editor])
28
+ // const onEmojify = useCallback(() => editor.chain().focus().aiEmojify().run(), [editor])
29
+ // const onCompleteSentence = useCallback(() => editor.chain().focus().aiComplete().run(), [editor])
30
+ // const onFixSpelling = useCallback(() => editor.chain().focus().aiFixSpellingAndGrammar().run(), [editor])
31
+ // const onMakeLonger = useCallback(() => editor.chain().focus().aiExtend().run(), [editor])
32
+ // const onMakeShorter = useCallback(() => editor.chain().focus().aiShorten().run(), [editor])
33
+ // const onTldr = useCallback(() => editor.chain().focus().aiTldr().run(), [editor])
34
+ // const onTone = useCallback((tone: string) => editor.chain().focus().aiAdjustTone(tone).run(), [editor])
35
+ // const onTranslate = useCallback((language: Language) => editor.chain().focus().aiTranslate(language).run(), [editor])
36
+ const onLink = useCallback(
37
+ (url: string, inNewTab?: boolean) =>
38
+ editor
39
+ .chain()
40
+ .focus()
41
+ .setLink({ href: url, target: inNewTab ? "_blank" : "" })
42
+ .run(),
43
+ [editor],
44
+ );
45
+
46
+ const onSetFont = useCallback(
47
+ (font: string) => {
48
+ if (!font || font.length === 0) {
49
+ return editor.chain().focus().unsetFontFamily().run();
50
+ }
51
+ return editor.chain().focus().setFontFamily(font).run();
52
+ },
53
+ [editor],
54
+ );
55
+
56
+ const onSetFontSize = useCallback(
57
+ (fontSize: string) => {
58
+ if (!fontSize || fontSize.length === 0) {
59
+ return editor.chain().focus().unsetFontSize().run();
60
+ }
61
+ return editor.chain().focus().setFontSize(fontSize).run();
62
+ },
63
+ [editor],
64
+ );
65
+
66
+ return {
67
+ onBold,
68
+ onItalic,
69
+ onStrike,
70
+ onUnderline,
71
+ onCode,
72
+ onCodeBlock,
73
+ onSubscript,
74
+ onSuperscript,
75
+ onAlignLeft,
76
+ onAlignCenter,
77
+ onAlignRight,
78
+ onAlignJustify,
79
+ onChangeColor,
80
+ onClearColor,
81
+ onChangeHighlight,
82
+ onClearHighlight,
83
+ onSetFont,
84
+ onSetFontSize,
85
+ // onSimplify,
86
+ // onEmojify,
87
+ // onCompleteSentence,
88
+ // onFixSpelling,
89
+ // onMakeLonger,
90
+ // onMakeShorter,
91
+ // onTldr,
92
+ // onTone,
93
+ // onTranslate,
94
+ onLink,
95
+ };
96
+ };