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