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