Moving from a list of nodes to a tree #2403
Replies: 5 comments 15 replies
-
CC @miguelcmedeiros @brian-superlist @knopp @Jethro87 @jmatth @aloisdeniel @BazinC @jezell - I'd appreciate your thoughts on this possible shift in |
Beta Was this translation helpful? Give feedback.
-
I hope you don't mind me chiming in, but this is something I am very interested in. I've been working on creating an outline editing package based on super_editor in the last weeks, and this would make things much easier. With my work based on a recent super_editor from main branch, I decided to go for an To illustrate this, here a short window of the editor. My notion of a Treenode means a data structure that contains a title DocumentNode and zero or more ParagraphNodes. As the DocumentNodes are linearly exposed via the MutableDocument interface, the core super editor library does not need any knowledge about my tree, but commands, reactions and keyboard actions do, by casting the editor's document to an OutlineTreeDocument. While the caret can be moved seamlessly between the nodes, selections are modified by a reaction to assure the user does not violate the tree structure. I also use reactions to take care of the caret jumping over hidden areas, when parts of the document are folded, as with super_editor as is I did not find a way to implement this earlier in the pipeline without having to rewrite larger parts. outline_editor.mp4While the distinction between a "tree node", that only defines logical document structure, and a DocumentNode for the editing experience lead me to a somewhat confusing nomenclature and may not fit for every use case, it worked out for me, as outline editing is very much a structure thing. My main project is not far enough that I could really judge if my approach holds in the long run, but it works fine enough for now. Right now, I stopped working on the outline library, as I suspect with the advent of immutable nodes and your work on a tree structure, a bigger refactoring or rewrite will happen anyway, and because for me it does enough for now to continue on other aspects of my project. |
Beta Was this translation helpful? Give feedback.
-
I can see how it is definitively more complex, but in my opinion it is also required if super_editor wants to be the final boss of editors. Implementing tables, or columns layouts with current implementation is tedious, and having a tree representation would make their implementation simply natural. A nice implementation that can be used as reference is ProseMirror. The documentation is a great source of inspiration! Also, a great library that should be considered is Yjs, and more specifically its Rust+FFI implementation : y-crdt. This allows real time updates from multiple sources, and smartly resolve conflicts. It solves a lot of issues, and has a tree representation. |
Beta Was this translation helpful? Give feedback.
-
should it be not tree, but something like custom embeds in Quill delta? New table module was implemented using this feature It allows embed any custom type, but the most trivial is embedded delta. |
Beta Was this translation helpful? Give feedback.
-
This question touches on a lot of different subjects that I'm struggling to tie together in a coherent way, so I'll just summarize my opinions up front:
> If anyone has good alternative suggestions, I'd like to hear them.I think a tree structure is the right way to go to implement the features you listed, but to play devil's advocate for a second: you can get most or all of the way to each of those features without a tree.
Regarding yjs and similarA couple other comments have already called out the yjs/yrs libraries as a potential way to store document state. As much as I'd like sync as a natively supported feature in SuperEditor, I don't think adding a dependency on FFI is the way to go. I think a more reasonable approach is to consider that some implementations may use other data structures as their source of truth, in which case
This is actually possible right now, and I have a very basic and barely working proof of concept that uses Loro to store and sync two SuperEditor documents. Regarding ProseMirrorProseMirror was also already mentioned in another comment, so I'll just add a +1 to using it as a reference if/when SuperEditor makes significant changes to its document structure. It is successful enough in the JS space to have at least one commercialized offering built on it (TipTap, which I think also uses yjs for sync), supports an official sync/collaboration interface without being opinionated about the implementation, and has a change transaction system with an official undo/redo plugin built on top of it. It seems ideal as a document editor framework. I don't know that I would call any of these implementations replacements for a truly tree based document structure, but it is possible to approximate such features using just the current flat list implementation. |
Beta Was this translation helpful? Give feedback.
-
We have some features that we want to implement, which imply a document hierarchy where currently there is none.
For example, we want to support tables, where each cell contains any number of other nodes. We also want to make it possible for clients to create the concept of "banners", which are decorated boxes that contain any number of other nodes.
These features impact painting, layout, and UX. E.g.,
These requirements seem to point towards the need for a tree-based document, instead of a list-based document. This requirement isn't a surprise. Most document formats are structured as trees, e.g., XML, HTML, Markdown. However, we wanted to take our simplistic document list as far as we could before investing in the complexity of a tree-based document. Along those lines, if there's a reasonable path to implement these requirements without moving away from a document list, I'd like to hear about it.
API Problems
By leaning into a document list instead of a document tree, we've created a number of heavily used APIs that don't necessarily make sense in a tree.
Document APIs:
Edit requests:
In other words, all APIs that imply a natural order to nodes are now ambiguous, at best.
We can, in theory, continue to support these APIs by selecting a single tree traversal policy. For example, we can say that we'll always traverse the tree in a depth-first order. This would retain ordering, and would allow us to speak about each node as sitting at a specific index. However, it's unclear if any of the existing uses of the aforementioned APIs would work as expected with tree traversal indices. For example, the composite nodes themselves would be traversed, and would receive an index. Should composite nodes interact with UX policies the same way as a paragraph? Does it make any sense for the caret to placed within a composite node? Probably not.
Comparisons
HTML
HTML is probably the most popular document format due to its use in every webpage. HTML is based on the DOM, which is comprised of a tree of Elements:
https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API
HTML Elements are notoriously annoying to work with. The use of this tree structure eventually lead to the advent of JQuery - a collection of functions for more easily querying and mutating the DOM.
https://jquery.com/
Markdown
Markdown is a very simple markup language, which is used in many text editors today. This discussion post is written in Markdown.
While the syntax is minimal, the structure remains hierarchical. I don't know the exact reason that Markdown went hierarchical, but one important detail about Markdown is that it officially supports embedding HTML within it. Therefore, Markdown was probably constrained by HTML's existing decision.
Here's a random specification and implementation of a Markdown Abstract Syntax Tree (AST): https://github.com/syntax-tree/mdast
DocX
DocX is Microsoft's document format for Word. It's serialization format is XML, so it's a hierarchy that's similar in nature to HTML.
LaTeX
LaTeX is a document format that's used heavily in academia. It also uses a hierarchical structure.
Example:
Non-Tree Document Formats
In a quick search for popular document formats that aren't tree-based, the only formats I found are Comma-Separated Values (CSV), and Rich-Text Format (RTF). Both of these formats have a far more narrow application than what Super Editor needs to support.
Should we move to a tree structure?
The first question we need to align on is wether a tree-based document is the right move. It will be a significant move, and will almost certainly require significant rework among our users. If anyone has good alternative suggestions, I'd like to hear them.
Should we separate logical nodes from visual nodes?
Currently, every node in a
Document
has a visual purpose: paragraph, image, horizontal rule, list item, task.In a world with hierarchy, we need to decide whether we should use a special type of node for hierarchy (
CompositeNode
), or whether we start from the premise that any node might have children.Beta Was this translation helpful? Give feedback.
All reactions