33 * SPDX-License-Identifier: AGPL-3.0-or-later
44 */
55
6+ import type { Node } from '@tiptap/pm/model'
7+ import type { MarkdownSerializerState } from 'prosemirror-markdown'
8+
69import { mergeAttributes , wrappingInputRule } from '@tiptap/core'
710import { TaskItem as TipTapTaskItem } from '@tiptap/extension-list'
811import { Plugin } from '@tiptap/pm/state'
@@ -13,6 +16,7 @@ const TaskItem = TipTapTaskItem.extend({
1316 return {
1417 nested : true ,
1518 HTMLAttributes : { } ,
19+ taskListTypeName : 'taskList' ,
1620 }
1721 } ,
1822
@@ -21,55 +25,59 @@ const TaskItem = TipTapTaskItem.extend({
2125 content : 'paragraph block*' ,
2226
2327 addAttributes ( ) {
24- const adjust = { ...this . parent ( ) }
28+ const adjust = { ...this . parent ?. ( ) }
2529 adjust . checked . parseHTML = ( el ) => {
26- return el . querySelector ( 'input[type=checkbox]' ) ?. checked
30+ return ( el . querySelector ( 'input[type=checkbox]' ) as HTMLInputElement )
31+ ?. checked
2732 }
2833 return adjust
2934 } ,
3035
31- parseHTML : [
32- {
33- priority : 101 ,
34- tag : 'li' ,
35- getAttrs : ( el ) => {
36- const checkbox = el . querySelector ( 'input[type=checkbox]' )
37- return checkbox
36+ parseHTML ( ) {
37+ return [
38+ {
39+ priority : 101 ,
40+ tag : 'li' ,
41+ getAttrs : ( el ) => {
42+ const checkbox = el . querySelector ( 'input[type=checkbox]' )
43+ return checkbox
44+ } ,
45+ context : 'taskList/' ,
3846 } ,
39- context : 'taskList/' ,
40- } ,
41- ] ,
47+ ]
48+ } ,
4249
4350 renderHTML ( { node, HTMLAttributes } ) {
44- const listAttributes = { class : 'task-list-item checkbox-item' }
51+ const listAttributes = {
52+ class : `task-list-item checkbox-item${ node . attrs . checked ? ' checked' : '' } ` ,
53+ } as const
4554 const checkboxAttributes = {
4655 type : 'checkbox' ,
4756 class : '' ,
4857 contenteditable : false ,
49- }
50- if ( node . attrs . checked ) {
51- checkboxAttributes . checked = true
52- listAttributes . class += ' checked'
53- }
58+ ...( node . attrs . checked ? { checked : true } : { } ) ,
59+ } as const
60+
5461 return [
5562 'li' ,
5663 mergeAttributes ( HTMLAttributes , listAttributes ) ,
5764 [ 'input' , checkboxAttributes ] ,
58- [ 'label' , 0 ] ,
65+ [ 'div' , { class : 'task-item-content' } , 0 ] ,
5966 ]
6067 } ,
6168
6269 // overwrite the parent node view so renderHTML gets used
63- addNodeView : false ,
70+ addNodeView : ( ) => null ,
6471
65- toMarkdown : ( state , node ) => {
72+ // @ts -expect-error - toMarkdown is a custom field not part of the official Tiptap API
73+ toMarkdown : ( state : MarkdownSerializerState , node : Node ) => {
6674 state . write ( `[${ node . attrs . checked ? 'x' : ' ' } ] ` )
6775 state . renderContent ( node )
6876 } ,
6977
7078 addInputRules ( ) {
7179 return [
72- ...this . parent ( ) ,
80+ ...( this . parent ?. ( ) || [ ] ) ,
7381 wrappingInputRule ( {
7482 find : / ^ \s * ( [ - + * ] ) \s ( \[ ( x | X | \s ) ? \] ) \s $ / ,
7583 type : this . type ,
@@ -92,18 +100,22 @@ const TaskItem = TipTapTaskItem.extend({
92100 left : event . clientX ,
93101 top : event . clientY ,
94102 } )
103+ if ( ! coordinates ) {
104+ return
105+ }
95106 const position = state . doc . resolve ( coordinates . pos )
96107 const parentList = findParentNodeClosestToPos (
97108 position ,
98- function ( node ) {
109+ function ( node : Node ) {
99110 return (
100111 node . type === schema . nodes . taskItem
101112 || node . type === schema . nodes . listItem
102113 )
103114 } ,
104115 )
105116 const isListClicked =
106- event . target . tagName . toLowerCase ( ) === 'li'
117+ event . target instanceof Element
118+ && event . target . tagName . toLowerCase ( ) === 'li'
107119 if (
108120 ! isListClicked
109121 || ! parentList
0 commit comments