import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js"; import { Extension, Node, getNodeAtPosition, getNodeType, getPreviousBlockSibling, getRenderedAttributes, isAtEndOfNode, isAtStartOfNode, isNodeActive, mergeAttributes, parseIndentedBlocks, renderNestedMarkdownContent, wrappingInputRule } from "@tiptap/core"; import { Fragment } from "@tiptap/pm/model"; import { Plugin, TextSelection } from "@tiptap/pm/state"; //#region src/bullet-list/bullet-list.ts const ListItemName$1 = "listItem"; const TextStyleName$1 = "textStyle"; /** * Matches a bullet list to a dash or asterisk. */ const bulletListInputRegex = /^\s*([-+*])\s$/; /** * This extension allows you to create bullet lists. * This requires the ListItem extension * @see https://tiptap.dev/api/nodes/bullet-list * @see https://tiptap.dev/api/nodes/list-item. */ const BulletList = Node.create({ name: "bulletList", addOptions() { return { itemTypeName: "listItem", HTMLAttributes: {}, keepMarks: false, keepAttributes: false }; }, group: "block list", content() { return `${this.options.itemTypeName}+`; }, parseHTML() { return [{ tag: "ul" }]; }, renderHTML({ HTMLAttributes }) { return [ "ul", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0 ]; }, markdownTokenName: "list", parseMarkdown: (token, helpers) => { if (token.type !== "list" || token.ordered) return []; return { type: "bulletList", content: token.items ? helpers.parseChildren(token.items) : [] }; }, renderMarkdown: (node, h) => { if (!node.content) return ""; return h.renderChildren(node.content, "\n"); }, markdownOptions: { indentsContent: true }, addCommands() { return { toggleBulletList: () => ({ commands, chain }) => { if (this.options.keepAttributes) return chain().toggleList(this.name, this.options.itemTypeName, this.options.keepMarks).updateAttributes(ListItemName$1, this.editor.getAttributes(TextStyleName$1)).run(); return commands.toggleList(this.name, this.options.itemTypeName, this.options.keepMarks); } }; }, addKeyboardShortcuts() { return { "Mod-Shift-8": () => this.editor.commands.toggleBulletList() }; }, addInputRules() { let inputRule = wrappingInputRule({ find: bulletListInputRegex, type: this.type }); if (this.options.keepMarks || this.options.keepAttributes) inputRule = wrappingInputRule({ find: bulletListInputRegex, type: this.type, keepMarks: this.options.keepMarks, keepAttributes: this.options.keepAttributes, getAttributes: () => { return this.editor.getAttributes(TextStyleName$1); }, editor: this.editor }); return [inputRule]; } }); //#endregion //#region src/helpers/getBranchingNestedListAtCursor.ts /** * Resolves a branching nested list immediately after the cursor when the selection is * collapsed at the end of a textblock inside a list item. * * @param state - The editor state to inspect. * @param itemName - The list item node name (for example `listItem` or `taskItem`). * @param wrapperNames - List wrapper node names (for example `bulletList` and `orderedList`). * @returns Resolved positions and nodes for hoisting, or `null` when not applicable. * * @example * ```ts * const context = getBranchingNestedListAtCursor(editor.state, 'listItem', [ * 'bulletList', * 'orderedList', * ]) * * if (context) { * // cursor is at the end of Item 1 before a branching nested sublist * } * ``` */ const getBranchingNestedListAtCursor = (state, itemName, wrapperNames) => { const { selection } = state; if (!selection.empty) return null; const { $from } = selection; if (!$from.parent.isTextblock) return null; if ($from.parentOffset !== $from.parent.content.size) return null; let listItemDepth = -1; for (let depth = $from.depth; depth > 0; depth -= 1) if ($from.node(depth).type.name === itemName) { listItemDepth = depth; break; } if (listItemDepth < 0) return null; const listItem = $from.node(listItemDepth); const indexInListItem = $from.index(listItemDepth); if (indexInListItem + 1 >= listItem.childCount) return null; const nextChild = listItem.child(indexInListItem + 1); if (!wrapperNames.includes(nextChild.type.name)) return null; const itemType = state.schema.nodes[itemName]; let hasBranching = false; nextChild.forEach((child) => { if (child.type === itemType && child.childCount > 1) hasBranching = true; }); if (!hasBranching) return null; const nodeAfter = state.doc.resolve($from.after()).nodeAfter; if (!nodeAfter || !wrapperNames.includes(nodeAfter.type.name)) return null; const items = []; nodeAfter.forEach((child) => { items.push(child); }); if (items.length === 0) return null; return { listItemDepth, nestedList: nodeAfter, nestedListPos: $from.after(), insertPos: $from.after(listItemDepth), items }; }; //#endregion //#region src/helpers/hoistBranchingNestedList.ts /** * Hoists all list items from a branching nested list after the cursor into the parent list. * * Use this when `joinForward` cannot restructure a nested list that contains list items * with sublists (see issue #6906). * * @param state - The editor state to transform. * @param dispatch - Optional dispatch function for the transaction. * @param itemName - The list item node name (for example `listItem` or `taskItem`). * @param wrapperNames - List wrapper node names (for example `bulletList` and `orderedList`). * @returns `true` when the nested list was hoisted, otherwise `false`. * * @example * ```ts * // Cursor at the end of "Item 1" before a nested list with branching items. * hoistBranchingNestedList(editor.state, editor.view.dispatch, 'listItem', [ * 'bulletList', * 'orderedList', * ]) * ``` */ const hoistBranchingNestedList = (state, dispatch, itemName, wrapperNames) => { const context = getBranchingNestedListAtCursor(state, itemName, wrapperNames); if (!context) return false; const { selection } = state; const { nestedList, nestedListPos, insertPos, items } = context; const tr = state.tr; tr.delete(nestedListPos, nestedListPos + nestedList.nodeSize); const mappedInsertPos = tr.mapping.map(insertPos); tr.insert(mappedInsertPos, Fragment.from(items)); tr.setSelection(selection.map(tr.doc, tr.mapping)); if (dispatch) dispatch(tr); return true; }; //#endregion //#region src/helpers/handleDeleteBranchingNestedList.ts /** * Handles Delete for a list item when a branching nested sublist follows the cursor. * * @param editor - The editor instance whose state should be updated. * @param itemName - The list item node name (for example `listItem` or `taskItem`). * @param wrapperNames - List wrapper node names (for example `bulletList` and `orderedList`). * @returns `true` when the nested list was hoisted, otherwise `false`. * * @example * ```ts * Delete: () => * handleDeleteBranchingNestedList(editor, 'listItem', ['bulletList', 'orderedList']), * ``` */ const handleDeleteBranchingNestedList = (editor, itemName, wrapperNames) => { return hoistBranchingNestedList(editor.state, editor.view.dispatch, itemName, wrapperNames); }; //#endregion //#region src/helpers/createBranchingListDeleteKeymap.ts /** * Creates a high-priority keymap extension that handles Delete for branching nested lists. * Kept separate from the list item node so Enter/Tab shortcuts keep their default priority. */ const createBranchingListDeleteKeymap = (itemName, wrapperNames) => { return Extension.create({ name: `${itemName}BranchingDeleteKeymap`, priority: 101, addKeyboardShortcuts() { const handleDelete = () => handleDeleteBranchingNestedList(this.editor, itemName, wrapperNames); return { Delete: handleDelete, "Mod-Delete": handleDelete }; } }); }; //#endregion //#region src/ordered-list/roman.ts const ROMAN_NUMERALS = [ [1e3, "m"], [900, "cm"], [500, "d"], [400, "cd"], [100, "c"], [90, "xc"], [50, "l"], [40, "xl"], [10, "x"], [9, "ix"], [5, "v"], [4, "iv"], [1, "i"] ]; const ALPHA_NUMERALS = "abcdefghijklmnopqrstuvwxyz"; /** * Marker segment for ordered list lines: numeric, roman, or 1–2 letter alpha. * Roman is matched before alpha so "iii" is roman; invalid romans like "aa" fall through to alpha. */ const ORDERED_LIST_MARKER_PATTERN = String.raw`\d+|[ivxlcdmIVXLCDM]+|${"[a-zA-Z]{1,2}"}`; /** * Convert a number to lowercase roman numerals. * @example toRoman(1) // 'i' * @example toRoman(4) // 'iv' */ function toRoman(num) { let remaining = num; let result = ""; for (const [value, numeral] of ROMAN_NUMERALS) while (remaining >= value) { result += numeral; remaining -= value; } return result; } /** * Convert a number to uppercase roman numerals. * @example toRomanUpper(1) // 'I' * @example toRomanUpper(4) // 'IV' */ function toRomanUpper(num) { return toRoman(num).toUpperCase(); } function fromRoman(roman) { const lower = roman.toLowerCase(); let index = 0; let result = 0; while (index < lower.length) { let matched = false; for (const [value, numeral] of ROMAN_NUMERALS) if (lower.startsWith(numeral, index)) { result += value; index += numeral.length; matched = true; break; } if (!matched) return 0; } return result; } function isValidRoman(marker) { if (!/^[ivxlcdmIVXLCDM]+$/.test(marker)) return false; const value = fromRoman(marker); if (value <= 0) return false; return (marker === marker.toLowerCase() ? toRoman(value) : toRomanUpper(value)) === marker; } function fromAlpha(marker) { const lower = marker.toLowerCase(); if (lower.length === 1) return lower.charCodeAt(0) - "a".charCodeAt(0) + 1; if (lower.length === 2) { const first = lower.charCodeAt(0) - "a".charCodeAt(0); const second = lower.charCodeAt(1) - "a".charCodeAt(0); return (first + 1) * 26 + second + 1; } return 0; } function toRomanAlpha(num) { if (num <= 26) return ALPHA_NUMERALS[num - 1]; const first = Math.floor((num - 1) / 26) - 1; const second = (num - 1) % 26; if (first < 0) return ALPHA_NUMERALS[second]; return ALPHA_NUMERALS[first] + ALPHA_NUMERALS[second]; } /** * Extract the list marker type from a marker string. * Supports "1", "a", "A", "i", "I" marker styles. * * @param marker The text content of the list marker (e.g. "a", "1", "iii", "b") * @returns The normalized type string, or undefined for default numeric type */ function detectMarkerType(marker) { if (!marker || /^\d+$/.test(marker)) return; if (isValidRoman(marker)) return marker === marker.toLowerCase() ? "i" : "I"; if (/^[a-z]{1,2}$/.test(marker)) return "a"; if (/^[A-Z]{1,2}$/.test(marker)) return "A"; } /** * Convert a list marker string to its numeric start position. * * @param marker The text content of the list marker (e.g. "3", "b", "II") * @returns The 1-based start value for the ordered list */ function markerToStart(marker) { if (/^\d+$/.test(marker)) return parseInt(marker, 10); const type = detectMarkerType(marker); if (type === "i" || type === "I") return fromRoman(marker); if (type === "a" || type === "A") { const start = fromAlpha(marker); return start > 0 ? start : 1; } const parsed = parseInt(marker, 10); return Number.isNaN(parsed) ? 1 : parsed; } function startToMarker(type, start) { if (type === "numeric") return String(start); switch (type) { case "a": return toRomanAlpha(start); case "A": return toRomanAlpha(start).toUpperCase(); case "i": return toRoman(start); case "I": return toRomanUpper(start); default: return String(start); } } /** * Returns true when all markers share the same style and increment by 1. * Style is inferred from the first marker so ambiguous letters (e.g. "c", "i") * are not re-classified differently on later lines. */ function areOrderedListMarkersSequential(markers) { var _detectMarkerType; if (markers.length === 0) return false; const firstType = (_detectMarkerType = detectMarkerType(markers[0])) !== null && _detectMarkerType !== void 0 ? _detectMarkerType : "numeric"; const firstStart = markerToStart(markers[0]); if (firstStart < 1) return false; for (let i = 0; i < markers.length; i++) { const expected = startToMarker(firstType, firstStart + i); if (markers[i] !== expected) return false; } return true; } /** * Parse a list marker into HTML ordered-list attrs (type + start). */ function parseListMarker(marker) { return { type: detectMarkerType(marker), start: markerToStart(marker) }; } /** * Build orderedList node attrs from the first list item marker. */ function buildOrderedListAttrsFromMarker(marker) { const { type, start } = parseListMarker(marker); const attrs = {}; if (type) attrs.type = type; if (start !== 1) attrs.start = start; return attrs; } /** * Returns the list marker prefix for a given item at a given index. * * @param type The list type attribute (e.g. "a", "A", "i", "I", null/undefined for default) * @param index The zero-based index of the list item * @param separator The separator to use (default: ". ") * @returns The marker string (e.g. "a. ", "I. ", "1. ") */ function getListMarker(type, index, separator = ". ") { const position = index + 1; if (!type || type === "1") return `${position}${separator}`; switch (type) { case "a": return `${toRomanAlpha(position)}${separator}`; case "A": return `${toRomanAlpha(position).toUpperCase()}${separator}`; case "i": return `${toRoman(position)}${separator}`; case "I": return `${toRomanUpper(position)}${separator}`; default: return `${position}${separator}`; } } //#endregion //#region src/item/list-item.ts function isSameLineOrderedListToken(token) { var _token$tokens, _token$tokens2; const nestedToken = (_token$tokens = token.tokens) === null || _token$tokens === void 0 ? void 0 : _token$tokens[0]; return Boolean(token.text && ((_token$tokens2 = token.tokens) === null || _token$tokens2 === void 0 ? void 0 : _token$tokens2.length) === 1 && (nestedToken === null || nestedToken === void 0 ? void 0 : nestedToken.type) === "list" && nestedToken.ordered && nestedToken.raw === token.text); } function parseSameLineOrderedListText(text, helpers) { if (helpers.tokenizeInline) return helpers.parseInline(helpers.tokenizeInline(text)); return helpers.parseInline([{ type: "text", raw: text, text }]); } /** * This extension allows you to create list items. * @see https://www.tiptap.dev/api/nodes/list-item */ const ListItem = Node.create({ name: "listItem", addOptions() { return { HTMLAttributes: {}, bulletListTypeName: "bulletList", orderedListTypeName: "orderedList" }; }, content: "paragraph block*", defining: true, parseHTML() { return [{ tag: "li" }]; }, renderHTML({ HTMLAttributes }) { return [ "li", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0 ]; }, markdownTokenName: "list_item", parseMarkdown: (token, helpers) => { var _helpers$parseBlockCh; if (token.type !== "list_item") return []; const parseBlockChildren = (_helpers$parseBlockCh = helpers.parseBlockChildren) !== null && _helpers$parseBlockCh !== void 0 ? _helpers$parseBlockCh : helpers.parseChildren; let content = []; if (token.tokens && token.tokens.length > 0) { if (isSameLineOrderedListToken(token)) return { type: "listItem", content: [{ type: "paragraph", content: parseSameLineOrderedListText(token.text || "", helpers) }] }; if (token.tokens.some((t) => t.type === "paragraph")) content = parseBlockChildren(token.tokens); else { const firstToken = token.tokens[0]; if (firstToken && firstToken.type === "text" && firstToken.tokens && firstToken.tokens.length > 0) { content = [{ type: "paragraph", content: helpers.parseInline(firstToken.tokens) }]; if (token.tokens.length > 1) { const additionalContent = parseBlockChildren(token.tokens.slice(1)); content.push(...additionalContent); } } else content = parseBlockChildren(token.tokens); } } if (content.length === 0) content = [{ type: "paragraph", content: [] }]; return { type: "listItem", content }; }, renderMarkdown: (node, h, ctx) => { return renderNestedMarkdownContent(node, h, (context) => { if (context.parentType === "bulletList") return "- "; if (context.parentType === "orderedList") { var _context$meta, _context$meta2; const start = ((_context$meta = context.meta) === null || _context$meta === void 0 || (_context$meta = _context$meta.parentAttrs) === null || _context$meta === void 0 ? void 0 : _context$meta.start) || 1; return getListMarker((_context$meta2 = context.meta) === null || _context$meta2 === void 0 || (_context$meta2 = _context$meta2.parentAttrs) === null || _context$meta2 === void 0 ? void 0 : _context$meta2.type, start - 1 + (context.index || 0), ". "); } return "- "; }, ctx, { alignNestedToPrefix: (ctx === null || ctx === void 0 ? void 0 : ctx.parentType) === "orderedList" }); }, addExtensions() { return [createBranchingListDeleteKeymap(this.name, [this.options.bulletListTypeName, this.options.orderedListTypeName])]; }, addKeyboardShortcuts() { return { Enter: () => this.editor.commands.splitListItem(this.name), Tab: () => this.editor.commands.sinkListItem(this.name), "Shift-Tab": () => this.editor.commands.liftListItem(this.name) }; } }); //#endregion //#region src/keymap/listHelpers/findListItemPos.ts const findListItemPos = (typeOrName, state) => { const { $from } = state.selection; const nodeType = getNodeType(typeOrName, state.schema); let currentNode = null; let currentDepth = $from.depth; let currentPos = $from.pos; let targetDepth = null; while (currentDepth > 0 && targetDepth === null) { currentNode = $from.node(currentDepth); if (currentNode.type === nodeType) targetDepth = currentDepth; else { currentDepth -= 1; currentPos -= 1; } } if (targetDepth === null) return null; return { $pos: state.doc.resolve(currentPos), depth: targetDepth }; }; //#endregion //#region src/keymap/listHelpers/getNextListDepth.ts const getNextListDepth = (typeOrName, state) => { const listItemPos = findListItemPos(typeOrName, state); if (!listItemPos) return false; const [, depth] = getNodeAtPosition(state, typeOrName, listItemPos.$pos.pos + 4); return depth; }; //#endregion //#region src/keymap/listHelpers/hasListBefore.ts const hasListBefore = (editorState, name, parentListTypes) => { const { $anchor } = editorState.selection; const previousNodePos = Math.max(0, $anchor.pos - 2); const previousNode = editorState.doc.resolve(previousNodePos).node(); if (!previousNode || !parentListTypes.includes(previousNode.type.name)) return false; return true; }; //#endregion //#region src/keymap/listHelpers/handleBackspace.ts const handleBackspace = (editor, name, parentListTypes) => { if (editor.commands.undoInputRule()) return true; if (editor.state.selection.from !== editor.state.selection.to) return false; if (!isNodeActive(editor.state, name) && hasListBefore(editor.state, name, parentListTypes)) { const { $anchor } = editor.state.selection; const $listPos = editor.state.doc.resolve($anchor.before() - 1); const listDescendants = []; $listPos.node().descendants((node, pos) => { if (node.type.name === name) listDescendants.push({ node, pos }); }); const lastItem = listDescendants.at(-1); if (!lastItem) return false; const $lastItemPos = editor.state.doc.resolve($listPos.start() + lastItem.pos + 1); return editor.chain().cut({ from: $anchor.start() - 1, to: $anchor.end() + 1 }, $lastItemPos.end()).joinForward().run(); } if (!isNodeActive(editor.state, name)) return false; if (!isAtStartOfNode(editor.state)) return false; const { $from } = editor.state.selection; const itemDepth = $from.depth - 1; if ($from.node(itemDepth).type !== editor.schema.nodes[name] || $from.index(itemDepth) !== 0) return false; return editor.chain().liftListItem(name).run(); }; //#endregion //#region src/keymap/listHelpers/nextListIsDeeper.ts const nextListIsDeeper = (typeOrName, state) => { const listDepth = getNextListDepth(typeOrName, state); const listItemPos = findListItemPos(typeOrName, state); if (!listItemPos || !listDepth) return false; if (listDepth > listItemPos.depth) return true; return false; }; //#endregion //#region src/keymap/listHelpers/nextListIsHigher.ts const nextListIsHigher = (typeOrName, state) => { const listDepth = getNextListDepth(typeOrName, state); const listItemPos = findListItemPos(typeOrName, state); if (!listItemPos || !listDepth) return false; if (listDepth < listItemPos.depth) return true; return false; }; //#endregion //#region src/keymap/listHelpers/handleDelete.ts const handleDelete = (editor, name) => { if (!isNodeActive(editor.state, name)) return false; if (!isAtEndOfNode(editor.state, name)) return false; const { selection } = editor.state; const { $from, $to } = selection; if (!selection.empty && $from.sameParent($to)) return false; if (nextListIsDeeper(name, editor.state)) return editor.chain().focus(editor.state.selection.from + 4).lift(name).joinBackward().run(); if (nextListIsHigher(name, editor.state)) return editor.chain().joinForward().joinBackward().run(); return editor.commands.joinItemForward(); }; //#endregion //#region src/keymap/listHelpers/handleTab.ts const handleTab = (editor, name, parentListTypes) => { const { state } = editor; const { selection } = state; if (!selection.empty) return false; const { $from } = selection; if ($from.parentOffset !== 0) return false; if (!$from.parent.isTextblock) return false; if (isNodeActive(state, name)) return false; const previous = getPreviousBlockSibling($from); if (!previous || !parentListTypes.includes(previous.type.name)) return false; const lastItem = previous.lastChild; if (!lastItem || lastItem.type.name !== name) return false; const block = $from.parent; if (!lastItem.canReplace(lastItem.childCount, lastItem.childCount, Fragment.from(block))) return false; const blockStart = $from.before(); const blockEnd = $from.after(); const insideLastItemEnd = blockStart - 2; return editor.commands.command(({ tr, dispatch }) => { if (dispatch) { tr.delete(blockStart, blockEnd).insert(insideLastItemEnd, Fragment.from(block)); tr.setSelection(TextSelection.create(tr.doc, insideLastItemEnd + 1)); tr.scrollIntoView(); } return true; }); }; //#endregion //#region src/keymap/listHelpers/hasListItemAfter.ts const hasListItemAfter = (typeOrName, state) => { var _$targetPos$nodeAfter; const { $anchor } = state.selection; const $targetPos = state.doc.resolve($anchor.pos - $anchor.parentOffset - 2); if ($targetPos.index() === $targetPos.parent.childCount - 1) return false; if (((_$targetPos$nodeAfter = $targetPos.nodeAfter) === null || _$targetPos$nodeAfter === void 0 ? void 0 : _$targetPos$nodeAfter.type.name) !== typeOrName) return false; return true; }; //#endregion //#region src/keymap/listHelpers/hasListItemBefore.ts const hasListItemBefore = (typeOrName, state) => { var _$targetPos$nodeBefor; const { $anchor } = state.selection; const $targetPos = state.doc.resolve($anchor.pos - 2); if ($targetPos.index() === 0) return false; if (((_$targetPos$nodeBefor = $targetPos.nodeBefore) === null || _$targetPos$nodeBefor === void 0 ? void 0 : _$targetPos$nodeBefor.type.name) !== typeOrName) return false; return true; }; //#endregion //#region src/keymap/listHelpers/listItemHasSubList.ts const listItemHasSubList = (typeOrName, state, node) => { if (!node) return false; const nodeType = getNodeType(typeOrName, state.schema); let hasSubList = false; node.descendants((child) => { if (child.type === nodeType) hasSubList = true; }); return hasSubList; }; //#endregion //#region src/keymap/listHelpers/index.ts var listHelpers_exports = /* @__PURE__ */ __exportAll({ findListItemPos: () => findListItemPos, getNextListDepth: () => getNextListDepth, handleBackspace: () => handleBackspace, handleDelete: () => handleDelete, handleTab: () => handleTab, hasListBefore: () => hasListBefore, hasListItemAfter: () => hasListItemAfter, hasListItemBefore: () => hasListItemBefore, listItemHasSubList: () => listItemHasSubList, nextListIsDeeper: () => nextListIsDeeper, nextListIsHigher: () => nextListIsHigher }); //#endregion //#region src/keymap/list-keymap.ts /** * This extension registers custom keymaps to change the behaviour of the backspace and delete keys. * By default Prosemirror keyhandling will always lift or sink items so paragraphs are joined into * the adjacent or previous list item. This extension will prevent this behaviour and instead will * try to join paragraphs from two list items into a single list item. * @see https://www.tiptap.dev/api/extensions/list-keymap */ const ListKeymap = Extension.create({ name: "listKeymap", addOptions() { return { listTypes: [{ itemName: "listItem", wrapperNames: ["bulletList", "orderedList"] }, { itemName: "taskItem", wrapperNames: ["taskList"] }] }; }, addKeyboardShortcuts() { return { Delete: ({ editor }) => { let handled = false; this.options.listTypes.forEach(({ itemName }) => { if (editor.state.schema.nodes[itemName] === void 0) return; if (handleDelete(editor, itemName)) handled = true; }); return handled; }, "Mod-Delete": ({ editor }) => { let handled = false; this.options.listTypes.forEach(({ itemName }) => { if (editor.state.schema.nodes[itemName] === void 0) return; if (handleDelete(editor, itemName)) handled = true; }); return handled; }, Backspace: ({ editor }) => { let handled = false; this.options.listTypes.forEach(({ itemName, wrapperNames }) => { if (editor.state.schema.nodes[itemName] === void 0) return; if (handleBackspace(editor, itemName, wrapperNames)) handled = true; }); return handled; }, "Mod-Backspace": ({ editor }) => { let handled = false; this.options.listTypes.forEach(({ itemName, wrapperNames }) => { if (editor.state.schema.nodes[itemName] === void 0) return; if (handleBackspace(editor, itemName, wrapperNames)) handled = true; }); return handled; }, Tab: ({ editor }) => { for (const { itemName, wrapperNames } of this.options.listTypes) { if (editor.state.schema.nodes[itemName] === void 0) continue; if (handleTab(editor, itemName, wrapperNames)) return true; } return false; } }; } }); //#endregion //#region src/ordered-list/utils.ts /** * Matches an ordered list item line with optional leading whitespace. * Captures: (1) indentation spaces, (2) item marker (number, letter, or roman numeral), * (3) separator (. or )), (4) content after marker * * Examples: "1. Item", " a) Nested item", " I. Roman item", "iii. Another", "aa. Item 27" */ const ORDERED_LIST_ITEM_REGEX = new RegExp(`^(\\s*)(${ORDERED_LIST_MARKER_PATTERN})([.)])\\s+(.*)$`); /** * Matches any line that starts with whitespace (indented content). * Used to identify continuation content that belongs to a list item. */ const INDENTED_LINE_REGEX = /^\s/; /** * This are blocks that can interrupt a paragraph, so a line starting with one of * them can never be lazy continuation text of a list item */ const PARAGRAPH_INTERRUPTERS = { heading: /^#{1,6}(?:\s|$)/, bulletItem: /^[-+*]\s+/, codeFence: /^(?:```|~~~)/, blockMath: /^\$\$/, thematicBreak: /^(?:(?:-[ \t]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})$/ }; function isOrderedListMarkerLine(line) { return ORDERED_LIST_ITEM_REGEX.test(line.trimStart()); } function isBlockContentLine(line) { const trimmedLine = line.trimStart(); return PARAGRAPH_INTERRUPTERS.bulletItem.test(trimmedLine) || isOrderedListMarkerLine(trimmedLine) || PARAGRAPH_INTERRUPTERS.heading.test(trimmedLine) || PARAGRAPH_INTERRUPTERS.thematicBreak.test(trimmedLine) && !trimmedLine.startsWith("-") || /^>\s?/.test(trimmedLine) || PARAGRAPH_INTERRUPTERS.codeFence.test(trimmedLine) || PARAGRAPH_INTERRUPTERS.blockMath.test(trimmedLine); } function interruptsLazyContinuation(line) { return Object.values(PARAGRAPH_INTERRUPTERS).some((pattern) => pattern.test(line)); } function splitItemContent(contentLines) { const paragraphLines = []; const blockLines = []; let reachedBlockBoundary = false; contentLines.forEach((line) => { if (reachedBlockBoundary) { blockLines.push(line); return; } if (line.trim() === "") { reachedBlockBoundary = true; blockLines.push(line); return; } if (paragraphLines.length > 0 && isBlockContentLine(line)) { reachedBlockBoundary = true; blockLines.push(line); return; } paragraphLines.push(line); }); return { paragraphLines, blockLines }; } /** * Collects all ordered list items from lines, parsing them into a flat array * with indentation information. Stops collecting continuation content when * encountering nested list items, allowing them to be processed separately. * * @param lines - Array of source lines to parse * @returns Tuple of [listItems array, number of lines consumed] */ function collectOrderedListItems(lines) { const listItems = []; let currentLineIndex = 0; let consumed = 0; while (currentLineIndex < lines.length) { const line = lines[currentLineIndex]; const match = line.match(ORDERED_LIST_ITEM_REGEX); if (!match) break; const [, indent, marker, _separator, content] = match; const indentLevel = indent.length; const number = parseInt(marker, 10); const markerType = isNaN(number) ? detectMarkerType(marker) : void 0; const itemNumber = isNaN(number) ? markerToStart(marker) : number; const itemContentLines = [content]; let nextLineIndex = currentLineIndex + 1; const itemLines = [line]; let sawBlankLine = false; while (nextLineIndex < lines.length) { const nextLine = lines[nextLineIndex]; if (nextLine.match(ORDERED_LIST_ITEM_REGEX)) break; if (nextLine.trim() === "") { itemLines.push(nextLine); itemContentLines.push(""); sawBlankLine = true; nextLineIndex += 1; } else if (nextLine.match(INDENTED_LINE_REGEX)) { const leadingWhitespace = nextLine.length - nextLine.trimStart().length; const contentIndent = indentLevel + marker.length + 1; itemLines.push(nextLine); itemContentLines.push(nextLine.slice(Math.min(leadingWhitespace, contentIndent))); nextLineIndex += 1; } else { if (sawBlankLine || interruptsLazyContinuation(nextLine)) break; itemLines.push(nextLine); itemContentLines.push(nextLine); nextLineIndex += 1; } } listItems.push({ indent: indentLevel, number: itemNumber, type: markerType, content: itemContentLines.join("\n").trim(), contentLines: itemContentLines, raw: itemLines.join("\n") }); consumed = nextLineIndex; currentLineIndex = nextLineIndex; } return [listItems, consumed]; } const PLAIN_TEXT_ORDERED_LIST_LINE_REGEX = new RegExp(`^(${ORDERED_LIST_MARKER_PATTERN})([.)])\\s+(.+)$`); /** * Parse plain-text pasted ordered list lines into JSONContent, or null if not a typed list. */ function parsePlainTextOrderedListPaste(text) { const lines = text.split("\n").filter((l) => l.trim().length > 0); if (lines.length === 0) return null; const parsedItems = []; for (const line of lines) { const match = line.trim().match(PLAIN_TEXT_ORDERED_LIST_LINE_REGEX); if (!match) return null; parsedItems.push({ marker: match[1], content: match[3] }); } if (!areOrderedListMarkersSequential(parsedItems.map((item) => item.marker))) return null; return { type: "orderedList", attrs: buildOrderedListAttrsFromMarker(parsedItems[0].marker), content: parsedItems.map((item) => ({ type: "listItem", content: [{ type: "paragraph", content: [{ type: "text", text: item.content }] }] })) }; } /** * Recursively builds a nested structure from a flat array of list items * based on their indentation levels. Creates proper markdown tokens with * nested lists where appropriate. * * @param items - Flat array of list items with indentation info * @param baseIndent - The indentation level to process at this recursion level * @param lexer - Markdown lexer for parsing inline and block content * @returns Array of list_item tokens with proper nesting */ function buildNestedStructure(items, baseIndent, lexer) { const result = []; let currentIndex = 0; while (currentIndex < items.length) { const item = items[currentIndex]; if (item.indent === baseIndent) { const { paragraphLines, blockLines } = splitItemContent(item.contentLines); const mainText = paragraphLines.join("\n").trim(); const tokens = []; if (mainText) tokens.push({ type: "paragraph", raw: mainText, tokens: lexer.inlineTokens(mainText) }); const additionalContent = blockLines.join("\n").trim(); if (additionalContent) { const blockTokens = lexer.blockTokens(additionalContent); tokens.push(...blockTokens); } let lookAheadIndex = currentIndex + 1; const nestedItems = []; while (lookAheadIndex < items.length && items[lookAheadIndex].indent > baseIndent) { nestedItems.push(items[lookAheadIndex]); lookAheadIndex += 1; } if (nestedItems.length > 0) { const nestedListItems = buildNestedStructure(nestedItems, Math.min(...nestedItems.map((nestedItem) => nestedItem.indent)), lexer); tokens.push({ type: "list", ordered: true, start: nestedItems[0].number, typeMarker: nestedItems[0].type, items: nestedListItems, raw: nestedItems.map((nestedItem) => nestedItem.raw).join("\n") }); } result.push({ type: "list_item", raw: item.raw, tokens }); currentIndex = lookAheadIndex; } else currentIndex += 1; } return result; } /** * Parses markdown list item tokens into Tiptap JSONContent structure, * ensuring text content is properly wrapped in paragraph nodes. * * @param items - Array of markdown tokens representing list items * @param helpers - Markdown parse helpers for recursive parsing * @returns Array of listItem JSONContent nodes */ function parseListItems(items, helpers) { return items.map((item) => { if (item.type !== "list_item") return helpers.parseChildren([item])[0]; const content = []; if (item.tokens && item.tokens.length > 0) item.tokens.forEach((itemToken) => { if (itemToken.type === "paragraph" || itemToken.type === "list" || itemToken.type === "blockquote" || itemToken.type === "code") content.push(...helpers.parseChildren([itemToken])); else if (itemToken.type === "text" && itemToken.tokens) { const inlineContent = helpers.parseChildren([itemToken]); content.push({ type: "paragraph", content: inlineContent }); } else { const parsed = helpers.parseChildren([itemToken]); if (parsed.length > 0) content.push(...parsed); } }); return { type: "listItem", content }; }); } //#endregion //#region src/ordered-list/collectOrderedListItemsFromSource.ts const ORDERED_LIST_SCAN_WINDOW = 64; function takeLines(source, max) { const lines = []; let start = 0; while (lines.length < max) { const lineEnd = source.indexOf("\n", start); if (lineEnd === -1) { lines.push(source.slice(start)); return lines; } lines.push(source.slice(start, lineEnd)); start = lineEnd + 1; } return lines; } /** * Collect ordered list items from the start of `source`, reading only the lines the list needs. * * @param source Markdown beginning at a block boundary. * @returns The items, how many lines they consumed, and the lines that were read. * @example * collectOrderedListItemsFromSource('1. one\n2. two\n\nAfter') * // → [[item, item], 3, ['1. one', '2. two', '', 'After']] */ function collectOrderedListItemsFromSource(source) { const firstLineEnd = source.indexOf("\n"); const firstLine = firstLineEnd === -1 ? source : source.slice(0, firstLineEnd); if (!ORDERED_LIST_ITEM_REGEX.test(firstLine)) return [ [], 0, [] ]; let windowSize = ORDERED_LIST_SCAN_WINDOW; let lines = takeLines(source, windowSize); let collected = collectOrderedListItems(lines); while (collected[1] === lines.length && lines.length === windowSize) { windowSize *= 2; lines = takeLines(source, windowSize); collected = collectOrderedListItems(lines); } return [ collected[0], collected[1], lines ]; } //#endregion //#region src/ordered-list/ordered-list.ts const ListItemName = "listItem"; const TextStyleName = "textStyle"; /** * Matches an ordered list to a 1. on input (or any number followed by a dot). */ const orderedListInputRegex = /^(\d+)\.\s$/; /** * Maps CSS list-style-type values to HTML type attribute values. * Google Docs and Word often use CSS instead of the HTML type attribute. */ function cssListStyleTypeToHtmlType(style) { const match = style.match(/list-style-type\s*:\s*([^;]+)/i); if (!match) return null; switch (match[1].trim().toLowerCase()) { case "upper-roman": return "I"; case "lower-roman": return "i"; case "upper-alpha": case "upper-latin": return "A"; case "lower-alpha": case "lower-latin": return "a"; default: return null; } } /** * This extension allows you to create ordered lists. * This requires the ListItem extension * @see https://www.tiptap.dev/api/nodes/ordered-list * @see https://www.tiptap.dev/api/nodes/list-item */ const OrderedList = Node.create({ name: "orderedList", addOptions() { return { itemTypeName: "listItem", HTMLAttributes: {}, keepMarks: false, keepAttributes: false }; }, group: "block list", content() { return `${this.options.itemTypeName}+`; }, addAttributes() { return { start: { default: 1, parseHTML: (element) => { return element.hasAttribute("start") ? parseInt(element.getAttribute("start") || "", 10) : 1; } }, type: { default: null, parseHTML: (element) => { const htmlType = element.getAttribute("type"); if (htmlType) return htmlType; const style = element.getAttribute("style"); if (style) { const mappedFromOl = cssListStyleTypeToHtmlType(style); if (mappedFromOl) return mappedFromOl; } const firstLi = element.querySelector("li"); if (firstLi) { const liStyle = firstLi.getAttribute("style"); if (liStyle) { const mappedFromLi = cssListStyleTypeToHtmlType(liStyle); if (mappedFromLi) return mappedFromLi; } } return null; } } }; }, parseHTML() { return [{ tag: "ol" }]; }, renderHTML({ HTMLAttributes }) { const { start, type, ...attributesWithoutType } = HTMLAttributes; const attrs = mergeAttributes(this.options.HTMLAttributes, attributesWithoutType); if (start !== 1) attrs.start = start; if (type && type !== "1") attrs.type = type; return [ "ol", attrs, 0 ]; }, markdownTokenName: "list", parseMarkdown: (token, helpers) => { if (token.type !== "list" || !token.ordered) return []; const startValue = token.start || 1; const typeValue = token.typeMarker; const content = token.items ? parseListItems(token.items, helpers) : []; const attrs = {}; if (startValue !== 1) attrs.start = startValue; if (typeValue) attrs.type = typeValue; if (Object.keys(attrs).length > 0) return { type: "orderedList", attrs, content }; return { type: "orderedList", content }; }, renderMarkdown: (node, h) => { if (!node.content) return ""; return h.renderChildren(node.content, "\n"); }, markdownTokenizer: { name: "orderedList", level: "block", start: () => -1, tokenize: (src, _tokens, lexer) => { var _listItems$, _listItems$2; const [listItems, consumed, lines] = collectOrderedListItemsFromSource(src); if (listItems.length === 0) return; const items = buildNestedStructure(listItems, listItems[0].indent, lexer); if (items.length === 0) return; return { type: "list", ordered: true, start: ((_listItems$ = listItems[0]) === null || _listItems$ === void 0 ? void 0 : _listItems$.number) || 1, typeMarker: (_listItems$2 = listItems[0]) === null || _listItems$2 === void 0 ? void 0 : _listItems$2.type, items, raw: lines.slice(0, consumed).join("\n") }; } }, markdownOptions: { indentsContent: true }, addCommands() { return { toggleOrderedList: () => ({ commands, chain }) => { if (this.options.keepAttributes) return chain().toggleList(this.name, this.options.itemTypeName, this.options.keepMarks).updateAttributes(ListItemName, this.editor.getAttributes(TextStyleName)).run(); return commands.toggleList(this.name, this.options.itemTypeName, this.options.keepMarks); } }; }, addKeyboardShortcuts() { return { "Mod-Shift-7": () => this.editor.commands.toggleOrderedList() }; }, addProseMirrorPlugins() { return [new Plugin({ props: { handlePaste: (view, event) => { var _event$clipboardData, _event$clipboardData2; const html = (_event$clipboardData = event.clipboardData) === null || _event$clipboardData === void 0 ? void 0 : _event$clipboardData.getData("text/html"); if (html === null || html === void 0 ? void 0 : html.trim()) return false; const text = (_event$clipboardData2 = event.clipboardData) === null || _event$clipboardData2 === void 0 ? void 0 : _event$clipboardData2.getData("text/plain"); if (!text) return false; const orderedListContent = parsePlainTextOrderedListPaste(text); if (!orderedListContent) return false; try { const orderedListNode = view.state.schema.nodeFromJSON(orderedListContent); const tr = view.state.tr.replaceSelectionWith(orderedListNode); view.dispatch(tr); return true; } catch { return false; } } } })]; }, addInputRules() { const joinPredicate = (match, node) => { return (!node.attrs.type || node.attrs.type === "1") && node.childCount + node.attrs.start === +match[1]; }; let inputRule = wrappingInputRule({ find: orderedListInputRegex, type: this.type, getAttributes: (match) => ({ start: +match[1] }), joinPredicate }); if (this.options.keepMarks || this.options.keepAttributes) inputRule = wrappingInputRule({ find: orderedListInputRegex, type: this.type, keepMarks: this.options.keepMarks, keepAttributes: this.options.keepAttributes, getAttributes: (match) => ({ start: +match[1], ...this.editor.getAttributes(TextStyleName) }), joinPredicate, editor: this.editor }); return [inputRule]; } }); //#endregion //#region src/task-item/task-item.ts /** * Matches a task item to a - [ ] on input. */ const inputRegex = /^\s*(\[([( |x])?\])\s$/; /** * Hides the checkbox label visually while keeping it in the accessibility tree. */ const visuallyHiddenStyle = "position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0"; const getCheckboxLabel = (node, checked, a11y) => { var _a11y$checkboxLabel; return (a11y === null || a11y === void 0 || (_a11y$checkboxLabel = a11y.checkboxLabel) === null || _a11y$checkboxLabel === void 0 ? void 0 : _a11y$checkboxLabel.call(a11y, node, checked)) || `Task item checkbox for ${node.textContent || "empty task item"}`; }; /** * This extension allows you to create task items. * @see https://www.tiptap.dev/api/nodes/task-item */ const TaskItem = Node.create({ name: "taskItem", addOptions() { return { nested: false, HTMLAttributes: {}, taskListTypeName: "taskList", a11y: void 0 }; }, content() { return this.options.nested ? "paragraph block*" : "paragraph+"; }, defining: true, addAttributes() { return { checked: { default: false, keepOnSplit: false, parseHTML: (element) => { const dataChecked = element.getAttribute("data-checked"); return dataChecked === "" || dataChecked === "true"; }, renderHTML: (attributes) => ({ "data-checked": attributes.checked }) } }; }, parseHTML() { return [{ tag: `li[data-type="${this.name}"]`, priority: 51, contentElement: (element) => { var _element$querySelecto; return (_element$querySelecto = element.querySelector("div")) !== null && _element$querySelecto !== void 0 ? _element$querySelecto : element; } }]; }, renderHTML({ node, HTMLAttributes }) { return [ "li", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { "data-type": this.name }), [ "label", ["input", { type: "checkbox", checked: node.attrs.checked ? "checked" : null }], ["span"] ], ["div", 0] ]; }, parseMarkdown: (token, h) => { const content = []; if (token.tokens && token.tokens.length > 0) content.push(h.createNode("paragraph", {}, h.parseInline(token.tokens))); else if (token.text) content.push(h.createNode("paragraph", {}, [h.createNode("text", { text: token.text })])); else content.push(h.createNode("paragraph", {}, [])); if (token.nestedTokens && token.nestedTokens.length > 0) { const nestedContent = h.parseChildren(token.nestedTokens); content.push(...nestedContent); } return h.createNode("taskItem", { checked: token.checked || false }, content); }, renderMarkdown: (node, h) => { var _node$attrs; const prefix = `- [${((_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.checked) ? "x" : " "}] `; return renderNestedMarkdownContent(node, h, prefix); }, addExtensions() { if (!this.options.nested) return []; return [createBranchingListDeleteKeymap(this.name, [this.options.taskListTypeName])]; }, addKeyboardShortcuts() { const shortcuts = { Enter: () => this.editor.commands.splitListItem(this.name), "Shift-Tab": () => this.editor.commands.liftListItem(this.name) }; if (!this.options.nested) return shortcuts; return { ...shortcuts, Tab: () => this.editor.commands.sinkListItem(this.name) }; }, addNodeView() { return ({ node, HTMLAttributes, getPos, editor }) => { const listItem = document.createElement("li"); const checkboxWrapper = document.createElement("label"); const checkboxStyler = document.createElement("span"); const checkbox = document.createElement("input"); const content = document.createElement("div"); checkboxStyler.style.cssText = visuallyHiddenStyle; const updateA11Y = (currentNode) => { const label = getCheckboxLabel(currentNode, currentNode.attrs.checked, this.options.a11y); checkbox.setAttribute("aria-label", label); checkboxStyler.textContent = label; }; updateA11Y(node); checkboxWrapper.contentEditable = "false"; checkbox.type = "checkbox"; checkbox.addEventListener("mousedown", (event) => event.preventDefault()); checkbox.addEventListener("change", (event) => { if (!editor.isEditable && !this.options.onReadOnlyChecked) { checkbox.checked = !checkbox.checked; return; } const { checked } = event.target; if (editor.isEditable && typeof getPos === "function") editor.chain().focus(void 0, { scrollIntoView: false }).command(({ tr }) => { const position = getPos(); if (typeof position !== "number") return false; const currentNode = tr.doc.nodeAt(position); tr.setNodeMarkup(position, void 0, { ...currentNode === null || currentNode === void 0 ? void 0 : currentNode.attrs, checked }); return true; }).run(); if (!editor.isEditable && this.options.onReadOnlyChecked) { if (!this.options.onReadOnlyChecked(node, checked)) checkbox.checked = !checkbox.checked; } }); Object.entries(this.options.HTMLAttributes).forEach(([key, value]) => { listItem.setAttribute(key, value); }); listItem.dataset.checked = node.attrs.checked; checkbox.checked = node.attrs.checked; checkboxWrapper.append(checkbox, checkboxStyler); listItem.append(checkboxWrapper, content); Object.entries(HTMLAttributes).forEach(([key, value]) => { listItem.setAttribute(key, value); }); let prevRenderedAttributeKeys = new Set(Object.keys(HTMLAttributes)); return { dom: listItem, contentDOM: content, update: (updatedNode) => { if (updatedNode.type !== this.type) return false; listItem.dataset.checked = updatedNode.attrs.checked; checkbox.checked = updatedNode.attrs.checked; updateA11Y(updatedNode); const extensionAttributes = editor.extensionManager.attributes; const newHTMLAttributes = getRenderedAttributes(updatedNode, extensionAttributes); const newKeys = new Set(Object.keys(newHTMLAttributes)); const staticAttrs = this.options.HTMLAttributes; prevRenderedAttributeKeys.forEach((key) => { if (!newKeys.has(key)) { if (key in staticAttrs) listItem.setAttribute(key, staticAttrs[key]); else listItem.removeAttribute(key); } }); Object.entries(newHTMLAttributes).forEach(([key, value]) => { if (value === null || value === void 0) { if (key in staticAttrs) listItem.setAttribute(key, staticAttrs[key]); else listItem.removeAttribute(key); } else listItem.setAttribute(key, value); }); prevRenderedAttributeKeys = newKeys; return true; } }; }; }, addInputRules() { return [wrappingInputRule({ find: inputRegex, type: this.type, getAttributes: (match) => ({ checked: match[match.length - 1] === "x" }) })]; } }); //#endregion //#region src/task-list/task-list.ts /** * This extension allows you to create task lists. * @see https://www.tiptap.dev/api/nodes/task-list */ const TaskList = Node.create({ name: "taskList", addOptions() { return { itemTypeName: "taskItem", HTMLAttributes: {} }; }, group: "block list", content() { return `${this.options.itemTypeName}+`; }, parseHTML() { return [{ tag: `ul[data-type="${this.name}"]`, priority: 51 }]; }, renderHTML({ HTMLAttributes }) { return [ "ul", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { "data-type": this.name }), 0 ]; }, parseMarkdown: (token, h) => { return h.createNode("taskList", {}, h.parseChildren(token.items || [])); }, renderMarkdown: (node, h) => { if (!node.content) return ""; return h.renderChildren(node.content, "\n"); }, markdownTokenizer: { name: "taskList", level: "block", start(src) { var _src$match; const index = (_src$match = src.match(/^\s*[-+*]\s+\[([ xX])\]\s+/)) === null || _src$match === void 0 ? void 0 : _src$match.index; return index !== void 0 ? index : -1; }, tokenize(src, tokens, lexer) { const parseTaskListContent = (content) => { const nestedResult = parseIndentedBlocks(content, { itemPattern: /^(\s*)([-+*])\s+\[([ xX])\]\s+(.*)$/, extractItemData: (match) => ({ indentLevel: match[1].length, mainContent: match[4], checked: match[3].toLowerCase() === "x" }), createToken: (data, nestedTokens) => ({ type: "taskItem", raw: "", mainContent: data.mainContent, indentLevel: data.indentLevel, checked: data.checked, text: data.mainContent, tokens: lexer.inlineTokens(data.mainContent), nestedTokens }), customNestedParser: parseTaskListContent }, lexer); if (nestedResult) { const taskListToken = { type: "taskList", raw: nestedResult.raw, items: nestedResult.items }; const remainder = content.slice(nestedResult.raw.length); if (remainder.trim()) return [taskListToken, ...lexer.blockTokens(remainder)]; return [taskListToken]; } return lexer.blockTokens(content); }; const result = parseIndentedBlocks(src, { itemPattern: /^(\s*)([-+*])\s+\[([ xX])\]\s+(.*)$/, extractItemData: (match) => ({ indentLevel: match[1].length, mainContent: match[4], checked: match[3].toLowerCase() === "x" }), createToken: (data, nestedTokens) => ({ type: "taskItem", raw: "", mainContent: data.mainContent, indentLevel: data.indentLevel, checked: data.checked, text: data.mainContent, tokens: lexer.inlineTokens(data.mainContent), nestedTokens }), customNestedParser: parseTaskListContent }, lexer); if (!result) return; return { type: "taskList", raw: result.raw, items: result.items }; } }, markdownOptions: { indentsContent: true }, addCommands() { return { toggleTaskList: () => ({ commands }) => { return commands.toggleList(this.name, this.options.itemTypeName); } }; }, addKeyboardShortcuts() { return { "Mod-Shift-9": () => this.editor.commands.toggleTaskList() }; } }); //#endregion //#region src/kit/index.ts /** * The table kit is a collection of table editor extensions. * * It’s a good starting point for building your own table in Tiptap. */ const ListKit = Extension.create({ name: "listKit", addExtensions() { const extensions = []; if (this.options.bulletList !== false) extensions.push(BulletList.configure(this.options.bulletList)); if (this.options.listItem !== false) extensions.push(ListItem.configure(this.options.listItem)); if (this.options.listKeymap !== false) extensions.push(ListKeymap.configure(this.options.listKeymap)); if (this.options.orderedList !== false) extensions.push(OrderedList.configure(this.options.orderedList)); if (this.options.taskItem !== false) extensions.push(TaskItem.configure(this.options.taskItem)); if (this.options.taskList !== false) extensions.push(TaskList.configure(this.options.taskList)); return extensions; } }); //#endregion export { BulletList, ListItem, ListKeymap, ListKit, ORDERED_LIST_MARKER_PATTERN, OrderedList, TaskItem, TaskList, areOrderedListMarkersSequential, buildOrderedListAttrsFromMarker, bulletListInputRegex, detectMarkerType, getListMarker, inputRegex, listHelpers_exports as listHelpers, markerToStart, orderedListInputRegex, parseListMarker, parsePlainTextOrderedListPaste, toRoman, toRomanUpper }; //# sourceMappingURL=index.js.map