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