Week 3: Custom Keyboard Shortcuts

Hello!
This is my 3rd weekly update. Read previous updates: Week 1, Week 2.

I’ve spent the week implementing and unit-testing the keymapHandler module. This module is responsible for abstracting the in-memory keymap. It can be queried and updated through provided methods. See my current progress in my PR #3252.

Data Structure

The default keymap will be stored as a list of JavaScript objects in the source code.
For example,

[
    { accelerator: 'CommandOrControl+N', command: 'newNoteItem', label: 'New note', section: 'File' },
    { accelerator: 'CommandOrControl+T', command: 'newTodoItem', label: 'New to-do', section: 'File' },
    ....
]

This would allow us the maximum customizability.

Some shortcuts have different default Accelerators depending on the platform, like the shortcut “Search in all notes”. They can be stored like this:

{ accelerator: shim.isMac() ? 'Shift+Command+F' : 'F6', command: 'focusSearch', label: 'Search in all the notes', section: 'Edit' }

This will give us the correct Accelerator in run-time.

Some shortcuts are available only on specific platforms. It’s perfectly okay to keep them in the default keymap. We can use a simple platforms property to remember if they should be shown in the graphical shortcut editor or not, as a UX measure.

{ accelerator: 'CommandOrControl+,', command: 'options', label: 'Options', section: 'Tools', platforms: ['linux', 'windows'] },
{ accelerator: 'CommandOrControl+,', command: 'preferences', label: 'Preferences', section: 'File', platforms: ['darwin'] }

Above shortcut item will be shown in the graphical shortcut editor only on linux and windows platforms. If there’s no platform property, it’s shown in all platforms. It’s as simple as that.

The label and section properties will be useful in the graphical shortcut editor.

Initialization

It benefits a lot if we have a way to retrieve shortcuts by command. Similar to the CLI client, the module first reads the default keymap list, and creates a keymap object in which the keys are commands and values are shortcut objects.

This allows us to easily get and set shortcuts via simple methods.

Custom Keymap

Because keymap.json is the keymap file for the CLI client, we can’t use that. I think keymap-desktop.json would be a good choice for this. This is not final. Feel free to post what you think.

The module provides the method overrideKeymap() which accepts the custom keymap JSON as parameter and updates the in-memory keymap accordingly. This has to be done before any getAccelerator() calls in order to get the updated Accelerators. Any validation errors caused by missing commands, accelerators, invalid accelerators, incorrect commands will abort the method and log a helpful error message to the console.

Custom keymap items require command and accelerator properties. Following is an example for keymap-desktop.json file:

[
    { "accelerator": "CommandOrControl+M", "command": "newNoteItem" },
    { "accelerator": "CommandOrControl+Y", "command": "newTodoItem" },
    { "accelerator": "CommandOrControl+L", "command": "goToAnything" }
]

What’s next?

  • Check for conflicting Accelerators. If there’s the same Accelerator for two or more commands, it should warn the user. This can be tricky sometimes. What if user changes command X’s accelerator to Y’s default accelerator, and also change Y’s accelerator to X’s default accelerator?
  • Best way to handle different context: Same shortcut can mean two different commands in two contexts.
  • Since the changes are done in-memory, how to handle a “Save”? Maybe compare default keymap and current in-memory keymap and generate a JSON with the changes. This JSON can be stringify-ed and saved to keymap-desktop.json file.
  • Restore default Accelerator for some command.

Cheers!

5 Likes

Quick question/suggestion (sorry if this is the wrong place and/or if it's already defined):

Do you plan on (having a possibility to) automatically sync the user-defined shortcuts between Joplin on different computers? If yes, I'm looking forward to it. If not, I'd like to suggest it as an additional feature :slight_smile:

3 Likes

No, syncing is not planned. But export and import.

3 Likes