-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
View events, keystroke handler, commands, composer – their place in the editor architecture #340
Comments
This ticket should not have any action. There will be other tickets, though, in which we'll link to this one. |
PS. There's a case in which we totally change the meaning of a key – e.g. when enter is pressed while a widget is selected we open an "edit widget balloon". These are special cases where we could listen directly on the view (because the action isn't any more happening in the editable). However, we can also make it possible to bind more commands to a single keystroke (and prioritise them). This, fortunately, it's a pretty rare case, so it should not be concerned by it. |
Okay, so I will just comment on what I don't like (and describe an alternative idea that we discussed). The whole idea is okay but there's one thing that just bugs me a lot... And I just can't stop myself from commenting on it. I know we have to accept something and finally code something but, no matter what we choose, I'd like to have my word here :) You say that command == action but what does it really mean? By the way how you described it, the command is really a shortcut to what user did and does not have much to do with user intention (the original idea). So your idea means that we have You also argue, that this makes re-binding easy, etc. But then you are able to rebind Even more, you already provide a scenario when your idea is wrong (widgets). I'd argue, that outdenting on enter is also a case of "totally change the meaning of a key". On the other hand, I suppose that Enter is the only key with such broad meaning so we won't have those problems with other keystrokes. So, my idea is that we do not treat commands as "whatever user did" but really "actions" or "intentions". Then we could have () => {
if ( positionAtTheEndOfListItem ) {
editor.execute( 'outdentList' );
}
}; We could also make API a bit different and use three parameters for binding: Apart from that we'd need some priorities, but this maybe coud be build on BTW. Note how "expanding keystroke" (using priorities) is similar to "expanding command" (using I am sorry I am discussing this again, but:
All in all, I can accept going your way but I think it's not perfect and we can do better with only a bit more work. |
I knew it... I should've blocked commenting :P. Going into the direction that you proposed, when commands are immutable and all happens within keystroke handler is possible, but will require a lot of additions to the keystroke handler. Most likely, to make all this configurable, we'll have need to introduce a concept of "actions" there. This may be a way to go. Especially that @fredck just brought to me a problem with my initial proposal. The extensions (like the enter to outdent) are based on existence of the base commands. If you won't load the enter feature, then it's not a big problem – you apparently don't want to handle enter. But if you'll load a replacement of the enter feature which binds Enter to a different command (e.g. So this is a strong argument for keeping commands immutable and the whole configuration outside. But as I said – it's not very clear how to do this in the keystroke handler to ensure that it's still configurable in a declarative way (through the config). We could perhaps also make some things configurable using the config object and some through Another thing to keep in mind is being able to retrieve the keystrokes => actions pairs from the keystroke handler so you can create a keystrokes help (in CKE4 it's done pretty automatically IIRC). That's another argument for introducing the concept of "actions" to the keystroke handler. There could be a predefined set of actions, so features would know to which actions they can listen. This, of course, can be implemented using events. The feature would then define the default keystroke which triggers certain actions – e.g. that Enter triggers "enter" action (and some keystrokes could be predefined). Then, in the keystroke handler configuration you'd simply pass pairs of "keystroke => action" or "keystroke => null" to unbind some defaults. If this sounds great to you (it sounds ok to me), then we can investigate this idea further. But please – do not underestimate our tricky cases. I'm really unsure how all the feature will integrate with each other. Also, we'll have to decide whether headings feature extends the enter command or enter action. The latter sounds cleaner (because we'd make commands immutable), but it's harder to implement in the heading feature (because it must borrow part of enter feature behaviour)... And last but not least – how can then a developer reproduce the behaviour of "enter"? The keystroke handler would always be required then – which sounds fine, I guess, because it's a part of base Anyway, we should be careful. PS. One thing that we could think through is whether "key actions" could not be generalised to "user actions" and action handler. Perhaps it'd be useful in other cases as well. Let's talk about this F2F. |
We've talked with @fredck... and we'll go with the initial proposal I made in this ticket and we may make some improvements (mainly to the keystroke handler) in the future :D. Things that we agreed on:
|
As for the event names – if we'll allow things like optional modifiers (see https://github.com/ckeditor/ckeditor5-core/issues/20), then it will be hard to come up with some events... We should be careful here. |
There's yet another topic that I wanted to mention. There's a difference between how we define commands in CKEditor 4 and 5. In CKEditor 4, the link command will open the link dialog. In CKEditor 5 the link command applies specified link to the current selection. This is an important shift. In CKEditor 4 there's no way to reuse functionality of the dialogs (which, on "ok" insert or change stuff in the editable). That's why the link command in CKEditor 5 is focused on the engine. However, it's not clear now how you can open the link balloon. You'd like to do that if you used external buttons which should interact with the feature's UI (external link button should be able to open the link balloon). I proposed two solutions in https://github.com/ckeditor/ckeditor5-link/issues/45. Either we expose more commands (like |
The final event names can be numbers, like |
I think we're mostly talking about being able to execute a feature by invoking the UI connected to it. Some ideas:
|
This is a followup of or concerns the following issues:
The problem
Most features have to react to user actions which often mean reacting to a certain keystroke. This sounds trivial, but it turns out that this concerns many layers of the editor architecture:
Thanks to the event-based architecture, a feature may interact with pretty much every layer here. This is, on one hand, great and gives amazing flexibility. But on the other hand, it's a curse of abundance when it comes to deciding where you want to hook in your code.
These were some of the problematic subjects:
As I mentioned, there are many possible solutions, so it's hard to explain why all of the alternatives are flawed (and that's why we discussed this for a month, changing opinions couple of times). Let me only describe the architecture which IMO works best.
Solution
Keystroke handler
The keystroke handler should be used to map keystrokes to commands. Imagine e.g. a graphical program like Photoshop which lets you define keystrokes. You have a list of commands and you can assign keystrokes to them. This is what keystroke handler should be used in CKEditor 5.
In the specific case of Enter, the action of "enter" is also called "enter" (enter command). The authors of the Editing spec (see my comment) tried to call this action "insertParagraph", but it's very weird that insertParagraph's default action is e.g. to create a new list item or outdent a list.
Anyway, the most important thing to remember is actions == commands.
There's a tricky thing here, though. If we'd use keystroke handler to assign such actions as "enter" or "delete" to keystrokes, then we wouldn't be able to switch to the
beforeinput
event once it's ready. Keydown is fired beforebeforeinput
so by handling specific keys through the keystroke handler, we'd block firingbeforeinput
. The power ofbeforeinput
is that it is far better integrated with the OS and the browser than we can be by listening to key events, so as soon as it's available we'd like to use it.I thought that we could divide keys into two categories – those which will have specific
beforeinput
type and those which won't. But then I realised that most of the keystrokes will have assigned input types. So there's no other choice – at some point in the future, we'll have to remove keystroke bindings. However, things should work smoothly because it will still be possible to e.g. bind "moveToNextParagraph" command to Shift+Enter.tl;dr: All features should use keystroke handler to bind commands to keys. Commands == actions.
Place of extension
Since keystrokes should be configurable, which means that you should be able to rebound any action (command) to any keystroke, the command should be atomic. E.g. enter should always execute the "enter" command and this command should take care of handling lists, headings, etc.
This means that it's the commands which should be extendable. For that we need smart
beforeExecute
andafterExecute
events to be fired by them.The composer
Composer should only contain action primitives – operations on the model. We've just made commands extensible and keystrokes configurable, but we also need to make those composer methods extensible as well. However, the set of methods should be closed and defined by the engine. There will be 4 methods:
You should use those methods when dealing with unknown content of the model. E.g. the delete feature uses delete composer method in order to delete selected part of the content. The delete feature doesn't know about other features enabled in the editor, so it wouldn't know itself how to perform their deletion. Similarly, when typing into a non-collapsed selection, the typing feature must delete the selected content, prior to inserting content in it. In both cases, the composer's method should be used.
Note: based on these 4 methods we could implement the whole editor. We won't do that, because, in order to have more semantical operations on the model (in terms of OT), we often will use deltas directly, but the more feature use composer methods, the better.
Summary
I hope that the proposal looks good to you because I really don't want to discuss it further. I can clarify some things, but I don't want to explore other directions, because we've seen too many of them leading to a mess.
The text was updated successfully, but these errors were encountered: