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; };