Skip to content
Source
Sign inSign up

composer-undo-grouping.ts

43 lines1.7 KB

1import { closeHistory, isHistoryTransaction } from "@tiptap/pm/history";
2import type { Transaction } from "@tiptap/pm/state";
3import { ReplaceStep } from "@tiptap/pm/transform";
4​
5// Whole-draft rewrites make all edits adjacent; explicit boundaries keep change kinds separate.
6export const COMPOSER_UNDO_GROUP_DELAY = 1000;
7​
8export type ComposerChangeKind = "insert" | "delete" | "other";
9​
10/** Tag custom paste/cut dispatches; ProseMirror only tags its built-in paths. */
11export function markAsClipboardEdit(tr: Transaction, event: "paste" | "cut"): Transaction {
12 return tr.setMeta("uiEvent", event);
14​
15function changeKindOf(tr: Transaction): ComposerChangeKind | null {
16 if (!tr.docChanged) return null;
17 const uiEvent = tr.getMeta("uiEvent");
18 if (uiEvent === "paste" || uiEvent === "drop" || uiEvent === "cut") return "other";
19 // IME input replaces its own range on each keystroke; it is still typing.
20 if (tr.getMeta("composition") != null) return "insert";
21 let kind: ComposerChangeKind | null = null;
22 for (const step of tr.steps) {
23 if (!(step instanceof ReplaceStep)) return "other";
24 const inserts = step.slice.size > 0;
25 const deletes = step.to > step.from;
26 const stepKind = inserts && !deletes ? "insert" : deletes && !inserts ? "delete" : "other";
27 if (kind !== null && kind !== stepKind) return "other";
28 kind = stepKind;
29 }
30 return kind;
32​
33/** Must run before the transaction is applied. */
34export function groupUndoByChangeKind(
35 tr: Transaction,
36 previous: ComposerChangeKind | null,
37): ComposerChangeKind | null {
38 if (isHistoryTransaction(tr)) return "other";
39 const kind = changeKindOf(tr);
40 if (kind === null) return previous;
41 if (previous !== null && (kind !== previous || kind === "other")) closeHistory(tr);
42 return kind;