OpenProject is the leading open source project management software.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openproject/frontend/app/services/keyboard-shortcut-service.js

176 lines
5.1 KiB

//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
module.exports = function($window, $rootScope, $timeout, PathHelper) {
// modalHelperInstance
// Mousetrap
10 years ago
// TODO: globals, should be wrapped in .constant
var accessKeys = {
preview: 1,
newWorkPackage: 2,
edit: 3,
quickSearch: 4,
projectSearch: 5,
help: 6,
moreMenu: 7,
details: 8
10 years ago
};
10 years ago
// maybe move it to a .constant
var shortcuts = {
'?': showHelpModal,
'up up down down left right left right b a enter': showHelpModal,
'g m': 'myPagePath',
'g o': projectScoped('projectPath'),
'g w p': projectScoped('projectWorkPackagesPath'),
'g w i': projectScoped('projectWikiPath'),
'g a': projectScoped('activityFromPath'),
'g c': projectScoped('projectCalendarPath'),
'g n': projectScoped('projectNewsPath'),
'g t': projectScoped('projectTimelinesPath'),
'n w p': projectScoped('projectWorkPackageNewPath'),
'g e': accessKey('edit'),
'g p': accessKey('preview'),
'd w p': accessKey('details'),
'm': accessKey('moreMenu'),
'p': accessKey('projectSearch'),
's': accessKey('quickSearch'),
'k': focusPrevItem,
'j': focusNextItem
};
function accessKey(keyName) {
var key = accessKeys[keyName];
return function() {
var elem = angular.element('[accesskey=' + key + ']:first');
if (elem.is('input')) {
// timeout with delay so that the key is not
// triggered on the input
$timeout(function() {
elem.focus();
});
} else if(elem.is('[href]')) {
clickLink(elem[0]);
} else {
elem.click();
}
10 years ago
};
}
function projectScoped(action) {
return function() {
var projectIdentifier = $rootScope.projectIdentifier;
if (projectIdentifier) {
var url = PathHelper[action](projectIdentifier);
$window.location.href = url;
}
};
}
function clickLink(link) {
var cancelled = false;
if (document.createEvent) {
var event = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true
});
cancelled = !link.dispatchEvent(event);
}
else if (link.fireEvent) {
cancelled = !link.fireEvent('onclick');
}
if (!cancelled) {
window.location = link.href;
}
}
function showHelpModal() {
modalHelperInstance.createModal(PathHelper.keyboardShortcutsHelpPath());
}
10 years ago
// this could be extracted into a separate component if it grows
var accessibleListSelector = 'table.list, table.keyboard-accessible-list';
var accessibleRowSelector = 'table.list tr, table.keyboard-accessible-list tr';
function findListInPage() {
var domLists, focusElements;
focusElements = [];
domLists = angular.element(accessibleListSelector);
domLists.find('tbody tr').each(function(index, tr){
var firstLink = angular.element(tr).find(':visible:tabbable:not(.toggle-all, input)')[0];
if ( firstLink !== undefined ) { focusElements.push(firstLink); }
});
return focusElements;
10 years ago
}
function focusItemOffset(offset) {
var list, index;
list = findListInPage();
if (list === null) { return; }
10 years ago
index = list.indexOf(
angular
.element(document.activeElement)
.parents(accessibleRowSelector)
.find(':visible:tabbable:not(input)')[0]
10 years ago
);
angular.element(list[(index+offset+list.length) % list.length]).focus();
10 years ago
}
function focusNextItem() {
focusItemOffset(1);
10 years ago
}
function focusPrevItem() {
focusItemOffset(-1);
10 years ago
}
var KeyboardShortcutService = {
activate: function() {
_.forEach(shortcuts, function(action, key) {
if (_.isFunction(action)) {
Mousetrap.bind(key, action);
} else {
Mousetrap.bind(key, function() {
var url = PathHelper[action]();
$window.location.href = url;
});
}
});
}
};
return KeyboardShortcutService;
};