]> git.wh0rd.org Git - tt-rss.git/blob - viewfeed.js
make main refetch cycle asynchronous
[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 function catchup_callback() {
21         if (xmlhttp_rpc.readyState == 4) {
22                 try {
23                         debug("catchup_callback");
24                         notify("");                     
25                         all_counters_callback();
26                         if (_catchup_callback_func) {
27                                 setTimeout(_catchup_callback_func, 10); 
28                         }
29                 } catch (e) {
30                         exception_error("catchup_callback", e);
31                 }
32         }
33 }
34
35 function catchup_callback2(transport, callback) {
36         try {
37                 debug("catchup_callback2 " + transport + ", " + callback);
38                 notify("");                     
39                 all_counters_callback2(transport);
40                 if (callback) {
41                         setTimeout(callback, 10);       
42                 }
43         } catch (e) {
44                 exception_error("catchup_callback2", e);
45         }
46 }
47
48 function headlines_callback() {
49         if (xmlhttp.readyState == 4) {
50                 debug("headlines_callback");
51                 var f = document.getElementById("headlines-frame");
52                 try {
53                         if (feed_cur_page == 0) { 
54                                 debug("resetting headlines scrollTop");
55                                 f.scrollTop = 0; 
56                         }
57                 } catch (e) { };
58
59                 if (xmlhttp.responseXML) {
60                         var headlines = xmlhttp.responseXML.getElementsByTagName("headlines")[0];
61                         var counters = xmlhttp.responseXML.getElementsByTagName("counters")[0];
62                         var articles = xmlhttp.responseXML.getElementsByTagName("article");
63                         var runtime_info = xmlhttp.responseXML.getElementsByTagName("runtime-info");
64
65                         if (feed_cur_page == 0) {
66                                 if (headlines) {
67                                         f.innerHTML = headlines.firstChild.nodeValue;
68                                 } else {
69                                         debug("headlines_callback: returned no data");
70                                 f.innerHTML = "<div class='whiteBox'>" + __('Could not update headlines (missing XML data)') + "</div>";
71         
72                                 }
73                         } else {
74                                 if (headlines) {
75                                         debug("adding some more headlines...");
76
77                                         var c = document.getElementById("headlinesList");
78
79                                         if (!c) {
80                                                 c = document.getElementById("headlinesInnerContainer");
81                                         }
82
83                                         c.innerHTML = c.innerHTML + headlines.firstChild.nodeValue;
84                                 } else {
85                                         debug("headlines_callback: returned no data");
86                                         notify_error("Error while trying to load more headlines");      
87                                 }
88
89                         }
90
91                         if (articles) {
92                                 for (var i = 0; i < articles.length; i++) {
93                                         var a_id = articles[i].getAttribute("id");
94                                         debug("found id: " + a_id);
95                                         cache_inject(a_id, articles[i].firstChild.nodeValue);
96                                 }
97                         } else {
98                                 debug("no cached articles received");
99                         }
100
101                         if (counters) {
102                                 debug("parsing piggybacked counters: " + counters);
103                                 parse_counters(counters, false);
104                         } else {
105                                 debug("counters container not found in reply");
106                         }
107
108                         if (runtime_info) {
109                                 debug("parsing runtime info: " + runtime_info[0]);
110                                 parse_runtime_info(runtime_info[0]);
111                         } else {
112                                 debug("counters container not found in reply");
113                         }
114
115                 } else {
116                         debug("headlines_callback: returned no XML object");
117                         f.innerHTML = "<div class='whiteBox'>" + __('Could not update headlines (missing XML object)') + "</div>";
118                 }
119
120                 if (typeof correctPNG != 'undefined') {
121                         correctPNG();
122                 }
123
124                 if (_cdm_wd_timeout) window.clearTimeout(_cdm_wd_timeout);
125
126                 if (!document.getElementById("headlinesList") && 
127                                 getInitParam("cdm_auto_catchup") == 1) {
128                         debug("starting CDM watchdog");
129                         _cdm_wd_timeout = window.setTimeout("cdmWatchdog()", 5000);
130                         _cdm_wd_vishist = new Array();
131                 } else {
132                         debug("not in CDM mode or watchdog disabled");
133                 }
134
135                 if (_tag_cdm_scroll) {
136                         try {
137                                 document.getElementById("headlinesInnerContainer").scrollTop = _tag_cdm_scroll;
138                                 _tag_cdm_scroll = false;
139                                 debug("resetting headlinesInner scrollTop");
140
141                         } catch (e) { }
142                 }
143
144                 notify("");
145         }
146 }
147
148 function render_article(article) {
149         try {
150                 var f = document.getElementById("content-frame");
151                 try {
152                         f.scrollTop = 0;
153                 } catch (e) { };
154
155                 f.innerHTML = article;
156
157         } catch (e) {
158                 exception_error("render_article", e);
159         }
160 }
161
162 function article_callback() {
163         if (xmlhttp.readyState == 4) {
164                 debug("article_callback");
165
166                 try {
167                         if (xmlhttp.responseXML) {
168                                 var reply = xmlhttp.responseXML.firstChild.firstChild;
169
170                                 var articles = xmlhttp.responseXML.getElementsByTagName("article");
171
172                                 for (var i = 0; i < articles.length; i++) {
173                                         var a_id = articles[i].getAttribute("id");
174
175                                         debug("found id: " + a_id);
176
177                                         if (a_id == active_post_id) {
178                                                 debug("active article, rendering...");                                  
179                                                 render_article(articles[i].firstChild.nodeValue);
180                                         }
181
182                                         cache_inject(a_id, articles[i].firstChild.nodeValue);
183                                 }
184                         
185                         } else {
186                                 debug("article_callback: returned no XML object");
187                                 var f = document.getElementById("content-frame");
188                                 f.innerHTML = "<div class='whiteBox'>" + __('Could not display article (missing XML object)') + "</div>";
189                         }
190                 } catch (e) {
191                         exception_error("article_callback", e);
192                 }
193
194                 var date = new Date();
195                 last_article_view = date.getTime() / 1000;
196
197                 if (typeof correctPNG != 'undefined') {
198                         correctPNG();
199                 }
200
201                 if (_reload_feedlist_after_view) {
202                         setTimeout('updateFeedList(false, false)', 50);                 
203                         _reload_feedlist_after_view = false;
204                 } else {
205                         var counters = xmlhttp.responseXML.getElementsByTagName("counters")[0];
206
207                         if (counters) {
208                                 debug("parsing piggybacked counters: " + counters);
209                                 parse_counters(counters, false);
210                         } else {
211                                 debug("counters container not found in reply");
212                         }
213                 }
214
215                 notify("");
216         }
217 }
218
219 function view(id, feed_id, skip_history) {
220         
221         try {
222                 debug("loading article: " + id + "/" + feed_id);
223
224                 active_real_feed_id = feed_id;
225
226                 var cached_article = cache_find(id);
227
228                 debug("cache check result: " + (cached_article != false));
229         
230                 enableHotkeys();
231         
232                 //setActiveFeedId(feed_id);
233
234                 var query = "backend.php?op=view&id=" + param_escape(id) +
235                         "&feed=" + param_escape(feed_id);
236
237                 var date = new Date();
238
239                 if (!xmlhttp_ready(xmlhttp) && last_article_view < date.getTime() / 1000 - 15) {
240                         debug("<b>xmlhttp seems to be stuck at view, aborting</b>");
241                         xmlhttp.abort();
242                         if (is_safari()) {
243                                 debug("trying alternative reset method for Safari");
244                                 xmlhttp = Ajax.getTransport();
245                         }
246                 }
247
248                 if (xmlhttp_ready(xmlhttp)) {
249
250                         active_post_id = id; 
251
252                         cleanSelected("headlinesList");
253
254                         var crow = document.getElementById("RROW-" + active_post_id);
255
256                         var article_is_unread = crow.className.match("Unread");
257                         debug("article is unread: " + article_is_unread);                       
258
259                         crow.className = crow.className.replace("Unread", "");
260
261                         var upd_img_pic = document.getElementById("FUPDPIC-" + active_post_id);
262
263                         if (upd_img_pic) {
264                                 upd_img_pic.src = "images/blank_icon.gif";
265                         }
266
267                         selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', false);
268                         markHeadline(active_post_id);
269
270                         var neighbor_ids = getRelativePostIds(active_post_id);
271
272                         /* only request uncached articles */
273
274                         var cids_to_request = Array();
275
276                         for (var i = 0; i < neighbor_ids.length; i++) {
277                                 if (!cache_check(neighbor_ids[i])) {
278                                         cids_to_request.push(neighbor_ids[i]);
279                                 }
280                         }
281
282                         debug("additional ids: " + cids_to_request.toString());                 
283
284                         /* additional info for piggyback counters */
285
286                         if (tagsAreDisplayed()) {
287                                 query = query + "&omode=lt";
288                         } else {
289                                 query = query + "&omode=flc";
290                         }
291
292                         var date = new Date();
293                         var timestamp = Math.round(date.getTime() / 1000);
294                         query = query + "&ts=" + timestamp;
295
296                         query = query + "&cids=" + cids_to_request.toString();
297
298                         if (!cached_article) {
299
300                                 notify_progress("Loading, please wait...");
301
302                                 debug(query);
303
304                                 xmlhttp.open("GET", query, true);
305                                 xmlhttp.onreadystatechange=article_callback;
306                                 xmlhttp.send(null);
307                         } else if (cached_article && article_is_unread) {
308
309                                 query = query + "&mode=prefetch";
310
311                                 debug(query);
312
313                                 xmlhttp.open("GET", query, true);
314                                 xmlhttp.onreadystatechange=article_callback;
315                                 xmlhttp.send(null);
316
317                                 render_article(cached_article);
318
319                         } else if (cached_article) {
320
321                                 query = query + "&mode=prefetch_old";
322
323                                 debug(query);
324
325                                 xmlhttp.open("GET", query, true);
326                                 xmlhttp.onreadystatechange=article_callback;
327                                 xmlhttp.send(null);
328
329                                 render_article(cached_article);
330
331                         }
332
333                         cache_expire();
334
335                 } else {
336                         debug("xmlhttp busy (@view)");
337                         printLockingError();
338                 }  
339
340         } catch (e) {
341                 exception_error("view", e);
342         }
343 }
344
345 function tMark(id) {
346         return toggleMark(id);
347 }
348
349 function tPub(id) {
350         return togglePub(id);
351 }
352
353 function tMark_afh_off(effect) {
354         try {
355                 var elem = effect.effects[0].element;
356
357                 debug("tMark_afh_off : " + elem.id);
358
359                 if (elem) {
360                         elem.src = elem.src.replace("mark_set", "mark_unset");
361                         elem.alt = __("Star article");
362                         Element.show(elem);
363                 }
364
365         } catch (e) {
366                 exception_error("tMark_afh_off", e);
367         }
368 }
369
370 function tPub_afh_off(effect) {
371         try {
372                 var elem = effect.effects[0].element;
373
374                 debug("tPub_afh_off : " + elem.id);
375
376                 if (elem) {
377                         elem.src = elem.src.replace("pub_set", "pub_unset");
378                         elem.alt = __("Publish article");
379                         Element.show(elem);
380                 }
381
382         } catch (e) {
383                 exception_error("tPub_afh_off", e);
384         }
385 }
386
387 function toggleMark(id, client_only, no_effects) {
388
389         try {
390
391                 var query = "backend.php?op=rpc&id=" + id + "&subop=mark";
392         
393                 query = query + "&afid=" + getActiveFeedId();
394         
395                 if (tagsAreDisplayed()) {
396                         query = query + "&omode=tl";
397                 } else {
398                         query = query + "&omode=flc";
399                 }
400         
401                 var mark_img = document.getElementById("FMPIC-" + id);
402                 var vfeedu = document.getElementById("FEEDU--1");
403                 var crow = document.getElementById("RROW-" + id);
404         
405                 if (mark_img.src.match("mark_unset")) {
406                         mark_img.src = mark_img.src.replace("mark_unset", "mark_set");
407                         mark_img.alt = __("Unstar article");
408                         query = query + "&mark=1";
409         
410 /*                      if (vfeedu && crow.className.match("Unread")) {
411                                 vfeedu.innerHTML = (+vfeedu.innerHTML) + 1;
412                         } */
413         
414                 } else {
415                         //mark_img.src = "images/mark_unset.png";
416                         mark_img.alt = __("Please wait...");
417                         query = query + "&mark=0";
418         
419 /*                      if (vfeedu && crow.className.match("Unread")) {
420                                 vfeedu.innerHTML = (+vfeedu.innerHTML) - 1;
421                         } */
422         
423                         if (document.getElementById("headlinesList") && !no_effects) {
424                                 Effect.Puff(mark_img, {duration : 0.25, afterFinish: tMark_afh_off});
425                         } else { 
426                                 mark_img.src = mark_img.src.replace("mark_set", "mark_unset");
427                                 mark_img.alt = __("Star article");
428                         }
429                 }
430         
431 /*              var vfeedctr = document.getElementById("FEEDCTR--1");
432                 var vfeedr = document.getElementById("FEEDR--1");
433         
434                 if (vfeedu && vfeedctr) {
435                         if ((+vfeedu.innerHTML) > 0) {
436                                 if (crow.className.match("Unread") && !vfeedr.className.match("Unread")) {
437                                         vfeedr.className = vfeedr.className + "Unread";
438                                         vfeedctr.className = "odd";
439                                 }
440                         } else {
441                                 vfeedctr.className = "invisible";
442                                 vfeedr.className = vfeedr.className.replace("Unread", "");
443                         }
444                 }
445         
446                 debug("toggle starred for aid " + id);
447         
448                 //new Ajax.Request(query); */
449
450                 if (!client_only) {
451                         debug(query);
452
453 //                      xmlhttp_rpc.open("GET", query, true);
454 //                      xmlhttp_rpc.onreadystatechange=all_counters_callback;
455 //                      xmlhttp_rpc.send(null);
456
457                         new Ajax.Request(query, {
458                                 onComplete: function(transport) { 
459                                         all_counters_callback2(transport); 
460                                 } });
461
462                 }
463
464         } catch (e) {
465                 exception_error("toggleMark", e);
466         }
467 }
468
469 function togglePub(id, client_only, no_effects) {
470
471         try {
472
473                 var query = "backend.php?op=rpc&id=" + id + "&subop=publ";
474         
475                 query = query + "&afid=" + getActiveFeedId();
476         
477                 if (tagsAreDisplayed()) {
478                         query = query + "&omode=tl";
479                 } else {
480                         query = query + "&omode=flc";
481                 }
482         
483                 var mark_img = document.getElementById("FPPIC-" + id);
484                 var vfeedu = document.getElementById("FEEDU--2");
485                 var crow = document.getElementById("RROW-" + id);
486         
487                 if (mark_img.src.match("pub_unset")) {
488                         mark_img.src = mark_img.src.replace("pub_unset", "pub_set");
489                         mark_img.alt = __("Unpublish article");
490                         query = query + "&pub=1";
491         
492 /*                      if (vfeedu && crow.className.match("Unread")) {
493                                 vfeedu.innerHTML = (+vfeedu.innerHTML) + 1;
494                         } */
495         
496                 } else {
497                         //mark_img.src = "images/pub_unset.png";
498                         mark_img.alt = __("Please wait...");
499                         query = query + "&pub=0";
500         
501 /*                      if (vfeedu && crow.className.match("Unread")) {
502                                 vfeedu.innerHTML = (+vfeedu.innerHTML) - 1;
503                         } */
504
505                         if (document.getElementById("headlinesList") && !no_effects) {
506                                 Effect.Puff(mark_img, {duration : 0.25, afterFinish: tPub_afh_off});
507                         } else { 
508                                 mark_img.src = mark_img.src.replace("pub_set", "pub_unset");
509                                 mark_img.alt = __("Publish article");
510                         }
511                 }
512         
513 /*              var vfeedctr = document.getElementById("FEEDCTR--2");
514                 var vfeedr = document.getElementById("FEEDR--2");
515         
516                 if (vfeedu && vfeedctr) {
517                         if ((+vfeedu.innerHTML) > 0) {
518                                 if (crow.className.match("Unread") && !vfeedr.className.match("Unread")) {
519                                         vfeedr.className = vfeedr.className + "Unread";
520                                         vfeedctr.className = "odd";
521                                 }
522                         } else {
523                                 vfeedctr.className = "invisible";
524                                 vfeedr.className = vfeedr.className.replace("Unread", "");
525                         }
526                 }
527         
528                 debug("toggle published for aid " + id);
529         
530                 new Ajax.Request(query); */
531
532                 if (!client_only) {
533                         new Ajax.Request(query, {
534                                 onComplete: function(transport) { 
535                                         all_counters_callback2(transport); 
536                                 } });
537                 }
538
539         } catch (e) {
540
541                 exception_error("togglePub", e);
542         }
543 }
544
545 function correctHeadlinesOffset(id) {
546         
547         try {
548
549                 var hlist = document.getElementById("headlinesList");
550                 var container = document.getElementById("headlinesInnerContainer");
551                 var row = document.getElementById("RROW-" + id);
552         
553                 var viewport = container.offsetHeight;
554         
555                 var rel_offset_top = row.offsetTop - container.scrollTop;
556                 var rel_offset_bottom = row.offsetTop + row.offsetHeight - container.scrollTop;
557         
558                 debug("Rtop: " + rel_offset_top + " Rbtm: " + rel_offset_bottom);
559                 debug("Vport: " + viewport);
560
561                 if (rel_offset_top <= 0 || rel_offset_top > viewport) {
562                         container.scrollTop = row.offsetTop;
563                 } else if (rel_offset_bottom > viewport) {
564
565                         /* doesn't properly work with Opera in some cases because
566                                 Opera fucks up element scrolling */
567
568                         container.scrollTop = row.offsetTop + row.offsetHeight - viewport;              
569                 } 
570
571         } catch (e) {
572                 exception_error("correctHeadlinesOffset", e);
573         }
574
575 }
576
577 function moveToPost(mode) {
578
579         // check for combined mode
580         if (!document.getElementById("headlinesList"))
581                 return;
582
583         var rows = getVisibleHeadlineIds();
584
585         var prev_id = false;
586         var next_id = false;
587
588         if (!document.getElementById('RROW-' + active_post_id)) {
589                 active_post_id = false;
590         }
591
592         if (active_post_id == false) {
593                 next_id = getFirstVisibleHeadlineId();
594                 prev_id = getLastVisibleHeadlineId();
595         } else {        
596                 for (var i = 0; i < rows.length; i++) {
597                         if (rows[i] == active_post_id) {
598                                 prev_id = rows[i-1];
599                                 next_id = rows[i+1];                    
600                         }
601                 }
602         }
603
604         if (mode == "next") {
605                 if (next_id) {
606                         correctHeadlinesOffset(next_id);
607                         view(next_id, getActiveFeedId());
608                 }
609         }
610
611         if (mode == "prev") {
612                 if (prev_id) {
613                         correctHeadlinesOffset(prev_id);
614                         view(prev_id, getActiveFeedId());
615                 }
616         } 
617 }
618
619 function toggleUnread(id, cmode) {
620         try {
621         
622                 var row = document.getElementById("RROW-" + id);
623                 if (row) {
624                         var nc = row.className;
625                         nc = nc.replace("Unread", "");
626                         nc = nc.replace("Selected", "");
627
628                         if (row.className.match("Unread")) {
629                                 row.className = nc;
630                         } else {
631                                 row.className = nc + "Unread";
632                         }
633
634                         if (!cmode) cmode = 2;
635
636                         var query = "backend.php?op=rpc&subop=catchupSelected&ids=" +
637                                 param_escape(id) + "&cmode=" + param_escape(cmode);
638
639 //                      notify_progress("Loading, please wait...");
640
641                         new Ajax.Request(query, {
642                                 onComplete: function(transport) { 
643                                         all_counters_callback2(transport); 
644                                 } });
645
646                 }
647
648
649         } catch (e) {
650                 exception_error("toggleUnread", e);
651         }
652 }
653
654 function selectionToggleUnread(cdm_mode, set_state, callback_func, no_error) {
655         try {
656 /*              if (!xmlhttp_ready(xmlhttp_rpc)) {
657                         printLockingError();
658                         return;
659                 } */
660         
661                 var rows;
662
663                 if (cdm_mode) {
664                         rows = cdmGetSelectedArticles();
665                 } else {        
666                         rows = getSelectedTableRowIds("headlinesList", "RROW", "RCHK");
667                 }
668
669                 if (rows.length == 0 && !no_error) {
670                         alert(__("No articles are selected."));
671                         return;
672                 }
673
674                 for (i = 0; i < rows.length; i++) {
675                         var row = document.getElementById("RROW-" + rows[i]);
676                         if (row) {
677                                 var nc = row.className;
678                                 nc = nc.replace("Unread", "");
679                                 nc = nc.replace("Selected", "");
680
681                                 if (set_state == undefined) {
682                                         if (row.className.match("Unread")) {
683                                                 row.className = nc + "Selected";
684                                         } else {
685                                                 row.className = nc + "UnreadSelected";
686                                         }
687                                 }
688
689                                 if (set_state == false) {
690                                         row.className = nc + "Selected";
691                                 }
692
693                                 if (set_state == true) {
694                                         row.className = nc + "UnreadSelected";
695                                 }
696                         }
697                 }
698
699                 if (rows.length > 0) {
700
701                         var cmode = "";
702
703                         if (set_state == undefined) {
704                                 cmode = "2";
705                         } else if (set_state == true) {
706                                 cmode = "1";
707                         } else if (set_state == false) {
708                                 cmode = "0";
709                         }
710
711                         var query = "backend.php?op=rpc&subop=catchupSelected&ids=" +
712                                 param_escape(rows.toString()) + "&cmode=" + cmode;
713
714 //                      _catchup_callback_func = callback_func;
715
716                         debug(callback_func);
717
718                         notify_progress("Loading, please wait...");
719
720 /*                      xmlhttp_rpc.open("GET", query, true);
721                         xmlhttp_rpc.onreadystatechange=catchup_callback;
722                         xmlhttp_rpc.send(null); */
723
724                         new Ajax.Request(query, {
725                                 onComplete: function(transport) { 
726                                         catchup_callback2(transport, callback_func); 
727                                 } });
728
729                 }
730
731         } catch (e) {
732                 exception_error("selectionToggleUnread", e);
733         }
734 }
735
736 function selectionToggleMarked(cdm_mode) {
737         try {
738         
739                 var rows;
740                 
741                 if (cdm_mode) {
742                         rows = cdmGetSelectedArticles();
743                 } else {        
744                         rows = getSelectedTableRowIds("headlinesList", "RROW", "RCHK");
745                 }       
746
747                 if (rows.length == 0) {
748                         alert(__("No articles are selected."));
749                         return;
750                 }
751
752                 for (i = 0; i < rows.length; i++) {
753                         toggleMark(rows[i], true, true);
754                 }
755
756                 if (rows.length > 0) {
757
758                         var query = "backend.php?op=rpc&subop=markSelected&ids=" +
759                                 param_escape(rows.toString()) + "&cmode=2";
760
761                         query = query + "&afid=" + getActiveFeedId();
762
763 /*                      if (tagsAreDisplayed()) {
764                                 query = query + "&omode=tl";
765                         } else {
766                                 query = query + "&omode=flc";
767                         } */
768
769                         query = query + "&omode=lc";
770
771                         new Ajax.Request(query, {
772                                 onComplete: function(transport) { 
773                                         all_counters_callback2(transport); 
774                                 } });
775
776                 }
777
778         } catch (e) {
779                 exception_error("selectionToggleMarked", e);
780         }
781 }
782
783 function selectionTogglePublished(cdm_mode) {
784         try {
785         
786                 var rows;
787                 
788                 if (cdm_mode) {
789                         rows = cdmGetSelectedArticles();
790                 } else {        
791                         rows = getSelectedTableRowIds("headlinesList", "RROW", "RCHK");
792                 }       
793
794                 if (rows.length == 0) {
795                         alert(__("No articles are selected."));
796                         return;
797                 }
798
799                 for (i = 0; i < rows.length; i++) {
800                         togglePub(rows[i], true, true);
801                 }
802
803                 if (rows.length > 0) {
804
805                         var query = "backend.php?op=rpc&subop=publishSelected&ids=" +
806                                 param_escape(rows.toString()) + "&cmode=2";
807
808                         query = query + "&afid=" + getActiveFeedId();
809
810 /*                      if (tagsAreDisplayed()) {
811                                 query = query + "&omode=tl";
812                         } else {
813                                 query = query + "&omode=flc";
814                         } */
815
816                         query = query + "&omode=lc";
817
818                         new Ajax.Request(query, {
819                                 onComplete: function(transport) { 
820                                         all_counters_callback2(transport); 
821                                 } });
822
823                 }
824
825         } catch (e) {
826                 exception_error("selectionToggleMarked", e);
827         }
828 }
829
830 function cdmGetSelectedArticles() {
831         var sel_articles = new Array();
832         var container = document.getElementById("headlinesInnerContainer");
833
834         for (i = 0; i < container.childNodes.length; i++) {
835                 var child = container.childNodes[i];
836
837                 if (child.id.match("RROW-") && child.className.match("Selected")) {
838                         var c_id = child.id.replace("RROW-", "");
839                         sel_articles.push(c_id);
840                 }
841         }
842
843         return sel_articles;
844 }
845
846 function cdmGetVisibleArticles() {
847         var sel_articles = new Array();
848         var container = document.getElementById("headlinesInnerContainer");
849
850         for (i = 0; i < container.childNodes.length; i++) {
851                 var child = container.childNodes[i];
852
853                 if (child.id.match("RROW-")) {
854                         var c_id = child.id.replace("RROW-", "");
855                         sel_articles.push(c_id);
856                 }
857         }
858
859         return sel_articles;
860 }
861
862 function cdmGetUnreadArticles() {
863         var sel_articles = new Array();
864         var container = document.getElementById("headlinesInnerContainer");
865
866         for (i = 0; i < container.childNodes.length; i++) {
867                 var child = container.childNodes[i];
868
869                 if (child.id.match("RROW-") && child.className.match("Unread")) {
870                         var c_id = child.id.replace("RROW-", "");
871                         sel_articles.push(c_id);
872                 }
873         }
874
875         return sel_articles;
876 }
877
878
879 // mode = all,none,unread
880 function cdmSelectArticles(mode) {
881         var container = document.getElementById("headlinesInnerContainer");
882
883         for (i = 0; i < container.childNodes.length; i++) {
884                 var child = container.childNodes[i];
885
886                 if (child.id.match("RROW-")) {
887                         var aid = child.id.replace("RROW-", "");
888
889                         var cb = document.getElementById("RCHK-" + aid);
890
891                         if (mode == "all") {
892                                 if (!child.className.match("Selected")) {
893                                         child.className = child.className + "Selected";
894                                         cb.checked = true;
895                                 }
896                         } else if (mode == "unread") {
897                                 if (child.className.match("Unread") && !child.className.match("Selected")) {
898                                         child.className = child.className + "Selected";
899                                         cb.checked = true;
900                                 }
901                         } else {
902                                 child.className = child.className.replace("Selected", "");
903                                 cb.checked = false;
904                         }
905                 }               
906         }
907 }
908
909 function catchupPage() {
910
911         var fn = getFeedName(getActiveFeedId(), active_feed_is_cat);
912         
913         var str = __("Mark all visible articles in %s as read?");
914
915         str = str.replace("%s", fn);
916
917         if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
918                 return;
919         }
920
921         if (document.getElementById("headlinesList")) {
922                 selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', true, 'Unread', true);
923                 selectionToggleUnread(false, false, 'viewCurrentFeed()', true);
924                 selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', false);
925         } else {
926                 cdmSelectArticles('all');
927                 selectionToggleUnread(true, false, 'viewCurrentFeed()', true)
928                 cdmSelectArticles('none');
929         }
930 }
931
932 function catchupSelection() {
933
934         try {
935
936                 var rows;
937         
938                 if (document.getElementById("headlinesList")) {
939                         rows = getSelectedTableRowIds("headlinesList", "RROW", "RCHK");
940                 } else {        
941                         rows = cdmGetSelectedArticles();
942                 }
943         
944                 if (rows.length == 0) {
945                         alert(__("No articles are selected."));
946                         return;
947                 }
948         
949         
950                 var fn = getFeedName(getActiveFeedId(), active_feed_is_cat);
951                 
952                 var str = __("Mark all selected articles in %s as read?");
953         
954                 str = str.replace("%s", fn);
955         
956                 if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
957                         return;
958                 }
959         
960                 if (document.getElementById("headlinesList")) {
961                         selectionToggleUnread(false, false, 'viewCurrentFeed()', true);
962         //              selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', false);
963                 } else {
964                         selectionToggleUnread(true, false, 'viewCurrentFeed()', true)
965         //              cdmSelectArticles('none');
966                 }
967
968         } catch (e) {
969                 exception_error("catchupSelection", e);
970         }
971 }
972
973
974 function labelFromSearch(search, search_mode, match_on, feed_id, is_cat) {
975
976         if (!xmlhttp_ready(xmlhttp_rpc)) {
977                 printLockingError();
978         }
979
980         var title = prompt(__("Please enter label title:"), "");
981
982         if (title) {
983
984                 var query = "backend.php?op=labelFromSearch&search=" + param_escape(search) +
985                         "&smode=" + param_escape(search_mode) + "&match=" + param_escape(match_on) +
986                         "&feed=" + param_escape(feed_id) + "&is_cat=" + param_escape(is_cat) + 
987                         "&title=" + param_escape(title);
988
989                 debug("LFS: " + query);
990         
991                 xmlhttp_rpc.open("GET", query, true);
992                 xmlhttp_rpc.onreadystatechange=dlg_frefresh_callback;
993                 xmlhttp_rpc.send(null);
994         }
995
996 }
997
998 function editArticleTags(id, feed_id, cdm_enabled) {
999         _tag_active_post_id = id;
1000         _tag_active_feed_id = feed_id;
1001         _tag_active_cdm = cdm_enabled;
1002
1003         cache_invalidate(id);
1004
1005         try {
1006                 _tag_cdm_scroll = document.getElementById("headlinesInnerContainer").scrollTop;
1007         } catch (e) { }
1008         displayDlg('editArticleTags', id);
1009 }
1010
1011
1012 function tag_saved_callback() {
1013         if (xmlhttp_rpc.readyState == 4) {
1014                 try {
1015                         debug("in tag_saved_callback");
1016
1017                         closeInfoBox();
1018                         notify("");
1019
1020                         if (tagsAreDisplayed()) {
1021                                 _reload_feedlist_after_view = true;
1022                         }
1023
1024                         if (!_tag_active_cdm) {
1025                                 if (active_post_id == _tag_active_post_id) {
1026                                         debug("reloading current article");
1027                                         view(_tag_active_post_id, _tag_active_feed_id);                 
1028                                 }
1029                         } else {
1030                                 debug("reloading current feed");
1031                                 viewCurrentFeed();
1032                         }
1033
1034                 } catch (e) {
1035                         exception_error("catchup_callback", e);
1036                 }
1037         }
1038 }
1039
1040 function editTagsSave() {
1041
1042         if (!xmlhttp_ready(xmlhttp_rpc)) {
1043                 printLockingError();
1044         }
1045
1046         notify_progress("Saving article tags...");
1047
1048         var form = document.forms["tag_edit_form"];
1049
1050         var query = Form.serialize("tag_edit_form");
1051
1052         query = "backend.php?op=rpc&subop=setArticleTags&" + query;
1053
1054         debug(query);
1055
1056         xmlhttp_rpc.open("GET", query, true);                   
1057         xmlhttp_rpc.onreadystatechange=tag_saved_callback;
1058         xmlhttp_rpc.send(null);
1059
1060 }
1061
1062 function editTagsInsert() {
1063         try {
1064
1065                 var form = document.forms["tag_edit_form"];
1066
1067                 var found_tags = form.found_tags;
1068                 var tags_str = form.tags_str;
1069
1070                 var tag = found_tags[found_tags.selectedIndex].value;
1071
1072                 if (tags_str.value.length > 0 && 
1073                                 tags_str.value.lastIndexOf(", ") != tags_str.value.length - 2) {
1074
1075                         tags_str.value = tags_str.value + ", ";
1076                 }
1077
1078                 tags_str.value = tags_str.value + tag + ", ";
1079
1080                 found_tags.selectedIndex = 0;
1081                 
1082         } catch (e) {
1083                 exception_error(e, "editTagsInsert");
1084         }
1085 }
1086
1087 function cdmWatchdog() {
1088
1089         try {
1090
1091                 var ctr = document.getElementById("headlinesInnerContainer");
1092
1093                 if (!ctr) return;
1094
1095                 var ids = new Array();
1096
1097                 var e = ctr.firstChild;
1098
1099                 while (e) {
1100                         if (e.className && e.className == "cdmArticleUnread" && e.id &&
1101                                         e.id.match("RROW-")) {
1102
1103                                 // article fits in viewport OR article is longer than viewport and
1104                                 // its bottom is visible
1105
1106                                 if (ctr.scrollTop <= e.offsetTop && e.offsetTop + e.offsetHeight <=
1107                                                 ctr.scrollTop + ctr.offsetHeight) {
1108
1109 //                                      debug(e.id + " is visible " + e.offsetTop + "." + 
1110 //                                              (e.offsetTop + e.offsetHeight) + " vs " + ctr.scrollTop + "." +
1111 //                                              (ctr.scrollTop + ctr.offsetHeight));
1112
1113                                         ids.push(e.id.replace("RROW-", ""));
1114
1115                                 } else if (e.offsetHeight > ctr.offsetHeight &&
1116                                                 e.offsetTop + e.offsetHeight >= ctr.scrollTop &&
1117                                                 e.offsetTop + e.offsetHeight <= ctr.scrollTop + ctr.offsetHeight) {
1118
1119                                         ids.push(e.id.replace("RROW-", "")); 
1120
1121                                 }
1122
1123                                 // method 2: article bottom is visible and is in upper 1/2 of the viewport
1124
1125 /*                              if (e.offsetTop + e.offsetHeight >= ctr.scrollTop &&
1126                                                 e.offsetTop + e.offsetHeight <= ctr.scrollTop + ctr.offsetHeight/2) {
1127
1128                                         ids.push(e.id.replace("RROW-", "")); 
1129
1130                                 } */
1131
1132                         }
1133
1134                         e = e.nextSibling;
1135                 }
1136
1137                 debug("cdmWatchdog, ids= " + ids.toString());
1138
1139                 if (ids.length > 0) {
1140
1141                         for (var i = 0; i < ids.length; i++) {
1142                                 var e = document.getElementById("RROW-" + ids[i]);
1143                                 if (e) {
1144                                         e.className = e.className.replace("Unread", "");
1145                                 }
1146                         }
1147
1148                         var query = "backend.php?op=rpc&subop=catchupSelected&ids=" +
1149                                 param_escape(ids.toString()) + "&cmode=0";
1150
1151                         new Ajax.Request(query, {
1152                                 onComplete: function(transport) { 
1153                                         all_counters_callback2(transport); 
1154                                 } });
1155
1156                 }
1157
1158                 _cdm_wd_timeout = window.setTimeout("cdmWatchdog()", 4000);
1159
1160         } catch (e) {
1161                 exception_error(e, "cdmWatchdog");
1162         }
1163
1164 }
1165
1166
1167 function cache_inject(id, article) {
1168         if (!cache_check(id)) {
1169                 debug("cache_article: miss: " + id);
1170
1171                 var cache_obj = new Array();
1172
1173                 cache_obj["id"] = id;
1174                 cache_obj["data"] = article;
1175
1176                 article_cache.push(cache_obj);
1177
1178         } else {
1179                 debug("cache_article: hit: " + id);
1180         }
1181 }
1182
1183 function cache_find(id) {
1184         for (var i = 0; i < article_cache.length; i++) {
1185                 if (article_cache[i]["id"] == id) {
1186                         return article_cache[i]["data"];
1187                 }
1188         }
1189         return false;
1190 }
1191
1192 function cache_check(id) {
1193         for (var i = 0; i < article_cache.length; i++) {
1194                 if (article_cache[i]["id"] == id) {
1195                         return true;
1196                 }
1197         }
1198         return false;
1199 }
1200
1201 function cache_expire() {
1202         while (article_cache.length > 20) {
1203                 article_cache.shift();
1204         }
1205 }
1206
1207 function cache_invalidate(id) {
1208         var i = 0
1209
1210         try {   
1211
1212                 while (i < article_cache.length) {
1213                         if (article_cache[i]["id"] == id) {
1214                                 debug("cache_invalidate: removed id " + id);
1215                                 article_cache.splice(i, 1);
1216                                 return true;
1217                         }
1218                         i++;
1219                 }
1220                 debug("cache_invalidate: id not found: " + id);
1221                 return false;
1222         } catch (e) {
1223                 exception_error("cache_invalidate", e);
1224         }
1225 }
1226
1227 function getActiveArticleId() {
1228         return active_post_id;
1229 }
1230
1231 function cdmMouseIn(elem) {
1232         try {
1233                 if (elem.id && elem.id.match("RROW-")) {
1234                         var id = elem.id.replace("RROW-", "");
1235                         active_post_id = id;
1236                 }
1237         } catch (e) {
1238                 exception_error("cdmMouseIn", e);
1239         }
1240
1241 }
1242
1243 function cdmMouseOut(elem) {
1244         active_post_id = false;
1245 }
1246
1247 function headlines_scroll_handler() {
1248         try {
1249
1250                 var e = document.getElementById("headlinesInnerContainer");
1251
1252                 if (e.scrollTop + e.offsetHeight > e.scrollHeight - 300) {
1253                         debug("more cowbell!");
1254
1255                         viewNextFeedPage();
1256                 }
1257
1258         } catch (e) {
1259                 exception_error("headlines_scroll_handler", e);
1260         }
1261 }
1262
1263 function catchupRelativeToArticle(below) {
1264
1265         try {
1266
1267                 if (!xmlhttp_ready(xmlhttp_rpc)) {
1268                         printLockingError();
1269                 }
1270         
1271                 if (!getActiveArticleId()) {
1272                         alert(__("No article is selected."));
1273                         return;
1274                 }
1275
1276                 var visible_ids;
1277
1278                 if (document.getElementById("headlinesList")) {
1279                         visible_ids = getVisibleHeadlineIds();
1280                 } else {
1281                         visible_ids = cdmGetVisibleArticles();
1282                 }
1283
1284                 var ids_to_mark = new Array();
1285
1286                 if (!below) {
1287                         for (var i = 0; i < visible_ids.length; i++) {
1288                                 if (visible_ids[i] != getActiveArticleId()) {
1289                                         var e = document.getElementById("RROW-" + visible_ids[i]);
1290
1291                                         if (e && e.className.match("Unread")) {
1292                                                 ids_to_mark.push(visible_ids[i]);
1293                                         }
1294                                 } else {
1295                                         break;
1296                                 }
1297                         }
1298                 } else {
1299                         for (var i = visible_ids.length-1; i >= 0; i--) {
1300                                 if (visible_ids[i] != getActiveArticleId()) {
1301                                         var e = document.getElementById("RROW-" + visible_ids[i]);
1302
1303                                         if (e && e.className.match("Unread")) {
1304                                                 ids_to_mark.push(visible_ids[i]);
1305                                         }
1306                                 } else {
1307                                         break;
1308                                 }
1309                         }
1310                 }
1311
1312                 if (ids_to_mark.length == 0) {
1313                         alert(__("No articles found to mark"));
1314                 } else {
1315                         var msg = __("Mark %d article(s) as read?").replace("%d", ids_to_mark.length);
1316
1317                         if (confirm(msg)) {
1318
1319                                 for (var i = 0; i < ids_to_mark.length; i++) {
1320                                         var e = document.getElementById("RROW-" + ids_to_mark[i]);
1321                                         e.className = e.className.replace("Unread", "");
1322                                 }
1323
1324                                 var query = "backend.php?op=rpc&subop=catchupSelected&ids=" +
1325                                         param_escape(ids_to_mark.toString()) + "&cmode=0";
1326
1327                                 xmlhttp_rpc.open("GET", query, true);
1328                                 xmlhttp_rpc.onreadystatechange=catchup_callback;
1329                                 xmlhttp_rpc.send(null);
1330         
1331                         }
1332                 }
1333
1334         } catch (e) {
1335                 exception_error("catchupRelativeToArticle", e);
1336         }
1337 }