@docs.plus/extension-indent
Version:
A Tiptap extension for managing text indentation in documents
1 lines • 8.22 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/indent.ts"],"sourcesContent":["export * from './indent'\n","import { Extension } from '@tiptap/core'\nimport { TextSelection } from '@tiptap/pm/state'\n\n/**\n * Configuration options for the Indent extension\n */\nexport interface IndentOptions {\n /**\n * Character(s) to insert for each indentation\n * @default ' ' (2 spaces)\n */\n indentChars: string\n\n /**\n * Whether the extension is enabled\n * @default true\n */\n enabled: boolean\n\n /**\n * List of node types that can accept indentation\n * If not provided or empty, all nodes will be indented\n * @default ['paragraph', 'listItem', 'orderedList']\n */\n allowedNodeTypes?: string[]\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n indent: {\n /**\n * Add indentation at cursor position or to selected content\n */\n indent: () => ReturnType\n /**\n * Remove indentation at cursor position or from selected content\n */\n outdent: () => ReturnType\n }\n }\n}\n\n/**\n * Indent Extension for Tiptap\n *\n * Provides functionality to indent and outdent text in the editor.\n */\nexport const Indent = Extension.create<IndentOptions>({\n name: 'indent',\n\n addOptions() {\n return {\n indentChars: ' ',\n enabled: true,\n allowedNodeTypes: ['paragraph', 'listItem', 'orderedList']\n }\n },\n\n addCommands() {\n /**\n * Checks if the current node type is allowed for indentation\n * @param state - Editor state\n * @returns boolean indicating if indentation is allowed\n */\n const isNodeAllowed = (state: any) => {\n const { allowedNodeTypes } = this.options\n\n if (!allowedNodeTypes?.length) return true\n\n const $pos = state.selection.$from\n const node = $pos.node()\n\n return allowedNodeTypes.includes(node.type.name)\n }\n\n return {\n /**\n * Adds indentation at cursor position or to selected content\n */\n indent:\n () =>\n ({ tr, state, dispatch }) => {\n if (!this.options.enabled || !isNodeAllowed(state)) return false\n\n const { selection } = state\n const { indentChars } = this.options\n\n // Handle empty selection (cursor position)\n if (selection.empty) {\n if (dispatch) {\n tr.insertText(indentChars, selection.from, selection.to)\n dispatch(tr)\n }\n return true\n }\n\n // Handle multiline selection\n const lines = state.doc.textBetween(selection.from, selection.to).split('\\n')\n let pos = selection.from\n\n if (dispatch) {\n for (const line of lines) {\n const lineStart = pos\n const lineEnd = lineStart + line.length\n\n tr.insertText(indentChars, lineStart, lineStart)\n pos = lineEnd + indentChars.length + 1 // +1 for newline\n }\n\n dispatch(tr)\n }\n\n return true\n },\n\n /**\n * Removes indentation at cursor position or from selected content\n */\n outdent:\n () =>\n ({ tr, state, dispatch }) => {\n if (!this.options.enabled || !isNodeAllowed(state)) return false\n\n const { selection } = state\n const { indentChars } = this.options\n let madeChanges = false\n\n // Handle empty selection (cursor position)\n if (selection.empty) {\n const $cursor = (selection as TextSelection).$cursor\n if (!$cursor) return false\n\n const cursorPos = $cursor.pos\n const lineStart = state.doc.resolve(cursorPos).start()\n const textBeforeCursor = state.doc.textBetween(lineStart, cursorPos)\n const isAtLineStart = cursorPos === lineStart\n\n // Check if there are indentation characters to remove\n if (textBeforeCursor.endsWith(indentChars)) {\n if (dispatch) {\n tr.delete(cursorPos - indentChars.length, cursorPos)\n madeChanges = true\n }\n } else if (isAtLineStart && textBeforeCursor.startsWith(indentChars)) {\n if (dispatch) {\n tr.delete(lineStart, lineStart + indentChars.length)\n madeChanges = true\n }\n }\n\n if (dispatch) {\n if (!madeChanges) {\n tr.setSelection(TextSelection.create(tr.doc, cursorPos))\n }\n dispatch(tr)\n }\n\n return true\n }\n\n // Handle multiline selection\n const lines = state.doc.textBetween(selection.from, selection.to).split('\\n')\n let pos = selection.from\n\n if (dispatch) {\n for (const line of lines) {\n const lineStart = pos\n\n if (line.startsWith(indentChars)) {\n tr.delete(lineStart, lineStart + indentChars.length)\n pos = lineStart + line.length - indentChars.length + 1\n } else {\n pos = lineStart + line.length + 1\n }\n }\n\n dispatch(tr)\n }\n\n return true\n }\n }\n },\n\n /**\n * Add keyboard shortcuts for indent/outdent functionality\n */\n addKeyboardShortcuts() {\n return {\n Tab: () => (this.options.enabled ? this.editor.commands.indent() : false),\n 'Shift-Tab': () => (this.options.enabled ? this.editor.commands.outdent() : false)\n }\n }\n})\n\nexport const indentCSS = null // No CSS needed for this approach\n"],"mappings":"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","names":["src_exports","__export","Indent","indentCSS","__toCommonJS","import_core","import_state","Indent","isNodeAllowed","state","allowedNodeTypes","node","tr","dispatch","selection","indentChars","lines","pos","line","lineStart","lineEnd","madeChanges","$cursor","cursorPos","textBeforeCursor","isAtLineStart","indentCSS"]}