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