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