]> git.wh0rd.org Git - tt-rss.git/blob - viewfeed.js
archiveSelection: invalidate article cache
[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 // FIXME: kludges, needs proper implementation
6 var _reload_feedlist_after_view = false;
7
8 var _cdm_wd_timeout = false;
9 var _cdm_wd_vishist = new Array();
10
11 var article_cache = new Array();
12
13 var vgroup_last_feed = false;
14 var post_under_pointer = false;
15
16 var last_requested_article = false;
17
18 function toggle_published_callback(transport) {
19         try {
20                 if (transport.responseXML) {
21
22                         all_counters_callback2(transport);
23
24                         var note = transport.responseXML.getElementsByTagName("note")[0];
25
26                         if (note) {
27                                 var note_id = note.getAttribute("id");
28                                 var note_size = note.getAttribute("size");
29                                 var note_content = note.firstChild.nodeValue;
30
31                                 var container = $('POSTNOTE-' + note_id);
32
33                                 cache_invalidate(note_id);
34
35                                 if (container) {
36                                         if (note_size == "0") {
37                                                 Element.hide(container);
38                                         } else {
39                                                 container.innerHTML = note_content;
40                                                 Element.show(container);
41                                         }
42                                 }
43                         }       
44                 }
45
46         } catch (e) {
47                 exception_error("toggle_published_callback", e, transport);
48         }
49 }
50
51 function catchup_callback2(transport, callback) {
52         try {
53                 debug("catchup_callback2 " + transport + ", " + callback);
54                 notify("");                     
55                 all_counters_callback2(transport);
56                 if (callback) {
57                         setTimeout(callback, 10);       
58                 }
59         } catch (e) {
60                 exception_error("catchup_callback2", e, transport);
61         }
62 }
63
64 function clean_feed_selections() {
65         try {
66                 var feeds = $("feedList").getElementsByTagName("LI");
67
68                 for (var i = 0; i < feeds.length; i++) {
69                         if (feeds[i].id && feeds[i].id.match("FEEDR-")) {
70                                 feeds[i].className = feeds[i].className.replace("Selected", "");
71                         }                       
72                         if (feeds[i].id && feeds[i].id.match("FCAT-")) {
73                                 feeds[i].className = feeds[i].className.replace("Selected", "");
74                         }
75                 }
76         } catch (e) {
77                 exception_error("clean_feed_selections", e);
78         }
79 }
80
81 function headlines_callback2(transport, feed_cur_page) {
82         try {
83
84                 if (!transport.responseText && db) {
85                         offlineConfirmModeChange();
86                         return;
87                 }
88
89                 loading_set_progress(100);
90
91                 debug("headlines_callback2 [page=" + feed_cur_page + "]");
92
93                 if (!transport_error_check(transport)) return;
94
95                 clean_feed_selections();
96         
97                 var is_cat = false;
98                 var feed_id = false;
99
100                 if (transport.responseXML) {
101                         var headlines = transport.responseXML.getElementsByTagName("headlines")[0];
102                         if (headlines) {
103                                 is_cat = headlines.getAttribute("is_cat");
104                                 feed_id = headlines.getAttribute("id");
105                                 setActiveFeedId(feed_id, is_cat);
106                         }
107                 }
108
109                 var ll = $('FLL-' + feed_id);
110
111                 if (!is_cat) {
112                         var feedr = $("FEEDR-" + feed_id);
113                         if (feedr && !feedr.className.match("Selected")) {      
114                                 feedr.className = feedr.className + "Selected";
115                         } 
116                         if (feedr && ll) feedr.removeChild(ll);
117                 } else {
118                         var feedr = $("FCAT-" + feed_id);
119                         if (feedr && !feedr.className.match("Selected")) {      
120                                 feedr.className = feedr.className + "Selected";
121                         } 
122
123                         var fcap = $("FCAP-" + feed_id);
124                         if (fcap && ll) fcap.removeChild(ll);
125
126                 }
127
128                 var img = $('FIMG-' + feed_id);
129
130                 if (img && !is_cat) {
131                         img.src = img.alt;
132                 }
133
134                 var f = $("headlines-frame");
135                 try {
136                         if (feed_cur_page == 0) { 
137                                 debug("resetting headlines scrollTop");
138                                 f.scrollTop = 0; 
139                         }
140                 } catch (e) { };
141         
142                 if (transport.responseXML) {
143                         var headlines = transport.responseXML.getElementsByTagName("headlines")[0];
144                         var headlines_count_obj = transport.responseXML.getElementsByTagName("headlines-count")[0];
145                         var headlines_unread_obj = transport.responseXML.getElementsByTagName("headlines-unread")[0];
146                         var disable_cache_obj = transport.responseXML.getElementsByTagName("disable-cache")[0];
147
148                         var vgroup_last_feed_obj =  transport.responseXML.getElementsByTagName("vgroup-last-feed")[0];
149
150                         var headlines_count = headlines_count_obj.getAttribute("value");
151                         var headlines_unread = headlines_unread_obj.getAttribute("value");
152                         var disable_cache = disable_cache_obj.getAttribute("value") != "0";
153
154                         vgroup_last_feed = vgroup_last_feed_obj.getAttribute("value");
155
156                         if (headlines_count == 0) {
157                                 _infscroll_disable = 1;
158                         } else {
159                                 _infscroll_disable = 0;
160                         }
161
162                         var counters = transport.responseXML.getElementsByTagName("counters")[0];
163                         var articles = transport.responseXML.getElementsByTagName("article");
164                         var runtime_info = transport.responseXML.getElementsByTagName("runtime-info");
165         
166                         if (feed_cur_page == 0) {
167                                 if (headlines) {
168                                         f.innerHTML = headlines.firstChild.nodeValue;
169
170                                         var cache_prefix = "";
171
172                                         if (is_cat) {
173                                                 cache_prefix = "C:";
174                                         } else {
175                                                 cache_prefix = "F:";
176                                         }
177
178                                         cache_invalidate(cache_prefix + feed_id);
179
180                                         if (!disable_cache) {
181                                                 cache_inject(cache_prefix + feed_id,
182                                                         headlines.firstChild.nodeValue, headlines_unread);
183                                         }
184
185                                 } else {
186                                         debug("headlines_callback: returned no data");
187                                 f.innerHTML = "<div class='whiteBox'>" + __('Could not update headlines (missing XML data)') + "</div>";
188         
189                                 }
190                         } else {
191                                 if (headlines) {
192                                         if (headlines_count > 0) {
193                                                 debug("adding some more headlines...");
194         
195                                                 var c = $("headlinesList");
196                 
197                                                 if (!c) {
198                                                         c = $("headlinesInnerContainer");
199                                                 }
200
201                                                 var ids = getSelectedArticleIds2();
202         
203                                                 c.innerHTML = c.innerHTML + headlines.firstChild.nodeValue;
204
205                                                 debug("restore selected ids: " + ids);
206
207                                                 for (var i = 0; i < ids.length; i++) {
208                                                         markHeadline(ids[i]);
209                                                 }
210
211                                         } else {
212                                                 debug("no new headlines received");
213                                         }
214                                 } else {
215                                         debug("headlines_callback: returned no data");
216                                         notify_error("Error while trying to load more headlines");      
217                                 }
218
219                         }
220         
221                         if (articles) {
222                                 for (var i = 0; i < articles.length; i++) {
223                                         var a_id = articles[i].getAttribute("id");
224                                         debug("found id: " + a_id);
225                                         cache_inject(a_id, articles[i].firstChild.nodeValue);
226                                 }
227                         } else {
228                                 debug("no cached articles received");
229                         }
230         
231                         if (counters) {
232                                 debug("parsing piggybacked counters: " + counters);
233                                 parse_counters(counters, false);
234                         } else {
235                                 debug("counters container not found in reply, requesting...");
236                                 request_counters();
237                         }
238         
239                         if (runtime_info) {
240                                 debug("parsing runtime info: " + runtime_info[0]);
241                                 parse_runtime_info(runtime_info[0]);
242                         } else {
243                                 debug("counters container not found in reply");
244                         }
245         
246                 } else {
247                         debug("headlines_callback: returned no XML object");
248                         f.innerHTML = "<div class='whiteBox'>" + __('Could not update headlines (missing XML object)') + "</div>";
249                 }
250         
251                 if (typeof correctPNG != 'undefined') {
252                         correctPNG();
253                 }
254         
255                 if (_cdm_wd_timeout) window.clearTimeout(_cdm_wd_timeout);
256         
257                 if (!$("headlinesList") && 
258                                 getActiveFeedId() != -3 &&
259                                 getInitParam("cdm_auto_catchup") == 1) {
260                         debug("starting CDM watchdog");
261                         _cdm_wd_timeout = window.setTimeout("cdmWatchdog()", 5000);
262                         _cdm_wd_vishist = new Array();
263                 } else {
264                         debug("not in CDM mode or watchdog disabled");
265                 }
266         
267                 _feed_cur_page = feed_cur_page;
268                 _infscroll_request_sent = 0;
269
270                 notify("");
271
272                 remove_splash();
273
274         } catch (e) {
275                 exception_error("headlines_callback2", e, transport);
276         }
277 }
278
279 function render_article(article) {
280         try {
281                 var f = $("content-frame");
282                 try {
283                         f.scrollTop = 0;
284                 } catch (e) { };
285
286                 var fi = $("content-insert");
287
288                 try {
289                         fi.scrollTop = 0;
290                 } catch (e) { };
291                 
292                 fi.innerHTML = article;
293                 
294 //              article.evalScripts();          
295
296         } catch (e) {
297                 exception_error("render_article", e);
298         }
299 }
300
301 function showArticleInHeadlines(id) {
302
303         try {
304
305                 cleanSelected("headlinesList");
306         
307                 var crow = $("RROW-" + id);
308
309                 if (!crow) return;
310
311                 var article_is_unread = crow.className.match("Unread");
312                         
313                 crow.className = crow.className.replace("Unread", "");
314
315                 selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', false);
316         
317                 var upd_img_pic = $("FUPDPIC-" + id);
318
319                 var cache_prefix = "";
320                                 
321                 if (activeFeedIsCat()) {
322                         cache_prefix = "C:";
323                 } else {
324                         cache_prefix = "F:";
325                 }
326
327                 var view_mode = false;
328
329                 try {
330                         view_mode = document.forms['main_toolbar_form'].view_mode;      
331                         view_mode = view_mode[view_mode.selectedIndex].value;
332                 } catch (e) {
333                         //
334                 }
335
336                 if (upd_img_pic && (upd_img_pic.src.match("updated.png") || 
337                                         upd_img_pic.src.match("fresh_sign.png"))) {
338
339                         upd_img_pic.src = "images/blank_icon.gif";
340
341                         cache_invalidate(cache_prefix + getActiveFeedId());
342
343                         cache_inject(cache_prefix + getActiveFeedId(),
344                                 $("headlines-frame").innerHTML,
345                                 get_feed_unread(getActiveFeedId()));
346
347                 } else if (article_is_unread && view_mode == "all_articles") {
348
349                         cache_invalidate(cache_prefix + getActiveFeedId());
350
351                         cache_inject(cache_prefix + getActiveFeedId(),
352                                 $("headlines-frame").innerHTML,
353                                 get_feed_unread(getActiveFeedId())-1);
354
355                 } else if (article_is_unread) {
356                         cache_invalidate(cache_prefix + getActiveFeedId());
357                 }
358
359                 markHeadline(id);
360
361         } catch (e) {
362                 exception_error("showArticleInHeadlines", e);
363         }
364 }
365
366 function article_callback2(transport, id, feed_id) {
367         try {
368                 debug("article_callback2 " + id);
369
370                 if (!transport.responseText && db) {
371                         offlineConfirmModeChange();
372                         return;
373                 }
374
375                 if (transport.responseXML) {
376
377                         if (!transport_error_check(transport)) return;
378
379 /*                      var ll = $('LL-' + id);
380                         var content = $('HLC-' + id);
381
382                         if (ll && content) content.removeChild(ll); */
383                         
384                         var upic = $('FUPDPIC-' + id);
385
386                         if (upic) {
387                                 upic.src = 'images/blank_icon.gif';
388                         }
389
390                         if (id != last_requested_article) {
391                                 debug("requested article id is out of sequence, aborting");
392                                 return;
393                         }
394
395                         active_real_feed_id = feed_id;
396                         active_post_id = id; 
397
398                         debug("looking for articles to cache...");
399
400                         var articles = transport.responseXML.getElementsByTagName("article");
401
402                         for (var i = 0; i < articles.length; i++) {
403                                 var a_id = articles[i].getAttribute("id");
404
405                                 debug("found id: " + a_id);
406
407                                 if (a_id == active_post_id) {
408                                         debug("active article, rendering...");                                  
409                                         render_article(articles[i].firstChild.nodeValue);
410                                 }
411
412                                 cache_inject(a_id, articles[i].firstChild.nodeValue);
413                         }
414
415
416                         showArticleInHeadlines(id);     
417
418                         if (db) {
419                                 db.execute("UPDATE articles SET unread = 0 WHERE id = ?", [id]);
420                         }
421
422                         var reply = transport.responseXML.firstChild.firstChild;
423                 
424                 } else {
425                         debug("article_callback: returned no XML object");
426                         //var f = $("content-frame");
427                         //f.innerHTML = "<div class='whiteBox'>" + __('Could not display article (missing XML object)') + "</div>";
428                 }
429
430                 var date = new Date();
431                 last_article_view = date.getTime() / 1000;
432
433                 if (typeof correctPNG != 'undefined') {
434                         correctPNG();
435                 }
436
437                 if (_reload_feedlist_after_view) {
438                         setTimeout('updateFeedList(false, false)', 50);                 
439                         _reload_feedlist_after_view = false;
440                 } else {
441                         if (transport.responseXML) {
442                                 var counters = transport.responseXML.getElementsByTagName("counters")[0];
443
444                                 if (counters) {
445                                         debug("parsing piggybacked counters: " + counters);
446                                         parse_counters(counters, false);
447                                 } else {
448                                         debug("counters container not found in reply, requesting...");
449                                         request_counters();
450                                 }
451                         }
452                 }
453
454                 notify("");
455         } catch (e) {
456                 exception_error("article_callback2", e, transport);
457         }
458 }
459
460 function view(id, feed_id, skip_history) {
461         
462         try {
463                 debug("loading article: " + id + "/" + feed_id);
464
465                 if (offline_mode) return view_offline(id, feed_id);
466
467                 var cached_article = cache_find(id);
468
469                 debug("cache check result: " + (cached_article != false));
470         
471                 enableHotkeys();
472                 hideAuxDlg();
473
474                 //setActiveFeedId(feed_id);
475
476                 var query = "backend.php?op=view&id=" + param_escape(id) +
477                         "&feed=" + param_escape(feed_id);
478
479                 var date = new Date();
480
481                 var neighbor_ids = getRelativePostIds(active_post_id);
482
483                 /* only request uncached articles */
484
485                 var cids_to_request = Array();
486
487                 for (var i = 0; i < neighbor_ids.length; i++) {
488                         if (!cache_check(neighbor_ids[i])) {
489                                 cids_to_request.push(neighbor_ids[i]);
490                         }
491                 }
492
493                 debug("additional ids: " + cids_to_request.toString());                 
494
495                 /* additional info for piggyback counters */
496
497                 if (tagsAreDisplayed()) {
498                         query = query + "&omode=lt";
499                 } else {
500                         query = query + "&omode=flc";
501                 }
502
503                 var date = new Date();
504                 var timestamp = Math.round(date.getTime() / 1000);
505                 query = query + "&ts=" + timestamp;
506
507                 query = query + "&cids=" + cids_to_request.toString();
508
509                 var crow = $("RROW-" + id);
510                 var article_is_unread = crow.className.match("Unread");
511
512                 if (!async_counters_work) {
513                         query = query + "&csync=true";
514                 }
515
516                 showArticleInHeadlines(id);
517
518                 if (!cached_article) {
519
520 //                      notify_progress("Loading, please wait...", true);
521
522 /*                      var content = $('HLC-' + id);
523
524                         if (content && !$('LL-' + id)) {
525                                 var ll = document.createElement('img');
526                 
527                                 ll.src = 'images/indicator_tiny.gif';
528                                 ll.className = 'hlLoading';
529                                 ll.id = 'LL-' + id;
530
531                                 content.appendChild(ll);
532
533                         } */
534
535                         var upic = $('FUPDPIC-' + id);
536
537                         if (upic) {     
538                                 upic.src = 'images/indicator_white.gif'; 
539                         }
540
541                 } else if (cached_article && article_is_unread) {
542
543                         query = query + "&mode=prefetch";
544
545                         render_article(cached_article);
546
547                 } else if (cached_article) {
548
549                         query = query + "&mode=prefetch_old";
550                         render_article(cached_article);
551
552                 }
553
554                 cache_expire();
555
556                 last_requested_article = id;
557
558                 new Ajax.Request(query, {
559                         onComplete: function(transport) { 
560                                 article_callback2(transport, id, feed_id); 
561                         } });
562
563                 return false;
564
565         } catch (e) {
566                 exception_error("view", e);
567         }
568 }
569
570 function tMark(id) {
571         return toggleMark(id);
572 }
573
574 function tPub(id) {
575         return togglePub(id);
576 }
577
578 function tMark_afh_off(effect) {
579         try {
580                 var elem = effect.effects[0].element;
581
582                 debug("tMark_afh_off : " + elem.id);
583
584                 if (elem) {
585                         elem.src = elem.src.replace("mark_set", "mark_unset");
586                         elem.alt = __("Star article");
587                         Element.show(elem);
588                 }
589
590         } catch (e) {
591                 exception_error("tMark_afh_off", e);
592         }
593 }
594
595 function tPub_afh_off(effect) {
596         try {
597                 var elem = effect.effects[0].element;
598
599                 debug("tPub_afh_off : " + elem.id);
600
601                 if (elem) {
602                         elem.src = elem.src.replace("pub_set", "pub_unset");
603                         elem.alt = __("Publish article");
604                         Element.show(elem);
605                 }
606
607         } catch (e) {
608                 exception_error("tPub_afh_off", e);
609         }
610 }
611
612 function toggleMark(id, client_only, no_effects) {
613
614         try {
615
616                 var query = "backend.php?op=rpc&id=" + id + "&subop=mark";
617         
618                 query = query + "&afid=" + getActiveFeedId();
619         
620                 if (tagsAreDisplayed()) {
621                         query = query + "&omode=tl";
622                 } else {
623                         query = query + "&omode=flc";
624                 }
625         
626                 var mark_img = $("FMPIC-" + id);
627
628                 if (!mark_img) return;
629
630                 var vfeedu = $("FEEDU--1");
631                 var crow = $("RROW-" + id);
632         
633                 if (mark_img.src.match("mark_unset")) {
634                         mark_img.src = mark_img.src.replace("mark_unset", "mark_set");
635                         mark_img.alt = __("Unstar article");
636                         query = query + "&mark=1";
637
638                         if (db) {
639                                 db.execute("UPDATE articles SET marked = 1 WHERE id = ?", [id]);
640                         }
641
642                 } else {
643                         //mark_img.src = "images/mark_unset.png";
644                         mark_img.alt = __("Please wait...");
645                         query = query + "&mark=0";
646         
647                         if ($("headlinesList") && !no_effects) {
648                                 Effect.Puff(mark_img, {duration : 0.25, afterFinish: tMark_afh_off});
649                         } else { 
650                                 mark_img.src = mark_img.src.replace("mark_set", "mark_unset");
651                                 mark_img.alt = __("Star article");
652                         }
653
654                         if (db) {
655                                 db.execute("UPDATE articles SET marked = 0 WHERE id = ?", [id]);
656                         }
657
658                 }
659
660                 if (!no_effects) update_local_feedlist_counters();
661
662                 if (!client_only) {
663                         debug(query);
664
665                         new Ajax.Request(query, {
666                                 onComplete: function(transport) { 
667                                         all_counters_callback2(transport); 
668                                 } });
669
670                 }
671
672         } catch (e) {
673                 exception_error("toggleMark", e);
674         }
675 }
676
677 function togglePub(id, client_only, no_effects, note) {
678
679         try {
680
681                 var query = "backend.php?op=rpc&id=" + id + "&subop=publ";
682         
683                 query = query + "&afid=" + getActiveFeedId();
684
685                 if (note != undefined) {
686                         query = query + "&note=" + param_escape(note);
687                 } else {
688                         query = query + "&note=undefined";
689                 }
690         
691                 if (tagsAreDisplayed()) {
692                         query = query + "&omode=tl";
693                 } else {
694                         query = query + "&omode=flc";
695                 }
696         
697                 var mark_img = $("FPPIC-" + id);
698
699                 if (!mark_img) return;
700
701                 var vfeedu = $("FEEDU--2");
702                 var crow = $("RROW-" + id);
703         
704                 if (mark_img.src.match("pub_unset") || note != undefined) {
705                         mark_img.src = mark_img.src.replace("pub_unset", "pub_set");
706                         mark_img.alt = __("Unpublish article");
707                         query = query + "&pub=1";
708
709                 } else {
710                         //mark_img.src = "images/pub_unset.png";
711                         mark_img.alt = __("Please wait...");
712                         query = query + "&pub=0";
713         
714                         if ($("headlinesList") && !no_effects) {
715                                 Effect.Puff(mark_img, {duration : 0.25, afterFinish: tPub_afh_off});
716                         } else { 
717                                 mark_img.src = mark_img.src.replace("pub_set", "pub_unset");
718                                 mark_img.alt = __("Publish article");
719                         }
720                 }
721
722                 if (!client_only) {
723                         new Ajax.Request(query, {
724                                 onComplete: function(transport) { 
725                                         toggle_published_callback(transport); 
726                                 } });
727                 }
728
729         } catch (e) {
730
731                 exception_error("togglePub", e);
732         }
733 }
734
735 function correctHeadlinesOffset(id) {
736         
737         try {
738
739                 var hlist = $("headlinesList");
740                 var container = $("headlinesInnerContainer");
741                 var row = $("RROW-" + id);
742         
743                 var viewport = container.offsetHeight;
744         
745                 var rel_offset_top = row.offsetTop - container.scrollTop;
746                 var rel_offset_bottom = row.offsetTop + row.offsetHeight - container.scrollTop;
747         
748                 debug("Rtop: " + rel_offset_top + " Rbtm: " + rel_offset_bottom);
749                 debug("Vport: " + viewport);
750
751                 if (rel_offset_top <= 0 || rel_offset_top > viewport) {
752                         container.scrollTop = row.offsetTop;
753                 } else if (rel_offset_bottom > viewport) {
754
755                         /* doesn't properly work with Opera in some cases because
756                                 Opera fucks up element scrolling */
757
758                         container.scrollTop = row.offsetTop + row.offsetHeight - viewport;              
759                 } 
760
761         } catch (e) {
762                 exception_error("correctHeadlinesOffset", e);
763         }
764
765 }
766
767 function moveToPost(mode) {
768
769         try {
770
771                 var rows;
772
773                 if (isCdmMode()) {
774                         rows = cdmGetVisibleArticles();
775                 } else {
776                         rows = getVisibleHeadlineIds();
777                 }
778                 
779                 var prev_id = false;
780                 var next_id = false;
781                 
782                 if (!$('RROW-' + active_post_id)) {
783                         active_post_id = false;
784                 }
785                 
786                 if (active_post_id == false) {
787                         next_id = getFirstVisibleHeadlineId();
788                         prev_id = getLastVisibleHeadlineId();
789                 } else {        
790                         for (var i = 0; i < rows.length; i++) {
791                                 if (rows[i] == active_post_id) {
792                                         prev_id = rows[i-1];
793                                         next_id = rows[i+1];                    
794                                 }
795                         }
796                 }
797                 
798                 if (mode == "next") {
799                         if (next_id) {
800                                 if (isCdmMode()) {
801         
802                                         if (!cdmArticleIsActuallyVisible(next_id)) {
803                                                 cdmScrollToArticleId(next_id);
804                                         }
805                                         cdmSelectArticles("none");
806                                         toggleUnread(next_id, 0, true);
807                                         toggleSelected(next_id);
808
809                                 } else {
810                                         correctHeadlinesOffset(next_id);
811                                         view(next_id, getActiveFeedId());
812                                 }
813                         }
814                 }
815                 
816                 if (mode == "prev") {
817                         if (prev_id) {
818                                 if (isCdmMode()) {
819                                         cdmScrollToArticleId(prev_id);
820                                         cdmSelectArticles("none");
821                                         toggleUnread(prev_id, 0, true);
822                                         toggleSelected(prev_id);
823                                 } else {
824                                         correctHeadlinesOffset(prev_id);
825                                         view(prev_id, getActiveFeedId());
826                                 }
827                         }
828                 } 
829
830         } catch (e) {
831                 exception_error("moveToPost", e);
832         }
833 }
834
835 function toggleSelected(id) {
836         try {
837         
838                 var cb = $("RCHK-" + id);
839
840                 var row = $("RROW-" + id);
841                 if (row) {
842                         var nc = row.className;
843                         
844                         if (!nc.match("Selected")) {
845                                 nc = nc + "Selected";
846                                 if (cb) {
847                                         cb.checked = true;
848                                 }
849
850                                 // In CDM basically last selected article == active article
851                                 if (isCdmMode()) active_post_id = id;
852                         } else {
853                                 nc = nc.replace("Selected", "");
854                                 if (cb) {
855                                         cb.checked = false;
856                                 }
857
858                         }
859
860                         row.className = nc;
861                 }
862         } catch (e) {
863                 exception_error("toggleSelected", e);
864         }
865 }
866
867 function toggleUnread_afh(effect) {
868         try {
869
870                 var elem = effect.element;
871                 elem.style.backgroundColor = "";
872
873         } catch (e) {
874                 exception_error("toggleUnread_afh", e);
875         }
876
877
878 function toggleUnread(id, cmode, effect) {
879         try {
880         
881                 var row = $("RROW-" + id);
882                 if (row) {
883                         var nc = row.className;
884                         var is_selected = row.className.match("Selected");
885                         nc = nc.replace("Unread", "");
886                         nc = nc.replace("Selected", "");
887
888                         // since we are removing selection from the object, uncheck
889                         // corresponding checkbox
890
891                         var cb = $("RCHK-" + id);
892                         if (cb) {
893                                 cb.checked = false;
894                         }
895
896                         // NOTE: I'm not sure that resetting selection here is a feature -fox
897
898                         if (cmode == undefined || cmode == 2) {
899                                 if (row.className.match("Unread")) {
900                                         row.className = nc;
901
902                                         if (effect) {
903                                                 new Effect.Highlight(row, {duration: 1, startcolor: "#fff7d5",
904                                                         afterFinish: toggleUnread_afh,
905                                                         queue: { position:'end', scope: 'TMRQ-' + id, limit: 1 } } );
906                                         } 
907
908                                 } else {
909                                         row.className = nc + "Unread";
910                                 }
911
912                                 if (db) {
913                                         db.execute("UPDATE articles SET unread = not unread "+
914                                                 "WHERE id = ?", [id]);
915                                 }
916
917                         } else if (cmode == 0) {
918                                 row.className = nc;
919
920                                 if (effect) {
921                                         new Effect.Highlight(row, {duration: 1, startcolor: "#fff7d5",
922                                                 afterFinish: toggleUnread_afh,
923                                                 queue: { position:'end', scope: 'TMRQ-' + id, limit: 1 } } );
924                                 } 
925
926                                 if (db) {
927                                         db.execute("UPDATE articles SET unread = 0 "+
928                                                 "WHERE id = ?", [id]);
929                                 }
930
931                         } else if (cmode == 1) {
932                                 row.className = nc + "Unread";
933
934                                 if (db) {
935                                         db.execute("UPDATE articles SET unread = 1 "+
936                                                 "WHERE id = ?", [id]);
937                                 }
938
939                         }
940
941                         update_local_feedlist_counters();
942
943                         // Disable unmarking as selected for the time being (16.05.08) -fox
944                         if (is_selected) row.className = row.className + "Selected";
945
946                         if (cmode == undefined) cmode = 2;
947
948                         var query = "backend.php?op=rpc&subop=catchupSelected" +
949                                 "&cmode=" + param_escape(cmode);
950
951                         var ids = "?ids=" + param_escape(id);
952
953 //                      notify_progress("Loading, please wait...");
954
955                         new Ajax.Request(query, {
956                                 parameters: ids,
957                                 onComplete: function(transport) { 
958                                         all_counters_callback2(transport); 
959                                 } });
960
961                 }
962
963         } catch (e) {
964                 exception_error("toggleUnread", e);
965         }
966 }
967
968 function selectionRemoveLabel(id) {
969         try {
970
971                 var ids = getSelectedArticleIds2();
972
973                 if (ids.length == 0) {
974                         alert(__("No articles are selected."));
975                         return;
976                 }
977
978 //              var ok = confirm(__("Remove selected articles from label?"));
979
980 //              if (ok) {
981
982                         var query = "backend.php?op=rpc&subop=removeFromLabel&ids=" +
983                                 param_escape(ids.toString()) + "&lid=" + param_escape(id);
984
985 //                      notify_progress("Loading, please wait...");
986
987                         cache_invalidate("F:" + (-11 - id));
988
989                         new Ajax.Request(query, {
990                                 onComplete: function(transport) { 
991                                         show_labels_in_headlines(transport);
992                                         all_counters_callback2(transport);
993                                 } });
994
995 //              }
996
997         } catch (e) {
998                 exception_error("selectionAssignLabel", e);
999
1000         }
1001 }
1002
1003 function selectionAssignLabel(id) {
1004         try {
1005
1006                 var ids = getSelectedArticleIds2();
1007
1008                 if (ids.length == 0) {
1009                         alert(__("No articles are selected."));
1010                         return;
1011                 }
1012
1013 //              var ok = confirm(__("Assign selected articles to label?"));
1014
1015 //              if (ok) {
1016
1017                         cache_invalidate("F:" + (-11 - id));
1018
1019                         var query = "backend.php?op=rpc&subop=assignToLabel&ids=" +
1020                                 param_escape(ids.toString()) + "&lid=" + param_escape(id);
1021
1022 //                      notify_progress("Loading, please wait...");
1023
1024                         new Ajax.Request(query, {
1025                                 onComplete: function(transport) { 
1026                                         show_labels_in_headlines(transport);
1027                                         all_counters_callback2(transport);
1028                                 } });
1029
1030 //              }
1031
1032         } catch (e) {
1033                 exception_error("selectionAssignLabel", e);
1034
1035         }
1036 }
1037
1038 function selectionToggleUnread(cdm_mode, set_state, callback_func, no_error) {
1039         try {
1040                 var rows;
1041
1042                 if (cdm_mode) {
1043                         rows = cdmGetSelectedArticles();
1044                 } else {        
1045                         rows = getSelectedTableRowIds("headlinesList", "RROW", "RCHK");
1046                 }
1047
1048                 if (rows.length == 0 && !no_error) {
1049                         alert(__("No articles are selected."));
1050                         return;
1051                 }
1052
1053                 for (i = 0; i < rows.length; i++) {
1054                         var row = $("RROW-" + rows[i]);
1055                         if (row) {
1056                                 var nc = row.className;
1057                                 nc = nc.replace("Unread", "");
1058                                 nc = nc.replace("Selected", "");
1059
1060                                 if (set_state == undefined) {
1061                                         if (row.className.match("Unread")) {
1062                                                 row.className = nc + "Selected";
1063                                         } else {
1064                                                 row.className = nc + "UnreadSelected";
1065                                         }
1066                                         if (db) {
1067                                                 db.execute("UPDATE articles SET unread = NOT unread WHERE id = ?", 
1068                                                         [rows[i]]);
1069                                         }
1070                                 }
1071
1072                                 if (set_state == false) {
1073                                         row.className = nc + "Selected";
1074                                         if (db) {
1075                                                 db.execute("UPDATE articles SET unread = 0 WHERE id = ?", 
1076                                                         [rows[i]]);
1077                                         }
1078                                 }
1079
1080                                 if (set_state == true) {
1081                                         row.className = nc + "UnreadSelected";
1082                                         if (db) {
1083                                                 db.execute("UPDATE articles SET unread = 1 WHERE id = ?", 
1084                                                         [rows[i]]);
1085                                         }
1086                                 }
1087                         }
1088                 }
1089
1090                 if (rows.length > 0) {
1091
1092                         update_local_feedlist_counters();
1093
1094                         var cmode = "";
1095
1096                         if (set_state == undefined) {
1097                                 cmode = "2";
1098                         } else if (set_state == true) {
1099                                 cmode = "1";
1100                         } else if (set_state == false) {
1101                                 cmode = "0";
1102                         }
1103
1104                         var query = "backend.php?op=rpc&subop=catchupSelected" +
1105                                 "&cmode=" + cmode;
1106
1107                         var ids = "?ids=" + param_escape(rows.toString()); 
1108
1109                         notify_progress("Loading, please wait...");
1110
1111                         new Ajax.Request(query, {
1112                                 parameters: ids,
1113                                 onComplete: function(transport) { 
1114                                         catchup_callback2(transport, callback_func); 
1115                                 } });
1116
1117                 }
1118
1119         } catch (e) {
1120                 exception_error("selectionToggleUnread", e);
1121         }
1122 }
1123
1124 function selectionToggleMarked(cdm_mode) {
1125         try {
1126         
1127                 var rows;
1128                 
1129                 if (cdm_mode) {
1130                         rows = cdmGetSelectedArticles();
1131                 } else {        
1132                         rows = getSelectedTableRowIds("headlinesList", "RROW", "RCHK");
1133                 }       
1134
1135                 if (rows.length == 0) {
1136                         alert(__("No articles are selected."));
1137                         return;
1138                 }
1139
1140                 for (i = 0; i < rows.length; i++) {
1141                         toggleMark(rows[i], true, true);
1142                 }
1143
1144                 update_local_feedlist_counters();
1145
1146                 if (rows.length > 0) {
1147
1148                         var query = "backend.php?op=rpc&subop=markSelected&ids=" +
1149                                 param_escape(rows.toString()) + "&cmode=2";
1150
1151                         query = query + "&afid=" + getActiveFeedId();
1152
1153 /*                      if (tagsAreDisplayed()) {
1154                                 query = query + "&omode=tl";
1155                         } else {
1156                                 query = query + "&omode=flc";
1157                         } */
1158
1159                         query = query + "&omode=lc";
1160
1161                         new Ajax.Request(query, {
1162                                 onComplete: function(transport) { 
1163                                         all_counters_callback2(transport); 
1164                                 } });
1165
1166                 }
1167
1168         } catch (e) {
1169                 exception_error("selectionToggleMarked", e);
1170         }
1171 }
1172
1173 function selectionTogglePublished(cdm_mode) {
1174         try {
1175         
1176                 var rows;
1177                 
1178                 if (cdm_mode) {
1179                         rows = cdmGetSelectedArticles();
1180                 } else {        
1181                         rows = getSelectedTableRowIds("headlinesList", "RROW", "RCHK");
1182                 }       
1183
1184                 if (rows.length == 0) {
1185                         alert(__("No articles are selected."));
1186                         return;
1187                 }
1188
1189                 for (i = 0; i < rows.length; i++) {
1190                         togglePub(rows[i], true, true);
1191                 }
1192
1193                 if (rows.length > 0) {
1194
1195                         var query = "backend.php?op=rpc&subop=publishSelected&ids=" +
1196                                 param_escape(rows.toString()) + "&cmode=2";
1197
1198                         query = query + "&afid=" + getActiveFeedId();
1199
1200 /*                      if (tagsAreDisplayed()) {
1201                                 query = query + "&omode=tl";
1202                         } else {
1203                                 query = query + "&omode=flc";
1204                         } */
1205
1206                         query = query + "&omode=lc";
1207
1208                         new Ajax.Request(query, {
1209                                 onComplete: function(transport) { 
1210                                         all_counters_callback2(transport); 
1211                                 } });
1212
1213                 }
1214
1215         } catch (e) {
1216                 exception_error("selectionToggleMarked", e);
1217         }
1218 }
1219
1220 function cdmGetSelectedArticles() {
1221         var sel_articles = new Array();
1222         var container = $("headlinesInnerContainer");
1223
1224         for (i = 0; i < container.childNodes.length; i++) {
1225                 var child = container.childNodes[i];
1226
1227                 if (child.id && child.id.match("RROW-") && child.className.match("Selected")) {
1228                         var c_id = child.id.replace("RROW-", "");
1229                         sel_articles.push(c_id);
1230                 }
1231         }
1232
1233         return sel_articles;
1234 }
1235
1236 function cdmGetVisibleArticles() {
1237         var sel_articles = new Array();
1238         var container = $("headlinesInnerContainer");
1239
1240         if (!container) return sel_articles;
1241
1242         for (i = 0; i < container.childNodes.length; i++) {
1243                 var child = container.childNodes[i];
1244
1245                 if (child.id && child.id.match("RROW-")) {
1246                         var c_id = child.id.replace("RROW-", "");
1247                         sel_articles.push(c_id);
1248                 }
1249         }
1250
1251         return sel_articles;
1252 }
1253
1254 function cdmGetUnreadArticles() {
1255         var sel_articles = new Array();
1256         var container = $("headlinesInnerContainer");
1257
1258         for (i = 0; i < container.childNodes.length; i++) {
1259                 var child = container.childNodes[i];
1260
1261                 if (child.id && child.id.match("RROW-") && child.className.match("Unread")) {
1262                         var c_id = child.id.replace("RROW-", "");
1263                         sel_articles.push(c_id);
1264                 }
1265         }
1266
1267         return sel_articles;
1268 }
1269
1270
1271 // mode = all,none,unread
1272 function cdmSelectArticles(mode) {
1273         var container = $("headlinesInnerContainer");
1274
1275         for (i = 0; i < container.childNodes.length; i++) {
1276                 var child = container.childNodes[i];
1277
1278                 if (child.id && child.id.match("RROW-")) {
1279                         var aid = child.id.replace("RROW-", "");
1280
1281                         var cb = $("RCHK-" + aid);
1282
1283                         if (mode == "all") {
1284                                 if (!child.className.match("Selected")) {
1285                                         child.className = child.className + "Selected";
1286                                         cb.checked = true;
1287                                 }
1288                         } else if (mode == "unread") {
1289                                 if (child.className.match("Unread") && !child.className.match("Selected")) {
1290                                         child.className = child.className + "Selected";
1291                                         cb.checked = true;
1292                                 }
1293                         } else {
1294                                 child.className = child.className.replace("Selected", "");
1295                                 cb.checked = false;
1296                         }
1297                 }               
1298         }
1299 }
1300
1301 function catchupPage() {
1302
1303         var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
1304         
1305         var str = __("Mark all visible articles in %s as read?");
1306
1307         str = str.replace("%s", fn);
1308
1309         if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1310                 return;
1311         }
1312
1313         if ($("headlinesList")) {
1314                 selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', true, 'Unread', true);
1315                 selectionToggleUnread(false, false, 'viewCurrentFeed()', true);
1316                 selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', false);
1317         } else {
1318                 cdmSelectArticles('all');
1319                 selectionToggleUnread(true, false, 'viewCurrentFeed()', true)
1320                 cdmSelectArticles('none');
1321         }
1322 }
1323
1324 function deleteSelection() {
1325
1326         try {
1327
1328                 var rows;
1329         
1330                 if ($("headlinesList")) {
1331                         rows = getSelectedTableRowIds("headlinesList", "RROW", "RCHK");
1332                 } else {        
1333                         rows = cdmGetSelectedArticles();
1334                 }
1335         
1336                 if (rows.length == 0) {
1337                         alert(__("No articles are selected."));
1338                         return;
1339                 }
1340         
1341         
1342                 var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
1343                 var str;
1344                 var op;
1345         
1346                 if (getActiveFeedId() != 0) {
1347                         str = __("Delete %d selected articles in %s?");
1348                 } else {
1349                         str = __("Delete %d selected articles?");
1350                 }
1351         
1352                 str = str.replace("%d", rows.length);
1353                 str = str.replace("%s", fn);
1354         
1355                 if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1356                         return;
1357                 }
1358
1359                 query = "backend.php?op=rpc&subop=delete&ids=" + param_escape(rows);
1360
1361                 debug(query);
1362
1363                 new Ajax.Request(query, {
1364                         onComplete: function(transport) {
1365                                         viewCurrentFeed();
1366                                 } });
1367
1368         } catch (e) {
1369                 exception_error("deleteSelection", e);
1370         }
1371 }
1372
1373 function archiveSelection() {
1374
1375         try {
1376
1377                 var rows;
1378         
1379                 if ($("headlinesList")) {
1380                         rows = getSelectedTableRowIds("headlinesList", "RROW", "RCHK");
1381                 } else {        
1382                         rows = cdmGetSelectedArticles();
1383                 }
1384         
1385                 if (rows.length == 0) {
1386                         alert(__("No articles are selected."));
1387                         return;
1388                 }
1389         
1390         
1391                 var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
1392                 var str;
1393                 var op;
1394         
1395                 if (getActiveFeedId() != 0) {
1396                         str = __("Archive %d selected articles in %s?");
1397                         op = "archive";
1398                 } else {
1399                         str = __("Move %d archived articles back?");
1400                         op = "unarchive";
1401                 }
1402         
1403                 str = str.replace("%d", rows.length);
1404                 str = str.replace("%s", fn);
1405         
1406                 if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1407                         return;
1408                 }
1409
1410                 query = "backend.php?op=rpc&subop="+op+"&ids=" + param_escape(rows);
1411
1412                 debug(query);
1413
1414                 for (var i = 0; i < rows.length; i++) {
1415                         cache_invalidate(rows[i]);
1416                 }
1417
1418                 new Ajax.Request(query, {
1419                         onComplete: function(transport) {
1420                                         viewCurrentFeed();
1421                                 } });
1422
1423         } catch (e) {
1424                 exception_error("archiveSelection", e);
1425         }
1426 }
1427
1428 function catchupSelection() {
1429
1430         try {
1431
1432                 var rows;
1433         
1434                 if ($("headlinesList")) {
1435                         rows = getSelectedTableRowIds("headlinesList", "RROW", "RCHK");
1436                 } else {        
1437                         rows = cdmGetSelectedArticles();
1438                 }
1439         
1440                 if (rows.length == 0) {
1441                         alert(__("No articles are selected."));
1442                         return;
1443                 }
1444         
1445         
1446                 var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
1447                 
1448                 var str = __("Mark %d selected articles in %s as read?");
1449         
1450                 str = str.replace("%d", rows.length);
1451                 str = str.replace("%s", fn);
1452         
1453                 if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1454                         return;
1455                 }
1456         
1457                 if ($("headlinesList")) {
1458                         selectionToggleUnread(false, false, 'viewCurrentFeed()', true);
1459         //              selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', false);
1460                 } else {
1461                         selectionToggleUnread(true, false, 'viewCurrentFeed()', true)
1462         //              cdmSelectArticles('none');
1463                 }
1464
1465         } catch (e) {
1466                 exception_error("catchupSelection", e);
1467         }
1468 }
1469
1470 function editArticleTags(id, feed_id, cdm_enabled) {
1471         displayDlg('editArticleTags', id);
1472 }
1473
1474
1475 function tag_saved_callback(transport) {
1476         try {
1477                 debug("in tag_saved_callback");
1478
1479                 closeInfoBox();
1480                 notify("");
1481
1482                 if (tagsAreDisplayed()) {
1483                         _reload_feedlist_after_view = true;
1484                 }
1485
1486
1487                 if (transport.responseXML) {
1488                         var tags_str = transport.responseXML.getElementsByTagName("tags-str")[0];
1489                         
1490                         if (tags_str) {
1491                                 var id = tags_str.getAttribute("id");
1492
1493                                 if (id) {
1494                                         var tags = $("ATSTR-" + id);
1495                                         if (tags) {
1496                                                 tags.innerHTML = tags_str.firstChild.nodeValue;
1497                                         }
1498                                 }
1499                         }
1500                 }
1501
1502         } catch (e) {
1503                 exception_error("tag_saved_callback", e);
1504         }
1505 }
1506
1507 function editTagsSave() {
1508
1509         notify_progress("Saving article tags...");
1510
1511         var form = document.forms["tag_edit_form"];
1512
1513         var query = Form.serialize("tag_edit_form");
1514
1515         query = "backend.php?op=rpc&subop=setArticleTags&" + query;
1516
1517         debug(query);
1518
1519         new Ajax.Request(query, {
1520                 onComplete: function(transport) {
1521                                 tag_saved_callback(transport);
1522                         } });
1523
1524 }
1525
1526 function editTagsInsert() {
1527         try {
1528
1529                 var form = document.forms["tag_edit_form"];
1530
1531                 var found_tags = form.found_tags;
1532                 var tags_str = form.tags_str;
1533
1534                 var tag = found_tags[found_tags.selectedIndex].value;
1535
1536                 if (tags_str.value.length > 0 && 
1537                                 tags_str.value.lastIndexOf(", ") != tags_str.value.length - 2) {
1538
1539                         tags_str.value = tags_str.value + ", ";
1540                 }
1541
1542                 tags_str.value = tags_str.value + tag + ", ";
1543
1544                 found_tags.selectedIndex = 0;
1545                 
1546         } catch (e) {
1547                 exception_error("editTagsInsert", e);
1548         }
1549 }
1550
1551 function cdmScrollViewport(where) {
1552         debug("cdmScrollViewport: " + where);
1553
1554         var ctr = $("headlinesInnerContainer");
1555
1556         if (!ctr) return;
1557
1558         if (where == "bottom") {
1559                 ctr.scrollTop = ctr.scrollHeight;
1560         } else {
1561                 ctr.scrollTop = where;
1562         }
1563 }
1564
1565 function cdmArticleIsBelowViewport(id) {
1566         try {
1567                 var ctr = $("headlinesInnerContainer");
1568                 var e = $("RROW-" + id);
1569
1570                 if (!e || !ctr) return;
1571
1572                 // article starts below viewport
1573
1574                 if (ctr.scrollTop < e.offsetTop) {
1575                         return true;
1576                 } else {        
1577                         return false;
1578                 }
1579
1580         } catch (e) {
1581                 exception_error("cdmArticleIsVisible", e);
1582         }
1583 }
1584
1585 function cdmArticleIsAboveViewport(id) {
1586         try {
1587                 var ctr = $("headlinesInnerContainer");
1588                 var e = $("RROW-" + id);
1589
1590                 if (!e || !ctr) return;
1591
1592                 // article starts above viewport
1593
1594                 if (ctr.scrollTop > e.offsetTop + e.offsetHeight) {
1595                         return true;
1596                 } else {        
1597                         return false;
1598                 }
1599
1600         } catch (e) {
1601                 exception_error("cdmArticleIsVisible", e);
1602         }
1603 }
1604
1605 function cdmScrollToArticleId(id) {
1606         try {
1607                 var ctr = $("headlinesInnerContainer");
1608                 var e = $("RROW-" + id);
1609
1610                 if (!e || !ctr) return;
1611
1612                 ctr.scrollTop = e.offsetTop;
1613
1614         } catch (e) {
1615                 exception_error("cdmScrollToArticleId", e);
1616         }
1617 }
1618
1619 function cdmArticleIsActuallyVisible(id) {
1620         try {
1621                 var ctr = $("headlinesInnerContainer");
1622                 var e = $("RROW-" + id);
1623
1624                 if (!e || !ctr) return;
1625
1626                 // article fits in viewport OR article is longer than viewport and
1627                 // its bottom is visible
1628
1629                 if (ctr.scrollTop <= e.offsetTop && e.offsetTop + e.offsetHeight <=
1630                                 ctr.scrollTop + ctr.offsetHeight) {
1631
1632                         return true;
1633                 
1634                 } else if (e.offsetHeight > ctr.offsetHeight &&
1635                                 e.offsetTop + e.offsetHeight >= ctr.scrollTop &&
1636                                 e.offsetTop + e.offsetHeight <= ctr.scrollTop + ctr.offsetHeight) {
1637
1638                         return true;
1639
1640                 }
1641
1642                 return false;
1643
1644         } catch (e) {
1645                 exception_error("cdmArticleIsVisible", e);
1646         }
1647 }
1648
1649 function cdmWatchdog() {
1650
1651         try {
1652
1653                 var ctr = $("headlinesInnerContainer");
1654
1655                 if (!ctr) return;
1656
1657                 var ids = new Array();
1658
1659                 var e = ctr.firstChild;
1660
1661                 while (e) {
1662                         if (e.className && e.className == "cdmArticleUnread" && e.id &&
1663                                         e.id.match("RROW-")) {
1664
1665                                 // article fits in viewport OR article is longer than viewport and
1666                                 // its bottom is visible
1667
1668                                 if (ctr.scrollTop <= e.offsetTop && e.offsetTop + e.offsetHeight <=
1669                                                 ctr.scrollTop + ctr.offsetHeight) {
1670
1671 //                                      debug(e.id + " is visible " + e.offsetTop + "." + 
1672 //                                              (e.offsetTop + e.offsetHeight) + " vs " + ctr.scrollTop + "." +
1673 //                                              (ctr.scrollTop + ctr.offsetHeight));
1674
1675                                         ids.push(e.id.replace("RROW-", ""));
1676
1677                                 } else if (e.offsetHeight > ctr.offsetHeight &&
1678                                                 e.offsetTop + e.offsetHeight >= ctr.scrollTop &&
1679                                                 e.offsetTop + e.offsetHeight <= ctr.scrollTop + ctr.offsetHeight) {
1680
1681                                         ids.push(e.id.replace("RROW-", "")); 
1682
1683                                 }
1684
1685                                 // method 2: article bottom is visible and is in upper 1/2 of the viewport
1686
1687 /*                              if (e.offsetTop + e.offsetHeight >= ctr.scrollTop &&
1688                                                 e.offsetTop + e.offsetHeight <= ctr.scrollTop + ctr.offsetHeight/2) {
1689
1690                                         ids.push(e.id.replace("RROW-", "")); 
1691
1692                                 } */
1693
1694                         }
1695
1696                         e = e.nextSibling;
1697                 }
1698
1699                 debug("cdmWatchdog, ids= " + ids.toString());
1700
1701                 if (ids.length > 0) {
1702
1703                         for (var i = 0; i < ids.length; i++) {
1704                                 var e = $("RROW-" + ids[i]);
1705                                 if (e) {
1706                                         e.className = e.className.replace("Unread", "");
1707                                 }
1708                         }
1709
1710                         var query = "backend.php?op=rpc&subop=catchupSelected" +
1711                                 "&cmode=0";
1712
1713                         var ids = "?ids=" + param_escape(ids.toString());
1714
1715                         new Ajax.Request(query, {
1716                                 parameters: ids,
1717                                 onComplete: function(transport) { 
1718                                         all_counters_callback2(transport); 
1719                                 } });
1720
1721                 }
1722
1723                 _cdm_wd_timeout = window.setTimeout("cdmWatchdog()", 4000);
1724
1725         } catch (e) {
1726                 exception_error("cdmWatchdog", e);
1727         }
1728
1729 }
1730
1731
1732 function cache_inject(id, article, param) {
1733         try {
1734                 if (!cache_check_param(id, param)) {
1735                         debug("cache_article: miss: " + id + " [p=" + param + "]");
1736         
1737         
1738                         if (db) {
1739
1740                            var date = new Date();
1741                       var ts = Math.round(date.getTime() / 1000);
1742
1743                                 db.execute("INSERT INTO cache (id, article, param, added) VALUES (?, ?, ?, ?)",
1744                                         [id, article, param, ts]);                              
1745                         } else {
1746         
1747                                 var cache_obj = new Array();
1748         
1749                                 cache_obj["id"] = id;
1750                                 cache_obj["data"] = article;
1751                                 cache_obj["param"] = param;
1752         
1753                                 article_cache.push(cache_obj);
1754                         }
1755         
1756                 } else {
1757                         debug("cache_article: hit: " + id + " [p=" + param + "]");
1758                 }
1759         } catch (e) {   
1760                 exception_error("cache_inject", e);
1761         }
1762 }
1763
1764 function cache_find(id) {
1765
1766         if (db) {
1767                 var rs = db.execute("SELECT article FROM cache WHERE id = ?", [id]);
1768                 var a = false;
1769
1770                 if (rs.isValidRow()) {
1771                         var a = rs.field(0);                    
1772                 }
1773
1774                 rs.close();
1775
1776                 return a;
1777
1778         } else {
1779                 for (var i = 0; i < article_cache.length; i++) {
1780                         if (article_cache[i]["id"] == id) {
1781                                 return article_cache[i]["data"];
1782                         }
1783                 }
1784         }
1785         return false;
1786 }
1787
1788 function cache_find_param(id, param) {
1789
1790         if (db) {
1791                 var rs = db.execute("SELECT article FROM cache WHERE id = ? AND param = ?",
1792                         [id, param]);
1793                 var a = false;
1794
1795                 if (rs.isValidRow()) {
1796                         a = rs.field(0);
1797                 }
1798
1799                 rs.close();
1800
1801                 return a;
1802
1803         } else {
1804                 for (var i = 0; i < article_cache.length; i++) {
1805                         if (article_cache[i]["id"] == id && article_cache[i]["param"] == param) {
1806                                 return article_cache[i]["data"];
1807                         }
1808                 }
1809         }
1810         return false;
1811 }
1812
1813 function cache_check(id) {
1814
1815         if (db) {
1816                 var rs = db.execute("SELECT COUNT(*) AS c FROM cache WHERE id = ?",
1817                         [id]);
1818                 var a = false;
1819
1820                 if (rs.isValidRow()) {
1821                          a = rs.field(0) != "0";
1822                 }
1823
1824                 rs.close();
1825
1826                 return a;
1827
1828         } else {
1829                 for (var i = 0; i < article_cache.length; i++) {
1830                         if (article_cache[i]["id"] == id) {
1831                                 return true;
1832                         }
1833                 }
1834         }
1835         return false;
1836 }
1837
1838 function cache_check_param(id, param) {
1839
1840         if (db) {
1841                 var rs = db.execute("SELECT COUNT(*) AS c FROM cache WHERE id = ? AND param = ?",
1842                         [id, param]);
1843                 var a = false;
1844
1845                 if (rs.isValidRow()) {
1846                         a = rs.field(0) != "0";
1847                 }
1848
1849                 rs.close();
1850
1851                 return a;
1852
1853         } else {
1854                 for (var i = 0; i < article_cache.length; i++) {
1855                         if (article_cache[i]["id"] == id && article_cache[i]["param"] == param) {
1856                                 return true;
1857                         }
1858                 }
1859         }
1860         return false;
1861 }
1862
1863 function cache_expire() {
1864         if (db) {
1865                 var date = new Date();
1866                 var ts = Math.round(date.getTime() / 1000);
1867
1868                 db.execute("DELETE FROM cache WHERE added < ? - 1800 AND id LIKE 'FEEDLIST'", [ts]);
1869                 db.execute("DELETE FROM cache WHERE added < ? - 600 AND (id LIKE 'F:%' OR id LIKE 'C:%')", [ts]);
1870                 db.execute("DELETE FROM cache WHERE added < ? - 86400", [ts]);
1871
1872
1873         } else {
1874                 while (article_cache.length > 25) {
1875                         article_cache.shift();
1876                 }
1877         }
1878 }
1879
1880 function cache_empty() {
1881         article_cache = new Array();
1882 }
1883
1884 function cache_invalidate(id) {
1885         try {   
1886
1887                 if (db) {
1888                         rs = db.execute("DELETE FROM cache WHERE id = ?", [id]);
1889                         return rs.rowsAffected != 0;
1890                 } else {
1891
1892                         var i = 0
1893
1894                         while (i < article_cache.length) {
1895                                 if (article_cache[i]["id"] == id) {
1896                                         debug("cache_invalidate: removed id " + id);
1897                                         article_cache.splice(i, 1);
1898                                         return true;
1899                                 }
1900                                 i++;
1901                         }
1902                 }
1903
1904                 debug("cache_invalidate: id not found: " + id);
1905                 return false;
1906         } catch (e) {
1907                 exception_error("cache_invalidate", e);
1908         }
1909 }
1910
1911 function getActiveArticleId() {
1912         return active_post_id;
1913 }
1914
1915 function cdmClicked(id) {
1916         try {
1917                 var elem = $("RROW-" + id);
1918
1919                 if (elem) {
1920                         var id = elem.id.replace("RROW-", "");
1921                         active_post_id = id;
1922
1923 //                      cdmSelectArticles("none");
1924                         toggleUnread(id, 0, true);
1925 //                      toggleSelected(id);
1926
1927                 }
1928         } catch (e) {
1929                 exception_error("cdmClicked", e);
1930         } 
1931 }
1932
1933 function preload_article_callback(transport) {
1934         try {
1935                 if (transport.responseXML) {
1936                         var articles = transport.responseXML.getElementsByTagName("article");
1937
1938                         for (var i = 0; i < articles.length; i++) {
1939                                 var id = articles[i].getAttribute("id");
1940                                 if (!cache_check(id)) {
1941                                         cache_inject(id, articles[i].firstChild.nodeValue);                             
1942                                         debug("preloaded article: " + id);
1943                                 }
1944                         }
1945                 }
1946         } catch (e) {
1947                 exception_error("preload_article_callback", e);
1948         }
1949 }
1950
1951 function preloadArticleUnderPointer(id) {
1952         try {
1953                 if (getInitParam("bw_limit") == "1") return;
1954
1955                 if (post_under_pointer == id && !cache_check(id)) {
1956
1957                         debug("trying to preload article " + id);
1958
1959                         var neighbor_ids = getRelativePostIds(id, 1);
1960
1961                         /* only request uncached articles */
1962
1963                         var cids_to_request = Array();
1964
1965                         for (var i = 0; i < neighbor_ids.length; i++) {
1966                                 if (!cache_check(neighbor_ids[i])) {
1967                                         cids_to_request.push(neighbor_ids[i]);
1968                                 }
1969                         }
1970                         debug("additional ids: " + cids_to_request.toString());
1971
1972                         cids_to_request.push(id);
1973
1974                         var query = "backend.php?op=rpc&subop=getArticles&ids=" + 
1975                                 cids_to_request.toString();
1976                         new Ajax.Request(query, {
1977                                 onComplete: function(transport) { 
1978                                         preload_article_callback(transport);
1979                         } });
1980                 }
1981         } catch (e) {
1982                 exception_error("preloadArticleUnderPointer", e);
1983         }
1984 }
1985
1986 function postMouseIn(id) {
1987         try {
1988                 if (post_under_pointer != id) {
1989                         post_under_pointer = id;
1990                         if (!isCdmMode()) {
1991                                 window.setTimeout("preloadArticleUnderPointer(" + id + ")", 250);
1992                         }
1993                 }
1994
1995         } catch (e) {
1996                 exception_error("postMouseIn", e);
1997         }
1998 }
1999
2000 function postMouseOut(id) {
2001         try {
2002                 post_under_pointer = false;
2003         } catch (e) {
2004                 exception_error("postMouseOut", e);
2005         }
2006 }
2007
2008 function headlines_scroll_handler() {
2009         try {
2010
2011                 var e = $("headlinesInnerContainer");
2012
2013                 // don't do infinite scrolling when Limit == All
2014
2015                 var toolbar_form = document.forms["main_toolbar_form"];
2016
2017                 var limit = toolbar_form.limit[toolbar_form.limit.selectedIndex];
2018                 if (limit.value != 0) {
2019                 
2020                         debug((e.scrollTop + e.offsetHeight) + " vs " + e.scrollHeight + " dis? " +
2021                                 _infscroll_disable);
2022
2023                         if (e.scrollTop + e.offsetHeight > e.scrollHeight - 100) {
2024                                 if (!_infscroll_disable) {
2025                                         debug("more cowbell!");
2026                                         viewNextFeedPage();
2027                                 }
2028                         }
2029                 }
2030
2031         } catch (e) {
2032                 exception_error("headlines_scroll_handler", e);
2033         }
2034 }
2035
2036 function catchupRelativeToArticle(below) {
2037
2038         try {
2039
2040
2041                 if (!getActiveArticleId()) {
2042                         alert(__("No article is selected."));
2043                         return;
2044                 }
2045
2046                 var visible_ids;
2047
2048                 if ($("headlinesList")) {
2049                         visible_ids = getVisibleHeadlineIds();
2050                 } else {
2051                         visible_ids = cdmGetVisibleArticles();
2052                 }
2053
2054                 var ids_to_mark = new Array();
2055
2056                 if (!below) {
2057                         for (var i = 0; i < visible_ids.length; i++) {
2058                                 if (visible_ids[i] != getActiveArticleId()) {
2059                                         var e = $("RROW-" + visible_ids[i]);
2060
2061                                         if (e && e.className.match("Unread")) {
2062                                                 ids_to_mark.push(visible_ids[i]);
2063                                         }
2064                                 } else {
2065                                         break;
2066                                 }
2067                         }
2068                 } else {
2069                         for (var i = visible_ids.length-1; i >= 0; i--) {
2070                                 if (visible_ids[i] != getActiveArticleId()) {
2071                                         var e = $("RROW-" + visible_ids[i]);
2072
2073                                         if (e && e.className.match("Unread")) {
2074                                                 ids_to_mark.push(visible_ids[i]);
2075                                         }
2076                                 } else {
2077                                         break;
2078                                 }
2079                         }
2080                 }
2081
2082                 if (ids_to_mark.length == 0) {
2083                         alert(__("No articles found to mark"));
2084                 } else {
2085                         var msg = __("Mark %d article(s) as read?").replace("%d", ids_to_mark.length);
2086
2087                         if (confirm(msg)) {
2088
2089                                 for (var i = 0; i < ids_to_mark.length; i++) {
2090                                         var e = $("RROW-" + ids_to_mark[i]);
2091                                         e.className = e.className.replace("Unread", "");
2092                                 }
2093
2094                                 var query = "backend.php?op=rpc&subop=catchupSelected" +
2095                                         "&cmode=0";
2096
2097                                 var ids = "?ids=" + param_escape(ids_to_mark.toString()); 
2098
2099                                 new Ajax.Request(query, {
2100                                         parameters: ids,
2101                                         onComplete: function(transport) { 
2102                                                 catchup_callback2(transport); 
2103                                         } });
2104
2105                         }
2106                 }
2107
2108         } catch (e) {
2109                 exception_error("catchupRelativeToArticle", e);
2110         }
2111 }
2112
2113 function cdmExpandArticle(a_id) {
2114         try {
2115                 var id = 'CICD-' + a_id;
2116
2117                 try {
2118                         Element.hide("CEXC-" + a_id);
2119                 } catch (e) { } 
2120
2121                 Effect.Appear(id, {duration : 0.5, 
2122                         beforeStart: function(effect) { 
2123                                 var h_id = 'CICH-' + a_id;
2124                                 var h_elem = $(h_id);
2125                                 if (h_elem) { h_elem.style.display = "none"; }
2126
2127                                 toggleUnread(a_id, 0);
2128                         }});
2129
2130
2131         } catch (e) {
2132                 exception_error("appearBlockElementF", e);
2133         }
2134
2135 }
2136
2137 function fixHeadlinesOrder(ids) {
2138         try {
2139                 for (var i = 0; i < ids.length; i++) {
2140                         var e = $("RROW-" + ids[i]);
2141
2142                         if (e) {
2143                                 if (i % 2 == 0) {
2144                                         e.className = e.className.replace("even", "odd");
2145                                 } else {
2146                                         e.className = e.className.replace("odd", "even");
2147                                 }
2148                         }
2149                 }
2150         } catch (e) {
2151                 exception_error("fixHeadlinesOrder", e);
2152         }
2153 }
2154
2155 function hideReadHeadlines() {
2156         try {
2157
2158                 var ids = false;
2159                 var vis_ids = new Array();
2160
2161                 if ($("headlinesList")) {
2162                         ids = getVisibleHeadlineIds();
2163                 } else {
2164                         ids = cdmGetVisibleArticles();
2165                 }
2166
2167                 var read_headlines_visible = true;
2168
2169                 for (var i = 0; i < ids.length; i++) {
2170                         var row = $("RROW-" + ids[i]);
2171
2172                         if (row && row.className) {
2173                                 if (read_headlines_visible) {
2174                                         if (row.className.match("Unread") || row.className.match("Selected")) {
2175                                                 Element.show(row);
2176                                                 vis_ids.push(ids[i]);
2177                                         } else {
2178                                                 //Effect.Fade(row, {duration : 0.3});
2179                                                 Element.hide(row);
2180                                         }
2181                                 } else {
2182                                         Element.show(row);
2183                                         vis_ids.push(ids[i]);
2184                                 }
2185                         }
2186                 }
2187                 
2188                 fixHeadlinesOrder(vis_ids);
2189
2190                 read_headlines_visible = !read_headlines_visible;
2191
2192         } catch (e) {
2193                 exception_error("hideReadHeadlines", e);
2194         } 
2195 }
2196
2197 function invertHeadlineSelection() {
2198         try {
2199                 var rows = new Array();
2200                 var r = false;
2201                 
2202                 if (!isCdmMode()) {             
2203                         r = document.getElementsByTagName("TR");
2204                 } else {
2205                         r = document.getElementsByTagName("DIV");
2206                 }
2207
2208                 for (var i = 0; i < r.length; i++) {
2209                         if (r[i].id && r[i].id.match("RROW-")) {
2210                                 rows.push(r[i]);
2211                         }
2212                 }
2213                 
2214                 for (var i = 0; i < rows.length; i++) {
2215                         var nc = rows[i].className;
2216                         var id = rows[i].id.replace("RROW-", "");
2217                         var cb = $("RCHK-" + id);
2218
2219                         if (!rows[i].className.match("Selected")) {
2220                                 nc = nc + "Selected";
2221                                 cb.checked = true;
2222                         } else {
2223                                 nc = nc.replace("Selected", "");
2224                                 cb.checked = false;
2225                         }
2226
2227                         rows[i].className = nc;
2228
2229                 }
2230
2231         } catch (e) {
2232                 exception_error("invertHeadlineSelection", e);
2233         }
2234 }
2235
2236 function getArticleUnderPointer() {
2237         return post_under_pointer;
2238 }
2239
2240 function zoomToArticle(id) {
2241         try {
2242                 var w = window.open("backend.php?op=view&mode=zoom&id=" + param_escape(id), 
2243                         "ttrss_zoom_" + id,
2244                         "status=0,toolbar=0,location=0,width=450,height=300,scrollbars=1,menubar=0");
2245
2246         } catch (e) {
2247                 exception_error("zoomToArticle", e);
2248         }
2249 }
2250
2251 function showOriginalArticleInline(id) {
2252         try {
2253
2254                 var query = "backend.php?op=rpc&subop=getArticleLink&id=" + id;
2255
2256                 notify_progress("Loading, please wait...", true);
2257
2258                 new Ajax.Request(query, {
2259                         onComplete: function(transport) { 
2260
2261                                 if (transport.responseXML) {
2262                         
2263                                         var link = transport.responseXML.getElementsByTagName("link")[0];
2264                                         var id = transport.responseXML.getElementsByTagName("id")[0];
2265
2266                                         notify("");
2267
2268                                         if (link && id) {
2269                                                 link = link.firstChild.nodeValue;
2270
2271                                                 var ci = $("content-insert");
2272
2273                                                 var tmp = "<iframe id=\"inline_orig_article\" width=\""+ci.offsetWidth+"\" height=\""+ci.offsetHeight+"\" style=\"border-width : 0px;\" src=\""+link+"\"></iframe>";
2274
2275                                                 render_article(tmp);
2276
2277                                         }
2278                                 }
2279                         } });
2280
2281         } catch (e) {
2282                 exception_error("showOriginalArticleInline", e);
2283         }
2284 }
2285
2286
2287 function scrollArticle(offset) {
2288         try {
2289                 if (!isCdmMode()) {
2290                         var ci = $("content-insert");
2291                         if (ci) {
2292                                 ci.scrollTop += offset;
2293                         }
2294                 } else {
2295                         var hi = $("headlinesInnerContainer");
2296                         if (hi) {
2297                                 hi.scrollTop += offset;
2298                         }
2299
2300                 }
2301         } catch (e) {
2302                 exception_error("scrollArticle", e);
2303         }
2304 }
2305
2306 function show_labels_in_headlines(transport) {
2307         try {
2308                 if (transport.responseXML) {
2309                         var info = transport.responseXML.getElementsByTagName("info-for-headlines")[0];
2310
2311                         var elems = info.getElementsByTagName("entry");
2312
2313                         for (var l = 0; l < elems.length; l++) {
2314                                 var e_id = elems[l].getAttribute("id");
2315
2316                                 if (e_id) {
2317
2318                                         var ctr = $("HLLCTR-" + e_id);
2319
2320                                         if (ctr) {
2321                                                 ctr.innerHTML = elems[l].firstChild.nodeValue;
2322                                         }
2323                                 }
2324
2325                         }
2326
2327                 }
2328         } catch (e) {
2329                 exception_error("show_labels_in_headlines", e);
2330
2331         }
2332 }
2333
2334 function toggleHeadlineActions() {
2335         try {
2336                 var e = $("headlineActionsBody");
2337                 var p = $("headlineActionsDrop");
2338
2339                 if (!Element.visible(e)) {
2340                         Element.show(e);
2341                 } else {
2342                         Element.hide(e);
2343                 }
2344
2345                 e.scrollTop = 0;
2346                 e.style.left = (p.offsetLeft + 1) + "px";
2347                 e.style.top = (p.offsetTop + p.offsetHeight + 2) + "px";
2348
2349         } catch (e) {
2350                 exception_error("toggleHeadlineActions", e);
2351         }
2352 }
2353
2354 function publishWithNote(id, def_note) {
2355         try {
2356                 if (!def_note) def_note = '';
2357
2358                 var note = prompt(__("Please enter a note for this article:"), def_note);
2359
2360                 if (note != undefined) {
2361                         togglePub(id, false, false, note);
2362                 }
2363
2364         } catch (e) {
2365                 exception_error("publishWithNote", e);
2366         }
2367 }