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