diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js index 66cb9fd7672d32ea519fc482e91889d84da5acb3..85636f3e5d28d3ae4690e1235537a1655bcb4c90 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js @@ -1,6 +1,9 @@ import $ from 'jquery'; import Cookies from 'js-cookie'; import Mousetrap from 'mousetrap'; +import Vue from 'vue'; +import { disableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle'; +import ShortcutsToggle from './shortcuts_toggle.vue'; import axios from '../../lib/utils/axios_utils'; import { refreshCurrentPage, visitUrl } from '../../lib/utils/url_utility'; import findAndFollowLink from '../../lib/utils/navigation_utility'; @@ -15,6 +18,15 @@ Mousetrap.stopCallback = (e, element, combo) => { return defaultStopCallback(e, element, combo); }; +function initToggleButton() { + return new Vue({ + el: document.querySelector('.js-toggle-shortcuts'), + render(createElement) { + return createElement(ShortcutsToggle); + }, + }); +} + export default class Shortcuts { constructor() { this.onToggleHelp = this.onToggleHelp.bind(this); @@ -48,6 +60,14 @@ export default class Shortcuts { $(this).remove(); e.preventDefault(); }); + + $('.js-shortcuts-modal-trigger') + .off('click') + .on('click', this.onToggleHelp); + + if (shouldDisableShortcuts()) { + disableShortcuts(); + } } onToggleHelp(e) { @@ -104,7 +124,8 @@ export default class Shortcuts { } return $('.js-more-help-button').remove(); - }); + }) + .then(initToggleButton); } focusFilter(e) { diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.js new file mode 100644 index 0000000000000000000000000000000000000000..66aa1b752aebd808b3f2fdcb7c1922fc044b8279 --- /dev/null +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.js @@ -0,0 +1,22 @@ +import Mousetrap from 'mousetrap'; +import 'mousetrap/plugins/pause/mousetrap-pause'; + +const shorcutsDisabledKey = 'shortcutsDisabled'; + +export const shouldDisableShortcuts = () => { + try { + return localStorage.getItem(shorcutsDisabledKey) === 'true'; + } catch (e) { + return false; + } +}; + +export function enableShortcuts() { + localStorage.setItem(shorcutsDisabledKey, false); + Mousetrap.unpause(); +} + +export function disableShortcuts() { + localStorage.setItem(shorcutsDisabledKey, true); + Mousetrap.pause(); +} diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue b/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue new file mode 100644 index 0000000000000000000000000000000000000000..a53b1b06be94175f718c7a02442898be775eb206 --- /dev/null +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue @@ -0,0 +1,60 @@ + + + + + + + + + {{ content }} + + + + + + + {{ content }} + + + + + {{ __('Enable or disable keyboard shortcuts') }} + + diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 5f8f2333e40d00c03e5db6ea08d0e357bb87fe48..4b9304cfdb9a7c66a4d0900447a265a4721fe122 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -6,6 +6,7 @@ = _('Keyboard Shortcuts') %small = link_to _('(Show all)'), '#', class: 'js-more-help-button' + .js-toggle-shortcuts %button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') } %span{ "aria-hidden": true } × .modal-body diff --git a/app/views/layouts/header/_help_dropdown.html.haml b/app/views/layouts/header/_help_dropdown.html.haml index 93854c212df0ae68cf6d775d8bb8838ad137a218..a003d6f8903e2ec28459817586a029eed31757d0 100644 --- a/app/views/layouts/header/_help_dropdown.html.haml +++ b/app/views/layouts/header/_help_dropdown.html.haml @@ -4,6 +4,10 @@ = link_to _("Help"), help_path %li = link_to _("Support"), support_url + %li + %button.js-shortcuts-modal-trigger{ type: "button" } + = _("Keyboard shortcuts") + %span.text-secondary.float-right{ "aria-hidden": true }= '?'.html_safe = render_if_exists "shared/learn_gitlab_menu_item" %li.divider %li diff --git a/changelogs/unreleased/22113-improve-keyboard-shortcuts-or-allow-them-to-be-disabled.yml b/changelogs/unreleased/22113-improve-keyboard-shortcuts-or-allow-them-to-be-disabled.yml new file mode 100644 index 0000000000000000000000000000000000000000..45957b4acb2c51483ca7d9897d5538f2c21a7299 --- /dev/null +++ b/changelogs/unreleased/22113-improve-keyboard-shortcuts-or-allow-them-to-be-disabled.yml @@ -0,0 +1,5 @@ +--- +title: Allow keyboard shortcuts to be disabled +merge_request: 18782 +author: +type: added diff --git a/doc/user/shortcuts.md b/doc/user/shortcuts.md index 54e7938d8f7c07bf9a3cebcadb8f2e273fc08005..0d7220fbd428b1235093c452c056c32239c1b5bf 100644 --- a/doc/user/shortcuts.md +++ b/doc/user/shortcuts.md @@ -6,7 +6,10 @@ disqus_identifier: 'https://docs.gitlab.com/ee/workflow/shortcuts.html' # GitLab keyboard shortcuts GitLab has many useful keyboard shortcuts to make it easier to access different features. -You can see the quick reference sheet within GitLab itself with Shift + ?. +You can see a modal listing keyboard shortcuts within GitLab itself by pressing ?, +or clicking **Keyboard shortcuts** in the Help menu at the top right. +From [GitLab 12.8 onwards](https://gitlab.com/gitlab-org/gitlab/issues/22113), +keyboard shortcuts can be disabled using the **Enable**/**Disable** toggle in this modal window. The [Global Shortcuts](#global-shortcuts) work from any area of GitLab, but you must be in specific pages for the other shortcuts to be available, as explained in each diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 9edcf28e93e02a8036624d38c4f80f30aaca8c76..67e4cf7ddc1fb43d221f013a7203ac8533faeefb 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -375,6 +375,12 @@ msgid_plural "%{releases} releases" msgstr[0] "" msgstr[1] "" +msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled" +msgstr "" + +msgid "%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled" +msgstr "" + msgid "%{service_title} activated." msgstr "" @@ -7137,6 +7143,9 @@ msgstr "" msgid "Enable mirror configuration" msgstr "" +msgid "Enable or disable keyboard shortcuts" +msgstr "" + msgid "Enable or disable the Pseudonymizer data collection." msgstr "" @@ -10909,6 +10918,9 @@ msgstr "" msgid "Keyboard Shortcuts" msgstr "" +msgid "Keyboard shortcuts" +msgstr "" + msgid "Kubernetes" msgstr "" diff --git a/spec/features/projects/user_uses_shortcuts_spec.rb b/spec/features/projects/user_uses_shortcuts_spec.rb index c6efe1f1896ef4b6797e4e0e3531994e2819ef17..beed1c07e51d2c18f424add00fee2e8e79135766 100644 --- a/spec/features/projects/user_uses_shortcuts_spec.rb +++ b/spec/features/projects/user_uses_shortcuts_spec.rb @@ -17,6 +17,59 @@ wait_for_requests end + context 'disabling shortcuts' do + before do + page.evaluate_script("localStorage.removeItem('shortcutsDisabled')") + end + + it 'can disable shortcuts from help menu' do + open_modal_shortcut_keys + click_toggle_button + close_modal + + open_modal_shortcut_keys + + # modal-shortcuts still in the DOM, but hidden + expect(find('#modal-shortcuts', visible: false)).not_to be_visible + + page.refresh + open_modal_shortcut_keys + + # after reload, shortcuts modal doesn't exist at all until we add it + expect(page).not_to have_selector('#modal-shortcuts') + end + + it 're-enables shortcuts' do + open_modal_shortcut_keys + click_toggle_button + close_modal + + open_modal_from_help_menu + click_toggle_button + close_modal + + open_modal_shortcut_keys + expect(find('#modal-shortcuts')).to be_visible + end + + def open_modal_shortcut_keys + find('body').native.send_key('?') + end + + def open_modal_from_help_menu + find('.header-help-dropdown-toggle').click + find('button', text: 'Keyboard shortcuts').click + end + + def click_toggle_button + find('.js-toggle-shortcuts .gl-toggle').click + end + + def close_modal + find('.modal button[aria-label="Close"]').click + end + end + context 'when navigating to the Project pages' do it 'redirects to the details page' do visit project_issues_path(project)