]> git.wh0rd.org Git - tt-rss.git/blob - viewfeed.js
5f5108780f5eebe8863ddeaa5c4769708b90325d
[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 //                      notify_progress("Loading, please wait...");
938
939                         cache_invalidate("F:" + (-11 - id));
940
941                         new Ajax.Request("backend.php", {
942                                 parameters: query,
943                                 onComplete: function(transport) { 
944                                         show_labels_in_headlines(transport);
945                                         all_counters_callback2(transport);
946                                 } });
947
948 //              }
949
950         } catch (e) {
951                 exception_error("selectionAssignLabel", e);
952
953         }
954 }
955
956 function selectionAssignLabel(id) {
957         try {
958
959                 var ids = getSelectedArticleIds2();
960
961                 if (ids.length == 0) {
962                         alert(__("No articles are selected."));
963                         return;
964                 }
965
966 //              var ok = confirm(__("Assign selected articles to label?"));
967
968 //              if (ok) {
969
970                         cache_invalidate("F:" + (-11 - id));
971
972                         var query = "?op=rpc&subop=assignToLabel&ids=" +
973                                 param_escape(ids.toString()) + "&lid=" + param_escape(id);
974
975 //                      notify_progress("Loading, please wait...");
976
977                         new Ajax.Request("backend.php", {
978                                 parameters: query,
979                                 onComplete: function(transport) { 
980                                         show_labels_in_headlines(transport);
981                                         all_counters_callback2(transport);
982                                 } });
983
984 //              }
985
986         } catch (e) {
987                 exception_error("selectionAssignLabel", e);
988
989         }
990 }
991
992 function selectionToggleUnread(set_state, callback_func, no_error) {
993         try {
994                 var rows = getSelectedArticleIds2();
995
996                 if (rows.length == 0 && !no_error) {
997                         alert(__("No articles are selected."));
998                         return;
999                 }
1000
1001                 for (i = 0; i < rows.length; i++) {
1002                         var row = $("RROW-" + rows[i]);
1003                         if (row) {
1004                                 var nc = row.className;
1005                                 nc = nc.replace("Unread", "");
1006                                 nc = nc.replace("Selected", "");
1007
1008                                 if (set_state == undefined) {
1009                                         if (row.className.match("Unread")) {
1010                                                 row.className = nc + "Selected";
1011                                         } else {
1012                                                 row.className = nc + "UnreadSelected";
1013                                         }
1014                                         if (db) {
1015                                                 db.execute("UPDATE articles SET unread = NOT unread WHERE id = ?", 
1016                                                         [rows[i]]);
1017                                         }
1018                                 }
1019
1020                                 if (set_state == false) {
1021                                         row.className = nc + "Selected";
1022                                         if (db) {
1023                                                 db.execute("UPDATE articles SET unread = 0 WHERE id = ?", 
1024                                                         [rows[i]]);
1025                                         }
1026                                 }
1027
1028                                 if (set_state == true) {
1029                                         row.className = nc + "UnreadSelected";
1030                                         if (db) {
1031                                                 db.execute("UPDATE articles SET unread = 1 WHERE id = ?", 
1032                                                         [rows[i]]);
1033                                         }
1034                                 }
1035                         }
1036                 }
1037
1038                 if (rows.length > 0) {
1039
1040                         update_local_feedlist_counters();
1041
1042                         var cmode = "";
1043
1044                         if (set_state == undefined) {
1045                                 cmode = "2";
1046                         } else if (set_state == true) {
1047                                 cmode = "1";
1048                         } else if (set_state == false) {
1049                                 cmode = "0";
1050                         }
1051
1052                         var query = "?op=rpc&subop=catchupSelected" +
1053                                 "&cmode=" + cmode + "&ids=" + param_escape(rows.toString()); 
1054
1055                         notify_progress("Loading, please wait...");
1056
1057                         new Ajax.Request("backend.php", {
1058                                 parameters: query,
1059                                 onComplete: function(transport) { 
1060                                         catchup_callback2(transport, callback_func); 
1061                                 } });
1062
1063                 }
1064
1065         } catch (e) {
1066                 exception_error("selectionToggleUnread", e);
1067         }
1068 }
1069
1070 function selectionToggleMarked() {
1071         try {
1072         
1073                 var rows = getSelectedArticleIds2();
1074                 
1075                 if (rows.length == 0) {
1076                         alert(__("No articles are selected."));
1077                         return;
1078                 }
1079
1080                 for (i = 0; i < rows.length; i++) {
1081                         toggleMark(rows[i], true, true);
1082                 }
1083
1084                 update_local_feedlist_counters();
1085
1086                 if (rows.length > 0) {
1087
1088                         var query = "?op=rpc&subop=markSelected&ids=" +
1089                                 param_escape(rows.toString()) + "&cmode=2";
1090
1091                         query = query + "&afid=" + getActiveFeedId();
1092
1093                         query = query + "&omode=lc";
1094
1095                         new Ajax.Request("backend.php", {
1096                                 parameters: query,
1097                                 onComplete: function(transport) { 
1098                                         all_counters_callback2(transport); 
1099                                 } });
1100
1101                 }
1102
1103         } catch (e) {
1104                 exception_error("selectionToggleMarked", e);
1105         }
1106 }
1107
1108 function selectionTogglePublished() {
1109         try {
1110         
1111                 var rows = getSelectedArticleIds2();
1112
1113                 if (rows.length == 0) {
1114                         alert(__("No articles are selected."));
1115                         return;
1116                 }
1117
1118                 for (i = 0; i < rows.length; i++) {
1119                         togglePub(rows[i], true, true);
1120                 }
1121
1122                 if (rows.length > 0) {
1123
1124                         var query = "?op=rpc&subop=publishSelected&ids=" +
1125                                 param_escape(rows.toString()) + "&cmode=2";
1126
1127                         query = query + "&afid=" + getActiveFeedId();
1128
1129                         query = query + "&omode=lc";
1130
1131                         new Ajax.Request("backend.php", {
1132                                 parameters: query,
1133                                 onComplete: function(transport) { 
1134                                         all_counters_callback2(transport); 
1135                                 } });
1136
1137                 }
1138
1139         } catch (e) {
1140                 exception_error("selectionToggleMarked", e);
1141         }
1142 }
1143
1144 function cdmGetSelectedArticles() {
1145         var sel_articles = new Array();
1146         var container = $("headlinesInnerContainer");
1147
1148         for (i = 0; i < container.childNodes.length; i++) {
1149                 var child = container.childNodes[i];
1150
1151                 if (child.id && child.id.match("RROW-") && child.className.match("Selected")) {
1152                         var c_id = child.id.replace("RROW-", "");
1153                         sel_articles.push(c_id);
1154                 }
1155         }
1156
1157         return sel_articles;
1158 }
1159
1160 function cdmGetVisibleArticles() {
1161         var sel_articles = new Array();
1162         var container = $("headlinesInnerContainer");
1163
1164         if (!container) return sel_articles;
1165
1166         for (i = 0; i < container.childNodes.length; i++) {
1167                 var child = container.childNodes[i];
1168
1169                 if (child.id && child.id.match("RROW-")) {
1170                         var c_id = child.id.replace("RROW-", "");
1171                         sel_articles.push(c_id);
1172                 }
1173         }
1174
1175         return sel_articles;
1176 }
1177
1178 function cdmGetUnreadArticles() {
1179         var sel_articles = new Array();
1180         var container = $("headlinesInnerContainer");
1181
1182         for (i = 0; i < container.childNodes.length; i++) {
1183                 var child = container.childNodes[i];
1184
1185                 if (child.id && child.id.match("RROW-") && child.className.match("Unread")) {
1186                         var c_id = child.id.replace("RROW-", "");
1187                         sel_articles.push(c_id);
1188                 }
1189         }
1190
1191         return sel_articles;
1192 }
1193
1194
1195 // mode = all,none,unread
1196 function cdmSelectArticles(mode) {
1197         var container = $("headlinesInnerContainer");
1198
1199         for (i = 0; i < container.childNodes.length; i++) {
1200                 var child = container.childNodes[i];
1201
1202                 if (child.id && child.id.match("RROW-")) {
1203                         var aid = child.id.replace("RROW-", "");
1204
1205                         var cb = $("RCHK-" + aid);
1206
1207                         if (mode == "all") {
1208                                 if (!child.className.match("Selected")) {
1209                                         child.className = child.className + "Selected";
1210                                         cb.checked = true;
1211                                 }
1212                         } else if (mode == "unread") {
1213                                 if (child.className.match("Unread") && !child.className.match("Selected")) {
1214                                         child.className = child.className + "Selected";
1215                                         cb.checked = true;
1216                                 }
1217                         } else {
1218                                 child.className = child.className.replace("Selected", "");
1219                                 cb.checked = false;
1220                         }
1221                 }               
1222         }
1223 }
1224
1225 function catchupPage() {
1226
1227         var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
1228         
1229         var str = __("Mark all visible articles in %s as read?");
1230
1231         str = str.replace("%s", fn);
1232
1233         if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1234                 return;
1235         }
1236
1237         if ($("headlinesList")) {
1238                 selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', true, 'Unread', true);
1239                 selectionToggleUnread(false, 'viewCurrentFeed()', true);
1240                 selectTableRowsByIdPrefix('headlinesList', 'RROW-', 'RCHK-', false);
1241         } else {
1242                 cdmSelectArticles('all');
1243                 selectionToggleUnread(false, 'viewCurrentFeed()', true)
1244                 cdmSelectArticles('none');
1245         }
1246 }
1247
1248 function deleteSelection() {
1249
1250         try {
1251         
1252                 var rows = getSelectedArticleIds2();
1253
1254                 if (rows.length == 0) {
1255                         alert(__("No articles are selected."));
1256                         return;
1257                 }
1258         
1259                 var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
1260                 var str;
1261                 var op;
1262         
1263                 if (getActiveFeedId() != 0) {
1264                         str = __("Delete %d selected articles in %s?");
1265                 } else {
1266                         str = __("Delete %d selected articles?");
1267                 }
1268         
1269                 str = str.replace("%d", rows.length);
1270                 str = str.replace("%s", fn);
1271         
1272                 if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1273                         return;
1274                 }
1275
1276                 query = "?op=rpc&subop=delete&ids=" + param_escape(rows);
1277
1278                 console.log(query);
1279
1280                 new Ajax.Request("backend.php", {
1281                         parameters: query,
1282                         onComplete: function(transport) {
1283                                         viewCurrentFeed();
1284                                 } });
1285
1286         } catch (e) {
1287                 exception_error("deleteSelection", e);
1288         }
1289 }
1290
1291 function archiveSelection() {
1292
1293         try {
1294
1295                 var rows = getSelectedArticleIds2();
1296
1297                 if (rows.length == 0) {
1298                         alert(__("No articles are selected."));
1299                         return;
1300                 }
1301         
1302                 var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
1303                 var str;
1304                 var op;
1305         
1306                 if (getActiveFeedId() != 0) {
1307                         str = __("Archive %d selected articles in %s?");
1308                         op = "archive";
1309                 } else {
1310                         str = __("Move %d archived articles back?");
1311                         op = "unarchive";
1312                 }
1313         
1314                 str = str.replace("%d", rows.length);
1315                 str = str.replace("%s", fn);
1316         
1317                 if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1318                         return;
1319                 }
1320
1321                 query = "?op=rpc&subop="+op+"&ids=" + param_escape(rows);
1322
1323                 console.log(query);
1324
1325                 for (var i = 0; i < rows.length; i++) {
1326                         cache_invalidate(rows[i]);
1327                 }
1328
1329                 new Ajax.Request("backend.php", {
1330                         parameters: query,
1331                         onComplete: function(transport) {
1332                                         viewCurrentFeed();
1333                                 } });
1334
1335         } catch (e) {
1336                 exception_error("archiveSelection", e);
1337         }
1338 }
1339
1340 function catchupSelection() {
1341
1342         try {
1343
1344                 var rows = getSelectedArticleIds2();
1345
1346                 if (rows.length == 0) {
1347                         alert(__("No articles are selected."));
1348                         return;
1349                 }
1350         
1351                 var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
1352                 
1353                 var str = __("Mark %d selected articles in %s as read?");
1354         
1355                 str = str.replace("%d", rows.length);
1356                 str = str.replace("%s", fn);
1357         
1358                 if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1359                         return;
1360                 }
1361         
1362                 if ($("headlinesList")) {
1363                         selectionToggleUnread(false, 'viewCurrentFeed()', true);
1364                 } else {
1365                         selectionToggleUnread(false, 'viewCurrentFeed()', true)
1366                 }
1367
1368         } catch (e) {
1369                 exception_error("catchupSelection", e);
1370         }
1371 }
1372
1373 function editArticleTags(id, feed_id, cdm_enabled) {
1374         displayDlg('editArticleTags', id,
1375                            function () {
1376                                         $("tags_str").focus();
1377
1378                                         new Ajax.Autocompleter('tags_str', 'tags_choices',
1379                                            "backend.php?op=rpc&subop=completeTags",
1380                                            { tokens: ',', paramName: "search" });
1381                            });
1382 }
1383
1384 function editTagsSave() {
1385
1386         notify_progress("Saving article tags...");
1387
1388         var form = document.forms["tag_edit_form"];
1389
1390         var query = Form.serialize("tag_edit_form");
1391
1392         query = "?op=rpc&subop=setArticleTags&" + query;
1393
1394         console.log(query);
1395
1396         new Ajax.Request("backend.php", {
1397                 parameters: query,
1398                 onComplete: function(transport) {
1399                                 try {
1400                                         console.log("tags saved...");
1401                         
1402                                         closeInfoBox();
1403                                         notify("");
1404                         
1405                                         if (tagsAreDisplayed()) {
1406                                                 _reload_feedlist_after_view = true;
1407                                         }                       
1408                         
1409                                         if (transport.responseXML) {
1410                                                 var tags_str = transport.responseXML.getElementsByTagName("tags-str")[0];
1411                                                 
1412                                                 if (tags_str) {
1413                                                         var id = tags_str.getAttribute("id");
1414                         
1415                                                         if (id) {
1416                                                                 var tags = $("ATSTR-" + id);
1417                                                                 if (tags) {
1418                                                                         tags.innerHTML = tags_str.firstChild.nodeValue;
1419                                                                 }
1420
1421                                                                 cache_invalidate(id);
1422                                                         }
1423                                                 }
1424                                         }
1425                         
1426                                 } catch (e) {
1427                                         exception_error("editTagsSave", e);
1428                                 }
1429                         } });
1430 }
1431
1432 function editTagsInsert() {
1433         try {
1434
1435                 var form = document.forms["tag_edit_form"];
1436
1437                 var found_tags = form.found_tags;
1438                 var tags_str = form.tags_str;
1439
1440                 var tag = found_tags[found_tags.selectedIndex].value;
1441
1442                 if (tags_str.value.length > 0 && 
1443                                 tags_str.value.lastIndexOf(", ") != tags_str.value.length - 2) {
1444
1445                         tags_str.value = tags_str.value + ", ";
1446                 }
1447
1448                 tags_str.value = tags_str.value + tag + ", ";
1449
1450                 found_tags.selectedIndex = 0;
1451                 
1452         } catch (e) {
1453                 exception_error("editTagsInsert", e);
1454         }
1455 }
1456
1457 function cdmScrollViewport(where) {
1458         console.log("cdmScrollViewport: " + where);
1459
1460         var ctr = $("headlinesInnerContainer");
1461
1462         if (!ctr) return;
1463
1464         if (where == "bottom") {
1465                 ctr.scrollTop = ctr.scrollHeight;
1466         } else {
1467                 ctr.scrollTop = where;
1468         }
1469 }
1470
1471 function cdmArticleIsBelowViewport(id) {
1472         try {
1473                 var ctr = $("headlinesInnerContainer");
1474                 var e = $("RROW-" + id);
1475
1476                 if (!e || !ctr) return;
1477
1478                 // article starts below viewport
1479
1480                 if (ctr.scrollTop < e.offsetTop) {
1481                         return true;
1482                 } else {        
1483                         return false;
1484                 }
1485
1486         } catch (e) {
1487                 exception_error("cdmArticleIsVisible", e);
1488         }
1489 }
1490
1491 function cdmArticleIsAboveViewport(id) {
1492         try {
1493                 var ctr = $("headlinesInnerContainer");
1494                 var e = $("RROW-" + id);
1495
1496                 if (!e || !ctr) return;
1497
1498                 // article starts above viewport
1499
1500                 if (ctr.scrollTop > e.offsetTop + e.offsetHeight) {
1501                         return true;
1502                 } else {        
1503                         return false;
1504                 }
1505
1506         } catch (e) {
1507                 exception_error("cdmArticleIsVisible", e);
1508         }
1509 }
1510
1511 function cdmScrollToArticleId(id) {
1512         try {
1513                 var ctr = $("headlinesInnerContainer");
1514                 var e = $("RROW-" + id);
1515
1516                 if (!e || !ctr) return;
1517
1518                 ctr.scrollTop = e.offsetTop;
1519
1520         } catch (e) {
1521                 exception_error("cdmScrollToArticleId", e);
1522         }
1523 }
1524
1525 function cdmArticleIsActuallyVisible(id) {
1526         try {
1527                 var ctr = $("headlinesInnerContainer");
1528                 var e = $("RROW-" + id);
1529
1530                 if (!e || !ctr) return;
1531
1532                 // article fits in viewport OR article is longer than viewport and
1533                 // its bottom is visible
1534
1535                 if (ctr.scrollTop <= e.offsetTop && e.offsetTop + e.offsetHeight <=
1536                                 ctr.scrollTop + ctr.offsetHeight) {
1537
1538                         return true;
1539                 
1540                 } else if (e.offsetHeight > ctr.offsetHeight &&
1541                                 e.offsetTop + e.offsetHeight >= ctr.scrollTop &&
1542                                 e.offsetTop + e.offsetHeight <= ctr.scrollTop + ctr.offsetHeight) {
1543
1544                         return true;
1545
1546                 }
1547
1548                 return false;
1549
1550         } catch (e) {
1551                 exception_error("cdmArticleIsVisible", e);
1552         }
1553 }
1554
1555 function cdmWatchdog() {
1556
1557         try {
1558
1559                 var ctr = $("headlinesInnerContainer");
1560
1561                 if (!ctr) return;
1562
1563                 var ids = new Array();
1564
1565                 var e = ctr.firstChild;
1566
1567                 while (e) {
1568                         if (e.className && e.className == "cdmArticleUnread" && e.id &&
1569                                         e.id.match("RROW-")) {
1570
1571                                 // article fits in viewport OR article is longer than viewport and
1572                                 // its bottom is visible
1573
1574                                 if (ctr.scrollTop <= e.offsetTop && e.offsetTop + e.offsetHeight <=
1575                                                 ctr.scrollTop + ctr.offsetHeight) {
1576
1577 //                                      console.log(e.id + " is visible " + e.offsetTop + "." + 
1578 //                                              (e.offsetTop + e.offsetHeight) + " vs " + ctr.scrollTop + "." +
1579 //                                              (ctr.scrollTop + ctr.offsetHeight));
1580
1581                                         ids.push(e.id.replace("RROW-", ""));
1582
1583                                 } else if (e.offsetHeight > ctr.offsetHeight &&
1584                                                 e.offsetTop + e.offsetHeight >= ctr.scrollTop &&
1585                                                 e.offsetTop + e.offsetHeight <= ctr.scrollTop + ctr.offsetHeight) {
1586
1587                                         ids.push(e.id.replace("RROW-", "")); 
1588
1589                                 }
1590
1591                                 // method 2: article bottom is visible and is in upper 1/2 of the viewport
1592
1593 /*                              if (e.offsetTop + e.offsetHeight >= ctr.scrollTop &&
1594                                                 e.offsetTop + e.offsetHeight <= ctr.scrollTop + ctr.offsetHeight/2) {
1595
1596                                         ids.push(e.id.replace("RROW-", "")); 
1597
1598                                 } */
1599
1600                         }
1601
1602                         e = e.nextSibling;
1603                 }
1604
1605                 console.log("cdmWatchdog, ids= " + ids.toString());
1606
1607                 if (ids.length > 0) {
1608
1609                         for (var i = 0; i < ids.length; i++) {
1610                                 var e = $("RROW-" + ids[i]);
1611                                 if (e) {
1612                                         e.className = e.className.replace("Unread", "");
1613                                 }
1614                         }
1615
1616                         var query = "?op=rpc&subop=catchupSelected" +
1617                                 "&cmode=0" + "&ids=" + param_escape(ids.toString());
1618
1619                         new Ajax.Request("backend.php", {
1620                                 parameters: query,
1621                                 onComplete: function(transport) { 
1622                                         all_counters_callback2(transport); 
1623                                 } });
1624
1625                 }
1626
1627                 _cdm_wd_timeout = window.setTimeout("cdmWatchdog()", 4000);
1628
1629         } catch (e) {
1630                 exception_error("cdmWatchdog", e);
1631         }
1632
1633 }
1634
1635
1636 function cache_inject(id, article, param) {
1637
1638         try {
1639                 if (!cache_check_param(id, param)) {
1640                         console.log("cache_article: miss: " + id + " [p=" + param + "]");
1641
1642                    var date = new Date();
1643               var ts = Math.round(date.getTime() / 1000);
1644
1645                         if (db) {
1646
1647                                 db.execute("INSERT INTO cache (id, article, param, added) VALUES (?, ?, ?, ?)",
1648                                         [id, article, param, ts]);                              
1649                         } else {
1650         
1651                                 var cache_obj = {};
1652         
1653                                 cache_obj["id"] = id;
1654                                 cache_obj["data"] = article;
1655                                 cache_obj["param"] = param;
1656
1657                                 if (param) id = id + ":" + param;
1658
1659                                 cache_added["TS:" + id] = ts;
1660
1661                                 if (has_local_storage()) 
1662                                         localStorage.setItem(id, JSON.stringify(cache_obj));
1663                                 else
1664                                         article_cache.push(cache_obj);
1665                         }
1666         
1667                 } else {
1668                         console.log("cache_article: hit: " + id + " [p=" + param + "]");
1669                 }
1670         } catch (e) {   
1671                 exception_error("cache_inject", e);
1672         }
1673 }
1674
1675 function cache_find(id) {
1676
1677         if (db) {
1678                 var rs = db.execute("SELECT article FROM cache WHERE id = ?", [id]);
1679                 var a = false;
1680
1681                 if (rs.isValidRow()) {
1682                         var a = rs.field(0);                    
1683                 }
1684
1685                 rs.close();
1686
1687                 return a;
1688
1689         } else {
1690
1691                 if (has_local_storage()) {
1692                         var cache_obj = localStorage.getItem(id);
1693         
1694                         if (cache_obj) {
1695                                 cache_obj = JSON.parse(cache_obj);
1696
1697                                 if (cache_obj)
1698                                         return cache_obj['data'];
1699                         }
1700
1701                 } else {
1702                         for (var i = 0; i < article_cache.length; i++) {
1703                                 if (article_cache[i]["id"] == id) {
1704                                         return article_cache[i]["data"];
1705                                 }
1706                         }
1707                 }
1708         }
1709         return false;
1710 }
1711
1712 function cache_find_param(id, param) {
1713
1714         if (db) {
1715                 var rs = db.execute("SELECT article FROM cache WHERE id = ? AND param = ?",
1716                         [id, param]);
1717                 var a = false;
1718
1719                 if (rs.isValidRow()) {
1720                         a = rs.field(0);
1721                 }
1722
1723                 rs.close();
1724
1725                 return a;
1726
1727         } else {
1728
1729                 if (has_local_storage()) {
1730
1731                         if (param) id = id + ":" + param;
1732
1733                         var cache_obj = localStorage.getItem(id);
1734
1735                         if (cache_obj) {
1736                                 cache_obj = JSON.parse(cache_obj);
1737
1738                                 if (cache_obj)
1739                                         return cache_obj['data'];
1740                         }
1741
1742                 } else {
1743                         for (var i = 0; i < article_cache.length; i++) {
1744                                 if (article_cache[i]["id"] == id && article_cache[i]["param"] == param) {
1745                                         return article_cache[i]["data"];
1746                                 }
1747                         }
1748                 }
1749         }
1750         return false;
1751 }
1752
1753 function cache_check(id) {
1754
1755         if (db) {
1756                 var rs = db.execute("SELECT COUNT(*) AS c FROM cache WHERE id = ?",
1757                         [id]);
1758                 var a = false;
1759
1760                 if (rs.isValidRow()) {
1761                          a = rs.field(0) != "0";
1762                 }
1763
1764                 rs.close();
1765
1766                 return a;
1767
1768         } else {
1769                 if (has_local_storage()) {
1770                         if (localStorage.getItem(id))
1771                                 return true;
1772                 } else {
1773                         for (var i = 0; i < article_cache.length; i++) {
1774                                 if (article_cache[i]["id"] == id) {
1775                                         return true;
1776                                 }
1777                         }
1778                 }
1779         }
1780         return false;
1781 }
1782
1783 function cache_check_param(id, param) {
1784
1785         if (db) {
1786                 var rs = db.execute("SELECT COUNT(*) AS c FROM cache WHERE id = ? AND param = ?",
1787                         [id, param]);
1788                 var a = false;
1789
1790                 if (rs.isValidRow()) {
1791                         a = rs.field(0) != "0";
1792                 }
1793
1794                 rs.close();
1795
1796                 return a;
1797
1798         } else {
1799
1800                 if (has_local_storage()) {
1801
1802                         if (param) id = id + ":" + param;
1803
1804                         if (localStorage.getItem(id))
1805                                 return true;
1806
1807                 } else {
1808                         for (var i = 0; i < article_cache.length; i++) {
1809                                 if (article_cache[i]["id"] == id && article_cache[i]["param"] == param) {
1810                                         return true;
1811                                 }
1812                         }
1813                 }
1814         }
1815         return false;
1816 }
1817
1818 function cache_expire() {
1819         if (db) {
1820                 var date = new Date();
1821                 var ts = Math.round(date.getTime() / 1000);
1822
1823                 db.execute("DELETE FROM cache WHERE added < ? - 1800 AND id LIKE 'FEEDLIST'", [ts]);
1824                 db.execute("DELETE FROM cache WHERE added < ? - 600 AND (id LIKE 'F:%' OR id LIKE 'C:%')", [ts]);
1825                 db.execute("DELETE FROM cache WHERE added < ? - 86400", [ts]);
1826
1827
1828         } else {
1829                 if (has_local_storage()) {
1830
1831                         var date = new Date();
1832                         var timestamp = Math.round(date.getTime() / 1000);
1833
1834                         for (var id in cache_added) {
1835                                 var tmp = [];
1836
1837                                 var key_id = id.replace("TS:", "");
1838
1839                                 //console.warn("CEXP:" + key_id);
1840
1841                                 if (timestamp - cache_added[id] > 180) {
1842                                         cache_invalidate(key_id);
1843                                 } else {
1844                                         tmp[id] = cache_added[id];
1845                                 }
1846
1847                                 cache_added = tmp;
1848                         }
1849
1850                 } else {
1851                         while (article_cache.length > 25) {
1852                                 article_cache.shift();
1853                         }
1854                 }
1855         }
1856 }
1857
1858 function cache_flush() {
1859         if (db) {
1860                 db.execute("DELETE FROM cache");
1861         } else if (has_local_storage()) {
1862                 localStorage.clear();
1863         } else {
1864                 article_cache = new Array();
1865         }
1866 }
1867
1868 function cache_invalidate(id) {
1869         try {   
1870
1871                 if (db) {
1872                         rs = db.execute("DELETE FROM cache WHERE id = ?", [id]);
1873                         return rs.rowsAffected != 0;
1874                 } else {
1875
1876                         if (has_local_storage()) {
1877
1878                                 var tmp = [];
1879                                 var found = false;
1880
1881                                 for (var key in cache_added) {
1882                                         var key_id = key.replace("TS:", "");
1883
1884 //                                      console.warn("cache_invalidate: " + key_id + " cmp " + id);
1885
1886                                         if (key_id == id || key_id.indexOf(id + ":") == 0) {
1887                                                 localStorage.removeItem(key_id);
1888                                                 found = true;
1889                                                 break;
1890                                         } else {
1891                                                 tmp[key] = cache_added[key];
1892                                         }
1893                                 }
1894
1895                                 cache_added = tmp;
1896
1897                                 return found;
1898
1899                         } else {
1900                                 var i = 0
1901
1902                                 while (i < article_cache.length) {
1903                                         if (article_cache[i]["id"] == id) {
1904                                                 console.log("cache_invalidate: removed id " + id);
1905                                                 article_cache.splice(i, 1);
1906                                                 return true;
1907                                         }
1908                                         i++;
1909                                 }
1910                         }
1911                 }
1912
1913                 console.log("cache_invalidate: id not found: " + id);
1914                 return false;
1915         } catch (e) {
1916                 exception_error("cache_invalidate", e);
1917         }
1918 }
1919
1920 function getActiveArticleId() {
1921         return active_post_id;
1922 }
1923
1924 function preloadBatchedArticles() {
1925         try {
1926
1927                 var query = "?op=rpc&subop=getArticles&ids=" + 
1928                         preload_id_batch.toString();
1929
1930                 new Ajax.Request("backend.php", {
1931                         parameters: query,
1932                         onComplete: function(transport) { 
1933
1934                                 preload_id_batch = [];
1935
1936                                 var articles = transport.responseXML.getElementsByTagName("article");
1937
1938                                 for (var i = 0; i < articles.length; i++) {
1939                                         var id = articles[i].getAttribute("id");
1940                                         if (!cache_check(id)) {
1941                                                 cache_inject(id, articles[i].firstChild.nodeValue);                             
1942                                                 console.log("preloaded article: " + id);
1943                                         }
1944                                 }
1945                 } }); 
1946
1947         } catch (e) {
1948                 exception_error("preloadBatchedArticles", e);
1949         }
1950 }
1951
1952 function preloadArticleUnderPointer(id) {
1953         try {
1954                 if (getInitParam("bw_limit") == "1") return;
1955
1956                 if (post_under_pointer == id && !cache_check(id)) {
1957
1958                         console.log("trying to preload article " + id);
1959
1960                         var neighbor_ids = getRelativePostIds(id, 1);
1961
1962                         /* only request uncached articles */
1963
1964                         if (preload_id_batch.indexOf(id) == -1) {
1965                                 for (var i = 0; i < neighbor_ids.length; i++) {
1966                                         if (!cache_check(neighbor_ids[i])) {
1967                                                 preload_id_batch.push(neighbor_ids[i]);
1968                                         }
1969                                 }
1970                         }
1971
1972                         if (preload_id_batch.indexOf(id) == -1)
1973                                 preload_id_batch.push(id);
1974
1975                         console.log("preload ids batch: " + preload_id_batch.toString());
1976
1977                         window.clearTimeout(preload_timeout_id);
1978                         preload_batch_timeout_id = window.setTimeout('preloadBatchedArticles()', 1000);
1979
1980                 }
1981         } catch (e) {
1982                 exception_error("preloadArticleUnderPointer", e);
1983         }
1984 }
1985
1986 function postMouseIn(id) {
1987         try {
1988                 if (post_under_pointer != id) {
1989                         post_under_pointer = id;
1990                         if (!isCdmMode()) {
1991                                 window.setTimeout("preloadArticleUnderPointer(" + id + ")", 250);
1992                         }
1993                 }
1994
1995         } catch (e) {
1996                 exception_error("postMouseIn", e);
1997         }
1998 }
1999
2000 function postMouseOut(id) {
2001         try {
2002                 post_under_pointer = false;
2003         } catch (e) {
2004                 exception_error("postMouseOut", e);
2005         }
2006 }
2007
2008 function headlines_scroll_handler() {
2009         try {
2010
2011                 var e = $("headlinesInnerContainer");
2012
2013                 var toolbar_form = document.forms["main_toolbar_form"];
2014
2015 //              console.log((e.scrollTop + e.offsetHeight) + " vs " + e.scrollHeight + " dis? " +
2016 //                      _infscroll_disable);
2017
2018                 if (e.scrollTop + e.offsetHeight > e.scrollHeight - 100) {
2019                         if (!_infscroll_disable) {
2020                                 console.log("more cowbell!");
2021                                 viewNextFeedPage();
2022                         }
2023                 }
2024
2025         } catch (e) {
2026                 exception_error("headlines_scroll_handler", e);
2027         }
2028 }
2029
2030 function catchupRelativeToArticle(below) {
2031
2032         try {
2033
2034
2035                 if (!getActiveArticleId()) {
2036                         alert(__("No article is selected."));
2037                         return;
2038                 }
2039
2040                 var visible_ids;
2041
2042                 if ($("headlinesList")) {
2043                         visible_ids = getVisibleHeadlineIds();
2044                 } else {
2045                         visible_ids = cdmGetVisibleArticles();
2046                 }
2047
2048                 var ids_to_mark = new Array();
2049
2050                 if (!below) {
2051                         for (var i = 0; i < visible_ids.length; i++) {
2052                                 if (visible_ids[i] != getActiveArticleId()) {
2053                                         var e = $("RROW-" + visible_ids[i]);
2054
2055                                         if (e && e.className.match("Unread")) {
2056                                                 ids_to_mark.push(visible_ids[i]);
2057                                         }
2058                                 } else {
2059                                         break;
2060                                 }
2061                         }
2062                 } else {
2063                         for (var i = visible_ids.length-1; i >= 0; i--) {
2064                                 if (visible_ids[i] != getActiveArticleId()) {
2065                                         var e = $("RROW-" + visible_ids[i]);
2066
2067                                         if (e && e.className.match("Unread")) {
2068                                                 ids_to_mark.push(visible_ids[i]);
2069                                         }
2070                                 } else {
2071                                         break;
2072                                 }
2073                         }
2074                 }
2075
2076                 if (ids_to_mark.length == 0) {
2077                         alert(__("No articles found to mark"));
2078                 } else {
2079                         var msg = __("Mark %d article(s) as read?").replace("%d", ids_to_mark.length);
2080
2081                         if (getInitParam("confirm_feed_catchup") != 1 || confirm(msg)) {
2082
2083                                 for (var i = 0; i < ids_to_mark.length; i++) {
2084                                         var e = $("RROW-" + ids_to_mark[i]);
2085                                         e.className = e.className.replace("Unread", "");
2086                                 }
2087
2088                                 var query = "?op=rpc&subop=catchupSelected" +
2089                                         "&cmode=0" + "&ids=" + param_escape(ids_to_mark.toString()); 
2090
2091                                 new Ajax.Request("backend.php", {
2092                                         parameters: query,
2093                                         onComplete: function(transport) { 
2094                                                 catchup_callback2(transport); 
2095                                         } });
2096
2097                         }
2098                 }
2099
2100         } catch (e) {
2101                 exception_error("catchupRelativeToArticle", e);
2102         }
2103 }
2104
2105 function cdmExpandArticle(id) {
2106         try {
2107
2108                 var elem = $("CICD-" + active_post_id);
2109
2110                 if (id == active_post_id && Element.visible(elem))
2111                         return true;
2112
2113                 cdmSelectArticles("none");
2114
2115                 var old_offset = $("RROW-" + id).offsetTop;
2116
2117                 if (active_post_id && elem) {
2118                         Element.hide(elem);
2119                         Element.show("CEXC-" + active_post_id);
2120                 }
2121
2122                 active_post_id = id;
2123
2124                 elem = $("CICD-" + id);
2125
2126                 if (!Element.visible(elem)) {
2127                         Element.show(elem);
2128                         Element.hide("CEXC-" + id);
2129                 }
2130
2131                 var new_offset = $("RROW-" + id).offsetTop;
2132
2133                 $("headlinesInnerContainer").scrollTop += (new_offset-old_offset);
2134
2135                 if ($("RROW-" + id).offsetTop != old_offset) 
2136                         $("headlinesInnerContainer").scrollTop = new_offset;
2137
2138                 toggleUnread(id, 0, true);
2139                 toggleSelected(id);
2140
2141         } catch (e) {
2142                 exception_error("cdmExpandArticle", e);
2143         }
2144
2145         return false;
2146 }
2147
2148 function fixHeadlinesOrder(ids) {
2149         try {
2150                 for (var i = 0; i < ids.length; i++) {
2151                         var e = $("RROW-" + ids[i]);
2152
2153                         if (e) {
2154                                 if (i % 2 == 0) {
2155                                         e.className = e.className.replace("even", "odd");
2156                                 } else {
2157                                         e.className = e.className.replace("odd", "even");
2158                                 }
2159                         }
2160                 }
2161         } catch (e) {
2162                 exception_error("fixHeadlinesOrder", e);
2163         }
2164 }
2165
2166 function hideReadHeadlines() {
2167         try {
2168
2169                 var ids = false;
2170                 var vis_ids = new Array();
2171
2172                 if ($("headlinesList")) {
2173                         ids = getVisibleHeadlineIds();
2174                 } else {
2175                         ids = cdmGetVisibleArticles();
2176                 }
2177
2178                 var read_headlines_visible = true;
2179
2180                 for (var i = 0; i < ids.length; i++) {
2181                         var row = $("RROW-" + ids[i]);
2182
2183                         if (row && row.className) {
2184                                 if (read_headlines_visible) {
2185                                         if (row.className.match("Unread") || row.className.match("Selected")) {
2186                                                 Element.show(row);
2187                                                 vis_ids.push(ids[i]);
2188                                         } else {
2189                                                 //Effect.Fade(row, {duration : 0.3});
2190                                                 Element.hide(row);
2191                                         }
2192                                 } else {
2193                                         Element.show(row);
2194                                         vis_ids.push(ids[i]);
2195                                 }
2196                         }
2197                 }
2198                 
2199                 fixHeadlinesOrder(vis_ids);
2200
2201                 read_headlines_visible = !read_headlines_visible;
2202
2203         } catch (e) {
2204                 exception_error("hideReadHeadlines", e);
2205         } 
2206 }
2207
2208 function invertHeadlineSelection() {
2209         try {
2210                 var rows = new Array();
2211                 var r = false;
2212                 
2213                 if (!isCdmMode()) {             
2214                         r = document.getElementsByTagName("TR");
2215                 } else {
2216                         r = document.getElementsByTagName("DIV");
2217                 }
2218
2219                 for (var i = 0; i < r.length; i++) {
2220                         if (r[i].id && r[i].id.match("RROW-")) {
2221                                 rows.push(r[i]);
2222                         }
2223                 }
2224                 
2225                 for (var i = 0; i < rows.length; i++) {
2226                         var nc = rows[i].className;
2227                         var id = rows[i].id.replace("RROW-", "");
2228                         var cb = $("RCHK-" + id);
2229
2230                         if (!rows[i].className.match("Selected")) {
2231                                 nc = nc + "Selected";
2232                                 cb.checked = true;
2233                         } else {
2234                                 nc = nc.replace("Selected", "");
2235                                 cb.checked = false;
2236                         }
2237
2238                         rows[i].className = nc;
2239
2240                 }
2241
2242         } catch (e) {
2243                 exception_error("invertHeadlineSelection", e);
2244         }
2245 }
2246
2247 function getArticleUnderPointer() {
2248         return post_under_pointer;
2249 }
2250
2251 function zoomToArticle(id) {
2252         try {
2253                 var w = window.open("backend.php?op=view&mode=zoom&id=" + param_escape(id), 
2254                         "ttrss_zoom_" + id,
2255                         "status=0,toolbar=0,location=0,width=450,height=300,scrollbars=1,menubar=0");
2256
2257         } catch (e) {
2258                 exception_error("zoomToArticle", e);
2259         }
2260 }
2261
2262 function scrollArticle(offset) {
2263         try {
2264                 if (!isCdmMode()) {
2265                         var ci = $("content-insert");
2266                         if (ci) {
2267                                 ci.scrollTop += offset;
2268                         }
2269                 } else {
2270                         var hi = $("headlinesInnerContainer");
2271                         if (hi) {
2272                                 hi.scrollTop += offset;
2273                         }
2274
2275                 }
2276         } catch (e) {
2277                 exception_error("scrollArticle", e);
2278         }
2279 }
2280
2281 function show_labels_in_headlines(transport) {
2282         try {
2283                 if (transport.responseXML) {
2284                         var info = transport.responseXML.getElementsByTagName("info-for-headlines")[0];
2285
2286                         var elems = info.getElementsByTagName("entry");
2287
2288                         for (var l = 0; l < elems.length; l++) {
2289                                 var e_id = elems[l].getAttribute("id");
2290
2291                                 if (e_id) {
2292
2293                                         var ctr = $("HLLCTR-" + e_id);
2294
2295                                         if (ctr) {
2296                                                 ctr.innerHTML = elems[l].firstChild.nodeValue;
2297                                         }
2298                                 }
2299
2300                         }
2301
2302                 }
2303         } catch (e) {
2304                 exception_error("show_labels_in_headlines", e);
2305
2306         }
2307 }
2308
2309 function toggleHeadlineActions() {
2310         try {
2311                 var e = $("headlineActionsBody");
2312                 var p = $("headlineActionsDrop");
2313
2314                 if (!Element.visible(e)) {
2315                         Element.show(e);
2316                 } else {
2317                         Element.hide(e);
2318                 }
2319
2320                 e.scrollTop = 0;
2321                 e.style.left = (p.offsetLeft + 1) + "px";
2322                 e.style.top = (p.offsetTop + p.offsetHeight + 2) + "px";
2323
2324         } catch (e) {
2325                 exception_error("toggleHeadlineActions", e);
2326         }
2327 }
2328
2329 function publishWithNote(id, def_note) {
2330         try {
2331                 if (!def_note) def_note = '';
2332
2333                 var note = prompt(__("Please enter a note for this article:"), def_note);
2334
2335                 if (note != undefined) {
2336                         togglePub(id, false, false, note);
2337                 }
2338
2339         } catch (e) {
2340                 exception_error("publishWithNote", e);
2341         }
2342 }
2343
2344 function emailArticle(id) {
2345         try {
2346                 if (!id) {
2347                         var ids = getSelectedArticleIds2();
2348
2349                         if (ids.length == 0) {
2350                                 alert(__("No articles are selected."));
2351                                 return;
2352                         }
2353
2354                         id = ids.toString();
2355                 }
2356
2357                 displayDlg('emailArticle', id, 
2358                    function () {                                
2359                                 document.forms['article_email_form'].destination.focus();
2360
2361                            new Ajax.Autocompleter('destination', 'destination_choices',
2362                                    "backend.php?op=rpc&subop=completeEmails",
2363                                    { tokens: '', paramName: "search" });
2364
2365                         });
2366
2367         } catch (e) {
2368                 exception_error("emailArticle", e);
2369         }
2370 }
2371
2372 function emailArticleDo() {
2373         try {
2374                 var f = document.forms['article_email_form'];
2375
2376                 if (f.destination.value == "") {
2377                         alert("Please fill in the destination email.");
2378                         return;
2379                 }
2380
2381                 if (f.subject.value == "") {
2382                         alert("Please fill in the subject.");
2383                         return;
2384                 }
2385
2386                 var query = Form.serialize("article_email_form");
2387
2388 //              console.log(query);
2389
2390                 new Ajax.Request("backend.php", {
2391                         parameters: query,
2392                         onComplete: function(transport) { 
2393                                 try {
2394
2395                                         var error = transport.responseXML.getElementsByTagName('error')[0];
2396
2397                                         if (error) {
2398                                                 alert(__('Error sending email:') + ' ' + error.firstChild.nodeValue);
2399                                         } else {
2400                                                 notify_info('Your message has been sent.');
2401                                                 closeInfoBox();
2402                                         }
2403
2404                                 } catch (e) {
2405                                         exception_error("sendEmailDo", e);
2406                                 }
2407
2408                         } });
2409
2410         } catch (e) {
2411                 exception_error("emailArticleDo", e);
2412         }
2413 }
2414
2415 function cdmDismissArticle(id) {
2416         try {
2417                 var elem = $("RROW-" + id);
2418
2419                 toggleUnread(id, 0, true);
2420
2421                 new Effect.Fade(elem, {duration : 0.5});
2422
2423         } catch (e) {
2424                 exception_error("cdmDismissArticle", e);
2425         }
2426 }
2427
2428 function cdmDismissSelectedArticles() {
2429         try {
2430
2431                 var ids = getSelectedArticleIds2();
2432
2433                 for (var i = 0; i < ids.length; i++) {
2434                         var elem = $("RROW-" + ids[i]);
2435                         new Effect.Fade(elem, {duration : 0.5});
2436                 }
2437
2438                 if (ids.length > 0)
2439                         selectionToggleUnread(false);
2440
2441         } catch (e) {
2442                 exception_error("cdmDismissArticle", e);
2443         }
2444 }