Commit 496208c9 authored by Jacob Schatz's avatar Jacob Schatz

Merge branch 'menu-shortcut' into 'master'

Add keyboard shortcuts to main menu.

Closes #30036

See merge request !10243
parents 8a574030 518203d5
...@@ -24,7 +24,6 @@ ...@@ -24,7 +24,6 @@
/* global Search */ /* global Search */
/* global Admin */ /* global Admin */
/* global NamespaceSelects */ /* global NamespaceSelects */
/* global ShortcutsDashboardNavigation */
/* global Project */ /* global Project */
/* global ProjectAvatar */ /* global ProjectAvatar */
/* global CompareAutocomplete */ /* global CompareAutocomplete */
...@@ -378,7 +377,6 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -378,7 +377,6 @@ const ShortcutsBlob = require('./shortcuts_blob');
break; break;
case 'dashboard': case 'dashboard':
case 'root': case 'root':
shortcut_handler = new ShortcutsDashboardNavigation();
new UserCallout(); new UserCallout();
break; break;
case 'groups': case 'groups':
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */
/* global Mousetrap */ /* global Mousetrap */
/* global findFileURL */ /* global findFileURL */
import findAndFollowLink from './shortcuts_dashboard_navigation';
(function() { (function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
...@@ -14,11 +15,33 @@ ...@@ -14,11 +15,33 @@
} }
Mousetrap.bind('?', this.onToggleHelp); Mousetrap.bind('?', this.onToggleHelp);
Mousetrap.bind('s', Shortcuts.focusSearch); Mousetrap.bind('s', Shortcuts.focusSearch);
Mousetrap.bind('f', (function(_this) { Mousetrap.bind('f', (e => this.focusFilter(e)));
return function(e) {
return _this.focusFilter(e); const $globalDropdownMenu = $('.global-dropdown-menu');
}; const $globalDropdownToggle = $('.global-dropdown-toggle');
})(this));
$('.global-dropdown').on('hide.bs.dropdown', () => {
$globalDropdownMenu.removeClass('shortcuts');
});
Mousetrap.bind('n', () => {
$globalDropdownMenu.toggleClass('shortcuts');
$globalDropdownToggle.trigger('click');
if (!$globalDropdownMenu.is(':visible')) {
$globalDropdownToggle.blur();
}
});
Mousetrap.bind('shift+t', () => findAndFollowLink('.shortcuts-todos'));
Mousetrap.bind('shift+a', () => findAndFollowLink('.dashboard-shortcuts-activity'));
Mousetrap.bind('shift+i', () => findAndFollowLink('.dashboard-shortcuts-issues'));
Mousetrap.bind('shift+m', () => findAndFollowLink('.dashboard-shortcuts-merge_requests'));
Mousetrap.bind('shift+p', () => findAndFollowLink('.dashboard-shortcuts-projects'));
Mousetrap.bind('shift+g', () => findAndFollowLink('.dashboard-shortcuts-groups'));
Mousetrap.bind('shift+l', () => findAndFollowLink('.dashboard-shortcuts-milestones'));
Mousetrap.bind('shift+s', () => findAndFollowLink('.dashboard-shortcuts-snippets'));
Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], this.toggleMarkdownPreview); Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], this.toggleMarkdownPreview);
if (typeof findFileURL !== "undefined" && findFileURL !== null) { if (typeof findFileURL !== "undefined" && findFileURL !== null) {
Mousetrap.bind('t', function() { Mousetrap.bind('t', function() {
......
/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign */ /**
/* global Mousetrap */ * Helper function that finds the href of the fiven selector and updates the location.
/* global Shortcuts */ *
* @param {String} selector
*/
export default (selector) => {
const link = document.querySelector(selector).getAttribute('href');
require('./shortcuts');
(function() {
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
this.ShortcutsDashboardNavigation = (function(superClass) {
extend(ShortcutsDashboardNavigation, superClass);
function ShortcutsDashboardNavigation() {
ShortcutsDashboardNavigation.__super__.constructor.call(this);
Mousetrap.bind('g a', function() {
return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-activity');
});
Mousetrap.bind('g i', function() {
return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-issues');
});
Mousetrap.bind('g m', function() {
return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-merge_requests');
});
Mousetrap.bind('g t', function() {
return ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-todos');
});
Mousetrap.bind('g p', function() {
return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-projects');
});
}
ShortcutsDashboardNavigation.findAndFollowLink = function(selector) {
var link;
link = $(selector).attr('href');
if (link) { if (link) {
return window.location = link; window.location = link;
} }
}; };
return ShortcutsDashboardNavigation;
})(Shortcuts);
}).call(window);
/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign */ /* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign */
/* global Mousetrap */ /* global Mousetrap */
/* global Shortcuts */ /* global Shortcuts */
import findAndFollowLink from './shortcuts_dashboard_navigation';
require('./shortcuts'); require('./shortcuts');
...@@ -13,59 +14,23 @@ require('./shortcuts'); ...@@ -13,59 +14,23 @@ require('./shortcuts');
function ShortcutsNavigation() { function ShortcutsNavigation() {
ShortcutsNavigation.__super__.constructor.call(this); ShortcutsNavigation.__super__.constructor.call(this);
Mousetrap.bind('g p', function() { Mousetrap.bind('g p', () => findAndFollowLink('.shortcuts-project'));
return ShortcutsNavigation.findAndFollowLink('.shortcuts-project'); Mousetrap.bind('g e', () => findAndFollowLink('.shortcuts-project-activity'));
}); Mousetrap.bind('g f', () => findAndFollowLink('.shortcuts-tree'));
Mousetrap.bind('g e', function() { Mousetrap.bind('g c', () => findAndFollowLink('.shortcuts-commits'));
return ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity'); Mousetrap.bind('g j', () => findAndFollowLink('.shortcuts-builds'));
}); Mousetrap.bind('g n', () => findAndFollowLink('.shortcuts-network'));
Mousetrap.bind('g f', function() { Mousetrap.bind('g d', () => findAndFollowLink('.shortcuts-repository-charts'));
return ShortcutsNavigation.findAndFollowLink('.shortcuts-tree'); Mousetrap.bind('g i', () => findAndFollowLink('.shortcuts-issues'));
}); Mousetrap.bind('g b', () => findAndFollowLink('.shortcuts-issue-boards'));
Mousetrap.bind('g c', function() { Mousetrap.bind('g m', () => findAndFollowLink('.shortcuts-merge_requests'));
return ShortcutsNavigation.findAndFollowLink('.shortcuts-commits'); Mousetrap.bind('g t', () => findAndFollowLink('.shortcuts-todos'));
}); Mousetrap.bind('g w', () => findAndFollowLink('.shortcuts-wiki'));
Mousetrap.bind('g b', function() { Mousetrap.bind('g s', () => findAndFollowLink('.shortcuts-snippets'));
return ShortcutsNavigation.findAndFollowLink('.shortcuts-builds'); Mousetrap.bind('i', () => findAndFollowLink('.shortcuts-new-issue'));
});
Mousetrap.bind('g n', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-network');
});
Mousetrap.bind('g g', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-repository-charts');
});
Mousetrap.bind('g i', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues');
});
Mousetrap.bind('g l', function() {
ShortcutsNavigation.findAndFollowLink('.shortcuts-issue-boards');
});
Mousetrap.bind('g m', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests');
});
Mousetrap.bind('g t', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-todos');
});
Mousetrap.bind('g w', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki');
});
Mousetrap.bind('g s', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-snippets');
});
Mousetrap.bind('i', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-new-issue');
});
this.enabledHelp.push('.hidden-shortcut.project'); this.enabledHelp.push('.hidden-shortcut.project');
} }
ShortcutsNavigation.findAndFollowLink = function(selector) {
var link;
link = $(selector).attr('href');
if (link) {
return window.location = link;
}
};
return ShortcutsNavigation; return ShortcutsNavigation;
})(Shortcuts); })(Shortcuts);
}).call(window); }).call(window);
...@@ -187,6 +187,15 @@ ...@@ -187,6 +187,15 @@
} }
} }
.shortcut-mappings {
display: none;
}
&.shortcuts .shortcut-mappings {
display: inline-block;
margin-right: 5px;
}
ul { ul {
margin: 0; margin: 0;
padding: 0; padding: 0;
......
...@@ -15,6 +15,10 @@ ...@@ -15,6 +15,10 @@
%tr %tr
%th %th
%th Global Shortcuts %th Global Shortcuts
%tr
%td.shortcut
.key n
%td Main Navigation
%tr %tr
%td.shortcut %td.shortcut
.key s .key s
...@@ -39,24 +43,46 @@ ...@@ -39,24 +43,46 @@
.key .key
%i.fa.fa-arrow-up %i.fa.fa-arrow-up
%td Edit last comment (when focused on an empty textarea) %td Edit last comment (when focused on an empty textarea)
%tbody
%tr %tr
%th %td.shortcut
%th Project Files browsing .key shift t
%td
Go to todos
%tr %tr
%td.shortcut %td.shortcut
.key .key shift a
%i.fa.fa-arrow-up %td
%td Move selection up Go to the activity feed
%tr %tr
%td.shortcut %td.shortcut
.key .key shift p
%i.fa.fa-arrow-down %td
%td Move selection down Go to projects
%tr %tr
%td.shortcut %td.shortcut
.key enter .key shift i
%td Open Selection %td
Go to issues
%tr
%td.shortcut
.key shift m
%td
Go to merge requests
%tr
%td.shortcut
.key shift g
%td
Go to groups
%tr
%td.shortcut
.key shift l
%td
Go to milestones
%tr
%td.shortcut
.key shift s
%td
Go to snippets
%tbody %tbody
%tr %tr
%th %th
...@@ -79,51 +105,8 @@ ...@@ -79,51 +105,8 @@
%td.shortcut %td.shortcut
.key esc .key esc
%td Go back %td Go back
%tbody
%tr
%th
%th Project File
%tr
%td.shortcut
.key y
%td Go to file permalink
.col-lg-4 .col-lg-4
%table.shortcut-mappings %table.shortcut-mappings
%tbody.hidden-shortcut.project{ style: 'display:none' }
%tr
%th
%th Global Dashboard
%tr
%td.shortcut
.key g
.key a
%td
Go to the activity feed
%tr
%td.shortcut
.key g
.key p
%td
Go to projects
%tr
%td.shortcut
.key g
.key i
%td
Go to issues
%tr
%td.shortcut
.key g
.key m
%td
Go to merge requests
%tr
%td.shortcut
.key g
.key t
%td
Go to todos
%tbody %tbody
%tr %tr
%th %th
...@@ -155,7 +138,7 @@ ...@@ -155,7 +138,7 @@
%tr %tr
%td.shortcut %td.shortcut
.key g .key g
.key b .key j
%td %td
Go to jobs Go to jobs
%tr %tr
...@@ -167,7 +150,7 @@ ...@@ -167,7 +150,7 @@
%tr %tr
%td.shortcut %td.shortcut
.key g .key g
.key g .key d
%td %td
Go to repository charts Go to repository charts
%tr %tr
...@@ -179,7 +162,7 @@ ...@@ -179,7 +162,7 @@
%tr %tr
%td.shortcut %td.shortcut
.key g .key g
.key l .key b
%td %td
Go to issue boards Go to issue boards
%tr %tr
...@@ -194,6 +177,12 @@ ...@@ -194,6 +177,12 @@
.key s .key s
%td %td
Go to snippets Go to snippets
%tr
%td.shortcut
.key g
.key w
%td
Go to wiki
%tr %tr
%td.shortcut %td.shortcut
.key t .key t
...@@ -202,6 +191,33 @@ ...@@ -202,6 +191,33 @@
%td.shortcut %td.shortcut
.key i .key i
%td New issue %td New issue
%tbody
%tr
%th
%th Project Files browsing
%tr
%td.shortcut
.key
%i.fa.fa-arrow-up
%td Move selection up
%tr
%td.shortcut
.key
%i.fa.fa-arrow-down
%td Move selection down
%tr
%td.shortcut
.key enter
%td Open Selection
%tbody
%tr
%th
%th Project File
%tr
%td.shortcut
.key y
%td Go to file permalink
.col-lg-4 .col-lg-4
%table.shortcut-mappings %table.shortcut-mappings
%tbody.hidden-shortcut.network{ style: 'display:none' } %tbody.hidden-shortcut.network{ style: 'display:none' }
......
%ul %ul
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
= link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
P
%span %span
Projects Projects
= nav_link(path: 'dashboard#activity') do = nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
A
%span %span
Activity Activity
- if koding_enabled? - if koding_enabled?
...@@ -13,25 +21,45 @@ ...@@ -13,25 +21,45 @@
%span %span
Koding Koding
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to dashboard_groups_path, title: 'Groups' do = link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups', title: 'Groups' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
G
%span %span
Groups Groups
= nav_link(controller: 'dashboard/milestones') do = nav_link(controller: 'dashboard/milestones') do
= link_to dashboard_milestones_path, title: 'Milestones' do = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', title: 'Milestones' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
L
%span %span
Milestones Milestones
= nav_link(path: 'dashboard#issues') do = nav_link(path: 'dashboard#issues') do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
I
%span %span
Issues Issues
.badge= number_with_delimiter(cached_assigned_issuables_count(current_user, :issues, :opened)) .badge= number_with_delimiter(cached_assigned_issuables_count(current_user, :issues, :opened))
= nav_link(path: 'dashboard#merge_requests') do = nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
M
%span %span
Merge Requests Merge Requests
.badge= number_with_delimiter(cached_assigned_issuables_count(current_user, :merge_requests, :opened)) .badge= number_with_delimiter(cached_assigned_issuables_count(current_user, :merge_requests, :opened))
= nav_link(controller: 'dashboard/snippets') do = nav_link(controller: 'dashboard/snippets') do
= link_to dashboard_snippets_path, title: 'Snippets' do = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: 'Snippets' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
S
%span %span
Snippets Snippets
%li.divider %li.divider
......
---
title: Add keyboard shortcuts to main menu
merge_request:
author:
...@@ -26,7 +26,7 @@ Feature: Project Shortcuts ...@@ -26,7 +26,7 @@ Feature: Project Shortcuts
@javascript @javascript
Scenario: Navigate to repository charts tab Scenario: Navigate to repository charts tab
Given I press "g" and "g" Given I press "g" and "d"
Then the active sub tab should be Charts Then the active sub tab should be Charts
And the active main tab should be Repository And the active main tab should be Repository
......
...@@ -20,9 +20,9 @@ class Spinach::Features::ProjectShortcuts < Spinach::FeatureSteps ...@@ -20,9 +20,9 @@ class Spinach::Features::ProjectShortcuts < Spinach::FeatureSteps
find('body').native.send_key('n') find('body').native.send_key('n')
end end
step 'I press "g" and "g"' do step 'I press "g" and "d"' do
find('body').native.send_key('g')
find('body').native.send_key('g') find('body').native.send_key('g')
find('body').native.send_key('d')
end end
step 'I press "g" and "s"' do step 'I press "g" and "s"' do
......
...@@ -14,7 +14,7 @@ describe 'Issue Boards shortcut', feature: true, js: true do ...@@ -14,7 +14,7 @@ describe 'Issue Boards shortcut', feature: true, js: true do
end end
it 'takes user to issue board index' do it 'takes user to issue board index' do
find('body').native.send_keys('gl') find('body').native.send_keys('gb')
expect(page).to have_selector('.boards-list') expect(page).to have_selector('.boards-list')
wait_for_vue_resource wait_for_vue_resource
......
...@@ -3,27 +3,23 @@ require 'spec_helper' ...@@ -3,27 +3,23 @@ require 'spec_helper'
feature 'Dashboard shortcuts', feature: true, js: true do feature 'Dashboard shortcuts', feature: true, js: true do
before do before do
login_as :user login_as :user
visit dashboard_projects_path visit root_dashboard_path
end end
scenario 'Navigate to tabs' do scenario 'Navigate to tabs' do
find('body').native.send_key('g') find('body').native.send_keys([:shift, 'P'])
find('body').native.send_key('p')
check_page_title('Projects') check_page_title('Projects')
find('body').native.send_key('g') find('body').native.send_key([:shift, 'I'])
find('body').native.send_key('i')
check_page_title('Issues') check_page_title('Issues')
find('body').native.send_key('g') find('body').native.send_key([:shift, 'M'])
find('body').native.send_key('m')
check_page_title('Merge Requests') check_page_title('Merge Requests')
find('body').native.send_key('g') find('body').native.send_keys([:shift, 'T'])
find('body').native.send_key('t')
check_page_title('Todos') check_page_title('Todos')
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment