@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.
- package/README.md +458 -0
- package/package.json +100 -0
- package/src/editor/constants.tsx +66 -0
- package/src/editor/container.css +46 -0
- package/src/editor/control/character-count/index.tsx +39 -0
- package/src/editor/control/drag-handle/index.tsx +85 -0
- package/src/editor/control/drag-handle/use.content.actions.ts +71 -0
- package/src/editor/control/drag-handle/use.data.ts +29 -0
- package/src/editor/control/drag-handle/use.handle.id.ts +6 -0
- package/src/editor/control/index.tsx +35 -0
- package/src/editor/editor.css +626 -0
- package/src/editor/extension/block-quote-figure/BlockquoteFigure.ts +73 -0
- package/src/editor/extension/block-quote-figure/Quote/Quote.ts +31 -0
- package/src/editor/extension/block-quote-figure/Quote/index.ts +1 -0
- package/src/editor/extension/block-quote-figure/QuoteCaption/QuoteCaption.ts +54 -0
- package/src/editor/extension/block-quote-figure/QuoteCaption/index.ts +1 -0
- package/src/editor/extension/block-quote-figure/index.ts +1 -0
- package/src/editor/extension/document/index.ts +5 -0
- package/src/editor/extension/figcaption/Figcaption.ts +90 -0
- package/src/editor/extension/figcaption/index.ts +1 -0
- package/src/editor/extension/figure/Figure.ts +62 -0
- package/src/editor/extension/figure/index.ts +1 -0
- package/src/editor/extension/font-size/FontSize.ts +64 -0
- package/src/editor/extension/font-size/index.ts +1 -0
- package/src/editor/extension/global-drag-handle/clipboard-serializer.ts +28 -0
- package/src/editor/extension/global-drag-handle/index.ts +377 -0
- package/src/editor/extension/heading/index.ts +13 -0
- package/src/editor/extension/horizontal-rule/HorizontalRule.ts +10 -0
- package/src/editor/extension/horizontal-rule/index.ts +1 -0
- package/src/editor/extension/image/index.ts +5 -0
- package/src/editor/extension/image-block/ImageBlock.ts +103 -0
- package/src/editor/extension/image-block/components/ImageBlockMenu.tsx +100 -0
- package/src/editor/extension/image-block/components/ImageBlockView.tsx +47 -0
- package/src/editor/extension/image-block/components/ImageBlockWidth.tsx +40 -0
- package/src/editor/extension/image-block/index.ts +1 -0
- package/src/editor/extension/image-upload/ImageUpload.ts +58 -0
- package/src/editor/extension/image-upload/index.ts +1 -0
- package/src/editor/extension/image-upload/view/ImageUpload.tsx +27 -0
- package/src/editor/extension/image-upload/view/ImageUploader.tsx +64 -0
- package/src/editor/extension/image-upload/view/hooks.ts +109 -0
- package/src/editor/extension/image-upload/view/index.tsx +1 -0
- package/src/editor/extension/index.ts +30 -0
- package/src/editor/extension/link/Link.ts +39 -0
- package/src/editor/extension/link/index.ts +1 -0
- package/src/editor/extension/multi-column/Column.ts +33 -0
- package/src/editor/extension/multi-column/Columns.ts +65 -0
- package/src/editor/extension/multi-column/index.ts +2 -0
- package/src/editor/extension/multi-column/menus/ColumnsMenu.tsx +82 -0
- package/src/editor/extension/multi-column/menus/index.ts +1 -0
- package/src/editor/extension/selection/Selection.ts +36 -0
- package/src/editor/extension/selection/index.ts +1 -0
- package/src/editor/extension/slash-command/MenuList.tsx +145 -0
- package/src/editor/extension/slash-command/groups.ts +153 -0
- package/src/editor/extension/slash-command/index.ts +277 -0
- package/src/editor/extension/slash-command/types.ts +25 -0
- package/src/editor/extension/table/Cell.ts +126 -0
- package/src/editor/extension/table/Header.ts +89 -0
- package/src/editor/extension/table/Row.ts +8 -0
- package/src/editor/extension/table/Table.ts +9 -0
- package/src/editor/extension/table/index.ts +4 -0
- package/src/editor/extension/table/menus/TableColumn/index.tsx +73 -0
- package/src/editor/extension/table/menus/TableColumn/utils.ts +38 -0
- package/src/editor/extension/table/menus/TableRow/index.tsx +74 -0
- package/src/editor/extension/table/menus/TableRow/utils.ts +38 -0
- package/src/editor/extension/table/menus/index.tsx +2 -0
- package/src/editor/extension/table/utils.ts +258 -0
- package/src/editor/extension/task-item/index.ts +1 -0
- package/src/editor/extension/task-item/task-item.ts +225 -0
- package/src/editor/extension/task-list/index.ts +1 -0
- package/src/editor/extension/task-list/task-list.ts +81 -0
- package/src/editor/extension/trailing-node/index.ts +1 -0
- package/src/editor/extension/trailing-node/trailing-node.ts +70 -0
- package/src/editor/extension/unique-id/index.ts +1 -0
- package/src/editor/extension/unique-id/uniqueId.ts +123 -0
- package/src/editor/hooks.ts +264 -0
- package/src/editor/index.tsx +53 -0
- package/src/editor/menus/LinkMenu/LinkMenu.tsx +75 -0
- package/src/editor/menus/LinkMenu/index.tsx +1 -0
- package/src/editor/menus/TextMenu/TextMenu.tsx +193 -0
- package/src/editor/menus/TextMenu/components/AIDropdown.tsx +140 -0
- package/src/editor/menus/TextMenu/components/ContentTypePicker.tsx +76 -0
- package/src/editor/menus/TextMenu/components/EditLinkPopover.tsx +25 -0
- package/src/editor/menus/TextMenu/components/FontFamilyPicker.tsx +84 -0
- package/src/editor/menus/TextMenu/components/FontSizePicker.tsx +56 -0
- package/src/editor/menus/TextMenu/hooks/useTextmenuCommands.ts +96 -0
- package/src/editor/menus/TextMenu/hooks/useTextmenuContentTypes.ts +86 -0
- package/src/editor/menus/TextMenu/hooks/useTextmenuStates.ts +50 -0
- package/src/editor/menus/TextMenu/index.tsx +2 -0
- package/src/editor/menus/types.ts +21 -0
- package/src/editor/panels/Colorpicker/ColorButton.tsx +35 -0
- package/src/editor/panels/Colorpicker/Colorpicker.tsx +67 -0
- package/src/editor/panels/Colorpicker/index.tsx +2 -0
- package/src/editor/panels/LinkEditorPanel/LinkEditorPanel.tsx +76 -0
- package/src/editor/panels/LinkEditorPanel/index.tsx +1 -0
- package/src/editor/panels/LinkPreviewPanel/LinkPreviewPanel.tsx +32 -0
- package/src/editor/panels/LinkPreviewPanel/index.tsx +1 -0
- package/src/editor/panels/index.tsx +3 -0
- package/src/editor/types.tsx +38 -0
- package/src/editor/ui/Button/Button.tsx +70 -0
- package/src/editor/ui/Button/index.tsx +2 -0
- package/src/editor/ui/Dropdown/Dropdown.tsx +39 -0
- package/src/editor/ui/Dropdown/index.tsx +1 -0
- package/src/editor/ui/Icon.tsx +21 -0
- package/src/editor/ui/Loader/Loader.tsx +39 -0
- package/src/editor/ui/Loader/index.ts +1 -0
- package/src/editor/ui/Loader/types.ts +7 -0
- package/src/editor/ui/Panel/index.tsx +109 -0
- package/src/editor/ui/PopoverMenu.tsx +127 -0
- package/src/editor/ui/Spinner/Spinner.tsx +10 -0
- package/src/editor/ui/Spinner/index.tsx +1 -0
- package/src/editor/ui/Surface.tsx +27 -0
- package/src/editor/ui/Textarea/Textarea.tsx +20 -0
- package/src/editor/ui/Textarea/index.tsx +1 -0
- package/src/editor/ui/Toggle/Toggle.tsx +39 -0
- package/src/editor/ui/Toggle/index.tsx +1 -0
- package/src/editor/ui/Toolbar.tsx +107 -0
- package/src/editor/ui/Tooltip/index.tsx +77 -0
- package/src/editor/ui/Tooltip/types.ts +17 -0
- package/src/editor/utils/cssVar.ts +14 -0
- package/src/editor/utils/getRenderContainer.ts +39 -0
- package/src/editor/utils/index.ts +16 -0
- package/src/editor/utils/isCustomNodeSelected.ts +47 -0
- package/src/editor/utils/isTextSelected.ts +25 -0
- package/src/editor/utils/locale.ts +5 -0
- package/src/editor/viewer/index.tsx +26 -0
- package/src/globals.css +1 -0
- package/src/index.ts +7 -0
- package/src/locales/en-us.ts +133 -0
- package/src/locales/zh-cn.ts +133 -0
- 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
|
+
};
|