tiptap: fix indent

This commit is contained in:
fantasticit 2022-04-29 17:11:57 +08:00
parent 473e2958c0
commit 585e0aa7a6
1 changed files with 85 additions and 21 deletions

View File

@ -1,7 +1,13 @@
import { Command, Extension } from '@tiptap/core'; import { Command, Extension } from '@tiptap/core';
import { sinkListItem, liftListItem } from 'prosemirror-schema-list'; import { sinkListItem, liftListItem } from 'prosemirror-schema-list';
import { TextSelection, AllSelection, Transaction } from 'prosemirror-state'; import { TextSelection, AllSelection, Transaction } from 'prosemirror-state';
import { isListActive, getNodeType } from 'tiptap/prose-utils'; import { isListActive, isListNode, clamp, getNodeType } from 'tiptap/prose-utils';
type IndentOptions = {
types: string[];
indentLevels: number[];
defaultIndentLevel: number;
};
declare module '@tiptap/core' { declare module '@tiptap/core' {
interface Commands { interface Commands {
@ -12,12 +18,35 @@ declare module '@tiptap/core' {
} }
} }
export type Options = { export enum IndentProps {
type: 'space' | 'tab'; min = 0,
size: number; max = 210,
}; more = 30,
less = -30,
}
const updateIndent = (tr: Transaction, options: Options): Transaction => { function setNodeIndentMarkup(tr: Transaction, pos: number, delta: number): Transaction {
if (!tr.doc) return tr;
const node = tr.doc.nodeAt(pos);
if (!node) return tr;
const minIndent = IndentProps.min;
const maxIndent = IndentProps.max;
const indent = clamp((node.attrs.indent || 0) + delta, minIndent, maxIndent);
if (indent === node.attrs.indent) return tr;
const nodeAttrs = {
...node.attrs,
indent,
};
return tr.setNodeMarkup(pos, node.type, nodeAttrs, node.marks);
}
function updateIndentLevel(tr: Transaction, delta: number): Transaction {
const { doc, selection } = tr; const { doc, selection } = tr;
if (!doc || !selection) return tr; if (!doc || !selection) return tr;
@ -26,23 +55,50 @@ const updateIndent = (tr: Transaction, options: Options): Transaction => {
return tr; return tr;
} }
const { to } = selection; const { from, to } = selection;
const text = options.type === 'space' ? Array(options.size).fill(' ').join('') : '\t'; doc.nodesBetween(from, to, (node, pos) => {
const nodeType = node.type;
return tr.insertText(text, to); if (nodeType.name === 'paragraph' || nodeType.name === 'heading') {
}; tr = setNodeIndentMarkup(tr, pos, delta);
return false;
}
if (isListNode(node)) {
return false;
}
return true;
});
export const Indent = Extension.create<Options>({ return tr;
}
export const Indent = Extension.create<IndentOptions>({
name: 'indent', name: 'indent',
addOptions() { addOptions() {
const options: Options = { return {
type: 'space', types: ['heading', 'paragraph'],
size: 2, indentLevels: [0, 30, 60, 90, 120, 150, 180, 210],
defaultIndentLevel: 0,
}; };
},
return options; addGlobalAttributes() {
return [
{
types: this.options.types,
attributes: {
indent: {
default: this.options.defaultIndentLevel,
renderHTML: (attributes) => ({
style: `margin-left: ${attributes.indent}px!important;`,
}),
parseHTML: (element) => parseInt(element.style.marginLeft) || this.options.defaultIndentLevel,
},
},
},
];
}, },
addCommands() { addCommands() {
@ -56,11 +112,15 @@ export const Indent = Extension.create<Options>({
return sinkListItem(type)(state, dispatch); return sinkListItem(type)(state, dispatch);
} }
const _tr = updateIndent(tr, this.options); const { selection } = state;
if (_tr.docChanged) { tr = tr.setSelection(selection);
dispatch?.(_tr); tr = updateIndentLevel(tr, IndentProps.more);
if (tr.docChanged) {
dispatch && dispatch(tr);
return true; return true;
} }
return false; return false;
}, },
outdent: outdent:
@ -72,11 +132,15 @@ export const Indent = Extension.create<Options>({
return liftListItem(type)(state, dispatch); return liftListItem(type)(state, dispatch);
} }
const _tr = updateIndent(tr, this.options); const { selection } = state;
if (_tr.docChanged) { tr = tr.setSelection(selection);
dispatch?.(_tr); tr = updateIndentLevel(tr, IndentProps.less);
if (tr.docChanged) {
dispatch && dispatch(tr);
return true; return true;
} }
return false; return false;
}, },
}; };