@thomasjahoda-forks/tiptap-extension-list 3.0.7

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 (76) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +18 -0
  3. package/dist/bullet-list/index.cjs +93 -0
  4. package/dist/bullet-list/index.cjs.map +1 -0
  5. package/dist/bullet-list/index.d.cts +51 -0
  6. package/dist/bullet-list/index.d.ts +51 -0
  7. package/dist/bullet-list/index.js +65 -0
  8. package/dist/bullet-list/index.js.map +1 -0
  9. package/dist/index.cjs +736 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.d.cts +300 -0
  12. package/dist/index.d.ts +300 -0
  13. package/dist/index.js +705 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/item/index.cjs +62 -0
  16. package/dist/item/index.cjs.map +1 -0
  17. package/dist/item/index.d.cts +29 -0
  18. package/dist/item/index.d.ts +29 -0
  19. package/dist/item/index.js +35 -0
  20. package/dist/item/index.js.map +1 -0
  21. package/dist/keymap/index.cjs +308 -0
  22. package/dist/keymap/index.cjs.map +1 -0
  23. package/dist/keymap/index.d.cts +63 -0
  24. package/dist/keymap/index.d.ts +63 -0
  25. package/dist/keymap/index.js +286 -0
  26. package/dist/keymap/index.js.map +1 -0
  27. package/dist/kit/index.cjs +714 -0
  28. package/dist/kit/index.cjs.map +1 -0
  29. package/dist/kit/index.d.cts +212 -0
  30. package/dist/kit/index.d.ts +212 -0
  31. package/dist/kit/index.js +695 -0
  32. package/dist/kit/index.js.map +1 -0
  33. package/dist/ordered-list/index.cjs +113 -0
  34. package/dist/ordered-list/index.cjs.map +1 -0
  35. package/dist/ordered-list/index.d.cts +52 -0
  36. package/dist/ordered-list/index.d.ts +52 -0
  37. package/dist/ordered-list/index.js +85 -0
  38. package/dist/ordered-list/index.js.map +1 -0
  39. package/dist/task-item/index.cjs +199 -0
  40. package/dist/task-item/index.cjs.map +1 -0
  41. package/dist/task-item/index.d.cts +53 -0
  42. package/dist/task-item/index.d.ts +53 -0
  43. package/dist/task-item/index.js +171 -0
  44. package/dist/task-item/index.js.map +1 -0
  45. package/dist/task-list/index.cjs +69 -0
  46. package/dist/task-list/index.cjs.map +1 -0
  47. package/dist/task-list/index.d.cts +34 -0
  48. package/dist/task-list/index.d.ts +34 -0
  49. package/dist/task-list/index.js +42 -0
  50. package/dist/task-list/index.js.map +1 -0
  51. package/package.json +106 -0
  52. package/src/bullet-list/bullet-list.ts +126 -0
  53. package/src/bullet-list/index.ts +1 -0
  54. package/src/index.ts +7 -0
  55. package/src/item/index.ts +1 -0
  56. package/src/item/list-item.ts +64 -0
  57. package/src/keymap/index.ts +2 -0
  58. package/src/keymap/list-keymap.ts +106 -0
  59. package/src/keymap/listHelpers/findListItemPos.ts +30 -0
  60. package/src/keymap/listHelpers/getNextListDepth.ts +16 -0
  61. package/src/keymap/listHelpers/handleBackspace.ts +85 -0
  62. package/src/keymap/listHelpers/handleDelete.ts +44 -0
  63. package/src/keymap/listHelpers/hasListBefore.ts +15 -0
  64. package/src/keymap/listHelpers/hasListItemAfter.ts +17 -0
  65. package/src/keymap/listHelpers/hasListItemBefore.ts +17 -0
  66. package/src/keymap/listHelpers/index.ts +10 -0
  67. package/src/keymap/listHelpers/listItemHasSubList.ts +21 -0
  68. package/src/keymap/listHelpers/nextListIsDeeper.ts +19 -0
  69. package/src/keymap/listHelpers/nextListIsHigher.ts +19 -0
  70. package/src/kit/index.ts +81 -0
  71. package/src/ordered-list/index.ts +1 -0
  72. package/src/ordered-list/ordered-list.ts +151 -0
  73. package/src/task-item/index.ts +1 -0
  74. package/src/task-item/task-item.ts +266 -0
  75. package/src/task-list/index.ts +1 -0
  76. package/src/task-list/task-list.ts +79 -0
@@ -0,0 +1,81 @@
1
+ import { Extension } from '@tiptap/core'
2
+
3
+ import type { BulletListOptions } from '../bullet-list/index.js'
4
+ import { BulletList } from '../bullet-list/index.js'
5
+ import type { ListItemOptions } from '../item/index.js'
6
+ import { ListItem } from '../item/index.js'
7
+ import type { ListKeymapOptions } from '../keymap/index.js'
8
+ import { ListKeymap } from '../keymap/index.js'
9
+ import type { OrderedListOptions } from '../ordered-list/index.js'
10
+ import { OrderedList } from '../ordered-list/index.js'
11
+ import type { TaskItemOptions } from '../task-item/index.js'
12
+ import { TaskItem } from '../task-item/index.js'
13
+ import type { TaskListOptions } from '../task-list/index.js'
14
+ import { TaskList } from '../task-list/index.js'
15
+
16
+ export interface ListKitOptions {
17
+ /**
18
+ * If set to false, the bulletList extension will not be registered
19
+ * @example table: false
20
+ */
21
+ bulletList: Partial<BulletListOptions> | false
22
+ /**
23
+ * If set to false, the listItem extension will not be registered
24
+ */
25
+ listItem: Partial<ListItemOptions> | false
26
+ /**
27
+ * If set to false, the listKeymap extension will not be registered
28
+ */
29
+ listKeymap: Partial<ListKeymapOptions> | false
30
+ /**
31
+ * If set to false, the orderedList extension will not be registered
32
+ */
33
+ orderedList: Partial<OrderedListOptions> | false
34
+ /**
35
+ * If set to false, the taskItem extension will not be registered
36
+ */
37
+ taskItem: Partial<TaskItemOptions> | false
38
+ /**
39
+ * If set to false, the taskList extension will not be registered
40
+ */
41
+ taskList: Partial<TaskListOptions> | false
42
+ }
43
+
44
+ /**
45
+ * The table kit is a collection of table editor extensions.
46
+ *
47
+ * It’s a good starting point for building your own table in Tiptap.
48
+ */
49
+ export const ListKit = Extension.create<ListKitOptions>({
50
+ name: 'listKit',
51
+
52
+ addExtensions() {
53
+ const extensions = []
54
+
55
+ if (this.options.bulletList !== false) {
56
+ extensions.push(BulletList.configure(this.options.bulletList))
57
+ }
58
+
59
+ if (this.options.listItem !== false) {
60
+ extensions.push(ListItem.configure(this.options.listItem))
61
+ }
62
+
63
+ if (this.options.listKeymap !== false) {
64
+ extensions.push(ListKeymap.configure(this.options.listKeymap))
65
+ }
66
+
67
+ if (this.options.orderedList !== false) {
68
+ extensions.push(OrderedList.configure(this.options.orderedList))
69
+ }
70
+
71
+ if (this.options.taskItem !== false) {
72
+ extensions.push(TaskItem.configure(this.options.taskItem))
73
+ }
74
+
75
+ if (this.options.taskList !== false) {
76
+ extensions.push(TaskList.configure(this.options.taskList))
77
+ }
78
+
79
+ return extensions
80
+ },
81
+ })
@@ -0,0 +1 @@
1
+ export * from './ordered-list.js'
@@ -0,0 +1,151 @@
1
+ import { mergeAttributes, Node, wrappingInputRule } from '@tiptap/core'
2
+
3
+ const ListItemName = 'listItem'
4
+ const TextStyleName = 'textStyle'
5
+
6
+ export interface OrderedListOptions {
7
+ /**
8
+ * The node type name for list items.
9
+ * @default 'listItem'
10
+ * @example 'myListItem'
11
+ */
12
+ itemTypeName: string
13
+
14
+ /**
15
+ * The HTML attributes for an ordered list node.
16
+ * @default {}
17
+ * @example { class: 'foo' }
18
+ */
19
+ HTMLAttributes: Record<string, any>
20
+
21
+ /**
22
+ * Keep the marks when splitting a list item.
23
+ * @default false
24
+ * @example true
25
+ */
26
+ keepMarks: boolean
27
+
28
+ /**
29
+ * Keep the attributes when splitting a list item.
30
+ * @default false
31
+ * @example true
32
+ */
33
+ keepAttributes: boolean
34
+ }
35
+
36
+ declare module '@tiptap/core' {
37
+ interface Commands<ReturnType> {
38
+ orderedList: {
39
+ /**
40
+ * Toggle an ordered list
41
+ * @example editor.commands.toggleOrderedList()
42
+ */
43
+ toggleOrderedList: () => ReturnType
44
+ }
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Matches an ordered list to a 1. on input (or any number followed by a dot).
50
+ */
51
+ export const orderedListInputRegex = /^(\d+)\.\s$/
52
+
53
+ /**
54
+ * This extension allows you to create ordered lists.
55
+ * This requires the ListItem extension
56
+ * @see https://www.tiptap.dev/api/nodes/ordered-list
57
+ * @see https://www.tiptap.dev/api/nodes/list-item
58
+ */
59
+ export const OrderedList = Node.create<OrderedListOptions>({
60
+ name: 'orderedList',
61
+
62
+ addOptions() {
63
+ return {
64
+ itemTypeName: 'listItem',
65
+ HTMLAttributes: {},
66
+ keepMarks: false,
67
+ keepAttributes: false,
68
+ }
69
+ },
70
+
71
+ group: 'block list',
72
+
73
+ content() {
74
+ return `${this.options.itemTypeName}+`
75
+ },
76
+
77
+ addAttributes() {
78
+ return {
79
+ start: {
80
+ default: 1,
81
+ parseHTML: element => {
82
+ return element.hasAttribute('start') ? parseInt(element.getAttribute('start') || '', 10) : 1
83
+ },
84
+ },
85
+ type: {
86
+ default: null,
87
+ parseHTML: element => element.getAttribute('type'),
88
+ },
89
+ }
90
+ },
91
+
92
+ parseHTML() {
93
+ return [
94
+ {
95
+ tag: 'ol',
96
+ },
97
+ ]
98
+ },
99
+
100
+ renderHTML({ HTMLAttributes }) {
101
+ const { start, ...attributesWithoutStart } = HTMLAttributes
102
+
103
+ return start === 1
104
+ ? ['ol', mergeAttributes(this.options.HTMLAttributes, attributesWithoutStart), 0]
105
+ : ['ol', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
106
+ },
107
+
108
+ addCommands() {
109
+ return {
110
+ toggleOrderedList:
111
+ () =>
112
+ ({ commands, chain }) => {
113
+ if (this.options.keepAttributes) {
114
+ return chain()
115
+ .toggleList(this.name, this.options.itemTypeName, this.options.keepMarks)
116
+ .updateAttributes(ListItemName, this.editor.getAttributes(TextStyleName))
117
+ .run()
118
+ }
119
+ return commands.toggleList(this.name, this.options.itemTypeName, this.options.keepMarks)
120
+ },
121
+ }
122
+ },
123
+
124
+ addKeyboardShortcuts() {
125
+ return {
126
+ 'Mod-Shift-7': () => this.editor.commands.toggleOrderedList(),
127
+ }
128
+ },
129
+
130
+ addInputRules() {
131
+ let inputRule = wrappingInputRule({
132
+ find: orderedListInputRegex,
133
+ type: this.type,
134
+ getAttributes: match => ({ start: +match[1] }),
135
+ joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1],
136
+ })
137
+
138
+ if (this.options.keepMarks || this.options.keepAttributes) {
139
+ inputRule = wrappingInputRule({
140
+ find: orderedListInputRegex,
141
+ type: this.type,
142
+ keepMarks: this.options.keepMarks,
143
+ keepAttributes: this.options.keepAttributes,
144
+ getAttributes: match => ({ start: +match[1], ...this.editor.getAttributes(TextStyleName) }),
145
+ joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1],
146
+ editor: this.editor,
147
+ })
148
+ }
149
+ return [inputRule]
150
+ },
151
+ })
@@ -0,0 +1 @@
1
+ export * from './task-item.js'
@@ -0,0 +1,266 @@
1
+ import type { KeyboardShortcutCommand } from '@tiptap/core'
2
+ import { mergeAttributes, Node, wrappingInputRule } from '@tiptap/core'
3
+ import type { Node as ProseMirrorNode } from '@tiptap/pm/model'
4
+
5
+ export interface TaskItemOptions {
6
+ /**
7
+ * A callback function that is called when the checkbox is clicked while the editor is in readonly mode.
8
+ * @param node The prosemirror node of the task item
9
+ * @param checked The new checked state
10
+ * @returns boolean whether the change to the checkbox state should be accepted or reverted - or whether the editor content should be updated
11
+ */
12
+ onReadOnlyChecked?: (node: ProseMirrorNode, checked: boolean) => boolean | 'updateEditorContent'
13
+
14
+ /**
15
+ * Controls whether the task items can be nested or not.
16
+ * @default false
17
+ * @example true
18
+ */
19
+ nested: boolean
20
+
21
+ /**
22
+ * HTML attributes to add to the task item element.
23
+ * @default {}
24
+ * @example { class: 'foo' }
25
+ */
26
+ HTMLAttributes: Record<string, any>
27
+
28
+ /**
29
+ * The node type for taskList nodes
30
+ * @default 'taskList'
31
+ * @example 'myCustomTaskList'
32
+ */
33
+ taskListTypeName: string
34
+
35
+ /**
36
+ * Accessibility options for the task item.
37
+ * @default {}
38
+ * @example
39
+ * ```js
40
+ * {
41
+ * checkboxLabel: (node) => `Task item: ${node.textContent || 'empty task item'}`
42
+ * }
43
+ */
44
+ a11y?: {
45
+ checkboxLabel?: (node: ProseMirrorNode, checked: boolean) => string
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Matches a task item to a - [ ] on input.
51
+ */
52
+ export const inputRegex = /^\s*(\[([( |x])?\])\s$/
53
+
54
+ /**
55
+ * This extension allows you to create task items.
56
+ * @see https://www.tiptap.dev/api/nodes/task-item
57
+ */
58
+ export const TaskItem = Node.create<TaskItemOptions>({
59
+ name: 'taskItem',
60
+
61
+ addOptions() {
62
+ return {
63
+ nested: false,
64
+ HTMLAttributes: {},
65
+ taskListTypeName: 'taskList',
66
+ a11y: undefined,
67
+ }
68
+ },
69
+
70
+ content() {
71
+ return this.options.nested ? 'paragraph block*' : 'paragraph+'
72
+ },
73
+
74
+ defining: true,
75
+
76
+ addAttributes() {
77
+ return {
78
+ checked: {
79
+ default: false,
80
+ keepOnSplit: false,
81
+ parseHTML: element => {
82
+ const dataChecked = element.getAttribute('data-checked')
83
+
84
+ return dataChecked === '' || dataChecked === 'true'
85
+ },
86
+ renderHTML: attributes => ({
87
+ 'data-checked': attributes.checked,
88
+ }),
89
+ },
90
+ }
91
+ },
92
+
93
+ parseHTML() {
94
+ return [
95
+ {
96
+ tag: `li[data-type="${this.name}"]`,
97
+ priority: 51,
98
+ },
99
+ ]
100
+ },
101
+
102
+ renderHTML({ node, HTMLAttributes }) {
103
+ return [
104
+ 'li',
105
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
106
+ 'data-type': this.name,
107
+ }),
108
+ [
109
+ 'label',
110
+ [
111
+ 'input',
112
+ {
113
+ type: 'checkbox',
114
+ checked: node.attrs.checked ? 'checked' : null,
115
+ },
116
+ ],
117
+ ['span'],
118
+ ],
119
+ ['div', 0],
120
+ ]
121
+ },
122
+
123
+ addKeyboardShortcuts() {
124
+ const shortcuts: {
125
+ [key: string]: KeyboardShortcutCommand
126
+ } = {
127
+ Enter: () => this.editor.commands.splitListItem(this.name),
128
+ 'Shift-Tab': () => this.editor.commands.liftListItem(this.name),
129
+ }
130
+
131
+ if (!this.options.nested) {
132
+ return shortcuts
133
+ }
134
+
135
+ return {
136
+ ...shortcuts,
137
+ Tab: () => this.editor.commands.sinkListItem(this.name),
138
+ }
139
+ },
140
+
141
+ addNodeView() {
142
+ return ({ node, HTMLAttributes, getPos, editor }) => {
143
+ const listItem = document.createElement('li')
144
+ const checkboxWrapper = document.createElement('label')
145
+ const checkboxStyler = document.createElement('span')
146
+ const checkbox = document.createElement('input')
147
+ const content = document.createElement('div')
148
+
149
+ const updateA11Y = () => {
150
+ checkbox.ariaLabel =
151
+ this.options.a11y?.checkboxLabel?.(node, checkbox.checked) ||
152
+ `Task item checkbox for ${node.textContent || 'empty task item'}`
153
+ }
154
+
155
+ updateA11Y()
156
+
157
+ checkboxWrapper.contentEditable = 'false'
158
+ checkbox.type = 'checkbox'
159
+ checkbox.addEventListener('mousedown', event => event.preventDefault())
160
+ checkbox.addEventListener('change', event => {
161
+ // if the editor isn’t editable and we don't have a handler for
162
+ // readonly checks we have to undo the latest change
163
+ if (!editor.isEditable && !this.options.onReadOnlyChecked) {
164
+ checkbox.checked = !checkbox.checked
165
+
166
+ return
167
+ }
168
+
169
+ const { checked } = event.target as any
170
+
171
+ if (editor.isEditable && typeof getPos === 'function') {
172
+ editor
173
+ .chain()
174
+ .focus(undefined, { scrollIntoView: false })
175
+ .command(({ tr }) => {
176
+ const position = getPos()
177
+
178
+ if (typeof position !== 'number') {
179
+ return false
180
+ }
181
+ const currentNode = tr.doc.nodeAt(position)
182
+
183
+ tr.setNodeMarkup(position, undefined, {
184
+ ...currentNode?.attrs,
185
+ checked,
186
+ })
187
+
188
+ return true
189
+ })
190
+ .run()
191
+ }
192
+ if (!editor.isEditable && this.options.onReadOnlyChecked) {
193
+ // Reset state if onReadOnlyChecked returns false
194
+ const result = this.options.onReadOnlyChecked(node, checked)
195
+ if (result === false) {
196
+ checkbox.checked = !checkbox.checked
197
+ } else if (result === true) {
198
+ // simply accept the change and do nothing
199
+ } else if (result === 'updateEditorContent') {
200
+ // update the editor content to reflect the change
201
+ editor
202
+ .chain()
203
+ .command(({ tr }) => {
204
+ const position = getPos()
205
+
206
+ if (typeof position !== 'number') {
207
+ return false
208
+ }
209
+ const currentNode = tr.doc.nodeAt(position)
210
+
211
+ tr.setNodeMarkup(position, undefined, {
212
+ ...currentNode?.attrs,
213
+ checked,
214
+ })
215
+
216
+ return true
217
+ })
218
+ .run()
219
+ }
220
+ }
221
+ })
222
+
223
+ Object.entries(this.options.HTMLAttributes).forEach(([key, value]) => {
224
+ listItem.setAttribute(key, value)
225
+ })
226
+
227
+ listItem.dataset.checked = node.attrs.checked
228
+ checkbox.checked = node.attrs.checked
229
+
230
+ checkboxWrapper.append(checkbox, checkboxStyler)
231
+ listItem.append(checkboxWrapper, content)
232
+
233
+ Object.entries(HTMLAttributes).forEach(([key, value]) => {
234
+ listItem.setAttribute(key, value)
235
+ })
236
+
237
+ return {
238
+ dom: listItem,
239
+ contentDOM: content,
240
+ update: updatedNode => {
241
+ if (updatedNode.type !== this.type) {
242
+ return false
243
+ }
244
+
245
+ listItem.dataset.checked = updatedNode.attrs.checked
246
+ checkbox.checked = updatedNode.attrs.checked
247
+ updateA11Y()
248
+
249
+ return true
250
+ },
251
+ }
252
+ }
253
+ },
254
+
255
+ addInputRules() {
256
+ return [
257
+ wrappingInputRule({
258
+ find: inputRegex,
259
+ type: this.type,
260
+ getAttributes: match => ({
261
+ checked: match[match.length - 1] === 'x',
262
+ }),
263
+ }),
264
+ ]
265
+ },
266
+ })
@@ -0,0 +1 @@
1
+ export * from './task-list.js'
@@ -0,0 +1,79 @@
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
+ HTMLAttributes: Record<string, any>
17
+ }
18
+
19
+ declare module '@tiptap/core' {
20
+ interface Commands<ReturnType> {
21
+ taskList: {
22
+ /**
23
+ * Toggle a task list
24
+ * @example editor.commands.toggleTaskList()
25
+ */
26
+ toggleTaskList: () => ReturnType
27
+ }
28
+ }
29
+ }
30
+
31
+ /**
32
+ * This extension allows you to create task lists.
33
+ * @see https://www.tiptap.dev/api/nodes/task-list
34
+ */
35
+ export const TaskList = Node.create<TaskListOptions>({
36
+ name: 'taskList',
37
+
38
+ addOptions() {
39
+ return {
40
+ itemTypeName: 'taskItem',
41
+ HTMLAttributes: {},
42
+ }
43
+ },
44
+
45
+ group: 'block list',
46
+
47
+ content() {
48
+ return `${this.options.itemTypeName}+`
49
+ },
50
+
51
+ parseHTML() {
52
+ return [
53
+ {
54
+ tag: `ul[data-type="${this.name}"]`,
55
+ priority: 51,
56
+ },
57
+ ]
58
+ },
59
+
60
+ renderHTML({ HTMLAttributes }) {
61
+ return ['ul', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { 'data-type': this.name }), 0]
62
+ },
63
+
64
+ addCommands() {
65
+ return {
66
+ toggleTaskList:
67
+ () =>
68
+ ({ commands }) => {
69
+ return commands.toggleList(this.name, this.options.itemTypeName)
70
+ },
71
+ }
72
+ },
73
+
74
+ addKeyboardShortcuts() {
75
+ return {
76
+ 'Mod-Shift-9': () => this.editor.commands.toggleTaskList(),
77
+ }
78
+ },
79
+ })