tetrons 2.3.21 → 2.3.23

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 (29) hide show
  1. package/dist/components/UI/Button.tsx +0 -0
  2. package/dist/components/UI/Dropdown.tsx +0 -0
  3. package/dist/components/tetrons/EditorContent.tsx +282 -0
  4. package/dist/components/tetrons/ResizableImage.ts +39 -0
  5. package/dist/components/tetrons/ResizableImageComponent.tsx +77 -0
  6. package/dist/components/tetrons/ResizableVideo.ts +66 -0
  7. package/dist/components/tetrons/ResizableVideoComponent.tsx +56 -0
  8. package/dist/components/tetrons/helpers.ts +0 -0
  9. package/dist/components/tetrons/toolbar/ActionGroup.tsx +218 -0
  10. package/dist/components/tetrons/toolbar/ClipboardGroup.tsx +58 -0
  11. package/dist/components/tetrons/toolbar/FileGroup.tsx +66 -0
  12. package/dist/components/tetrons/toolbar/FontStyleGroup.tsx +194 -0
  13. package/dist/components/tetrons/toolbar/InsertGroup.tsx +267 -0
  14. package/dist/components/tetrons/toolbar/ListAlignGroup.tsx +69 -0
  15. package/dist/components/tetrons/toolbar/MiscGroup.tsx +71 -0
  16. package/dist/components/tetrons/toolbar/TableContextMenu.tsx +91 -0
  17. package/dist/components/tetrons/toolbar/TetronsToolbar.tsx +71 -0
  18. package/dist/components/tetrons/toolbar/ToolbarButton.tsx +36 -0
  19. package/dist/components/tetrons/toolbar/extensions/Comment.ts +72 -0
  20. package/dist/components/tetrons/toolbar/extensions/Embed.ts +113 -0
  21. package/dist/components/tetrons/toolbar/extensions/FontFamily.ts +43 -0
  22. package/dist/components/tetrons/toolbar/extensions/FontSize.ts +43 -0
  23. package/dist/components/tetrons/toolbar/extensions/ResizableTable.ts +16 -0
  24. package/dist/components/tetrons/toolbar/marks/Subscript.ts +45 -0
  25. package/dist/components/tetrons/toolbar/marks/Superscript.ts +45 -0
  26. package/dist/index.js +0 -1
  27. package/dist/index.mjs +0 -1
  28. package/package.json +7 -7
  29. package/dist/tetrons-UCHWNATC.css +0 -372
@@ -0,0 +1,267 @@
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
+ return (
124
+ <div className="insert-group">
125
+ <input
126
+ type="file"
127
+ accept="image/*"
128
+ ref={imageInputRef}
129
+ className="hidden-input"
130
+ onChange={handleImageUpload}
131
+ aria-label="Upload Image"
132
+ title="Upload Image"
133
+ />
134
+ <input
135
+ type="file"
136
+ accept="video/*"
137
+ ref={videoInputRef}
138
+ className="hidden-input"
139
+ onChange={handleVideoUpload}
140
+ aria-label="Upload Video"
141
+ title="Upload Video"
142
+ />
143
+
144
+ <ToolbarButton
145
+ icon={MdTableChart}
146
+ label="Insert Table"
147
+ onClick={() => setShowTableGrid(!showTableGrid)}
148
+ />
149
+ {showTableGrid && (
150
+ <div
151
+ className="table-grid-popup"
152
+ onMouseLeave={() => setShowTableGrid(false)}
153
+ >
154
+ <div className="table-grid">
155
+ {[...Array(10)].map((_, row) =>
156
+ [...Array(10)].map((_, col) => {
157
+ const isSelected = row < selectedRows && col < selectedCols;
158
+ return (
159
+ <div
160
+ key={`${row}-${col}`}
161
+ className={`table-grid-cell ${
162
+ isSelected ? "selected" : ""
163
+ }`}
164
+ onMouseEnter={() => handleTableCellHover(row + 1, col + 1)}
165
+ onClick={() => handleTableInsert(row + 1, col + 1)}
166
+ />
167
+ );
168
+ })
169
+ )}
170
+ </div>
171
+ <div className="table-grid-label">
172
+ {selectedRows} x {selectedCols}
173
+ </div>
174
+ </div>
175
+ )}
176
+
177
+ <ToolbarButton
178
+ icon={MdInsertPhoto}
179
+ label="Insert Image"
180
+ onClick={() => imageInputRef.current?.click()}
181
+ />
182
+ <ToolbarButton
183
+ icon={MdVideoLibrary}
184
+ label="Insert Video"
185
+ onClick={() => videoInputRef.current?.click()}
186
+ />
187
+ <ToolbarButton
188
+ icon={MdInsertLink}
189
+ label="Insert Link"
190
+ onClick={() => {
191
+ const url = prompt("Enter URL");
192
+ if (url) {
193
+ editor
194
+ .chain()
195
+ .focus()
196
+ .extendMarkRange("link")
197
+ .setLink({ href: url })
198
+ .run();
199
+ }
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="emoji-picker">
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
+ const width = prompt("Enter width in pixels", "560");
252
+ const height = prompt("Enter height in pixels", "315");
253
+
254
+ editor
255
+ .chain()
256
+ .focus()
257
+ .setEmbed({
258
+ src: embedUrl,
259
+ width: width ? parseInt(width) : 560,
260
+ height: height ? parseInt(height) : 315,
261
+ })
262
+ .run();
263
+ }}
264
+ />
265
+ </div>
266
+ );
267
+ }
@@ -0,0 +1,69 @@
1
+ "use client";
2
+ import React from "react";
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="list-align-group">
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
+ }
@@ -0,0 +1,71 @@
1
+ import React from "react";
2
+ import {
3
+ MdUndo,
4
+ MdRedo,
5
+ MdRefresh,
6
+ MdVisibility,
7
+ MdCode,
8
+ } from "react-icons/md";
9
+ import { Editor } from "@tiptap/react";
10
+ import ToolbarButton from "./ToolbarButton";
11
+
12
+ interface MiscGroupProps {
13
+ editor: Editor;
14
+ }
15
+
16
+ export default function MiscGroup({ editor }: MiscGroupProps) {
17
+ const handlePreview = () => {
18
+ const html = editor.getHTML();
19
+ const previewWindow = window.open("", "_blank");
20
+ if (previewWindow) {
21
+ previewWindow.document.open();
22
+ previewWindow.document.write(`
23
+ <html>
24
+ <head>
25
+ <title>Preview</title>
26
+ <style>
27
+ body { font-family: sans-serif; padding: 2rem; line-height: 1.6; }
28
+ </style>
29
+ </head>
30
+ <body>${html}</body>
31
+ </html>
32
+ `);
33
+ previewWindow.document.close();
34
+ }
35
+ };
36
+
37
+ return (
38
+ <div className="misc-group">
39
+ <ToolbarButton
40
+ icon={MdUndo}
41
+ label="Undo"
42
+ onClick={() => editor.chain().focus().undo().run()}
43
+ disabled={!editor.can().undo()}
44
+ />
45
+ <ToolbarButton
46
+ icon={MdRedo}
47
+ label="Redo"
48
+ onClick={() => editor.chain().focus().redo().run()}
49
+ disabled={!editor.can().redo()}
50
+ />
51
+ <ToolbarButton
52
+ icon={MdRefresh}
53
+ label="Reset Formatting"
54
+ onClick={() =>
55
+ editor.chain().focus().unsetAllMarks().clearNodes().run()
56
+ }
57
+ />
58
+ <ToolbarButton
59
+ icon={MdCode}
60
+ label="Toggle Code Block"
61
+ onClick={() => editor.chain().focus().toggleCodeBlock().run()}
62
+ isActive={editor.isActive("codeBlock")}
63
+ />
64
+ <ToolbarButton
65
+ icon={MdVisibility}
66
+ label="Preview"
67
+ onClick={handlePreview}
68
+ />
69
+ </div>
70
+ );
71
+ }
@@ -0,0 +1,91 @@
1
+ "use client";
2
+
3
+ import React, { useEffect, useState } from "react";
4
+ import { Editor } from "@tiptap/react";
5
+
6
+ interface ContextMenuProps {
7
+ editor: Editor;
8
+ }
9
+
10
+ export default function TableContextMenu({ editor }: ContextMenuProps) {
11
+ const [menuPosition, setMenuPosition] = useState<{
12
+ x: number;
13
+ y: number;
14
+ } | null>(null);
15
+
16
+ useEffect(() => {
17
+ const handleContextMenu = (event: MouseEvent) => {
18
+ const target = event.target as HTMLElement;
19
+
20
+ if (target.closest("td") || target.closest("th")) {
21
+ event.preventDefault();
22
+ setMenuPosition({ x: event.pageX, y: event.pageY });
23
+ } else {
24
+ setMenuPosition(null);
25
+ }
26
+ };
27
+
28
+ const handleClick = () => setMenuPosition(null);
29
+
30
+ document.addEventListener("contextmenu", handleContextMenu);
31
+ document.addEventListener("click", handleClick);
32
+
33
+ return () => {
34
+ document.removeEventListener("contextmenu", handleContextMenu);
35
+ document.removeEventListener("click", handleClick);
36
+ };
37
+ }, []);
38
+
39
+ const insertRowAbove = () => editor.chain().focus().addRowBefore().run();
40
+ const insertRowBelow = () => editor.chain().focus().addRowAfter().run();
41
+ const insertColLeft = () => editor.chain().focus().addColumnBefore().run();
42
+ const insertColRight = () => editor.chain().focus().addColumnAfter().run();
43
+ const deleteRow = () => editor.chain().focus().deleteRow().run();
44
+ const deleteCol = () => editor.chain().focus().deleteColumn().run();
45
+
46
+ if (!menuPosition) return null;
47
+
48
+ return (
49
+ <ul
50
+ className="absolute bg-white shadow border rounded text-sm z-50"
51
+ style={{ top: menuPosition.y, left: menuPosition.x }}
52
+ >
53
+ <li
54
+ className="px-3 py-1 hover:bg-gray-100 cursor-pointer"
55
+ onClick={insertRowAbove}
56
+ >
57
+ Insert Row Above
58
+ </li>
59
+ <li
60
+ className="px-3 py-1 hover:bg-gray-100 cursor-pointer"
61
+ onClick={insertRowBelow}
62
+ >
63
+ Insert Row Below
64
+ </li>
65
+ <li
66
+ className="px-3 py-1 hover:bg-gray-100 cursor-pointer"
67
+ onClick={insertColLeft}
68
+ >
69
+ Insert Column Left
70
+ </li>
71
+ <li
72
+ className="px-3 py-1 hover:bg-gray-100 cursor-pointer"
73
+ onClick={insertColRight}
74
+ >
75
+ Insert Column Right
76
+ </li>
77
+ <li
78
+ className="px-3 py-1 hover:bg-red-100 cursor-pointer"
79
+ onClick={deleteRow}
80
+ >
81
+ Delete Row
82
+ </li>
83
+ <li
84
+ className="px-3 py-1 hover:bg-red-100 cursor-pointer"
85
+ onClick={deleteCol}
86
+ >
87
+ Delete Column
88
+ </li>
89
+ </ul>
90
+ );
91
+ }
@@ -0,0 +1,71 @@
1
+ "use client";
2
+
3
+ import React, { useEffect, useState } from "react";
4
+ import type { Editor } from "@tiptap/react";
5
+
6
+ import ActionGroup from "./ActionGroup";
7
+ import ClipboardGroup from "./ClipboardGroup";
8
+ import FontStyleGroup from "./FontStyleGroup";
9
+ import InsertGroup from "./InsertGroup";
10
+ import ListAlignGroup from "./ListAlignGroup";
11
+ import MiscGroup from "./MiscGroup";
12
+ import FileGroup from "./FileGroup";
13
+
14
+ export default function TetronsToolbar({
15
+ editor,
16
+ version,
17
+ }: {
18
+ editor: Editor;
19
+ version: "free" | "pro" | "premium" | "platinum";
20
+ }) {
21
+ const [autoSave, setAutoSave] = useState(false);
22
+
23
+ useEffect(() => {
24
+ if (!editor) return;
25
+
26
+ const handleUpdate = () => {
27
+ if (!autoSave) return;
28
+ const content = editor.getJSON();
29
+ fetch("/api/save", {
30
+ method: "POST",
31
+ headers: { "Content-Type": "application/json" },
32
+ body: JSON.stringify(content),
33
+ }).catch((err) => console.error("Auto-save failed:", err));
34
+ };
35
+
36
+ editor.on("update", handleUpdate);
37
+ return () => {
38
+ editor.off("update", handleUpdate);
39
+ };
40
+ }, [autoSave, editor]);
41
+
42
+ return (
43
+ <div className="tetrons-toolbar">
44
+ {version !== "free" && (
45
+ <div className="group">
46
+ <input
47
+ type="checkbox"
48
+ id="autoSave"
49
+ checked={autoSave}
50
+ onChange={(e) => setAutoSave(e.target.checked)}
51
+ />
52
+ <label htmlFor="autoSave">Auto Save</label>
53
+ </div>
54
+ )}
55
+
56
+ {["pro", "premium", "platinum"].includes(version) && (
57
+ <FileGroup editor={editor} />
58
+ )}
59
+ <ClipboardGroup editor={editor} />
60
+ <FontStyleGroup editor={editor} />
61
+ <ListAlignGroup editor={editor} />
62
+ {["premium", "platinum"].includes(version) && (
63
+ <>
64
+ <InsertGroup editor={editor} />
65
+ <ActionGroup editor={editor} />
66
+ </>
67
+ )}
68
+ {version === "platinum" && <MiscGroup editor={editor} />}
69
+ </div>
70
+ );
71
+ }
@@ -0,0 +1,36 @@
1
+ import React from "react";
2
+ import type { IconType } from "react-icons";
3
+
4
+ export type ToolbarButtonProps = {
5
+ icon: IconType;
6
+ onClick: () => void;
7
+ disabled?: boolean;
8
+ title?: string;
9
+ label?: string;
10
+ isActive?: boolean;
11
+ };
12
+
13
+ const ToolbarButton = React.forwardRef<HTMLButtonElement, ToolbarButtonProps>(
14
+ (
15
+ { icon: Icon, onClick, disabled = false, title, label, isActive = false },
16
+ ref
17
+ ) => {
18
+ return (
19
+ <button
20
+ type="button"
21
+ ref={ref}
22
+ onClick={onClick}
23
+ disabled={disabled}
24
+ title={title ?? label}
25
+ aria-label={title ?? label}
26
+ className={`toolbar-button ${isActive ? "active" : ""}`}
27
+ >
28
+ <Icon size={20} />
29
+ </button>
30
+ );
31
+ }
32
+ );
33
+
34
+ ToolbarButton.displayName = "ToolbarButton";
35
+
36
+ export default ToolbarButton;
@@ -0,0 +1,72 @@
1
+ import { Mark, mergeAttributes } from "@tiptap/core";
2
+
3
+ export interface CommentOptions {
4
+ HTMLAttributes: {
5
+ class?: string;
6
+ style?: string;
7
+ [key: string]: unknown;
8
+ };
9
+ }
10
+
11
+ declare module "@tiptap/core" {
12
+ interface Commands<ReturnType> {
13
+ comment: {
14
+ setComment: (comment: string) => ReturnType;
15
+ unsetComment: () => ReturnType;
16
+ };
17
+ }
18
+ }
19
+
20
+ export const Comment = Mark.create<CommentOptions>({
21
+ name: "comment",
22
+
23
+ addOptions() {
24
+ return {
25
+ HTMLAttributes: {},
26
+ };
27
+ },
28
+
29
+ addAttributes() {
30
+ return {
31
+ comment: {
32
+ default: "",
33
+ },
34
+ };
35
+ },
36
+
37
+ parseHTML() {
38
+ return [
39
+ {
40
+ tag: "span[data-comment]",
41
+ },
42
+ ];
43
+ },
44
+
45
+ renderHTML({ HTMLAttributes }) {
46
+ return [
47
+ "span",
48
+ mergeAttributes(HTMLAttributes, {
49
+ "data-comment": HTMLAttributes.comment,
50
+ class: "comment-highlight",
51
+ title: HTMLAttributes.comment,
52
+ style: "background-color: rgba(255, 230, 0, 0.3);",
53
+ }),
54
+ 0,
55
+ ];
56
+ },
57
+
58
+ addCommands() {
59
+ return {
60
+ setComment:
61
+ (comment) =>
62
+ ({ commands }) => {
63
+ return commands.setMark(this.name, { comment });
64
+ },
65
+ unsetComment:
66
+ () =>
67
+ ({ commands }) => {
68
+ return commands.unsetMark(this.name);
69
+ },
70
+ };
71
+ },
72
+ });