]> git.wh0rd.org Git - tt-rss.git/blob - tt-rss.js
move runtime-info to JSON, simplify init-params format
[tt-rss.git] / tt-rss.js
1
2 var total_unread = 0;
3 var first_run = true;
4 var display_tags = false;
5 var global_unread = -1;
6 var active_title_text = "";
7 var current_subtitle = "";
8 var daemon_enabled = false;
9 //var _qfd_deleted_feed = 0;
10 var firsttime_update = true;
11 var _active_feed_id = 0;
12 var _active_feed_is_cat = false;
13 var number_of_feeds = 0;
14 var _hfd_scrolltop = 0;
15 var hotkey_prefix = false;
16 var hotkey_prefix_pressed = false;
17 var init_params = new Object();
18 var ver_offset = 0;
19 var hor_offset = 0;
20 var feeds_sort_by_unread = false;
21 var feedlist_sortable_enabled = false;
22
23 function activeFeedIsCat() {
24         return _active_feed_is_cat;
25 }
26
27 function getActiveFeedId() {
28         try {
29                 //console.log("gAFID: " + _active_feed_id);
30                 return _active_feed_id;
31         } catch (e) {
32                 exception_error("getActiveFeedId", e);
33         }
34 }
35
36 function setActiveFeedId(id, is_cat) {
37         try {
38                 //console.log("sAFID(" + id + ", " + is_cat + ")");
39                 _active_feed_id = id;
40
41                 if (is_cat != undefined) {
42                         _active_feed_is_cat = is_cat;
43                 }
44
45         } catch (e) {
46                 exception_error("setActiveFeedId", e);
47         }
48 }
49
50
51 function isFeedlistSortable() {
52         return feedlist_sortable_enabled;
53 }
54
55 function tagsAreDisplayed() {
56         return display_tags;
57 }
58
59 function toggleTags(show_all) {
60
61         try {
62
63         console.log("toggleTags: " + show_all + "; " + display_tags);
64
65         var p = $("dispSwitchPrompt");
66
67         if (!show_all && !display_tags) {
68                 displayDlg("printTagCloud");
69         } else if (show_all) {
70                 closeInfoBox();
71                 display_tags = true;
72                 p.innerHTML = __("display feeds");
73                 notify_progress("Loading, please wait...", true);
74                 updateFeedList();
75         } else if (display_tags) {
76                 display_tags = false;
77                 p.innerHTML = __("tag cloud");
78                 notify_progress("Loading, please wait...", true);
79                 updateFeedList();
80         }
81
82         } catch (e) {
83                 exception_error("toggleTags", e);
84         }
85 }
86
87 function dlg_frefresh_callback(transport, deleted_feed) {
88         if (getActiveFeedId() == deleted_feed) {
89                 setTimeout("viewfeed(-5)", 100);
90         }
91
92         setTimeout('updateFeedList(false, false)', 50);
93         closeInfoBox();
94 }
95
96 function scheduleFeedUpdate() {
97
98         console.log("in scheduleFeedUpdate");
99
100         var query_str = "backend.php?op=rpc&subop=updateAllFeeds";
101
102         var omode;
103
104         if (firsttime_update && !navigator.userAgent.match("Opera")) {
105                 firsttime_update = false;
106                 omode = "T";
107         } else {
108                 if (display_tags) {
109                         omode = "tl";
110                 } else {
111                         omode = "flc";
112                 }
113         }
114         
115         query_str = query_str + "&omode=" + omode;
116         query_str = query_str + "&uctr=" + global_unread;
117
118         console.log("REFETCH query: " + query_str);
119
120         new Ajax.Request("backend.php", {
121                 parameters: query_str,
122                 onComplete: function(transport) { 
123                                 parse_counters_reply(transport, true);
124                         } });
125 }
126
127 function updateFeedList() {
128         try {
129                 console.log("updateFeedList");
130         
131                 if (offline_mode) return render_offline_feedlist();
132         
133                 var query_str = "backend.php?op=feeds";
134         
135                 if (display_tags) {
136                         query_str = query_str + "&tags=1";
137                 }
138         
139                 if (getActiveFeedId() && !activeFeedIsCat()) {
140                         query_str = query_str + "&actid=" + getActiveFeedId();
141                 }
142                 
143                 new Ajax.Request("backend.php", {
144                         parameters: query_str,
145                         onComplete: function(transport) { 
146                                 render_feedlist(transport.responseText);
147                         } });
148
149         } catch (e) {
150                 exception_error("updateFeedList", e);
151         }
152 }
153
154 function catchupAllFeeds() {
155
156         var str = __("Mark all articles as read?");
157
158         if (getInitParam("confirm_feed_catchup") != 1 || confirm(str)) {
159
160                 var query_str = "backend.php?op=feeds&subop=catchupAll";
161
162                 notify_progress("Marking all feeds as read...");
163
164                 console.log("catchupAllFeeds Q=" + query_str);
165
166                 new Ajax.Request("backend.php", {
167                         parameters: query_str,
168                         onComplete: function(transport) { 
169                                 feedlist_callback2(transport); 
170                         } });
171
172                 global_unread = 0;
173                 updateTitle("");
174         }
175 }
176
177 function viewCurrentFeed(subop) {
178
179 //      if (getActiveFeedId()) {
180         if (getActiveFeedId() != undefined) {
181                 viewfeed(getActiveFeedId(), subop, activeFeedIsCat());
182         } else {
183 //              viewfeed(-1, subop); // FIXME
184         }
185         return false; // block unneeded form submits
186 }
187
188 function viewfeed(feed, subop) {
189         var f = window.frames["feeds-frame"];
190         f.viewfeed(feed, subop);
191 }
192
193 function timeout() {
194         if (getInitParam("bw_limit") == "1") return;
195
196         scheduleFeedUpdate(false);
197
198         var refresh_time = getInitParam("feeds_frame_refresh");
199
200         if (!refresh_time) refresh_time = 600; 
201
202         setTimeout("timeout()", refresh_time*1000);
203 }
204
205 function resetSearch() {
206         var searchbox = $("searchbox")
207
208         if (searchbox.value != "" && getActiveFeedId()) {       
209                 searchbox.value = "";
210                 viewfeed(getActiveFeedId(), "");
211         }
212 }
213
214 function search() {
215         closeInfoBox(); 
216         viewCurrentFeed(0, "");
217 }
218
219 // if argument is undefined, current subtitle is not updated
220 // use blank string to clear subtitle
221 function updateTitle(s) {
222         var tmp = "Tiny Tiny RSS";
223
224         if (s != undefined) {
225                 current_subtitle = s;
226         }
227
228         if (global_unread > 0) {
229                 tmp = tmp + " (" + global_unread + ")";
230         }
231
232         if (current_subtitle) {
233                 tmp = tmp + " - " + current_subtitle;
234         }
235
236         if (active_title_text.length > 0) {
237                 tmp = tmp + " > " + active_title_text;
238         }
239
240         if (window.fluid) {
241                 if (global_unread > 0) {
242                         window.fluid.dockBadge = global_unread;
243                 } else {
244                         window.fluid.dockBadge = "";
245                 }
246         }
247
248         document.title = tmp;
249 }
250
251 function genericSanityCheck() {
252
253 //      if (!Ajax.getTransport()) fatalError(1);
254
255         setCookie("ttrss_test", "TEST");
256         
257         if (getCookie("ttrss_test") != "TEST") {
258                 fatalError(2);
259         }
260
261         return true;
262 }
263
264 function init() {
265
266         try {
267
268                 init_gears();
269
270                 Form.disable("main_toolbar_form");
271
272                 if (!genericSanityCheck()) 
273                         return;
274
275                 var params = "&ua=" + param_escape(navigator.userAgent);
276
277                 loading_set_progress(30);
278
279                 new Ajax.Request("backend.php", {
280                         parameters: "backend.php?op=rpc&subop=sanityCheck" + params,
281                         onComplete: function(transport) {
282                                         backend_sanity_check_callback(transport);
283                                 } });
284
285         } catch (e) {
286                 exception_error("init", e);
287         }
288 }
289
290 function resize_headlines(delta_x, delta_y) {
291
292         try {
293
294                 console.log("resize_headlines: " + delta_x + ":" + delta_y);
295         
296                 var h_frame = $("headlines-frame");
297                 var c_frame = $("content-frame");
298                 var f_frame = $("footer");
299                 var feeds_frame = $("feeds-holder");
300                 var resize_grab = $("resize-grabber");
301                 var resize_handle = $("resize-handle");
302
303                 if (!c_frame || !h_frame) return;
304         
305                 if (feeds_frame && getInitParam("theme") == "old-skool") {
306                                 feeds_frame.style.bottom = f_frame.offsetHeight + "px";         
307                 }
308         
309                 if (getInitParam("theme_options").match("horiz_resize")) {
310         
311                         if (delta_x != undefined) {
312                                 if (c_frame.offsetLeft - delta_x > feeds_frame.offsetWidth + feeds_frame.offsetLeft + 100 && c_frame.offsetWidth + delta_x > 100) {
313                                         hor_offset = hor_offset + delta_x;
314                                 }
315                         }
316         
317                         console.log("resize_headlines: HOR-mode: " + hor_offset);
318         
319                         c_frame.style.width = (400 + hor_offset) + "px";
320                         h_frame.style.right = c_frame.offsetWidth - 1 + "px";
321         
322                         resize_grab.style.top = (h_frame.offsetTop + h_frame.offsetHeight - 60) + "px";
323                         resize_grab.style.left = (h_frame.offsetLeft + h_frame.offsetWidth - 
324                                 4) + "px";
325                         resize_grab.style.display = "block";
326
327                         //resize_handle.src = "themes/"+getInitParam('theme')+"/images/resize_handle_vert.png";
328                         resize_handle.style.paddingTop = (resize_grab.offsetHeight / 2 - 7) + "px";
329         
330                 } else {
331         
332                         if (delta_y != undefined) {
333                                 if (c_frame.offsetHeight + delta_y > 100 && h_frame.offsetHeight - delta_y > 100) {
334                                         ver_offset = ver_offset + delta_y;
335                                 }
336                         }
337         
338                         console.log("resize_headlines: VER-mode: " + ver_offset);
339         
340                         h_frame.style.height = (300 - ver_offset) + "px";
341         
342                         c_frame.style.top = (h_frame.offsetTop + h_frame.offsetHeight + 0) + "px";
343                         h_frame.style.height = h_frame.offsetHeight + "px";
344
345                         // Workaround for Opera: force the content page to be re-rendered, 
346                         // so it is not truncated:
347                         var content_pane = $("content-insert");
348                         content_pane.innerHTML = content_pane.innerHTML;
349                 }
350         
351                 if (getInitParam("cookie_lifetime") != 0) {
352                         setCookie("ttrss_offset_ver", ver_offset, 
353                                 getInitParam("cookie_lifetime"));
354                         setCookie("ttrss_offset_hor", hor_offset, 
355                                 getInitParam("cookie_lifetime"));
356                 } else {
357                         setCookie("ttrss_offset_ver", ver_offset);
358                         setCookie("ttrss_offset_hor", hor_offset);
359                 }
360
361         } catch (e) {
362                 exception_error("resize_headlines", e);
363         }
364
365 }
366
367 function init_second_stage() {
368
369         try {
370
371                 delCookie("ttrss_test");
372
373 //              document.onresize = resize_headlines;
374                 window.onresize=resize_headlines;
375
376                 var toolbar = document.forms["main_toolbar_form"];
377
378                 dropboxSelect(toolbar.view_mode, getInitParam("default_view_mode"));
379                 dropboxSelect(toolbar.order_by, getInitParam("default_view_order_by"));
380
381                 daemon_enabled = getInitParam("daemon_enabled") == 1;
382                 feeds_sort_by_unread = getInitParam("feeds_sort_by_unread") == 1;
383
384 /*              var fl = cache_find_param("FEEDLIST", getInitParam("num_feeds"));
385
386                 if (fl) {
387                         render_feedlist(fl);
388                         if ($("feedList")) {
389                                 request_counters();
390                         } else {
391                                 setTimeout('updateFeedList(false, false)', 50);
392                         }
393                 } else {
394                         setTimeout('updateFeedList(false, false)', 50);
395                 } */
396
397                 setTimeout('updateFeedList(false, false)', 50);
398
399                 console.log("second stage ok");
400
401                 loading_set_progress(60);
402
403                 ver_offset = parseInt(getCookie("ttrss_offset_ver"));
404                 hor_offset = parseInt(getCookie("ttrss_offset_hor"));
405
406                 console.log("got offsets from cookies: ver " + ver_offset + " hor " + hor_offset);
407
408                 /* fuck IE */
409
410                 if (isNaN(hor_offset)) hor_offset = 0;
411                 if (isNaN(ver_offset)) ver_offset = 0;
412
413                 console.log("offsets from cookies [x:y]: " + hor_offset + ":" + ver_offset);
414
415                 resize_headlines();
416
417                 enable_offline_reading();
418
419                 if (has_local_storage())
420                         localStorage.clear();
421
422         } catch (e) {
423                 exception_error("init_second_stage", e);
424         }
425 }
426
427 function quickMenuChange() {
428         var chooser = $("quickMenuChooser");
429         var opid = chooser[chooser.selectedIndex].value;
430
431         chooser.selectedIndex = 0;
432         quickMenuGo(opid);
433 }
434
435 function quickMenuGo(opid) {
436         try {
437
438                 if (opid == "qmcPrefs") {
439                         gotoPreferences();
440                 }
441         
442                 if (opid == "qmcSearch") {
443                         displayDlg("search", getActiveFeedId() + ":" + activeFeedIsCat(), 
444                                 function() { 
445                                         document.forms['search_form'].query.focus();
446                                 });
447                         return;
448                 }
449         
450                 if (opid == "qmcAddFeed") {
451                         quickAddFeed();
452                         return;
453                 }
454
455                 if (opid == "qmcEditFeed") {
456                         editFeedDlg(getActiveFeedId());
457                 }
458         
459                 if (opid == "qmcRemoveFeed") {
460                         var actid = getActiveFeedId();
461
462                         if (activeFeedIsCat()) {
463                                 alert(__("You can't unsubscribe from the category."));
464                                 return;
465                         }       
466
467                         if (!actid) {
468                                 alert(__("Please select some feed first."));
469                                 return;
470                         }
471
472                         var fn = getFeedName(actid);
473
474                         var pr = __("Unsubscribe from %s?").replace("%s", fn);
475
476                         if (confirm(pr)) {
477                                 unsubscribeFeed(actid);
478                         }
479                 
480                         return;
481                 }
482
483                 if (opid == "qmcUpdateFeeds") {
484                         scheduleFeedUpdate(true);
485                         return;
486                 }
487         
488                 if (opid == "qmcCatchupAll") {
489                         catchupAllFeeds();
490                         return;
491                 }
492         
493                 if (opid == "qmcShowOnlyUnread") {
494                         toggleDispRead();
495                         return;
496                 }
497         
498                 if (opid == "qmcAddFilter") {
499                         displayDlg('quickAddFilter', '',
500                            function () {document.forms['filter_add_form'].reg_exp.focus();});
501                 }
502
503                 if (opid == "qmcAddLabel") {
504                         addLabel();
505                 }
506
507                 if (opid == "qmcRescoreFeed") {
508                         rescoreCurrentFeed();
509                 }
510
511                 if (opid == "qmcHKhelp") {
512                         //Element.show("hotkey_help_overlay");
513                         Effect.Appear("hotkey_help_overlay", {duration : 0.3});
514                 }
515
516                 if (opid == "qmcResetUI") {
517                         hor_offset = 0;
518                         ver_offset = 0;
519                         resize_headlines();
520                 }
521
522                 if (opid == "qmcToggleReorder") {
523                         feedlist_sortable_enabled = !feedlist_sortable_enabled;
524
525                         if (feedlist_sortable_enabled) {
526                                 notify_info("Category reordering enabled");
527                                 toggle_sortable_feedlist(true);
528                         } else {
529                                 notify_info("Category reordering disabled");
530                                 toggle_sortable_feedlist(false);
531                         }
532                 }
533
534                 if (opid == "qmcResetCats") {
535
536                         if (confirm(__("Reset category order?"))) {
537
538                                 var query = "?op=feeds&subop=catsortreset";
539
540                                 notify_progress("Loading, please wait...", true);
541
542                                 new Ajax.Request("backend.php", {
543                                         parameters: query,
544                                         onComplete: function(transport) { 
545                                                 window.setTimeout('updateFeedList(false, false)', 50);
546                                         } });
547                         }
548                 }
549
550         } catch (e) {
551                 exception_error("quickMenuGo", e);
552         }
553 }
554
555 function updateFeedTitle(t) {
556         active_title_text = t;
557         updateTitle();
558 }
559
560 function toggleDispRead() {
561         try {
562
563                 var hide = !(getInitParam("hide_read_feeds") == "1");
564
565                 hideOrShowFeeds(hide);
566
567                 var query = "?op=rpc&subop=setpref&key=HIDE_READ_FEEDS&value=" + 
568                         param_escape(hide);
569
570                 new Ajax.Request("backend.php", {
571                         parameters: query,
572                         onComplete: function(transport) { 
573                                 setInitParam("hide_read_feeds", hide);
574                         } });
575                                 
576         } catch (e) {
577                 exception_error("toggleDispRead", e);
578         }
579 }
580
581 function parse_runtime_info(elem) {
582
583         if (!elem || !elem.firstChild) {
584                 return;
585         }
586
587         var data = JSON.parse(elem.firstChild.nodeValue);
588
589         console.log("parsing runtime info...");
590
591         for (k in data) {
592                 var v = data[k];
593
594                 console.log("RI: " + k + " => " + v);
595
596                 if (k == "num_feeds") {
597                         init_params[k] = v;                                     
598                 }
599
600                 if (k == "new_version_available") {
601                         var icon = $("newVersionIcon");
602                         if (icon) {
603                                 if (v == "1") {
604                                         icon.style.display = "inline";
605                                 } else {
606                                         icon.style.display = "none";
607                                 }
608                         }
609                 }
610
611                 var error_flag;
612
613                 if (k == "daemon_is_running" && v != 1) {
614                         notify_error("<span onclick=\"javascript:explainError(1)\">Update daemon is not running.</span>", true);
615                         error_flag = true;
616                 }
617
618                 if (k == "daemon_stamp_ok" && v != 1) {
619                         notify_error("<span onclick=\"javascript:explainError(3)\">Update daemon is not updating feeds.</span>", true);
620                         error_flag = true;
621                 }
622
623                 if (!error_flag) {
624                         notify('');
625                 } 
626
627         }
628 }
629
630 function catchupCurrentFeed() {
631
632         var fn = getFeedName(getActiveFeedId(), activeFeedIsCat());
633         
634         var str = __("Mark all articles in %s as read?").replace("%s", fn);
635
636         if (getInitParam("confirm_feed_catchup") != 1 || confirm(str)) {
637                 return viewCurrentFeed('MarkAllRead')
638         }
639 }
640
641 function catchupFeedInGroup(id) {
642
643         try {
644
645                 var title = getFeedName(id);
646
647                 var str = __("Mark all articles in %s as read?").replace("%s", title);
648
649                 if (getInitParam("confirm_feed_catchup") != 1 || confirm(str)) {
650                         return viewCurrentFeed('MarkAllReadGR:' + id)
651                 }
652
653         } catch (e) {
654                 exception_error("catchupFeedInGroup", e);
655         }
656 }
657
658 function editFeedDlg(feed) {
659         try {
660
661                 if (!feed) {
662                         alert(__("Please select some feed first."));
663                         return;
664                 }
665         
666                 if ((feed <= 0) || activeFeedIsCat() || tagsAreDisplayed()) {
667                         alert(__("You can't edit this kind of feed."));
668                         return;
669                 }
670         
671                 var query = "";
672         
673                 if (feed > 0) {
674                         query = "?op=pref-feeds&subop=editfeed&id=" +   param_escape(feed);
675                 } else {
676                         query = "?op=pref-labels&subop=edit&id=" +      param_escape(-feed-11);
677                 }
678
679                 disableHotkeys();
680
681                 notify_progress("Loading, please wait...", true);
682
683                 new Ajax.Request("backend.php", {
684                         parameters: query,
685                         onComplete: function(transport) { 
686                                 infobox_callback2(transport); 
687                                 document.forms["edit_feed_form"].title.focus();
688                         } });
689
690         } catch (e) {
691                 exception_error("editFeedDlg", e);
692         }
693 }
694
695 /* this functions duplicate those of prefs.js feed editor, with
696         some differences because there is no feedlist */
697
698 function feedEditCancel() {
699         closeInfoBox();
700         return false;
701 }
702
703 function feedEditSave() {
704
705         try {
706         
707                 // FIXME: add parameter validation
708
709                 var query = Form.serialize("edit_feed_form");
710
711                 notify_progress("Saving feed...");
712
713                 new Ajax.Request("backend.php", {
714                         parameters: query,
715                         onComplete: function(transport) { 
716                                 dlg_frefresh_callback(transport); 
717                         } });
718
719                 cache_flush();
720                 closeInfoBox();
721
722                 return false;
723
724         } catch (e) {
725                 exception_error("feedEditSave (main)", e);
726         } 
727 }
728
729 function collapse_feedlist() {
730         try {
731                 console.log("collapse_feedlist");
732                 
733                 var theme = getInitParam("theme");
734                 if (theme != "" && 
735                                 !getInitParam("theme_options").match("collapse_feedlist")) return;
736
737                 var fl = $("feeds-holder");
738                 var fh = $("headlines-frame");
739                 var fc = $("content-frame");
740                 var ft = $("toolbar");
741                 var ff = $("footer");
742                 var fhdr = $("header");
743                 var fbtn = $("collapse_feeds_btn");
744
745                 if (!Element.visible(fl)) {
746                         Element.show(fl);
747                         fbtn.innerHTML = "&lt;&lt;";
748
749                         if (theme != "graycube") {
750
751                                 fh.style.left = fl.offsetWidth + "px";
752                                 ft.style.left = fl.offsetWidth + "px";
753                                 if (fc) fc.style.left = fl.offsetWidth + "px";
754                                 if (ff && theme != "compat") ff.style.left = (fl.offsetWidth-1) + "px";
755
756                                 if (theme == "compact") fhdr.style.left = (fl.offsetWidth + 10) + "px";
757                         } else {
758                                 fh.style.left = fl.offsetWidth + 40 + "px";
759                                 ft.style.left = fl.offsetWidth + 40 +"px";
760                                 if (fc) fc.style.left = fl.offsetWidth + 40 + "px";
761                         }
762
763                         query = "?op=rpc&subop=setpref&key=_COLLAPSED_FEEDLIST&value=false";
764
765                         new Ajax.Request("backend.php", { parameters: query });
766
767                 } else {
768                         Element.hide(fl);
769                         fbtn.innerHTML = "&gt;&gt;";
770
771                         if (theme != "graycube") {
772
773                                 fh.style.left = "0px";
774                                 ft.style.left = "0px";
775                                 if (fc) fc.style.left = "0px";
776                                 if (ff) ff.style.left = "0px";
777
778                                 if (theme == "compact") fhdr.style.left = "10px";
779
780                         } else {
781                                 fh.style.left = "20px";
782                                 ft.style.left = "20px";
783                                 if (fc) fc.style.left = "20px";
784
785                         }
786
787                         query = "?op=rpc&subop=setpref&key=_COLLAPSED_FEEDLIST&value=true";
788
789                         new Ajax.Request("backend.php", { parameters: query });
790
791                 }
792         } catch (e) {
793                 exception_error("collapse_feedlist", e);
794         }
795 }
796
797 function viewModeChanged() {
798         cache_flush();
799         return viewCurrentFeed(0, '')
800 }
801
802 function viewLimitChanged() {
803         cache_flush();
804         return viewCurrentFeed(0, '')
805 }
806
807 /* function adjustArticleScore(id, score) {
808         try {
809
810                 var pr = prompt(__("Assign score to article:"), score);
811
812                 if (pr != undefined) {
813                         var query = "?op=rpc&subop=setScore&id=" + id + "&score=" + pr;
814
815                         new Ajax.Request("backend.php", {
816                         parameters: query,
817                         onComplete: function(transport) {
818                                         viewCurrentFeed();
819                                 } });
820
821                 }
822         } catch (e) {
823                 exception_error("adjustArticleScore", e);
824         }
825 } */
826
827 function rescoreCurrentFeed() {
828
829         var actid = getActiveFeedId();
830
831         if (activeFeedIsCat() || actid < 0 || tagsAreDisplayed()) {
832                 alert(__("You can't rescore this kind of feed."));
833                 return;
834         }       
835
836         if (!actid) {
837                 alert(__("Please select some feed first."));
838                 return;
839         }
840
841         var fn = getFeedName(actid);
842         var pr = __("Rescore articles in %s?").replace("%s", fn);
843
844         if (confirm(pr)) {
845                 notify_progress("Rescoring articles...");
846
847                 var query = "?op=pref-feeds&subop=rescore&quiet=1&ids=" + actid;
848
849                 new Ajax.Request("backend.php", {
850                         parameters: query,
851                         onComplete: function(transport) {
852                                 viewCurrentFeed();
853                         } });
854         }
855 }
856
857 function hotkey_handler(e) {
858
859         try {
860
861                 var keycode;
862                 var shift_key = false;
863
864                 var cmdline = $('cmdline');
865                 var feedlist = $('feedList');
866
867                 try {
868                         shift_key = e.shiftKey;
869                 } catch (e) {
870
871                 }
872         
873                 if (window.event) {
874                         keycode = window.event.keyCode;
875                 } else if (e) {
876                         keycode = e.which;
877                 }
878
879                 var keychar = String.fromCharCode(keycode);
880
881                 if (keycode == 27) { // escape
882                         if (Element.visible("hotkey_help_overlay")) {
883                                 Element.hide("hotkey_help_overlay");
884                         }
885                         hotkey_prefix = false;
886                         closeInfoBox();
887                 } 
888
889                 if (!hotkeys_enabled) {
890                         console.log("hotkeys disabled");
891                         return;
892                 }
893
894                 if (keycode == 16) return; // ignore lone shift
895
896                 if ((keycode == 70 || keycode == 67 || keycode == 71) 
897                                 && !hotkey_prefix) {
898
899                         var date = new Date();
900                         var ts = Math.round(date.getTime() / 1000);
901
902                         hotkey_prefix = keycode;
903                         hotkey_prefix_pressed = ts;
904
905                         cmdline.innerHTML = keychar;
906                         Element.show(cmdline);
907
908                         console.log("KP: PREFIX=" + keycode + " CHAR=" + keychar + " TS=" + ts);
909                         return true;
910                 }
911
912                 if (Element.visible("hotkey_help_overlay")) {
913                         Element.hide("hotkey_help_overlay");
914                 }
915
916                 /* Global hotkeys */
917
918                 Element.hide(cmdline);
919
920                 if (!hotkey_prefix) {
921
922                         if ((keycode == 191 || keychar == '?') && shift_key) { // ?
923                                 if (!Element.visible("hotkey_help_overlay")) {
924                                         //Element.show("hotkey_help_overlay");
925                                         Effect.Appear("hotkey_help_overlay", {duration : 0.3});
926                                 } else {
927                                         Element.hide("hotkey_help_overlay");
928                                 }
929                                 return false;
930                         }
931
932                         if (keycode == 191 || keychar == '/') { // /
933                                 displayDlg("search", getActiveFeedId() + ":" + activeFeedIsCat(), 
934                                         function() { 
935                                                 document.forms['search_form'].query.focus();
936                                         });
937                                 return false;
938                         }
939
940                         if (keycode == 82 && shift_key) { // R
941                                 scheduleFeedUpdate(true);
942                                 return;
943                         }
944
945                         if (keycode == 74) { // j
946                                 var feed = getActiveFeedId();
947                                 var new_feed = getRelativeFeedId2(feed, activeFeedIsCat(), 'prev');
948 //                              alert(feed + " IC: " + activeFeedIsCat() + " => " + new_feed);
949                                 if (new_feed) {
950                                         var is_cat = new_feed.match("CAT:");
951                                         if (is_cat) {
952                                                 new_feed = new_feed.replace("CAT:", "");
953                                                 viewCategory(new_feed);
954                                         } else {
955                                                 viewfeed(new_feed, '', false);
956                                         }
957                                 }
958                                 return;
959                         }
960         
961                         if (keycode == 75) { // k
962                                 var feed = getActiveFeedId();
963                                 var new_feed = getRelativeFeedId2(feed, activeFeedIsCat(), 'next');
964 //                              alert(feed + " IC: " + activeFeedIsCat() + " => " + new_feed);
965                                 if (new_feed) {
966                                         var is_cat = new_feed.match("CAT:");
967                                         if (is_cat == "CAT:") {
968                                                 new_feed = new_feed.replace("CAT:", "");
969                                                 viewCategory(new_feed);
970                                         } else {
971                                                 viewfeed(new_feed, '', false);
972                                         }
973                                 }
974                                 return;
975                         }
976
977                         if (shift_key && keycode == 40) { // shift-down
978                                 catchupRelativeToArticle(1);
979                                 return;
980                         }
981
982                         if (shift_key && keycode == 38) { // shift-up
983                                 catchupRelativeToArticle(0);
984                                 return;
985                         }
986
987                         if (shift_key && keycode == 78) { // N
988                                 scrollArticle(50);      
989                                 return;
990                         }
991
992                         if (shift_key && keycode == 80) { // P
993                                 scrollArticle(-50);     
994                                 return;
995                         }
996
997                         if (keycode == 68 && shift_key) { // shift-D
998                                 if (isCdmMode()) {
999                                         //cdmDismissArticle(active_post_id);
1000                                         cdmDismissSelectedArticles();
1001                                 }
1002                         }
1003
1004                         if (keycode == 78 || keycode == 40) { // n, down
1005                                 if (typeof moveToPost != 'undefined') {
1006                                         moveToPost('next');
1007                                         return false;
1008                                 }
1009                         }
1010         
1011                         if (keycode == 80 || keycode == 38) { // p, up
1012                                 if (typeof moveToPost != 'undefined') {
1013                                         moveToPost('prev');
1014                                         return false;
1015                                 }
1016                         }
1017
1018                         if (keycode == 83 && shift_key) { // S
1019                                 var id = getActiveArticleId();
1020                                 if (id) {                               
1021                                         togglePub(id);
1022                                 }
1023                                 return;
1024                         }
1025
1026                         if (keycode == 83) { // s
1027                                 var id = getActiveArticleId();
1028                                 if (id) {                               
1029                                         toggleMark(id);
1030                                 }
1031                                 return;
1032                         }
1033
1034
1035                         if (keycode == 85) { // u
1036                                 var id = getActiveArticleId();
1037                                 if (id) {                               
1038                                         toggleUnread(id);
1039                                 }
1040                                 return;
1041                         }
1042
1043                         if (keycode == 84 && shift_key) { // T
1044                                 var id = getActiveArticleId();
1045                                 if (id) {
1046                                         editArticleTags(id, getActiveFeedId(), isCdmMode());
1047                                         return;
1048                                 }
1049                         }
1050
1051                         if (keycode == 9) { // tab
1052                                 var id = getArticleUnderPointer();
1053                                 if (id) {                               
1054                                         var cb = $("RCHK-" + id);
1055
1056                                         if (cb) {
1057                                                 cb.checked = !cb.checked;
1058                                                 toggleSelectRowById(cb, "RROW-" + id);
1059                                                 return false;
1060                                         }
1061                                 }
1062                         }
1063
1064                         if (keycode == 79) { // o
1065                                 if (getActiveArticleId()) {
1066                                         openArticleInNewWindow(getActiveArticleId());
1067                                         return;
1068                                 }
1069                         }
1070
1071                         if (keycode == 81 && shift_key) { // Q
1072                                 if (typeof catchupAllFeeds != 'undefined') {
1073                                         catchupAllFeeds();
1074                                         return;
1075                                 }
1076                         }
1077
1078                         if (keycode == 88) { // x
1079                                 if (activeFeedIsCat()) {
1080                                         toggleCollapseCat(getActiveFeedId());
1081                                 }
1082                         }
1083                 }
1084
1085                 /* Prefix f */
1086
1087                 if (hotkey_prefix == 70) { // f 
1088
1089                         hotkey_prefix = false;
1090
1091                         if (keycode == 81) { // q
1092                                 if (getActiveFeedId()) {
1093                                         catchupCurrentFeed();
1094                                         return;
1095                                 }
1096                         }
1097
1098                         if (keycode == 82) { // r
1099                                 if (getActiveFeedId()) {
1100                                         viewfeed(getActiveFeedId(), "ForceUpdate", activeFeedIsCat());
1101                                         return;
1102                                 }
1103                         }
1104
1105                         if (keycode == 65) { // a
1106                                 toggleDispRead();
1107                                 return false;
1108                         }
1109
1110                         if (keycode == 85 && shift_key) { // U
1111                                 scheduleFeedUpdate(true);
1112                                 return false;
1113                         }
1114
1115                         if (keycode == 85) { // u
1116                                 if (getActiveFeedId()) {
1117                                         viewfeed(getActiveFeedId(), "ForceUpdate");
1118                                         return false;
1119                                 }
1120                         }
1121
1122                         if (keycode == 69) { // e
1123                                 editFeedDlg(getActiveFeedId());
1124                                 return false;
1125                         }
1126
1127                         if (keycode == 83) { // s
1128                                 quickAddFeed();
1129                                 return false;
1130                         }
1131
1132                         if (keycode == 67 && shift_key) { // C
1133                                 if (typeof catchupAllFeeds != 'undefined') {
1134                                         catchupAllFeeds();
1135                                         return false;
1136                                 }
1137                         }
1138
1139                         if (keycode == 67) { // c
1140                                 if (getActiveFeedId()) {
1141                                         catchupCurrentFeed();
1142                                         return false;
1143                                 }
1144                         }
1145
1146                         if (keycode == 87) { // w
1147                                 feeds_sort_by_unread = !feeds_sort_by_unread;
1148                                 return resort_feedlist();
1149                         }
1150
1151                         if (keycode == 72) { // h
1152                                 hideReadHeadlines();
1153                                 return;
1154                         }
1155
1156                         if (keycode == 88) { // x
1157                                 reverseHeadlineOrder();
1158                                 return;
1159                         }
1160                 }
1161
1162                 /* Prefix c */
1163
1164                 if (hotkey_prefix == 67) { // c
1165                         hotkey_prefix = false;
1166
1167                         if (keycode == 70) { // f
1168                                 displayDlg('quickAddFilter', '',
1169                                    function () {document.forms['filter_add_form'].reg_exp.focus();});
1170                                 return false;
1171                         }
1172
1173                         if (keycode == 76) { // l
1174                                 addLabel();
1175                                 return false;
1176                         }
1177
1178                         if (keycode == 83) { // s
1179                                 if (typeof collapse_feedlist != 'undefined') {
1180                                         collapse_feedlist();
1181                                         return false;
1182                                 }
1183                         }
1184
1185                         if (keycode == 77) { // m
1186                                 feedlist_sortable_enabled = !feedlist_sortable_enabled;
1187                                 if (feedlist_sortable_enabled) {
1188                                         notify_info("Category reordering enabled");
1189                                         toggle_sortable_feedlist(true);
1190                                 } else {
1191                                         notify_info("Category reordering disabled");
1192                                         toggle_sortable_feedlist(false);
1193                                 }
1194                         }
1195
1196                         if (keycode == 78) { // n
1197                                 catchupRelativeToArticle(1);
1198                                 return;
1199                         }
1200
1201                         if (keycode == 80) { // p
1202                                 catchupRelativeToArticle(0);
1203                                 return;
1204                         }
1205
1206
1207                 }
1208
1209                 /* Prefix g */
1210
1211                 if (hotkey_prefix == 71) { // g
1212
1213                         hotkey_prefix = false;
1214
1215
1216                         if (keycode == 65) { // a
1217                                 viewfeed(-4);
1218                                 return false;
1219                         }
1220
1221                         if (keycode == 83) { // s
1222                                 viewfeed(-1);
1223                                 return false;
1224                         }
1225
1226                         if (keycode == 80 && shift_key) { // P
1227                                 gotoPreferences();
1228                                 return false;
1229                         }
1230
1231                         if (keycode == 80) { // p
1232                                 viewfeed(-2);
1233                                 return false;
1234                         }
1235
1236                         if (keycode == 70) { // f
1237                                 viewfeed(-3);
1238                                 return false;
1239                         }
1240
1241                         if (keycode == 84 && shift_key) { // T
1242                                 toggleTags();
1243                                 return false;
1244                         }
1245                 }
1246
1247                 /* Cmd */
1248
1249                 if (hotkey_prefix == 224 || hotkey_prefix == 91) { // f 
1250                         hotkey_prefix = false;
1251                         return;
1252                 }
1253
1254                 if (hotkey_prefix) {
1255                         console.log("KP: PREFIX=" + hotkey_prefix + " CODE=" + keycode + " CHAR=" + keychar);
1256                 } else {
1257                         console.log("KP: CODE=" + keycode + " CHAR=" + keychar);
1258                 }
1259
1260
1261         } catch (e) {
1262                 exception_error("hotkey_handler", e);
1263         }
1264 }
1265
1266 function feedsSortByUnread() {
1267         return feeds_sort_by_unread;
1268 }
1269
1270 function inPreferences() {
1271         return false;
1272 }
1273
1274 function reverseHeadlineOrder() {
1275         try {
1276
1277                 var query_str = "?op=rpc&subop=togglepref&key=REVERSE_HEADLINES";
1278
1279                 new Ajax.Request("backend.php", {
1280                         parameters: query_str,
1281                         onComplete: function(transport) { 
1282                                         viewCurrentFeed();
1283                                 } });
1284
1285         } catch (e) {
1286                 exception_error("reverseHeadlineOrder", e);
1287         }
1288 }
1289
1290 function showFeedsWithErrors() {
1291         displayDlg('feedUpdateErrors');
1292 }
1293