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