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