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