Add keyboard shortcuts

issue6104
review32881002
This commit is contained in:
Cédric Krier
2017-08-30 18:27:52 +02:00
parent f240d86595
commit d027e9ee81
7 changed files with 199 additions and 14 deletions
+1
View File
@@ -1,3 +1,4 @@
* Add keyboard shortcuts
* Update states of both toolbar and menu
* Improve toolbar order
* Use login details in title
+2 -1
View File
@@ -23,7 +23,8 @@
"d3": "^3.5",
"c3": "^0.4",
"papaparse": "^4.1",
"fullcalendar": "^3.0"
"fullcalendar": "^3.0",
"mousetrap": "^1.6"
},
"devDependencies": {
"qunit": "^1.18"
+1
View File
@@ -20,6 +20,7 @@ this repository contains the full copyright notices and license terms. -->
<script type="text/javascript" src="bower_components/papaparse/papaparse.min.js"></script>
<script type="text/javascript" src="bower_components/fullcalendar/dist/fullcalendar.min.js"></script>
<script type="text/javascript" src="bower_components/fullcalendar/dist/locale-all.js"></script>
<script type="text/javascript" src="bower_components/mousetrap/mousetrap.min.js"></script>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="bower_components/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css">
+162 -1
View File
@@ -529,7 +529,8 @@ var Sao = {};
'class': 'global-search-container'
});
this.search_entry = jQuery('<input>', {
'class': 'form-control',
'id': 'global-search-entry',
'class': 'form-control mousetrap',
'placeholder': Sao.i18n.gettext('Search...')
});
this.el.append(this.search_entry);
@@ -591,6 +592,166 @@ var Sao = {};
}
});
function shortcuts_defs() {
// Shortcuts available on Tab on this format:
// {shortcut, label, id of tab button or callback method}
return [
{
shortcut: 'ctrl+a',
label: Sao.i18n.gettext('New'),
id: 'new_',
}, {
shortcut: 'ctrl+s',
label: Sao.i18n.gettext('Save'),
id: 'save',
}, {
shortcut: 'ctrl+l',
label: Sao.i18n.gettext('Switch'),
id: 'switch_',
}, {
shortcut: 'ctrl+r',
label: Sao.i18n.gettext('Reload/Undo'),
id: 'reload',
}, {
shortcut: 'ctrl+shift+d',
label: Sao.i18n.gettext('Duplicate'),
id: 'copy',
}, {
shortcut: 'ctrl+d',
label: Sao.i18n.gettext('Delete'),
id: 'delete_',
}, {
shortcut: 'ctrl+up',
label: Sao.i18n.gettext('Previous'),
id: 'previous',
}, {
shortcut: 'ctrl+down',
label: Sao.i18n.gettext('Next'),
id: 'next',
}, {
shortcut: 'ctrl+f',
label: Sao.i18n.gettext('Search'),
id: 'search',
}, {
shortcut: 'ctrl+x',
label: Sao.i18n.gettext('Close Tab'),
id: 'close',
}, {
shortcut: 'ctrl+shift+t',
label: Sao.i18n.gettext('Attachment'),
id: 'attach',
}, {
shortcut: 'ctrl+shift+o',
label: Sao.i18n.gettext('Note'),
id: 'note',
}, {
shortcut: 'ctrl+e',
label: Sao.i18n.gettext('Action'),
id: 'action',
}, {
shortcut: 'ctrl+shift+r',
label: Sao.i18n.gettext('Relate'),
id: 'relate',
}, {
shortcut: 'ctrl+p',
label: Sao.i18n.gettext('Print'),
id: 'print',
}, {
shortcut: 'ctrl+left',
label: Sao.i18n.gettext('Previous tab'),
callback: function() {
Sao.Tab.previous_tab();
},
}, {
shortcut: 'ctrl+right',
label: Sao.i18n.gettext('Next tab'),
callback: function() {
Sao.Tab.next_tab();
},
}, {
shortcut: 'ctrl+k',
label: Sao.i18n.gettext('Global search'),
callback: function() {
jQuery('#global-search-entry').focus();
},
}, {
shortcut: 'ctrl+h',
label: Sao.i18n.gettext('Show this help'),
callback: function() {
shortcuts_dialog();
},
},
];
}
jQuery(document).ready(function() {
set_shortcuts();
});
function set_shortcuts() {
shortcuts_defs().forEach(function(definition) {
Mousetrap.bind(definition.shortcut, function() {
if (definition.id){
var current_tab = Sao.Tab.tabs.get_current();
if (current_tab) {
var focused = $(':focus');
focused.blur();
current_tab.el.find('a[id="' + definition.id + '"]').click();
focused.focus();
}
} else if (definition.callback) {
jQuery.when().then(definition.callback);
}
return false;
});
});
}
function shortcuts_dialog() {
var dialog = new Sao.Dialog(Sao.i18n.gettext('Keyboard shortcuts'),
'shortcut-dialog', 'm');
jQuery('<button>', {
'class': 'close',
'data-dismiss': 'modal',
'aria-label': Sao.i18n.gettext("Close"),
}).append(jQuery('<span>', {
'aria-hidden': true,
}).append('&times;')).prependTo(dialog.header);
var row = jQuery('<div/>', {
'class': 'row'
}).appendTo(dialog.body);
var global_shortcuts_dl = jQuery('<dl/>', {
'class': 'dl-horizontal col-md-6'
}).append(jQuery('<h5/>')
.append(Sao.i18n.gettext('Global shortcuts')))
.appendTo(row);
var tab_shortcuts_dl = jQuery('<dl/>', {
'class': 'dl-horizontal col-md-6'
}).append(jQuery('<h5/>')
.append(Sao.i18n.gettext('Tab shortcuts')))
.appendTo(row);
shortcuts_defs().forEach(function(definition) {
var dt = jQuery('<dt/>').append(definition.label);
var dd = jQuery('<dd/>').append(jQuery('<kbd>')
.append(definition.shortcut));
var dest_dl;
if (definition.id) {
dest_dl = tab_shortcuts_dl;
} else {
dest_dl = global_shortcuts_dl;
}
dt.appendTo(dest_dl);
dd.appendTo(dest_dl);
});
dialog.modal.on('hidden.bs.modal', function() {
jQuery(this).remove();
});
dialog.modal.modal('show');
return false;
}
// Fix stacked modal
jQuery(document)
.on('show.bs.modal', '.modal', function(event) {
+1 -1
View File
@@ -33,7 +33,7 @@
}).append(Sao.i18n.gettext('Filters'));
this.filter_button.click(this.search_box.bind(this));
this.search_entry = jQuery('<input/>', {
'class': 'form-control',
'class': 'form-control mousetrap',
'placeholder': Sao.i18n.gettext('Search')
});
this.search_list = jQuery('<datalist/>');
+21
View File
@@ -128,6 +128,7 @@
'role': 'presentation'
});
var link = jQuery('<a/>', {
'id': item.id,
'role': 'menuitem',
'href': '#',
'tabindex': -1
@@ -389,6 +390,26 @@
tabs.trigger('ready');
};
Sao.Tab.previous_tab = function() {
Sao.Tab.move('prevAll');
};
Sao.Tab.next_tab = function() {
Sao.Tab.move('nextAll');
};
Sao.Tab.move = function(direction) {
var current_tab = this.tabs.get_current();
var tabs = jQuery('#tabs');
var tablist = jQuery('#tablist');
var tab = tablist.find('#nav-' + current_tab.id);
var next = tab[direction]('li').first();
if (next) {
next.find('a').tab('show');
tabs.trigger('ready');
}
};
Sao.Tab.Form = Sao.class_(Sao.Tab, {
class_: 'tab-form',
init: function(model_name, attributes) {
+11 -11
View File
@@ -1202,7 +1202,7 @@ function eval_pyson(value){
}).appendTo(this.el);
this.input = this.labelled = jQuery('<input/>', {
'type': 'text',
'class': 'form-control input-sm'
'class': 'form-control input-sm mousetrap'
}).appendTo(this.group);
if (!jQuery.isEmptyObject(attributes.autocomplete)) {
this.datalist = jQuery('<datalist/>').appendTo(this.el);
@@ -1336,7 +1336,7 @@ function eval_pyson(value){
}).appendTo(this.el);
this.input = jQuery('<input/>', {
'type': 'text',
'class': 'form-control input-sm'
'class': 'form-control input-sm mousetrap'
}).appendTo(this.date);
jQuery('<span/>', {
'class': 'input-group-btn'
@@ -1428,7 +1428,7 @@ function eval_pyson(value){
});
this.input = this.labelled = jQuery('<input/>', {
'type': 'text',
'class': 'form-control input-sm'
'class': 'form-control input-sm mousetrap'
}).appendTo(this.el);
this.el.change(this.focus_out.bind(this));
},
@@ -1487,7 +1487,7 @@ function eval_pyson(value){
'class': this.class_
});
this.select = this.labelled = jQuery('<select/>', {
'class': 'form-control input-sm'
'class': 'form-control input-sm mousetrap'
});
this.el.append(this.select);
this.select.change(this.focus_out.bind(this));
@@ -1578,7 +1578,7 @@ function eval_pyson(value){
});
this.input = this.labelled = jQuery('<input/>', {
'type': 'checkbox',
'class': 'form-control input-sm'
'class': 'form-control input-sm mousetrap'
}).appendTo(this.el);
this.input.change(this.focus_out.bind(this));
this.input.click(function() {
@@ -1617,7 +1617,7 @@ function eval_pyson(value){
'class': this.class_
});
this.input = this.labelled = jQuery('<textarea/>', {
'class': 'form-control input-sm'
'class': 'form-control input-sm mousetrap'
}).appendTo(this.el);
this.input.change(this.focus_out.bind(this));
if (this.attributes.translate) {
@@ -1673,7 +1673,7 @@ function eval_pyson(value){
this.get_toolbar().appendTo(this.el);
}
this.input = this.labelled = jQuery('<div/>', {
'class': 'richtext',
'class': 'richtext mousetrap',
'contenteditable': true
}).appendTo(jQuery('<div/>', {
'class': 'panel-body'
@@ -1855,7 +1855,7 @@ function eval_pyson(value){
}).appendTo(this.el);
this.entry = this.labelled = jQuery('<input/>', {
'type': 'input',
'class': 'form-control input-sm'
'class': 'form-control input-sm mousetrap'
}).appendTo(group);
// Use keydown to not receive focus-in TAB
this.entry.on('keydown', this.key_press.bind(this));
@@ -2924,7 +2924,7 @@ function eval_pyson(value){
}).appendTo(toolbar);
this.entry = jQuery('<input/>', {
type: 'text',
'class': 'form-control input-sm'
'class': 'form-control input-sm mousetrap'
}).appendTo(group);
// Use keydown to not receive focus-in TAB
this.entry.on('keydown', this.key_press.bind(this));
@@ -3912,7 +3912,7 @@ function eval_pyson(value){
}).appendTo(this.el);
this.input = this.labelled = jQuery('<input/>', {
'type': 'text',
'class': 'form-control input-sm'
'class': 'form-control input-sm mousetrap'
}).appendTo(group);
this.button = jQuery('<button/>', {
'class': 'btn btn-default',
@@ -3960,7 +3960,7 @@ function eval_pyson(value){
create_widget: function() {
Sao.View.Form.Dict.Selection._super.create_widget.call(this);
var select = jQuery('<select/>', {
'class': 'form-control input-sm'
'class': 'form-control input-sm mousetrap'
});
select.change(
this.parent_widget.focus_out.bind(this.parent_widget));