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