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