]> git.wh0rd.org Git - tt-rss.git/blob - viewfeed.js
assign/remove to label rpc: use JSON
[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 /*      displayDlg('editArticleTags', id,
1069                            function () {
1070                                         $("tags_str").focus();
1071
1072                                         new Ajax.Autocompleter('tags_str', 'tags_choices',
1073                                            "backend.php?op=rpc&subop=completeTags",
1074                                            { tokens: ',', paramName: "search" });
1075                            }); */
1076
1077                 var query = "backend.php?op=dlg&id=editArticleTags&param=" + param_escape(id);
1078
1079                 if (dijit.byId("editTagsDlg"))
1080                         dijit.byId("editTagsDlg").destroyRecursive();
1081
1082                 dialog = new dijit.Dialog({
1083                         id: "editTagsDlg",
1084                         title: __("Edit article Tags"),
1085                         style: "width: 600px",
1086                         execute: function() {
1087                                 if (this.validate()) {
1088                                         var query = dojo.objectToQuery(this.attr('value'));
1089
1090                                         notify_progress("Saving article tags...", true);
1091
1092                                         new Ajax.Request("backend.php", {
1093                                         parameters: query,
1094                                         onComplete: function(transport) {
1095                                                 notify('');
1096                                                 dialog.hide();
1097         
1098                                                 if (transport.responseXML) {
1099                                                         var tags_str = transport.responseXML.getElementsByTagName("tags-str")[0];
1100                                                         
1101                                                         if (tags_str) {
1102                                                                 var id = tags_str.getAttribute("id");
1103                                 
1104                                                                 if (id) {
1105                                                                         var tags = $("ATSTR-" + id);
1106                                                                         if (tags) {
1107                                                                                 tags.innerHTML = tags_str.firstChild.nodeValue;
1108                                                                         }
1109         
1110                                                                         cache_invalidate(id);
1111                                                                 }
1112                                                         }
1113                                                 }
1114         
1115                                         }});
1116                                 }
1117                         },
1118                         href: query,
1119                 });
1120
1121                 var tmph = dojo.connect(dialog, 'onLoad', function() {
1122                 dojo.disconnect(tmph);
1123
1124                         new Ajax.Autocompleter('tags_str', 'tags_choices',
1125                            "backend.php?op=rpc&subop=completeTags",
1126                            { tokens: ',', paramName: "search" });
1127                 });
1128
1129                 dialog.show();
1130
1131 }
1132
1133 function cdmScrollToArticleId(id) {
1134         try {
1135                 var ctr = $("headlines-frame");
1136                 var e = $("RROW-" + id);
1137
1138                 if (!e || !ctr) return;
1139
1140                 ctr.scrollTop = e.offsetTop;
1141
1142         } catch (e) {
1143                 exception_error("cdmScrollToArticleId", e);
1144         }
1145 }
1146
1147 function cdmWatchdog() {
1148
1149         try {
1150
1151                 var ctr = $("headlines-frame");
1152
1153                 if (!ctr) return;
1154
1155                 var ids = new Array();
1156
1157                 var e = ctr.firstChild;
1158
1159                 while (e) {
1160                         if (e.className && e.hasClassName("Unread") && e.id &&
1161                                         e.id.match("RROW-")) {
1162
1163                                 // article fits in viewport OR article is longer than viewport and
1164                                 // its bottom is visible
1165
1166                                 if (ctr.scrollTop <= e.offsetTop && e.offsetTop + e.offsetHeight <=
1167                                                 ctr.scrollTop + ctr.offsetHeight) {
1168
1169 //                                      console.log(e.id + " is visible " + e.offsetTop + "." + 
1170 //                                              (e.offsetTop + e.offsetHeight) + " vs " + ctr.scrollTop + "." +
1171 //                                              (ctr.scrollTop + ctr.offsetHeight));
1172
1173                                         ids.push(e.id.replace("RROW-", ""));
1174
1175                                 } else if (e.offsetHeight > ctr.offsetHeight &&
1176                                                 e.offsetTop + e.offsetHeight >= ctr.scrollTop &&
1177                                                 e.offsetTop + e.offsetHeight <= ctr.scrollTop + ctr.offsetHeight) {
1178
1179                                         ids.push(e.id.replace("RROW-", "")); 
1180
1181                                 }
1182
1183                                 // method 2: article bottom is visible and is in upper 1/2 of the viewport
1184
1185 /*                              if (e.offsetTop + e.offsetHeight >= ctr.scrollTop &&
1186                                                 e.offsetTop + e.offsetHeight <= ctr.scrollTop + ctr.offsetHeight/2) {
1187
1188                                         ids.push(e.id.replace("RROW-", "")); 
1189
1190                                 } */
1191
1192                         }
1193
1194                         e = e.nextSibling;
1195                 }
1196
1197                 console.log("cdmWatchdog, ids= " + ids.toString());
1198
1199                 if (ids.length > 0) {
1200
1201                         for (var i = 0; i < ids.length; i++) {
1202                                 var e = $("RROW-" + ids[i]);
1203                                 if (e) {
1204                                         e.removeClassName("Unread");
1205                                 }
1206                         }
1207
1208                         var query = "?op=rpc&subop=catchupSelected" +
1209                                 "&cmode=0" + "&ids=" + param_escape(ids.toString());
1210
1211                         new Ajax.Request("backend.php", {
1212                                 parameters: query,
1213                                 onComplete: function(transport) { 
1214                                         handle_rpc_json(transport); 
1215                                 } });
1216
1217                 }
1218
1219                 _cdm_wd_timeout = window.setTimeout("cdmWatchdog()", 4000);
1220
1221         } catch (e) {
1222                 exception_error("cdmWatchdog", e);
1223         }
1224
1225 }
1226
1227
1228 function cache_inject(id, article, param) {
1229
1230         try {
1231                 if (!cache_check_param(id, param)) {
1232                         //console.log("cache_article: miss: " + id + " [p=" + param + "]");
1233
1234                    var date = new Date();
1235               var ts = Math.round(date.getTime() / 1000);
1236
1237                         var cache_obj = {};
1238         
1239                         cache_obj["id"] = id;
1240                         cache_obj["data"] = article;
1241                         cache_obj["param"] = param;
1242
1243                         if (param) id = id + ":" + param;
1244
1245                         cache_added["TS:" + id] = ts;
1246
1247                         if (has_local_storage()) 
1248                                 sessionStorage.setItem(id, JSON.stringify(cache_obj));
1249                         else
1250                                 article_cache.push(cache_obj);
1251
1252                 } else {
1253                         //console.log("cache_article: hit: " + id + " [p=" + param + "]");
1254                 }
1255         } catch (e) {   
1256                 exception_error("cache_inject", e);
1257         }
1258 }
1259
1260 function cache_find(id) {
1261
1262         if (has_local_storage()) {
1263                 var cache_obj = sessionStorage.getItem(id);
1264
1265                 if (cache_obj) {
1266                         cache_obj = JSON.parse(cache_obj);
1267
1268                         if (cache_obj)
1269                                 return cache_obj['data'];
1270                 }
1271
1272         } else {
1273                 for (var i = 0; i < article_cache.length; i++) {
1274                         if (article_cache[i]["id"] == id) {
1275                                 return article_cache[i]["data"];
1276                         }
1277                 }
1278         }
1279         return false;
1280 }
1281
1282 function cache_find_param(id, param) {
1283
1284         if (has_local_storage()) {
1285
1286                 if (param) id = id + ":" + param;
1287
1288                 var cache_obj = sessionStorage.getItem(id);
1289
1290                 if (cache_obj) {
1291                         cache_obj = JSON.parse(cache_obj);
1292
1293                         if (cache_obj)
1294                                 return cache_obj['data'];
1295                 }
1296
1297         } else {
1298                 for (var i = 0; i < article_cache.length; i++) {
1299                         if (article_cache[i]["id"] == id && article_cache[i]["param"] == param) {
1300                                 return article_cache[i]["data"];
1301                         }
1302                 }
1303         }
1304
1305         return false;
1306 }
1307
1308 function cache_check(id) {
1309         if (has_local_storage()) {
1310                 if (sessionStorage.getItem(id))
1311                         return true;
1312         } else {
1313                 for (var i = 0; i < article_cache.length; i++) {
1314                         if (article_cache[i]["id"] == id) {
1315                                 return true;
1316                         }
1317                 }
1318         }
1319         return false;
1320 }
1321
1322 function cache_check_param(id, param) {
1323         if (has_local_storage()) {
1324
1325                 if (param) id = id + ":" + param;
1326
1327                 if (sessionStorage.getItem(id))
1328                         return true;
1329
1330         } else {
1331                 for (var i = 0; i < article_cache.length; i++) {
1332                         if (article_cache[i]["id"] == id && article_cache[i]["param"] == param) {
1333                                 return true;
1334                         }
1335                 }
1336         }
1337         return false;
1338 }
1339
1340 function cache_expire() {
1341 if (has_local_storage()) {
1342
1343                 var date = new Date();
1344                 var timestamp = Math.round(date.getTime() / 1000);
1345
1346                 for (var i = 0; i < sessionStorage.length; i++) {
1347
1348                         var id = sessionStorage.key(i);
1349
1350                         if (timestamp - cache_added["TS:" + id] > 180) {
1351                                 sessionStorage.removeItem(id);
1352                         }
1353                 }
1354
1355         } else {
1356                 while (article_cache.length > 25) {
1357                         article_cache.shift();
1358                 }
1359         }
1360 }
1361
1362 function cache_flush() {
1363         if (has_local_storage()) {
1364                 sessionStorage.clear();
1365         } else {
1366                 article_cache = new Array();
1367         }
1368 }
1369
1370 function cache_invalidate(id) {
1371         try {   
1372                 if (has_local_storage()) {
1373
1374                         var found = false;
1375
1376                         for (var i = 0; i < sessionStorage.length; i++) {
1377                                 var key = sessionStorage.key(i);
1378
1379 //                                      console.warn("cache_invalidate: " + key_id + " cmp " + id);
1380
1381                                 if (key == id || key.indexOf(id + ":") == 0) {
1382                                         sessionStorage.removeItem(key);
1383                                         found = true;
1384                                         break;
1385                                 }
1386                         }
1387
1388                         return found;
1389
1390                 } else {
1391                         var i = 0
1392
1393                         while (i < article_cache.length) {
1394                                 if (article_cache[i]["id"] == id) {
1395                                         //console.log("cache_invalidate: removed id " + id);
1396                                         article_cache.splice(i, 1);
1397                                         return true;
1398                                 }
1399                                 i++;
1400                         }
1401                 }
1402
1403                 //console.log("cache_invalidate: id not found: " + id);
1404                 return false;
1405         } catch (e) {
1406                 exception_error("cache_invalidate", e);
1407         }
1408 }
1409
1410 function getActiveArticleId() {
1411         return active_post_id;
1412 }
1413
1414 function preloadBatchedArticles() {
1415         try {
1416
1417                 var query = "?op=rpc&subop=getArticles&ids=" + 
1418                         preload_id_batch.toString();
1419
1420                 new Ajax.Request("backend.php", {
1421                         parameters: query,
1422                         onComplete: function(transport) { 
1423
1424                                 preload_id_batch = [];
1425
1426                                 var articles = transport.responseXML.getElementsByTagName("article");
1427
1428                                 for (var i = 0; i < articles.length; i++) {
1429                                         var id = articles[i].getAttribute("id");
1430                                         if (!cache_check(id)) {
1431                                                 cache_inject(id, articles[i].firstChild.nodeValue);                             
1432                                                 console.log("preloaded article: " + id);
1433                                         }
1434                                 }
1435                 } }); 
1436
1437         } catch (e) {
1438                 exception_error("preloadBatchedArticles", e);
1439         }
1440 }
1441
1442 function preloadArticleUnderPointer(id) {
1443         try {
1444                 if (getInitParam("bw_limit") == "1") return;
1445
1446                 if (post_under_pointer == id && !cache_check(id)) {
1447
1448                         console.log("trying to preload article " + id);
1449
1450                         var neighbor_ids = getRelativePostIds(id, 1);
1451
1452                         /* only request uncached articles */
1453
1454                         if (preload_id_batch.indexOf(id) == -1) {
1455                                 for (var i = 0; i < neighbor_ids.length; i++) {
1456                                         if (!cache_check(neighbor_ids[i])) {
1457                                                 preload_id_batch.push(neighbor_ids[i]);
1458                                         }
1459                                 }
1460                         }
1461
1462                         if (preload_id_batch.indexOf(id) == -1)
1463                                 preload_id_batch.push(id);
1464
1465                         //console.log("preload ids batch: " + preload_id_batch.toString());
1466
1467                         window.clearTimeout(preload_timeout_id);
1468                         preload_batch_timeout_id = window.setTimeout('preloadBatchedArticles()', 1000);
1469
1470                 }
1471         } catch (e) {
1472                 exception_error("preloadArticleUnderPointer", e);
1473         }
1474 }
1475
1476 function postMouseIn(id) {
1477         try {
1478                 if (post_under_pointer != id) {
1479                         post_under_pointer = id;
1480                         if (!isCdmMode()) {
1481                                 window.setTimeout("preloadArticleUnderPointer(" + id + ")", 250);
1482                         }
1483                 }
1484
1485         } catch (e) {
1486                 exception_error("postMouseIn", e);
1487         }
1488 }
1489
1490 function postMouseOut(id) {
1491         try {
1492                 post_under_pointer = false;
1493         } catch (e) {
1494                 exception_error("postMouseOut", e);
1495         }
1496 }
1497
1498 function headlines_scroll_handler(e) {
1499         try {
1500
1501                 if (e.scrollTop + e.offsetHeight > e.scrollHeight - 100) {
1502                         if (!_infscroll_disable) {
1503                                 viewNextFeedPage();
1504                         }
1505                 }
1506
1507         } catch (e) {
1508                 exception_error("headlines_scroll_handler", e);
1509         }
1510 }
1511
1512 function catchupRelativeToArticle(below) {
1513
1514         try {
1515
1516
1517                 if (!getActiveArticleId()) {
1518                         alert(__("No article is selected."));
1519                         return;
1520                 }
1521
1522                 var visible_ids = getVisibleArticleIds();
1523
1524                 var ids_to_mark = new Array();
1525
1526                 if (!below) {
1527                         for (var i = 0; i < visible_ids.length; i++) {
1528                                 if (visible_ids[i] != getActiveArticleId()) {
1529                                         var e = $("RROW-" + visible_ids[i]);
1530
1531                                         if (e && e.hasClassName("Unread")) {
1532                                                 ids_to_mark.push(visible_ids[i]);
1533                                         }
1534                                 } else {
1535                                         break;
1536                                 }
1537                         }
1538                 } else {
1539                         for (var i = visible_ids.length-1; i >= 0; i--) {
1540                                 if (visible_ids[i] != getActiveArticleId()) {
1541                                         var e = $("RROW-" + visible_ids[i]);
1542
1543                                         if (e && e.hasClassName("Unread")) {
1544                                                 ids_to_mark.push(visible_ids[i]);
1545                                         }
1546                                 } else {
1547                                         break;
1548                                 }
1549                         }
1550                 }
1551
1552                 if (ids_to_mark.length == 0) {
1553                         alert(__("No articles found to mark"));
1554                 } else {
1555                         var msg = __("Mark %d article(s) as read?").replace("%d", ids_to_mark.length);
1556
1557                         if (getInitParam("confirm_feed_catchup") != 1 || confirm(msg)) {
1558
1559                                 for (var i = 0; i < ids_to_mark.length; i++) {
1560                                         var e = $("RROW-" + ids_to_mark[i]);
1561                                         e.removeClassName("Unread");
1562                                 }
1563
1564                                 var query = "?op=rpc&subop=catchupSelected" +
1565                                         "&cmode=0" + "&ids=" + param_escape(ids_to_mark.toString()); 
1566
1567                                 new Ajax.Request("backend.php", {
1568                                         parameters: query,
1569                                         onComplete: function(transport) { 
1570                                                 handle_rpc_json(transport); 
1571                                         } });
1572
1573                         }
1574                 }
1575
1576         } catch (e) {
1577                 exception_error("catchupRelativeToArticle", e);
1578         }
1579 }
1580
1581 function cdmExpandArticle(id) {
1582         try {
1583
1584                 hideAuxDlg();
1585
1586                 var elem = $("CICD-" + active_post_id);
1587
1588                 var upd_img_pic = $("FUPDPIC-" + id);
1589
1590                 if (upd_img_pic && (upd_img_pic.src.match("updated.png") || 
1591                                 upd_img_pic.src.match("fresh_sign.png"))) {
1592
1593                         upd_img_pic.src = "images/blank_icon.gif";
1594                 }
1595
1596                 if (id == active_post_id && Element.visible(elem))
1597                         return true;
1598
1599                 selectArticles("none");
1600
1601                 var old_offset = $("RROW-" + id).offsetTop;
1602
1603                 if (active_post_id && elem && !getInitParam("cdm_expanded")) {
1604                         Element.hide(elem);
1605                         Element.show("CEXC-" + active_post_id);
1606                 }
1607
1608                 active_post_id = id;
1609
1610                 elem = $("CICD-" + id);
1611
1612                 if (!Element.visible(elem)) {
1613                         Element.show(elem);
1614                         Element.hide("CEXC-" + id);
1615
1616                         if ($("CWRAP-" + id).innerHTML == "") {
1617
1618                                 $("FUPDPIC-" + id).src = "images/indicator_tiny.gif";
1619
1620                                 $("CWRAP-" + id).innerHTML = "<div class=\"insensitive\">" + 
1621                                         __("Loading, please wait...") + "</div>";
1622         
1623                                 var query = "?op=rpc&subop=cdmGetArticle&id=" + param_escape(id);
1624         
1625                                 //console.log(query);
1626         
1627                                 new Ajax.Request("backend.php", {
1628                                         parameters: query,
1629                                         onComplete: function(transport) { 
1630                                                 $("FUPDPIC-" + id).src = 'images/blank_icon.gif';
1631         
1632                                                 handle_rpc_json(transport);
1633
1634                                                 var reply = JSON.parse(transport.responseText);
1635
1636                                                 if (reply) {
1637                                                         var article = reply['article']['content'];
1638                                                         var recv_id = reply['article']['id'];
1639         
1640                                                         if (recv_id == id)
1641                                                                 $("CWRAP-" + id).innerHTML = article;
1642         
1643                                                 } else {
1644                                                         $("CWRAP-" + id).innerHTML = __("Unable to load article.");
1645         
1646                                                 }
1647                                 }});
1648         
1649                         }
1650                 }
1651
1652                 var new_offset = $("RROW-" + id).offsetTop;
1653
1654                 $("headlines-frame").scrollTop += (new_offset-old_offset);
1655
1656                 if ($("RROW-" + id).offsetTop != old_offset) 
1657                         $("headlines-frame").scrollTop = new_offset;
1658
1659                 toggleUnread(id, 0, true);
1660                 toggleSelected(id);
1661
1662         } catch (e) {
1663                 exception_error("cdmExpandArticle", e);
1664         }
1665
1666         return false;
1667 }
1668
1669 function fixHeadlinesOrder(ids) {
1670         try {
1671                 for (var i = 0; i < ids.length; i++) {
1672                         var e = $("RROW-" + ids[i]);
1673
1674                         if (e) {
1675                                 if (i % 2 == 0) {
1676                                         e.removeClassName("even");
1677                                         e.addClassName("odd");
1678                                 } else {
1679                                         e.removeClassName("odd");
1680                                         e.addClassName("even");
1681                                 }
1682                         }
1683                 }
1684         } catch (e) {
1685                 exception_error("fixHeadlinesOrder", e);
1686         }
1687 }
1688
1689 function getArticleUnderPointer() {
1690         return post_under_pointer;
1691 }
1692
1693 function zoomToArticle(event, id) {
1694         try {
1695                 var cached_article = cache_find(id);
1696
1697                 if (dijit.byId("ATAB-" + id))
1698                         if (!event || !event.shiftKey)
1699                                 return dijit.byId("content-tabs").selectChild(dijit.byId("ATAB-" + id));
1700
1701                 if (cached_article) {
1702                         //closeArticlePanel();
1703                 
1704                         var article_pane = new dijit.layout.ContentPane({ 
1705                                 title: __("Loading...") , content: cached_article, 
1706                                 style: 'padding : 0px;',
1707                                 id: 'ATAB-' + id,
1708                                 closable: true });
1709         
1710                         dijit.byId("content-tabs").addChild(article_pane);
1711
1712                         if (!event || !event.shiftKey)
1713                                 dijit.byId("content-tabs").selectChild(article_pane);
1714
1715                         if ($("PTITLE-" + id))
1716                                 article_pane.attr('title', $("PTITLE-" + id).innerHTML);
1717
1718                 } else {
1719
1720                         var query = "?op=rpc&subop=getArticles&ids=" + param_escape(id);
1721         
1722                         notify_progress("Loading, please wait...", true);
1723         
1724                         new Ajax.Request("backend.php", {
1725                                 parameters: query,
1726                                 onComplete: function(transport) { 
1727                                         notify('');
1728         
1729                                         if (transport.responseXML) {
1730                                                 //closeArticlePanel();
1731         
1732                                                 var article = transport.responseXML.getElementsByTagName("article")[0];
1733                                                 var content = article.firstChild.nodeValue;
1734         
1735                                                 var article_pane = new dijit.layout.ContentPane({ 
1736                                                         title: "article-" + id , content: content, 
1737                                                         style: 'padding : 0px;',
1738                                                         id: 'ATAB-' + id,
1739                                                         closable: true });
1740         
1741                                                 dijit.byId("content-tabs").addChild(article_pane);
1742
1743                                                 if (!event || !event.shiftKey)
1744                                                         dijit.byId("content-tabs").selectChild(article_pane);
1745
1746                                                 if ($("PTITLE-" + id))
1747                                                         article_pane.attr('title', $("PTITLE-" + id).innerHTML);
1748                                         }
1749         
1750                                 } });
1751                         }
1752
1753         } catch (e) {
1754                 exception_error("zoomToArticle", e);
1755         }
1756 }
1757
1758 function scrollArticle(offset) {
1759         try {
1760                 if (!isCdmMode()) {
1761                         var ci = $("content-insert");
1762                         if (ci) {
1763                                 ci.scrollTop += offset;
1764                         }
1765                 } else {
1766                         var hi = $("headlines-frame");
1767                         if (hi) {
1768                                 hi.scrollTop += offset;
1769                         }
1770
1771                 }
1772         } catch (e) {
1773                 exception_error("scrollArticle", e);
1774         }
1775 }
1776
1777 function show_labels_in_headlines(transport) {
1778         try {
1779                 var data = JSON.parse(transport.responseText);
1780
1781                 if (data) {
1782                         data['info-for-headlines'].each(function(elem) {
1783                                 var ctr = $("HLLCTR-" + elem.id);
1784
1785                                 if (ctr) ctr.innerHTML = elem.labels;
1786                         });
1787                 }
1788         } catch (e) {
1789                 exception_error("show_labels_in_headlines", e);
1790         }
1791 }
1792
1793 function toggleHeadlineActions() {
1794         try {
1795                 var e = $("headlineActionsBody");
1796                 var p = $("headlineActionsDrop");
1797
1798                 if (!Element.visible(e)) {
1799                         Element.show(e);
1800                 } else {
1801                         Element.hide(e);
1802                 }
1803
1804                 e.scrollTop = 0;
1805                 e.style.left = (p.offsetLeft + 1) + "px";
1806                 e.style.top = (p.offsetTop + p.offsetHeight + 2) + "px";
1807
1808         } catch (e) {
1809                 exception_error("toggleHeadlineActions", e);
1810         }
1811 }
1812
1813 function publishWithNote(id, def_note) {
1814         try {
1815                 if (!def_note) def_note = '';
1816
1817                 var note = prompt(__("Please enter a note for this article:"), def_note);
1818
1819                 if (note != undefined) {
1820                         togglePub(id, false, false, note);
1821                 }
1822
1823         } catch (e) {
1824                 exception_error("publishWithNote", e);
1825         }
1826 }
1827
1828 function emailArticle(id) {
1829         try {
1830                 if (!id) {
1831                         var ids = getSelectedArticleIds2();
1832
1833                         if (ids.length == 0) {
1834                                 alert(__("No articles are selected."));
1835                                 return;
1836                         }
1837
1838                         id = ids.toString();
1839                 }
1840
1841                 if (dijit.byId("emailArticleDlg"))
1842                         dijit.byId("emailArticleDlg").destroyRecursive();
1843
1844                 var query = "backend.php?op=dlg&id=emailArticle&param=" + param_escape(id);
1845
1846                 dialog = new dijit.Dialog({
1847                         id: "emailArticleDlg",
1848                         title: __("Forward article by email"),
1849                         style: "width: 600px",
1850                         execute: function() {
1851                                 if (this.validate()) {
1852
1853                                         new Ajax.Request("backend.php", {
1854                                                 parameters: dojo.objectToQuery(this.attr('value')),
1855                                                 onComplete: function(transport) { 
1856                 
1857                                                         var error = transport.responseXML.getElementsByTagName('error')[0];
1858                         
1859                                                         if (error) {
1860                                                                 alert(__('Error sending email:') + ' ' + error.firstChild.nodeValue);
1861                                                         } else {
1862                                                                 notify_info('Your message has been sent.');
1863                                                                 dialog.hide();
1864                                                         }
1865                         
1866                                         } });
1867                                 }
1868                         },
1869                         href: query});
1870
1871                 var tmph = dojo.connect(dialog, 'onLoad', function() {
1872                 dojo.disconnect(tmph);
1873
1874                    new Ajax.Autocompleter('emailArticleDlg_destination', 'emailArticleDlg_dst_choices',
1875                            "backend.php?op=rpc&subop=completeEmails",
1876                            { tokens: '', paramName: "search" });
1877                 });
1878
1879                 dialog.show();
1880
1881                 /* displayDlg('emailArticle', id, 
1882                    function () {                                
1883                                 document.forms['article_email_form'].destination.focus();
1884
1885                            new Ajax.Autocompleter('destination', 'destination_choices',
1886                                    "backend.php?op=rpc&subop=completeEmails",
1887                                    { tokens: '', paramName: "search" });
1888
1889                         }); */
1890
1891         } catch (e) {
1892                 exception_error("emailArticle", e);
1893         }
1894 }
1895
1896 function dismissArticle(id) {
1897         try {
1898                 var elem = $("RROW-" + id);
1899
1900                 toggleUnread(id, 0, true);
1901
1902                 new Effect.Fade(elem, {duration : 0.5});
1903
1904                 active_post_id = false;
1905
1906         } catch (e) {
1907                 exception_error("dismissArticle", e);
1908         }
1909 }
1910
1911 function dismissSelectedArticles() {
1912         try {
1913
1914                 var ids = getVisibleArticleIds();
1915                 var tmp = [];
1916                 var sel = [];
1917
1918                 for (var i = 0; i < ids.length; i++) {
1919                         var elem = $("RROW-" + ids[i]);
1920
1921                         if (elem.className && elem.hasClassName("Selected") && 
1922                                         ids[i] != active_post_id) {
1923                                 new Effect.Fade(elem, {duration : 0.5});
1924                                 sel.push(ids[i]);
1925                         } else {
1926                                 tmp.push(ids[i]);
1927                         }
1928                 }
1929
1930                 if (sel.length > 0)
1931                         selectionToggleUnread(false);
1932
1933                 fixHeadlinesOrder(tmp);
1934
1935         } catch (e) {
1936                 exception_error("dismissSelectedArticles", e);
1937         }
1938 }
1939
1940 function dismissReadArticles() {
1941         try {
1942
1943                 var ids = getVisibleArticleIds();
1944                 var tmp = [];
1945
1946                 for (var i = 0; i < ids.length; i++) {
1947                         var elem = $("RROW-" + ids[i]);
1948
1949                         if (elem.className && !elem.hasClassName("Unread") && 
1950                                         !elem.hasClassName("Selected")) {
1951                         
1952                                 new Effect.Fade(elem, {duration : 0.5});
1953                         } else {
1954                                 tmp.push(ids[i]);
1955                         }
1956                 }
1957
1958                 fixHeadlinesOrder(tmp);
1959
1960         } catch (e) {
1961                 exception_error("dismissSelectedArticles", e);
1962         }
1963 }
1964
1965 function getVisibleArticleIds() {
1966         var ids = [];
1967
1968         try {
1969                 
1970                 getLoadedArticleIds().each(function(id) {
1971                         var elem = $("RROW-" + id);
1972                         if (elem && Element.visible(elem))
1973                                 ids.push(id);
1974                         });
1975
1976         } catch (e) {
1977                 exception_error("getVisibleArticleIds", e);
1978         }
1979
1980         return ids;
1981 }
1982
1983 function cdmClicked(event, id) {
1984         try {
1985                 var shift_key = event.shiftKey;
1986
1987                 hideAuxDlg();
1988
1989                 if (!event.ctrlKey) {
1990
1991                         if (!getInitParam("cdm_expanded")) {
1992                                 return cdmExpandArticle(id);
1993                         } else {
1994
1995                                 selectArticles("none");
1996                                 toggleSelected(id);
1997         
1998                                 var elem = $("RROW-" + id);
1999         
2000                                 if (elem)
2001                                         elem.removeClassName("Unread");
2002         
2003                                 var upd_img_pic = $("FUPDPIC-" + id);
2004         
2005                                 if (upd_img_pic && (upd_img_pic.src.match("updated.png") || 
2006                                                 upd_img_pic.src.match("fresh_sign.png"))) {
2007         
2008                                         upd_img_pic.src = "images/blank_icon.gif";
2009                                 }
2010         
2011                                 active_post_id = id;
2012         
2013                                 var query = "?op=rpc&subop=catchupSelected" +
2014                                         "&cmode=0&ids=" + param_escape(id);
2015         
2016                                 new Ajax.Request("backend.php", {
2017                                         parameters: query,
2018                                         onComplete: function(transport) { 
2019                                                 handle_json_reply(transport); 
2020                                         } });
2021                         }
2022
2023                 } else {
2024                         toggleSelected(id, true);
2025                         toggleUnread(id, 0, false);
2026                         zoomToArticle(event, id);
2027                 }
2028
2029         } catch (e) {
2030                 exception_error("cdmClicked");
2031         }
2032
2033         return false;
2034 }
2035
2036 function postClicked(event, id) {
2037         try {
2038
2039                 if (!event.ctrlKey) {
2040                         return true;
2041                 } else {
2042                         postOpenInNewTab(event, id);
2043                         return false;
2044                 }
2045
2046         } catch (e) {
2047                 exception_error("postClicked");
2048         }
2049 }
2050
2051 function hlOpenInNewTab(event, id) {
2052         toggleUnread(id, 0, false);
2053         zoomToArticle(event, id);
2054 }
2055
2056 function postOpenInNewTab(event, id) {
2057         closeArticlePanel(id);
2058         zoomToArticle(event, id);
2059 }
2060
2061 function hlClicked(event, id) {
2062         try {
2063                 if (event.altKey) {
2064                         openArticleInNewWindow(id);
2065                 } else if (!event.ctrlKey) {
2066                         view(id);
2067                         return true;
2068                 } else {
2069                         toggleSelected(id);
2070                         toggleUnread(id, 0, false);
2071                         zoomToArticle(event, id);
2072                         return false;
2073                 }
2074
2075         } catch (e) {
2076                 exception_error("hlClicked");
2077         }
2078 }
2079
2080 function getFirstVisibleHeadlineId() {
2081         var rows = getVisibleArticleIds();
2082         return rows[0];
2083         
2084 }
2085
2086 function getLastVisibleHeadlineId() {
2087         var rows = getVisibleArticleIds();
2088         return rows[rows.length-1];
2089 }
2090
2091 function openArticleInNewWindow(id) {
2092         toggleUnread(id, 0, false);
2093         window.open("backend.php?op=la&id=" + id);
2094 }
2095
2096 function isCdmMode() {
2097         return getInitParam("combined_display_mode");
2098 }
2099
2100 function markHeadline(id) {
2101         var row = $("RROW-" + id);
2102         if (row) {
2103                 var check = $("RCHK-" + id);
2104
2105                 if (check) {
2106                         check.checked = true;
2107                 }
2108
2109                 row.addClassName("Selected");
2110         }
2111 }
2112
2113 function getRelativePostIds(id, limit) {
2114
2115         var tmp = [];
2116
2117         try {
2118
2119                 if (!limit) limit = 3;
2120         
2121                 var ids = getVisibleArticleIds();
2122         
2123                 for (var i = 0; i < ids.length; i++) {
2124                         if (ids[i] == id) {
2125                                 for (var k = 1; k <= limit; k++) {
2126                                         if (i > k-1) tmp.push(ids[i-k]);
2127                                         if (i < ids.length-k) tmp.push(ids[i+k]);
2128                                 }
2129                                 break;
2130                         }
2131                 }
2132
2133         } catch (e) {
2134                 exception_error("getRelativePostIds", e);
2135         }
2136
2137         return tmp;
2138 }
2139
2140 function correctHeadlinesOffset(id) {
2141         
2142         try {
2143
2144                 var container = $("headlines-frame");
2145                 var row = $("RROW-" + id);
2146         
2147                 var viewport = container.offsetHeight;
2148         
2149                 var rel_offset_top = row.offsetTop - container.scrollTop;
2150                 var rel_offset_bottom = row.offsetTop + row.offsetHeight - container.scrollTop;
2151         
2152                 //console.log("Rtop: " + rel_offset_top + " Rbtm: " + rel_offset_bottom);
2153                 //console.log("Vport: " + viewport);
2154
2155                 if (rel_offset_top <= 0 || rel_offset_top > viewport) {
2156                         container.scrollTop = row.offsetTop;
2157                 } else if (rel_offset_bottom > viewport) {
2158
2159                         /* doesn't properly work with Opera in some cases because
2160                                 Opera fucks up element scrolling */
2161
2162                         container.scrollTop = row.offsetTop + row.offsetHeight - viewport;              
2163                 } 
2164
2165         } catch (e) {
2166                 exception_error("correctHeadlinesOffset", e);
2167         }
2168
2169 }
2170
2171 function headlineActionsChange(elem) {
2172         try {
2173                 eval(elem.value);
2174                 elem.attr('value', 'false');
2175         } catch (e) {
2176                 exception_error("headlineActionsChange", e);
2177         }
2178 }
2179
2180 function closeArticlePanel() {
2181
2182         var tabs = dijit.byId("content-tabs");
2183         var child = tabs.selectedChildWidget;
2184
2185         if (child && tabs.getIndexOfChild(child) > 0) {
2186                 tabs.removeChild(child);
2187                 child.destroy();
2188         } else {
2189                 if (dijit.byId("content-insert"))
2190                         dijit.byId("headlines-wrap-inner").removeChild(
2191                                 dijit.byId("content-insert"));
2192         }
2193 }
2194
2195 function initHeadlinesMenu() {
2196         try {
2197                 if (dijit.byId("headlinesMenu"))
2198                         dijit.byId("headlinesMenu").destroyRecursive();
2199
2200                 var ids = [];
2201
2202                 if (!isCdmMode()) {
2203                         nodes = $$("#headlines-frame > div[id*=RROW]");
2204                 } else {
2205                         nodes = $$("#headlines-frame span[id*=RTITLE]");
2206                 }
2207
2208                 nodes.each(function(node) {
2209                         ids.push(node.id);
2210                 });
2211
2212                 var menu = new dijit.Menu({
2213                         id: "headlinesMenu",
2214                         targetNodeIds: ids,
2215                 });
2216
2217                 var tmph = dojo.connect(menu, '_openMyself', function (event) {
2218                         var callerNode = event.target, match = null, tries = 0;
2219
2220                         while (match == null && callerNode && tries <= 3) {
2221                                 match = callerNode.id.match("^[A-Z]+[-]([0-9]+)$");
2222                                 callerNode = callerNode.parentNode;
2223                                 ++tries;
2224                         }
2225
2226                         if (match) this.callerRowId = parseInt(match[1]);
2227
2228                 });
2229
2230                 if (!isCdmMode())
2231                         menu.addChild(new dijit.MenuItem({
2232                                 label: __("View article"),
2233                                 onClick: function(event) {
2234                                         view(this.getParent().callerRowId);
2235                                 }}));
2236
2237                 menu.addChild(new dijit.MenuItem({
2238                         label: __("View in a new tab"),
2239                         onClick: function(event) {
2240                                 hlOpenInNewTab(event, this.getParent().callerRowId);
2241                                 }}));
2242
2243                 menu.addChild(new dijit.MenuSeparator());
2244
2245                 menu.addChild(new dijit.MenuItem({
2246                         label: __("Open original article"),
2247                         onClick: function(event) {
2248                                 openArticleInNewWindow(this.getParent().callerRowId);
2249                         }}));
2250
2251                 var labels = dijit.byId("feedTree").model.getItemsInCategory(-2);
2252
2253                 if (labels) {
2254
2255                         menu.addChild(new dijit.MenuSeparator());
2256
2257                         var labelsMenu = new dijit.Menu({ownerMenu: menu});
2258
2259                         labels.each(function(label) {
2260                                 var id = label.id[0];
2261                                 var bare_id = id.substr(id.indexOf(":")+1);
2262                                 var name = label.name[0];
2263
2264                                 bare_id = -11-bare_id;
2265
2266                                 labelsMenu.addChild(new dijit.MenuItem({
2267                                         label: name,
2268                                         labelId: bare_id,
2269                                         onClick: function(event) {
2270                                                 //console.log(this.labelId);
2271                                                 //console.log(this.getParent().ownerMenu.callerRowId);
2272                                                 selectionAssignLabel(this.labelId, 
2273                                                         [this.getParent().ownerMenu.callerRowId]);
2274                                 }}));
2275                         });
2276
2277                         menu.addChild(new dijit.PopupMenuItem({
2278                                 label: __("Labels"),
2279                                 popup: labelsMenu,
2280                         }));
2281                 }
2282
2283                 menu.startup();
2284
2285         } catch (e) {
2286                 exception_error("initHeadlinesMenu", e);
2287         }
2288 }
2289
2290 function tweetArticle(id) {
2291         try {
2292                 var query = "?op=rpc&subop=getTweetInfo&id=" + param_escape(id);
2293
2294                 console.log(query);
2295
2296                 var d = new Date();
2297       var ts = d.getTime();
2298
2299                 var w = window.open('backend.php?op=loading', 'ttrss_tweet',
2300                         "status=0,toolbar=0,location=0,width=500,height=400,scrollbars=1,menubar=0");
2301
2302                 new Ajax.Request("backend.php", {
2303                         parameters: query, 
2304                         onComplete: function(transport) {
2305                                 var ti = JSON.parse(transport.responseText);
2306
2307                                 var share_url = "http://twitter.com/share?_=" + ts +
2308                                         "&text=" + param_escape(ti.title) + 
2309                                         "&url=" + param_escape(ti.link);
2310                                                 
2311                                 w.location.href = share_url;
2312
2313                         } });
2314
2315
2316         } catch (e) {
2317                 exception_error("tweetArticle", e);
2318         }
2319 }