]> git.wh0rd.org Git - tt-rss.git/blob - viewfeed.js
request counters asynchronously after viewfeed has succeeded but w/o counters block
[tt-rss.git] / viewfeed.js
1 var active_post_id = false;
2 var last_article_view = false;
3 var active_real_feed_id = false;
4
5 var _tag_active_post_id = false;
6 var _tag_active_feed_id = false;
7 var _tag_active_cdm = false;
8
9 // FIXME: kludge, to restore scrollTop after tag editor terminates
10 var _tag_cdm_scroll = false;
11
12 // FIXME: kludges, needs proper implementation
13 var _reload_feedlist_after_view = false;
14
15 var _cdm_wd_timeout = false;
16 var _cdm_wd_vishist = new Array();
17
18 var article_cache = new Array();
19
20 var vgroup_last_feed = false;
21 var post_under_pointer = false;
22
23 function catchup_callback() {
24         if (xmlhttp_rpc.readyState == 4) {
25                 try {
26                         debug("catchup_callback");
27                         notify("");                     
28                         all_counters_callback2(xmlhttp_rpc);
29                         if (_catchup_callback_func) {
30                                 setTimeout(_catchup_callback_func, 10); 
31                         }
32                 } catch (e) {
33                         exception_error("catchup_callback", e);
34                 }
35         }
36 }
37
38 function catchup_callback2(transport, callback) {
39         try {
40                 debug("catchup_callback2 " + transport + ", " + callback);
41                 notify("");                     
42                 all_counters_callback2(transport);
43                 if (callback) {
44                         setTimeout(callback, 10);       
45                 }
46         } catch (e) {
47                 exception_error("catchup_callback2", e);
48         }
49 }
50
51 function clean_feed_selections() {
52         try {
53                 var feeds = document.getElementById("feedList").getElementsByTagName("LI");
54
55                 for (var i = 0; i < feeds.length; i++) {
56                         if (feeds[i].id && feeds[i].id.match("FEEDR-")) {
57                                 feeds[i].className = feeds[i].className.replace("Selected", "");
58                         }                       
59                 }
60         } catch (e) {
61                 exception_error("clean_feed_selections", e);
62         }
63 }
64
65 function headlines_callback2(transport, active_feed_id, is_cat, feed_cur_page) {
66         try {
67
68                 loading_set_progress(100);
69
70                 debug("headlines_callback2 [page=" + feed_cur_page + "]");
71
72                 clean_feed_selections();
73
74                 setActiveFeedId(active_feed_id);
75                 
76                 if (is_cat != undefined) {
77                         active_feed_is_cat = is_cat;
78                 }
79         
80                 if (!is_cat) {
81                         var feedr = document.getElementById("FEEDR-" + active_feed_id);
82                         if (feedr && !feedr.className.match("Selected")) {      
83                                 feedr.className = feedr.className + "Selected";
84                         } 
85                 }
86         
87                 var f = document.getElementById("headlines-frame");
88                 try {
89                         if (feed_cur_page == 0) { 
90                                 debug("resetting headlines scrollTop");
91                                 f.scrollTop = 0; 
92                         }
93                 } catch (e) { };
94         
95                 if (transport.responseXML) {
96                         var headlines = transport.responseXML.getElementsByTagName("headlines")[0];
97                         var headlines_count_obj = transport.responseXML.getElementsByTagName("headlines-count")[0];
98                         var headlines_unread_obj = transport.responseXML.getElementsByTagName("headlines-unread")[0];
99                         var disable_cache_obj = transport.responseXML.getElementsByTagName("disable-cache")[0];
100
101                         var vgroup_last_feed_obj =  transport.responseXML.getElementsByTagName("vgroup-last-feed")[0];
102
103                         var headlines_count = headlines_count_obj.getAttribute("value");
104                         var headlines_unread = headlines_unread_obj.getAttribute("value");
105                         var disable_cache = disable_cache_obj.getAttribute("value") != "0";
106
107                         vgroup_last_feed = vgroup_last_feed_obj.getAttribute("value");
108
109                         if (headlines_count == 0) {
110                                 _infscroll_disable = 1;
111                         } else {
112                                 _infscroll_disable = 0;
113                         }
114
115                         var counters = transport.responseXML.getElementsByTagName("counters")[0];
116                         var articles = transport.responseXML.getElementsByTagName("article");
117                         var runtime_info = transport.responseXML.getElementsByTagName("runtime-info");
118         
119                         if (feed_cur_page == 0) {
120                                 if (headlines) {
121                                         f.innerHTML = headlines.firstChild.nodeValue;
122
123                                         var cache_prefix = "";
124
125                                         if (is_cat) {
126                                                 cache_prefix = "C:";
127                                         } else {
128                                                 cache_prefix = "F:";
129                                         }
130
131                                         cache_invalidate(cache_prefix + active_feed_id);
132
133                                         if (!disable_cache) {
134                                                 cache_inject(cache_prefix + active_feed_id,
135                                                         headlines.firstChild.nodeValue, headlines_unread);
136                                         }
137
138                                 } else {
139                                         debug("headlines_callback: returned no data");
140                                 f.innerHTML = "<div class='whiteBox'>" + __('Could not update headlines (missing XML data)') + "</div>";
141         
142                                 }
143                         } else {
144                                 if (headlines) {
145                                         if (headlines_count > 0) {
146                                                 debug("adding some more headlines...");
147         
148                                                 var c = document.getElementById("headlinesList");
149                 
150                                                 if (!c) {
151                                                         c = document.getElementById("headlinesInnerContainer");
152                                                 }
153
154                                                 var ids = getSelectedArticleIds2();
155         
156                                                 c.innerHTML = c.innerHTML + headlines.firstChild.nodeValue;
157
158                                                 debug("restore selected ids: " + ids);
159
160                                                 for (var i = 0; i < ids.length; i++) {
161                                                         markHeadline(ids[i]);
162                                                 }
163
164                                                 subtoolbarSearch();
165
166                                         } else {
167                                                 debug("no new headlines received");
168                                         }
169                                 } else {
170                                         debug("headlines_callback: returned no data");
171                                         notify_error("Error while trying to load more headlines");      
172                                 }
173
174                         }
175         
176                         if (articles) {
177                                 for (var i = 0; i < articles.length; i++) {
178                                         var a_id = articles[i].getAttribute("id");
179                                         debug("found id: " + a_id);
180                                         cache_inject(a_id, articles[i].firstChild.nodeValue);
181                                 }
182                         } else {
183                                 debug("no cached articles received");
184                         }
185         
186                         if (counters) {
187                                 debug("parsing piggybacked counters: " + counters);
188                                 parse_counters(counters, false);
189                         } else {
190                                 debug("counters container not found in reply, requesting...");
191                                 request_counters();
192                         }
193         
194                         if (runtime_info) {
195                                 debug("parsing runtime info: " + runtime_info[0]);
196                                 parse_runtime_info(runtime_info[0]);
197                         } else {
198                                 debug("counters container not found in reply");
199                         }
200         
201                 } else {
202                         debug("headlines_callback: returned no XML object");
203                         f.innerHTML = "<div class='whiteBox'>" + __('Could not update headlines (missing XML object)') + "</div>";
204                 }
205         
206                 if (typeof correctPNG != 'undefined') {
207                         correctPNG();
208                 }
209         
210                 if (_cdm_wd_timeout) window.clearTimeout(_cdm_wd_timeout);
211         
212                 if (!document.getElementById("headlinesList") && 
213                                 getInitParam("cdm_auto_catchup") == 1) {
214                         debug("starting CDM watchdog");
215                         _cdm_wd_timeout = window.setTimeout("cdmWatchdog()", 5000);
216                         _cdm_wd_vishist = new Array();
217                 } else {
218                         debug("not in CDM mode or watchdog disabled");
219                 }
220         
221                 if (_tag_cdm_scroll) {
222                         try {
223                                 document.getElementById("headlinesInnerContainer").scrollTop = _tag_cdm_scroll;
224                                 _tag_cdm_scroll = false;
225                                 debug("resetting headlinesInner scrollTop");
226         
227                         } catch (e) { }
228                 }
229         
230                 _feed_cur_page = feed_cur_page;
231                 _infscroll_request_sent = 0;
232
233                 notify("");
234
235                 remove_splash();
236
237         } catch (e) {
238                 exception_error("headlines_callback2", e);
239         }
240 }
241
242 function render_article(article) {
243         try {
244                 var f = document.getElementById("content-frame");
245                 try {
246                         f.scrollTop = 0;
247                 } catch (e) { };
248
249                 f.innerHTML = article;
250
251         } catch (e) {
252                 exception_error("render_article", e);
253         }
254 }
255
256 function showArticleInHeadlines(id) {
257
258         try {
259
260                 cleanSelected("headlinesList");
261         
262                 var crow = document.getElementById("RROW-" + id);
263
264                 if (!crow) return;
265
266                 var article_is_unread = crow.className.match("Unread");
267                         
268                 crow.className = crow.className.replace("Unread", "");
269
270                 selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', false);
271         
272                 var upd_img_pic = document.getElementById("FUPDPIC-" + id);
273
274                 var cache_prefix = "";
275                                 
276                 if (activeFeedIsCat()) {
277                         cache_prefix = "C:";
278                 } else {
279                         cache_prefix = "F:";
280                 }
281
282                 var view_mode = false;
283
284                 try {
285                         view_mode = document.forms['main_toolbar_form'].view_mode;      
286                         view_mode = view_mode[view_mode.selectedIndex].value;
287                 } catch (e) {
288                         exception_error("showArticleInHeadlines/viewmode", e, true);
289                 }
290
291                 if (upd_img_pic && upd_img_pic.src.match("updated.png")) {
292                         upd_img_pic.src = "images/blank_icon.gif";
293
294                         cache_invalidate(cache_prefix + getActiveFeedId());
295
296                         cache_inject(cache_prefix + getActiveFeedId(),
297                                 document.getElementById("headlines-frame").innerHTML,
298                                 get_feed_unread(getActiveFeedId()));
299
300                 } else if (article_is_unread && view_mode == "all_articles") {
301
302                         cache_invalidate(cache_prefix + getActiveFeedId());
303
304                         cache_inject(cache_prefix + getActiveFeedId(),
305                                 document.getElementById("headlines-frame").innerHTML,
306                                 get_feed_unread(getActiveFeedId())-1);
307
308                 } else if (article_is_unread) {
309                         cache_invalidate(cache_prefix + getActiveFeedId());
310                 }
311
312                 markHeadline(id);
313
314         } catch (e) {
315                 exception_error("showArticleInHeadlines", e);
316         }
317 }
318
319 function article_callback2(transport, id, feed_id) {
320         try {
321                 debug("article_callback2 " + id);
322
323                 if (transport.responseXML) {
324
325                         active_real_feed_id = feed_id;
326                         active_post_id = id; 
327
328                         //showArticleInHeadlines(id);   
329
330                         var reply = transport.responseXML.firstChild.firstChild;
331
332                         var articles = transport.responseXML.getElementsByTagName("article");
333
334                         for (var i = 0; i < articles.length; i++) {
335                                 var a_id = articles[i].getAttribute("id");
336
337                                 debug("found id: " + a_id);
338
339                                 if (a_id == active_post_id) {
340                                         debug("active article, rendering...");                                  
341                                         render_article(articles[i].firstChild.nodeValue);
342                                 }
343
344                                 cache_inject(a_id, articles[i].firstChild.nodeValue);
345                         }
346                 
347                 } else {
348                         debug("article_callback: returned no XML object");
349                         var f = document.getElementById("content-frame");
350                         f.innerHTML = "<div class='whiteBox'>" + __('Could not display article (missing XML object)') + "</div>";
351                 }
352
353                 var date = new Date();
354                 last_article_view = date.getTime() / 1000;
355
356                 if (typeof correctPNG != 'undefined') {
357                         correctPNG();
358                 }
359
360                 if (_reload_feedlist_after_view) {
361                         setTimeout('updateFeedList(false, false)', 50);                 
362                         _reload_feedlist_after_view = false;
363                 } else {
364                         var counters = transport.responseXML.getElementsByTagName("counters")[0];
365
366                         if (counters) {
367                                 debug("parsing piggybacked counters: " + counters);
368                                 parse_counters(counters, false);
369                         } else {
370                                 debug("counters container not found in reply");
371                         }
372                 }
373
374                 notify("");
375         } catch (e) {
376                 exception_error("article_callback2", e);
377         }
378 }
379
380 function view(id, feed_id, skip_history) {
381         
382         try {
383                 debug("loading article: " + id + "/" + feed_id);
384         
385                 var cached_article = cache_find(id);
386
387                 debug("cache check result: " + (cached_article != false));
388         
389                 enableHotkeys();
390         
391                 //setActiveFeedId(feed_id);
392
393                 var query = "backend.php?op=view&id=" + param_escape(id) +
394                         "&feed=" + param_escape(feed_id);
395
396                 var date = new Date();
397
398                 var neighbor_ids = getRelativePostIds(active_post_id);
399
400                 /* only request uncached articles */
401
402                 var cids_to_request = Array();
403
404                 for (var i = 0; i < neighbor_ids.length; i++) {
405                         if (!cache_check(neighbor_ids[i])) {
406                                 cids_to_request.push(neighbor_ids[i]);
407                         }
408                 }
409
410                 debug("additional ids: " + cids_to_request.toString());                 
411
412                 /* additional info for piggyback counters */
413
414                 if (tagsAreDisplayed()) {
415                         query = query + "&omode=lt";
416                 } else {
417                         query = query + "&omode=flc";
418                 }
419
420                 var date = new Date();
421                 var timestamp = Math.round(date.getTime() / 1000);
422                 query = query + "&ts=" + timestamp;
423
424                 query = query + "&cids=" + cids_to_request.toString();
425
426                 var crow = document.getElementById("RROW-" + id);
427                 var article_is_unread = crow.className.match("Unread");
428
429                 showArticleInHeadlines(id);
430
431                 if (!cached_article) {
432
433                         notify_progress("Loading, please wait...");
434
435                 } else if (cached_article && article_is_unread) {
436
437                         query = query + "&mode=prefetch";
438
439                         render_article(cached_article);
440
441                 } else if (cached_article) {
442
443                         query = query + "&mode=prefetch_old";
444                         render_article(cached_article);
445                 }
446
447                 cache_expire();
448
449                 new Ajax.Request(query, {
450                         onComplete: function(transport) { 
451                                 article_callback2(transport, id, feed_id); 
452                         } });
453
454         } catch (e) {
455                 exception_error("view", e);
456         }
457 }
458
459 function tMark(id) {
460         return toggleMark(id);
461 }
462
463 function tPub(id) {
464         return togglePub(id);
465 }
466
467 function tMark_afh_off(effect) {
468         try {
469                 var elem = effect.effects[0].element;
470
471                 debug("tMark_afh_off : " + elem.id);
472
473                 if (elem) {
474                         elem.src = elem.src.replace("mark_set", "mark_unset");
475                         elem.alt = __("Star article");
476                         Element.show(elem);
477                 }
478
479         } catch (e) {
480                 exception_error("tMark_afh_off", e);
481         }
482 }
483
484 function tPub_afh_off(effect) {
485         try {
486                 var elem = effect.effects[0].element;
487
488                 debug("tPub_afh_off : " + elem.id);
489
490                 if (elem) {
491                         elem.src = elem.src.replace("pub_set", "pub_unset");
492                         elem.alt = __("Publish article");
493                         Element.show(elem);
494                 }
495
496         } catch (e) {
497                 exception_error("tPub_afh_off", e);
498         }
499 }
500
501 function toggleMark(id, client_only, no_effects) {
502
503         try {
504
505                 var query = "backend.php?op=rpc&id=" + id + "&subop=mark";
506         
507                 query = query + "&afid=" + getActiveFeedId();
508         
509                 if (tagsAreDisplayed()) {
510                         query = query + "&omode=tl";
511                 } else {
512                         query = query + "&omode=flc";
513                 }
514         
515                 var mark_img = document.getElementById("FMPIC-" + id);
516                 var vfeedu = document.getElementById("FEEDU--1");
517                 var crow = document.getElementById("RROW-" + id);
518         
519                 if (mark_img.src.match("mark_unset")) {
520                         mark_img.src = mark_img.src.replace("mark_unset", "mark_set");
521                         mark_img.alt = __("Unstar article");
522                         query = query + "&mark=1";
523
524                 } else {
525                         //mark_img.src = "images/mark_unset.png";
526                         mark_img.alt = __("Please wait...");
527                         query = query + "&mark=0";
528         
529                         if (document.getElementById("headlinesList") && !no_effects) {
530                                 Effect.Puff(mark_img, {duration : 0.25, afterFinish: tMark_afh_off});
531                         } else { 
532                                 mark_img.src = mark_img.src.replace("mark_set", "mark_unset");
533                                 mark_img.alt = __("Star article");
534                         }
535                 }
536
537                 if (!client_only) {
538                         debug(query);
539
540                         new Ajax.Request(query, {
541                                 onComplete: function(transport) { 
542                                         all_counters_callback2(transport); 
543                                 } });
544
545                 }
546
547         } catch (e) {
548                 exception_error("toggleMark", e);
549         }
550 }
551
552 function togglePub(id, client_only, no_effects) {
553
554         try {
555
556                 var query = "backend.php?op=rpc&id=" + id + "&subop=publ";
557         
558                 query = query + "&afid=" + getActiveFeedId();
559         
560                 if (tagsAreDisplayed()) {
561                         query = query + "&omode=tl";
562                 } else {
563                         query = query + "&omode=flc";
564                 }
565         
566                 var mark_img = document.getElementById("FPPIC-" + id);
567                 var vfeedu = document.getElementById("FEEDU--2");
568                 var crow = document.getElementById("RROW-" + id);
569         
570                 if (mark_img.src.match("pub_unset")) {
571                         mark_img.src = mark_img.src.replace("pub_unset", "pub_set");
572                         mark_img.alt = __("Unpublish article");
573                         query = query + "&pub=1";
574         
575
576                 } else {
577                         //mark_img.src = "images/pub_unset.png";
578                         mark_img.alt = __("Please wait...");
579                         query = query + "&pub=0";
580         
581                         if (document.getElementById("headlinesList") && !no_effects) {
582                                 Effect.Puff(mark_img, {duration : 0.25, afterFinish: tPub_afh_off});
583                         } else { 
584                                 mark_img.src = mark_img.src.replace("pub_set", "pub_unset");
585                                 mark_img.alt = __("Publish article");
586                         }
587                 }
588
589                 if (!client_only) {
590                         new Ajax.Request(query, {
591                                 onComplete: function(transport) { 
592                                         all_counters_callback2(transport); 
593                                 } });
594                 }
595
596         } catch (e) {
597
598                 exception_error("togglePub", e);
599         }
600 }
601
602 function correctHeadlinesOffset(id) {
603         
604         try {
605
606                 var hlist = document.getElementById("headlinesList");
607                 var container = document.getElementById("headlinesInnerContainer");
608                 var row = document.getElementById("RROW-" + id);
609         
610                 var viewport = container.offsetHeight;
611         
612                 var rel_offset_top = row.offsetTop - container.scrollTop;
613                 var rel_offset_bottom = row.offsetTop + row.offsetHeight - container.scrollTop;
614         
615                 debug("Rtop: " + rel_offset_top + " Rbtm: " + rel_offset_bottom);
616                 debug("Vport: " + viewport);
617
618                 if (rel_offset_top <= 0 || rel_offset_top > viewport) {
619                         container.scrollTop = row.offsetTop;
620                 } else if (rel_offset_bottom > viewport) {
621
622                         /* doesn't properly work with Opera in some cases because
623                                 Opera fucks up element scrolling */
624
625                         container.scrollTop = row.offsetTop + row.offsetHeight - viewport;              
626                 } 
627
628         } catch (e) {
629                 exception_error("correctHeadlinesOffset", e);
630         }
631
632 }
633
634 function moveToPost(mode) {
635
636         try {
637
638                 var rows;
639
640                 if (isCdmMode()) {
641                         rows = cdmGetVisibleArticles();
642                 } else {
643                         rows = getVisibleHeadlineIds();
644                 }
645                 
646                 var prev_id = false;
647                 var next_id = false;
648                 
649                 if (!document.getElementById('RROW-' + active_post_id)) {
650                         active_post_id = false;
651                 }
652                 
653                 if (active_post_id == false) {
654                         next_id = getFirstVisibleHeadlineId();
655                         prev_id = getLastVisibleHeadlineId();
656                 } else {        
657                         for (var i = 0; i < rows.length; i++) {
658                                 if (rows[i] == active_post_id) {
659                                         prev_id = rows[i-1];
660                                         next_id = rows[i+1];                    
661                                 }
662                         }
663                 }
664                 
665                 if (mode == "next") {
666                         if (next_id) {
667                                 if (isCdmMode()) {
668         
669                                         if (!cdmArticleIsActuallyVisible(next_id)) {
670                                                 cdmScrollToArticleId(next_id);
671                                         }
672                                         cdmSelectArticles("none");
673                                         toggleUnread(next_id, 0, true);
674                                         toggleSelected(next_id);
675
676                                 } else {
677                                         correctHeadlinesOffset(next_id);
678                                         view(next_id, getActiveFeedId());
679                                 }
680                         }
681                 }
682                 
683                 if (mode == "prev") {
684                         if (prev_id) {
685                                 if (isCdmMode()) {
686                                         cdmScrollToArticleId(prev_id);
687                                         cdmSelectArticles("none");
688                                         toggleUnread(prev_id, 0, true);
689                                         toggleSelected(prev_id);
690                                 } else {
691                                         correctHeadlinesOffset(prev_id);
692                                         view(prev_id, getActiveFeedId());
693                                 }
694                         }
695                 } 
696
697         } catch (e) {
698                 exception_error("moveToPost", e);
699         }
700 }
701
702 function toggleSelected(id) {
703         try {
704         
705                 var cb = document.getElementById("RCHK-" + id);
706
707                 var row = document.getElementById("RROW-" + id);
708                 if (row) {
709                         var nc = row.className;
710                         
711                         if (!nc.match("Selected")) {
712                                 nc = nc + "Selected";
713                                 if (cb) {
714                                         cb.checked = true;
715                                 }
716
717                                 // In CDM basically last selected article == active article
718                                 if (isCdmMode()) active_post_id = id;
719                         } else {
720                                 nc = nc.replace("Selected", "");
721                                 if (cb) {
722                                         cb.checked = false;
723                                 }
724
725                         }
726
727                         row.className = nc;
728                 }
729         } catch (e) {
730                 exception_error("toggleSelected", e);
731         }
732 }
733
734 function toggleUnread_afh(effect) {
735         try {
736
737                 var elem = effect.element;
738                 elem.style.backgroundColor = "";
739
740         } catch (e) {
741                 exception_error("toggleUnread_afh", e);
742         }
743
744
745 function toggleUnread(id, cmode, effect) {
746         try {
747         
748                 var row = document.getElementById("RROW-" + id);
749                 if (row) {
750                         var nc = row.className;
751                         var is_selected = row.className.match("Selected");
752                         nc = nc.replace("Unread", "");
753                         nc = nc.replace("Selected", "");
754
755                         // since we are removing selection from the object, uncheck
756                         // corresponding checkbox
757
758                         var cb = document.getElementById("RCHK-" + id);
759                         if (cb) {
760                                 cb.checked = false;
761                         }
762
763                         // NOTE: I'm not sure that resetting selection here is a feature -fox
764
765                         if (cmode == undefined || cmode == 2) {
766                                 if (row.className.match("Unread")) {
767                                         row.className = nc;
768
769                                         if (effect) {
770                                                 new Effect.Highlight(row, {duration: 1, startcolor: "#fff7d5",
771                                                         afterFinish: toggleUnread_afh,
772                                                         queue: { position:'end', scope: 'TMRQ-' + id, limit: 1 } } );
773                                         } 
774
775                                 } else {
776                                         row.className = nc + "Unread";
777                                 }
778                         } else if (cmode == 0) {
779                                 row.className = nc;
780
781                                 if (effect) {
782                                         new Effect.Highlight(row, {duration: 1, startcolor: "#fff7d5",
783                                                 afterFinish: toggleUnread_afh,
784                                                 queue: { position:'end', scope: 'TMRQ-' + id, limit: 1 } } );
785                                 } 
786                         } else if (cmode == 1) {
787                                 row.className = nc + "Unread";
788                         }
789
790                         // Disable unmarking as selected for the time being (16.05.08) -fox
791                         if (is_selected) row.className = row.className + "Selected";
792
793                         if (cmode == undefined) cmode = 2;
794
795                         var query = "backend.php?op=rpc&subop=catchupSelected&ids=" +
796                                 param_escape(id) + "&cmode=" + param_escape(cmode);
797
798 //                      notify_progress("Loading, please wait...");
799
800                         new Ajax.Request(query, {
801                                 onComplete: function(transport) { 
802                                         all_counters_callback2(transport); 
803                                 } });
804
805                 }
806
807
808         } catch (e) {
809                 exception_error("toggleUnread", e);
810         }
811 }
812
813 function selectionToggleUnread(cdm_mode, set_state, callback_func, no_error) {
814         try {
815                 var rows;
816
817                 if (cdm_mode) {
818                         rows = cdmGetSelectedArticles();
819                 } else {        
820                         rows = getSelectedTableRowIds("headlinesList", "RROW", "RCHK");
821                 }
822
823                 if (rows.length == 0 && !no_error) {
824                         alert(__("No articles are selected."));
825                         return;
826                 }
827
828                 for (i = 0; i < rows.length; i++) {
829                         var row = document.getElementById("RROW-" + rows[i]);
830                         if (row) {
831                                 var nc = row.className;
832                                 nc = nc.replace("Unread", "");
833                                 nc = nc.replace("Selected", "");
834
835                                 if (set_state == undefined) {
836                                         if (row.className.match("Unread")) {
837                                                 row.className = nc + "Selected";
838                                         } else {
839                                                 row.className = nc + "UnreadSelected";
840                                         }
841                                 }
842
843                                 if (set_state == false) {
844                                         row.className = nc + "Selected";
845                                 }
846
847                                 if (set_state == true) {
848                                         row.className = nc + "UnreadSelected";
849                                 }
850                         }
851                 }
852
853                 if (rows.length > 0) {
854
855                         var cmode = "";
856
857                         if (set_state == undefined) {
858                                 cmode = "2";
859                         } else if (set_state == true) {
860                                 cmode = "1";
861                         } else if (set_state == false) {
862                                 cmode = "0";
863                         }
864
865                         var query = "backend.php?op=rpc&subop=catchupSelected&ids=" +
866                                 param_escape(rows.toString()) + "&cmode=" + cmode;
867
868                         notify_progress("Loading, please wait...");
869
870                         new Ajax.Request(query, {
871                                 onComplete: function(transport) { 
872                                         catchup_callback2(transport, callback_func); 
873                                 } });
874
875                 }
876
877         } catch (e) {
878                 exception_error("selectionToggleUnread", e);
879         }
880 }
881
882 function selectionToggleMarked(cdm_mode) {
883         try {
884         
885                 var rows;
886                 
887                 if (cdm_mode) {
888                         rows = cdmGetSelectedArticles();
889                 } else {        
890                         rows = getSelectedTableRowIds("headlinesList", "RROW", "RCHK");
891                 }       
892
893                 if (rows.length == 0) {
894                         alert(__("No articles are selected."));
895                         return;
896                 }
897
898                 for (i = 0; i < rows.length; i++) {
899                         toggleMark(rows[i], true, true);
900                 }
901
902                 if (rows.length > 0) {
903
904                         var query = "backend.php?op=rpc&subop=markSelected&ids=" +
905                                 param_escape(rows.toString()) + "&cmode=2";
906
907                         query = query + "&afid=" + getActiveFeedId();
908
909 /*                      if (tagsAreDisplayed()) {
910                                 query = query + "&omode=tl";
911                         } else {
912                                 query = query + "&omode=flc";
913                         } */
914
915                         query = query + "&omode=lc";
916
917                         new Ajax.Request(query, {
918                                 onComplete: function(transport) { 
919                                         all_counters_callback2(transport); 
920                                 } });
921
922                 }
923
924         } catch (e) {
925                 exception_error("selectionToggleMarked", e);
926         }
927 }
928
929 function selectionTogglePublished(cdm_mode) {
930         try {
931         
932                 var rows;
933                 
934                 if (cdm_mode) {
935                         rows = cdmGetSelectedArticles();
936                 } else {        
937                         rows = getSelectedTableRowIds("headlinesList", "RROW", "RCHK");
938                 }       
939
940                 if (rows.length == 0) {
941                         alert(__("No articles are selected."));
942                         return;
943                 }
944
945                 for (i = 0; i < rows.length; i++) {
946                         togglePub(rows[i], true, true);
947                 }
948
949                 if (rows.length > 0) {
950
951                         var query = "backend.php?op=rpc&subop=publishSelected&ids=" +
952                                 param_escape(rows.toString()) + "&cmode=2";
953
954                         query = query + "&afid=" + getActiveFeedId();
955
956 /*                      if (tagsAreDisplayed()) {
957                                 query = query + "&omode=tl";
958                         } else {
959                                 query = query + "&omode=flc";
960                         } */
961
962                         query = query + "&omode=lc";
963
964                         new Ajax.Request(query, {
965                                 onComplete: function(transport) { 
966                                         all_counters_callback2(transport); 
967                                 } });
968
969                 }
970
971         } catch (e) {
972                 exception_error("selectionToggleMarked", e);
973         }
974 }
975
976 function cdmGetSelectedArticles() {
977         var sel_articles = new Array();
978         var container = document.getElementById("headlinesInnerContainer");
979
980         for (i = 0; i < container.childNodes.length; i++) {
981                 var child = container.childNodes[i];
982
983                 if (child.id.match("RROW-") && child.className.match("Selected")) {
984                         var c_id = child.id.replace("RROW-", "");
985                         sel_articles.push(c_id);
986                 }
987         }
988
989         return sel_articles;
990 }
991
992 function cdmGetVisibleArticles() {
993         var sel_articles = new Array();
994         var container = document.getElementById("headlinesInnerContainer");
995
996         if (!container) return sel_articles;
997
998         for (i = 0; i < container.childNodes.length; i++) {
999                 var child = container.childNodes[i];
1000
1001                 if (child.id.match("RROW-")) {
1002                         var c_id = child.id.replace("RROW-", "");
1003                         sel_articles.push(c_id);
1004                 }
1005         }
1006
1007         return sel_articles;
1008 }
1009
1010 function cdmGetUnreadArticles() {
1011         var sel_articles = new Array();
1012         var container = document.getElementById("headlinesInnerContainer");
1013
1014         for (i = 0; i < container.childNodes.length; i++) {
1015                 var child = container.childNodes[i];
1016
1017                 if (child.id.match("RROW-") && child.className.match("Unread")) {
1018                         var c_id = child.id.replace("RROW-", "");
1019                         sel_articles.push(c_id);
1020                 }
1021         }
1022
1023         return sel_articles;
1024 }
1025
1026
1027 // mode = all,none,unread
1028 function cdmSelectArticles(mode) {
1029         var container = document.getElementById("headlinesInnerContainer");
1030
1031         for (i = 0; i < container.childNodes.length; i++) {
1032                 var child = container.childNodes[i];
1033
1034                 if (child.id.match("RROW-")) {
1035                         var aid = child.id.replace("RROW-", "");
1036
1037                         var cb = document.getElementById("RCHK-" + aid);
1038
1039                         if (mode == "all") {
1040                                 if (!child.className.match("Selected")) {
1041                                         child.className = child.className + "Selected";
1042                                         cb.checked = true;
1043                                 }
1044                         } else if (mode == "unread") {
1045                                 if (child.className.match("Unread") && !child.className.match("Selected")) {
1046                                         child.className = child.className + "Selected";
1047                                         cb.checked = true;
1048                                 }
1049                         } else {
1050                                 child.className = child.className.replace("Selected", "");
1051                                 cb.checked = false;
1052                         }
1053                 }               
1054         }
1055 }
1056
1057 function catchupPage() {
1058
1059         var fn = getFeedName(getActiveFeedId(), active_feed_is_cat);
1060         
1061         var str = __("Mark all visible articles in %s as read?");
1062
1063         str = str.replace("%s", fn);
1064
1065         if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1066                 return;
1067         }
1068
1069         if (document.getElementById("headlinesList")) {
1070                 selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', true, 'Unread', true);
1071                 selectionToggleUnread(false, false, 'viewCurrentFeed()', true);
1072                 selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', false);
1073         } else {
1074                 cdmSelectArticles('all');
1075                 selectionToggleUnread(true, false, 'viewCurrentFeed()', true)
1076                 cdmSelectArticles('none');
1077         }
1078 }
1079
1080 function catchupSelection() {
1081
1082         try {
1083
1084                 var rows;
1085         
1086                 if (document.getElementById("headlinesList")) {
1087                         rows = getSelectedTableRowIds("headlinesList", "RROW", "RCHK");
1088                 } else {        
1089                         rows = cdmGetSelectedArticles();
1090                 }
1091         
1092                 if (rows.length == 0) {
1093                         alert(__("No articles are selected."));
1094                         return;
1095                 }
1096         
1097         
1098                 var fn = getFeedName(getActiveFeedId(), active_feed_is_cat);
1099                 
1100                 var str = __("Mark %d selected articles in %s as read?");
1101         
1102                 str = str.replace("%d", rows.length);
1103                 str = str.replace("%s", fn);
1104         
1105                 if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1106                         return;
1107                 }
1108         
1109                 if (document.getElementById("headlinesList")) {
1110                         selectionToggleUnread(false, false, 'viewCurrentFeed()', true);
1111         //              selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', false);
1112                 } else {
1113                         selectionToggleUnread(true, false, 'viewCurrentFeed()', true)
1114         //              cdmSelectArticles('none');
1115                 }
1116
1117         } catch (e) {
1118                 exception_error("catchupSelection", e);
1119         }
1120 }
1121
1122
1123 function labelFromSearch(search, search_mode, match_on, feed_id, is_cat) {
1124
1125         if (!xmlhttp_ready(xmlhttp_rpc)) {
1126                 printLockingError();
1127         }
1128
1129         var title = prompt(__("Please enter label title:"), "");
1130
1131         if (title) {
1132
1133                 var query = "backend.php?op=labelFromSearch&search=" + param_escape(search) +
1134                         "&smode=" + param_escape(search_mode) + "&match=" + param_escape(match_on) +
1135                         "&feed=" + param_escape(feed_id) + "&is_cat=" + param_escape(is_cat) + 
1136                         "&title=" + param_escape(title);
1137
1138                 debug("LFS: " + query);
1139
1140                 new Ajax.Request(query, {
1141                         onComplete: function(transport) {
1142                                         dlg_frefresh_callback(transport);
1143                                 } });
1144         }
1145 }
1146
1147 function editArticleTags(id, feed_id, cdm_enabled) {
1148         _tag_active_post_id = id;
1149         _tag_active_feed_id = feed_id;
1150         _tag_active_cdm = cdm_enabled;
1151
1152         cache_invalidate(id);
1153
1154         try {
1155                 _tag_cdm_scroll = document.getElementById("headlinesInnerContainer").scrollTop;
1156         } catch (e) { }
1157         displayDlg('editArticleTags', id);
1158 }
1159
1160
1161 function tag_saved_callback(transport) {
1162         try {
1163                 debug("in tag_saved_callback");
1164
1165                 closeInfoBox();
1166                 notify("");
1167
1168                 if (tagsAreDisplayed()) {
1169                         _reload_feedlist_after_view = true;
1170                 }
1171
1172                 if (!_tag_active_cdm) {
1173                         if (active_post_id == _tag_active_post_id) {
1174                                 debug("reloading current article");
1175                                 view(_tag_active_post_id, _tag_active_feed_id);                 
1176                         }
1177                 } else {
1178                         debug("reloading current feed");
1179                         viewCurrentFeed();
1180                 }
1181
1182         } catch (e) {
1183                 exception_error("catchup_callback", e);
1184         }
1185 }
1186
1187 function editTagsSave() {
1188
1189         notify_progress("Saving article tags...");
1190
1191         var form = document.forms["tag_edit_form"];
1192
1193         var query = Form.serialize("tag_edit_form");
1194
1195         query = "backend.php?op=rpc&subop=setArticleTags&" + query;
1196
1197         debug(query);
1198
1199         new Ajax.Request(query, {
1200                 onComplete: function(transport) {
1201                                 tag_saved_callback(transport);
1202                         } });
1203
1204 }
1205
1206 function editTagsInsert() {
1207         try {
1208
1209                 var form = document.forms["tag_edit_form"];
1210
1211                 var found_tags = form.found_tags;
1212                 var tags_str = form.tags_str;
1213
1214                 var tag = found_tags[found_tags.selectedIndex].value;
1215
1216                 if (tags_str.value.length > 0 && 
1217                                 tags_str.value.lastIndexOf(", ") != tags_str.value.length - 2) {
1218
1219                         tags_str.value = tags_str.value + ", ";
1220                 }
1221
1222                 tags_str.value = tags_str.value + tag + ", ";
1223
1224                 found_tags.selectedIndex = 0;
1225                 
1226         } catch (e) {
1227                 exception_error("editTagsInsert", e);
1228         }
1229 }
1230
1231 function cdmScrollViewport(where) {
1232         debug("cdmScrollViewport: " + where);
1233
1234         var ctr = document.getElementById("headlinesInnerContainer");
1235
1236         if (!ctr) return;
1237
1238         if (where == "bottom") {
1239                 ctr.scrollTop = ctr.scrollHeight;
1240         } else {
1241                 ctr.scrollTop = where;
1242         }
1243 }
1244
1245 function cdmArticleIsBelowViewport(id) {
1246         try {
1247                 var ctr = document.getElementById("headlinesInnerContainer");
1248                 var e = document.getElementById("RROW-" + id);
1249
1250                 if (!e || !ctr) return;
1251
1252                 // article starts below viewport
1253
1254                 if (ctr.scrollTop < e.offsetTop) {
1255                         return true;
1256                 } else {        
1257                         return false;
1258                 }
1259
1260         } catch (e) {
1261                 exception_error("cdmArticleIsVisible", e);
1262         }
1263 }
1264
1265 function cdmArticleIsAboveViewport(id) {
1266         try {
1267                 var ctr = document.getElementById("headlinesInnerContainer");
1268                 var e = document.getElementById("RROW-" + id);
1269
1270                 if (!e || !ctr) return;
1271
1272                 // article starts above viewport
1273
1274                 if (ctr.scrollTop > e.offsetTop + e.offsetHeight) {
1275                         return true;
1276                 } else {        
1277                         return false;
1278                 }
1279
1280         } catch (e) {
1281                 exception_error("cdmArticleIsVisible", e);
1282         }
1283 }
1284
1285 function cdmScrollToArticleId(id) {
1286         try {
1287                 var ctr = document.getElementById("headlinesInnerContainer");
1288                 var e = document.getElementById("RROW-" + id);
1289
1290                 if (!e || !ctr) return;
1291
1292                 ctr.scrollTop = e.offsetTop;
1293
1294         } catch (e) {
1295                 exception_error("cdmScrollToArticleId", e);
1296         }
1297 }
1298
1299 function cdmArticleIsActuallyVisible(id) {
1300         try {
1301                 var ctr = document.getElementById("headlinesInnerContainer");
1302                 var e = document.getElementById("RROW-" + id);
1303
1304                 if (!e || !ctr) return;
1305
1306                 // article fits in viewport OR article is longer than viewport and
1307                 // its bottom is visible
1308
1309                 if (ctr.scrollTop <= e.offsetTop && e.offsetTop + e.offsetHeight <=
1310                                 ctr.scrollTop + ctr.offsetHeight) {
1311
1312                         return true;
1313                 
1314                 } else if (e.offsetHeight > ctr.offsetHeight &&
1315                                 e.offsetTop + e.offsetHeight >= ctr.scrollTop &&
1316                                 e.offsetTop + e.offsetHeight <= ctr.scrollTop + ctr.offsetHeight) {
1317
1318                         return true;
1319
1320                 }
1321
1322                 return false;
1323
1324         } catch (e) {
1325                 exception_error("cdmArticleIsVisible", e);
1326         }
1327 }
1328
1329 function cdmWatchdog() {
1330
1331         try {
1332
1333                 var ctr = document.getElementById("headlinesInnerContainer");
1334
1335                 if (!ctr) return;
1336
1337                 var ids = new Array();
1338
1339                 var e = ctr.firstChild;
1340
1341                 while (e) {
1342                         if (e.className && e.className == "cdmArticleUnread" && e.id &&
1343                                         e.id.match("RROW-")) {
1344
1345                                 // article fits in viewport OR article is longer than viewport and
1346                                 // its bottom is visible
1347
1348                                 if (ctr.scrollTop <= e.offsetTop && e.offsetTop + e.offsetHeight <=
1349                                                 ctr.scrollTop + ctr.offsetHeight) {
1350
1351 //                                      debug(e.id + " is visible " + e.offsetTop + "." + 
1352 //                                              (e.offsetTop + e.offsetHeight) + " vs " + ctr.scrollTop + "." +
1353 //                                              (ctr.scrollTop + ctr.offsetHeight));
1354
1355                                         ids.push(e.id.replace("RROW-", ""));
1356
1357                                 } else if (e.offsetHeight > ctr.offsetHeight &&
1358                                                 e.offsetTop + e.offsetHeight >= ctr.scrollTop &&
1359                                                 e.offsetTop + e.offsetHeight <= ctr.scrollTop + ctr.offsetHeight) {
1360
1361                                         ids.push(e.id.replace("RROW-", "")); 
1362
1363                                 }
1364
1365                                 // method 2: article bottom is visible and is in upper 1/2 of the viewport
1366
1367 /*                              if (e.offsetTop + e.offsetHeight >= ctr.scrollTop &&
1368                                                 e.offsetTop + e.offsetHeight <= ctr.scrollTop + ctr.offsetHeight/2) {
1369
1370                                         ids.push(e.id.replace("RROW-", "")); 
1371
1372                                 } */
1373
1374                         }
1375
1376                         e = e.nextSibling;
1377                 }
1378
1379                 debug("cdmWatchdog, ids= " + ids.toString());
1380
1381                 if (ids.length > 0) {
1382
1383                         for (var i = 0; i < ids.length; i++) {
1384                                 var e = document.getElementById("RROW-" + ids[i]);
1385                                 if (e) {
1386                                         e.className = e.className.replace("Unread", "");
1387                                 }
1388                         }
1389
1390                         var query = "backend.php?op=rpc&subop=catchupSelected&ids=" +
1391                                 param_escape(ids.toString()) + "&cmode=0";
1392
1393                         new Ajax.Request(query, {
1394                                 onComplete: function(transport) { 
1395                                         all_counters_callback2(transport); 
1396                                 } });
1397
1398                 }
1399
1400                 _cdm_wd_timeout = window.setTimeout("cdmWatchdog()", 4000);
1401
1402         } catch (e) {
1403                 exception_error("cdmWatchdog", e);
1404         }
1405
1406 }
1407
1408
1409 function cache_inject(id, article, param) {
1410         if (!cache_check_param(id, param)) {
1411                 debug("cache_article: miss: " + id + " [p=" + param + "]");
1412
1413                 var cache_obj = new Array();
1414
1415                 cache_obj["id"] = id;
1416                 cache_obj["data"] = article;
1417                 cache_obj["param"] = param;
1418
1419                 article_cache.push(cache_obj);
1420
1421         } else {
1422                 debug("cache_article: hit: " + id + " [p=" + param + "]");
1423         }
1424 }
1425
1426 function cache_find(id) {
1427         for (var i = 0; i < article_cache.length; i++) {
1428                 if (article_cache[i]["id"] == id) {
1429                         return article_cache[i]["data"];
1430                 }
1431         }
1432         return false;
1433 }
1434
1435 function cache_find_param(id, param) {
1436         for (var i = 0; i < article_cache.length; i++) {
1437                 if (article_cache[i]["id"] == id && article_cache[i]["param"] == param) {
1438                         return article_cache[i]["data"];
1439                 }
1440         }
1441         return false;
1442 }
1443
1444 function cache_check(id) {
1445         for (var i = 0; i < article_cache.length; i++) {
1446                 if (article_cache[i]["id"] == id) {
1447                         return true;
1448                 }
1449         }
1450         return false;
1451 }
1452
1453 function cache_check_param(id, param) {
1454         for (var i = 0; i < article_cache.length; i++) {
1455
1456 //              debug("cache_check_param " + article_cache[i]["id"] + ":" + 
1457 //                      article_cache[i]["param"] + " vs " + id + ":" + param);
1458
1459                 if (article_cache[i]["id"] == id && article_cache[i]["param"] == param) {
1460                         return true;
1461                 }
1462         }
1463         return false;
1464 }
1465
1466 function cache_expire() {
1467         while (article_cache.length > 25) {
1468                 article_cache.shift();
1469         }
1470 }
1471
1472 function cache_empty() {
1473         article_cache = new Array();
1474 }
1475
1476 function cache_invalidate(id) {
1477         var i = 0
1478
1479         try {   
1480
1481                 while (i < article_cache.length) {
1482                         if (article_cache[i]["id"] == id) {
1483                                 debug("cache_invalidate: removed id " + id);
1484                                 article_cache.splice(i, 1);
1485                                 return true;
1486                         }
1487                         i++;
1488                 }
1489                 debug("cache_invalidate: id not found: " + id);
1490                 return false;
1491         } catch (e) {
1492                 exception_error("cache_invalidate", e);
1493         }
1494 }
1495
1496 function getActiveArticleId() {
1497         return active_post_id;
1498 }
1499
1500 function cdmClicked(elem) {
1501         try {
1502                 if (elem.id && elem.id.match("RROW-")) {
1503                         var id = elem.id.replace("RROW-", "");
1504                         active_post_id = id;
1505
1506                         cdmSelectArticles("none");
1507                         toggleUnread(id, 0, true);
1508                         toggleSelected(id);
1509
1510                 }
1511         } catch (e) {
1512                 exception_error("cdmClicked", e);
1513         } 
1514 }
1515
1516 function preload_article_callback(transport) {
1517         try {
1518                 if (transport.responseXML) {
1519                         var articles = transport.responseXML.getElementsByTagName("article");
1520
1521                         for (var i = 0; i < articles.length; i++) {
1522                                 var id = articles[i].getAttribute("id");
1523                                 if (!cache_check(id)) {
1524                                         cache_inject(id, articles[i].firstChild.nodeValue);                             
1525                                         debug("preloaded article: " + id);
1526                                 }
1527                         }
1528                 }
1529         } catch (e) {
1530                 exception_error("preload_article_callback", e);
1531         }
1532 }
1533
1534 function preloadArticleUnderPointer(id) {
1535         try {
1536                 if (post_under_pointer == id && !cache_check(id)) {
1537
1538                         debug("trying to preload article " + id);
1539
1540                         var neighbor_ids = getRelativePostIds(id, 1);
1541
1542                         /* only request uncached articles */
1543
1544                         var cids_to_request = Array();
1545
1546                         for (var i = 0; i < neighbor_ids.length; i++) {
1547                                 if (!cache_check(neighbor_ids[i])) {
1548                                         cids_to_request.push(neighbor_ids[i]);
1549                                 }
1550                         }
1551                         debug("additional ids: " + cids_to_request.toString());
1552
1553                         cids_to_request.push(id);
1554
1555                         var query = "backend.php?op=rpc&subop=getArticles&ids=" + 
1556                                 cids_to_request.toString();
1557                         new Ajax.Request(query, {
1558                                 onComplete: function(transport) { 
1559                                         preload_article_callback(transport);
1560                         } });
1561                 }
1562         } catch (e) {
1563                 exception_error("preloadArticleUnderPointer", e);
1564         }
1565 }
1566
1567 function postMouseIn(id) {
1568         try {
1569                 if (post_under_pointer != id) {
1570                         post_under_pointer = id;
1571                         if (!isCdmMode()) {
1572                                 window.setTimeout("preloadArticleUnderPointer(" + id + ")", 250);
1573                         }
1574                 }
1575
1576         } catch (e) {
1577                 exception_error("postMouseIn", e);
1578         }
1579 }
1580
1581 function postMouseOut(id) {
1582         try {
1583                 post_under_pointer = false;
1584         } catch (e) {
1585                 exception_error("postMouseOut", e);
1586         }
1587 }
1588
1589 function headlines_scroll_handler() {
1590         try {
1591
1592                 var e = document.getElementById("headlinesInnerContainer");
1593
1594                 // don't do infinite scrolling when Limit == All
1595
1596                 var toolbar_form = document.forms["main_toolbar_form"];
1597
1598                 var limit = toolbar_form.limit[toolbar_form.limit.selectedIndex];
1599                 if (limit.value != 0) {
1600                 
1601                         debug((e.scrollTop + e.offsetHeight) + " vs " + e.scrollHeight + " dis? " +
1602                                 _infscroll_disable);
1603
1604                         if (e.scrollTop + e.offsetHeight > e.scrollHeight - 100) {
1605                                 if (!_infscroll_disable) {
1606                                         debug("more cowbell!");
1607                                         viewNextFeedPage();
1608                                 }
1609                         }
1610                 }
1611
1612         } catch (e) {
1613                 exception_error("headlines_scroll_handler", e);
1614         }
1615 }
1616
1617 function catchupRelativeToArticle(below) {
1618
1619         try {
1620
1621                 if (!xmlhttp_ready(xmlhttp_rpc)) {
1622                         printLockingError();
1623                 }
1624         
1625                 if (!getActiveArticleId()) {
1626                         alert(__("No article is selected."));
1627                         return;
1628                 }
1629
1630                 var visible_ids;
1631
1632                 if (document.getElementById("headlinesList")) {
1633                         visible_ids = getVisibleHeadlineIds();
1634                 } else {
1635                         visible_ids = cdmGetVisibleArticles();
1636                 }
1637
1638                 var ids_to_mark = new Array();
1639
1640                 if (!below) {
1641                         for (var i = 0; i < visible_ids.length; i++) {
1642                                 if (visible_ids[i] != getActiveArticleId()) {
1643                                         var e = document.getElementById("RROW-" + visible_ids[i]);
1644
1645                                         if (e && e.className.match("Unread")) {
1646                                                 ids_to_mark.push(visible_ids[i]);
1647                                         }
1648                                 } else {
1649                                         break;
1650                                 }
1651                         }
1652                 } else {
1653                         for (var i = visible_ids.length-1; i >= 0; i--) {
1654                                 if (visible_ids[i] != getActiveArticleId()) {
1655                                         var e = document.getElementById("RROW-" + visible_ids[i]);
1656
1657                                         if (e && e.className.match("Unread")) {
1658                                                 ids_to_mark.push(visible_ids[i]);
1659                                         }
1660                                 } else {
1661                                         break;
1662                                 }
1663                         }
1664                 }
1665
1666                 if (ids_to_mark.length == 0) {
1667                         alert(__("No articles found to mark"));
1668                 } else {
1669                         var msg = __("Mark %d article(s) as read?").replace("%d", ids_to_mark.length);
1670
1671                         if (confirm(msg)) {
1672
1673                                 for (var i = 0; i < ids_to_mark.length; i++) {
1674                                         var e = document.getElementById("RROW-" + ids_to_mark[i]);
1675                                         e.className = e.className.replace("Unread", "");
1676                                 }
1677
1678                                 var query = "backend.php?op=rpc&subop=catchupSelected&ids=" +
1679                                         param_escape(ids_to_mark.toString()) + "&cmode=0";
1680
1681                                 new Ajax.Request(query, {
1682                                         onComplete: function(transport) { 
1683                                                 catchup_callback2(transport); 
1684                                         } });
1685
1686                         }
1687                 }
1688
1689         } catch (e) {
1690                 exception_error("catchupRelativeToArticle", e);
1691         }
1692 }
1693
1694 function cdmExpandArticle(a_id) {
1695         try {
1696                 var id = 'CICD-' + a_id;
1697
1698                 Effect.Appear(id, {duration : 0.5, 
1699                         beforeStart: function(effect) { 
1700                                 var h_id = 'CICH-' + a_id;
1701                                 var h_elem = document.getElementById(h_id);
1702                                 if (h_elem) { h_elem.style.display = "none"; }
1703
1704                                 toggleUnread(a_id, 0);
1705                         }});
1706
1707
1708         } catch (e) {
1709                 exception_error("appearBlockElementF", e);
1710         }
1711
1712 }
1713
1714 function fixHeadlinesOrder(ids) {
1715         try {
1716                 for (var i = 0; i < ids.length; i++) {
1717                         var e = document.getElementById("RROW-" + ids[i]);
1718
1719                         if (e) {
1720                                 if (i % 2 == 0) {
1721                                         e.className = e.className.replace("even", "odd");
1722                                 } else {
1723                                         e.className = e.className.replace("odd", "even");
1724                                 }
1725                         }
1726                 }
1727         } catch (e) {
1728                 exception_error("fixHeadlinesOrder", e);
1729         }
1730 }
1731
1732 function subtoolbarSearch() {
1733         try {
1734
1735                 var q = document.getElementById("subtoolbar_search_box");
1736                 
1737                 if (!q) return;
1738
1739                 q = q.value.toUpperCase();
1740
1741                 var ids = false;
1742                 var vis_ids = new Array();
1743
1744                 if (document.getElementById("headlinesList")) {
1745                         ids = getVisibleHeadlineIds();
1746                 } else {
1747                         ids = cdmGetVisibleArticles();
1748                 }
1749
1750                 for (var i = 0; i < ids.length; i++) {
1751                         var title = document.getElementById("RTITLE-" + ids[i]);
1752
1753                         if (title) {
1754                                 if (!title.innerHTML.toUpperCase().match(q)) {
1755                                         Element.hide(document.getElementById("RROW-" + ids[i]));
1756                                 } else {
1757                                         Element.show(document.getElementById("RROW-" + ids[i]));
1758                                         vis_ids.push(ids[i]);
1759                                 }
1760                         }
1761                 }
1762
1763                 fixHeadlinesOrder(vis_ids);
1764
1765         } catch (e) {
1766                 exception_error("subtoolbarSearch", e);
1767         } 
1768 }
1769
1770 function getArticleUnderPointer() {
1771         return post_under_pointer;
1772 }