]> git.wh0rd.org - tt-rss.git/blobdiff - js/feedlist.js
scroll handler: performance improvements
[tt-rss.git] / js / feedlist.js
index ffcecc7c4650e1a070bd115adf62bdef7abb0352..78a125998aba70cb4b907989866cd7e3bcf650e6 100644 (file)
-var _infscroll_disable = 0;
-var _infscroll_request_sent = 0;
-var _search_query = false;
-var _viewfeed_last = 0;
-var _viewfeed_timeout = false;
+let _infscroll_disable = 0;
+let _infscroll_request_sent = 0;
 
-var counters_last_request = 0;
+let _search_query = false;
+let _viewfeed_last = 0;
+let _viewfeed_timeout = false;
 
-function viewCategory(cat) {
-       viewfeed(cat, '', true);
-       return false;
+let counters_last_request = 0;
+let _counters_prev = [];
+
+function resetCounterCache() {
+       _counters_prev = [];
 }
 
 function loadMoreHeadlines() {
-       try {
-               console.log("loadMoreHeadlines");
-
-               var offset = 0;
-
-               var view_mode = document.forms["main_toolbar_form"].view_mode.value;
-               var unread_in_buffer = $$("#headlines-frame > div[id*=RROW][class*=Unread]").length;
-               var num_all = $$("#headlines-frame > div[id*=RROW]").length;
-               var num_unread = getFeedUnread(getActiveFeedId(), activeFeedIsCat());
-
-               // TODO implement marked & published
-
-               if (view_mode == "marked") {
-                       console.warn("loadMoreHeadlines: marked is not implemented, falling back.");
-                       offset = num_all;
-               } else if (view_mode == "published") {
-                       console.warn("loadMoreHeadlines: published is not implemented, falling back.");
-                       offset = num_all;
-               } else if (view_mode == "unread") {
-                       offset = unread_in_buffer;
-               } else if (_search_query) {
-                       offset = num_all;
-               } else if (view_mode == "adaptive") {
-                       offset = num_unread > 0 ? unread_in_buffer : num_all;
-               } else {
-                       offset = num_all;
-               }
+       console.log("loadMoreHeadlines");
+
+       let offset = 0;
+
+       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
+
+       if (view_mode == "marked") {
+               console.warn("loadMoreHeadlines: marked is not implemented, falling back.");
+               offset = num_all;
+       } else if (view_mode == "published") {
+               console.warn("loadMoreHeadlines: published is not implemented, falling back.");
+               offset = num_all;
+       } else if (view_mode == "unread") {
+               offset = unread_in_buffer;
+       } else if (_search_query) {
+               offset = num_all;
+       } else if (view_mode == "adaptive" && !(getActiveFeedId() == -1 && !activeFeedIsCat())) {
+               // ^ starred feed shows both unread & read articles in adaptive mode
+               offset = num_unread > 0 ? unread_in_buffer : num_all;
+       } else {
+               offset = num_all;
+       }
 
-               console.log("offset: " + offset);
+       console.log("offset: " + offset);
 
-               viewfeed(getActiveFeedId(), '', activeFeedIsCat(), offset, false, true);
+       viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat(), offset: offset, infscroll_req: true});
 
-       } catch (e) {
-               exception_error("viewNextFeedPage", e);
-       }
 }
 
+function cleanup_memory(root) {
+       const dijits = dojo.query("[widgetid]", dijit.byId(root).domNode).map(dijit.byNode);
 
-function viewfeed(feed, method, is_cat, offset, background, infscroll_req, can_wait) {
-       try {
-               if (is_cat == undefined)
-                       is_cat = false;
-               else
-                       is_cat = !!is_cat;
+       dijits.each(function (d) {
+               dojo.destroy(d.domNode);
+       });
 
-               if (method == undefined) method = '';
-               if (offset == undefined) offset = 0;
-               if (background == undefined) background = false;
-               if (infscroll_req == undefined) infscroll_req = false;
+       $$("#" + root + " *").each(function (i) {
+               i.parentNode ? i.parentNode.removeChild(i) : true;
+       });
+}
 
-               last_requested_article = 0;
+function viewfeed(params) {
+       const feed = params.feed;
+       let is_cat = !!params.is_cat || false;
+       let offset = params.offset || 0;
+       let background = params.background || false;
+       let infscroll_req = params.infscroll_req || false;
+       const can_wait = params.can_wait;
+       const viewfeed_debug = params.viewfeed_debug;
+       const method = params.method;
 
-               if (feed != getActiveFeedId() || activeFeedIsCat() != is_cat) {
-                       if (!background && _search_query) _search_query = false;
-               }
+       if (infscroll_req == undefined) infscroll_req = false;
 
-               if (!background) {
-                       _viewfeed_last = get_timestamp();
+       last_requested_article = 0;
 
-                       if (getActiveFeedId() != feed || offset == 0) {
-                               setActiveArticleId(0);
-                               _infscroll_disable = 0;
-                       }
+       if (feed != getActiveFeedId() || activeFeedIsCat() != is_cat) {
+               if (!background && _search_query) _search_query = false;
+       }
 
-                       if (offset != 0 && !method) {
-                               var timestamp = get_timestamp();
+       if (!background) {
+               _viewfeed_last = get_timestamp();
 
-                               if (_infscroll_request_sent && _infscroll_request_sent + 30 > timestamp) {
-                                       //console.log("infscroll request in progress, aborting");
-                                       return;
-                               }
+               if (getActiveFeedId() != feed || !infscroll_req) {
+                       setActiveArticleId(0);
+                       _infscroll_disable = 0;
+
+                       cleanup_memory("headlines-frame");
+                       _headlines_scroll_offset = 0;
+               }
+
+               if (infscroll_req) {
+                       const timestamp = get_timestamp();
 
-                               _infscroll_request_sent = timestamp;
+                       if (_infscroll_request_sent && _infscroll_request_sent + 30 > timestamp) {
+                               //console.log("infscroll request in progress, aborting");
+                               return;
                        }
+
+                       _infscroll_request_sent = timestamp;
                }
+       }
 
-               Form.enable("main_toolbar_form");
+       Form.enable("main_toolbar_form");
 
-               var toolbar_query = Form.serialize("main_toolbar_form");
+       let query = Object.assign({op: "feeds", method: "view", feed: feed},
+        dojo.formToObject("main_toolbar_form"));
 
-               var query = "?op=feeds&method=view&feed=" + param_escape(feed) + "&" +
-                       toolbar_query;
+       if (method) query.m = method;
 
-               if (method) {
-                       query = query + "&m=" + param_escape(method);
+       if (offset > 0) {
+               if (current_first_id) {
+                       query.fid = current_first_id;
                }
+       }
 
-               if (current_top_article_id && offset > 0) {
-                       query = query + "&topid=" + param_escape(current_top_article_id);
+       if (!background) {
+               if (_search_query) {
+                       query = Object.assign(query, _search_query);
                }
 
-               if (!background) {
-                       if (_search_query) {
-                               force_nocache = true;
-                               query = query + "&" + _search_query;
-                               //_search_query = false;
-                       }
-
-                       if (offset != 0) {
-                               query = query + "&skip=" + offset;
+               if (offset != 0) {
+                       query.skip = offset;
 
-                               // to prevent duplicate feed titles when showing grouped vfeeds
-                               if (vgroup_last_feed) {
-                                       query = query + "&vgrlf=" + param_escape(vgroup_last_feed);
-                               }
-                       } else {
-                               if (!method && !is_cat && feed == getActiveFeedId()) {
-                                       query = query + "&m=ForceUpdate";
-                               }
+                       // 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";
                        }
 
-                       Form.enable("main_toolbar_form");
+               Form.enable("main_toolbar_form");
 
-                       if (!setFeedExpandoIcon(feed, is_cat,
-                               (is_cat) ? 'images/indicator_tiny.gif' : 'images/indicator_white.gif'))
-                                       notify_progress("Loading, please wait...", true);
-               }
+               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;
+       query.cat = is_cat;
 
-               console.log(query);
+       if (can_wait && _viewfeed_timeout) {
+               setFeedExpandoIcon(getActiveFeedId(), activeFeedIsCat(), 'images/blank_icon.gif');
+               clearTimeout(_viewfeed_timeout);
+       }
 
-               if (can_wait && _viewfeed_timeout) {
-                       setFeedExpandoIcon(getActiveFeedId(), activeFeedIsCat(), 'images/blank_icon.gif');
-                       clearTimeout(_viewfeed_timeout);
-               }
+       setActiveFeedId(feed, is_cat);
 
-               setActiveFeedId(feed, is_cat);
+       if (viewfeed_debug) {
+               window.open("backend.php?" +
+                       dojo.objectToQuery(
+                               Object.assign({debug: 1, csrf_token: getInitParam("csrf_token")}, query)
+                       ));
+       }
 
-               timeout_ms = can_wait ? 250 : 0;
-               _viewfeed_timeout = setTimeout(function() {
+       const timeout_ms = can_wait ? 250 : 0;
+       _viewfeed_timeout = setTimeout(() => {
 
-                       new Ajax.Request("backend.php", {
-                               parameters: query,
-                               onComplete: function(transport) {
-                                       setFeedExpandoIcon(feed, is_cat, 'images/blank_icon.gif');
-                                       headlines_callback2(transport, offset, background, infscroll_req);
-                                       PluginHost.run(PluginHost.HOOK_FEED_LOADED, [feed, is_cat]);
-                               } });
-               }, timeout_ms); // Wait 250ms
+               xhrPost("backend.php", query, (transport) => {
+            try {
+                setFeedExpandoIcon(feed, is_cat, 'images/blank_icon.gif');
+                headlines_callback2(transport, offset, background, infscroll_req);
+                PluginHost.run(PluginHost.HOOK_FEED_LOADED, [feed, is_cat]);
+            } catch (e) {
+                exception_error(e);
+            }
+               });
+
+       }, timeout_ms); // Wait 250ms
 
-       } catch (e) {
-               exception_error("viewfeed", e);
-       }
 }
 
 function feedlist_init() {
-       try {
-               console.log("in feedlist init");
-
-               loading_set_progress(50);
+       console.log("in feedlist init");
 
-               document.onkeydown = hotkey_handler;
-               setTimeout("hotkey_prefix_timeout()", 5*1000);
+       loading_set_progress(50);
 
-               if (!getActiveFeedId()) {
-                       viewfeed(-3);
-               } else {
-                       viewfeed(getActiveFeedId(), '', activeFeedIsCat());
-               }
+       document.onkeydown = hotkey_handler;
+       setInterval(hotkey_prefix_timeout, 5*1000);
+       setInterval(catchupBatchedArticles, 3*1000);
 
-               hideOrShowFeeds(getInitParam("hide_read_feeds") == 1);
+       if (!getActiveFeedId()) {
+               viewfeed({feed: -3});
+       } else {
+               viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat()});
+       }
 
-               request_counters(true);
-               timeout();
+       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;
+                       }
+               });
 
-       } catch (e) {
-               exception_error("feedlist/init", e);
+               dialog.show();
        }
+
+       // bw_limit disables timeout() so we request initial counters separately
+    if (getInitParam("bw_limit") == "1") {
+               request_counters(true);
+    } else {
+       setTimeout(timeout, 250);
+    }
 }
 
 
 function request_counters(force) {
-       try {
-               var date = new Date();
-               var timestamp = Math.round(date.getTime() / 1000);
+       const date = new Date();
+       const timestamp = Math.round(date.getTime() / 1000);
 
-               if (force || timestamp - counters_last_request > 5) {
-                       console.log("scheduling request of counters...");
+       if (force || timestamp - counters_last_request > 5) {
+               console.log("scheduling request of counters...");
 
-                       counters_last_request = timestamp;
+               counters_last_request = timestamp;
 
-                       var query = "?op=rpc&method=getAllCounters&seq=" + next_seq();
+               let query = {op: "rpc", method: "getAllCounters", seq: next_seq()};
 
-                       if (!force)
-                               query = query + "&last_article_id=" + getInitParam("last_article_id");
+               if (!force)
+                       query.last_article_id = getInitParam("last_article_id");
 
-                       console.log(query);
+               xhrPost("backend.php", query, (transport) => {
+            handle_rpc_json(transport);
+               });
 
-                       new Ajax.Request("backend.php", {
-                               parameters: query,
-                               onComplete: function(transport) {
-                                       try {
-                                               handle_rpc_json(transport);
-                                       } catch (e) {
-                                               exception_error("request_counters", e);
-                                       }
-                               } });
+       } else {
+               console.log("request_counters: rate limit reached: " + (timestamp - counters_last_request));
+       }
+}
 
-               } else {
-                       console.log("request_counters: rate limit reached: " + (timestamp - counters_last_request));
-               }
+// 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;
+       }
 
-       } catch (e) {
-               exception_error("request_counters", e);
+       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 (a[propName] !== b[propName]) {
+                       return false;
+               }
        }
+
+       // If we made it this far, objects
+       // are considered equivalent
+       return true;
 }
 
-function parse_counters(elems, scheduled_call) {
-       try {
-               for (var l = 0; l < elems.length; l++) {
-
-                       var id = elems[l].id;
-                       var kind = elems[l].kind;
-                       var ctr = parseInt(elems[l].counter);
-                       var error = elems[l].error;
-                       var has_img = elems[l].has_img;
-                       var updated = elems[l].updated;
-                       var auxctr = parseInt(elems[l].auxcounter);
-
-                       if (id == "global-unread") {
-                               global_unread = ctr;
-                               updateTitle();
-                               continue;
-                       }
 
-                       if (id == "subscribed-feeds") {
-                               feeds_found = ctr;
-                               continue;
-                       }
+function parse_counters(elems) {
+       for (let l = 0; l < elems.length; l++) {
 
-                       if (getFeedUnread(id, (kind == "cat")) != ctr ||
-                                       (kind == "cat")) {
-                       }
+               if (_counters_prev[l] && counter_is_equal(elems[l], _counters_prev[l])) {
+                       continue;
+               }
 
-                       setFeedUnread(id, (kind == "cat"), ctr);
-                       setFeedValue(id, (kind == "cat"), 'auxcounter', auxctr);
+               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 (kind != "cat") {
-                               setFeedValue(id, false, 'error', error);
-                               setFeedValue(id, false, 'updated', updated);
+               if (id == "global-unread") {
+                       global_unread = ctr;
+                       updateTitle();
+                       continue;
+               }
 
-                               if (id > 0) {
-                                       if (has_img) {
-                                               setFeedIcon(id, false,
-                                                       getInitParam("icons_url") + "/" + id + ".ico");
-                                       } else {
-                                               setFeedIcon(id, false, 'images/blank_icon.gif');
-                                       }
+               if (id == "subscribed-feeds") {
+                       /* feeds_found = ctr; */
+                       continue;
+               }
+
+               /*if (getFeedUnread(id, (kind == "cat")) != ctr ||
+                               (kind == "cat")) {
+               }*/
+
+               setFeedUnread(id, (kind == "cat"), ctr);
+               setFeedValue(id, (kind == "cat"), 'auxcounter', auxctr);
+
+               if (kind != "cat") {
+                       setFeedValue(id, false, 'error', error);
+                       setFeedValue(id, false, 'updated', updated);
+
+                       if (id > 0) {
+                               if (has_img) {
+                                       setFeedIcon(id, false,
+                                               getInitParam("icons_url") + "/" + id + ".ico?" + has_img);
+                               } else {
+                                       setFeedIcon(id, false, 'images/blank_icon.gif');
                                }
                        }
                }
+       }
 
-               hideOrShowFeeds(getInitParam("hide_read_feeds") == 1);
+       hideOrShowFeeds(getInitParam("hide_read_feeds") == 1);
 
-       } catch (e) {
-               exception_error("parse_counters", e);
-       }
+       _counters_prev = elems;
 }
 
 function getFeedUnread(feed, is_cat) {
        try {
-               var tree = dijit.byId("feedTree");
+               const tree = dijit.byId("feedTree");
 
                if (tree && tree.model)
                        return tree.model.getFeedUnread(feed, is_cat);
@@ -288,7 +339,7 @@ function getFeedUnread(feed, is_cat) {
 
 function getFeedCategory(feed) {
        try {
-               var tree = dijit.byId("feedTree");
+               const tree = dijit.byId("feedTree");
 
                if (tree && tree.model)
                        return tree.getFeedCategory(feed);
@@ -301,14 +352,17 @@ function getFeedCategory(feed) {
 }
 
 function hideOrShowFeeds(hide) {
-       var tree = dijit.byId("feedTree");
+       const tree = dijit.byId("feedTree");
 
        if (tree)
                return tree.hideRead(hide, getInitParam("hide_read_shows_special"));
 }
 
 function getFeedName(feed, is_cat) {
-       var tree = dijit.byId("feedTree");
+
+       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');
@@ -316,7 +370,7 @@ function getFeedName(feed, is_cat) {
 
 function getFeedValue(feed, is_cat, key) {
        try {
-               var tree = dijit.byId("feedTree");
+               const tree = dijit.byId("feedTree");
 
                if (tree && tree.model)
                        return tree.model.getFeedValue(feed, is_cat, key);
@@ -328,20 +382,15 @@ function getFeedValue(feed, is_cat, key) {
 }
 
 function setFeedUnread(feed, is_cat, unread) {
-       try {
-               var tree = dijit.byId("feedTree");
-
-               if (tree && tree.model)
-                       return tree.model.setFeedUnread(feed, is_cat, unread);
+       const tree = dijit.byId("feedTree");
 
-       } catch (e) {
-               exception_error("setFeedUnread", e);
-       }
+       if (tree && tree.model)
+               return tree.model.setFeedUnread(feed, is_cat, unread);
 }
 
 function setFeedValue(feed, is_cat, key, value) {
        try {
-               var tree = dijit.byId("feedTree");
+               const tree = dijit.byId("feedTree");
 
                if (tree && tree.model)
                        return tree.model.setFeedValue(feed, is_cat, key, value);
@@ -352,50 +401,31 @@ function setFeedValue(feed, is_cat, key, value) {
 }
 
 function selectFeed(feed, is_cat) {
-       try {
-               var tree = dijit.byId("feedTree");
-
-               if (tree) return tree.selectFeed(feed, is_cat);
+       const tree = dijit.byId("feedTree");
 
-       } catch (e) {
-               exception_error("selectFeed", e);
-       }
+       if (tree) return tree.selectFeed(feed, is_cat);
 }
 
 function setFeedIcon(feed, is_cat, src) {
-       try {
-               var tree = dijit.byId("feedTree");
-
-               if (tree) return tree.setFeedIcon(feed, is_cat, src);
+       const tree = dijit.byId("feedTree");
 
-       } catch (e) {
-               exception_error("setFeedIcon", e);
-       }
+       if (tree) return tree.setFeedIcon(feed, is_cat, src);
 }
 
 function setFeedExpandoIcon(feed, is_cat, src) {
-       try {
-               var tree = dijit.byId("feedTree");
+       const tree = dijit.byId("feedTree");
 
-               if (tree) return tree.setFeedExpandoIcon(feed, is_cat, src);
+       if (tree) return tree.setFeedExpandoIcon(feed, is_cat, src);
 
-       } catch (e) {
-               exception_error("setFeedIcon", e);
-       }
        return false;
 }
 
 function getNextUnreadFeed(feed, is_cat) {
-       try {
-               var tree = dijit.byId("feedTree");
-               var nuf = tree.model.getNextUnreadFeed(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');
-
-       } catch (e) {
-               exception_error("getNextUnreadFeed", e);
-       }
+       if (nuf)
+               return tree.model.store.getValue(nuf, 'bare_id');
 }
 
 function catchupCurrentFeed(mode) {
@@ -403,108 +433,124 @@ function catchupCurrentFeed(mode) {
 }
 
 function catchupFeedInGroup(id) {
-       try {
+       const title = getFeedName(id);
+
+       const str = __("Mark all articles in %s as read?").replace("%s", title);
+
+       if (getInitParam("confirm_feed_catchup") != 1 || confirm(str)) {
 
-               var title = getFeedName(id);
+               const rows = $$("#headlines-frame > div[id*=RROW][data-orig-feed-id='"+id+"']");
 
-               var str = __("Mark all articles in %s as read?").replace("%s", title);
+               if (rows.length > 0) {
 
-               if (getInitParam("confirm_feed_catchup") != 1 || confirm(str)) {
-                       return viewCurrentFeed('MarkAllReadGR:' + id);
+                       rows.each(function (row) {
+                               row.removeClassName("Unread");
+
+                               if (row.getAttribute("data-article-id") != getActiveArticleId()) {
+                                       new Effect.Fade(row, {duration: 0.5});
+                               }
+
+                       });
+
+                       const feedTitles = $$("#headlines-frame > div[class='cdmFeedTitle']");
+
+                       for (let i = 0; i < feedTitles.length; i++) {
+                               if (feedTitles[i].getAttribute("data-feed-id") == id) {
+
+                                       if (i < feedTitles.length - 1) {
+                                               new Effect.Fade(feedTitles[i], {duration: 0.5});
+                                       }
+
+                                       break;
+                               }
+                       }
+
+                       updateFloatingTitle(true);
                }
 
-       } catch (e) {
-               exception_error("catchupFeedInGroup", e);
+               notify_progress("Loading, please wait...", true);
+
+               xhrPost("backend.php", { op: "rpc", method: "catchupFeed", feed_id: id, is_cat: false}, (transport) => {
+            handle_rpc_json(transport);
+               });
        }
 }
 
 function catchupFeed(feed, is_cat, mode) {
-       try {
-               if (is_cat == undefined) is_cat = false;
-
-               var str = false;
-
-               switch (mode) {
-               case "1day":
-                       str = __("Mark all articles in %s older than 1 day as read?");
-                       break;
-               case "1week":
-                       str = __("Mark all articles in %s older than 1 week as read?");
-                       break;
-               case "2week":
-                       str = __("Mark all articles in %s older than 2 weeks as read?");
-                       break;
-               default:
-                       str = __("Mark all articles in %s as read?");
-               }
-
-               var fn = getFeedName(feed, is_cat);
-
-               str = str.replace("%s", fn);
+       if (is_cat == undefined) 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?");
+       }
 
-               if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
-                       return;
-               }
+       const mark_what = last_search_query && last_search_query[0] ? __("search results") : __("all articles");
+       const fn = getFeedName(feed, is_cat);
 
-               var catchup_query = "?op=rpc&method=catchupFeed&feed_id=" +
-                       feed + "&is_cat=" + is_cat + "&mode=" + mode;
+       str = str.replace("%s", fn)
+               .replace("%w", mark_what);
 
-               console.log(catchup_query);
+       if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
+               return;
+       }
 
-               notify_progress("Loading, please wait...", true);
+       const catchup_query = {op: 'rpc', method: 'catchupFeed', feed_id: feed,
+               is_cat: is_cat, mode: mode, search_query: last_search_query[0],
+               search_lang: last_search_query[1]};
 
-               new Ajax.Request("backend.php", {
-                       parameters: catchup_query,
-                       onComplete: function(transport) {
-                                       handle_rpc_json(transport);
+       notify_progress("Loading, please wait...", true);
 
-                                       var show_next_feed = getInitParam("on_catchup_show_next_feed") == "1";
+       xhrPost("backend.php", catchup_query, (transport) => {
+        handle_rpc_json(transport);
 
-                                       if (show_next_feed) {
-                                               var nuf = getNextUnreadFeed(feed, is_cat);
+        const show_next_feed = getInitParam("on_catchup_show_next_feed") == "1";
 
-                                               if (nuf) {
-                                                       viewfeed(nuf, '', is_cat);
-                                               }
-                                       } else {
-                                               if (feed == getActiveFeedId() && is_cat == activeFeedIsCat()) {
-                                                       viewCurrentFeed();
-                                               }
-                                       }
+        if (show_next_feed) {
+            const nuf = getNextUnreadFeed(feed, is_cat);
 
-                                       notify("");
-                               } });
+            if (nuf) {
+                viewfeed({feed: nuf, is_cat: is_cat});
+            }
+        } else if (feed == getActiveFeedId() && is_cat == activeFeedIsCat()) {
+            viewCurrentFeed();
+        }
 
-       } catch (e) {
-               exception_error("catchupFeed", e);
-       }
+        notify("");
+       });
 }
 
 function decrementFeedCounter(feed, is_cat) {
-       try {
-               var ctr = getFeedUnread(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 (ctr > 0) {
+               setFeedUnread(feed, is_cat, ctr - 1);
+               global_unread = global_unread - 1;
+               updateTitle();
 
-                       if (!is_cat) {
-                               var cat = parseInt(getFeedCategory(feed));
+               if (!is_cat) {
+                       const cat = parseInt(getFeedCategory(feed));
 
-                               if (!isNaN(cat)) {
-                                       ctr = getFeedUnread(cat, true);
+                       if (!isNaN(cat)) {
+                               ctr = getFeedUnread(cat, true);
 
-                                       if (ctr > 0) {
-                                               setFeedUnread(cat, true, ctr - 1);
-                                       }
+                               if (ctr > 0) {
+                                       setFeedUnread(cat, true, ctr - 1);
                                }
                        }
                }
-
-       } catch (e) {
-               exception_error("decrement_feed_counter", e);
        }
+
 }