kanbanworkflowstimelinescrumrubyroadmapproject-planningproject-managementopenprojectangularissue-trackerifcgantt-chartganttbug-trackerboardsbcf
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.
175 lines
5.1 KiB
175 lines
5.1 KiB
//-- copyright
|
|
// OpenProject is a project management system.
|
|
// Copyright (C) 2012-2015 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-2013 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
|
|
// 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
|
|
};
|
|
|
|
// 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();
|
|
}
|
|
};
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
function focusItemOffset(offset) {
|
|
var list, index;
|
|
list = findListInPage();
|
|
if (list === null) { return; }
|
|
index = list.indexOf(
|
|
angular
|
|
.element(document.activeElement)
|
|
.parents(accessibleRowSelector)
|
|
.find(':visible:tabbable:not(input)')[0]
|
|
);
|
|
angular.element(list[(index+offset+list.length) % list.length]).focus();
|
|
}
|
|
|
|
function focusNextItem() {
|
|
focusItemOffset(1);
|
|
}
|
|
|
|
function focusPrevItem() {
|
|
focusItemOffset(-1);
|
|
}
|
|
|
|
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;
|
|
};
|
|
|