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