Skip to content

Commit d910c3f

Browse files
authored
Merge pull request #8568 from nextcloud/fix/task_item_click
fix(TaskItem): use div instead of label element as task item wrapper
2 parents 87964e4 + 6a2a606 commit d910c3f

7 files changed

Lines changed: 43 additions & 31 deletions

File tree

cypress/e2e/nodes/ListItem.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { ListItem, OrderedList } from '@tiptap/extension-list'
77
/* eslint-enable import/no-named-as-default */
88
import Markdown from './../../../src/extensions/Markdown.js'
99
import BulletList from './../../../src/nodes/BulletList.ts'
10-
import TaskItem from './../../../src/nodes/TaskItem.js'
10+
import TaskItem from './../../../src/nodes/TaskItem.ts'
1111
import TaskList from './../../../src/nodes/TaskList.ts'
1212
import { createCustomEditor } from './../../support/components.js'
1313
import { expectMarkdown, loadMarkdown, runCommands } from './helpers.js'

src/css/prosemirror.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,11 @@ div.ProseMirror {
7575
background-color: var(--color-primary-element);
7676
border-color: var(--color-primary-element);
7777
}
78-
> label > p {
78+
> .task-item-content > p {
7979
color: var(--color-text-maxcontrast);
8080
}
8181
}
82-
label {
82+
.task-item-content {
8383
display: block;
8484
flex-grow: 1;
8585
max-width: calc(100% - 28px);

src/extensions/RichText.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import OrderedList from './../nodes/OrderedList.ts'
4848
import Paragraph from './../nodes/Paragraph.js'
4949
import Preview from './../nodes/Preview.js'
5050
import Table from './../nodes/Table.js'
51-
import TaskItem from './../nodes/TaskItem.js'
51+
import TaskItem from './../nodes/TaskItem.ts'
5252
import TaskList from './../nodes/TaskList.ts'
5353
import TrailingNode from './../nodes/TrailingNode.js'
5454
import Emoji from './Emoji.js'
Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
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+
69
import { mergeAttributes, wrappingInputRule } from '@tiptap/core'
710
import { TaskItem as TipTapTaskItem } from '@tiptap/extension-list'
811
import { 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

src/tests/extensions/Markdown.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { Italic, Link, Strong, Underline } from '../../marks/index.js'
1414
import Image from '../../nodes/Image.js'
1515
import OrderedList from '../../nodes/OrderedList.ts'
1616
import Table from '../../nodes/Table.js'
17-
import TaskItem from '../../nodes/TaskItem.js'
17+
import TaskItem from '../../nodes/TaskItem.ts'
1818
import TaskList from '../../nodes/TaskList.ts'
1919
import createCustomEditor from '../testHelpers/createCustomEditor.ts'
2020
import ImageInline from './../../nodes/ImageInline.js'

src/tests/nodes/TaskItem.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
markdownThroughEditorHtml,
1111
} from '../testHelpers/markdown.js'
1212
import Markdown from './../../extensions/Markdown.js'
13-
import TaskItem from './../../nodes/TaskItem.js'
13+
import TaskItem from './../../nodes/TaskItem.ts'
1414
import TaskList from './../../nodes/TaskList.ts'
1515

1616
describe('TaskItem extension', () => {

src/tests/tiptap.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe('TipTap', () => {
3131
it('render taskList', () => {
3232
const markdown = '* [ ] item 1\n'
3333
expect(renderedHTML(markdown)).toEqual(
34-
'<ul class="contains-task-list"><li dir="ltr" data-checked="false" class="task-list-item checkbox-item"><input type="checkbox" class="" contenteditable="false"><label><p dir="ltr">item 1</p></label></li></ul>',
34+
'<ul class="contains-task-list"><li dir="ltr" data-checked="false" class="task-list-item checkbox-item"><input type="checkbox" class="" contenteditable="false"><div class="task-item-content"><p dir="ltr">item 1</p></div></li></ul>',
3535
)
3636
})
3737
})

0 commit comments

Comments
 (0)