feat(text-editor): add SharedTree-based undo/redo with transactions#26338
feat(text-editor): add SharedTree-based undo/redo with transactions#26338brrichards wants to merge 9 commits intomicrosoft:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This pull request adds undo/redo functionality to the formatted text editor using SharedTree's Revertible API and wraps Quill operations in transactions for atomic undo/redo behavior.
Changes:
- New
undoRedo.tsmodule withcreateUndoRedoStacks()function for managing undo/redo stacks - Transaction wrapping in
quillFormattedView.tsxusingTree.runTransaction()for atomic operations - API updates to pass
treeViewEventsto FormattedMainView and expose undo/redo methods via ref handle - Test updates to accommodate new API and add basic undo/redo tests
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| examples/data-objects/text-editor/src/formatted/undoRedo.ts | New file implementing undo/redo stack management using SharedTree's Revertible API |
| examples/data-objects/text-editor/src/formatted/quillFormattedView.tsx | Wraps Quill delta operations in transactions, disables built-in Quill history, adds undo/redo toolbar handlers, exposes undo/redo via ref handle |
| examples/data-objects/text-editor/src/test/textEditor.test.tsx | Updates all FormattedMainView usages to pass treeViewEvents, adds two new undo/redo tests |
| examples/data-objects/text-editor/src/app.tsx | Updates view component signatures to receive treeView parameter and passes treeViewEvents to FormattedMainView |
examples/data-objects/text-editor/src/formatted/quillFormattedView.tsx
Outdated
Show resolved
Hide resolved
examples/data-objects/text-editor/src/formatted/quillFormattedView.tsx
Outdated
Show resolved
Hide resolved
examples/data-objects/text-editor/src/formatted/quillFormattedView.tsx
Outdated
Show resolved
Hide resolved
| if (op.attributes) { | ||
| root.formatRange(cpPos, cpCount, quillAttrsToPartial(op.attributes)); | ||
| // Wrap all tree mutations in a transaction so they undo/redo as one atomic unit | ||
| TreeAlpha.branch(root)?.runTransaction(() => { |
There was a problem hiding this comment.
I don't think we want to no-op if the node is not part of a branch.
If the node is unhydrated, this code should still be able to work (and if it did require a branch, it should error if there isn't one rather than doing a no-op).
Maybe something like (TreeAlpha.branch(root)?.runTransaction ?? (f) => f()) )(() => {
@noencke: it would be nice if we had an easy way to group edits if a node is part of a branch, but do the edits anyway if not part of a branch. Such an API couldn't support rollback via return value, but might be handy. I think this is a usability gap with the new branch based API.
There was a problem hiding this comment.
What if we used an if else. So if the node is unhyrdrated we apply the edits directly. Something like:
const branch = TreeAlpha.branch(root);
const applyDelta = (): void => {
// ... all changes (unchanged) ...
};
if (branch === undefined) {
applyDelta()
else {
branch.runTransaction(applyDelta);
}
I saw this being used in tableSchema.ts for unhydrated nodes and looks like it could be used here as well
There was a problem hiding this comment.
I will attempt to address this in my next revision of the APIs. If we have branch() return either a tree view in the hydrated case (as it currently does) or a new "unhydrated tree branch" in the unhydrated case, then we can have runTransaction live on both and you can do TreeAlpha.branch(node).runTransaction regardless of whether node is hydrated or not. In the unhydrated case, the "transaction" will not actually be a transaction, it will just run the edits as normal.
Summary
RevertibleAPIChanges
Undo/Redo Implementation (
undoRedo.ts)createUndoRedoStacks()function that listens tocommitAppliedeventsRevertibleobjectsCommitKind.UndovsCommitKind.Defaultto route revertibles to the right stackTransaction Support (
quillFormattedView.tsx)Tree.runTransaction()so complex edits (delete + insert + format) undo/redo as a single unit