]> git.wh0rd.org Git - tt-rss.git/blob - viewfeed.js
add dijit/dojo stuff; initial ui mockup
[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) {    
73                                 feedr.addClassName("Selected");
74                         } 
75                 } else {
76                         var feedr = $("FCAT-" + feed_id);
77                         if (feedr) {    
78                                 feedr.addClassName("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 (parseInt(headlines_count) < getInitParam("default_article_limit")) {
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.hasClassName("Unread");
258                         
259                 crow.removeClassName("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                         var reply = transport.responseXML.firstChild.firstChild;
364                 
365                 } else {
366                         console.warn("article_callback: returned no XML object");
367                         //var f = $("content-frame");
368                         //f.innerHTML = "<div class='whiteBox'>" + __('Could not display article (missing XML object)') + "</div>";
369                 }
370
371                 var date = new Date();
372                 last_article_view = date.getTime() / 1000;
373
374                 if (_reload_feedlist_after_view) {
375                         setTimeout('updateFeedList(false, false)', 50);                 
376                         _reload_feedlist_after_view = false;
377                 } else {
378                         request_counters();
379                 }
380
381                 notify("");
382         } catch (e) {
383                 exception_error("article_callback2", e, transport);
384         }
385 }
386
387 function view(id) {
388         try {
389                 console.log("loading article: " + id);
390
391                 var cached_article = cache_find(id);
392
393                 console.log("cache check result: " + (cached_article != false));
394         
395                 enableHotkeys();
396                 hideAuxDlg();
397
398                 var query = "?op=view&id=" + param_escape(id);
399
400                 var neighbor_ids = getRelativePostIds(active_post_id);
401
402                 /* only request uncached articles */
403
404                 var cids_to_request = Array();
405
406                 for (var i = 0; i < neighbor_ids.length; i++) {
407                         if (!cache_check(neighbor_ids[i])) {
408                                 cids_to_request.push(neighbor_ids[i]);
409                         }
410                 }
411
412                 console.log("additional ids: " + cids_to_request.toString());                   
413         
414                 query = query + "&cids=" + cids_to_request.toString();
415
416                 var crow = $("RROW-" + id);
417                 var article_is_unread = crow.hasClassName("Unread");
418
419                 active_post_id = id;
420                 showArticleInHeadlines(id);
421
422                 if (!cached_article) {
423
424                         var upic = $('FUPDPIC-' + id);
425
426                         if (upic) {     
427                                 upic.src = getInitParam("sign_progress");
428                         }
429
430                 } else if (cached_article && article_is_unread) {
431
432                         query = query + "&mode=prefetch";
433
434                         render_article(cached_article);
435
436                 } else if (cached_article) {
437
438                         query = query + "&mode=prefetch_old";
439                         render_article(cached_article);
440
441                 }
442
443                 cache_expire();
444
445                 last_requested_article = id;
446
447                 new Ajax.Request("backend.php", {
448                         parameters: query,
449                         onComplete: function(transport) { 
450                                 article_callback2(transport, id); 
451                         } });
452
453                 return false;
454
455         } catch (e) {
456                 exception_error("view", e);
457         }
458 }
459
460 function tMark(id) {
461         return toggleMark(id);
462 }
463
464 function tPub(id) {
465         return togglePub(id);
466 }
467
468 function tMark_afh_off(effect) {
469         try {
470                 var elem = effect.effects[0].element;
471
472                 //console.log("tMark_afh_off : " + elem.id);
473
474                 if (elem) {
475                         elem.src = elem.src.replace("mark_set", "mark_unset");
476                         elem.alt = __("Star article");
477                         Element.show(elem);
478                 }
479
480         } catch (e) {
481                 exception_error("tMark_afh_off", e);
482         }
483 }
484
485 function tPub_afh_off(effect) {
486         try {
487                 var elem = effect.effects[0].element;
488
489                 //console.log("tPub_afh_off : " + elem.id);
490
491                 if (elem) {
492                         elem.src = elem.src.replace("pub_set", "pub_unset");
493                         elem.alt = __("Publish article");
494                         Element.show(elem);
495                 }
496
497         } catch (e) {
498                 exception_error("tPub_afh_off", e);
499         }
500 }
501
502 function toggleMark(id, client_only) {
503         try {
504                 var query = "?op=rpc&id=" + id + "&subop=mark";
505         
506                 var img = $("FMPIC-" + id);
507
508                 if (!img) return;
509         
510                 if (img.src.match("mark_unset")) {
511                         img.src = img.src.replace("mark_unset", "mark_set");
512                         img.alt = __("Unstar article");
513                         query = query + "&mark=1";
514
515                 } else {
516                         img.src = img.src.replace("mark_set", "mark_unset");
517                         img.alt = __("Star article");
518                         query = query + "&mark=0";
519                 }
520
521                 if (!client_only) {
522                         new Ajax.Request("backend.php", {
523                                 parameters: query,
524                                 onComplete: function(transport) { 
525                                         handle_rpc_reply(transport); 
526                                 } });
527                 }
528
529         } catch (e) {
530                 exception_error("toggleMark", e);
531         }
532 }
533
534 function togglePub(id, client_only, no_effects, note) {
535         try {
536                 var query = "?op=rpc&id=" + id + "&subop=publ";
537         
538                 if (note != undefined) {
539                         query = query + "&note=" + param_escape(note);
540                 } else {
541                         query = query + "&note=undefined";
542                 }
543
544                 var img = $("FPPIC-" + id);
545
546                 if (!img) return;
547         
548                 if (img.src.match("pub_unset") || note != undefined) {
549                         img.src = img.src.replace("pub_unset", "pub_set");
550                         img.alt = __("Unpublish article");
551                         query = query + "&pub=1";
552
553                 } else {
554                         img.src = img.src.replace("pub_set", "pub_unset");
555                         img.alt = __("Publish article");
556
557                         query = query + "&pub=0";
558                 }
559
560                 if (!client_only) {
561                         new Ajax.Request("backend.php", {
562                                 parameters: query,
563                                 onComplete: function(transport) { 
564                                         handle_rpc_reply(transport);
565                 
566                                         var note = transport.responseXML.getElementsByTagName("note")[0];
567                 
568                                         if (note) {
569                                                 var note_id = note.getAttribute("id");
570                                                 var note_size = note.getAttribute("size");
571                                                 var note_content = note.firstChild.nodeValue;
572                 
573                                                 var container = $('POSTNOTE-' + note_id);
574                 
575                                                 cache_invalidate(note_id);
576                 
577                                                 if (container) {
578                                                         if (note_size == "0") {
579                                                                 Element.hide(container);
580                                                         } else {
581                                                                 container.innerHTML = note_content;
582                                                                 Element.show(container);
583                                                         }
584                                                 }
585                                         }       
586
587                                 } });
588                 }
589
590         } catch (e) {
591                 exception_error("togglePub", e);
592         }
593 }
594
595 function moveToPost(mode) {
596
597         try {
598
599                 var rows = getVisibleArticleIds();
600
601                 var prev_id = false;
602                 var next_id = false;
603                 
604                 if (!$('RROW-' + active_post_id)) {
605                         active_post_id = false;
606                 }
607                 
608                 if (active_post_id == false) {
609                         next_id = getFirstVisibleHeadlineId();
610                         prev_id = getLastVisibleHeadlineId();
611                 } else {        
612                         for (var i = 0; i < rows.length; i++) {
613                                 if (rows[i] == active_post_id) {
614                                         prev_id = rows[i-1];
615                                         next_id = rows[i+1];                    
616                                 }
617                         }
618                 }
619                 
620                 if (mode == "next") {
621                         if (next_id) {
622                                 if (isCdmMode()) {
623         
624                                         cdmExpandArticle(next_id);
625                                         cdmScrollToArticleId(next_id);
626
627                                 } else {
628                                         correctHeadlinesOffset(next_id);
629                                         view(next_id, getActiveFeedId());
630                                 }
631                         }
632                 }
633                 
634                 if (mode == "prev") {
635                         if (prev_id) {
636                                 if (isCdmMode()) {
637                                         cdmExpandArticle(prev_id);
638                                         cdmScrollToArticleId(prev_id);
639                                 } else {
640                                         correctHeadlinesOffset(prev_id);
641                                         view(prev_id, getActiveFeedId());
642                                 }
643                         }
644                 } 
645
646         } catch (e) {
647                 exception_error("moveToPost", e);
648         }
649 }
650
651 function toggleSelected(id) {
652         try {
653         
654                 var cb = $("RCHK-" + id);
655                 var row = $("RROW-" + id);
656
657                 if (row) {
658                         if (row.hasClassName('Selected')) {
659                                 row.removeClassName('Selected');
660                                 if (cb) cb.checked = false;
661                         } else {
662                                 row.addClassName('Selected');
663                                 if (cb) cb.checked = true;
664                         }
665                 }
666         } catch (e) {
667                 exception_error("toggleSelected", e);
668         }
669 }
670
671 function toggleUnread_afh(effect) {
672         try {
673
674                 var elem = effect.element;
675                 elem.style.backgroundColor = "";
676
677         } catch (e) {
678                 exception_error("toggleUnread_afh", e);
679         }
680
681
682 function toggleUnread(id, cmode, effect) {
683         try {
684         
685                 var row = $("RROW-" + id);
686                 if (row) {
687                         if (cmode == undefined || cmode == 2) {
688                                 if (row.hasClassName("Unread")) {
689                                         row.removeClassName("Unread");
690
691                                         if (effect) {
692                                                 new Effect.Highlight(row, {duration: 1, startcolor: "#fff7d5",
693                                                         afterFinish: toggleUnread_afh,
694                                                         queue: { position:'end', scope: 'TMRQ-' + id, limit: 1 } } );
695                                         } 
696
697                                 } else {
698                                         row.addClassName("Unread");
699                                 }
700
701                         } else if (cmode == 0) {
702
703                                 row.removeClassName("Unread");
704
705                                 if (effect) {
706                                         new Effect.Highlight(row, {duration: 1, startcolor: "#fff7d5",
707                                                 afterFinish: toggleUnread_afh,
708                                                 queue: { position:'end', scope: 'TMRQ-' + id, limit: 1 } } );
709                                 } 
710
711                         } else if (cmode == 1) {
712                                 row.addClassName("Unread");
713                         }
714
715                         if (cmode == undefined) cmode = 2;
716
717                         var query = "?op=rpc&subop=catchupSelected" +
718                                 "&cmode=" + param_escape(cmode) + "&ids=" + param_escape(id);
719
720 //                      notify_progress("Loading, please wait...");
721
722                         new Ajax.Request("backend.php", {
723                                 parameters: query,
724                                 onComplete: function(transport) { 
725                                         handle_rpc_reply(transport); 
726                                 } });
727
728                 }
729
730         } catch (e) {
731                 exception_error("toggleUnread", e);
732         }
733 }
734
735 function selectionRemoveLabel(id) {
736         try {
737
738                 var ids = getSelectedArticleIds2();
739
740                 if (ids.length == 0) {
741                         alert(__("No articles are selected."));
742                         return;
743                 }
744
745 //              var ok = confirm(__("Remove selected articles from label?"));
746
747 //              if (ok) {
748
749                         var query = "?op=rpc&subop=removeFromLabel&ids=" +
750                                 param_escape(ids.toString()) + "&lid=" + param_escape(id);
751
752                         console.log(query);
753
754 //                      notify_progress("Loading, please wait...");
755
756                         cache_invalidate("F:" + (-11 - id));
757
758                         new Ajax.Request("backend.php", {
759                                 parameters: query,
760                                 onComplete: function(transport) { 
761                                         show_labels_in_headlines(transport);
762                                         handle_rpc_reply(transport);
763                                 } });
764
765 //              }
766
767         } catch (e) {
768                 exception_error("selectionAssignLabel", e);
769
770         }
771 }
772
773 function selectionAssignLabel(id) {
774         try {
775
776                 var ids = getSelectedArticleIds2();
777
778                 if (ids.length == 0) {
779                         alert(__("No articles are selected."));
780                         return;
781                 }
782
783 //              var ok = confirm(__("Assign selected articles to label?"));
784
785 //              if (ok) {
786
787                         cache_invalidate("F:" + (-11 - id));
788
789                         var query = "?op=rpc&subop=assignToLabel&ids=" +
790                                 param_escape(ids.toString()) + "&lid=" + param_escape(id);
791
792                         console.log(query);
793
794 //                      notify_progress("Loading, please wait...");
795
796                         new Ajax.Request("backend.php", {
797                                 parameters: query,
798                                 onComplete: function(transport) { 
799                                         show_labels_in_headlines(transport);
800                                         handle_rpc_reply(transport);
801                                 } });
802
803 //              }
804
805         } catch (e) {
806                 exception_error("selectionAssignLabel", e);
807
808         }
809 }
810
811 function selectionToggleUnread(set_state, callback_func, no_error) {
812         try {
813                 var rows = getSelectedArticleIds2();
814
815                 if (rows.length == 0 && !no_error) {
816                         alert(__("No articles are selected."));
817                         return;
818                 }
819
820                 for (i = 0; i < rows.length; i++) {
821                         var row = $("RROW-" + rows[i]);
822                         if (row) {
823                                 if (set_state == undefined) {
824                                         if (row.hasClassName("Unread")) {
825                                                 row.removeClassName("Unread");
826                                         } else {
827                                                 row.addClassName("Unread");
828                                         }
829                                 }
830
831                                 if (set_state == false) {
832                                         row.removeClassName("Unread");
833                                 }
834
835                                 if (set_state == true) {
836                                         row.addClassName("Unread");
837                                 }
838                         }
839                 }
840
841                 if (rows.length > 0) {
842
843                         var cmode = "";
844
845                         if (set_state == undefined) {
846                                 cmode = "2";
847                         } else if (set_state == true) {
848                                 cmode = "1";
849                         } else if (set_state == false) {
850                                 cmode = "0";
851                         }
852
853                         var query = "?op=rpc&subop=catchupSelected" +
854                                 "&cmode=" + cmode + "&ids=" + param_escape(rows.toString()); 
855
856                         notify_progress("Loading, please wait...");
857
858                         new Ajax.Request("backend.php", {
859                                 parameters: query,
860                                 onComplete: function(transport) { 
861                                         catchup_callback2(transport, callback_func); 
862                                 } });
863
864                 }
865
866         } catch (e) {
867                 exception_error("selectionToggleUnread", e);
868         }
869 }
870
871 function selectionToggleMarked() {
872         try {
873         
874                 var rows = getSelectedArticleIds2();
875                 
876                 if (rows.length == 0) {
877                         alert(__("No articles are selected."));
878                         return;
879                 }
880
881                 for (i = 0; i < rows.length; i++) {
882                         toggleMark(rows[i], true, true);
883                 }
884
885                 if (rows.length > 0) {
886
887                         var query = "?op=rpc&subop=markSelected&ids=" +
888                                 param_escape(rows.toString()) + "&cmode=2";
889
890                         new Ajax.Request("backend.php", {
891                                 parameters: query,
892                                 onComplete: function(transport) { 
893                                         handle_rpc_reply(transport); 
894                                 } });
895
896                 }
897
898         } catch (e) {
899                 exception_error("selectionToggleMarked", e);
900         }
901 }
902
903 function selectionTogglePublished() {
904         try {
905         
906                 var rows = getSelectedArticleIds2();
907
908                 if (rows.length == 0) {
909                         alert(__("No articles are selected."));
910                         return;
911                 }
912
913                 for (i = 0; i < rows.length; i++) {
914                         togglePub(rows[i], true, true);
915                 }
916
917                 if (rows.length > 0) {
918
919                         var query = "?op=rpc&subop=publishSelected&ids=" +
920                                 param_escape(rows.toString()) + "&cmode=2";
921
922                         new Ajax.Request("backend.php", {
923                                 parameters: query,
924                                 onComplete: function(transport) { 
925                                         handle_rpc_reply(transport); 
926                                 } });
927
928                 }
929
930         } catch (e) {
931                 exception_error("selectionToggleMarked", e);
932         }
933 }
934
935 function getSelectedArticleIds2() {
936
937         var rv = [];
938
939         $$("#headlinesInnerContainer > div[id*=RROW][class*=Selected]").each(
940                 function(child) {
941                         rv.push(child.id.replace("RROW-", ""));
942                 });
943
944         return rv;
945 }
946
947 function getLoadedArticleIds() {
948         var rv = [];
949
950         var children = $$("#headlinesInnerContainer > div[id*=RROW-]");
951
952         children.each(function(child) {
953                         rv.push(child.id.replace("RROW-", ""));
954                 });
955
956         return rv;
957
958 }
959
960 // mode = all,none,unread,invert
961 function selectArticles(mode) {
962         try {
963
964                 var children = $$("#headlinesInnerContainer > div[id*=RROW]");
965
966                 children.each(function(child) {
967                         var id = child.id.replace("RROW-", "");
968                         var cb = $("RCHK-" + id);
969
970                         if (mode == "all") {
971                                 child.addClassName("Selected");
972                                 cb.checked = true;
973                         } else if (mode == "unread") {
974                                 if (child.hasClassName("Unread")) {
975                                         child.addClassName("Selected");
976                                         cb.checked = true;
977                                 } else {
978                                         child.removeClassName("Selected");
979                                         cb.checked = false;
980                                 }
981                         } else if (mode == "invert") {
982                                 if (child.hasClassName("Selected")) {
983                                         child.removeClassName("Selected");
984                                         cb.checked = false;
985                                 } else {
986                                         child.addClassName("Selected");
987                                         cb.checked = true;
988                                 }
989
990                         } else {
991                                 child.removeClassName("Selected");
992                                 cb.checked = false;
993                         }
994                 });
995
996         } catch (e) {
997                 exception_error("selectArticles", e);
998         }
999 }
1000
1001 function catchupPage() {
1002
1003         var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
1004         
1005         var str = __("Mark all visible articles in %s as read?");
1006
1007         str = str.replace("%s", fn);
1008
1009         if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1010                 return;
1011         }
1012
1013         selectArticles('all');
1014         selectionToggleUnread(false, 'viewCurrentFeed()', true)
1015         selectArticles('none');
1016 }
1017
1018 function deleteSelection() {
1019
1020         try {
1021         
1022                 var rows = getSelectedArticleIds2();
1023
1024                 if (rows.length == 0) {
1025                         alert(__("No articles are selected."));
1026                         return;
1027                 }
1028         
1029                 var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
1030                 var str;
1031                 var op;
1032         
1033                 if (getActiveFeedId() != 0) {
1034                         str = __("Delete %d selected articles in %s?");
1035                 } else {
1036                         str = __("Delete %d selected articles?");
1037                 }
1038         
1039                 str = str.replace("%d", rows.length);
1040                 str = str.replace("%s", fn);
1041         
1042                 if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1043                         return;
1044                 }
1045
1046                 query = "?op=rpc&subop=delete&ids=" + param_escape(rows);
1047
1048                 console.log(query);
1049
1050                 new Ajax.Request("backend.php", {
1051                         parameters: query,
1052                         onComplete: function(transport) {
1053                                         viewCurrentFeed();
1054                                 } });
1055
1056         } catch (e) {
1057                 exception_error("deleteSelection", e);
1058         }
1059 }
1060
1061 function archiveSelection() {
1062
1063         try {
1064
1065                 var rows = getSelectedArticleIds2();
1066
1067                 if (rows.length == 0) {
1068                         alert(__("No articles are selected."));
1069                         return;
1070                 }
1071         
1072                 var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
1073                 var str;
1074                 var op;
1075         
1076                 if (getActiveFeedId() != 0) {
1077                         str = __("Archive %d selected articles in %s?");
1078                         op = "archive";
1079                 } else {
1080                         str = __("Move %d archived articles back?");
1081                         op = "unarchive";
1082                 }
1083         
1084                 str = str.replace("%d", rows.length);
1085                 str = str.replace("%s", fn);
1086         
1087                 if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1088                         return;
1089                 }
1090
1091                 query = "?op=rpc&subop="+op+"&ids=" + param_escape(rows);
1092
1093                 console.log(query);
1094
1095                 for (var i = 0; i < rows.length; i++) {
1096                         cache_invalidate(rows[i]);
1097                 }
1098
1099                 new Ajax.Request("backend.php", {
1100                         parameters: query,
1101                         onComplete: function(transport) {
1102                                         viewCurrentFeed();
1103                                 } });
1104
1105         } catch (e) {
1106                 exception_error("archiveSelection", e);
1107         }
1108 }
1109
1110 function catchupSelection() {
1111
1112         try {
1113
1114                 var rows = getSelectedArticleIds2();
1115
1116                 if (rows.length == 0) {
1117                         alert(__("No articles are selected."));
1118                         return;
1119                 }
1120         
1121                 var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
1122                 
1123                 var str = __("Mark %d selected articles in %s as read?");
1124         
1125                 str = str.replace("%d", rows.length);
1126                 str = str.replace("%s", fn);
1127         
1128                 if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1129                         return;
1130                 }
1131         
1132                 selectionToggleUnread(false, 'viewCurrentFeed()', true)
1133
1134         } catch (e) {
1135                 exception_error("catchupSelection", e);
1136         }
1137 }
1138
1139 function editArticleTags(id, feed_id, cdm_enabled) {
1140         displayDlg('editArticleTags', id,
1141                            function () {
1142                                         $("tags_str").focus();
1143
1144                                         new Ajax.Autocompleter('tags_str', 'tags_choices',
1145                                            "backend.php?op=rpc&subop=completeTags",
1146                                            { tokens: ',', paramName: "search" });
1147                            });
1148 }
1149
1150 function editTagsSave() {
1151
1152         notify_progress("Saving article tags...");
1153
1154         var form = document.forms["tag_edit_form"];
1155
1156         var query = Form.serialize("tag_edit_form");
1157
1158         query = "?op=rpc&subop=setArticleTags&" + query;
1159
1160         //console.log(query);
1161
1162         new Ajax.Request("backend.php", {
1163                 parameters: query,
1164                 onComplete: function(transport) {
1165                                 try {
1166                                         //console.log("tags saved...");
1167                         
1168                                         closeInfoBox();
1169                                         notify("");
1170                         
1171                                         if (tagsAreDisplayed()) {
1172                                                 _reload_feedlist_after_view = true;
1173                                         }                       
1174                         
1175                                         if (transport.responseXML) {
1176                                                 var tags_str = transport.responseXML.getElementsByTagName("tags-str")[0];
1177                                                 
1178                                                 if (tags_str) {
1179                                                         var id = tags_str.getAttribute("id");
1180                         
1181                                                         if (id) {
1182                                                                 var tags = $("ATSTR-" + id);
1183                                                                 if (tags) {
1184                                                                         tags.innerHTML = tags_str.firstChild.nodeValue;
1185                                                                 }
1186
1187                                                                 cache_invalidate(id);
1188                                                         }
1189                                                 }
1190                                         }
1191                         
1192                                 } catch (e) {
1193                                         exception_error("editTagsSave", e);
1194                                 }
1195                         } });
1196 }
1197
1198 function editTagsInsert() {
1199         try {
1200
1201                 var form = document.forms["tag_edit_form"];
1202
1203                 var found_tags = form.found_tags;
1204                 var tags_str = form.tags_str;
1205
1206                 var tag = found_tags[found_tags.selectedIndex].value;
1207
1208                 if (tags_str.value.length > 0 && 
1209                                 tags_str.value.lastIndexOf(", ") != tags_str.value.length - 2) {
1210
1211                         tags_str.value = tags_str.value + ", ";
1212                 }
1213
1214                 tags_str.value = tags_str.value + tag + ", ";
1215
1216                 found_tags.selectedIndex = 0;
1217                 
1218         } catch (e) {
1219                 exception_error("editTagsInsert", e);
1220         }
1221 }
1222
1223 function cdmScrollToArticleId(id) {
1224         try {
1225                 var ctr = $("headlinesInnerContainer");
1226                 var e = $("RROW-" + id);
1227
1228                 if (!e || !ctr) return;
1229
1230                 ctr.scrollTop = e.offsetTop;
1231
1232         } catch (e) {
1233                 exception_error("cdmScrollToArticleId", e);
1234         }
1235 }
1236
1237 function cdmWatchdog() {
1238
1239         try {
1240
1241                 var ctr = $("headlinesInnerContainer");
1242
1243                 if (!ctr) return;
1244
1245                 var ids = new Array();
1246
1247                 var e = ctr.firstChild;
1248
1249                 while (e) {
1250                         if (e.className && e.hasClassName("Unread") && e.id &&
1251                                         e.id.match("RROW-")) {
1252
1253                                 // article fits in viewport OR article is longer than viewport and
1254                                 // its bottom is visible
1255
1256                                 if (ctr.scrollTop <= e.offsetTop && e.offsetTop + e.offsetHeight <=
1257                                                 ctr.scrollTop + ctr.offsetHeight) {
1258
1259 //                                      console.log(e.id + " is visible " + e.offsetTop + "." + 
1260 //                                              (e.offsetTop + e.offsetHeight) + " vs " + ctr.scrollTop + "." +
1261 //                                              (ctr.scrollTop + ctr.offsetHeight));
1262
1263                                         ids.push(e.id.replace("RROW-", ""));
1264
1265                                 } else if (e.offsetHeight > ctr.offsetHeight &&
1266                                                 e.offsetTop + e.offsetHeight >= ctr.scrollTop &&
1267                                                 e.offsetTop + e.offsetHeight <= ctr.scrollTop + ctr.offsetHeight) {
1268
1269                                         ids.push(e.id.replace("RROW-", "")); 
1270
1271                                 }
1272
1273                                 // method 2: article bottom is visible and is in upper 1/2 of the viewport
1274
1275 /*                              if (e.offsetTop + e.offsetHeight >= ctr.scrollTop &&
1276                                                 e.offsetTop + e.offsetHeight <= ctr.scrollTop + ctr.offsetHeight/2) {
1277
1278                                         ids.push(e.id.replace("RROW-", "")); 
1279
1280                                 } */
1281
1282                         }
1283
1284                         e = e.nextSibling;
1285                 }
1286
1287                 console.log("cdmWatchdog, ids= " + ids.toString());
1288
1289                 if (ids.length > 0) {
1290
1291                         for (var i = 0; i < ids.length; i++) {
1292                                 var e = $("RROW-" + ids[i]);
1293                                 if (e) {
1294                                         e.removeClassName("Unread");
1295                                 }
1296                         }
1297
1298                         var query = "?op=rpc&subop=catchupSelected" +
1299                                 "&cmode=0" + "&ids=" + param_escape(ids.toString());
1300
1301                         new Ajax.Request("backend.php", {
1302                                 parameters: query,
1303                                 onComplete: function(transport) { 
1304                                         handle_rpc_reply(transport); 
1305                                 } });
1306
1307                 }
1308
1309                 _cdm_wd_timeout = window.setTimeout("cdmWatchdog()", 4000);
1310
1311         } catch (e) {
1312                 exception_error("cdmWatchdog", e);
1313         }
1314
1315 }
1316
1317
1318 function cache_inject(id, article, param) {
1319
1320         try {
1321                 if (!cache_check_param(id, param)) {
1322                         //console.log("cache_article: miss: " + id + " [p=" + param + "]");
1323
1324                    var date = new Date();
1325               var ts = Math.round(date.getTime() / 1000);
1326
1327                         var cache_obj = {};
1328         
1329                         cache_obj["id"] = id;
1330                         cache_obj["data"] = article;
1331                         cache_obj["param"] = param;
1332
1333                         if (param) id = id + ":" + param;
1334
1335                         cache_added["TS:" + id] = ts;
1336
1337                         if (has_local_storage()) 
1338                                 localStorage.setItem(id, JSON.stringify(cache_obj));
1339                         else
1340                                 article_cache.push(cache_obj);
1341
1342                 } else {
1343                         //console.log("cache_article: hit: " + id + " [p=" + param + "]");
1344                 }
1345         } catch (e) {   
1346                 exception_error("cache_inject", e);
1347         }
1348 }
1349
1350 function cache_find(id) {
1351
1352         if (has_local_storage()) {
1353                 var cache_obj = localStorage.getItem(id);
1354
1355                 if (cache_obj) {
1356                         cache_obj = JSON.parse(cache_obj);
1357
1358                         if (cache_obj)
1359                                 return cache_obj['data'];
1360                 }
1361
1362         } else {
1363                 for (var i = 0; i < article_cache.length; i++) {
1364                         if (article_cache[i]["id"] == id) {
1365                                 return article_cache[i]["data"];
1366                         }
1367                 }
1368         }
1369         return false;
1370 }
1371
1372 function cache_find_param(id, param) {
1373
1374         if (has_local_storage()) {
1375
1376                 if (param) id = id + ":" + param;
1377
1378                 var cache_obj = localStorage.getItem(id);
1379
1380                 if (cache_obj) {
1381                         cache_obj = JSON.parse(cache_obj);
1382
1383                         if (cache_obj)
1384                                 return cache_obj['data'];
1385                 }
1386
1387         } else {
1388                 for (var i = 0; i < article_cache.length; i++) {
1389                         if (article_cache[i]["id"] == id && article_cache[i]["param"] == param) {
1390                                 return article_cache[i]["data"];
1391                         }
1392                 }
1393         }
1394
1395         return false;
1396 }
1397
1398 function cache_check(id) {
1399         if (has_local_storage()) {
1400                 if (localStorage.getItem(id))
1401                         return true;
1402         } else {
1403                 for (var i = 0; i < article_cache.length; i++) {
1404                         if (article_cache[i]["id"] == id) {
1405                                 return true;
1406                         }
1407                 }
1408         }
1409         return false;
1410 }
1411
1412 function cache_check_param(id, param) {
1413         if (has_local_storage()) {
1414
1415                 if (param) id = id + ":" + param;
1416
1417                 if (localStorage.getItem(id))
1418                         return true;
1419
1420         } else {
1421                 for (var i = 0; i < article_cache.length; i++) {
1422                         if (article_cache[i]["id"] == id && article_cache[i]["param"] == param) {
1423                                 return true;
1424                         }
1425                 }
1426         }
1427         return false;
1428 }
1429
1430 function cache_expire() {
1431 if (has_local_storage()) {
1432
1433                 var date = new Date();
1434                 var timestamp = Math.round(date.getTime() / 1000);
1435
1436                 for (var i = 0; i < localStorage.length; i++) {
1437
1438                         var id = localStorage.key(i);
1439
1440                         if (timestamp - cache_added["TS:" + id] > 180) {
1441                                 localStorage.removeItem(id);
1442                         }
1443                 }
1444
1445         } else {
1446                 while (article_cache.length > 25) {
1447                         article_cache.shift();
1448                 }
1449         }
1450 }
1451
1452 function cache_flush() {
1453         if (has_local_storage()) {
1454                 localStorage.clear();
1455         } else {
1456                 article_cache = new Array();
1457         }
1458 }
1459
1460 function cache_invalidate(id) {
1461         try {   
1462                 if (has_local_storage()) {
1463
1464                         var found = false;
1465
1466                         for (var i = 0; i < localStorage.length; i++) {
1467                                 var key = localStorage.key(i);
1468
1469 //                                      console.warn("cache_invalidate: " + key_id + " cmp " + id);
1470
1471                                 if (key == id || key.indexOf(id + ":") == 0) {
1472                                         localStorage.removeItem(key);
1473                                         found = true;
1474                                         break;
1475                                 }
1476                         }
1477
1478                         return found;
1479
1480                 } else {
1481                         var i = 0
1482
1483                         while (i < article_cache.length) {
1484                                 if (article_cache[i]["id"] == id) {
1485                                         //console.log("cache_invalidate: removed id " + id);
1486                                         article_cache.splice(i, 1);
1487                                         return true;
1488                                 }
1489                                 i++;
1490                         }
1491                 }
1492
1493                 //console.log("cache_invalidate: id not found: " + id);
1494                 return false;
1495         } catch (e) {
1496                 exception_error("cache_invalidate", e);
1497         }
1498 }
1499
1500 function getActiveArticleId() {
1501         return active_post_id;
1502 }
1503
1504 function preloadBatchedArticles() {
1505         try {
1506
1507                 var query = "?op=rpc&subop=getArticles&ids=" + 
1508                         preload_id_batch.toString();
1509
1510                 new Ajax.Request("backend.php", {
1511                         parameters: query,
1512                         onComplete: function(transport) { 
1513
1514                                 preload_id_batch = [];
1515
1516                                 var articles = transport.responseXML.getElementsByTagName("article");
1517
1518                                 for (var i = 0; i < articles.length; i++) {
1519                                         var id = articles[i].getAttribute("id");
1520                                         if (!cache_check(id)) {
1521                                                 cache_inject(id, articles[i].firstChild.nodeValue);                             
1522                                                 console.log("preloaded article: " + id);
1523                                         }
1524                                 }
1525                 } }); 
1526
1527         } catch (e) {
1528                 exception_error("preloadBatchedArticles", e);
1529         }
1530 }
1531
1532 function preloadArticleUnderPointer(id) {
1533         try {
1534                 if (getInitParam("bw_limit") == "1") return;
1535
1536                 if (post_under_pointer == id && !cache_check(id)) {
1537
1538                         console.log("trying to preload article " + id);
1539
1540                         var neighbor_ids = getRelativePostIds(id, 1);
1541
1542                         /* only request uncached articles */
1543
1544                         if (preload_id_batch.indexOf(id) == -1) {
1545                                 for (var i = 0; i < neighbor_ids.length; i++) {
1546                                         if (!cache_check(neighbor_ids[i])) {
1547                                                 preload_id_batch.push(neighbor_ids[i]);
1548                                         }
1549                                 }
1550                         }
1551
1552                         if (preload_id_batch.indexOf(id) == -1)
1553                                 preload_id_batch.push(id);
1554
1555                         //console.log("preload ids batch: " + preload_id_batch.toString());
1556
1557                         window.clearTimeout(preload_timeout_id);
1558                         preload_batch_timeout_id = window.setTimeout('preloadBatchedArticles()', 1000);
1559
1560                 }
1561         } catch (e) {
1562                 exception_error("preloadArticleUnderPointer", e);
1563         }
1564 }
1565
1566 function postMouseIn(id) {
1567         try {
1568                 if (post_under_pointer != id) {
1569                         post_under_pointer = id;
1570                         if (!isCdmMode()) {
1571                                 window.setTimeout("preloadArticleUnderPointer(" + id + ")", 250);
1572                         }
1573                 }
1574
1575         } catch (e) {
1576                 exception_error("postMouseIn", e);
1577         }
1578 }
1579
1580 function postMouseOut(id) {
1581         try {
1582                 post_under_pointer = false;
1583         } catch (e) {
1584                 exception_error("postMouseOut", e);
1585         }
1586 }
1587
1588 function headlines_scroll_handler() {
1589         try {
1590
1591                 var e = $("headlinesInnerContainer");
1592
1593                 var toolbar_form = document.forms["main_toolbar_form"];
1594
1595 //              console.log((e.scrollTop + e.offsetHeight) + " vs " + e.scrollHeight + " dis? " +
1596 //                      _infscroll_disable);
1597
1598                 if (e.scrollTop + e.offsetHeight > e.scrollHeight - 100) {
1599                         if (!_infscroll_disable) {
1600                                 viewNextFeedPage();
1601                         }
1602                 }
1603
1604         } catch (e) {
1605                 exception_error("headlines_scroll_handler", e);
1606         }
1607 }
1608
1609 function catchupRelativeToArticle(below) {
1610
1611         try {
1612
1613
1614                 if (!getActiveArticleId()) {
1615                         alert(__("No article is selected."));
1616                         return;
1617                 }
1618
1619                 var visible_ids = getVisibleArticleIds();
1620
1621                 var ids_to_mark = new Array();
1622
1623                 if (!below) {
1624                         for (var i = 0; i < visible_ids.length; i++) {
1625                                 if (visible_ids[i] != getActiveArticleId()) {
1626                                         var e = $("RROW-" + visible_ids[i]);
1627
1628                                         if (e && e.hasClassName("Unread")) {
1629                                                 ids_to_mark.push(visible_ids[i]);
1630                                         }
1631                                 } else {
1632                                         break;
1633                                 }
1634                         }
1635                 } else {
1636                         for (var i = visible_ids.length-1; i >= 0; i--) {
1637                                 if (visible_ids[i] != getActiveArticleId()) {
1638                                         var e = $("RROW-" + visible_ids[i]);
1639
1640                                         if (e && e.hasClassName("Unread")) {
1641                                                 ids_to_mark.push(visible_ids[i]);
1642                                         }
1643                                 } else {
1644                                         break;
1645                                 }
1646                         }
1647                 }
1648
1649                 if (ids_to_mark.length == 0) {
1650                         alert(__("No articles found to mark"));
1651                 } else {
1652                         var msg = __("Mark %d article(s) as read?").replace("%d", ids_to_mark.length);
1653
1654                         if (getInitParam("confirm_feed_catchup") != 1 || confirm(msg)) {
1655
1656                                 for (var i = 0; i < ids_to_mark.length; i++) {
1657                                         var e = $("RROW-" + ids_to_mark[i]);
1658                                         e.removeClassName("Unread");
1659                                 }
1660
1661                                 var query = "?op=rpc&subop=catchupSelected" +
1662                                         "&cmode=0" + "&ids=" + param_escape(ids_to_mark.toString()); 
1663
1664                                 new Ajax.Request("backend.php", {
1665                                         parameters: query,
1666                                         onComplete: function(transport) { 
1667                                                 catchup_callback2(transport); 
1668                                         } });
1669
1670                         }
1671                 }
1672
1673         } catch (e) {
1674                 exception_error("catchupRelativeToArticle", e);
1675         }
1676 }
1677
1678 function cdmExpandArticle(id) {
1679         try {
1680
1681                 hideAuxDlg();
1682
1683                 var elem = $("CICD-" + active_post_id);
1684
1685                 var upd_img_pic = $("FUPDPIC-" + id);
1686
1687                 if (upd_img_pic && (upd_img_pic.src.match("updated.png") || 
1688                                 upd_img_pic.src.match("fresh_sign.png"))) {
1689
1690                         upd_img_pic.src = "images/blank_icon.gif";
1691                 }
1692
1693                 if (id == active_post_id && Element.visible(elem))
1694                         return true;
1695
1696                 selectArticles("none");
1697
1698                 var old_offset = $("RROW-" + id).offsetTop;
1699
1700                 if (active_post_id && elem && !getInitParam("cdm_expanded")) {
1701                         Element.hide(elem);
1702                         Element.show("CEXC-" + active_post_id);
1703                 }
1704
1705                 active_post_id = id;
1706
1707                 elem = $("CICD-" + id);
1708
1709                 if (!Element.visible(elem)) {
1710                         Element.show(elem);
1711                         Element.hide("CEXC-" + id);
1712
1713                         if ($("CWRAP-" + id).innerHTML == "") {
1714
1715                                 $("FUPDPIC-" + id).src = "images/indicator_tiny.gif";
1716
1717                                 $("CWRAP-" + id).innerHTML = "<div class=\"insensitive\">" + 
1718                                         __("Loading, please wait...") + "</div>";
1719         
1720                                 var query = "?op=rpc&subop=cdmGetArticle&id=" + param_escape(id);
1721         
1722                                 //console.log(query);
1723         
1724                                 new Ajax.Request("backend.php", {
1725                                         parameters: query,
1726                                         onComplete: function(transport) { 
1727                                                 $("FUPDPIC-" + id).src = 'images/blank_icon.gif';
1728         
1729                                                 if (transport.responseXML) {
1730                                                         var article = transport.responseXML.getElementsByTagName("article")[0];
1731                                                         var recv_id = article.getAttribute("id");
1732         
1733                                                         if (recv_id == id)
1734                                                                 $("CWRAP-" + id).innerHTML = article.firstChild.nodeValue;
1735         
1736                                                 } else {
1737                                                         $("CWRAP-" + id).innerHTML = __("Unable to load article.");
1738         
1739                                                 }
1740                                 }});
1741         
1742                         }
1743                 }
1744
1745                 var new_offset = $("RROW-" + id).offsetTop;
1746
1747                 $("headlinesInnerContainer").scrollTop += (new_offset-old_offset);
1748
1749                 if ($("RROW-" + id).offsetTop != old_offset) 
1750                         $("headlinesInnerContainer").scrollTop = new_offset;
1751
1752                 toggleUnread(id, 0, true);
1753                 toggleSelected(id);
1754
1755         } catch (e) {
1756                 exception_error("cdmExpandArticle", e);
1757         }
1758
1759         return false;
1760 }
1761
1762 function fixHeadlinesOrder(ids) {
1763         try {
1764                 for (var i = 0; i < ids.length; i++) {
1765                         var e = $("RROW-" + ids[i]);
1766
1767                         if (e) {
1768                                 if (i % 2 == 0) {
1769                                         e.removeClassName("even");
1770                                         e.addClassName("odd");
1771                                 } else {
1772                                         e.removeClassName("odd");
1773                                         e.addClassName("even");
1774                                 }
1775                         }
1776                 }
1777         } catch (e) {
1778                 exception_error("fixHeadlinesOrder", e);
1779         }
1780 }
1781
1782 function getArticleUnderPointer() {
1783         return post_under_pointer;
1784 }
1785
1786 function zoomToArticle(id) {
1787         try {
1788                 var w = window.open("backend.php?op=view&mode=zoom&id=" + param_escape(id), 
1789                         "ttrss_zoom_" + id,
1790                         "status=0,toolbar=0,location=0,width=450,height=300,scrollbars=1,menubar=0");
1791
1792         } catch (e) {
1793                 exception_error("zoomToArticle", e);
1794         }
1795 }
1796
1797 function scrollArticle(offset) {
1798         try {
1799                 if (!isCdmMode()) {
1800                         var ci = $("content-insert");
1801                         if (ci) {
1802                                 ci.scrollTop += offset;
1803                         }
1804                 } else {
1805                         var hi = $("headlinesInnerContainer");
1806                         if (hi) {
1807                                 hi.scrollTop += offset;
1808                         }
1809
1810                 }
1811         } catch (e) {
1812                 exception_error("scrollArticle", e);
1813         }
1814 }
1815
1816 function show_labels_in_headlines(transport) {
1817         try {
1818                 if (transport.responseXML) {
1819                         var info = transport.responseXML.getElementsByTagName("info-for-headlines")[0];
1820
1821                         var elems = info.getElementsByTagName("entry");
1822
1823                         for (var l = 0; l < elems.length; l++) {
1824                                 var e_id = elems[l].getAttribute("id");
1825
1826                                 if (e_id) {
1827
1828                                         var ctr = $("HLLCTR-" + e_id);
1829
1830                                         if (ctr) {
1831                                                 ctr.innerHTML = elems[l].firstChild.nodeValue;
1832                                         }
1833                                 }
1834
1835                         }
1836
1837                 }
1838         } catch (e) {
1839                 exception_error("show_labels_in_headlines", e);
1840
1841         }
1842 }
1843
1844 function toggleHeadlineActions() {
1845         try {
1846                 var e = $("headlineActionsBody");
1847                 var p = $("headlineActionsDrop");
1848
1849                 if (!Element.visible(e)) {
1850                         Element.show(e);
1851                 } else {
1852                         Element.hide(e);
1853                 }
1854
1855                 e.scrollTop = 0;
1856                 e.style.left = (p.offsetLeft + 1) + "px";
1857                 e.style.top = (p.offsetTop + p.offsetHeight + 2) + "px";
1858
1859         } catch (e) {
1860                 exception_error("toggleHeadlineActions", e);
1861         }
1862 }
1863
1864 function publishWithNote(id, def_note) {
1865         try {
1866                 if (!def_note) def_note = '';
1867
1868                 var note = prompt(__("Please enter a note for this article:"), def_note);
1869
1870                 if (note != undefined) {
1871                         togglePub(id, false, false, note);
1872                 }
1873
1874         } catch (e) {
1875                 exception_error("publishWithNote", e);
1876         }
1877 }
1878
1879 function emailArticle(id) {
1880         try {
1881                 if (!id) {
1882                         var ids = getSelectedArticleIds2();
1883
1884                         if (ids.length == 0) {
1885                                 alert(__("No articles are selected."));
1886                                 return;
1887                         }
1888
1889                         id = ids.toString();
1890                 }
1891
1892                 displayDlg('emailArticle', id, 
1893                    function () {                                
1894                                 document.forms['article_email_form'].destination.focus();
1895
1896                            new Ajax.Autocompleter('destination', 'destination_choices',
1897                                    "backend.php?op=rpc&subop=completeEmails",
1898                                    { tokens: '', paramName: "search" });
1899
1900                         });
1901
1902         } catch (e) {
1903                 exception_error("emailArticle", e);
1904         }
1905 }
1906
1907 function emailArticleDo() {
1908         try {
1909                 var f = document.forms['article_email_form'];
1910
1911                 if (f.destination.value == "") {
1912                         alert("Please fill in the destination email.");
1913                         return;
1914                 }
1915
1916                 if (f.subject.value == "") {
1917                         alert("Please fill in the subject.");
1918                         return;
1919                 }
1920
1921                 var query = Form.serialize("article_email_form");
1922
1923 //              console.log(query);
1924
1925                 new Ajax.Request("backend.php", {
1926                         parameters: query,
1927                         onComplete: function(transport) { 
1928                                 try {
1929
1930                                         var error = transport.responseXML.getElementsByTagName('error')[0];
1931
1932                                         if (error) {
1933                                                 alert(__('Error sending email:') + ' ' + error.firstChild.nodeValue);
1934                                         } else {
1935                                                 notify_info('Your message has been sent.');
1936                                                 closeInfoBox();
1937                                         }
1938
1939                                 } catch (e) {
1940                                         exception_error("sendEmailDo", e);
1941                                 }
1942
1943                         } });
1944
1945         } catch (e) {
1946                 exception_error("emailArticleDo", e);
1947         }
1948 }
1949
1950 function dismissArticle(id) {
1951         try {
1952                 var elem = $("RROW-" + id);
1953
1954                 toggleUnread(id, 0, true);
1955
1956                 new Effect.Fade(elem, {duration : 0.5});
1957
1958                 active_post_id = false;
1959
1960         } catch (e) {
1961                 exception_error("dismissArticle", e);
1962         }
1963 }
1964
1965 function dismissSelectedArticles() {
1966         try {
1967
1968                 var ids = getVisibleArticleIds();
1969                 var tmp = [];
1970                 var sel = [];
1971
1972                 for (var i = 0; i < ids.length; i++) {
1973                         var elem = $("RROW-" + ids[i]);
1974
1975                         if (elem.className && elem.hasClassName("Selected") && 
1976                                         ids[i] != active_post_id) {
1977                                 new Effect.Fade(elem, {duration : 0.5});
1978                                 sel.push(ids[i]);
1979                         } else {
1980                                 tmp.push(ids[i]);
1981                         }
1982                 }
1983
1984                 if (sel.length > 0)
1985                         selectionToggleUnread(false);
1986
1987                 fixHeadlinesOrder(tmp);
1988
1989         } catch (e) {
1990                 exception_error("dismissSelectedArticles", e);
1991         }
1992 }
1993
1994 function dismissReadArticles() {
1995         try {
1996
1997                 var ids = getVisibleArticleIds();
1998                 var tmp = [];
1999
2000                 for (var i = 0; i < ids.length; i++) {
2001                         var elem = $("RROW-" + ids[i]);
2002
2003                         if (elem.className && !elem.hasClassName("Unread") && 
2004                                         !elem.hasClassName("Selected")) {
2005                         
2006                                 new Effect.Fade(elem, {duration : 0.5});
2007                         } else {
2008                                 tmp.push(ids[i]);
2009                         }
2010                 }
2011
2012                 fixHeadlinesOrder(tmp);
2013
2014         } catch (e) {
2015                 exception_error("dismissSelectedArticles", e);
2016         }
2017 }
2018
2019 function getVisibleArticleIds() {
2020         var ids = [];
2021
2022         try {
2023                 
2024                 getLoadedArticleIds().each(function(id) {
2025                         var elem = $("RROW-" + id);
2026                         if (elem && Element.visible(elem))
2027                                 ids.push(id);
2028                         });
2029
2030         } catch (e) {
2031                 exception_error("getVisibleArticleIds", e);
2032         }
2033
2034         return ids;
2035 }
2036
2037 function cdmClicked(event, id) {
2038         try {
2039                 var shift_key = event.shiftKey;
2040
2041                 hideAuxDlg();
2042
2043                 if (!event.ctrlKey) {
2044                         selectArticles("none");
2045                         toggleSelected(id);
2046
2047                         var elem = $("RROW-" + id);
2048
2049                         if (elem)
2050                                 elem.removeClassName("Unread");
2051
2052                         var upd_img_pic = $("FUPDPIC-" + id);
2053
2054                         if (upd_img_pic && (upd_img_pic.src.match("updated.png") || 
2055                                         upd_img_pic.src.match("fresh_sign.png"))) {
2056
2057                                 upd_img_pic.src = "images/blank_icon.gif";
2058                         }
2059
2060                         active_post_id = id;
2061
2062                         var query = "?op=rpc&subop=catchupSelected" +
2063                                 "&cmode=0&ids=" + param_escape(id);
2064
2065                         new Ajax.Request("backend.php", {
2066                                 parameters: query,
2067                                 onComplete: function(transport) { 
2068                                         handle_rpc_reply(transport); 
2069                                 } });
2070
2071                         return true;
2072                 } else {
2073                         toggleSelected(id);
2074                 }
2075
2076         } catch (e) {
2077                 exception_error("cdmClicked");
2078         }
2079
2080         return false;
2081 }
2082
2083 function hlClicked(event, id) {
2084         try {
2085
2086                 if (!event.ctrlKey) {
2087                         view(id);
2088                         return true;
2089                 } else {
2090                         toggleSelected(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 = $("headlinesInnerContainer");
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