tetrons 0.1.0 → 2.1.1

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 (67) hide show
  1. package/dist/index.d.mts +5 -0
  2. package/dist/index.d.ts +5 -0
  3. package/dist/index.js +17177 -0
  4. package/dist/index.mjs +17190 -0
  5. package/dist/tsconfig.tsbuildinfo +1 -0
  6. package/package.json +77 -61
  7. package/.hintrc +0 -13
  8. package/eslint.config.mjs +0 -16
  9. package/next.config.ts +0 -7
  10. package/postcss.config.mjs +0 -5
  11. package/public/editor-content.json +0 -27
  12. package/public/favicon-16x16.png +0 -0
  13. package/public/favicon-32x32.png +0 -0
  14. package/public/favicon-64x64.png +0 -0
  15. package/public/favicon-768x768.png +0 -0
  16. package/public/file.svg +0 -1
  17. package/public/globe.svg +0 -1
  18. package/public/next.svg +0 -1
  19. package/public/site.webmanifest +0 -20
  20. package/public/vercel.svg +0 -1
  21. package/public/window.svg +0 -1
  22. package/src/app/api/export/route.ts +0 -0
  23. package/src/app/api/save/route.ts +0 -18
  24. package/src/app/favicon-16x16.png +0 -0
  25. package/src/app/favicon-32x32.png +0 -0
  26. package/src/app/favicon-64x64.png +0 -0
  27. package/src/app/favicon-768x768.png +0 -0
  28. package/src/app/favicon.ico +0 -0
  29. package/src/app/globals.css +0 -207
  30. package/src/app/layout.tsx +0 -47
  31. package/src/app/page.tsx +0 -11
  32. package/src/components/UI/Button.tsx +0 -0
  33. package/src/components/UI/Dropdown.tsx +0 -0
  34. package/src/components/tetrons/EditorContent.tsx +0 -210
  35. package/src/components/tetrons/ResizableImage.ts +0 -39
  36. package/src/components/tetrons/ResizableImageComponent.tsx +0 -77
  37. package/src/components/tetrons/ResizableVideo.ts +0 -66
  38. package/src/components/tetrons/ResizableVideoComponent.tsx +0 -56
  39. package/src/components/tetrons/helpers.ts +0 -0
  40. package/src/components/tetrons/toolbar/ActionGroup.tsx +0 -222
  41. package/src/components/tetrons/toolbar/ClipboardGroup.tsx +0 -57
  42. package/src/components/tetrons/toolbar/FileGroup.tsx +0 -70
  43. package/src/components/tetrons/toolbar/FontStyleGroup.tsx +0 -198
  44. package/src/components/tetrons/toolbar/InsertGroup.tsx +0 -268
  45. package/src/components/tetrons/toolbar/ListAlignGroup.tsx +0 -69
  46. package/src/components/tetrons/toolbar/MiscGroup.tsx +0 -70
  47. package/src/components/tetrons/toolbar/TableContextMenu.tsx +0 -91
  48. package/src/components/tetrons/toolbar/TetronsToolbar.tsx +0 -60
  49. package/src/components/tetrons/toolbar/ToolbarButton.tsx +0 -38
  50. package/src/components/tetrons/toolbar/extensions/Comment.ts +0 -72
  51. package/src/components/tetrons/toolbar/extensions/Embed.ts +0 -113
  52. package/src/components/tetrons/toolbar/extensions/FontFamily.ts +0 -43
  53. package/src/components/tetrons/toolbar/extensions/FontSize.ts +0 -43
  54. package/src/components/tetrons/toolbar/extensions/ResizableTable.ts +0 -16
  55. package/src/components/tetrons/toolbar/marks/Subscript.ts +0 -45
  56. package/src/components/tetrons/toolbar/marks/Superscript.ts +0 -45
  57. package/src/index.ts +0 -3
  58. package/src/lib/export.ts +0 -0
  59. package/src/lib/tiptap-extensions.ts +0 -0
  60. package/src/types/dom-to-pdf.d.ts +0 -13
  61. package/src/types/editor.d.ts +0 -0
  62. package/src/types/emoji-picker.d.ts +0 -18
  63. package/src/types/global.d.ts +0 -6
  64. package/src/types/html2pdf.d.ts +0 -1
  65. package/src/types/tiptap-extensions.d.ts +0 -23
  66. package/src/utils/loadEmojiPicker.ts +0 -12
  67. package/tsconfig.json +0 -41
@@ -1,57 +0,0 @@
1
- import {
2
- MdContentPaste,
3
- MdContentCut,
4
- MdContentCopy,
5
- MdFormatPaint,
6
- } from "react-icons/md";
7
- import { Editor } from "@tiptap/react";
8
- import ToolbarButton from "./ToolbarButton";
9
-
10
- export default function ClipboardGroup({ editor }: { editor: Editor }) {
11
- return (
12
- <div className="flex gap-1 border-r pr-3">
13
- <ToolbarButton
14
- icon={MdContentPaste}
15
- title="Paste"
16
- onClick={async () => {
17
- try {
18
- const text = await navigator.clipboard.readText();
19
- editor.chain().focus().insertContent(text).run();
20
- } catch (error) {
21
- console.error("Failed to read clipboard contents:", error);
22
- }
23
- }}
24
- />
25
- <ToolbarButton
26
- icon={MdContentCut}
27
- title="Cut"
28
- onClick={() => {
29
- const { from, to } = editor.state.selection;
30
- if (from === to) return;
31
- const selectedText = editor.state.doc.textBetween(from, to);
32
- navigator.clipboard.writeText(selectedText).then(() => {
33
- editor.chain().focus().deleteRange({ from, to }).run();
34
- });
35
- }}
36
- />
37
- <ToolbarButton
38
- icon={MdContentCopy}
39
- title="Copy"
40
- onClick={() => {
41
- const { from, to } = editor.state.selection;
42
- if (from === to) return;
43
- const selectedText = editor.state.doc.textBetween(from, to);
44
- navigator.clipboard.writeText(selectedText);
45
- }}
46
- />
47
- <ToolbarButton
48
- icon={MdFormatPaint}
49
- title="Format Painter"
50
- onClick={() => {
51
- const currentMarks = editor.getAttributes("textStyle");
52
- localStorage.setItem("formatPainter", JSON.stringify(currentMarks));
53
- }}
54
- />
55
- </div>
56
- );
57
- }
@@ -1,70 +0,0 @@
1
- "use client";
2
-
3
- import { Editor } from "@tiptap/react";
4
- import { FaRegFolderOpen } from "react-icons/fa";
5
- import { VscNewFile } from "react-icons/vsc";
6
- import ToolbarButton from "./ToolbarButton";
7
- import { useRef } from "react";
8
-
9
- type FileGroupProps = {
10
- editor: Editor;
11
- };
12
-
13
- export default function FileGroup({ editor }: FileGroupProps) {
14
- const fileInputRef = useRef<HTMLInputElement>(null);
15
-
16
- const handleNew = () => {
17
- if (
18
- confirm(
19
- "Are you sure you want to create a new document? Unsaved changes will be lost."
20
- )
21
- ) {
22
- editor.commands.clearContent();
23
- }
24
- };
25
-
26
- const handleOpen = () => {
27
- fileInputRef.current?.click();
28
- };
29
-
30
- const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
31
- const file = e.target.files?.[0];
32
- if (!file) return;
33
-
34
- try {
35
- const text = await file.text();
36
- const json = JSON.parse(text);
37
- editor.commands.setContent(json);
38
- } catch (error) {
39
- console.error("Error reading file:", error);
40
- alert(
41
- "Failed to open or parse the file. Make sure it's a valid JSON file."
42
- );
43
- } finally {
44
- e.target.value = "";
45
- }
46
- };
47
-
48
- return (
49
- <div
50
- className="flex items-center gap-1 border-r pr-3"
51
- role="group"
52
- aria-label="File actions"
53
- >
54
- <input
55
- type="file"
56
- accept=".json"
57
- ref={fileInputRef}
58
- onChange={handleFileChange}
59
- className="hidden"
60
- aria-label="Open JSON file"
61
- />
62
- <ToolbarButton icon={VscNewFile} onClick={handleNew} title="New" />
63
- <ToolbarButton
64
- icon={FaRegFolderOpen}
65
- onClick={handleOpen}
66
- title="Open File"
67
- />
68
- </div>
69
- );
70
- }
@@ -1,198 +0,0 @@
1
- "use client";
2
-
3
- import {
4
- MdFormatBold,
5
- MdFormatItalic,
6
- MdFormatUnderlined,
7
- MdStrikethroughS,
8
- MdSubscript,
9
- MdSuperscript,
10
- MdFormatClear,
11
- MdFormatPaint,
12
- } from "react-icons/md";
13
- import { ImTextColor } from "react-icons/im";
14
- import { BiSolidColorFill } from "react-icons/bi";
15
- import { Editor } from "@tiptap/react";
16
- import ToolbarButton from "./ToolbarButton";
17
- import { useEffect, useState } from "react";
18
-
19
- interface FontStyleGroupProps {
20
- editor: Editor;
21
- }
22
-
23
- export default function FontStyleGroup({ editor }: FontStyleGroupProps) {
24
- const [textColor, setTextColor] = useState("#000000");
25
- const [highlightColor, setHighlightColor] = useState("#ffff00");
26
- const [fontFamily, setFontFamily] = useState("Arial");
27
- const [fontSize, setFontSize] = useState("16px");
28
-
29
- useEffect(() => {
30
- if (!editor) return;
31
-
32
- const updateStates = () => {
33
- const highlight = editor.getAttributes("highlight");
34
- setHighlightColor(highlight?.color || "#ffff00");
35
-
36
- const color = editor.getAttributes("textStyle")?.color;
37
- setTextColor(color || "#000000");
38
-
39
- const fontAttr = editor.getAttributes("fontFamily")?.font || "Arial";
40
- setFontFamily(fontAttr);
41
-
42
- const sizeAttr = editor.getAttributes("fontSize")?.size || "16px";
43
- setFontSize(sizeAttr);
44
- };
45
-
46
- updateStates();
47
-
48
- editor.on("selectionUpdate", updateStates);
49
- editor.on("transaction", updateStates);
50
-
51
- return () => {
52
- editor.off("selectionUpdate", updateStates);
53
- editor.off("transaction", updateStates);
54
- };
55
- }, [editor]);
56
-
57
- return (
58
- <div className="flex gap-1 border-r pr-3 items-center">
59
- <select
60
- title="Font Family"
61
- className="text-sm border rounded px-1 py-0.5 mr-2"
62
- value={fontFamily}
63
- onChange={(e) => {
64
- const value = e.target.value;
65
- setFontFamily(value);
66
- editor.chain().focus().setFontFamily(value).run();
67
- }}
68
- >
69
- <option value="Arial">Arial</option>
70
- <option value="Georgia">Georgia</option>
71
- <option value="Times New Roman">Times New Roman</option>
72
- <option value="Courier New">Courier New</option>
73
- <option value="Verdana">Verdana</option>
74
- </select>
75
-
76
- <select
77
- title="Font Size"
78
- className="text-sm border rounded px-1 py-0.5 mr-2"
79
- value={fontSize}
80
- onChange={(e) => {
81
- const value = e.target.value;
82
- setFontSize(value);
83
- editor.chain().focus().setFontSize(value).run();
84
- }}
85
- >
86
- <option value="12px">12</option>
87
- <option value="14px">14</option>
88
- <option value="16px">16</option>
89
- <option value="18px">18</option>
90
- <option value="24px">24</option>
91
- <option value="36px">36</option>
92
- <option value="48px">48</option>
93
- <option value="64px">64</option>
94
- <option value="72px">72</option>
95
- </select>
96
-
97
- <ToolbarButton
98
- icon={MdFormatBold}
99
- label="Bold"
100
- onClick={() => editor.chain().focus().toggleBold().run()}
101
- isActive={editor.isActive("bold")}
102
- />
103
- <ToolbarButton
104
- icon={MdFormatItalic}
105
- label="Italic"
106
- onClick={() => editor.chain().focus().toggleItalic().run()}
107
- isActive={editor.isActive("italic")}
108
- />
109
- <ToolbarButton
110
- icon={MdFormatUnderlined}
111
- label="Underline"
112
- onClick={() => editor.chain().focus().toggleUnderline().run()}
113
- isActive={editor.isActive("underline")}
114
- />
115
- <ToolbarButton
116
- icon={MdStrikethroughS}
117
- label="Strikethrough"
118
- onClick={() => editor.chain().focus().toggleStrike().run()}
119
- isActive={editor.isActive("strike")}
120
- />
121
- <ToolbarButton
122
- icon={MdSubscript}
123
- label="Subscript"
124
- onClick={() => editor.chain().focus().toggleSubscript().run()}
125
- isActive={editor.isActive("subscript")}
126
- />
127
- <ToolbarButton
128
- icon={MdSuperscript}
129
- label="Superscript"
130
- onClick={() => editor.chain().focus().toggleSuperscript().run()}
131
- isActive={editor.isActive("superscript")}
132
- />
133
-
134
- <label
135
- title="Font Color"
136
- aria-label="Font Color"
137
- className="relative w-8 h-8 flex items-center justify-center cursor-pointer color-label"
138
- style={{ "--indicator-color": textColor } as React.CSSProperties}
139
- >
140
- <ImTextColor size={20} className="text-gray-700" />
141
- <div className="color-indicator" />
142
- <input
143
- type="color"
144
- value={textColor}
145
- onChange={(e) => {
146
- const color = e.target.value;
147
- setTextColor(color);
148
- editor.chain().focus().setColor(color).run();
149
- }}
150
- className="absolute inset-0 opacity-0 cursor-pointer"
151
- />
152
- </label>
153
-
154
- <label
155
- title="Highlight Color"
156
- aria-label="Highlight Color"
157
- className="relative w-8 h-8 flex items-center justify-center cursor-pointer color-label"
158
- style={{ "--indicator-color": highlightColor } as React.CSSProperties}
159
- >
160
- <BiSolidColorFill size={20} className="text-gray-700" />
161
- <div className="color-indicator" />
162
- <input
163
- type="color"
164
- value={highlightColor}
165
- onChange={(e) => {
166
- const color = e.target.value;
167
- setHighlightColor(color);
168
- editor.chain().focus().setHighlight({ color }).run();
169
- }}
170
- className="absolute inset-0 opacity-0 cursor-pointer"
171
- />
172
- </label>
173
-
174
- <ToolbarButton
175
- icon={MdFormatClear}
176
- label="Clear Formatting"
177
- onClick={() => editor.chain().focus().unsetAllMarks().run()}
178
- />
179
- <ToolbarButton
180
- icon={MdFormatPaint}
181
- label="Apply Painter Format"
182
- onClick={() => {
183
- const format = JSON.parse(
184
- localStorage.getItem("formatPainter") || "{}"
185
- );
186
- if (format.color) editor.chain().focus().setColor(format.color).run();
187
- if (format.backgroundColor) {
188
- editor
189
- .chain()
190
- .focus()
191
- .setHighlight({ color: format.backgroundColor })
192
- .run();
193
- }
194
- }}
195
- />
196
- </div>
197
- );
198
- }
@@ -1,268 +0,0 @@
1
- "use client";
2
-
3
- import React, { useRef, useState } from "react";
4
- import {
5
- MdTableChart,
6
- MdInsertPhoto,
7
- MdInsertLink,
8
- MdInsertComment,
9
- MdInsertEmoticon,
10
- MdHorizontalRule,
11
- MdVideoLibrary,
12
- MdOutlineOndemandVideo,
13
- } from "react-icons/md";
14
- import { Editor } from "@tiptap/react";
15
- import ToolbarButton from "./ToolbarButton";
16
- import Picker from "@emoji-mart/react";
17
-
18
- type Emoji = {
19
- native: string;
20
- };
21
-
22
- export default function InsertGroup({ editor }: { editor: Editor }) {
23
- const [showTableGrid, setShowTableGrid] = useState(false);
24
- const [selectedRows, setSelectedRows] = useState(1);
25
- const [selectedCols, setSelectedCols] = useState(1);
26
-
27
- const imageInputRef = useRef<HTMLInputElement>(null);
28
- const videoInputRef = useRef<HTMLInputElement>(null);
29
-
30
- const [showPicker, setShowPicker] = useState(false);
31
-
32
- const addEmoji = (emoji: Emoji) => {
33
- editor.chain().focus().insertContent(emoji.native).run();
34
- setShowPicker(false);
35
- };
36
-
37
- const handleTableCellHover = (row: number, col: number) => {
38
- setSelectedRows(row);
39
- setSelectedCols(col);
40
- };
41
-
42
- const handleTableInsert = (rows: number, cols: number) => {
43
- editor
44
- .chain()
45
- .focus()
46
- .insertTable({
47
- rows: rows || 1,
48
- cols: cols || 1,
49
- withHeaderRow: true,
50
- })
51
- .run();
52
-
53
- setShowTableGrid(false);
54
- setSelectedRows(1);
55
- setSelectedCols(1);
56
- };
57
-
58
- const handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
59
- const file = e.target.files?.[0];
60
- if (file) {
61
- const reader = new FileReader();
62
- reader.onload = () => {
63
- editor
64
- .chain()
65
- .focus()
66
- .setImage({ src: reader.result as string })
67
- .run();
68
- };
69
- reader.readAsDataURL(file);
70
- }
71
- };
72
-
73
- const handleVideoUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
74
- const file = e.target.files?.[0];
75
- if (file) {
76
- const reader = new FileReader();
77
- reader.onload = () => {
78
- editor
79
- .chain()
80
- .focus()
81
- .setVideo({ src: reader.result as string, controls: true })
82
- .run();
83
- };
84
- reader.readAsDataURL(file);
85
- }
86
- };
87
-
88
- function normalizeEmbedUrl(url: string): string {
89
- try {
90
- const u = new URL(url);
91
- const hostname = u.hostname.replace("www.", "").toLowerCase();
92
- if (hostname === "youtube.com" || hostname === "youtu.be") {
93
- let videoId = u.searchParams.get("v");
94
- if (!videoId && hostname === "youtu.be") {
95
- videoId = u.pathname.slice(1);
96
- }
97
- if (videoId) {
98
- return `https://www.youtube.com/embed/${videoId}`;
99
- }
100
- }
101
- if (hostname === "vimeo.com") {
102
- const videoId = u.pathname.split("/")[1];
103
- if (videoId) {
104
- return `https://player.vimeo.com/video/${videoId}`;
105
- }
106
- }
107
- if (
108
- hostname === "google.com" ||
109
- hostname === "maps.google.com" ||
110
- hostname === "goo.gl"
111
- ) {
112
- if (url.includes("/maps/embed")) {
113
- return url;
114
- }
115
- return url.replace("/maps/", "/maps/embed/");
116
- }
117
- return url;
118
- } catch {
119
- return url;
120
- }
121
- }
122
-
123
-
124
- return (
125
- <div className="flex gap-1 border-r pr-3 relative">
126
- <input
127
- type="file"
128
- accept="image/*"
129
- ref={imageInputRef}
130
- className="hidden"
131
- onChange={handleImageUpload}
132
- aria-label="Upload Image"
133
- title="Upload Image"
134
- />
135
- <input
136
- type="file"
137
- accept="video/*"
138
- ref={videoInputRef}
139
- className="hidden"
140
- onChange={handleVideoUpload}
141
- aria-label="Upload Video"
142
- title="Upload Video"
143
- />
144
-
145
- <ToolbarButton
146
- icon={MdTableChart}
147
- label="Insert Table"
148
- onClick={() => setShowTableGrid(!showTableGrid)}
149
- />
150
- {showTableGrid && (
151
- <div
152
- className="absolute top-10 left-0 bg-white border rounded shadow p-2 z-20"
153
- onMouseLeave={() => setShowTableGrid(false)}
154
- >
155
- <div className="grid grid-cols-10 gap-[1px]">
156
- {[...Array(10)].map((_, row) =>
157
- [...Array(10)].map((_, col) => {
158
- const isSelected = row < selectedRows && col < selectedCols;
159
- return (
160
- <div
161
- key={`${row}-${col}`}
162
- className={`w-5 h-5 border cursor-pointer ${
163
- isSelected ? "bg-blue-500" : "bg-gray-100"
164
- }`}
165
- onMouseEnter={() => handleTableCellHover(row + 1, col + 1)}
166
- onClick={() => handleTableInsert(row + 1, col + 1)}
167
- />
168
- );
169
- })
170
- )}
171
- </div>
172
- <div className="text-sm mt-2 text-center text-gray-600">
173
- {selectedRows} x {selectedCols}
174
- </div>
175
- </div>
176
- )}
177
-
178
- <ToolbarButton
179
- icon={MdInsertPhoto}
180
- label="Insert Image"
181
- onClick={() => imageInputRef.current?.click()}
182
- />
183
- <ToolbarButton
184
- icon={MdVideoLibrary}
185
- label="Insert Video"
186
- onClick={() => videoInputRef.current?.click()}
187
- />
188
- <ToolbarButton
189
- icon={MdInsertLink}
190
- label="Insert Link"
191
- onClick={() => {
192
- const url = prompt("Enter URL");
193
- if (url)
194
- editor
195
- .chain()
196
- .focus()
197
- .extendMarkRange("link")
198
- .setLink({ href: url })
199
- .run();
200
- }}
201
- />
202
- <ToolbarButton
203
- icon={MdInsertComment}
204
- label="Insert Comment"
205
- onClick={() => {
206
- const comment = prompt("Enter your comment");
207
- if (
208
- comment &&
209
- editor.can().chain().focus().setComment(comment).run()
210
- ) {
211
- editor.chain().focus().setComment(comment).run();
212
- } else {
213
- console.warn("Cannot apply comment — maybe no selection?");
214
- }
215
- }}
216
- />
217
- <div className="relative">
218
- <ToolbarButton
219
- icon={MdInsertEmoticon}
220
- label="Emoji"
221
- onClick={() => setShowPicker(!showPicker)}
222
- />
223
- {showPicker && (
224
- <div className="absolute z-50 top-10 left-0">
225
- <Picker
226
- onEmojiSelect={addEmoji}
227
- theme="auto"
228
- emoji="point_up"
229
- showPreview={false}
230
- showSkinTones={true}
231
- emojiTooltip={true}
232
- />
233
- </div>
234
- )}
235
- </div>
236
- <ToolbarButton
237
- icon={MdHorizontalRule}
238
- label="Horizontal Line"
239
- onClick={() => editor.chain().focus().setHorizontalRule().run()}
240
- />
241
- <ToolbarButton
242
- icon={MdOutlineOndemandVideo}
243
- label="Embed"
244
- onClick={() => {
245
- const url = prompt(
246
- "Enter embed URL (YouTube, Vimeo, Google Maps, etc.)"
247
- );
248
- if (!url) return;
249
-
250
- const embedUrl = normalizeEmbedUrl(url);
251
-
252
- const width = prompt("Enter width in pixels", "560");
253
- const height = prompt("Enter height in pixels", "315");
254
-
255
- editor
256
- .chain()
257
- .focus()
258
- .setEmbed({
259
- src: embedUrl,
260
- width: width ? parseInt(width) : 560,
261
- height: height ? parseInt(height) : 315,
262
- })
263
- .run();
264
- }}
265
- />
266
- </div>
267
- );
268
- }
@@ -1,69 +0,0 @@
1
- "use client";
2
-
3
- import {
4
- MdFormatListBulleted,
5
- MdFormatListNumbered,
6
- MdFormatIndentDecrease,
7
- MdFormatIndentIncrease,
8
- MdFormatAlignLeft,
9
- MdFormatAlignCenter,
10
- MdFormatAlignRight,
11
- MdFormatAlignJustify,
12
- } from "react-icons/md";
13
- import { Editor } from "@tiptap/react";
14
- import ToolbarButton from "./ToolbarButton";
15
-
16
- export default function ListAlignGroup({ editor }: { editor: Editor }) {
17
- return (
18
- <div className="flex gap-1 border-r pr-3">
19
- <ToolbarButton
20
- icon={MdFormatListBulleted}
21
- title="Bulleted List"
22
- onClick={() => editor.chain().focus().toggleBulletList().run()}
23
- disabled={!editor.can().toggleBulletList()}
24
- />
25
- <ToolbarButton
26
- icon={MdFormatListNumbered}
27
- title="Numbered List"
28
- onClick={() => editor.chain().focus().toggleOrderedList().run()}
29
- disabled={!editor.can().toggleOrderedList()}
30
- />
31
- <ToolbarButton
32
- icon={MdFormatIndentIncrease}
33
- title="Increase Indent"
34
- onClick={() => editor.chain().focus().sinkListItem("listItem").run()}
35
- disabled={!editor.can().sinkListItem("listItem")}
36
- />
37
- <ToolbarButton
38
- icon={MdFormatIndentDecrease}
39
- title="Decrease Indent"
40
- onClick={() => editor.chain().focus().liftListItem("listItem").run()}
41
- disabled={!editor.can().liftListItem("listItem")}
42
- />
43
- <ToolbarButton
44
- icon={MdFormatAlignLeft}
45
- title="Align Left"
46
- onClick={() => editor.chain().focus().setTextAlign("left").run()}
47
- disabled={!editor.can().setTextAlign("left")}
48
- />
49
- <ToolbarButton
50
- icon={MdFormatAlignCenter}
51
- title="Align Center"
52
- onClick={() => editor.chain().focus().setTextAlign("center").run()}
53
- disabled={!editor.can().setTextAlign("center")}
54
- />
55
- <ToolbarButton
56
- icon={MdFormatAlignRight}
57
- title="Align Right"
58
- onClick={() => editor.chain().focus().setTextAlign("right").run()}
59
- disabled={!editor.can().setTextAlign("right")}
60
- />
61
- <ToolbarButton
62
- icon={MdFormatAlignJustify}
63
- title="Justify"
64
- onClick={() => editor.chain().focus().setTextAlign("justify").run()}
65
- disabled={!editor.can().setTextAlign("justify")}
66
- />
67
- </div>
68
- );
69
- }