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