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