]> git.wh0rd.org Git - tt-rss.git/blob - viewfeed.js
tweak catchup_callback
[tt-rss.git] / viewfeed.js
1 var active_post_id = false;
2 var _catchup_callback_func = false;
3 var last_article_view = false;
4 var active_real_feed_id = false;
5
6 var _tag_active_post_id = false;
7 var _tag_active_feed_id = false;
8 var _tag_active_cdm = false;
9
10 // FIXME: kludge, to restore scrollTop after tag editor terminates
11 var _tag_cdm_scroll = false;
12
13 // FIXME: kludges, needs proper implementation
14 var _reload_feedlist_after_view = false;
15
16 var _cdm_wd_timeout = false;
17 var _cdm_wd_vishist = new Array();
18
19 var article_cache = new Array();
20
21 function catchup_callback() {
22         if (xmlhttp_rpc.readyState == 4) {
23                 try {
24                         debug("catchup_callback");
25                         notify("");                     
26                         all_counters_callback();
27                         if (_catchup_callback_func) {
28                                 setTimeout(_catchup_callback_func, 10); 
29                         }
30                 } catch (e) {
31                         exception_error("catchup_callback", e);
32                 }
33         }
34 }
35
36 function headlines_callback() {
37         if (xmlhttp.readyState == 4) {
38                 debug("headlines_callback");
39                 var f = document.getElementById("headlines-frame");
40                 try {
41                         f.scrollTop = 0;
42                 } catch (e) { };
43
44                 if (xmlhttp.responseXML) {
45                         var headlines = xmlhttp.responseXML.getElementsByTagName("headlines")[0];
46                         var counters = xmlhttp.responseXML.getElementsByTagName("counters")[0];
47                         var articles = xmlhttp.responseXML.getElementsByTagName("article");
48                         var runtime_info = xmlhttp.responseXML.getElementsByTagName("runtime-info");
49
50                         if (headlines) {
51                                 f.innerHTML = headlines.firstChild.nodeValue;
52                         } else {
53                                 debug("headlines_callback: returned no data");
54                         f.innerHTML = "<div class='whiteBox'>" + __('Could not update headlines (missing XML data)') + "</div>";
55
56                         }
57
58                         if (articles) {
59                                 for (var i = 0; i < articles.length; i++) {
60                                         var a_id = articles[i].getAttribute("id");
61                                         debug("found id: " + a_id);
62                                         cache_inject(a_id, articles[i].firstChild.nodeValue);
63                                 }
64                         } else {
65                                 debug("no cached articles received");
66                         }
67
68                         if (counters) {
69                                 debug("parsing piggybacked counters: " + counters);
70                                 parse_counters(counters, false);
71                         } else {
72                                 debug("counters container not found in reply");
73                         }
74
75                         if (runtime_info) {
76                                 debug("parsing runtime info: " + runtime_info[0]);
77                                 parse_runtime_info(runtime_info[0]);
78                         } else {
79                                 debug("counters container not found in reply");
80                         }
81
82                 } else {
83                         debug("headlines_callback: returned no XML object");
84                         f.innerHTML = "<div class='whiteBox'>" + __('Could not update headlines (missing XML object)') + "</div>";
85                 }
86
87                 if (typeof correctPNG != 'undefined') {
88                         correctPNG();
89                 }
90
91                 if (_cdm_wd_timeout) window.clearTimeout(_cdm_wd_timeout);
92
93                 if (!document.getElementById("headlinesList") && 
94                                 getInitParam("cdm_auto_catchup") == 1) {
95                         debug("starting CDM watchdog");
96                         _cdm_wd_timeout = window.setTimeout("cdmWatchdog()", 5000);
97                         _cdm_wd_vishist = new Array();
98                 } else {
99                         debug("not in CDM mode or watchdog disabled");
100                 }
101
102                 if (_tag_cdm_scroll) {
103                         try {
104                                 document.getElementById("headlinesInnerContainer").scrollTop = _tag_cdm_scroll;
105                                 _tag_cdm_scroll = false;
106                         } catch (e) { }
107                 }
108
109                 notify("");
110         }
111 }
112
113 function render_article(article) {
114         try {
115                 var f = document.getElementById("content-frame");
116                 try {
117                         f.scrollTop = 0;
118                 } catch (e) { };
119
120                 f.innerHTML = article;
121
122         } catch (e) {
123                 exception_error("render_article", e);
124         }
125 }
126
127 function article_callback() {
128         if (xmlhttp.readyState == 4) {
129                 debug("article_callback");
130
131                 try {
132                         if (xmlhttp.responseXML) {
133                                 var reply = xmlhttp.responseXML.firstChild.firstChild;
134
135                                 var articles = xmlhttp.responseXML.getElementsByTagName("article");
136
137                                 for (var i = 0; i < articles.length; i++) {
138                                         var a_id = articles[i].getAttribute("id");
139
140                                         debug("found id: " + a_id);
141
142                                         if (a_id == active_post_id) {
143                                                 debug("active article, rendering...");                                  
144                                                 render_article(articles[i].firstChild.nodeValue);
145                                         }
146
147                                         cache_inject(a_id, articles[i].firstChild.nodeValue);
148                                 }
149                         
150                         } else {
151                                 debug("article_callback: returned no XML object");
152                                 var f = document.getElementById("content-frame");
153                                 f.innerHTML = "<div class='whiteBox'>" + __('Could not display article (missing XML object)') + "</div>";
154                         }
155                 } catch (e) {
156                         exception_error("article_callback", e);
157                 }
158
159                 var date = new Date();
160                 last_article_view = date.getTime() / 1000;
161
162                 if (typeof correctPNG != 'undefined') {
163                         correctPNG();
164                 }
165
166                 if (_reload_feedlist_after_view) {
167                         setTimeout('updateFeedList(false, false)', 50);                 
168                         _reload_feedlist_after_view = false;
169                 } else {
170                         var counters = xmlhttp.responseXML.getElementsByTagName("counters")[0];
171
172                         if (counters) {
173                                 debug("parsing piggybacked counters: " + counters);
174                                 parse_counters(counters, false);
175                         } else {
176                                 debug("counters container not found in reply");
177                         }
178                 }
179
180                 notify("");
181         }
182 }
183
184 function view(id, feed_id, skip_history) {
185         
186         try {
187                 debug("loading article: " + id + "/" + feed_id);
188
189                 active_real_feed_id = feed_id;
190
191                 var cached_article = cache_find(id);
192
193                 debug("cache check result: " + (cached_article != false));
194         
195                 enableHotkeys();
196         
197                 //setActiveFeedId(feed_id);
198
199                 var query = "backend.php?op=view&id=" + param_escape(id) +
200                         "&feed=" + param_escape(feed_id);
201
202                 var date = new Date();
203
204                 if (!xmlhttp_ready(xmlhttp) && last_article_view < date.getTime() / 1000 - 15) {
205                         debug("<b>xmlhttp seems to be stuck at view, aborting</b>");
206                         xmlhttp.abort();
207                         if (is_safari()) {
208                                 debug("trying alternative reset method for Safari");
209                                 xmlhttp = Ajax.getTransport();
210                         }
211                 }
212
213                 if (xmlhttp_ready(xmlhttp)) {
214
215                         active_post_id = id; 
216
217                         cleanSelected("headlinesList");
218
219                         var crow = document.getElementById("RROW-" + active_post_id);
220
221                         var article_is_unread = crow.className.match("Unread");
222                         debug("article is unread: " + article_is_unread);                       
223
224                         crow.className = crow.className.replace("Unread", "");
225
226                         var upd_img_pic = document.getElementById("FUPDPIC-" + active_post_id);
227
228                         if (upd_img_pic) {
229                                 upd_img_pic.src = "images/blank_icon.gif";
230                         }
231
232                         selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', false);
233                         markHeadline(active_post_id);
234
235                         var neighbor_ids = getRelativePostIds(active_post_id);
236
237                         /* only request uncached articles */
238
239                         var cids_to_request = Array();
240
241                         for (var i = 0; i < neighbor_ids.length; i++) {
242                                 if (!cache_check(neighbor_ids[i])) {
243                                         cids_to_request.push(neighbor_ids[i]);
244                                 }
245                         }
246
247                         debug("additional ids: " + cids_to_request.toString());                 
248
249                         /* additional info for piggyback counters */
250
251                         if (tagsAreDisplayed()) {
252                                 query = query + "&omode=lt";
253                         } else {
254                                 query = query + "&omode=flc";
255                         }
256
257                         var date = new Date();
258                         var timestamp = Math.round(date.getTime() / 1000);
259                         query = query + "&ts=" + timestamp;
260
261                         query = query + "&cids=" + cids_to_request.toString();
262
263                         if (!cached_article) {
264
265                                 notify_progress("Loading, please wait...");
266
267                                 debug(query);
268
269                                 xmlhttp.open("GET", query, true);
270                                 xmlhttp.onreadystatechange=article_callback;
271                                 xmlhttp.send(null);
272                         } else if (cached_article && article_is_unread) {
273
274                                 query = query + "&mode=prefetch";
275
276                                 debug(query);
277
278                                 xmlhttp.open("GET", query, true);
279                                 xmlhttp.onreadystatechange=article_callback;
280                                 xmlhttp.send(null);
281
282                                 render_article(cached_article);
283
284                         } else if (cached_article) {
285
286                                 query = query + "&mode=prefetch_old";
287
288                                 debug(query);
289
290                                 xmlhttp.open("GET", query, true);
291                                 xmlhttp.onreadystatechange=article_callback;
292                                 xmlhttp.send(null);
293
294                                 render_article(cached_article);
295
296                         }
297
298                         cache_expire();
299
300                 } else {
301                         debug("xmlhttp busy (@view)");
302                         printLockingError();
303                 }  
304
305         } catch (e) {
306                 exception_error("view", e);
307         }
308 }
309
310 function tMark(id) {
311         return toggleMark(id);
312 }
313
314 function toggleMark(id) {
315
316         if (!xmlhttp_ready(xmlhttp_rpc)) {
317                 printLockingError();
318                 return;
319         }
320
321         var query = "backend.php?op=rpc&id=" + id + "&subop=mark";
322
323         var mark_img = document.getElementById("FMPIC-" + id);
324         var vfeedu = document.getElementById("FEEDU--1");
325         var crow = document.getElementById("RROW-" + id);
326
327         if (mark_img.alt != "Reset mark") {
328                 mark_img.src = "images/mark_set.png";
329                 mark_img.alt = "Reset mark";
330                 query = query + "&mark=1";
331
332                 if (vfeedu && crow.className.match("Unread")) {
333                         vfeedu.innerHTML = (+vfeedu.innerHTML) + 1;
334                 }
335
336         } else {
337                 mark_img.src = "images/mark_unset.png";
338                 mark_img.alt = "Set mark";
339                 query = query + "&mark=0";
340
341                 if (vfeedu && crow.className.match("Unread")) {
342                         vfeedu.innerHTML = (+vfeedu.innerHTML) - 1;
343                 }
344
345         }
346
347         var vfeedctr = document.getElementById("FEEDCTR--1");
348         var vfeedr = document.getElementById("FEEDR--1");
349
350         if (vfeedu && vfeedctr) {
351                 if ((+vfeedu.innerHTML) > 0) {
352                         if (crow.className.match("Unread") && !vfeedr.className.match("Unread")) {
353                                 vfeedr.className = vfeedr.className + "Unread";
354                                 vfeedctr.className = "odd";
355                         }
356                 } else {
357                         vfeedctr.className = "invisible";
358                         vfeedr.className = vfeedr.className.replace("Unread", "");
359                 }
360         }
361
362         debug("toggle starred for aid " + id);
363
364         new Ajax.Request(query);
365
366 }
367
368 function correctHeadlinesOffset(id) {
369         
370         try {
371
372                 var hlist = document.getElementById("headlinesList");
373                 var container = document.getElementById("headlinesInnerContainer");
374                 var row = document.getElementById("RROW-" + id);
375         
376                 var viewport = container.offsetHeight;
377         
378                 var rel_offset_top = row.offsetTop - container.scrollTop;
379                 var rel_offset_bottom = row.offsetTop + row.offsetHeight - container.scrollTop;
380         
381                 debug("Rtop: " + rel_offset_top + " Rbtm: " + rel_offset_bottom);
382                 debug("Vport: " + viewport);
383
384                 if (rel_offset_top <= 0 || rel_offset_top > viewport) {
385                         container.scrollTop = row.offsetTop;
386                 } else if (rel_offset_bottom > viewport) {
387
388                         /* doesn't properly work with Opera in some cases because
389                                 Opera fucks up element scrolling */
390
391                         container.scrollTop = row.offsetTop + row.offsetHeight - viewport;              
392                 } 
393
394         } catch (e) {
395                 exception_error("correctHeadlinesOffset", e);
396         }
397
398 }
399
400 function moveToPost(mode) {
401
402         // check for combined mode
403         if (!document.getElementById("headlinesList"))
404                 return;
405
406         var rows = getVisibleHeadlineIds();
407
408         var prev_id = false;
409         var next_id = false;
410
411         if (!document.getElementById('RROW-' + active_post_id)) {
412                 active_post_id = false;
413         }
414
415         if (active_post_id == false) {
416                 next_id = getFirstVisibleHeadlineId();
417                 prev_id = getLastVisibleHeadlineId();
418         } else {        
419                 for (var i = 0; i < rows.length; i++) {
420                         if (rows[i] == active_post_id) {
421                                 prev_id = rows[i-1];
422                                 next_id = rows[i+1];                    
423                         }
424                 }
425         }
426
427         if (mode == "next") {
428                 if (next_id) {
429                         correctHeadlinesOffset(next_id);
430                         view(next_id, getActiveFeedId());
431                 }
432         }
433
434         if (mode == "prev") {
435                 if (prev_id) {
436                         correctHeadlinesOffset(prev_id);
437                         view(prev_id, getActiveFeedId());
438                 }
439         } 
440 }
441
442 function toggleUnread(id, cmode) {
443         try {
444                 if (!xmlhttp_ready(xmlhttp_rpc)) {
445                         printLockingError();
446                         return;
447                 }
448         
449                 var row = document.getElementById("RROW-" + id);
450                 if (row) {
451                         var nc = row.className;
452                         nc = nc.replace("Unread", "");
453                         nc = nc.replace("Selected", "");
454
455                         if (row.className.match("Unread")) {
456                                 row.className = nc;
457                         } else {
458                                 row.className = nc + "Unread";
459                         }
460
461                         if (!cmode) cmode = 2;
462
463                         var query = "backend.php?op=rpc&subop=catchupSelected&ids=" +
464                                 param_escape(id) + "&cmode=" + param_escape(cmode);
465
466                         notify_progress("Loading, please wait...");
467
468                         xmlhttp_rpc.open("GET", query, true);
469                         xmlhttp_rpc.onreadystatechange=all_counters_callback;
470                         xmlhttp_rpc.send(null);
471
472                 }
473
474
475         } catch (e) {
476                 exception_error("toggleUnread", e);
477         }
478 }
479
480 function selectionToggleUnread(cdm_mode, set_state, callback_func, no_error) {
481         try {
482                 if (!xmlhttp_ready(xmlhttp_rpc)) {
483                         printLockingError();
484                         return;
485                 }
486         
487                 var rows;
488
489                 if (cdm_mode) {
490                         rows = cdmGetSelectedArticles();
491                 } else {        
492                         rows = getSelectedTableRowIds("headlinesList", "RROW", "RCHK");
493                 }
494
495                 if (rows.length == 0 && !no_error) {
496                         alert(__("No articles are selected."));
497                         return;
498                 }
499
500                 for (i = 0; i < rows.length; i++) {
501                         var row = document.getElementById("RROW-" + rows[i]);
502                         if (row) {
503                                 var nc = row.className;
504                                 nc = nc.replace("Unread", "");
505                                 nc = nc.replace("Selected", "");
506
507                                 if (row.className.match("Unread")) {
508                                         row.className = nc + "Selected";
509                                 } else {
510                                         row.className = nc + "UnreadSelected";
511                                 }
512                         }
513                 }
514
515                 if (rows.length > 0) {
516
517                         var cmode = "";
518
519                         if (set_state == undefined) {
520                                 cmode = "2";
521                         } else if (set_state == true) {
522                                 cmode = "1";
523                         } else if (set_state == false) {
524                                 cmode = "0";
525                         }
526
527                         var query = "backend.php?op=rpc&subop=catchupSelected&ids=" +
528                                 param_escape(rows.toString()) + "&cmode=" + cmode;
529
530                         _catchup_callback_func = callback_func;
531
532                         notify_progress("Loading, please wait...");
533
534                         xmlhttp_rpc.open("GET", query, true);
535                         xmlhttp_rpc.onreadystatechange=catchup_callback;
536                         xmlhttp_rpc.send(null);
537
538                 }
539
540         } catch (e) {
541                 exception_error("selectionToggleUnread", e);
542         }
543 }
544
545 function selectionToggleMarked(cdm_mode) {
546         try {
547                 if (!xmlhttp_ready(xmlhttp_rpc)) {
548                         printLockingError();
549                         return;
550                 }
551         
552                 var rows;
553                 
554                 if (cdm_mode) {
555                         rows = cdmGetSelectedArticles();
556                 } else {        
557                         rows = getSelectedTableRowIds("headlinesList", "RROW", "RCHK");
558                 }       
559
560                 if (rows.length == 0) {
561                         alert(__("No articles are selected."));
562                         return;
563                 }
564
565                 for (i = 0; i < rows.length; i++) {
566                         var row = document.getElementById("RROW-" + rows[i]);
567                         var mark_img = document.getElementById("FMARKPIC-" + rows[i]);
568
569                         if (row && mark_img) {
570
571                                 if (mark_img.alt == "Set mark") {
572                                         mark_img.src = "images/mark_set.png";
573                                         mark_img.alt = "Reset mark";
574                                         mark_img.setAttribute('onclick', 
575                                                 'javascript:toggleMark('+rows[i]+', false)');
576
577                                 } else {
578                                         mark_img.src = "images/mark_unset.png";
579                                         mark_img.alt = "Set mark";
580                                         mark_img.setAttribute('onclick', 
581                                                 'javascript:toggleMark('+rows[i]+', true)');
582                                 }
583                         }
584                 }
585
586                 if (rows.length > 0) {
587
588                         var query = "backend.php?op=rpc&subop=markSelected&ids=" +
589                                 param_escape(rows.toString()) + "&cmode=2";
590
591                         xmlhttp_rpc.open("GET", query, true);
592                         xmlhttp_rpc.onreadystatechange=all_counters_callback;
593                         xmlhttp_rpc.send(null);
594
595                 }
596
597         } catch (e) {
598                 exception_error("selectionToggleMarked", e);
599         }
600 }
601
602 function cdmGetSelectedArticles() {
603         var sel_articles = new Array();
604         var container = document.getElementById("headlinesInnerContainer");
605
606         for (i = 0; i < container.childNodes.length; i++) {
607                 var child = container.childNodes[i];
608
609                 if (child.id.match("RROW-") && child.className.match("Selected")) {
610                         var c_id = child.id.replace("RROW-", "");
611                         sel_articles.push(c_id);
612                 }
613         }
614
615         return sel_articles;
616 }
617
618 // mode = all,none,unread
619 function cdmSelectArticles(mode) {
620         var container = document.getElementById("headlinesInnerContainer");
621
622         for (i = 0; i < container.childNodes.length; i++) {
623                 var child = container.childNodes[i];
624
625                 if (child.id.match("RROW-")) {
626                         var aid = child.id.replace("RROW-", "");
627
628                         var cb = document.getElementById("RCHK-" + aid);
629
630                         if (mode == "all") {
631                                 if (!child.className.match("Selected")) {
632                                         child.className = child.className + "Selected";
633                                         cb.checked = true;
634                                 }
635                         } else if (mode == "unread") {
636                                 if (child.className.match("Unread") && !child.className.match("Selected")) {
637                                         child.className = child.className + "Selected";
638                                         cb.checked = true;
639                                 }
640                         } else {
641                                 child.className = child.className.replace("Selected", "");
642                                 cb.checked = false;
643                         }
644                 }               
645         }
646 }
647
648 function catchupPage() {
649
650         if (document.getElementById("headlinesList")) {
651                 selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', true, 'Unread', true);
652                 selectionToggleUnread(false, false, 'viewCurrentFeed()', true);
653                 selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', false);
654         } else {
655                 cdmSelectArticles('all');
656                 selectionToggleUnread(true, false, 'viewCurrentFeed()', true)
657                 cdmSelectArticles('none');
658         }
659 }
660
661 function labelFromSearch(search, search_mode, match_on, feed_id, is_cat) {
662
663         if (!xmlhttp_ready(xmlhttp_rpc)) {
664                 printLockingError();
665         }
666
667         var title = prompt("Please enter label title:", "");
668
669         if (title) {
670
671                 var query = "backend.php?op=labelFromSearch&search=" + param_escape(search) +
672                         "&smode=" + param_escape(search_mode) + "&match=" + param_escape(match_on) +
673                         "&feed=" + param_escape(feed_id) + "&is_cat=" + param_escape(is_cat) + 
674                         "&title=" + param_escape(title);
675
676                 debug("LFS: " + query);
677         
678                 xmlhttp_rpc.open("GET", query, true);
679                 xmlhttp_rpc.onreadystatechange=dlg_frefresh_callback;
680                 xmlhttp_rpc.send(null);
681         }
682
683 }
684
685 function editArticleTags(id, feed_id, cdm_enabled) {
686         _tag_active_post_id = id;
687         _tag_active_feed_id = feed_id;
688         _tag_active_cdm = cdm_enabled;
689
690         cache_invalidate(id);
691
692         try {
693                 _tag_cdm_scroll = document.getElementById("headlinesInnerContainer").scrollTop;
694         } catch (e) { }
695         displayDlg('editArticleTags', id);
696 }
697
698
699 function tag_saved_callback() {
700         if (xmlhttp_rpc.readyState == 4) {
701                 try {
702                         debug("in tag_saved_callback");
703
704                         closeInfoBox();
705                         notify("");
706
707                         if (tagsAreDisplayed()) {
708                                 _reload_feedlist_after_view = true;
709                         }
710
711                         if (!_tag_active_cdm) {
712                                 if (active_post_id == _tag_active_post_id) {
713                                         debug("reloading current article");
714                                         view(_tag_active_post_id, _tag_active_feed_id);                 
715                                 }
716                         } else {
717                                 debug("reloading current feed");
718                                 viewCurrentFeed();
719                         }
720
721                 } catch (e) {
722                         exception_error("catchup_callback", e);
723                 }
724         }
725 }
726
727 function editTagsSave() {
728
729         if (!xmlhttp_ready(xmlhttp_rpc)) {
730                 printLockingError();
731         }
732
733         notify_progress("Saving article tags...");
734
735         var form = document.forms["tag_edit_form"];
736
737         var query = Form.serialize("tag_edit_form");
738
739         query = "backend.php?op=rpc&subop=setArticleTags&" + query;
740
741         debug(query);
742
743         xmlhttp_rpc.open("GET", query, true);                   
744         xmlhttp_rpc.onreadystatechange=tag_saved_callback;
745         xmlhttp_rpc.send(null);
746
747 }
748
749 function editTagsInsert() {
750         try {
751
752                 var form = document.forms["tag_edit_form"];
753
754                 var found_tags = form.found_tags;
755                 var tags_str = form.tags_str;
756
757                 var tag = found_tags[found_tags.selectedIndex].value;
758
759                 if (tags_str.value.length > 0 && 
760                                 tags_str.value.lastIndexOf(", ") != tags_str.value.length - 2) {
761
762                         tags_str.value = tags_str.value + ", ";
763                 }
764
765                 tags_str.value = tags_str.value + tag + ", ";
766
767                 found_tags.selectedIndex = 0;
768                 
769         } catch (e) {
770                 exception_error(e, "editTagsInsert");
771         }
772 }
773
774 function cdmWatchdog() {
775
776         try {
777
778                 var ctr = document.getElementById("headlinesInnerContainer");
779
780                 if (!ctr) return;
781
782                 var ids = new Array();
783
784                 var e = ctr.firstChild;
785
786                 while (e) {
787                         if (e.className && e.className == "cdmArticleUnread" && e.id &&
788                                         e.id.match("RROW-")) {
789
790                                 // article fits in viewport OR article is longer than viewport and
791                                 // its bottom is visible
792
793                                 if (ctr.scrollTop <= e.offsetTop && e.offsetTop + e.offsetHeight <=
794                                                 ctr.scrollTop + ctr.offsetHeight) {
795
796 //                                      debug(e.id + " is visible " + e.offsetTop + "." + 
797 //                                              (e.offsetTop + e.offsetHeight) + " vs " + ctr.scrollTop + "." +
798 //                                              (ctr.scrollTop + ctr.offsetHeight));
799
800                                         ids.push(e.id.replace("RROW-", ""));
801
802                                 } else if (e.offsetHeight > ctr.offsetHeight &&
803                                                 e.offsetTop + e.offsetHeight >= ctr.scrollTop &&
804                                                 e.offsetTop + e.offsetHeight <= ctr.scrollTop + ctr.offsetHeight) {
805
806                                         ids.push(e.id.replace("RROW-", "")); 
807
808                                 }
809
810                                 // method 2: article bottom is visible and is in upper 1/2 of the viewport
811
812 /*                              if (e.offsetTop + e.offsetHeight >= ctr.scrollTop &&
813                                                 e.offsetTop + e.offsetHeight <= ctr.scrollTop + ctr.offsetHeight/2) {
814
815                                         ids.push(e.id.replace("RROW-", "")); 
816
817                                 } */
818
819                         }
820
821                         e = e.nextSibling;
822                 }
823
824                 debug("cdmWatchdog, ids= " + ids.toString());
825
826                 if (ids.length > 0 && xmlhttp_ready(xmlhttp_rpc)) {
827
828                         for (var i = 0; i < ids.length; i++) {
829                                 var e = document.getElementById("RROW-" + ids[i]);
830                                 if (e) {
831                                         e.className = e.className.replace("Unread", "");
832                                 }
833                         }
834
835                         var query = "backend.php?op=rpc&subop=catchupSelected&ids=" +
836                                 param_escape(ids.toString()) + "&cmode=0";
837
838                         xmlhttp_rpc.open("GET", query, true);
839                         xmlhttp_rpc.onreadystatechange=all_counters_callback;
840                         xmlhttp_rpc.send(null); 
841
842                 }
843
844                 _cdm_wd_timeout = window.setTimeout("cdmWatchdog()", 4000);
845
846         } catch (e) {
847                 exception_error(e, "cdmWatchdog");
848         }
849
850 }
851
852
853 function cache_inject(id, article) {
854         if (!cache_check(id)) {
855                 debug("cache_article: miss: " + id);
856
857                 var cache_obj = new Array();
858
859                 cache_obj["id"] = id;
860                 cache_obj["data"] = article;
861
862                 article_cache.push(cache_obj);
863
864         } else {
865                 debug("cache_article: hit: " + id);
866         }
867 }
868
869 function cache_find(id) {
870         for (var i = 0; i < article_cache.length; i++) {
871                 if (article_cache[i]["id"] == id) {
872                         return article_cache[i]["data"];
873                 }
874         }
875         return false;
876 }
877
878 function cache_check(id) {
879         for (var i = 0; i < article_cache.length; i++) {
880                 if (article_cache[i]["id"] == id) {
881                         return true;
882                 }
883         }
884         return false;
885 }
886
887 function cache_expire() {
888         while (article_cache.length > 20) {
889                 article_cache.shift();
890         }
891 }
892
893 function cache_invalidate(id) {
894         var i = 0
895
896         try {   
897
898                 while (i < article_cache.length) {
899                         if (article_cache[i]["id"] == id) {
900                                 debug("cache_invalidate: removed id " + id);
901                                 article_cache.splice(i, 1);
902                                 return true;
903                         }
904                         i++;
905                 }
906                 debug("cache_invalidate: id not found: " + id);
907                 return false;
908         } catch (e) {
909                 exception_error("cache_invalidate", e);
910         }
911 }