import type { Schema } from 'prosemirror-model' import { type Transaction, TextSelection, AllSelection } from 'prosemirror-state' import type { EditorView } from 'prosemirror-view' import { isList } from '../utils' type IndentKey = 'indent' | 'textIndent' function setNodeIndentMarkup(tr: Transaction, pos: number, delta: number, indentKey: IndentKey): Transaction { if (!tr.doc) return tr const node = tr.doc.nodeAt(pos) if (!node) return tr const minIndent = 0 const maxIndent = 8 let indent = (node.attrs[indentKey] || 0) + delta if (indent < minIndent) indent = minIndent if (indent > maxIndent) indent = maxIndent if (indent === node.attrs[indentKey]) return tr const nodeAttrs = { ...node.attrs, [indentKey]: indent, } return tr.setNodeMarkup(pos, node.type, nodeAttrs, node.marks) } const setIndent = (tr: Transaction, schema: Schema, delta: number, indentKey: IndentKey): Transaction => { const { selection, doc } = tr if (!selection || !doc) return tr if (!(selection instanceof TextSelection || selection instanceof AllSelection)) return tr const { from, to } = selection doc.nodesBetween(from, to, (node, pos) => { const nodeType = node.type if (nodeType.name === 'paragraph' || nodeType.name === 'blockquote') { tr = setNodeIndentMarkup(tr, pos, delta, indentKey) return false } else if (isList(node, schema)) return false return true }) return tr } export const indentCommand = (view: EditorView, delta: number) => { const { state } = view const { schema, selection } = state const tr = setIndent( state.tr.setSelection(selection), schema, delta, 'indent', ) if (tr.docChanged) { view.dispatch(tr) return true } return false } export const textIndentCommand = (view: EditorView, delta: number) => { const { state } = view const { schema, selection } = state const tr = setIndent( state.tr.setSelection(selection), schema, delta, 'textIndent', ) if (tr.docChanged) { view.dispatch(tr) return true } return false }