tiptap/packages/extension-task-item/src/task-item.ts

161 lines
3.6 KiB
TypeScript
Raw Normal View History

import { Node, mergeAttributes } from '@tiptap/core'
import { wrappingInputRule } from 'prosemirror-inputrules'
export interface TaskItemOptions {
nested: boolean,
2021-04-21 15:43:31 +08:00
HTMLAttributes: Record<string, any>,
}
2020-11-13 23:44:22 +08:00
export const inputRegex = /^\s*(\[([ |x])\])\s$/
2021-02-11 01:25:08 +08:00
export const TaskItem = Node.create<TaskItemOptions>({
2020-11-04 03:57:09 +08:00
name: 'taskItem',
2021-02-11 01:25:08 +08:00
defaultOptions: {
2020-11-13 23:44:22 +08:00
nested: false,
HTMLAttributes: {},
},
content() {
2021-01-30 04:45:51 +08:00
return this.options.nested ? 'paragraph block*' : 'paragraph+'
},
defining: true,
addAttributes() {
return {
2020-10-30 21:55:48 +08:00
checked: {
default: false,
2020-10-30 21:55:48 +08:00
parseHTML: element => ({
checked: element.getAttribute('data-checked') === 'true',
}),
renderHTML: attributes => ({
'data-checked': attributes.checked,
}),
keepOnSplit: false,
},
}
},
parseHTML() {
return [
{
2020-11-04 03:57:09 +08:00
tag: 'li[data-type="taskItem"]',
priority: 51,
},
]
},
2020-11-13 23:07:20 +08:00
renderHTML({ HTMLAttributes }) {
2020-11-25 16:50:54 +08:00
return ['li', mergeAttributes(
this.options.HTMLAttributes,
HTMLAttributes,
{ 'data-type': 'taskItem' },
), 0]
},
addKeyboardShortcuts() {
2020-10-30 21:55:48 +08:00
const shortcuts = {
Enter: () => this.editor.commands.splitListItem('taskItem'),
'Shift-Tab': () => this.editor.commands.liftListItem('taskItem'),
2020-10-30 21:55:48 +08:00
}
if (!this.options.nested) {
return shortcuts
}
return {
...shortcuts,
Tab: () => this.editor.commands.sinkListItem('taskItem'),
}
},
2020-10-30 21:55:48 +08:00
addNodeView() {
2020-12-03 01:13:16 +08:00
return ({
node,
HTMLAttributes,
getPos,
editor,
}) => {
2020-10-30 21:55:48 +08:00
const listItem = document.createElement('li')
2021-04-07 00:57:39 +08:00
const checkboxWrapper = document.createElement('label')
const checkboxStyler = document.createElement('span')
2020-10-30 21:55:48 +08:00
const checkbox = document.createElement('input')
const content = document.createElement('div')
2021-04-07 00:57:39 +08:00
checkboxWrapper.contentEditable = 'false'
2020-10-30 21:55:48 +08:00
checkbox.type = 'checkbox'
checkbox.addEventListener('change', event => {
// if the editor isnt editable
// we have to undo the latest change
if (!editor.isEditable) {
checkbox.checked = !checkbox.checked
return
}
2020-10-30 21:55:48 +08:00
const { checked } = event.target as any
if (editor.isEditable && typeof getPos === 'function') {
2021-04-23 05:10:54 +08:00
editor
.chain()
.focus()
.command(({ tr }) => {
tr.setNodeMarkup(getPos(), undefined, {
checked,
})
return true
})
.run()
2020-10-30 21:55:48 +08:00
}
})
listItem.dataset.checked = node.attrs.checked
2020-12-03 01:13:16 +08:00
if (node.attrs.checked) {
2020-10-30 21:55:48 +08:00
checkbox.setAttribute('checked', 'checked')
}
2021-04-07 00:57:39 +08:00
checkboxWrapper.append(checkbox, checkboxStyler)
listItem.append(checkboxWrapper, content)
2020-10-30 21:55:48 +08:00
2021-04-07 00:57:39 +08:00
Object
.entries(HTMLAttributes)
.forEach(([key, value]) => {
listItem.setAttribute(key, value)
})
2020-10-30 21:55:48 +08:00
return {
dom: listItem,
contentDOM: content,
2020-12-03 01:13:16 +08:00
update: updatedNode => {
if (updatedNode.type !== this.type) {
return false
}
listItem.dataset.checked = updatedNode.attrs.checked
2020-12-03 01:13:16 +08:00
if (updatedNode.attrs.checked) {
checkbox.setAttribute('checked', 'checked')
} else {
checkbox.removeAttribute('checked')
}
return true
},
2020-10-30 21:55:48 +08:00
}
}
},
addInputRules() {
return [
wrappingInputRule(
inputRegex,
this.type,
match => ({
checked: match[match.length - 1] === 'x',
}),
),
]
},
})