New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: QuillJS Delta <-> SuperEditor translation with no diffing #1883
base: main
Are you sure you want to change the base?
Conversation
@roughike FYI - as I've investigated approaches to undo/redo, I'm close to coming to the conclusion that perhaps the easiest way to achieve state jumps is to internally use Quill Deltas as a memento representation. I imagine that would greatly reduce the need for any kind of additional or external mapping to Deltas. Would you like to discuss this? |
@matthew-carroll @roughike Is it correct to say that this PR would provide the only way that one can achieve collaborative editing with SuperEditor? Edit: (Simply) |
@theniceboy any user can implement their own |
Got it. I was curious if SuperEditor implements a well established OT format. The Quill Delta format, for example, has support in many backend languages and it's easy to implement collaborative editing because a simple |
From my perspective, Quill Deltas are one way to implement undo/redo, but totally not needed. I think one good way to implement undo/redo is as follows:
For example: class UndoRedoStack {
final _undoStack = <EditRequest>[];
final _redoStack = <EditRequest>[];
void recordDocumentChange(EditRequest change) {
// Add the inverted version of the change into the undo stack.
_undoStack.add(change.invert());
// Since redo is only available after the user undoes operations, we should
// clear the redo stack every time the user changes the document manually.
_redoStack.clear();
}
EditRequest? undo() => _applyChange(_undoStack, _redoStack);
EditRequest? redo() => _applyChange(_redoStack, _undoStack);
EditRequest? _applyChange(
List<EditRequest> source,
List<EditRequest> destination,
) {
if (source.isEmpty) return null;
final change = source.removeLast();
destination.add(change.invert());
// Return the EditRequest that should be applied to the document to perform
// the desired undo/redo operation.
return change;
}
} This would already implement basic undo/redo functionality. Only thing needed is the Adding throttling and composing edit requests together within a timeframe could be done with yet another method You can basically copy-paste this, change One other thing is being able to "transform the stacks". If undo stack has a single change, "delete (c) from position 2", and a remote user inserts "X" in the beginning of the document, then "delete (c) from position 2" has to become "delete (c) from position 3" so that the collaboration works properly. It's a bit out of scope for a general purpose editor, but it's already implemented in the Delta version of undo/redo I linked above. It would require yet another tl;dr: Undo/redo does not require Quill, and I'm not sure if using Quill will make it easier. We'd still need to translate from Quill <-> SuperEditor. After my PR lands, I think it would be easier to just drop-in my Quill undo/redo stack, but there would be a lot of back-and-forth Quill <-> SuperEditor translation ping-pong there. The undo/redo stack could and should probably be implemented without Quill. |
@roughike I've already done a bit of work/investigation into this. Encoding reverse actions is proving to be a headache. PR: #1881 Write-up: https://github.com/superlistapp/super_editor/wiki/Design-Thoughts:-Undo-Redo I'm at the point where I'm considering using Deltas so that all changes can be serialized, such that those changes can be played back to move to an earlier history. I understand that Quill Deltas aren't necessary - it could be any delta format. But if I'm going to implement a delta format, I would think that using the Quill Delta format would have the most crossover value in terms of what Superlist needs, as well as other clients who have asked for Delta support. |
This PR only adds support for translating SuperEditor documents and changes on SuperEditor documents into Quill Deltas. There's no collaboration support coming in this PR - Quill Deltas are just a way to represent:
Quill Deltas by themselves don't add collaboration capabilities to SuperEditor. So in a way, this PR is not much different from a Markdown <-> SuperEditor converter (except that Markdown does not have a way of saying "insert (a) at position 2"). Whenever this PR ready, SuperEditor will still have no collaboration support. However, if you dig a bit into Quill Deltas, you'll find that the Delta format is specifically built for Google Docs style realtime collaboration support with Operational Transformation. So having a Quill Delta support for SuperEditor is one building block for supporting realtime collaboration with OT, as it's a really nice data format and an utility library for that. |
To me, Playing back I will take a look at what you've written in detail, but I'm also up for brainstorming about this. |
I think that most/all of the complexity is in the "playing back" part. Maybe we can chat on Wed? |
@matthew-carroll I will join the call about 15-20mins late. |
… multiple attributions
A very long-running PR for a plug-in SuperEditor <-> QuillJS Delta translation layer.
Here be dragons - don't use this yet and don't bother reviewing it. It's very early and a lot of things will change.
super_editor_quill.mov
TBD - incomplete list, will be updated
🚧 (in progress): Delta -> SuperEditor for attributions (bold, italics, underline, custom attribute) - can be done today, no blockers.
\n
is done properly.hr
andimage
- should be customizable. A task element should not be built-in, but it should be easy to create a custom task element.img: <url>
instead ofimage:<url>
, that should be allowed. Likewise,bold: true
by default, but should be customizable tochonky: 'yes'
or whatever if someone wants to do so.What is this?
It's a new package called
super_editor_quill
- a two-way translation layer between SuperEditorEditEvent
s and QuillJS Deltas that does not require document diffing.It allows us to generate QuillJS Deltas from SuperEditor
EditEvent
s. It also allows us to convert a QuillJS Delta to SuperEditorEditRequest
s that can be applied to the SuperEditor document.There's an example app where there's a SuperEditor and a QuillJS editor side-by-side. Editing the SuperEditor document contents updates the Quill editor contents and vice-versa. All edits are also displayed in a list. This allows us to test that the translation works in practice.
What is it not?
This package does not handle conflict resolution or Operational Transformation in any way. It just converts SuperEditor
EditEvent
s toDelta
s andDelta
s to SuperEditorEditRequest
s.It's conceptually the same as SuperEditor Document <-> Markdown converter - things are converted to one format and back. The only distinction is that Markdown represents a whole document, but a Quill Delta can represent a document and a surgical change in a document. This package deals with the latter.
Although this package does not solve any of the Operational Transformation parts of the equation, it's one very significant building block of it.
Current API (subject to change most likely)