]> git.wh0rd.org - tt-rss.git/blobdiff - viewfeed.js
reduce the number of always included libraries
[tt-rss.git] / viewfeed.js
index 9d5af3adf23bfec08e7a7fc9f504a529da9a51af..9cb902315a714bb5ca4cd8972551ce09993547d3 100644 (file)
@@ -1,6 +1,4 @@
 var active_post_id = false;
-var last_article_view = false;
-var active_real_feed_id = false;
 
 var article_cache = new Array();
 
@@ -9,23 +7,27 @@ var post_under_pointer = false;
 
 var last_requested_article = false;
 
-var preload_id_batch = [];
-var preload_timeout_id = false;
+var catchup_id_batch = [];
+var catchup_timeout_id = false;
+var feed_precache_timeout_id = false;
+var precache_idle_timeout_id = false;
 
-var cache_added = [];
+var cids_requested = [];
 
-function headlines_callback2(transport, feed_cur_page) {
+var has_storage = 'sessionStorage' in window && window['sessionStorage'] !== null;
+
+function headlines_callback2(transport, offset, background, infscroll_req) {
        try {
                handle_rpc_json(transport);
 
                loading_set_progress(25);
 
-               console.log("headlines_callback2 [page=" + feed_cur_page + "]");
+               console.log("headlines_callback2 [offset=" + offset + "] B:" + background + " I:" + infscroll_req);
 
                var is_cat = false;
                var feed_id = false;
 
-               var reply;
+               var reply = false;
 
                try {
                        reply = JSON.parse(transport.responseText);
@@ -38,20 +40,26 @@ function headlines_callback2(transport, feed_cur_page) {
                        is_cat = reply['headlines']['is_cat'];
                        feed_id = reply['headlines']['id'];
 
-                       setActiveFeedId(feed_id, is_cat);
+                       if (background) {
+                               var content = reply['headlines']['content'];
 
-                       var update_btn = document.forms["main_toolbar_form"].update;
+                               if (getInitParam("cdm_auto_catchup") == 1) {
+                                       content = content + "<div id='headlines-spacer'></div>";
+                               }
 
-                       update_btn.disabled = !(feed_id >= 0 && !is_cat);
+                               cache_headlines(feed_id, is_cat, reply['headlines']['toolbar'], content);
+                               return;
+                       }
+
+                       setActiveFeedId(feed_id, is_cat);
 
                        try {
-                               if (feed_cur_page == 0) {
+                               if (offset == 0 && infscroll_req == false) {
                                        $("headlines-frame").scrollTop = 0;
                                }
                        } catch (e) { };
 
                        var headlines_count = reply['headlines-info']['count'];
-                       var headlines_unread = reply['headlines-info']['unread'];
 
                        vgroup_last_feed = reply['headlines-info']['vgroup_last_feed'];
 
@@ -63,30 +71,27 @@ function headlines_callback2(transport, feed_cur_page) {
 
                        var counters = reply['counters'];
                        var articles = reply['articles'];
-                       var runtime_info = reply['runtime-info'];
+                       //var runtime_info = reply['runtime-info'];
 
-                       if (feed_cur_page == 0) {
+                       if (offset == 0 && infscroll_req == false) {
                                dijit.byId("headlines-frame").attr('content',
                                        reply['headlines']['content']);
 
                                dijit.byId("headlines-toolbar").attr('content',
                                        reply['headlines']['toolbar']);
 
-                               var hsp = $("headlines-spacer");
-                               if (!hsp) hsp = document.createElement("DIV");
 
-                               hsp.id = "headlines-spacer";
-
-                               if (!_infscroll_disable)
-                                       hsp.innerHTML = "<img src='images/indicator_tiny.gif'> " +
-                                               __("Loading, please wait...");
-
-                               dijit.byId('headlines-frame').domNode.appendChild(hsp);
+                               if (getInitParam("cdm_auto_catchup") == 1) {
+                                       var hsp = $("headlines-spacer");
+                                       if (!hsp) hsp = new Element("DIV", {"id": "headlines-spacer"});
+                                       dijit.byId('headlines-frame').domNode.appendChild(hsp);
+                               }
 
                                initHeadlinesMenu();
 
                        } else {
-                               if (headlines_count > 0) {
+
+                               if (headlines_count > 0 && feed_id == getActiveFeedId() && is_cat == activeFeedIsCat()) {
                                        console.log("adding some more headlines...");
 
                                        var c = dijit.byId("headlines-frame");
@@ -100,18 +105,21 @@ function headlines_callback2(transport, feed_cur_page) {
                                                c.domNode.removeChild(hsp);
 
                                        $$("#headlines-tmp > div").each(function(row) {
-                                               c.domNode.appendChild(row);
+                                               if ($$("#headlines-frame DIV[id="+row.id+"]").length == 0) {
+                                                       row.style.display = 'none';
+                                                       c.domNode.appendChild(row);
+                                               } else {
+                                                       row.parentNode.removeChild(row);
+                                               }
                                        });
 
-                                       if (!hsp) hsp = document.createElement("DIV");
+                                       if (!hsp) hsp = new Element("DIV", {"id": "headlines-spacer"});
 
-                                       hsp.id = "headlines-spacer";
+                                       fixHeadlinesOrder(getLoadedArticleIds());
 
-                                       if (!_infscroll_disable)
-                                               hsp.innerHTML = "<img src='images/indicator_tiny.gif'> " +
-                                                       __("Loading, please wait...");
-
-                                       c.domNode.appendChild(hsp);
+                                       if (getInitParam("cdm_auto_catchup") == 1) {
+                                               c.domNode.appendChild(hsp);
+                                       }
 
                                        console.log("restore selected ids: " + ids);
 
@@ -121,6 +129,12 @@ function headlines_callback2(transport, feed_cur_page) {
 
                                        initHeadlinesMenu();
 
+                                       $$("#headlines-frame > div[id*=RROW]").each(
+                                       function(child) {
+                                               if (!Element.visible(child))
+                                                       new Effect.Appear(child, { duration : 0.5 });
+                                       });
+
                                } else {
                                        console.log("no new headlines received");
 
@@ -130,27 +144,34 @@ function headlines_callback2(transport, feed_cur_page) {
                                }
                        }
 
+                       if (headlines_count > 0)
+                               cache_headlines(feed_id, is_cat, reply['headlines']['toolbar'], $("headlines-frame").innerHTML);
+
                        if (articles) {
                                for (var i = 0; i < articles.length; i++) {
                                        var a_id = articles[i]['id'];
-                                       cache_inject(a_id, articles[i]['content']);
+                                       cache_set("article:" + a_id, articles[i]['content']);
                                }
                        } else {
                                console.log("no cached articles received");
                        }
 
+                       // do not precache stuff after fresh feed
+                       if (feed_id != -3)
+                               precache_headlines();
+
                        if (counters)
                                parse_counters(counters);
                        else
                                request_counters();
 
                } else {
-                       console.warn("headlines_callback: returned no XML object");
+                       console.error("Invalid object received: " + transport.responseText);
                        dijit.byId("headlines-frame").attr('content', "<div class='whiteBox'>" +
-                                       __('Could not update headlines (invalid object received)') + "</div>");
+                                       __('Could not update headlines (invalid object received - see error console for details)') +
+                                       "</div>");
                }
 
-               _feed_cur_page = feed_cur_page;
                _infscroll_request_sent = 0;
 
                notify("");
@@ -202,14 +223,6 @@ function showArticleInHeadlines(id) {
 
                var upd_img_pic = $("FUPDPIC-" + id);
 
-               var cache_prefix = "";
-
-               if (activeFeedIsCat()) {
-                       cache_prefix = "C:";
-               } else {
-                       cache_prefix = "F:";
-               }
-
                var view_mode = false;
 
                try {
@@ -224,22 +237,10 @@ function showArticleInHeadlines(id) {
 
                        upd_img_pic.src = "images/blank_icon.gif";
 
-                       cache_invalidate(cache_prefix + getActiveFeedId());
-
-                       /* cache_inject(cache_prefix + getActiveFeedId(),
-                               $("headlines-frame").innerHTML,
-                               getFeedUnread(getActiveFeedId())); */
+                       cache_headlines(getActiveFeedId(), activeFeedIsCat(), null, $("headlines-frame").innerHTML);
 
                } else if (article_is_unread && view_mode == "all_articles") {
-
-                       cache_invalidate(cache_prefix + getActiveFeedId());
-
-                       /* cache_inject(cache_prefix + getActiveFeedId(),
-                               $("headlines-frame").innerHTML,
-                               getFeedUnread(getActiveFeedId())-1); */
-
-               } else if (article_is_unread) {
-                       cache_invalidate(cache_prefix + getActiveFeedId());
+                       cache_headlines(getActiveFeedId(), activeFeedIsCat(), null, $("headlines-frame").innerHTML);
                }
 
                markHeadline(id);
@@ -258,37 +259,53 @@ function article_callback2(transport, id) {
 
                handle_rpc_json(transport);
 
-               var reply = JSON.parse(transport.responseText);
+               var reply = false;
+
+               try {
+                       reply = JSON.parse(transport.responseText);
+               } catch (e) {
+                       console.error(e);
+               }
 
                if (reply) {
+
                        var upic = $('FUPDPIC-' + id);
 
                        if (upic) upic.src = 'images/blank_icon.gif';
 
-                       if (id != last_requested_article) {
-                               console.log("requested article id is out of sequence, aborting");
-                               return;
-                       }
-
                        reply.each(function(article) {
                                if (active_post_id == article['id']) {
                                        render_article(article['content']);
                                }
-                               cache_inject(article['id'], article['content']);
+                               cids_requested.remove(article['id']);
+
+                               cache_set("article:" + article['id'], article['content']);
                        });
 
+//                     if (id != last_requested_article) {
+//                             console.log("requested article id is out of sequence, aborting");
+//                             return;
+//                     }
+
                } else {
-                       console.warn("article_callback: returned invalid data");
+                       console.error("Invalid object received: " + transport.responseText);
 
                        render_article("<div class='whiteBox'>" +
-                                       __('Could not display article (invalid data received)') + "</div>");
+                                       __('Could not display article (invalid object received - see error console for details)') + "</div>");
                }
 
-               var date = new Date();
-               last_article_view = date.getTime() / 1000;
-
                request_counters();
 
+               try {
+                       if (!_infscroll_disable &&
+                                       $$("#headlines-frame > div[id*=RROW]").last().hasClassName("Selected")) {
+
+                               loadMoreHeadlines();
+                       }
+               } catch (e) {
+                       console.warn(e);
+               }
+
                notify("");
        } catch (e) {
                exception_error("article_callback2", e, transport);
@@ -299,7 +316,7 @@ function view(id) {
        try {
                console.log("loading article: " + id);
 
-               var cached_article = cache_find(id);
+               var cached_article = cache_get("article:" + id);
 
                console.log("cache check result: " + (cached_article != false));
 
@@ -307,16 +324,18 @@ function view(id) {
 
                var query = "?op=view&id=" + param_escape(id);
 
-               var neighbor_ids = getRelativePostIds(active_post_id);
+               var neighbor_ids = getRelativePostIds(id);
 
                /* only request uncached articles */
 
-               var cids_to_request = Array();
+               var cids_to_request = [];
 
                for (var i = 0; i < neighbor_ids.length; i++) {
-                       if (!cache_check(neighbor_ids[i])) {
-                               cids_to_request.push(neighbor_ids[i]);
-                       }
+                       if (cids_requested.indexOf(neighbor_ids[i]) == -1)
+                               if (!cache_get("article:" + neighbor_ids[i])) {
+                                       cids_to_request.push(neighbor_ids[i]);
+                                       cids_requested.push(neighbor_ids[i]);
+                               }
                }
 
                console.log("additional ids: " + cids_to_request.toString());
@@ -329,6 +348,8 @@ function view(id) {
                active_post_id = id;
                showArticleInHeadlines(id);
 
+               precache_headlines();
+
                if (!cached_article) {
 
                        var upic = $('FUPDPIC-' + id);
@@ -348,9 +369,23 @@ function view(id) {
                        query = query + "&mode=prefetch_old";
                        render_article(cached_article);
 
-               }
+                       // if we don't need to request any relative ids, we might as well skip
+                       // the server roundtrip altogether
+                       if (cids_to_request.length == 0) {
+
+                               try {
+                                       if (!_infscroll_disable &&
+                                               $$("#headlines-frame > div[id*=RROW]").last().hasClassName("Selected")) {
+
+                                                       loadMoreHeadlines();
+                                       }
+                               } catch (e) {
+                                       console.warn(e);
+                               }
 
-               cache_expire();
+                               return;
+                       }
+               }
 
                last_requested_article = id;
 
@@ -369,14 +404,6 @@ function view(id) {
        }
 }
 
-function tMark(id) {
-       return toggleMark(id);
-}
-
-function tPub(id) {
-       return togglePub(id);
-}
-
 function toggleMark(id, client_only) {
        try {
                var query = "?op=rpc&id=" + id + "&subop=mark";
@@ -396,6 +423,8 @@ function toggleMark(id, client_only) {
                        query = query + "&mark=0";
                }
 
+               cache_headlines(getActiveFeedId(), activeFeedIsCat(), null, $("headlines-frame").innerHTML);
+
                if (!client_only) {
                        new Ajax.Request("backend.php", {
                                parameters: query,
@@ -435,6 +464,8 @@ function togglePub(id, client_only, no_effects, note) {
                        query = query + "&pub=0";
                }
 
+               cache_headlines(getActiveFeedId(), activeFeedIsCat(), null, $("headlines-frame").innerHTML);
+
                if (!client_only) {
                        new Ajax.Request("backend.php", {
                                parameters: query,
@@ -591,34 +622,24 @@ function toggleUnread(id, cmode, effect) {
 function selectionRemoveLabel(id, ids) {
        try {
 
-               if (!ids) var ids = getSelectedArticleIds2();
+               if (!ids) ids = getSelectedArticleIds2();
 
                if (ids.length == 0) {
                        alert(__("No articles are selected."));
                        return;
                }
 
-//             var ok = confirm(__("Remove selected articles from label?"));
-
-//             if (ok) {
-
-                       var query = "?op=rpc&subop=removeFromLabel&ids=" +
-                               param_escape(ids.toString()) + "&lid=" + param_escape(id);
-
-                       console.log(query);
-
-//                     notify_progress("Loading, please wait...");
-
-                       cache_invalidate("F:" + (-11 - id));
+               var query = "?op=rpc&subop=removeFromLabel&ids=" +
+                       param_escape(ids.toString()) + "&lid=" + param_escape(id);
 
-                       new Ajax.Request("backend.php", {
-                               parameters: query,
-                               onComplete: function(transport) {
-                                       handle_rpc_json(transport);
-                                       show_labels_in_headlines(transport);
-                               } });
+               console.log(query);
 
-//             }
+               new Ajax.Request("backend.php", {
+                       parameters: query,
+                       onComplete: function(transport) {
+                               handle_rpc_json(transport);
+                               show_labels_in_headlines(transport);
+                       } });
 
        } catch (e) {
                exception_error("selectionAssignLabel", e);
@@ -636,27 +657,17 @@ function selectionAssignLabel(id, ids) {
                        return;
                }
 
-//             var ok = confirm(__("Assign selected articles to label?"));
-
-//             if (ok) {
-
-                       cache_invalidate("F:" + (-11 - id));
+               var query = "?op=rpc&subop=assignToLabel&ids=" +
+                       param_escape(ids.toString()) + "&lid=" + param_escape(id);
 
-                       var query = "?op=rpc&subop=assignToLabel&ids=" +
-                               param_escape(ids.toString()) + "&lid=" + param_escape(id);
-
-                       console.log(query);
-
-//                     notify_progress("Loading, please wait...");
-
-                       new Ajax.Request("backend.php", {
-                               parameters: query,
-                               onComplete: function(transport) {
-                                       handle_rpc_json(transport);
-                                       show_labels_in_headlines(transport);
-                               } });
+               console.log(query);
 
-//             }
+               new Ajax.Request("backend.php", {
+                       parameters: query,
+                       onComplete: function(transport) {
+                               handle_rpc_json(transport);
+                               show_labels_in_headlines(transport);
+                       } });
 
        } catch (e) {
                exception_error("selectionAssignLabel", e);
@@ -673,7 +684,7 @@ function selectionToggleUnread(set_state, callback, no_error) {
                        return;
                }
 
-               for (i = 0; i < rows.length; i++) {
+               for (var i = 0; i < rows.length; i++) {
                        var row = $("RROW-" + rows[i]);
                        if (row) {
                                if (set_state == undefined) {
@@ -735,7 +746,7 @@ function selectionToggleMarked() {
                        return;
                }
 
-               for (i = 0; i < rows.length; i++) {
+               for (var i = 0; i < rows.length; i++) {
                        toggleMark(rows[i], true, true);
                }
 
@@ -767,7 +778,7 @@ function selectionTogglePublished() {
                        return;
                }
 
-               for (i = 0; i < rows.length; i++) {
+               for (var i = 0; i < rows.length; i++) {
                        togglePub(rows[i], true, true);
                }
 
@@ -868,7 +879,7 @@ function catchupPage() {
        }
 
        selectArticles('all');
-       selectionToggleUnread(false, 'viewCurrentFeed()', true)
+       selectionToggleUnread(false, 'viewCurrentFeed()', true);
        selectArticles('none');
 }
 
@@ -885,7 +896,6 @@ function deleteSelection() {
 
                var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
                var str;
-               var op;
 
                if (getActiveFeedId() != 0) {
                        str = __("Delete %d selected articles in %s?");
@@ -951,7 +961,7 @@ function archiveSelection() {
                console.log(query);
 
                for (var i = 0; i < rows.length; i++) {
-                       cache_invalidate(rows[i]);
+                       cache_delete("article:" + rows[i]);
                }
 
                new Ajax.Request("backend.php", {
@@ -988,7 +998,7 @@ function catchupSelection() {
                        return;
                }
 
-               selectionToggleUnread(false, 'viewCurrentFeed()', true)
+               selectionToggleUnread(false, 'viewCurrentFeed()', true);
 
        } catch (e) {
                exception_error("catchupSelection", e);
@@ -1020,7 +1030,7 @@ function editArticleTags(id) {
                                                var data = JSON.parse(transport.responseText);
 
                                                if (data) {
-                                                       var tags_str = data.tags_str;
+                                                       var tags_str = article.tags;
                                                        var id = tags_str.id;
 
                                                        var tags = $("ATSTR-" + id);
@@ -1029,7 +1039,7 @@ function editArticleTags(id) {
                                                        if (tags) tags.innerHTML = tags_str.content;
                                                        if (tooltip) tooltip.attr('label', tags_str.content_full);
 
-                                                       cache_invalidate(id);
+                                                       cache_delete("article:" + id);
                                                }
 
                                        }});
@@ -1064,274 +1074,16 @@ function cdmScrollToArticleId(id) {
        }
 }
 
-function cache_inject(id, article, param) {
-
-       try {
-               if (!cache_check_param(id, param)) {
-                       //console.log("cache_article: miss: " + id + " [p=" + param + "]");
-
-                  var date = new Date();
-             var ts = Math.round(date.getTime() / 1000);
-
-                       var cache_obj = {};
-
-                       cache_obj["id"] = id;
-                       cache_obj["data"] = article;
-                       cache_obj["param"] = param;
-
-                       if (param) id = id + ":" + param;
-
-                       cache_added["TS:" + id] = ts;
-
-                       if (has_local_storage())
-                               sessionStorage.setItem(id, JSON.stringify(cache_obj));
-                       else
-                               article_cache.push(cache_obj);
-
-               } else {
-                       //console.log("cache_article: hit: " + id + " [p=" + param + "]");
-               }
-       } catch (e) {
-               exception_error("cache_inject", e);
-       }
-}
-
-function cache_find(id) {
-
-       if (has_local_storage()) {
-               var cache_obj = sessionStorage.getItem(id);
-
-               if (cache_obj) {
-                       cache_obj = JSON.parse(cache_obj);
-
-                       if (cache_obj)
-                               return cache_obj['data'];
-               }
-
-       } else {
-               for (var i = 0; i < article_cache.length; i++) {
-                       if (article_cache[i]["id"] == id) {
-                               return article_cache[i]["data"];
-                       }
-               }
-       }
-       return false;
-}
-
-function cache_find_param(id, param) {
-
-       if (has_local_storage()) {
-
-               if (param) id = id + ":" + param;
-
-               var cache_obj = sessionStorage.getItem(id);
-
-               if (cache_obj) {
-                       cache_obj = JSON.parse(cache_obj);
-
-                       if (cache_obj)
-                               return cache_obj['data'];
-               }
-
-       } else {
-               for (var i = 0; i < article_cache.length; i++) {
-                       if (article_cache[i]["id"] == id && article_cache[i]["param"] == param) {
-                               return article_cache[i]["data"];
-                       }
-               }
-       }
-
-       return false;
-}
-
-function cache_check(id) {
-       if (has_local_storage()) {
-               if (sessionStorage.getItem(id))
-                       return true;
-       } else {
-               for (var i = 0; i < article_cache.length; i++) {
-                       if (article_cache[i]["id"] == id) {
-                               return true;
-                       }
-               }
-       }
-       return false;
-}
-
-function cache_check_param(id, param) {
-       if (has_local_storage()) {
-
-               if (param) id = id + ":" + param;
-
-               if (sessionStorage.getItem(id))
-                       return true;
-
-       } else {
-               for (var i = 0; i < article_cache.length; i++) {
-                       if (article_cache[i]["id"] == id && article_cache[i]["param"] == param) {
-                               return true;
-                       }
-               }
-       }
-       return false;
-}
-
-function cache_expire() {
-if (has_local_storage()) {
-
-               var date = new Date();
-               var timestamp = Math.round(date.getTime() / 1000);
-
-               for (var i = 0; i < sessionStorage.length; i++) {
-
-                       var id = sessionStorage.key(i);
-
-                       if (timestamp - cache_added["TS:" + id] > 180) {
-                               sessionStorage.removeItem(id);
-                       }
-               }
-
-       } else {
-               while (article_cache.length > 25) {
-                       article_cache.shift();
-               }
-       }
-}
-
-function cache_flush() {
-       if (has_local_storage()) {
-               sessionStorage.clear();
-       } else {
-               article_cache = new Array();
-       }
-}
-
-function cache_invalidate(id) {
-       try {
-               if (has_local_storage()) {
-
-                       var found = false;
-
-                       for (var i = 0; i < sessionStorage.length; i++) {
-                               var key = sessionStorage.key(i);
-
-//                                     console.warn("cache_invalidate: " + key_id + " cmp " + id);
-
-                               if (key == id || key.indexOf(id + ":") == 0) {
-                                       sessionStorage.removeItem(key);
-                                       found = true;
-                                       break;
-                               }
-                       }
-
-                       return found;
-
-               } else {
-                       var i = 0
-
-                       while (i < article_cache.length) {
-                               if (article_cache[i]["id"] == id) {
-                                       //console.log("cache_invalidate: removed id " + id);
-                                       article_cache.splice(i, 1);
-                                       return true;
-                               }
-                               i++;
-                       }
-               }
-
-               //console.log("cache_invalidate: id not found: " + id);
-               return false;
-       } catch (e) {
-               exception_error("cache_invalidate", e);
-       }
-}
-
 function getActiveArticleId() {
        return active_post_id;
 }
 
-function preloadBatchedArticles() {
-       try {
-
-               var query = "?op=rpc&subop=getArticles&ids=" +
-                       preload_id_batch.toString();
-
-               new Ajax.Request("backend.php", {
-                       parameters: query,
-                       onComplete: function(transport) {
-
-                               preload_id_batch = [];
-
-                               var articles = JSON.parse(transport.responseText);
-
-                               for (var i = 0; i < articles.length; i++) {
-                                       var id = articles[i]['id'];
-                                       if (!cache_check(id)) {
-                                               cache_inject(id, articles[i]['content']);
-                                               console.log("preloaded article: " + id);
-                                       }
-                               }
-               } });
-
-       } catch (e) {
-               exception_error("preloadBatchedArticles", e);
-       }
-}
-
-function preloadArticleUnderPointer(id) {
-       try {
-               if (getInitParam("bw_limit") == "1") return;
-
-               if (post_under_pointer == id && !cache_check(id)) {
-
-                       console.log("trying to preload article " + id);
-
-                       var neighbor_ids = getRelativePostIds(id, 1);
-
-                       /* only request uncached articles */
-
-                       if (preload_id_batch.indexOf(id) == -1) {
-                               for (var i = 0; i < neighbor_ids.length; i++) {
-                                       if (!cache_check(neighbor_ids[i])) {
-                                               preload_id_batch.push(neighbor_ids[i]);
-                                       }
-                               }
-                       }
-
-                       if (preload_id_batch.indexOf(id) == -1)
-                               preload_id_batch.push(id);
-
-                       //console.log("preload ids batch: " + preload_id_batch.toString());
-
-                       window.clearTimeout(preload_timeout_id);
-                       preload_batch_timeout_id = window.setTimeout('preloadBatchedArticles()', 1000);
-
-               }
-       } catch (e) {
-               exception_error("preloadArticleUnderPointer", e);
-       }
-}
-
 function postMouseIn(id) {
-       try {
-               if (post_under_pointer != id) {
-                       post_under_pointer = id;
-                       if (!isCdmMode()) {
-                               window.setTimeout("preloadArticleUnderPointer(" + id + ")", 250);
-                       }
-               }
-
-       } catch (e) {
-               exception_error("postMouseIn", e);
-       }
+       post_under_pointer = id;
 }
 
 function postMouseOut(id) {
-       try {
-               post_under_pointer = false;
-       } catch (e) {
-               exception_error("postMouseOut", e);
-       }
+       post_under_pointer = false;
 }
 
 function headlines_scroll_handler(e) {
@@ -1342,7 +1094,13 @@ function headlines_scroll_handler(e) {
                        if (hsp && (e.scrollTop + e.offsetHeight > hsp.offsetTop) ||
                                        e.scrollTop + e.offsetHeight > e.scrollHeight - 100) {
 
-                               viewNextFeedPage();
+                               if (hsp)
+                                       hsp.innerHTML = "<img src='images/indicator_tiny.gif'> " +
+                                               __("Loading, please wait...");
+
+                               loadMoreHeadlines();
+                               return;
+
                        }
                } else {
                        if (hsp) hsp.innerHTML = "";
@@ -1350,45 +1108,68 @@ function headlines_scroll_handler(e) {
 
                if (getInitParam("cdm_auto_catchup") == 1) {
 
-                       var ids = [];
-
                        $$("#headlines-frame > div[id*=RROW][class*=Unread]").each(
                                function(child) {
                                        if ($("headlines-frame").scrollTop >
                                                        (child.offsetTop + child.offsetHeight/2)) {
 
-                                               ids.push(child.id.replace("RROW-", ""));
+                                               var id = child.id.replace("RROW-", "");
+
+                                               if (catchup_id_batch.indexOf(id) == -1)
+                                                       catchup_id_batch.push(id);
+
+                                               //console.log("auto_catchup_batch: " + catchup_id_batch.toString());
                                        }
                                });
 
-                       if (ids.length > 0) {
+                       if (catchup_id_batch.length > 0) {
+                               window.clearTimeout(catchup_timeout_id);
 
-                               var query = "?op=rpc&subop=catchupSelected" +
-                                       "&cmode=0&ids=" + param_escape(ids.toString());
+                               if (!_infscroll_request_sent) {
+                                       catchup_timeout_id = window.setTimeout('catchupBatchedArticles()',
+                                               2000);
+                               }
+                       }
+               }
 
-                               new Ajax.Request("backend.php", {
-                                       parameters: query,
-                                       onComplete: function(transport) {
-                                               handle_rpc_json(transport);
+       } catch (e) {
+               console.warn("headlines_scroll_handler: " + e);
+       }
+}
 
-                                               ids.each(function(id) {
-                                                       var elem = $("RROW-" + id);
-                                                       if (elem) elem.removeClassName("Unread");
-                                               });
-                                       } });
-                       }
+function catchupBatchedArticles() {
+       try {
+               if (catchup_id_batch.length > 0 && !_infscroll_request_sent) {
+
+                       var query = "?op=rpc&subop=catchupSelected" +
+                               "&cmode=0&ids=" + param_escape(catchup_id_batch.toString());
+
+                       new Ajax.Request("backend.php", {
+                               parameters: query,
+                               onComplete: function(transport) {
+                                       handle_rpc_json(transport);
+
+                                       catchup_id_batch.each(function(id) {
+                                               var elem = $("RROW-" + id);
+                                               if (elem) elem.removeClassName("Unread");
+                                       });
+
+                                       catchup_id_batch = [];
+                               } });
                }
+
        } catch (e) {
-               exception_error("headlines_scroll_handler", e);
+               exception_error("catchupBatchedArticles", e);
        }
 }
 
-function catchupRelativeToArticle(below) {
+function catchupRelativeToArticle(below, id) {
 
        try {
 
+               if (!id) id = getActiveArticleId();
 
-               if (!getActiveArticleId()) {
+               if (!id) {
                        alert(__("No article is selected."));
                        return;
                }
@@ -1399,7 +1180,7 @@ function catchupRelativeToArticle(below) {
 
                if (!below) {
                        for (var i = 0; i < visible_ids.length; i++) {
-                               if (visible_ids[i] != getActiveArticleId()) {
+                               if (visible_ids[i] != id) {
                                        var e = $("RROW-" + visible_ids[i]);
 
                                        if (e && e.hasClassName("Unread")) {
@@ -1411,7 +1192,7 @@ function catchupRelativeToArticle(below) {
                        }
                } else {
                        for (var i = visible_ids.length-1; i >= 0; i--) {
-                               if (visible_ids[i] != getActiveArticleId()) {
+                               if (visible_ids[i] != id) {
                                        var e = $("RROW-" + visible_ids[i]);
 
                                        if (e && e.hasClassName("Unread")) {
@@ -1496,28 +1277,39 @@ function cdmExpandArticle(id) {
 
                                var query = "?op=rpc&subop=cdmGetArticle&id=" + param_escape(id);
 
-                               //console.log(query);
+                               var neighbor_ids = getRelativePostIds(id);
+
+                               /* only request uncached articles */
+                               var cids_to_request = [];
+
+                               for (var i = 0; i < neighbor_ids.length; i++) {
+                                       if (cids_requested.indexOf(neighbor_ids[i]) == -1)
+                                               if ($("CWRAP-" + neighbor_ids[i]).innerHTML == "") {
+                                                       cids_to_request.push(neighbor_ids[i]);
+                                                       cids_requested.push(neighbor_ids[i]);
+                                               }
+                               }
+
+                               console.log("additional ids: " + cids_to_request.toString());
+
+                               query = query + "&cids=" + cids_to_request.toString();
+
+                               console.log(query);
 
                                new Ajax.Request("backend.php", {
                                        parameters: query,
                                        onComplete: function(transport) {
+
                                                $("FUPDPIC-" + id).src = 'images/blank_icon.gif';
 
                                                handle_rpc_json(transport);
 
                                                var reply = JSON.parse(transport.responseText);
 
-                                               if (reply) {
-                                                       var article = reply['article']['content'];
-                                                       var recv_id = reply['article']['id'];
-
-                                                       if (recv_id == id)
-                                                               $("CWRAP-" + id).innerHTML = article;
-
-                                               } else {
-                                                       $("CWRAP-" + id).innerHTML = __("Unable to load article.");
-
-                                               }
+                                               reply.each(function(article) {
+                                                       $("CWRAP-" + article['id']).innerHTML = article['content'];
+                                                       cids_requested.remove(article['id']);
+                                               });
                                }});
 
                        }
@@ -1566,7 +1358,7 @@ function getArticleUnderPointer() {
 
 function zoomToArticle(event, id) {
        try {
-               var cached_article = cache_find(id);
+               var cached_article = cache_get("article: " + id);
 
                if (dijit.byId("ATAB-" + id))
                        if (!event || !event.shiftKey)
@@ -1662,13 +1454,16 @@ function show_labels_in_headlines(transport) {
 
                                if (ctr) ctr.innerHTML = elem.labels;
                        });
+
+                       cache_headlines(getActiveFeedId(), activeFeedIsCat(), null, $("headlines-frame").innerHTML);
+
                }
        } catch (e) {
                exception_error("show_labels_in_headlines", e);
        }
 }
 
-function toggleHeadlineActions() {
+/* function toggleHeadlineActions() {
        try {
                var e = $("headlineActionsBody");
                var p = $("headlineActionsDrop");
@@ -1686,7 +1481,7 @@ function toggleHeadlineActions() {
        } catch (e) {
                exception_error("toggleHeadlineActions", e);
        }
-}
+} */
 
 /* function publishWithNote(id, def_note) {
        try {
@@ -1862,7 +1657,7 @@ function getVisibleArticleIds() {
 
 function cdmClicked(event, id) {
        try {
-               var shift_key = event.shiftKey;
+               //var shift_key = event.shiftKey;
 
                hideAuxDlg();
 
@@ -2001,14 +1796,14 @@ function getRelativePostIds(id, limit) {
 
        try {
 
-               if (!limit) limit = 3;
+               if (!limit) limit = 6; //3
 
                var ids = getVisibleArticleIds();
 
                for (var i = 0; i < ids.length; i++) {
                        if (ids[i] == id) {
                                for (var k = 1; k <= limit; k++) {
-                                       if (i > k-1) tmp.push(ids[i-k]);
+                                       //if (i > k-1) tmp.push(ids[i-k]);
                                        if (i < ids.length-k) tmp.push(ids[i+k]);
                                }
                                break;
@@ -2131,7 +1926,20 @@ function initHeadlinesMenu() {
                                hlOpenInNewTab(event, this.getParent().callerRowId);
                                }}));
 
-//             menu.addChild(new dijit.MenuSeparator());
+               menu.addChild(new dijit.MenuSeparator());
+
+               menu.addChild(new dijit.MenuItem({
+                       label: __("Mark above as read"),
+                       onClick: function(event) {
+                               catchupRelativeToArticle(0, this.getParent().callerRowId);
+                               }}));
+
+               menu.addChild(new dijit.MenuItem({
+                       label: __("Mark below as read"),
+                       onClick: function(event) {
+                               catchupRelativeToArticle(1, this.getParent().callerRowId);
+                               }}));
+
 
                var labels = dijit.byId("feedTree").model.getItemsInCategory(-2);
 
@@ -2243,7 +2051,7 @@ function editArticleNote(id) {
 
                                                var reply = JSON.parse(transport.responseText);
 
-                                               cache_invalidate(id);
+                                               cache_delete("article:" + id);
 
                                                var elem = $("POSTNOTE-" + id);
 
@@ -2295,3 +2103,143 @@ function player(elem) {
        }
 }
 
+function cache_set(id, obj) {
+       //console.log("cache_set: " + id);
+       if (has_storage)
+               try {
+                       sessionStorage[id] = obj;
+               } catch (e) {
+                       sessionStorage.clear();
+               }
+}
+
+function cache_get(id) {
+       if (has_storage)
+               return sessionStorage[id];
+}
+
+function cache_clear() {
+       if (has_storage)
+               sessionStorage.clear();
+}
+
+function cache_delete(id) {
+       if (has_storage)
+               sessionStorage.removeItem(id);
+}
+
+function cache_headlines(feed, is_cat, toolbar_obj, content_obj) {
+       if (toolbar_obj && content_obj) {
+               cache_set("feed:" + feed + ":" + is_cat,
+                       JSON.stringify({toolbar: toolbar_obj, content: content_obj}));
+       } else {
+               try {
+                       obj =   cache_get("feed:" + feed + ":" + is_cat);
+
+                       if (obj) {
+                               obj = JSON.parse(obj);
+
+                               if (toolbar_obj) obj.toolbar = toolbar_obj;
+                               if (content_obj) obj.content = content_obj;
+
+                               cache_set("feed:" + feed + ":" + is_cat, JSON.stringify(obj));
+                       }
+
+               } catch (e) {
+                       console.warn("cache_headlines failed: " + e);
+               }
+       }
+}
+
+function render_local_headlines(feed, is_cat, obj) {
+       try {
+
+               dijit.byId("headlines-toolbar").attr('content',
+                       obj.toolbar);
+
+               dijit.byId("headlines-frame").attr('content',
+                       obj.content);
+
+               dojo.parser.parse('headlines-toolbar');
+
+               $("headlines-frame").scrollTop = 0;
+               selectArticles('none');
+               setActiveFeedId(feed, is_cat);
+               initHeadlinesMenu();
+
+               precache_headlines();
+
+       } catch (e) {
+               exception_error("render_local_headlines", e);
+       }
+}
+
+function precache_headlines_idle() {
+       try {
+               if (!feed_precache_timeout_id) {
+                       var feeds = dijit.byId("feedTree").getVisibleUnreadFeeds();
+                       var uncached = [];
+
+                       feeds.each(function(item) {
+                               if (parseInt(item[0]) > 0 && !cache_get("feed:" + item[0] + ":" + item[1]))
+                                       uncached.push(item);
+                       });
+
+                       if (uncached.length > 0) {
+                               var rf = uncached[Math.floor(Math.random()*uncached.length)];
+                               viewfeed(rf[0], '', rf[1], 0, true);
+                       }
+               }
+               precache_idle_timeout_id = setTimeout("precache_headlines_idle()", 1000*30);
+
+       } catch (e) {
+               exception_error("precache_headlines_idle", e);
+       }
+}
+
+function precache_headlines() {
+       try {
+
+               if (!feed_precache_timeout_id) {
+                       feed_precache_timeout_id = window.setTimeout(function() {
+                               var nuf = getNextUnreadFeed(getActiveFeedId(), activeFeedIsCat());
+                               var nf = dijit.byId("feedTree").getNextFeed(getActiveFeedId(), activeFeedIsCat());
+
+                               if (nuf && !cache_get("feed:" + nuf + ":" + activeFeedIsCat()))
+                                       viewfeed(nuf, '', activeFeedIsCat(), 0, true);
+
+                               if (nf != nuf && nf && !cache_get("feed:" + nf[0] + ":" + nf[1]))
+                                       viewfeed(nf[0], '', nf[1], 0, true);
+
+                               window.setTimeout(function() {
+                                       feed_precache_timeout_id = false;
+                                       }, 3000);
+                       }, 1000);
+               }
+
+       } catch (e) {
+               exception_error("precache_headlines", e);
+       }
+}
+
+function shareArticle(id) {
+       try {
+               if (dijit.byId("shareArticleDlg"))
+                       dijit.byId("shareArticleDlg").destroyRecursive();
+
+               var query = "backend.php?op=dlg&id=shareArticle&param=" + param_escape(id);
+
+               dialog = new dijit.Dialog({
+                       id: "shareArticleDlg",
+                       title: __("Share article by URL"),
+                       style: "width: 600px",
+                       href: query});
+
+               dialog.show();
+
+       } catch (e) {
+               exception_error("emailArticle", e);
+       }
+}
+
+