question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

consider adding a "commands" concept

See original GitHub issue

Do you want to request a feature or report a bug?

Feature idea.

What’s the current behavior?

Currently we have three concepts that all contribute to “editing” in Slate editors:

  • Event handlers, whose job is to intercept user input and translate it into changes to the editor’s content that Slate understands.

  • Changes, whose job is to provide high-level ways of manipulating the editor’s content, so that event handlers (and other programmatic use cases) can easily apply changes.

  • Operations, whose job is to convey the lowest-level description of the individual transformations that were applied to the editor’s content, for use in collaborative editing.

However, all of this currently rests on the ability of event handlers to be able to easily read user intent. Sometimes that’s easy, for example detecting when <kbd>enter</kbd> is pressed. But sometimes it can be very convoluted, which leads to a few different problems…

  1. In “soft keyboards”, certain key presses don’t actually fire with complete information (because they can’t), so you can no longer detect keys like <kbd>backspace</kbd> being pressed purely from the keydown event. Getting around this would require all plugins to handle the complexity of <kbd>backspace</kbd> detection themselves.

  2. When plugin’s want to modify behaviors that maybe interact with other plugins, there’s no way for one plugin to guarantee that it has hooked into all instances of a particular user intent. For example, a plugin may listen for <kbd>cmd+b</kbd> to catch “bold” intent, without knowing that another plugin introduced a shortcut that makes <kbd>cmd-*</kbd> also trigger bold formatting. This isn’t solvable without an extra “intentful” layer.

  3. Browsers differ as to which behaviors they support natively in contenteditable elements, and some browsers provide extra UI for certain actions. For example, iOS provides a contextmenu that includes <kbd>B/I/U</kbd> for formatting. Since Slate is schema-agnostic, there’s no way to translate the “schema-specific” actions from these context menus into behaviors in a Slate editor.

There might be a way to solve this…

What’s the expected behavior?

We’d introduce a new layer of logic for editing that is focused on user intent. One one side of it would be the middleman between event handlers, browser-specific contextmenu UIs, and other client-specific behaviors. And on the other side, would be the Slate-specific changes and operations that change the editor’s content.

Based on @majelbstoat’s previous discussion, it think it could be good to call it “commands”. This also maps nicely to browser’s concept of execCommand.

It would be similar in concept to the beforeinput/input events from the Input Events spec, except that it would be browser and device agnostic, and it would be extendable by developers and plugins.

For example, consider mapping many different inputs to the same 'bold' command…

cmd+b -> bold
** -> bold
click bold button -> bold

And then defining what 'bold' does in a single place:

onCommand(command, change, editor) {
  switch (command.type) {
    case 'bold': {
      change.toggleMarkAtRange(command.range, 'bold')
      return
    }
    ...
  }
}

This seems good. But it brings up some questions:

  • Should commands all take a range object that defaults to the current selection? This seems like a good way to do it, especially since that’s how InputEvent works, where each action corresponds to a range in the document (collapsed or not). Or, more simply, are commands always performed in the context of the user on the current selection?

  • How should actions take in extra information about the action? For example, for inserting text we need a way to specify the text to insert. For dynamic mark/blocks we might need a way to pass in mark.data or block.data properties. The InputEvent spec handles this by using a combination of event.data and event.dataTransfer properties, so we might be able to get away with a single options/data dictionary.

  • Might this eliminate the need for the “current selection transforms”? We have things like change.insertText(...), but if there’s a command for it instead, we could just do editor.command('insert_text', ...) instead and simplify the number of concepts.

  • How would commands define their “merging” behaviors with previous commands? For example if two insert_text commands occur adjacent to each other, they should be merged into a single entry in the history’s undo stack (like other editors).

  • Do we need an equivalent of the document.queryCommandState API for determining which way certain commands will toggle? For use in determining whether toolbar buttons should be active or not for example.


@majelbstoat you mentioned in https://github.com/ianstormtaylor/slate/issues/1730 that you use this pattern in Medium. Is there any way you could describe your architecture/findings? Any information would be super helpful!


Existing Issues

  • #1456 might be helped by this concept, in that it could create more “intentful” layer that can decide on the history boundaries, so merging is more intuitive.

  • #1730 would be helped in as far as grouping of changes/operations is concerned. Although I think from an OT/CRDT point of view you’d still want to be transforming the lowest-level operations instead of higher-level ones.

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:3
  • Comments:11 (6 by maintainers)

github_iconTop GitHub Comments

2reactions
YurkaninRyancommented, Aug 12, 2018

Really at this point it just feels like we are firing actions, and the command handler is just a reducer. I think introducing magic there would actually limit users. We might reduce a lot of code, but use cases outside of our own might be hurt.

It sounds like we can extract the most value from having standardized into action names. And a way for plugins to reference other plugins action names.

For example if I want to hijack slate-edit-list’s command to break and make a nest list item, I could just make a command that that grabs it’s command action, and handles it. Then put my plugin in the correct spot in my plugins order.

1reaction
ianstormtaylorcommented, Sep 4, 2018

@justinweiss I was thinking about something for the toolbar button active states… right now these are determined by one-off functions everywhere. But if we introduce the idea of a “command stack” it seems like we might also need a “query stack” (or similarly named) for determining active states. Something similar to queryCommandState potentially.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Basic concepts for add-in commands - Office Add-ins
Add -in commands are UI elements that extend the Office UI and start actions in your add-in. You can use add-in commands to...
Read more >
Adding New Commands - Guy M. Haas
Adding New Commands. Introduction. One of the things we should demand from a powerful programming language is the ability to build abstractions by...
Read more >
[25] Best Twitch Command Ideas (MUST Have Commands)
But with so many commands out there, how can you know which to include in your stream? Below I have created a list...
Read more >
BASIC Commands - Dartmouth
BASIC Commands. This document is reproduced ... Normal precedence rules are used: Exponentiation, multiply and divide, add and subtract.
Read more >
Commands - discord.py
Adding bot arguments with function parameters is only the first step in defining your bot's command interface. To actually make use of the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found