diff options
Diffstat (limited to 'comm/mail/modules/ShortcutsManager.jsm')
-rw-r--r-- | comm/mail/modules/ShortcutsManager.jsm | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/comm/mail/modules/ShortcutsManager.jsm b/comm/mail/modules/ShortcutsManager.jsm new file mode 100644 index 0000000000..38167d887b --- /dev/null +++ b/comm/mail/modules/ShortcutsManager.jsm @@ -0,0 +1,345 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Module used to collect all global shortcuts that can (will) be customizable. + * Use the shortcuts[] array to add global shortcuts that need to work on the + * whole window. The `context` property allows using the same shortcut for + * different context. The event handling needs to be defined in the window. + */ + +const EXPORTED_SYMBOLS = ["ShortcutsManager"]; + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const ShortcutsManager = { + /** + * Fluent strings mapping to allow updating strings without changing all the + * IDs in the shortcuts.ftl file. This is needed because the IDs are + * dynamically generated. + * + * @type {object} + */ + fluentMapping: { + "meta-shift-alt-shortcut-key": "meta-shift-alt-shortcut-key2", + "ctrl-shift-alt-shortcut-key": "ctrl-shift-alt-shortcut-key2", + "meta-ctrl-shift-alt-shortcut-key": "meta-ctrl-shift-alt-shortcut-key2", + }, + + /** + * Data set for a shortcut. + * + * @typedef {object} Shortcut + * @property {string} id - The id for this shortcut. + * @property {string} name - The name of this shortcut. TODO: This should use + * fluent to be translatable in the future, once we decide to expose this + * array and make it customizable. + * @property {?string} key - The keyboard key used by this shortcut, or null + * if the shortcut is disabled. + * @property {object} modifiers - The list of modifiers expected by this + * shortcut in order to be triggered, organized per platform. + * @property {string[]} context - An array of strings representing the context + * string to filter out duplicated shortcuts, if necessary. + */ + /** + * @type {Shortcut[]} + */ + shortcuts: [ + /* Numbers. */ + { + id: "space-mail", + name: "Open the Mail space", + key: "1", + modifiers: { + win: { + metaKey: false, + ctrlKey: false, + shiftKey: false, + altKey: true, + }, + macosx: { + metaKey: false, + ctrlKey: true, + shiftKey: false, + altKey: false, + }, + linux: { + metaKey: false, + ctrlKey: true, + shiftKey: false, + altKey: false, + }, + }, + context: [], + }, + { + id: "space-addressbook", + name: "Open the Address Book space", + key: "2", + modifiers: { + win: { + metaKey: false, + ctrlKey: false, + shiftKey: false, + altKey: true, + }, + macosx: { + metaKey: false, + ctrlKey: true, + shiftKey: false, + altKey: false, + }, + linux: { + metaKey: false, + ctrlKey: true, + shiftKey: false, + altKey: false, + }, + }, + context: [], + }, + { + id: "space-calendar", + name: "Open the Calendar space", + key: "3", + modifiers: { + win: { + metaKey: false, + ctrlKey: false, + shiftKey: false, + altKey: true, + }, + macosx: { + metaKey: false, + ctrlKey: true, + shiftKey: false, + altKey: false, + }, + linux: { + metaKey: false, + ctrlKey: true, + shiftKey: false, + altKey: false, + }, + }, + context: [], + }, + { + id: "space-tasks", + name: "Open the Tasks space", + key: "4", + modifiers: { + win: { + metaKey: false, + ctrlKey: false, + shiftKey: false, + altKey: true, + }, + macosx: { + metaKey: false, + ctrlKey: true, + shiftKey: false, + altKey: false, + }, + linux: { + metaKey: false, + ctrlKey: true, + shiftKey: false, + altKey: false, + }, + }, + context: [], + }, + { + id: "space-chat", + name: "Open the Chat space", + key: "5", + modifiers: { + win: { + metaKey: false, + ctrlKey: false, + shiftKey: false, + altKey: true, + }, + macosx: { + metaKey: false, + ctrlKey: true, + shiftKey: false, + altKey: false, + }, + linux: { + metaKey: false, + ctrlKey: true, + shiftKey: false, + altKey: false, + }, + }, + context: [], + }, + { + id: "space-toggle", + name: "Toggle the Spaces Toolbar", + key: null, // Disabled shortcut. + code: null, + modifiers: { + win: { + metaKey: false, + ctrlKey: false, + shiftKey: false, + altKey: false, + }, + macosx: { + metaKey: false, + ctrlKey: false, + shiftKey: false, + altKey: false, + }, + linux: { + metaKey: false, + ctrlKey: false, + shiftKey: false, + altKey: false, + }, + }, + context: [], + }, + /* Characters. */ + /* Special characters. */ + ], + + /** + * Find the matching shortcut from a keydown DOM Event. + * + * @param {Event} event - The keydown DOM Event. + * @param {?string} context - The context string to filter out duplicated + * shortcuts, if necessary. + * @returns {?Shortcut} - The matching shortcut, or null if nothing matches. + */ + matches(event, context = null) { + let found = []; + for (let shortcut of this.shortcuts) { + // No need to run any other condition if the base key doesn't match. + if (shortcut.key != event.key) { + continue; + } + + // Skip this key if we require a context not present, or we don't require + // a context and key has some. + if ( + (context && !shortcut.context.includes(context)) || + (!context && shortcut.context.length) + ) { + continue; + } + + found.push(shortcut); + } + + if (found.length > 1) { + // Trigger an error since we don't want to allow multiple shortcuts to + // run at the same time. If this happens, we got a problem! + throw new Error( + `Multiple shortcuts (${found + .map(f => f.id) + .join(",")}) are conflicting with the keydown event:\n + - KEY: ${event.key}\n + - CTRL: ${event.ctrlKey}\n + - META: ${event.metaKey}\n + - SHIFT: ${event.shiftKey}\n + - ALT: ${event.altKey}\n + - CONTEXT: ${context}\n` + ); + } + + if (!found.length) { + return null; + } + + let shortcut = found[0]; + let mods = shortcut.modifiers[AppConstants.platform]; + // Return the shortcut if it doesn't require any modifier and no modifier + // is present in the key press event. + if ( + !Object.keys(mods).length && + !(event.ctrlKey || event.metaKey) && + !event.shiftKey && + !event.altKey + ) { + return shortcut; + } + + // Perfectly match all modifiers to prevent false positives. + return mods.metaKey == event.metaKey && + mods.ctrlKey == event.ctrlKey && + mods.shiftKey == event.shiftKey && + mods.altKey == event.altKey + ? shortcut + : null; + }, + + /** + * Generate a string that will be used to create the fluent ID to visually + * represent the keyboard shortcut. + * + * @param {string} id - The ID of the requested shortcut. + * @returns {?object} - An object containing the generate shortcut and aria + * string, if available. + * @property {string} localizedShortcut - The shortcut in a human-readable, + * localized and platform-specific form. + * @property {string} ariaKeyShortcuts - The shortcut in a form appropriate + * for the aria-keyshortcuts attribute. + */ + async getShortcutStrings(id) { + let shortcut = this.shortcuts.find(s => s.id == id); + if (!shortcut?.key) { + return null; + } + + let platform = AppConstants.platform; + let string = []; + let aria = []; + if (shortcut.modifiers[platform].metaKey) { + string.push("meta"); + aria.push("Meta"); + } + + if (shortcut.modifiers[platform].ctrlKey) { + string.push("ctrl"); + aria.push("Control"); + } + + if (shortcut.modifiers[platform].shiftKey) { + string.push("shift"); + aria.push("Shift"); + } + + if (shortcut.modifiers[platform].altKey) { + string.push("alt"); + aria.push("Alt"); + } + string.push("shortcut-key"); + aria.push(shortcut.key.toUpperCase()); + + // Check if the ID was updated in the fluent file and replace it. + let stringId = string.join("-"); + stringId = this.fluentMapping[stringId] || stringId; + + let value = await this.l10n.formatValue(stringId, { + key: shortcut.key.toUpperCase(), + }); + + return { localizedShortcut: value, ariaKeyShortcuts: aria.join("+") }; + }, +}; + +XPCOMUtils.defineLazyGetter( + ShortcutsManager, + "l10n", + () => new Localization(["messenger/shortcuts.ftl"]) +); |