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