Refactor hotkeys to use keypress instead of keydown

keydown returns the "raw" key in event.which. Depending on the keyboard
layout, this may not be what is wanted. For example, on a German
keyboard, Shift+7 has to be pressed to get a slash. However, event.which
will be 55, which corresponds to "7". In the keypress event, however,
event.which will be 47, which corresponds to "/".

Sadly, several important keys (such as escape and the arrow keys) do not
trigger a keypress event. Therefore, they have to be handled using a
keydown event.

This change refactors the hotkey support to make use of keypress events
whenever possible. This will make hotkeys work regardless of the user's
keyboard layout. Escape and arrow keys are still handled via keydown
events.

There should be only one change in behavior: I could not make Ctrl+/
work and therefore rebound the help dialog to "?".
This commit is contained in:
Michael Kuhn 2019-03-11 11:29:10 +01:00
parent 355723ca59
commit e74f7bde22
7 changed files with 47 additions and 46 deletions

View File

@ -1222,40 +1222,36 @@
function get_hotkeys_map() { function get_hotkeys_map() {
$hotkeys = array( $hotkeys = array(
// "navigation" => array(
"k" => "next_feed", "k" => "next_feed",
"j" => "prev_feed", "j" => "prev_feed",
"n" => "next_article", "n" => "next_article",
"p" => "prev_article", "p" => "prev_article",
"(38)|up" => "prev_article", "(38)|Up" => "prev_article",
"(40)|down" => "next_article", "(40)|Down" => "next_article",
// "^(38)|Ctrl-up" => "prev_article_noscroll", "*(38)|Shift+Up" => "article_scroll_up",
// "^(40)|Ctrl-down" => "next_article_noscroll", "*(40)|Shift+Down" => "article_scroll_down",
"(191)|/" => "search_dialog", "^(38)|Ctrl+Up" => "prev_article_noscroll",
// "article" => array( "^(40)|Ctrl+Down" => "next_article_noscroll",
"/" => "search_dialog",
"s" => "toggle_mark", "s" => "toggle_mark",
"*s" => "toggle_publ", "S" => "toggle_publ",
"u" => "toggle_unread", "u" => "toggle_unread",
"*t" => "edit_tags", "T" => "edit_tags",
"o" => "open_in_new_window", "o" => "open_in_new_window",
"c p" => "catchup_below", "c p" => "catchup_below",
"c n" => "catchup_above", "c n" => "catchup_above",
"*n" => "article_scroll_down", "N" => "article_scroll_down",
"*p" => "article_scroll_up", "P" => "article_scroll_up",
"*(38)|Shift+up" => "article_scroll_up", "a W" => "toggle_widescreen",
"*(40)|Shift+down" => "article_scroll_down",
"a *w" => "toggle_widescreen",
"a e" => "toggle_embed_original", "a e" => "toggle_embed_original",
"e" => "email_article", "e" => "email_article",
"a q" => "close_article", "a q" => "close_article",
// "article_selection" => array(
"a a" => "select_all", "a a" => "select_all",
"a u" => "select_unread", "a u" => "select_unread",
"a *u" => "select_marked", "a U" => "select_marked",
"a p" => "select_published", "a p" => "select_published",
"a i" => "select_invert", "a i" => "select_invert",
"a n" => "select_none", "a n" => "select_none",
// "feed" => array(
"f r" => "feed_refresh", "f r" => "feed_refresh",
"f a" => "feed_unhide_read", "f a" => "feed_unhide_read",
"f s" => "feed_subscribe", "f s" => "feed_subscribe",
@ -1263,31 +1259,26 @@
"f q" => "feed_catchup", "f q" => "feed_catchup",
"f x" => "feed_reverse", "f x" => "feed_reverse",
"f g" => "feed_toggle_vgroup", "f g" => "feed_toggle_vgroup",
"f *d" => "feed_debug_update", "f D" => "feed_debug_update",
"f *g" => "feed_debug_viewfeed", "f G" => "feed_debug_viewfeed",
"f *c" => "toggle_combined_mode", "f C" => "toggle_combined_mode",
"f c" => "toggle_cdm_expanded", "f c" => "toggle_cdm_expanded",
"*q" => "catchup_all", "Q" => "catchup_all",
"x" => "cat_toggle_collapse", "x" => "cat_toggle_collapse",
// "goto" => array(
"g a" => "goto_all", "g a" => "goto_all",
"g f" => "goto_fresh", "g f" => "goto_fresh",
"g s" => "goto_marked", "g s" => "goto_marked",
"g p" => "goto_published", "g p" => "goto_published",
"g t" => "goto_tagcloud", "g t" => "goto_tagcloud",
"g *p" => "goto_prefs", "g P" => "goto_prefs",
// "other" => array(
"r" => "select_article_cursor", "r" => "select_article_cursor",
"c l" => "create_label", "c l" => "create_label",
"c f" => "create_filter", "c f" => "create_filter",
"c s" => "collapse_sidebar", "c s" => "collapse_sidebar",
"a *n" => "toggle_night_mode", "a N" => "toggle_night_mode",
"^(191)|Ctrl+/" => "help_dialog", "?" => "help_dialog",
); );
$hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
$hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_MAP) as $plugin) { foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_MAP) as $plugin) {
$hotkeys = $plugin->hook_hotkey_map($hotkeys); $hotkeys = $plugin->hook_hotkey_map($hotkeys);
} }

View File

@ -60,14 +60,12 @@ define(["dojo/_base/declare"], function (declare) {
const hotkeys_map = App.getInitParam("hotkeys"); const hotkeys_map = App.getInitParam("hotkeys");
const keycode = event.which; const keycode = event.which;
const keychar = String.fromCharCode(keycode).toLowerCase(); const keychar = String.fromCharCode(keycode);
if (keycode == 27) { // escape and drop prefix if (keycode == 27) { // escape and drop prefix
this.hotkey_prefix = false; this.hotkey_prefix = false;
} }
if (keycode == 16 || keycode == 17) return; // ignore lone shift / ctrl
if (!this.hotkey_prefix && hotkeys_map[0].indexOf(keychar) != -1) { if (!this.hotkey_prefix && hotkeys_map[0].indexOf(keychar) != -1) {
this.hotkey_prefix = keychar; this.hotkey_prefix = keychar;
@ -87,13 +85,19 @@ define(["dojo/_base/declare"], function (declare) {
Element.hide("cmdline"); Element.hide("cmdline");
let hotkey_name = keychar.search(/[a-zA-Z0-9]/) != -1 ? keychar : "(" + keycode + ")"; let hotkey_name = "";
// ensure ^*char notation if (event.type == "keydown") {
if (event.shiftKey) hotkey_name = "*" + hotkey_name; hotkey_name = "(" + keycode + ")";
if (event.ctrlKey) hotkey_name = "^" + hotkey_name;
if (event.altKey) hotkey_name = "+" + hotkey_name; // ensure ^*char notation
if (event.metaKey) hotkey_name = "%" + hotkey_name; if (event.shiftKey) hotkey_name = "*" + hotkey_name;
if (event.ctrlKey) hotkey_name = "^" + hotkey_name;
if (event.altKey) hotkey_name = "+" + hotkey_name;
if (event.metaKey) hotkey_name = "%" + hotkey_name;
} else {
hotkey_name = keychar ? keychar : "(" + keycode + ")";
}
const hotkey_full = this.hotkey_prefix ? this.hotkey_prefix + " " + hotkey_name : hotkey_name; const hotkey_full = this.hotkey_prefix ? this.hotkey_prefix + " " + hotkey_name : hotkey_name;
this.hotkey_prefix = false; this.hotkey_prefix = false;

View File

@ -196,6 +196,7 @@ define(["dojo/_base/declare"], function (declare) {
App.setLoadingProgress(50); App.setLoadingProgress(50);
document.onkeydown = (event) => { return App.hotkeyHandler(event) }; document.onkeydown = (event) => { return App.hotkeyHandler(event) };
document.onkeypress = (event) => { return App.hotkeyHandler(event) };
window.onresize = () => { Headlines.scrollHandler(); } window.onresize = () => { Headlines.scrollHandler(); }
if (!this.getActive()) { if (!this.getActive()) {

View File

@ -78,6 +78,7 @@ require(["dojo/_base/kernel",
this.enableCsrfSupport(); this.enableCsrfSupport();
document.onkeydown = (event) => { return App.hotkeyHandler(event) }; document.onkeydown = (event) => { return App.hotkeyHandler(event) };
document.onkeypress = (event) => { return App.hotkeyHandler(event) };
App.setLoadingProgress(50); App.setLoadingProgress(50);
Notify.close(); Notify.close();

View File

@ -206,6 +206,10 @@ require(["dojo/_base/kernel",
hotkeyHandler(event) { hotkeyHandler(event) {
if (event.target.nodeName == "INPUT" || event.target.nodeName == "TEXTAREA") return; if (event.target.nodeName == "INPUT" || event.target.nodeName == "TEXTAREA") return;
// Arrow buttons and escape are not reported via keypress, handle them via keydown.
// escape = 27, left = 37, up = 38, right = 39, down = 40
if (event.type == "keydown" && event.which != 27 && (event.which < 37 || event.which > 40)) return;
const action_name = App.keyeventToAction(event); const action_name = App.keyeventToAction(event);
if (action_name) { if (action_name) {

View File

@ -24,11 +24,11 @@ class GoogleReaderKeys extends Plugin {
$hotkeys["r"] = "feed_refresh"; $hotkeys["r"] = "feed_refresh";
$hotkeys["m"] = "toggle_unread"; $hotkeys["m"] = "toggle_unread";
$hotkeys["o"] = "toggle_expand"; $hotkeys["o"] = "toggle_expand";
$hotkeys["(13)|enter"] = "toggle_expand"; $hotkeys["\r|Enter"] = "toggle_expand";
$hotkeys["*(191)|?"] = "help_dialog"; $hotkeys["?"] = "help_dialog";
$hotkeys["(32)|space"] = "next_article"; $hotkeys[" |Space"] = "next_article";
$hotkeys["(38)|up"] = "article_scroll_up"; $hotkeys["(38)|Up"] = "article_scroll_up";
$hotkeys["(40)|down"] = "article_scroll_down"; $hotkeys["(40)|Down"] = "article_scroll_down";
return $hotkeys; return $hotkeys;
} }

View File

@ -16,8 +16,8 @@ class Hotkeys_Noscroll extends Plugin {
function hook_hotkey_map($hotkeys) { function hook_hotkey_map($hotkeys) {
$hotkeys["(40)|down"] = "next_article_noscroll"; $hotkeys["(40)|Down"] = "next_article_noscroll";
$hotkeys["(38)|up"] = "prev_article_noscroll"; $hotkeys["(38)|Up"] = "prev_article_noscroll";
$hotkeys["n"] = "next_article_noscroll"; $hotkeys["n"] = "next_article_noscroll";
$hotkeys["p"] = "prev_article_noscroll"; $hotkeys["p"] = "prev_article_noscroll";