@tiptap/extension-list 3.24.0 → 3.25.0
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.
- package/dist/index.cjs +180 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +157 -44
- package/dist/index.js.map +1 -1
- package/dist/item/index.cjs +120 -3
- package/dist/item/index.cjs.map +1 -1
- package/dist/item/index.js +117 -0
- package/dist/item/index.js.map +1 -1
- package/dist/keymap/index.cjs +37 -47
- package/dist/keymap/index.cjs.map +1 -1
- package/dist/keymap/index.js +30 -40
- package/dist/keymap/index.js.map +1 -1
- package/dist/kit/index.cjs +180 -67
- package/dist/kit/index.cjs.map +1 -1
- package/dist/kit/index.js +157 -44
- package/dist/kit/index.js.map +1 -1
- package/dist/task-item/index.cjs +120 -5
- package/dist/task-item/index.cjs.map +1 -1
- package/dist/task-item/index.js +115 -0
- package/dist/task-item/index.js.map +1 -1
- package/package.json +5 -5
- package/src/helpers/createBranchingListDeleteKeymap.ts +24 -0
- package/src/helpers/getBranchingNestedListAtCursor.ts +116 -0
- package/src/helpers/handleDeleteBranchingNestedList.ts +25 -0
- package/src/helpers/hasBranchingNestedListAfterCursor.ts +30 -0
- package/src/helpers/hoistBranchingNestedList.ts +56 -0
- package/src/item/list-item.ts +11 -0
- package/src/keymap/listHelpers/handleBackspace.ts +3 -22
- package/src/task-item/task-item.ts +10 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/task-item/index.ts","../../src/task-item/task-item.ts"],"sourcesContent":["export * from './task-item.js'\n","import type { KeyboardShortcutCommand } from '@tiptap/core'\nimport {\n getRenderedAttributes,\n mergeAttributes,\n Node,\n renderNestedMarkdownContent,\n wrappingInputRule,\n} from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\n\nexport interface TaskItemOptions {\n /**\n * A callback function that is called when the checkbox is clicked while the editor is in readonly mode.\n * @param node The prosemirror node of the task item\n * @param checked The new checked state\n * @returns boolean\n */\n onReadOnlyChecked?: (node: ProseMirrorNode, checked: boolean) => boolean\n\n /**\n * Controls whether the task items can be nested or not.\n * @default false\n * @example true\n */\n nested: boolean\n\n /**\n * HTML attributes to add to the task item element.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n\n /**\n * The node type for taskList nodes\n * @default 'taskList'\n * @example 'myCustomTaskList'\n */\n taskListTypeName: string\n\n /**\n * Accessibility options for the task item.\n * @default {}\n * @example\n * ```js\n * {\n * checkboxLabel: (node) => `Task item: ${node.textContent || 'empty task item'}`\n * }\n */\n a11y?: {\n checkboxLabel?: (node: ProseMirrorNode, checked: boolean) => string\n }\n}\n\n/**\n * Matches a task item to a - [ ] on input.\n */\nexport const inputRegex = /^\\s*(\\[([( |x])?\\])\\s$/\n\n/**\n * This extension allows you to create task items.\n * @see https://www.tiptap.dev/api/nodes/task-item\n */\nexport const TaskItem = Node.create<TaskItemOptions>({\n name: 'taskItem',\n\n addOptions() {\n return {\n nested: false,\n HTMLAttributes: {},\n taskListTypeName: 'taskList',\n a11y: undefined,\n }\n },\n\n content() {\n return this.options.nested ? 'paragraph block*' : 'paragraph+'\n },\n\n defining: true,\n\n addAttributes() {\n return {\n checked: {\n default: false,\n keepOnSplit: false,\n parseHTML: element => {\n const dataChecked = element.getAttribute('data-checked')\n\n return dataChecked === '' || dataChecked === 'true'\n },\n renderHTML: attributes => ({\n 'data-checked': attributes.checked,\n }),\n },\n }\n },\n\n parseHTML() {\n return [\n {\n tag: `li[data-type=\"${this.name}\"]`,\n priority: 51,\n },\n ]\n },\n\n renderHTML({ node, HTMLAttributes }) {\n return [\n 'li',\n mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {\n 'data-type': this.name,\n }),\n [\n 'label',\n [\n 'input',\n {\n type: 'checkbox',\n checked: node.attrs.checked ? 'checked' : null,\n },\n ],\n ['span'],\n ],\n ['div', 0],\n ]\n },\n\n parseMarkdown: (token, h) => {\n // Parse the task item's text content into paragraph content\n const content = []\n\n // First, add the main paragraph content\n if (token.tokens && token.tokens.length > 0) {\n // If we have tokens, create a paragraph with the inline content\n content.push(h.createNode('paragraph', {}, h.parseInline(token.tokens)))\n } else if (token.text) {\n // If we have raw text, create a paragraph with text node\n content.push(h.createNode('paragraph', {}, [h.createNode('text', { text: token.text })]))\n } else {\n // Fallback: empty paragraph\n content.push(h.createNode('paragraph', {}, []))\n }\n\n // Then, add any nested content (like nested task lists)\n if (token.nestedTokens && token.nestedTokens.length > 0) {\n const nestedContent = h.parseChildren(token.nestedTokens)\n content.push(...nestedContent)\n }\n\n return h.createNode('taskItem', { checked: token.checked || false }, content)\n },\n\n renderMarkdown: (node, h) => {\n const checkedChar = node.attrs?.checked ? 'x' : ' '\n const prefix = `- [${checkedChar}] `\n\n return renderNestedMarkdownContent(node, h, prefix)\n },\n\n addKeyboardShortcuts() {\n const shortcuts: {\n [key: string]: KeyboardShortcutCommand\n } = {\n Enter: () => this.editor.commands.splitListItem(this.name),\n 'Shift-Tab': () => this.editor.commands.liftListItem(this.name),\n }\n\n if (!this.options.nested) {\n return shortcuts\n }\n\n return {\n ...shortcuts,\n Tab: () => this.editor.commands.sinkListItem(this.name),\n }\n },\n\n addNodeView() {\n return ({ node, HTMLAttributes, getPos, editor }) => {\n const listItem = document.createElement('li')\n const checkboxWrapper = document.createElement('label')\n const checkboxStyler = document.createElement('span')\n const checkbox = document.createElement('input')\n const content = document.createElement('div')\n\n const updateA11Y = (currentNode: ProseMirrorNode) => {\n checkbox.ariaLabel =\n this.options.a11y?.checkboxLabel?.(currentNode, checkbox.checked) ||\n `Task item checkbox for ${currentNode.textContent || 'empty task item'}`\n }\n\n updateA11Y(node)\n\n checkboxWrapper.contentEditable = 'false'\n checkbox.type = 'checkbox'\n checkbox.addEventListener('mousedown', event => event.preventDefault())\n checkbox.addEventListener('change', event => {\n // if the editor isn’t editable and we don't have a handler for\n // readonly checks we have to undo the latest change\n if (!editor.isEditable && !this.options.onReadOnlyChecked) {\n checkbox.checked = !checkbox.checked\n\n return\n }\n\n const { checked } = event.target as any\n\n if (editor.isEditable && typeof getPos === 'function') {\n editor\n .chain()\n .focus(undefined, { scrollIntoView: false })\n .command(({ tr }) => {\n const position = getPos()\n\n if (typeof position !== 'number') {\n return false\n }\n const currentNode = tr.doc.nodeAt(position)\n\n tr.setNodeMarkup(position, undefined, {\n ...currentNode?.attrs,\n checked,\n })\n\n return true\n })\n .run()\n }\n if (!editor.isEditable && this.options.onReadOnlyChecked) {\n // Reset state if onReadOnlyChecked returns false\n if (!this.options.onReadOnlyChecked(node, checked)) {\n checkbox.checked = !checkbox.checked\n }\n }\n })\n\n Object.entries(this.options.HTMLAttributes).forEach(([key, value]) => {\n listItem.setAttribute(key, value)\n })\n\n listItem.dataset.checked = node.attrs.checked\n checkbox.checked = node.attrs.checked\n\n checkboxWrapper.append(checkbox, checkboxStyler)\n listItem.append(checkboxWrapper, content)\n\n Object.entries(HTMLAttributes).forEach(([key, value]) => {\n listItem.setAttribute(key, value)\n })\n\n // Track the keys of previously rendered HTML attributes for proper removal\n let prevRenderedAttributeKeys = new Set(Object.keys(HTMLAttributes))\n\n return {\n dom: listItem,\n contentDOM: content,\n update: updatedNode => {\n if (updatedNode.type !== this.type) {\n return false\n }\n\n listItem.dataset.checked = updatedNode.attrs.checked\n checkbox.checked = updatedNode.attrs.checked\n updateA11Y(updatedNode)\n\n // Sync all HTML attributes from the updated node\n const extensionAttributes = editor.extensionManager.attributes\n const newHTMLAttributes = getRenderedAttributes(updatedNode, extensionAttributes)\n const newKeys = new Set(Object.keys(newHTMLAttributes))\n\n // Remove attributes that were previously rendered but are no longer present\n // If the attribute exists in static options, restore it instead of removing\n const staticAttrs = this.options.HTMLAttributes\n\n prevRenderedAttributeKeys.forEach(key => {\n if (!newKeys.has(key)) {\n if (key in staticAttrs) {\n listItem.setAttribute(key, staticAttrs[key])\n } else {\n listItem.removeAttribute(key)\n }\n }\n })\n\n // Update or add new attributes\n Object.entries(newHTMLAttributes).forEach(([key, value]) => {\n if (value === null || value === undefined) {\n // If the attribute exists in static options, restore it instead of removing\n if (key in staticAttrs) {\n listItem.setAttribute(key, staticAttrs[key])\n } else {\n listItem.removeAttribute(key)\n }\n } else {\n listItem.setAttribute(key, value)\n }\n })\n\n // Update the tracked keys for next update\n prevRenderedAttributeKeys = newKeys\n\n return true\n },\n }\n }\n },\n\n addInputRules() {\n return [\n wrappingInputRule({\n find: inputRegex,\n type: this.type,\n getAttributes: match => ({\n checked: match[match.length - 1] === 'x',\n }),\n }),\n ]\n },\n})\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/task-item/index.ts","../../src/task-item/task-item.ts","../../src/helpers/createBranchingListDeleteKeymap.ts","../../src/helpers/hoistBranchingNestedList.ts","../../src/helpers/getBranchingNestedListAtCursor.ts","../../src/helpers/handleDeleteBranchingNestedList.ts"],"sourcesContent":["export * from './task-item.js'\n","import type { KeyboardShortcutCommand } from '@tiptap/core'\nimport {\n getRenderedAttributes,\n mergeAttributes,\n Node,\n renderNestedMarkdownContent,\n wrappingInputRule,\n} from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\n\nimport { createBranchingListDeleteKeymap } from '../helpers/createBranchingListDeleteKeymap.js'\n\nexport interface TaskItemOptions {\n /**\n * A callback function that is called when the checkbox is clicked while the editor is in readonly mode.\n * @param node The prosemirror node of the task item\n * @param checked The new checked state\n * @returns boolean\n */\n onReadOnlyChecked?: (node: ProseMirrorNode, checked: boolean) => boolean\n\n /**\n * Controls whether the task items can be nested or not.\n * @default false\n * @example true\n */\n nested: boolean\n\n /**\n * HTML attributes to add to the task item element.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n\n /**\n * The node type for taskList nodes\n * @default 'taskList'\n * @example 'myCustomTaskList'\n */\n taskListTypeName: string\n\n /**\n * Accessibility options for the task item.\n * @default {}\n * @example\n * ```js\n * {\n * checkboxLabel: (node) => `Task item: ${node.textContent || 'empty task item'}`\n * }\n */\n a11y?: {\n checkboxLabel?: (node: ProseMirrorNode, checked: boolean) => string\n }\n}\n\n/**\n * Matches a task item to a - [ ] on input.\n */\nexport const inputRegex = /^\\s*(\\[([( |x])?\\])\\s$/\n\n/**\n * This extension allows you to create task items.\n * @see https://www.tiptap.dev/api/nodes/task-item\n */\nexport const TaskItem = Node.create<TaskItemOptions>({\n name: 'taskItem',\n\n addOptions() {\n return {\n nested: false,\n HTMLAttributes: {},\n taskListTypeName: 'taskList',\n a11y: undefined,\n }\n },\n\n content() {\n return this.options.nested ? 'paragraph block*' : 'paragraph+'\n },\n\n defining: true,\n\n addAttributes() {\n return {\n checked: {\n default: false,\n keepOnSplit: false,\n parseHTML: element => {\n const dataChecked = element.getAttribute('data-checked')\n\n return dataChecked === '' || dataChecked === 'true'\n },\n renderHTML: attributes => ({\n 'data-checked': attributes.checked,\n }),\n },\n }\n },\n\n parseHTML() {\n return [\n {\n tag: `li[data-type=\"${this.name}\"]`,\n priority: 51,\n },\n ]\n },\n\n renderHTML({ node, HTMLAttributes }) {\n return [\n 'li',\n mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {\n 'data-type': this.name,\n }),\n [\n 'label',\n [\n 'input',\n {\n type: 'checkbox',\n checked: node.attrs.checked ? 'checked' : null,\n },\n ],\n ['span'],\n ],\n ['div', 0],\n ]\n },\n\n parseMarkdown: (token, h) => {\n // Parse the task item's text content into paragraph content\n const content = []\n\n // First, add the main paragraph content\n if (token.tokens && token.tokens.length > 0) {\n // If we have tokens, create a paragraph with the inline content\n content.push(h.createNode('paragraph', {}, h.parseInline(token.tokens)))\n } else if (token.text) {\n // If we have raw text, create a paragraph with text node\n content.push(h.createNode('paragraph', {}, [h.createNode('text', { text: token.text })]))\n } else {\n // Fallback: empty paragraph\n content.push(h.createNode('paragraph', {}, []))\n }\n\n // Then, add any nested content (like nested task lists)\n if (token.nestedTokens && token.nestedTokens.length > 0) {\n const nestedContent = h.parseChildren(token.nestedTokens)\n content.push(...nestedContent)\n }\n\n return h.createNode('taskItem', { checked: token.checked || false }, content)\n },\n\n renderMarkdown: (node, h) => {\n const checkedChar = node.attrs?.checked ? 'x' : ' '\n const prefix = `- [${checkedChar}] `\n\n return renderNestedMarkdownContent(node, h, prefix)\n },\n\n addExtensions() {\n if (!this.options.nested) {\n return []\n }\n\n return [createBranchingListDeleteKeymap(this.name, [this.options.taskListTypeName])]\n },\n\n addKeyboardShortcuts() {\n const shortcuts: {\n [key: string]: KeyboardShortcutCommand\n } = {\n Enter: () => this.editor.commands.splitListItem(this.name),\n 'Shift-Tab': () => this.editor.commands.liftListItem(this.name),\n }\n\n if (!this.options.nested) {\n return shortcuts\n }\n\n return {\n ...shortcuts,\n Tab: () => this.editor.commands.sinkListItem(this.name),\n }\n },\n\n addNodeView() {\n return ({ node, HTMLAttributes, getPos, editor }) => {\n const listItem = document.createElement('li')\n const checkboxWrapper = document.createElement('label')\n const checkboxStyler = document.createElement('span')\n const checkbox = document.createElement('input')\n const content = document.createElement('div')\n\n const updateA11Y = (currentNode: ProseMirrorNode) => {\n checkbox.ariaLabel =\n this.options.a11y?.checkboxLabel?.(currentNode, checkbox.checked) ||\n `Task item checkbox for ${currentNode.textContent || 'empty task item'}`\n }\n\n updateA11Y(node)\n\n checkboxWrapper.contentEditable = 'false'\n checkbox.type = 'checkbox'\n checkbox.addEventListener('mousedown', event => event.preventDefault())\n checkbox.addEventListener('change', event => {\n // if the editor isn’t editable and we don't have a handler for\n // readonly checks we have to undo the latest change\n if (!editor.isEditable && !this.options.onReadOnlyChecked) {\n checkbox.checked = !checkbox.checked\n\n return\n }\n\n const { checked } = event.target as any\n\n if (editor.isEditable && typeof getPos === 'function') {\n editor\n .chain()\n .focus(undefined, { scrollIntoView: false })\n .command(({ tr }) => {\n const position = getPos()\n\n if (typeof position !== 'number') {\n return false\n }\n const currentNode = tr.doc.nodeAt(position)\n\n tr.setNodeMarkup(position, undefined, {\n ...currentNode?.attrs,\n checked,\n })\n\n return true\n })\n .run()\n }\n if (!editor.isEditable && this.options.onReadOnlyChecked) {\n // Reset state if onReadOnlyChecked returns false\n if (!this.options.onReadOnlyChecked(node, checked)) {\n checkbox.checked = !checkbox.checked\n }\n }\n })\n\n Object.entries(this.options.HTMLAttributes).forEach(([key, value]) => {\n listItem.setAttribute(key, value)\n })\n\n listItem.dataset.checked = node.attrs.checked\n checkbox.checked = node.attrs.checked\n\n checkboxWrapper.append(checkbox, checkboxStyler)\n listItem.append(checkboxWrapper, content)\n\n Object.entries(HTMLAttributes).forEach(([key, value]) => {\n listItem.setAttribute(key, value)\n })\n\n // Track the keys of previously rendered HTML attributes for proper removal\n let prevRenderedAttributeKeys = new Set(Object.keys(HTMLAttributes))\n\n return {\n dom: listItem,\n contentDOM: content,\n update: updatedNode => {\n if (updatedNode.type !== this.type) {\n return false\n }\n\n listItem.dataset.checked = updatedNode.attrs.checked\n checkbox.checked = updatedNode.attrs.checked\n updateA11Y(updatedNode)\n\n // Sync all HTML attributes from the updated node\n const extensionAttributes = editor.extensionManager.attributes\n const newHTMLAttributes = getRenderedAttributes(updatedNode, extensionAttributes)\n const newKeys = new Set(Object.keys(newHTMLAttributes))\n\n // Remove attributes that were previously rendered but are no longer present\n // If the attribute exists in static options, restore it instead of removing\n const staticAttrs = this.options.HTMLAttributes\n\n prevRenderedAttributeKeys.forEach(key => {\n if (!newKeys.has(key)) {\n if (key in staticAttrs) {\n listItem.setAttribute(key, staticAttrs[key])\n } else {\n listItem.removeAttribute(key)\n }\n }\n })\n\n // Update or add new attributes\n Object.entries(newHTMLAttributes).forEach(([key, value]) => {\n if (value === null || value === undefined) {\n // If the attribute exists in static options, restore it instead of removing\n if (key in staticAttrs) {\n listItem.setAttribute(key, staticAttrs[key])\n } else {\n listItem.removeAttribute(key)\n }\n } else {\n listItem.setAttribute(key, value)\n }\n })\n\n // Update the tracked keys for next update\n prevRenderedAttributeKeys = newKeys\n\n return true\n },\n }\n }\n },\n\n addInputRules() {\n return [\n wrappingInputRule({\n find: inputRegex,\n type: this.type,\n getAttributes: match => ({\n checked: match[match.length - 1] === 'x',\n }),\n }),\n ]\n },\n})\n","import { Extension } from '@tiptap/core'\n\nimport { handleDeleteBranchingNestedList } from './handleDeleteBranchingNestedList.js'\n\n/**\n * Creates a high-priority keymap extension that handles Delete for branching nested lists.\n * Kept separate from the list item node so Enter/Tab shortcuts keep their default priority.\n */\nexport const createBranchingListDeleteKeymap = (itemName: string, wrapperNames: string[]) => {\n return Extension.create({\n name: `${itemName}BranchingDeleteKeymap`,\n priority: 101,\n\n addKeyboardShortcuts() {\n const handleDelete = () =>\n handleDeleteBranchingNestedList(this.editor, itemName, wrapperNames)\n\n return {\n Delete: handleDelete,\n 'Mod-Delete': handleDelete,\n }\n },\n })\n}\n","import { Fragment } from '@tiptap/pm/model'\nimport type { EditorState, Transaction } from '@tiptap/pm/state'\n\nimport { getBranchingNestedListAtCursor } from './getBranchingNestedListAtCursor.js'\n\n/**\n * Hoists all list items from a branching nested list after the cursor into the parent list.\n *\n * Use this when `joinForward` cannot restructure a nested list that contains list items\n * with sublists (see issue #6906).\n *\n * @param state - The editor state to transform.\n * @param dispatch - Optional dispatch function for the transaction.\n * @param itemName - The list item node name (for example `listItem` or `taskItem`).\n * @param wrapperNames - List wrapper node names (for example `bulletList` and `orderedList`).\n * @returns `true` when the nested list was hoisted, otherwise `false`.\n *\n * @example\n * ```ts\n * // Cursor at the end of \"Item 1\" before a nested list with branching items.\n * hoistBranchingNestedList(editor.state, editor.view.dispatch, 'listItem', [\n * 'bulletList',\n * 'orderedList',\n * ])\n * ```\n */\nexport const hoistBranchingNestedList = (\n state: EditorState,\n dispatch: ((tr: Transaction) => void) | undefined,\n itemName: string,\n wrapperNames: string[],\n) => {\n const context = getBranchingNestedListAtCursor(state, itemName, wrapperNames)\n\n if (!context) {\n return false\n }\n\n const { selection } = state\n const { nestedList, nestedListPos, insertPos, items } = context\n const tr = state.tr\n\n tr.delete(nestedListPos, nestedListPos + nestedList.nodeSize)\n\n const mappedInsertPos = tr.mapping.map(insertPos)\n\n tr.insert(mappedInsertPos, Fragment.from(items))\n\n tr.setSelection(selection.map(tr.doc, tr.mapping))\n\n if (dispatch) {\n dispatch(tr)\n }\n\n return true\n}\n","import type { Node } from '@tiptap/pm/model'\nimport type { EditorState } from '@tiptap/pm/state'\n\nexport type BranchingNestedListAtCursor = {\n listItemDepth: number\n nestedList: Node\n nestedListPos: number\n insertPos: number\n items: Node[]\n}\n\n/**\n * Resolves a branching nested list immediately after the cursor when the selection is\n * collapsed at the end of a textblock inside a list item.\n *\n * @param state - The editor state to inspect.\n * @param itemName - The list item node name (for example `listItem` or `taskItem`).\n * @param wrapperNames - List wrapper node names (for example `bulletList` and `orderedList`).\n * @returns Resolved positions and nodes for hoisting, or `null` when not applicable.\n *\n * @example\n * ```ts\n * const context = getBranchingNestedListAtCursor(editor.state, 'listItem', [\n * 'bulletList',\n * 'orderedList',\n * ])\n *\n * if (context) {\n * // cursor is at the end of Item 1 before a branching nested sublist\n * }\n * ```\n */\nexport const getBranchingNestedListAtCursor = (\n state: EditorState,\n itemName: string,\n wrapperNames: string[],\n): BranchingNestedListAtCursor | null => {\n const { selection } = state\n\n if (!selection.empty) {\n return null\n }\n\n const { $from } = selection\n\n if (!$from.parent.isTextblock) {\n return null\n }\n\n if ($from.parentOffset !== $from.parent.content.size) {\n return null\n }\n\n let listItemDepth = -1\n\n for (let depth = $from.depth; depth > 0; depth -= 1) {\n if ($from.node(depth).type.name === itemName) {\n listItemDepth = depth\n break\n }\n }\n\n if (listItemDepth < 0) {\n return null\n }\n\n const listItem = $from.node(listItemDepth)\n const indexInListItem = $from.index(listItemDepth)\n\n if (indexInListItem + 1 >= listItem.childCount) {\n return null\n }\n\n const nextChild = listItem.child(indexInListItem + 1)\n\n if (!wrapperNames.includes(nextChild.type.name)) {\n return null\n }\n\n const itemType = state.schema.nodes[itemName]\n let hasBranching = false\n\n nextChild.forEach(child => {\n if (child.type === itemType && child.childCount > 1) {\n hasBranching = true\n }\n })\n\n if (!hasBranching) {\n return null\n }\n\n const nodeAfter = state.doc.resolve($from.after()).nodeAfter\n\n if (!nodeAfter || !wrapperNames.includes(nodeAfter.type.name)) {\n return null\n }\n\n const items: Node[] = []\n\n nodeAfter.forEach(child => {\n items.push(child)\n })\n\n if (items.length === 0) {\n return null\n }\n\n return {\n listItemDepth,\n nestedList: nodeAfter,\n nestedListPos: $from.after(),\n insertPos: $from.after(listItemDepth),\n items,\n }\n}\n","import type { Editor } from '@tiptap/core'\n\nimport { hoistBranchingNestedList } from './hoistBranchingNestedList.js'\n\n/**\n * Handles Delete for a list item when a branching nested sublist follows the cursor.\n *\n * @param editor - The editor instance whose state should be updated.\n * @param itemName - The list item node name (for example `listItem` or `taskItem`).\n * @param wrapperNames - List wrapper node names (for example `bulletList` and `orderedList`).\n * @returns `true` when the nested list was hoisted, otherwise `false`.\n *\n * @example\n * ```ts\n * Delete: () =>\n * handleDeleteBranchingNestedList(editor, 'listItem', ['bulletList', 'orderedList']),\n * ```\n */\nexport const handleDeleteBranchingNestedList = (\n editor: Editor,\n itemName: string,\n wrapperNames: string[],\n) => {\n return hoistBranchingNestedList(editor.state, editor.view.dispatch, itemName, wrapperNames)\n}\n"],"mappings":"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","names":["import_core"]}
|
package/dist/task-item/index.js
CHANGED
|
@@ -6,6 +6,115 @@ import {
|
|
|
6
6
|
renderNestedMarkdownContent,
|
|
7
7
|
wrappingInputRule
|
|
8
8
|
} from "@tiptap/core";
|
|
9
|
+
|
|
10
|
+
// src/helpers/createBranchingListDeleteKeymap.ts
|
|
11
|
+
import { Extension } from "@tiptap/core";
|
|
12
|
+
|
|
13
|
+
// src/helpers/hoistBranchingNestedList.ts
|
|
14
|
+
import { Fragment } from "@tiptap/pm/model";
|
|
15
|
+
|
|
16
|
+
// src/helpers/getBranchingNestedListAtCursor.ts
|
|
17
|
+
var getBranchingNestedListAtCursor = (state, itemName, wrapperNames) => {
|
|
18
|
+
const { selection } = state;
|
|
19
|
+
if (!selection.empty) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const { $from } = selection;
|
|
23
|
+
if (!$from.parent.isTextblock) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
if ($from.parentOffset !== $from.parent.content.size) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
let listItemDepth = -1;
|
|
30
|
+
for (let depth = $from.depth; depth > 0; depth -= 1) {
|
|
31
|
+
if ($from.node(depth).type.name === itemName) {
|
|
32
|
+
listItemDepth = depth;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (listItemDepth < 0) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const listItem = $from.node(listItemDepth);
|
|
40
|
+
const indexInListItem = $from.index(listItemDepth);
|
|
41
|
+
if (indexInListItem + 1 >= listItem.childCount) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const nextChild = listItem.child(indexInListItem + 1);
|
|
45
|
+
if (!wrapperNames.includes(nextChild.type.name)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const itemType = state.schema.nodes[itemName];
|
|
49
|
+
let hasBranching = false;
|
|
50
|
+
nextChild.forEach((child) => {
|
|
51
|
+
if (child.type === itemType && child.childCount > 1) {
|
|
52
|
+
hasBranching = true;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
if (!hasBranching) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const nodeAfter = state.doc.resolve($from.after()).nodeAfter;
|
|
59
|
+
if (!nodeAfter || !wrapperNames.includes(nodeAfter.type.name)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const items = [];
|
|
63
|
+
nodeAfter.forEach((child) => {
|
|
64
|
+
items.push(child);
|
|
65
|
+
});
|
|
66
|
+
if (items.length === 0) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
listItemDepth,
|
|
71
|
+
nestedList: nodeAfter,
|
|
72
|
+
nestedListPos: $from.after(),
|
|
73
|
+
insertPos: $from.after(listItemDepth),
|
|
74
|
+
items
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// src/helpers/hoistBranchingNestedList.ts
|
|
79
|
+
var hoistBranchingNestedList = (state, dispatch, itemName, wrapperNames) => {
|
|
80
|
+
const context = getBranchingNestedListAtCursor(state, itemName, wrapperNames);
|
|
81
|
+
if (!context) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
const { selection } = state;
|
|
85
|
+
const { nestedList, nestedListPos, insertPos, items } = context;
|
|
86
|
+
const tr = state.tr;
|
|
87
|
+
tr.delete(nestedListPos, nestedListPos + nestedList.nodeSize);
|
|
88
|
+
const mappedInsertPos = tr.mapping.map(insertPos);
|
|
89
|
+
tr.insert(mappedInsertPos, Fragment.from(items));
|
|
90
|
+
tr.setSelection(selection.map(tr.doc, tr.mapping));
|
|
91
|
+
if (dispatch) {
|
|
92
|
+
dispatch(tr);
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// src/helpers/handleDeleteBranchingNestedList.ts
|
|
98
|
+
var handleDeleteBranchingNestedList = (editor, itemName, wrapperNames) => {
|
|
99
|
+
return hoistBranchingNestedList(editor.state, editor.view.dispatch, itemName, wrapperNames);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// src/helpers/createBranchingListDeleteKeymap.ts
|
|
103
|
+
var createBranchingListDeleteKeymap = (itemName, wrapperNames) => {
|
|
104
|
+
return Extension.create({
|
|
105
|
+
name: `${itemName}BranchingDeleteKeymap`,
|
|
106
|
+
priority: 101,
|
|
107
|
+
addKeyboardShortcuts() {
|
|
108
|
+
const handleDelete = () => handleDeleteBranchingNestedList(this.editor, itemName, wrapperNames);
|
|
109
|
+
return {
|
|
110
|
+
Delete: handleDelete,
|
|
111
|
+
"Mod-Delete": handleDelete
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// src/task-item/task-item.ts
|
|
9
118
|
var inputRegex = /^\s*(\[([( |x])?\])\s$/;
|
|
10
119
|
var TaskItem = Node.create({
|
|
11
120
|
name: "taskItem",
|
|
@@ -85,6 +194,12 @@ var TaskItem = Node.create({
|
|
|
85
194
|
const prefix = `- [${checkedChar}] `;
|
|
86
195
|
return renderNestedMarkdownContent(node, h, prefix);
|
|
87
196
|
},
|
|
197
|
+
addExtensions() {
|
|
198
|
+
if (!this.options.nested) {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
return [createBranchingListDeleteKeymap(this.name, [this.options.taskListTypeName])];
|
|
202
|
+
},
|
|
88
203
|
addKeyboardShortcuts() {
|
|
89
204
|
const shortcuts = {
|
|
90
205
|
Enter: () => this.editor.commands.splitListItem(this.name),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/task-item/task-item.ts"],"sourcesContent":["import type { KeyboardShortcutCommand } from '@tiptap/core'\nimport {\n getRenderedAttributes,\n mergeAttributes,\n Node,\n renderNestedMarkdownContent,\n wrappingInputRule,\n} from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\n\nexport interface TaskItemOptions {\n /**\n * A callback function that is called when the checkbox is clicked while the editor is in readonly mode.\n * @param node The prosemirror node of the task item\n * @param checked The new checked state\n * @returns boolean\n */\n onReadOnlyChecked?: (node: ProseMirrorNode, checked: boolean) => boolean\n\n /**\n * Controls whether the task items can be nested or not.\n * @default false\n * @example true\n */\n nested: boolean\n\n /**\n * HTML attributes to add to the task item element.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n\n /**\n * The node type for taskList nodes\n * @default 'taskList'\n * @example 'myCustomTaskList'\n */\n taskListTypeName: string\n\n /**\n * Accessibility options for the task item.\n * @default {}\n * @example\n * ```js\n * {\n * checkboxLabel: (node) => `Task item: ${node.textContent || 'empty task item'}`\n * }\n */\n a11y?: {\n checkboxLabel?: (node: ProseMirrorNode, checked: boolean) => string\n }\n}\n\n/**\n * Matches a task item to a - [ ] on input.\n */\nexport const inputRegex = /^\\s*(\\[([( |x])?\\])\\s$/\n\n/**\n * This extension allows you to create task items.\n * @see https://www.tiptap.dev/api/nodes/task-item\n */\nexport const TaskItem = Node.create<TaskItemOptions>({\n name: 'taskItem',\n\n addOptions() {\n return {\n nested: false,\n HTMLAttributes: {},\n taskListTypeName: 'taskList',\n a11y: undefined,\n }\n },\n\n content() {\n return this.options.nested ? 'paragraph block*' : 'paragraph+'\n },\n\n defining: true,\n\n addAttributes() {\n return {\n checked: {\n default: false,\n keepOnSplit: false,\n parseHTML: element => {\n const dataChecked = element.getAttribute('data-checked')\n\n return dataChecked === '' || dataChecked === 'true'\n },\n renderHTML: attributes => ({\n 'data-checked': attributes.checked,\n }),\n },\n }\n },\n\n parseHTML() {\n return [\n {\n tag: `li[data-type=\"${this.name}\"]`,\n priority: 51,\n },\n ]\n },\n\n renderHTML({ node, HTMLAttributes }) {\n return [\n 'li',\n mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {\n 'data-type': this.name,\n }),\n [\n 'label',\n [\n 'input',\n {\n type: 'checkbox',\n checked: node.attrs.checked ? 'checked' : null,\n },\n ],\n ['span'],\n ],\n ['div', 0],\n ]\n },\n\n parseMarkdown: (token, h) => {\n // Parse the task item's text content into paragraph content\n const content = []\n\n // First, add the main paragraph content\n if (token.tokens && token.tokens.length > 0) {\n // If we have tokens, create a paragraph with the inline content\n content.push(h.createNode('paragraph', {}, h.parseInline(token.tokens)))\n } else if (token.text) {\n // If we have raw text, create a paragraph with text node\n content.push(h.createNode('paragraph', {}, [h.createNode('text', { text: token.text })]))\n } else {\n // Fallback: empty paragraph\n content.push(h.createNode('paragraph', {}, []))\n }\n\n // Then, add any nested content (like nested task lists)\n if (token.nestedTokens && token.nestedTokens.length > 0) {\n const nestedContent = h.parseChildren(token.nestedTokens)\n content.push(...nestedContent)\n }\n\n return h.createNode('taskItem', { checked: token.checked || false }, content)\n },\n\n renderMarkdown: (node, h) => {\n const checkedChar = node.attrs?.checked ? 'x' : ' '\n const prefix = `- [${checkedChar}] `\n\n return renderNestedMarkdownContent(node, h, prefix)\n },\n\n addKeyboardShortcuts() {\n const shortcuts: {\n [key: string]: KeyboardShortcutCommand\n } = {\n Enter: () => this.editor.commands.splitListItem(this.name),\n 'Shift-Tab': () => this.editor.commands.liftListItem(this.name),\n }\n\n if (!this.options.nested) {\n return shortcuts\n }\n\n return {\n ...shortcuts,\n Tab: () => this.editor.commands.sinkListItem(this.name),\n }\n },\n\n addNodeView() {\n return ({ node, HTMLAttributes, getPos, editor }) => {\n const listItem = document.createElement('li')\n const checkboxWrapper = document.createElement('label')\n const checkboxStyler = document.createElement('span')\n const checkbox = document.createElement('input')\n const content = document.createElement('div')\n\n const updateA11Y = (currentNode: ProseMirrorNode) => {\n checkbox.ariaLabel =\n this.options.a11y?.checkboxLabel?.(currentNode, checkbox.checked) ||\n `Task item checkbox for ${currentNode.textContent || 'empty task item'}`\n }\n\n updateA11Y(node)\n\n checkboxWrapper.contentEditable = 'false'\n checkbox.type = 'checkbox'\n checkbox.addEventListener('mousedown', event => event.preventDefault())\n checkbox.addEventListener('change', event => {\n // if the editor isn’t editable and we don't have a handler for\n // readonly checks we have to undo the latest change\n if (!editor.isEditable && !this.options.onReadOnlyChecked) {\n checkbox.checked = !checkbox.checked\n\n return\n }\n\n const { checked } = event.target as any\n\n if (editor.isEditable && typeof getPos === 'function') {\n editor\n .chain()\n .focus(undefined, { scrollIntoView: false })\n .command(({ tr }) => {\n const position = getPos()\n\n if (typeof position !== 'number') {\n return false\n }\n const currentNode = tr.doc.nodeAt(position)\n\n tr.setNodeMarkup(position, undefined, {\n ...currentNode?.attrs,\n checked,\n })\n\n return true\n })\n .run()\n }\n if (!editor.isEditable && this.options.onReadOnlyChecked) {\n // Reset state if onReadOnlyChecked returns false\n if (!this.options.onReadOnlyChecked(node, checked)) {\n checkbox.checked = !checkbox.checked\n }\n }\n })\n\n Object.entries(this.options.HTMLAttributes).forEach(([key, value]) => {\n listItem.setAttribute(key, value)\n })\n\n listItem.dataset.checked = node.attrs.checked\n checkbox.checked = node.attrs.checked\n\n checkboxWrapper.append(checkbox, checkboxStyler)\n listItem.append(checkboxWrapper, content)\n\n Object.entries(HTMLAttributes).forEach(([key, value]) => {\n listItem.setAttribute(key, value)\n })\n\n // Track the keys of previously rendered HTML attributes for proper removal\n let prevRenderedAttributeKeys = new Set(Object.keys(HTMLAttributes))\n\n return {\n dom: listItem,\n contentDOM: content,\n update: updatedNode => {\n if (updatedNode.type !== this.type) {\n return false\n }\n\n listItem.dataset.checked = updatedNode.attrs.checked\n checkbox.checked = updatedNode.attrs.checked\n updateA11Y(updatedNode)\n\n // Sync all HTML attributes from the updated node\n const extensionAttributes = editor.extensionManager.attributes\n const newHTMLAttributes = getRenderedAttributes(updatedNode, extensionAttributes)\n const newKeys = new Set(Object.keys(newHTMLAttributes))\n\n // Remove attributes that were previously rendered but are no longer present\n // If the attribute exists in static options, restore it instead of removing\n const staticAttrs = this.options.HTMLAttributes\n\n prevRenderedAttributeKeys.forEach(key => {\n if (!newKeys.has(key)) {\n if (key in staticAttrs) {\n listItem.setAttribute(key, staticAttrs[key])\n } else {\n listItem.removeAttribute(key)\n }\n }\n })\n\n // Update or add new attributes\n Object.entries(newHTMLAttributes).forEach(([key, value]) => {\n if (value === null || value === undefined) {\n // If the attribute exists in static options, restore it instead of removing\n if (key in staticAttrs) {\n listItem.setAttribute(key, staticAttrs[key])\n } else {\n listItem.removeAttribute(key)\n }\n } else {\n listItem.setAttribute(key, value)\n }\n })\n\n // Update the tracked keys for next update\n prevRenderedAttributeKeys = newKeys\n\n return true\n },\n }\n }\n },\n\n addInputRules() {\n return [\n wrappingInputRule({\n find: inputRegex,\n type: this.type,\n getAttributes: match => ({\n checked: match[match.length - 1] === 'x',\n }),\n }),\n ]\n },\n})\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/task-item/task-item.ts","../../src/helpers/createBranchingListDeleteKeymap.ts","../../src/helpers/hoistBranchingNestedList.ts","../../src/helpers/getBranchingNestedListAtCursor.ts","../../src/helpers/handleDeleteBranchingNestedList.ts"],"sourcesContent":["import type { KeyboardShortcutCommand } from '@tiptap/core'\nimport {\n getRenderedAttributes,\n mergeAttributes,\n Node,\n renderNestedMarkdownContent,\n wrappingInputRule,\n} from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\n\nimport { createBranchingListDeleteKeymap } from '../helpers/createBranchingListDeleteKeymap.js'\n\nexport interface TaskItemOptions {\n /**\n * A callback function that is called when the checkbox is clicked while the editor is in readonly mode.\n * @param node The prosemirror node of the task item\n * @param checked The new checked state\n * @returns boolean\n */\n onReadOnlyChecked?: (node: ProseMirrorNode, checked: boolean) => boolean\n\n /**\n * Controls whether the task items can be nested or not.\n * @default false\n * @example true\n */\n nested: boolean\n\n /**\n * HTML attributes to add to the task item element.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n\n /**\n * The node type for taskList nodes\n * @default 'taskList'\n * @example 'myCustomTaskList'\n */\n taskListTypeName: string\n\n /**\n * Accessibility options for the task item.\n * @default {}\n * @example\n * ```js\n * {\n * checkboxLabel: (node) => `Task item: ${node.textContent || 'empty task item'}`\n * }\n */\n a11y?: {\n checkboxLabel?: (node: ProseMirrorNode, checked: boolean) => string\n }\n}\n\n/**\n * Matches a task item to a - [ ] on input.\n */\nexport const inputRegex = /^\\s*(\\[([( |x])?\\])\\s$/\n\n/**\n * This extension allows you to create task items.\n * @see https://www.tiptap.dev/api/nodes/task-item\n */\nexport const TaskItem = Node.create<TaskItemOptions>({\n name: 'taskItem',\n\n addOptions() {\n return {\n nested: false,\n HTMLAttributes: {},\n taskListTypeName: 'taskList',\n a11y: undefined,\n }\n },\n\n content() {\n return this.options.nested ? 'paragraph block*' : 'paragraph+'\n },\n\n defining: true,\n\n addAttributes() {\n return {\n checked: {\n default: false,\n keepOnSplit: false,\n parseHTML: element => {\n const dataChecked = element.getAttribute('data-checked')\n\n return dataChecked === '' || dataChecked === 'true'\n },\n renderHTML: attributes => ({\n 'data-checked': attributes.checked,\n }),\n },\n }\n },\n\n parseHTML() {\n return [\n {\n tag: `li[data-type=\"${this.name}\"]`,\n priority: 51,\n },\n ]\n },\n\n renderHTML({ node, HTMLAttributes }) {\n return [\n 'li',\n mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {\n 'data-type': this.name,\n }),\n [\n 'label',\n [\n 'input',\n {\n type: 'checkbox',\n checked: node.attrs.checked ? 'checked' : null,\n },\n ],\n ['span'],\n ],\n ['div', 0],\n ]\n },\n\n parseMarkdown: (token, h) => {\n // Parse the task item's text content into paragraph content\n const content = []\n\n // First, add the main paragraph content\n if (token.tokens && token.tokens.length > 0) {\n // If we have tokens, create a paragraph with the inline content\n content.push(h.createNode('paragraph', {}, h.parseInline(token.tokens)))\n } else if (token.text) {\n // If we have raw text, create a paragraph with text node\n content.push(h.createNode('paragraph', {}, [h.createNode('text', { text: token.text })]))\n } else {\n // Fallback: empty paragraph\n content.push(h.createNode('paragraph', {}, []))\n }\n\n // Then, add any nested content (like nested task lists)\n if (token.nestedTokens && token.nestedTokens.length > 0) {\n const nestedContent = h.parseChildren(token.nestedTokens)\n content.push(...nestedContent)\n }\n\n return h.createNode('taskItem', { checked: token.checked || false }, content)\n },\n\n renderMarkdown: (node, h) => {\n const checkedChar = node.attrs?.checked ? 'x' : ' '\n const prefix = `- [${checkedChar}] `\n\n return renderNestedMarkdownContent(node, h, prefix)\n },\n\n addExtensions() {\n if (!this.options.nested) {\n return []\n }\n\n return [createBranchingListDeleteKeymap(this.name, [this.options.taskListTypeName])]\n },\n\n addKeyboardShortcuts() {\n const shortcuts: {\n [key: string]: KeyboardShortcutCommand\n } = {\n Enter: () => this.editor.commands.splitListItem(this.name),\n 'Shift-Tab': () => this.editor.commands.liftListItem(this.name),\n }\n\n if (!this.options.nested) {\n return shortcuts\n }\n\n return {\n ...shortcuts,\n Tab: () => this.editor.commands.sinkListItem(this.name),\n }\n },\n\n addNodeView() {\n return ({ node, HTMLAttributes, getPos, editor }) => {\n const listItem = document.createElement('li')\n const checkboxWrapper = document.createElement('label')\n const checkboxStyler = document.createElement('span')\n const checkbox = document.createElement('input')\n const content = document.createElement('div')\n\n const updateA11Y = (currentNode: ProseMirrorNode) => {\n checkbox.ariaLabel =\n this.options.a11y?.checkboxLabel?.(currentNode, checkbox.checked) ||\n `Task item checkbox for ${currentNode.textContent || 'empty task item'}`\n }\n\n updateA11Y(node)\n\n checkboxWrapper.contentEditable = 'false'\n checkbox.type = 'checkbox'\n checkbox.addEventListener('mousedown', event => event.preventDefault())\n checkbox.addEventListener('change', event => {\n // if the editor isn’t editable and we don't have a handler for\n // readonly checks we have to undo the latest change\n if (!editor.isEditable && !this.options.onReadOnlyChecked) {\n checkbox.checked = !checkbox.checked\n\n return\n }\n\n const { checked } = event.target as any\n\n if (editor.isEditable && typeof getPos === 'function') {\n editor\n .chain()\n .focus(undefined, { scrollIntoView: false })\n .command(({ tr }) => {\n const position = getPos()\n\n if (typeof position !== 'number') {\n return false\n }\n const currentNode = tr.doc.nodeAt(position)\n\n tr.setNodeMarkup(position, undefined, {\n ...currentNode?.attrs,\n checked,\n })\n\n return true\n })\n .run()\n }\n if (!editor.isEditable && this.options.onReadOnlyChecked) {\n // Reset state if onReadOnlyChecked returns false\n if (!this.options.onReadOnlyChecked(node, checked)) {\n checkbox.checked = !checkbox.checked\n }\n }\n })\n\n Object.entries(this.options.HTMLAttributes).forEach(([key, value]) => {\n listItem.setAttribute(key, value)\n })\n\n listItem.dataset.checked = node.attrs.checked\n checkbox.checked = node.attrs.checked\n\n checkboxWrapper.append(checkbox, checkboxStyler)\n listItem.append(checkboxWrapper, content)\n\n Object.entries(HTMLAttributes).forEach(([key, value]) => {\n listItem.setAttribute(key, value)\n })\n\n // Track the keys of previously rendered HTML attributes for proper removal\n let prevRenderedAttributeKeys = new Set(Object.keys(HTMLAttributes))\n\n return {\n dom: listItem,\n contentDOM: content,\n update: updatedNode => {\n if (updatedNode.type !== this.type) {\n return false\n }\n\n listItem.dataset.checked = updatedNode.attrs.checked\n checkbox.checked = updatedNode.attrs.checked\n updateA11Y(updatedNode)\n\n // Sync all HTML attributes from the updated node\n const extensionAttributes = editor.extensionManager.attributes\n const newHTMLAttributes = getRenderedAttributes(updatedNode, extensionAttributes)\n const newKeys = new Set(Object.keys(newHTMLAttributes))\n\n // Remove attributes that were previously rendered but are no longer present\n // If the attribute exists in static options, restore it instead of removing\n const staticAttrs = this.options.HTMLAttributes\n\n prevRenderedAttributeKeys.forEach(key => {\n if (!newKeys.has(key)) {\n if (key in staticAttrs) {\n listItem.setAttribute(key, staticAttrs[key])\n } else {\n listItem.removeAttribute(key)\n }\n }\n })\n\n // Update or add new attributes\n Object.entries(newHTMLAttributes).forEach(([key, value]) => {\n if (value === null || value === undefined) {\n // If the attribute exists in static options, restore it instead of removing\n if (key in staticAttrs) {\n listItem.setAttribute(key, staticAttrs[key])\n } else {\n listItem.removeAttribute(key)\n }\n } else {\n listItem.setAttribute(key, value)\n }\n })\n\n // Update the tracked keys for next update\n prevRenderedAttributeKeys = newKeys\n\n return true\n },\n }\n }\n },\n\n addInputRules() {\n return [\n wrappingInputRule({\n find: inputRegex,\n type: this.type,\n getAttributes: match => ({\n checked: match[match.length - 1] === 'x',\n }),\n }),\n ]\n },\n})\n","import { Extension } from '@tiptap/core'\n\nimport { handleDeleteBranchingNestedList } from './handleDeleteBranchingNestedList.js'\n\n/**\n * Creates a high-priority keymap extension that handles Delete for branching nested lists.\n * Kept separate from the list item node so Enter/Tab shortcuts keep their default priority.\n */\nexport const createBranchingListDeleteKeymap = (itemName: string, wrapperNames: string[]) => {\n return Extension.create({\n name: `${itemName}BranchingDeleteKeymap`,\n priority: 101,\n\n addKeyboardShortcuts() {\n const handleDelete = () =>\n handleDeleteBranchingNestedList(this.editor, itemName, wrapperNames)\n\n return {\n Delete: handleDelete,\n 'Mod-Delete': handleDelete,\n }\n },\n })\n}\n","import { Fragment } from '@tiptap/pm/model'\nimport type { EditorState, Transaction } from '@tiptap/pm/state'\n\nimport { getBranchingNestedListAtCursor } from './getBranchingNestedListAtCursor.js'\n\n/**\n * Hoists all list items from a branching nested list after the cursor into the parent list.\n *\n * Use this when `joinForward` cannot restructure a nested list that contains list items\n * with sublists (see issue #6906).\n *\n * @param state - The editor state to transform.\n * @param dispatch - Optional dispatch function for the transaction.\n * @param itemName - The list item node name (for example `listItem` or `taskItem`).\n * @param wrapperNames - List wrapper node names (for example `bulletList` and `orderedList`).\n * @returns `true` when the nested list was hoisted, otherwise `false`.\n *\n * @example\n * ```ts\n * // Cursor at the end of \"Item 1\" before a nested list with branching items.\n * hoistBranchingNestedList(editor.state, editor.view.dispatch, 'listItem', [\n * 'bulletList',\n * 'orderedList',\n * ])\n * ```\n */\nexport const hoistBranchingNestedList = (\n state: EditorState,\n dispatch: ((tr: Transaction) => void) | undefined,\n itemName: string,\n wrapperNames: string[],\n) => {\n const context = getBranchingNestedListAtCursor(state, itemName, wrapperNames)\n\n if (!context) {\n return false\n }\n\n const { selection } = state\n const { nestedList, nestedListPos, insertPos, items } = context\n const tr = state.tr\n\n tr.delete(nestedListPos, nestedListPos + nestedList.nodeSize)\n\n const mappedInsertPos = tr.mapping.map(insertPos)\n\n tr.insert(mappedInsertPos, Fragment.from(items))\n\n tr.setSelection(selection.map(tr.doc, tr.mapping))\n\n if (dispatch) {\n dispatch(tr)\n }\n\n return true\n}\n","import type { Node } from '@tiptap/pm/model'\nimport type { EditorState } from '@tiptap/pm/state'\n\nexport type BranchingNestedListAtCursor = {\n listItemDepth: number\n nestedList: Node\n nestedListPos: number\n insertPos: number\n items: Node[]\n}\n\n/**\n * Resolves a branching nested list immediately after the cursor when the selection is\n * collapsed at the end of a textblock inside a list item.\n *\n * @param state - The editor state to inspect.\n * @param itemName - The list item node name (for example `listItem` or `taskItem`).\n * @param wrapperNames - List wrapper node names (for example `bulletList` and `orderedList`).\n * @returns Resolved positions and nodes for hoisting, or `null` when not applicable.\n *\n * @example\n * ```ts\n * const context = getBranchingNestedListAtCursor(editor.state, 'listItem', [\n * 'bulletList',\n * 'orderedList',\n * ])\n *\n * if (context) {\n * // cursor is at the end of Item 1 before a branching nested sublist\n * }\n * ```\n */\nexport const getBranchingNestedListAtCursor = (\n state: EditorState,\n itemName: string,\n wrapperNames: string[],\n): BranchingNestedListAtCursor | null => {\n const { selection } = state\n\n if (!selection.empty) {\n return null\n }\n\n const { $from } = selection\n\n if (!$from.parent.isTextblock) {\n return null\n }\n\n if ($from.parentOffset !== $from.parent.content.size) {\n return null\n }\n\n let listItemDepth = -1\n\n for (let depth = $from.depth; depth > 0; depth -= 1) {\n if ($from.node(depth).type.name === itemName) {\n listItemDepth = depth\n break\n }\n }\n\n if (listItemDepth < 0) {\n return null\n }\n\n const listItem = $from.node(listItemDepth)\n const indexInListItem = $from.index(listItemDepth)\n\n if (indexInListItem + 1 >= listItem.childCount) {\n return null\n }\n\n const nextChild = listItem.child(indexInListItem + 1)\n\n if (!wrapperNames.includes(nextChild.type.name)) {\n return null\n }\n\n const itemType = state.schema.nodes[itemName]\n let hasBranching = false\n\n nextChild.forEach(child => {\n if (child.type === itemType && child.childCount > 1) {\n hasBranching = true\n }\n })\n\n if (!hasBranching) {\n return null\n }\n\n const nodeAfter = state.doc.resolve($from.after()).nodeAfter\n\n if (!nodeAfter || !wrapperNames.includes(nodeAfter.type.name)) {\n return null\n }\n\n const items: Node[] = []\n\n nodeAfter.forEach(child => {\n items.push(child)\n })\n\n if (items.length === 0) {\n return null\n }\n\n return {\n listItemDepth,\n nestedList: nodeAfter,\n nestedListPos: $from.after(),\n insertPos: $from.after(listItemDepth),\n items,\n }\n}\n","import type { Editor } from '@tiptap/core'\n\nimport { hoistBranchingNestedList } from './hoistBranchingNestedList.js'\n\n/**\n * Handles Delete for a list item when a branching nested sublist follows the cursor.\n *\n * @param editor - The editor instance whose state should be updated.\n * @param itemName - The list item node name (for example `listItem` or `taskItem`).\n * @param wrapperNames - List wrapper node names (for example `bulletList` and `orderedList`).\n * @returns `true` when the nested list was hoisted, otherwise `false`.\n *\n * @example\n * ```ts\n * Delete: () =>\n * handleDeleteBranchingNestedList(editor, 'listItem', ['bulletList', 'orderedList']),\n * ```\n */\nexport const handleDeleteBranchingNestedList = (\n editor: Editor,\n itemName: string,\n wrapperNames: string[],\n) => {\n return hoistBranchingNestedList(editor.state, editor.view.dispatch, itemName, wrapperNames)\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiptap/extension-list",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.25.0",
|
|
4
4
|
"description": "List extension for tiptap",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tiptap",
|
|
@@ -92,12 +92,12 @@
|
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
|
-
"@tiptap/core": "^3.
|
|
96
|
-
"@tiptap/pm": "^3.
|
|
95
|
+
"@tiptap/core": "^3.25.0",
|
|
96
|
+
"@tiptap/pm": "^3.25.0"
|
|
97
97
|
},
|
|
98
98
|
"peerDependencies": {
|
|
99
|
-
"@tiptap/core": "3.
|
|
100
|
-
"@tiptap/pm": "3.
|
|
99
|
+
"@tiptap/core": "3.25.0",
|
|
100
|
+
"@tiptap/pm": "3.25.0"
|
|
101
101
|
},
|
|
102
102
|
"scripts": {
|
|
103
103
|
"build": "tsup"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Extension } from '@tiptap/core'
|
|
2
|
+
|
|
3
|
+
import { handleDeleteBranchingNestedList } from './handleDeleteBranchingNestedList.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a high-priority keymap extension that handles Delete for branching nested lists.
|
|
7
|
+
* Kept separate from the list item node so Enter/Tab shortcuts keep their default priority.
|
|
8
|
+
*/
|
|
9
|
+
export const createBranchingListDeleteKeymap = (itemName: string, wrapperNames: string[]) => {
|
|
10
|
+
return Extension.create({
|
|
11
|
+
name: `${itemName}BranchingDeleteKeymap`,
|
|
12
|
+
priority: 101,
|
|
13
|
+
|
|
14
|
+
addKeyboardShortcuts() {
|
|
15
|
+
const handleDelete = () =>
|
|
16
|
+
handleDeleteBranchingNestedList(this.editor, itemName, wrapperNames)
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
Delete: handleDelete,
|
|
20
|
+
'Mod-Delete': handleDelete,
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { Node } from '@tiptap/pm/model'
|
|
2
|
+
import type { EditorState } from '@tiptap/pm/state'
|
|
3
|
+
|
|
4
|
+
export type BranchingNestedListAtCursor = {
|
|
5
|
+
listItemDepth: number
|
|
6
|
+
nestedList: Node
|
|
7
|
+
nestedListPos: number
|
|
8
|
+
insertPos: number
|
|
9
|
+
items: Node[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolves a branching nested list immediately after the cursor when the selection is
|
|
14
|
+
* collapsed at the end of a textblock inside a list item.
|
|
15
|
+
*
|
|
16
|
+
* @param state - The editor state to inspect.
|
|
17
|
+
* @param itemName - The list item node name (for example `listItem` or `taskItem`).
|
|
18
|
+
* @param wrapperNames - List wrapper node names (for example `bulletList` and `orderedList`).
|
|
19
|
+
* @returns Resolved positions and nodes for hoisting, or `null` when not applicable.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const context = getBranchingNestedListAtCursor(editor.state, 'listItem', [
|
|
24
|
+
* 'bulletList',
|
|
25
|
+
* 'orderedList',
|
|
26
|
+
* ])
|
|
27
|
+
*
|
|
28
|
+
* if (context) {
|
|
29
|
+
* // cursor is at the end of Item 1 before a branching nested sublist
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export const getBranchingNestedListAtCursor = (
|
|
34
|
+
state: EditorState,
|
|
35
|
+
itemName: string,
|
|
36
|
+
wrapperNames: string[],
|
|
37
|
+
): BranchingNestedListAtCursor | null => {
|
|
38
|
+
const { selection } = state
|
|
39
|
+
|
|
40
|
+
if (!selection.empty) {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { $from } = selection
|
|
45
|
+
|
|
46
|
+
if (!$from.parent.isTextblock) {
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if ($from.parentOffset !== $from.parent.content.size) {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let listItemDepth = -1
|
|
55
|
+
|
|
56
|
+
for (let depth = $from.depth; depth > 0; depth -= 1) {
|
|
57
|
+
if ($from.node(depth).type.name === itemName) {
|
|
58
|
+
listItemDepth = depth
|
|
59
|
+
break
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (listItemDepth < 0) {
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const listItem = $from.node(listItemDepth)
|
|
68
|
+
const indexInListItem = $from.index(listItemDepth)
|
|
69
|
+
|
|
70
|
+
if (indexInListItem + 1 >= listItem.childCount) {
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const nextChild = listItem.child(indexInListItem + 1)
|
|
75
|
+
|
|
76
|
+
if (!wrapperNames.includes(nextChild.type.name)) {
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const itemType = state.schema.nodes[itemName]
|
|
81
|
+
let hasBranching = false
|
|
82
|
+
|
|
83
|
+
nextChild.forEach(child => {
|
|
84
|
+
if (child.type === itemType && child.childCount > 1) {
|
|
85
|
+
hasBranching = true
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
if (!hasBranching) {
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const nodeAfter = state.doc.resolve($from.after()).nodeAfter
|
|
94
|
+
|
|
95
|
+
if (!nodeAfter || !wrapperNames.includes(nodeAfter.type.name)) {
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const items: Node[] = []
|
|
100
|
+
|
|
101
|
+
nodeAfter.forEach(child => {
|
|
102
|
+
items.push(child)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
if (items.length === 0) {
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
listItemDepth,
|
|
111
|
+
nestedList: nodeAfter,
|
|
112
|
+
nestedListPos: $from.after(),
|
|
113
|
+
insertPos: $from.after(listItemDepth),
|
|
114
|
+
items,
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Editor } from '@tiptap/core'
|
|
2
|
+
|
|
3
|
+
import { hoistBranchingNestedList } from './hoistBranchingNestedList.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Handles Delete for a list item when a branching nested sublist follows the cursor.
|
|
7
|
+
*
|
|
8
|
+
* @param editor - The editor instance whose state should be updated.
|
|
9
|
+
* @param itemName - The list item node name (for example `listItem` or `taskItem`).
|
|
10
|
+
* @param wrapperNames - List wrapper node names (for example `bulletList` and `orderedList`).
|
|
11
|
+
* @returns `true` when the nested list was hoisted, otherwise `false`.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* Delete: () =>
|
|
16
|
+
* handleDeleteBranchingNestedList(editor, 'listItem', ['bulletList', 'orderedList']),
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export const handleDeleteBranchingNestedList = (
|
|
20
|
+
editor: Editor,
|
|
21
|
+
itemName: string,
|
|
22
|
+
wrapperNames: string[],
|
|
23
|
+
) => {
|
|
24
|
+
return hoistBranchingNestedList(editor.state, editor.view.dispatch, itemName, wrapperNames)
|
|
25
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { EditorState } from '@tiptap/pm/state'
|
|
2
|
+
|
|
3
|
+
import { getBranchingNestedListAtCursor } from './getBranchingNestedListAtCursor.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns whether the cursor is at the end of a list item textblock and the next
|
|
7
|
+
* sibling is a nested list that contains at least one branching list item.
|
|
8
|
+
*
|
|
9
|
+
* @param state - The editor state to inspect.
|
|
10
|
+
* @param itemName - The list item node name (for example `listItem` or `taskItem`).
|
|
11
|
+
* @param wrapperNames - List wrapper node names (for example `bulletList` and `orderedList`).
|
|
12
|
+
* @returns `true` when {@link hoistBranchingNestedList} should handle Delete.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* if (hasBranchingNestedListAfterCursor(editor.state, 'listItem', ['bulletList', 'orderedList'])) {
|
|
17
|
+
* hoistBranchingNestedList(editor.state, editor.view.dispatch, 'listItem', [
|
|
18
|
+
* 'bulletList',
|
|
19
|
+
* 'orderedList',
|
|
20
|
+
* ])
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export const hasBranchingNestedListAfterCursor = (
|
|
25
|
+
state: EditorState,
|
|
26
|
+
itemName: string,
|
|
27
|
+
wrapperNames: string[],
|
|
28
|
+
): boolean => {
|
|
29
|
+
return getBranchingNestedListAtCursor(state, itemName, wrapperNames) !== null
|
|
30
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Fragment } from '@tiptap/pm/model'
|
|
2
|
+
import type { EditorState, Transaction } from '@tiptap/pm/state'
|
|
3
|
+
|
|
4
|
+
import { getBranchingNestedListAtCursor } from './getBranchingNestedListAtCursor.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Hoists all list items from a branching nested list after the cursor into the parent list.
|
|
8
|
+
*
|
|
9
|
+
* Use this when `joinForward` cannot restructure a nested list that contains list items
|
|
10
|
+
* with sublists (see issue #6906).
|
|
11
|
+
*
|
|
12
|
+
* @param state - The editor state to transform.
|
|
13
|
+
* @param dispatch - Optional dispatch function for the transaction.
|
|
14
|
+
* @param itemName - The list item node name (for example `listItem` or `taskItem`).
|
|
15
|
+
* @param wrapperNames - List wrapper node names (for example `bulletList` and `orderedList`).
|
|
16
|
+
* @returns `true` when the nested list was hoisted, otherwise `false`.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* // Cursor at the end of "Item 1" before a nested list with branching items.
|
|
21
|
+
* hoistBranchingNestedList(editor.state, editor.view.dispatch, 'listItem', [
|
|
22
|
+
* 'bulletList',
|
|
23
|
+
* 'orderedList',
|
|
24
|
+
* ])
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export const hoistBranchingNestedList = (
|
|
28
|
+
state: EditorState,
|
|
29
|
+
dispatch: ((tr: Transaction) => void) | undefined,
|
|
30
|
+
itemName: string,
|
|
31
|
+
wrapperNames: string[],
|
|
32
|
+
) => {
|
|
33
|
+
const context = getBranchingNestedListAtCursor(state, itemName, wrapperNames)
|
|
34
|
+
|
|
35
|
+
if (!context) {
|
|
36
|
+
return false
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const { selection } = state
|
|
40
|
+
const { nestedList, nestedListPos, insertPos, items } = context
|
|
41
|
+
const tr = state.tr
|
|
42
|
+
|
|
43
|
+
tr.delete(nestedListPos, nestedListPos + nestedList.nodeSize)
|
|
44
|
+
|
|
45
|
+
const mappedInsertPos = tr.mapping.map(insertPos)
|
|
46
|
+
|
|
47
|
+
tr.insert(mappedInsertPos, Fragment.from(items))
|
|
48
|
+
|
|
49
|
+
tr.setSelection(selection.map(tr.doc, tr.mapping))
|
|
50
|
+
|
|
51
|
+
if (dispatch) {
|
|
52
|
+
dispatch(tr)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return true
|
|
56
|
+
}
|
package/src/item/list-item.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { JSONContent, MarkdownParseHelpers, MarkdownToken } from '@tiptap/core'
|
|
2
2
|
import { mergeAttributes, Node, renderNestedMarkdownContent } from '@tiptap/core'
|
|
3
3
|
|
|
4
|
+
import { createBranchingListDeleteKeymap } from '../helpers/createBranchingListDeleteKeymap.js'
|
|
5
|
+
|
|
4
6
|
export interface ListItemOptions {
|
|
5
7
|
/**
|
|
6
8
|
* The HTML attributes for a list item node.
|
|
@@ -180,6 +182,15 @@ export const ListItem = Node.create<ListItemOptions>({
|
|
|
180
182
|
)
|
|
181
183
|
},
|
|
182
184
|
|
|
185
|
+
addExtensions() {
|
|
186
|
+
return [
|
|
187
|
+
createBranchingListDeleteKeymap(this.name, [
|
|
188
|
+
this.options.bulletListTypeName,
|
|
189
|
+
this.options.orderedListTypeName,
|
|
190
|
+
]),
|
|
191
|
+
]
|
|
192
|
+
},
|
|
193
|
+
|
|
183
194
|
addKeyboardShortcuts() {
|
|
184
195
|
return {
|
|
185
196
|
Enter: () => this.editor.commands.splitListItem(this.name),
|
|
@@ -2,10 +2,7 @@ import type { Editor } from '@tiptap/core'
|
|
|
2
2
|
import { isAtStartOfNode, isNodeActive } from '@tiptap/core'
|
|
3
3
|
import type { Node } from '@tiptap/pm/model'
|
|
4
4
|
|
|
5
|
-
import { findListItemPos } from './findListItemPos.js'
|
|
6
5
|
import { hasListBefore } from './hasListBefore.js'
|
|
7
|
-
import { hasListItemBefore } from './hasListItemBefore.js'
|
|
8
|
-
import { listItemHasSubList } from './listItemHasSubList.js'
|
|
9
6
|
|
|
10
7
|
export const handleBackspace = (editor: Editor, name: string, parentListTypes: string[]) => {
|
|
11
8
|
// this is required to still handle the undo handling
|
|
@@ -62,24 +59,8 @@ export const handleBackspace = (editor: Editor, name: string, parentListTypes: s
|
|
|
62
59
|
return false
|
|
63
60
|
}
|
|
64
61
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return false
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const $prev = editor.state.doc.resolve(listItemPos.$pos.pos - 2)
|
|
72
|
-
const prevNode = $prev.node(listItemPos.depth)
|
|
73
|
-
|
|
74
|
-
const previousListItemHasSubList = listItemHasSubList(name, editor.state, prevNode)
|
|
75
|
-
|
|
76
|
-
// if the previous item is a list item and doesn't have a sublist, join the list items
|
|
77
|
-
if (hasListItemBefore(name, editor.state) && !previousListItemHasSubList) {
|
|
78
|
-
return editor.commands.joinItemBackward()
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// otherwise in the end, a backspace should
|
|
82
|
-
// always just lift the list item if
|
|
83
|
-
// joining / merging is not possible
|
|
62
|
+
// At the start of a list item, lift it out. Top-level items split the
|
|
63
|
+
// wrapping list around them; nested items get promoted into the outer
|
|
64
|
+
// list. A second backspace then falls through to the merge branch above.
|
|
84
65
|
return editor.chain().liftListItem(name).run()
|
|
85
66
|
}
|