fix double saving of undo states

This commit is contained in:
fcolona 2025-07-28 14:31:11 -03:00
parent 84de8aec3e
commit becac6ade8
2 changed files with 35 additions and 10 deletions

View file

@ -40,7 +40,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const key = Symbol("richText"); const key = Symbol("richText");
const [context, setContextProperty] = contextProperty<RichTextInputAPI>(key); const [context, setContextProperty] = contextProperty<RichTextInputAPI>(key);
const [globalInputHandler, setupGlobalInputHandler] = useInputHandler(); const [globalInputHandler, setupGlobalInputHandler] = useInputHandler( { handleUndo: false });
const [lifecycle, instances, setupLifecycleHooks] = const [lifecycle, instances, setupLifecycleHooks] =
lifecycleHooks<RichTextInputAPI>(); lifecycleHooks<RichTextInputAPI>();
const apiStore = writable<SurroundedAPI | null>(null); const apiStore = writable<SurroundedAPI | null>(null);
@ -97,7 +97,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const nodes = getNormalizingNodeStore(); const nodes = getNormalizingNodeStore();
const [richTextPromise, resolve] = useRichTextResolve(); const [richTextPromise, resolve] = useRichTextResolve();
const { mirror, preventResubscription } = useDOMMirror(); const { mirror, preventResubscription } = useDOMMirror();
const [inputHandler, setupInputHandler] = useInputHandler(); const [inputHandler, setupInputHandler] = useInputHandler({ handleUndo: true });
const [customStyles, stylesResolve] = promiseWithResolver<Record<string, any>>(); const [customStyles, stylesResolve] = promiseWithResolver<Record<string, any>>();
export function attachShadow(element: Element): void { export function attachShadow(element: Element): void {

View file

@ -96,23 +96,37 @@ function getCaretPosition(element: Element) {
* Prevents that too many event listeners are attached and allows for some * Prevents that too many event listeners are attached and allows for some
* coordination between them. * coordination between them.
*/ */
function useInputHandler(): [InputHandlerAPI, SetupInputHandlerAction] { interface InputHandlerOptions {
handleUndo: boolean;
}
function useInputHandler(
options: InputHandlerOptions,
): [InputHandlerAPI, SetupInputHandlerAction] {
const beforeInput = new HandlerList<InputEventParams>(); const beforeInput = new HandlerList<InputEventParams>();
const insertText = new HandlerList<InsertTextParams>(); const insertText = new HandlerList<InsertTextParams>();
const afterInput = new HandlerList<EventParams>(); const afterInput = new HandlerList<EventParams>();
const undoManager = new UndoManager(); let undoManager: UndoManager | null = null;
let hasSetupObserver = false; let isUndoing = false;
const config = {
let observer: MutationObserver | null = null;
const observerConfig = {
attributes: true, attributes: true,
childList: true, childList: true,
subtree: true, subtree: true,
}; };
const observer = new MutationObserver(onMutation); let hasSetupObserver = false;
if (options.handleUndo){
undoManager = new UndoManager();
observer = new MutationObserver(onMutation);
}
function onMutation(mutationsList: MutationRecord[]) { function onMutation(mutationsList: MutationRecord[]) {
if(isUndoing) return;
const element = <Element> mutationsList[0].target; const element = <Element> mutationsList[0].target;
undoManager.register(element.innerHTML, getMaxOffset(element)); undoManager!.register(element.innerHTML, getMaxOffset(element));
} }
async function onBeforeInput(this: Element, event: InputEvent): Promise<void> { async function onBeforeInput(this: Element, event: InputEvent): Promise<void> {
@ -149,14 +163,17 @@ function useInputHandler(): [InputHandlerAPI, SetupInputHandlerAction] {
} }
async function onInput(this: Element, event: Event): Promise<void> { async function onInput(this: Element, event: Event): Promise<void> {
await afterInput.dispatch({ event });
if (!undoManager) { return; }
if (!hasSetupObserver) { if (!hasSetupObserver) {
observer.observe(this, config); observer!.observe(this, observerConfig);
hasSetupObserver = true; hasSetupObserver = true;
} }
const position = getCaretPosition(this); const position = getCaretPosition(this);
undoManager.register(this.innerHTML, position - 1); undoManager.register(this.innerHTML, position - 1);
undoManager.clearRedoStack(); undoManager.clearRedoStack();
await afterInput.dispatch({ event });
} }
const pointerDown = new HandlerList<{ event: PointerEvent }>(); const pointerDown = new HandlerList<{ event: PointerEvent }>();
@ -186,11 +203,19 @@ function useInputHandler(): [InputHandlerAPI, SetupInputHandlerAction] {
} else if (event.code === "Tab") { } else if (event.code === "Tab") {
specialKey.dispatch({ event, action: "tab" }); specialKey.dispatch({ event, action: "tab" });
} else if ((event.ctrlKey || event.metaKey) && event.key == "z") { } else if ((event.ctrlKey || event.metaKey) && event.key == "z") {
if (!undoManager) { return; }
event.preventDefault(); event.preventDefault();
isUndoing = true;
undoManager.undo(this); undoManager.undo(this);
setTimeout(() => { isUndoing = false }); //reset the flag when the current execution stack is cleared
} else if ((event.ctrlKey || event.metaKey) && event.key == "y") { } else if ((event.ctrlKey || event.metaKey) && event.key == "y") {
if (!undoManager) { return; }
event.preventDefault(); event.preventDefault();
isUndoing = true;
undoManager.redo(this); undoManager.redo(this);
setTimeout(() => { isUndoing = false }); //reset the flag when the current execution stack is cleared
} }
} }