]> git.wh0rd.org Git - tt-rss.git/blob - js/viewfeed.js
Merge branch 'new_tab_mac_fix' into 'master'
[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 = getVisibleArticleIds();
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_afh(effect) {
697         try {
698
699                 var elem = effect.element;
700                 elem.style.backgroundColor = "";
701
702         } catch (e) {
703                 exception_error("toggleUnread_afh", e);
704         }
705 }
706
707 function toggleUnread(id, cmode, effect) {
708         try {
709
710                 var row = $("RROW-" + id);
711                 if (row) {
712                         var tmpClassName = row.className;
713
714                         if (cmode == undefined || cmode == 2) {
715                                 if (row.hasClassName("Unread")) {
716                                         row.removeClassName("Unread");
717
718                                 } else {
719                                         row.addClassName("Unread");
720                                 }
721
722                         } else if (cmode == 0) {
723
724                                 row.removeClassName("Unread");
725
726                         } else if (cmode == 1) {
727                                 row.addClassName("Unread");
728                         }
729
730                         if (cmode == undefined) cmode = 2;
731
732                         var query = "?op=rpc&method=catchupSelected" +
733                                 "&cmode=" + param_escape(cmode) + "&ids=" + param_escape(id);
734
735 //                      notify_progress("Loading, please wait...");
736
737                         if (tmpClassName != row.className) {
738                                 new Ajax.Request("backend.php", {
739                                         parameters: query,
740                                         onComplete: function (transport) {
741                                                 handle_rpc_json(transport);
742                                         }
743                                 });
744                         }
745
746                 }
747
748         } catch (e) {
749                 exception_error("toggleUnread", e);
750         }
751 }
752
753 function selectionRemoveLabel(id, ids) {
754         try {
755
756                 if (!ids) ids = getSelectedArticleIds2();
757
758                 if (ids.length == 0) {
759                         alert(__("No articles are selected."));
760                         return;
761                 }
762
763                 var query = "?op=article&method=removeFromLabel&ids=" +
764                         param_escape(ids.toString()) + "&lid=" + param_escape(id);
765
766                 console.log(query);
767
768                 new Ajax.Request("backend.php", {
769                         parameters: query,
770                         onComplete: function(transport) {
771                                 handle_rpc_json(transport);
772                                 show_labels_in_headlines(transport);
773                         } });
774
775         } catch (e) {
776                 exception_error("selectionAssignLabel", e);
777
778         }
779 }
780
781 function selectionAssignLabel(id, ids) {
782         try {
783
784                 if (!ids) ids = getSelectedArticleIds2();
785
786                 if (ids.length == 0) {
787                         alert(__("No articles are selected."));
788                         return;
789                 }
790
791                 var query = "?op=article&method=assignToLabel&ids=" +
792                         param_escape(ids.toString()) + "&lid=" + param_escape(id);
793
794                 console.log(query);
795
796                 new Ajax.Request("backend.php", {
797                         parameters: query,
798                         onComplete: function(transport) {
799                                 handle_rpc_json(transport);
800                                 show_labels_in_headlines(transport);
801                         } });
802
803         } catch (e) {
804                 exception_error("selectionAssignLabel", e);
805
806         }
807 }
808
809 function selectionToggleUnread(set_state, callback, no_error, ids) {
810         try {
811                 var rows = ids ? ids : getSelectedArticleIds2();
812
813                 if (rows.length == 0 && !no_error) {
814                         alert(__("No articles are selected."));
815                         return;
816                 }
817
818                 for (var i = 0; i < rows.length; i++) {
819                         var row = $("RROW-" + rows[i]);
820                         if (row) {
821                                 if (set_state == undefined) {
822                                         if (row.hasClassName("Unread")) {
823                                                 row.removeClassName("Unread");
824                                         } else {
825                                                 row.addClassName("Unread");
826                                         }
827                                 }
828
829                                 if (set_state == false) {
830                                         row.removeClassName("Unread");
831                                 }
832
833                                 if (set_state == true) {
834                                         row.addClassName("Unread");
835                                 }
836                         }
837                 }
838
839                 updateFloatingTitle(true);
840
841                 if (rows.length > 0) {
842
843                         var cmode = "";
844
845                         if (set_state == undefined) {
846                                 cmode = "2";
847                         } else if (set_state == true) {
848                                 cmode = "1";
849                         } else if (set_state == false) {
850                                 cmode = "0";
851                         }
852
853                         var query = "?op=rpc&method=catchupSelected" +
854                                 "&cmode=" + cmode + "&ids=" + param_escape(rows.toString());
855
856                         notify_progress("Loading, please wait...");
857
858                         new Ajax.Request("backend.php", {
859                                 parameters: query,
860                                 onComplete: function(transport) {
861                                         handle_rpc_json(transport);
862                                         if (callback) callback(transport);
863                                 } });
864
865                 }
866
867         } catch (e) {
868                 exception_error("selectionToggleUnread", e);
869         }
870 }
871
872 // sel_state ignored
873 function selectionToggleMarked(sel_state, callback, no_error, ids) {
874         try {
875
876                 var rows = ids ? ids : getSelectedArticleIds2();
877
878                 if (rows.length == 0 && !no_error) {
879                         alert(__("No articles are selected."));
880                         return;
881                 }
882
883                 for (var i = 0; i < rows.length; i++) {
884                         toggleMark(rows[i], true, true);
885                 }
886
887                 if (rows.length > 0) {
888
889                         var query = "?op=rpc&method=markSelected&ids=" +
890                                 param_escape(rows.toString()) + "&cmode=2";
891
892                         new Ajax.Request("backend.php", {
893                                 parameters: query,
894                                 onComplete: function(transport) {
895                                         handle_rpc_json(transport);
896                                         if (callback) callback(transport);
897                                 } });
898
899                 }
900
901         } catch (e) {
902                 exception_error("selectionToggleMarked", e);
903         }
904 }
905
906 // sel_state ignored
907 function selectionTogglePublished(sel_state, callback, no_error, ids) {
908         try {
909
910                 var rows = ids ? ids : getSelectedArticleIds2();
911
912                 if (rows.length == 0 && !no_error) {
913                         alert(__("No articles are selected."));
914                         return;
915                 }
916
917                 for (var i = 0; i < rows.length; i++) {
918                         togglePub(rows[i], true, true);
919                 }
920
921                 if (rows.length > 0) {
922
923                         var query = "?op=rpc&method=publishSelected&ids=" +
924                                 param_escape(rows.toString()) + "&cmode=2";
925
926                         new Ajax.Request("backend.php", {
927                                 parameters: query,
928                                 onComplete: function(transport) {
929                                         handle_rpc_json(transport);
930                                 } });
931
932                 }
933
934         } catch (e) {
935                 exception_error("selectionToggleMarked", e);
936         }
937 }
938
939 function getSelectedArticleIds2() {
940
941         var rv = [];
942
943         $$("#headlines-frame > div[id*=RROW][class*=Selected]").each(
944                 function(child) {
945                         rv.push(child.id.replace("RROW-", ""));
946                 });
947
948         return rv;
949 }
950
951 function getLoadedArticleIds() {
952         var rv = [];
953
954         var children = $$("#headlines-frame > div[id*=RROW-]");
955
956         children.each(function(child) {
957                         rv.push(child.id.replace("RROW-", ""));
958                 });
959
960         return rv;
961
962 }
963
964 // mode = all,none,unread,invert,marked,published
965 function selectArticles(mode, query) {
966         try {
967
968                 if (!query) query = "#headlines-frame > div[id*=RROW]";
969
970                 var children = $$(query);
971
972                 children.each(function(child) {
973                         var id = child.id.replace("RROW-", "");
974
975                         var cb = dijit.getEnclosingWidget(
976                                         child.getElementsByClassName("rchk")[0]);
977
978                         if (mode == "all") {
979                                 child.addClassName("Selected");
980                                 if (cb) cb.attr("checked", true);
981                         } else if (mode == "unread") {
982                                 if (child.hasClassName("Unread")) {
983                                         child.addClassName("Selected");
984                                         if (cb) cb.attr("checked", true);
985                                 } else {
986                                         child.removeClassName("Selected");
987                                         if (cb) cb.attr("checked", false);
988                                 }
989                         } else if (mode == "marked") {
990                                 if (child.hasClassName("marked")) {
991                                         child.addClassName("Selected");
992                                         if (cb) cb.attr("checked", true);
993                                 } else {
994                                         child.removeClassName("Selected");
995                                         if (cb) cb.attr("checked", false);
996                                 }
997                         } else if (mode == "published") {
998                                 if (child.hasClassName("published")) {
999                                         child.addClassName("Selected");
1000                                         if (cb) cb.attr("checked", true);
1001                                 } else {
1002                                         child.removeClassName("Selected");
1003                                         if (cb) cb.attr("checked", false);
1004                                 }
1005
1006                         } else if (mode == "invert") {
1007                                 if (child.hasClassName("Selected")) {
1008                                         child.removeClassName("Selected");
1009                                         if (cb) cb.attr("checked", false);
1010                                 } else {
1011                                         child.addClassName("Selected");
1012                                         if (cb) cb.attr("checked", true);
1013                                 }
1014
1015                         } else {
1016                                 child.removeClassName("Selected");
1017                                 if (cb) cb.attr("checked", false);
1018                         }
1019                 });
1020
1021                 updateSelectedPrompt();
1022
1023         } catch (e) {
1024                 exception_error("selectArticles", e);
1025         }
1026 }
1027
1028 function deleteSelection() {
1029
1030         try {
1031
1032                 var rows = getSelectedArticleIds2();
1033
1034                 if (rows.length == 0) {
1035                         alert(__("No articles are selected."));
1036                         return;
1037                 }
1038
1039                 var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
1040                 var str;
1041
1042                 if (getActiveFeedId() != 0) {
1043                         str = ngettext("Delete %d selected article in %s?", "Delete %d selected articles in %s?" , rows.length);
1044                 } else {
1045                         str = ngettext("Delete %d selected article?", "Delete %d selected articles?", rows.length);
1046                 }
1047
1048                 str = str.replace("%d", rows.length);
1049                 str = str.replace("%s", fn);
1050
1051                 if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1052                         return;
1053                 }
1054
1055                 query = "?op=rpc&method=delete&ids=" + param_escape(rows);
1056
1057                 console.log(query);
1058
1059                 new Ajax.Request("backend.php", {
1060                         parameters: query,
1061                         onComplete: function(transport) {
1062                                         handle_rpc_json(transport);
1063                                         viewCurrentFeed();
1064                                 } });
1065
1066         } catch (e) {
1067                 exception_error("deleteSelection", e);
1068         }
1069 }
1070
1071 function archiveSelection() {
1072
1073         try {
1074
1075                 var rows = getSelectedArticleIds2();
1076
1077                 if (rows.length == 0) {
1078                         alert(__("No articles are selected."));
1079                         return;
1080                 }
1081
1082                 var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
1083                 var str;
1084                 var op;
1085
1086                 if (getActiveFeedId() != 0) {
1087                         str = ngettext("Archive %d selected article in %s?", "Archive %d selected articles in %s?", rows.length);
1088                         op = "archive";
1089                 } else {
1090                         str = ngettext("Move %d archived article back?", "Move %d archived articles back?", rows.length);
1091
1092                         str += " " + __("Please note that unstarred articles might get purged on next feed update.");
1093
1094                         op = "unarchive";
1095                 }
1096
1097                 str = str.replace("%d", rows.length);
1098                 str = str.replace("%s", fn);
1099
1100                 if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1101                         return;
1102                 }
1103
1104                 query = "?op=rpc&method="+op+"&ids=" + param_escape(rows);
1105
1106                 console.log(query);
1107
1108                 for (var i = 0; i < rows.length; i++) {
1109                         cache_delete("article:" + rows[i]);
1110                 }
1111
1112                 new Ajax.Request("backend.php", {
1113                         parameters: query,
1114                         onComplete: function(transport) {
1115                                         handle_rpc_json(transport);
1116                                         viewCurrentFeed();
1117                                 } });
1118
1119         } catch (e) {
1120                 exception_error("archiveSelection", e);
1121         }
1122 }
1123
1124 function catchupSelection() {
1125
1126         try {
1127
1128                 var rows = getSelectedArticleIds2();
1129
1130                 if (rows.length == 0) {
1131                         alert(__("No articles are selected."));
1132                         return;
1133                 }
1134
1135                 var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
1136
1137                 var str = ngettext("Mark %d selected article in %s as read?", "Mark %d selected articles in %s as read?", rows.length);
1138
1139                 str = str.replace("%d", rows.length);
1140                 str = str.replace("%s", fn);
1141
1142                 if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
1143                         return;
1144                 }
1145
1146                 selectionToggleUnread(false, 'viewCurrentFeed()', true);
1147
1148         } catch (e) {
1149                 exception_error("catchupSelection", e);
1150         }
1151 }
1152
1153 function editArticleTags(id) {
1154                 var query = "backend.php?op=article&method=editArticleTags&param=" + param_escape(id);
1155
1156                 if (dijit.byId("editTagsDlg"))
1157                         dijit.byId("editTagsDlg").destroyRecursive();
1158
1159                 dialog = new dijit.Dialog({
1160                         id: "editTagsDlg",
1161                         title: __("Edit article Tags"),
1162                         style: "width: 600px",
1163                         execute: function() {
1164                                 if (this.validate()) {
1165                                         var query = dojo.objectToQuery(this.attr('value'));
1166
1167                                         notify_progress("Saving article tags...", true);
1168
1169                                         new Ajax.Request("backend.php", {
1170                                         parameters: query,
1171                                         onComplete: function(transport) {
1172                                                 try {
1173                                                         notify('');
1174                                                         dialog.hide();
1175
1176                                                         var data = JSON.parse(transport.responseText);
1177
1178                                                         if (data) {
1179                                                                 var id = data.id;
1180
1181                                                                 console.log(id);
1182
1183                                                                 var tags = $("ATSTR-" + id);
1184                                                                 var tooltip = dijit.byId("ATSTRTIP-" + id);
1185
1186                                                                 if (tags) tags.innerHTML = data.content;
1187                                                                 if (tooltip) tooltip.attr('label', data.content_full);
1188                                                         }
1189                                                 } catch (e) {
1190                                                         exception_error("editArticleTags/inner", e);
1191                                                 }
1192
1193                                         }});
1194                                 }
1195                         },
1196                         href: query
1197                 });
1198
1199                 var tmph = dojo.connect(dialog, 'onLoad', function() {
1200                 dojo.disconnect(tmph);
1201
1202                         new Ajax.Autocompleter('tags_str', 'tags_choices',
1203                            "backend.php?op=article&method=completeTags",
1204                            { tokens: ',', paramName: "search" });
1205                 });
1206
1207                 dialog.show();
1208
1209 }
1210
1211 function cdmScrollToArticleId(id, force) {
1212         try {
1213                 var ctr = $("headlines-frame");
1214                 var e = $("RROW-" + id);
1215
1216                 if (!e || !ctr) return;
1217
1218                 if (force || e.offsetTop+e.offsetHeight > (ctr.scrollTop+ctr.offsetHeight) ||
1219                                 e.offsetTop < ctr.scrollTop) {
1220
1221                         // expanded cdm has a 4px margin now
1222                         ctr.scrollTop = parseInt(e.offsetTop) - 4;
1223                 }
1224
1225         } catch (e) {
1226                 exception_error("cdmScrollToArticleId", e);
1227         }
1228 }
1229
1230 function setActiveArticleId(id) {
1231         console.log("setActiveArticleId:" + id);
1232
1233         _active_article_id = id;
1234         PluginHost.run(PluginHost.HOOK_ARTICLE_SET_ACTIVE, _active_article_id);
1235 }
1236
1237 function getActiveArticleId() {
1238         return _active_article_id;
1239 }
1240
1241 function postMouseIn(e, id) {
1242         post_under_pointer = id;
1243 }
1244
1245 function postMouseOut(id) {
1246         post_under_pointer = false;
1247 }
1248
1249 function unpackVisibleHeadlines() {
1250         try {
1251                 if (!isCdmMode() || !getInitParam("cdm_expanded")) return;
1252
1253                 $$("#headlines-frame > div[id*=RROW]").each(
1254                         function(child) {
1255                                 if (child.offsetTop <= $("headlines-frame").scrollTop +
1256                                         $("headlines-frame").offsetHeight) {
1257
1258                                         var cencw = $("CENCW-" + child.id.replace("RROW-", ""));
1259
1260                                         if (cencw) {
1261                                                 cencw.innerHTML = htmlspecialchars_decode(cencw.innerHTML);
1262                                                 cencw.setAttribute('id', '');
1263
1264                                                 PluginHost.run(PluginHost.HOOK_ARTICLE_RENDERED_CDM, child);
1265
1266                                                 Element.show(cencw);
1267                                         }
1268                                 }
1269                         }
1270                 );
1271
1272         } catch (e) {
1273                 exception_error("unpackVisibleHeadlines", e);
1274         }
1275 }
1276
1277 function headlines_scroll_handler(e) {
1278         try {
1279                 var hsp = $("headlines-spacer");
1280
1281                 unpackVisibleHeadlines();
1282
1283                 // set topmost child in the buffer as active
1284                 if (isCdmMode() && getInitParam("cdm_auto_catchup") == 1 &&
1285                                 getSelectedArticleIds2().length <= 1 &&
1286                                 getInitParam("cdm_expanded")) {
1287                         var rows = $$("#headlines-frame > div[id*=RROW]");
1288
1289                         for (var i = 0; i < rows.length; i++) {
1290                                 var child = rows[i];
1291
1292                                 if ($("headlines-frame").scrollTop <= child.offsetTop &&
1293                                         child.offsetTop - $("headlines-frame").scrollTop < 100 &&
1294                                         child.id.replace("RROW-", "") != _active_article_id) {
1295
1296                                         if (_active_article_id) {
1297                                                 var row = $("RROW-" + _active_article_id);
1298                                                 if (row) row.removeClassName("active");
1299                                         }
1300
1301                                         _active_article_id = child.id.replace("RROW-", "");
1302                                         showArticleInHeadlines(_active_article_id, true);
1303                                         updateSelectedPrompt();
1304                                         break;
1305                                 }
1306                         }
1307                 }
1308
1309                 if (!_infscroll_disable) {
1310                         if (hsp && hsp.offsetTop - 250 <= e.scrollTop + e.offsetHeight) {
1311
1312                                 hsp.innerHTML = "<span class='loading'><img src='images/indicator_tiny.gif'> " +
1313                                         __("Loading, please wait...") + "</span>";
1314
1315                                 loadMoreHeadlines();
1316                                 return;
1317
1318                         }
1319                 }
1320
1321                 if (isCdmMode()) {
1322                         updateFloatingTitle();
1323                 }
1324
1325                 catchupCurrentBatchIfNeeded();
1326
1327                 if (getInitParam("cdm_auto_catchup") == 1) {
1328
1329                         // let's get DOM some time to settle down
1330                         var ts = new Date().getTime();
1331                         if (ts - _last_headlines_update < 100) return;
1332
1333                         $$("#headlines-frame > div[id*=RROW][class*=Unread]").each(
1334                                 function(child) {
1335                                         if (child.hasClassName("Unread") && $("headlines-frame").scrollTop >
1336                                                         (child.offsetTop + child.offsetHeight/2)) {
1337
1338                                                 var id = child.id.replace("RROW-", "");
1339
1340                                                 if (catchup_id_batch.indexOf(id) == -1)
1341                                                         catchup_id_batch.push(id);
1342
1343                                                 //console.log("auto_catchup_batch: " + catchup_id_batch.toString());
1344                                         }
1345
1346                                 });
1347
1348                         if (_infscroll_disable) {
1349                                 var child = $$("#headlines-frame div[id*=RROW]").last();
1350
1351                                 if (child && $("headlines-frame").scrollTop >
1352                                                 (child.offsetTop + child.offsetHeight - 50)) {
1353
1354                                         console.log("we seem to be at an end");
1355
1356                                         if (getInitParam("on_catchup_show_next_feed") == "1") {
1357                                                 openNextUnreadFeed();
1358                                         }
1359                                 }
1360                         }
1361                 }
1362
1363         } catch (e) {
1364                 console.warn("headlines_scroll_handler: " + e);
1365         }
1366 }
1367
1368 function openNextUnreadFeed() {
1369         try {
1370                 var is_cat = activeFeedIsCat();
1371                 var nuf = getNextUnreadFeed(getActiveFeedId(), is_cat);
1372                 if (nuf) viewfeed({feed: nuf, is_cat: is_cat});
1373         } catch (e) {
1374                 exception_error("openNextUnreadFeed", e);
1375         }
1376 }
1377
1378 function catchupBatchedArticles() {
1379         try {
1380                 if (catchup_id_batch.length > 0 && !_infscroll_request_sent && !_catchup_request_sent) {
1381
1382                         console.log("catchupBatchedArticles: working");
1383
1384                         // make a copy of the array
1385                         var batch = catchup_id_batch.slice();
1386                         var query = "?op=rpc&method=catchupSelected" +
1387                                 "&cmode=0&ids=" + param_escape(batch.toString());
1388
1389                         console.log(query);
1390
1391                         _catchup_request_sent = true;
1392
1393                         new Ajax.Request("backend.php", {
1394                                 parameters: query,
1395                                 onComplete: function(transport) {
1396                                         handle_rpc_json(transport);
1397
1398                                         _catchup_request_sent = false;
1399
1400                                         reply = JSON.parse(transport.responseText);
1401                                         var batch = reply.ids;
1402
1403                                         batch.each(function(id) {
1404                                                 console.log(id);
1405                                                 var elem = $("RROW-" + id);
1406                                                 if (elem) elem.removeClassName("Unread");
1407                                                 catchup_id_batch.remove(id);
1408                                         });
1409
1410                                         updateFloatingTitle(true);
1411
1412                                 } });
1413                 }
1414
1415         } catch (e) {
1416                 exception_error("catchupBatchedArticles", e);
1417         }
1418 }
1419
1420 function catchupRelativeToArticle(below, id) {
1421
1422         try {
1423
1424                 if (!id) id = getActiveArticleId();
1425
1426                 if (!id) {
1427                         alert(__("No article is selected."));
1428                         return;
1429                 }
1430
1431                 var visible_ids = getVisibleArticleIds();
1432
1433                 var ids_to_mark = new Array();
1434
1435                 if (!below) {
1436                         for (var i = 0; i < visible_ids.length; i++) {
1437                                 if (visible_ids[i] != id) {
1438                                         var e = $("RROW-" + visible_ids[i]);
1439
1440                                         if (e && e.hasClassName("Unread")) {
1441                                                 ids_to_mark.push(visible_ids[i]);
1442                                         }
1443                                 } else {
1444                                         break;
1445                                 }
1446                         }
1447                 } else {
1448                         for (var i = visible_ids.length-1; i >= 0; i--) {
1449                                 if (visible_ids[i] != id) {
1450                                         var e = $("RROW-" + visible_ids[i]);
1451
1452                                         if (e && e.hasClassName("Unread")) {
1453                                                 ids_to_mark.push(visible_ids[i]);
1454                                         }
1455                                 } else {
1456                                         break;
1457                                 }
1458                         }
1459                 }
1460
1461                 if (ids_to_mark.length == 0) {
1462                         alert(__("No articles found to mark"));
1463                 } else {
1464                         var msg = ngettext("Mark %d article as read?", "Mark %d articles as read?", ids_to_mark.length).replace("%d", ids_to_mark.length);
1465
1466                         if (getInitParam("confirm_feed_catchup") != 1 || confirm(msg)) {
1467
1468                                 for (var i = 0; i < ids_to_mark.length; i++) {
1469                                         var e = $("RROW-" + ids_to_mark[i]);
1470                                         e.removeClassName("Unread");
1471                                 }
1472
1473                                 var query = "?op=rpc&method=catchupSelected" +
1474                                         "&cmode=0" + "&ids=" + param_escape(ids_to_mark.toString());
1475
1476                                 new Ajax.Request("backend.php", {
1477                                         parameters: query,
1478                                         onComplete: function(transport) {
1479                                                 handle_rpc_json(transport);
1480                                         } });
1481
1482                         }
1483                 }
1484
1485         } catch (e) {
1486                 exception_error("catchupRelativeToArticle", e);
1487         }
1488 }
1489
1490 function cdmCollapseArticle(event, id, unmark) {
1491         try {
1492                 if (unmark == undefined) unmark = true;
1493
1494                 var row = $("RROW-" + id);
1495                 var elem = $("CICD-" + id);
1496
1497                 if (elem && row) {
1498                         var collapse = $$("div#RROW-" + id +
1499                                 " span[class='collapseBtn']")[0];
1500
1501                         Element.hide(elem);
1502                         Element.show("CEXC-" + id);
1503                         Element.hide(collapse);
1504
1505                         if (unmark) {
1506                                 row.removeClassName("active");
1507
1508                                 markHeadline(id, false);
1509
1510                                 if (id == getActiveArticleId()) {
1511                                         setActiveArticleId(0);
1512                                 }
1513
1514                                 updateSelectedPrompt();
1515                         }
1516
1517                         if (event) Event.stop(event);
1518
1519                         PluginHost.run(PluginHost.HOOK_ARTICLE_COLLAPSED, id);
1520
1521                         if (row.offsetTop < $("headlines-frame").scrollTop)
1522                                 scrollToRowId(row.id);
1523
1524                         $("floatingTitle").style.visibility = "hidden";
1525                         $("floatingTitle").setAttribute("rowid", false);
1526                 }
1527
1528         } catch (e) {
1529                 exception_error("cdmCollapseArticle", e);
1530         }
1531 }
1532
1533 function cdmExpandArticle(id, noexpand) {
1534         try {
1535                 console.log("cdmExpandArticle " + id);
1536
1537                 if (!$("RROW-" + id)) return false;
1538
1539                 var oldrow = $("RROW-" + getActiveArticleId());
1540
1541                 var elem = $("CICD-" + getActiveArticleId());
1542
1543                 if (id == getActiveArticleId() && Element.visible(elem))
1544                         return true;
1545
1546                 selectArticles("none");
1547
1548                 var old_offset = $("RROW-" + id).offsetTop;
1549
1550                 if (getActiveArticleId() && elem && !getInitParam("cdm_expanded")) {
1551                         var collapse = $$("div#RROW-" + getActiveArticleId() +
1552                                 " span[class='collapseBtn']")[0];
1553
1554                         Element.hide(elem);
1555                         Element.show("CEXC-" + getActiveArticleId());
1556                         Element.hide(collapse);
1557                 }
1558
1559                 if (oldrow) oldrow.removeClassName("active");
1560
1561                 setActiveArticleId(id);
1562
1563                 elem = $("CICD-" + id);
1564
1565                 var collapse = $$("div#RROW-" + id +
1566                                 " span[class='collapseBtn']")[0];
1567
1568                 var cencw = $("CENCW-" + id);
1569
1570                 if (!Element.visible(elem) && !noexpand) {
1571                         if (cencw) {
1572                                 cencw.innerHTML = htmlspecialchars_decode(cencw.innerHTML);
1573                                 cencw.setAttribute('id', '');
1574                                 Element.show(cencw);
1575                         }
1576
1577                         Element.show(elem);
1578                         Element.hide("CEXC-" + id);
1579                         Element.show(collapse);
1580                 }
1581
1582                 var new_offset = $("RROW-" + id).offsetTop;
1583
1584                 if (old_offset > new_offset)
1585                         $("headlines-frame").scrollTop -= (old_offset-new_offset);
1586
1587                 if (!noexpand) {
1588                         if (catchup_id_batch.indexOf(id) == -1)
1589                                 catchup_id_batch.push(id);
1590
1591                         catchupCurrentBatchIfNeeded();
1592                 }
1593
1594                 toggleSelected(id);
1595                 $("RROW-" + id).addClassName("active");
1596
1597                 PluginHost.run(PluginHost.HOOK_ARTICLE_EXPANDED, id);
1598
1599         } catch (e) {
1600                 exception_error("cdmExpandArticle", e);
1601         }
1602
1603         return false;
1604 }
1605
1606 function getArticleUnderPointer() {
1607         return post_under_pointer;
1608 }
1609
1610 function scrollArticle(offset) {
1611         try {
1612                 if (!isCdmMode()) {
1613                         var ci = $("content-insert");
1614                         if (ci) {
1615                                 ci.scrollTop += offset;
1616                         }
1617                 } else {
1618                         var hi = $("headlines-frame");
1619                         if (hi) {
1620                                 hi.scrollTop += offset;
1621                         }
1622
1623                 }
1624         } catch (e) {
1625                 exception_error("scrollArticle", e);
1626         }
1627 }
1628
1629 function show_labels_in_headlines(transport) {
1630         try {
1631                 var data = JSON.parse(transport.responseText);
1632
1633                 if (data) {
1634                         data['info-for-headlines'].each(function(elem) {
1635                                 $$(".HLLCTR-" + elem.id).each(function(ctr) {
1636                                         ctr.innerHTML = elem.labels;
1637                                 });
1638                         });
1639                 }
1640         } catch (e) {
1641                 exception_error("show_labels_in_headlines", e);
1642         }
1643 }
1644
1645 function dismissArticle(id) {
1646         try {
1647                 var elem = $("RROW-" + id);
1648
1649                 if (!elem) return;
1650
1651                 toggleUnread(id, 0, true);
1652
1653                 new Effect.Fade(elem, {duration : 0.5});
1654
1655                 // Remove the content, too
1656                 var elem_content = $("CICD-" + id);
1657                 if (elem_content) {
1658                         Element.remove(elem_content);
1659                 }
1660
1661                 if (id == getActiveArticleId()) {
1662                         setActiveArticleId(0);
1663                 }
1664
1665         } catch (e) {
1666                 exception_error("dismissArticle", e);
1667         }
1668 }
1669
1670 function dismissSelectedArticles() {
1671         try {
1672
1673                 var ids = getVisibleArticleIds();
1674                 var tmp = [];
1675                 var sel = [];
1676
1677                 for (var i = 0; i < ids.length; i++) {
1678                         var elem = $("RROW-" + ids[i]);
1679
1680                         if (elem.className && elem.hasClassName("Selected") &&
1681                                         ids[i] != getActiveArticleId()) {
1682                                 new Effect.Fade(elem, {duration : 0.5});
1683                                 sel.push(ids[i]);
1684
1685                                 // Remove the content, too
1686                                 var elem_content = $("CICD-" + ids[i]);
1687                                 if (elem_content) {
1688                                         Element.remove(elem_content);
1689                                 }
1690                         } else {
1691                                 tmp.push(ids[i]);
1692                         }
1693                 }
1694
1695                 if (sel.length > 0)
1696                         selectionToggleUnread(false);
1697
1698
1699         } catch (e) {
1700                 exception_error("dismissSelectedArticles", e);
1701         }
1702 }
1703
1704 function dismissReadArticles() {
1705         try {
1706
1707                 var ids = getVisibleArticleIds();
1708                 var tmp = [];
1709
1710                 for (var i = 0; i < ids.length; i++) {
1711                         var elem = $("RROW-" + ids[i]);
1712
1713                         if (elem.className && !elem.hasClassName("Unread") &&
1714                                         !elem.hasClassName("Selected")) {
1715
1716                                 new Effect.Fade(elem, {duration : 0.5});
1717
1718                                 // Remove the content, too
1719                                 var elem_content = $("CICD-" + ids[i]);
1720                                 if (elem_content) {
1721                                         Element.remove(elem_content);
1722                                 }
1723                         } else {
1724                                 tmp.push(ids[i]);
1725                         }
1726                 }
1727
1728         } catch (e) {
1729                 exception_error("dismissReadArticles", e);
1730         }
1731 }
1732
1733 // we don't really hide rows anymore
1734 function getVisibleArticleIds() {
1735         return getLoadedArticleIds();
1736
1737         /*var ids = [];
1738
1739         try {
1740
1741                 getLoadedArticleIds().each(function(id) {
1742                         var elem = $("RROW-" + id);
1743                         if (elem && Element.visible(elem))
1744                                 ids.push(id);
1745                         });
1746
1747         } catch (e) {
1748                 exception_error("getVisibleArticleIds", e);
1749         }
1750
1751         return ids; */
1752 }
1753
1754 function cdmClicked(event, id) {
1755         try {
1756                 //var shift_key = event.shiftKey;
1757
1758                 if (!event.ctrlKey && !event.metaKey) {
1759
1760                         if (!getInitParam("cdm_expanded")) {
1761                                 return cdmExpandArticle(id);
1762                         } else {
1763
1764                                 var elem = $("RROW-" + getActiveArticleId());
1765
1766                                 if (elem) elem.removeClassName("active");
1767
1768                                 selectArticles("none");
1769                                 toggleSelected(id);
1770
1771                                 var elem = $("RROW-" + id);
1772                                 var article_is_unread = elem.hasClassName("Unread");
1773
1774                                 elem.removeClassName("Unread");
1775                                 elem.addClassName("active");
1776
1777                                 setActiveArticleId(id);
1778
1779                                 if (article_is_unread) {
1780                                         decrementFeedCounter(getActiveFeedId(), activeFeedIsCat());
1781                                         updateFloatingTitle(true);
1782                                 }
1783
1784                                 var query = "?op=rpc&method=catchupSelected" +
1785                                         "&cmode=0&ids=" + param_escape(id);
1786
1787                                 new Ajax.Request("backend.php", {
1788                                         parameters: query,
1789                                         onComplete: function(transport) {
1790                                                 handle_rpc_json(transport);
1791                                         } });
1792
1793                                 return !event.shiftKey;
1794                         }
1795
1796                 } else if (event.target.parents(".cdmHeader").length > 0) {
1797
1798                         toggleSelected(id, true);
1799
1800                         var elem = $("RROW-" + id);
1801                         var article_is_unread = elem.hasClassName("Unread");
1802
1803                         if (article_is_unread) {
1804                                 decrementFeedCounter(getActiveFeedId(), activeFeedIsCat());
1805                         }
1806
1807                         toggleUnread(id, 0, false);
1808
1809                         openArticleInNewWindow(id);
1810                 }
1811
1812                 var unread_in_buffer = $$("#headlines-frame > div[id*=RROW][class*=Unread]").length
1813                 request_counters(unread_in_buffer == 0);
1814
1815         } catch (e) {
1816                 exception_error("cdmClicked");
1817         }
1818
1819         return false;
1820 }
1821
1822 function hlClicked(event, id) {
1823         try {
1824                 if (event.which == 2) {
1825                         view(id);
1826                         return true;
1827                 } else if (event.ctrlKey || event.metaKey) {
1828                         toggleSelected(id, true);
1829                         toggleUnread(id, 0, false);
1830                         openArticleInNewWindow(id);
1831                         return false;
1832                 } else {
1833                         view(id);
1834                         return false;
1835                 }
1836
1837         } catch (e) {
1838                 exception_error("hlClicked");
1839         }
1840 }
1841
1842 function getFirstVisibleHeadlineId() {
1843         var rows = getVisibleArticleIds();
1844         return rows[0];
1845
1846 }
1847
1848 function getLastVisibleHeadlineId() {
1849         var rows = getVisibleArticleIds();
1850         return rows[rows.length-1];
1851 }
1852
1853 function openArticleInNewWindow(id) {
1854         toggleUnread(id, 0, false);
1855         window.open("backend.php?op=article&method=redirect&id=" + id);
1856 }
1857
1858 function isCdmMode() {
1859         return getInitParam("combined_display_mode");
1860 }
1861
1862 function markHeadline(id, marked) {
1863         if (marked == undefined) marked = true;
1864
1865         var row = $("RROW-" + id);
1866         if (row) {
1867                 var check = dijit.getEnclosingWidget(
1868                                 row.getElementsByClassName("rchk")[0]);
1869
1870                 if (check) {
1871                         check.attr("checked", marked);
1872                 }
1873
1874                 if (marked)
1875                         row.addClassName("Selected");
1876                 else
1877                         row.removeClassName("Selected");
1878         }
1879 }
1880
1881 function getRelativePostIds(id, limit) {
1882
1883         var tmp = [];
1884
1885         try {
1886
1887                 if (!limit) limit = 6; //3
1888
1889                 var ids = getVisibleArticleIds();
1890
1891                 for (var i = 0; i < ids.length; i++) {
1892                         if (ids[i] == id) {
1893                                 for (var k = 1; k <= limit; k++) {
1894                                         //if (i > k-1) tmp.push(ids[i-k]);
1895                                         if (i < ids.length-k) tmp.push(ids[i+k]);
1896                                 }
1897                                 break;
1898                         }
1899                 }
1900
1901         } catch (e) {
1902                 exception_error("getRelativePostIds", e);
1903         }
1904
1905         return tmp;
1906 }
1907
1908 function correctHeadlinesOffset(id) {
1909
1910         try {
1911
1912                 var container = $("headlines-frame");
1913                 var row = $("RROW-" + id);
1914
1915                 if (!container || !row) return;
1916
1917                 var viewport = container.offsetHeight;
1918
1919                 var rel_offset_top = row.offsetTop - container.scrollTop;
1920                 var rel_offset_bottom = row.offsetTop + row.offsetHeight - container.scrollTop;
1921
1922                 //console.log("Rtop: " + rel_offset_top + " Rbtm: " + rel_offset_bottom);
1923                 //console.log("Vport: " + viewport);
1924
1925                 if (rel_offset_top <= 0 || rel_offset_top > viewport) {
1926                         container.scrollTop = row.offsetTop;
1927                 } else if (rel_offset_bottom > viewport) {
1928
1929                         /* doesn't properly work with Opera in some cases because
1930                                 Opera fucks up element scrolling */
1931
1932                         container.scrollTop = row.offsetTop + row.offsetHeight - viewport;
1933                 }
1934
1935         } catch (e) {
1936                 exception_error("correctHeadlinesOffset", e);
1937         }
1938
1939 }
1940
1941 function headlineActionsChange(elem) {
1942         try {
1943                 eval(elem.value);
1944                 elem.attr('value', 'false');
1945         } catch (e) {
1946                 exception_error("headlineActionsChange", e);
1947         }
1948 }
1949
1950 function closeArticlePanel() {
1951
1952         if (dijit.byId("content-insert"))
1953                 dijit.byId("headlines-wrap-inner").removeChild(
1954                         dijit.byId("content-insert"));
1955 }
1956
1957 function initFloatingMenu() {
1958         try {
1959                 if (dijit.byId("floatingMenu"))
1960                         dijit.byId("floatingMenu").destroyRecursive();
1961
1962                         var menu = new dijit.Menu({
1963                                 id: "floatingMenu",
1964                                 targetNodeIds: ["floatingTitle"]
1965                         });
1966
1967                         var id = $("floatingTitle").getAttribute("rowid").replace("RROW-", "");
1968
1969                         headlinesMenuCommon(menu, id);
1970
1971                         menu.startup();
1972         } catch (e) {
1973                 exception_error("initFloatingMenu", e);
1974         }
1975 }
1976
1977 function headlinesMenuCommon(menu, base_id) {
1978         try {
1979
1980                 menu.addChild(new dijit.MenuItem({
1981                         label: __("Open original article"),
1982                         onClick: function(event) {
1983                                 openArticleInNewWindow(base_id ? base_id : this.getParent().callerRowId);
1984                         }}));
1985
1986                 menu.addChild(new dijit.MenuItem({
1987                         label: __("Display article URL"),
1988                         onClick: function(event) {
1989                                 displayArticleUrl(base_id ? base_id : this.getParent().callerRowId);
1990                         }}));
1991
1992                 menu.addChild(new dijit.MenuSeparator());
1993
1994                 menu.addChild(new dijit.MenuItem({
1995                         label: __("Toggle unread"),
1996                         onClick: function(event) {
1997                                 var ids = getSelectedArticleIds2();
1998                                 // cast to string
1999                                 var id = (base_id ? base_id : this.getParent().callerRowId) + "";
2000                                 ids = ids.size() != 0 && ids.indexOf(id) != -1 ? ids : [id];
2001
2002                                 selectionToggleUnread(undefined, false, true, ids);
2003                                 }}));
2004
2005                 menu.addChild(new dijit.MenuItem({
2006                         label: __("Toggle starred"),
2007                         onClick: function(event) {
2008                                 var ids = getSelectedArticleIds2();
2009                                 // cast to string
2010                                 var id = (base_id ? base_id : this.getParent().callerRowId) + "";
2011                                 ids = ids.size() != 0 && ids.indexOf(id) != -1 ? ids : [id];
2012
2013                                 selectionToggleMarked(undefined, false, true, ids);
2014                                 }}));
2015
2016                 menu.addChild(new dijit.MenuItem({
2017                         label: __("Toggle published"),
2018                         onClick: function(event) {
2019                                 var ids = getSelectedArticleIds2();
2020                                 // cast to string
2021                                 var id = (base_id ? base_id : this.getParent().callerRowId) + "";
2022                                 ids = ids.size() != 0 && ids.indexOf(id) != -1 ? ids : [id];
2023
2024                                 selectionTogglePublished(undefined, false, true, ids);
2025                                 }}));
2026
2027                 menu.addChild(new dijit.MenuSeparator());
2028
2029                 menu.addChild(new dijit.MenuItem({
2030                         label: __("Mark above as read"),
2031                         onClick: function(event) {
2032                                 catchupRelativeToArticle(0, base_id ? base_id : this.getParent().callerRowId);
2033                                 }}));
2034
2035                 menu.addChild(new dijit.MenuItem({
2036                         label: __("Mark below as read"),
2037                         onClick: function(event) {
2038                                 catchupRelativeToArticle(1, base_id ? base_id : this.getParent().callerRowId);
2039                                 }}));
2040
2041
2042                 var labels = dijit.byId("feedTree").model.getItemsInCategory(-2);
2043
2044                 if (labels) {
2045
2046                         menu.addChild(new dijit.MenuSeparator());
2047
2048                         var labelAddMenu = new dijit.Menu({ownerMenu: menu});
2049                         var labelDelMenu = new dijit.Menu({ownerMenu: menu});
2050
2051                         labels.each(function(label) {
2052                                 var id = label.id[0];
2053                                 var bare_id = id.substr(id.indexOf(":")+1);
2054                                 var name = label.name[0];
2055
2056                                 bare_id = feed_to_label_id(bare_id);
2057
2058                                 labelAddMenu.addChild(new dijit.MenuItem({
2059                                         label: name,
2060                                         labelId: bare_id,
2061                                         onClick: function(event) {
2062                                                 var ids = getSelectedArticleIds2();
2063                                                 // cast to string
2064                                                 var id = (base_id ? base_id : this.getParent().ownerMenu.callerRowId) + "";
2065
2066                                                 ids = ids.size() != 0 && ids.indexOf(id) != -1 ? ids : [id];
2067
2068                                                 selectionAssignLabel(this.labelId, ids);
2069                                 }}));
2070
2071                                 labelDelMenu.addChild(new dijit.MenuItem({
2072                                         label: name,
2073                                         labelId: bare_id,
2074                                         onClick: function(event) {
2075                                                 var ids = getSelectedArticleIds2();
2076                                                 // cast to string
2077                                                 var id = (base_id ? base_id : this.getParent().ownerMenu.callerRowId) + "";
2078
2079                                                 ids = ids.size() != 0 && ids.indexOf(id) != -1 ? ids : [id];
2080
2081                                                 selectionRemoveLabel(this.labelId, ids);
2082                                 }}));
2083
2084                         });
2085
2086                         menu.addChild(new dijit.PopupMenuItem({
2087                                 label: __("Assign label"),
2088                                 popup: labelAddMenu
2089                         }));
2090
2091                         menu.addChild(new dijit.PopupMenuItem({
2092                                 label: __("Remove label"),
2093                                 popup: labelDelMenu
2094                         }));
2095
2096                 }
2097
2098
2099         } catch (e) {
2100                 exception_error("headlinesMenuCommon", e);
2101         }
2102 }
2103
2104 function initHeadlinesMenu() {
2105         try {
2106                 if (dijit.byId("headlinesMenu"))
2107                         dijit.byId("headlinesMenu").destroyRecursive();
2108
2109                 var ids = [];
2110
2111                 if (!isCdmMode()) {
2112                         nodes = $$("#headlines-frame > div[id*=RROW]");
2113                 } else {
2114                         nodes = $$("#headlines-frame span[id*=RTITLE]");
2115                 }
2116
2117                 nodes.each(function(node) {
2118                         ids.push(node.id);
2119                 });
2120
2121                 var menu = new dijit.Menu({
2122                         id: "headlinesMenu",
2123                         targetNodeIds: ids
2124                 });
2125
2126                 var tmph = dojo.connect(menu, '_openMyself', function (event) {
2127                         var callerNode = event.target, match = null, tries = 0;
2128
2129                         while (match == null && callerNode && tries <= 3) {
2130                                 match = callerNode.id.match("^[A-Z]+[-]([0-9]+)$");
2131                                 callerNode = callerNode.parentNode;
2132                                 ++tries;
2133                         }
2134
2135                         if (match) this.callerRowId = parseInt(match[1]);
2136
2137                 });
2138
2139                 headlinesMenuCommon(menu, false);
2140
2141                 menu.startup();
2142
2143                 /* vgroup feed title menu */
2144
2145                 var nodes = $$("#headlines-frame > div[class='cdmFeedTitle']");
2146                 var ids = [];
2147
2148                 nodes.each(function(node) {
2149                         ids.push(node.id);
2150                 });
2151
2152                 if (ids.length > 0) {
2153                         if (dijit.byId("headlinesFeedTitleMenu"))
2154                                 dijit.byId("headlinesFeedTitleMenu").destroyRecursive();
2155
2156                         var menu = new dijit.Menu({
2157                                 id: "headlinesFeedTitleMenu",
2158                                 targetNodeIds: ids
2159                         });
2160
2161                         var tmph = dojo.connect(menu, '_openMyself', function (event) {
2162                                 var callerNode = event.target, match = null, tries = 0;
2163
2164                                 while (match == null && callerNode && tries <= 3) {
2165                                         console.log(callerNode.id);
2166
2167                                         match = callerNode.id.match("^[A-Z]+[-]([0-9]+)$");
2168                                         callerNode = callerNode.parentNode;
2169                                         ++tries;
2170
2171                                         console.log(match[1]);
2172                                 }
2173
2174                                 if (match) this.callerRowId = parseInt(match[1]);
2175
2176                         });
2177
2178                         menu.addChild(new dijit.MenuItem({
2179                                 label: __("Select articles in group"),
2180                                 onClick: function(event) {
2181                                         selectArticles("all",
2182                                                 "#headlines-frame > div[id*=RROW]"+
2183                                                 "[orig-feed-id='"+menu.callerRowId+"']");
2184
2185                                 }}));
2186
2187                         menu.addChild(new dijit.MenuItem({
2188                                 label: __("Mark group as read"),
2189                                 onClick: function(event) {
2190                                         selectArticles("none");
2191                                         selectArticles("all",
2192                                                 "#headlines-frame > div[id*=RROW]"+
2193                                                 "[orig-feed-id='"+menu.callerRowId+"']");
2194
2195                                         catchupSelection();
2196                                 }}));
2197
2198
2199                         menu.addChild(new dijit.MenuItem({
2200                                 label: __("Mark feed as read"),
2201                                 onClick: function(event) {
2202                                         catchupFeedInGroup(menu.callerRowId);
2203                                 }}));
2204
2205                         menu.addChild(new dijit.MenuItem({
2206                                 label: __("Edit feed"),
2207                                 onClick: function(event) {
2208                                         editFeed(menu.callerRowId);
2209                                 }}));
2210
2211                         menu.startup();
2212
2213                 }
2214
2215         } catch (e) {
2216                 exception_error("initHeadlinesMenu", e);
2217         }
2218 }
2219
2220 function cache_set(id, obj) {
2221         //console.log("cache_set: " + id);
2222         if (has_storage)
2223                 try {
2224                         sessionStorage[id] = obj;
2225                 } catch (e) {
2226                         sessionStorage.clear();
2227                 }
2228 }
2229
2230 function cache_get(id) {
2231         if (has_storage)
2232                 return sessionStorage[id];
2233 }
2234
2235 function cache_clear() {
2236         if (has_storage)
2237                 sessionStorage.clear();
2238 }
2239
2240 function cache_delete(id) {
2241         if (has_storage)
2242                 sessionStorage.removeItem(id);
2243 }
2244
2245 function cancelSearch() {
2246         try {
2247                 _search_query = "";
2248                 viewCurrentFeed();
2249         } catch (e) {
2250                 exception_error("cancelSearch", e);
2251         }
2252 }
2253
2254 function setSelectionScore() {
2255         try {
2256                 var ids = getSelectedArticleIds2();
2257
2258                 if (ids.length > 0) {
2259                         console.log(ids);
2260
2261                         var score = prompt(__("Please enter new score for selected articles:"), score);
2262
2263                         if (score != undefined) {
2264                                 var query = "op=article&method=setScore&id=" + param_escape(ids.toString()) +
2265                                         "&score=" + param_escape(score);
2266
2267                                 new Ajax.Request("backend.php", {
2268                                         parameters: query,
2269                                         onComplete: function(transport) {
2270                                                 var reply = JSON.parse(transport.responseText);
2271                                                 if (reply) {
2272                                                         console.log(ids);
2273
2274                                                         ids.each(function(id) {
2275                                                                 var row = $("RROW-" + id);
2276
2277                                                                 if (row) {
2278                                                                         var pic = row.getElementsByClassName("hlScorePic")[0];
2279
2280                                                                         if (pic) {
2281                                                                                 pic.src = pic.src.replace(/score_.*?\.png/,
2282                                                                                         reply["score_pic"]);
2283                                                                                 pic.setAttribute("score", score);
2284                                                                         }
2285                                                                 }
2286                                                         });
2287                                                 }
2288                                         } });
2289                         }
2290
2291                 } else {
2292                         alert(__("No articles are selected."));
2293                 }
2294         } catch (e) {
2295                 exception_error("setSelectionScore", e);
2296         }
2297 }
2298
2299 function updateScore(id) {
2300         try {
2301                 var pic = $$("#RROW-" + id + " .hlScorePic")[0];
2302
2303                 if (pic) {
2304
2305                         var query = "op=article&method=getScore&id=" + param_escape(id);
2306
2307                         new Ajax.Request("backend.php", {
2308                                 parameters: query,
2309                                 onComplete: function(transport) {
2310                                         console.log(transport.responseText);
2311
2312                                         var reply = JSON.parse(transport.responseText);
2313
2314                                         if (reply) {
2315                                                 pic.src = pic.src.replace(/score_.*?\.png/, reply["score_pic"]);
2316                                                 pic.setAttribute("score", reply["score"]);
2317                                                 pic.setAttribute("title", reply["score"]);
2318                                         }
2319                                 } });
2320                 }
2321
2322         } catch (e) {
2323                 exception_error("updateScore", e);
2324         }
2325 }
2326
2327 function changeScore(id, pic) {
2328         try {
2329                 var score = pic.getAttribute("score");
2330
2331                 var new_score = prompt(__("Please enter new score for this article:"), score);
2332
2333                 if (new_score != undefined) {
2334
2335                         var query = "op=article&method=setScore&id=" + param_escape(id) +
2336                                 "&score=" + param_escape(new_score);
2337
2338                         new Ajax.Request("backend.php", {
2339                                 parameters: query,
2340                                 onComplete: function(transport) {
2341                                         var reply = JSON.parse(transport.responseText);
2342
2343                                         if (reply) {
2344                                                 pic.src = pic.src.replace(/score_.*?\.png/, reply["score_pic"]);
2345                                                 pic.setAttribute("score", new_score);
2346                                                 pic.setAttribute("title", new_score);
2347                                         }
2348                                 } });
2349                 }
2350         } catch (e) {
2351                 exception_error("changeScore", e);
2352         }
2353 }
2354
2355 function displayArticleUrl(id) {
2356         try {
2357                 var query = "op=rpc&method=getlinktitlebyid&id=" + param_escape(id);
2358
2359                         new Ajax.Request("backend.php", {
2360                                 parameters: query,
2361                                 onComplete: function(transport) {
2362                                         var reply = JSON.parse(transport.responseText);
2363
2364                                         if (reply && reply.link) {
2365                                                 prompt(__("Article URL:"), reply.link);
2366                                         }
2367                                 } });
2368         } catch (e) {
2369                 exception_error("changeScore", e);
2370         }
2371 }
2372
2373 function scrollToRowId(id) {
2374         try {
2375                 var row = $(id);
2376
2377                 if (row)
2378                         $("headlines-frame").scrollTop = row.offsetTop - 4;
2379
2380         } catch (e) {
2381                 exception_error("scrollToRowId", e);
2382         }
2383 }
2384
2385 function updateFloatingTitle(unread_only) {
2386         try {
2387                 if (!isCdmMode()) return;
2388
2389                 var hf = $("headlines-frame");
2390
2391                 var elems = $$("#headlines-frame > div[id*=RROW]");
2392
2393                 for (var i = 0; i < elems.length; i++) {
2394
2395                         var child = elems[i];
2396
2397                         if (child && child.offsetTop + child.offsetHeight > hf.scrollTop) {
2398
2399                                 var header = child.getElementsByClassName("cdmHeader")[0];
2400
2401                                 if (unread_only || child.id != $("floatingTitle").getAttribute("rowid")) {
2402                                         if (child.id != $("floatingTitle").getAttribute("rowid")) {
2403                                                 $("floatingTitle").setAttribute("rowid", child.id);
2404                                                 $("floatingTitle").innerHTML = header.innerHTML;
2405                                                 $("floatingTitle").firstChild.innerHTML = "<img class='anchor markedPic' src='images/page_white_go.png' onclick=\"scrollToRowId('"+child.id+"')\">" + $("floatingTitle").firstChild.innerHTML;
2406
2407                                                 initFloatingMenu();
2408
2409                                                 var cb = $$("#floatingTitle .dijitCheckBox")[0];
2410
2411                                                 if (cb)
2412                                                         cb.parentNode.removeChild(cb);
2413                                         }
2414
2415                                         if (child.hasClassName("Unread"))
2416                                                 $("floatingTitle").addClassName("Unread");
2417                                         else
2418                                                 $("floatingTitle").removeClassName("Unread");
2419
2420                                         PluginHost.run(PluginHost.HOOK_FLOATING_TITLE, child);
2421                                 }
2422
2423                                 $("floatingTitle").style.marginRight = hf.offsetWidth - child.offsetWidth + "px";
2424                                 if (header.offsetTop + header.offsetHeight < hf.scrollTop + $("floatingTitle").offsetHeight - 5 &&
2425                                     child.offsetTop + child.offsetHeight >= hf.scrollTop + $("floatingTitle").offsetHeight - 5)
2426                                         $("floatingTitle").style.visibility = "visible";
2427                                 else
2428                                         $("floatingTitle").style.visibility = "hidden";
2429
2430                                 return;
2431
2432                         }
2433                 }
2434
2435         } catch (e) {
2436                 exception_error("updateFloatingTitle", e);
2437         }
2438 }
2439
2440 function catchupCurrentBatchIfNeeded() {
2441         if (catchup_id_batch.length > 0) {
2442                 window.clearTimeout(catchup_timeout_id);
2443                 catchup_timeout_id = window.setTimeout('catchupBatchedArticles()', 1000);
2444
2445                 if (catchup_id_batch.length >= 10) {
2446                         catchupBatchedArticles();
2447                 }
2448         }
2449 }
2450
2451 function cdmFooterClick(event) {
2452         event.stopPropagation();
2453 }