mismatch between "editor" and "value" concepts
See original GitHub issueDo you want to request a feature or report a bug?
Discussion.
What’s the current behavior?
Just opening this up for discussion, since it’s something that I think Slate hasn’t really solved well so far, and leads to it being hard to decide in either direction for the API…
Right now there are two concepts vying for the “top spot” in the architecture:
-
value
— which holds the document, the selection, the history and the schema. This can easily be created server-side (or wherever), since it’s not coupled to the view layer at all. (I’d considerchange
to be equivalent tovalue
here, since they hold all the same data.) -
editor
— which holds the plugins, the current value, and the schema to an extent. This is less easy to use server-side, since the editor is inherently tied to the view layer. (Although this need not always be the case.)
So far, it’s been unclear which is the “primary” concept. For example…
-
Event handlers are passed both a
change
and aneditor
to allow the user to decide which they want to use. You normally use thechange
, but sometimes, for example if you want to trigger an async change later, you need to use theeditor
. -
For the idea of “commands and queries” from #2066, it’s unclear whether these commands should live inside the schema (or similar) and be exposed as
change.command(...)
. Or whether, since they are tied to the plugins, they should be exposed aseditor.command(...)
. Both have tradeoffs. -
You could make the argument that
history
shouldn’t really be inside thevalue
, and instead be controlled by theeditor
which is the long-lived entity—which would seem totally reasonable to me. -
Right now there’s a confusion in that the editor’s plugins create a final
schema
object that gets set on thevalue
. But the value can also be initialized with its ownschema
, which will be overwritten… and it’s not clear how they interact.
What’s the expected behavior?
I’m not totally sure. This is something that I’ve wrestled with, but never found a nice mental model for being clear about the distinctions between the two. I’d love for anyone with ideas to comment! (Even if you aren’t sure yourself, anything is helpful!)
I think it’s helpful to have a few goals:
-
It would be good for “commands”, “history”, etc. to all be able to be modeled in server-side environments where there is no concept of the “view” layer. This doesn’t necessarily mean it has to be the
value
model, but it has to be something that isn’t tied to the DOM or rendering. -
It would be best if the top-level concept can unify the plugins, schema, history, commands, etc. in a way that makes it clear where all of this stuff stems from.
Issue Analytics
- State:
- Created 5 years ago
- Comments:18 (15 by maintainers)
Top GitHub Comments
Hmm I’ve been wrestling with this one myself. The mental model I have for the Editor is that it is simply the view layer. Given a bunch of props like value / schema etc., Render something to the page. The value is then just the pure state of the app: what is the current snapshot of the app’s state.
I think the problem is that this gives us the “view” and the “model” but it doesn’t really tell us where the controller lives (in a traditional MVC architecture). So the current “controller” is a combination of change, value and the editor that don’t always know how to talk to each other. This is why there is some confusion about whether to make changes you need to talk to the value or the editor.
I wonder if there should be a third component like
Controller
that contains all the event / state management. Then the “editor” is just a simple view layer that renders and proxies interaction events into the controller. The controller would also just be pure non-react JS, easy to work with on the server side. It would probably also be easier to test than the editor.The big downside I see to this is that in React a component is generally also the controller. This might cause us to move further away from React best practices.
I’m just going to leave a summary of my current API architecture thoughts here…
Introduce the
Editor
controller. The piece is to introduce a new “plain JS” controller exposed in the coreslate
module, calledEditor
. It will contain the core editor logic (like ensuring normalization, running through the plugins stack, etc.) that is currently partly inslate-react
. It will be used byslate-react
, or on the server-side by itself, or in the future in non-React view layers to get the basic editor behaviors. If you exposed a Slate-like API to developers you could even bundle theEditor
in your API library.Fold the
Stack
intoEditor
. Once you have the concept of this editor controller, you don’t actually need the stack. It lives in “models” right now but it’s not really a model. It’s just thin layer around some iterations methods that can easily be moved to become methods on theEditor
instance instead. This makes the public API more clear, such that plugins can invoke the stack if they have a need to (as the core plugin currently does).Add a
change.editor
reference. Changes are actually editor-specific instead of value-specific like they are currently architected. This also clears up the idea that changes are “models”, while they are in a sense, they are not immutable like the rest of Slate’s models. By making them editor-aware we can…Remove the
schema
property fromValue
. With the reference tochange.editor
and thuschange.editor.schema
, we no longer need to have thevalue.schema
coupling which is confusing because the schema is really an editor-specific object. This also eliminates another “non-model” model, because the schema isn’t really a “model” in the sense of being serializable or immutable in the way that all other Slate models are. (This is a pattern, there are a handful of things in “models” right now that don’t really belong.)Remove the
value.change()
method. Similarly, with changes now tied to theEditor
controller, changes can no longer be created with only access to avalue
, because they rely on access to the editor’sschema
to make decisions about how the change occurs (for example for void nodes).Add the “commands” concept to the editor. This introduces a new concept called “commands” which are part of the
Editor
and can be defined in plugins. This relates to #2066 and allows for editor-level commands that can be defined in a single place, but used in many. It is very similar in approach to Redux Actions, or the Elm Architecture, in terms of having a pre-defined set of “commands” that can change the state. And now that theChange
objects are editor-aware, each command can be exposed aschange[command]
, exactly like the current change methods. Which brings us to…Add “queries” support. I was originally thinking of these as two separate concepts, but I think instead commands could return values, in which case “queries” are just commands that return values but don’t add any operations to the stack. This way we don’t need to introduce an entirely new concept? Although it can be done either way, introducing a separate “queries” concept would work just as well.
Convert the current “changes” into commands. With the concept of commands introduced, all of what are currently called “changes” can now be implemented as commands in the core plugin, instead of the current custom approach. This is functionally equivalent, but it means that now even the core commands can be overridden for use cases that need it. (This is something that comes up somewhat often in Slack as a question, and isn’t easily solved right now by any other means.)
Convert
History
into commands onvalue.data
. Right now the history is implemented as “changes” but it also special-cased in a way that makes it very hard to customized. With the new pluggable commands concept though, history can change to become three commands:save
,undo
,redo
which are overridable by the user. And instead of needing to live in a customHistory
model, they can be implemented to live in thevalue.data
namespace asdata.undos
anddata.redos
. This way users can implement their own history, using a totally different data structure if they’d like.At the end you end up with a clearer delineations:
<Editor>
,<Island>
,render*
The only overlap remaining that I can see still is that “plugins” end up defining logic that lives in both the Controller and the View layers. But I’m not sure if there’s a way around this. (If anyone has ideas I’m open!)
How does that sound?