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