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