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