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