@tiptap/extension-list 3.26.1 → 3.27.1

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/item/index.ts","../../src/item/list-item.ts","../../src/helpers/createBranchingListDeleteKeymap.ts","../../src/helpers/hoistBranchingNestedList.ts","../../src/helpers/getBranchingNestedListAtCursor.ts","../../src/helpers/handleDeleteBranchingNestedList.ts"],"sourcesContent":["export * from './list-item.js'\n","import type { JSONContent, MarkdownParseHelpers, MarkdownToken } from '@tiptap/core'\nimport { mergeAttributes, Node, renderNestedMarkdownContent } from '@tiptap/core'\n\nimport { createBranchingListDeleteKeymap } from '../helpers/createBranchingListDeleteKeymap.js'\n\nexport interface ListItemOptions {\n /**\n * The HTML attributes for a list item node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n\n /**\n * The node type for bulletList nodes\n * @default 'bulletList'\n * @example 'myCustomBulletList'\n */\n bulletListTypeName: string\n\n /**\n * The node type for orderedList nodes\n * @default 'orderedList'\n * @example 'myCustomOrderedList'\n */\n orderedListTypeName: string\n}\n\nfunction isSameLineOrderedListToken(token: MarkdownToken): boolean {\n const nestedToken = token.tokens?.[0]\n\n return Boolean(\n token.text &&\n token.tokens?.length === 1 &&\n nestedToken?.type === 'list' &&\n nestedToken.ordered &&\n nestedToken.raw === token.text,\n )\n}\n\nfunction parseSameLineOrderedListText(text: string, helpers: MarkdownParseHelpers): JSONContent[] {\n if (helpers.tokenizeInline) {\n return helpers.parseInline(helpers.tokenizeInline(text))\n }\n\n return helpers.parseInline([\n {\n type: 'text',\n raw: text,\n text,\n },\n ])\n}\n\n/**\n * This extension allows you to create list items.\n * @see https://www.tiptap.dev/api/nodes/list-item\n */\nexport const ListItem = Node.create<ListItemOptions>({\n name: 'listItem',\n\n addOptions() {\n return {\n HTMLAttributes: {},\n bulletListTypeName: 'bulletList',\n orderedListTypeName: 'orderedList',\n }\n },\n\n content: 'paragraph block*',\n\n defining: true,\n\n parseHTML() {\n return [\n {\n tag: 'li',\n },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['li', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n\n markdownTokenName: 'list_item',\n\n parseMarkdown: (token, helpers) => {\n if (token.type !== 'list_item') {\n return []\n }\n\n const parseBlockChildren = helpers.parseBlockChildren ?? helpers.parseChildren\n let content: any[] = []\n\n if (token.tokens && token.tokens.length > 0) {\n if (isSameLineOrderedListToken(token)) {\n return {\n type: 'listItem',\n content: [\n {\n type: 'paragraph',\n content: parseSameLineOrderedListText(token.text || '', helpers),\n },\n ],\n }\n }\n\n // Check if we have paragraph tokens (complex list items)\n const hasParagraphTokens = token.tokens.some(t => t.type === 'paragraph')\n\n if (hasParagraphTokens) {\n // If we have paragraph tokens, parse them as block elements\n content = parseBlockChildren(token.tokens)\n } else {\n // Check if the first token is a text token with nested inline tokens\n const firstToken = token.tokens[0]\n\n if (\n firstToken &&\n firstToken.type === 'text' &&\n firstToken.tokens &&\n firstToken.tokens.length > 0\n ) {\n // Parse the inline content from the text token\n const inlineContent = helpers.parseInline(firstToken.tokens)\n\n // Start with the paragraph containing the inline content\n content = [\n {\n type: 'paragraph',\n content: inlineContent,\n },\n ]\n\n // If there are additional tokens after the first text token (like nested lists),\n // parse them as block elements and add them\n if (token.tokens.length > 1) {\n const remainingTokens = token.tokens.slice(1)\n const additionalContent = parseBlockChildren(remainingTokens)\n content.push(...additionalContent)\n }\n } else {\n // Fallback: parse all tokens as block elements\n content = parseBlockChildren(token.tokens)\n }\n }\n }\n\n // Ensure we always have at least an empty paragraph\n if (content.length === 0) {\n content = [\n {\n type: 'paragraph',\n content: [],\n },\n ]\n }\n\n return {\n type: 'listItem',\n content,\n }\n },\n\n renderMarkdown: (node, h, ctx) => {\n return renderNestedMarkdownContent(\n node,\n h,\n (context: any) => {\n if (context.parentType === 'bulletList') {\n return '- '\n }\n if (context.parentType === 'orderedList') {\n const start = context.meta?.parentAttrs?.start || 1\n return `${start + context.index}. `\n }\n // Fallback to bullet list for unknown parent types\n return '- '\n },\n ctx,\n )\n },\n\n addExtensions() {\n return [\n createBranchingListDeleteKeymap(this.name, [\n this.options.bulletListTypeName,\n this.options.orderedListTypeName,\n ]),\n ]\n },\n\n addKeyboardShortcuts() {\n return {\n Enter: () => this.editor.commands.splitListItem(this.name),\n Tab: () => this.editor.commands.sinkListItem(this.name),\n 'Shift-Tab': () => this.editor.commands.liftListItem(this.name),\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"]}
1
+ {"version":3,"sources":["../../src/item/index.ts","../../src/item/list-item.ts","../../src/helpers/createBranchingListDeleteKeymap.ts","../../src/helpers/hoistBranchingNestedList.ts","../../src/helpers/getBranchingNestedListAtCursor.ts","../../src/helpers/handleDeleteBranchingNestedList.ts","../../src/ordered-list/roman.ts"],"sourcesContent":["export * from './list-item.js'\n","import type { JSONContent, MarkdownParseHelpers, MarkdownToken } from '@tiptap/core'\nimport { mergeAttributes, Node, renderNestedMarkdownContent } from '@tiptap/core'\n\nimport { createBranchingListDeleteKeymap } from '../helpers/createBranchingListDeleteKeymap.js'\n\nimport { getListMarker } from '../ordered-list/roman.js'\n\nexport interface ListItemOptions {\n /**\n * The HTML attributes for a list item node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n\n /**\n * The node type for bulletList nodes\n * @default 'bulletList'\n * @example 'myCustomBulletList'\n */\n bulletListTypeName: string\n\n /**\n * The node type for orderedList nodes\n * @default 'orderedList'\n * @example 'myCustomOrderedList'\n */\n orderedListTypeName: string\n}\n\nfunction isSameLineOrderedListToken(token: MarkdownToken): boolean {\n const nestedToken = token.tokens?.[0]\n\n return Boolean(\n token.text &&\n token.tokens?.length === 1 &&\n nestedToken?.type === 'list' &&\n nestedToken.ordered &&\n nestedToken.raw === token.text,\n )\n}\n\nfunction parseSameLineOrderedListText(text: string, helpers: MarkdownParseHelpers): JSONContent[] {\n if (helpers.tokenizeInline) {\n return helpers.parseInline(helpers.tokenizeInline(text))\n }\n\n return helpers.parseInline([\n {\n type: 'text',\n raw: text,\n text,\n },\n ])\n}\n\n/**\n * This extension allows you to create list items.\n * @see https://www.tiptap.dev/api/nodes/list-item\n */\nexport const ListItem = Node.create<ListItemOptions>({\n name: 'listItem',\n\n addOptions() {\n return {\n HTMLAttributes: {},\n bulletListTypeName: 'bulletList',\n orderedListTypeName: 'orderedList',\n }\n },\n\n content: 'paragraph block*',\n\n defining: true,\n\n parseHTML() {\n return [\n {\n tag: 'li',\n },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['li', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n\n markdownTokenName: 'list_item',\n\n parseMarkdown: (token, helpers) => {\n if (token.type !== 'list_item') {\n return []\n }\n\n const parseBlockChildren = helpers.parseBlockChildren ?? helpers.parseChildren\n let content: any[] = []\n\n if (token.tokens && token.tokens.length > 0) {\n if (isSameLineOrderedListToken(token)) {\n return {\n type: 'listItem',\n content: [\n {\n type: 'paragraph',\n content: parseSameLineOrderedListText(token.text || '', helpers),\n },\n ],\n }\n }\n\n // Check if we have paragraph tokens (complex list items)\n const hasParagraphTokens = token.tokens.some(t => t.type === 'paragraph')\n\n if (hasParagraphTokens) {\n // If we have paragraph tokens, parse them as block elements\n content = parseBlockChildren(token.tokens)\n } else {\n // Check if the first token is a text token with nested inline tokens\n const firstToken = token.tokens[0]\n\n if (\n firstToken &&\n firstToken.type === 'text' &&\n firstToken.tokens &&\n firstToken.tokens.length > 0\n ) {\n // Parse the inline content from the text token\n const inlineContent = helpers.parseInline(firstToken.tokens)\n\n // Start with the paragraph containing the inline content\n content = [\n {\n type: 'paragraph',\n content: inlineContent,\n },\n ]\n\n // If there are additional tokens after the first text token (like nested lists),\n // parse them as block elements and add them\n if (token.tokens.length > 1) {\n const remainingTokens = token.tokens.slice(1)\n const additionalContent = parseBlockChildren(remainingTokens)\n content.push(...additionalContent)\n }\n } else {\n // Fallback: parse all tokens as block elements\n content = parseBlockChildren(token.tokens)\n }\n }\n }\n\n // Ensure we always have at least an empty paragraph\n if (content.length === 0) {\n content = [\n {\n type: 'paragraph',\n content: [],\n },\n ]\n }\n\n return {\n type: 'listItem',\n content,\n }\n },\n\n renderMarkdown: (node, h, ctx) => {\n return renderNestedMarkdownContent(\n node,\n h,\n (context: any) => {\n if (context.parentType === 'bulletList') {\n return '- '\n }\n if (context.parentType === 'orderedList') {\n const start = context.meta?.parentAttrs?.start || 1\n const type = context.meta?.parentAttrs?.type as string | undefined\n const index = start - 1 + (context.index || 0)\n return getListMarker(type, index, '. ')\n }\n // Fallback to bullet list for unknown parent types\n return '- '\n },\n ctx,\n )\n },\n\n addExtensions() {\n return [\n createBranchingListDeleteKeymap(this.name, [\n this.options.bulletListTypeName,\n this.options.orderedListTypeName,\n ]),\n ]\n },\n\n addKeyboardShortcuts() {\n return {\n Enter: () => this.editor.commands.splitListItem(this.name),\n Tab: () => this.editor.commands.sinkListItem(this.name),\n 'Shift-Tab': () => this.editor.commands.liftListItem(this.name),\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","const ROMAN_NUMERALS: [number, string][] = [\n [1000, 'm'],\n [900, 'cm'],\n [500, 'd'],\n [400, 'cd'],\n [100, 'c'],\n [90, 'xc'],\n [50, 'l'],\n [40, 'xl'],\n [10, 'x'],\n [9, 'ix'],\n [5, 'v'],\n [4, 'iv'],\n [1, 'i'],\n]\n\nconst ALPHA_NUMERALS = 'abcdefghijklmnopqrstuvwxyz'\n\n/** Alpha list markers support at most 2 letters (a–z, aa–zz), matching {@link fromAlpha}. */\nexport const ORDERED_LIST_ALPHA_MARKER_PATTERN = '[a-zA-Z]{1,2}'\n\n/**\n * Marker segment for ordered list lines: numeric, roman, or 1–2 letter alpha.\n * Roman is matched before alpha so \"iii\" is roman; invalid romans like \"aa\" fall through to alpha.\n */\nexport const ORDERED_LIST_MARKER_PATTERN = String.raw`\\d+|[ivxlcdmIVXLCDM]+|${ORDERED_LIST_ALPHA_MARKER_PATTERN}`\n\n/**\n * Convert a number to lowercase roman numerals.\n * @example toRoman(1) // 'i'\n * @example toRoman(4) // 'iv'\n */\nexport function toRoman(num: number): string {\n let remaining = num\n let result = ''\n\n for (const [value, numeral] of ROMAN_NUMERALS) {\n while (remaining >= value) {\n result += numeral\n remaining -= value\n }\n }\n\n return result\n}\n\n/**\n * Convert a number to uppercase roman numerals.\n * @example toRomanUpper(1) // 'I'\n * @example toRomanUpper(4) // 'IV'\n */\nexport function toRomanUpper(num: number): string {\n return toRoman(num).toUpperCase()\n}\n\nfunction fromRoman(roman: string): number {\n const lower = roman.toLowerCase()\n let index = 0\n let result = 0\n\n while (index < lower.length) {\n let matched = false\n\n for (const [value, numeral] of ROMAN_NUMERALS) {\n if (lower.startsWith(numeral, index)) {\n result += value\n index += numeral.length\n matched = true\n break\n }\n }\n\n if (!matched) {\n return 0\n }\n }\n\n return result\n}\n\nfunction isValidRoman(marker: string): boolean {\n if (!/^[ivxlcdmIVXLCDM]+$/.test(marker)) {\n return false\n }\n\n const value = fromRoman(marker)\n\n if (value <= 0) {\n return false\n }\n\n const expected = marker === marker.toLowerCase() ? toRoman(value) : toRomanUpper(value)\n\n return expected === marker\n}\n\nfunction fromAlpha(marker: string): number {\n const lower = marker.toLowerCase()\n\n if (lower.length === 1) {\n return lower.charCodeAt(0) - 'a'.charCodeAt(0) + 1\n }\n\n if (lower.length === 2) {\n const first = lower.charCodeAt(0) - 'a'.charCodeAt(0)\n const second = lower.charCodeAt(1) - 'a'.charCodeAt(0)\n\n return (first + 1) * 26 + second + 1\n }\n\n return 0\n}\n\nfunction toRomanAlpha(num: number): string {\n if (num <= 26) {\n return ALPHA_NUMERALS[num - 1]\n }\n\n const first = Math.floor((num - 1) / 26) - 1\n const second = (num - 1) % 26\n\n if (first < 0) {\n return ALPHA_NUMERALS[second]\n }\n\n return ALPHA_NUMERALS[first] + ALPHA_NUMERALS[second]\n}\n\n/**\n * Extract the list marker type from a marker string.\n * Supports \"1\", \"a\", \"A\", \"i\", \"I\" marker styles.\n *\n * @param marker The text content of the list marker (e.g. \"a\", \"1\", \"iii\", \"b\")\n * @returns The normalized type string, or undefined for default numeric type\n */\nexport function detectMarkerType(marker: string): string | undefined {\n if (!marker || /^\\d+$/.test(marker)) {\n return undefined\n }\n\n if (isValidRoman(marker)) {\n return marker === marker.toLowerCase() ? 'i' : 'I'\n }\n\n if (/^[a-z]{1,2}$/.test(marker)) {\n return 'a'\n }\n\n if (/^[A-Z]{1,2}$/.test(marker)) {\n return 'A'\n }\n\n return undefined\n}\n\n/**\n * Convert a list marker string to its numeric start position.\n *\n * @param marker The text content of the list marker (e.g. \"3\", \"b\", \"II\")\n * @returns The 1-based start value for the ordered list\n */\nexport function markerToStart(marker: string): number {\n if (/^\\d+$/.test(marker)) {\n return parseInt(marker, 10)\n }\n\n const type = detectMarkerType(marker)\n\n if (type === 'i' || type === 'I') {\n return fromRoman(marker)\n }\n\n if (type === 'a' || type === 'A') {\n const start = fromAlpha(marker)\n\n return start > 0 ? start : 1\n }\n\n const parsed = parseInt(marker, 10)\n\n return Number.isNaN(parsed) ? 1 : parsed\n}\n\nfunction startToMarker(type: string, start: number): string {\n if (type === 'numeric') {\n return String(start)\n }\n\n switch (type) {\n case 'a':\n return toRomanAlpha(start)\n case 'A':\n return toRomanAlpha(start).toUpperCase()\n case 'i':\n return toRoman(start)\n case 'I':\n return toRomanUpper(start)\n default:\n return String(start)\n }\n}\n\n/**\n * Returns true when all markers share the same style and increment by 1.\n * Style is inferred from the first marker so ambiguous letters (e.g. \"c\", \"i\")\n * are not re-classified differently on later lines.\n */\nexport function areOrderedListMarkersSequential(markers: string[]): boolean {\n if (markers.length === 0) {\n return false\n }\n\n const firstType = detectMarkerType(markers[0]) ?? 'numeric'\n const firstStart = markerToStart(markers[0])\n\n if (firstStart < 1) {\n return false\n }\n\n for (let i = 0; i < markers.length; i++) {\n const expected = startToMarker(firstType, firstStart + i)\n\n if (markers[i] !== expected) {\n return false\n }\n }\n\n return true\n}\n\nexport interface ParsedListMarker {\n type?: string\n start: number\n}\n\n/**\n * Parse a list marker into HTML ordered-list attrs (type + start).\n */\nexport function parseListMarker(marker: string): ParsedListMarker {\n return {\n type: detectMarkerType(marker),\n start: markerToStart(marker),\n }\n}\n\n/**\n * Build orderedList node attrs from the first list item marker.\n */\nexport function buildOrderedListAttrsFromMarker(marker: string): Record<string, string | number> {\n const { type, start } = parseListMarker(marker)\n const attrs: Record<string, string | number> = {}\n\n if (type) {\n attrs.type = type\n }\n\n if (start !== 1) {\n attrs.start = start\n }\n\n return attrs\n}\n\n/**\n * Returns the list marker prefix for a given item at a given index.\n *\n * @param type The list type attribute (e.g. \"a\", \"A\", \"i\", \"I\", null/undefined for default)\n * @param index The zero-based index of the list item\n * @param separator The separator to use (default: \". \")\n * @returns The marker string (e.g. \"a. \", \"I. \", \"1. \")\n */\nexport function getListMarker(\n type: string | null | undefined,\n index: number,\n separator = '. ',\n): string {\n const position = index + 1\n\n if (!type || type === '1') {\n return `${position}${separator}`\n }\n\n switch (type) {\n case 'a':\n return `${toRomanAlpha(position)}${separator}`\n case 'A':\n return `${toRomanAlpha(position).toUpperCase()}${separator}`\n case 'i':\n return `${toRoman(position)}${separator}`\n case 'I':\n return `${toRomanUpper(position)}${separator}`\n default:\n return `${position}${separator}`\n }\n}\n"],"mappings":"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","names":["import_core"]}
@@ -108,6 +108,69 @@ var createBranchingListDeleteKeymap = (itemName, wrapperNames) => {
108
108
  });
109
109
  };
110
110
 
111
+ // src/ordered-list/roman.ts
112
+ var ROMAN_NUMERALS = [
113
+ [1e3, "m"],
114
+ [900, "cm"],
115
+ [500, "d"],
116
+ [400, "cd"],
117
+ [100, "c"],
118
+ [90, "xc"],
119
+ [50, "l"],
120
+ [40, "xl"],
121
+ [10, "x"],
122
+ [9, "ix"],
123
+ [5, "v"],
124
+ [4, "iv"],
125
+ [1, "i"]
126
+ ];
127
+ var ALPHA_NUMERALS = "abcdefghijklmnopqrstuvwxyz";
128
+ var ORDERED_LIST_ALPHA_MARKER_PATTERN = "[a-zA-Z]{1,2}";
129
+ var ORDERED_LIST_MARKER_PATTERN = String.raw`\d+|[ivxlcdmIVXLCDM]+|${ORDERED_LIST_ALPHA_MARKER_PATTERN}`;
130
+ function toRoman(num) {
131
+ let remaining = num;
132
+ let result = "";
133
+ for (const [value, numeral] of ROMAN_NUMERALS) {
134
+ while (remaining >= value) {
135
+ result += numeral;
136
+ remaining -= value;
137
+ }
138
+ }
139
+ return result;
140
+ }
141
+ function toRomanUpper(num) {
142
+ return toRoman(num).toUpperCase();
143
+ }
144
+ function toRomanAlpha(num) {
145
+ if (num <= 26) {
146
+ return ALPHA_NUMERALS[num - 1];
147
+ }
148
+ const first = Math.floor((num - 1) / 26) - 1;
149
+ const second = (num - 1) % 26;
150
+ if (first < 0) {
151
+ return ALPHA_NUMERALS[second];
152
+ }
153
+ return ALPHA_NUMERALS[first] + ALPHA_NUMERALS[second];
154
+ }
155
+ function getListMarker(type, index, separator = ". ") {
156
+ const position = index + 1;
157
+ if (!type || type === "1") {
158
+ return `${position}${separator}`;
159
+ }
160
+ switch (type) {
161
+ case "a":
162
+ return `${toRomanAlpha(position)}${separator}`;
163
+ case "A":
164
+ return `${toRomanAlpha(position).toUpperCase()}${separator}`;
165
+ case "i":
166
+ return `${toRoman(position)}${separator}`;
167
+ case "I":
168
+ return `${toRomanUpper(position)}${separator}`;
169
+ default:
170
+ return `${position}${separator}`;
171
+ }
172
+ }
173
+
111
174
  // src/item/list-item.ts
112
175
  function isSameLineOrderedListToken(token) {
113
176
  var _a, _b;
@@ -210,13 +273,15 @@ var ListItem = Node.create({
210
273
  node,
211
274
  h,
212
275
  (context) => {
213
- var _a, _b;
276
+ var _a, _b, _c, _d;
214
277
  if (context.parentType === "bulletList") {
215
278
  return "- ";
216
279
  }
217
280
  if (context.parentType === "orderedList") {
218
281
  const start = ((_b = (_a = context.meta) == null ? void 0 : _a.parentAttrs) == null ? void 0 : _b.start) || 1;
219
- return `${start + context.index}. `;
282
+ const type = (_d = (_c = context.meta) == null ? void 0 : _c.parentAttrs) == null ? void 0 : _d.type;
283
+ const index = start - 1 + (context.index || 0);
284
+ return getListMarker(type, index, ". ");
220
285
  }
221
286
  return "- ";
222
287
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/item/list-item.ts","../../src/helpers/createBranchingListDeleteKeymap.ts","../../src/helpers/hoistBranchingNestedList.ts","../../src/helpers/getBranchingNestedListAtCursor.ts","../../src/helpers/handleDeleteBranchingNestedList.ts"],"sourcesContent":["import type { JSONContent, MarkdownParseHelpers, MarkdownToken } from '@tiptap/core'\nimport { mergeAttributes, Node, renderNestedMarkdownContent } from '@tiptap/core'\n\nimport { createBranchingListDeleteKeymap } from '../helpers/createBranchingListDeleteKeymap.js'\n\nexport interface ListItemOptions {\n /**\n * The HTML attributes for a list item node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n\n /**\n * The node type for bulletList nodes\n * @default 'bulletList'\n * @example 'myCustomBulletList'\n */\n bulletListTypeName: string\n\n /**\n * The node type for orderedList nodes\n * @default 'orderedList'\n * @example 'myCustomOrderedList'\n */\n orderedListTypeName: string\n}\n\nfunction isSameLineOrderedListToken(token: MarkdownToken): boolean {\n const nestedToken = token.tokens?.[0]\n\n return Boolean(\n token.text &&\n token.tokens?.length === 1 &&\n nestedToken?.type === 'list' &&\n nestedToken.ordered &&\n nestedToken.raw === token.text,\n )\n}\n\nfunction parseSameLineOrderedListText(text: string, helpers: MarkdownParseHelpers): JSONContent[] {\n if (helpers.tokenizeInline) {\n return helpers.parseInline(helpers.tokenizeInline(text))\n }\n\n return helpers.parseInline([\n {\n type: 'text',\n raw: text,\n text,\n },\n ])\n}\n\n/**\n * This extension allows you to create list items.\n * @see https://www.tiptap.dev/api/nodes/list-item\n */\nexport const ListItem = Node.create<ListItemOptions>({\n name: 'listItem',\n\n addOptions() {\n return {\n HTMLAttributes: {},\n bulletListTypeName: 'bulletList',\n orderedListTypeName: 'orderedList',\n }\n },\n\n content: 'paragraph block*',\n\n defining: true,\n\n parseHTML() {\n return [\n {\n tag: 'li',\n },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['li', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n\n markdownTokenName: 'list_item',\n\n parseMarkdown: (token, helpers) => {\n if (token.type !== 'list_item') {\n return []\n }\n\n const parseBlockChildren = helpers.parseBlockChildren ?? helpers.parseChildren\n let content: any[] = []\n\n if (token.tokens && token.tokens.length > 0) {\n if (isSameLineOrderedListToken(token)) {\n return {\n type: 'listItem',\n content: [\n {\n type: 'paragraph',\n content: parseSameLineOrderedListText(token.text || '', helpers),\n },\n ],\n }\n }\n\n // Check if we have paragraph tokens (complex list items)\n const hasParagraphTokens = token.tokens.some(t => t.type === 'paragraph')\n\n if (hasParagraphTokens) {\n // If we have paragraph tokens, parse them as block elements\n content = parseBlockChildren(token.tokens)\n } else {\n // Check if the first token is a text token with nested inline tokens\n const firstToken = token.tokens[0]\n\n if (\n firstToken &&\n firstToken.type === 'text' &&\n firstToken.tokens &&\n firstToken.tokens.length > 0\n ) {\n // Parse the inline content from the text token\n const inlineContent = helpers.parseInline(firstToken.tokens)\n\n // Start with the paragraph containing the inline content\n content = [\n {\n type: 'paragraph',\n content: inlineContent,\n },\n ]\n\n // If there are additional tokens after the first text token (like nested lists),\n // parse them as block elements and add them\n if (token.tokens.length > 1) {\n const remainingTokens = token.tokens.slice(1)\n const additionalContent = parseBlockChildren(remainingTokens)\n content.push(...additionalContent)\n }\n } else {\n // Fallback: parse all tokens as block elements\n content = parseBlockChildren(token.tokens)\n }\n }\n }\n\n // Ensure we always have at least an empty paragraph\n if (content.length === 0) {\n content = [\n {\n type: 'paragraph',\n content: [],\n },\n ]\n }\n\n return {\n type: 'listItem',\n content,\n }\n },\n\n renderMarkdown: (node, h, ctx) => {\n return renderNestedMarkdownContent(\n node,\n h,\n (context: any) => {\n if (context.parentType === 'bulletList') {\n return '- '\n }\n if (context.parentType === 'orderedList') {\n const start = context.meta?.parentAttrs?.start || 1\n return `${start + context.index}. `\n }\n // Fallback to bullet list for unknown parent types\n return '- '\n },\n ctx,\n )\n },\n\n addExtensions() {\n return [\n createBranchingListDeleteKeymap(this.name, [\n this.options.bulletListTypeName,\n this.options.orderedListTypeName,\n ]),\n ]\n },\n\n addKeyboardShortcuts() {\n return {\n Enter: () => this.editor.commands.splitListItem(this.name),\n Tab: () => this.editor.commands.sinkListItem(this.name),\n 'Shift-Tab': () => this.editor.commands.liftListItem(this.name),\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":[]}
1
+ {"version":3,"sources":["../../src/item/list-item.ts","../../src/helpers/createBranchingListDeleteKeymap.ts","../../src/helpers/hoistBranchingNestedList.ts","../../src/helpers/getBranchingNestedListAtCursor.ts","../../src/helpers/handleDeleteBranchingNestedList.ts","../../src/ordered-list/roman.ts"],"sourcesContent":["import type { JSONContent, MarkdownParseHelpers, MarkdownToken } from '@tiptap/core'\nimport { mergeAttributes, Node, renderNestedMarkdownContent } from '@tiptap/core'\n\nimport { createBranchingListDeleteKeymap } from '../helpers/createBranchingListDeleteKeymap.js'\n\nimport { getListMarker } from '../ordered-list/roman.js'\n\nexport interface ListItemOptions {\n /**\n * The HTML attributes for a list item node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n\n /**\n * The node type for bulletList nodes\n * @default 'bulletList'\n * @example 'myCustomBulletList'\n */\n bulletListTypeName: string\n\n /**\n * The node type for orderedList nodes\n * @default 'orderedList'\n * @example 'myCustomOrderedList'\n */\n orderedListTypeName: string\n}\n\nfunction isSameLineOrderedListToken(token: MarkdownToken): boolean {\n const nestedToken = token.tokens?.[0]\n\n return Boolean(\n token.text &&\n token.tokens?.length === 1 &&\n nestedToken?.type === 'list' &&\n nestedToken.ordered &&\n nestedToken.raw === token.text,\n )\n}\n\nfunction parseSameLineOrderedListText(text: string, helpers: MarkdownParseHelpers): JSONContent[] {\n if (helpers.tokenizeInline) {\n return helpers.parseInline(helpers.tokenizeInline(text))\n }\n\n return helpers.parseInline([\n {\n type: 'text',\n raw: text,\n text,\n },\n ])\n}\n\n/**\n * This extension allows you to create list items.\n * @see https://www.tiptap.dev/api/nodes/list-item\n */\nexport const ListItem = Node.create<ListItemOptions>({\n name: 'listItem',\n\n addOptions() {\n return {\n HTMLAttributes: {},\n bulletListTypeName: 'bulletList',\n orderedListTypeName: 'orderedList',\n }\n },\n\n content: 'paragraph block*',\n\n defining: true,\n\n parseHTML() {\n return [\n {\n tag: 'li',\n },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['li', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n\n markdownTokenName: 'list_item',\n\n parseMarkdown: (token, helpers) => {\n if (token.type !== 'list_item') {\n return []\n }\n\n const parseBlockChildren = helpers.parseBlockChildren ?? helpers.parseChildren\n let content: any[] = []\n\n if (token.tokens && token.tokens.length > 0) {\n if (isSameLineOrderedListToken(token)) {\n return {\n type: 'listItem',\n content: [\n {\n type: 'paragraph',\n content: parseSameLineOrderedListText(token.text || '', helpers),\n },\n ],\n }\n }\n\n // Check if we have paragraph tokens (complex list items)\n const hasParagraphTokens = token.tokens.some(t => t.type === 'paragraph')\n\n if (hasParagraphTokens) {\n // If we have paragraph tokens, parse them as block elements\n content = parseBlockChildren(token.tokens)\n } else {\n // Check if the first token is a text token with nested inline tokens\n const firstToken = token.tokens[0]\n\n if (\n firstToken &&\n firstToken.type === 'text' &&\n firstToken.tokens &&\n firstToken.tokens.length > 0\n ) {\n // Parse the inline content from the text token\n const inlineContent = helpers.parseInline(firstToken.tokens)\n\n // Start with the paragraph containing the inline content\n content = [\n {\n type: 'paragraph',\n content: inlineContent,\n },\n ]\n\n // If there are additional tokens after the first text token (like nested lists),\n // parse them as block elements and add them\n if (token.tokens.length > 1) {\n const remainingTokens = token.tokens.slice(1)\n const additionalContent = parseBlockChildren(remainingTokens)\n content.push(...additionalContent)\n }\n } else {\n // Fallback: parse all tokens as block elements\n content = parseBlockChildren(token.tokens)\n }\n }\n }\n\n // Ensure we always have at least an empty paragraph\n if (content.length === 0) {\n content = [\n {\n type: 'paragraph',\n content: [],\n },\n ]\n }\n\n return {\n type: 'listItem',\n content,\n }\n },\n\n renderMarkdown: (node, h, ctx) => {\n return renderNestedMarkdownContent(\n node,\n h,\n (context: any) => {\n if (context.parentType === 'bulletList') {\n return '- '\n }\n if (context.parentType === 'orderedList') {\n const start = context.meta?.parentAttrs?.start || 1\n const type = context.meta?.parentAttrs?.type as string | undefined\n const index = start - 1 + (context.index || 0)\n return getListMarker(type, index, '. ')\n }\n // Fallback to bullet list for unknown parent types\n return '- '\n },\n ctx,\n )\n },\n\n addExtensions() {\n return [\n createBranchingListDeleteKeymap(this.name, [\n this.options.bulletListTypeName,\n this.options.orderedListTypeName,\n ]),\n ]\n },\n\n addKeyboardShortcuts() {\n return {\n Enter: () => this.editor.commands.splitListItem(this.name),\n Tab: () => this.editor.commands.sinkListItem(this.name),\n 'Shift-Tab': () => this.editor.commands.liftListItem(this.name),\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","const ROMAN_NUMERALS: [number, string][] = [\n [1000, 'm'],\n [900, 'cm'],\n [500, 'd'],\n [400, 'cd'],\n [100, 'c'],\n [90, 'xc'],\n [50, 'l'],\n [40, 'xl'],\n [10, 'x'],\n [9, 'ix'],\n [5, 'v'],\n [4, 'iv'],\n [1, 'i'],\n]\n\nconst ALPHA_NUMERALS = 'abcdefghijklmnopqrstuvwxyz'\n\n/** Alpha list markers support at most 2 letters (a–z, aa–zz), matching {@link fromAlpha}. */\nexport const ORDERED_LIST_ALPHA_MARKER_PATTERN = '[a-zA-Z]{1,2}'\n\n/**\n * Marker segment for ordered list lines: numeric, roman, or 1–2 letter alpha.\n * Roman is matched before alpha so \"iii\" is roman; invalid romans like \"aa\" fall through to alpha.\n */\nexport const ORDERED_LIST_MARKER_PATTERN = String.raw`\\d+|[ivxlcdmIVXLCDM]+|${ORDERED_LIST_ALPHA_MARKER_PATTERN}`\n\n/**\n * Convert a number to lowercase roman numerals.\n * @example toRoman(1) // 'i'\n * @example toRoman(4) // 'iv'\n */\nexport function toRoman(num: number): string {\n let remaining = num\n let result = ''\n\n for (const [value, numeral] of ROMAN_NUMERALS) {\n while (remaining >= value) {\n result += numeral\n remaining -= value\n }\n }\n\n return result\n}\n\n/**\n * Convert a number to uppercase roman numerals.\n * @example toRomanUpper(1) // 'I'\n * @example toRomanUpper(4) // 'IV'\n */\nexport function toRomanUpper(num: number): string {\n return toRoman(num).toUpperCase()\n}\n\nfunction fromRoman(roman: string): number {\n const lower = roman.toLowerCase()\n let index = 0\n let result = 0\n\n while (index < lower.length) {\n let matched = false\n\n for (const [value, numeral] of ROMAN_NUMERALS) {\n if (lower.startsWith(numeral, index)) {\n result += value\n index += numeral.length\n matched = true\n break\n }\n }\n\n if (!matched) {\n return 0\n }\n }\n\n return result\n}\n\nfunction isValidRoman(marker: string): boolean {\n if (!/^[ivxlcdmIVXLCDM]+$/.test(marker)) {\n return false\n }\n\n const value = fromRoman(marker)\n\n if (value <= 0) {\n return false\n }\n\n const expected = marker === marker.toLowerCase() ? toRoman(value) : toRomanUpper(value)\n\n return expected === marker\n}\n\nfunction fromAlpha(marker: string): number {\n const lower = marker.toLowerCase()\n\n if (lower.length === 1) {\n return lower.charCodeAt(0) - 'a'.charCodeAt(0) + 1\n }\n\n if (lower.length === 2) {\n const first = lower.charCodeAt(0) - 'a'.charCodeAt(0)\n const second = lower.charCodeAt(1) - 'a'.charCodeAt(0)\n\n return (first + 1) * 26 + second + 1\n }\n\n return 0\n}\n\nfunction toRomanAlpha(num: number): string {\n if (num <= 26) {\n return ALPHA_NUMERALS[num - 1]\n }\n\n const first = Math.floor((num - 1) / 26) - 1\n const second = (num - 1) % 26\n\n if (first < 0) {\n return ALPHA_NUMERALS[second]\n }\n\n return ALPHA_NUMERALS[first] + ALPHA_NUMERALS[second]\n}\n\n/**\n * Extract the list marker type from a marker string.\n * Supports \"1\", \"a\", \"A\", \"i\", \"I\" marker styles.\n *\n * @param marker The text content of the list marker (e.g. \"a\", \"1\", \"iii\", \"b\")\n * @returns The normalized type string, or undefined for default numeric type\n */\nexport function detectMarkerType(marker: string): string | undefined {\n if (!marker || /^\\d+$/.test(marker)) {\n return undefined\n }\n\n if (isValidRoman(marker)) {\n return marker === marker.toLowerCase() ? 'i' : 'I'\n }\n\n if (/^[a-z]{1,2}$/.test(marker)) {\n return 'a'\n }\n\n if (/^[A-Z]{1,2}$/.test(marker)) {\n return 'A'\n }\n\n return undefined\n}\n\n/**\n * Convert a list marker string to its numeric start position.\n *\n * @param marker The text content of the list marker (e.g. \"3\", \"b\", \"II\")\n * @returns The 1-based start value for the ordered list\n */\nexport function markerToStart(marker: string): number {\n if (/^\\d+$/.test(marker)) {\n return parseInt(marker, 10)\n }\n\n const type = detectMarkerType(marker)\n\n if (type === 'i' || type === 'I') {\n return fromRoman(marker)\n }\n\n if (type === 'a' || type === 'A') {\n const start = fromAlpha(marker)\n\n return start > 0 ? start : 1\n }\n\n const parsed = parseInt(marker, 10)\n\n return Number.isNaN(parsed) ? 1 : parsed\n}\n\nfunction startToMarker(type: string, start: number): string {\n if (type === 'numeric') {\n return String(start)\n }\n\n switch (type) {\n case 'a':\n return toRomanAlpha(start)\n case 'A':\n return toRomanAlpha(start).toUpperCase()\n case 'i':\n return toRoman(start)\n case 'I':\n return toRomanUpper(start)\n default:\n return String(start)\n }\n}\n\n/**\n * Returns true when all markers share the same style and increment by 1.\n * Style is inferred from the first marker so ambiguous letters (e.g. \"c\", \"i\")\n * are not re-classified differently on later lines.\n */\nexport function areOrderedListMarkersSequential(markers: string[]): boolean {\n if (markers.length === 0) {\n return false\n }\n\n const firstType = detectMarkerType(markers[0]) ?? 'numeric'\n const firstStart = markerToStart(markers[0])\n\n if (firstStart < 1) {\n return false\n }\n\n for (let i = 0; i < markers.length; i++) {\n const expected = startToMarker(firstType, firstStart + i)\n\n if (markers[i] !== expected) {\n return false\n }\n }\n\n return true\n}\n\nexport interface ParsedListMarker {\n type?: string\n start: number\n}\n\n/**\n * Parse a list marker into HTML ordered-list attrs (type + start).\n */\nexport function parseListMarker(marker: string): ParsedListMarker {\n return {\n type: detectMarkerType(marker),\n start: markerToStart(marker),\n }\n}\n\n/**\n * Build orderedList node attrs from the first list item marker.\n */\nexport function buildOrderedListAttrsFromMarker(marker: string): Record<string, string | number> {\n const { type, start } = parseListMarker(marker)\n const attrs: Record<string, string | number> = {}\n\n if (type) {\n attrs.type = type\n }\n\n if (start !== 1) {\n attrs.start = start\n }\n\n return attrs\n}\n\n/**\n * Returns the list marker prefix for a given item at a given index.\n *\n * @param type The list type attribute (e.g. \"a\", \"A\", \"i\", \"I\", null/undefined for default)\n * @param index The zero-based index of the list item\n * @param separator The separator to use (default: \". \")\n * @returns The marker string (e.g. \"a. \", \"I. \", \"1. \")\n */\nexport function getListMarker(\n type: string | null | undefined,\n index: number,\n separator = '. ',\n): string {\n const position = index + 1\n\n if (!type || type === '1') {\n return `${position}${separator}`\n }\n\n switch (type) {\n case 'a':\n return `${toRomanAlpha(position)}${separator}`\n case 'A':\n return `${toRomanAlpha(position).toUpperCase()}${separator}`\n case 'i':\n return `${toRoman(position)}${separator}`\n case 'I':\n return `${toRomanUpper(position)}${separator}`\n default:\n return `${position}${separator}`\n }\n}\n"],"mappings":"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","names":[]}