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