diff --git a/package-lock.json b/package-lock.json index f23a1ff975..23426ce3d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1785,7 +1785,7 @@ }, "array-equal": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", "dev": true }, @@ -1916,7 +1916,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -1929,7 +1929,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -2036,7 +2036,7 @@ }, "babel-plugin-syntax-object-rest-spread": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", "dev": true }, @@ -2329,7 +2329,7 @@ }, "callsites": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", "dev": true }, @@ -3291,7 +3291,7 @@ }, "expand-range": { "version": "1.8.2", - "resolved": "http://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "dev": true, "requires": { @@ -6129,7 +6129,7 @@ }, "jsesc": { "version": "1.3.0", - "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", "dev": true }, @@ -6159,7 +6159,7 @@ }, "json5": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", "dev": true }, @@ -8315,7 +8315,7 @@ }, "resolve": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", "dev": true }, @@ -9736,7 +9736,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } diff --git a/packages/insomnia-app/.electronbuilder b/packages/insomnia-app/.electronbuilder index ab0c8373f5..9f3979e125 100644 --- a/packages/insomnia-app/.electronbuilder +++ b/packages/insomnia-app/.electronbuilder @@ -24,6 +24,7 @@ }, "mac": { "category": "public.app-category.developer-tools", + "darkModeSupport": true, "target": [ "dmg", "zip" diff --git a/packages/insomnia-app/app/common/constants.js b/packages/insomnia-app/app/common/constants.js index 907e84c7b3..e5c9680daf 100644 --- a/packages/insomnia-app/app/common/constants.js +++ b/packages/insomnia-app/app/common/constants.js @@ -69,12 +69,19 @@ export const FLEXIBLE_URL_REGEX = /^(http|https):\/\/[\wàâäèéêëîïôóœ export const CHECK_FOR_UPDATES_INTERVAL = 1000 * 60 * 60 * 3; // 3 hours export const PLUGIN_PATH = path.join(getDataDirectory(), 'plugins'); +// Available editor key maps +export const EDITOR_KEY_MAP_DEFAULT = 'default'; +export const EDITOR_KEY_MAP_EMACS = 'emacs'; +export const EDITOR_KEY_MAP_SUBLIME = 'sublime'; +export const EDITOR_KEY_MAP_VIM = 'vim'; + // Hotkeys export const MNEMONIC_SYM = isMac() ? '' : '&'; -export const MOD_SYM = isMac() ? '⌘' : 'Ctrl'; -export const ALT_SYM = isMac() ? '⌃' : 'Alt'; -export const SHIFT_SYM = isMac() ? '⇧' : 'Shift'; export const CTRL_SYM = isMac() ? '⌃' : 'Ctrl'; +export const ALT_SYM = isMac() ? '⌥' : 'Alt'; +export const SHIFT_SYM = isMac() ? '⇧' : 'Shift'; +export const META_SYM = isMac() ? '⌘' : 'Super'; + export function joinHotKeys(keys) { return keys.join(isMac() ? '' : '+'); } diff --git a/packages/insomnia-app/app/common/har.js b/packages/insomnia-app/app/common/har.js index cd8ef6eeb9..4065bd38dc 100644 --- a/packages/insomnia-app/app/common/har.js +++ b/packages/insomnia-app/app/common/har.js @@ -339,12 +339,7 @@ export async function exportHarWithRenderedRequest( // Set auth header if we have it if (!misc.hasAuthHeader(renderedRequest.headers)) { - const header = await getAuthHeader( - renderedRequest._id, - url, - renderedRequest.method, - renderedRequest.authentication, - ); + const header = await getAuthHeader(renderedRequest, url); if (header) { renderedRequest.headers.push({ name: header.name, diff --git a/packages/insomnia-app/app/common/hotkeys-listener.js b/packages/insomnia-app/app/common/hotkeys-listener.js new file mode 100644 index 0000000000..74b2ce7eef --- /dev/null +++ b/packages/insomnia-app/app/common/hotkeys-listener.js @@ -0,0 +1,72 @@ +// @flow +import type { HotKeyDefinition, KeyBindings } from './hotkeys'; +import { getPlatformKeyCombinations } from './hotkeys'; +import * as models from '../models'; + +function _pressedHotKey(e: KeyboardEvent, bindings: KeyBindings): boolean { + const isCtrlPressed = e.ctrlKey; + const isAltPressed = e.altKey; + const isShiftPressed = e.shiftKey; + const isMetaPressed = e.metaKey; + const keyCombList = getPlatformKeyCombinations(bindings); + + for (const keyComb of keyCombList) { + const { ctrl, alt, shift, meta, keyCode } = keyComb; + + if ((ctrl && !isCtrlPressed) || (!ctrl && isCtrlPressed)) { + continue; + } + + if ((alt && !isAltPressed) || (!alt && isAltPressed)) { + continue; + } + + if ((shift && !isShiftPressed) || (!shift && isShiftPressed)) { + continue; + } + + if ((meta && !isMetaPressed) || (!meta && isMetaPressed)) { + continue; + } + + if (keyCode !== e.keyCode) { + continue; + } + + return true; + } + + return false; +} + +/** + * Check whether a hotkey has been pressed. + * @param e the activated keyboard event. + * @param definition the hotkey definition being checked. + * @returns {Promise} + */ +export async function pressedHotKey( + e: KeyboardEvent, + definition: HotKeyDefinition, +): Promise { + const settings = await models.settings.getOrCreate(); + return _pressedHotKey(e, settings.hotKeyRegistry[definition.id]); +} + +/** + * Call callback if the hotkey has been pressed. + * @param e the activated keyboard event. + * @param definition the hotkey definition being checked. + * @param callback to be called if the hotkey has been activated. + * @returns {Promise} + */ +export async function executeHotKey( + e: KeyboardEvent, + definition: HotKeyDefinition, + callback: Function, +): Promise { + const settings = await models.settings.getOrCreate(); + if (_pressedHotKey(e, settings.hotKeyRegistry[definition.id])) { + callback(); + } +} diff --git a/packages/insomnia-app/app/common/hotkeys.js b/packages/insomnia-app/app/common/hotkeys.js index ba1e536ec7..b734b4185f 100644 --- a/packages/insomnia-app/app/common/hotkeys.js +++ b/packages/insomnia-app/app/common/hotkeys.js @@ -2,304 +2,371 @@ import keycodes from './keycodes'; import { isMac } from './constants'; -export type Hotkey = { +/** + * The readable definition of a hotkey. + * The {@code id} the hotkey's reference id. + */ +export type HotKeyDefinition = { + id: string, description: string, - meta: boolean, +}; + +/** + * The combination of key presses that will activate a hotkey if pressed. + */ +export type KeyCombination = { + ctrl: boolean, alt: boolean, shift: boolean, - keycode: number | Array, - metaIsCtrl?: boolean, + meta: boolean, + keyCode: number, }; -export const SHOW_WORKSPACE_SETTINGS: Hotkey = { - description: 'Show Workspace Settings', - meta: true, - alt: false, - shift: true, - keycode: keycodes.comma, +/** + * The collection of a hotkey's key combinations for each platforms. + */ +export type KeyBindings = { + macKeys: Array, + // The key combinations for both Windows and Linux. + winLinuxKeys: Array, }; -export const SHOW_REQUEST_SETTINGS: Hotkey = { - description: 'Show Request Settings', - meta: true, - alt: true, - shift: true, - keycode: keycodes.comma, +/** + * The collection of defined hotkeys. + * The registry maps a hotkey by its reference id to its key bindings. + */ +export type HotKeyRegistry = { + [refId: string]: KeyBindings, }; -export const SHOW_KEYBOARD_SHORTCUTS: Hotkey = { - description: 'Show Keyboard Shortcuts', - meta: true, - alt: false, - shift: true, - keycode: keycodes.forwardslash, -}; - -export const SHOW_SETTINGS: Hotkey = { - description: 'Show App Preferences', - meta: true, - alt: false, - shift: false, - keycode: keycodes.comma, -}; - -export const TOGGLE_MAIN_MENU: Hotkey = { - description: 'Toggle Main Menu', - meta: true, - alt: true, - shift: false, - keycode: keycodes.comma, -}; - -export const TOGGLE_SIDEBAR: Hotkey = { - description: 'Toggle Sidebar', - meta: true, - alt: false, - shift: false, - keycode: keycodes.backslash, -}; - -export const SHOW_QUICK_SWITCHER: Hotkey = { - description: 'Switch Requests', - meta: true, - alt: false, - shift: false, - keycode: keycodes.p, -}; - -export const RELOAD_PLUGINS: Hotkey = { - description: 'Reload Plugins', - meta: true, - alt: false, - shift: true, - keycode: keycodes.r, -}; - -export const SHOW_AUTOCOMPLETE: Hotkey = { - description: 'Show Autocomplete', - meta: true, - metaIsCtrl: true, - alt: false, - shift: false, - keycode: keycodes.space, -}; - -export const SEND_REQUEST: Hotkey = { - description: 'Send Request', - meta: true, - alt: false, - shift: false, - keycode: [keycodes.enter, keycodes.r], -}; - -export const SEND_REQUEST_F5: Hotkey = { - description: 'Send Request', - meta: false, - alt: false, - shift: false, - keycode: keycodes.f5, -}; - -export const SHOW_SEND_OPTIONS: Hotkey = { - description: 'Send Request (Options)', - meta: true, - alt: false, - shift: true, - keycode: keycodes.enter, -}; - -export const SHOW_ENVIRONMENTS: Hotkey = { - description: 'Show Environment Editor', - meta: true, - alt: false, - shift: false, - keycode: keycodes.e, -}; - -export const TOGGLE_ENVIRONMENTS_MENU: Hotkey = { - description: 'Switch Environments', - meta: true, - alt: false, - shift: true, - keycode: keycodes.e, -}; - -export const TOGGLE_METHOD_DROPDOWN: Hotkey = { - description: 'Change HTTP Method', - meta: true, - alt: false, - shift: true, - keycode: keycodes.l, -}; - -export const TOGGLE_HISTORY_DROPDOWN: Hotkey = { - description: 'Show Request History', - meta: true, - alt: false, - shift: true, - keycode: keycodes.h, -}; - -export const FOCUS_URL: Hotkey = { - description: 'Focus URL', - meta: true, - alt: false, - shift: false, - keycode: keycodes.l, -}; - -export const GENERATE_CODE: Hotkey = { - description: 'Generate Code', - meta: true, - alt: false, - shift: true, - keycode: keycodes.g, -}; - -export const FOCUS_FILTER: Hotkey = { - description: 'Filter Sidebar', - meta: true, - alt: false, - shift: true, - keycode: keycodes.f, -}; - -export const FOCUS_RESPONSE: Hotkey = { - description: 'Focus Response', - meta: true, - alt: false, - shift: false, - keycode: keycodes.singlequote, -}; - -export const SHOW_COOKIES: Hotkey = { - description: 'Edit Cookies', - meta: true, - alt: false, - shift: false, - keycode: keycodes.k, -}; - -export const CREATE_REQUEST: Hotkey = { - description: 'Create Request', - meta: true, - alt: false, - shift: false, - keycode: keycodes.n, -}; - -export const DELETE_REQUEST: Hotkey = { - description: 'Delete Request', - meta: true, - alt: false, - shift: true, - keycode: keycodes.delete, -}; - -export const CREATE_FOLDER: Hotkey = { - description: 'Create Folder', - meta: true, - alt: false, - shift: true, - keycode: keycodes.n, -}; - -export const DUPLICATE_REQUEST: Hotkey = { - description: 'Duplicate Request', - meta: true, - alt: false, - shift: false, - keycode: keycodes.d, -}; - -export const CLOSE_DROPDOWN: Hotkey = { - description: 'Close Dropdown', - meta: false, - alt: false, - shift: false, - keycode: keycodes.esc, -}; - -export const CLOSE_MODAL: Hotkey = { - description: 'Close Modal', - meta: false, - alt: false, - shift: false, - keycode: keycodes.esc, -}; - -export const UNCOVER_VARIABLES: Hotkey = { - description: 'Uncover Variables', - meta: false, - alt: true, - shift: true, - keycode: keycodes.u, -}; - -export function pressedHotKey(e: KeyboardEvent, definition: Hotkey): boolean { - const isMetaPressed = isMac() ? e.metaKey : e.ctrlKey; - const isAltPressed = isMac() ? e.ctrlKey : e.altKey; - const isShiftPressed = e.shiftKey; - - const { meta, alt, shift, keycode } = definition; - const codes = Array.isArray(keycode) ? keycode : [keycode]; - - for (const code of codes) { - if ((alt && !isAltPressed) || (!alt && isAltPressed)) { - continue; - } - - if ((meta && !isMetaPressed) || (!meta && isMetaPressed)) { - continue; - } - - if ((shift && !isShiftPressed) || (!shift && isShiftPressed)) { - continue; - } - - if (code !== e.keyCode) { - continue; - } - - return true; - } - - return false; +function defineHotKey(id: string, description: string): HotKeyDefinition { + return { + id: id, + description: description, + }; } -export function executeHotKey(e: KeyboardEvent, definition: Hotkey, callback: Function): void { - if (pressedHotKey(e, definition)) { - callback(); - } +function keyComb( + ctrl: boolean, + alt: boolean, + shift: boolean, + meta: boolean, + keyCode: number, +): KeyCombination { + return { + ctrl: ctrl, + alt: alt, + shift: shift, + meta: meta, + keyCode: keyCode, + }; } -export function getChar(hotkey: Hotkey) { - const codes = Array.isArray(hotkey.keycode) ? hotkey.keycode : [hotkey.keycode]; - const chars = []; +function keyBinds( + mac: KeyCombination | Array, + winLinux: KeyCombination | Array, +): KeyBindings { + if (!Array.isArray(mac)) { + mac = [mac]; + } - for (const keycode of codes) { - const v = Object.keys(keycodes).find(k => keycodes[k] === keycode); + if (!Array.isArray(winLinux)) { + winLinux = [winLinux]; + } - if (!v) { - console.error('Invalid hotkey', hotkey); - } else if (v.toUpperCase() === 'ENTER') { - chars.push('Enter'); - } else if (v.toUpperCase() === 'DELETE') { - chars.push('Delete'); - } else if (v.toUpperCase() === 'COMMA') { - chars.push(','); - } else if (v.toUpperCase() === 'BACKSLASH') { - chars.push('\\'); - } else if (v.toUpperCase() === 'FORWARDSLASH') { - chars.push('/'); - } else if (v.toUpperCase() === 'SINGLEQUOTE') { - chars.push("'"); - } else if (v.toUpperCase() === 'SPACE') { - chars.push('Space'); + return { + macKeys: mac, + winLinuxKeys: winLinux, + }; +} + +/** + * The collection of available hotkeys' and their definitions. + */ +// Not using dot, because NeDB prohibits field names to contain dots. +export const hotKeyRefs = { + WORKSPACE_SHOW_SETTINGS: defineHotKey('workspace_showSettings', 'Show Workspace Settings'), + + REQUEST_SHOW_SETTINGS: defineHotKey('request_showSettings', 'Show Request Settings'), + + PREFERENCES_SHOW_KEYBOARD_SHORTCUTS: defineHotKey( + 'preferences_showKeyboardShortcuts', + 'Show Keyboard Shortcuts', + ), + + PREFERENCES_SHOW_GENERAL: defineHotKey('preferences_showGeneral', 'Show App Preferences'), + + TOGGLE_MAIN_MENU: defineHotKey('toggleMainMenu', 'Toggle Main Menu'), + + SIDEBAR_TOGGLE: defineHotKey('sidebar_toggle', 'Toggle Sidebar'), + + REQUEST_QUICK_SWITCH: defineHotKey('request_quickSwitch', 'Switch Requests'), + + PLUGIN_RELOAD: defineHotKey('plugin_reload', 'Reload Plugins'), + + SHOW_AUTOCOMPLETE: defineHotKey('showAutocomplete', 'Show Autocomplete'), + + REQUEST_SEND: defineHotKey('request_send', 'Send Request'), + + REQUEST_SHOW_OPTIONS: defineHotKey('request_showOptions', 'Send Request (Options)'), + + ENVIRONMENT_SHOW_EDITOR: defineHotKey('environment_showEditor', 'Show Environment Editor'), + + ENVIRONMENT_SHOW_SWITCH_MENU: defineHotKey('environment_showSwitchMenu', 'Switch Environments'), + + REQUEST_TOGGLE_HTTP_METHOD_MENU: defineHotKey( + 'request_toggleHttpMethodMenu', + 'Change HTTP Method', + ), + + REQUEST_TOGGLE_HISTORY: defineHotKey('request_toggleHistory', 'Show Request History'), + + REQUEST_FOCUS_URL: defineHotKey('request_focusUrl', 'Focus URL'), + + REQUEST_SHOW_GENERATE_CODE_EDITOR: defineHotKey( + 'request_showGenerateCodeEditor', + 'Generate Code', + ), + + SIDEBAR_FOCUS_FILTER: defineHotKey('sidebar_focusFilter', 'Filter Sidebar'), + + RESPONSE_FOCUS: defineHotKey('response_focus', 'Focus Response'), + + SHOW_COOKIES_EDITOR: defineHotKey('showCookiesEditor', 'Edit Cookies'), + + REQUEST_SHOW_CREATE: defineHotKey('request_showCreate', 'Create Request'), + + REQUEST_SHOW_DELETE: defineHotKey('request_showDelete', 'Delete Request'), + + REQUEST_SHOW_CREATE_FOLDER: defineHotKey('request_showCreateFolder', 'Create Folder'), + + REQUEST_SHOW_DUPLICATE: defineHotKey('request_showDuplicate', 'Duplicate Request'), + + CLOSE_DROPDOWN: defineHotKey('closeDropdown', 'Close Dropdown'), + + CLOSE_MODAL: defineHotKey('closeModal', 'Close Modal'), + + ENVIRONMENT_UNCOVER_VARIABLES: defineHotKey('environment_uncoverVariables', 'Uncover Variables'), +}; + +/** + * The default key bindings values of all available hotkeys. + */ +const defaultRegistry: HotKeyRegistry = { + [hotKeyRefs.WORKSPACE_SHOW_SETTINGS.id]: keyBinds( + keyComb(false, false, true, true, keycodes.comma), + keyComb(true, false, true, false, keycodes.comma), + ), + + [hotKeyRefs.REQUEST_SHOW_SETTINGS.id]: keyBinds( + keyComb(false, true, true, true, keycodes.comma), + keyComb(true, true, true, false, keycodes.comma), + ), + + [hotKeyRefs.PREFERENCES_SHOW_KEYBOARD_SHORTCUTS.id]: keyBinds( + keyComb(false, false, true, true, keycodes.forwardslash), + keyComb(true, false, true, false, keycodes.forwardslash), + ), + + [hotKeyRefs.PREFERENCES_SHOW_GENERAL.id]: keyBinds( + keyComb(false, false, false, true, keycodes.comma), + keyComb(true, false, false, false, keycodes.comma), + ), + + [hotKeyRefs.TOGGLE_MAIN_MENU.id]: keyBinds( + keyComb(false, true, false, true, keycodes.comma), + keyComb(true, true, false, false, keycodes.comma), + ), + + [hotKeyRefs.SIDEBAR_TOGGLE.id]: keyBinds( + keyComb(false, false, false, true, keycodes.backslash), + keyComb(true, false, false, false, keycodes.backslash), + ), + + [hotKeyRefs.REQUEST_QUICK_SWITCH.id]: keyBinds( + keyComb(false, false, false, true, keycodes.p), + keyComb(true, false, false, false, keycodes.p), + ), + + [hotKeyRefs.PLUGIN_RELOAD.id]: keyBinds( + keyComb(false, false, true, true, keycodes.r), + keyComb(true, false, true, false, keycodes.r), + ), + + [hotKeyRefs.SHOW_AUTOCOMPLETE.id]: keyBinds( + keyComb(true, false, false, false, keycodes.space), + keyComb(true, false, false, false, keycodes.space), + ), + + [hotKeyRefs.REQUEST_SEND.id]: keyBinds( + [ + keyComb(false, false, false, true, keycodes.enter), + keyComb(false, false, false, true, keycodes.r), + keyComb(false, false, false, false, keycodes.f5), + ], + [ + keyComb(true, false, false, false, keycodes.enter), + keyComb(true, false, false, false, keycodes.r), + keyComb(false, false, false, false, keycodes.f5), + ], + ), + + [hotKeyRefs.REQUEST_SHOW_OPTIONS.id]: keyBinds( + keyComb(false, false, true, true, keycodes.enter), + keyComb(true, false, true, false, keycodes.enter), + ), + + [hotKeyRefs.ENVIRONMENT_SHOW_EDITOR.id]: keyBinds( + keyComb(false, false, false, true, keycodes.e), + keyComb(true, false, false, false, keycodes.e), + ), + + [hotKeyRefs.ENVIRONMENT_SHOW_SWITCH_MENU.id]: keyBinds( + keyComb(false, false, true, true, keycodes.e), + keyComb(true, false, true, false, keycodes.e), + ), + + [hotKeyRefs.REQUEST_TOGGLE_HTTP_METHOD_MENU.id]: keyBinds( + keyComb(false, false, true, true, keycodes.l), + keyComb(true, false, true, false, keycodes.l), + ), + + [hotKeyRefs.REQUEST_TOGGLE_HISTORY.id]: keyBinds( + keyComb(false, false, true, true, keycodes.h), + keyComb(true, false, true, false, keycodes.h), + ), + + [hotKeyRefs.REQUEST_FOCUS_URL.id]: keyBinds( + keyComb(false, false, false, true, keycodes.l), + keyComb(true, false, false, false, keycodes.l), + ), + + [hotKeyRefs.REQUEST_SHOW_GENERATE_CODE_EDITOR.id]: keyBinds( + keyComb(false, false, true, true, keycodes.g), + keyComb(true, false, true, false, keycodes.g), + ), + + [hotKeyRefs.SIDEBAR_FOCUS_FILTER.id]: keyBinds( + keyComb(false, false, true, true, keycodes.f), + keyComb(true, false, true, false, keycodes.f), + ), + + [hotKeyRefs.RESPONSE_FOCUS.id]: keyBinds( + keyComb(false, false, false, true, keycodes.singlequote), + keyComb(true, false, false, false, keycodes.singlequote), + ), + + [hotKeyRefs.SHOW_COOKIES_EDITOR.id]: keyBinds( + keyComb(false, false, false, true, keycodes.k), + keyComb(true, false, false, false, keycodes.k), + ), + + [hotKeyRefs.REQUEST_SHOW_CREATE.id]: keyBinds( + keyComb(false, false, false, true, keycodes.n), + keyComb(true, false, false, false, keycodes.n), + ), + + [hotKeyRefs.REQUEST_SHOW_DELETE.id]: keyBinds( + keyComb(false, false, true, true, keycodes.delete), + keyComb(true, false, true, false, keycodes.delete), + ), + + [hotKeyRefs.REQUEST_SHOW_CREATE_FOLDER.id]: keyBinds( + keyComb(false, false, true, true, keycodes.n), + keyComb(true, false, true, false, keycodes.n), + ), + + [hotKeyRefs.REQUEST_SHOW_DUPLICATE.id]: keyBinds( + keyComb(false, false, false, true, keycodes.d), + keyComb(true, false, false, false, keycodes.d), + ), + + [hotKeyRefs.CLOSE_DROPDOWN.id]: keyBinds( + keyComb(false, false, false, false, keycodes.esc), + keyComb(false, false, false, false, keycodes.esc), + ), + + [hotKeyRefs.CLOSE_MODAL.id]: keyBinds( + keyComb(false, false, false, false, keycodes.esc), + keyComb(false, false, false, false, keycodes.esc), + ), + + [hotKeyRefs.ENVIRONMENT_UNCOVER_VARIABLES.id]: keyBinds( + keyComb(false, true, true, false, keycodes.u), + keyComb(false, true, true, false, keycodes.u), + ), +}; + +function copyKeyCombs(sources: Array): Array { + let targets: Array = []; + sources.forEach(keyComb => { + targets.push(Object.assign({}, keyComb)); + }); + return targets; +} + +/** + * Get a new copy of hotkey registry with default values. + * @returns {HotKeyRegistry} + */ +export function newDefaultRegistry(): HotKeyRegistry { + let newDefaults: HotKeyRegistry = {}; + for (const refId in defaultRegistry) { + if (!defaultRegistry.hasOwnProperty(refId)) { + continue; + } + const keyBindings: KeyBindings = defaultRegistry[refId]; + newDefaults[refId] = { + macKeys: copyKeyCombs(keyBindings.macKeys), + winLinuxKeys: copyKeyCombs(keyBindings.winLinuxKeys), + }; + } + return newDefaults; +} + +/** + * Get the key combinations based on the current platform. + * @param bindings + * @returns {Array} + */ +export function getPlatformKeyCombinations(bindings: KeyBindings): Array { + if (isMac()) { + return bindings.macKeys; + } + return bindings.winLinuxKeys; +} + +/** + * Gets the displayed text of a key code. + * @param keyCode + * @returns {string} + */ +export function getChar(keyCode: number): string { + let char; + const keyCodeStr = Object.keys(keycodes).find(k => keycodes[k] === keyCode); + + if (!keyCodeStr) { + console.error('Invalid key code', keyCode); + } else { + const cap = keyCodeStr.toUpperCase(); + if (cap === 'ENTER') { + char = 'Enter'; + } else if (cap === 'DELETE') { + char = 'Delete'; + } else if (cap === 'COMMA') { + char = ','; + } else if (cap === 'BACKSLASH') { + char = '\\'; + } else if (cap === 'FORWARDSLASH') { + char = '/'; + } else if (cap === 'SINGLEQUOTE') { + char = "'"; + } else if (cap === 'SPACE') { + char = 'Space'; } else { - chars.push(v.toUpperCase()); + char = cap; } } - return chars[0] || 'unknown'; + return char || 'unknown'; } diff --git a/packages/insomnia-app/app/models/settings.js b/packages/insomnia-app/app/models/settings.js index 9310f99461..c1dfd93b81 100644 --- a/packages/insomnia-app/app/models/settings.js +++ b/packages/insomnia-app/app/models/settings.js @@ -2,6 +2,7 @@ import type { BaseModel } from './index'; import * as db from '../common/database'; import { UPDATE_CHANNEL_STABLE } from '../common/constants'; +import * as hotkeys from '../common/hotkeys'; type BaseSettings = { showPasswords: boolean, @@ -34,6 +35,7 @@ type BaseSettings = { fontInterface: string | null, fontSize: number, fontVariantLigatures: boolean, + hotKeyRegistry: hotkeys.HotKeyRegistry, }; export type Settings = BaseModel & BaseSettings; @@ -75,6 +77,7 @@ export function init(): BaseSettings { fontInterface: null, fontSize: 13, fontVariantLigatures: false, + hotKeyRegistry: hotkeys.newDefaultRegistry(), }; } diff --git a/packages/insomnia-app/app/network/__tests__/authentication.test.js b/packages/insomnia-app/app/network/__tests__/authentication.test.js index d5edece4f3..779a3c6239 100644 --- a/packages/insomnia-app/app/network/__tests__/authentication.test.js +++ b/packages/insomnia-app/app/network/__tests__/authentication.test.js @@ -3,7 +3,7 @@ import { AUTH_OAUTH_1 } from '../../common/constants'; describe('OAuth 1.0', () => { it('Does OAuth 1.0', async () => { - const header = await getAuthHeader('req_123', 'https://insomnia.rest/', 'GET', { + const authentication = { type: AUTH_OAUTH_1, consumerKey: 'consumerKey', consumerSecret: 'consumerSecret', @@ -13,7 +13,9 @@ describe('OAuth 1.0', () => { signatureMethod: 'HMAC-SHA1', nonce: 'nonce', timestamp: '1234567890', - }); + }; + const request = { url: 'https://insomnia.rest/', method: 'GET', authentication }; + const header = await getAuthHeader(request, 'https://insomnia.rest/'); expect(header).toEqual({ name: 'Authorization', @@ -31,7 +33,7 @@ describe('OAuth 1.0', () => { }); it('Does OAuth 1.0 with RSA-SHA1', async () => { - const header = await getAuthHeader('req_123', 'https://insomnia.rest/', 'GET', { + const authentication = { type: AUTH_OAUTH_1, consumerKey: 'consumerKey', consumerSecret: 'consumerSecret', @@ -57,7 +59,9 @@ describe('OAuth 1.0', () => { '-----END RSA PRIVATE KEY-----', nonce: 'nonce', timestamp: '1234567890', - }); + }; + const request = { url: 'https://insomnia.rest/', method: 'GET', authentication }; + const header = await getAuthHeader(request, 'https://insomnia.rest/'); expect(header).toEqual({ name: 'Authorization', @@ -75,12 +79,14 @@ describe('OAuth 1.0', () => { }); it('Does OAuth 1.0 with defaults', async () => { - const header = await getAuthHeader('req_123', 'https://insomnia.rest/', 'GET', { + const authentication = { type: AUTH_OAUTH_1, consumerKey: 'consumerKey', consumerSecret: 'consumerSecret', signatureMethod: 'HMAC-SHA1', - }); + }; + const request = { url: 'https://insomnia.rest/', method: 'GET', authentication }; + const header = await getAuthHeader(request, 'https://insomnia.rest/'); expect(header.name).toBe('Authorization'); expect(header.value).toMatch( diff --git a/packages/insomnia-app/app/network/authentication.js b/packages/insomnia-app/app/network/authentication.js index 0d9dc5da91..7b451a346b 100644 --- a/packages/insomnia-app/app/network/authentication.js +++ b/packages/insomnia-app/app/network/authentication.js @@ -11,7 +11,7 @@ import getOAuth2Token from './o-auth-2/get-token'; import getOAuth1Token from './o-auth-1/get-token'; import * as Hawk from 'hawk'; import jwtAuthentication from 'jwt-authentication'; -import type { RequestAuthentication } from '../models/request'; +import type { RenderedRequest } from '../common/render'; import { getBasicAuthHeader } from './basic-auth/get-header'; import { getBearerAuthHeader } from './bearer-auth/get-header'; @@ -21,11 +21,12 @@ type Header = { }; export async function getAuthHeader( - requestId: string, + renderedRequest: RenderedRequest, url: string, - method: string, - authentication: RequestAuthentication, ): Promise
{ + const { method, authentication } = renderedRequest; + const requestId = renderedRequest._id; + if (authentication.disabled) { return null; } @@ -69,11 +70,21 @@ export async function getAuthHeader( } if (authentication.type === AUTH_HAWK) { - const { id, key, algorithm, ext } = authentication; - const header = Hawk.client.header(url, method, { + const { id, key, algorithm, ext, validatePayload } = authentication; + let headerOptions = { credentials: { id, key, algorithm }, ext: ext, - }); + }; + + if (validatePayload) { + const payloadValidationFields = { + payload: renderedRequest.body.text, + contentType: renderedRequest.body.mimeType, + }; + headerOptions = Object.assign({}, payloadValidationFields, headerOptions); + } + + const header = Hawk.client.header(url, method, headerOptions); return { name: 'Authorization', value: header.field, diff --git a/packages/insomnia-app/app/network/network.js b/packages/insomnia-app/app/network/network.js index 250665e773..4886718c80 100644 --- a/packages/insomnia-app/app/network/network.js +++ b/packages/insomnia-app/app/network/network.js @@ -594,12 +594,7 @@ export async function _actuallySend( } else if (renderedRequest.authentication.type === AUTH_NETRC) { setOpt(Curl.option.NETRC, Curl.netrc.REQUIRED); } else { - const authHeader = await getAuthHeader( - renderedRequest._id, - finalUrl, - renderedRequest.method, - renderedRequest.authentication, - ); + const authHeader = await getAuthHeader(renderedRequest, finalUrl); if (authHeader) { headers.push({ diff --git a/packages/insomnia-app/app/plugins/install.js b/packages/insomnia-app/app/plugins/install.js index 6d001904dc..c73d7dcef7 100644 --- a/packages/insomnia-app/app/plugins/install.js +++ b/packages/insomnia-app/app/plugins/install.js @@ -78,11 +78,6 @@ async function _isInsomniaPlugin(lookupName: string): Promise { }, }, (err, stdout, stderr) => { - if (err) { - reject(new Error(`${lookupName} npm error: ${err.message}`)); - return; - } - if (stderr) { reject(new Error(`Yarn error ${stderr.toString('utf8')}`)); return; @@ -91,8 +86,15 @@ async function _isInsomniaPlugin(lookupName: string): Promise { let yarnOutput; try { yarnOutput = JSON.parse(stdout.toString('utf8')); - } catch (err) { - reject(new Error(`Yarn response not JSON: ${err.message}`)); + } catch (ex) { + // Output is not JSON. Check if yarn/electron terminated with non-zero exit code. + // In certain environments electron can exit with error even if output is OK. + // Parsing is attemted before checking exit code as workaround for false errors. + if (err) { + reject(new Error(`${lookupName} npm error: ${err.message}`)); + } else { + reject(new Error(`Yarn response not JSON: ${ex.message}`)); + } return; } @@ -149,7 +151,10 @@ async function _installPluginToTmpDir(lookupName: string): Promise<{ tmpDir: str }, }, (err, stdout, stderr) => { - if (err) { + // Check yarn/electron process exit code. + // In certain environments electron can exit with error even if the command was perfomed sucesfully. + // Checking for sucess message in output is a workaround for false errors. + if (err && !stdout.toString('utf8').includes('success')) { reject(new Error(`${lookupName} install error: ${err.message}`)); return; } diff --git a/packages/insomnia-app/app/ui/components/base/dropdown/dropdown-hint.js b/packages/insomnia-app/app/ui/components/base/dropdown/dropdown-hint.js index 703ab27389..d2123ee431 100644 --- a/packages/insomnia-app/app/ui/components/base/dropdown/dropdown-hint.js +++ b/packages/insomnia-app/app/ui/components/base/dropdown/dropdown-hint.js @@ -1,16 +1,16 @@ // @flow import * as React from 'react'; -import type { Hotkey as HotkeyType } from '../../../../common/hotkeys'; import Hotkey from '../../hotkey'; +import type { KeyBindings } from '../../../../common/hotkeys'; type Props = { - hotkey: HotkeyType, + keyBindings: KeyBindings, }; class DropdownHint extends React.PureComponent { render() { - const { hotkey } = this.props; - return ; + const { keyBindings } = this.props; + return ; } } diff --git a/packages/insomnia-app/app/ui/components/base/dropdown/dropdown.js b/packages/insomnia-app/app/ui/components/base/dropdown/dropdown.js index a94d4b7cec..490609e684 100644 --- a/packages/insomnia-app/app/ui/components/base/dropdown/dropdown.js +++ b/packages/insomnia-app/app/ui/components/base/dropdown/dropdown.js @@ -8,7 +8,8 @@ import DropdownItem from './dropdown-item'; import DropdownDivider from './dropdown-divider'; import { fuzzyMatch } from '../../../../common/misc'; import KeydownBinder from '../../keydown-binder'; -import * as hotkeys from '../../../../common/hotkeys'; +import { executeHotKey } from '../../../../common/hotkeys-listener'; +import { hotKeyRefs } from '../../../../common/hotkeys'; const dropdownsContainer = document.querySelector('#dropdowns-container'); @@ -115,7 +116,7 @@ class Dropdown extends PureComponent { this._handleDropdownNavigation(e); - hotkeys.executeHotKey(e, hotkeys.CLOSE_DROPDOWN, () => { + executeHotKey(e, hotKeyRefs.CLOSE_DROPDOWN, () => { this.hide(); }); } diff --git a/packages/insomnia-app/app/ui/components/base/modal.js b/packages/insomnia-app/app/ui/components/base/modal.js index 7afb6e662b..8775c5ceb8 100644 --- a/packages/insomnia-app/app/ui/components/base/modal.js +++ b/packages/insomnia-app/app/ui/components/base/modal.js @@ -3,7 +3,8 @@ import PropTypes from 'prop-types'; import autobind from 'autobind-decorator'; import classnames from 'classnames'; import KeydownBinder from '../keydown-binder'; -import * as hotkeys from '../../../common/hotkeys'; +import { hotKeyRefs } from '../../../common/hotkeys'; +import { pressedHotKey } from '../../../common/hotkeys-listener'; // Keep global z-index reference so that every modal will // appear over top of an existing one. @@ -21,7 +22,7 @@ class Modal extends PureComponent { }; } - _handleKeyDown(e) { + async _handleKeyDown(e) { if (!this.state.open) { return; } @@ -32,7 +33,7 @@ class Modal extends PureComponent { } const closeOnKeyCodes = this.props.closeOnKeyCodes || []; - const pressedEscape = hotkeys.pressedHotKey(e, hotkeys.CLOSE_MODAL); + const pressedEscape = await pressedHotKey(e, hotKeyRefs.CLOSE_MODAL); const pressedCloseButton = closeOnKeyCodes.find(c => c === e.keyCode); // Pressed escape diff --git a/packages/insomnia-app/app/ui/components/codemirror/code-editor.js b/packages/insomnia-app/app/ui/components/codemirror/code-editor.js index 59f7f5d954..255b06de81 100644 --- a/packages/insomnia-app/app/ui/components/codemirror/code-editor.js +++ b/packages/insomnia-app/app/ui/components/codemirror/code-editor.js @@ -10,7 +10,8 @@ import { showModal } from '../modals/index'; import FilterHelpModal from '../modals/filter-help-modal'; import * as misc from '../../../common/misc'; import prettify from 'insomnia-prettify'; -import { DEBOUNCE_MILLIS, isMac } from '../../../common/constants'; +import { DEBOUNCE_MILLIS, EDITOR_KEY_MAP_VIM, isMac } from '../../../common/constants'; +import keyCodes from '../../../common/keycodes'; import './base-imports'; import { getTagDefinitions } from '../../../templating/index'; import Dropdown from '../base/dropdown/dropdown'; @@ -287,6 +288,7 @@ class CodeEditor extends React.Component { this.codeMirror.on('blur', this._codemirrorBlur); this.codeMirror.on('paste', this._codemirrorPaste); this.codeMirror.on('scroll', this._codemirrorScroll); + this.codeMirror.on('keyHandled', this._codemirrorKeyHandled); // Prevent these things if we're type === "password" this.codeMirror.on('copy', this._codemirrorPreventWhenTypePassword); @@ -664,6 +666,18 @@ class CodeEditor extends React.Component { this._persistState(); } + _codemirrorKeyHandled(codeMirror, keyName, event) { + const { keyMap } = this.props; + const { keyCode } = event; + + const isVimKeyMap = keyMap === EDITOR_KEY_MAP_VIM; + const pressedEscape = keyCode === keyCodes.esc; + + if (isVimKeyMap && pressedEscape) { + event.stopPropagation(); + } + } + _codemirrorValueBeforeChange(doc, change) { // If we're in single-line mode, merge all changed lines into one if (this.props.singleLine && change.text && change.text.length > 1) { diff --git a/packages/insomnia-app/app/ui/components/dropdowns/environments-dropdown.js b/packages/insomnia-app/app/ui/components/dropdowns/environments-dropdown.js index 1ac8a95a39..19e3469d36 100644 --- a/packages/insomnia-app/app/ui/components/dropdowns/environments-dropdown.js +++ b/packages/insomnia-app/app/ui/components/dropdowns/environments-dropdown.js @@ -11,17 +11,19 @@ import { } from '../base/dropdown'; import { showModal } from '../modals/index'; import Tooltip from '../tooltip'; -import * as hotkeys from '../../../common/hotkeys'; import KeydownBinder from '../keydown-binder'; import type { Workspace } from '../../../models/workspace'; import type { Environment } from '../../../models/environment'; +import type { HotKeyRegistry } from '../../../common/hotkeys'; +import { hotKeyRefs } from '../../../common/hotkeys'; +import { executeHotKey } from '../../../common/hotkeys-listener'; type Props = { - workspace: Workspace, handleChangeEnvironment: Function, workspace: Workspace, environments: Array, environmentHighlightColorStyle: String, + hotKeyRegistry: HotKeyRegistry, // Optional className?: string, @@ -57,7 +59,7 @@ class EnvironmentsDropdown extends React.PureComponent { } _handleKeydown(e: KeyboardEvent) { - hotkeys.executeHotKey(e, hotkeys.TOGGLE_ENVIRONMENTS_MENU, () => { + executeHotKey(e, hotKeyRefs.ENVIRONMENT_SHOW_SWITCH_MENU, () => { this._dropdown && this._dropdown.toggle(true); }); } @@ -69,6 +71,7 @@ class EnvironmentsDropdown extends React.PureComponent { environments, activeEnvironment, environmentHighlightColorStyle, + hotKeyRegistry, ...other } = this.props; @@ -125,7 +128,7 @@ class EnvironmentsDropdown extends React.PureComponent { Manage Environments - + diff --git a/packages/insomnia-app/app/ui/components/dropdowns/request-actions-dropdown.js b/packages/insomnia-app/app/ui/components/dropdowns/request-actions-dropdown.js index c515f8d3f1..1aa69d6dfd 100644 --- a/packages/insomnia-app/app/ui/components/dropdowns/request-actions-dropdown.js +++ b/packages/insomnia-app/app/ui/components/dropdowns/request-actions-dropdown.js @@ -10,7 +10,7 @@ import { DropdownItem, } from '../base/dropdown/index'; import * as models from '../../../models'; -import * as hotkeys from '../../../common/hotkeys'; +import { hotKeyRefs } from '../../../common/hotkeys'; @autobind class RequestActionsDropdown extends PureComponent { @@ -44,6 +44,7 @@ class RequestActionsDropdown extends PureComponent { const { request, // eslint-disable-line no-unused-vars handleShowSettings, + hotKeyRegistry, ...other } = this.props; @@ -54,11 +55,13 @@ class RequestActionsDropdown extends PureComponent { Duplicate - + Generate Code - + Copy as Curl @@ -71,7 +74,7 @@ class RequestActionsDropdown extends PureComponent { Settings - + ); @@ -84,6 +87,7 @@ RequestActionsDropdown.propTypes = { handleCopyAsCurl: PropTypes.func.isRequired, handleShowSettings: PropTypes.func.isRequired, request: PropTypes.object.isRequired, + hotKeyRegistry: PropTypes.object.isRequired, }; export default RequestActionsDropdown; diff --git a/packages/insomnia-app/app/ui/components/dropdowns/request-group-actions-dropdown.js b/packages/insomnia-app/app/ui/components/dropdowns/request-group-actions-dropdown.js index 5134ae3085..a964bd268f 100644 --- a/packages/insomnia-app/app/ui/components/dropdowns/request-group-actions-dropdown.js +++ b/packages/insomnia-app/app/ui/components/dropdowns/request-group-actions-dropdown.js @@ -12,7 +12,7 @@ import { import EnvironmentEditModal from '../modals/environment-edit-modal'; import * as models from '../../../models'; import { showPrompt, showModal } from '../modals/index'; -import * as hotkeys from '../../../common/hotkeys'; +import { hotKeyRefs } from '../../../common/hotkeys'; @autobind class RequestGroupActionsDropdown extends PureComponent { @@ -63,6 +63,7 @@ class RequestGroupActionsDropdown extends PureComponent { render() { const { requestGroup, // eslint-disable-line no-unused-vars + hotKeyRegistry, ...other } = this.props; @@ -73,11 +74,11 @@ class RequestGroupActionsDropdown extends PureComponent { New Request - + New Folder - + @@ -102,6 +103,7 @@ class RequestGroupActionsDropdown extends PureComponent { RequestGroupActionsDropdown.propTypes = { workspace: PropTypes.object.isRequired, + hotKeyRegistry: PropTypes.object.isRequired, handleCreateRequest: PropTypes.func.isRequired, handleCreateRequestGroup: PropTypes.func.isRequired, handleDuplicateRequestGroup: PropTypes.func.isRequired, diff --git a/packages/insomnia-app/app/ui/components/dropdowns/response-history-dropdown.js b/packages/insomnia-app/app/ui/components/dropdowns/response-history-dropdown.js index d1fec75add..992420609d 100644 --- a/packages/insomnia-app/app/ui/components/dropdowns/response-history-dropdown.js +++ b/packages/insomnia-app/app/ui/components/dropdowns/response-history-dropdown.js @@ -6,9 +6,10 @@ import StatusTag from '../tags/status-tag'; import URLTag from '../tags/url-tag'; import PromptButton from '../base/prompt-button'; import KeydownBinder from '../keydown-binder'; -import * as hotkeys from '../../../common/hotkeys'; import TimeTag from '../tags/time-tag'; import SizeTag from '../tags/size-tag'; +import { executeHotKey } from '../../../common/hotkeys-listener'; +import { hotKeyRefs } from '../../../common/hotkeys'; @autobind class ResponseHistoryDropdown extends PureComponent { @@ -29,7 +30,7 @@ class ResponseHistoryDropdown extends PureComponent { } _handleKeydown(e) { - hotkeys.executeHotKey(e, hotkeys.TOGGLE_HISTORY_DROPDOWN, () => { + executeHotKey(e, hotKeyRefs.REQUEST_TOGGLE_HISTORY, () => { this._dropdown && this._dropdown.toggle(true); }); } diff --git a/packages/insomnia-app/app/ui/components/dropdowns/workspace-dropdown.js b/packages/insomnia-app/app/ui/components/dropdowns/workspace-dropdown.js index fa55637be1..6850972f6c 100644 --- a/packages/insomnia-app/app/ui/components/dropdowns/workspace-dropdown.js +++ b/packages/insomnia-app/app/ui/components/dropdowns/workspace-dropdown.js @@ -17,8 +17,9 @@ import WorkspaceShareSettingsModal from '../modals/workspace-share-settings-moda import * as session from '../../../sync/session'; import LoginModal from '../modals/login-modal'; import Tooltip from '../tooltip'; -import * as hotkeys from '../../../common/hotkeys'; import KeydownBinder from '../keydown-binder'; +import { hotKeyRefs } from '../../../common/hotkeys'; +import { executeHotKey } from '../../../common/hotkeys-listener'; @autobind class WorkspaceDropdown extends PureComponent { @@ -91,7 +92,7 @@ class WorkspaceDropdown extends PureComponent { } _handleKeydown(e) { - hotkeys.executeHotKey(e, hotkeys.TOGGLE_MAIN_MENU, () => { + executeHotKey(e, hotKeyRefs.TOGGLE_MAIN_MENU, () => { this._dropdown && this._dropdown.toggle(true); }); } @@ -103,6 +104,7 @@ class WorkspaceDropdown extends PureComponent { activeWorkspace, unseenWorkspaces, isLoading, + hotKeyRegistry, ...other } = this.props; @@ -145,7 +147,7 @@ class WorkspaceDropdown extends PureComponent { {activeWorkspace.name} Workspace Settings - + @@ -176,7 +178,7 @@ class WorkspaceDropdown extends PureComponent { Preferences - + Import/Export @@ -215,6 +217,7 @@ WorkspaceDropdown.propTypes = { workspaces: PropTypes.arrayOf(PropTypes.object).isRequired, unseenWorkspaces: PropTypes.arrayOf(PropTypes.object).isRequired, activeWorkspace: PropTypes.object.isRequired, + hotKeyRegistry: PropTypes.object.isRequired, // Optional className: PropTypes.string, diff --git a/packages/insomnia-app/app/ui/components/editors/auth/hawk-auth.js b/packages/insomnia-app/app/ui/components/editors/auth/hawk-auth.js index 5ac43a008c..3e28cd6480 100644 --- a/packages/insomnia-app/app/ui/components/editors/auth/hawk-auth.js +++ b/packages/insomnia-app/app/ui/components/editors/auth/hawk-auth.js @@ -49,6 +49,11 @@ class HawkAuth extends React.PureComponent { this._handleChangeProperty('ext', value); } + _handleChangePayloadValidation(): void { + const { request } = this.props; + this._handleChangeProperty('validatePayload', !request.authentication.validatePayload); + } + renderHawkAuthenticationFields(): React.Node { const hawkAuthId = this.renderInputRow('Auth ID', 'id', this._handleChangeHawkAuthId); @@ -66,7 +71,13 @@ class HawkAuth extends React.PureComponent { const ext = this.renderInputRow('Ext', 'ext', this._handleChangeExt); - return [hawkAuthId, hawkAuthKey, algorithm, ext]; + const payloadValidation = this.renderButtonRow( + 'Validate Payload', + 'validatePayload', + this._handleChangePayloadValidation, + ); + + return [hawkAuthId, hawkAuthKey, algorithm, ext, payloadValidation]; } renderSelectRow( @@ -115,7 +126,7 @@ class HawkAuth extends React.PureComponent { isVariableUncovered, } = this.props; - const {authentication} = request; + const { authentication } = request; const id = label.replace(/ /g, '-'); return ( @@ -146,6 +157,44 @@ class HawkAuth extends React.PureComponent { ); } + renderButtonRow(label: string, property: string, onChange: Function): React.Element<*> { + const { request } = this.props; + const { authentication } = request; + const id = label.replace(/ /g, '-'); + return ( + + + + + +
+ +
+ + + ); + } + render() { const fields = this.renderHawkAuthenticationFields(); const { authentication } = this.props.request; diff --git a/packages/insomnia-app/app/ui/components/hotkey.js b/packages/insomnia-app/app/ui/components/hotkey.js index 6cd6c22181..33de89c00f 100644 --- a/packages/insomnia-app/app/ui/components/hotkey.js +++ b/packages/insomnia-app/app/ui/components/hotkey.js @@ -1,12 +1,12 @@ // @flow import * as React from 'react'; import classnames from 'classnames'; -import type { Hotkey as HotkeyType } from '../../common/hotkeys'; +import { ALT_SYM, CTRL_SYM, isMac, joinHotKeys, META_SYM, SHIFT_SYM } from '../../common/constants'; +import type { KeyBindings } from '../../common/hotkeys'; import * as hotkeys from '../../common/hotkeys'; -import { ALT_SYM, CTRL_SYM, isMac, joinHotKeys, MOD_SYM, SHIFT_SYM } from '../../common/constants'; type Props = { - hotkey: HotkeyType, + keyBindings: KeyBindings, // Optional className?: string, @@ -14,22 +14,18 @@ type Props = { class Hotkey extends React.PureComponent { render() { - const { hotkey, className } = this.props; - const { alt, shift, meta, metaIsCtrl } = hotkey; + const { keyBindings, className } = this.props; + + const keyComb = hotkeys.getPlatformKeyCombinations(keyBindings)[0]; + const { ctrl, alt, shift, meta, keyCode } = keyComb; const chars = []; alt && chars.push(ALT_SYM); shift && chars.push(SHIFT_SYM); + ctrl && chars.push(CTRL_SYM); + meta && chars.push(META_SYM); - if (meta) { - if (metaIsCtrl) { - chars.push(CTRL_SYM); - } else { - chars.push(isMac() ? MOD_SYM : CTRL_SYM); - } - } - - chars.push(hotkeys.getChar(hotkey)); + chars.push(hotkeys.getChar(keyCode)); return ( diff --git a/packages/insomnia-app/app/ui/components/modals/settings-modal.js b/packages/insomnia-app/app/ui/components/modals/settings-modal.js index 7461c711cf..23ad070f55 100644 --- a/packages/insomnia-app/app/ui/components/modals/settings-modal.js +++ b/packages/insomnia-app/app/ui/components/modals/settings-modal.js @@ -137,7 +137,7 @@ class SettingsModal extends PureComponent { - + diff --git a/packages/insomnia-app/app/ui/components/request-pane.js b/packages/insomnia-app/app/ui/components/request-pane.js index 94b99b5961..d128e5fbb8 100644 --- a/packages/insomnia-app/app/ui/components/request-pane.js +++ b/packages/insomnia-app/app/ui/components/request-pane.js @@ -29,8 +29,8 @@ import { showModal } from './modals/index'; import RequestSettingsModal from './modals/request-settings-modal'; import MarkdownPreview from './markdown-preview'; import type { Settings } from '../../models/settings'; -import * as hotkeys from '../../common/hotkeys'; import ErrorBoundary from './error-boundary'; +import { hotKeyRefs } from '../../common/hotkeys'; type Props = { // Functions @@ -171,38 +171,42 @@ class RequestPane extends React.PureComponent { const paneHeaderClasses = 'pane__header theme--pane__header'; const paneBodyClasses = 'pane__body theme--pane__body'; + const hotKeyRegistry = settings.hotKeyRegistry; + if (!request) { return (
-
+
- - - - - - - - - - - - + + + + + + + + + + + +
New Request - - - -
Switch Requests - - - -
Edit Environments - - - -
New Request + + + +
Switch Requests + + + +
Edit Environments + + + +
@@ -251,6 +255,7 @@ class RequestPane extends React.PureComponent { isVariableUncovered={isVariableUncovered} handleGetRenderContext={handleGetRenderContext} request={request} + hotKeyRegistry={settings.hotKeyRegistry} />
@@ -266,7 +271,7 @@ class RequestPane extends React.PureComponent { ? getContentTypeName(request.body.mimeType) : 'Body'} {numBodyParams ? {numBodyParams} : null} - + @@ -275,7 +280,7 @@ class RequestPane extends React.PureComponent { request={request} className="tall"> {getAuthTypeName(request.authentication.type) || 'Auth'} - + @@ -295,7 +300,7 @@ class RequestPane extends React.PureComponent { Docs {request.description && ( - + )} @@ -341,7 +346,7 @@ class RequestPane extends React.PureComponent { - + @@ -419,10 +424,10 @@ class RequestPane extends React.PureComponent {

- + -
-
+
+

diff --git a/packages/insomnia-app/app/ui/components/settings/shortcuts.js b/packages/insomnia-app/app/ui/components/settings/shortcuts.js index 2b28a49cb5..c3da84fe15 100644 --- a/packages/insomnia-app/app/ui/components/settings/shortcuts.js +++ b/packages/insomnia-app/app/ui/components/settings/shortcuts.js @@ -1,17 +1,23 @@ +// @flow import React, { PureComponent } from 'react'; import autobind from 'autobind-decorator'; import Hotkey from '../hotkey'; -import * as hotkeys from '../../../common/hotkeys'; +import type { HotKeyDefinition, HotKeyRegistry } from '../../../common/hotkeys'; +import { hotKeyRefs } from '../../../common/hotkeys'; + +type Props = { + hotKeyRegistry: HotKeyRegistry, +}; @autobind -class Shortcuts extends PureComponent { - renderHotkey(hotkey, i) { +class Shortcuts extends PureComponent { + renderHotKey(def: HotKeyDefinition, i: number) { return ( - {hotkey.description} + {def.description} - + @@ -19,33 +25,38 @@ class Shortcuts extends PureComponent { } render() { + const hotKeyDefs: Array = [ + hotKeyRefs.PREFERENCES_SHOW_KEYBOARD_SHORTCUTS, + hotKeyRefs.REQUEST_QUICK_SWITCH, + hotKeyRefs.REQUEST_SEND, + hotKeyRefs.REQUEST_SHOW_OPTIONS, + hotKeyRefs.REQUEST_SHOW_CREATE, + hotKeyRefs.REQUEST_SHOW_DELETE, + hotKeyRefs.REQUEST_SHOW_CREATE_FOLDER, + hotKeyRefs.REQUEST_SHOW_DUPLICATE, + hotKeyRefs.SHOW_COOKIES_EDITOR, + hotKeyRefs.ENVIRONMENT_SHOW_EDITOR, + hotKeyRefs.ENVIRONMENT_SHOW_SWITCH_MENU, + hotKeyRefs.REQUEST_FOCUS_URL, + hotKeyRefs.RESPONSE_FOCUS, + hotKeyRefs.REQUEST_TOGGLE_HTTP_METHOD_MENU, + hotKeyRefs.SIDEBAR_TOGGLE, + hotKeyRefs.REQUEST_TOGGLE_HISTORY, + hotKeyRefs.SHOW_AUTOCOMPLETE, + hotKeyRefs.PREFERENCES_SHOW_GENERAL, + hotKeyRefs.WORKSPACE_SHOW_SETTINGS, + hotKeyRefs.REQUEST_SHOW_SETTINGS, + hotKeyRefs.TOGGLE_MAIN_MENU, + hotKeyRefs.PLUGIN_RELOAD, + hotKeyRefs.ENVIRONMENT_UNCOVER_VARIABLES, + ]; return (
- {this.renderHotkey(hotkeys.SHOW_KEYBOARD_SHORTCUTS)} - {this.renderHotkey(hotkeys.SHOW_QUICK_SWITCHER)} - {this.renderHotkey(hotkeys.SEND_REQUEST)} - {this.renderHotkey(hotkeys.SHOW_SEND_OPTIONS)} - {this.renderHotkey(hotkeys.CREATE_REQUEST)} - {this.renderHotkey(hotkeys.DELETE_REQUEST)} - {this.renderHotkey(hotkeys.CREATE_FOLDER)} - {this.renderHotkey(hotkeys.DUPLICATE_REQUEST)} - {this.renderHotkey(hotkeys.SHOW_COOKIES)} - {this.renderHotkey(hotkeys.SHOW_ENVIRONMENTS)} - {this.renderHotkey(hotkeys.TOGGLE_ENVIRONMENTS_MENU)} - {this.renderHotkey(hotkeys.FOCUS_URL)} - {this.renderHotkey(hotkeys.FOCUS_RESPONSE)} - {this.renderHotkey(hotkeys.TOGGLE_METHOD_DROPDOWN)} - {this.renderHotkey(hotkeys.TOGGLE_SIDEBAR)} - {this.renderHotkey(hotkeys.TOGGLE_HISTORY_DROPDOWN)} - {this.renderHotkey(hotkeys.SHOW_AUTOCOMPLETE)} - {this.renderHotkey(hotkeys.SHOW_SETTINGS)} - {this.renderHotkey(hotkeys.SHOW_WORKSPACE_SETTINGS)} - {this.renderHotkey(hotkeys.SHOW_REQUEST_SETTINGS)} - {this.renderHotkey(hotkeys.TOGGLE_MAIN_MENU)} - {this.renderHotkey(hotkeys.RELOAD_PLUGINS)} - {this.renderHotkey(hotkeys.UNCOVER_VARIABLES)} + {hotKeyDefs.map((def: HotKeyDefinition, idx: number) => { + return this.renderHotKey(def, idx); + })}
diff --git a/packages/insomnia-app/app/ui/components/sidebar/sidebar-children.js b/packages/insomnia-app/app/ui/components/sidebar/sidebar-children.js index b19c78fc85..8dccdbdf7d 100644 --- a/packages/insomnia-app/app/ui/components/sidebar/sidebar-children.js +++ b/packages/insomnia-app/app/ui/components/sidebar/sidebar-children.js @@ -6,6 +6,7 @@ import * as models from '../../../models/index'; import type { RequestGroup } from '../../../models/request-group'; import type { Workspace } from '../../../models/workspace'; import type { Request } from '../../../models/request'; +import type { HotKeyRegistry } from '../../../common/hotkeys'; type Child = { doc: Request | RequestGroup, @@ -29,6 +30,7 @@ type Props = { childObjects: Array, workspace: Workspace, filter: string, + hotKeyRegistry: HotKeyRegistry, // Optional activeRequest?: Request, @@ -50,6 +52,7 @@ class SidebarChildren extends React.PureComponent { handleActivateRequest, activeRequest, workspace, + hotKeyRegistry, } = this.props; const activeRequestId = activeRequest ? activeRequest._id : 'n/a'; @@ -73,6 +76,7 @@ class SidebarChildren extends React.PureComponent { isActive={child.doc._id === activeRequestId} request={child.doc} workspace={workspace} + hotKeyRegistry={hotKeyRegistry} /> ); } @@ -113,6 +117,7 @@ class SidebarChildren extends React.PureComponent { numChildren={child.children.length} workspace={workspace} requestGroup={requestGroup} + hotKeyRegistry={hotKeyRegistry} children={children} /> ); diff --git a/packages/insomnia-app/app/ui/components/sidebar/sidebar-filter.js b/packages/insomnia-app/app/ui/components/sidebar/sidebar-filter.js index 06d86c778f..bc74b692a8 100644 --- a/packages/insomnia-app/app/ui/components/sidebar/sidebar-filter.js +++ b/packages/insomnia-app/app/ui/components/sidebar/sidebar-filter.js @@ -4,13 +4,16 @@ import autobind from 'autobind-decorator'; import { Dropdown, DropdownHint, DropdownButton, DropdownItem } from '../base/dropdown'; import { DEBOUNCE_MILLIS } from '../../../common/constants'; import KeydownBinder from '../keydown-binder'; -import * as hotkeys from '../../../common/hotkeys'; +import type { HotKeyRegistry } from '../../../common/hotkeys'; +import { hotKeyRefs } from '../../../common/hotkeys'; +import { executeHotKey } from '../../../common/hotkeys-listener'; type Props = { onChange: string => void, requestCreate: () => void, requestGroupCreate: () => void, filter: string, + hotKeyRegistry: HotKeyRegistry, }; @autobind @@ -48,13 +51,13 @@ class SidebarFilter extends React.PureComponent { } _handleKeydown(e: KeyboardEvent) { - hotkeys.executeHotKey(e, hotkeys.FOCUS_FILTER, () => { + executeHotKey(e, hotKeyRefs.SIDEBAR_FOCUS_FILTER, () => { this._input && this._input.focus(); }); } render() { - const { filter } = this.props; + const { filter, hotKeyRegistry } = this.props; return (
@@ -79,11 +82,13 @@ class SidebarFilter extends React.PureComponent { New Request - + New Folder - +
diff --git a/packages/insomnia-app/app/ui/components/sidebar/sidebar-request-group-row.js b/packages/insomnia-app/app/ui/components/sidebar/sidebar-request-group-row.js index 441c45a7b4..2fd5316d10 100644 --- a/packages/insomnia-app/app/ui/components/sidebar/sidebar-request-group-row.js +++ b/packages/insomnia-app/app/ui/components/sidebar/sidebar-request-group-row.js @@ -55,6 +55,7 @@ class SidebarRequestGroupRow extends PureComponent { isDragging, isDraggingOver, workspace, + hotKeyRegistry, } = this.props; const { dragDirection } = this.state; @@ -98,6 +99,7 @@ class SidebarRequestGroupRow extends PureComponent { handleMoveRequestGroup={handleMoveRequestGroup} workspace={workspace} requestGroup={requestGroup} + hotKeyRegistry={hotKeyRegistry} right /> @@ -122,6 +124,7 @@ class SidebarRequestGroupRow extends PureComponent { workspace={workspace} requestCreate={handleCreateRequest} filter={filter} + hotKeyRegistry={hotKeyRegistry} /> )} @@ -146,6 +149,7 @@ SidebarRequestGroupRow.propTypes = { isCollapsed: PropTypes.bool.isRequired, workspace: PropTypes.object.isRequired, requestGroup: PropTypes.object.isRequired, + hotKeyRegistry: PropTypes.object.isRequired, // React DnD isDragging: PropTypes.bool, diff --git a/packages/insomnia-app/app/ui/components/sidebar/sidebar-request-row.js b/packages/insomnia-app/app/ui/components/sidebar/sidebar-request-row.js index fbf266408f..518e951be7 100644 --- a/packages/insomnia-app/app/ui/components/sidebar/sidebar-request-row.js +++ b/packages/insomnia-app/app/ui/components/sidebar/sidebar-request-row.js @@ -78,6 +78,7 @@ class SidebarRequestRow extends PureComponent { request, requestGroup, isActive, + hotKeyRegistry, } = this.props; const { dragDirection } = this.state; @@ -134,6 +135,7 @@ class SidebarRequestRow extends PureComponent { handleShowSettings={this._handleShowRequestSettings} request={request} requestGroup={requestGroup} + hotKeyRegistry={hotKeyRegistry} /> @@ -161,6 +163,7 @@ SidebarRequestRow.propTypes = { // Other filter: PropTypes.string.isRequired, isActive: PropTypes.bool.isRequired, + hotKeyRegistry: PropTypes.object.isRequired, // React DnD isDragging: PropTypes.bool, diff --git a/packages/insomnia-app/app/ui/components/sidebar/sidebar.js b/packages/insomnia-app/app/ui/components/sidebar/sidebar.js index d34fa1b5ba..291b26485d 100644 --- a/packages/insomnia-app/app/ui/components/sidebar/sidebar.js +++ b/packages/insomnia-app/app/ui/components/sidebar/sidebar.js @@ -55,6 +55,7 @@ class Sidebar extends PureComponent { handleActivateRequest, activeRequest, environmentHighlightColorStyle, + hotKeyRegistry, } = this.props; return ( @@ -77,6 +78,7 @@ class Sidebar extends PureComponent { activeWorkspace={workspace} workspaces={workspaces} unseenWorkspaces={unseenWorkspaces} + hotKeyRegistry={hotKeyRegistry} handleExportFile={handleExportFile} handleImportFile={handleImportFile} handleSetActiveWorkspace={handleSetActiveWorkspace} @@ -90,6 +92,7 @@ class Sidebar extends PureComponent { environments={environments} workspace={workspace} environmentHighlightColorStyle={environmentHighlightColorStyle} + hotKeyRegistry={hotKeyRegistry} />