Improve plugins design
See original GitHub issueDo you want to request a feature or report a bug?
Feature
What’s the current behavior?
Right now it’s so hard to enclose logic together inside a plugin due to the lack of method like e.g. onKeyDown
Slate: 0.50
What’s the expected behavior?
I think the philosophy behind plugins or what makes it a great pattern in software is that it helps us to enclose all the logic that belongs to a single feature together, in one place and actually, that’s where his name comes from: plug-in.
Today I was writing a mention plugin and I notice how my code was dispersed through many files, making it hard to understand and far away from what a plugin is.
I think that we should add methods like onKeyDown
to the editor interface, to help us improve and enclose more our plugins.
Also, our goal with plugins should be that we only need a plugin to implement a complete feature, this way the community is going to be able to enrich more the Slate ecosystem through plugins to do many things.
That’s my humble opinion.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:1
- Comments:10 (3 by maintainers)
Hey @focux thanks for opening this. I understand the frustration. I think there’s definitely a missing piece right now for making it easier to tie interaction into inputs to Slate’s
editor
. I’m just not sure that putting them in plugins is the way to do it.Definitely share here what you think they are doing well that we can take. I’d be interested to hear more about Next’s and Figma’s. (Although I don’t really want to copy anything Webpack has done because I think it’s my least favorite piece of software 😄)
I’m not sure that plugins by definition have to be able to override every single behavior. We could call them something else if that’s people’s impression. It seems like a way to guarantee that they become amorphous and hard to reason about.
Why No Events?
Like @CameronAckermanSEL mentioned, there were reasons that the event-related logic was taken out of plugins. Some of them have to do with how modern browsers are moving away from pure keyboard inputs, some have to do with how React’s future seems to involve less direct DOM event usage, and some have to do with ensuring that plugins are flexible.
Modern browsers, especially mobile ones, have a lot of rich text input types that no longer map 1-to-1 to keyboard events. And when composition due to IME is concerned things get even murkier. In earlier versions of Slate plugins would often be overriding the wrong triggers to provide functionality. Using commands instead makes it clear what they should be overriding.
For example, earlier plugins would often listen for keydown events of <kbd>-</kbd><kbd>Space</kbd> for trigger “auto-list-item” logic. But this becomes leaky on platforms that don’t trigger events for spacebar. Or, listening for <kbd>Cmd</kbd><kbd>B</kbd> for bold, which doesn’t catch all cases where a
beforeinput
with input type offormatBold
occurs (eg. right-click or the <kbd>B</kbd> button in the iOS toolbar). There are lots of leaky things that happen when listening to a single DOM event to capture intent because of cross-browser compatibility.I think I’d like to keep Slate’s editor mixins separate from the complexity of DOM events.
We’ve kind of forgotten how hard it is to manage incompatible events for the majority of app-related events because they’ve been standardized fairly well by browsers these days. And before they were, React’s own Synthetic Events system paved over them for us.
If the Synthetic Events were extendable I’d probably have done that.
But it seems like React is attempting to solve some of this with their “Flare” (for example the
usePress
hook) project. It’s still in unstable territory, but it seems to be moving to using Hooks instead of classic DOM event handlers to listen for events. One of the benefits of this is abstracting over the DOM event layer to potentially have a one-to-many relationship between intent and events. Hopefully it will be extendable, it seems like it will.(React’s Synthetic Events already work this way actually—they are often one-to-many without us realizing it.)
Using Hooks?
I could imagine a future where we have a hook like
useBold
:Which help you ensure you’re handling all the cases by listening for either:
beforeinput
wheninputType === 'formatBold'
Potentially this could be something that
slate-react
maintains in it’s own codebase, to make it easy for people to stay consistent, since I agree that right now it’s a huge pain to get all of the cases for DOM events right.Implementing Mentions
I think mentions is one of the hardest things to implement in the current architecture. And I think it’s because it has a lot of complications…
The standard way to do it involves keeping “state” in the actual content when a user types
@ma
for instance on their way to typing a whole name. And then it also needs to toggle open/closed some sort of suggestions list. And not only that, but then map the standard keyboard events inside the<Editable />
to controlling the active state of the suggestions. That’s a lot of overlapping concerns.It’s much harder than other floating toolbar-type use cases where once the toolbar is opened it can listen to its own events until it’s closed. But it also has never been easy to implement I feel like, even in prior versions of Slate it seemed like the feature people struggled with the most.
“Feature” Plugins?
Previously Slate recommended grouping “features” together into a single plugin. But I’m not sure this was a good idea in retrospect, for a couple reasons.
One thing is that there are often “helpers” that a given feature needs. If it’s lists then you might want to have a
indentList
,outdentList
,wrapList
,unwrapList
, etc. And these things are schema-specific, because they depend on the naming schemes that the user has chosen.Previously in the case of things like GitBook’s
slate-edit-list
plugin these would be return after calling a function. But this is really awkward. It makes it very hard for people to keep track of them all in one place. It makes much more sense for the helpers to beexport
ed directly from the package and then pass in the configuration when calling them instead.Also problematic was the idea of “disabling” plugins. If a plugin contains all the logic for lists, this includes the schema rules and commands. When you “disable” it, you don’t actually want the schema for list items to be removed. You still want to ensure that any existing list items are correctly formed. But you might instead want to tweak your top-level schema to remove any list items it finds. Similarly, you don’t want a
format_list
command to suddenly not exist as a concept anymore, you just want to turn it into a no-op.Further, if people want to use plugin-specific behaviors on the server-side, it’s problematic that the plugins contains tons of DOM-related and React-related logic.
Anyways, I’d love to hear other people’s takes on this. What other options there might be for us to make it easier for people to implement these more complex behaviors. Or even how plugins might be improved to make some of this stuff easier—hopefully with coupling concerns too much.
I don’t claim that we’ve gotten it right yet, just that there were a handful of reasons for making the change. In general I think they make things easier to reason about. But it might take adjusting to figure out how to approach them in the new architecture.
For anything that was possible before but now isn’t I’d be curious to hear. Hopefully we can figure out a way to solve it.
One more thing I’ll add is that this is easier said than done. The more we abstract into a plugin and hide from a potential user, the more likely a user is going to have to deal with behavior conflicts. Consider a plugin that provides schema rules for the document structure. How do we manage a case where multiple plugins provide conflicting rules? What happens when a plugin provides some features that you don’t want, but doesn’t provide a mechanism to turn these off? You have to fork right? That defeats the purpose of these plugins a lot of the time.
One major advantage to “some assembly required” is that the end user of the plugin gets more hands on control with how the plugin is integrated into their editor ecosystem. Even if the plugin is huge and has a lot of behaviors that are potentially undesirable, now it’s easier to turn them off.
I think ultimately this new plugin user experience is not that much different to how a typical library is consumed. A user will have to export some functions, read some examples on how they’re hooked up. I understand that the “plug and play” experience is not there anymore in quite the same way, but I think largely rich text editing toolkits require careful attention to detail so this might be better in the long run.
I also disagree with the implied assertion that plugins that require some additional hook up will be harmful to the ecosystem. We can still have a rich pool of plugins that provide a great deal of high value behaviors. I’m skeptical that this is going to be a material disincentive for plugin maintainers to produce plugins. The folks who would be producing plugins will still do so regardless of whether the consumer needs to do additional hook up.