From 049a37aa0e7d37cafd979e7d470c7649700b5010 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sat, 1 Dec 2018 17:05:28 +0300 Subject: WIP reshuffling of JS global context into separate logical objects --- js/FeedTree.js | 2 +- js/feedlist.js | 639 +++++++++++++++++++++++++------------------- js/functions.js | 178 +++++++------ js/prefs.js | 26 +- js/tt-rss.js | 609 ++++++++++++++++-------------------------- js/viewfeed.js | 808 ++++++++++++++++++++++++++------------------------------ 6 files changed, 1072 insertions(+), 1190 deletions(-) (limited to 'js') diff --git a/js/FeedTree.js b/js/FeedTree.js index b37d339c2..21a19bf85 100755 --- a/js/FeedTree.js +++ b/js/FeedTree.js @@ -97,7 +97,7 @@ define(["dojo/_base/declare", "dojo/dom-construct", "dijit/Tree", "dijit/Menu"], menu.addChild(new dijit.MenuItem({ label: __("Mark all feeds as read"), onClick: function() { - catchupAllFeeds(); + Feeds.catchupAllFeeds(); }})); menu.bindDomNode(tnode.domNode); diff --git a/js/feedlist.js b/js/feedlist.js index 4d5dbe2ac..b808451b4 100644 --- a/js/feedlist.js +++ b/js/feedlist.js @@ -1,302 +1,430 @@ -let infscroll_in_progress = 0; -let infscroll_disabled = 0; +let counters_last_request = 0; -let _infscroll_timeout = false; -let _search_query = false; -let _viewfeed_wait_timeout = false; +const Counters = { +}; + +const Feeds = { + _active_feed_id: 0, + _active_feed_is_cat: false, + infscroll_in_progress: 0, + infscroll_disabled: 0, + _infscroll_timeout: false, + _search_query: false, + _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; + } -let counters_last_request = 0; -let _counters_prev = []; + for (let i = 0; i < aProps.length; i++) { + const propName = aProps[i]; -function resetCounterCache() { - _counters_prev = []; -} + // If values of same property are not equal, + // objects are not equivalent + if (a[propName] !== b[propName]) { + return false; + } + } -function loadMoreHeadlines() { - const view_mode = document.forms["main_toolbar_form"].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 = getFeedUnread(getActiveFeedId(), activeFeedIsCat()); - - // TODO implement marked & published - - let offset = num_all; - - switch (view_mode) { - case "marked": - case "published": - console.warn("loadMoreHeadlines: ", view_mode, "not implemented"); - break; - case "unread": - offset = unread_in_buffer; - break; - case "adaptive": - if (!(getActiveFeedId() == -1 && !activeFeedIsCat())) - offset = num_unread > 0 ? unread_in_buffer : num_all; - break; - } + // If we made it this far, objects + // are considered equivalent + return true; + }, + resetCounters: function () { + this._counters_prev = []; + }, + parseCounters: function (elems) { + for (let l = 0; l < elems.length; l++) { + + if (this._counters_prev[l] && this.counterEquals(elems[l], this._counters_prev[l])) { + continue; + } - console.log("loadMoreHeadlines, offset=", offset); + 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; + const auxctr = parseInt(elems[l].auxcounter); + + if (id == "global-unread") { + App.global_unread = ctr; + App.updateTitle(); + continue; + } - viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat(), offset: offset, infscroll_req: true}); -} + if (id == "subscribed-feeds") { + /* feeds_found = ctr; */ + continue; + } -function cleanup_memory(root) { - const dijits = dojo.query("[widgetid]", dijit.byId(root).domNode).map(dijit.byNode); + /*if (getFeedUnread(id, (kind == "cat")) != ctr || + (kind == "cat")) { + }*/ - dijits.each(function (d) { - dojo.destroy(d.domNode); - }); + setFeedUnread(id, (kind == "cat"), ctr); + setFeedValue(id, (kind == "cat"), 'auxcounter', auxctr); - $$("#" + root + " *").each(function (i) { - i.parentNode ? i.parentNode.removeChild(i) : true; - }); -} + if (kind != "cat") { + setFeedValue(id, false, 'error', error); + setFeedValue(id, false, 'updated', updated); -function viewfeed(params) { - const feed = params.feed; - const is_cat = !!params.is_cat || false; - const offset = params.offset || 0; - const viewfeed_debug = params.viewfeed_debug; - 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 (feed != getActiveFeedId() || activeFeedIsCat() != is_cat) { - _search_query = false; - } + if (id > 0) { + if (has_img) { + setFeedIcon(id, false, + getInitParam("icons_url") + "/" + id + ".ico?" + has_img); + } else { + setFeedIcon(id, false, 'images/blank_icon.gif'); + } + } + } + } - if (offset != 0) { - if (infscroll_in_progress) - return; + this.hideOrShowFeeds(getInitParam("hide_read_feeds") == 1); + this._counters_prev = elems; + }, + viewCurrentFeed: function(method) { + console.log("viewCurrentFeed: " + method); - infscroll_in_progress = 1; + if (this.getActiveFeedId() != undefined) { + this.viewfeed({feed: Feeds.getActiveFeedId(), is_cat: Feeds.activeFeedIsCat(), method: method}); + } + return false; // block unneeded form submits + }, + openNextUnreadFeed: function() { + const is_cat = Feeds.activeFeedIsCat(); + const nuf = getNextUnreadFeed(Feeds.getActiveFeedId(), is_cat); + if (nuf) this.viewfeed({feed: nuf, is_cat: is_cat}); + }, + collapseFeedlist: function() { + Element.toggle("feeds-holder"); - window.clearTimeout(_infscroll_timeout); - _infscroll_timeout = window.setTimeout(() => { - console.log('infscroll request timed out, aborting'); - infscroll_in_progress = 0; + const splitter = $("feeds-holder_splitter"); - // call scroll handler to maybe repeat infscroll request - headlinesScrollHandler(); - }, 10 * 1000); - } + Element.visible("feeds-holder") ? splitter.show() : splitter.hide(); - Form.enable("main_toolbar_form"); + dijit.byId("main").resize(); + }, + cancelSearch: function() { + this._search_query = ""; + Feeds.viewCurrentFeed(); + }, + requestCounters: function(force) { + const date = new Date(); + const timestamp = Math.round(date.getTime() / 1000); - let query = Object.assign({op: "feeds", method: "view", feed: feed}, - dojo.formToObject("main_toolbar_form")); + if (force || timestamp - counters_last_request > 5) { + console.log("scheduling request of counters..."); - if (method) query.m = method; + counters_last_request = timestamp; - if (offset > 0) { - if (current_first_id) { - query.fid = current_first_id; - } - } + let query = {op: "rpc", method: "getAllCounters", seq: App.next_seq()}; - if (_search_query) { - query = Object.assign(query, _search_query); - } + if (!force) + query.last_article_id = getInitParam("last_article_id"); - if (offset != 0) { - query.skip = offset; + xhrPost("backend.php", query, (transport) => { + App.handleRpcJson(transport); + }); - // to prevent duplicate feed titles when showing grouped vfeeds - if (vgroup_last_feed) { - query.vgrlf = vgroup_last_feed; - } - } else if (!is_cat && feed == getActiveFeedId() && !params.method) { - query.m = "ForceUpdate"; + } else { + console.log("request_counters: rate limit reached: " + (timestamp - counters_last_request)); } + }, + reload: function() { + try { + Element.show("feedlistLoading"); - Form.enable("main_toolbar_form"); + this.resetCounters(); - if (!delayed) - if (!setFeedExpandoIcon(feed, is_cat, - (is_cat) ? 'images/indicator_tiny.gif' : 'images/indicator_white.gif')) - notify_progress("Loading, please wait...", true); + if (dijit.byId("feedTree")) { + dijit.byId("feedTree").destroyRecursive(); + } - query.cat = is_cat; + const store = new dojo.data.ItemFileWriteStore({ + url: "backend.php?op=pref_feeds&method=getfeedtree&mode=2" + }); - setActiveFeedId(feed, is_cat); + const treeModel = new fox.FeedStoreModel({ + store: store, + query: { + "type": getInitParam('enable_feed_cats') == 1 ? "category" : "feed" + }, + rootId: "root", + rootLabel: "Feeds", + childrenAttrs: ["items"] + }); - if (viewfeed_debug) { - window.open("backend.php?" + - dojo.objectToQuery( - Object.assign({debug: 1, csrf_token: getInitParam("csrf_token")}, query) - )); - } + 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.viewfeed({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); + + const tmph2 = dojo.connect(tree, 'onLoad', function () { + dojo.disconnect(tmph2); + Element.hide("feedlistLoading"); - window.clearTimeout(_viewfeed_wait_timeout); - _viewfeed_wait_timeout = window.setTimeout(() => { - catchupBatchedArticles(() => { - xhrPost("backend.php", query, (transport) => { try { - setFeedExpandoIcon(feed, is_cat, 'images/blank_icon.gif'); - headlines_callback2(transport, offset); - PluginHost.run(PluginHost.HOOK_FEED_LOADED, [feed, is_cat]); + Feeds.init(); + setLoadingProgress(25); } catch (e) { exception_error(e); } }); - }); - }, delayed ? 250 : 0); -} -function feedlist_init() { - console.log("in feedlist init"); + tree.startup(); + } catch (e) { + exception_error(e); + } + }, + init: function() { + console.log("in feedlist init"); - setLoadingProgress(50); + setLoadingProgress(50); - document.onkeydown = hotkey_handler; - setInterval(hotkeyPrefixTimeout, 3*1000); - setInterval(catchupBatchedArticles, 10*1000); + document.onkeydown = App.hotkeyHandler; + setInterval(hotkeyPrefixTimeout, 3 * 1000); + setInterval(catchupBatchedArticles, 10 * 1000); - if (!getActiveFeedId()) { - viewfeed({feed: -3}); - } else { - viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat()}); - } + if (!this.getActiveFeedId()) { + this.viewfeed({feed: -3}); + } else { + this.viewfeed({feed: this.getActiveFeedId(), is_cat: this.activeFeedIsCat()}); + } - hideOrShowFeeds(getInitParam("hide_read_feeds") == 1); - - if (getInitParam("is_default_pw")) { - console.warn("user password is at default value"); - - const dialog = new dijit.Dialog({ - title: __("Your password is at default value"), - href: "backend.php?op=dlg&method=defaultpasswordwarning", - id: 'infoBox', - style: "width: 600px", - onCancel: function() { - return true; - }, - onExecute: function() { - return true; - }, - onClose: function() { - return true; - } - }); + this.hideOrShowFeeds(getInitParam("hide_read_feeds") == 1); + + if (getInitParam("is_default_pw")) { + console.warn("user password is at default value"); + + const dialog = new dijit.Dialog({ + title: __("Your password is at default value"), + href: "backend.php?op=dlg&method=defaultpasswordwarning", + id: 'infoBox', + style: "width: 600px", + onCancel: function () { + return true; + }, + onExecute: function () { + return true; + }, + onClose: function () { + return true; + } + }); - dialog.show(); - } + dialog.show(); + } - // bw_limit disables timeout() so we request initial counters separately - if (getInitParam("bw_limit") == "1") { - request_counters(true); - } else { - setTimeout(timeout, 250); - } -} + // bw_limit disables timeout() so we request initial counters separately + if (getInitParam("bw_limit") == "1") { + this.requestCounters(true); + } else { + setTimeout(() => { + this.requestCounters(true); + + setInterval(() => { + this.requestCounters(); + }, 60 * 1000) + }, 250); + } + }, + activeFeedIsCat: function() { + return !!this._active_feed_is_cat; + }, + getActiveFeedId: function() { + return this._active_feed_id; + }, + setActiveFeedId: function(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.selectFeed(id, is_cat); + + PluginHost.run(PluginHost.HOOK_FEED_SET_ACTIVE, _active_article_id); + }, + selectFeed: function(feed, is_cat) { + const tree = dijit.byId("feedTree"); + if (tree) return tree.selectFeed(feed, is_cat); + }, + toggleDispRead: function() { + const hide = !(getInitParam("hide_read_feeds") == "1"); -function request_counters(force) { - const date = new Date(); - const timestamp = Math.round(date.getTime() / 1000); + xhrPost("backend.php", {op: "rpc", method: "setpref", key: "HIDE_READ_FEEDS", value: hide}, () => { + this.hideOrShowFeeds(hide); + setInitParam("hide_read_feeds", hide); + }); + }, + hideOrShowFeeds: function(hide) { + const tree = dijit.byId("feedTree"); - if (force || timestamp - counters_last_request > 5) { - console.log("scheduling request of counters..."); + if (tree) + return tree.hideRead(hide, getInitParam("hide_read_shows_special")); + }, + viewfeed: 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 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 (feed != Feeds.getActiveFeedId() || Feeds.activeFeedIsCat() != is_cat) { + this._search_query = false; + setActiveArticleId(0); + } - counters_last_request = timestamp; + if (offset != 0) { + if (this.infscroll_in_progress) + return; - let query = {op: "rpc", method: "getAllCounters", seq: next_seq()}; + this.infscroll_in_progress = 1; - if (!force) - query.last_article_id = getInitParam("last_article_id"); + 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", query, (transport) => { - handle_rpc_json(transport); - }); + // call scroll handler to maybe repeat infscroll request + Headlines.scrollHandler(); + }, 10 * 1000); + } - } else { - console.log("request_counters: rate limit reached: " + (timestamp - counters_last_request)); - } -} + Form.enable("main_toolbar_form"); -// NOTE: this implementation is incomplete -// for general objects but good enough for counters -// http://adripofjavascript.com/blog/drips/object-equality-in-javascript.html -function counter_is_equal(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; - } + let query = Object.assign({op: "feeds", method: "view", feed: feed}, + dojo.formToObject("main_toolbar_form")); - for (let i = 0; i < aProps.length; i++) { - const propName = aProps[i]; + if (method) query.m = method; - // If values of same property are not equal, - // objects are not equivalent - if (a[propName] !== b[propName]) { - return false; + if (offset > 0) { + if (current_first_id) { + query.fid = current_first_id; + } } - } - - // If we made it this far, objects - // are considered equivalent - return true; -} + if (this._search_query) { + query = Object.assign(query, this._search_query); + } -function parse_counters(elems) { - for (let l = 0; l < elems.length; l++) { + if (offset != 0) { + query.skip = offset; - if (_counters_prev[l] && counter_is_equal(elems[l], _counters_prev[l])) { - continue; + // to prevent duplicate feed titles when showing grouped vfeeds + if (vgroup_last_feed) { + query.vgrlf = vgroup_last_feed; + } + } else if (!is_cat && feed == Feeds.getActiveFeedId() && !params.method) { + query.m = "ForceUpdate"; } - 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; - const auxctr = parseInt(elems[l].auxcounter); - - if (id == "global-unread") { - global_unread = ctr; - updateTitle(); - continue; + Form.enable("main_toolbar_form"); + + if (!delayed) + if (!setFeedExpandoIcon(feed, is_cat, + (is_cat) ? 'images/indicator_tiny.gif' : 'images/indicator_white.gif')) + notify_progress("Loading, please wait...", true); + + query.cat = is_cat; + + Feeds.setActiveFeedId(feed, is_cat); + + if (viewfeed_debug) { + window.open("backend.php?" + + dojo.objectToQuery( + Object.assign({debug: 1, csrf_token: getInitParam("csrf_token")}, query) + )); } - if (id == "subscribed-feeds") { - /* feeds_found = ctr; */ - continue; + window.clearTimeout(this._viewfeed_wait_timeout); + this._viewfeed_wait_timeout = window.setTimeout(() => { + catchupBatchedArticles(() => { + xhrPost("backend.php", query, (transport) => { + try { + setFeedExpandoIcon(feed, is_cat, 'images/blank_icon.gif'); + Headlines.onLoaded(transport, offset); + PluginHost.run(PluginHost.HOOK_FEED_LOADED, [feed, is_cat]); + } catch (e) { + exception_error(e); + } + }); + }); + }, delayed ? 250 : 0); + }, + catchupAllFeeds: function() { + const str = __("Mark all articles as read?"); + + if (getInitParam("confirm_feed_catchup") != 1 || confirm(str)) { + + notify_progress("Marking all feeds as read..."); + + xhrPost("backend.php", {op: "feeds", method: "catchupAll"}, () => { + this.requestCounters(true); + this.viewCurrentFeed(); + }); + + App.global_unread = 0; + App.updateTitle(); } + }, + decrementFeedCounter: function(feed, is_cat) { + let ctr = getFeedUnread(feed, is_cat); - /*if (getFeedUnread(id, (kind == "cat")) != ctr || - (kind == "cat")) { - }*/ + if (ctr > 0) { + setFeedUnread(feed, is_cat, ctr - 1); + App.global_unread -= 1; + App.updateTitle(); - setFeedUnread(id, (kind == "cat"), ctr); - setFeedValue(id, (kind == "cat"), 'auxcounter', auxctr); + if (!is_cat) { + const cat = parseInt(getFeedCategory(feed)); - if (kind != "cat") { - setFeedValue(id, false, 'error', error); - setFeedValue(id, false, 'updated', updated); + if (!isNaN(cat)) { + ctr = getFeedUnread(cat, true); - if (id > 0) { - if (has_img) { - setFeedIcon(id, false, - getInitParam("icons_url") + "/" + id + ".ico?" + has_img); - } else { - setFeedIcon(id, false, 'images/blank_icon.gif'); + if (ctr > 0) { + setFeedUnread(cat, true, ctr - 1); + } } } } } - - hideOrShowFeeds(getInitParam("hide_read_feeds") == 1); - - _counters_prev = elems; -} +}; function getFeedUnread(feed, is_cat) { try { @@ -326,13 +454,6 @@ function getFeedCategory(feed) { return false; } -function hideOrShowFeeds(hide) { - const tree = dijit.byId("feedTree"); - - if (tree) - return tree.hideRead(hide, getInitParam("hide_read_shows_special")); -} - function getFeedName(feed, is_cat) { if (isNaN(feed)) return feed; // it's a tag @@ -375,12 +496,6 @@ function setFeedValue(feed, is_cat, key, value) { } } -function selectFeed(feed, is_cat) { - const tree = dijit.byId("feedTree"); - - if (tree) return tree.selectFeed(feed, is_cat); -} - function setFeedIcon(feed, is_cat, src) { const tree = dijit.byId("feedTree"); @@ -404,7 +519,7 @@ function getNextUnreadFeed(feed, is_cat) { } function catchupCurrentFeed(mode) { - catchupFeed(getActiveFeedId(), activeFeedIsCat(), mode); + catchupFeed(Feeds.getActiveFeedId(), Feeds.activeFeedIsCat(), mode); } function catchupFeedInGroup(id) { @@ -440,13 +555,13 @@ function catchupFeedInGroup(id) { } } - updateFloatingTitle(true); + Headlines.updateFloatingTitle(true); } notify_progress("Loading, please wait...", true); xhrPost("backend.php", { op: "rpc", method: "catchupFeed", feed_id: id, is_cat: false}, (transport) => { - handle_rpc_json(transport); + App.handleRpcJson(transport); }); } } @@ -487,7 +602,7 @@ function catchupFeed(feed, is_cat, mode) { notify_progress("Loading, please wait...", true); xhrPost("backend.php", catchup_query, (transport) => { - handle_rpc_json(transport); + App.handleRpcJson(transport); const show_next_feed = getInitParam("on_catchup_show_next_feed") == "1"; @@ -495,37 +610,15 @@ function catchupFeed(feed, is_cat, mode) { const nuf = getNextUnreadFeed(feed, is_cat); if (nuf) { - viewfeed({feed: nuf, is_cat: is_cat}); + Feeds.viewfeed({feed: nuf, is_cat: is_cat}); } - } else if (feed == getActiveFeedId() && is_cat == activeFeedIsCat()) { - viewCurrentFeed(); + } else if (feed == Feeds.getActiveFeedId() && is_cat == Feeds.activeFeedIsCat()) { + Feeds.viewCurrentFeed(); } notify(""); }); } -function decrementFeedCounter(feed, is_cat) { - let ctr = getFeedUnread(feed, is_cat); - - if (ctr > 0) { - setFeedUnread(feed, is_cat, ctr - 1); - global_unread = global_unread - 1; - updateTitle(); - - if (!is_cat) { - const cat = parseInt(getFeedCategory(feed)); - - if (!isNaN(cat)) { - ctr = getFeedUnread(cat, true); - - if (ctr > 0) { - setFeedUnread(cat, true, ctr - 1); - } - } - } - } - -} diff --git a/js/functions.js b/js/functions.js index 4a9785fac..d9778ad22 100755 --- a/js/functions.js +++ b/js/functions.js @@ -50,13 +50,85 @@ function xhrJson(url, params, complete) { } /* add method to remove element from array */ - Array.prototype.remove = function(s) { for (let i=0; i < this.length; i++) { if (s == this[i]) this.splice(i, 1); } }; +const Utils = { + 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=" + param_escape(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(""); + + if (callback) callback(transport); + } catch (e) { + exception_error(e); + } + }); + + return false; + }, +}; + function report_error(message, filename, lineno, colno, error) { exception_error(error, null, filename, lineno); } @@ -324,51 +396,6 @@ function closeInfoBox() { return false; } -function displayDlg(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(""); - - if (callback) callback(transport); - } catch (e) { - exception_error(e); - } - }); - - return false; -} - function getInitParam(key) { return init_params[key]; } @@ -453,7 +480,7 @@ function filterDlgCheckAction(sender) { function explainError(code) { - return displayDlg(__("Error explained"), "explainError", code); + return Utils.displayDlg(__("Error explained"), "explainError", code); } function setLoadingProgress(p) { @@ -489,9 +516,9 @@ function uploadIconHandler(rc) { case 0: notify_info("Upload complete."); if (inPreferences()) { - updateFeedList(); + Feeds.reload(); } else { - setTimeout('updateFeedList(false, false)', 50); + setTimeout('Feeds.reload(false, false)', 50); } break; case 1: @@ -514,9 +541,9 @@ function removeFeedIcon(id) { xhrPost("backend.php", query, (transport) => { notify_info("Feed icon removed."); if (inPreferences()) { - updateFeedList(); + Feeds.reload(); } else { - setTimeout('updateFeedList(false, false)', 50); + setTimeout('Feeds.reload(false, false)', 50); } }); } @@ -556,7 +583,7 @@ function addLabel(select, callback) { } else if (inPreferences()) { updateLabelList(); } else { - updateFeedList(); + Feeds.reload(); } }); } @@ -616,7 +643,7 @@ function quickAddFeed() { dialog.hide(); notify_info(__("Subscribed to %s").replace("%s", feed_url)); - updateFeedList(); + Feeds.reload(); break; case 2: dialog.show_error(__("Specified URL seems to be invalid.")); @@ -889,7 +916,7 @@ function quickAddFilter() { if (!inPreferences()) { query = { op: "pref-filters", method: "newfilter", - feed: getActiveFeedId(), is_cat: activeFeedIsCat() }; + feed: Feeds.getActiveFeedId(), is_cat: Feeds.activeFeedIsCat() }; } else { query = { op: "pref-filters", method: "newfilter" }; } @@ -973,8 +1000,8 @@ function quickAddFilter() { if (selectedText != "") { - const feed_id = activeFeedIsCat() ? 'CAT:' + parseInt(getActiveFeedId()) : - getActiveFeedId(); + const feed_id = Feeds.activeFeedIsCat() ? 'CAT:' + parseInt(Feeds.getActiveFeedId()) : + Feeds.getActiveFeedId(); const rule = { reg_exp: selectedText, feed_id: [feed_id], filter_type: 1 }; @@ -991,12 +1018,12 @@ function quickAddFilter() { if (reply && reply.title) title = reply.title; - if (title || getActiveFeedId() || activeFeedIsCat()) { + if (title || Feeds.getActiveFeedId() || Feeds.activeFeedIsCat()) { - console.log(title + " " + getActiveFeedId()); + console.log(title + " " + Feeds.getActiveFeedId()); - const feed_id = activeFeedIsCat() ? 'CAT:' + parseInt(getActiveFeedId()) : - getActiveFeedId(); + const feed_id = Feeds.activeFeedIsCat() ? 'CAT:' + parseInt(Feeds.getActiveFeedId()) : + Feeds.getActiveFeedId(); const rule = { reg_exp: title, feed_id: [feed_id], filter_type: 1 }; @@ -1024,12 +1051,13 @@ function unsubscribeFeed(feed_id, title) { if (dijit.byId("feedEditDlg")) dijit.byId("feedEditDlg").hide(); if (inPreferences()) { - updateFeedList(); + Feeds.reload(); } else { - if (feed_id == getActiveFeedId()) - setTimeout(function() { viewfeed({feed:-5}) }, 100); + if (feed_id == Feeds.getActiveFeedId()) + setTimeout(() => { Feeds.viewfeed({feed:-5}) }, + 100); - if (feed_id < 0) updateFeedList(); + if (feed_id < 0) Feeds.reload(); } }); } @@ -1219,7 +1247,7 @@ function editFeed(feed) { xhrPost("backend.php", dialog.attr('value'), () => { dialog.hide(); notify(''); - updateFeedList(); + Feeds.reload(); }); } }, @@ -1290,7 +1318,7 @@ function feedBrowser() { xhrPost("backend.php", query, () => { notify(''); - updateFeedList(); + Feeds.reload(); }); } else { @@ -1375,7 +1403,7 @@ function showFeedsWithErrors() { xhrPost("backend.php", query, () => { notify(''); dialog.hide(); - updateFeedList(); + Feeds.reload(); }); } @@ -1399,22 +1427,6 @@ function get_timestamp() { return Math.round(date.getTime() / 1000); } -function helpDialog(topic) { - const query = "backend.php?op=backend&method=help&topic=" + param_escape(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(); -} - // noinspection JSUnusedGlobalSymbols function label_to_feed_id(label) { return _label_base_index - 1 - Math.abs(label); diff --git a/js/prefs.js b/js/prefs.js index 50283efbf..bb547c113 100755 --- a/js/prefs.js +++ b/js/prefs.js @@ -6,7 +6,7 @@ function notify_callback2(transport, sticky) { notify_info(transport.responseText, sticky); } -function updateFeedList() { +function Feeds.reload() { const user_search = $("feed_search"); let search = ""; @@ -324,7 +324,7 @@ function removeSelectedFeeds() { ids: sel_rows.toString() }; xhrPost("backend.php", query, () => { - updateFeedList(); + Feeds.reload(); }); } @@ -524,7 +524,7 @@ function editSelectedFeeds() { xhrPost("backend.php", query, () => { dialog.hide(); - updateFeedList(); + Feeds.reload(); }); } }, @@ -618,7 +618,7 @@ function selectTab(id, noupdate) { switch (id) { case "feedConfig": - updateFeedList(); + Feeds.reload(); break; case "filterConfig": updateFilterList(); @@ -764,7 +764,7 @@ function pref_hotkey_handler(e) { quickAddFilter(); return false; case "help_dialog": - helpDialog("main"); + Utils.helpDialog("main"); return false; default: console.log("unhandled action: " + action_name + "; keycode: " + e.which); @@ -782,7 +782,7 @@ function removeCategory(id, item) { xhrPost("backend.php", query, () => { notify(''); - updateFeedList(); + Feeds.reload(); }); } } @@ -798,7 +798,7 @@ function removeSelectedCategories() { ids: sel_rows.toString() }; xhrPost("backend.php", query, () => { - updateFeedList(); + Feeds.reload(); }); } } else { @@ -816,7 +816,7 @@ function createCategory() { xhrPost("backend.php", { op: "pref-feeds", method: "addCat", cat: title }, () => { notify(''); - updateFeedList(); + Feeds.reload(); }); } } @@ -847,7 +847,7 @@ function showInactiveFeeds() { xhrPost("backend.php", query, () => { notify(''); dialog.hide(); - updateFeedList(); + Feeds.reload(); }); } @@ -1037,7 +1037,7 @@ function resetFeedOrder() { notify_progress("Loading, please wait..."); xhrPost("backend.php", { op: "pref-feeds", method: "feedsortreset" }, () => { - updateFeedList(); + Feeds.reload(); }); } @@ -1045,7 +1045,7 @@ function resetCatOrder() { notify_progress("Loading, please wait..."); xhrPost("backend.php", { op: "pref-feeds", method: "catsortreset" }, () => { - updateFeedList(); + Feeds.reload(); }); } @@ -1057,7 +1057,7 @@ function editCat(id, item) { notify_progress("Loading, please wait..."); xhrPost("backend.php", { op: 'pref-feeds', method: 'renamecat', id: id, title: new_name }, () => { - updateFeedList(); + Feeds.reload(); }); } } @@ -1176,7 +1176,7 @@ function batchSubscribe() { xhrPost("backend.php", this.attr('value'), () => { notify(""); - updateFeedList(); + Feeds.reload(); dialog.hide(); }); } diff --git a/js/tt-rss.js b/js/tt-rss.js index 99a484e9a..b5b785321 100644 --- a/js/tt-rss.js +++ b/js/tt-rss.js @@ -1,148 +1,215 @@ /* global dijit, __ */ -let global_unread = -1; let _widescreen_mode = false; -let _rpc_seq = 0; -let _active_feed_id = 0; -let _active_feed_is_cat = false; let hotkey_actions = {}; -let _headlines_scroll_timeout = false; -function next_seq() { - _rpc_seq += 1; - return _rpc_seq; -} +const App = { + _rpc_seq: 0, + global_unread: -1, + next_seq: function() { + this._rpc_seq += 1; + return this._rpc_seq; + }, + get_seq: function() { + return this._rpc_seq; + }, + updateTitle: function() { + let tmp = "Tiny Tiny RSS"; + + if (this.global_unread > 0) { + tmp = "(" + this.global_unread + ") " + tmp; + } -function get_seq() { - return _rpc_seq; -} + document.title = tmp; + }, + isCombinedMode: function() { + return getInitParam("combined_display_mode"); + }, + hotkeyHandler(event) { + if (event.target.nodeName == "INPUT" || event.target.nodeName == "TEXTAREA") return; -function activeFeedIsCat() { - return !!_active_feed_is_cat; -} + const action_name = keyeventToAction(event); -function getActiveFeedId() { - return _active_feed_id; -} + if (action_name) { + const action_func = hotkey_actions[action_name]; -function setActiveFeedId(id, is_cat) { - hash_set('f', id); - hash_set('c', is_cat ? 1 : 0); + if (action_func != null) { + action_func(); + event.stopPropagation(); + return false; + } + } + }, + switchPanelMode: function(wide) { + if (App.isCombinedMode()) return; - _active_feed_id = id; - _active_feed_is_cat = is_cat; + const article_id = getActiveArticleId(); - $("headlines-frame").setAttribute("feed-id", id); - $("headlines-frame").setAttribute("is-cat", is_cat ? 1 : 0); + if (wide) { + dijit.byId("headlines-wrap-inner").attr("design", 'sidebar'); + dijit.byId("content-insert").attr("region", "trailing"); - selectFeed(id, is_cat); + dijit.byId("content-insert").domNode.setStyle({width: '50%', + height: 'auto', + borderTopWidth: '0px' }); - PluginHost.run(PluginHost.HOOK_FEED_SET_ACTIVE, _active_article_id); -} + if (parseInt(getCookie("ttrss_ci_width")) > 0) { + dijit.byId("content-insert").domNode.setStyle( + {width: getCookie("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'}); -function updateFeedList() { - try { - Element.show("feedlistLoading"); + if (parseInt(getCookie("ttrss_ci_height")) > 0) { + dijit.byId("content-insert").domNode.setStyle( + {height: getCookie("ttrss_ci_height") + "px" }); + } - resetCounterCache(); + $("headlines-frame").setStyle({ borderBottomWidth: '1px' }); + $("headlines-frame").removeClassName("wide"); - if (dijit.byId("feedTree")) { - dijit.byId("feedTree").destroyRecursive(); } - const store = new dojo.data.ItemFileWriteStore({ - url: "backend.php?op=pref_feeds&method=getfeedtree&mode=2" - }); + Article.closeArticlePanel(); - const treeModel = new fox.FeedStoreModel({ - store: store, - query: { - "type": getInitParam('enable_feed_cats') == 1 ? "category" : "feed" - }, - rootId: "root", - rootLabel: "Feeds", - childrenAttrs: ["items"] - }); + if (article_id) view(article_id); - 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); - viewfeed({feed: feed, is_cat: is_cat}); - return false; - }, - openOnClick: false, - showRoot: false, - persist: true, - id: "feedTree", - }, "feedTree"); - - var tmph = dojo.connect(dijit.byId('feedMenu'), '_openMyself', function (event) { - console.log(dijit.getEnclosingWidget(event.target)); - dojo.disconnect(tmph); - }); + xhrPost("backend.php", {op: "rpc", method: "setpanelmode", wide: wide ? 1 : 0}); + }, + parseRuntimeInfo: function(data) { - $("feeds-holder").appendChild(tree.domNode); + //console.log("parsing runtime info..."); - var tmph = dojo.connect(tree, 'onLoad', function () { - dojo.disconnect(tmph); - Element.hide("feedlistLoading"); + for (const k in data) { + const v = data[k]; - try { - feedlist_init(); + if (k == "dep_ts" && parseInt(getInitParam("dep_ts")) > 0) { + if (parseInt(getInitParam("dep_ts")) < parseInt(v) && getInitParam("reload_on_ts_change")) { + window.location.reload(); + } + } - setLoadingProgress(25); - } catch (e) { - exception_error(e); + if (k == "daemon_is_running" && v != 1) { + notify_error("Update daemon is not running.", true); + return; } - }); - tree.startup(); - } catch (e) { - exception_error(e); - } -} + if (k == "update_result") { + const updatesIcon = dijit.byId("updatesIcon").domNode; -function catchupAllFeeds() { + if (v) { + Element.show(updatesIcon); + } else { + Element.hide(updatesIcon); + } + } - const str = __("Mark all articles as read?"); + if (k == "daemon_stamp_ok" && v != 1) { + notify_error("Update daemon is not updating feeds.", true); + return; + } - if (getInitParam("confirm_feed_catchup") != 1 || confirm(str)) { + if (k == "max_feed_id" || k == "num_feeds") { + if (init_params[k] != v) { + console.log("feed count changed, need to reload feedlist."); + Feeds.reload(); + } + } - notify_progress("Marking all feeds as read..."); + init_params[k] = v; + notify(''); + } - xhrPost("backend.php", {op: "feeds", method: "catchupAll"}, () => { - request_counters(true); - viewCurrentFeed(); - }); + PluginHost.run(PluginHost.HOOK_RUNTIME_INFO_LOADED, data); + }, + handleRpcJson: function(transport) { - global_unread = 0; - updateTitle(""); - } -} + const netalert_dijit = dijit.byId("net-alert"); + let netalert = false; -function viewCurrentFeed(method) { - console.log("viewCurrentFeed: " + method); + if (netalert_dijit) netalert = netalert_dijit.domNode; - if (getActiveFeedId() != undefined) { - viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat(), method: method}); - } - return false; // block unneeded form submits -} + try { + const reply = JSON.parse(transport.responseText); -function timeout() { - if (getInitParam("bw_limit") != "1") { - request_counters(true); - setTimeout(timeout, 60*1000); - } -} + if (reply) { + + const error = reply['error']; + + if (error) { + const code = error['code']; + const msg = error['msg']; + + console.warn("[handleRpcJson] received fatal error " + code + "/" + msg); + + if (code != 0) { + fatalError(code, msg); + return false; + } + } + + const seq = reply['seq']; + + if (seq && this.get_seq() != seq) { + console.log("[handleRpcJson] sequence mismatch: " + seq + + " (want: " + this.get_seq() + ")"); + return true; + } + + const message = reply['message']; + + if (message == "UPDATE_COUNTERS") { + console.log("need to refresh counters..."); + setInitParam("last_article_id", -1); + 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(); + else + notify_error("Communication problem with server."); + } + + } catch (e) { + if (netalert) + netalert.show(); + else + notify_error("Communication problem with server."); + + console.error(e); + } + + return false; + }, +}; function search() { const query = "backend.php?op=feeds&method=search¶m=" + - param_escape(getActiveFeedId() + ":" + activeFeedIsCat()); + param_escape(Feeds.getActiveFeedId() + ":" + Feeds.activeFeedIsCat()); if (dijit.byId("searchDlg")) dijit.byId("searchDlg").destroyRecursive(); @@ -153,9 +220,9 @@ function search() { style: "width: 600px", execute: function() { if (this.validate()) { - _search_query = this.attr('value'); + Feeds._search_query = this.attr('value'); this.hide(); - viewCurrentFeed(); + Feeds.viewCurrentFeed(); } }, href: query}); @@ -163,16 +230,6 @@ function search() { dialog.show(); } -function updateTitle() { - let tmp = "Tiny Tiny RSS"; - - if (global_unread > 0) { - tmp = "(" + global_unread + ") " + tmp; - } - - document.title = tmp; -} - function genericSanityCheck() { setCookie("ttrss_test", "TEST"); @@ -272,15 +329,15 @@ function init() { function init_hotkey_actions() { hotkey_actions["next_feed"] = function() { const rv = dijit.byId("feedTree").getNextFeed( - getActiveFeedId(), activeFeedIsCat()); + Feeds.getActiveFeedId(), Feeds.activeFeedIsCat()); - if (rv) viewfeed({feed: rv[0], is_cat: rv[1], delayed: true}) + if (rv) Feeds.viewfeed({feed: rv[0], is_cat: rv[1], delayed: true}) }; hotkey_actions["prev_feed"] = function() { const rv = dijit.byId("feedTree").getPreviousFeed( - getActiveFeedId(), activeFeedIsCat()); + Feeds.getActiveFeedId(), Feeds.activeFeedIsCat()); - if (rv) viewfeed({feed: rv[0], is_cat: rv[1], delayed: true}) + if (rv) Feeds.viewfeed({feed: rv[0], is_cat: rv[1], delayed: true}) }; hotkey_actions["next_article"] = function() { moveToPost('next'); @@ -320,7 +377,7 @@ function init_hotkey_actions() { } hotkey_actions["open_in_new_window"] = function() { if (getActiveArticleId()) { - openArticleInNewWindow(getActiveArticleId()); + Article.openArticleInNewWindow(getActiveArticleId()); } }; hotkey_actions["catchup_below"] = function() { @@ -336,10 +393,10 @@ function init_hotkey_actions() { scrollArticle(-40); }; hotkey_actions["close_article"] = function() { - if (isCombinedMode()) { + if (App.isCombinedMode()) { cdmCollapseActive(); } else { - closeArticlePanel(); + Article.closeArticlePanel(); } }; hotkey_actions["email_article"] = function() { @@ -370,20 +427,20 @@ function init_hotkey_actions() { selectArticles('none'); }; hotkey_actions["feed_refresh"] = function() { - if (getActiveFeedId() != undefined) { - viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat()}); + if (Feeds.getActiveFeedId() != undefined) { + Feeds.viewfeed({feed: Feeds.getActiveFeedId(), is_cat: Feeds.activeFeedIsCat()}); return; } }; hotkey_actions["feed_unhide_read"] = function() { - toggleDispRead(); + Feeds.toggleDispRead(); }; hotkey_actions["feed_subscribe"] = function() { quickAddFeed(); }; hotkey_actions["feed_debug_update"] = function() { - if (!activeFeedIsCat() && parseInt(getActiveFeedId()) > 0) { - window.open("backend.php?op=feeds&method=update_debugger&feed_id=" + getActiveFeedId() + + if (!Feeds.activeFeedIsCat() && parseInt(Feeds.getActiveFeedId()) > 0) { + window.open("backend.php?op=feeds&method=update_debugger&feed_id=" + Feeds.getActiveFeedId() + "&csrf_token=" + getInitParam("csrf_token")); } else { alert("You can't debug this kind of feed."); @@ -391,17 +448,17 @@ function init_hotkey_actions() { }; hotkey_actions["feed_debug_viewfeed"] = function() { - viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat(), viewfeed_debug: true}); + Feeds.viewfeed({feed: Feeds.getActiveFeedId(), is_cat: Feeds.activeFeedIsCat(), viewfeed_debug: true}); }; hotkey_actions["feed_edit"] = function() { - if (activeFeedIsCat()) + if (Feeds.activeFeedIsCat()) alert(__("You can't edit this kind of feed.")); else - editFeed(getActiveFeedId()); + editFeed(Feeds.getActiveFeedId()); }; hotkey_actions["feed_catchup"] = function() { - if (getActiveFeedId() != undefined) { + if (Feeds.getActiveFeedId() != undefined) { catchupCurrentFeed(); return; } @@ -411,32 +468,32 @@ function init_hotkey_actions() { }; hotkey_actions["feed_toggle_vgroup"] = function() { xhrPost("backend.php", {op: "rpc", method: "togglepref", key: "VFEED_GROUP_BY_FEED"}, () => { - viewCurrentFeed(); + Feeds.viewCurrentFeed(); }) }; hotkey_actions["catchup_all"] = function() { - catchupAllFeeds(); + Feeds.catchupAllFeeds(); }; hotkey_actions["cat_toggle_collapse"] = function() { - if (activeFeedIsCat()) { - dijit.byId("feedTree").collapseCat(getActiveFeedId()); + if (Feeds.activeFeedIsCat()) { + dijit.byId("feedTree").collapseCat(Feeds.getActiveFeedId()); return; } }; hotkey_actions["goto_all"] = function() { - viewfeed({feed: -4}); + Feeds.viewfeed({feed: -4}); }; hotkey_actions["goto_fresh"] = function() { - viewfeed({feed: -3}); + Feeds.viewfeed({feed: -3}); }; hotkey_actions["goto_marked"] = function() { - viewfeed({feed: -1}); + Feeds.viewfeed({feed: -1}); }; hotkey_actions["goto_published"] = function() { - viewfeed({feed: -2}); + Feeds.viewfeed({feed: -2}); }; hotkey_actions["goto_tagcloud"] = function() { - displayDlg(__("Tag cloud"), "printTagCloud"); + Utils.displayDlg(__("Tag cloud"), "printTagCloud"); }; hotkey_actions["goto_prefs"] = function() { gotoPreferences(); @@ -467,7 +524,7 @@ function init_hotkey_actions() { quickAddFilter(); }; hotkey_actions["collapse_sidebar"] = function() { - collapse_feedlist(); + Feeds.viewCurrentFeed(); }; hotkey_actions["toggle_embed_original"] = function() { if (typeof embedOriginalArticle != "undefined") { @@ -478,32 +535,32 @@ function init_hotkey_actions() { } }; hotkey_actions["toggle_widescreen"] = function() { - if (!isCombinedMode()) { + if (!App.isCombinedMode()) { _widescreen_mode = !_widescreen_mode; // reset stored sizes because geometry changed setCookie("ttrss_ci_width", 0); setCookie("ttrss_ci_height", 0); - switchPanelMode(_widescreen_mode); + App.switchPanelMode(_widescreen_mode); } else { alert(__("Widescreen is not available in combined mode.")); } }; hotkey_actions["help_dialog"] = function() { - helpDialog("main"); + Utils.helpDialog("main"); }; hotkey_actions["toggle_combined_mode"] = function() { notify_progress("Loading, please wait..."); - const value = isCombinedMode() ? "false" : "true"; + const value = App.isCombinedMode() ? "false" : "true"; xhrPost("backend.php", {op: "rpc", method: "setpref", key: "COMBINED_DISPLAY_MODE", value: value}, () => { setInitParam("combined_display_mode", !getInitParam("combined_display_mode")); - closeArticlePanel(); - viewCurrentFeed(); + Article.closeArticlePanel(); + Feeds.viewCurrentFeed(); }) }; hotkey_actions["toggle_cdm_expanded"] = function() { @@ -513,15 +570,15 @@ function init_hotkey_actions() { xhrPost("backend.php", { op: "rpc", method: "setpref", key: "CDM_EXPANDED", value: value }, () => { setInitParam("cdm_expanded", !getInitParam("cdm_expanded")); - viewCurrentFeed(); + Feeds.viewCurrentFeed(); }); }; } function init_second_stage() { - updateFeedList(); - closeArticlePanel(); + Feeds.reload(); + Article.closeArticlePanel(); if (parseInt(getCookie("ttrss_fh_width")) > 0) { dijit.byId("feeds-holder").domNode.setStyle( @@ -559,7 +616,7 @@ function init_second_stage() { const hash_feed_is_cat = hash_get('c') == "1"; if (hash_feed_id != undefined) { - setActiveFeedId(hash_feed_id, hash_feed_is_cat); + Feeds.setActiveFeedId(hash_feed_id, hash_feed_is_cat); } setLoadingProgress(50); @@ -569,15 +626,9 @@ function init_second_stage() { sessionStorage.clear(); _widescreen_mode = getInitParam("widescreen"); - switchPanelMode(_widescreen_mode); - - $("headlines-frame").onscroll = (event) => { - clearTimeout(_headlines_scroll_timeout); - _headlines_scroll_timeout = window.setTimeout(function() { - //console.log('done scrolling', event); - headlinesScrollHandler(event); - }, 50); - } + App.switchPanelMode(_widescreen_mode); + + Headlines.initScrollHandler(); console.log("second stage ok"); @@ -596,7 +647,7 @@ function quickMenuGo(opid) { document.location.href = "backend.php?op=logout"; break; case "qmcTagCloud": - displayDlg(__("Tag cloud"), "printTagCloud"); + Utils.displayDlg(__("Tag cloud"), "printTagCloud"); break; case "qmcSearch": search(); @@ -608,15 +659,15 @@ function quickMenuGo(opid) { window.location.href = "backend.php?op=digest"; break; case "qmcEditFeed": - if (activeFeedIsCat()) + if (Feeds.activeFeedIsCat()) alert(__("You can't edit this kind of feed.")); else - editFeed(getActiveFeedId()); + editFeed(Feeds.getActiveFeedId()); break; case "qmcRemoveFeed": - var actid = getActiveFeedId(); + var actid = Feeds.getActiveFeedId(); - if (activeFeedIsCat()) { + if (Feeds.activeFeedIsCat()) { alert(__("You can't unsubscribe from the category.")); return; } @@ -635,120 +686,35 @@ function quickMenuGo(opid) { } break; case "qmcCatchupAll": - catchupAllFeeds(); + Feeds.catchupAllFeeds(); break; case "qmcShowOnlyUnread": - toggleDispRead(); + Feeds.toggleDispRead(); break; case "qmcToggleWidescreen": - if (!isCombinedMode()) { + if (!App.isCombinedMode()) { _widescreen_mode = !_widescreen_mode; // reset stored sizes because geometry changed setCookie("ttrss_ci_width", 0); setCookie("ttrss_ci_height", 0); - switchPanelMode(_widescreen_mode); + App.switchPanelMode(_widescreen_mode); } else { alert(__("Widescreen is not available in combined mode.")); } break; case "qmcHKhelp": - helpDialog("main"); + Utils.helpDialog("main"); break; default: console.log("quickMenuGo: unknown action: " + opid); } } -function toggleDispRead() { - - const hide = !(getInitParam("hide_read_feeds") == "1"); - - xhrPost("backend.php", {op: "rpc", method: "setpref", key: "HIDE_READ_FEEDS", value: hide}, () => { - hideOrShowFeeds(hide); - setInitParam("hide_read_feeds", hide); - }); -} - -function parse_runtime_info(data) { - - //console.log("parsing runtime info..."); - - for (const k in data) { - const v = data[k]; - -// console.log("RI: " + k + " => " + v); - - if (k == "dep_ts" && parseInt(getInitParam("dep_ts")) > 0) { - if (parseInt(getInitParam("dep_ts")) < parseInt(v) && getInitParam("reload_on_ts_change")) { - window.location.reload(); - } - } - - if (k == "daemon_is_running" && v != 1) { - notify_error("Update daemon is not running.", true); - return; - } - - if (k == "update_result") { - const updatesIcon = dijit.byId("updatesIcon").domNode; - - if (v) { - Element.show(updatesIcon); - } else { - Element.hide(updatesIcon); - } - } - - if (k == "daemon_stamp_ok" && v != 1) { - notify_error("Update daemon is not updating feeds.", true); - return; - } - - if (k == "max_feed_id" || k == "num_feeds") { - if (init_params[k] != v) { - console.log("feed count changed, need to reload feedlist."); - updateFeedList(); - } - } - - init_params[k] = v; - notify(''); - } - - PluginHost.run(PluginHost.HOOK_RUNTIME_INFO_LOADED, data); -} - -function collapse_feedlist() { - Element.toggle("feeds-holder"); - - const splitter = $("feeds-holder_splitter"); - - Element.visible("feeds-holder") ? splitter.show() : splitter.hide(); - - dijit.byId("main").resize(); -} - function viewModeChanged() { cache_clear(); - return viewCurrentFeed(''); -} - -function hotkey_handler(e) { - if (e.target.nodeName == "INPUT" || e.target.nodeName == "TEXTAREA") return; - - const action_name = keyeventToAction(e); - - if (action_name) { - const action_func = hotkey_actions[action_name]; - - if (action_func != null) { - action_func(); - e.stopPropagation(); - return false; - } - } + return Feeds.viewCurrentFeed(''); } function inPreferences() { @@ -769,136 +735,15 @@ function reverseHeadlineOrder() { order_by.attr('value', value); - viewCurrentFeed(); - -} - -function handle_rpc_json(transport, scheduled_call) { - - const netalert_dijit = dijit.byId("net-alert"); - let netalert = false; - - if (netalert_dijit) netalert = netalert_dijit.domNode; - - try { - const reply = JSON.parse(transport.responseText); - - if (reply) { - - const error = reply['error']; - - if (error) { - const code = error['code']; - const msg = error['msg']; - - console.warn("[handle_rpc_json] received fatal error " + code + "/" + msg); - - if (code != 0) { - fatalError(code, msg); - return false; - } - } - - const seq = reply['seq']; - - if (seq && get_seq() != seq) { - console.log("[handle_rpc_json] sequence mismatch: " + seq + - " (want: " + get_seq() + ")"); - return true; - } - - const message = reply['message']; - - if (message == "UPDATE_COUNTERS") { - console.log("need to refresh counters..."); - setInitParam("last_article_id", -1); - request_counters(true); - } - - const counters = reply['counters']; - - if (counters) - parse_counters(counters, scheduled_call); - - const runtime_info = reply['runtime-info']; - - if (runtime_info) - parse_runtime_info(runtime_info); - - if (netalert) netalert.hide(); - - return reply; - - } else { - if (netalert) - netalert.show(); - else - notify_error("Communication problem with server."); - } - - } catch (e) { - if (netalert) - netalert.show(); - else - notify_error("Communication problem with server."); - - console.error(e); - } - - return false; -} - -function switchPanelMode(wide) { - if (isCombinedMode()) return; - - const article_id = getActiveArticleId(); - - 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(getCookie("ttrss_ci_width")) > 0) { - dijit.byId("content-insert").domNode.setStyle( - {width: getCookie("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(getCookie("ttrss_ci_height")) > 0) { - dijit.byId("content-insert").domNode.setStyle( - {height: getCookie("ttrss_ci_height") + "px" }); - } - - $("headlines-frame").setStyle({ borderBottomWidth: '1px' }); - $("headlines-frame").removeClassName("wide"); - - } - - closeArticlePanel(); - - if (article_id) view(article_id); + Feeds.viewCurrentFeed(); - xhrPost("backend.php", {op: "rpc", method: "setpanelmode", wide: wide ? 1 : 0}); } function update_random_feed() { console.log("in update_random_feed"); xhrPost("backend.php", { op: "rpc", method: "updateRandomFeed" }, (transport) => { - handle_rpc_json(transport, true); + App.handleRpcJson(transport, true); window.setTimeout(update_random_feed, 30*1000); }); } diff --git a/js/viewfeed.js b/js/viewfeed.js index 87d0fc55d..637aa0473 100755 --- a/js/viewfeed.js +++ b/js/viewfeed.js @@ -15,191 +15,401 @@ let last_search_query; let has_storage = 'sessionStorage' in window && window['sessionStorage'] !== null; -function headlines_callback2(transport, offset) { - const reply = handle_rpc_json(transport); +const Article = { + closeArticlePanel: function () { + if (dijit.byId("content-insert")) + dijit.byId("headlines-wrap-inner").removeChild( + dijit.byId("content-insert")); + }, + displayArticleUrl: function (id) { + const query = {op: "rpc", method: "getlinktitlebyid", id: id}; - console.log("headlines_callback2, offset=", offset); + xhrJson("backend.php", query, (reply) => { + if (reply && reply.link) { + prompt(__("Article URL:"), reply.link); + } + }); + }, + openArticleInNewWindow: function (id) { + const w = window.open(""); + w.opener = null; + w.location = "backend.php?op=article&method=redirect&id=" + id; + }, + renderArticle: function (article) { + Utils.cleanupMemory("content-insert"); + + dijit.byId("headlines-wrap-inner").addChild( + dijit.byId("content-insert")); - let is_cat = false; - let feed_id = false; + const c = dijit.byId("content-insert"); - if (reply) { + try { + c.domNode.scrollTop = 0; + } catch (e) { + } - is_cat = reply['headlines']['is_cat']; - feed_id = reply['headlines']['id']; - last_search_query = reply['headlines']['search_query']; + c.attr('content', article); + PluginHost.run(PluginHost.HOOK_ARTICLE_RENDERED, c.domNode); - if (feed_id != -7 && (feed_id != getActiveFeedId() || is_cat != activeFeedIsCat())) - return; + correctHeadlinesOffset(getActiveArticleId()); try { - if (offset == 0) { - $("headlines-frame").scrollTop = 0; - - Element.hide("floatingTitle"); - $("floatingTitle").setAttribute("data-article-id", 0); - $("floatingTitle").innerHTML = ""; - } - } catch (e) { } + c.focus(); + } catch (e) { + } + }, +} - $("headlines-frame").removeClassName("cdm"); - $("headlines-frame").removeClassName("normal"); +const Headlines = { + _headlines_scroll_timeout: 0, + 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(); + }, 50); + } + }, + loadMoreHeadlines: function() { + const view_mode = document.forms["main_toolbar_form"].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 = getFeedUnread(Feeds.getActiveFeedId(), Feeds.activeFeedIsCat()); - $("headlines-frame").addClassName(isCombinedMode() ? "cdm" : "normal"); + // TODO implement marked & published - const headlines_count = reply['headlines-info']['count']; - infscroll_disabled = parseInt(headlines_count) != 30; + let offset = num_all; - console.log('received', headlines_count, 'headlines, infscroll disabled=', infscroll_disabled); + switch (view_mode) { + case "marked": + case "published": + console.warn("loadMoreHeadlines: ", view_mode, "not implemented"); + break; + case "unread": + offset = unread_in_buffer; + break; + case "adaptive": + if (!(Feeds.getActiveFeedId() == -1 && !Feeds.activeFeedIsCat())) + offset = num_unread > 0 ? unread_in_buffer : num_all; + break; + } - vgroup_last_feed = reply['headlines-info']['vgroup_last_feed']; - current_first_id = reply['headlines']['first_id']; + console.log("loadMoreHeadlines, offset=", offset); - if (offset == 0) { - loaded_article_ids = []; + Feeds.viewfeed({feed: Feeds.getActiveFeedId(), is_cat: Feeds.activeFeedIsCat(), offset: offset}); + }, + scrollHandler: function() { + try { + Headlines.unpackVisibleArticles(); - dojo.html.set($("headlines-toolbar"), - reply['headlines']['toolbar'], - {parseContent: true}); + if (App.isCombinedMode()) { + Headlines.updateFloatingTitle(); - $("headlines-frame").innerHTML = ''; + // set topmost child in the buffer as active + if (getInitParam("cdm_expanded") && getInitParam("cdm_auto_catchup") == 1) { - let tmp = document.createElement("div"); - tmp.innerHTML = reply['headlines']['content']; - dojo.parser.parse(tmp); + const rows = $$("#headlines-frame > div[id*=RROW]"); - while (tmp.hasChildNodes()) { - const row = tmp.removeChild(tmp.firstChild); + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; - if (loaded_article_ids.indexOf(row.id) == -1 || row.hasClassName("feed-title")) { - dijit.byId("headlines-frame").domNode.appendChild(row); + if ($("headlines-frame").scrollTop <= row.offsetTop && + row.offsetTop - $("headlines-frame").scrollTop < 100 && + row.getAttribute("data-article-id") != getActiveArticleId()) { - loaded_article_ids.push(row.id); + setActiveArticleId(row.getAttribute("data-article-id")); + break; + } + } } } - let hsp = $("headlines-spacer"); - if (!hsp) hsp = new Element("DIV", {"id": "headlines-spacer"}); - dijit.byId('headlines-frame').domNode.appendChild(hsp); + if (!Feeds.infscroll_disabled) { + const hsp = $("headlines-spacer"); + const container = $("headlines-frame"); - initHeadlinesMenu(); + if (hsp && hsp.offsetTop - 250 <= container.scrollTop + container.offsetHeight) { - if (infscroll_disabled) - hsp.innerHTML = "" + - __("Click to open next unread feed.") + ""; + hsp.innerHTML = " " + + __("Loading, please wait...") + ""; - if (_search_query) { - $("feed_title").innerHTML += "" + - " (" + __("Cancel search") + ")" + - ""; + Headlines.loadMoreHeadlines(); + return; + } } - } else if (headlines_count > 0 && feed_id == getActiveFeedId() && is_cat == activeFeedIsCat()) { - const c = dijit.byId("headlines-frame"); - //const ids = getSelectedArticleIds2(); + if (getInitParam("cdm_auto_catchup") == 1) { + + let rows = $$("#headlines-frame > div[id*=RROW][class*=Unread]"); - let hsp = $("headlines-spacer"); + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + + if ($("headlines-frame").scrollTop > (row.offsetTop + row.offsetHeight / 2)) { + const id = row.getAttribute("data-article-id") + + if (catchup_id_batch.indexOf(id) == -1) + catchup_id_batch.push(id); - if (hsp) - c.domNode.removeChild(hsp); + } else { + break; + } + } - let tmp = document.createElement("div"); - tmp.innerHTML = reply['headlines']['content']; - dojo.parser.parse(tmp); + if (Feeds.infscroll_disabled) { + const row = $$("#headlines-frame div[id*=RROW]").last(); - while (tmp.hasChildNodes()) { - let row = tmp.removeChild(tmp.firstChild); + if (row && $("headlines-frame").scrollTop > + (row.offsetTop + row.offsetHeight - 50)) { - if (loaded_article_ids.indexOf(row.id) == -1 || row.hasClassName("feed-title")) { - dijit.byId("headlines-frame").domNode.appendChild(row); + console.log("we seem to be at an end"); - loaded_article_ids.push(row.id); + if (getInitParam("on_catchup_show_next_feed") == "1") { + Feeds.openNextUnreadFeed(); + } + } } } + } catch (e) { + console.warn("scrollHandler", e); + } + }, + updateFloatingTitle: function(unread_only) { + if (!App.isCombinedMode()/* || !getInitParam("cdm_expanded")*/) return; - if (!hsp) hsp = new Element("DIV", {"id": "headlines-spacer"}); - c.domNode.appendChild(hsp); + const hf = $("headlines-frame"); + const elems = $$("#headlines-frame > div[id*=RROW]"); + const ft = $("floatingTitle"); - if (headlines_count < 30) infscroll_disabled = true; + for (let i = 0; i < elems.length; i++) { + const row = elems[i]; - /* console.log("restore selected ids: " + ids); + if (row && row.offsetTop + row.offsetHeight > hf.scrollTop) { - for (let i = 0; i < ids.length; i++) { - markHeadline(ids[i]); - } */ + const header = row.select(".header")[0]; + const id = row.getAttribute("data-article-id"); - initHeadlinesMenu(); + if (unread_only || id != ft.getAttribute("data-article-id")) { + if (id != ft.getAttribute("data-article-id")) { - if (infscroll_disabled) { - hsp.innerHTML = "" + - __("Click to open next unread feed.") + ""; - } + ft.setAttribute("data-article-id", id); + ft.innerHTML = header.innerHTML; + ft.firstChild.innerHTML = "" + ft.firstChild.innerHTML; - } else { - console.log("no new headlines received"); + initFloatingMenu(); + + const cb = ft.select(".rchk")[0]; - const first_id_changed = reply['headlines']['first_id_changed']; - console.log("first id changed:" + first_id_changed); + if (cb) + cb.parentNode.removeChild(cb); + } - let hsp = $("headlines-spacer"); + if (row.hasClassName("Unread")) + ft.addClassName("Unread"); + else + ft.removeClassName("Unread"); - if (hsp) { - if (first_id_changed) { - hsp.innerHTML = "" + - __("New articles found, reload feed to continue.") + ""; - } else { - hsp.innerHTML = "" + - __("Click to open next unread feed.") + ""; + PluginHost.run(PluginHost.HOOK_FLOATING_TITLE, row); } + + ft.style.marginRight = hf.offsetWidth - row.offsetWidth + "px"; + + if (header.offsetTop + header.offsetHeight < hf.scrollTop + ft.offsetHeight - 5 && + row.offsetTop + row.offsetHeight >= hf.scrollTop + ft.offsetHeight - 5) + new Effect.Appear(ft, {duration: 0.3}); + else + Element.hide(ft); + + return; } } + }, + unpackVisibleArticles: function() { + if (!App.isCombinedMode() || !getInitParam("cdm_expanded")) return; - } else { - console.error("Invalid object received: " + transport.responseText); - dijit.byId("headlines-frame").attr('content', "
" + - __('Could not update headlines (invalid object received - see error console for details)') + - "
"); - } + const rows = $$("#headlines-frame div[id*=RROW][data-content]"); + const threshold = $("headlines-frame").scrollTop + $("headlines-frame").offsetHeight + 600; - infscroll_in_progress = 0; + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; - // this is used to auto-catchup articles if needed after infscroll request has finished, - // unpack visible articles, etc - headlinesScrollHandler(); + if (row.offsetTop <= threshold) { + console.log("unpacking: " + row.id); - // if we have some more space in the buffer, why not try to fill it - if (!infscroll_disabled && $("headlines-spacer") && - $("headlines-spacer").offsetTop < $("headlines-frame").offsetHeight) { + row.select(".content-inner")[0].innerHTML = row.getAttribute("data-content"); + row.removeAttribute("data-content"); - window.setTimeout(function() { - loadMoreHeadlines(); - }, 500); - } + PluginHost.run(PluginHost.HOOK_ARTICLE_RENDERED_CDM, row); + } else { + break; + } + } + }, + onLoaded: function(transport, offset) { + const reply = App.handleRpcJson(transport); - notify(""); -} + console.log("Headlines.onLoaded: offset=", offset); -function render_article(article) { - cleanup_memory("content-insert"); + let is_cat = false; + let feed_id = false; - dijit.byId("headlines-wrap-inner").addChild( - dijit.byId("content-insert")); + if (reply) { - const c = dijit.byId("content-insert"); + is_cat = reply['headlines']['is_cat']; + feed_id = reply['headlines']['id']; + last_search_query = reply['headlines']['search_query']; - try { - c.domNode.scrollTop = 0; - } catch (e) { } + if (feed_id != -7 && (feed_id != Feeds.getActiveFeedId() || is_cat != Feeds.activeFeedIsCat())) + return; - c.attr('content', article); - PluginHost.run(PluginHost.HOOK_ARTICLE_RENDERED, c.domNode); + try { + if (offset == 0) { + $("headlines-frame").scrollTop = 0; - correctHeadlinesOffset(getActiveArticleId()); + Element.hide("floatingTitle"); + $("floatingTitle").setAttribute("data-article-id", 0); + $("floatingTitle").innerHTML = ""; + } + } catch (e) { + } - try { - c.focus(); - } catch (e) { } -} + $("headlines-frame").removeClassName("cdm"); + $("headlines-frame").removeClassName("normal"); + + $("headlines-frame").addClassName(App.isCombinedMode() ? "cdm" : "normal"); + + const headlines_count = reply['headlines-info']['count']; + Feeds.infscroll_disabled = parseInt(headlines_count) != 30; + + console.log('received', headlines_count, 'headlines, infscroll disabled=', Feeds.infscroll_disabled); + + vgroup_last_feed = reply['headlines-info']['vgroup_last_feed']; + current_first_id = reply['headlines']['first_id']; + + if (offset == 0) { + loaded_article_ids = []; + + dojo.html.set($("headlines-toolbar"), + reply['headlines']['toolbar'], + {parseContent: true}); + + $("headlines-frame").innerHTML = ''; + + let tmp = document.createElement("div"); + tmp.innerHTML = reply['headlines']['content']; + dojo.parser.parse(tmp); + + while (tmp.hasChildNodes()) { + const row = tmp.removeChild(tmp.firstChild); + + if (loaded_article_ids.indexOf(row.id) == -1 || row.hasClassName("feed-title")) { + dijit.byId("headlines-frame").domNode.appendChild(row); + + loaded_article_ids.push(row.id); + } + } + + let hsp = $("headlines-spacer"); + if (!hsp) hsp = new Element("DIV", {"id": "headlines-spacer"}); + dijit.byId('headlines-frame').domNode.appendChild(hsp); + + initHeadlinesMenu(); + + if (Feeds.infscroll_disabled) + hsp.innerHTML = "" + + __("Click to open next unread feed.") + ""; + + if (Feeds._search_query) { + $("feed_title").innerHTML += "" + + " (" + __("Cancel search") + ")" + + ""; + } + + } else if (headlines_count > 0 && feed_id == Feeds.getActiveFeedId() && is_cat == Feeds.activeFeedIsCat()) { + const c = dijit.byId("headlines-frame"); + //const ids = getSelectedArticleIds2(); + + let hsp = $("headlines-spacer"); + + if (hsp) + c.domNode.removeChild(hsp); + + let tmp = document.createElement("div"); + tmp.innerHTML = reply['headlines']['content']; + dojo.parser.parse(tmp); + + while (tmp.hasChildNodes()) { + let row = tmp.removeChild(tmp.firstChild); + + if (loaded_article_ids.indexOf(row.id) == -1 || row.hasClassName("feed-title")) { + dijit.byId("headlines-frame").domNode.appendChild(row); + + loaded_article_ids.push(row.id); + } + } + + if (!hsp) hsp = new Element("DIV", {"id": "headlines-spacer"}); + c.domNode.appendChild(hsp); + + /* console.log("restore selected ids: " + ids); + + for (let i = 0; i < ids.length; i++) { + markHeadline(ids[i]); + } */ + + initHeadlinesMenu(); + + if (Feeds.infscroll_disabled) { + hsp.innerHTML = "" + + __("Click to open next unread feed.") + ""; + } + + } else { + console.log("no new headlines received"); + + const first_id_changed = reply['headlines']['first_id_changed']; + console.log("first id changed:" + first_id_changed); + + let hsp = $("headlines-spacer"); + + if (hsp) { + if (first_id_changed) { + hsp.innerHTML = "" + + __("New articles found, reload feed to continue.") + ""; + } else { + hsp.innerHTML = "" + + __("Click to open next unread feed.") + ""; + } + } + } + + } else { + console.error("Invalid object received: " + transport.responseText); + dijit.byId("headlines-frame").attr('content', "
" + + __('Could not update headlines (invalid object received - see error console for details)') + + "
"); + } + + Feeds.infscroll_in_progress = 0; + + // this is used to auto-catchup articles if needed after infscroll request has finished, + // unpack visible articles, etc + this.scrollHandler(); + + // if we have some more space in the buffer, why not try to fill it + if (!Feeds.infscroll_disabled && $("headlines-spacer") && + $("headlines-spacer").offsetTop < $("headlines-frame").offsetHeight) { + + window.setTimeout(function () { + this.loadMoreHeadlines(); + }, 500); + } + + notify(""); + }, +}; function view(id, noexpand) { setActiveArticleId(id); @@ -220,19 +430,19 @@ function view(id, noexpand) { if (cached_article) { console.log('rendering cached', id); - render_article(cached_article); + Article.renderArticle(cached_article); return false; } xhrPost("backend.php", {op: "article", method: "view", id: id, cids: cids.toString()}, (transport) => { try { - const reply = handle_rpc_json(transport); + const reply = App.handleRpcJson(transport); if (reply) { reply.each(function(article) { if (getActiveArticleId() == article['id']) { - render_article(article['content']); + Article.renderArticle(article['content']); } //cids_requested.remove(article['id']); @@ -242,7 +452,7 @@ function view(id, noexpand) { } else { console.error("Invalid object received: " + transport.responseText); - render_article("
" + + Article.renderArticle("
" + __('Could not display article (invalid object received - see error console for details)') + "
"); } @@ -281,7 +491,7 @@ function toggleMark(id, client_only) { if (!client_only) xhrPost("backend.php", query, (transport) => { - handle_rpc_json(transport); + App.handleRpcJson(transport); }); } } @@ -308,7 +518,7 @@ function togglePub(id, client_only) { if (!client_only) xhrPost("backend.php", query, (transport) => { - handle_rpc_json(transport); + App.handleRpcJson(transport); }); } @@ -349,7 +559,7 @@ function moveToPost(mode, noscroll, noexpand) { if (mode == "next") { if (next_id || getActiveArticleId()) { - if (isCombinedMode()) { + if (App.isCombinedMode()) { const article = $("RROW-" + getActiveArticleId()); const ctr = $("headlines-frame"); @@ -373,7 +583,7 @@ function moveToPost(mode, noscroll, noexpand) { if (mode == "prev") { if (prev_id || getActiveArticleId()) { - if (isCombinedMode()) { + if (App.isCombinedMode()) { const article = $("RROW-" + getActiveArticleId()); const prev_article = $("RROW-" + prev_id); @@ -432,7 +642,7 @@ function toggleUnread(id, cmode) { if (row.className != origClassName) xhrPost("backend.php", {op: "rpc", method: "catchupSelected", cmode: cmode, ids: id},(transport) => { - handle_rpc_json(transport); + App.handleRpcJson(transport); }); } } @@ -449,7 +659,7 @@ function selectionRemoveLabel(id, ids) { ids: ids.toString(), lid: id }; xhrPost("backend.php", query, (transport) => { - handle_rpc_json(transport); + App.handleRpcJson(transport); updateHeadlineLabels(transport); }); } @@ -466,7 +676,7 @@ function selectionAssignLabel(id, ids) { ids: ids.toString(), lid: id }; xhrPost("backend.php", query, (transport) => { - handle_rpc_json(transport); + App.handleRpcJson(transport); updateHeadlineLabels(transport); }); } @@ -509,7 +719,7 @@ function selectionToggleUnread(params) { notify_progress("Loading, please wait..."); xhrPost("backend.php", query, (transport) => { - handle_rpc_json(transport); + App.handleRpcJson(transport); if (callback) callback(transport); }); } @@ -530,7 +740,7 @@ function selectionToggleMarked(ids) { ids: rows.toString(), cmode: 2 }; xhrPost("backend.php", query, (transport) => { - handle_rpc_json(transport); + App.handleRpcJson(transport); }); } @@ -552,7 +762,7 @@ function selectionTogglePublished(ids) { ids: rows.toString(), cmode: 2 }; xhrPost("backend.php", query, (transport) => { - handle_rpc_json(transport); + App.handleRpcJson(transport); }); } } @@ -652,10 +862,10 @@ function deleteSelection() { return; } - const fn = getFeedName(getActiveFeedId(), activeFeedIsCat()); + const fn = getFeedName(Feeds.getActiveFeedId(), Feeds.activeFeedIsCat()); let str; - if (getActiveFeedId() != 0) { + if (Feeds.getActiveFeedId() != 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); @@ -671,8 +881,8 @@ function deleteSelection() { const query = { op: "rpc", method: "delete", ids: rows.toString() }; xhrPost("backend.php", query, (transport) => { - handle_rpc_json(transport); - viewCurrentFeed(); + App.handleRpcJson(transport); + Feeds.viewCurrentFeed(); }); } @@ -686,11 +896,11 @@ function archiveSelection() { return; } - const fn = getFeedName(getActiveFeedId(), activeFeedIsCat()); + const fn = getFeedName(Feeds.getActiveFeedId(), Feeds.activeFeedIsCat()); let str; let op; - if (getActiveFeedId() != 0) { + if (Feeds.getActiveFeedId() != 0) { str = ngettext("Archive %d selected article in %s?", "Archive %d selected articles in %s?", rows.length); op = "archive"; } else { @@ -714,8 +924,8 @@ function archiveSelection() { const query = {op: "rpc", method: op, ids: rows.toString()}; xhrPost("backend.php", query, (transport) => { - handle_rpc_json(transport); - viewCurrentFeed(); + App.handleRpcJson(transport); + Feeds.viewCurrentFeed(); }); } @@ -728,7 +938,7 @@ function catchupSelection() { return; } - const fn = getFeedName(getActiveFeedId(), activeFeedIsCat()); + const fn = getFeedName(Feeds.getActiveFeedId(), Feeds.activeFeedIsCat()); let str = ngettext("Mark %d selected article in %s as read?", "Mark %d selected articles in %s as read?", rows.length); @@ -838,9 +1048,9 @@ function setActiveArticleId(id) { if (row.hasClassName("Unread")) { catchupBatchedArticles(() => { - decrementFeedCounter(getActiveFeedId(), activeFeedIsCat()); + Feeds.decrementFeedCounter(Feeds.getActiveFeedId(), Feeds.activeFeedIsCat()); toggleUnread(id, 0); - updateFloatingTitle(true); + Headlines.updateFloatingTitle(true); }); } @@ -870,111 +1080,6 @@ function postMouseOut(id) { post_under_pointer = false; } -function unpackVisibleArticles() { - if (!isCombinedMode() || !getInitParam("cdm_expanded")) return; - - const rows = $$("#headlines-frame div[id*=RROW][data-content]"); - const threshold = $("headlines-frame").scrollTop + $("headlines-frame").offsetHeight + 600; - - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - - if (row.offsetTop <= threshold) { - console.log("unpacking: " + row.id); - - row.select(".content-inner")[0].innerHTML = row.getAttribute("data-content"); - row.removeAttribute("data-content"); - - PluginHost.run(PluginHost.HOOK_ARTICLE_RENDERED_CDM, row); - } else { - break; - } - } -} - -function headlinesScrollHandler(/* event */) { - try { - unpackVisibleArticles(); - - if (isCombinedMode()) { - updateFloatingTitle(); - - // set topmost child in the buffer as active - if (getInitParam("cdm_expanded") && getInitParam("cdm_auto_catchup") == 1) { - - const rows = $$("#headlines-frame > div[id*=RROW]"); - - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - - if ($("headlines-frame").scrollTop <= row.offsetTop && - row.offsetTop - $("headlines-frame").scrollTop < 100 && - row.getAttribute("data-article-id") != getActiveArticleId()) { - - setActiveArticleId(row.getAttribute("data-article-id")); - break; - } - } - } - } - - if (!infscroll_disabled) { - const hsp = $("headlines-spacer"); - const container = $("headlines-frame"); - - if (hsp && hsp.offsetTop - 250 <= container.scrollTop + container.offsetHeight) { - - hsp.innerHTML = " " + - __("Loading, please wait...") + ""; - - loadMoreHeadlines(); - return; - } - } - - if (getInitParam("cdm_auto_catchup") == 1) { - - let rows = $$("#headlines-frame > div[id*=RROW][class*=Unread]"); - - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - - if ($("headlines-frame").scrollTop > (row.offsetTop + row.offsetHeight/2)) { - const id = row.getAttribute("data-article-id") - - if (catchup_id_batch.indexOf(id) == -1) - catchup_id_batch.push(id); - - } else { - break; - } - } - - if (infscroll_disabled) { - const row = $$("#headlines-frame div[id*=RROW]").last(); - - if (row && $("headlines-frame").scrollTop > - (row.offsetTop + row.offsetHeight - 50)) { - - console.log("we seem to be at an end"); - - if (getInitParam("on_catchup_show_next_feed") == "1") { - openNextUnreadFeed(); - } - } - } - } - } catch (e) { - console.warn("headlinesScrollHandler", e); - } -} - -function openNextUnreadFeed() { - const is_cat = activeFeedIsCat(); - const nuf = getNextUnreadFeed(getActiveFeedId(), is_cat); - if (nuf) viewfeed({feed: nuf, is_cat: is_cat}); -} - function catchupBatchedArticles(callback) { console.log("catchupBatchedArticles, size=", catchup_id_batch.length); @@ -986,7 +1091,7 @@ function catchupBatchedArticles(callback) { cmode: 0, ids: batch.toString() }; xhrPost("backend.php", query, (transport) => { - const reply = handle_rpc_json(transport); + const reply = App.handleRpcJson(transport); if (reply) { const batch = reply.ids; @@ -998,7 +1103,7 @@ function catchupBatchedArticles(callback) { }); } - updateFloatingTitle(true); + Headlines.updateFloatingTitle(true); if (callback) callback(); }); @@ -1062,7 +1167,7 @@ function catchupRelativeToArticle(below, id) { cmode: 0, ids: ids_to_mark.toString() }; xhrPost("backend.php", query, (transport) => { - handle_rpc_json(transport); + App.handleRpcJson(transport); }); } } @@ -1073,7 +1178,7 @@ function getArticleUnderPointer() { } function scrollArticle(offset) { - if (!isCombinedMode()) { + if (!App.isCombinedMode()) { const ci = $("content-insert"); if (ci) { ci.scrollTop += offset; @@ -1102,7 +1207,7 @@ function updateHeadlineLabels(transport) { function cdmClicked(event, id, in_body) { if (!in_body && (event.ctrlKey || id == getActiveArticleId() || getInitParam("cdm_expanded"))) { - openArticleInNewWindow(id); + Article.openArticleInNewWindow(id); } setActiveArticleId(id); @@ -1110,115 +1215,20 @@ function cdmClicked(event, id, in_body) { if (!getInitParam("cdm_expanded")) cdmScrollToArticleId(id); - //var shift_key = event.shiftKey; - - /* if (!event.ctrlKey && !event.metaKey) { - - let elem = $("RROW-" + getActiveArticleId()); - - if (elem) elem.removeClassName("active"); - - selectArticles("none"); - toggleSelected(id); - - elem = $("RROW-" + id); - const article_is_unread = elem.hasClassName("Unread"); - - elem.removeClassName("Unread"); - elem.addClassName("active"); - - setActiveArticleId(id); - - if (article_is_unread) { - decrementFeedCounter(getActiveFeedId(), activeFeedIsCat()); - updateFloatingTitle(true); - - const query = { - op: "rpc", method: "catchupSelected", - cmode: 0, ids: id - }; - - xhrPost("backend.php", query, (transport) => { - handle_rpc_json(transport); - }); - } - - return !event.shiftKey; - - } else if (!in_body) { - - toggleSelected(id, true); - - let elem = $("RROW-" + id); - const article_is_unread = elem.hasClassName("Unread"); - - if (article_is_unread) { - decrementFeedCounter(getActiveFeedId(), activeFeedIsCat()); - } - - toggleUnread(id, 0, false); - - openArticleInNewWindow(id); - } else { - return true; - } - - const unread_in_buffer = $$("#headlines-frame > div[id*=RROW][class*=Unread]").length - request_counters(unread_in_buffer == 0); */ - return false; } function hlClicked(event, id) { if (event.ctrlKey) { - openArticleInNewWindow(id); + Article.openArticleInNewWindow(id); setActiveArticleId(id); } else { view(id); } return false; - - /* if (event.which == 2) { - view(id); - return true; - } else if (event.ctrlKey || event.metaKey) { - openArticleInNewWindow(id); - return false; - } else { - view(id); - return false; - } */ -} - -function openArticleInNewWindow(id) { - const w = window.open(""); - w.opener = null; - w.location = "backend.php?op=article&method=redirect&id=" + id; -} - -function isCombinedMode() { - return getInitParam("combined_display_mode"); } -/* function markHeadline(id, marked) { - if (marked == undefined) marked = true; - - const row = $("RROW-" + id); - if (row) { - const check = dijit.getEnclosingWidget( - row.getElementsByClassName("rchk")[0]); - - if (check) { - check.attr("checked", marked); - } - - if (marked) - row.addClassName("Selected"); - else - row.removeClassName("Selected"); - } -} */ function getRelativePostIds(id, limit) { @@ -1262,6 +1272,7 @@ function correctHeadlinesOffset(id) { } } +// noinspection JSUnusedGlobalSymbols function headlineActionsChange(elem) { eval(elem.value); elem.attr('value', 'false'); @@ -1286,12 +1297,6 @@ function cdmCollapseActive(event) { } } -function closeArticlePanel() { - if (dijit.byId("content-insert")) - dijit.byId("headlines-wrap-inner").removeChild( - dijit.byId("content-insert")); -} - function initFloatingMenu() { if (!dijit.byId("floatingMenu")) { @@ -1311,14 +1316,14 @@ function headlinesMenuCommon(menu) { menu.addChild(new dijit.MenuItem({ label: __("Open original article"), onClick: function (event) { - openArticleInNewWindow(this.getParent().currentTarget.getAttribute("data-article-id")); + Article.openArticleInNewWindow(this.getParent().currentTarget.getAttribute("data-article-id")); } })); menu.addChild(new dijit.MenuItem({ label: __("Display article URL"), onClick: function (event) { - displayArticleUrl(this.getParent().currentTarget.getAttribute("data-article-id")); + Article.displayArticleUrl(this.getParent().currentTarget.getAttribute("data-article-id")); } })); @@ -1524,11 +1529,7 @@ function cache_delete(id) { sessionStorage.removeItem(id); } -function cancelSearch() { - _search_query = ""; - viewCurrentFeed(); -} - +// noinspection JSUnusedGlobalSymbols function setSelectionScore() { const ids = getSelectedArticleIds2(); @@ -1565,6 +1566,7 @@ function setSelectionScore() { } } +// noinspection JSUnusedGlobalSymbols function changeScore(id, pic) { const score = pic.getAttribute("score"); @@ -1583,73 +1585,3 @@ function changeScore(id, pic) { } } -function displayArticleUrl(id) { - const query = { op: "rpc", method: "getlinktitlebyid", id: id }; - - xhrJson("backend.php", query, (reply) => { - if (reply && reply.link) { - prompt(__("Article URL:"), reply.link); - } - }); - -} - -// floatingTitle goto button uses this -/* function scrollToRowId(id) { - const row = $(id); - - if (row) - $("headlines-frame").scrollTop = row.offsetTop - 4; -} */ - -function updateFloatingTitle(unread_only) { - if (!isCombinedMode()/* || !getInitParam("cdm_expanded")*/) return; - - 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) { - - const header = row.select(".header")[0]; - var id = row.getAttribute("data-article-id"); - - if (unread_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.firstChild.innerHTML = "" + ft.firstChild.innerHTML; - - initFloatingMenu(); - - const cb = ft.select(".rchk")[0]; - - if (cb) - cb.parentNode.removeChild(cb); - } - - if (row.hasClassName("Unread")) - ft.addClassName("Unread"); - else - ft.removeClassName("Unread"); - - PluginHost.run(PluginHost.HOOK_FLOATING_TITLE, row); - } - - ft.style.marginRight = hf.offsetWidth - row.offsetWidth + "px"; - - if (header.offsetTop + header.offsetHeight < hf.scrollTop + ft.offsetHeight - 5 && - row.offsetTop + row.offsetHeight >= hf.scrollTop + ft.offsetHeight - 5) - new Effect.Appear(ft, {duration: 0.3}); - else - Element.hide(ft); - - return; - } - } -} -- cgit v1.2.3