1
0
mirror of https://git.tt-rss.org/git/tt-rss.git synced 2025-12-13 14:25:55 +00:00
Files
tt-rss/js/App.js
Frenck Lutke 27b676b7b2 fix checkboxes shown as checked when they're not with mysql
The issue occurs because boolean/tinyint values are retrieved from mysql
as strings, and in php/js all non-empty strings are cast as boolean
true.

Current PDO mysql driver doesn't support `PDO::ATTR_STRINGIFY_FETCHES =
false`, and if I disable prepare-emulation so it uses the native MySQL
driver instead which supposedly does support it, prepare statements no
longer play nice with named parameters.

Every remaining clean solution that comes to mind that can cover all
cases, just for MySQL, adds an annoying amount of additional code /
overhead.

As long as the `App.FormFields.checkbox_tag()` JS function is the only
one suffering from the lack of conversion, I'll go with easy ugly over
here.
2021-02-25 12:24:23 +01:00

1236 lines
39 KiB
JavaScript

'use strict';
/* eslint-disable new-cap */
/* global __, Article, Headlines, Filters, fox */
/* global xhr, dojo, dijit, PluginHost, Notify, Feeds, Cookie */
/* global CommonDialogs, Plugins */
const App = {
_initParams: [],
_rpc_seq: 0,
hotkey_prefix: 0,
hotkey_prefix_pressed: false,
hotkey_prefix_timeout: 0,
global_unread: -1,
_widescreen_mode: false,
_loading_progress: 0,
hotkey_actions: {},
is_prefs: false,
LABEL_BASE_INDEX: -1024,
FormFields: {
attributes_to_string: function(attributes) {
return Object.keys(attributes).map((k) =>
`${App.escapeHtml(k)}="${App.escapeHtml(attributes[k])}"`)
.join(" ");
},
hidden_tag: function(name, value, attributes = {}, id = "") {
return `<input id="${App.escapeHtml(id)}" dojoType="dijit.form.TextBox" ${this.attributes_to_string(attributes)}
style="display : none" name="${name}" value="${App.escapeHtml(value)}"></input>`
},
// allow html inside because of icons
button_tag: function(value, type, attributes = {}) {
return `<button dojoType="dijit.form.Button" ${this.attributes_to_string(attributes)}
type="${type}">${value}</button>`
},
icon: function(icon, attributes = {}) {
return `<i class="material-icons" ${this.attributes_to_string(attributes)}>${icon}</i>`;
},
submit_tag: function(value, attributes = {}) {
return this.button_tag(value, "submit", {...{class: "alt-primary"}, ...attributes});
},
cancel_dialog_tag: function(value, attributes = {}) {
return this.button_tag(value, "", {...{onclick: "App.dialogOf(this).hide()"}, ...attributes});
},
checkbox_tag: function(name, checked = false, value = "", attributes = {}, id = "") {
// checked !== '0' prevents mysql "boolean" false to be implicitly cast as true
return `<input dojoType="dijit.form.CheckBox" type="checkbox" name="${App.escapeHtml(name)}"
${checked !== '0' && checked ? "checked" : ""}
${value ? `value="${App.escapeHtml(value)}"` : ""}
${this.attributes_to_string(attributes)} id="${App.escapeHtml(id)}">`
},
select_tag: function(name, value, values = [], attributes = {}, id = "") {
return `
<select name="${name}" dojoType="fox.form.Select" id="${App.escapeHtml(id)}" ${this.attributes_to_string(attributes)}>
${values.map((v) =>
`<option ${v == value ? 'selected="selected"' : ''} value="${App.escapeHtml(v)}">${App.escapeHtml(v)}</option>`
).join("")}
</select>
`
},
select_hash: function(name, value, values = {}, attributes = {}, id = "") {
return `
<select name="${name}" dojoType="fox.form.Select" id="${App.escapeHtml(id)}" ${this.attributes_to_string(attributes)}>
${Object.keys(values).map((vk) =>
`<option ${vk == value ? 'selected="selected"' : ''} value="${App.escapeHtml(vk)}">${App.escapeHtml(values[vk])}</option>`
).join("")}
</select>
`
}
},
Scrollable: {
scrollByPages: function (elem, page_offset) {
if (!elem) return;
/* keep a line or so from the previous page */
const offset = (elem.offsetHeight - (page_offset > 0 ? 50 : -50)) * page_offset;
this.scroll(elem, offset);
},
scroll: function(elem, offset) {
if (!elem) return;
elem.scrollTop += offset;
},
isChildVisible: function(elem, ctr) {
if (!elem) return;
const ctop = ctr.scrollTop;
const cbottom = ctop + ctr.offsetHeight;
const etop = elem.offsetTop;
const ebottom = etop + elem.offsetHeight;
return etop >= ctop && ebottom <= cbottom ||
etop < ctop && ebottom > ctop || ebottom > cbottom && etop < cbottom;
},
fitsInContainer: function (elem, ctr) {
if (!elem) return;
return elem.offsetTop + elem.offsetHeight <= ctr.scrollTop + ctr.offsetHeight &&
elem.offsetTop >= ctr.scrollTop;
},
scrollTo: function (elem, ctr, params = {}) {
const force_to_top = params.force_to_top || false;
if (!elem || !ctr) return;
if (force_to_top || !App.Scrollable.fitsInContainer(elem, ctr)) {
ctr.scrollTop = elem.offsetTop;
}
}
},
byId: function(id) {
return document.getElementById(id);
},
find: function(query) {
return document.querySelector(query)
},
findAll: function(query) {
return document.querySelectorAll(query);
},
dialogOf: function (elem) {
// elem could be a Dijit widget
elem = elem.domNode ? elem.domNode : elem;
return dijit.getEnclosingWidget(elem.closest('.dijitDialog'));
},
getPhArgs(plugin, method, args = {}) {
return {...{op: "pluginhandler", plugin: plugin, method: method}, ...args};
},
label_to_feed_id: function(label) {
return this.LABEL_BASE_INDEX - 1 - Math.abs(label);
},
feed_to_label_id: function(feed) {
return this.LABEL_BASE_INDEX - 1 + Math.abs(feed);
},
getInitParam: function(k) {
return this._initParams[k];
},
setInitParam: function(k, v) {
this._initParams[k] = v;
},
nightModeChanged: function(is_night, link) {
console.log("night mode changed to", is_night);
if (link) {
const css_override = is_night ? "themes/night.css" : "themes/light.css";
link.setAttribute("href", css_override + "?" + Date.now());
}
},
setupNightModeDetection: function(callback) {
if (!App.byId("theme_css")) {
const mql = window.matchMedia('(prefers-color-scheme: dark)');
try {
mql.addEventListener("change", () => {
this.nightModeChanged(mql.matches, App.byId("theme_auto_css"));
});
} catch (e) {
console.warn("exception while trying to set MQL event listener");
}
const link = document.createElement("link");
link.rel = "stylesheet";
link.id = "theme_auto_css";
if (callback) {
link.onload = function() {
document.querySelector("body").removeClassName("css_loading");
callback();
};
link.onerror = function() {
alert("Fatal error while loading application stylesheet: " + link.getAttribute("href"));
}
}
this.nightModeChanged(mql.matches, link);
document.querySelector("head").appendChild(link);
} else {
document.querySelector("body").removeClassName("css_loading");
if (callback) callback();
}
},
postCurrentWindow: function(target, params) {
const form = document.createElement("form");
form.setAttribute("method", "post");
form.setAttribute("action", App.getInitParam("self_url_prefix") + "/" + target);
for (const [k,v] of Object.entries(params)) {
const field = document.createElement("input");
field.setAttribute("name", k);
field.setAttribute("value", v);
field.setAttribute("type", "hidden");
form.appendChild(field);
}
document.body.appendChild(form);
form.submit();
form.parentNode.removeChild(form);
},
postOpenWindow: function(target, params) {
const w = window.open("");
if (w) {
w.opener = null;
const form = document.createElement("form");
form.setAttribute("method", "post");
form.setAttribute("action", App.getInitParam("self_url_prefix") + "/" + target);
for (const [k,v] of Object.entries(params)) {
const field = document.createElement("input");
field.setAttribute("name", k);
field.setAttribute("value", v);
field.setAttribute("type", "hidden");
form.appendChild(field);
}
w.document.body.appendChild(form);
form.submit();
}
},
urlParam: function(name) {
try {
const results = new RegExp('[?&]' + name + '=([^&#]*)').exec(window.location.href);
return decodeURIComponent(results[1].replace(/\+/g, " ")) || 0;
} catch (e) {
return 0;
}
},
next_seq: function() {
this._rpc_seq += 1;
return this._rpc_seq;
},
get_seq: function() {
return this._rpc_seq;
},
setLoadingProgress: function(p) {
this._loading_progress += p;
if (dijit.byId("loading_bar"))
dijit.byId("loading_bar").update({progress: this._loading_progress});
if (this._loading_progress >= 90) {
App.byId("overlay").hide();
}
},
isCombinedMode: function() {
return this.getInitParam("combined_display_mode");
},
getActionByHotkeySequence: function(sequence) {
const hotkeys_map = this.getInitParam("hotkeys");
for (const seq in hotkeys_map[1]) {
if (hotkeys_map[1].hasOwnProperty(seq)) {
if (seq == sequence) {
return hotkeys_map[1][seq];
}
}
}
},
keyeventToAction: function(event) {
const hotkeys_map = this.getInitParam("hotkeys");
const keycode = event.which;
const keychar = String.fromCharCode(keycode);
if (keycode == 27) { // escape and drop prefix
this.hotkey_prefix = false;
}
if (!this.hotkey_prefix && hotkeys_map[0].indexOf(keychar) != -1) {
this.hotkey_prefix = keychar;
App.byId("cmdline").innerHTML = keychar;
Element.show("cmdline");
window.clearTimeout(this.hotkey_prefix_timeout);
this.hotkey_prefix_timeout = window.setTimeout(() => {
this.hotkey_prefix = false;
Element.hide("cmdline");
}, 3 * 1000);
event.stopPropagation();
return false;
}
Element.hide("cmdline");
let hotkey_name = "";
if (event.type == "keydown") {
hotkey_name = "(" + keycode + ")";
// ensure ^*char notation
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 + ")";
}
let hotkey_full = this.hotkey_prefix ? this.hotkey_prefix + " " + hotkey_name : hotkey_name;
this.hotkey_prefix = false;
let action_name = this.getActionByHotkeySequence(hotkey_full);
// check for mode-specific hotkey
if (!action_name) {
hotkey_full = (this.isCombinedMode() ? "{C}" : "{3}") + hotkey_full;
action_name = this.getActionByHotkeySequence(hotkey_full);
}
console.log('keyeventToAction', hotkey_full, '=>', action_name);
return action_name;
},
cleanupMemory: function(root) {
const dijits = dojo.query("[widgetid]", dijit.byId(root).domNode).map(dijit.byNode);
dijits.forEach(function (d) {
dojo.destroy(d.domNode);
});
App.findAll("#" + root + " *").forEach(function (i) {
i.parentNode ? i.parentNode.removeChild(i) : true;
});
},
// htmlspecialchars()-alike for headlines data-content attribute
escapeHtml: function(p) {
if (typeof p == "string") {
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return p.replace(/[&<>"']/g, function(m) { return map[m]; });
} else {
return p;
}
},
// http://stackoverflow.com/questions/6251937/how-to-get-selecteduser-highlighted-text-in-contenteditable-element-and-replac
getSelectedText: function() {
let text = "";
if (typeof window.getSelection != "undefined") {
const sel = window.getSelection();
if (sel.rangeCount) {
const container = document.createElement("div");
for (let i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
text = container.innerHTML;
}
} else if (typeof document.selection != "undefined") {
if (document.selection.type == "Text") {
text = document.selection.createRange().textText;
}
}
return text.stripTags();
},
displayIfChecked: function(checkbox, elemId) {
if (checkbox.checked) {
Element.show(elemId);
} else {
Element.hide(elemId);
}
},
hotkeyHelp: function() {
xhr.post("backend.php", {op: "rpc", method: "hotkeyHelp"}, (reply) => {
const dialog = new fox.SingleUseDialog({
title: __("Keyboard shortcuts"),
content: reply,
});
dialog.show();
});
},
handleRpcJson: function(reply) {
const netalert = App.find(".net-alert");
if (reply) {
const error = reply['error'];
const seq = reply['seq'];
const message = reply['message'];
const counters = reply['counters'];
const runtime_info = reply['runtime-info'];
if (error && error.code && error.code != App.Error.E_SUCCESS) {
console.warn("handleRpcJson: fatal error", error);
this.Error.fatal(error.code);
return false;
}
if (seq && this.get_seq() != seq) {
console.warn("handleRpcJson: sequence mismatch: ", seq, '!=', this.get_seq());
return false;
}
// not in preferences
if (typeof Feeds != "undefined") {
if (message == "UPDATE_COUNTERS") {
console.log("need to refresh counters for", reply.feeds);
Feeds.requestCounters(reply.feeds);
}
if (counters)
Feeds.parseCounters(counters);
}
if (runtime_info)
this.parseRuntimeInfo(runtime_info);
if (netalert) netalert.hide();
return true;
} else {
if (netalert) netalert.show();
Notify.error("Communication problem with server.");
return false;
}
},
parseRuntimeInfo: function(data) {
Object.keys(data).forEach((k) => {
const v = data[k];
console.log("RI:", k, "=>", v);
if (k == "daemon_is_running" && v != 1) {
Notify.error("Update daemon is not running.", true);
return;
}
if (k == "recent_log_events") {
const alert = App.find(".log-alert");
if (alert) {
v > 0 ? alert.show() : alert.hide();
}
}
if (k == "daemon_stamp_ok" && v != 1) {
Notify.error("Update daemon is not updating feeds.", true);
return;
}
if (typeof Feeds != "undefined") {
if (k == "max_feed_id" || k == "num_feeds") {
if (this.getInitParam(k) && this.getInitParam(k) != v) {
console.log("feed count changed, need to reload feedlist:", this.getInitParam(k), v);
Feeds.reload();
}
}
}
this.setInitParam(k, v);
});
PluginHost.run(PluginHost.HOOK_RUNTIME_INFO_LOADED, data);
},
backendSanityCallback: function(reply) {
console.log("sanity check ok");
const params = reply['init-params'];
if (params) {
console.log('reading init-params...');
Object.keys(params).forEach((k) => {
switch (k) {
case "label_base_index":
this.LABEL_BASE_INDEX = parseInt(params[k]);
break;
case "cdm_auto_catchup":
if (params[k] == 1) {
const hl = App.byId("headlines-frame");
if (hl) hl.addClassName("auto_catchup");
}
break;
case "hotkeys":
// filter mnemonic definitions (used for help panel) from hotkeys map
// i.e. *(191)|Ctrl-/ -> *(191)
{
const tmp = [];
Object.keys(params[k][1]).forEach((sequence) => {
const filtered = sequence.replace(/\|.*$/, "");
tmp[filtered] = params[k][1][sequence];
});
params[k][1] = tmp;
}
break;
}
console.log("IP:", k, "=>", params[k]);
this.setInitParam(k, params[k]);
});
// PluginHost might not be available on non-index pages
if (typeof PluginHost !== 'undefined')
PluginHost.run(PluginHost.HOOK_PARAMS_LOADED, this._initParams);
}
this.initSecondStage();
},
Error: {
E_SUCCESS: "E_SUCCESS",
E_UNAUTHORIZED: "E_UNAUTHORIZED",
E_SCHEMA_MISMATCH: "E_SCHEMA_MISMATCH",
fatal: function (error, params = {}) {
if (error == App.Error.E_UNAUTHORIZED) {
window.location.href = "index.php";
return;
} else if (error == App.Error.E_SCHEMA_MISMATCH) {
window.location.href = "public.php?op=dbupdate";
return;
}
return this.report(__("Fatal error: %s").replace("%s", error),
{...{title: __("Fatal error")}, ...params});
},
report: function(error, params = {}) {
if (!error) return;
console.error("error.report:", error, params);
const message = params.message ? params.message : error.toString();
try {
xhr.post("backend.php",
{op: "rpc", method: "log",
file: params.filename ? params.filename : error.fileName,
line: params.lineno ? params.lineno : error.lineNumber,
msg: message,
context: error.stack},
(reply) => {
console.warn("[Error.report] log response", reply);
});
} catch (re) {
console.error("[Error.report] exception while saving logging error on server", re);
}
try {
const dialog = new fox.SingleUseDialog({
title: params.title || __("Unhandled exception"),
content: `
<div class='exception-contents'>
<h3>${message}</h3>
<header>${__('Stack trace')}</header>
<section>
<textarea readonly='readonly'>${error.stack}</textarea>
</section>
${params && params.info ?
`
<header>${__('Additional information')}</header>
<section>
<textarea readonly='readonly'>${params.info}</textarea>
</section>
` : ''}
</div>
<footer class='text-center'>
<button dojoType="dijit.form.Button" class='alt-primary' type='submit'>
${__('Close this window')}
</button>
</footer>
</div>`
});
dialog.show();
} catch (de) {
console.error("[Error.report] exception while showing error dialog", de);
alert(error.stack ? error.stack : message);
}
},
onWindowError: function (message, filename, lineno, colno, error) {
// called without context (this) from window.onerror
App.Error.report(error,
{message: message, filename: filename, lineno: lineno, colno: colno});
},
},
isPrefs() {
return this.is_prefs;
},
audioCanPlay: function(ctype) {
const a = document.createElement('audio');
return a.canPlayType(ctype);
},
init: function(parser, is_prefs) {
this.is_prefs = is_prefs;
window.onerror = this.Error.onWindowError;
this.setInitParam("csrf_token", __csrf_token);
this.setupNightModeDetection(() => {
parser.parse();
console.log('is_prefs', this.is_prefs);
if (!this.checkBrowserFeatures())
return;
this.setLoadingProgress(30);
this.initHotkeyActions();
const params = {
op: "rpc",
method: "sanityCheck",
clientTzOffset: new Date().getTimezoneOffset() * 60,
hasSandbox: "sandbox" in document.createElement("iframe")
};
xhr.json("backend.php", params, (reply) => {
try {
this.backendSanityCallback(reply);
} catch (e) {
this.Error.report(e);
}
});
});
},
checkBrowserFeatures: function() {
let errorMsg = "";
['MutationObserver'].forEach(function(wf) {
if (!(wf in window)) {
errorMsg = `Browser feature check failed: <code>window.${wf}</code> not found.`;
throw new Error(errorMsg);
}
});
if (errorMsg) {
this.Error.fatal(errorMsg, {info: navigator.userAgent});
}
return errorMsg == "";
},
updateRuntimeInfo: function() {
xhr.json("backend.php", {op: "rpc", method: "getruntimeinfo"}, () => {
// handled by xhr.json()
});
},
initSecondStage: function() {
document.onkeydown = (event) => this.hotkeyHandler(event);
document.onkeypress = (event) => this.hotkeyHandler(event);
if (this.is_prefs) {
this.setLoadingProgress(70);
Notify.close();
let tab = this.urlParam('tab');
if (tab) {
tab = dijit.byId(tab + "Tab");
if (tab) {
dijit.byId("pref-tabs").selectChild(tab);
const method = this.urlParam("method");
if (method) {
switch (method) {
case "editfeed":
window.setTimeout(() => {
CommonDialogs.editFeed(this.urlParam('methodparam'))
}, 100);
break;
default:
console.warn("initSecondStage, unknown method:", method);
}
}
}
} else {
let tab = localStorage.getItem("ttrss:prefs-tab");
if (tab) {
tab = dijit.byId(tab);
if (tab) {
dijit.byId("pref-tabs").selectChild(tab);
}
}
}
dojo.connect(dijit.byId("pref-tabs"), "selectChild", function (elem) {
localStorage.setItem("ttrss:prefs-tab", elem.id);
App.updateRuntimeInfo();
});
} else {
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',
(args) => {
if (args && args.w >= 0) {
Cookie.set("ttrss_fh_width", args.w, this.getInitParam("cookie_lifetime"));
}
});
dojo.connect(dijit.byId('content-insert'), 'resize',
(args) => {
if (args && args.w >= 0 && args.h >= 0) {
Cookie.set("ttrss_ci_width", args.w, this.getInitParam("cookie_lifetime"));
Cookie.set("ttrss_ci_height", args.h, this.getInitParam("cookie_lifetime"));
}
});
const toolbar = document.forms["toolbar-main"];
dijit.getEnclosingWidget(toolbar.view_mode).attr('value',
this.getInitParam("default_view_mode"));
dijit.getEnclosingWidget(toolbar.order_by).attr('value',
this.getInitParam("default_view_order_by"));
this.setLoadingProgress(50);
this._widescreen_mode = this.getInitParam("widescreen");
this.switchPanelMode(this._widescreen_mode);
Headlines.initScrollHandler();
if (this.getInitParam("simple_update")) {
console.log("scheduling simple feed updater...");
window.setInterval(() => { Feeds.updateRandom() }, 30 * 1000);
}
if (this.getInitParam('check_for_updates')) {
window.setInterval(() => {
this.checkForUpdates();
}, 3600 * 1000);
}
PluginHost.run(PluginHost.HOOK_INIT_COMPLETE, null);
}
if (!this.getInitParam("bw_limit"))
window.setInterval(() => {
App.updateRuntimeInfo();
}, 60 * 1000)
console.log("second stage ok");
},
checkForUpdates: function() {
console.log('checking for updates...');
xhr.json("backend.php", {op: 'rpc', method: 'checkforupdates'})
.then((reply) => {
console.log('update reply', reply);
if (reply.id) {
App.byId("updates-available").show();
} else {
App.byId("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() {
const view_mode = document.forms["toolbar-main"].view_mode.value;
App.findAll("body")[0].setAttribute("view-mode", view_mode);
return Feeds.reloadCurrent('');
},
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, pgup = 33, pgdn = 34, insert = 45, delete = 46
if (event.type == "keydown" && event.which != 27 && (event.which < 33 || event.which > 46)) return;
const action_name = this.keyeventToAction(event);
if (action_name) {
const action_func = this.hotkey_actions[action_name];
if (action_func != null) {
action_func(event);
event.stopPropagation();
return false;
}
}
},
switchPanelMode: function(wide) {
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" });
}
App.byId("headlines-frame").setStyle({ borderBottomWidth: '0px' });
App.byId("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" });
}
App.byId("headlines-frame").setStyle({ borderBottomWidth: '1px' });
App.byId("headlines-frame").removeClassName("wide");
}
Article.close();
if (article_id) Article.view(article_id);
xhr.post("backend.php", {op: "rpc", method: "setpanelmode", wide: wide ? 1 : 0});
},
initHotkeyActions: function() {
if (this.is_prefs) {
this.hotkey_actions["feed_subscribe"] = () => {
CommonDialogs.subscribeToFeed();
};
this.hotkey_actions["create_label"] = () => {
CommonDialogs.addLabel();
};
this.hotkey_actions["create_filter"] = () => {
Filters.edit();
};
this.hotkey_actions["help_dialog"] = () => {
this.hotkeyHelp();
};
} else {
this.hotkey_actions["next_feed"] = () => {
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"] = () => {
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_or_scroll"] = (event) => {
if (this.isCombinedMode())
Headlines.scroll(Headlines.line_scroll_offset, event);
else
Headlines.move('next');
};
this.hotkey_actions["prev_article_or_scroll"] = (event) => {
if (this.isCombinedMode())
Headlines.scroll(-Headlines.line_scroll_offset, event);
else
Headlines.move('prev');
};
this.hotkey_actions["next_article_noscroll"] = () => {
Headlines.move('next');
};
this.hotkey_actions["prev_article_noscroll"] = () => {
Headlines.move('prev');
};
this.hotkey_actions["next_article_noexpand"] = () => {
Headlines.move('next', {no_expand: true});
};
this.hotkey_actions["prev_article_noexpand"] = () => {
Headlines.move('prev', {no_expand: true});
};
this.hotkey_actions["search_dialog"] = () => {
Feeds.search();
};
this.hotkey_actions["cancel_search"] = () => {
Feeds.cancelSearch();
};
this.hotkey_actions["toggle_mark"] = () => {
Headlines.selectionToggleMarked();
};
this.hotkey_actions["toggle_publ"] = () => {
Headlines.selectionTogglePublished();
};
this.hotkey_actions["toggle_unread"] = () => {
Headlines.selectionToggleUnread({no_error: 1});
};
this.hotkey_actions["edit_tags"] = () => {
const id = Article.getActive();
if (id) {
Article.editTags(id);
}
};
this.hotkey_actions["open_in_new_window"] = () => {
if (Article.getActive()) {
Article.openInNewWindow(Article.getActive());
}
};
this.hotkey_actions["catchup_below"] = () => {
Headlines.catchupRelativeTo(1);
};
this.hotkey_actions["catchup_above"] = () => {
Headlines.catchupRelativeTo(0);
};
this.hotkey_actions["article_scroll_down"] = (event) => {
if (this.isCombinedMode())
Headlines.scroll(Headlines.line_scroll_offset, event);
else
Article.scroll(Headlines.line_scroll_offset, event);
};
this.hotkey_actions["article_scroll_up"] = (event) => {
if (this.isCombinedMode())
Headlines.scroll(-Headlines.line_scroll_offset, event);
else
Article.scroll(-Headlines.line_scroll_offset, event);
};
this.hotkey_actions["next_headlines_page"] = (event) => {
Headlines.scrollByPages(1, event);
};
this.hotkey_actions["prev_headlines_page"] = (event) => {
Headlines.scrollByPages(-1, event);
};
this.hotkey_actions["article_page_down"] = (event) => {
if (this.isCombinedMode())
Headlines.scrollByPages(1, event);
else
Article.scrollByPages(1, event);
};
this.hotkey_actions["article_page_up"] = (event) => {
if (this.isCombinedMode())
Headlines.scrollByPages(-1, event);
else
Article.scrollByPages(-1, event);
};
this.hotkey_actions["close_article"] = () => {
if (this.isCombinedMode()) {
Article.cdmUnsetActive();
} else {
Article.close();
}
};
this.hotkey_actions["email_article"] = () => {
if (typeof Plugins.Mail != "undefined") {
Plugins.Mail.onHotkey(Headlines.getSelected());
} else {
alert(__("Please enable mail or mailto plugin first."));
}
};
this.hotkey_actions["select_all"] = () => {
Headlines.select('all');
};
this.hotkey_actions["select_unread"] = () => {
Headlines.select('unread');
};
this.hotkey_actions["select_marked"] = () => {
Headlines.select('marked');
};
this.hotkey_actions["select_published"] = () => {
Headlines.select('published');
};
this.hotkey_actions["select_invert"] = () => {
Headlines.select('invert');
};
this.hotkey_actions["select_none"] = () => {
Headlines.select('none');
};
this.hotkey_actions["feed_refresh"] = () => {
if (typeof Feeds.getActive() != "undefined") {
Feeds.open({feed: Feeds.getActive(), is_cat: Feeds.activeIsCat()});
}
};
this.hotkey_actions["feed_unhide_read"] = () => {
Feeds.toggleUnread();
};
this.hotkey_actions["feed_subscribe"] = () => {
CommonDialogs.subscribeToFeed();
};
this.hotkey_actions["feed_debug_update"] = () => {
if (!Feeds.activeIsCat() && parseInt(Feeds.getActive()) > 0) {
/* global __csrf_token */
App.postOpenWindow("backend.php", {op: "feeds", method: "updatedebugger",
feed_id: Feeds.getActive(), csrf_token: __csrf_token});
} else {
alert("You can't debug this kind of feed.");
}
};
this.hotkey_actions["feed_debug_viewfeed"] = () => {
App.postOpenWindow("backend.php", {op: "feeds", method: "view",
feed: Feeds.getActive(), timestamps: 1, debug: 1, cat: Feeds.activeIsCat(), csrf_token: __csrf_token});
};
this.hotkey_actions["feed_edit"] = () => {
if (Feeds.activeIsCat())
alert(__("You can't edit this kind of feed."));
else
CommonDialogs.editFeed(Feeds.getActive());
};
this.hotkey_actions["feed_catchup"] = () => {
if (typeof Feeds.getActive() != "undefined") {
Feeds.catchupCurrent();
}
};
this.hotkey_actions["feed_reverse"] = () => {
Headlines.reverse();
};
this.hotkey_actions["feed_toggle_vgroup"] = () => {
xhr.post("backend.php", {op: "rpc", method: "togglepref", key: "VFEED_GROUP_BY_FEED"}, () => {
Feeds.reloadCurrent();
})
};
this.hotkey_actions["catchup_all"] = () => {
Feeds.catchupAll();
};
this.hotkey_actions["cat_toggle_collapse"] = () => {
if (Feeds.activeIsCat()) {
dijit.byId("feedTree").collapseCat(Feeds.getActive());
}
};
this.hotkey_actions["goto_read"] = () => {
Feeds.open({feed: -6});
};
this.hotkey_actions["goto_all"] = () => {
Feeds.open({feed: -4});
};
this.hotkey_actions["goto_fresh"] = () => {
Feeds.open({feed: -3});
};
this.hotkey_actions["goto_marked"] = () => {
Feeds.open({feed: -1});
};
this.hotkey_actions["goto_published"] = () => {
Feeds.open({feed: -2});
};
this.hotkey_actions["goto_prefs"] = () => {
App.openPreferences();
};
this.hotkey_actions["select_article_cursor"] = () => {
const id = Article.getUnderPointer();
if (id) {
const row = App.byId(`RROW-${id}`);
if (row)
row.toggleClassName("Selected");
}
};
this.hotkey_actions["create_label"] = () => {
CommonDialogs.addLabel();
};
this.hotkey_actions["create_filter"] = () => {
Filters.edit();
};
this.hotkey_actions["collapse_sidebar"] = () => {
Feeds.toggle();
};
this.hotkey_actions["toggle_full_text"] = () => {
if (typeof Plugins.Af_Readability != "undefined") {
if (Article.getActive())
Plugins.Af_Readability.embed(Article.getActive());
} else {
alert(__("Please enable af_readability first."));
}
};
this.hotkey_actions["toggle_widescreen"] = () => {
if (!this.isCombinedMode()) {
this._widescreen_mode = !this._widescreen_mode;
// reset stored sizes because geometry changed
Cookie.set("ttrss_ci_width", 0);
Cookie.set("ttrss_ci_height", 0);
this.switchPanelMode(this._widescreen_mode);
} else {
alert(__("Widescreen is not available in combined mode."));
}
};
this.hotkey_actions["help_dialog"] = () => {
this.hotkeyHelp();
};
this.hotkey_actions["toggle_combined_mode"] = () => {
const value = this.isCombinedMode() ? "false" : "true";
xhr.post("backend.php", {op: "rpc", method: "setpref", key: "COMBINED_DISPLAY_MODE", value: value}, () => {
this.setInitParam("combined_display_mode",
!this.getInitParam("combined_display_mode"));
Article.close();
Headlines.renderAgain();
})
};
this.hotkey_actions["toggle_cdm_expanded"] = () => {
const value = this.getInitParam("cdm_expanded") ? "false" : "true";
xhr.post("backend.php", {op: "rpc", method: "setpref", key: "CDM_EXPANDED", value: value}, () => {
this.setInitParam("cdm_expanded", !this.getInitParam("cdm_expanded"));
Headlines.renderAgain();
});
};
}
},
openPreferences: function(tab) {
document.location.href = "prefs.php" + (tab ? "?tab=" + tab : "");
},
onActionSelected: function(opid) {
switch (opid) {
case "qmcPrefs":
App.openPreferences();
break;
case "qmcLogout":
App.postCurrentWindow("public.php", {op: "logout", csrf_token: __csrf_token});
break;
case "qmcSearch":
Feeds.search();
break;
case "qmcAddFeed":
CommonDialogs.subscribeToFeed();
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 (!this.isCombinedMode()) {
this._widescreen_mode = !this._widescreen_mode;
// reset stored sizes because geometry changed
Cookie.set("ttrss_ci_width", 0);
Cookie.set("ttrss_ci_height", 0);
this.switchPanelMode(this._widescreen_mode);
} else {
alert(__("Widescreen is not available in combined mode."));
}
break;
case "qmcHKhelp":
this.hotkeyHelp()
break;
default:
console.log("quickMenuGo: unknown action: " + opid);
}
}
}