tt-rss/js/tt-rss.js
Michael Kuhn 4a2a90c980 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.
2019-04-13 22:34:57 +02:00

602 lines
17 KiB
JavaScript

'use strict'
/* global dijit,__ */
let App;
let CommonDialogs;
let Filters;
let Feeds;
let Headlines;
let Article;
let PluginHost;
const Plugins = {};
require(["dojo/_base/kernel",
"dojo/_base/declare",
"dojo/ready",
"dojo/parser",
"fox/AppBase",
"dojo/_base/loader",
"dojo/_base/html",
"dojo/query",
"dijit/ProgressBar",
"dijit/ColorPalette",
"dijit/Dialog",
"dijit/form/Button",
"dijit/form/ComboButton",
"dijit/form/CheckBox",
"dijit/form/DropDownButton",
"dijit/form/FilteringSelect",
"dijit/form/Form",
"dijit/form/RadioButton",
"dijit/form/Select",
"dijit/form/MultiSelect",
"dijit/form/SimpleTextarea",
"dijit/form/TextBox",
"dijit/form/ComboBox",
"dijit/form/ValidationTextBox",
"dijit/InlineEditBox",
"dijit/layout/AccordionContainer",
"dijit/layout/BorderContainer",
"dijit/layout/ContentPane",
"dijit/layout/TabContainer",
"dijit/PopupMenuItem",
"dijit/Menu",
"dijit/Toolbar",
"dijit/Tree",
"dijit/tree/dndSource",
"dijit/tree/ForestStoreModel",
"dojo/data/ItemFileWriteStore",
"fox/PluginHost",
"fox/CommonFilters",
"fox/CommonDialogs",
"fox/Feeds",
"fox/Headlines",
"fox/Article",
"fox/FeedStoreModel",
"fox/FeedTree",
"fox/Toolbar"], function (dojo, declare, ready, parser, AppBase) {
ready(function () {
try {
const _App = declare("fox.App", AppBase, {
global_unread: -1,
_widescreen_mode: false,
hotkey_actions: {},
constructor: function () {
parser.parse();
if (!this.checkBrowserFeatures())
return;
this.setLoadingProgress(30);
this.initHotkeyActions();
const a = document.createElement('audio');
const hasAudio = !!a.canPlayType;
const hasSandbox = "sandbox" in document.createElement("iframe");
const hasMp3 = !!(a.canPlayType && a.canPlayType('audio/mpeg;').replace(/no/, ''));
const clientTzOffset = new Date().getTimezoneOffset() * 60;
const params = {
op: "rpc", method: "sanityCheck", hasAudio: hasAudio,
hasMp3: hasMp3,
clientTzOffset: clientTzOffset,
hasSandbox: hasSandbox
};
xhrPost("backend.php", params, (transport) => {
try {
App.backendSanityCallback(transport);
} catch (e) {
App.Error.report(e);
}
});
},
checkBrowserFeatures: function() {
let errorMsg = "";
['MutationObserver'].each(function(wf) {
if (! (wf in window)) {
errorMsg = `Browser feature check failed: <code>window.${wf}</code> not found.`;
throw $break;
}
});
if (errorMsg) {
this.Error.fatal(errorMsg, {info: navigator.userAgent});
}
return errorMsg == "";
},
initSecondStage: function () {
this.enableCsrfSupport();
Feeds.reload();
Article.close();
if (parseInt(Cookie.get("ttrss_fh_width")) > 0) {
dijit.byId("feeds-holder").domNode.setStyle(
{width: Cookie.get("ttrss_fh_width") + "px"});
}
dijit.byId("main").resize();
dojo.connect(dijit.byId('feeds-holder'), 'resize',
function (args) {
if (args && args.w >= 0) {
Cookie.set("ttrss_fh_width", args.w, App.getInitParam("cookie_lifetime"));
}
});
dojo.connect(dijit.byId('content-insert'), 'resize',
function (args) {
if (args && args.w >= 0 && args.h >= 0) {
Cookie.set("ttrss_ci_width", args.w, App.getInitParam("cookie_lifetime"));
Cookie.set("ttrss_ci_height", args.h, App.getInitParam("cookie_lifetime"));
}
});
const toolbar = document.forms["toolbar-main"];
dijit.getEnclosingWidget(toolbar.view_mode).attr('value',
App.getInitParam("default_view_mode"));
dijit.getEnclosingWidget(toolbar.order_by).attr('value',
App.getInitParam("default_view_order_by"));
const hash_feed_id = hash_get('f');
const hash_feed_is_cat = hash_get('c') == "1";
if (hash_feed_id != undefined) {
Feeds.setActive(hash_feed_id, hash_feed_is_cat);
}
App.setLoadingProgress(50);
this._widescreen_mode = App.getInitParam("widescreen");
this.switchPanelMode(this._widescreen_mode);
Headlines.initScrollHandler();
if (App.getInitParam("simple_update")) {
console.log("scheduling simple feed updater...");
window.setInterval(() => { Feeds.updateRandom() }, 30 * 1000);
}
if (App.getInitParam('check_for_updates')) {
window.setInterval(() => {
App.checkForUpdates();
}, 3600 * 1000);
}
console.log("second stage ok");
PluginHost.run(PluginHost.HOOK_INIT_COMPLETE, null);
},
checkForUpdates: function() {
console.log('checking for updates...');
xhrJson("backend.php", {op: 'rpc', method: 'checkforupdates'})
.then((reply) => {
console.log('update reply', reply);
if (reply.id) {
$("updates-available").show();
} else {
$("updates-available").hide();
}
});
},
updateTitle: function() {
let tmp = "Tiny Tiny RSS";
if (this.global_unread > 0) {
tmp = "(" + this.global_unread + ") " + tmp;
}
document.title = tmp;
},
onViewModeChanged: function() {
return Feeds.reloadCurrent('');
},
isCombinedMode: function() {
return App.getInitParam("combined_display_mode");
},
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) {
const action_func = this.hotkey_actions[action_name];
if (action_func != null) {
action_func();
event.stopPropagation();
return false;
}
}
},
switchPanelMode: function(wide) {
//if (App.isCombinedMode()) return;
const article_id = Article.getActive();
if (wide) {
dijit.byId("headlines-wrap-inner").attr("design", 'sidebar');
dijit.byId("content-insert").attr("region", "trailing");
dijit.byId("content-insert").domNode.setStyle({width: '50%',
height: 'auto',
borderTopWidth: '0px' });
if (parseInt(Cookie.get("ttrss_ci_width")) > 0) {
dijit.byId("content-insert").domNode.setStyle(
{width: Cookie.get("ttrss_ci_width") + "px" });
}
$("headlines-frame").setStyle({ borderBottomWidth: '0px' });
$("headlines-frame").addClassName("wide");
} else {
dijit.byId("content-insert").attr("region", "bottom");
dijit.byId("content-insert").domNode.setStyle({width: 'auto',
height: '50%',
borderTopWidth: '0px'});
if (parseInt(Cookie.get("ttrss_ci_height")) > 0) {
dijit.byId("content-insert").domNode.setStyle(
{height: Cookie.get("ttrss_ci_height") + "px" });
}
$("headlines-frame").setStyle({ borderBottomWidth: '1px' });
$("headlines-frame").removeClassName("wide");
}
Article.close();
if (article_id) Article.view(article_id);
xhrPost("backend.php", {op: "rpc", method: "setpanelmode", wide: wide ? 1 : 0});
},
initHotkeyActions: function() {
this.hotkey_actions["next_feed"] = function () {
const rv = dijit.byId("feedTree").getNextFeed(
Feeds.getActive(), Feeds.activeIsCat());
if (rv) Feeds.open({feed: rv[0], is_cat: rv[1], delayed: true})
};
this.hotkey_actions["prev_feed"] = function () {
const rv = dijit.byId("feedTree").getPreviousFeed(
Feeds.getActive(), Feeds.activeIsCat());
if (rv) Feeds.open({feed: rv[0], is_cat: rv[1], delayed: true})
};
this.hotkey_actions["next_article"] = function () {
Headlines.move('next');
};
this.hotkey_actions["prev_article"] = function () {
Headlines.move('prev');
};
this.hotkey_actions["next_article_noscroll"] = function () {
Headlines.move('next', true);
};
this.hotkey_actions["prev_article_noscroll"] = function () {
Headlines.move('prev', true);
};
this.hotkey_actions["next_article_noexpand"] = function () {
Headlines.move('next', true, true);
};
this.hotkey_actions["prev_article_noexpand"] = function () {
Headlines.move('prev', true, true);
};
this.hotkey_actions["search_dialog"] = function () {
Feeds.search();
};
this.hotkey_actions["toggle_mark"] = function () {
Headlines.selectionToggleMarked();
};
this.hotkey_actions["toggle_publ"] = function () {
Headlines.selectionTogglePublished();
};
this.hotkey_actions["toggle_unread"] = function () {
Headlines.selectionToggleUnread({no_error: 1});
};
this.hotkey_actions["edit_tags"] = function () {
const id = Article.getActive();
if (id) {
Article.editTags(id);
}
};
this.hotkey_actions["open_in_new_window"] = function () {
if (Article.getActive()) {
Article.openInNewWindow(Article.getActive());
}
};
this.hotkey_actions["catchup_below"] = function () {
Headlines.catchupRelativeTo(1);
};
this.hotkey_actions["catchup_above"] = function () {
Headlines.catchupRelativeTo(0);
};
this.hotkey_actions["article_scroll_down"] = function () {
Article.scroll(40);
};
this.hotkey_actions["article_scroll_up"] = function () {
Article.scroll(-40);
};
this.hotkey_actions["close_article"] = function () {
if (App.isCombinedMode()) {
Article.cdmUnsetActive();
} else {
Article.close();
}
};
this.hotkey_actions["email_article"] = function () {
if (typeof Plugins.Mail != "undefined") {
Plugins.Mail.onHotkey(Headlines.getSelected());
} else {
alert(__("Please enable mail or mailto plugin first."));
}
};
this.hotkey_actions["select_all"] = function () {
Headlines.select('all');
};
this.hotkey_actions["select_unread"] = function () {
Headlines.select('unread');
};
this.hotkey_actions["select_marked"] = function () {
Headlines.select('marked');
};
this.hotkey_actions["select_published"] = function () {
Headlines.select('published');
};
this.hotkey_actions["select_invert"] = function () {
Headlines.select('invert');
};
this.hotkey_actions["select_none"] = function () {
Headlines.select('none');
};
this.hotkey_actions["feed_refresh"] = function () {
if (Feeds.getActive() != undefined) {
Feeds.open({feed: Feeds.getActive(), is_cat: Feeds.activeIsCat()});
}
};
this.hotkey_actions["feed_unhide_read"] = function () {
Feeds.toggleUnread();
};
this.hotkey_actions["feed_subscribe"] = function () {
CommonDialogs.quickAddFeed();
};
this.hotkey_actions["feed_debug_update"] = function () {
if (!Feeds.activeIsCat() && parseInt(Feeds.getActive()) > 0) {
window.open("backend.php?op=feeds&method=update_debugger&feed_id=" + Feeds.getActive() +
"&csrf_token=" + App.getInitParam("csrf_token"));
} else {
alert("You can't debug this kind of feed.");
}
};
this.hotkey_actions["feed_debug_viewfeed"] = function () {
Feeds.open({feed: Feeds.getActive(), is_cat: Feeds.activeIsCat(), viewfeed_debug: true});
};
this.hotkey_actions["feed_edit"] = function () {
if (Feeds.activeIsCat())
alert(__("You can't edit this kind of feed."));
else
CommonDialogs.editFeed(Feeds.getActive());
};
this.hotkey_actions["feed_catchup"] = function () {
if (Feeds.getActive() != undefined) {
Feeds.catchupCurrent();
}
};
this.hotkey_actions["feed_reverse"] = function () {
Headlines.reverse();
};
this.hotkey_actions["feed_toggle_vgroup"] = function () {
xhrPost("backend.php", {op: "rpc", method: "togglepref", key: "VFEED_GROUP_BY_FEED"}, () => {
Feeds.reloadCurrent();
})
};
this.hotkey_actions["catchup_all"] = function () {
Feeds.catchupAll();
};
this.hotkey_actions["cat_toggle_collapse"] = function () {
if (Feeds.activeIsCat()) {
dijit.byId("feedTree").collapseCat(Feeds.getActive());
}
};
this.hotkey_actions["goto_read"] = function () {
Feeds.open({feed: -6});
};
this.hotkey_actions["goto_all"] = function () {
Feeds.open({feed: -4});
};
this.hotkey_actions["goto_fresh"] = function () {
Feeds.open({feed: -3});
};
this.hotkey_actions["goto_marked"] = function () {
Feeds.open({feed: -1});
};
this.hotkey_actions["goto_published"] = function () {
Feeds.open({feed: -2});
};
this.hotkey_actions["goto_tagcloud"] = function () {
App.displayDlg(__("Tag cloud"), "printTagCloud");
};
this.hotkey_actions["goto_prefs"] = function () {
document.location.href = "prefs.php";
};
this.hotkey_actions["select_article_cursor"] = function () {
const id = Article.getUnderPointer();
if (id) {
const row = $("RROW-" + id);
if (row)
row.toggleClassName("Selected");
}
};
this.hotkey_actions["create_label"] = function () {
CommonDialogs.addLabel();
};
this.hotkey_actions["create_filter"] = function () {
Filters.quickAddFilter();
};
this.hotkey_actions["collapse_sidebar"] = function () {
Feeds.toggle();
};
this.hotkey_actions["toggle_embed_original"] = function () {
if (typeof embedOriginalArticle != "undefined") {
if (Article.getActive())
embedOriginalArticle(Article.getActive());
} else {
alert(__("Please enable embed_original plugin first."));
}
};
this.hotkey_actions["toggle_widescreen"] = function () {
if (!App.isCombinedMode()) {
App._widescreen_mode = !App._widescreen_mode;
// reset stored sizes because geometry changed
Cookie.set("ttrss_ci_width", 0);
Cookie.set("ttrss_ci_height", 0);
App.switchPanelMode(App._widescreen_mode);
} else {
alert(__("Widescreen is not available in combined mode."));
}
};
this.hotkey_actions["help_dialog"] = function () {
App.helpDialog("main");
};
this.hotkey_actions["toggle_combined_mode"] = function () {
const value = App.isCombinedMode() ? "false" : "true";
xhrPost("backend.php", {op: "rpc", method: "setpref", key: "COMBINED_DISPLAY_MODE", value: value}, () => {
App.setInitParam("combined_display_mode",
!App.getInitParam("combined_display_mode"));
Article.close();
Headlines.renderAgain();
})
};
this.hotkey_actions["toggle_cdm_expanded"] = function () {
const value = App.getInitParam("cdm_expanded") ? "false" : "true";
xhrPost("backend.php", {op: "rpc", method: "setpref", key: "CDM_EXPANDED", value: value}, () => {
App.setInitParam("cdm_expanded", !App.getInitParam("cdm_expanded"));
Headlines.renderAgain();
});
};
this.hotkey_actions["toggle_night_mode"] = function () {
App.toggleNightMode();
};
},
onActionSelected: function(opid) {
switch (opid) {
case "qmcPrefs":
document.location.href = "prefs.php";
break;
case "qmcLogout":
document.location.href = "backend.php?op=logout";
break;
case "qmcTagCloud":
App.displayDlg(__("Tag cloud"), "printTagCloud");
break;
case "qmcSearch":
Feeds.search();
break;
case "qmcAddFeed":
CommonDialogs.quickAddFeed();
break;
case "qmcDigest":
window.location.href = "backend.php?op=digest";
break;
case "qmcEditFeed":
if (Feeds.activeIsCat())
alert(__("You can't edit this kind of feed."));
else
CommonDialogs.editFeed(Feeds.getActive());
break;
case "qmcRemoveFeed":
const actid = Feeds.getActive();
if (!actid) {
alert(__("Please select some feed first."));
return;
}
if (Feeds.activeIsCat()) {
alert(__("You can't unsubscribe from the category."));
return;
}
const fn = Feeds.getName(actid);
if (confirm(__("Unsubscribe from %s?").replace("%s", fn))) {
CommonDialogs.unsubscribeFeed(actid);
}
break;
case "qmcCatchupAll":
Feeds.catchupAll();
break;
case "qmcShowOnlyUnread":
Feeds.toggleUnread();
break;
case "qmcToggleWidescreen":
if (!App.isCombinedMode()) {
App._widescreen_mode = !App._widescreen_mode;
// reset stored sizes because geometry changed
Cookie.set("ttrss_ci_width", 0);
Cookie.set("ttrss_ci_height", 0);
App.switchPanelMode(App._widescreen_mode);
} else {
alert(__("Widescreen is not available in combined mode."));
}
break;
case "qmcToggleNightMode":
App.toggleNightMode();
break;
case "qmcHKhelp":
App.helpDialog("main");
break;
default:
console.log("quickMenuGo: unknown action: " + opid);
}
},
isPrefs: function() {
return false;
}
});
App = new _App();
} catch (e) {
App.Error.report(e);
}
});
});
function hash_get(key) {
const kv = window.location.hash.substring(1).toQueryParams();
return kv[key];
}
function hash_set(key, value) {
const kv = window.location.hash.substring(1).toQueryParams();
kv[key] = value;
window.location.hash = $H(kv).toQueryString();
}