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