From f76e382387d77b162afa75a538763075f283a861 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Sun, 17 Sep 2017 16:04:56 +0200 Subject: [PATCH] Revamp Keyboard Shortcuts (#487) * More shortcuts and accessibility tweaks * Use hotkey objects everywhere * A few last-minute tweaks --- app/common/hotkeys.js | 265 +++++++++++++++++ app/common/keycodes.js | 101 +++++++ .../__tests__/url-matches-cert-host.test.js | 2 +- app/network/network.js | 2 +- app/network/url-matches-cert-host.js | 8 +- .../components/base/dropdown/dropdown-hint.js | 26 +- app/ui/components/base/dropdown/dropdown.js | 38 +-- app/ui/components/base/modal.js | 36 ++- .../dropdowns/environments-dropdown.js | 71 +++-- .../components/dropdowns/method-dropdown.js | 10 +- .../dropdowns/request-actions-dropdown.js | 6 +- .../request-group-actions-dropdown.js | 4 +- .../dropdowns/response-history-dropdown.js | 60 ++-- .../dropdowns/workspace-dropdown.js | 160 ++++++----- .../components/editors/auth/o-auth-2-auth.js | 3 +- app/ui/components/hotkey.js | 42 +-- app/ui/components/keydown-binder.js | 57 ++++ .../modals/workspace-share-settings-modal.js | 4 +- app/ui/components/request-pane.js | 7 +- app/ui/components/request-url-bar.js | 83 +++--- app/ui/components/response-pane.js | 9 +- app/ui/components/settings/shortcuts.js | 42 +-- app/ui/components/sidebar/sidebar-filter.js | 60 ++-- .../components/sidebar/sidebar-request-row.js | 2 +- app/ui/containers/app.js | 266 ++++++------------ app/ui/css/components/forms.less | 24 +- app/ui/css/components/sidebar.less | 34 +-- 27 files changed, 943 insertions(+), 479 deletions(-) create mode 100644 app/common/hotkeys.js create mode 100644 app/common/keycodes.js create mode 100644 app/ui/components/keydown-binder.js diff --git a/app/common/hotkeys.js b/app/common/hotkeys.js new file mode 100644 index 0000000000..8c7930541b --- /dev/null +++ b/app/common/hotkeys.js @@ -0,0 +1,265 @@ +// @flow +import keycodes from './keycodes'; +import {isMac} from './constants'; +import {trackEvent} from '../analytics/index'; + +export type Hotkey = { + description: string, + meta: boolean, + alt: boolean, + shift: boolean, + keycode: number | Array +}; + +export const SHOW_WORKSPACE_SETTINGS: Hotkey = { + description: 'Show Workspace Settings', + meta: true, + alt: false, + shift: true, + keycode: keycodes.comma +}; + +export const SHOW_REQUEST_SETTINGS: Hotkey = { + description: 'Show Request Settings', + meta: true, + alt: true, + shift: true, + keycode: keycodes.comma +}; + +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 SHOW_AUTOCOMPLETE: Hotkey = { + description: 'Show Autocomplete', + meta: 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 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 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 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; +} + +export function executeHotKey ( + e: KeyboardEvent, + definition: Hotkey, + callback: Function +): void { + if (pressedHotKey(e, definition)) { + callback(); + trackEvent('Hotkey', definition.description); + } +} + +export function getChar (hotkey: Hotkey) { + const codes = Array.isArray(hotkey.keycode) ? hotkey.keycode : [hotkey.keycode]; + const chars = []; + + for (const keycode of codes) { + const v = Object.keys(keycodes).find(k => keycodes[k] === keycode); + + if (!v) { + console.error('Invalid hotkey', hotkey); + } else if (v.toUpperCase() === 'ENTER') { + chars.push('Enter'); + } 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() === 'SPACE') { + chars.push('Space'); + } else { + chars.push(v.toUpperCase()); + } + } + + return chars[0] || 'unknown'; +} diff --git a/app/common/keycodes.js b/app/common/keycodes.js new file mode 100644 index 0000000000..83f4e26b10 --- /dev/null +++ b/app/common/keycodes.js @@ -0,0 +1,101 @@ +export default { + backspace: 8, + tab: 9, + enter: 13, + shift: 16, + ctrl: 17, + alt: 18, + pausebreak: 19, + capslock: 20, + esc: 27, + space: 32, + pageup: 33, + pagedown: 34, + end: 35, + home: 36, + leftarrow: 37, + uparrow: 38, + rightarrow: 39, + downarrow: 40, + insert: 45, + delete: 46, + 0: 48, + 1: 49, + 2: 50, + 3: 51, + 4: 52, + 5: 53, + 6: 54, + 7: 55, + 8: 56, + 9: 57, + a: 65, + b: 66, + c: 67, + d: 68, + e: 69, + f: 70, + g: 71, + h: 72, + i: 73, + j: 74, + k: 75, + l: 76, + m: 77, + n: 78, + o: 79, + p: 80, + q: 81, + r: 82, + s: 83, + t: 84, + u: 85, + v: 86, + w: 87, + x: 88, + y: 89, + z: 90, + leftwindowkey: 91, + rightwindowkey: 92, + selectkey: 93, + numpad0: 96, + numpad1: 97, + numpad2: 98, + numpad3: 99, + numpad4: 100, + numpad5: 101, + numpad6: 102, + numpad7: 103, + numpad8: 104, + numpad9: 105, + multiply: 106, + add: 107, + subtract: 109, + decimalpoint: 110, + divide: 111, + f1: 112, + f2: 113, + f3: 114, + f4: 115, + f5: 116, + f6: 117, + f7: 118, + f8: 119, + f9: 120, + f10: 121, + f11: 122, + f12: 123, + numlock: 144, + scrolllock: 145, + semicolon: 186, + equalsign: 187, + comma: 188, + dash: 189, + period: 190, + forwardslash: 191, + graveaccent: 192, + openbracket: 219, + backslash: 220, + closebracket: 221, + singlequote: 222 +}; diff --git a/app/network/__tests__/url-matches-cert-host.test.js b/app/network/__tests__/url-matches-cert-host.test.js index d58a3569be..2c29e6350d 100644 --- a/app/network/__tests__/url-matches-cert-host.test.js +++ b/app/network/__tests__/url-matches-cert-host.test.js @@ -1,4 +1,4 @@ -import urlMatchesCertHost from '../url-matches-cert-host'; +import {urlMatchesCertHost} from '../url-matches-cert-host'; import {globalBeforeEach} from '../../__jest__/before-each'; describe('urlMatchesCertHost', () => { diff --git a/app/network/network.js b/app/network/network.js index 8454d59698..26bd7d929e 100644 --- a/app/network/network.js +++ b/app/network/network.js @@ -25,7 +25,7 @@ import * as plugins from '../plugins/index'; import * as pluginContexts from '../plugins/context/index'; import {getAuthHeader} from './authentication'; import {cookiesFromJar, jarFromCookies} from '../common/cookies'; -import urlMatchesCertHost from './url-matches-cert-host'; +import {urlMatchesCertHost} from './url-matches-cert-host'; import aws4 from 'aws4'; export type ResponsePatch = { diff --git a/app/network/url-matches-cert-host.js b/app/network/url-matches-cert-host.js index af1f342d6c..56545fa3de 100644 --- a/app/network/url-matches-cert-host.js +++ b/app/network/url-matches-cert-host.js @@ -4,7 +4,7 @@ import {escapeRegex, setDefaultProtocol} from '../common/misc'; const DEFAULT_PORT = 443; -export default function urlMatchesCertHost (certificateHost, requestUrl) { +export function urlMatchesCertHost (certificateHost, requestUrl) { const cHostWithProtocol = setDefaultProtocol(certificateHost, 'https:'); const {hostname, port} = urlParse(requestUrl); const {hostname: cHostname, port: cPort} = certificateUrlParse(cHostWithProtocol); @@ -14,5 +14,9 @@ export default function urlMatchesCertHost (certificateHost, requestUrl) { const cHostnameRegex = escapeRegex(cHostname || '').replace(/\\\*/g, '.*'); - return (assumedCPort === assumedPort && !!hostname.match(`^${cHostnameRegex}$`)); + if (assumedCPort !== assumedPort) { + return false; + } + + return !!(hostname || '').match(`^${cHostnameRegex}$`); } diff --git a/app/ui/components/base/dropdown/dropdown-hint.js b/app/ui/components/base/dropdown/dropdown-hint.js index a3f4494667..c7378aec83 100644 --- a/app/ui/components/base/dropdown/dropdown-hint.js +++ b/app/ui/components/base/dropdown/dropdown-hint.js @@ -1,27 +1,21 @@ +// @flow import React, {PureComponent} from 'react'; -import PropTypes from 'prop-types'; +import type {Hotkey as HotkeyType} from '../../../../common/hotkeys'; import Hotkey from '../../hotkey'; +type Props = { + hotkey: HotkeyType, +}; + class DropdownHint extends PureComponent { + props: Props; + render () { - const {char, shift, alt} = this.props; + const {hotkey} = this.props; return ( - + ); } } -DropdownHint.propTypes = { - char: PropTypes.string.isRequired, - - // Optional - alt: PropTypes.bool, - shift: PropTypes.bool -}; - export default DropdownHint; diff --git a/app/ui/components/base/dropdown/dropdown.js b/app/ui/components/base/dropdown/dropdown.js index 36acf6223e..62434b6d55 100644 --- a/app/ui/components/base/dropdown/dropdown.js +++ b/app/ui/components/base/dropdown/dropdown.js @@ -7,6 +7,9 @@ import DropdownButton from './dropdown-button'; 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'; @autobind class Dropdown extends PureComponent { @@ -113,10 +116,9 @@ class Dropdown extends PureComponent { this._handleDropdownNavigation(e); - if (e.key === 'Escape') { - e.preventDefault(); + executeHotKey(e, hotkeys.CLOSE_DROPDOWN, () => { this.hide(); - } + }); } _checkSizeAndPosition () { @@ -242,8 +244,6 @@ class Dropdown extends PureComponent { } componentDidMount () { - document.body.addEventListener('keydown', this._handleBodyKeyDown); - // Move the element to the body so we can position absolutely if (this._dropdownMenu) { const el = ReactDOM.findDOMNode(this._dropdownMenu); @@ -252,8 +252,6 @@ class Dropdown extends PureComponent { } componentWillUnmount () { - document.body.removeEventListener('keydown', this._handleBodyKeyDown); - // Remove the element from the body if (this._dropdownMenu) { const el = ReactDOM.findDOMNode(this._dropdownMenu); @@ -272,7 +270,7 @@ class Dropdown extends PureComponent { this.props.onHide && this.props.onHide(); } - show () { + show (filterVisible = false) { const bodyHeight = document.body.getBoundingClientRect().height; const dropdownTop = this._node.getBoundingClientRect().top; const dropUp = dropdownTop > bodyHeight - 200; @@ -280,7 +278,7 @@ class Dropdown extends PureComponent { this.setState({ open: true, dropUp, - filterVisible: false, + filterVisible, filter: '', filterItems: null, filterActiveIndex: -1, @@ -290,11 +288,11 @@ class Dropdown extends PureComponent { this.props.onOpen && this.props.onOpen(); } - toggle () { + toggle (filterVisible = false) { if (this.state.open) { this.hide(); } else { - this.show(); + this.show(filterVisible); } } @@ -405,14 +403,16 @@ class Dropdown extends PureComponent { } return ( -
- {finalChildren} -
+ +
+ {finalChildren} +
+
); } } diff --git a/app/ui/components/base/modal.js b/app/ui/components/base/modal.js index 9843a534c9..28ada62cda 100644 --- a/app/ui/components/base/modal.js +++ b/app/ui/components/base/modal.js @@ -2,7 +2,9 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import autobind from 'autobind-decorator'; import classnames from 'classnames'; -import {isMac} from '../../../common/constants'; +import KeydownBinder from '../keydown-binder'; +import * as hotkeys from '../../../common/hotkeys'; +import {pressedHotKey} from '../../../common/hotkeys'; // Keep global z-index reference so that every modal will // appear over top of an existing one. @@ -25,20 +27,13 @@ class Modal extends PureComponent { return; } - // Don't bubble up meta events up past the modal no matter what - // Example: ctrl+Enter to send requests - const isMeta = isMac() ? e.metaKey : e.ctrlKey; - if (isMeta) { - e.stopPropagation(); - } - // Don't check for close keys if we don't want them if (this.props.noEscape) { return; } const closeOnKeyCodes = this.props.closeOnKeyCodes || []; - const pressedEscape = e.keyCode === 27; + const pressedEscape = pressedHotKey(e, hotkeys.CLOSE_MODAL); const pressedElse = closeOnKeyCodes.find(c => c === e.keyCode); if (pressedEscape || pressedElse) { @@ -134,19 +129,20 @@ class Modal extends PureComponent { } return ( -
-
-
-
- {children} + +
+
+
+
+ {children} +
-
+ ); } } diff --git a/app/ui/components/dropdowns/environments-dropdown.js b/app/ui/components/dropdowns/environments-dropdown.js index 4037482b6c..2b3d278cef 100644 --- a/app/ui/components/dropdowns/environments-dropdown.js +++ b/app/ui/components/dropdowns/environments-dropdown.js @@ -6,6 +6,9 @@ import {Dropdown, DropdownButton, DropdownDivider, DropdownHint, DropdownItem} f import {showModal} from '../modals/index'; import {trackEvent} from '../../../analytics/index'; import Tooltip from '../tooltip'; +import {executeHotKey} from '../../../common/hotkeys'; +import * as hotkeys from '../../../common/hotkeys'; +import KeydownBinder from '../keydown-binder'; @autobind class EnvironmentsDropdown extends PureComponent { @@ -18,6 +21,10 @@ class EnvironmentsDropdown extends PureComponent { showModal(EnvironmentsModal, this.props.workspace); } + _setDropdownRef (n) { + this._dropdown = n; + } + renderEnvironmentItem (environment) { return ( { + this._dropdown && this._dropdown.toggle(true); + }); + } + render () { const { className, @@ -52,40 +65,42 @@ class EnvironmentsDropdown extends PureComponent { } return ( - - -
- {(!activeEnvironment && subEnvironments.length > 0) && ( - - - - )} -
- {activeEnvironment && activeEnvironment.color && ( - + + + +
+ {(!activeEnvironment && subEnvironments.length > 0) && ( + + + )} - {description} +
+ {activeEnvironment && activeEnvironment.color && ( + + )} + {description} +
+
- -
- + - Activate Environment - {subEnvironments.map(this.renderEnvironmentItem)} + Activate Environment + {subEnvironments.map(this.renderEnvironmentItem)} - - No Environment - + + No Environment + - General + General - - Manage Environments - - - + + Manage Environments + + + + ); } } diff --git a/app/ui/components/dropdowns/method-dropdown.js b/app/ui/components/dropdowns/method-dropdown.js index 871b927160..0bbbef1e25 100644 --- a/app/ui/components/dropdowns/method-dropdown.js +++ b/app/ui/components/dropdowns/method-dropdown.js @@ -10,6 +10,10 @@ const LOCALSTORAGE_KEY = 'insomnia.httpMethods'; @autobind class MethodDropdown extends PureComponent { + _setDropdownRef (n) { + this._dropdown = n; + } + _handleSetCustomMethod () { let recentMethods; try { @@ -62,6 +66,10 @@ class MethodDropdown extends PureComponent { trackEvent('Request', 'Set Method', method); } + toggle () { + this._dropdown && this._dropdown.toggle(true); + } + render () { const { method, @@ -70,7 +78,7 @@ class MethodDropdown extends PureComponent { ...extraProps } = this.props; return ( - + {method} diff --git a/app/ui/components/dropdowns/request-actions-dropdown.js b/app/ui/components/dropdowns/request-actions-dropdown.js index 2d9de8433d..9f4faa59e5 100644 --- a/app/ui/components/dropdowns/request-actions-dropdown.js +++ b/app/ui/components/dropdowns/request-actions-dropdown.js @@ -6,6 +6,7 @@ import {Dropdown, DropdownButton, DropdownHint, DropdownItem} from '../base/drop import * as models from '../../../models'; import {trackEvent} from '../../../analytics/index'; import {DropdownDivider} from '../base/dropdown/index'; +import * as hotkeys from '../../../common/hotkeys'; @autobind class RequestActionsDropdown extends PureComponent { @@ -53,10 +54,11 @@ class RequestActionsDropdown extends PureComponent { Duplicate - + Generate Code + Settings - + ); diff --git a/app/ui/components/dropdowns/request-group-actions-dropdown.js b/app/ui/components/dropdowns/request-group-actions-dropdown.js index c992cfc6fa..f62691b320 100644 --- a/app/ui/components/dropdowns/request-group-actions-dropdown.js +++ b/app/ui/components/dropdowns/request-group-actions-dropdown.js @@ -8,6 +8,7 @@ import * as models from '../../../models'; import {showModal} from '../modals'; import {trackEvent} from '../../../analytics/index'; import {showPrompt} from '../modals/index'; +import * as hotkeys from '../../../common/hotkeys'; @autobind class RequestGroupActionsDropdown extends PureComponent { @@ -69,10 +70,11 @@ class RequestGroupActionsDropdown extends PureComponent { New Request - + New Folder + diff --git a/app/ui/components/dropdowns/response-history-dropdown.js b/app/ui/components/dropdowns/response-history-dropdown.js index 151bca62e6..e2f719399f 100644 --- a/app/ui/components/dropdowns/response-history-dropdown.js +++ b/app/ui/components/dropdowns/response-history-dropdown.js @@ -7,9 +7,16 @@ import StatusTag from '../tags/status-tag'; import TimeTag from '../tags/time-tag'; import PromptButton from '../base/prompt-button'; import {trackEvent} from '../../../analytics/index'; +import KeydownBinder from '../keydown-binder'; +import * as hotkeys from '../../../common/hotkeys'; +import {executeHotKey} from '../../../common/hotkeys'; @autobind class ResponseHistoryDropdown extends PureComponent { + _setDropdownRef (n) { + this._dropdown = n; + } + _handleDeleteResponses () { trackEvent('History', 'Delete Responses'); this.props.handleDeleteResponses(this.props.requestId); @@ -25,6 +32,12 @@ class ResponseHistoryDropdown extends PureComponent { this.props.handleSetActiveResponse(response); } + _handleKeydown (e) { + executeHotKey(e, hotkeys.TOGGLE_HISTORY_DROPDOWN, () => { + this._dropdown && this._dropdown.toggle(true); + }); + } + componentWillUnmount () { clearTimeout(this._interval); } @@ -68,28 +81,31 @@ class ResponseHistoryDropdown extends PureComponent { const isLatestResponseActive = !responses.length || activeResponse._id === responses[0]._id; return ( - - - {isLatestResponseActive - ? - : } - - Response History - - - Delete Current Response - - - - Clear History - - Past Responses - {responses.map(this.renderDropdownItem)} - + + + + {isLatestResponseActive + ? + : } + + Response History + + + Delete Current Response + + + + Clear History + + Past Responses + {responses.map(this.renderDropdownItem)} + + ); } } diff --git a/app/ui/components/dropdowns/workspace-dropdown.js b/app/ui/components/dropdowns/workspace-dropdown.js index 86fc68db97..fac4b77b72 100644 --- a/app/ui/components/dropdowns/workspace-dropdown.js +++ b/app/ui/components/dropdowns/workspace-dropdown.js @@ -18,6 +18,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 {executeHotKey} from '../../../common/hotkeys'; +import * as hotkeys from '../../../common/hotkeys'; +import KeydownBinder from '../keydown-binder'; @autobind class WorkspaceDropdown extends PureComponent { @@ -44,6 +47,10 @@ class WorkspaceDropdown extends PureComponent { } } + _setDropdownRef (n) { + this._dropdown = n; + } + _handleShowLogin () { showModal(LoginModal); } @@ -90,6 +97,12 @@ class WorkspaceDropdown extends PureComponent { }); } + _handleKeydown (e) { + executeHotKey(e, hotkeys.TOGGLE_MAIN_MENU, () => { + this._dropdown && this._dropdown.toggle(true); + }); + } + render () { const { className, @@ -112,83 +125,86 @@ class WorkspaceDropdown extends PureComponent { ); return ( - - -

-
- {isLoading ? : null} - {unseenWorkspaces.length > 0 && ( - - - - )} - -
- {activeWorkspace.name} -

-
- {activeWorkspace.name} - - Workspace Settings - - + + + +

+
+ {isLoading ? : null} + {unseenWorkspaces.length > 0 && ( + + + + )} + +
+ {activeWorkspace.name} +

+
+ {activeWorkspace.name} + + Workspace Settings + + - - Share {activeWorkspace.name} - + + Share {activeWorkspace.name} + - Switch Workspace + Switch Workspace - {nonActiveWorkspaces.map(w => { - const isUnseen = !!unseenWorkspaces.find(v => v._id === w._id); - return ( - - To {w.name} - {isUnseen && ( - - - - )} + {nonActiveWorkspaces.map(w => { + const isUnseen = !!unseenWorkspaces.find(v => v._id === w._id); + return ( + + To {w.name} + {isUnseen && ( + + + + )} + + ); + })} + + + New Workspace + + + Insomnia Version {getAppVersion()} + + + Preferences + + + + Import/Export + + + {/* Not Logged In */} + + {!this.state.loggedIn && ( + + Log In - ); - })} + )} - - New Workspace - - - Insomnia Version {getAppVersion()} - - - Preferences - - - - Import/Export - - - {/* Not Logged In */} - - {!this.state.loggedIn && ( - - Log In - - )} - - {!this.state.loggedIn && ( - - Upgrade to Plus - - - )} -
+ {!this.state.loggedIn && ( + + Upgrade to Plus + + + )} +
+ ); } } diff --git a/app/ui/components/editors/auth/o-auth-2-auth.js b/app/ui/components/editors/auth/o-auth-2-auth.js index 961093d017..9e9ba3a19f 100644 --- a/app/ui/components/editors/auth/o-auth-2-auth.js +++ b/app/ui/components/editors/auth/o-auth-2-auth.js @@ -269,7 +269,8 @@ class OAuth2Auth extends React.PureComponent { 'redirectUrl', this._handleChangeRedirectUrl, 'This can be whatever you want or need it to be. Insomnia will automatically ' + - 'detect the redirect from the browser window and extract the credentials.' + 'detect a redirect in the client browser window and extract the code from the ' + + 'redirected URL' ); const state = this.renderInputRow( diff --git a/app/ui/components/hotkey.js b/app/ui/components/hotkey.js index 91496c4b7a..5a83eb4b12 100644 --- a/app/ui/components/hotkey.js +++ b/app/ui/components/hotkey.js @@ -1,34 +1,40 @@ -import React, {PureComponent} from 'react'; -import PropTypes from 'prop-types'; +// @flow +import React from 'react'; +import classnames from 'classnames'; +import type {Hotkey as HotkeyType} from '../../common/hotkeys'; import {ALT_SYM, CTRL_SYM, isMac, joinHotKeys, MOD_SYM, SHIFT_SYM} from '../../common/constants'; +import * as hotkeys from '../../common/hotkeys'; + +type Props = { + hotkey: HotkeyType, + + // Optional + className?: string +}; + +class Hotkey extends React.PureComponent { + props: Props; -class Hotkey extends PureComponent { render () { - const {char, shift, alt, ctrl, className} = this.props; + const {hotkey, className} = this.props; + const {alt, shift, meta} = hotkey; const chars = [ ]; alt && chars.push(ALT_SYM); shift && chars.push(SHIFT_SYM); - ctrl && chars.push(CTRL_SYM); - !ctrl && chars.push(MOD_SYM); - chars.push(char); + + if (meta) { + chars.push(isMac() ? MOD_SYM : CTRL_SYM); + } + + chars.push(hotkeys.getChar(hotkey)); return ( - + {joinHotKeys(chars)} ); } } -Hotkey.propTypes = { - char: PropTypes.string.isRequired, - - // Optional - alt: PropTypes.bool, - shift: PropTypes.bool, - ctrl: PropTypes.bool, - className: PropTypes.string -}; - export default Hotkey; diff --git a/app/ui/components/keydown-binder.js b/app/ui/components/keydown-binder.js new file mode 100644 index 0000000000..a2c4012b49 --- /dev/null +++ b/app/ui/components/keydown-binder.js @@ -0,0 +1,57 @@ +// @flow +import React from 'react'; +import ReactDOM from 'react-dom'; +import autobind from 'autobind-decorator'; +import {isMac} from '../../common/constants'; + +type Props = { + onKeydown: Function, + children: React.Children, + disabled?: boolean, + scoped?: boolean, + stopMetaPropagation?: boolean +}; + +@autobind +class KeydownBinder extends React.Component { + props: Props; + + _handleKeydown (e: KeyboardEvent) { + const {stopMetaPropagation, onKeydown, disabled} = this.props; + + if (disabled) { + return; + } + + const isMeta = isMac() ? e.metaKey : e.ctrlKey; + if (stopMetaPropagation && isMeta) { + e.stopPropagation(); + } + + onKeydown(e); + } + + componentDidMount () { + if (this.props.scoped) { + const el = ReactDOM.findDOMNode(this); + el && el.addEventListener('keydown', this._handleKeydown); + } else { + document.body && document.body.addEventListener('keydown', this._handleKeydown); + } + } + + componentWillUnmount () { + if (this.props.scoped) { + const el = ReactDOM.findDOMNode(this); + el && el.removeEventListener('keydown', this._handleKeydown); + } else { + document.body && document.body.removeEventListener('keydown', this._handleKeydown); + } + } + + render () { + return this.props.children; + } +} + +export default KeydownBinder; diff --git a/app/ui/components/modals/workspace-share-settings-modal.js b/app/ui/components/modals/workspace-share-settings-modal.js index 510a7bcb9a..c48b44ea1a 100644 --- a/app/ui/components/modals/workspace-share-settings-modal.js +++ b/app/ui/components/modals/workspace-share-settings-modal.js @@ -103,8 +103,10 @@ class WorkspaceShareSettingsModal extends PureComponent { async show () { this._resetState(); - await this._load(); this.modal.show(); + + // This takes a while, so do it after show() + await this._load(); } hide () { diff --git a/app/ui/components/request-pane.js b/app/ui/components/request-pane.js index 69a90f569c..1186e11435 100644 --- a/app/ui/components/request-pane.js +++ b/app/ui/components/request-pane.js @@ -24,6 +24,7 @@ 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'; @autobind class RequestPane extends React.PureComponent { @@ -199,19 +200,19 @@ class RequestPane extends React.PureComponent { New Request - + Switch Requests - + Edit Environments - + diff --git a/app/ui/components/request-url-bar.js b/app/ui/components/request-url-bar.js index b8b8f50a8d..f193abe05e 100644 --- a/app/ui/components/request-url-bar.js +++ b/app/ui/components/request-url-bar.js @@ -9,6 +9,9 @@ import {showPrompt} from './modals/index'; import MethodDropdown from './dropdowns/method-dropdown'; import PromptButton from './base/prompt-button'; import OneLineEditor from './codemirror/one-line-editor'; +import {executeHotKey} from '../../common/hotkeys'; +import * as hotkeys from '../../common/hotkeys'; +import KeydownBinder from './keydown-binder'; @autobind class RequestUrlBar extends PureComponent { @@ -28,6 +31,10 @@ class RequestUrlBar extends PureComponent { this._dropdown = n; } + _setMethodDropdownRef (n) { + this._methodDropdown = n; + } + _setInputRef (n) { this._input = n; } @@ -109,13 +116,25 @@ class RequestUrlBar extends PureComponent { return; } - // meta+l - const metaPressed = isMac() ? e.metaKey : e.ctrlKey; - if (metaPressed && e.keyCode === 76) { - e.preventDefault(); + executeHotKey(e, hotkeys.FOCUS_URL, () => { + if (!this._input) { + return; + } + this._input.focus(); this._input.selectAll(); - } + }); + + executeHotKey(e, hotkeys.TOGGLE_METHOD_DROPDOWN, () => { + if (!this._methodDropdown) { + return; + } + this._methodDropdown.toggle(); + }); + + executeHotKey(e, hotkeys.SHOW_SEND_OPTIONS, () => { + this._dropdown.toggle(true); + }); } _handleSend () { @@ -201,14 +220,6 @@ class RequestUrlBar extends PureComponent { this._handleFormSubmit(e); } - componentDidMount () { - document.body.addEventListener('keydown', this._handleKeyDown); - } - - componentWillUnmount () { - document.body.removeEventListener('keydown', this._handleKeyDown); - } - componentWillReceiveProps (nextProps) { if (nextProps.requestId !== this.props.requestId) { this._handleResetTimeouts(); @@ -252,7 +263,7 @@ class RequestUrlBar extends PureComponent { Basic Send Now - + Generate Client Code @@ -296,26 +307,30 @@ class RequestUrlBar extends PureComponent { } = this.props; return ( -
- - {method} - -
- - {this.renderSendButton()} - -
+ +
+ + {method} + +
+ + {this.renderSendButton()} + +
+
); } } diff --git a/app/ui/components/response-pane.js b/app/ui/components/response-pane.js index 3fca5ef5d5..a275b99b28 100644 --- a/app/ui/components/response-pane.js +++ b/app/ui/components/response-pane.js @@ -25,6 +25,7 @@ import {getSetCookieHeaders, nullFn} from '../../common/misc'; import {cancelCurrentRequest} from '../../network/network'; import {trackEvent} from '../../analytics'; import Hotkey from './hotkey'; +import * as hotkeys from '../../common/hotkeys'; @autobind class ResponsePane extends PureComponent { @@ -186,25 +187,25 @@ class ResponsePane extends PureComponent { Send Request - + Focus Url Bar - + Manage Cookies - + Edit Environments - + diff --git a/app/ui/components/settings/shortcuts.js b/app/ui/components/settings/shortcuts.js index 427cb18ee8..3c8827947a 100644 --- a/app/ui/components/settings/shortcuts.js +++ b/app/ui/components/settings/shortcuts.js @@ -1,13 +1,16 @@ import React, {PureComponent} from 'react'; +import autobind from 'autobind-decorator'; import Hotkey from '../hotkey'; +import * as hotkeys from '../../../common/hotkeys'; +@autobind class Shortcuts extends PureComponent { - renderHotkey (name, char, shift, alt, ctrl) { + renderHotkey (hotkey, i) { return ( - - {name} + + {hotkey.description} - + ); @@ -18,19 +21,24 @@ class Shortcuts extends PureComponent {
- {this.renderHotkey('Switch Requests', 'P')} - {this.renderHotkey('Send Request', 'Enter')} - {this.renderHotkey('New Request', 'N')} - {this.renderHotkey('Duplicate Request', 'D')} - {this.renderHotkey('Show Cookie Manager', 'K')} - {this.renderHotkey('Show Environment Editor', 'E')} - {this.renderHotkey('Focus URL Bar', 'L')} - {this.renderHotkey('Toggle Sidebar', '\\')} - {this.renderHotkey('Show Autocomplete Dropdown', 'Space', false, false, true)} - {this.renderHotkey('Show App Preferences', ',')} - {this.renderHotkey('Show Workspace Settings', ',', true)} - {this.renderHotkey('Show Request Settings', ',', true, true)} - {this.renderHotkey('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.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.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)}
diff --git a/app/ui/components/sidebar/sidebar-filter.js b/app/ui/components/sidebar/sidebar-filter.js index 28188a381a..6816a611e0 100644 --- a/app/ui/components/sidebar/sidebar-filter.js +++ b/app/ui/components/sidebar/sidebar-filter.js @@ -4,9 +4,16 @@ import autobind from 'autobind-decorator'; import {Dropdown, DropdownHint, DropdownButton, DropdownItem} from '../base/dropdown'; import {DEBOUNCE_MILLIS} from '../../../common/constants'; import {trackEvent} from '../../../analytics/index'; +import KeydownBinder from '../keydown-binder'; +import {executeHotKey} from '../../../common/hotkeys'; +import * as hotkeys from '../../../common/hotkeys'; @autobind class SidebarFilter extends PureComponent { + _setInputRef (n) { + this._input = n; + } + _handleOnChange (e) { const value = e.target.value; @@ -32,31 +39,40 @@ class SidebarFilter extends PureComponent { trackEvent('Request', 'Create', 'Sidebar Filter'); } + _handleKeydown (e) { + executeHotKey(e, hotkeys.FOCUS_FILTER, () => { + this._input && this._input.focus(); + }); + } + render () { return ( -
-
- + +
+
+ +
+ + + + + + New Request + + + + New Folder + + +
- - - - - - New Request - - - - New Folder - - -
- + ); } } diff --git a/app/ui/components/sidebar/sidebar-request-row.js b/app/ui/components/sidebar/sidebar-request-row.js index 89576a3f1f..c2e14b7cd1 100644 --- a/app/ui/components/sidebar/sidebar-request-row.js +++ b/app/ui/components/sidebar/sidebar-request-row.js @@ -95,7 +95,7 @@ class SidebarRequestRow extends PureComponent { if (!request) { node = (
  • -
    +
    diff --git a/app/ui/containers/app.js b/app/ui/containers/app.js index 0b4892eb22..ec3e8561a1 100644 --- a/app/ui/containers/app.js +++ b/app/ui/containers/app.js @@ -16,7 +16,7 @@ import CookiesModal from '../components/modals/cookies-modal'; import RequestSwitcherModal from '../components/modals/request-switcher-modal'; import ChangelogModal from '../components/modals/changelog-modal'; import SettingsModal, {TAB_INDEX_SHORTCUTS} from '../components/modals/settings-modal'; -import {COLLAPSE_SIDEBAR_REMS, DEFAULT_PANE_HEIGHT, DEFAULT_PANE_WIDTH, DEFAULT_SIDEBAR_WIDTH, getAppVersion, isMac, MAX_PANE_HEIGHT, MAX_PANE_WIDTH, MAX_SIDEBAR_REMS, MIN_PANE_HEIGHT, MIN_PANE_WIDTH, MIN_SIDEBAR_REMS, PREVIEW_MODE_SOURCE} from '../../common/constants'; +import {COLLAPSE_SIDEBAR_REMS, DEFAULT_PANE_HEIGHT, DEFAULT_PANE_WIDTH, DEFAULT_SIDEBAR_WIDTH, getAppVersion, MAX_PANE_HEIGHT, MAX_PANE_WIDTH, MAX_SIDEBAR_REMS, MIN_PANE_HEIGHT, MIN_PANE_WIDTH, MIN_SIDEBAR_REMS, PREVIEW_MODE_SOURCE} from '../../common/constants'; import * as globalActions from '../redux/modules/global'; import * as db from '../../common/database'; import * as models from '../../models'; @@ -35,17 +35,9 @@ import * as render from '../../common/render'; import {getKeys} from '../../templating/utils'; import {showAlert, showPrompt} from '../components/modals/index'; import {exportHar} from '../../common/har'; - -const KEY_ENTER = 13; -const KEY_COMMA = 188; -const KEY_D = 68; -const KEY_E = 69; -const KEY_K = 75; -const KEY_L = 76; -const KEY_N = 78; -const KEY_P = 80; -const KEY_R = 82; -const KEY_F5 = 116; +import * as hotkeys from '../../common/hotkeys'; +import KeydownBinder from '../components/keydown-binder'; +import {executeHotKey} from '../../common/hotkeys'; @autobind class App extends PureComponent { @@ -66,106 +58,52 @@ class App extends PureComponent { this._savePaneWidth = debounce(paneWidth => this._updateActiveWorkspaceMeta({paneWidth})); this._savePaneHeight = debounce(paneHeight => this._updateActiveWorkspaceMeta({paneHeight})); - this._saveSidebarWidth = debounce(sidebarWidth => this._updateActiveWorkspaceMeta({sidebarWidth})); + this._saveSidebarWidth = debounce( + sidebarWidth => this._updateActiveWorkspaceMeta({sidebarWidth})); this._globalKeyMap = null; } _setGlobalKeyMap () { this._globalKeyMap = [ - { // Show Workspace Settings - meta: true, - shift: true, - alt: false, - key: KEY_COMMA, - callback: () => { - const {activeWorkspace} = this.props; - showModal(WorkspaceSettingsModal, activeWorkspace); - trackEvent('HotKey', 'Workspace Settings'); + [hotkeys.SHOW_WORKSPACE_SETTINGS, () => { + const {activeWorkspace} = this.props; + showModal(WorkspaceSettingsModal, activeWorkspace); + }], + [hotkeys.SHOW_REQUEST_SETTINGS, () => { + if (this.props.activeRequest) { + showModal(RequestSettingsModal, {request: this.props.activeRequest}); } - }, { - meta: true, - shift: true, - alt: true, - key: KEY_COMMA, - callback: () => { - if (this.props.activeRequest) { - showModal(RequestSettingsModal, {request: this.props.activeRequest}); - trackEvent('HotKey', 'Request Settings'); - } - } - }, { - meta: true, - shift: false, - alt: false, - key: KEY_P, - callback: () => { - showModal(RequestSwitcherModal); - trackEvent('HotKey', 'Quick Switcher'); - } - }, { - meta: true, - shift: false, - alt: false, - key: [KEY_ENTER, KEY_R], - callback: this._handleSendShortcut - }, { - meta: false, - shift: false, - alt: false, - key: [KEY_F5], - callback: this._handleSendShortcut - }, { - meta: true, - shift: false, - alt: false, - key: KEY_E, - callback: () => { - const {activeWorkspace} = this.props; - showModal(WorkspaceEnvironmentsEditModal, activeWorkspace); - trackEvent('HotKey', 'Environments'); - } - }, { - meta: true, - shift: false, - alt: false, - key: KEY_L, - callback: () => { - const node = document.body.querySelector('.urlbar input'); - node && node.focus(); - trackEvent('HotKey', 'Url'); - } - }, { - meta: true, - shift: false, - alt: false, - key: KEY_K, - callback: () => { - const {activeWorkspace} = this.props; - showModal(CookiesModal, activeWorkspace); - trackEvent('HotKey', 'Cookies'); - } - }, { - meta: true, - shift: false, - alt: false, - key: KEY_N, - callback: () => { - const {activeRequest, activeWorkspace} = this.props; - const parentId = activeRequest ? activeRequest.parentId : activeWorkspace._id; - this._requestCreate(parentId); - trackEvent('HotKey', 'Request Create'); - } - }, { - meta: true, - shift: false, - alt: false, - key: KEY_D, - callback: async () => { - await this._requestDuplicate(this.props.activeRequest); - trackEvent('HotKey', 'Request Duplicate'); - } - } + }], + [hotkeys.SHOW_QUICK_SWITCHER, () => { + showModal(RequestSwitcherModal); + }], + [hotkeys.SEND_REQUEST, this._handleSendShortcut], + [hotkeys.SEND_REQUEST_F5, this._handleSendShortcut], + [hotkeys.SHOW_ENVIRONMENTS, () => { + const {activeWorkspace} = this.props; + showModal(WorkspaceEnvironmentsEditModal, activeWorkspace); + }], + [hotkeys.SHOW_COOKIES, () => { + const {activeWorkspace} = this.props; + showModal(CookiesModal, activeWorkspace); + }], + [hotkeys.CREATE_REQUEST, () => { + const {activeRequest, activeWorkspace} = this.props; + const parentId = activeRequest ? activeRequest.parentId : activeWorkspace._id; + this._requestCreate(parentId); + }], + [hotkeys.CREATE_FOLDER, () => { + const {activeRequest, activeWorkspace} = this.props; + const parentId = activeRequest ? activeRequest.parentId : activeWorkspace._id; + this._requestGroupCreate(parentId); + }], + [hotkeys.GENERATE_CODE, async () => { + showModal(GenerateCodeModal, this.props.activeRequest); + }], + [hotkeys.DUPLICATE_REQUEST, async () => { + await this._requestDuplicate(this.props.activeRequest); + }] ]; } @@ -175,7 +113,6 @@ class App extends PureComponent { activeRequest ? activeRequest._id : 'n/a', activeEnvironment ? activeEnvironment._id : 'n/a', ); - trackEvent('HotKey', 'Send'); } _setRequestPaneRef (n) { @@ -629,31 +566,8 @@ class App extends PureComponent { } _handleKeyDown (e) { - const isMetaPressed = isMac() ? e.metaKey : e.ctrlKey; - const isAltPressed = isMac() ? e.ctrlKey : e.altKey; - const isShiftPressed = e.shiftKey; - - for (const {meta, shift, alt, key, callback} of this._globalKeyMap) { - const keys = Array.isArray(key) ? key : [key]; - for (const key of keys) { - if ((alt && !isAltPressed) || (!alt && isAltPressed)) { - continue; - } - - if ((meta && !isMetaPressed) || (!meta && isMetaPressed)) { - continue; - } - - if ((shift && !isShiftPressed) || (!shift && isShiftPressed)) { - continue; - } - - if (key !== e.keyCode) { - continue; - } - - callback(); - } + for (const [definition, callback] of this._globalKeyMap) { + executeHotKey(e, definition, callback); } } @@ -708,7 +622,6 @@ class App extends PureComponent { // Bind mouse and key handlers document.addEventListener('mouseup', this._handleMouseUp); document.addEventListener('mousemove', this._handleMouseMove); - document.addEventListener('keydown', this._handleKeyDown); this._setGlobalKeyMap(); // Update title @@ -797,55 +710,56 @@ class App extends PureComponent { // Remove mouse and key handlers document.removeEventListener('mouseup', this._handleMouseUp); document.removeEventListener('mousemove', this._handleMouseMove); - document.removeEventListener('keydown', this._handleKeyDown); } render () { return ( -
    - - + +
    + + - {/* Block all mouse activity by showing an overlay while dragging */} - {this.state.showDragOverlay ?
    : null} -
    + {/* Block all mouse activity by showing an overlay while dragging */} + {this.state.showDragOverlay ?
    : null} +
    + ); } } diff --git a/app/ui/css/components/forms.less b/app/ui/css/components/forms.less index dd922ac099..ec2328ca9d 100644 --- a/app/ui/css/components/forms.less +++ b/app/ui/css/components/forms.less @@ -45,7 +45,8 @@ label { font-weight: 600; display: block; - padding-top: @padding-xs; + margin-top: @padding-xs; + padding-top: 0; * { font-weight: normal; @@ -279,3 +280,24 @@ input[type="color"] { height: @line-height-xs !important; padding: @padding-xxs !important; } + +label { + // So we can highlight from CSS checkbox selector + position: relative; + z-index: 1; + + input[type="checkbox"]:focus, + input[type="radio"]:focus { + &::before { + z-index: -1; + content: ' '; + position: absolute; + top: -@padding-xxs; + bottom: -@padding-xxs; + left: -@padding-xxs; + right: -@padding-xxs; + border-radius: @radius-sm; + background-color: var(--hl-sm); + } + } +} diff --git a/app/ui/css/components/sidebar.less b/app/ui/css/components/sidebar.less index 407751deb4..380477afa1 100644 --- a/app/ui/css/components/sidebar.less +++ b/app/ui/css/components/sidebar.less @@ -242,18 +242,19 @@ } } - // Active sidebar request - &.sidebar__item--request > button { - border-left: @padding-xs solid transparent; + & > button { + border-left: @padding-xxs solid transparent; } - &.sidebar__item--active.sidebar__item--request > button, - &.sidebar__item--active.sidebar__item--request > .sidebar__actions, - &:hover > button, - & > button:focus, - &:hover .sidebar__actions { - transition: background-color 130ms ease-out; + &.sidebar__item--active.sidebar__item--request, + &:focus, + &:hover { background-color: @hl-xs; + transition: background-color 0ms; + } + + & > button:focus { + border-left-color: var(--color-surprise); } .tag { @@ -287,31 +288,31 @@ // Padding for nested folders .sidebar__list .sidebar__clickable { - padding-left: @padding-md; + padding-left: @padding-sm; } .sidebar__list .sidebar__list .sidebar__clickable { - padding-left: @padding-md * 2; + padding-left: @padding-sm + @padding-md * 1; } .sidebar__list .sidebar__list .sidebar__list .sidebar__clickable { - padding-left: @padding-md * 3; + padding-left: @padding-sm + @padding-md * 2; } .sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__clickable { - padding-left: @padding-md * 4; + padding-left: @padding-sm + @padding-md * 3; } .sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__clickable { - padding-left: @padding-md * 5; + padding-left: @padding-sm + @padding-md * 4; } .sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__clickable { - padding-left: @padding-md * 6; + padding-left: @padding-sm + @padding-md * 5; } .sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__clickable { - padding-left: @padding-md * 7; + padding-left: @padding-sm + @padding-md * 6; } // ~~~~~~~~~~~~~~~ // @@ -332,6 +333,7 @@ } & > button:hover, + & .dropdown > button:focus, & > .dropdown:hover > button { color: var(--color-font); }