From 4a2a90c980bb7436150ed82556fdb6f4db3ff138 Mon Sep 17 00:00:00 2001 From: Michael Kuhn Date: Sun, 7 Apr 2019 12:21:52 +0200 Subject: [PATCH 1/2] Fix focus issues with hotkeys Since making use of keypress in addition to keydown, hotkeys did not work in certain scenarios, including clicking on the feed tree expanders or empty spaces of the toolbar. This issue is caused by dijit.Tree and dijit.Toolbar implementing the _KeyNavMixin, which explicitly stops propagation of keypress events. This change contains two main fixes plus a smaller hotfix: 1. It overrides _onContainerKeydown and _onContainerKeypress for fox.FeedTree (which inherits from dijit.Tree). 2. It adds fox.Toolbar, which overrides _onContainerKeydown, _onContainerKeypress and focus. This fixes hotkeys being swallowed and the first focusable child receiving focus when clicking on an empty space of the toolbar. 3. It adds the same handling of keydown and keypress to the prefs hotkey handler as is done in the main hotkey handler. --- classes/pref/feeds.php | 6 +++--- classes/pref/filters.php | 10 +++++----- classes/pref/labels.php | 2 +- classes/pref/prefs.php | 2 +- classes/pref/users.php | 2 +- index.php | 2 +- js/FeedTree.js | 5 ++++- js/Toolbar.js | 14 ++++++++++++++ js/prefs.js | 7 ++++++- js/tt-rss.js | 5 +++-- 10 files changed, 39 insertions(+), 16 deletions(-) create mode 100755 js/Toolbar.js diff --git a/classes/pref/feeds.php b/classes/pref/feeds.php index b90fd5848..bb854553d 100755 --- a/classes/pref/feeds.php +++ b/classes/pref/feeds.php @@ -1208,7 +1208,7 @@ class Pref_Feeds extends Handler_Protected { print '
'; - print "
"; #toolbar + print "
"; #toolbar print "
execute([$_SESSION['uid']]); - print "
"; + print "
"; print "
". "" . __('Select').""; print "
"; @@ -1506,7 +1506,7 @@ class Pref_Feeds extends Handler_Protected { FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?"); $sth->execute([$_SESSION['uid']]); - print "
"; + print "
"; print "
". "" . __('Select').""; print "
"; diff --git a/classes/pref/filters.php b/classes/pref/filters.php index 37a7236ee..9663bfdd0 100755 --- a/classes/pref/filters.php +++ b/classes/pref/filters.php @@ -354,7 +354,7 @@ class Pref_Filters extends Handler_Protected { print "
".__("Match")."
"; print "
"; - print "
"; + print "
"; print "
". "" . __('Select').""; @@ -414,7 +414,7 @@ class Pref_Filters extends Handler_Protected { print "
"; - print "
"; + print "
"; print "
". "" . __('Select').""; @@ -757,7 +757,7 @@ class Pref_Filters extends Handler_Protected { print "
"; print "
"; - print "
"; + print "
"; if (array_key_exists("search", $_REQUEST)) { $_SESSION["prefs_filter_search"] = $filter_search; @@ -856,7 +856,7 @@ class Pref_Filters extends Handler_Protected { print "
".__("Match")."
"; print "
"; - print "
"; + print "
"; print "
". "" . __('Select').""; @@ -885,7 +885,7 @@ class Pref_Filters extends Handler_Protected { print "
"; - print "
"; + print "
"; print "
". "" . __('Select').""; diff --git a/classes/pref/labels.php b/classes/pref/labels.php index 5dadddab4..90cb32214 100644 --- a/classes/pref/labels.php +++ b/classes/pref/labels.php @@ -251,7 +251,7 @@ class Pref_Labels extends Handler_Protected { print "
"; print "
"; - print "
"; + print "
"; print "
". "" . __('Select').""; diff --git a/classes/pref/prefs.php b/classes/pref/prefs.php index d75b47f87..fbc7f000a 100644 --- a/classes/pref/prefs.php +++ b/classes/pref/prefs.php @@ -1021,7 +1021,7 @@ class Pref_Prefs extends Handler_Protected { } function editPrefProfiles() { - print "
"; + print "
"; print "
". "" . __('Select').""; diff --git a/classes/pref/users.php b/classes/pref/users.php index 7b75bb872..12be1207f 100644 --- a/classes/pref/users.php +++ b/classes/pref/users.php @@ -313,7 +313,7 @@ class Pref_Users extends Handler_Protected { print "
"; print "
"; - print "
"; + print "
"; $user_search = trim(clean($_REQUEST["search"])); diff --git a/index.php b/index.php index c35b3d5c6..3a2029289 100644 --- a/index.php +++ b/index.php @@ -145,7 +145,7 @@
-
+
diff --git a/js/FeedTree.js b/js/FeedTree.js index 976848d28..99685a781 100755 --- a/js/FeedTree.js +++ b/js/FeedTree.js @@ -2,7 +2,10 @@ define(["dojo/_base/declare", "dojo/dom-construct", "dijit/Tree", "dijit/Menu"], function (declare, domConstruct) { return declare("fox.FeedTree", dijit.Tree, { - _onKeyPress: function(/* Event */ e) { + _onContainerKeydown: function(/* Event */ e) { + return; // Stop dijit.Tree from interpreting keystrokes + }, + _onContainerKeypress: function(/* Event */ e) { return; // Stop dijit.Tree from interpreting keystrokes }, _createTreeNode: function(args) { diff --git a/js/Toolbar.js b/js/Toolbar.js new file mode 100755 index 000000000..6d2c20058 --- /dev/null +++ b/js/Toolbar.js @@ -0,0 +1,14 @@ +/* global dijit */ +define(["dojo/_base/declare", "dijit/Toolbar"], function (declare) { + return declare("fox.Toolbar", dijit.Toolbar, { + _onContainerKeydown: function(/* Event */ e) { + return; // Stop dijit.Toolbar from interpreting keystrokes + }, + _onContainerKeypress: function(/* Event */ e) { + return; // Stop dijit.Toolbar from interpreting keystrokes + }, + focus: function() { + return; // Stop dijit.Toolbar from focusing the first child on click + }, + }); +}); diff --git a/js/prefs.js b/js/prefs.js index ae6286330..69b7899ec 100755 --- a/js/prefs.js +++ b/js/prefs.js @@ -53,7 +53,8 @@ require(["dojo/_base/kernel", "fox/PrefFilterStore", "fox/PrefFeedTree", "fox/PrefFilterTree", - "fox/PrefLabelTree"], function (dojo, declare, ready, parser, AppBase) { + "fox/PrefLabelTree", + "fox/Toolbar"], function (dojo, declare, ready, parser, AppBase) { ready(function () { try { @@ -118,6 +119,10 @@ require(["dojo/_base/kernel", hotkeyHandler: function (event) { 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); if (action_name) { diff --git a/js/tt-rss.js b/js/tt-rss.js index eaedecd22..5db742f07 100644 --- a/js/tt-rss.js +++ b/js/tt-rss.js @@ -54,7 +54,8 @@ require(["dojo/_base/kernel", "fox/Headlines", "fox/Article", "fox/FeedStoreModel", - "fox/FeedTree"], function (dojo, declare, ready, parser, AppBase) { + "fox/FeedTree", + "fox/Toolbar"], function (dojo, declare, ready, parser, AppBase) { ready(function () { try { @@ -203,7 +204,7 @@ require(["dojo/_base/kernel", isCombinedMode: function() { return App.getInitParam("combined_display_mode"); }, - hotkeyHandler(event) { + hotkeyHandler: function(event) { if (event.target.nodeName == "INPUT" || event.target.nodeName == "TEXTAREA") return; // Arrow buttons and escape are not reported via keypress, handle them via keydown. From e38fcd6deac9a63654fb0eb61fffa9ad747e4c50 Mon Sep 17 00:00:00 2001 From: Michael Kuhn Date: Sat, 13 Apr 2019 22:36:15 +0200 Subject: [PATCH 2/2] Fix button focus issues This change introduces derived classes for ComboButton, DropDownButton and Select that make sure that buttons do not remain focused after their menus are closed. This allows using hotkeys after closing them. --- classes/article.php | 2 +- classes/feeds.php | 8 ++++---- classes/pref/feeds.php | 28 ++++++++++++++-------------- classes/pref/filters.php | 18 +++++++++--------- classes/pref/labels.php | 2 +- classes/pref/prefs.php | 12 ++++++------ classes/pref/users.php | 6 +++--- index.php | 8 ++++---- js/form/ComboButton.js | 12 ++++++++++++ js/form/DropDownButton.js | 12 ++++++++++++ js/form/Select.js | 8 ++++++++ js/prefs.js | 5 ++++- js/tt-rss.js | 5 ++++- 13 files changed, 82 insertions(+), 44 deletions(-) create mode 100755 js/form/ComboButton.js create mode 100755 js/form/DropDownButton.js create mode 100755 js/form/Select.js diff --git a/classes/article.php b/classes/article.php index ff83143d6..defd1d9cd 100755 --- a/classes/article.php +++ b/classes/article.php @@ -516,7 +516,7 @@ class Article extends Handler_Protected { $rv .= "
"; } - $rv .= "
". + $rv .= "
". "" . __('Attachments').""; $rv .= "
"; diff --git a/classes/feeds.php b/classes/feeds.php index 292c9e0d4..41251a408 100755 --- a/classes/feeds.php +++ b/classes/feeds.php @@ -54,7 +54,7 @@ class Feeds extends Handler_Protected { $reply .= ""; $reply .= ""; $reply .= " "; - $reply .= " + dojoType="fox.form.Select" size="3"> @@ -734,7 +734,7 @@ class Feeds extends Handler_Protected { print "
"; print ""; print_select("search_language", get_pref('DEFAULT_SEARCH_LANGUAGE'), Pref_Feeds::get_ts_languages(), - "dojoType='dijit.form.Select' title=\"".__('Used for word stemming')."\""); + "dojoType='fox.form.Select' title=\"".__('Used for word stemming')."\""); print "
"; } diff --git a/classes/pref/feeds.php b/classes/pref/feeds.php index bb854553d..fbbbdb4f4 100755 --- a/classes/pref/feeds.php +++ b/classes/pref/feeds.php @@ -571,7 +571,7 @@ class Pref_Feeds extends Handler_Protected { print " "; print_feed_cat_select("cat_id", $cat_id, - 'dojoType="dijit.form.Select"'); + 'dojoType="fox.form.Select"'); print ""; } @@ -602,7 +602,7 @@ class Pref_Feeds extends Handler_Protected { print " "; print_select("feed_language", $feed_language, $this::get_ts_languages(), - 'dojoType="dijit.form.Select"'); + 'dojoType="fox.form.Select"'); print ""; } @@ -621,7 +621,7 @@ class Pref_Feeds extends Handler_Protected { print " "; print_select_hash("update_interval", $update_interval, $update_intervals, - 'dojoType="dijit.form.Select"'); + 'dojoType="fox.form.Select"'); print ""; @@ -634,7 +634,7 @@ class Pref_Feeds extends Handler_Protected { print " "; print_select_hash("purge_interval", $purge_interval, $purge_intervals, - 'dojoType="dijit.form.Select" ' . + 'dojoType="fox.form.Select" ' . ((FORCE_ARTICLE_PURGE == 0) ? "" : 'disabled="1"')); print ""; @@ -826,7 +826,7 @@ class Pref_Feeds extends Handler_Protected { print " "; print_feed_cat_select("cat_id", false, - 'disabled="1" dojoType="dijit.form.Select"'); + 'disabled="1" dojoType="fox.form.Select"'); $this->batch_edit_cbox("cat_id"); @@ -840,7 +840,7 @@ class Pref_Feeds extends Handler_Protected { print " "; print_select("feed_language", "", $this::get_ts_languages(), - 'disabled="1" dojoType="dijit.form.Select"'); + 'disabled="1" dojoType="fox.form.Select"'); $this->batch_edit_cbox("feed_language"); @@ -859,7 +859,7 @@ class Pref_Feeds extends Handler_Protected { print " "; print_select_hash("update_interval", "", $update_intervals, - 'disabled="1" dojoType="dijit.form.Select"'); + 'disabled="1" dojoType="fox.form.Select"'); $this->batch_edit_cbox("update_interval"); @@ -874,7 +874,7 @@ class Pref_Feeds extends Handler_Protected { print " "; print_select_hash("purge_interval", "", $purge_intervals, - 'disabled="1" dojoType="dijit.form.Select"'); + 'disabled="1" dojoType="fox.form.Select"'); $this->batch_edit_cbox("purge_interval"); @@ -1217,7 +1217,7 @@ class Pref_Feeds extends Handler_Protected { __('Search')."
"; - print "
". + print "
". "" . __('Select').""; print "
"; print "
".__('None')."
"; print "
"; - print "
". + print "
". "" . __('Feeds').""; print "
"; print "
"; if (get_pref('ENABLE_FEED_CATS')) { - print "
". + print "
". "" . __('Categories').""; print "
"; print "
execute([$_SESSION['uid']]); print "
"; - print "
". + print "
". "" . __('Select').""; print "
"; print "
execute([$_SESSION['uid']]); print "
"; - print "
". + print "
". "" . __('Select').""; print "
"; print "
"; print " "; - print_feed_cat_select("cat", false, 'dojoType="dijit.form.Select"'); + print_feed_cat_select("cat", false, 'dojoType="fox.form.Select"'); print ""; } diff --git a/classes/pref/filters.php b/classes/pref/filters.php index 9663bfdd0..041951b35 100755 --- a/classes/pref/filters.php +++ b/classes/pref/filters.php @@ -356,7 +356,7 @@ class Pref_Filters extends Handler_Protected { print "
"; - print "
". + print "
". "" . __('Select').""; print "
"; print "
"; - print "
". + print "
". "" . __('Select').""; print "
"; print "
"; - print "
". + print "
". "" . __('Select').""; print "
"; print "
"; - print "
". + print "
". "" . __('Select').""; print "
"; print "
"; - print "
". + print "
". "" . __('Select').""; print "
"; print "
"; print " "; print_select_hash("filter_type", $filter_type, $filter_types, - 'dojoType="dijit.form.Select"'); + 'dojoType="fox.form.Select"'); print " "; print ""; @@ -1042,7 +1042,7 @@ class Pref_Filters extends Handler_Protected { print "
"; - print ""; $issel = $value == "default.php" ? "selected='selected'" : ""; print ""; @@ -618,11 +618,11 @@ class Pref_Prefs extends Handler_Protected { global $update_intervals_nodefault; print_select_hash($pref_name, $value, $update_intervals_nodefault, - 'dojoType="dijit.form.Select"'); + 'dojoType="fox.form.Select"'); } else if ($pref_name == "DEFAULT_SEARCH_LANGUAGE") { print_select($pref_name, $value, Pref_Feeds::get_ts_languages(), - 'dojoType="dijit.form.Select"'); + 'dojoType="fox.form.Select"'); } else if ($type_name == "bool") { @@ -715,7 +715,7 @@ class Pref_Prefs extends Handler_Protected { print_hidden("op", "pref-prefs"); print_hidden("method", "saveconfig"); - print "
+ print "
".__('Save configuration')."
"; - print "
". + print "
". "" . __('Select').""; print "
"; print "
". + print "
". "" . __('Select').""; print "
"; print "
+ dojoType="fox.form.Select"> @@ -182,7 +182,7 @@ -
+
@@ -215,7 +215,7 @@ } ?> -
+
menu
diff --git a/js/form/ComboButton.js b/js/form/ComboButton.js new file mode 100755 index 000000000..1084cda9c --- /dev/null +++ b/js/form/ComboButton.js @@ -0,0 +1,12 @@ +/* global dijit */ +define(["dojo/_base/declare", "dijit/form/ComboButton"], function (declare) { + return declare("fox.form.ComboButton", dijit.form.ComboButton, { + startup: function() { + this.inherited(arguments); + this.dropDown.autoFocus = true; // Allow dropdown menu to be focused on click + }, + focus: function() { + return; // Stop dijit.form.ComboButton from keeping focus after closing the menu + }, + }); +}); diff --git a/js/form/DropDownButton.js b/js/form/DropDownButton.js new file mode 100755 index 000000000..0c182772a --- /dev/null +++ b/js/form/DropDownButton.js @@ -0,0 +1,12 @@ +/* global dijit */ +define(["dojo/_base/declare", "dijit/form/DropDownButton"], function (declare) { + return declare("fox.form.DropDownButton", dijit.form.DropDownButton, { + startup: function() { + this.inherited(arguments); + this.dropDown.autoFocus = true; // Allow dropdown menu to be focused on click + }, + focus: function() { + return; // Stop dijit.form.DropDownButton from keeping focus after closing the menu + }, + }); +}); diff --git a/js/form/Select.js b/js/form/Select.js new file mode 100755 index 000000000..c62db1821 --- /dev/null +++ b/js/form/Select.js @@ -0,0 +1,8 @@ +/* global dijit */ +define(["dojo/_base/declare", "dijit/form/Select"], function (declare) { + return declare("fox.form.Select", dijit.form.Select, { + focus: function() { + return; // Stop dijit.form.Select from keeping focus after closing the menu + }, + }); +}); diff --git a/js/prefs.js b/js/prefs.js index 69b7899ec..844ce8c8a 100755 --- a/js/prefs.js +++ b/js/prefs.js @@ -54,7 +54,10 @@ require(["dojo/_base/kernel", "fox/PrefFeedTree", "fox/PrefFilterTree", "fox/PrefLabelTree", - "fox/Toolbar"], function (dojo, declare, ready, parser, AppBase) { + "fox/Toolbar", + "fox/form/Select", + "fox/form/ComboButton", + "fox/form/DropDownButton"], function (dojo, declare, ready, parser, AppBase) { ready(function () { try { diff --git a/js/tt-rss.js b/js/tt-rss.js index 5db742f07..bf0434a95 100644 --- a/js/tt-rss.js +++ b/js/tt-rss.js @@ -55,7 +55,10 @@ require(["dojo/_base/kernel", "fox/Article", "fox/FeedStoreModel", "fox/FeedTree", - "fox/Toolbar"], function (dojo, declare, ready, parser, AppBase) { + "fox/Toolbar", + "fox/form/Select", + "fox/form/ComboButton", + "fox/form/DropDownButton"], function (dojo, declare, ready, parser, AppBase) { ready(function () { try {