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