diff --git a/docs/api/nodes/task-item.md b/docs/api/nodes/task-item.md index e62bb0415..64004d65d 100644 --- a/docs/api/nodes/task-item.md +++ b/docs/api/nodes/task-item.md @@ -4,6 +4,7 @@ icon: task-line --- # TaskItem + [![Version](https://img.shields.io/npm/v/@tiptap/extension-task-item.svg?label=version)](https://www.npmjs.com/package/@tiptap/extension-task-item) [![Downloads](https://img.shields.io/npm/dm/@tiptap/extension-task-item.svg)](https://npmcharts.com/compare/@tiptap/extension-task-item?minimal=true) @@ -12,6 +13,7 @@ This extension renders a task item list element, which is a `
  • ` tag with a `d This extension doesn’t require any JavaScript framework, it’s based on Vanilla JavaScript. ## Installation + ```bash npm install @tiptap/extension-task-list @tiptap/extension-task-item ``` @@ -21,6 +23,7 @@ This extension requires the [`TaskList`](/api/nodes/task-list) node. ## Settings ### HTMLAttributes + Custom HTML attributes that should be added to the rendered HTML tag. ```js @@ -31,7 +34,32 @@ TaskItem.configure({ }) ``` +### nested + +Whether the task items are allowed to be nested within each other. + +```js +TaskItem.configure({ + nested: true, +}) +``` + +### onReadOnlyChecked + +A handler for when the task item is checked or unchecked while the editor is set to `readOnly`. + +If this is not supplied, the task items are immutable while the editor is `readOnly`. + +```js +TaskItem.configure({ + onReadOnlyChecked: (node, checked) => { + // do something + }, +}) +``` + ## Keyboard shortcuts + | Command | Windows/Linux | macOS | | --------------- | ------------------ | ------------------ | | splitListItem() | `Enter` | `Enter` | @@ -39,7 +67,9 @@ TaskItem.configure({ | liftListItem() | `Shift` `Tab` | `Shift` `Tab` | ## Source code + [packages/extension-task-item/](https://github.com/ueberdosis/tiptap/blob/main/packages/extension-task-item/) ## Usage + https://embed.tiptap.dev/preview/Nodes/TaskItem diff --git a/packages/extension-task-item/package.json b/packages/extension-task-item/package.json index 6f3d7ab94..18b0d2f3e 100644 --- a/packages/extension-task-item/package.json +++ b/packages/extension-task-item/package.json @@ -23,6 +23,9 @@ "peerDependencies": { "@tiptap/core": "^2.0.0-beta.1" }, + "dependencies": { + "prosemirror-model": "^1.16.1" + }, "repository": { "type": "git", "url": "https://github.com/ueberdosis/tiptap", diff --git a/packages/extension-task-item/src/task-item.ts b/packages/extension-task-item/src/task-item.ts index 40f507a2c..3ededf17a 100644 --- a/packages/extension-task-item/src/task-item.ts +++ b/packages/extension-task-item/src/task-item.ts @@ -1,9 +1,10 @@ import { Node, mergeAttributes, wrappingInputRule } from '@tiptap/core' +import { Node as ProseMirrorNode } from 'prosemirror-model' export interface TaskItemOptions { - nested: boolean, - checkable: boolean, - HTMLAttributes: Record, + onReadOnlyChecked?: (node: ProseMirrorNode, checked: boolean) => void + nested: boolean + HTMLAttributes: Record } export const inputRegex = /^\s*(\[([( |x])?\])\s$/ @@ -14,7 +15,6 @@ export const TaskItem = Node.create({ addOptions() { return { nested: false, - checkable: false, HTMLAttributes: {}, } }, @@ -30,8 +30,8 @@ export const TaskItem = Node.create({ checked: { default: false, keepOnSplit: false, - parseHTML: element => element.getAttribute('data-checked') === 'true', - renderHTML: attributes => ({ + parseHTML: (element) => element.getAttribute('data-checked') === 'true', + renderHTML: (attributes) => ({ 'data-checked': attributes.checked, }), }, @@ -50,28 +50,21 @@ export const TaskItem = Node.create({ renderHTML({ node, HTMLAttributes }) { return [ 'li', - mergeAttributes( - this.options.HTMLAttributes, - HTMLAttributes, - { 'data-type': this.name }, - ), + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { + 'data-type': this.name, + }), [ 'label', [ 'input', { type: 'checkbox', - checked: node.attrs.checked - ? 'checked' - : null, + checked: node.attrs.checked ? 'checked' : null, }, ], ['span'], ], - [ - 'div', - 0, - ], + ['div', 0], ] }, @@ -92,12 +85,7 @@ export const TaskItem = Node.create({ }, addNodeView() { - return ({ - node, - HTMLAttributes, - getPos, - editor, - }) => { + return ({ node, HTMLAttributes, getPos, editor }) => { const listItem = document.createElement('li') const checkboxWrapper = document.createElement('label') const checkboxStyler = document.createElement('span') @@ -106,10 +94,10 @@ export const TaskItem = Node.create({ checkboxWrapper.contentEditable = 'false' checkbox.type = 'checkbox' - checkbox.addEventListener('change', event => { - // if the editor isn’t editable and the item isn't checkable - // we have to undo the latest change - if (!editor.isEditable && !this.options.checkable) { + checkbox.addEventListener('change', (event) => { + // if the editor isn’t editable and we don't have a handler for + // readonly checks we have to undo the latest change + if (!editor.isEditable && !this.options.onReadOnlyChecked) { checkbox.checked = !checkbox.checked return @@ -134,6 +122,9 @@ export const TaskItem = Node.create({ }) .run() } + if (!editor.isEditable && this.options.onReadOnlyChecked) { + this.options.onReadOnlyChecked(node, checked) + } }) Object.entries(this.options.HTMLAttributes).forEach(([key, value]) => { @@ -148,16 +139,14 @@ export const TaskItem = Node.create({ checkboxWrapper.append(checkbox, checkboxStyler) listItem.append(checkboxWrapper, content) - Object - .entries(HTMLAttributes) - .forEach(([key, value]) => { - listItem.setAttribute(key, value) - }) + Object.entries(HTMLAttributes).forEach(([key, value]) => { + listItem.setAttribute(key, value) + }) return { dom: listItem, contentDOM: content, - update: updatedNode => { + update: (updatedNode) => { if (updatedNode.type !== this.type) { return false } @@ -180,7 +169,7 @@ export const TaskItem = Node.create({ wrappingInputRule({ find: inputRegex, type: this.type, - getAttributes: match => ({ + getAttributes: (match) => ({ checked: match[match.length - 1] === 'x', }), }),