summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/App.js1236
-rw-r--r--js/AppBase.js494
-rw-r--r--js/Article.js527
-rw-r--r--js/CommonDialogs.js215
-rw-r--r--js/CommonFilters.js657
-rw-r--r--js/FeedStoreModel.js24
-rwxr-xr-xjs/FeedTree.js67
-rw-r--r--js/Feeds.js1053
-rwxr-xr-xjs/Headlines.js2334
-rw-r--r--js/PluginHost.js6
-rw-r--r--js/PrefFeedStore.js5
-rw-r--r--js/PrefFeedTree.js3
-rw-r--r--js/PrefFilterStore.js5
-rw-r--r--js/PrefFilterTree.js10
-rw-r--r--js/PrefHelpers.js445
-rw-r--r--js/PrefLabelTree.js3
-rw-r--r--js/PrefUsers.js198
-rwxr-xr-xjs/Toolbar.js7
-rwxr-xr-xjs/common.js78
-rw-r--r--js/form/ValidationTextArea.js33
-rwxr-xr-xjs/prefs.js111
-rw-r--r--js/tt-rss.js555
-rw-r--r--js/utility.js4
23 files changed, 4064 insertions, 4006 deletions
diff --git a/js/App.js b/js/App.js
new file mode 100644
index 000000000..03103845e
--- /dev/null
+++ b/js/App.js
@@ -0,0 +1,1236 @@
+'use strict';
+
+/* global __, Article, Ajax, Headlines, Filters */
+/* global xhrPost, xhrJson, dojo, dijit, PluginHost, Notify, $$, Feeds, Cookie */
+/* global CommonDialogs, Plugins, Effect */
+
+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,
+ 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;
+ }
+ },
+ 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 (!$("theme_css")) {
+ const mql = window.matchMedia('(prefers-color-scheme: dark)');
+
+ try {
+ mql.addEventListener("change", () => {
+ this.nightModeChanged(mql.matches, $("theme_auto_css"));
+ });
+ } catch (e) {
+ console.warn("exception while trying to set MQL event listener");
+ }
+
+ const link = new Element("link", {
+ rel: "stylesheet",
+ id: "theme_auto_css"
+ });
+
+ if (callback) {
+ link.onload = function () {
+ document.querySelector("body").removeClassName("css_loading");
+ callback();
+ };
+
+ link.onerror = function(event) {
+ 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();
+ }
+ },
+ enableCsrfSupport: function() {
+ const _this = this;
+
+ Ajax.Base.prototype.initialize = Ajax.Base.prototype.initialize.wrap(
+ function (callOriginal, options) {
+
+ if (_this.getInitParam("csrf_token") != undefined) {
+ Object.extend(options, options || { });
+
+ if (Object.isString(options.parameters))
+ options.parameters = options.parameters.toQueryParams();
+ else if (Object.isHash(options.parameters))
+ options.parameters = options.parameters.toObject();
+
+ options.parameters["csrf_token"] = _this.getInitParam("csrf_token");
+ }
+
+ return callOriginal(options);
+ }
+ );
+ },
+ 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(param) {
+ return String(window.location.href).parseQuery()[param];
+ },
+ 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) {
+ $("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;
+ $("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.each(function (d) {
+ dojo.destroy(d.domNode);
+ });
+
+ $$("#" + root + " *").each(function (i) {
+ i.parentNode ? i.parentNode.removeChild(i) : true;
+ });
+ },
+ // htmlspecialchars()-alike for headlines data-content attribute
+ escapeHtml: function(text) {
+ const map = {
+ '&': '&amp;',
+ '<': '&lt;',
+ '>': '&gt;',
+ '"': '&quot;',
+ "'": '&#039;'
+ };
+
+ return text.replace(/[&<>"']/g, function(m) { return map[m]; });
+ },
+ displayIfChecked: function(checkbox, elemId) {
+ if (checkbox.checked) {
+ Effect.Appear(elemId, {duration : 0.5});
+ } else {
+ Effect.Fade(elemId, {duration : 0.5});
+ }
+ },
+ helpDialog: function(topic) {
+ if (dijit.byId("helpDlg"))
+ dijit.byId("helpDlg").destroyRecursive();
+
+ xhrPost("backend.php", {op: "backend", method: "help", topic: topic}, (transport) => {
+ const dialog = new dijit.Dialog({
+ id: "helpDlg",
+ title: __("Help"),
+ style: "width: 600px",
+ content: transport.responseText,
+ });
+
+ dialog.show();
+ });
+ },
+ displayDlg: function(title, id, param, callback) {
+ Notify.progress("Loading, please wait...", true);
+
+ const query = {op: "dlg", method: id, param: param};
+
+ xhrPost("backend.php", query, (transport) => {
+ try {
+ const content = transport.responseText;
+
+ let dialog = dijit.byId("infoBox");
+
+ if (!dialog) {
+ dialog = new dijit.Dialog({
+ title: title,
+ id: 'infoBox',
+ style: "width: 600px",
+ onCancel: function () {
+ return true;
+ },
+ onExecute: function () {
+ return true;
+ },
+ onClose: function () {
+ return true;
+ },
+ content: content
+ });
+ } else {
+ dialog.attr('title', title);
+ dialog.attr('content', content);
+ }
+
+ dialog.show();
+
+ Notify.close();
+
+ if (callback) callback(transport);
+ } catch (e) {
+ this.Error.report(e);
+ }
+ });
+
+ return false;
+ },
+ handleRpcJson: function(transport) {
+
+ const netalert = $$("#toolbar .net-alert")[0];
+
+ try {
+ const reply = JSON.parse(transport.responseText);
+
+ if (reply) {
+ const error = reply['error'];
+
+ if (error) {
+ const code = error['code'];
+ const msg = error['message'];
+
+ console.warn("[handleRpcJson] received fatal error ", code, msg);
+
+ if (code != 0) {
+ /* global ERRORS */
+ this.Error.fatal(ERRORS[code], {info: msg, code: code});
+ return false;
+ }
+ }
+
+ const seq = reply['seq'];
+
+ if (seq && this.get_seq() != seq) {
+ console.log("[handleRpcJson] sequence mismatch: ", seq, '!=', this.get_seq());
+ return true;
+ }
+
+ const message = reply['message'];
+
+ if (message == "UPDATE_COUNTERS") {
+ console.log("need to refresh counters...");
+ Feeds.requestCounters(true);
+ }
+
+ const counters = reply['counters'];
+
+ if (counters)
+ Feeds.parseCounters(counters);
+
+ const runtime_info = reply['runtime-info'];
+
+ if (runtime_info)
+ this.parseRuntimeInfo(runtime_info);
+
+ if (netalert) netalert.hide();
+
+ return reply;
+
+ } else {
+ if (netalert) netalert.show();
+
+ Notify.error("Communication problem with server.");
+ }
+
+ } catch (e) {
+ if (netalert) netalert.show();
+
+ Notify.error("Communication problem with server.");
+
+ console.error(e);
+ }
+
+ return false;
+ },
+ parseRuntimeInfo: function(data) {
+ for (const k in data) {
+ if (data.hasOwnProperty(k)) {
+ const v = data[k];
+
+ console.log("RI:", k, "=>", v);
+
+ if (k == "daemon_is_running" && v != 1) {
+ Notify.error("<span onclick=\"App.explainError(1)\">Update daemon is not running.</span>", true);
+ return;
+ }
+
+ if (k == "recent_log_events") {
+ const alert = $$(".log-alert")[0];
+
+ if (alert) {
+ v > 0 ? alert.show() : alert.hide();
+ }
+ }
+
+ if (k == "daemon_stamp_ok" && v != 1) {
+ Notify.error("<span onclick=\"App.explainError(3)\">Update daemon is not updating feeds.</span>", true);
+ return;
+ }
+
+ if (k == "max_feed_id" || k == "num_feeds") {
+ if (this.getInitParam(k) != v) {
+ console.log("feed count changed, need to reload feedlist.");
+ Feeds.reload();
+ }
+ }
+
+ this.setInitParam(k, v);
+ }
+ }
+
+ PluginHost.run(PluginHost.HOOK_RUNTIME_INFO_LOADED, data);
+ },
+ backendSanityCallback: function(transport) {
+ const reply = JSON.parse(transport.responseText);
+
+ /* global ERRORS */
+
+ if (!reply) {
+ this.Error.fatal(ERRORS[3], {info: transport.responseText});
+ return;
+ }
+
+ if (reply['error']) {
+ const code = reply['error']['code'];
+
+ if (code && code != 0) {
+ return this.Error.fatal(ERRORS[code],
+ {code: code, info: reply['error']['message']});
+ }
+ }
+
+ console.log("sanity check ok");
+
+ const params = reply['init-params'];
+
+ if (params) {
+ console.log('reading init-params...');
+
+ for (const k in params) {
+ if (params.hasOwnProperty(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 = $("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 = [];
+ for (const sequence in params[k][1]) {
+ if (params[k][1].hasOwnProperty(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();
+ },
+ explainError: function(code) {
+ return this.displayDlg(__("Error explained"), "explainError", code);
+ },
+ Error: {
+ fatal: function (error, params) {
+ params = params || {};
+
+ if (params.code) {
+ if (params.code == 6) {
+ window.location.href = "index.php";
+ return;
+ } else if (params.code == 5) {
+ window.location.href = "public.php?op=dbupdate";
+ return;
+ }
+ }
+
+ return this.report(error,
+ Object.extend({title: __("Fatal error")}, params));
+ },
+ report: function(error, params) {
+ params = params || {};
+
+ if (!error) return;
+
+ console.error("[Error.report]", error, params);
+
+ const message = params.message ? params.message : error.toString();
+
+ try {
+ xhrPost("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},
+ (transport) => {
+ console.warn("[Error.report] log response", transport.responseText);
+ });
+ } catch (re) {
+ console.error("[Error.report] exception while saving logging error on server", re);
+ }
+
+ try {
+ if (dijit.byId("exceptionDlg"))
+ dijit.byId("exceptionDlg").destroyRecursive();
+
+ let stack_msg = "";
+
+ if (error.stack)
+ stack_msg += `<div><b>Stack trace:</b></div>
+ <textarea name="stack" readonly="1">${error.stack}</textarea>`;
+
+ if (params.info)
+ stack_msg += `<div><b>Additional information:</b></div>
+ <textarea name="stack" readonly="1">${params.info}</textarea>`;
+
+ const content = `<div class="error-contents">
+ <p class="message">${message}</p>
+ ${stack_msg}
+ <div class="dlgButtons">
+ <button dojoType="dijit.form.Button"
+ onclick="dijit.byId('exceptionDlg').hide()">${__('Close this window')}</button>
+ </div>
+ </div>`;
+
+ const dialog = new dijit.Dialog({
+ id: "exceptionDlg",
+ title: params.title || __("Unhandled exception"),
+ style: "width: 600px",
+ content: content
+ });
+
+ 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;
+ },
+ 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();
+ this.enableCsrfSupport();
+
+ 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 {
+ this.backendSanityCallback(transport);
+ } catch (e) {
+ this.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 new Error(errorMsg);
+ }
+ });
+
+ if (errorMsg) {
+ this.Error.fatal(errorMsg, {info: navigator.userAgent});
+ }
+
+ return errorMsg == "";
+ },
+ initSecondStage: function() {
+
+ document.onkeydown = (event) => { return this.hotkeyHandler(event) };
+ document.onkeypress = (event) => { return 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);
+
+ switch (this.urlParam('method')) {
+ case "editfeed":
+ window.setTimeout(() => {
+ CommonDialogs.editFeed(this.urlParam('methodparam'))
+ }, 100);
+ break;
+ default:
+ console.warn("initSecondStage, unknown method:", this.urlParam("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);
+ });
+
+ } 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);
+ }
+
+ 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() {
+ const view_mode = document.forms["toolbar-main"].view_mode.value;
+
+ $$("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" });
+ }
+
+ $("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() {
+ if (this.is_prefs) {
+
+ this.hotkey_actions["feed_subscribe"] = () => {
+ CommonDialogs.quickAddFeed();
+ };
+
+ this.hotkey_actions["create_label"] = () => {
+ CommonDialogs.addLabel();
+ };
+
+ this.hotkey_actions["create_filter"] = () => {
+ Filters.quickAddFilter();
+ };
+
+ this.hotkey_actions["help_dialog"] = () => {
+ this.helpDialog("main");
+ };
+
+ } 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.quickAddFeed();
+ };
+ this.hotkey_actions["feed_debug_update"] = () => {
+ if (!Feeds.activeIsCat() && parseInt(Feeds.getActive()) > 0) {
+ //window.open("backend.php?op=feeds&method=update_debugger&feed_id=" + Feeds.getActive());
+
+ /* global __csrf_token */
+ App.postOpenWindow("backend.php", {op: "feeds", method: "update_debugger", feed_id: Feeds.getActive(), csrf_token: __csrf_token});
+
+ } else {
+ alert("You can't debug this kind of feed.");
+ }
+ };
+
+ this.hotkey_actions["feed_debug_viewfeed"] = () => {
+ Feeds.open({feed: Feeds.getActive(), is_cat: Feeds.activeIsCat(), viewfeed_debug: true});
+ };
+
+ 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"] = () => {
+ xhrPost("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_tagcloud"] = () => {
+ this.displayDlg(__("Tag cloud"), "printTagCloud");
+ };
+ this.hotkey_actions["goto_prefs"] = () => {
+ document.location.href = "prefs.php";
+ };
+ this.hotkey_actions["select_article_cursor"] = () => {
+ const id = Article.getUnderPointer();
+ if (id) {
+ const row = $("RROW-" + id);
+
+ if (row)
+ row.toggleClassName("Selected");
+ }
+ };
+ this.hotkey_actions["create_label"] = () => {
+ CommonDialogs.addLabel();
+ };
+ this.hotkey_actions["create_filter"] = () => {
+ Filters.quickAddFilter();
+ };
+ 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.helpDialog("main");
+ };
+ this.hotkey_actions["toggle_combined_mode"] = () => {
+ const value = this.isCombinedMode() ? "false" : "true";
+
+ xhrPost("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";
+
+ xhrPost("backend.php", {op: "rpc", method: "setpref", key: "CDM_EXPANDED", value: value}, () => {
+ this.setInitParam("cdm_expanded", !this.getInitParam("cdm_expanded"));
+ Headlines.renderAgain();
+ });
+ };
+ }
+ },
+ onActionSelected: function(opid) {
+ switch (opid) {
+ case "qmcPrefs":
+ document.location.href = "prefs.php";
+ break;
+ case "qmcLogout":
+ App.postCurrentWindow("public.php", {op: "logout", csrf_token: __csrf_token});
+ break;
+ case "qmcTagCloud":
+ this.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 (!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.helpDialog("main");
+ break;
+ default:
+ console.log("quickMenuGo: unknown action: " + opid);
+ }
+ }
+}
+
diff --git a/js/AppBase.js b/js/AppBase.js
deleted file mode 100644
index 86cc44e8a..000000000
--- a/js/AppBase.js
+++ /dev/null
@@ -1,494 +0,0 @@
-'use strict'
-/* global __, ngettext */
-define(["dojo/_base/declare"], function (declare) {
- return declare("fox.AppBase", null, {
- _initParams: [],
- _rpc_seq: 0,
- hotkey_prefix: 0,
- hotkey_prefix_pressed: false,
- hotkey_prefix_timeout: 0,
- constructor: function() {
- window.onerror = this.Error.onWindowError;
- },
- 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 (!$("theme_css")) {
- const mql = window.matchMedia('(prefers-color-scheme: dark)');
-
- try {
- mql.addEventListener("change", () => {
- this.nightModeChanged(mql.matches, $("theme_auto_css"));
- });
- } catch (e) {
- console.warn("exception while trying to set MQL event listener");
- }
-
- const link = new Element("link", {
- rel: "stylesheet",
- id: "theme_auto_css"
- });
-
- if (callback) {
- link.onload = function () {
- document.querySelector("body").removeClassName("css_loading");
- callback();
- };
-
- link.onerror = function(event) {
- 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();
- }
- },
- enableCsrfSupport: function() {
- Ajax.Base.prototype.initialize = Ajax.Base.prototype.initialize.wrap(
- function (callOriginal, options) {
-
- if (App.getInitParam("csrf_token") != undefined) {
- Object.extend(options, options || { });
-
- if (Object.isString(options.parameters))
- options.parameters = options.parameters.toQueryParams();
- else if (Object.isHash(options.parameters))
- options.parameters = options.parameters.toObject();
-
- options.parameters["csrf_token"] = App.getInitParam("csrf_token");
- }
-
- return callOriginal(options);
- }
- );
- },
- urlParam: function(param) {
- return String(window.location.href).parseQuery()[param];
- },
- next_seq: function() {
- this._rpc_seq += 1;
- return this._rpc_seq;
- },
- get_seq: function() {
- return this._rpc_seq;
- },
- setLoadingProgress: function(p) {
- loading_progress += p;
-
- if (dijit.byId("loading_bar"))
- dijit.byId("loading_bar").update({progress: loading_progress});
-
- if (loading_progress >= 90) {
- $("overlay").hide();
- }
-
- },
- keyeventToAction: function(event) {
-
- const hotkeys_map = App.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;
- $("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 + ")";
- }
-
- const hotkey_full = this.hotkey_prefix ? this.hotkey_prefix + " " + hotkey_name : hotkey_name;
- this.hotkey_prefix = false;
-
- let action_name = false;
-
- for (const sequence in hotkeys_map[1]) {
- if (hotkeys_map[1].hasOwnProperty(sequence)) {
- if (sequence == hotkey_full) {
- action_name = hotkeys_map[1][sequence];
- break;
- }
- }
- }
-
- 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.each(function (d) {
- dojo.destroy(d.domNode);
- });
-
- $$("#" + root + " *").each(function (i) {
- i.parentNode ? i.parentNode.removeChild(i) : true;
- });
- },
- helpDialog: function(topic) {
- const query = "backend.php?op=backend&method=help&topic=" + encodeURIComponent(topic);
-
- if (dijit.byId("helpDlg"))
- dijit.byId("helpDlg").destroyRecursive();
-
- const dialog = new dijit.Dialog({
- id: "helpDlg",
- title: __("Help"),
- style: "width: 600px",
- href: query,
- });
-
- dialog.show();
- },
- displayDlg: function(title, id, param, callback) {
- Notify.progress("Loading, please wait...", true);
-
- const query = {op: "dlg", method: id, param: param};
-
- xhrPost("backend.php", query, (transport) => {
- try {
- const content = transport.responseText;
-
- let dialog = dijit.byId("infoBox");
-
- if (!dialog) {
- dialog = new dijit.Dialog({
- title: title,
- id: 'infoBox',
- style: "width: 600px",
- onCancel: function () {
- return true;
- },
- onExecute: function () {
- return true;
- },
- onClose: function () {
- return true;
- },
- content: content
- });
- } else {
- dialog.attr('title', title);
- dialog.attr('content', content);
- }
-
- dialog.show();
-
- Notify.close();
-
- if (callback) callback(transport);
- } catch (e) {
- this.Error.report(e);
- }
- });
-
- return false;
- },
- handleRpcJson: function(transport) {
-
- const netalert = $$("#toolbar .net-alert")[0];
-
- try {
- const reply = JSON.parse(transport.responseText);
-
- if (reply) {
- const error = reply['error'];
-
- if (error) {
- const code = error['code'];
- const msg = error['message'];
-
- console.warn("[handleRpcJson] received fatal error ", code, msg);
-
- if (code != 0) {
- /* global ERRORS */
- this.Error.fatal(ERRORS[code], {info: msg, code: code});
- return false;
- }
- }
-
- const seq = reply['seq'];
-
- if (seq && this.get_seq() != seq) {
- console.log("[handleRpcJson] sequence mismatch: ", seq, '!=', this.get_seq());
- return true;
- }
-
- const message = reply['message'];
-
- if (message == "UPDATE_COUNTERS") {
- console.log("need to refresh counters...");
- Feeds.requestCounters(true);
- }
-
- const counters = reply['counters'];
-
- if (counters)
- Feeds.parseCounters(counters);
-
- const runtime_info = reply['runtime-info'];
-
- if (runtime_info)
- App.parseRuntimeInfo(runtime_info);
-
- if (netalert) netalert.hide();
-
- return reply;
-
- } else {
- if (netalert) netalert.show();
-
- Notify.error("Communication problem with server.");
- }
-
- } catch (e) {
- if (netalert) netalert.show();
-
- Notify.error("Communication problem with server.");
-
- console.error(e);
- }
-
- return false;
- },
- parseRuntimeInfo: function(data) {
- for (const k in data) {
- if (data.hasOwnProperty(k)) {
- const v = data[k];
-
- console.log("RI:", k, "=>", v);
-
- if (k == "daemon_is_running" && v != 1) {
- Notify.error("<span onclick=\"App.explainError(1)\">Update daemon is not running.</span>", true);
- return;
- }
-
- if (k == "recent_log_events") {
- const alert = $$(".log-alert")[0];
-
- if (alert) {
- v > 0 ? alert.show() : alert.hide();
- }
- }
-
- if (k == "daemon_stamp_ok" && v != 1) {
- Notify.error("<span onclick=\"App.explainError(3)\">Update daemon is not updating feeds.</span>", true);
- return;
- }
-
- if (k == "max_feed_id" || k == "num_feeds") {
- if (App.getInitParam(k) != v) {
- console.log("feed count changed, need to reload feedlist.");
- Feeds.reload();
- }
- }
-
- this.setInitParam(k, v);
- }
- }
-
- PluginHost.run(PluginHost.HOOK_RUNTIME_INFO_LOADED, data);
- },
- backendSanityCallback: function (transport) {
- const reply = JSON.parse(transport.responseText);
-
- /* global ERRORS */
-
- if (!reply) {
- this.Error.fatal(ERRORS[3], {info: transport.responseText});
- return;
- }
-
- if (reply['error']) {
- const code = reply['error']['code'];
-
- if (code && code != 0) {
- return this.Error.fatal(ERRORS[code],
- {code: code, info: reply['error']['message']});
- }
- }
-
- console.log("sanity check ok");
-
- const params = reply['init-params'];
-
- if (params) {
- console.log('reading init-params...');
-
- for (const k in params) {
- if (params.hasOwnProperty(k)) {
- switch (k) {
- case "label_base_index":
- LABEL_BASE_INDEX = parseInt(params[k]);
- break;
- case "cdm_auto_catchup":
- if (params[k] == 1) {
- const hl = $("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 = [];
- for (const sequence in params[k][1]) {
- if (params[k][1].hasOwnProperty(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, App._initParams);
- }
-
- this.initSecondStage();
- },
- explainError: function(code) {
- return this.displayDlg(__("Error explained"), "explainError", code);
- },
- Error: {
- fatal: function (error, params) {
- params = params || {};
-
- if (params.code) {
- if (params.code == 6) {
- window.location.href = "index.php";
- return;
- } else if (params.code == 5) {
- window.location.href = "public.php?op=dbupdate";
- return;
- }
- }
-
- return this.report(error,
- Object.extend({title: __("Fatal error")}, params));
- },
- report: function(error, params) {
- params = params || {};
-
- if (!error) return;
-
- console.error("[Error.report]", error, params);
-
- const message = params.message ? params.message : error.toString();
-
- try {
- xhrPost("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},
- (transport) => {
- console.warn("[Error.report] log response", transport.responseText);
- });
- } catch (re) {
- console.error("[Error.report] exception while saving logging error on server", re);
- }
-
- try {
- if (dijit.byId("exceptionDlg"))
- dijit.byId("exceptionDlg").destroyRecursive();
-
- let stack_msg = "";
-
- if (error.stack)
- stack_msg += `<div><b>Stack trace:</b></div>
- <textarea name="stack" readonly="1">${error.stack}</textarea>`;
-
- if (params.info)
- stack_msg += `<div><b>Additional information:</b></div>
- <textarea name="stack" readonly="1">${params.info}</textarea>`;
-
- let content = `<div class="error-contents">
- <p class="message">${message}</p>
- ${stack_msg}
- <div class="dlgButtons">
- <button dojoType="dijit.form.Button"
- onclick=\"dijit.byId('exceptionDlg').hide()">${__('Close this window')}</button>
- </div>
- </div>`;
-
- const dialog = new dijit.Dialog({
- id: "exceptionDlg",
- title: params.title || __("Unhandled exception"),
- style: "width: 600px",
- content: content
- });
-
- 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});
- },
- }
- });
-});
diff --git a/js/Article.js b/js/Article.js
index 50447c2a1..76637cd69 100644
--- a/js/Article.js
+++ b/js/Article.js
@@ -1,243 +1,272 @@
'use strict'
-/* global __, ngettext */
-define(["dojo/_base/declare"], function (declare) {
- Article = {
- _scroll_reset_timeout: false,
- getScoreClass: function (score) {
- if (score > 500) {
- return "score-high";
- } else if (score > 0) {
- return "score-half-high";
- } else if (score < -100) {
- return "score-low";
- } else if (score < 0) {
- return "score-half-low";
- } else {
- return "score-neutral";
- }
- },
- getScorePic: function (score) {
- if (score > 500) {
- return "trending_up";
- } else if (score > 0) {
- return "trending_up";
- } else if (score < 0) {
- return "trending_down";
- } else {
- return "trending_neutral";
- }
- },
- selectionSetScore: function () {
- const ids = Headlines.getSelected();
- if (ids.length > 0) {
- const score = prompt(__("Please enter new score for selected articles:"));
+/* global __, ngettext, App, Headlines, xhrPost, xhrJson, dojo, dijit, PluginHost, Notify, $$, Ajax */
+
+const Article = {
+ _scroll_reset_timeout: false,
+ getScoreClass: function (score) {
+ if (score > 500) {
+ return "score-high";
+ } else if (score > 0) {
+ return "score-half-high";
+ } else if (score < -100) {
+ return "score-low";
+ } else if (score < 0) {
+ return "score-half-low";
+ } else {
+ return "score-neutral";
+ }
+ },
+ getScorePic: function (score) {
+ if (score > 500) {
+ return "trending_up";
+ } else if (score > 0) {
+ return "trending_up";
+ } else if (score < 0) {
+ return "trending_down";
+ } else {
+ return "trending_neutral";
+ }
+ },
+ selectionSetScore: function () {
+ const ids = Headlines.getSelected();
- if (!isNaN(parseInt(score))) {
- ids.each((id) => {
- const row = $("RROW-" + id);
+ if (ids.length > 0) {
+ const score = prompt(__("Please enter new score for selected articles:"));
- if (row) {
- row.setAttribute("data-score", score);
+ if (!isNaN(parseInt(score))) {
+ ids.each((id) => {
+ const row = $("RROW-" + id);
- const pic = row.select(".icon-score")[0];
+ if (row) {
+ row.setAttribute("data-score", score);
- pic.innerHTML = Article.getScorePic(score);
- pic.setAttribute("title", score);
+ const pic = row.select(".icon-score")[0];
- ["score-low", "score-high", "score-half-low", "score-half-high", "score-neutral"]
- .each(function(scl) {
- if (row.hasClassName(scl))
- row.removeClassName(scl);
- });
+ pic.innerHTML = Article.getScorePic(score);
+ pic.setAttribute("title", score);
- row.addClassName(Article.getScoreClass(score));
- }
- });
- }
+ ["score-low", "score-high", "score-half-low", "score-half-high", "score-neutral"]
+ .each(function(scl) {
+ if (row.hasClassName(scl))
+ row.removeClassName(scl);
+ });
- } else {
- alert(__("No articles selected."));
+ row.addClassName(Article.getScoreClass(score));
+ }
+ });
}
- },
- setScore: function (id, pic) {
- const row = pic.up("div[id*=RROW]");
- if (row) {
- const score_old = row.getAttribute("data-score");
- const score = prompt(__("Please enter new score for this article:"), score_old);
-
- if (!isNaN(parseInt(score))) {
- row.setAttribute("data-score", score);
+ } else {
+ alert(__("No articles selected."));
+ }
+ },
+ setScore: function (id, pic) {
+ const row = pic.up("div[id*=RROW]");
- const pic = row.select(".icon-score")[0];
+ if (row) {
+ const score_old = row.getAttribute("data-score");
+ const score = prompt(__("Please enter new score for this article:"), score_old);
- pic.innerHTML = Article.getScorePic(score);
- pic.setAttribute("title", score);
+ if (!isNaN(parseInt(score))) {
+ row.setAttribute("data-score", score);
- ["score-low", "score-high", "score-half-low", "score-half-high", "score-neutral"]
- .each(function(scl) {
- if (row.hasClassName(scl))
- row.removeClassName(scl);
- });
+ const pic = row.select(".icon-score")[0];
- row.addClassName(Article.getScoreClass(score));
- }
- }
- },
- cdmUnsetActive: function (event) {
- const row = $("RROW-" + Article.getActive());
+ pic.innerHTML = Article.getScorePic(score);
+ pic.setAttribute("title", score);
- if (row) {
- row.removeClassName("active");
-
- if (event)
- event.stopPropagation();
+ ["score-low", "score-high", "score-half-low", "score-half-high", "score-neutral"]
+ .each(function(scl) {
+ if (row.hasClassName(scl))
+ row.removeClassName(scl);
+ });
- return false;
+ row.addClassName(Article.getScoreClass(score));
}
- },
- close: function () {
- if (dijit.byId("content-insert"))
- dijit.byId("headlines-wrap-inner").removeChild(
- dijit.byId("content-insert"));
-
- Article.setActive(0);
- },
- displayUrl: function (id) {
- const query = {op: "rpc", method: "getlinktitlebyid", id: id};
-
- xhrJson("backend.php", query, (reply) => {
- if (reply && reply.link) {
- prompt(__("Article URL:"), reply.link);
- }
- });
- },
- openInNewWindow: function (id) {
- const w = window.open("");
+ }
+ },
+ popupOpenUrl: function(url) {
+ const w = window.open("");
+
+ w.opener = null;
+ w.location = url;
+ },
+ /* popupOpenArticle: function(id) {
+ const w = window.open("",
+ "ttrss_article_popup",
+ "height=900,width=900,resizable=yes,status=no,location=no,menubar=no,directories=no,scrollbars=yes,toolbar=no");
+
+ if (w) {
+ w.opener = null;
+ w.location = "backend.php?op=article&method=view&mode=raw&html=1&zoom=1&id=" + id + "&csrf_token=" + App.getInitParam("csrf_token");
+ }
+ }, */
+ cdmUnsetActive: function (event) {
+ const row = $("RROW-" + Article.getActive());
- if (w) {
- w.opener = null;
- w.location = "backend.php?op=article&method=redirect&id=" + id;
+ if (row) {
+ row.removeClassName("active");
- Headlines.toggleUnread(id, 0);
- }
- },
- render: function (article) {
- App.cleanupMemory("content-insert");
+ if (event)
+ event.stopPropagation();
- dijit.byId("headlines-wrap-inner").addChild(
+ return false;
+ }
+ },
+ close: function () {
+ if (dijit.byId("content-insert"))
+ dijit.byId("headlines-wrap-inner").removeChild(
dijit.byId("content-insert"));
- const c = dijit.byId("content-insert");
+ Article.setActive(0);
+ },
+ displayUrl: function (id) {
+ const query = {op: "rpc", method: "getlinktitlebyid", id: id};
- try {
- c.domNode.scrollTop = 0;
- } catch (e) {
+ xhrJson("backend.php", query, (reply) => {
+ if (reply && reply.link) {
+ prompt(__("Article URL:"), reply.link);
}
+ });
+ },
+ openInNewWindow: function (id) {
+ /* global __csrf_token */
+ App.postOpenWindow("backend.php",
+ { "op": "article", "method": "redirect", "id": id, "csrf_token": __csrf_token });
+
+ Headlines.toggleUnread(id, 0);
+ },
+ render: function (article) {
+ App.cleanupMemory("content-insert");
+
+ dijit.byId("headlines-wrap-inner").addChild(
+ dijit.byId("content-insert"));
+
+ const c = dijit.byId("content-insert");
+
+ try {
+ c.domNode.scrollTop = 0;
+ } catch (e) {
+ }
- c.attr('content', article);
- PluginHost.run(PluginHost.HOOK_ARTICLE_RENDERED, c.domNode);
-
- Headlines.correctHeadlinesOffset(Article.getActive());
+ c.attr('content', article);
+ PluginHost.run(PluginHost.HOOK_ARTICLE_RENDERED, c.domNode);
- try {
- c.focus();
- } catch (e) {
- }
- },
- formatComments: function(hl) {
- let comments = "";
+ //Headlines.correctHeadlinesOffset(Article.getActive());
- if (hl.comments) {
- let comments_msg = __("comments");
+ try {
+ c.focus();
+ } catch (e) {
+ }
+ },
+ formatComments: function(hl) {
+ let comments = "";
- if (hl.num_comments > 0) {
- comments_msg = hl.num_comments + " " + ngettext("comment", "comments", hl.num_comments)
- }
+ if (hl.comments || hl.num_comments > 0) {
+ let comments_msg = __("comments");
- comments = `<a href="${escapeHtml(hl.comments)}">(${comments_msg})</a>`;
+ if (hl.num_comments > 0) {
+ comments_msg = hl.num_comments + " " + ngettext("comment", "comments", hl.num_comments)
}
- return comments;
- },
- formatOriginallyFrom: function(hl) {
- return hl.orig_feed ? `<span>
- ${__('Originally from:')} <a target="_blank" rel="noopener noreferrer" href="${escapeHtml(hl.orig_feed[1])}">${hl.orig_feed[0]}</a>
- </span>` : "";
- },
- unpack: function(row) {
- if (row.hasAttribute("data-content")) {
- console.log("unpacking: " + row.id);
+ comments = `<a target="_blank" rel="noopener noreferrer" href="${App.escapeHtml(hl.comments ? hl.comments : hl.link)}">(${comments_msg})</a>`;
+ }
- const container = row.querySelector(".content-inner");
+ return comments;
+ },
+ formatOriginallyFrom: function(hl) {
+ return hl.orig_feed ? `<span>
+ ${__('Originally from:')} <a target="_blank" rel="noopener noreferrer" href="${App.escapeHtml(hl.orig_feed[1])}">${hl.orig_feed[0]}</a>
+ </span>` : "";
+ },
+ unpack: function(row) {
+ if (row.hasAttribute("data-content")) {
+ console.log("unpacking: " + row.id);
- container.innerHTML = row.getAttribute("data-content").trim();
+ const container = row.querySelector(".content-inner");
- // blank content element might screw up onclick selection and keyboard moving
- if (container.textContent.length == 0)
- container.innerHTML += "&nbsp;";
+ container.innerHTML = row.getAttribute("data-content").trim();
- row.removeAttribute("data-content");
+ // blank content element might screw up onclick selection and keyboard moving
+ if (container.textContent.length == 0)
+ container.innerHTML += "&nbsp;";
- PluginHost.run(PluginHost.HOOK_ARTICLE_RENDERED_CDM, row);
- }
- },
- view: function (id, noexpand) {
- this.setActive(id);
-
- if (!noexpand) {
- const hl = Headlines.objectById(id);
-
- if (hl) {
-
- const comments = this.formatComments(hl);
- const originally_from = this.formatOriginallyFrom(hl);
-
- const article = `<div class="post post-${hl.id}" data-article-id="${hl.id}">
- <div class="header">
- <div class="row">
- <div class="title"><a target="_blank" rel="noopener noreferrer"
- title="${escapeHtml(hl.title)}"
- href="${escapeHtml(hl.link)}">${hl.title}</a></div>
- <div class="date">${hl.updated_long}</div>
- </div>
- <div class="row">
- <div class="buttons left">${hl.buttons_left}</div>
- <div class="comments">${comments}</div>
- <div class="author">${hl.author}</div>
- <i class="material-icons">label_outline</i>
- <span id="ATSTR-${hl.id}">${hl.tags_str}</span>
- &nbsp;<a title="${__("Edit tags for this article")}" href="#"
- onclick="Article.editTags(${hl.id})">(+)</a>
- <div class="buttons right">${hl.buttons}</div>
- </div>
+ // in expandable mode, save content for later, so that we can pack unfocused rows back
+ if (App.isCombinedMode() && $("main").hasClassName("expandable"))
+ row.setAttribute("data-content-original", row.getAttribute("data-content"));
+
+ row.removeAttribute("data-content");
+
+ PluginHost.run(PluginHost.HOOK_ARTICLE_RENDERED_CDM, row);
+ }
+ },
+ pack: function(row) {
+ if (row.hasAttribute("data-content-original")) {
+ console.log("packing", row.id);
+ row.setAttribute("data-content", row.getAttribute("data-content-original"));
+ row.removeAttribute("data-content-original");
+
+ row.querySelector(".content-inner").innerHTML = "&nbsp;";
+ }
+ },
+ view: function (id, no_expand) {
+ this.setActive(id);
+ Headlines.scrollToArticleId(id);
+
+ if (!no_expand) {
+ const hl = Headlines.objectById(id);
+
+ if (hl) {
+
+ const comments = this.formatComments(hl);
+ const originally_from = this.formatOriginallyFrom(hl);
+
+ const article = `<div class="post post-${hl.id}" data-article-id="${hl.id}">
+ <div class="header">
+ <div class="row">
+ <div class="title"><a target="_blank" rel="noopener noreferrer"
+ title="${App.escapeHtml(hl.title)}"
+ href="${App.escapeHtml(hl.link)}">${hl.title}</a></div>
+ <div class="date">${hl.updated_long}</div>
</div>
- <div id="POSTNOTE-${hl.id}">${hl.note}</div>
- <div class="content" lang="${hl.lang ? hl.lang : 'en'}">
- ${originally_from}
- ${hl.content}
- ${hl.enclosures}
+ <div class="row">
+ <div class="buttons left">${hl.buttons_left}</div>
+ <div class="comments">${comments}</div>
+ <div class="author">${hl.author}</div>
+ <i class="material-icons">label_outline</i>
+ <span id="ATSTR-${hl.id}">${hl.tags_str}</span>
+ &nbsp;<a title="${__("Edit tags for this article")}" href="#"
+ onclick="Article.editTags(${hl.id})">(+)</a>
+ <div class="buttons right">${hl.buttons}</div>
</div>
- </div>`;
+ </div>
+ <div id="POSTNOTE-${hl.id}">${hl.note}</div>
+ <div class="content" lang="${hl.lang ? hl.lang : 'en'}">
+ ${originally_from}
+ ${hl.content}
+ ${hl.enclosures}
+ </div>
+ </div>`;
- Headlines.toggleUnread(id, 0);
- this.render(article);
- }
+ Headlines.toggleUnread(id, 0);
+ this.render(article);
}
+ }
- return false;
- },
- editTags: function (id) {
- const query = "backend.php?op=article&method=editArticleTags&param=" + encodeURIComponent(id);
+ return false;
+ },
+ editTags: function (id) {
+ if (dijit.byId("editTagsDlg"))
+ dijit.byId("editTagsDlg").destroyRecursive();
- if (dijit.byId("editTagsDlg"))
- dijit.byId("editTagsDlg").destroyRecursive();
+ xhrPost("backend.php", {op: "article", method: "editarticletags", param: id}, (transport) => {
const dialog = new dijit.Dialog({
id: "editTagsDlg",
title: __("Edit article Tags"),
style: "width: 600px",
+ content: transport.responseText,
execute: function () {
if (this.validate()) {
Notify.progress("Saving article tags...", true);
@@ -264,7 +293,6 @@ define(["dojo/_base/declare"], function (declare) {
});
}
},
- href: query
});
const tmph = dojo.connect(dialog, 'onLoad', function () {
@@ -276,39 +304,31 @@ define(["dojo/_base/declare"], function (declare) {
});
dialog.show();
- },
- cdmScrollToId: function (id, force, event) {
- const ctr = $("headlines-frame");
- const e = $("RROW-" + id);
- const is_expanded = App.getInitParam("cdm_expanded");
-
- if (!e || !ctr) return;
- if (force || is_expanded || e.offsetTop + e.offsetHeight > (ctr.scrollTop + ctr.offsetHeight) ||
- e.offsetTop < ctr.scrollTop) {
+ });
- if (event && event.repeat || !is_expanded) {
- ctr.addClassName("forbid-smooth-scroll");
- window.clearTimeout(this._scroll_reset_timeout);
+ },
+ cdmMoveToId: function (id, params) {
+ params = params || {};
- this._scroll_reset_timeout = window.setTimeout(() => {
- if (ctr) ctr.removeClassName("forbid-smooth-scroll");
- }, 250)
+ const force_to_top = params.force_to_top || false;
- } else {
- ctr.removeClassName("forbid-smooth-scroll");
- }
+ const ctr = $("headlines-frame");
+ const row = $("RROW-" + id);
- ctr.scrollTop = e.offsetTop;
+ if (!row || !ctr) return;
- Element.hide("floatingTitle");
- }
- },
- setActive: function (id) {
- console.log("setActive", id);
+ if (force_to_top || !App.Scrollable.fitsInContainer(row, ctr)) {
+ ctr.scrollTop = row.offsetTop;
+ }
+ },
+ setActive: function (id) {
+ if (id != Article.getActive()) {
+ console.log("setActive", id, "was", Article.getActive());
- $$("div[id*=RROW][class*=active]").each((e) => {
- e.removeClassName("active");
+ $$("div[id*=RROW][class*=active]").each((row) => {
+ row.removeClassName("active");
+ Article.pack(row);
});
const row = $("RROW-" + id);
@@ -321,50 +341,29 @@ define(["dojo/_base/declare"], function (declare) {
PluginHost.run(PluginHost.HOOK_ARTICLE_SET_ACTIVE, row.getAttribute("data-article-id"));
}
- },
- getActive: function () {
- const row = document.querySelector("#headlines-frame > div[id*=RROW][class*=active]");
-
- if (row)
- return row.getAttribute("data-article-id");
- else
- return 0;
- },
- scrollByPages: function (page_offset, event) {
- const elem = App.isCombinedMode() ? $("headlines-frame") : $("content-insert");
-
- const offset = elem.offsetHeight * page_offset * 0.99;
-
- this.scroll(offset, event);
- },
- scroll: function (offset, event) {
-
- const elem = App.isCombinedMode() ? $("headlines-frame") : $("content-insert");
-
- if (event && event.repeat) {
- elem.addClassName("forbid-smooth-scroll");
- window.clearTimeout(this._scroll_reset_timeout);
-
- this._scroll_reset_timeout = window.setTimeout(() => {
- if (elem) elem.removeClassName("forbid-smooth-scroll");
- }, 250)
-
- } else {
- elem.removeClassName("forbid-smooth-scroll");
- }
-
- elem.scrollTop += offset;
- },
- mouseIn: function (id) {
- this.post_under_pointer = id;
- },
- mouseOut: function (id) {
- this.post_under_pointer = false;
- },
- getUnderPointer: function () {
- return this.post_under_pointer;
}
+ },
+ getActive: function () {
+ const row = document.querySelector("#headlines-frame > div[id*=RROW][class*=active]");
+
+ if (row)
+ return row.getAttribute("data-article-id");
+ else
+ return 0;
+ },
+ scrollByPages: function (page_offset) {
+ App.Scrollable.scrollByPages($("content-insert"), page_offset);
+ },
+ scroll: function (offset) {
+ App.Scrollable.scroll($("content-insert"), offset);
+ },
+ mouseIn: function (id) {
+ this.post_under_pointer = id;
+ },
+ mouseOut: function (/* id */) {
+ this.post_under_pointer = false;
+ },
+ getUnderPointer: function () {
+ return this.post_under_pointer;
}
-
- return Article;
-});
+}
diff --git a/js/CommonDialogs.js b/js/CommonDialogs.js
index c6d476de0..c59ef9b25 100644
--- a/js/CommonDialogs.js
+++ b/js/CommonDialogs.js
@@ -1,8 +1,9 @@
'use strict'
-/* global __, ngettext */
-define(["dojo/_base/declare"], function (declare) {
- // noinspection JSUnusedGlobalSymbols
- CommonDialogs = {
+
+/* global __, ngettext, dojo, dijit, Notify, App, Feeds, $$, xhrPost, xhrJson, Tables, Effect */
+
+/* exported CommonDialogs */
+const CommonDialogs = {
closeInfoBox: function() {
const dialog = dijit.byId("infoBox");
if (dialog) dialog.hide();
@@ -45,18 +46,20 @@ define(["dojo/_base/declare"], function (declare) {
xhr.onload = function () {
switch (parseInt(this.responseText)) {
case 0:
- Notify.info("Upload complete.");
+ {
+ Notify.info("Upload complete.");
- if (App.isPrefs())
- dijit.byId("feedTree").reload();
- else
- Feeds.reload();
+ if (App.isPrefs())
+ dijit.byId("feedTree").reload();
+ else
+ Feeds.reload();
- const icon = $$(".feed-editor-icon")[0];
+ const icon = $$(".feed-editor-icon")[0];
- if (icon)
- icon.src = icon.src.replace(/\?[0-9]+$/, "?" + new Date().getTime());
+ if (icon)
+ icon.src = icon.src.replace(/\?[0-9]+$/, "?" + new Date().getTime());
+ }
break;
case 1:
Notify.error("Upload failed: icon is too big.");
@@ -72,115 +75,120 @@ define(["dojo/_base/declare"], function (declare) {
return false;
},
quickAddFeed: function() {
- const query = "backend.php?op=feeds&method=quickAddFeed";
// overlapping widgets
if (dijit.byId("batchSubDlg")) dijit.byId("batchSubDlg").destroyRecursive();
if (dijit.byId("feedAddDlg")) dijit.byId("feedAddDlg").destroyRecursive();
- const dialog = new dijit.Dialog({
- id: "feedAddDlg",
- title: __("Subscribe to Feed"),
- style: "width: 600px",
- show_error: function (msg) {
- const elem = $("fadd_error_message");
-
- elem.innerHTML = msg;
-
- if (!Element.visible(elem))
- new Effect.Appear(elem);
-
- },
- execute: function () {
- if (this.validate()) {
- console.log(dojo.objectToQuery(this.attr('value')));
-
- const feed_url = this.attr('value').feed;
-
- Element.show("feed_add_spinner");
- Element.hide("fadd_error_message");
-
- xhrPost("backend.php", this.attr('value'), (transport) => {
- try {
-
- try {
- var reply = JSON.parse(transport.responseText);
- } catch (e) {
- Element.hide("feed_add_spinner");
- alert(__("Failed to parse output. This can indicate server timeout and/or network issues. Backend output was logged to browser console."));
- console.log('quickAddFeed, backend returned:' + transport.responseText);
- return;
- }
-
- const rc = reply['result'];
+ xhrPost("backend.php",
+ {op: "feeds", method: "quickAddFeed"},
+ (transport) => {
- Notify.close();
- Element.hide("feed_add_spinner");
+ const dialog = new dijit.Dialog({
+ id: "feedAddDlg",
+ title: __("Subscribe to Feed"),
+ style: "width: 600px",
+ content: transport.responseText,
+ show_error: function (msg) {
+ const elem = $("fadd_error_message");
- console.log(rc);
+ elem.innerHTML = msg;
- switch (parseInt(rc['code'])) {
- case 1:
- dialog.hide();
- Notify.info(__("Subscribed to %s").replace("%s", feed_url));
+ if (!Element.visible(elem))
+ new Effect.Appear(elem);
- if (App.isPrefs())
- dijit.byId("feedTree").reload();
- else
- Feeds.reload();
+ },
+ execute: function () {
+ if (this.validate()) {
+ console.log(dojo.objectToQuery(this.attr('value')));
- break;
- case 2:
- dialog.show_error(__("Specified URL seems to be invalid."));
- break;
- case 3:
- dialog.show_error(__("Specified URL doesn't seem to contain any feeds."));
- break;
- case 4:
- const feeds = rc['feeds'];
+ const feed_url = this.attr('value').feed;
- Element.show("fadd_multiple_notify");
+ Element.show("feed_add_spinner");
+ Element.hide("fadd_error_message");
- const select = dijit.byId("feedDlg_feedContainerSelect");
+ xhrPost("backend.php", this.attr('value'), (transport) => {
+ try {
- while (select.getOptions().length > 0)
- select.removeOption(0);
+ let reply;
- select.addOption({value: '', label: __("Expand to select feed")});
+ try {
+ reply = JSON.parse(transport.responseText);
+ } catch (e) {
+ Element.hide("feed_add_spinner");
+ alert(__("Failed to parse output. This can indicate server timeout and/or network issues. Backend output was logged to browser console."));
+ console.log('quickAddFeed, backend returned:' + transport.responseText);
+ return;
+ }
- let count = 0;
- for (const feedUrl in feeds) {
- if (feeds.hasOwnProperty(feedUrl)) {
- select.addOption({value: feedUrl, label: feeds[feedUrl]});
- count++;
+ const rc = reply['result'];
+
+ Notify.close();
+ Element.hide("feed_add_spinner");
+
+ console.log(rc);
+
+ switch (parseInt(rc['code'])) {
+ case 1:
+ dialog.hide();
+ Notify.info(__("Subscribed to %s").replace("%s", feed_url));
+
+ if (App.isPrefs())
+ dijit.byId("feedTree").reload();
+ else
+ Feeds.reload();
+
+ break;
+ case 2:
+ dialog.show_error(__("Specified URL seems to be invalid."));
+ break;
+ case 3:
+ dialog.show_error(__("Specified URL doesn't seem to contain any feeds."));
+ break;
+ case 4:
+ {
+ const feeds = rc['feeds'];
+
+ Element.show("fadd_multiple_notify");
+
+ const select = dijit.byId("feedDlg_feedContainerSelect");
+
+ while (select.getOptions().length > 0)
+ select.removeOption(0);
+
+ select.addOption({value: '', label: __("Expand to select feed")});
+
+ for (const feedUrl in feeds) {
+ if (feeds.hasOwnProperty(feedUrl)) {
+ select.addOption({value: feedUrl, label: feeds[feedUrl]});
+ }
+ }
+
+ Effect.Appear('feedDlg_feedsContainer', {duration: 0.5});
+ }
+ break;
+ case 5:
+ dialog.show_error(__("Couldn't download the specified URL: %s").replace("%s", rc['message']));
+ break;
+ case 6:
+ dialog.show_error(__("XML validation failed: %s").replace("%s", rc['message']));
+ break;
+ case 0:
+ dialog.show_error(__("You are already subscribed to this feed."));
+ break;
}
- }
- Effect.Appear('feedDlg_feedsContainer', {duration: 0.5});
-
- break;
- case 5:
- dialog.show_error(__("Couldn't download the specified URL: %s").replace("%s", rc['message']));
- break;
- case 6:
- dialog.show_error(__("XML validation failed: %s").replace("%s", rc['message']));
- break;
- case 0:
- dialog.show_error(__("You are already subscribed to this feed."));
- break;
+ } catch (e) {
+ console.error(transport.responseText);
+ App.Error.report(e);
+ }
+ });
}
-
- } catch (e) {
- console.error(transport.responseText);
- App.Error.report(e);
- }
+ },
});
- }
- },
- href: query
- });
- dialog.show();
+ dialog.show();
+ });
},
showFeedsWithErrors: function() {
const query = {op: "pref-feeds", method: "feedsWithErrors"};
@@ -436,7 +444,7 @@ define(["dojo/_base/declare"], function (declare) {
Notify.close();
if (App.isPrefs())
- dijit.byId("feedTree").reload();
+ dijit.byId("feedTree") && dijit.byId("feedTree").reload();
else
Feeds.reload();
@@ -478,6 +486,3 @@ define(["dojo/_base/declare"], function (declare) {
return false;
}
};
-
- return CommonDialogs;
-});
diff --git a/js/CommonFilters.js b/js/CommonFilters.js
index 1538a3fb3..9676abe9e 100644
--- a/js/CommonFilters.js
+++ b/js/CommonFilters.js
@@ -1,398 +1,383 @@
'use strict'
-/* global __, ngettext */
-define(["dojo/_base/declare"], function (declare) {
- Filters = {
- filterDlgCheckRegExp: function(sender) {
- const tooltip = dijit.byId("filterDlg_regExp_tip").domNode;
- try {
- sender.domNode.removeClassName("invalid");
- sender.domNode.removeClassName("valid");
+/* global __, App, Article, Lists, Effect */
+/* global xhrPost, dojo, dijit, Notify, $$, Feeds */
- new RegExp("/" + sender.value + "/");
+const Filters = {
+ filterDlgCheckAction: function(sender) {
+ const action = sender.value;
- sender.domNode.addClassName("valid");
- tooltip.innerText = __("Regular expression, without outer delimiters (i.e. slashes)");
+ const action_param = $("filterDlg_paramBox");
- } catch (e) {
- sender.domNode.addClassName("invalid");
+ if (!action_param) {
+ console.log("filterDlgCheckAction: can't find action param box!");
+ return;
+ }
- tooltip.innerText = e.message;
- }
- },
- filterDlgCheckAction: function(sender) {
- const action = sender.value;
+ // if selected action supports parameters, enable params field
+ if (action == 4 || action == 6 || action == 7 || action == 9) {
+ new Effect.Appear(action_param, {duration: 0.5});
- const action_param = $("filterDlg_paramBox");
+ Element.hide(dijit.byId("filterDlg_actionParam").domNode);
+ Element.hide(dijit.byId("filterDlg_actionParamLabel").domNode);
+ Element.hide(dijit.byId("filterDlg_actionParamPlugin").domNode);
- if (!action_param) {
- console.log("filterDlgCheckAction: can't find action param box!");
- return;
+ if (action == 7) {
+ Element.show(dijit.byId("filterDlg_actionParamLabel").domNode);
+ } else if (action == 9) {
+ Element.show(dijit.byId("filterDlg_actionParamPlugin").domNode);
+ } else {
+ Element.show(dijit.byId("filterDlg_actionParam").domNode);
}
- // if selected action supports parameters, enable params field
- if (action == 4 || action == 6 || action == 7 || action == 9) {
- new Effect.Appear(action_param, {duration: 0.5});
-
- Element.hide(dijit.byId("filterDlg_actionParam").domNode);
- Element.hide(dijit.byId("filterDlg_actionParamLabel").domNode);
- Element.hide(dijit.byId("filterDlg_actionParamPlugin").domNode);
+ } else {
+ Element.hide(action_param);
+ }
+ },
+ createNewRuleElement: function(parentNode, replaceNode) {
+ const form = document.forms["filter_new_rule_form"];
+ const query = {op: "pref-filters", method: "printrulename", rule: dojo.formToJson(form)};
- if (action == 7) {
- Element.show(dijit.byId("filterDlg_actionParamLabel").domNode);
- } else if (action == 9) {
- Element.show(dijit.byId("filterDlg_actionParamPlugin").domNode);
+ xhrPost("backend.php", query, (transport) => {
+ try {
+ const li = dojo.create("li");
+
+ const cb = dojo.create("input", {type: "checkbox"}, li);
+
+ new dijit.form.CheckBox({
+ onChange: function () {
+ Lists.onRowChecked(this);
+ },
+ }, cb);
+
+ dojo.create("input", {
+ type: "hidden",
+ name: "rule[]",
+ value: dojo.formToJson(form)
+ }, li);
+
+ dojo.create("span", {
+ onclick: function () {
+ dijit.byId('filterEditDlg').editRule(this);
+ },
+ innerHTML: transport.responseText
+ }, li);
+
+ if (replaceNode) {
+ parentNode.replaceChild(li, replaceNode);
} else {
- Element.show(dijit.byId("filterDlg_actionParam").domNode);
+ parentNode.appendChild(li);
}
-
- } else {
- Element.hide(action_param);
+ } catch (e) {
+ App.Error.report(e);
}
- },
- createNewRuleElement: function(parentNode, replaceNode) {
- const form = document.forms["filter_new_rule_form"];
- const query = {op: "pref-filters", method: "printrulename", rule: dojo.formToJson(form)};
-
- xhrPost("backend.php", query, (transport) => {
- try {
- const li = dojo.create("li");
-
- const cb = dojo.create("input", {type: "checkbox"}, li);
-
- new dijit.form.CheckBox({
- onChange: function () {
- Lists.onRowChecked(this);
- },
- }, cb);
-
- dojo.create("input", {
- type: "hidden",
- name: "rule[]",
- value: dojo.formToJson(form)
- }, li);
-
- dojo.create("span", {
- onclick: function () {
- dijit.byId('filterEditDlg').editRule(this);
- },
- innerHTML: transport.responseText
- }, li);
-
- if (replaceNode) {
- parentNode.replaceChild(li, replaceNode);
- } else {
- parentNode.appendChild(li);
- }
- } catch (e) {
- App.Error.report(e);
+ });
+ },
+ createNewActionElement: function(parentNode, replaceNode) {
+ const form = document.forms["filter_new_action_form"];
+
+ if (form.action_id.value == 7) {
+ form.action_param.value = form.action_param_label.value;
+ } else if (form.action_id.value == 9) {
+ form.action_param.value = form.action_param_plugin.value;
+ }
+
+ const query = {
+ op: "pref-filters", method: "printactionname",
+ action: dojo.formToJson(form)
+ };
+
+ xhrPost("backend.php", query, (transport) => {
+ try {
+ const li = dojo.create("li");
+
+ const cb = dojo.create("input", {type: "checkbox"}, li);
+
+ new dijit.form.CheckBox({
+ onChange: function () {
+ Lists.onRowChecked(this);
+ },
+ }, cb);
+
+ dojo.create("input", {
+ type: "hidden",
+ name: "action[]",
+ value: dojo.formToJson(form)
+ }, li);
+
+ dojo.create("span", {
+ onclick: function () {
+ dijit.byId('filterEditDlg').editAction(this);
+ },
+ innerHTML: transport.responseText
+ }, li);
+
+ if (replaceNode) {
+ parentNode.replaceChild(li, replaceNode);
+ } else {
+ parentNode.appendChild(li);
}
- });
- },
- createNewActionElement: function(parentNode, replaceNode) {
- const form = document.forms["filter_new_action_form"];
-
- if (form.action_id.value == 7) {
- form.action_param.value = form.action_param_label.value;
- } else if (form.action_id.value == 9) {
- form.action_param.value = form.action_param_plugin.value;
- }
-
- const query = {
- op: "pref-filters", method: "printactionname",
- action: dojo.formToJson(form)
- };
-
- xhrPost("backend.php", query, (transport) => {
- try {
- const li = dojo.create("li");
-
- const cb = dojo.create("input", {type: "checkbox"}, li);
-
- new dijit.form.CheckBox({
- onChange: function () {
- Lists.onRowChecked(this);
- },
- }, cb);
-
- dojo.create("input", {
- type: "hidden",
- name: "action[]",
- value: dojo.formToJson(form)
- }, li);
-
- dojo.create("span", {
- onclick: function () {
- dijit.byId('filterEditDlg').editAction(this);
- },
- innerHTML: transport.responseText
- }, li);
-
- if (replaceNode) {
- parentNode.replaceChild(li, replaceNode);
- } else {
- parentNode.appendChild(li);
- }
- } catch (e) {
- App.Error.report(e);
+ } catch (e) {
+ App.Error.report(e);
+ }
+ });
+ },
+ addFilterRule: function(replaceNode, ruleStr) {
+ if (dijit.byId("filterNewRuleDlg"))
+ dijit.byId("filterNewRuleDlg").destroyRecursive();
+
+ const rule_dlg = new dijit.Dialog({
+ id: "filterNewRuleDlg",
+ title: ruleStr ? __("Edit rule") : __("Add rule"),
+ style: "width: 600px",
+ execute: function () {
+ if (this.validate()) {
+ Filters.createNewRuleElement($("filterDlg_Matches"), replaceNode);
+ this.hide();
}
- });
- },
- addFilterRule: function(replaceNode, ruleStr) {
- if (dijit.byId("filterNewRuleDlg"))
- dijit.byId("filterNewRuleDlg").destroyRecursive();
-
- const query = "backend.php?op=pref-filters&method=newrule&rule=" +
- encodeURIComponent(ruleStr);
-
- const rule_dlg = new dijit.Dialog({
- id: "filterNewRuleDlg",
- title: ruleStr ? __("Edit rule") : __("Add rule"),
- style: "width: 600px",
- execute: function () {
- if (this.validate()) {
- Filters.createNewRuleElement($("filterDlg_Matches"), replaceNode);
- this.hide();
- }
- },
- href: query
- });
-
- rule_dlg.show();
- },
- addFilterAction: function(replaceNode, actionStr) {
- if (dijit.byId("filterNewActionDlg"))
- dijit.byId("filterNewActionDlg").destroyRecursive();
-
- const query = "backend.php?op=pref-filters&method=newaction&action=" +
- encodeURIComponent(actionStr);
-
- const rule_dlg = new dijit.Dialog({
- id: "filterNewActionDlg",
- title: actionStr ? __("Edit action") : __("Add action"),
- style: "width: 600px",
- execute: function () {
- if (this.validate()) {
- Filters.createNewActionElement($("filterDlg_Actions"), replaceNode);
- this.hide();
- }
- },
- href: query
- });
+ },
+ content: __('Loading, please wait...'),
+ });
- rule_dlg.show();
- },
- editFilterTest: function(query) {
+ const tmph = dojo.connect(rule_dlg, "onShow", null, function (/* e */) {
+ dojo.disconnect(tmph);
- if (dijit.byId("filterTestDlg"))
- dijit.byId("filterTestDlg").destroyRecursive();
-
- const test_dlg = new dijit.Dialog({
- id: "filterTestDlg",
- title: "Test Filter",
- style: "width: 600px",
- results: 0,
- limit: 100,
- max_offset: 10000,
- getTestResults: function (query, offset) {
- const updquery = query + "&offset=" + offset + "&limit=" + test_dlg.limit;
-
- console.log("getTestResults:" + offset);
+ xhrPost("backend.php", {op: 'pref-filters', method: 'newrule', rule: ruleStr}, (transport) => {
+ rule_dlg.attr('content', transport.responseText);
+ });
+ });
+
+ rule_dlg.show();
+ },
+ addFilterAction: function(replaceNode, actionStr) {
+ if (dijit.byId("filterNewActionDlg"))
+ dijit.byId("filterNewActionDlg").destroyRecursive();
+
+ const query = "backend.php?op=pref-filters&method=newaction&action=" +
+ encodeURIComponent(actionStr);
+
+ const rule_dlg = new dijit.Dialog({
+ id: "filterNewActionDlg",
+ title: actionStr ? __("Edit action") : __("Add action"),
+ style: "width: 600px",
+ execute: function () {
+ if (this.validate()) {
+ Filters.createNewActionElement($("filterDlg_Actions"), replaceNode);
+ this.hide();
+ }
+ },
+ href: query
+ });
- xhrPost("backend.php", updquery, (transport) => {
- try {
- const result = JSON.parse(transport.responseText);
+ rule_dlg.show();
+ },
+ editFilterTest: function(params) {
- if (result && dijit.byId("filterTestDlg") && dijit.byId("filterTestDlg").open) {
- test_dlg.results += result.length;
+ if (dijit.byId("filterTestDlg"))
+ dijit.byId("filterTestDlg").destroyRecursive();
- console.log("got results:" + result.length);
+ const test_dlg = new dijit.Dialog({
+ id: "filterTestDlg",
+ title: "Test Filter",
+ style: "width: 600px",
+ results: 0,
+ limit: 100,
+ max_offset: 10000,
+ getTestResults: function (params, offset) {
+ params.method = 'testFilterDo';
+ params.offset = offset;
+ params.limit = test_dlg.limit;
- $("prefFilterProgressMsg").innerHTML = __("Looking for articles (%d processed, %f found)...")
- .replace("%f", test_dlg.results)
- .replace("%d", offset);
+ console.log("getTestResults:" + offset);
- console.log(offset + " " + test_dlg.max_offset);
+ xhrPost("backend.php", params, (transport) => {
+ try {
+ const result = JSON.parse(transport.responseText);
- for (let i = 0; i < result.length; i++) {
- const tmp = new Element("table");
- tmp.innerHTML = result[i];
- dojo.parser.parse(tmp);
+ if (result && dijit.byId("filterTestDlg") && dijit.byId("filterTestDlg").open) {
+ test_dlg.results += result.length;
- $("prefFilterTestResultList").innerHTML += tmp.innerHTML;
- }
+ console.log("got results:" + result.length);
- if (test_dlg.results < 30 && offset < test_dlg.max_offset) {
+ $("prefFilterProgressMsg").innerHTML = __("Looking for articles (%d processed, %f found)...")
+ .replace("%f", test_dlg.results)
+ .replace("%d", offset);
- // get the next batch
- window.setTimeout(function () {
- test_dlg.getTestResults(query, offset + test_dlg.limit);
- }, 0);
+ console.log(offset + " " + test_dlg.max_offset);
- } else {
- // all done
+ for (let i = 0; i < result.length; i++) {
+ const tmp = dojo.create("table", { innerHTML: result[i]});
- Element.hide("prefFilterLoadingIndicator");
+ $("prefFilterTestResultList").innerHTML += tmp.innerHTML;
+ }
- if (test_dlg.results == 0) {
- $("prefFilterTestResultList").innerHTML = `<tr><td align='center'>
- ${__('No recent articles matching this filter have been found.')}</td></tr>`;
- $("prefFilterProgressMsg").innerHTML = "Articles matching this filter:";
- } else {
- $("prefFilterProgressMsg").innerHTML = __("Found %d articles matching this filter:")
- .replace("%d", test_dlg.results);
- }
+ if (test_dlg.results < 30 && offset < test_dlg.max_offset) {
- }
+ // get the next batch
+ window.setTimeout(function () {
+ test_dlg.getTestResults(params, offset + test_dlg.limit);
+ }, 0);
- } else if (!result) {
- console.log("getTestResults: can't parse results object");
+ } else {
+ // all done
Element.hide("prefFilterLoadingIndicator");
- Notify.error("Error while trying to get filter test results.");
+ if (test_dlg.results == 0) {
+ $("prefFilterTestResultList").innerHTML = `<tr><td align='center'>
+ ${__('No recent articles matching this filter have been found.')}</td></tr>`;
+ $("prefFilterProgressMsg").innerHTML = "Articles matching this filter:";
+ } else {
+ $("prefFilterProgressMsg").innerHTML = __("Found %d articles matching this filter:")
+ .replace("%d", test_dlg.results);
+ }
- } else {
- console.log("getTestResults: dialog closed, bailing out.");
}
- } catch (e) {
- App.Error.report(e);
- }
- });
- },
- href: query
- });
+ } else if (!result) {
+ console.log("getTestResults: can't parse results object");
- dojo.connect(test_dlg, "onLoad", null, function (e) {
- test_dlg.getTestResults(query, 0);
- });
+ Element.hide("prefFilterLoadingIndicator");
- test_dlg.show();
- },
- quickAddFilter: function() {
- let query;
+ Notify.error("Error while trying to get filter test results.");
- if (!App.isPrefs()) {
- query = {
- op: "pref-filters", method: "newfilter",
- feed: Feeds.getActive(), is_cat: Feeds.activeIsCat()
- };
- } else {
- query = {op: "pref-filters", method: "newfilter"};
- }
-
- console.log('quickAddFilter', query);
-
- if (dijit.byId("feedEditDlg"))
- dijit.byId("feedEditDlg").destroyRecursive();
-
- if (dijit.byId("filterEditDlg"))
- dijit.byId("filterEditDlg").destroyRecursive();
-
- const dialog = new dijit.Dialog({
- id: "filterEditDlg",
- title: __("Create Filter"),
- style: "width: 600px",
- test: function () {
- const query = "backend.php?" + dojo.formToQuery("filter_new_form") + "&savemode=test";
-
- Filters.editFilterTest(query);
- },
- selectRules: function (select) {
- Lists.select("filterDlg_Matches", select);
- },
- selectActions: function (select) {
- Lists.select("filterDlg_Actions", select);
- },
- editRule: function (e) {
- const li = e.parentNode;
- const rule = li.getElementsByTagName("INPUT")[1].value;
- Filters.addFilterRule(li, rule);
- },
- editAction: function (e) {
- const li = e.parentNode;
- const action = li.getElementsByTagName("INPUT")[1].value;
- Filters.addFilterAction(li, action);
- },
- addAction: function () {
- Filters.addFilterAction();
- },
- addRule: function () {
- Filters.addFilterRule();
- },
- deleteAction: function () {
- $$("#filterDlg_Actions li[class*=Selected]").each(function (e) {
- e.parentNode.removeChild(e)
- });
- },
- deleteRule: function () {
- $$("#filterDlg_Matches li[class*=Selected]").each(function (e) {
- e.parentNode.removeChild(e)
- });
- },
- execute: function () {
- if (this.validate()) {
+ } else {
+ console.log("getTestResults: dialog closed, bailing out.");
+ }
+ } catch (e) {
+ App.Error.report(e);
+ }
- const query = dojo.formToQuery("filter_new_form");
+ });
+ },
+ href: "backend.php?op=pref-filters&method=testFilterDlg"
+ });
+
+ dojo.connect(test_dlg, "onLoad", null, function (/* e */) {
+ test_dlg.getTestResults(params, 0);
+ });
+
+ test_dlg.show();
+ },
+ quickAddFilter: function() {
+ let query;
+
+ if (!App.isPrefs()) {
+ query = {
+ op: "pref-filters", method: "newfilter",
+ feed: Feeds.getActive(), is_cat: Feeds.activeIsCat()
+ };
+ } else {
+ query = {op: "pref-filters", method: "newfilter"};
+ }
+
+ console.log('quickAddFilter', query);
+
+ if (dijit.byId("feedEditDlg"))
+ dijit.byId("feedEditDlg").destroyRecursive();
+
+ if (dijit.byId("filterEditDlg"))
+ dijit.byId("filterEditDlg").destroyRecursive();
+
+ const dialog = new dijit.Dialog({
+ id: "filterEditDlg",
+ title: __("Create Filter"),
+ style: "width: 600px",
+ test: function () {
+ Filters.editFilterTest(dojo.formToObject("filter_new_form"));
+ },
+ selectRules: function (select) {
+ Lists.select("filterDlg_Matches", select);
+ },
+ selectActions: function (select) {
+ Lists.select("filterDlg_Actions", select);
+ },
+ editRule: function (e) {
+ const li = e.parentNode;
+ const rule = li.getElementsByTagName("INPUT")[1].value;
+ Filters.addFilterRule(li, rule);
+ },
+ editAction: function (e) {
+ const li = e.parentNode;
+ const action = li.getElementsByTagName("INPUT")[1].value;
+ Filters.addFilterAction(li, action);
+ },
+ addAction: function () {
+ Filters.addFilterAction();
+ },
+ addRule: function () {
+ Filters.addFilterRule();
+ },
+ deleteAction: function () {
+ $$("#filterDlg_Actions li[class*=Selected]").each(function (e) {
+ e.parentNode.removeChild(e)
+ });
+ },
+ deleteRule: function () {
+ $$("#filterDlg_Matches li[class*=Selected]").each(function (e) {
+ e.parentNode.removeChild(e)
+ });
+ },
+ execute: function () {
+ if (this.validate()) {
- xhrPost("backend.php", query, () => {
- if (App.isPrefs()) {
- dijit.byId("filterTree").reload();
- }
+ const query = dojo.formToQuery("filter_new_form");
- dialog.hide();
- });
- }
- },
- href: "backend.php?" + dojo.objectToQuery(query)
- });
+ xhrPost("backend.php", query, () => {
+ if (App.isPrefs()) {
+ dijit.byId("filterTree").reload();
+ }
- if (!App.isPrefs()) {
- const selectedText = getSelectionText();
+ dialog.hide();
+ });
+ }
+ },
+ href: "backend.php?" + dojo.objectToQuery(query)
+ });
- const lh = dojo.connect(dialog, "onLoad", function () {
- dojo.disconnect(lh);
+ if (!App.isPrefs()) {
+ /* global getSelectionText */
+ const selectedText = getSelectionText();
- if (selectedText != "") {
+ const lh = dojo.connect(dialog, "onLoad", function () {
+ dojo.disconnect(lh);
- const feed_id = Feeds.activeIsCat() ? 'CAT:' + parseInt(Feeds.getActive()) :
- Feeds.getActive();
+ if (selectedText != "") {
- const rule = {reg_exp: selectedText, feed_id: [feed_id], filter_type: 1};
+ const feed_id = Feeds.activeIsCat() ? 'CAT:' + parseInt(Feeds.getActive()) :
+ Feeds.getActive();
- Filters.addFilterRule(null, dojo.toJson(rule));
+ const rule = {reg_exp: selectedText, feed_id: [feed_id], filter_type: 1};
- } else {
+ Filters.addFilterRule(null, dojo.toJson(rule));
- const query = {op: "rpc", method: "getlinktitlebyid", id: Article.getActive()};
+ } else {
- xhrPost("backend.php", query, (transport) => {
- const reply = JSON.parse(transport.responseText);
+ const query = {op: "rpc", method: "getlinktitlebyid", id: Article.getActive()};
- let title = false;
+ xhrPost("backend.php", query, (transport) => {
+ const reply = JSON.parse(transport.responseText);
- if (reply && reply.title) title = reply.title;
+ let title = false;
- if (title || Feeds.getActive() || Feeds.activeIsCat()) {
+ if (reply && reply.title) title = reply.title;
- console.log(title + " " + Feeds.getActive());
+ if (title || Feeds.getActive() || Feeds.activeIsCat()) {
- const feed_id = Feeds.activeIsCat() ? 'CAT:' + parseInt(Feeds.getActive()) :
- Feeds.getActive();
+ console.log(title + " " + Feeds.getActive());
- const rule = {reg_exp: title, feed_id: [feed_id], filter_type: 1};
+ const feed_id = Feeds.activeIsCat() ? 'CAT:' + parseInt(Feeds.getActive()) :
+ Feeds.getActive();
- Filters.addFilterRule(null, dojo.toJson(rule));
- }
- });
- }
- });
- }
- dialog.show();
- },
- };
+ const rule = {reg_exp: title, feed_id: [feed_id], filter_type: 1};
- return Filters;
-});
+ Filters.addFilterRule(null, dojo.toJson(rule));
+ }
+ });
+ }
+ });
+ }
+ dialog.show();
+ },
+};
diff --git a/js/FeedStoreModel.js b/js/FeedStoreModel.js
index 7d8020871..736bfbed6 100644
--- a/js/FeedStoreModel.js
+++ b/js/FeedStoreModel.js
@@ -1,10 +1,12 @@
+/* global define, dijit */
+
define(["dojo/_base/declare", "dijit/tree/ForestStoreModel"], function (declare) {
return declare("fox.FeedStoreModel", dijit.tree.ForestStoreModel, {
getItemsInCategory: function (id) {
if (!this.store._itemsByIdentity) return undefined;
- let cat = this.store._itemsByIdentity['CAT:' + id];
+ const cat = this.store._itemsByIdentity['CAT:' + id];
if (cat && cat.items)
return cat.items;
@@ -18,6 +20,8 @@ define(["dojo/_base/declare", "dijit/tree/ForestStoreModel"], function (declare)
getFeedValue: function (feed, is_cat, key) {
if (!this.store._itemsByIdentity) return undefined;
+ let treeItem;
+
if (is_cat)
treeItem = this.store._itemsByIdentity['CAT:' + feed];
else
@@ -40,6 +44,8 @@ define(["dojo/_base/declare", "dijit/tree/ForestStoreModel"], function (declare)
if (!value) value = '';
if (!this.store._itemsByIdentity) return undefined;
+ let treeItem;
+
if (is_cat)
treeItem = this.store._itemsByIdentity['CAT:' + feed];
else
@@ -52,29 +58,31 @@ define(["dojo/_base/declare", "dijit/tree/ForestStoreModel"], function (declare)
if (!this.store._itemsByIdentity)
return null;
+ let treeItem;
+
if (is_cat) {
treeItem = this.store._itemsByIdentity['CAT:' + feed];
} else {
treeItem = this.store._itemsByIdentity['FEED:' + feed];
}
- let items = this.store._arrayOfAllItems;
+ const items = this.store._arrayOfAllItems;
for (let i = 0; i < items.length; i++) {
if (items[i] == treeItem) {
- for (var j = i + 1; j < items.length; j++) {
- let unread = this.store.getValue(items[j], 'unread');
- let id = this.store.getValue(items[j], 'id');
+ for (let j = i + 1; j < items.length; j++) {
+ const unread = this.store.getValue(items[j], 'unread');
+ const id = this.store.getValue(items[j], 'id');
if (unread > 0 && ((is_cat && id.match("CAT:")) || (!is_cat && id.match("FEED:")))) {
if (!is_cat || !(this.store.hasAttribute(items[j], 'parent_id') && this.store.getValue(items[j], 'parent_id') == feed)) return items[j];
}
}
- for (var j = 0; j < i; j++) {
- let unread = this.store.getValue(items[j], 'unread');
- let id = this.store.getValue(items[j], 'id');
+ for (let j = 0; j < i; j++) {
+ const unread = this.store.getValue(items[j], 'unread');
+ const id = this.store.getValue(items[j], 'id');
if (unread > 0 && ((is_cat && id.match("CAT:")) || (!is_cat && id.match("FEED:")))) {
if (!is_cat || !(this.store.hasAttribute(items[j], 'parent_id') && this.store.getValue(items[j], 'parent_id') == feed)) return items[j];
diff --git a/js/FeedTree.js b/js/FeedTree.js
index 85892b3d9..c61d8a50f 100755
--- a/js/FeedTree.js
+++ b/js/FeedTree.js
@@ -1,11 +1,46 @@
-/* global dijit */
-define(["dojo/_base/declare", "dojo/dom-construct", "dijit/Tree", "dijit/Menu"], function (declare, domConstruct) {
+/* eslint-disable prefer-rest-params */
+/* global __, dojo, dijit, define, App, Feeds, CommonDialogs */
+
+define(["dojo/_base/declare", "dojo/dom-construct", "dojo/_base/array", "dojo/cookie", "dijit/Tree", "dijit/Menu"], function (declare, domConstruct, array, cookie) {
return declare("fox.FeedTree", dijit.Tree, {
- _onContainerKeydown: function(/* Event */ e) {
+ // save state in localStorage instead of cookies
+ // reference: https://stackoverflow.com/a/27968996
+ _saveExpandedNodes: function(){
+ if(this.persist && this.cookieName){
+ var ary = [];
+ for(var id in this._openedNodes){
+ ary.push(id);
+ }
+ // Was:
+ // cookie(this.cookieName, ary.join(","), {expires: 365});
+ localStorage.setItem(this.cookieName, ary.join(","));
+ }
+ },
+ _initState: function(){
+ // summary:
+ // Load in which nodes should be opened automatically
+ this._openedNodes = {};
+ if(this.persist && this.cookieName){
+ // Was:
+ // var oreo = cookie(this.cookieName);
+ var oreo = localStorage.getItem(this.cookieName);
+ // migrate old data if nothing in localStorage
+ if(oreo == null || oreo === '') {
+ oreo = cookie(this.cookieName);
+ cookie(this.cookieName, null, { expires: -1 });
+ }
+ if(oreo){
+ array.forEach(oreo.split(','), function(item){
+ this._openedNodes[item] = true;
+ }, this);
+ }
+ }
+ },
+ _onContainerKeydown: function(/* Event */ /* e */) {
return; // Stop dijit.Tree from interpreting keystrokes
},
- _onContainerKeypress: function(/* Event */ e) {
+ _onContainerKeypress: function(/* Event */ /* e */) {
return; // Stop dijit.Tree from interpreting keystrokes
},
_createTreeNode: function(args) {
@@ -33,7 +68,7 @@ define(["dojo/_base/declare", "dojo/dom-construct", "dijit/Tree", "dijit/Menu"],
const id = args.item.id[0];
const bare_id = parseInt(id.substr(id.indexOf(':')+1));
- if (bare_id < LABEL_BASE_INDEX) {
+ if (bare_id < App.LABEL_BASE_INDEX) {
const label = dojo.create('i', { className: "material-icons icon icon-label", innerHTML: "label" });
//const fg_color = args.item.fg_color[0];
@@ -47,7 +82,7 @@ define(["dojo/_base/declare", "dojo/dom-construct", "dijit/Tree", "dijit/Menu"],
}
if (id.match("FEED:")) {
- let menu = new dijit.Menu();
+ const menu = new dijit.Menu();
menu.row_id = bare_id;
menu.addChild(new dijit.MenuItem({
@@ -66,8 +101,9 @@ define(["dojo/_base/declare", "dojo/dom-construct", "dijit/Tree", "dijit/Menu"],
menu.addChild(new dijit.MenuItem({
label: __("Debug feed"),
onClick: function() {
- window.open("backend.php?op=feeds&method=update_debugger&feed_id=" + this.getParent().row_id +
- "&csrf_token=" + App.getInitParam("csrf_token"));
+ /* global __csrf_token */
+ App.postOpenWindow("backend.php", {op: "feeds", method: "update_debugger",
+ feed_id: this.getParent().row_id, csrf_token: __csrf_token});
}}));
}
@@ -76,7 +112,7 @@ define(["dojo/_base/declare", "dojo/dom-construct", "dijit/Tree", "dijit/Menu"],
}
if (id.match("CAT:") && bare_id >= 0) {
- let menu = new dijit.Menu();
+ const menu = new dijit.Menu();
menu.row_id = bare_id;
menu.addChild(new dijit.MenuItem({
@@ -101,7 +137,7 @@ define(["dojo/_base/declare", "dojo/dom-construct", "dijit/Tree", "dijit/Menu"],
}
if (id.match("CAT:") && bare_id == -1) {
- let menu = new dijit.Menu();
+ const menu = new dijit.Menu();
menu.row_id = bare_id;
menu.addChild(new dijit.MenuItem({
@@ -145,15 +181,16 @@ define(["dojo/_base/declare", "dojo/dom-construct", "dijit/Tree", "dijit/Menu"],
}
},
getTooltip: function (item) {
- return [item.updated, item.error].filter(x => x && x != "").join(" - ");
+ return [item.updated, item.error].filter((x) => x && x != "").join(" - ");
},
getIconClass: function (item, opened) {
+ // eslint-disable-next-line no-nested-ternary
return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "feed-icon";
},
- getLabelClass: function (item, opened) {
+ getLabelClass: function (item/* , opened */) {
return (item.unread <= 0) ? "dijitTreeLabel" : "dijitTreeLabel Unread";
},
- getRowClass: function (item, opened) {
+ getRowClass: function (item/*, opened */) {
let rc = "dijitTreeRow";
const is_cat = String(item.id).indexOf('CAT:') != -1;
@@ -163,9 +200,9 @@ define(["dojo/_base/declare", "dojo/dom-construct", "dijit/Tree", "dijit/Menu"],
if (item.auxcounter > 0) rc += " Has_Aux";
if (item.markedcounter > 0) rc += " Has_Marked";
if (item.updates_disabled > 0) rc += " UpdatesDisabled";
- if (item.bare_id >= LABEL_BASE_INDEX && item.bare_id < 0 && !is_cat || item.bare_id == 0 && !is_cat) rc += " Special";
+ if (item.bare_id >= App.LABEL_BASE_INDEX && item.bare_id < 0 && !is_cat || item.bare_id == 0 && !is_cat) rc += " Special";
if (item.bare_id == -1 && is_cat) rc += " AlwaysVisible";
- if (item.bare_id < LABEL_BASE_INDEX) rc += " Label";
+ if (item.bare_id < App.LABEL_BASE_INDEX) rc += " Label";
return rc;
},
diff --git a/js/Feeds.js b/js/Feeds.js
index 7fa376984..9259cd547 100644
--- a/js/Feeds.js
+++ b/js/Feeds.js
@@ -1,232 +1,235 @@
'use strict'
-/* global __, ngettext */
-define(["dojo/_base/declare"], function (declare) {
- Feeds = {
- counters_last_request: 0,
- _active_feed_id: undefined,
- _active_feed_is_cat: false,
- infscroll_in_progress: 0,
- infscroll_disabled: 0,
- _infscroll_timeout: false,
- _search_query: false,
- last_search_query: [],
- _viewfeed_wait_timeout: false,
- _counters_prev: [],
- // NOTE: this implementation is incomplete
- // for general objects but good enough for counters
- // http://adripofjavascript.com/blog/drips/object-equality-in-javascript.html
- counterEquals: function(a, b) {
- // Create arrays of property names
- const aProps = Object.getOwnPropertyNames(a);
- const bProps = Object.getOwnPropertyNames(b);
-
- // If number of properties is different,
+
+/* global __, App, Headlines, xhrPost, dojo, dijit, Form, fox, PluginHost, Notify, $$ */
+
+const Feeds = {
+ counters_last_request: 0,
+ _active_feed_id: undefined,
+ _active_feed_is_cat: false,
+ infscroll_in_progress: 0,
+ infscroll_disabled: 0,
+ _infscroll_timeout: false,
+ _search_query: false,
+ last_search_query: [],
+ _viewfeed_wait_timeout: false,
+ _counters_prev: [],
+ // NOTE: this implementation is incomplete
+ // for general objects but good enough for counters
+ // http://adripofjavascript.com/blog/drips/object-equality-in-javascript.html
+ counterEquals: function(a, b) {
+ // Create arrays of property names
+ const aProps = Object.getOwnPropertyNames(a);
+ const bProps = Object.getOwnPropertyNames(b);
+
+ // If number of properties is different,
+ // objects are not equivalent
+ if (aProps.length != bProps.length) {
+ return false;
+ }
+
+ for (let i = 0; i < aProps.length; i++) {
+ const propName = aProps[i];
+
+ // If values of same property are not equal,
// objects are not equivalent
- if (aProps.length != bProps.length) {
+ if (a[propName] !== b[propName]) {
return false;
}
+ }
+
+ // If we made it this far, objects
+ // are considered equivalent
+ return true;
+ },
+ resetCounters: function () {
+ this._counters_prev = [];
+ },
+ parseCounters: function (elems) {
+ PluginHost.run(PluginHost.HOOK_COUNTERS_RECEIVED, elems);
+
+ for (let l = 0; l < elems.length; l++) {
+
+ if (Feeds._counters_prev[l] && this.counterEquals(elems[l], this._counters_prev[l])) {
+ continue;
+ }
- for (let i = 0; i < aProps.length; i++) {
- const propName = aProps[i];
+ const id = elems[l].id;
+ const kind = elems[l].kind;
+ const ctr = parseInt(elems[l].counter);
+ const error = elems[l].error;
+ const has_img = elems[l].has_img;
+ const updated = elems[l].updated;
- // If values of same property are not equal,
- // objects are not equivalent
- if (a[propName] !== b[propName]) {
- return false;
- }
+ if (id == "global-unread") {
+ App.global_unread = ctr;
+ App.updateTitle();
+ continue;
}
- // If we made it this far, objects
- // are considered equivalent
- return true;
- },
- resetCounters: function () {
- this._counters_prev = [];
- },
- parseCounters: function (elems) {
- PluginHost.run(PluginHost.HOOK_COUNTERS_RECEIVED, elems);
+ if (id == "subscribed-feeds") {
+ /* feeds_found = ctr; */
+ continue;
+ }
- for (let l = 0; l < elems.length; l++) {
+ /*if (this.getUnread(id, (kind == "cat")) != ctr ||
+ (kind == "cat")) {
+ }*/
- if (Feeds._counters_prev[l] && this.counterEquals(elems[l], this._counters_prev[l])) {
- continue;
- }
+ this.setUnread(id, (kind == "cat"), ctr);
+ this.setValue(id, (kind == "cat"), 'auxcounter', parseInt(elems[l].auxcounter));
+ this.setValue(id, (kind == "cat"), 'markedcounter', parseInt(elems[l].markedcounter));
- const id = elems[l].id;
- const kind = elems[l].kind;
- const ctr = parseInt(elems[l].counter);
- const error = elems[l].error;
- const has_img = elems[l].has_img;
- const updated = elems[l].updated;
-
- if (id == "global-unread") {
- App.global_unread = ctr;
- App.updateTitle();
- continue;
- }
+ if (kind != "cat") {
+ this.setValue(id, false, 'error', error);
+ this.setValue(id, false, 'updated', updated);
- if (id == "subscribed-feeds") {
- /* feeds_found = ctr; */
- continue;
- }
-
- /*if (this.getUnread(id, (kind == "cat")) != ctr ||
- (kind == "cat")) {
- }*/
-
- this.setUnread(id, (kind == "cat"), ctr);
- this.setValue(id, (kind == "cat"), 'auxcounter', parseInt(elems[l].auxcounter));
- this.setValue(id, (kind == "cat"), 'markedcounter', parseInt(elems[l].markedcounter));
-
- if (kind != "cat") {
- this.setValue(id, false, 'error', error);
- this.setValue(id, false, 'updated', updated);
-
- if (id > 0) {
- if (has_img) {
- this.setIcon(id, false,
- App.getInitParam("icons_url") + "/" + id + ".ico?" + has_img);
- } else {
- this.setIcon(id, false, 'images/blank_icon.gif');
- }
+ if (id > 0) {
+ if (has_img) {
+ this.setIcon(id, false,
+ App.getInitParam("icons_url") + "/" + id + ".ico?" + has_img);
+ } else {
+ this.setIcon(id, false, 'images/blank_icon.gif');
}
}
}
-
- Headlines.updateCurrentUnread();
-
- this.hideOrShowFeeds(App.getInitParam("hide_read_feeds"));
- this._counters_prev = elems;
-
- PluginHost.run(PluginHost.HOOK_COUNTERS_PROCESSED);
- },
- reloadCurrent: function(method) {
- if (this.getActive() != undefined) {
- console.log("reloadCurrent: " + method);
-
- this.open({feed: this.getActive(), is_cat: this.activeIsCat(), method: method});
+ }
+
+ Headlines.updateCurrentUnread();
+
+ this.hideOrShowFeeds(App.getInitParam("hide_read_feeds"));
+ this._counters_prev = elems;
+
+ PluginHost.run(PluginHost.HOOK_COUNTERS_PROCESSED);
+ },
+ reloadCurrent: function(method) {
+ if (this.getActive() != undefined) {
+ console.log("reloadCurrent: " + method);
+
+ this.open({feed: this.getActive(), is_cat: this.activeIsCat(), method: method});
+ }
+ return false; // block unneeded form submits
+ },
+ openNextUnread: function() {
+ const is_cat = this.activeIsCat();
+ const nuf = this.getNextUnread(this.getActive(), is_cat);
+ if (nuf) this.open({feed: nuf, is_cat: is_cat});
+ },
+ toggle: function() {
+ Element.toggle("feeds-holder");
+
+ const splitter = $("feeds-holder_splitter");
+
+ Element.visible("feeds-holder") ? splitter.show() : splitter.hide();
+
+ dijit.byId("main").resize();
+
+ Headlines.updateCurrentUnread();
+ },
+ cancelSearch: function() {
+ this._search_query = "";
+ this.reloadCurrent();
+ },
+ requestCounters: function() {
+ xhrPost("backend.php", {op: "rpc", method: "getAllCounters", seq: App.next_seq()}, (transport) => {
+ App.handleRpcJson(transport);
+ });
+ },
+ reload: function() {
+ try {
+ Element.show("feedlistLoading");
+
+ this.resetCounters();
+
+ if (dijit.byId("feedTree")) {
+ dijit.byId("feedTree").destroyRecursive();
}
- return false; // block unneeded form submits
- },
- openNextUnread: function() {
- const is_cat = this.activeIsCat();
- const nuf = this.getNextUnread(this.getActive(), is_cat);
- if (nuf) this.open({feed: nuf, is_cat: is_cat});
- },
- toggle: function() {
- Element.toggle("feeds-holder");
-
- const splitter = $("feeds-holder_splitter");
-
- Element.visible("feeds-holder") ? splitter.show() : splitter.hide();
-
- dijit.byId("main").resize();
-
- Headlines.updateCurrentUnread();
- },
- cancelSearch: function() {
- this._search_query = "";
- this.reloadCurrent();
- },
- requestCounters: function() {
- xhrPost("backend.php", {op: "rpc", method: "getAllCounters", seq: App.next_seq()}, (transport) => {
- App.handleRpcJson(transport);
- });
- },
- reload: function() {
- try {
- Element.show("feedlistLoading");
-
- this.resetCounters();
- if (dijit.byId("feedTree")) {
- dijit.byId("feedTree").destroyRecursive();
- }
-
- const store = new dojo.data.ItemFileWriteStore({
- url: "backend.php?op=pref_feeds&method=getfeedtree&mode=2"
- });
+ const store = new dojo.data.ItemFileWriteStore({
+ url: "backend.php?op=pref_feeds&method=getfeedtree&mode=2"
+ });
- // noinspection JSUnresolvedFunction
- const treeModel = new fox.FeedStoreModel({
- store: store,
- query: {
- "type": App.getInitParam('enable_feed_cats') ? "category" : "feed"
- },
- rootId: "root",
- rootLabel: "Feeds",
- childrenAttrs: ["items"]
- });
+ // noinspection JSUnresolvedFunction
+ const treeModel = new fox.FeedStoreModel({
+ store: store,
+ query: {
+ "type": App.getInitParam('enable_feed_cats') ? "category" : "feed"
+ },
+ rootId: "root",
+ rootLabel: "Feeds",
+ childrenAttrs: ["items"]
+ });
- // noinspection JSUnresolvedFunction
- const tree = new fox.FeedTree({
- model: treeModel,
- onClick: function (item/*, node*/) {
- const id = String(item.id);
- const is_cat = id.match("^CAT:");
- const feed = id.substr(id.indexOf(":") + 1);
- Feeds.open({feed: feed, is_cat: is_cat});
- return false;
- },
- openOnClick: false,
- showRoot: false,
- persist: true,
- id: "feedTree",
- }, "feedTree");
-
- const tmph = dojo.connect(dijit.byId('feedMenu'), '_openMyself', function (event) {
- console.log(dijit.getEnclosingWidget(event.target));
- dojo.disconnect(tmph);
- });
+ // noinspection JSUnresolvedFunction
+ const tree = new fox.FeedTree({
+ model: treeModel,
+ onClick: function (item/*, node*/) {
+ const id = String(item.id);
+ const is_cat = id.match("^CAT:");
+ const feed = id.substr(id.indexOf(":") + 1);
+ Feeds.open({feed: feed, is_cat: is_cat});
+ return false;
+ },
+ openOnClick: false,
+ showRoot: false,
+ persist: true,
+ id: "feedTree",
+ }, "feedTree");
+
+ const tmph = dojo.connect(dijit.byId('feedMenu'), '_openMyself', function (event) {
+ console.log(dijit.getEnclosingWidget(event.target));
+ dojo.disconnect(tmph);
+ });
- $("feeds-holder").appendChild(tree.domNode);
+ $("feeds-holder").appendChild(tree.domNode);
- const tmph2 = dojo.connect(tree, 'onLoad', function () {
- dojo.disconnect(tmph2);
- Element.hide("feedlistLoading");
+ const tmph2 = dojo.connect(tree, 'onLoad', function () {
+ dojo.disconnect(tmph2);
+ Element.hide("feedlistLoading");
- try {
- Feeds.init();
- App.setLoadingProgress(25);
- } catch (e) {
- App.Error.report(e);
- }
- });
+ try {
+ Feeds.init();
+ App.setLoadingProgress(25);
+ } catch (e) {
+ App.Error.report(e);
+ }
+ });
- tree.startup();
- } catch (e) {
- App.Error.report(e);
- }
- },
- init: function() {
- console.log("in feedlist init");
+ tree.startup();
+ } catch (e) {
+ App.Error.report(e);
+ }
+ },
+ init: function() {
+ console.log("in feedlist init");
- App.setLoadingProgress(50);
+ App.setLoadingProgress(50);
- document.onkeydown = (event) => { return App.hotkeyHandler(event) };
- document.onkeypress = (event) => { return App.hotkeyHandler(event) };
- window.onresize = () => { Headlines.scrollHandler(); }
+ //document.onkeydown = (event) => { return App.hotkeyHandler(event) };
+ //document.onkeypress = (event) => { return App.hotkeyHandler(event) };
+ window.onresize = () => { Headlines.scrollHandler(); }
- const hash_feed_id = hash_get('f');
- const hash_feed_is_cat = hash_get('c') == "1";
+ /* global hash_get */
+ const hash_feed_id = hash_get('f');
+ const hash_feed_is_cat = hash_get('c') == "1";
- if (hash_feed_id != undefined) {
- this.open({feed: hash_feed_id, is_cat: hash_feed_is_cat});
- } else {
- this.open({feed: -3});
- }
+ if (hash_feed_id != undefined) {
+ this.open({feed: hash_feed_id, is_cat: hash_feed_is_cat});
+ } else {
+ this.open({feed: -3});
+ }
- this.hideOrShowFeeds(App.getInitParam("hide_read_feeds"));
+ this.hideOrShowFeeds(App.getInitParam("hide_read_feeds"));
- if (App.getInitParam("is_default_pw")) {
- console.warn("user password is at default value");
+ if (App.getInitParam("is_default_pw")) {
+ console.warn("user password is at default value");
- if (dijit.byId("defaultPasswordDlg"))
- dijit.byId("defaultPasswordDlg").destroyRecursive();
+ if (dijit.byId("defaultPasswordDlg"))
+ dijit.byId("defaultPasswordDlg").destroyRecursive();
+ xhrPost("backend.php", {op: 'dlg', method: 'defaultpasswordwarning'}, (transport) => {
const dialog = new dijit.Dialog({
title: __("Your password is at default value"),
- href: "backend.php?op=dlg&method=defaultpasswordwarning",
+ content: transport.responseText,
id: 'defaultPasswordDlg',
style: "width: 600px",
onCancel: function () {
@@ -241,364 +244,366 @@ define(["dojo/_base/declare"], function (declare) {
});
dialog.show();
- }
-
- // bw_limit disables timeout() so we request initial counters separately
- if (App.getInitParam("bw_limit")) {
- this.requestCounters(true);
- } else {
- setTimeout(() => {
- this.requestCounters(true);
- setInterval(() => { this.requestCounters(); }, 60 * 1000)
- }, 250);
- }
- },
- activeIsCat: function() {
- return !!this._active_feed_is_cat;
- },
- getActive: function() {
- return this._active_feed_id;
- },
- setActive: function(id, is_cat) {
- console.log('setActive', id, is_cat);
-
- hash_set('f', id);
- hash_set('c', is_cat ? 1 : 0);
-
- this._active_feed_id = id;
- this._active_feed_is_cat = is_cat;
-
- $("headlines-frame").setAttribute("feed-id", id);
- $("headlines-frame").setAttribute("is-cat", is_cat ? 1 : 0);
-
- this.select(id, is_cat);
-
- PluginHost.run(PluginHost.HOOK_FEED_SET_ACTIVE, [this._active_feed_id, this._active_feed_is_cat]);
- },
- select: function(feed, is_cat) {
- const tree = dijit.byId("feedTree");
-
- if (tree) return tree.selectFeed(feed, is_cat);
- },
- toggleUnread: function() {
- const hide = !App.getInitParam("hide_read_feeds");
-
- xhrPost("backend.php", {op: "rpc", method: "setpref", key: "HIDE_READ_FEEDS", value: hide}, () => {
- this.hideOrShowFeeds(hide);
- App.setInitParam("hide_read_feeds", hide);
});
- },
- hideOrShowFeeds: function (hide) {
- /*const tree = dijit.byId("feedTree");
-
- if (tree)
- return tree.hideRead(hide, App.getInitParam("hide_read_shows_special"));*/
-
- $$("body")[0].setAttribute("hide-read-feeds", !!hide);
- $$("body")[0].setAttribute("hide-read-shows-special", !!App.getInitParam("hide_read_shows_special"));
- },
- open: function(params) {
- const feed = params.feed;
- const is_cat = !!params.is_cat || false;
- const offset = params.offset || 0;
- const viewfeed_debug = params.viewfeed_debug;
- const append = params.append || false;
- const method = params.method;
- // this is used to quickly switch between feeds, sets active but xhr is on a timeout
- const delayed = params.delayed || false;
-
- if (offset != 0) {
- if (this.infscroll_in_progress)
- return;
-
- this.infscroll_in_progress = 1;
-
- window.clearTimeout(this._infscroll_timeout);
- this._infscroll_timeout = window.setTimeout(() => {
- console.log('infscroll request timed out, aborting');
- this.infscroll_in_progress = 0;
-
- // call scroll handler to maybe repeat infscroll request
- Headlines.scrollHandler();
- }, 10 * 1000);
- }
-
- Form.enable("toolbar-main");
-
- let query = Object.assign({op: "feeds", method: "view", feed: feed},
- dojo.formToObject("toolbar-main"));
-
- if (method) query.m = method;
-
- if (offset > 0) {
- if (Headlines.current_first_id) {
- query.fid = Headlines.current_first_id;
- }
- }
-
- if (this._search_query) {
- query = Object.assign(query, this._search_query);
- }
-
- if (offset != 0) {
- query.skip = offset;
- } else if (!is_cat && feed == this.getActive() && !params.method) {
- query.m = "ForceUpdate";
- }
+ }
- Form.enable("toolbar-main");
-
- if (!delayed)
- if (!this.setExpando(feed, is_cat,
- (is_cat) ? 'images/indicator_tiny.gif' : 'images/indicator_white.gif'))
- Notify.progress("Loading, please wait...", true);
-
- query.cat = is_cat;
-
- this.setActive(feed, is_cat);
-
- if (viewfeed_debug) {
- window.open("backend.php?" +
- dojo.objectToQuery(
- Object.assign({csrf_token: App.getInitParam("csrf_token")}, query)
- ));
- }
-
- window.clearTimeout(this._viewfeed_wait_timeout);
- this._viewfeed_wait_timeout = window.setTimeout(() => {
- xhrPost("backend.php", query, (transport) => {
- try {
- window.clearTimeout(this._infscroll_timeout);
- this.setExpando(feed, is_cat, 'images/blank_icon.gif');
- Headlines.onLoaded(transport, offset, append);
- PluginHost.run(PluginHost.HOOK_FEED_LOADED, [feed, is_cat]);
- } catch (e) {
- App.Error.report(e);
- }
- });
- }, delayed ? 250 : 0);
- },
- catchupAll: function() {
- const str = __("Mark all articles as read?");
+ // bw_limit disables timeout() so we request initial counters separately
+ if (App.getInitParam("bw_limit")) {
+ this.requestCounters(true);
+ } else {
+ setTimeout(() => {
+ this.requestCounters(true);
+ setInterval(() => { this.requestCounters(); }, 60 * 1000)
+ }, 250);
+ }
+ },
+ activeIsCat: function() {
+ return !!this._active_feed_is_cat;
+ },
+ getActive: function() {
+ return this._active_feed_id;
+ },
+ setActive: function(id, is_cat) {
+ console.log('setActive', id, is_cat);
+
+ /* global hash_set */
+ hash_set('f', id);
+ hash_set('c', is_cat ? 1 : 0);
+
+ this._active_feed_id = id;
+ this._active_feed_is_cat = is_cat;
+
+ $("headlines-frame").setAttribute("feed-id", id);
+ $("headlines-frame").setAttribute("is-cat", is_cat ? 1 : 0);
+
+ this.select(id, is_cat);
+
+ PluginHost.run(PluginHost.HOOK_FEED_SET_ACTIVE, [this._active_feed_id, this._active_feed_is_cat]);
+ },
+ select: function(feed, is_cat) {
+ const tree = dijit.byId("feedTree");
+
+ if (tree) return tree.selectFeed(feed, is_cat);
+ },
+ toggleUnread: function() {
+ const hide = !App.getInitParam("hide_read_feeds");
+
+ xhrPost("backend.php", {op: "rpc", method: "setpref", key: "HIDE_READ_FEEDS", value: hide}, () => {
+ this.hideOrShowFeeds(hide);
+ App.setInitParam("hide_read_feeds", hide);
+ });
+ },
+ hideOrShowFeeds: function (hide) {
+ /*const tree = dijit.byId("feedTree");
+
+ if (tree)
+ return tree.hideRead(hide, App.getInitParam("hide_read_shows_special"));*/
+
+ $$("body")[0].setAttribute("hide-read-feeds", !!hide);
+ $$("body")[0].setAttribute("hide-read-shows-special", !!App.getInitParam("hide_read_shows_special"));
+ },
+ open: function(params) {
+ const feed = params.feed;
+ const is_cat = !!params.is_cat || false;
+ const offset = params.offset || 0;
+ const viewfeed_debug = params.viewfeed_debug;
+ const append = params.append || false;
+ const method = params.method;
+ // this is used to quickly switch between feeds, sets active but xhr is on a timeout
+ const delayed = params.delayed || false;
+
+ if (offset != 0) {
+ if (this.infscroll_in_progress)
+ return;
- if (App.getInitParam("confirm_feed_catchup") != 1 || confirm(str)) {
+ this.infscroll_in_progress = 1;
- Notify.progress("Marking all feeds as read...");
+ window.clearTimeout(this._infscroll_timeout);
+ this._infscroll_timeout = window.setTimeout(() => {
+ console.log('infscroll request timed out, aborting');
+ this.infscroll_in_progress = 0;
- xhrPost("backend.php", {op: "feeds", method: "catchupAll"}, () => {
- this.requestCounters(true);
- this.reloadCurrent();
- });
+ // call scroll handler to maybe repeat infscroll request
+ Headlines.scrollHandler();
+ }, 10 * 1000);
+ }
- App.global_unread = 0;
- App.updateTitle();
- }
- },
- catchupFeed: function(feed, is_cat, mode) {
- is_cat = is_cat || false;
-
- let str = false;
-
- switch (mode) {
- case "1day":
- str = __("Mark %w in %s older than 1 day as read?");
- break;
- case "1week":
- str = __("Mark %w in %s older than 1 week as read?");
- break;
- case "2week":
- str = __("Mark %w in %s older than 2 weeks as read?");
- break;
- default:
- str = __("Mark %w in %s as read?");
- }
+ Form.enable("toolbar-main");
- const mark_what = this.last_search_query && this.last_search_query[0] ? __("search results") : __("all articles");
- const fn = this.getName(feed, is_cat);
+ let query = Object.assign({op: "feeds", method: "view", feed: feed},
+ dojo.formToObject("toolbar-main"));
- str = str.replace("%s", fn)
- .replace("%w", mark_what);
+ if (method) query.m = method;
- if (App.getInitParam("confirm_feed_catchup") && !confirm(str)) {
- return;
+ if (offset > 0) {
+ if (Headlines.current_first_id) {
+ query.fid = Headlines.current_first_id;
}
-
- const catchup_query = {
- op: 'rpc', method: 'catchupFeed', feed_id: feed,
- is_cat: is_cat, mode: mode, search_query: this.last_search_query[0],
- search_lang: this.last_search_query[1]
- };
-
- Notify.progress("Loading, please wait...", true);
-
- xhrPost("backend.php", catchup_query, (transport) => {
- App.handleRpcJson(transport);
-
- const show_next_feed = App.getInitParam("on_catchup_show_next_feed");
-
- if (show_next_feed) {
- const nuf = this.getNextUnread(feed, is_cat);
-
- if (nuf) {
- this.open({feed: nuf, is_cat: is_cat});
- }
- } else if (feed == this.getActive() && is_cat == this.activeIsCat()) {
- this.reloadCurrent();
+ }
+
+ if (this._search_query) {
+ query = Object.assign(query, this._search_query);
+ }
+
+ if (offset != 0) {
+ query.skip = offset;
+ } else if (!is_cat && feed == this.getActive() && !params.method) {
+ query.m = "ForceUpdate";
+ }
+
+ Form.enable("toolbar-main");
+
+ if (!delayed)
+ if (!this.setExpando(feed, is_cat,
+ (is_cat) ? 'images/indicator_tiny.gif' : 'images/indicator_white.gif'))
+ Notify.progress("Loading, please wait...", true);
+
+ query.cat = is_cat;
+
+ this.setActive(feed, is_cat);
+
+ if (viewfeed_debug) {
+ window.open("backend.php?" +
+ dojo.objectToQuery(
+ Object.assign({csrf_token: App.getInitParam("csrf_token")}, query)
+ ));
+ }
+
+ window.clearTimeout(this._viewfeed_wait_timeout);
+ this._viewfeed_wait_timeout = window.setTimeout(() => {
+ xhrPost("backend.php", query, (transport) => {
+ try {
+ window.clearTimeout(this._infscroll_timeout);
+ this.setExpando(feed, is_cat, 'images/blank_icon.gif');
+ Headlines.onLoaded(transport, offset, append);
+ PluginHost.run(PluginHost.HOOK_FEED_LOADED, [feed, is_cat]);
+ } catch (e) {
+ App.Error.report(e);
}
-
- Notify.close();
});
- },
- catchupCurrent: function(mode) {
- this.catchupFeed(this.getActive(), this.activeIsCat(), mode);
- },
- catchupFeedInGroup: function(id) {
- const title = this.getName(id);
+ }, delayed ? 250 : 0);
+ },
+ catchupAll: function() {
+ const str = __("Mark all articles as read?");
- const str = __("Mark all articles in %s as read?").replace("%s", title);
+ if (App.getInitParam("confirm_feed_catchup") != 1 || confirm(str)) {
- if (App.getInitParam("confirm_feed_catchup") != 1 || confirm(str)) {
+ Notify.progress("Marking all feeds as read...");
- const rows = $$("#headlines-frame > div[id*=RROW][class*=Unread][data-orig-feed-id='" + id + "']");
-
- rows.each((row) => {
- row.removeClassName("Unread");
- })
- }
- },
- getUnread: function(feed, is_cat) {
- try {
- const tree = dijit.byId("feedTree");
-
- if (tree && tree.model)
- return tree.model.getFeedUnread(feed, is_cat);
+ xhrPost("backend.php", {op: "feeds", method: "catchupAll"}, () => {
+ this.requestCounters(true);
+ this.reloadCurrent();
+ });
- } catch (e) {
- //
+ App.global_unread = 0;
+ App.updateTitle();
+ }
+ },
+ catchupFeed: function(feed, is_cat, mode) {
+ is_cat = is_cat || false;
+
+ let str = false;
+
+ switch (mode) {
+ case "1day":
+ str = __("Mark %w in %s older than 1 day as read?");
+ break;
+ case "1week":
+ str = __("Mark %w in %s older than 1 week as read?");
+ break;
+ case "2week":
+ str = __("Mark %w in %s older than 2 weeks as read?");
+ break;
+ default:
+ str = __("Mark %w in %s as read?");
+ }
+
+ const mark_what = this.last_search_query && this.last_search_query[0] ? __("search results") : __("all articles");
+ const fn = this.getName(feed, is_cat);
+
+ str = str.replace("%s", fn)
+ .replace("%w", mark_what);
+
+ if (App.getInitParam("confirm_feed_catchup") && !confirm(str)) {
+ return;
+ }
+
+ const catchup_query = {
+ op: 'rpc', method: 'catchupFeed', feed_id: feed,
+ is_cat: is_cat, mode: mode, search_query: this.last_search_query[0],
+ search_lang: this.last_search_query[1]
+ };
+
+ Notify.progress("Loading, please wait...", true);
+
+ xhrPost("backend.php", catchup_query, (transport) => {
+ App.handleRpcJson(transport);
+
+ const show_next_feed = App.getInitParam("on_catchup_show_next_feed");
+
+ if (show_next_feed) {
+ const nuf = this.getNextUnread(feed, is_cat);
+
+ if (nuf) {
+ this.open({feed: nuf, is_cat: is_cat});
+ }
+ } else if (feed == this.getActive() && is_cat == this.activeIsCat()) {
+ this.reloadCurrent();
}
- return -1;
- },
- getCategory: function(feed) {
- try {
- const tree = dijit.byId("feedTree");
+ Notify.close();
+ });
+ },
+ catchupCurrent: function(mode) {
+ this.catchupFeed(this.getActive(), this.activeIsCat(), mode);
+ },
+ catchupFeedInGroup: function(id) {
+ const title = this.getName(id);
- if (tree && tree.model)
- return tree.getFeedCategory(feed);
+ const str = __("Mark all articles in %s as read?").replace("%s", title);
- } catch (e) {
- //
- }
+ if (App.getInitParam("confirm_feed_catchup") != 1 || confirm(str)) {
- return false;
- },
- getName: function(feed, is_cat) {
- if (isNaN(feed)) return feed; // it's a tag
+ const rows = $$("#headlines-frame > div[id*=RROW][class*=Unread][data-orig-feed-id='" + id + "']");
+ rows.each((row) => {
+ row.removeClassName("Unread");
+ })
+ }
+ },
+ getUnread: function(feed, is_cat) {
+ try {
const tree = dijit.byId("feedTree");
if (tree && tree.model)
- return tree.model.getFeedValue(feed, is_cat, 'name');
- },
- setUnread: function(feed, is_cat, unread) {
- const tree = dijit.byId("feedTree");
-
- if (tree && tree.model)
- return tree.model.setFeedUnread(feed, is_cat, unread);
- },
- setValue: function(feed, is_cat, key, value) {
- try {
- const tree = dijit.byId("feedTree");
-
- if (tree && tree.model)
- return tree.model.setFeedValue(feed, is_cat, key, value);
+ return tree.model.getFeedUnread(feed, is_cat);
- } catch (e) {
- //
- }
- },
- getValue: function(feed, is_cat, key) {
- try {
- const tree = dijit.byId("feedTree");
-
- if (tree && tree.model)
- return tree.model.getFeedValue(feed, is_cat, key);
+ } catch (e) {
+ //
+ }
- } catch (e) {
- //
- }
- return '';
- },
- setIcon: function(feed, is_cat, src) {
+ return -1;
+ },
+ getCategory: function(feed) {
+ try {
const tree = dijit.byId("feedTree");
- if (tree) return tree.setFeedIcon(feed, is_cat, src);
- },
- setExpando: function(feed, is_cat, src) {
+ if (tree && tree.model)
+ return tree.getFeedCategory(feed);
+
+ } catch (e) {
+ //
+ }
+
+ return false;
+ },
+ getName: function(feed, is_cat) {
+ if (isNaN(feed)) return feed; // it's a tag
+
+ const tree = dijit.byId("feedTree");
+
+ if (tree && tree.model)
+ return tree.model.getFeedValue(feed, is_cat, 'name');
+ },
+ setUnread: function(feed, is_cat, unread) {
+ const tree = dijit.byId("feedTree");
+
+ if (tree && tree.model)
+ return tree.model.setFeedUnread(feed, is_cat, unread);
+ },
+ setValue: function(feed, is_cat, key, value) {
+ try {
const tree = dijit.byId("feedTree");
- if (tree) return tree.setFeedExpandoIcon(feed, is_cat, src);
-
- return false;
- },
- getNextUnread: function(feed, is_cat) {
+ if (tree && tree.model)
+ return tree.model.setFeedValue(feed, is_cat, key, value);
+
+ } catch (e) {
+ //
+ }
+ },
+ getValue: function(feed, is_cat, key) {
+ try {
const tree = dijit.byId("feedTree");
- const nuf = tree.model.getNextUnreadFeed(feed, is_cat);
-
- if (nuf)
- return tree.model.store.getValue(nuf, 'bare_id');
- },
- search: function() {
- const query = "backend.php?op=feeds&method=search&param=" +
- encodeURIComponent(Feeds.getActive() + ":" + Feeds.activeIsCat());
-
- if (dijit.byId("searchDlg"))
- dijit.byId("searchDlg").destroyRecursive();
-
- const dialog = new dijit.Dialog({
- id: "searchDlg",
- title: __("Search"),
- style: "width: 600px",
- execute: function () {
- if (this.validate()) {
- Feeds._search_query = this.attr('value');
-
- // disallow empty queries
- if (!Feeds._search_query.query)
- Feeds._search_query = false;
-
- this.hide();
- Feeds.reloadCurrent();
- }
- },
- href: query
- });
-
- const tmph = dojo.connect(dialog, 'onLoad', function () {
- dojo.disconnect(tmph);
-
- if (Feeds._search_query) {
- if (Feeds._search_query.query)
- dijit.byId('search_query')
- .attr('value', Feeds._search_query.query);
-
- if (Feeds._search_query.search_language)
- dijit.byId('search_language')
- .attr('value', Feeds._search_query.search_language);
- }
-
- });
-
- dialog.show();
- },
- updateRandom: function() {
- console.log("in update_random_feed");
- xhrPost("backend.php", {op: "rpc", method: "updaterandomfeed"}, (transport) => {
- App.handleRpcJson(transport, true);
- });
- },
- };
-
- return Feeds;
-});
+ if (tree && tree.model)
+ return tree.model.getFeedValue(feed, is_cat, key);
+
+ } catch (e) {
+ //
+ }
+ return '';
+ },
+ setIcon: function(feed, is_cat, src) {
+ const tree = dijit.byId("feedTree");
+
+ if (tree) return tree.setFeedIcon(feed, is_cat, src);
+ },
+ setExpando: function(feed, is_cat, src) {
+ const tree = dijit.byId("feedTree");
+
+ if (tree) return tree.setFeedExpandoIcon(feed, is_cat, src);
+
+ return false;
+ },
+ getNextUnread: function(feed, is_cat) {
+ const tree = dijit.byId("feedTree");
+ const nuf = tree.model.getNextUnreadFeed(feed, is_cat);
+
+ if (nuf)
+ return tree.model.store.getValue(nuf, 'bare_id');
+ },
+ search: function() {
+ if (dijit.byId("searchDlg"))
+ dijit.byId("searchDlg").destroyRecursive();
+
+ xhrPost("backend.php",
+ {op: "feeds", method: "search",
+ param: Feeds.getActive() + ":" + Feeds.activeIsCat()},
+ (transport) => {
+ const dialog = new dijit.Dialog({
+ id: "searchDlg",
+ content: transport.responseText,
+ title: __("Search"),
+ style: "width: 600px",
+ execute: function () {
+ if (this.validate()) {
+ Feeds._search_query = this.attr('value');
+
+ // disallow empty queries
+ if (!Feeds._search_query.query)
+ Feeds._search_query = false;
+
+ this.hide();
+ Feeds.reloadCurrent();
+ }
+ },
+ });
+
+ const tmph = dojo.connect(dialog, 'onLoad', function () {
+ dojo.disconnect(tmph);
+
+ if (Feeds._search_query) {
+ if (Feeds._search_query.query)
+ dijit.byId('search_query')
+ .attr('value', Feeds._search_query.query);
+
+ if (Feeds._search_query.search_language)
+ dijit.byId('search_language')
+ .attr('value', Feeds._search_query.search_language);
+ }
+
+ });
+
+ dialog.show();
+ });
+
+ },
+ updateRandom: function() {
+ console.log("in update_random_feed");
+
+ xhrPost("backend.php", {op: "rpc", method: "updaterandomfeed"}, (transport) => {
+ App.handleRpcJson(transport, true);
+ });
+ },
+};
diff --git a/js/Headlines.js b/js/Headlines.js
index 540c400d3..b98098c33 100755
--- a/js/Headlines.js
+++ b/js/Headlines.js
@@ -1,944 +1,760 @@
'use strict';
-/* global __, ngettext */
-define(["dojo/_base/declare"], function (declare) {
- Headlines = {
- vgroup_last_feed: undefined,
- _headlines_scroll_timeout: 0,
- _observer_counters_timeout: 0,
- headlines: [],
- current_first_id: 0,
- _scroll_reset_timeout: false,
- row_observer: new MutationObserver((mutations) => {
- const modified = [];
- mutations.each((m) => {
- if (m.type == 'attributes' && ['class', 'data-score'].indexOf(m.attributeName) != -1) {
+/* global __, ngettext, Article, App */
+/* global xhrPost, dojo, dijit, PluginHost, Notify, $$, Feeds */
+/* global CommonDialogs */
+
+const Headlines = {
+ vgroup_last_feed: undefined,
+ _headlines_scroll_timeout: 0,
+ _observer_counters_timeout: 0,
+ headlines: [],
+ current_first_id: 0,
+ _scroll_reset_timeout: false,
+ default_force_previous: false,
+ default_force_to_top: false,
+ line_scroll_offset: 120, /* px */
+ sticky_header_observer: new IntersectionObserver(
+ (entries, observer) => {
+ entries.forEach((entry) => {
+ const header = entry.target.nextElementSibling;
+
+ if (entry.intersectionRatio == 0) {
+ header.setAttribute("stuck", "1");
+
+ } else if (entry.intersectionRatio == 1) {
+ header.removeAttribute("stuck");
+ }
+
+ //console.log(entry.target, header, entry.intersectionRatio);
- const row = m.target;
- const id = row.getAttribute("data-article-id");
+ });
+ },
+ {threshold: [0, 1], root: document.querySelector("#headlines-frame")}
+ ),
+ unpack_observer: new IntersectionObserver(
+ (entries, observer) => {
+ entries.forEach((entry) => {
+ if (entry.intersectionRatio > 0)
+ Article.unpack(entry.target);
+ });
+ },
+ {threshold: [0], root: document.querySelector("#headlines-frame")}
+ ),
+ row_observer: new MutationObserver((mutations) => {
+ const modified = [];
- if (Headlines.headlines[id]) {
- const hl = Headlines.headlines[id];
+ mutations.each((m) => {
+ if (m.type == 'attributes' && ['class', 'data-score'].indexOf(m.attributeName) != -1) {
- if (hl) {
- const hl_old = Object.extend({}, hl);
+ const row = m.target;
+ const id = row.getAttribute("data-article-id");
- hl.unread = row.hasClassName("Unread");
- hl.marked = row.hasClassName("marked");
- hl.published = row.hasClassName("published");
+ if (Headlines.headlines[id]) {
+ const hl = Headlines.headlines[id];
- // not sent by backend
- hl.selected = row.hasClassName("Selected");
- hl.active = row.hasClassName("active");
+ if (hl) {
+ const hl_old = Object.extend({}, hl);
- hl.score = row.getAttribute("data-score");
+ hl.unread = row.hasClassName("Unread");
+ hl.marked = row.hasClassName("marked");
+ hl.published = row.hasClassName("published");
- modified.push({id: hl.id, new: hl, old: hl_old, row: row});
- }
+ // not sent by backend
+ hl.selected = row.hasClassName("Selected");
+ hl.active = row.hasClassName("active");
+
+ hl.score = row.getAttribute("data-score");
+
+ modified.push({id: hl.id, new: hl, old: hl_old, row: row});
}
}
- });
+ }
+ });
- Headlines.updateSelectedPrompt();
- Headlines.updateFloatingTitle(true);
+ Headlines.updateSelectedPrompt();
- if ('requestIdleCallback' in window)
- window.requestIdleCallback(() => {
- Headlines.syncModified(modified);
- });
- else
+ if ('requestIdleCallback' in window)
+ window.requestIdleCallback(() => {
Headlines.syncModified(modified);
- }),
- syncModified: function(modified) {
- const ops = {
- tmark: [],
- tpub: [],
- read: [],
- unread: [],
- select: [],
- deselect: [],
- activate: [],
- deactivate: [],
- rescore: {},
- };
-
- modified.each(function(m) {
- if (m.old.marked != m.new.marked)
- ops.tmark.push(m.id);
-
- if (m.old.published != m.new.published)
- ops.tpub.push(m.id);
-
- if (m.old.unread != m.new.unread)
- m.new.unread ? ops.unread.push(m.id) : ops.read.push(m.id);
-
- if (m.old.selected != m.new.selected)
- m.new.selected ? ops.select.push(m.row) : ops.deselect.push(m.row);
-
- if (m.old.active != m.new.active)
- m.new.active ? ops.activate.push(m.row) : ops.deactivate.push(m.row);
-
- if (m.old.score != m.new.score) {
- const score = m.new.score;
-
- ops.rescore[score] = ops.rescore[score] || [];
- ops.rescore[score].push(m.id);
- }
});
+ else
+ Headlines.syncModified(modified);
+ }),
+ syncModified: function (modified) {
+ const ops = {
+ tmark: [],
+ tpub: [],
+ read: [],
+ unread: [],
+ select: [],
+ deselect: [],
+ activate: [],
+ deactivate: [],
+ rescore: {},
+ };
+
+ modified.each(function (m) {
+ if (m.old.marked != m.new.marked)
+ ops.tmark.push(m.id);
+
+ if (m.old.published != m.new.published)
+ ops.tpub.push(m.id);
+
+ if (m.old.unread != m.new.unread)
+ m.new.unread ? ops.unread.push(m.id) : ops.read.push(m.id);
+
+ if (m.old.selected != m.new.selected)
+ m.new.selected ? ops.select.push(m.row) : ops.deselect.push(m.row);
+
+ if (m.old.active != m.new.active)
+ m.new.active ? ops.activate.push(m.row) : ops.deactivate.push(m.row);
+
+ if (m.old.score != m.new.score) {
+ const score = m.new.score;
+
+ ops.rescore[score] = ops.rescore[score] || [];
+ ops.rescore[score].push(m.id);
+ }
+ });
- ops.select.each((row) => {
- const cb = dijit.getEnclosingWidget(row.select(".rchk")[0]);
+ ops.select.each((row) => {
+ const cb = dijit.getEnclosingWidget(row.select(".rchk")[0]);
- if (cb)
- cb.attr('checked', true);
- });
+ if (cb)
+ cb.attr('checked', true);
+ });
- ops.deselect.each((row) => {
- const cb = dijit.getEnclosingWidget(row.select(".rchk")[0]);
+ ops.deselect.each((row) => {
+ const cb = dijit.getEnclosingWidget(row.select(".rchk")[0]);
- if (cb && !row.hasClassName("active"))
- cb.attr('checked', false);
- });
+ if (cb && !row.hasClassName("active"))
+ cb.attr('checked', false);
+ });
- ops.activate.each((row) => {
- const cb = dijit.getEnclosingWidget(row.select(".rchk")[0]);
+ ops.activate.each((row) => {
+ const cb = dijit.getEnclosingWidget(row.select(".rchk")[0]);
- if (cb)
- cb.attr('checked', true);
- });
+ if (cb)
+ cb.attr('checked', true);
+ });
- ops.deactivate.each((row) => {
- const cb = dijit.getEnclosingWidget(row.select(".rchk")[0]);
+ ops.deactivate.each((row) => {
+ const cb = dijit.getEnclosingWidget(row.select(".rchk")[0]);
- if (cb && !row.hasClassName("Selected"))
- cb.attr('checked', false);
- });
+ if (cb && !row.hasClassName("Selected"))
+ cb.attr('checked', false);
+ });
- const promises = [];
+ const promises = [];
- if (ops.tmark.length != 0)
- promises.push(xhrPost("backend.php",
- { op: "rpc", method: "markSelected", ids: ops.tmark.toString(), cmode: 2}));
+ if (ops.tmark.length != 0)
+ promises.push(xhrPost("backend.php",
+ {op: "rpc", method: "markSelected", ids: ops.tmark.toString(), cmode: 2}));
- if (ops.tpub.length != 0)
- promises.push(xhrPost("backend.php",
- { op: "rpc", method: "publishSelected", ids: ops.tpub.toString(), cmode: 2}));
+ if (ops.tpub.length != 0)
+ promises.push(xhrPost("backend.php",
+ {op: "rpc", method: "publishSelected", ids: ops.tpub.toString(), cmode: 2}));
- if (ops.read.length != 0)
- promises.push(xhrPost("backend.php",
- { op: "rpc", method: "catchupSelected", ids: ops.read.toString(), cmode: 0}));
+ if (ops.read.length != 0)
+ promises.push(xhrPost("backend.php",
+ {op: "rpc", method: "catchupSelected", ids: ops.read.toString(), cmode: 0}));
- if (ops.unread.length != 0)
- promises.push(xhrPost("backend.php",
- { op: "rpc", method: "catchupSelected", ids: ops.unread.toString(), cmode: 1}));
+ if (ops.unread.length != 0)
+ promises.push(xhrPost("backend.php",
+ {op: "rpc", method: "catchupSelected", ids: ops.unread.toString(), cmode: 1}));
- const scores = Object.keys(ops.rescore);
+ const scores = Object.keys(ops.rescore);
- if (scores.length != 0) {
- scores.each((score) => {
- promises.push(xhrPost("backend.php",
- { op: "article", method: "setScore", id: ops.rescore[score].toString(), score: score }));
- });
- }
+ if (scores.length != 0) {
+ scores.each((score) => {
+ promises.push(xhrPost("backend.php",
+ {op: "article", method: "setScore", id: ops.rescore[score].toString(), score: score}));
+ });
+ }
- if (promises.length > 0)
- Promise.all([promises]).then(() => {
- window.clearTimeout(this._observer_counters_timeout);
+ if (promises.length > 0)
+ Promise.all([promises]).then(() => {
+ window.clearTimeout(this._observer_counters_timeout);
- this._observer_counters_timeout = setTimeout(() => {
- Feeds.requestCounters(true);
- }, 1000);
- });
+ this._observer_counters_timeout = setTimeout(() => {
+ Feeds.requestCounters(true);
+ }, 1000);
+ });
- },
- click: function (event, id, in_body) {
- in_body = in_body || false;
+ },
+ click: function (event, id, in_body) {
+ in_body = in_body || false;
- if (event.shiftKey && Article.getActive()) {
- Headlines.select('none');
+ if (event.shiftKey && Article.getActive()) {
+ Headlines.select('none');
- const ids = Headlines.getRange(Article.getActive(), id);
+ const ids = Headlines.getRange(Article.getActive(), id);
- console.log(Article.getActive(), id, ids);
+ console.log(Article.getActive(), id, ids);
- for (let i = 0; i < ids.length; i++)
- Headlines.select('all', ids[i]);
+ for (let i = 0; i < ids.length; i++)
+ Headlines.select('all', ids[i]);
- } else if (event.ctrlKey) {
- Headlines.select('invert', id);
- } else {
- if (App.isCombinedMode()) {
+ } else if (event.ctrlKey) {
+ Headlines.select('invert', id);
+ } else {
+ if (App.isCombinedMode()) {
- if (event.altKey && !in_body) {
+ if (event.altKey && !in_body) {
- Article.openInNewWindow(id);
- Headlines.toggleUnread(id, 0);
+ Article.openInNewWindow(id);
+ Headlines.toggleUnread(id, 0);
- } else if (Article.getActive() != id) {
+ } else if (Article.getActive() != id) {
- Headlines.select('none');
- Article.setActive(id);
+ Headlines.select('none');
- if (App.getInitParam("cdm_expanded")) {
- if (!in_body)
- Article.openInNewWindow(id);
+ const scroll_position_A = $("RROW-" + id).offsetTop - $("headlines-frame").scrollTop;
- Headlines.toggleUnread(id, 0);
- } else {
- Article.cdmScrollToId(id);
- }
+ Article.setActive(id);
- } else if (in_body) {
- Headlines.toggleUnread(id, 0);
- } else { /* !in body */
- Article.openInNewWindow(id);
- }
+ if (App.getInitParam("cdm_expanded")) {
+
+ if (!in_body)
+ Article.openInNewWindow(id);
- return in_body;
- } else {
- if (event.altKey) {
- Article.openInNewWindow(id);
Headlines.toggleUnread(id, 0);
} else {
- Headlines.select('none');
- Article.view(id);
- }
- }
- }
-
- return false;
- },
- initScrollHandler: function () {
- $("headlines-frame").onscroll = (event) => {
- clearTimeout(this._headlines_scroll_timeout);
- this._headlines_scroll_timeout = window.setTimeout(function () {
- //console.log('done scrolling', event);
- Headlines.scrollHandler(event);
- }, 50);
- }
- },
- loadMore: function () {
- const view_mode = document.forms["toolbar-main"].view_mode.value;
- const unread_in_buffer = $$("#headlines-frame > div[id*=RROW][class*=Unread]").length;
- const num_all = $$("#headlines-frame > div[id*=RROW]").length;
- const num_unread = Feeds.getUnread(Feeds.getActive(), Feeds.activeIsCat());
+ const scroll_position_B = $("RROW-" + id).offsetTop - $("headlines-frame").scrollTop;
- // TODO implement marked & published
+ // this would only work if there's enough space
+ $("headlines-frame").scrollTop -= scroll_position_A-scroll_position_B;
- let offset = num_all;
-
- switch (view_mode) {
- case "marked":
- case "published":
- console.warn("loadMore: ", view_mode, "not implemented");
- break;
- case "unread":
- offset = unread_in_buffer;
- break;
- case "adaptive":
- if (!(Feeds.getActive() == -1 && !Feeds.activeIsCat()))
- offset = num_unread > 0 ? unread_in_buffer : num_all;
- break;
- }
-
- console.log("loadMore, offset=", offset);
-
- Feeds.open({feed: Feeds.getActive(), is_cat: Feeds.activeIsCat(), offset: offset, append: true});
- },
- isChildVisible: function (elem, ctr) {
- 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
-
- },
- firstVisible: function() {
- const rows = $$("#headlines-frame > div[id*=RROW]");
- const ctr = $("headlines-frame");
+ Article.cdmMoveToId(id);
+ }
- for (let i = 0; i < rows.length; i++) {
- const row = rows[i];
+ } else if (in_body) {
+ Headlines.toggleUnread(id, 0);
+ } else { /* !in body */
+ Article.openInNewWindow(id);
+ }
- if (this.isChildVisible(row, ctr)) {
- return row.getAttribute("data-article-id");
+ return in_body;
+ } else {
+ if (event.altKey) {
+ Article.openInNewWindow(id);
+ Headlines.toggleUnread(id, 0);
+ } else {
+ Headlines.select('none');
+ Article.view(id);
}
}
- },
- scrollHandler: function (/*event*/) {
- try {
- Headlines.unpackVisible();
+ }
- if (App.isCombinedMode())
- Headlines.updateFloatingTitle();
+ return false;
+ },
+ initScrollHandler: function () {
+ $("headlines-frame").onscroll = (event) => {
+ clearTimeout(this._headlines_scroll_timeout);
+ this._headlines_scroll_timeout = window.setTimeout(function () {
+ //console.log('done scrolling', event);
+ Headlines.scrollHandler(event);
+ }, 50);
+ }
+ },
+ loadMore: function () {
+ const view_mode = document.forms["toolbar-main"].view_mode.value;
+ const unread_in_buffer = $$("#headlines-frame > div[id*=RROW][class*=Unread]").length;
+ const num_all = $$("#headlines-frame > div[id*=RROW]").length;
+ const num_unread = Feeds.getUnread(Feeds.getActive(), Feeds.activeIsCat());
+
+ // TODO implement marked & published
+
+ let offset = num_all;
+
+ switch (view_mode) {
+ case "marked":
+ case "published":
+ console.warn("loadMore: ", view_mode, "not implemented");
+ break;
+ case "unread":
+ offset = unread_in_buffer;
+ break;
+ case "adaptive":
+ if (!(Feeds.getActive() == -1 && !Feeds.activeIsCat()))
+ offset = num_unread > 0 ? unread_in_buffer : num_all;
+ break;
+ }
- if (!Feeds.infscroll_disabled && !Feeds.infscroll_in_progress) {
- const hsp = $("headlines-spacer");
- const container = $("headlines-frame");
+ console.log("loadMore, offset=", offset);
- if (hsp && hsp.previousSibling) {
- const last_row = hsp.previousSibling;
+ Feeds.open({feed: Feeds.getActive(), is_cat: Feeds.activeIsCat(), offset: offset, append: true});
+ },
+ isChildVisible: function (elem) {
+ return App.Scrollable.isChildVisible(elem, $("headlines-frame"));
+ },
+ firstVisible: function () {
+ const rows = $$("#headlines-frame > div[id*=RROW]");
- // invoke lazy load if last article in buffer is nearly visible OR is active
- if (Article.getActive() == last_row.getAttribute("data-article-id") || last_row.offsetTop - 250 <= container.scrollTop + container.offsetHeight) {
- hsp.innerHTML = "<span class='loading'><img src='images/indicator_tiny.gif'> " +
- __("Loading, please wait...") + "</span>";
+ for (let i = 0; i < rows.length; i++) {
+ const row = rows[i];
- Headlines.loadMore();
- return;
- }
+ if (this.isChildVisible(row)) {
+ return row.getAttribute("data-article-id");
+ }
+ }
+ },
+ scrollHandler: function (/*event*/) {
+ try {
+ if (!Feeds.infscroll_disabled && !Feeds.infscroll_in_progress) {
+ const hsp = $("headlines-spacer");
+ const container = $("headlines-frame");
+
+ if (hsp && hsp.previousSibling) {
+ const last_row = hsp.previousSibling;
+
+ // invoke lazy load if last article in buffer is nearly visible OR is active
+ if (Article.getActive() == last_row.getAttribute("data-article-id") || last_row.offsetTop - 250 <= container.scrollTop + container.offsetHeight) {
+ hsp.innerHTML = "<span class='loading'><img src='images/indicator_tiny.gif'> " +
+ __("Loading, please wait...") + "</span>";
+
+ Headlines.loadMore();
+ return;
}
}
+ }
- if (App.getInitParam("cdm_auto_catchup")) {
+ if (App.getInitParam("cdm_auto_catchup")) {
- let rows = $$("#headlines-frame > div[id*=RROW][class*=Unread]");
+ const rows = $$("#headlines-frame > div[id*=RROW][class*=Unread]");
- for (let i = 0; i < rows.length; i++) {
- const row = rows[i];
+ for (let i = 0; i < rows.length; i++) {
+ const row = rows[i];
- if ($("headlines-frame").scrollTop > (row.offsetTop + row.offsetHeight / 2)) {
- row.removeClassName("Unread");
- } else {
- break;
- }
+ if ($("headlines-frame").scrollTop > (row.offsetTop + row.offsetHeight / 2)) {
+ row.removeClassName("Unread");
+ } else {
+ break;
}
}
- } catch (e) {
- console.warn("scrollHandler", e);
}
- },
- updateFloatingTitle: function (status_only) {
- if (!App.isCombinedMode()/* || !App.getInitParam("cdm_expanded")*/) return;
-
- const safety_offset = 120; /* px, needed for firefox */
- const hf = $("headlines-frame");
- const elems = $$("#headlines-frame > div[id*=RROW]");
- const ft = $("floatingTitle");
-
- for (let i = 0; i < elems.length; i++) {
- const row = elems[i];
-
- if (row && row.offsetTop + row.offsetHeight > hf.scrollTop + safety_offset) {
-
- const header = row.select(".header")[0];
- const id = row.getAttribute("data-article-id");
-
- if (status_only || id != ft.getAttribute("data-article-id")) {
- if (id != ft.getAttribute("data-article-id")) {
-
- ft.setAttribute("data-article-id", id);
- ft.innerHTML = header.innerHTML;
-
- ft.select(".dijitCheckBox")[0].outerHTML = "<i class=\"material-icons icon-anchor\" onclick=\"Article.cdmScrollToId(" + id + ", true)\">expand_more</i>";
-
- this.initFloatingMenu();
-
- }
-
- if (row.hasClassName("Unread"))
- ft.addClassName("Unread");
- else
- ft.removeClassName("Unread");
-
- if (row.hasClassName("marked"))
- ft.addClassName("marked");
- else
- ft.removeClassName("marked");
-
- if (row.hasClassName("published"))
- ft.addClassName("published");
- else
- ft.removeClassName("published");
-
- PluginHost.run(PluginHost.HOOK_FLOATING_TITLE, row);
- }
+ } catch (e) {
+ console.warn("scrollHandler", e);
+ }
+ },
+ objectById: function (id) {
+ return this.headlines[id];
+ },
+ setCommonClasses: function () {
+ $("headlines-frame").removeClassName("cdm");
+ $("headlines-frame").removeClassName("normal");
+
+ $("headlines-frame").addClassName(App.isCombinedMode() ? "cdm" : "normal");
+
+ // for floating title because it's placed outside of headlines-frame
+ $("main").removeClassName("expandable");
+ $("main").removeClassName("expanded");
+
+ if (App.isCombinedMode())
+ $("main").addClassName(App.getInitParam("cdm_expanded") ? " expanded" : " expandable");
+ },
+ renderAgain: function () {
+ // TODO: wrap headline elements into a knockoutjs model to prevent all this stuff
+ Headlines.setCommonClasses();
+
+ $$("#headlines-frame > div[id*=RROW]").each((row) => {
+ const id = row.getAttribute("data-article-id");
+ const hl = this.headlines[id];
+
+ if (hl) {
+ const new_row = this.render({}, hl);
+
+ row.parentNode.replaceChild(new_row, row);
+
+ if (hl.active) {
+ new_row.addClassName("active");
+ Article.unpack(new_row);
- if (hf.scrollTop - row.offsetTop <= header.offsetHeight + safety_offset)
- ft.fade({duration: 0.2});
+ if (App.isCombinedMode())
+ Article.cdmMoveToId(id, {noscroll: true});
else
- ft.appear({duration: 0.2});
-
- return;
+ Article.view(id);
}
+
+ if (hl.selected) this.select("all", id);
}
- },
- unpackVisible: function () {
- if (!App.isCombinedMode() || !App.getInitParam("cdm_expanded")) return;
+ });
- const rows = $$("#headlines-frame div[id*=RROW][data-content]");
- const threshold = $("headlines-frame").scrollTop + $("headlines-frame").offsetHeight + 600;
+ $$(".cdm .header-sticky-guard").each((e) => {
+ this.sticky_header_observer.observe(e)
+ });
- for (let i = 0; i < rows.length; i++) {
- const row = rows[i];
+ if (App.getInitParam("cdm_expanded"))
+ $$("#headlines-frame > div[id*=RROW].cdm").each((e) => {
+ this.unpack_observer.observe(e)
+ });
- if (row.offsetTop <= threshold) {
- Article.unpack(row);
- } else {
- break;
- }
- }
- },
- objectById: function (id){
- return this.headlines[id];
- },
- renderAgain: function() {
- // TODO: wrap headline elements into a knockoutjs model to prevent all this stuff
+ },
+ render: function (headlines, hl) {
+ let row = null;
- $$("#headlines-frame > div[id*=RROW]").each((row) => {
- const id = row.getAttribute("data-article-id");
- const hl = this.headlines[id];
+ let row_class = "";
- if (hl) {
- const new_row = this.render({}, hl);
+ if (hl.marked) row_class += " marked";
+ if (hl.published) row_class += " published";
+ if (hl.unread) row_class += " Unread";
+ if (headlines.vfeed_group_enabled) row_class += " vgrlf";
- row.parentNode.replaceChild(new_row, row);
+ if (headlines.vfeed_group_enabled && hl.feed_title && this.vgroup_last_feed != hl.feed_id) {
+ const vgrhdr = `<div data-feed-id='${hl.feed_id}' class='feed-title'>
+ <div style='float : right'>${hl.feed_icon}</div>
+ <a class="title" href="#" onclick="Feeds.open({feed:${hl.feed_id}})">${hl.feed_title}
+ <a class="catchup" title="${__('mark feed as read')}" onclick="Feeds.catchupFeedInGroup(${hl.feed_id})" href="#"><i class="icon-done material-icons">done_all</i></a>
+ </div>`
- if (hl.active) {
- new_row.addClassName("active");
-
- if (App.isCombinedMode())
- Article.cdmScrollToId(id);
- else
- Article.view(id);
- }
+ const tmp = document.createElement("div");
+ tmp.innerHTML = vgrhdr;
- if (hl.selected) this.select("all", id);
+ $("headlines-frame").appendChild(tmp.firstChild);
- Article.unpack(new_row);
+ this.vgroup_last_feed = hl.feed_id;
+ }
- }
- });
- },
- render: function (headlines, hl) {
- let row = null;
+ if (App.isCombinedMode()) {
+ row_class += App.getInitParam("cdm_expanded") ? " expanded" : " expandable";
+
+ const comments = Article.formatComments(hl);
+ const originally_from = Article.formatOriginallyFrom(hl);
+
+ row = `<div class="cdm ${row_class} ${Article.getScoreClass(hl.score)}"
+ id="RROW-${hl.id}"
+ data-article-id="${hl.id}"
+ data-orig-feed-id="${hl.feed_id}"
+ data-content="${App.escapeHtml(hl.content)}"
+ data-score="${hl.score}"
+ data-article-title="${App.escapeHtml(hl.title)}"
+ onmouseover="Article.mouseIn(${hl.id})"
+ onmouseout="Article.mouseOut(${hl.id})">
+ <div class="header-sticky-guard"></div>
+ <div class="header">
+ <div class="left">
+ <input dojoType="dijit.form.CheckBox" type="checkbox" onclick="Headlines.onRowChecked(this)" class='rchk'>
+ <i class="marked-pic marked-${hl.id} material-icons" onclick="Headlines.toggleMark(${hl.id})">star</i>
+ <i class="pub-pic pub-${hl.id} material-icons" onclick="Headlines.togglePub(${hl.id})">rss_feed</i>
+ </div>
- let row_class = "";
+ <span onclick="return Headlines.click(event, ${hl.id});" data-article-id="${hl.id}" class="titleWrap hlMenuAttach">
+ <a class="title" title="${App.escapeHtml(hl.title)}" target="_blank" rel="noopener noreferrer" href="${App.escapeHtml(hl.link)}">
+ ${hl.title}</a>
+ <span class="author">${hl.author}</span>
+ ${hl.labels}
+ ${hl.cdm_excerpt ? hl.cdm_excerpt : ""}
+ </span>
+
+ <div class="feed">
+ <a href="#" style="background-color: ${hl.feed_bg_color}"
+ onclick="Feeds.open({feed:${hl.feed_id}})">${hl.feed_title}</a>
+ </div>
- if (hl.marked) row_class += " marked";
- if (hl.published) row_class += " published";
- if (hl.unread) row_class += " Unread";
- if (headlines.vfeed_group_enabled) row_class += " vgrlf";
+ <span class="updated" title="${hl.imported}">${hl.updated}</span>
- if (headlines.vfeed_group_enabled && hl.feed_title && this.vgroup_last_feed != hl.feed_id) {
- let vgrhdr = `<div data-feed-id='${hl.feed_id}' class='feed-title'>
- <div style='float : right'>${hl.feed_icon}</div>
- <a class="title" href="#" onclick="Feeds.open({feed:${hl.feed_id}})">${hl.feed_title}
- <a class="catchup" title="${__('mark feed as read')}" onclick="Feeds.catchupFeedInGroup(${hl.feed_id})" href="#"><i class="icon-done material-icons">done_all</i></a>
- </div>`
+ <div class="right">
+ <i class="material-icons icon-score" title="${hl.score}" onclick="Article.setScore(${hl.id}, this)">${Article.getScorePic(hl.score)}</i>
- const tmp = document.createElement("div");
- tmp.innerHTML = vgrhdr;
+ <span style="cursor : pointer" title="${App.escapeHtml(hl.feed_title)}" onclick="Feeds.open({feed:${hl.feed_id}})">
+ ${hl.feed_icon}</span>
+ </div>
- $("headlines-frame").appendChild(tmp.firstChild);
+ </div>
- this.vgroup_last_feed = hl.feed_id;
- }
+ <div class="content" onclick="return Headlines.click(event, ${hl.id}, true);">
+ <div id="POSTNOTE-${hl.id}">${hl.note}</div>
+ <div class="content-inner" lang="${hl.lang ? hl.lang : 'en'}">
+ <img src="${App.getInitParam('icon_indicator_white')}">
+ </div>
+ <div class="intermediate">
+ ${hl.enclosures}
+ </div>
+ <div class="footer" onclick="event.stopPropagation()">
- if (App.isCombinedMode()) {
- row_class += App.getInitParam("cdm_expanded") ? " expanded" : " expandable";
-
- const comments = Article.formatComments(hl);
- const originally_from = Article.formatOriginallyFrom(hl);
-
- row = `<div class="cdm ${row_class} ${Article.getScoreClass(hl.score)}"
- id="RROW-${hl.id}"
- data-article-id="${hl.id}"
- data-orig-feed-id="${hl.feed_id}"
- data-content="${escapeHtml(hl.content)}"
- data-score="${hl.score}"
- data-article-title="${escapeHtml(hl.title)}"
- onmouseover="Article.mouseIn(${hl.id})"
- onmouseout="Article.mouseOut(${hl.id})">
-
- <div class="header">
<div class="left">
- <input dojoType="dijit.form.CheckBox" type="checkbox" onclick="Headlines.onRowChecked(this)" class='rchk'>
- <i class="marked-pic marked-${hl.id} material-icons" onclick="Headlines.toggleMark(${hl.id})">star</i>
- <i class="pub-pic pub-${hl.id} material-icons" onclick="Headlines.togglePub(${hl.id})">rss_feed</i>
+ ${hl.buttons_left}
+ <i class="material-icons">label_outline</i>
+ <span id="ATSTR-${hl.id}">${hl.tags_str}</span>
+ <a title="${__("Edit tags for this article")}" href="#"
+ onclick="Article.editTags(${hl.id})">(+)</a>
+ ${comments}
</div>
-
- <span onclick="return Headlines.click(event, ${hl.id});" data-article-id="${hl.id}" class="titleWrap hlMenuAttach">
- <a class="title" title="${escapeHtml(hl.title)}" target="_blank" rel="noopener noreferrer" href="${escapeHtml(hl.link)}">
- ${hl.title}</a>
- <span class="author">${hl.author}</span>
- ${hl.labels}
- ${hl.cdm_excerpt ? hl.cdm_excerpt : ""}
- </span>
-
- <div class="feed">
- <a href="#" style="background-color: ${hl.feed_bg_color}"
- onclick="Feeds.open({feed:${hl.feed_id}})">${hl.feed_title}</a>
- </div>
-
- <span class="updated" title="${hl.imported}">${hl.updated}</span>
-
- <div class="right">
- <i class="material-icons icon-score" title="${hl.score}" onclick="Article.setScore(${hl.id}, this)">${Article.getScorePic(hl.score)}</i>
-
- <span style="cursor : pointer" title="${escapeHtml(hl.feed_title)}" onclick="Feeds.open({feed:${hl.feed_id}})">
- ${hl.feed_icon}</span>
- </div>
-
- </div>
-
- <div class="content" onclick="return Headlines.click(event, ${hl.id}, true);">
- <div id="POSTNOTE-${hl.id}">${hl.note}</div>
- <div class="content-inner" lang="${hl.lang ? hl.lang : 'en'}">
- <img src="${App.getInitParam('icon_indicator_white')}">
- </div>
- <div class="intermediate">
- ${hl.enclosures}
- </div>
- <div class="footer" onclick="event.stopPropagation()">
-
- <div class="left">
- ${hl.buttons_left}
- <i class="material-icons">label_outline</i>
- <span id="ATSTR-${hl.id}">${hl.tags_str}</span>
- <a title="${__("Edit tags for this article")}" href="#"
- onclick="Article.editTags(${hl.id})">(+)</a>
- ${comments}
- </div>
-
- <div class="right">
- ${originally_from}
- ${hl.buttons}
- </div>
+
+ <div class="right">
+ ${originally_from}
+ ${hl.buttons}
</div>
</div>
- </div>`;
-
-
- } else {
- row = `<div class="hl ${row_class} ${Article.getScoreClass(hl.score)}"
- id="RROW-${hl.id}"
- data-orig-feed-id="${hl.feed_id}"
- data-article-id="${hl.id}"
- data-score="${hl.score}"
- data-article-title="${escapeHtml(hl.title)}"
- onmouseover="Article.mouseIn(${hl.id})"
- onmouseout="Article.mouseOut(${hl.id})">
- <div class="left">
- <input dojoType="dijit.form.CheckBox" type="checkbox" onclick="Headlines.onRowChecked(this)" class='rchk'>
- <i class="marked-pic marked-${hl.id} material-icons" onclick="Headlines.toggleMark(${hl.id})">star</i>
- <i class="pub-pic pub-${hl.id} material-icons" onclick="Headlines.togglePub(${hl.id})">rss_feed</i>
- </div>
- <div onclick="return Headlines.click(event, ${hl.id})" class="title">
- <span data-article-id="${hl.id}" class="hl-content hlMenuAttach">
- <a class="title" href="${escapeHtml(hl.link)}">${hl.title} <span class="preview">${hl.content_preview}</span></a>
- <span class="author">${hl.author}</span>
- ${hl.labels}
+ </div>
+ </div>`;
+
+
+ } else {
+ row = `<div class="hl ${row_class} ${Article.getScoreClass(hl.score)}"
+ id="RROW-${hl.id}"
+ data-orig-feed-id="${hl.feed_id}"
+ data-article-id="${hl.id}"
+ data-score="${hl.score}"
+ data-article-title="${App.escapeHtml(hl.title)}"
+ onmouseover="Article.mouseIn(${hl.id})"
+ onmouseout="Article.mouseOut(${hl.id})">
+ <div class="left">
+ <input dojoType="dijit.form.CheckBox" type="checkbox" onclick="Headlines.onRowChecked(this)" class='rchk'>
+ <i class="marked-pic marked-${hl.id} material-icons" onclick="Headlines.toggleMark(${hl.id})">star</i>
+ <i class="pub-pic pub-${hl.id} material-icons" onclick="Headlines.togglePub(${hl.id})">rss_feed</i>
+ </div>
+ <div onclick="return Headlines.click(event, ${hl.id})" class="title">
+ <span data-article-id="${hl.id}" class="hl-content hlMenuAttach">
+ <a class="title" href="${App.escapeHtml(hl.link)}">${hl.title} <span class="preview">${hl.content_preview}</span></a>
+ <span class="author">${hl.author}</span>
+ ${hl.labels}
+ </span>
+ </div>
+ <span class="feed">
+ <a style="background : ${hl.feed_bg_color}" href="#" onclick="Feeds.open({feed:${hl.feed_id}})">${hl.feed_title}</a>
</span>
- </div>
- <span class="feed">
- <a style="background : ${hl.feed_bg_color}" href="#" onclick="Feeds.open({feed:${hl.feed_id}})">${hl.feed_title}</a>
- </span>
- <div title="${hl.imported}">
- <span class="updated">${hl.updated}</span>
- </div>
- <div class="right">
- <i class="material-icons icon-score" title="${hl.score}" onclick="Article.setScore(${hl.id}, this)">${Article.getScorePic(hl.score)}</i>
- <span onclick="Feeds.open({feed:${hl.feed_id}})" style="cursor : pointer" title="${escapeHtml(hl.feed_title)}">${hl.feed_icon}</span>
- </div>
- </div>
- `;
- }
+ <div title="${hl.imported}">
+ <span class="updated">${hl.updated}</span>
+ </div>
+ <div class="right">
+ <i class="material-icons icon-score" title="${hl.score}" onclick="Article.setScore(${hl.id}, this)">${Article.getScorePic(hl.score)}</i>
+ <span onclick="Feeds.open({feed:${hl.feed_id}})" style="cursor : pointer" title="${App.escapeHtml(hl.feed_title)}">${hl.feed_icon}</span>
+ </div>
+ </div>
+ `;
+ }
- const tmp = document.createElement("div");
- tmp.innerHTML = row;
- dojo.parser.parse(tmp);
+ const tmp = document.createElement("div");
+ tmp.innerHTML = row;
+ dojo.parser.parse(tmp);
- this.row_observer.observe(tmp.firstChild, {attributes: true});
+ this.row_observer.observe(tmp.firstChild, {attributes: true});
- PluginHost.run(PluginHost.HOOK_HEADLINE_RENDERED, tmp.firstChild);
+ PluginHost.run(PluginHost.HOOK_HEADLINE_RENDERED, tmp.firstChild);
- return tmp.firstChild;
- },
- updateCurrentUnread: function() {
- if ($("feed_current_unread")) {
- const feed_unread = Feeds.getUnread(Feeds.getActive(), Feeds.activeIsCat());
+ return tmp.firstChild;
+ },
+ updateCurrentUnread: function () {
+ if ($("feed_current_unread")) {
+ const feed_unread = Feeds.getUnread(Feeds.getActive(), Feeds.activeIsCat());
- if (feed_unread > 0 && !Element.visible("feeds-holder")) {
- $("feed_current_unread").innerText = feed_unread;
- Element.show("feed_current_unread");
- } else {
- Element.hide("feed_current_unread");
- }
+ if (feed_unread > 0 && !Element.visible("feeds-holder")) {
+ $("feed_current_unread").innerText = feed_unread;
+ Element.show("feed_current_unread");
+ } else {
+ Element.hide("feed_current_unread");
}
- },
- onLoaded: function (transport, offset, append) {
- const reply = App.handleRpcJson(transport);
-
- console.log("Headlines.onLoaded: offset=", offset, "append=", append);
-
- let is_cat = false;
- let feed_id = false;
-
- if (reply) {
-
- is_cat = reply['headlines']['is_cat'];
- feed_id = reply['headlines']['id'];
- Feeds.last_search_query = reply['headlines']['search_query'];
-
- if (feed_id != -7 && (feed_id != Feeds.getActive() || is_cat != Feeds.activeIsCat()))
- return;
-
- const headlines_count = reply['headlines-info']['count'];
-
- //this.vgroup_last_feed = reply['headlines-info']['vgroup_last_feed'];
- this.current_first_id = reply['headlines']['first_id'];
-
- console.log('received', headlines_count, 'headlines');
-
- if (!append) {
- Feeds.infscroll_disabled = parseInt(headlines_count) != 30;
- console.log('infscroll_disabled=', Feeds.infscroll_disabled);
-
- // TODO: the below needs to be applied again when switching expanded/expandable on the fly
- // via hotkeys, not just on feed load
+ }
+ },
+ onLoaded: function (transport, offset, append) {
+ const reply = App.handleRpcJson(transport);
- $("headlines-frame").removeClassName("cdm");
- $("headlines-frame").removeClassName("normal");
+ console.log("Headlines.onLoaded: offset=", offset, "append=", append);
- $("headlines-frame").addClassName(App.isCombinedMode() ? "cdm" : "normal");
+ let is_cat = false;
+ let feed_id = false;
- $("headlines-frame").setAttribute("is-vfeed",
- reply['headlines']['is_vfeed'] ? 1 : 0);
+ if (reply) {
- // for floating title because it's placed outside of headlines-frame
- $("main").removeClassName("expandable");
- $("main").removeClassName("expanded");
+ is_cat = reply['headlines']['is_cat'];
+ feed_id = reply['headlines']['id'];
+ Feeds.last_search_query = reply['headlines']['search_query'];
- if (App.isCombinedMode())
- $("main").addClassName(App.getInitParam("cdm_expanded") ? " expanded" : " expandable");
+ if (feed_id != -7 && (feed_id != Feeds.getActive() || is_cat != Feeds.activeIsCat()))
+ return;
- Article.setActive(0);
+ const headlines_count = reply['headlines-info']['count'];
- try {
- $("headlines-frame").removeClassName("smooth-scroll");
- $("headlines-frame").scrollTop = 0;
- $("headlines-frame").addClassName("smooth-scroll");
+ //this.vgroup_last_feed = reply['headlines-info']['vgroup_last_feed'];
+ this.current_first_id = reply['headlines']['first_id'];
- Element.hide("floatingTitle");
- $("floatingTitle").setAttribute("data-article-id", 0);
- $("floatingTitle").innerHTML = "";
- } catch (e) {
- console.warn(e);
- }
+ console.log('received', headlines_count, 'headlines');
- this.headlines = [];
- this.vgroup_last_feed = undefined;
+ if (!append) {
+ Feeds.infscroll_disabled = parseInt(headlines_count) != 30;
+ console.log('infscroll_disabled=', Feeds.infscroll_disabled);
- dojo.html.set($("toolbar-headlines"),
- reply['headlines']['toolbar'],
- {parseContent: true});
+ // also called in renderAgain() after view mode switch
+ Headlines.setCommonClasses();
- if (typeof reply['headlines']['content'] == 'string') {
- $("headlines-frame").innerHTML = reply['headlines']['content'];
- } else {
- $("headlines-frame").innerHTML = '';
+ $("headlines-frame").setAttribute("is-vfeed",
+ reply['headlines']['is_vfeed'] ? 1 : 0);
- for (let i = 0; i < reply['headlines']['content'].length; i++) {
- const hl = reply['headlines']['content'][i];
-
- $("headlines-frame").appendChild(this.render(reply['headlines'], hl));
+ Article.setActive(0);
- this.headlines[parseInt(hl.id)] = hl;
- }
- }
+ try {
+ $("headlines-frame").removeClassName("smooth-scroll");
+ $("headlines-frame").scrollTop = 0;
+ $("headlines-frame").addClassName("smooth-scroll");
+ } catch (e) {
+ console.warn(e);
+ }
- let hsp = $("headlines-spacer");
+ this.headlines = [];
+ this.vgroup_last_feed = undefined;
- if (!hsp) {
- hsp = document.createElement("div");
- hsp.id = "headlines-spacer";
- }
+ dojo.html.set($("toolbar-headlines"),
+ reply['headlines']['toolbar'],
+ {parseContent: true});
- dijit.byId('headlines-frame').domNode.appendChild(hsp);
+ if (typeof reply['headlines']['content'] == 'string') {
+ $("headlines-frame").innerHTML = reply['headlines']['content'];
+ } else {
+ $("headlines-frame").innerHTML = '';
- this.initHeadlinesMenu();
+ for (let i = 0; i < reply['headlines']['content'].length; i++) {
+ const hl = reply['headlines']['content'][i];
- if (Feeds.infscroll_disabled)
- hsp.innerHTML = "<a href='#' onclick='Feeds.openNextUnread()'>" +
- __("Click to open next unread feed.") + "</a>";
+ $("headlines-frame").appendChild(this.render(reply['headlines'], hl));
- if (Feeds._search_query) {
- $("feed_title").innerHTML += "<span id='cancel_search'>" +
- " (<a href='#' onclick='Feeds.cancelSearch()'>" + __("Cancel search") + "</a>)" +
- "</span>";
+ this.headlines[parseInt(hl.id)] = hl;
}
+ }
- Headlines.updateCurrentUnread();
-
- } else if (headlines_count > 0 && feed_id == Feeds.getActive() && is_cat == Feeds.activeIsCat()) {
- const c = dijit.byId("headlines-frame");
-
- let hsp = $("headlines-spacer");
+ let hsp = $("headlines-spacer");
- if (hsp)
- c.domNode.removeChild(hsp);
+ if (!hsp) {
+ hsp = document.createElement("div");
+ hsp.id = "headlines-spacer";
+ }
- let headlines_appended = 0;
+ dijit.byId('headlines-frame').domNode.appendChild(hsp);
- if (typeof reply['headlines']['content'] == 'string') {
- $("headlines-frame").innerHTML = reply['headlines']['content'];
- } else {
- for (let i = 0; i < reply['headlines']['content'].length; i++) {
- const hl = reply['headlines']['content'][i];
+ this.initHeadlinesMenu();
- if (!this.headlines[parseInt(hl.id)]) {
- $("headlines-frame").appendChild(this.render(reply['headlines'], hl));
+ if (Feeds.infscroll_disabled)
+ hsp.innerHTML = "<a href='#' onclick='Feeds.openNextUnread()'>" +
+ __("Click to open next unread feed.") + "</a>";
- this.headlines[parseInt(hl.id)] = hl;
- ++headlines_appended;
- }
- }
- }
-
- Feeds.infscroll_disabled = headlines_appended == 0;
+ if (Feeds._search_query) {
+ $("feed_title").innerHTML += "<span id='cancel_search'>" +
+ " (<a href='#' onclick='Feeds.cancelSearch()'>" + __("Cancel search") + "</a>)" +
+ "</span>";
+ }
- console.log('appended', headlines_appended, 'headlines, infscroll_disabled=', Feeds.infscroll_disabled);
+ Headlines.updateCurrentUnread();
- if (!hsp) {
- hsp = document.createElement("div");
- hsp.id = "headlines-spacer";
- }
+ } else if (headlines_count > 0 && feed_id == Feeds.getActive() && is_cat == Feeds.activeIsCat()) {
+ const c = dijit.byId("headlines-frame");
- c.domNode.appendChild(hsp);
+ let hsp = $("headlines-spacer");
- this.initHeadlinesMenu();
+ if (hsp)
+ c.domNode.removeChild(hsp);
- if (Feeds.infscroll_disabled) {
- hsp.innerHTML = "<a href='#' onclick='Feeds.openNextUnread()'>" +
- __("Click to open next unread feed.") + "</a>";
- }
+ let headlines_appended = 0;
+ if (typeof reply['headlines']['content'] == 'string') {
+ $("headlines-frame").innerHTML = reply['headlines']['content'];
} else {
- Feeds.infscroll_disabled = true;
- const first_id_changed = reply['headlines']['first_id_changed'];
+ for (let i = 0; i < reply['headlines']['content'].length; i++) {
+ const hl = reply['headlines']['content'][i];
- console.log("no headlines received, infscroll_disabled=", Feeds.infscroll_disabled, 'first_id_changed=', first_id_changed);
-
- let hsp = $("headlines-spacer");
+ if (!this.headlines[parseInt(hl.id)]) {
+ $("headlines-frame").appendChild(this.render(reply['headlines'], hl));
- if (hsp) {
- if (first_id_changed) {
- hsp.innerHTML = "<a href='#' onclick='Feeds.reloadCurrent()'>" +
- __("New articles found, reload feed to continue.") + "</a>";
- } else {
- hsp.innerHTML = "<a href='#' onclick='Feeds.openNextUnread()'>" +
- __("Click to open next unread feed.") + "</a>";
+ this.headlines[parseInt(hl.id)] = hl;
+ ++headlines_appended;
}
}
}
- } else {
- console.error("Invalid object received: " + transport.responseText);
- dijit.byId("headlines-frame").attr('content', "<div class='whiteBox'>" +
- __('Could not update headlines (invalid object received - see error console for details)') +
- "</div>");
- }
-
- Feeds.infscroll_in_progress = 0;
+ Feeds.infscroll_disabled = headlines_appended == 0;
- // this is used to auto-catchup articles if needed after infscroll request has finished,
- // unpack visible articles, fill buffer more, etc
- this.scrollHandler();
+ console.log('appended', headlines_appended, 'headlines, infscroll_disabled=', Feeds.infscroll_disabled);
- Notify.close();
- },
- reverse: function () {
- const toolbar = document.forms["toolbar-main"];
- const order_by = dijit.getEnclosingWidget(toolbar.order_by);
-
- let value = order_by.attr('value');
-
- if (value != "date_reverse")
- value = "date_reverse";
- else
- value = "default";
+ if (!hsp) {
+ hsp = document.createElement("div");
+ hsp.id = "headlines-spacer";
+ }
- order_by.attr('value', value);
+ c.domNode.appendChild(hsp);
- Feeds.reloadCurrent();
- },
- selectionToggleUnread: function (params) {
- params = params || {};
+ this.initHeadlinesMenu();
- const cmode = params.cmode != undefined ? params.cmode : 2;
- const no_error = params.no_error || false;
- const ids = params.ids || Headlines.getSelected();
+ if (Feeds.infscroll_disabled) {
+ hsp.innerHTML = "<a href='#' onclick='Feeds.openNextUnread()'>" +
+ __("Click to open next unread feed.") + "</a>";
+ }
- if (ids.length == 0) {
- if (!no_error)
- alert(__("No articles selected."));
+ } else {
+ Feeds.infscroll_disabled = true;
+ const first_id_changed = reply['headlines']['first_id_changed'];
- return;
- }
+ console.log("no headlines received, infscroll_disabled=", Feeds.infscroll_disabled, 'first_id_changed=', first_id_changed);
- ids.each((id) => {
- const row = $("RROW-" + id);
+ const hsp = $("headlines-spacer");
- if (row) {
- switch (cmode) {
- case 0:
- row.removeClassName("Unread");
- break;
- case 1:
- row.addClassName("Unread");
- break;
- case 2:
- row.toggleClassName("Unread");
+ if (hsp) {
+ if (first_id_changed) {
+ hsp.innerHTML = "<a href='#' onclick='Feeds.reloadCurrent()'>" +
+ __("New articles found, reload feed to continue.") + "</a>";
+ } else {
+ hsp.innerHTML = "<a href='#' onclick='Feeds.openNextUnread()'>" +
+ __("Click to open next unread feed.") + "</a>";
}
}
- });
- },
- selectionToggleMarked: function (ids) {
- ids = ids || Headlines.getSelected();
-
- if (ids.length == 0) {
- alert(__("No articles selected."));
- return;
}
- ids.each((id) => {
- this.toggleMark(id);
+ $$(".cdm .header-sticky-guard").each((e) => {
+ this.sticky_header_observer.observe(e)
});
- },
- selectionTogglePublished: function (ids) {
- ids = ids || Headlines.getSelected();
-
- if (ids.length == 0) {
- alert(__("No articles selected."));
- return;
- }
-
- ids.each((id) => {
- this.togglePub(id);
- });
- },
- toggleMark: function (id) {
- const row = $("RROW-" + id);
-
- if (row)
- row.toggleClassName("marked");
-
- },
- togglePub: function (id) {
- const row = $("RROW-" + id);
-
- if (row)
- row.toggleClassName("published");
- },
- move: function (mode, params) {
- params = params || {};
-
- const noscroll = params.noscroll || false;
- const noexpand = params.noexpand || false;
- const event = params.event;
-
- const rows = Headlines.getLoaded();
-
- let prev_id = false;
- let next_id = false;
-
- const active_row = $("RROW-" + Article.getActive());
- if (!active_row) {
- Article.setActive(0);
- }
-
- if (!Article.getActive() || (active_row && !Headlines.isChildVisible(active_row, $("headlines-frame")))) {
- next_id = Headlines.firstVisible();
- prev_id = next_id;
- } else {
- for (let i = 0; i < rows.length; i++) {
- if (rows[i] == Article.getActive()) {
-
- // Account for adjacent identical article ids.
- if (i > 0) prev_id = rows[i - 1];
+ if (App.getInitParam("cdm_expanded"))
+ $$("#headlines-frame > div[id*=RROW].cdm").each((e) => {
+ this.unpack_observer.observe(e)
+ });
- for (let j = i + 1; j < rows.length; j++) {
- if (rows[j] != Article.getActive()) {
- next_id = rows[j];
- break;
- }
- }
- break;
- }
- }
- }
+ } else {
+ console.error("Invalid object received: " + transport.responseText);
+ dijit.byId("headlines-frame").attr('content', "<div class='whiteBox'>" +
+ __('Could not update headlines (invalid object received - see error console for details)') +
+ "</div>");
+ }
- console.log("cur: " + Article.getActive() + " next: " + next_id);
+ Feeds.infscroll_in_progress = 0;
- if (mode === "next") {
- if (next_id || Article.getActive()) {
- if (App.isCombinedMode()) {
+ // this is used to auto-catchup articles if needed after infscroll request has finished,
+ // unpack visible articles, fill buffer more, etc
+ this.scrollHandler();
- //const row = $("RROW-" + Article.getActive());
- const ctr = $("headlines-frame");
+ Notify.close();
+ },
+ reverse: function () {
+ const toolbar = document.forms["toolbar-main"];
+ const order_by = dijit.getEnclosingWidget(toolbar.order_by);
- if (!noscroll) {
- Article.scroll(ctr.offsetHeight / 2, event);
- } else if (next_id) {
- Article.setActive(next_id);
- Article.cdmScrollToId(next_id, true, event);
- }
+ let value = order_by.attr('value');
- } else if (next_id) {
- Headlines.correctHeadlinesOffset(next_id);
- Article.view(next_id, noexpand);
- }
- }
- }
+ if (value != "date_reverse")
+ value = "date_reverse";
+ else
+ value = "default";
- if (mode === "prev") {
- if (prev_id || Article.getActive()) {
- if (App.isCombinedMode()) {
+ order_by.attr('value', value);
- const row = $("RROW-" + Article.getActive());
- //const prev_row = $("RROW-" + prev_id);
- const ctr = $("headlines-frame");
+ Feeds.reloadCurrent();
+ },
+ selectionToggleUnread: function (params) {
+ params = params || {};
- if (!noscroll) {
- Article.scroll(-ctr.offsetHeight / 2, event);
- } else {
- if (row && Math.round(row.offsetTop) < Math.round(ctr.scrollTop)) {
- Article.cdmScrollToId(Article.getActive(), noscroll, event);
- } else if (prev_id) {
- Article.setActive(prev_id);
- Article.cdmScrollToId(prev_id, noscroll, event);
- }
- }
+ const cmode = params.cmode != undefined ? params.cmode : 2;
+ const no_error = params.no_error || false;
+ const ids = params.ids || Headlines.getSelected();
- } else if (prev_id) {
- Headlines.correctHeadlinesOffset(prev_id);
- Article.view(prev_id, noexpand);
- }
- }
- }
- },
- updateSelectedPrompt: function () {
- const count = Headlines.getSelected().length;
- const elem = $("selected_prompt");
+ if (ids.length == 0) {
+ if (!no_error)
+ alert(__("No articles selected."));
- if (elem) {
- elem.innerHTML = ngettext("%d article selected",
- "%d articles selected", count).replace("%d", count);
+ return;
+ }
- count > 0 ? Element.show(elem) : Element.hide(elem);
- }
- },
- toggleUnread: function (id, cmode) {
+ ids.each((id) => {
const row = $("RROW-" + id);
if (row) {
- if (typeof cmode == "undefined") cmode = 2;
-
switch (cmode) {
case 0:
row.removeClassName("Unread");
@@ -948,554 +764,668 @@ define(["dojo/_base/declare"], function (declare) {
break;
case 2:
row.toggleClassName("Unread");
- break;
}
}
- },
- selectionRemoveLabel: function (id, ids) {
- if (!ids) ids = Headlines.getSelected();
+ });
+ },
+ selectionToggleMarked: function (ids) {
+ ids = ids || Headlines.getSelected();
+
+ if (ids.length == 0) {
+ alert(__("No articles selected."));
+ return;
+ }
- if (ids.length == 0) {
- alert(__("No articles selected."));
- return;
- }
+ ids.each((id) => {
+ this.toggleMark(id);
+ });
+ },
+ selectionTogglePublished: function (ids) {
+ ids = ids || Headlines.getSelected();
- const query = {
- op: "article", method: "removeFromLabel",
- ids: ids.toString(), lid: id
- };
+ if (ids.length == 0) {
+ alert(__("No articles selected."));
+ return;
+ }
- xhrPost("backend.php", query, (transport) => {
- App.handleRpcJson(transport);
- this.onLabelsUpdated(transport);
- });
- },
- selectionAssignLabel: function (id, ids) {
- if (!ids) ids = Headlines.getSelected();
+ ids.each((id) => {
+ this.togglePub(id);
+ });
+ },
+ toggleMark: function (id) {
+ const row = $("RROW-" + id);
+
+ if (row)
+ row.toggleClassName("marked");
+
+ },
+ togglePub: function (id) {
+ const row = $("RROW-" + id);
+
+ if (row)
+ row.toggleClassName("published");
+ },
+ move: function (mode, params) {
+ params = params || {};
+
+ const no_expand = params.no_expand || false;
+ const force_previous = params.force_previous || this.default_force_previous;
+ const force_to_top = params.force_to_top || this.default_force_to_top;
+
+ let prev_id = false;
+ let next_id = false;
+ let current_id = Article.getActive();
+
+ if (!Headlines.isChildVisible($("RROW-" + current_id))) {
+ console.log('active article is obscured, resetting to first visible...');
+ current_id = Headlines.firstVisible();
+ prev_id = current_id;
+ next_id = current_id;
+ } else {
+ const rows = Headlines.getLoaded();
- if (ids.length == 0) {
- alert(__("No articles selected."));
- return;
- }
+ for (let i = 0; i < rows.length; i++) {
+ if (rows[i] == current_id) {
- const query = {
- op: "article", method: "assignToLabel",
- ids: ids.toString(), lid: id
- };
+ // Account for adjacent identical article ids.
+ if (i > 0) prev_id = rows[i - 1];
- xhrPost("backend.php", query, (transport) => {
- App.handleRpcJson(transport);
- this.onLabelsUpdated(transport);
- });
- },
- deleteSelection: function () {
- const rows = Headlines.getSelected();
+ for (let j = i + 1; j < rows.length; j++) {
+ if (rows[j] != current_id) {
+ next_id = rows[j];
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
- if (rows.length == 0) {
- alert(__("No articles selected."));
- return;
+ console.log("cur: " + current_id + " next: " + next_id + " prev:" + prev_id);
+
+ if (mode === "next") {
+ if (next_id) {
+ if (App.isCombinedMode()) {
+ window.requestAnimationFrame(() => {
+ Article.setActive(next_id);
+ Article.cdmMoveToId(next_id, {force_to_top: force_to_top});
+ });
+ } else {
+ Article.view(next_id, no_expand);
+ }
}
+ } else if (mode === "prev") {
+ if (prev_id || current_id) {
+ if (App.isCombinedMode()) {
+ window.requestAnimationFrame(() => {
+ const row = $("RROW-" + current_id);
+ const ctr = $("headlines-frame");
+ const delta_px = Math.round(row.offsetTop) - Math.round(ctr.scrollTop);
- const fn = Feeds.getName(Feeds.getActive(), Feeds.activeIsCat());
- let str;
+ console.log('moving back, delta_px', delta_px);
- if (Feeds.getActive() != 0) {
- str = ngettext("Delete %d selected article in %s?", "Delete %d selected articles in %s?", rows.length);
- } else {
- str = ngettext("Delete %d selected article?", "Delete %d selected articles?", rows.length);
+ if (!force_previous && row && delta_px < -8) {
+ Article.setActive(current_id);
+ Article.cdmMoveToId(current_id, {force_to_top: force_to_top});
+ } else if (prev_id) {
+ Article.setActive(prev_id);
+ Article.cdmMoveToId(prev_id, {force_to_top: force_to_top});
+ }
+ });
+ } else if (prev_id) {
+ Article.view(prev_id, no_expand);
+ }
}
+ }
+ },
+ updateSelectedPrompt: function () {
+ const count = Headlines.getSelected().length;
+ const elem = $("selected_prompt");
- str = str.replace("%d", rows.length);
- str = str.replace("%s", fn);
+ if (elem) {
+ elem.innerHTML = ngettext("%d article selected",
+ "%d articles selected", count).replace("%d", count);
- if (App.getInitParam("confirm_feed_catchup") && !confirm(str)) {
- return;
+ count > 0 ? Element.show(elem) : Element.hide(elem);
+ }
+ },
+ toggleUnread: function (id, cmode) {
+ const row = $("RROW-" + id);
+
+ if (row) {
+ if (typeof cmode == "undefined") cmode = 2;
+
+ switch (cmode) {
+ case 0:
+ row.removeClassName("Unread");
+ break;
+ case 1:
+ row.addClassName("Unread");
+ break;
+ case 2:
+ row.toggleClassName("Unread");
+ break;
}
+ }
+ },
+ selectionRemoveLabel: function (id, ids) {
+ if (!ids) ids = Headlines.getSelected();
- const query = {op: "rpc", method: "delete", ids: rows.toString()};
+ if (ids.length == 0) {
+ alert(__("No articles selected."));
+ return;
+ }
- xhrPost("backend.php", query, (transport) => {
- App.handleRpcJson(transport);
- Feeds.reloadCurrent();
- });
- },
- getSelected: function () {
- const rv = [];
+ const query = {
+ op: "article", method: "removeFromLabel",
+ ids: ids.toString(), lid: id
+ };
+
+ xhrPost("backend.php", query, (transport) => {
+ App.handleRpcJson(transport);
+ this.onLabelsUpdated(transport);
+ });
+ },
+ selectionAssignLabel: function (id, ids) {
+ if (!ids) ids = Headlines.getSelected();
+
+ if (ids.length == 0) {
+ alert(__("No articles selected."));
+ return;
+ }
- $$("#headlines-frame > div[id*=RROW][class*=Selected]").each(
- function (child) {
- rv.push(child.getAttribute("data-article-id"));
- });
+ const query = {
+ op: "article", method: "assignToLabel",
+ ids: ids.toString(), lid: id
+ };
+
+ xhrPost("backend.php", query, (transport) => {
+ App.handleRpcJson(transport);
+ this.onLabelsUpdated(transport);
+ });
+ },
+ deleteSelection: function () {
+ const rows = Headlines.getSelected();
+
+ if (rows.length == 0) {
+ alert(__("No articles selected."));
+ return;
+ }
- // consider active article a honorary member of selected articles
- if (Article.getActive())
- rv.push(Article.getActive());
+ const fn = Feeds.getName(Feeds.getActive(), Feeds.activeIsCat());
+ let str;
- return rv.uniq();
- },
- getLoaded: function () {
- const rv = [];
+ if (Feeds.getActive() != 0) {
+ str = ngettext("Delete %d selected article in %s?", "Delete %d selected articles in %s?", rows.length);
+ } else {
+ str = ngettext("Delete %d selected article?", "Delete %d selected articles?", rows.length);
+ }
- const children = $$("#headlines-frame > div[id*=RROW-]");
+ str = str.replace("%d", rows.length);
+ str = str.replace("%s", fn);
- children.each(function (child) {
- if (Element.visible(child)) {
- rv.push(child.getAttribute("data-article-id"));
- }
+ if (App.getInitParam("confirm_feed_catchup") && !confirm(str)) {
+ return;
+ }
+
+ const query = {op: "rpc", method: "delete", ids: rows.toString()};
+
+ xhrPost("backend.php", query, (transport) => {
+ App.handleRpcJson(transport);
+ Feeds.reloadCurrent();
+ });
+ },
+ getSelected: function () {
+ const rv = [];
+
+ $$("#headlines-frame > div[id*=RROW][class*=Selected]").each(
+ function (child) {
+ rv.push(child.getAttribute("data-article-id"));
});
- return rv;
- },
- onRowChecked: function (elem) {
- const row = elem.domNode.up("div[id*=RROW]");
+ // consider active article a honorary member of selected articles
+ if (Article.getActive())
+ rv.push(Article.getActive());
- // do not allow unchecking active article checkbox
- if (row.hasClassName("active")) {
- elem.attr("checked", 1);
- return;
- }
+ return rv.uniq();
+ },
+ getLoaded: function () {
+ const rv = [];
- if (elem.attr("checked")) {
- row.addClassName("Selected");
- } else {
- row.removeClassName("Selected");
- }
- },
- getRange: function (start, stop) {
- if (start == stop)
- return [start];
+ const children = $$("#headlines-frame > div[id*=RROW-]");
- const rows = $$("#headlines-frame > div[id*=RROW]");
- const results = [];
- let collecting = false;
+ children.each(function (child) {
+ if (Element.visible(child)) {
+ rv.push(child.getAttribute("data-article-id"));
+ }
+ });
- for (let i = 0; i < rows.length; i++) {
- const row = rows[i];
- const id = row.getAttribute('data-article-id');
+ return rv;
+ },
+ onRowChecked: function (elem) {
+ const row = elem.domNode.up("div[id*=RROW]");
- if (id == start || id == stop) {
- if (!collecting) {
- collecting = true;
- } else {
- results.push(id);
- break;
- }
- }
+ // do not allow unchecking active article checkbox
+ if (row.hasClassName("active")) {
+ elem.attr("checked", 1);
+ return;
+ }
- if (collecting)
+ if (elem.attr("checked")) {
+ row.addClassName("Selected");
+ } else {
+ row.removeClassName("Selected");
+ }
+ },
+ getRange: function (start, stop) {
+ if (start == stop)
+ return [start];
+
+ const rows = $$("#headlines-frame > div[id*=RROW]");
+ const results = [];
+ let collecting = false;
+
+ for (let i = 0; i < rows.length; i++) {
+ const row = rows[i];
+ const id = row.getAttribute('data-article-id');
+
+ if (id == start || id == stop) {
+ if (!collecting) {
+ collecting = true;
+ } else {
results.push(id);
+ break;
+ }
}
- return results;
- },
- select: function (mode, articleId) {
- // mode = all,none,unread,invert,marked,published
- let query = "#headlines-frame > div[id*=RROW]";
+ if (collecting)
+ results.push(id);
+ }
- if (articleId) query += "[data-article-id=" + articleId + "]";
+ return results;
+ },
+ select: function (mode, articleId) {
+ // mode = all,none,unread,invert,marked,published
+ let query = "#headlines-frame > div[id*=RROW]";
+
+ if (articleId) query += "[data-article-id=" + articleId + "]";
+
+ switch (mode) {
+ case "none":
+ case "all":
+ case "invert":
+ break;
+ case "marked":
+ query += "[class*=marked]";
+ break;
+ case "published":
+ query += "[class*=published]";
+ break;
+ case "unread":
+ query += "[class*=Unread]";
+ break;
+ default:
+ console.warn("select: unknown mode", mode);
+ }
+
+ const rows = $$(query);
+
+ for (let i = 0; i < rows.length; i++) {
+ const row = rows[i];
switch (mode) {
case "none":
- case "all":
- case "invert":
+ row.removeClassName("Selected");
break;
- case "marked":
- query += "[class*=marked]";
- break;
- case "published":
- query += "[class*=published]";
- break;
- case "unread":
- query += "[class*=Unread]";
+ case "invert":
+ row.toggleClassName("Selected");
break;
default:
- console.warn("select: unknown mode", mode);
- }
-
- const rows = $$(query);
-
- for (let i = 0; i < rows.length; i++) {
- const row = rows[i];
-
- switch (mode) {
- case "none":
- row.removeClassName("Selected");
- break;
- case "invert":
- row.toggleClassName("Selected");
- break;
- default:
- row.addClassName("Selected");
- }
- }
- },
- archiveSelection: function () {
- const rows = Headlines.getSelected();
-
- if (rows.length == 0) {
- alert(__("No articles selected."));
- return;
+ row.addClassName("Selected");
}
+ }
+ },
+ archiveSelection: function () {
+ const rows = Headlines.getSelected();
- const fn = Feeds.getName(Feeds.getActive(), Feeds.activeIsCat());
- let str;
- let op;
+ if (rows.length == 0) {
+ alert(__("No articles selected."));
+ return;
+ }
- if (Feeds.getActive() != 0) {
- str = ngettext("Archive %d selected article in %s?", "Archive %d selected articles in %s?", rows.length);
- op = "archive";
- } else {
- str = ngettext("Move %d archived article back?", "Move %d archived articles back?", rows.length);
- str += " " + __("Please note that unstarred articles might get purged on next feed update.");
+ const fn = Feeds.getName(Feeds.getActive(), Feeds.activeIsCat());
+ let str;
+ let op;
- op = "unarchive";
- }
+ if (Feeds.getActive() != 0) {
+ str = ngettext("Archive %d selected article in %s?", "Archive %d selected articles in %s?", rows.length);
+ op = "archive";
+ } else {
+ str = ngettext("Move %d archived article back?", "Move %d archived articles back?", rows.length);
+ str += " " + __("Please note that unstarred articles might get purged on next feed update.");
- str = str.replace("%d", rows.length);
- str = str.replace("%s", fn);
+ op = "unarchive";
+ }
- if (App.getInitParam("confirm_feed_catchup") && !confirm(str)) {
- return;
- }
+ str = str.replace("%d", rows.length);
+ str = str.replace("%s", fn);
- const query = {op: "rpc", method: op, ids: rows.toString()};
+ if (App.getInitParam("confirm_feed_catchup") && !confirm(str)) {
+ return;
+ }
- xhrPost("backend.php", query, (transport) => {
- App.handleRpcJson(transport);
- Feeds.reloadCurrent();
- });
- },
- catchupSelection: function () {
- const rows = Headlines.getSelected();
+ const query = {op: "rpc", method: op, ids: rows.toString()};
- if (rows.length == 0) {
- alert(__("No articles selected."));
- return;
- }
+ xhrPost("backend.php", query, (transport) => {
+ App.handleRpcJson(transport);
+ Feeds.reloadCurrent();
+ });
+ },
+ catchupSelection: function () {
+ const rows = Headlines.getSelected();
+
+ if (rows.length == 0) {
+ alert(__("No articles selected."));
+ return;
+ }
- const fn = Feeds.getName(Feeds.getActive(), Feeds.activeIsCat());
+ const fn = Feeds.getName(Feeds.getActive(), Feeds.activeIsCat());
- let str = ngettext("Mark %d selected article in %s as read?", "Mark %d selected articles in %s as read?", rows.length);
+ let str = ngettext("Mark %d selected article in %s as read?", "Mark %d selected articles in %s as read?", rows.length);
- str = str.replace("%d", rows.length);
- str = str.replace("%s", fn);
+ str = str.replace("%d", rows.length);
+ str = str.replace("%s", fn);
- if (App.getInitParam("confirm_feed_catchup") && !confirm(str)) {
- return;
- }
+ if (App.getInitParam("confirm_feed_catchup") && !confirm(str)) {
+ return;
+ }
- Headlines.selectionToggleUnread({ids: rows, cmode: 0});
- },
- catchupRelativeTo: function (below, id) {
+ Headlines.selectionToggleUnread({ids: rows, cmode: 0});
+ },
+ catchupRelativeTo: function (below, id) {
- if (!id) id = Article.getActive();
+ if (!id) id = Article.getActive();
- if (!id) {
- alert(__("No article is selected."));
- return;
- }
+ if (!id) {
+ alert(__("No article is selected."));
+ return;
+ }
- const visible_ids = this.getLoaded();
+ const visible_ids = this.getLoaded();
- const ids_to_mark = [];
+ const ids_to_mark = [];
- if (!below) {
- for (let i = 0; i < visible_ids.length; i++) {
- if (visible_ids[i] != id) {
- const e = $("RROW-" + visible_ids[i]);
+ if (!below) {
+ for (let i = 0; i < visible_ids.length; i++) {
+ if (visible_ids[i] != id) {
+ const e = $("RROW-" + visible_ids[i]);
- if (e && e.hasClassName("Unread")) {
- ids_to_mark.push(visible_ids[i]);
- }
- } else {
- break;
+ if (e && e.hasClassName("Unread")) {
+ ids_to_mark.push(visible_ids[i]);
}
+ } else {
+ break;
}
- } else {
- for (let i = visible_ids.length - 1; i >= 0; i--) {
- if (visible_ids[i] != id) {
- const e = $("RROW-" + visible_ids[i]);
+ }
+ } else {
+ for (let i = visible_ids.length - 1; i >= 0; i--) {
+ if (visible_ids[i] != id) {
+ const e = $("RROW-" + visible_ids[i]);
- if (e && e.hasClassName("Unread")) {
- ids_to_mark.push(visible_ids[i]);
- }
- } else {
- break;
+ if (e && e.hasClassName("Unread")) {
+ ids_to_mark.push(visible_ids[i]);
}
+ } else {
+ break;
}
}
+ }
- if (ids_to_mark.length == 0) {
- alert(__("No articles found to mark"));
- } else {
- const msg = ngettext("Mark %d article as read?", "Mark %d articles as read?", ids_to_mark.length).replace("%d", ids_to_mark.length);
+ if (ids_to_mark.length == 0) {
+ alert(__("No articles found to mark"));
+ } else {
+ const msg = ngettext("Mark %d article as read?", "Mark %d articles as read?", ids_to_mark.length).replace("%d", ids_to_mark.length);
- if (App.getInitParam("confirm_feed_catchup") != 1 || confirm(msg)) {
+ if (App.getInitParam("confirm_feed_catchup") != 1 || confirm(msg)) {
- for (var i = 0; i < ids_to_mark.length; i++) {
- var e = $("RROW-" + ids_to_mark[i]);
- e.removeClassName("Unread");
- }
+ for (let i = 0; i < ids_to_mark.length; i++) {
+ const e = $("RROW-" + ids_to_mark[i]);
+ e.removeClassName("Unread");
}
}
- },
- onLabelsUpdated: function (transport) {
- const data = JSON.parse(transport.responseText);
-
- if (data) {
- data['info-for-headlines'].each(function (elem) {
- $$(".HLLCTR-" + elem.id).each(function (ctr) {
- ctr.innerHTML = elem.labels;
- });
+ }
+ },
+ onLabelsUpdated: function (transport) {
+ const data = JSON.parse(transport.responseText);
+
+ if (data) {
+ data['info-for-headlines'].each(function (elem) {
+ $$(".HLLCTR-" + elem.id).each(function (ctr) {
+ ctr.innerHTML = elem.labels;
});
+ });
+ }
+ },
+ onActionChanged: function (elem) {
+ // eslint-disable-next-line no-eval
+ eval(elem.value);
+ elem.attr('value', 'false');
+ },
+ scrollToArticleId: function (id) {
+ const container = $("headlines-frame");
+ const row = $("RROW-" + id);
+
+ if (!container || !row) return;
+
+ const viewport = container.offsetHeight;
+
+ const rel_offset_top = row.offsetTop - container.scrollTop;
+ const rel_offset_bottom = row.offsetTop + row.offsetHeight - container.scrollTop;
+
+ //console.log("Rtop: " + rel_offset_top + " Rbtm: " + rel_offset_bottom);
+ //console.log("Vport: " + viewport);
+
+ if (rel_offset_top <= 0 || rel_offset_top > viewport) {
+ container.scrollTop = row.offsetTop;
+ } else if (rel_offset_bottom > viewport) {
+ container.scrollTop = row.offsetTop + row.offsetHeight - viewport;
+ }
+ },
+ headlinesMenuCommon: function (menu) {
+
+ menu.addChild(new dijit.MenuItem({
+ label: __("Open original article"),
+ onClick: function (/* event */) {
+ Article.openInNewWindow(this.getParent().currentTarget.getAttribute("data-article-id"));
}
- },
- onActionChanged: function (elem) {
- eval(elem.value);
- elem.attr('value', 'false');
- },
- correctHeadlinesOffset: function (id) {
- const container = $("headlines-frame");
- const row = $("RROW-" + id);
+ }));
- if (!container || !row) return;
+ menu.addChild(new dijit.MenuItem({
+ label: __("Display article URL"),
+ onClick: function (/* event */) {
+ Article.displayUrl(this.getParent().currentTarget.getAttribute("data-article-id"));
+ }
+ }));
- const viewport = container.offsetHeight;
+ menu.addChild(new dijit.MenuSeparator());
- const rel_offset_top = row.offsetTop - container.scrollTop;
- const rel_offset_bottom = row.offsetTop + row.offsetHeight - container.scrollTop;
+ menu.addChild(new dijit.MenuItem({
+ label: __("Toggle unread"),
+ onClick: function () {
- //console.log("Rtop: " + rel_offset_top + " Rbtm: " + rel_offset_bottom);
- //console.log("Vport: " + viewport);
+ let ids = Headlines.getSelected();
+ // cast to string
+ const id = (this.getParent().currentTarget.getAttribute("data-article-id")) + "";
+ ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id];
- if (rel_offset_top <= 0 || rel_offset_top > viewport) {
- container.scrollTop = row.offsetTop;
- } else if (rel_offset_bottom > viewport) {
- container.scrollTop = row.offsetTop + row.offsetHeight - viewport;
+ Headlines.selectionToggleUnread({ids: ids, no_error: 1});
}
- },
- initFloatingMenu: function () {
- if (!dijit.byId("floatingMenu")) {
+ }));
- const menu = new dijit.Menu({
- id: "floatingMenu",
- selector: ".hlMenuAttach",
- targetNodeIds: ["floatingTitle"]
- });
-
- this.headlinesMenuCommon(menu);
+ menu.addChild(new dijit.MenuItem({
+ label: __("Toggle starred"),
+ onClick: function () {
+ let ids = Headlines.getSelected();
+ // cast to string
+ const id = (this.getParent().currentTarget.getAttribute("data-article-id")) + "";
+ ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id];
- menu.startup();
+ Headlines.selectionToggleMarked(ids);
}
- },
- headlinesMenuCommon: function (menu) {
+ }));
- menu.addChild(new dijit.MenuItem({
- label: __("Open original article"),
- onClick: function (event) {
- Article.openInNewWindow(this.getParent().currentTarget.getAttribute("data-article-id"));
- }
- }));
-
- menu.addChild(new dijit.MenuItem({
- label: __("Display article URL"),
- onClick: function (event) {
- Article.displayUrl(this.getParent().currentTarget.getAttribute("data-article-id"));
- }
- }));
-
- menu.addChild(new dijit.MenuSeparator());
+ menu.addChild(new dijit.MenuItem({
+ label: __("Toggle published"),
+ onClick: function () {
+ let ids = Headlines.getSelected();
+ // cast to string
+ const id = (this.getParent().currentTarget.getAttribute("data-article-id")) + "";
+ ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id];
- menu.addChild(new dijit.MenuItem({
- label: __("Toggle unread"),
- onClick: function () {
+ Headlines.selectionTogglePublished(ids);
+ }
+ }));
- let ids = Headlines.getSelected();
- // cast to string
- const id = (this.getParent().currentTarget.getAttribute("data-article-id")) + "";
- ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id];
+ menu.addChild(new dijit.MenuSeparator());
- Headlines.selectionToggleUnread({ids: ids, no_error: 1});
- }
- }));
+ menu.addChild(new dijit.MenuItem({
+ label: __("Mark above as read"),
+ onClick: function () {
+ Headlines.catchupRelativeTo(0, this.getParent().currentTarget.getAttribute("data-article-id"));
+ }
+ }));
- menu.addChild(new dijit.MenuItem({
- label: __("Toggle starred"),
- onClick: function () {
- let ids = Headlines.getSelected();
- // cast to string
- const id = (this.getParent().currentTarget.getAttribute("data-article-id")) + "";
- ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id];
+ menu.addChild(new dijit.MenuItem({
+ label: __("Mark below as read"),
+ onClick: function () {
+ Headlines.catchupRelativeTo(1, this.getParent().currentTarget.getAttribute("data-article-id"));
+ }
+ }));
- Headlines.selectionToggleMarked(ids);
- }
- }));
- menu.addChild(new dijit.MenuItem({
- label: __("Toggle published"),
- onClick: function () {
- let ids = Headlines.getSelected();
- // cast to string
- const id = (this.getParent().currentTarget.getAttribute("data-article-id")) + "";
- ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id];
+ const labels = App.getInitParam("labels");
- Headlines.selectionTogglePublished(ids);
- }
- }));
+ if (labels && labels.length) {
menu.addChild(new dijit.MenuSeparator());
- menu.addChild(new dijit.MenuItem({
- label: __("Mark above as read"),
- onClick: function () {
- Headlines.catchupRelativeTo(0, this.getParent().currentTarget.getAttribute("data-article-id"));
- }
- }));
+ const labelAddMenu = new dijit.Menu({ownerMenu: menu});
+ const labelDelMenu = new dijit.Menu({ownerMenu: menu});
- menu.addChild(new dijit.MenuItem({
- label: __("Mark below as read"),
- onClick: function () {
- Headlines.catchupRelativeTo(1, this.getParent().currentTarget.getAttribute("data-article-id"));
- }
- }));
+ labels.each(function (label) {
+ const bare_id = label.id;
+ const name = label.caption;
+ labelAddMenu.addChild(new dijit.MenuItem({
+ label: name,
+ labelId: bare_id,
+ onClick: function () {
- const labels = App.getInitParam("labels");
-
- if (labels && labels.length) {
-
- menu.addChild(new dijit.MenuSeparator());
-
- const labelAddMenu = new dijit.Menu({ownerMenu: menu});
- const labelDelMenu = new dijit.Menu({ownerMenu: menu});
-
- labels.each(function (label) {
- const bare_id = label.id;
- const name = label.caption;
-
- labelAddMenu.addChild(new dijit.MenuItem({
- label: name,
- labelId: bare_id,
- onClick: function () {
-
- let ids = Headlines.getSelected();
- // cast to string
- const id = (this.getParent().ownerMenu.currentTarget.getAttribute("data-article-id")) + "";
-
- ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id];
-
- Headlines.selectionAssignLabel(this.labelId, ids);
- }
- }));
+ let ids = Headlines.getSelected();
+ // cast to string
+ const id = (this.getParent().ownerMenu.currentTarget.getAttribute("data-article-id")) + "";
- labelDelMenu.addChild(new dijit.MenuItem({
- label: name,
- labelId: bare_id,
- onClick: function () {
- let ids = Headlines.getSelected();
- // cast to string
- const id = (this.getParent().ownerMenu.currentTarget.getAttribute("data-article-id")) + "";
+ ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id];
- ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id];
+ Headlines.selectionAssignLabel(this.labelId, ids);
+ }
+ }));
- Headlines.selectionRemoveLabel(this.labelId, ids);
- }
- }));
+ labelDelMenu.addChild(new dijit.MenuItem({
+ label: name,
+ labelId: bare_id,
+ onClick: function () {
+ let ids = Headlines.getSelected();
+ // cast to string
+ const id = (this.getParent().ownerMenu.currentTarget.getAttribute("data-article-id")) + "";
- });
+ ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id];
- menu.addChild(new dijit.PopupMenuItem({
- label: __("Assign label"),
- popup: labelAddMenu
- }));
-
- menu.addChild(new dijit.PopupMenuItem({
- label: __("Remove label"),
- popup: labelDelMenu
+ Headlines.selectionRemoveLabel(this.labelId, ids);
+ }
}));
- }
- },
- scrollByPages: function (offset, event) {
- const elem = $("headlines-frame");
-
- if (event && event.repeat) {
- elem.addClassName("forbid-smooth-scroll");
- window.clearTimeout(this._scroll_reset_timeout);
+ });
- this._scroll_reset_timeout = window.setTimeout(() => {
- if (elem) elem.removeClassName("forbid-smooth-scroll");
- }, 250)
- } else {
- elem.removeClassName("forbid-smooth-scroll");
- }
+ menu.addChild(new dijit.PopupMenuItem({
+ label: __("Assign label"),
+ popup: labelAddMenu
+ }));
- elem.scrollTop += elem.offsetHeight * offset * 0.99;
- },
- initHeadlinesMenu: function () {
- if (!dijit.byId("headlinesMenu")) {
+ menu.addChild(new dijit.PopupMenuItem({
+ label: __("Remove label"),
+ popup: labelDelMenu
+ }));
- const menu = new dijit.Menu({
- id: "headlinesMenu",
- targetNodeIds: ["headlines-frame"],
- selector: ".hlMenuAttach"
- });
+ }
+ },
+ scrollByPages: function (page_offset) {
+ App.Scrollable.scrollByPages($("headlines-frame"), page_offset);
+ },
+ scroll: function (offset) {
+ App.Scrollable.scroll($("headlines-frame"), offset);
+ },
+ initHeadlinesMenu: function () {
+ if (!dijit.byId("headlinesMenu")) {
+
+ const menu = new dijit.Menu({
+ id: "headlinesMenu",
+ targetNodeIds: ["headlines-frame"],
+ selector: ".hlMenuAttach"
+ });
- this.headlinesMenuCommon(menu);
+ this.headlinesMenuCommon(menu);
- menu.startup();
- }
+ menu.startup();
+ }
- /* vgroup feed title menu */
+ /* vgroup feed title menu */
- if (!dijit.byId("headlinesFeedTitleMenu")) {
+ if (!dijit.byId("headlinesFeedTitleMenu")) {
- const menu = new dijit.Menu({
- id: "headlinesFeedTitleMenu",
- targetNodeIds: ["headlines-frame"],
- selector: "div.cdmFeedTitle"
- });
+ const menu = new dijit.Menu({
+ id: "headlinesFeedTitleMenu",
+ targetNodeIds: ["headlines-frame"],
+ selector: "div.cdmFeedTitle"
+ });
- menu.addChild(new dijit.MenuItem({
- label: __("Select articles in group"),
- onClick: function (event) {
- Headlines.select("all",
- "#headlines-frame > div[id*=RROW]" +
- "[data-orig-feed-id='" + this.getParent().currentTarget.getAttribute("data-feed-id") + "']");
+ menu.addChild(new dijit.MenuItem({
+ label: __("Select articles in group"),
+ onClick: function (/* event */) {
+ Headlines.select("all",
+ "#headlines-frame > div[id*=RROW]" +
+ "[data-orig-feed-id='" + this.getParent().currentTarget.getAttribute("data-feed-id") + "']");
- }
- }));
+ }
+ }));
- menu.addChild(new dijit.MenuItem({
- label: __("Mark group as read"),
- onClick: function () {
- Headlines.select("none");
- Headlines.select("all",
- "#headlines-frame > div[id*=RROW]" +
- "[data-orig-feed-id='" + this.getParent().currentTarget.getAttribute("data-feed-id") + "']");
+ menu.addChild(new dijit.MenuItem({
+ label: __("Mark group as read"),
+ onClick: function () {
+ Headlines.select("none");
+ Headlines.select("all",
+ "#headlines-frame > div[id*=RROW]" +
+ "[data-orig-feed-id='" + this.getParent().currentTarget.getAttribute("data-feed-id") + "']");
- Headlines.catchupSelection();
- }
- }));
+ Headlines.catchupSelection();
+ }
+ }));
- menu.addChild(new dijit.MenuItem({
- label: __("Mark feed as read"),
- onClick: function () {
- Feeds.catchupFeedInGroup(this.getParent().currentTarget.getAttribute("data-feed-id"));
- }
- }));
+ menu.addChild(new dijit.MenuItem({
+ label: __("Mark feed as read"),
+ onClick: function () {
+ Feeds.catchupFeedInGroup(this.getParent().currentTarget.getAttribute("data-feed-id"));
+ }
+ }));
- menu.addChild(new dijit.MenuItem({
- label: __("Edit feed"),
- onClick: function () {
- CommonDialogs.editFeed(this.getParent().currentTarget.getAttribute("data-feed-id"));
- }
- }));
+ menu.addChild(new dijit.MenuItem({
+ label: __("Edit feed"),
+ onClick: function () {
+ CommonDialogs.editFeed(this.getParent().currentTarget.getAttribute("data-feed-id"));
+ }
+ }));
- menu.startup();
- }
+ menu.startup();
}
}
-
- return Headlines;
-});
+}
diff --git a/js/PluginHost.js b/js/PluginHost.js
index 71596ad31..caee79d58 100644
--- a/js/PluginHost.js
+++ b/js/PluginHost.js
@@ -1,6 +1,8 @@
// based on http://www.velvetcache.org/2010/08/19/a-simple-javascript-hooks-system
-PluginHost = {
+
+/* exported PluginHost */
+const PluginHost = {
HOOK_ARTICLE_RENDERED: 1,
HOOK_ARTICLE_RENDERED_CDM: 2,
HOOK_ARTICLE_SET_ACTIVE: 3,
@@ -31,7 +33,7 @@ PluginHost = {
}
},
unregister: function (name, callback) {
- for (var i = 0; i < this.hooks[name].length; i++)
+ for (let i = 0; i < this.hooks[name].length; i++)
if (this.hooks[name][i] == callback)
this.hooks[name].splice(i, 1);
}
diff --git a/js/PrefFeedStore.js b/js/PrefFeedStore.js
index 152ebba44..ee983af54 100644
--- a/js/PrefFeedStore.js
+++ b/js/PrefFeedStore.js
@@ -1,9 +1,10 @@
+/* global define, dojo */
+
define(["dojo/_base/declare", "dojo/data/ItemFileWriteStore"], function (declare) {
return declare("fox.PrefFeedStore", dojo.data.ItemFileWriteStore, {
- _saveEverything: function(saveCompleteCallback, saveFailedCallback,
- newFileContentString) {
+ _saveEverything: function(saveCompleteCallback, saveFailedCallback, newFileContentString) {
dojo.xhrPost({
url: "backend.php",
diff --git a/js/PrefFeedTree.js b/js/PrefFeedTree.js
index 3a5e33b2b..4ea0cdac1 100644
--- a/js/PrefFeedTree.js
+++ b/js/PrefFeedTree.js
@@ -1,4 +1,5 @@
-/* global lib,dijit */
+/* global __, lib, dijit, define, dojo, CommonDialogs, Notify, Tables, xhrPost */
+
define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], function (declare, domConstruct) {
return declare("fox.PrefFeedTree", lib.CheckBoxTree, {
diff --git a/js/PrefFilterStore.js b/js/PrefFilterStore.js
index cccec6479..a41d84129 100644
--- a/js/PrefFilterStore.js
+++ b/js/PrefFilterStore.js
@@ -1,9 +1,10 @@
+/* global define, dojo */
+
define(["dojo/_base/declare", "dojo/data/ItemFileWriteStore"], function (declare) {
return declare("fox.PrefFilterStore", dojo.data.ItemFileWriteStore, {
- _saveEverything: function (saveCompleteCallback, saveFailedCallback,
- newFileContentString) {
+ _saveEverything: function (saveCompleteCallback, saveFailedCallback, newFileContentString) {
dojo.xhrPost({
url: "backend.php",
diff --git a/js/PrefFilterTree.js b/js/PrefFilterTree.js
index a7c7464fb..0e8e52658 100644
--- a/js/PrefFilterTree.js
+++ b/js/PrefFilterTree.js
@@ -1,4 +1,5 @@
-/* global dijit,lib */
+/* global __, $$, define, lib, dijit, dojo, xhrPost, Notify, Filters, Lists */
+
define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], function (declare, domConstruct) {
return declare("fox.PrefFilterTree", lib.CheckBoxTree, {
@@ -149,9 +150,7 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], functio
style: "width: 600px",
test: function () {
- const query = "backend.php?" + dojo.formToQuery("filter_edit_form") + "&savemode=test";
-
- Filters.editFilterTest(query);
+ Filters.editFilterTest(dojo.formToObject("filter_edit_form"));
},
selectRules: function (select) {
Lists.select("filterDlg_Matches", select);
@@ -238,9 +237,6 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], functio
return false;
},
-
-
-
});
});
diff --git a/js/PrefHelpers.js b/js/PrefHelpers.js
index 4b908204c..12710bc6a 100644
--- a/js/PrefHelpers.js
+++ b/js/PrefHelpers.js
@@ -1,274 +1,273 @@
-define(["dojo/_base/declare"], function (declare) {
- Helpers = {
- AppPasswords: {
- getSelected: function() {
- return Tables.getSelected("app-password-list");
- },
- updateContent: function(data) {
- $("app_passwords_holder").innerHTML = data;
- dojo.parser.parse("app_passwords_holder");
- },
- removeSelected: function() {
- const rows = this.getSelected();
+'use strict';
- if (rows.length == 0) {
- alert("No passwords selected.");
- } else {
- if (confirm(__("Remove selected app passwords?"))) {
+/* global __, dijit, dojo, Tables, xhrPost, Notify, xhrJson */
- xhrPost("backend.php", {op: "pref-prefs", method: "deleteAppPassword", ids: rows.toString()}, (transport) => {
- this.updateContent(transport.responseText);
- Notify.close();
- });
+const Helpers = {
+ AppPasswords: {
+ getSelected: function() {
+ return Tables.getSelected("app-password-list");
+ },
+ updateContent: function(data) {
+ $("app_passwords_holder").innerHTML = data;
+ dojo.parser.parse("app_passwords_holder");
+ },
+ removeSelected: function() {
+ const rows = this.getSelected();
- Notify.progress("Loading, please wait...");
- }
- }
- },
- generate: function() {
- const title = prompt("Password description:")
+ if (rows.length == 0) {
+ alert("No passwords selected.");
+ } else if (confirm(__("Remove selected app passwords?"))) {
- if (title) {
- xhrPost("backend.php", {op: "pref-prefs", method: "generateAppPassword", title: title}, (transport) => {
- this.updateContent(transport.responseText);
- Notify.close();
- });
+ xhrPost("backend.php", {op: "pref-prefs", method: "deleteAppPassword", ids: rows.toString()}, (transport) => {
+ this.updateContent(transport.responseText);
+ Notify.close();
+ });
- Notify.progress("Loading, please wait...");
- }
- },
+ Notify.progress("Loading, please wait...");
+ }
},
- clearFeedAccessKeys: function() {
- if (confirm(__("This will invalidate all previously generated feed URLs. Continue?"))) {
- Notify.progress("Clearing URLs...");
+ generate: function() {
+ const title = prompt("Password description:")
- xhrPost("backend.php", {op: "pref-feeds", method: "clearKeys"}, () => {
- Notify.info("Generated URLs cleared.");
+ if (title) {
+ xhrPost("backend.php", {op: "pref-prefs", method: "generateAppPassword", title: title}, (transport) => {
+ this.updateContent(transport.responseText);
+ Notify.close();
});
- }
-
- return false;
- },
- updateEventLog: function() {
- xhrPost("backend.php", { op: "pref-system" }, (transport) => {
- dijit.byId('systemConfigTab').attr('content', transport.responseText);
- Notify.close();
- });
- },
- clearEventLog: function() {
- if (confirm(__("Clear event log?"))) {
Notify.progress("Loading, please wait...");
-
- xhrPost("backend.php", {op: "pref-system", method: "clearLog"}, () => {
- this.updateEventLog();
- });
}
},
- editProfiles: function() {
-
- if (dijit.byId("profileEditDlg"))
- dijit.byId("profileEditDlg").destroyRecursive();
+ },
+ clearFeedAccessKeys: function() {
+ if (confirm(__("This will invalidate all previously generated feed URLs. Continue?"))) {
+ Notify.progress("Clearing URLs...");
- const query = "backend.php?op=pref-prefs&method=editPrefProfiles";
+ xhrPost("backend.php", {op: "pref-feeds", method: "clearKeys"}, () => {
+ Notify.info("Generated URLs cleared.");
+ });
+ }
- // noinspection JSUnusedGlobalSymbols
- const dialog = new dijit.Dialog({
- id: "profileEditDlg",
- title: __("Settings Profiles"),
- style: "width: 600px",
- getSelectedProfiles: function () {
- return Tables.getSelected("pref-profiles-list");
- },
- removeSelected: function () {
- const sel_rows = this.getSelectedProfiles();
-
- if (sel_rows.length > 0) {
- if (confirm(__("Remove selected profiles? Active and default profiles will not be removed."))) {
- Notify.progress("Removing selected profiles...", true);
-
- const query = {
- op: "rpc", method: "remprofiles",
- ids: sel_rows.toString()
- };
-
- xhrPost("backend.php", query, () => {
- Notify.close();
- Helpers.editProfiles();
- });
- }
+ return false;
+ },
+ updateEventLog: function() {
+ xhrPost("backend.php", { op: "pref-system" }, (transport) => {
+ dijit.byId('systemConfigTab').attr('content', transport.responseText);
+ Notify.close();
+ });
+ },
+ clearEventLog: function() {
+ if (confirm(__("Clear event log?"))) {
+
+ Notify.progress("Loading, please wait...");
+
+ xhrPost("backend.php", {op: "pref-system", method: "clearLog"}, () => {
+ this.updateEventLog();
+ });
+ }
+ },
+ editProfiles: function() {
- } else {
- alert(__("No profiles selected."));
- }
- },
- activateProfile: function () {
- const sel_rows = this.getSelectedProfiles();
+ if (dijit.byId("profileEditDlg"))
+ dijit.byId("profileEditDlg").destroyRecursive();
- if (sel_rows.length == 1) {
- if (confirm(__("Activate selected profile?"))) {
- Notify.progress("Loading, please wait...");
+ const query = "backend.php?op=pref-prefs&method=editPrefProfiles";
- xhrPost("backend.php", {op: "rpc", method: "setprofile", id: sel_rows.toString()}, () => {
- window.location.reload();
- });
- }
+ // noinspection JSUnusedGlobalSymbols
+ const dialog = new dijit.Dialog({
+ id: "profileEditDlg",
+ title: __("Settings Profiles"),
+ style: "width: 600px",
+ getSelectedProfiles: function () {
+ return Tables.getSelected("pref-profiles-list");
+ },
+ removeSelected: function () {
+ const sel_rows = this.getSelectedProfiles();
- } else {
- alert(__("Please choose a profile to activate."));
- }
- },
- addProfile: function () {
- if (this.validate()) {
- Notify.progress("Creating profile...", true);
+ if (sel_rows.length > 0) {
+ if (confirm(__("Remove selected profiles? Active and default profiles will not be removed."))) {
+ Notify.progress("Removing selected profiles...", true);
- const query = {op: "rpc", method: "addprofile", title: dialog.attr('value').newprofile};
+ const query = {
+ op: "rpc", method: "remprofiles",
+ ids: sel_rows.toString()
+ };
xhrPost("backend.php", query, () => {
Notify.close();
Helpers.editProfiles();
});
-
- }
- },
- execute: function () {
- if (this.validate()) {
}
- },
- href: query
- });
- dialog.show();
- },
- customizeCSS: function() {
- const query = "backend.php?op=pref-prefs&method=customizeCSS";
+ } else {
+ alert(__("No profiles selected."));
+ }
+ },
+ activateProfile: function () {
+ const sel_rows = this.getSelectedProfiles();
- if (dijit.byId("cssEditDlg"))
- dijit.byId("cssEditDlg").destroyRecursive();
+ if (sel_rows.length == 1) {
+ if (confirm(__("Activate selected profile?"))) {
+ Notify.progress("Loading, please wait...");
- const dialog = new dijit.Dialog({
- id: "cssEditDlg",
- title: __("Customize stylesheet"),
- style: "width: 600px",
- apply: function() {
- xhrPost("backend.php", this.attr('value'), () => {
- new Effect.Appear("css_edit_apply_msg");
- $("user_css_style").innerText = this.attr('value');
- });
- },
- execute: function () {
- Notify.progress('Saving data...', true);
+ xhrPost("backend.php", {op: "rpc", method: "setprofile", id: sel_rows.toString()}, () => {
+ window.location.reload();
+ });
+ }
- xhrPost("backend.php", this.attr('value'), () => {
- window.location.reload();
- });
+ } else {
+ alert(__("Please choose a profile to activate."));
+ }
+ },
+ addProfile: function () {
+ if (this.validate()) {
+ Notify.progress("Creating profile...", true);
- },
- href: query
- });
+ const query = {op: "rpc", method: "addprofile", title: dialog.attr('value').newprofile};
- dialog.show();
- },
- confirmReset: function() {
- if (confirm(__("Reset to defaults?"))) {
- xhrPost("backend.php", {op: "pref-prefs", method: "resetconfig"}, (transport) => {
- Helpers.refresh();
- Notify.info(transport.responseText);
+ xhrPost("backend.php", query, () => {
+ Notify.close();
+ Helpers.editProfiles();
+ });
+
+ }
+ },
+ execute: function () {
+ if (this.validate()) {
+ //
+ }
+ },
+ href: query
+ });
+
+ dialog.show();
+ },
+ customizeCSS: function() {
+ const query = "backend.php?op=pref-prefs&method=customizeCSS";
+
+ if (dijit.byId("cssEditDlg"))
+ dijit.byId("cssEditDlg").destroyRecursive();
+
+ const dialog = new dijit.Dialog({
+ id: "cssEditDlg",
+ title: __("Customize stylesheet"),
+ style: "width: 600px",
+ apply: function() {
+ xhrPost("backend.php", this.attr('value'), () => {
+ new Effect.Appear("css_edit_apply_msg");
+ $("user_css_style").innerText = this.attr('value');
});
- }
- },
- clearPluginData: function(name) {
- if (confirm(__("Clear stored data for this plugin?"))) {
- Notify.progress("Loading, please wait...");
+ },
+ execute: function () {
+ Notify.progress('Saving data...', true);
- xhrPost("backend.php", {op: "pref-prefs", method: "clearplugindata", name: name}, () => {
- Helpers.refresh();
+ xhrPost("backend.php", this.attr('value'), () => {
+ window.location.reload();
});
- }
- },
- refresh: function() {
- xhrPost("backend.php", { op: "pref-prefs" }, (transport) => {
- dijit.byId('genConfigTab').attr('content', transport.responseText);
- Notify.close();
+
+ },
+ href: query
+ });
+
+ dialog.show();
+ },
+ confirmReset: function() {
+ if (confirm(__("Reset to defaults?"))) {
+ xhrPost("backend.php", {op: "pref-prefs", method: "resetconfig"}, (transport) => {
+ Helpers.refresh();
+ Notify.info(transport.responseText);
});
- },
- OPML: {
- import: function() {
- const opml_file = $("opml_file");
+ }
+ },
+ clearPluginData: function(name) {
+ if (confirm(__("Clear stored data for this plugin?"))) {
+ Notify.progress("Loading, please wait...");
- if (opml_file.value.length == 0) {
- alert(__("Please choose an OPML file first."));
- return false;
- } else {
- Notify.progress("Importing, please wait...", true);
+ xhrPost("backend.php", {op: "pref-prefs", method: "clearplugindata", name: name}, () => {
+ Helpers.refresh();
+ });
+ }
+ },
+ refresh: function() {
+ xhrPost("backend.php", { op: "pref-prefs" }, (transport) => {
+ dijit.byId('genConfigTab').attr('content', transport.responseText);
+ Notify.close();
+ });
+ },
+ OPML: {
+ import: function() {
+ const opml_file = $("opml_file");
+
+ if (opml_file.value.length == 0) {
+ alert(__("Please choose an OPML file first."));
+ return false;
+ } else {
+ Notify.progress("Importing, please wait...", true);
- Element.show("upload_iframe");
+ Element.show("upload_iframe");
- return true;
- }
- },
- onImportComplete: function(iframe) {
- if (!iframe.contentDocument.body.innerHTML) return false;
+ return true;
+ }
+ },
+ onImportComplete: function(iframe) {
+ if (!iframe.contentDocument.body.innerHTML) return false;
- Element.show(iframe);
+ Element.show(iframe);
- Notify.close();
+ Notify.close();
- if (dijit.byId('opmlImportDlg'))
- dijit.byId('opmlImportDlg').destroyRecursive();
+ if (dijit.byId('opmlImportDlg'))
+ dijit.byId('opmlImportDlg').destroyRecursive();
- const content = iframe.contentDocument.body.innerHTML;
+ const content = iframe.contentDocument.body.innerHTML;
- const dialog = new dijit.Dialog({
- id: "opmlImportDlg",
- title: __("OPML Import"),
- style: "width: 600px",
- onCancel: function () {
- window.location.reload();
- },
- execute: function () {
- window.location.reload();
- },
- content: content
- });
+ const dialog = new dijit.Dialog({
+ id: "opmlImportDlg",
+ title: __("OPML Import"),
+ style: "width: 600px",
+ onCancel: function () {
+ window.location.reload();
+ },
+ execute: function () {
+ window.location.reload();
+ },
+ content: content
+ });
- dojo.connect(dialog, "onShow", function () {
- Element.hide(iframe);
- });
+ dojo.connect(dialog, "onShow", function () {
+ Element.hide(iframe);
+ });
- dialog.show();
- },
- export: function() {
- console.log("export");
- window.open("backend.php?op=opml&method=export&" + dojo.formToQuery("opmlExportForm"));
- },
- changeKey: function() {
- if (confirm(__("Replace current OPML publishing address with a new one?"))) {
- Notify.progress("Trying to change address...", true);
+ dialog.show();
+ },
+ export: function() {
+ console.log("export");
+ window.open("backend.php?op=opml&method=export&" + dojo.formToQuery("opmlExportForm"));
+ },
+ changeKey: function() {
+ if (confirm(__("Replace current OPML publishing address with a new one?"))) {
+ Notify.progress("Trying to change address...", true);
- xhrJson("backend.php", {op: "pref-feeds", method: "regenOPMLKey"}, (reply) => {
- if (reply) {
- const new_link = reply.link;
- const e = $('pub_opml_url');
+ xhrJson("backend.php", {op: "pref-feeds", method: "regenOPMLKey"}, (reply) => {
+ if (reply) {
+ const new_link = reply.link;
+ const e = $('pub_opml_url');
- if (new_link) {
- e.href = new_link;
- e.innerHTML = new_link;
+ if (new_link) {
+ e.href = new_link;
+ e.innerHTML = new_link;
- new Effect.Highlight(e);
+ new Effect.Highlight(e);
- Notify.close();
+ Notify.close();
- } else {
- Notify.error("Could not change feed URL.");
- }
+ } else {
+ Notify.error("Could not change feed URL.");
}
- });
- }
- return false;
- },
- }
- };
-
- return Helpers;
-});
+ }
+ });
+ }
+ return false;
+ },
+ }
+};
diff --git a/js/PrefLabelTree.js b/js/PrefLabelTree.js
index 988e313b0..b14474feb 100644
--- a/js/PrefLabelTree.js
+++ b/js/PrefLabelTree.js
@@ -1,4 +1,5 @@
-/* global lib,dijit */
+/* global __, define, lib, dijit, dojo, xhrPost, Notify */
+
define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree", "dijit/form/DropDownButton"], function (declare, domConstruct) {
return declare("fox.PrefLabelTree", lib.CheckBoxTree, {
diff --git a/js/PrefUsers.js b/js/PrefUsers.js
index 55dc43dfa..8136e9c65 100644
--- a/js/PrefUsers.js
+++ b/js/PrefUsers.js
@@ -1,122 +1,120 @@
'use strict'
-/* global __, ngettext */
-define(["dojo/_base/declare"], function (declare) {
- Users = {
- reload: function(sort) {
- const user_search = $("user_search");
- const search = user_search ? user_search.value : "";
-
- xhrPost("backend.php", { op: "pref-users", sort: sort, search: search }, (transport) => {
- dijit.byId('userConfigTab').attr('content', transport.responseText);
- Notify.close();
- });
- },
- add: function() {
- const login = prompt(__("Please enter username:"), "");
- if (login) {
- Notify.progress("Adding user...");
+/* global __ */
+/* global xhrPost, dojo, dijit, Notify, Tables */
- xhrPost("backend.php", {op: "pref-users", method: "add", login: login}, (transport) => {
- alert(transport.responseText);
- Users.reload();
- });
+const Users = {
+ reload: function(sort) {
+ const user_search = $("user_search");
+ const search = user_search ? user_search.value : "";
- }
- },
- edit: function(id) {
- const query = "backend.php?op=pref-users&method=edit&id=" +
- encodeURIComponent(id);
-
- if (dijit.byId("userEditDlg"))
- dijit.byId("userEditDlg").destroyRecursive();
-
- const dialog = new dijit.Dialog({
- id: "userEditDlg",
- title: __("User Editor"),
- style: "width: 600px",
- execute: function () {
- if (this.validate()) {
- Notify.progress("Saving data...", true);
-
- xhrPost("backend.php", dojo.formToObject("user_edit_form"), (transport) => {
- dialog.hide();
- Users.reload();
- });
- }
- },
- href: query
- });
-
- dialog.show();
- },
- resetSelected: function() {
- const rows = this.getSelection();
+ xhrPost("backend.php", { op: "pref-users", sort: sort, search: search }, (transport) => {
+ dijit.byId('userConfigTab').attr('content', transport.responseText);
+ Notify.close();
+ });
+ },
+ add: function() {
+ const login = prompt(__("Please enter username:"), "");
- if (rows.length == 0) {
- alert(__("No users selected."));
- return;
- }
+ if (login) {
+ Notify.progress("Adding user...");
- if (rows.length > 1) {
- alert(__("Please select one user."));
- return;
- }
+ xhrPost("backend.php", {op: "pref-users", method: "add", login: login}, (transport) => {
+ alert(transport.responseText);
+ Users.reload();
+ });
- if (confirm(__("Reset password of selected user?"))) {
- Notify.progress("Resetting password for selected user...");
+ }
+ },
+ edit: function(id) {
+ const query = "backend.php?op=pref-users&method=edit&id=" +
+ encodeURIComponent(id);
+
+ if (dijit.byId("userEditDlg"))
+ dijit.byId("userEditDlg").destroyRecursive();
+
+ const dialog = new dijit.Dialog({
+ id: "userEditDlg",
+ title: __("User Editor"),
+ style: "width: 600px",
+ execute: function () {
+ if (this.validate()) {
+ Notify.progress("Saving data...", true);
+
+ xhrPost("backend.php", dojo.formToObject("user_edit_form"), (/* transport */) => {
+ dialog.hide();
+ Users.reload();
+ });
+ }
+ },
+ href: query
+ });
+
+ dialog.show();
+ },
+ resetSelected: function() {
+ const rows = this.getSelection();
+
+ if (rows.length == 0) {
+ alert(__("No users selected."));
+ return;
+ }
- const id = rows[0];
+ if (rows.length > 1) {
+ alert(__("Please select one user."));
+ return;
+ }
- xhrPost("backend.php", {op: "pref-users", method: "resetPass", id: id}, (transport) => {
- Notify.close();
- Notify.info(transport.responseText, true);
- });
+ if (confirm(__("Reset password of selected user?"))) {
+ Notify.progress("Resetting password for selected user...");
- }
- },
- removeSelected: function() {
- const sel_rows = this.getSelection();
+ const id = rows[0];
- if (sel_rows.length > 0) {
- if (confirm(__("Remove selected users? Neither default admin nor your account will be removed."))) {
- Notify.progress("Removing selected users...");
+ xhrPost("backend.php", {op: "pref-users", method: "resetPass", id: id}, (transport) => {
+ Notify.close();
+ Notify.info(transport.responseText, true);
+ });
- const query = {
- op: "pref-users", method: "remove",
- ids: sel_rows.toString()
- };
+ }
+ },
+ removeSelected: function() {
+ const sel_rows = this.getSelection();
- xhrPost("backend.php", query, () => {
- this.reload();
- });
- }
+ if (sel_rows.length > 0) {
+ if (confirm(__("Remove selected users? Neither default admin nor your account will be removed."))) {
+ Notify.progress("Removing selected users...");
- } else {
- alert(__("No users selected."));
- }
- },
- editSelected: function() {
- const rows = this.getSelection();
+ const query = {
+ op: "pref-users", method: "remove",
+ ids: sel_rows.toString()
+ };
- if (rows.length == 0) {
- alert(__("No users selected."));
- return;
+ xhrPost("backend.php", query, () => {
+ this.reload();
+ });
}
- if (rows.length > 1) {
- alert(__("Please select one user."));
- return;
- }
+ } else {
+ alert(__("No users selected."));
+ }
+ },
+ editSelected: function() {
+ const rows = this.getSelection();
- this.edit(rows[0]);
- },
- getSelection :function() {
- return Tables.getSelected("prefUserList");
+ if (rows.length == 0) {
+ alert(__("No users selected."));
+ return;
}
- }
- return Users;
-});
+ if (rows.length > 1) {
+ alert(__("Please select one user."));
+ return;
+ }
+ this.edit(rows[0]);
+ },
+ getSelection :function() {
+ return Tables.getSelected("prefUserList");
+ }
+}
diff --git a/js/Toolbar.js b/js/Toolbar.js
index 6d2c20058..d4993e713 100755
--- a/js/Toolbar.js
+++ b/js/Toolbar.js
@@ -1,10 +1,11 @@
-/* global dijit */
+/* global dijit, define */
+
define(["dojo/_base/declare", "dijit/Toolbar"], function (declare) {
return declare("fox.Toolbar", dijit.Toolbar, {
- _onContainerKeydown: function(/* Event */ e) {
+ _onContainerKeydown: function(/* Event */ /* e */) {
return; // Stop dijit.Toolbar from interpreting keystrokes
},
- _onContainerKeypress: function(/* Event */ e) {
+ _onContainerKeypress: function(/* Event */ /* e */) {
return; // Stop dijit.Toolbar from interpreting keystrokes
},
focus: function() {
diff --git a/js/common.js b/js/common.js
index 69b528a1c..c17e8bc45 100755
--- a/js/common.js
+++ b/js/common.js
@@ -1,21 +1,19 @@
-'use strict'
-/* global dijit, __ */
+'use strict';
-let LABEL_BASE_INDEX = -1024; /* not const because it's assigned at least once (by backend) */
-let loading_progress = 0;
+/* global dijit, __, App, Ajax */
/* error reporting shim */
-
// TODO: deprecated; remove
-function exception_error(e, e_compat, filename, lineno, colno) {
+/* function exception_error(e, e_compat, filename, lineno, colno) {
if (typeof e == "string")
e = e_compat;
App.Error.report(e, {filename: filename, lineno: lineno, colno: colno});
-}
+} */
/* xhr shorthand helpers */
+/* exported xhrPost */
function xhrPost(url, params, complete) {
console.log("xhrPost:", params);
@@ -31,6 +29,7 @@ function xhrPost(url, params, complete) {
});
}
+/* exported xhrJson */
function xhrJson(url, params, complete) {
return new Promise((resolve, reject) => {
return xhrPost(url, params).then((reply) => {
@@ -58,6 +57,7 @@ Array.prototype.remove = function(s) {
/* common helpers not worthy of separate Dojo modules */
+/* exported Lists */
const Lists = {
onRowChecked: function(elem) {
const checked = elem.domNode ? elem.attr("checked") : elem.checked;
@@ -87,7 +87,7 @@ const Lists = {
},
};
-// noinspection JSUnusedGlobalSymbols
+/* exported Tables */
const Tables = {
onRowChecked: function(elem) {
// account for dojo checkboxes
@@ -133,6 +133,7 @@ const Tables = {
}
};
+/* exported Cookie */
const Cookie = {
set: function (name, value, lifetime) {
const d = new Date();
@@ -152,12 +153,12 @@ const Cookie = {
},
delete: function(name) {
const expires = "expires=Thu, 01-Jan-1970 00:00:01 GMT";
- document.cookie = name + "=" + "" + "; " + expires;
+ document.cookie = name + "=; " + expires;
}
};
/* runtime notifications */
-
+/* exported Notify */
const Notify = {
KIND_GENERIC: 0,
KIND_INFO: 1,
@@ -237,30 +238,8 @@ const Notify = {
}
};
-// noinspection JSUnusedGlobalSymbols
-function displayIfChecked(checkbox, elemId) {
- if (checkbox.checked) {
- Effect.Appear(elemId, {duration : 0.5});
- } else {
- Effect.Fade(elemId, {duration : 0.5});
- }
-}
-
-/* function strip_tags(s) {
- return s.replace(/<\/?[^>]+(>|$)/g, "");
-} */
-
-// noinspection JSUnusedGlobalSymbols
-function label_to_feed_id(label) {
- return LABEL_BASE_INDEX - 1 - Math.abs(label);
-}
-
-// noinspection JSUnusedGlobalSymbols
-function feed_to_label_id(feed) {
- return LABEL_BASE_INDEX - 1 + Math.abs(feed);
-}
-
// http://stackoverflow.com/questions/6251937/how-to-get-selecteduser-highlighted-text-in-contenteditable-element-and-replac
+/* exported getSelectionText */
function getSelectionText() {
let text = "";
@@ -281,36 +260,3 @@ function getSelectionText() {
return text.stripTags();
}
-
-// noinspection JSUnusedGlobalSymbols
-function popupOpenUrl(url) {
- const w = window.open("");
-
- w.opener = null;
- w.location = url;
-}
-
-// noinspection JSUnusedGlobalSymbols
-function popupOpenArticle(id) {
- const w = window.open("",
- "ttrss_article_popup",
- "height=900,width=900,resizable=yes,status=no,location=no,menubar=no,directories=no,scrollbars=yes,toolbar=no");
-
- if (w) {
- w.opener = null;
- w.location = "backend.php?op=article&method=view&mode=raw&html=1&zoom=1&id=" + id + "&csrf_token=" + App.getInitParam("csrf_token");
- }
-}
-
-// htmlspecialchars()-alike for headlines data-content attribute
-function escapeHtml(text) {
- const map = {
- '&': '&amp;',
- '<': '&lt;',
- '>': '&gt;',
- '"': '&quot;',
- "'": '&#039;'
- };
-
- return text.replace(/[&<>"']/g, function(m) { return map[m]; });
-}
diff --git a/js/form/ValidationTextArea.js b/js/form/ValidationTextArea.js
new file mode 100644
index 000000000..a7993f716
--- /dev/null
+++ b/js/form/ValidationTextArea.js
@@ -0,0 +1,33 @@
+// https://stackoverflow.com/questions/19317258/how-to-use-dijit-textarea-validation-dojo-1-9
+
+define(["dojo/_base/declare", "dojo/_base/lang", "dijit/form/SimpleTextarea", "dijit/form/ValidationTextBox"],
+ function(declare, lang, SimpleTextarea, ValidationTextBox) {
+
+ return declare('fox.form.ValidationTextArea', [SimpleTextarea, ValidationTextBox], {
+ constructor: function(params){
+ this.constraints = {};
+ this.baseClass += ' dijitValidationTextArea';
+ },
+ templateString: "<textarea ${!nameAttrSetting} data-dojo-attach-point='focusNode,containerNode,textbox' autocomplete='off'></textarea>",
+ validator: function(value, constraints) {
+ //console.log(this, value, constraints);
+
+ if (this.required && this._isEmpty(value))
+ return false;
+
+ if (this.validregexp) {
+ try {
+ new RegExp("/" + value + "/");
+ } catch (e) {
+ return false;
+ }
+ }
+
+ return value.match(new RegExp(this._computeRegexp(constraints)));
+
+ /*return (new RegExp("^(?:" + this._computeRegexp(constraints) + ")"+(this.required?"":"?")+"$",["m"])).test(value) &&
+ (!this.required || !this._isEmpty(value)) &&
+ (this._isEmpty(value) || this.parse(value, constraints) !== undefined); // Boolean*/
+ }
+ })
+ });
diff --git a/js/prefs.js b/js/prefs.js
index 944e49258..a71b4f39e 100755
--- a/js/prefs.js
+++ b/js/prefs.js
@@ -1,19 +1,15 @@
'use strict'
-/* global dijit, __ */
-let App;
-let CommonDialogs;
-let Filters;
-let Users;
-let Helpers;
+/* global require, App */
+/* exported Plugins */
const Plugins = {};
require(["dojo/_base/kernel",
"dojo/_base/declare",
"dojo/ready",
"dojo/parser",
- "fox/AppBase",
+ "fox/App",
"dojo/_base/loader",
"dojo/_base/html",
"dijit/ColorPalette",
@@ -55,109 +51,16 @@ require(["dojo/_base/kernel",
"fox/PrefFilterTree",
"fox/PrefLabelTree",
"fox/Toolbar",
+ "fox/form/ValidationTextArea",
"fox/form/Select",
"fox/form/ComboButton",
- "fox/form/DropDownButton"], function (dojo, declare, ready, parser, AppBase) {
+ "fox/form/DropDownButton"], function (dojo, declare, ready, parser) {
ready(function () {
try {
- const _App = declare("fox.App", AppBase, {
- constructor: function() {
- this.setupNightModeDetection(() => {
- parser.parse();
-
- this.setLoadingProgress(50);
-
- const clientTzOffset = new Date().getTimezoneOffset() * 60;
- const params = {op: "rpc", method: "sanityCheck", clientTzOffset: clientTzOffset};
-
- xhrPost("backend.php", params, (transport) => {
- try {
- this.backendSanityCallback(transport);
- } catch (e) {
- this.Error.report(e);
- }
- });
- });
- },
- initSecondStage: function() {
- this.enableCsrfSupport();
-
- document.onkeydown = (event) => { return App.hotkeyHandler(event) };
- document.onkeypress = (event) => { return App.hotkeyHandler(event) };
- App.setLoadingProgress(50);
- Notify.close();
-
- let tab = App.urlParam('tab');
-
- if (tab) {
- tab = dijit.byId(tab + "Tab");
- if (tab) {
- dijit.byId("pref-tabs").selectChild(tab);
-
- switch (App.urlParam('method')) {
- case "editfeed":
- window.setTimeout(function () {
- CommonDialogs.editFeed(App.urlParam('methodparam'))
- }, 100);
- break;
- default:
- console.warn("initSecondStage, unknown method:", App.urlParam("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);
- });
-
- },
- 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) {
- switch (action_name) {
- case "feed_subscribe":
- CommonDialogs.quickAddFeed();
- return false;
- case "create_label":
- CommonDialogs.addLabel();
- return false;
- case "create_filter":
- Filters.quickAddFilter();
- return false;
- case "help_dialog":
- App.helpDialog("main");
- return false;
- default:
- console.log("unhandled action: " + action_name + "; keycode: " + event.which);
- }
- }
- },
- isPrefs: function() {
- return true;
- }
- });
-
- App = new _App();
-
+ App.init(parser, true);
} catch (e) {
- if (App && App.Error)
+ if (typeof App != "undefined" && App.Error)
App.Error.report(e);
else
alert(e + "\n\n" + e.stack);
diff --git a/js/tt-rss.js b/js/tt-rss.js
index d45dd5748..83c6681cd 100644
--- a/js/tt-rss.js
+++ b/js/tt-rss.js
@@ -1,21 +1,15 @@
'use strict'
-/* global dijit,__ */
-let App;
-let CommonDialogs;
-let Filters;
-let Feeds;
-let Headlines;
-let Article;
-let PluginHost;
+/* global require, App */
+/* exported Plugins */
const Plugins = {};
require(["dojo/_base/kernel",
"dojo/_base/declare",
"dojo/ready",
"dojo/parser",
- "fox/AppBase",
+ "fox/App",
"dojo/_base/loader",
"dojo/_base/html",
"dojo/query",
@@ -56,549 +50,16 @@ require(["dojo/_base/kernel",
"fox/FeedStoreModel",
"fox/FeedTree",
"fox/Toolbar",
+ "fox/form/ValidationTextArea",
"fox/form/Select",
"fox/form/ComboButton",
- "fox/form/DropDownButton"], function (dojo, declare, ready, parser, AppBase) {
+ "fox/form/DropDownButton"], function (dojo, declare, ready, parser) {
ready(function () {
try {
- const _App = declare("fox.App", AppBase, {
- global_unread: -1,
- _widescreen_mode: false,
- hotkey_actions: {},
- constructor: function () {
- this.setupNightModeDetection(() => {
- 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"));
-
- 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() {
- const view_mode = document.forms["toolbar-main"].view_mode.value;
-
- $$("body")[0].setAttribute("view-mode", view_mode);
-
- 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, pgup = 33, pgdn = 34
- if (event.type == "keydown" && event.which != 27 && (event.which < 33 || 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);
- 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_or_scroll"] = function (event) {
- Headlines.move('next', {event: event});
- };
- this.hotkey_actions["prev_article_or_scroll"] = function (event) {
- Headlines.move('prev', {event: event});
- };
- this.hotkey_actions["next_article_noscroll"] = function (event) {
- Headlines.move('next', {noscroll: true, event: event});
- };
- this.hotkey_actions["prev_article_noscroll"] = function (event) {
- Headlines.move('prev', {noscroll: true, event: event});
- };
- this.hotkey_actions["next_article_noexpand"] = function (event) {
- Headlines.move('next', {noscroll: true, noexpand: true, event: event});
- };
- this.hotkey_actions["prev_article_noexpand"] = function (event) {
- Headlines.move('prev', {noscroll: true, noexpand: true, event: event});
- };
- 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 (event) {
- const ctr = App.isCombinedMode() ? $("headlines-frame") : $("content-insert");
-
- if (ctr)
- Article.scroll(ctr.offsetHeight / 2, event);
- };
- this.hotkey_actions["article_scroll_up"] = function (event) {
- const ctr = App.isCombinedMode() ? $("headlines-frame") : $("content-insert");
-
- if (ctr)
- Article.scroll(-ctr.offsetHeight / 2, event);
- };
- this.hotkey_actions["next_article_page"] = function (event) {
- Headlines.scrollByPages(1, event);
- };
- this.hotkey_actions["prev_article_page"] = function (event) {
- Headlines.scrollByPages(-1, event);
- };
- this.hotkey_actions["article_page_down"] = function (event) {
- Article.scrollByPages(1, event);
- };
- this.hotkey_actions["article_page_up"] = function (event) {
- Article.scrollByPages(-1, event);
- };
- 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 (typeof 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 (typeof 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_full_text"] = function () {
- 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"] = 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();
- });
- };
- },
- 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 "qmcHKhelp":
- App.helpDialog("main");
- break;
- default:
- console.log("quickMenuGo: unknown action: " + opid);
- }
- },
- isPrefs: function() {
- return false;
- }
- });
-
- App = new _App();
+ App.init(parser, false);
} catch (e) {
- if (App && App.Error)
+ if (typeof App != "undefined" && App.Error)
App.Error.report(e);
else
alert(e + "\n\n" + e.stack);
@@ -606,11 +67,13 @@ require(["dojo/_base/kernel",
});
});
+/* exported hash_get */
function hash_get(key) {
const kv = window.location.hash.substring(1).toQueryParams();
return kv[key];
}
+/* exported hash_set */
function hash_set(key, value) {
const kv = window.location.hash.substring(1).toQueryParams();
kv[key] = value;
diff --git a/js/utility.js b/js/utility.js
index 2380f9823..eef1c6b61 100644
--- a/js/utility.js
+++ b/js/utility.js
@@ -1,4 +1,6 @@
-/* TODO: this should probably be something like night_mode.js since it does nothing specific to utility scripts */2
+/* global UtilityApp */
+
+/* TODO: this should probably be something like night_mode.js since it does nothing specific to utility scripts */
Event.observe(window, "load", function() {
const UtilityJS = {