@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,74 @@
1
+ import React, { type ReactElement, useCallback } from "react";
2
+ import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react";
3
+
4
+ import type { MenuProps, ShouldShowProps } from "../../../../menus/types";
5
+ import { Icon } from "../../../../ui/Icon";
6
+ import { Item } from "../../../../ui/PopoverMenu";
7
+ import { Toolbar } from "../../../../ui/Toolbar";
8
+ import { i18n } from "../../../../utils/locale";
9
+ import { isRowGripSelected } from "./utils";
10
+
11
+ export const TableRowMenu = React.memo(({ editor, appendTo }: MenuProps): ReactElement => {
12
+ const shouldShow = useCallback(
13
+ ({ view, state, from }: ShouldShowProps) => {
14
+ if (!(state && from)) {
15
+ return false;
16
+ }
17
+
18
+ return isRowGripSelected({ editor, view, state, from });
19
+ },
20
+ [editor],
21
+ );
22
+
23
+ const onAddRowBefore = useCallback(() => {
24
+ editor.chain().focus().addRowBefore().run();
25
+ }, [editor]);
26
+
27
+ const onAddRowAfter = useCallback(() => {
28
+ editor.chain().focus().addRowAfter().run();
29
+ }, [editor]);
30
+
31
+ const onDeleteRow = useCallback(() => {
32
+ editor.chain().focus().deleteRow().run();
33
+ }, [editor]);
34
+
35
+ return (
36
+ <BaseBubbleMenu
37
+ editor={editor}
38
+ pluginKey="tableRowMenu"
39
+ shouldShow={shouldShow}
40
+ tippyOptions={{
41
+ zIndex: 40,
42
+ appendTo: () => {
43
+ return appendTo?.current;
44
+ },
45
+ placement: "left",
46
+ offset: [0, 15],
47
+ popperOptions: {
48
+ modifiers: [{ name: "flip", enabled: false }],
49
+ },
50
+ }}
51
+ updateDelay={0}
52
+ >
53
+ <Toolbar.Wrapper isVertical>
54
+ <Item
55
+ close={false}
56
+ iconComponent={<Icon name="ArrowUpToLine" />}
57
+ label={i18n("tableColumnMenu.onAddRowBefore")}
58
+ onClick={onAddRowBefore}
59
+ />
60
+ <Item
61
+ close={false}
62
+ iconComponent={<Icon name="ArrowDownToLine" />}
63
+ label={i18n("tableColumnMenu.onAddRowAfter")}
64
+ onClick={onAddRowAfter}
65
+ />
66
+ <Item close={false} icon="Trash" label={i18n("tableColumnMenu.onDeleteRow")} onClick={onDeleteRow} />
67
+ </Toolbar.Wrapper>
68
+ </BaseBubbleMenu>
69
+ );
70
+ });
71
+
72
+ TableRowMenu.displayName = "TableRowMenu";
73
+
74
+ export default TableRowMenu;
@@ -0,0 +1,38 @@
1
+ import type { EditorState } from "@tiptap/pm/state";
2
+ import type { EditorView } from "@tiptap/pm/view";
3
+ import type { Editor } from "@tiptap/react";
4
+
5
+ import { Table } from "../..";
6
+ import { isTableSelected } from "../../utils";
7
+
8
+ export const isRowGripSelected = ({
9
+ editor,
10
+ view,
11
+ state,
12
+ from,
13
+ }: {
14
+ editor: Editor;
15
+ view: EditorView;
16
+ state: EditorState;
17
+ from: number;
18
+ }) => {
19
+ const domAtPos = view.domAtPos(from).node as HTMLElement;
20
+ const nodeDOM = view.nodeDOM(from) as HTMLElement;
21
+ const node = nodeDOM || domAtPos;
22
+
23
+ if (!(editor.isActive(Table.name) && node) || isTableSelected(state.selection)) {
24
+ return false;
25
+ }
26
+
27
+ let container = node;
28
+
29
+ while (container && !["TD", "TH"].includes(container.tagName)) {
30
+ container = container.parentElement!;
31
+ }
32
+
33
+ const gripRow = container?.querySelector?.("a.grip-row.selected");
34
+
35
+ return !!gripRow;
36
+ };
37
+
38
+ export default isRowGripSelected;
@@ -0,0 +1,2 @@
1
+ export { TableColumnMenu } from "./TableColumn";
2
+ export { TableRowMenu } from "./TableRow";
@@ -0,0 +1,258 @@
1
+ import { findParentNode } from "@tiptap/core";
2
+ import type { Node, ResolvedPos } from "@tiptap/pm/model";
3
+ import type { Selection, Transaction } from "@tiptap/pm/state";
4
+ import { CellSelection, TableMap } from "@tiptap/pm/tables";
5
+
6
+ const CELL_ROLE_REGEX = /cell/i;
7
+
8
+ // biome-ignore lint/suspicious/noExplicitAny: <isRectSelected>
9
+ export const isRectSelected = (rect: any) => (selection: CellSelection) => {
10
+ const map = TableMap.get(selection.$anchorCell.node(-1));
11
+ const start = selection.$anchorCell.start(-1);
12
+ const cells = map.cellsInRect(rect);
13
+ const selectedCells = map.cellsInRect(
14
+ map.rectBetween(selection.$anchorCell.pos - start, selection.$headCell.pos - start),
15
+ );
16
+
17
+ for (let i = 0, count = cells.length; i < count; i += 1) {
18
+ if (selectedCells.indexOf(cells[i]) === -1) {
19
+ return false;
20
+ }
21
+ }
22
+
23
+ return true;
24
+ };
25
+
26
+ export const findTable = (selection: Selection) =>
27
+ findParentNode((node) => node.type.spec.tableRole && node.type.spec.tableRole === "table")(selection);
28
+
29
+ // biome-ignore lint/suspicious/noExplicitAny: <isCellSelection>
30
+ export const isCellSelection = (selection: any) => selection instanceof CellSelection;
31
+
32
+ // biome-ignore lint/suspicious/noExplicitAny: <isColumnSelected>
33
+ export const isColumnSelected = (columnIndex: number) => (selection: any) => {
34
+ if (isCellSelection(selection)) {
35
+ const map = TableMap.get(selection.$anchorCell.node(-1));
36
+
37
+ return isRectSelected({
38
+ left: columnIndex,
39
+ right: columnIndex + 1,
40
+ top: 0,
41
+ bottom: map.height,
42
+ })(selection);
43
+ }
44
+
45
+ return false;
46
+ };
47
+
48
+ // biome-ignore lint/suspicious/noExplicitAny: <isRowSelected>
49
+ export const isRowSelected = (rowIndex: number) => (selection: any) => {
50
+ if (isCellSelection(selection)) {
51
+ const map = TableMap.get(selection.$anchorCell.node(-1));
52
+
53
+ return isRectSelected({
54
+ left: 0,
55
+ right: map.width,
56
+ top: rowIndex,
57
+ bottom: rowIndex + 1,
58
+ })(selection);
59
+ }
60
+
61
+ return false;
62
+ };
63
+
64
+ // biome-ignore lint/suspicious/noExplicitAny: <isTableSelected>
65
+ export const isTableSelected = (selection: any) => {
66
+ if (isCellSelection(selection)) {
67
+ const map = TableMap.get(selection.$anchorCell.node(-1));
68
+
69
+ return isRectSelected({
70
+ left: 0,
71
+ right: map.width,
72
+ top: 0,
73
+ bottom: map.height,
74
+ })(selection);
75
+ }
76
+
77
+ return false;
78
+ };
79
+
80
+ export const getCellsInColumn = (columnIndex: number | number[]) => (selection: Selection) => {
81
+ const table = findTable(selection);
82
+ if (table) {
83
+ const map = TableMap.get(table.node);
84
+ const indexes = Array.isArray(columnIndex) ? columnIndex : Array.from([columnIndex]);
85
+
86
+ return indexes.reduce(
87
+ (acc, index) => {
88
+ if (index >= 0 && index <= map.width - 1) {
89
+ const cells = map.cellsInRect({
90
+ left: index,
91
+ right: index + 1,
92
+ top: 0,
93
+ bottom: map.height,
94
+ });
95
+
96
+ return acc.concat(
97
+ cells.map((nodePos) => {
98
+ const node = table.node.nodeAt(nodePos);
99
+ const pos = nodePos + table.start;
100
+
101
+ return { pos, start: pos + 1, node };
102
+ }),
103
+ );
104
+ }
105
+
106
+ return acc;
107
+ },
108
+ [] as { pos: number; start: number; node: Node | null | undefined }[],
109
+ );
110
+ }
111
+ return null;
112
+ };
113
+
114
+ export const getCellsInRow = (rowIndex: number | number[]) => (selection: Selection) => {
115
+ const table = findTable(selection);
116
+
117
+ if (table) {
118
+ const map = TableMap.get(table.node);
119
+ const indexes = Array.isArray(rowIndex) ? rowIndex : Array.from([rowIndex]);
120
+
121
+ return indexes.reduce(
122
+ (acc, index) => {
123
+ if (index >= 0 && index <= map.height - 1) {
124
+ const cells = map.cellsInRect({
125
+ left: 0,
126
+ right: map.width,
127
+ top: index,
128
+ bottom: index + 1,
129
+ });
130
+
131
+ return acc.concat(
132
+ cells.map((nodePos) => {
133
+ const node = table.node.nodeAt(nodePos);
134
+ const pos = nodePos + table.start;
135
+ return { pos, start: pos + 1, node };
136
+ }),
137
+ );
138
+ }
139
+
140
+ return acc;
141
+ },
142
+ [] as { pos: number; start: number; node: Node | null | undefined }[],
143
+ );
144
+ }
145
+
146
+ return null;
147
+ };
148
+
149
+ export const getCellsInTable = (selection: Selection) => {
150
+ const table = findTable(selection);
151
+
152
+ if (table) {
153
+ const map = TableMap.get(table.node);
154
+ const cells = map.cellsInRect({
155
+ left: 0,
156
+ right: map.width,
157
+ top: 0,
158
+ bottom: map.height,
159
+ });
160
+
161
+ return cells.map((nodePos) => {
162
+ const node = table.node.nodeAt(nodePos);
163
+ const pos = nodePos + table.start;
164
+
165
+ return { pos, start: pos + 1, node };
166
+ });
167
+ }
168
+
169
+ return null;
170
+ };
171
+
172
+ export const findParentNodeClosestToPos = ($pos: ResolvedPos, predicate: (node: Node) => boolean) => {
173
+ for (let i = $pos.depth; i > 0; i -= 1) {
174
+ const node = $pos.node(i);
175
+
176
+ if (predicate(node)) {
177
+ return {
178
+ pos: i > 0 ? $pos.before(i) : 0,
179
+ start: $pos.start(i),
180
+ depth: i,
181
+ node,
182
+ };
183
+ }
184
+ }
185
+
186
+ return null;
187
+ };
188
+
189
+ export const findCellClosestToPos = ($pos: ResolvedPos) => {
190
+ const predicate = (node: Node) => node.type.spec.tableRole && CELL_ROLE_REGEX.test(node.type.spec.tableRole);
191
+
192
+ return findParentNodeClosestToPos($pos, predicate);
193
+ };
194
+
195
+ const select = (type: "row" | "column") => (index: number) => (tr: Transaction) => {
196
+ const table = findTable(tr.selection);
197
+ const isRowSelection = type === "row";
198
+
199
+ if (table) {
200
+ const map = TableMap.get(table.node);
201
+
202
+ // Check if the index is valid
203
+ if (index >= 0 && index < (isRowSelection ? map.height : map.width)) {
204
+ const left = isRowSelection ? 0 : index;
205
+ const top = isRowSelection ? index : 0;
206
+ const right = isRowSelection ? map.width : index + 1;
207
+ const bottom = isRowSelection ? index + 1 : map.height;
208
+
209
+ const cellsInFirstRow = map.cellsInRect({
210
+ left,
211
+ top,
212
+ right: isRowSelection ? right : left + 1,
213
+ bottom: isRowSelection ? top + 1 : bottom,
214
+ });
215
+
216
+ const cellsInLastRow =
217
+ bottom - top === 1
218
+ ? cellsInFirstRow
219
+ : map.cellsInRect({
220
+ left: isRowSelection ? left : right - 1,
221
+ top: isRowSelection ? bottom - 1 : top,
222
+ right,
223
+ bottom,
224
+ });
225
+
226
+ const head = table.start + cellsInFirstRow[0];
227
+ const anchor = table.start + (cellsInLastRow?.at(-1) ?? 0);
228
+ const $head = tr.doc.resolve(head);
229
+ const $anchor = tr.doc.resolve(anchor);
230
+
231
+ return tr.setSelection(new CellSelection($anchor, $head));
232
+ }
233
+ }
234
+ return tr;
235
+ };
236
+
237
+ export const selectColumn = select("column");
238
+
239
+ export const selectRow = select("row");
240
+
241
+ export const selectTable = (tr: Transaction) => {
242
+ const table = findTable(tr.selection);
243
+
244
+ if (table) {
245
+ const { map } = TableMap.get(table.node);
246
+
247
+ if (map?.length) {
248
+ const head = table.start + map[0];
249
+ const anchor = table.start + (map?.at(-1) ?? 0);
250
+ const $head = tr.doc.resolve(head);
251
+ const $anchor = tr.doc.resolve(anchor);
252
+
253
+ return tr.setSelection(new CellSelection($anchor, $head));
254
+ }
255
+ }
256
+
257
+ return tr;
258
+ };
@@ -0,0 +1 @@
1
+ export { TaskItem } from "./task-item";
@@ -0,0 +1,225 @@
1
+ import { type KeyboardShortcutCommand, mergeAttributes, Node, wrappingInputRule } from "@tiptap/core";
2
+ import type { Node as ProseMirrorNode } from "@tiptap/pm/model";
3
+
4
+ export interface TaskItemOptions {
5
+ /**
6
+ * A callback function that is called when the checkbox is clicked while the editor is in readonly mode.
7
+ * @param node The prosemirror node of the task item
8
+ * @param checked The new checked state
9
+ * @returns boolean
10
+ */
11
+ onReadOnlyChecked?: (node: ProseMirrorNode, checked: boolean) => boolean;
12
+
13
+ /**
14
+ * Controls whether the task items can be nested or not.
15
+ * @default false
16
+ * @example true
17
+ */
18
+ nested: boolean;
19
+
20
+ /**
21
+ * HTML attributes to add to the task item element.
22
+ * @default {}
23
+ * @example { class: 'foo' }
24
+ */
25
+
26
+ // biome-ignore lint/suspicious/noExplicitAny: <HTMLAttributes>
27
+ HTMLAttributes: Record<string, any>;
28
+
29
+ /**
30
+ * The node type for taskList nodes
31
+ * @default 'taskList'
32
+ * @example 'myCustomTaskList'
33
+ */
34
+ taskListTypeName: string;
35
+ }
36
+
37
+ /**
38
+ * Matches a task item to a - [ ] on input.
39
+ */
40
+ export const inputRegex = /^\s*(\[([( |x])?\])\s$/;
41
+
42
+ /**
43
+ * This extension allows you to create task items.
44
+ * @see https://www.tiptap.dev/api/nodes/task-item
45
+ */
46
+ export const TaskItem = Node.create<TaskItemOptions>({
47
+ name: "taskItem",
48
+
49
+ addOptions() {
50
+ return {
51
+ nested: false,
52
+ HTMLAttributes: {},
53
+ taskListTypeName: "taskList",
54
+ };
55
+ },
56
+
57
+ content() {
58
+ return this.options.nested ? "paragraph block*" : "paragraph+";
59
+ },
60
+
61
+ defining: true,
62
+
63
+ addAttributes() {
64
+ return {
65
+ checked: {
66
+ default: false,
67
+ keepOnSplit: false,
68
+ parseHTML: (element) => {
69
+ const dataChecked = element.getAttribute("data-checked");
70
+
71
+ return dataChecked === "" || dataChecked === "true";
72
+ },
73
+ renderHTML: (attributes) => ({
74
+ "data-checked": attributes.checked,
75
+ }),
76
+ },
77
+ };
78
+ },
79
+
80
+ parseHTML() {
81
+ return [
82
+ {
83
+ tag: `li[data-type="${this.name}"]`,
84
+ priority: 51,
85
+ },
86
+ ];
87
+ },
88
+
89
+ renderHTML({ node, HTMLAttributes }) {
90
+ return [
91
+ "li",
92
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
93
+ "data-type": this.name,
94
+ }),
95
+ [
96
+ "label",
97
+ [
98
+ "input",
99
+ {
100
+ type: "checkbox",
101
+ checked: node.attrs.checked ? "checked" : null,
102
+ },
103
+ ],
104
+ ["span"],
105
+ ],
106
+ ["div", 0],
107
+ ];
108
+ },
109
+
110
+ addKeyboardShortcuts() {
111
+ const shortcuts: {
112
+ [key: string]: KeyboardShortcutCommand;
113
+ } = {
114
+ Enter: () => this.editor.commands.splitListItem(this.name),
115
+ "Shift-Tab": () => this.editor.commands.liftListItem(this.name),
116
+ };
117
+
118
+ if (!this.options.nested) {
119
+ return shortcuts;
120
+ }
121
+
122
+ return {
123
+ ...shortcuts,
124
+ Tab: () => this.editor.commands.sinkListItem(this.name),
125
+ };
126
+ },
127
+
128
+ addNodeView() {
129
+ return ({ node, HTMLAttributes, getPos, editor }) => {
130
+ const listItem = document.createElement("li");
131
+ const checkboxWrapper = document.createElement("label");
132
+ const checkboxStyler = document.createElement("span");
133
+ const checkbox = document.createElement("input");
134
+ const content = document.createElement("div");
135
+
136
+ checkboxWrapper.contentEditable = "false";
137
+ checkbox.type = "checkbox";
138
+ checkbox.addEventListener("mousedown", (event) => event.preventDefault());
139
+ checkbox.addEventListener("change", (event) => {
140
+ // if the editor isn't editable and we don't have a handler for
141
+ // readonly checks we have to undo the latest change
142
+ if (!(editor.isEditable || this.options.onReadOnlyChecked)) {
143
+ checkbox.checked = !checkbox.checked;
144
+
145
+ return;
146
+ }
147
+
148
+ // biome-ignore lint/suspicious/noExplicitAny: <checked>
149
+ const { checked } = event.target as any;
150
+
151
+ if (editor.isEditable && typeof getPos === "function") {
152
+ editor
153
+ .chain()
154
+ .focus(undefined, { scrollIntoView: false })
155
+ .command(({ tr }) => {
156
+ const position = getPos();
157
+
158
+ if (typeof position !== "number") {
159
+ return false;
160
+ }
161
+ const currentNode = tr.doc.nodeAt(position);
162
+
163
+ tr.setNodeMarkup(position, undefined, {
164
+ ...currentNode?.attrs,
165
+ checked,
166
+ });
167
+
168
+ return true;
169
+ })
170
+ .run();
171
+ }
172
+ if (!editor.isEditable && this.options.onReadOnlyChecked && !this.options.onReadOnlyChecked(node, checked)) {
173
+ checkbox.checked = !checkbox.checked;
174
+ }
175
+ });
176
+
177
+ for (const [key, value] of Object.entries(this.options.HTMLAttributes)) {
178
+ listItem.setAttribute(key, value);
179
+ }
180
+
181
+ listItem.dataset.checked = node.attrs.checked;
182
+ if (node.attrs.checked) {
183
+ checkbox.setAttribute("checked", "checked");
184
+ }
185
+
186
+ checkboxWrapper.append(checkbox, checkboxStyler);
187
+ listItem.append(checkboxWrapper, content);
188
+
189
+ for (const [key, value] of Object.entries(HTMLAttributes)) {
190
+ listItem.setAttribute(key, value);
191
+ }
192
+
193
+ return {
194
+ dom: listItem,
195
+ contentDOM: content,
196
+ update: (updatedNode) => {
197
+ if (updatedNode.type !== this.type) {
198
+ return false;
199
+ }
200
+
201
+ listItem.dataset.checked = updatedNode.attrs.checked;
202
+ if (updatedNode.attrs.checked) {
203
+ checkbox.setAttribute("checked", "checked");
204
+ } else {
205
+ checkbox.removeAttribute("checked");
206
+ }
207
+
208
+ return true;
209
+ },
210
+ };
211
+ };
212
+ },
213
+
214
+ addInputRules() {
215
+ return [
216
+ wrappingInputRule({
217
+ find: inputRegex,
218
+ type: this.type,
219
+ getAttributes: (match) => ({
220
+ checked: match.at(-1) === "x",
221
+ }),
222
+ }),
223
+ ];
224
+ },
225
+ });
@@ -0,0 +1 @@
1
+ export { TaskList } from "./task-list";
@@ -0,0 +1,81 @@
1
+ import { mergeAttributes, Node } from "@tiptap/core";
2
+
3
+ export interface TaskListOptions {
4
+ /**
5
+ * The node type name for a task item.
6
+ * @default 'taskItem'
7
+ * @example 'myCustomTaskItem'
8
+ */
9
+ itemTypeName: string;
10
+
11
+ /**
12
+ * The HTML attributes for a task list node.
13
+ * @default {}
14
+ * @example { class: 'foo' }
15
+ */
16
+
17
+ // biome-ignore lint/suspicious/noExplicitAny: <HTMLAttributes>
18
+ HTMLAttributes: Record<string, any>;
19
+ }
20
+
21
+ declare module "@tiptap/core" {
22
+ interface Commands<ReturnType> {
23
+ taskList: {
24
+ /**
25
+ * Toggle a task list
26
+ * @example editor.commands.toggleTaskList()
27
+ */
28
+ toggleTaskList: () => ReturnType;
29
+ };
30
+ }
31
+ }
32
+
33
+ /**
34
+ * This extension allows you to create task lists.
35
+ * @see https://www.tiptap.dev/api/nodes/task-list
36
+ */
37
+ export const TaskList = Node.create<TaskListOptions>({
38
+ name: "taskList",
39
+
40
+ addOptions() {
41
+ return {
42
+ itemTypeName: "taskItem",
43
+ HTMLAttributes: {},
44
+ };
45
+ },
46
+
47
+ group: "block list",
48
+
49
+ content() {
50
+ return `${this.options.itemTypeName}+`;
51
+ },
52
+
53
+ parseHTML() {
54
+ return [
55
+ {
56
+ tag: `ul[data-type="${this.name}"]`,
57
+ priority: 51,
58
+ },
59
+ ];
60
+ },
61
+
62
+ renderHTML({ HTMLAttributes }) {
63
+ return ["ul", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { "data-type": this.name }), 0];
64
+ },
65
+
66
+ addCommands() {
67
+ return {
68
+ toggleTaskList:
69
+ () =>
70
+ ({ commands }) => {
71
+ return commands.toggleList(this.name, this.options.itemTypeName);
72
+ },
73
+ };
74
+ },
75
+
76
+ addKeyboardShortcuts() {
77
+ return {
78
+ "Mod-Shift-9": () => this.editor.commands.toggleTaskList(),
79
+ };
80
+ },
81
+ });
@@ -0,0 +1 @@
1
+ export { TrailingNode } from "./trailing-node";