]> git.wh0rd.org Git - tt-rss.git/blob - js/tt-rss.js
hotkey_handler: one more tiny tweak
[tt-rss.git] / js / tt-rss.js
1 var global_unread = -1;
2 var hotkey_prefix = false;
3 var hotkey_prefix_pressed = false;
4 var hotkey_actions = {};
5 var _widescreen_mode = false;
6 var _rpc_seq = 0;
7 var _active_feed_id = 0;
8 var _active_feed_is_cat = false;
9
10 function next_seq() {
11         _rpc_seq += 1;
12         return _rpc_seq;
13 }
14
15 function get_seq() {
16         return _rpc_seq;
17 }
18
19 function activeFeedIsCat() {
20         return _active_feed_is_cat;
21 }
22
23 function getActiveFeedId() {
24         return _active_feed_id;
25 }
26
27 function setActiveFeedId(id, is_cat) {
28         hash_set('f', id);
29         hash_set('c', is_cat ? 1 : 0);
30
31         _active_feed_id = id;
32         _active_feed_is_cat = is_cat;
33
34         $("headlines-frame").setAttribute("feed-id", id);
35         $("headlines-frame").setAttribute("is-cat", is_cat ? 1 : 0);
36
37         selectFeed(id, is_cat);
38
39         PluginHost.run(PluginHost.HOOK_FEED_SET_ACTIVE, _active_article_id);
40 }
41
42
43 function updateFeedList() {
44         try {
45                 Element.show("feedlistLoading");
46
47                 resetCounterCache();
48
49                 if (dijit.byId("feedTree")) {
50                         dijit.byId("feedTree").destroyRecursive();
51                 }
52
53                 var store = new dojo.data.ItemFileWriteStore({
54                         url: "backend.php?op=pref_feeds&method=getfeedtree&mode=2"
55                 });
56
57                 var treeModel = new fox.FeedStoreModel({
58                         store: store,
59                         query: {
60                                 "type": getInitParam('enable_feed_cats') == 1 ? "category" : "feed"
61                         },
62                         rootId: "root",
63                         rootLabel: "Feeds",
64                         childrenAttrs: ["items"]
65                 });
66
67                 var tree = new fox.FeedTree({
68                         model: treeModel,
69                         onClick: function (item, node) {
70                                 var id = String(item.id);
71                                 var is_cat = id.match("^CAT:");
72                                 var feed = id.substr(id.indexOf(":") + 1);
73                                 viewfeed({feed: feed, is_cat: is_cat});
74                                 return false;
75                         },
76                         openOnClick: false,
77                         showRoot: false,
78                         persist: true,
79                         id: "feedTree",
80                 }, "feedTree");
81
82                 /*              var menu = new dijit.Menu({id: 'feedMenu'});
83
84                  menu.addChild(new dijit.MenuItem({
85                  label: "Simple menu item"
86                  }));
87
88                  //             menu.bindDomNode(tree.domNode); */
89
90                 var tmph = dojo.connect(dijit.byId('feedMenu'), '_openMyself', function (event) {
91                         console.log(dijit.getEnclosingWidget(event.target));
92                         dojo.disconnect(tmph);
93                 });
94
95                 $("feeds-holder").appendChild(tree.domNode);
96
97                 var tmph = dojo.connect(tree, 'onLoad', function () {
98                         dojo.disconnect(tmph);
99                         Element.hide("feedlistLoading");
100
101                         try {
102                                 feedlist_init();
103
104                                 loading_set_progress(25);
105                         } catch (e) {
106                                 exception_error(e);
107                         }
108                 });
109
110                 tree.startup();
111         } catch (e) {
112                 exception_error(e);
113         }
114 }
115
116 function catchupAllFeeds() {
117
118         var str = __("Mark all articles as read?");
119
120         if (getInitParam("confirm_feed_catchup") != 1 || confirm(str)) {
121
122                 var query_str = "backend.php?op=feeds&method=catchupAll";
123
124                 notify_progress("Marking all feeds as read...");
125
126                 //console.log("catchupAllFeeds Q=" + query_str);
127
128                 new Ajax.Request("backend.php", {
129                         parameters: query_str,
130                         onComplete: function(transport) {
131                                 request_counters(true);
132                                 viewCurrentFeed();
133                         } });
134
135                 global_unread = 0;
136                 updateTitle("");
137         }
138 }
139
140 function viewCurrentFeed(method) {
141         console.log("viewCurrentFeed: " + method);
142
143         if (getActiveFeedId() != undefined) {
144                 viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat(), method: method});
145         }
146         return false; // block unneeded form submits
147 }
148
149 function timeout() {
150         if (getInitParam("bw_limit") != "1") {
151                 request_counters();
152                 setTimeout(timeout, 60*1000);
153         }
154 }
155
156 function search() {
157         var query = "backend.php?op=feeds&method=search&param=" +
158                 param_escape(getActiveFeedId() + ":" + activeFeedIsCat());
159
160         if (dijit.byId("searchDlg"))
161                 dijit.byId("searchDlg").destroyRecursive();
162
163         dialog = new dijit.Dialog({
164                 id: "searchDlg",
165                 title: __("Search"),
166                 style: "width: 600px",
167                 execute: function() {
168                         if (this.validate()) {
169                                 _search_query = dojo.objectToQuery(this.attr('value'));
170                                 this.hide();
171                                 viewCurrentFeed();
172                         }
173                 },
174                 href: query});
175
176         dialog.show();
177 }
178
179 function updateTitle() {
180         var tmp = "Tiny Tiny RSS";
181
182         if (global_unread > 0) {
183                 tmp = "(" + global_unread + ") " + tmp;
184         }
185
186         if (window.fluid) {
187                 if (global_unread > 0) {
188                         window.fluid.dockBadge = global_unread;
189                 } else {
190                         window.fluid.dockBadge = "";
191                 }
192         }
193
194         document.title = tmp;
195 }
196
197 function genericSanityCheck() {
198         setCookie("ttrss_test", "TEST");
199
200         if (getCookie("ttrss_test") != "TEST") {
201                 return fatalError(2);
202         }
203
204         return true;
205 }
206
207
208 function init() {
209
210         window.onerror = function(message, filename, lineno, colno, error) {
211                 report_error(message, filename, lineno, colno, error);
212         };
213
214         require(["dojo/_base/kernel",
215                         "dojo/ready",
216                         "dojo/parser",
217                         "dojo/_base/loader",
218                         "dojo/_base/html",
219                         "dojo/query",
220                         "dijit/ProgressBar",
221                         "dijit/ColorPalette",
222                         "dijit/Dialog",
223                         "dijit/form/Button",
224                         "dijit/form/ComboButton",
225                         "dijit/form/CheckBox",
226                         "dijit/form/DropDownButton",
227                         "dijit/form/FilteringSelect",
228                         "dijit/form/Form",
229                         "dijit/form/RadioButton",
230                         "dijit/form/Select",
231                         "dijit/form/SimpleTextarea",
232                         "dijit/form/TextBox",
233                         "dijit/form/ComboBox",
234                         "dijit/form/ValidationTextBox",
235                         "dijit/InlineEditBox",
236                         "dijit/layout/AccordionContainer",
237                         "dijit/layout/BorderContainer",
238                         "dijit/layout/ContentPane",
239                         "dijit/layout/TabContainer",
240                         "dijit/PopupMenuItem",
241                         "dijit/Menu",
242                         "dijit/Toolbar",
243                         "dijit/Tree",
244                         "dijit/tree/dndSource",
245                         "dijit/tree/ForestStoreModel",
246                         "dojo/data/ItemFileWriteStore",
247                         "fox/FeedTree" ], function (dojo, ready, parser) {
248
249                         ready(function() {
250
251                                 try {
252                                         parser.parse();
253
254                                         if (!genericSanityCheck())
255                                                 return false;
256
257                                         loading_set_progress(30);
258
259                                         var a = document.createElement('audio');
260
261                                         var hasAudio = !!a.canPlayType;
262                                         var hasSandbox = "sandbox" in document.createElement("iframe");
263                                         var hasMp3 = !!(a.canPlayType && a.canPlayType('audio/mpeg;').replace(/no/, ''));
264                                         var clientTzOffset = new Date().getTimezoneOffset() * 60;
265
266                                         init_hotkey_actions();
267
268                                         new Ajax.Request("backend.php", {
269                                                 parameters: {
270                                                         op: "rpc", method: "sanityCheck", hasAudio: hasAudio,
271                                                         hasMp3: hasMp3,
272                                                         clientTzOffset: clientTzOffset,
273                                                         hasSandbox: hasSandbox
274                                                 },
275                                                 onComplete: function (transport) {
276                                                         backend_sanity_check_callback(transport);
277                                                 }
278                                         });
279                                 } catch (e) {
280                                         exception_error(e);
281                                 }
282
283                         });
284
285
286         });
287 }
288
289 function init_hotkey_actions() {
290         hotkey_actions["next_feed"] = function() {
291                 var rv = dijit.byId("feedTree").getNextFeed(
292                         getActiveFeedId(), activeFeedIsCat());
293
294                 if (rv) viewfeed({feed: rv[0], is_cat: rv[1], can_wait: true})
295         };
296         hotkey_actions["prev_feed"] = function() {
297                 var rv = dijit.byId("feedTree").getPreviousFeed(
298                         getActiveFeedId(), activeFeedIsCat());
299
300                 if (rv) viewfeed({feed: rv[0], is_cat: rv[1], can_wait: true})
301         };
302         hotkey_actions["next_article"] = function() {
303                 moveToPost('next');
304         };
305         hotkey_actions["prev_article"] = function() {
306                 moveToPost('prev');
307         };
308         hotkey_actions["next_article_noscroll"] = function() {
309                 moveToPost('next', true);
310         };
311         hotkey_actions["prev_article_noscroll"] = function() {
312                 moveToPost('prev', true);
313         };
314         hotkey_actions["next_article_noexpand"] = function() {
315                 moveToPost('next', true, true);
316         };
317         hotkey_actions["prev_article_noexpand"] = function() {
318                 moveToPost('prev', true, true);
319         };
320         hotkey_actions["collapse_article"] = function() {
321                 var id = getActiveArticleId();
322                 var elem = $("CICD-"+id);
323
324                 if (elem) {
325                         if (elem.visible()) {
326                                 cdmCollapseArticle(null, id);
327                         }
328                         else {
329                                 cdmExpandArticle(id);
330                         }
331                 }
332         };
333         hotkey_actions["toggle_expand"] = function() {
334                 var id = getActiveArticleId();
335                 var elem = $("CICD-"+id);
336
337                 if (elem) {
338                         if (elem.visible()) {
339                                 cdmCollapseArticle(null, id, false);
340                         }
341                         else {
342                                 cdmExpandArticle(id);
343                         }
344                 }
345         };
346         hotkey_actions["search_dialog"] = function() {
347                 search();
348         };
349         hotkey_actions["toggle_mark"] = function() {
350                 selectionToggleMarked(undefined, false, true);
351         };
352         hotkey_actions["toggle_publ"] = function() {
353                 selectionTogglePublished(undefined, false, true);
354         };
355         hotkey_actions["toggle_unread"] = function() {
356                 selectionToggleUnread(undefined, false, true);
357         };
358         hotkey_actions["edit_tags"] = function() {
359                 var id = getActiveArticleId();
360                 if (id) {
361                         editArticleTags(id);
362                 };
363         }
364         hotkey_actions["open_in_new_window"] = function() {
365                 if (getActiveArticleId()) {
366                         openArticleInNewWindow(getActiveArticleId());
367                         return;
368                 }
369         };
370         hotkey_actions["catchup_below"] = function() {
371                 catchupRelativeToArticle(1);
372         };
373         hotkey_actions["catchup_above"] = function() {
374                 catchupRelativeToArticle(0);
375         };
376         hotkey_actions["article_scroll_down"] = function() {
377                 var ctr = $("content_insert") ? $("content_insert") : $("headlines-frame");
378
379                 scrollArticle(40);
380         };
381         hotkey_actions["article_scroll_up"] = function() {
382                 var ctr = $("content_insert") ? $("content_insert") : $("headlines-frame");
383
384                 scrollArticle(-40);
385         };
386         hotkey_actions["close_article"] = function() {
387                 if (isCdmMode()) {
388                         if (!getInitParam("cdm_expanded")) {
389                                 cdmCollapseArticle(false, getActiveArticleId());
390                         }
391                 } else {
392                         closeArticlePanel();
393                 }
394         };
395         hotkey_actions["email_article"] = function() {
396                 if (typeof emailArticle != "undefined") {
397                         emailArticle();
398                 } else if (typeof mailtoArticle != "undefined") {
399                         mailtoArticle();
400                 } else {
401                         alert(__("Please enable mail plugin first."));
402                 }
403         };
404         hotkey_actions["select_all"] = function() {
405                 selectArticles('all');
406         };
407         hotkey_actions["select_unread"] = function() {
408                 selectArticles('unread');
409         };
410         hotkey_actions["select_marked"] = function() {
411                 selectArticles('marked');
412         };
413         hotkey_actions["select_published"] = function() {
414                 selectArticles('published');
415         };
416         hotkey_actions["select_invert"] = function() {
417                 selectArticles('invert');
418         };
419         hotkey_actions["select_none"] = function() {
420                 selectArticles('none');
421         };
422         hotkey_actions["feed_refresh"] = function() {
423                 if (getActiveFeedId() != undefined) {
424                         viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat()});
425                         return;
426                 }
427         };
428         hotkey_actions["feed_unhide_read"] = function() {
429                 toggleDispRead();
430         };
431         hotkey_actions["feed_subscribe"] = function() {
432                 quickAddFeed();
433         };
434         hotkey_actions["feed_debug_update"] = function() {
435                 if (!activeFeedIsCat() && parseInt(getActiveFeedId()) > 0) {
436                         window.open("backend.php?op=feeds&method=update_debugger&feed_id=" + getActiveFeedId() +
437                                 "&csrf_token=" + getInitParam("csrf_token"));
438                 } else {
439                         alert("You can't debug this kind of feed.");
440                 }
441         };
442
443         hotkey_actions["feed_debug_viewfeed"] = function() {
444                 viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat(), viewfeed_debug: true});
445         };
446
447         hotkey_actions["feed_edit"] = function() {
448                 if (activeFeedIsCat())
449                         alert(__("You can't edit this kind of feed."));
450                 else
451                         editFeed(getActiveFeedId());
452         };
453         hotkey_actions["feed_catchup"] = function() {
454                 if (getActiveFeedId() != undefined) {
455                         catchupCurrentFeed();
456                         return;
457                 }
458         };
459         hotkey_actions["feed_reverse"] = function() {
460                 reverseHeadlineOrder();
461         };
462         hotkey_actions["feed_toggle_vgroup"] = function() {
463                 var query_str = "?op=rpc&method=togglepref&key=VFEED_GROUP_BY_FEED";
464
465                 new Ajax.Request("backend.php", {
466                         parameters: query_str,
467                         onComplete: function(transport) {
468                                 viewCurrentFeed();
469                         } });
470
471         };
472         hotkey_actions["catchup_all"] = function() {
473                 catchupAllFeeds();
474         };
475         hotkey_actions["cat_toggle_collapse"] = function() {
476                 if (activeFeedIsCat()) {
477                         dijit.byId("feedTree").collapseCat(getActiveFeedId());
478                         return;
479                 }
480         };
481         hotkey_actions["goto_all"] = function() {
482                 viewfeed({feed: -4});
483         };
484         hotkey_actions["goto_fresh"] = function() {
485                 viewfeed({feed: -3});
486         };
487         hotkey_actions["goto_marked"] = function() {
488                 viewfeed({feed: -1});
489         };
490         hotkey_actions["goto_published"] = function() {
491                 viewfeed({feed: -2});
492         };
493         hotkey_actions["goto_tagcloud"] = function() {
494                 displayDlg(__("Tag cloud"), "printTagCloud");
495         };
496         hotkey_actions["goto_prefs"] = function() {
497                 gotoPreferences();
498         };
499         hotkey_actions["select_article_cursor"] = function() {
500                 var id = getArticleUnderPointer();
501                 if (id) {
502                         var row = $("RROW-" + id);
503
504                         if (row) {
505                                 var cb = dijit.getEnclosingWidget(
506                                         row.getElementsByClassName("rchk")[0]);
507
508                                 if (cb) {
509                                         cb.attr("checked", !cb.attr("checked"));
510                                         toggleSelectRowById(cb, "RROW-" + id);
511                                         return false;
512                                 }
513                         }
514                 }
515         };
516         hotkey_actions["create_label"] = function() {
517                 addLabel();
518         };
519         hotkey_actions["create_filter"] = function() {
520                 quickAddFilter();
521         };
522         hotkey_actions["collapse_sidebar"] = function() {
523                 collapse_feedlist();
524         };
525         hotkey_actions["toggle_embed_original"] = function() {
526                 if (typeof embedOriginalArticle != "undefined") {
527                         if (getActiveArticleId())
528                                 embedOriginalArticle(getActiveArticleId());
529                 } else {
530                         alert(__("Please enable embed_original plugin first."));
531                 }
532         };
533         hotkey_actions["toggle_widescreen"] = function() {
534                 if (!isCdmMode()) {
535                         _widescreen_mode = !_widescreen_mode;
536
537                         // reset stored sizes because geometry changed
538                         setCookie("ttrss_ci_width", 0);
539                         setCookie("ttrss_ci_height", 0);
540
541                         switchPanelMode(_widescreen_mode);
542                 } else {
543                         alert(__("Widescreen is not available in combined mode."));
544                 }
545         };
546         hotkey_actions["help_dialog"] = function() {
547                 helpDialog("main");
548         };
549         hotkey_actions["toggle_combined_mode"] = function() {
550                 notify_progress("Loading, please wait...");
551
552                 var value = isCdmMode() ? "false" : "true";
553                 var query = "?op=rpc&method=setpref&key=COMBINED_DISPLAY_MODE&value=" + value;
554
555                 new Ajax.Request("backend.php", {
556                         parameters: query,
557                         onComplete: function(transport) {
558                                 setInitParam("combined_display_mode",
559                                         !getInitParam("combined_display_mode"));
560
561                                 closeArticlePanel();
562                                 viewCurrentFeed();
563
564                         } });
565         };
566         hotkey_actions["toggle_cdm_expanded"] = function() {
567                 notify_progress("Loading, please wait...");
568
569                 var value = getInitParam("cdm_expanded") ? "false" : "true";
570                 var query = "?op=rpc&method=setpref&key=CDM_EXPANDED&value=" + value;
571
572                 new Ajax.Request("backend.php", {
573                         parameters: query,
574                         onComplete: function(transport) {
575                                 setInitParam("cdm_expanded", !getInitParam("cdm_expanded"));
576                                 viewCurrentFeed();
577                         } });
578         };
579 }
580
581 function init_second_stage() {
582         updateFeedList();
583         closeArticlePanel();
584
585         if (parseInt(getCookie("ttrss_fh_width")) > 0) {
586                 dijit.byId("feeds-holder").domNode.setStyle(
587                         {width: getCookie("ttrss_fh_width") + "px" });
588         }
589
590         dijit.byId("main").resize();
591
592         var tmph = dojo.connect(dijit.byId('feeds-holder'), 'resize',
593                 function (args) {
594                         if (args && args.w >= 0) {
595                                 setCookie("ttrss_fh_width", args.w, getInitParam("cookie_lifetime"));
596                         }
597         });
598
599         var tmph = dojo.connect(dijit.byId('content-insert'), 'resize',
600                 function (args) {
601                         if (args && args.w >= 0 && args.h >= 0) {
602                                 setCookie("ttrss_ci_width", args.w, getInitParam("cookie_lifetime"));
603                                 setCookie("ttrss_ci_height", args.h, getInitParam("cookie_lifetime"));
604                         }
605         });
606
607         delCookie("ttrss_test");
608
609         var toolbar = document.forms["main_toolbar_form"];
610
611         dijit.getEnclosingWidget(toolbar.view_mode).attr('value',
612                 getInitParam("default_view_mode"));
613
614         dijit.getEnclosingWidget(toolbar.order_by).attr('value',
615                 getInitParam("default_view_order_by"));
616
617         feeds_sort_by_unread = getInitParam("feeds_sort_by_unread") == 1;
618
619         var hash_feed_id = hash_get('f');
620         var hash_feed_is_cat = hash_get('c') == "1";
621
622         if (hash_feed_id != undefined) {
623                 setActiveFeedId(hash_feed_id, hash_feed_is_cat);
624         }
625
626         loading_set_progress(50);
627
628         // can't use cache_clear() here because viewfeed might not have initialized yet
629         if ('sessionStorage' in window && window['sessionStorage'] !== null)
630                 sessionStorage.clear();
631
632         var hotkeys = getInitParam("hotkeys");
633         var tmp = [];
634
635         for (sequence in hotkeys[1]) {
636                 filtered = sequence.replace(/\|.*$/, "");
637                 tmp[filtered] = hotkeys[1][sequence];
638         }
639
640         hotkeys[1] = tmp;
641         setInitParam("hotkeys", hotkeys);
642
643         _widescreen_mode = getInitParam("widescreen");
644         switchPanelMode(_widescreen_mode);
645
646         console.log("second stage ok");
647
648         if (getInitParam("simple_update")) {
649                 console.log("scheduling simple feed updater...");
650                 window.setTimeout(update_random_feed, 30*1000);
651         }
652 }
653
654 function quickMenuGo(opid) {
655         switch (opid) {
656         case "qmcPrefs":
657                 gotoPreferences();
658                 break;
659         case "qmcLogout":
660                 gotoLogout();
661                 break;
662         case "qmcTagCloud":
663                 displayDlg(__("Tag cloud"), "printTagCloud");
664                 break;
665         case "qmcSearch":
666                 search();
667                 break;
668         case "qmcAddFeed":
669                 quickAddFeed();
670                 break;
671         case "qmcDigest":
672                 window.location.href = "backend.php?op=digest";
673                 break;
674         case "qmcEditFeed":
675                 if (activeFeedIsCat())
676                         alert(__("You can't edit this kind of feed."));
677                 else
678                         editFeed(getActiveFeedId());
679                 break;
680         case "qmcRemoveFeed":
681                 var actid = getActiveFeedId();
682
683                 if (activeFeedIsCat()) {
684                         alert(__("You can't unsubscribe from the category."));
685                         return;
686                 }
687
688                 if (!actid) {
689                         alert(__("Please select some feed first."));
690                         return;
691                 }
692
693                 var fn = getFeedName(actid);
694
695                 var pr = __("Unsubscribe from %s?").replace("%s", fn);
696
697                 if (confirm(pr)) {
698                         unsubscribeFeed(actid);
699                 }
700                 break;
701         case "qmcCatchupAll":
702                 catchupAllFeeds();
703                 break;
704         case "qmcShowOnlyUnread":
705                 toggleDispRead();
706                 break;
707         case "qmcAddFilter":
708                 quickAddFilter();
709                 break;
710         case "qmcAddLabel":
711                 addLabel();
712                 break;
713         case "qmcRescoreFeed":
714                 rescoreCurrentFeed();
715                 break;
716         case "qmcToggleWidescreen":
717                 if (!isCdmMode()) {
718                         _widescreen_mode = !_widescreen_mode;
719
720                         // reset stored sizes because geometry changed
721                         setCookie("ttrss_ci_width", 0);
722                         setCookie("ttrss_ci_height", 0);
723
724                         switchPanelMode(_widescreen_mode);
725                 } else {
726                         alert(__("Widescreen is not available in combined mode."));
727                 }
728                 break;
729         case "qmcHKhelp":
730                 helpDialog("main");
731                 break;
732         default:
733                 console.log("quickMenuGo: unknown action: " + opid);
734         }
735 }
736
737 function toggleDispRead() {
738
739         var hide = !(getInitParam("hide_read_feeds") == "1");
740
741         hideOrShowFeeds(hide);
742
743         var query = "?op=rpc&method=setpref&key=HIDE_READ_FEEDS&value=" +
744                 param_escape(hide);
745
746         setInitParam("hide_read_feeds", hide);
747
748         new Ajax.Request("backend.php", {
749                 parameters: query,
750                 onComplete: function(transport) {
751                 } });
752
753 }
754
755 function parse_runtime_info(data) {
756
757         //console.log("parsing runtime info...");
758
759         for (k in data) {
760                 var v = data[k];
761
762 //              console.log("RI: " + k + " => " + v);
763
764                 if (k == "dep_ts" && parseInt(getInitParam("dep_ts")) > 0) {
765                         if (parseInt(getInitParam("dep_ts")) < parseInt(v) && getInitParam("reload_on_ts_change")) {
766                                 window.location.reload();
767                         }
768                 }
769
770                 if (k == "daemon_is_running" && v != 1) {
771                         notify_error("<span onclick=\"explainError(1)\">Update daemon is not running.</span>", true);
772                         return;
773                 }
774
775                 if (k == "update_result") {
776                         var updatesIcon = dijit.byId("updatesIcon").domNode;
777
778                         if (v) {
779                                 Element.show(updatesIcon);
780                         } else {
781                                 Element.hide(updatesIcon);
782                         }
783                 }
784
785                 if (k == "daemon_stamp_ok" && v != 1) {
786                         notify_error("<span onclick=\"explainError(3)\">Update daemon is not updating feeds.</span>", true);
787                         return;
788                 }
789
790                 if (k == "max_feed_id" || k == "num_feeds") {
791                         if (init_params[k] != v) {
792                                 console.log("feed count changed, need to reload feedlist.");
793                                 updateFeedList();
794                         }
795                 }
796
797                 init_params[k] = v;
798                 notify('');
799         }
800
801         PluginHost.run(PluginHost.HOOK_RUNTIME_INFO_LOADED, data);
802 }
803
804 function collapse_feedlist() {
805         Element.toggle("feeds-holder");
806
807         var splitter = $("feeds-holder_splitter");
808
809         Element.visible("feeds-holder") ? splitter.show() : splitter.hide();
810
811         dijit.byId("main").resize();
812 }
813
814 function viewModeChanged() {
815         cache_clear();
816         return viewCurrentFeed('');
817 }
818
819 function rescoreCurrentFeed() {
820
821         var actid = getActiveFeedId();
822
823         if (activeFeedIsCat() || actid < 0) {
824                 alert(__("You can't rescore this kind of feed."));
825                 return;
826         }
827
828         if (!actid) {
829                 alert(__("Please select some feed first."));
830                 return;
831         }
832
833         var fn = getFeedName(actid);
834         var pr = __("Rescore articles in %s?").replace("%s", fn);
835
836         if (confirm(pr)) {
837                 notify_progress("Rescoring articles...");
838
839                 var query = "?op=pref-feeds&method=rescore&quiet=1&ids=" + actid;
840
841                 new Ajax.Request("backend.php", {
842                         parameters: query,
843                         onComplete: function(transport) {
844                                 viewCurrentFeed();
845                         } });
846         }
847 }
848
849 function hotkey_handler(e) {
850
851         if (e.target.nodeName == "INPUT" || e.target.nodeName == "TEXTAREA") return;
852
853         var keycode = false;
854
855         var cmdline = $('cmdline');
856
857         if (window.event) {
858                 keycode = window.event.keyCode;
859         } else if (e) {
860                 keycode = e.which;
861         }
862
863         if (keycode == 27) { // escape
864                 hotkey_prefix = false;
865         }
866
867         if (keycode == 16) return; // ignore lone shift
868         if (keycode == 17) return; // ignore lone ctrl
869
870         var hotkeys = getInitParam("hotkeys");
871         var keychar = String.fromCharCode(keycode).toLowerCase();
872
873         if (!hotkey_prefix && hotkeys[0].indexOf(keychar) != -1) {
874
875                 var date = new Date();
876                 var ts = Math.round(date.getTime() / 1000);
877
878                 hotkey_prefix = keychar;
879                 hotkey_prefix_pressed = ts;
880
881                 cmdline.innerHTML = keychar;
882                 Element.show(cmdline);
883
884                 e.stopPropagation();
885
886                 // returning false here literally disables ctrl-c in browser lol (because C is a valid prefix)
887                 return true;
888         }
889
890         Element.hide(cmdline);
891
892         var hotkey = keychar.search(/[a-zA-Z0-9]/) != -1 ? keychar : "(" + keycode + ")";
893
894         // ensure ^*char notation
895         if (e.shiftKey) hotkey = "*" + hotkey;
896         if (e.ctrlKey) hotkey = "^" + hotkey;
897         if (e.altKey) hotkey = "+" + hotkey;
898         if (e.metaKey) hotkey = "%" + hotkey;
899
900         hotkey = hotkey_prefix ? hotkey_prefix + " " + hotkey : hotkey;
901         hotkey_prefix = false;
902
903         var hotkey_action = false;
904         var hotkeys = getInitParam("hotkeys");
905
906         for (sequence in hotkeys[1]) {
907                 if (sequence == hotkey) {
908                         hotkey_action = hotkeys[1][sequence];
909                         break;
910                 }
911         }
912
913         var action = hotkey_actions[hotkey_action];
914
915         if (action != null) {
916                 action();
917                 e.stopPropagation();
918                 return false;
919         }
920 }
921
922 function inPreferences() {
923         return false;
924 }
925
926 function reverseHeadlineOrder() {
927
928         var toolbar = document.forms["main_toolbar_form"];
929         var order_by = dijit.getEnclosingWidget(toolbar.order_by);
930
931         var value = order_by.attr('value');
932
933         if (value == "date_reverse")
934                 value = "default";
935         else
936                 value = "date_reverse";
937
938         order_by.attr('value', value);
939
940         viewCurrentFeed();
941
942 }
943
944 function handle_rpc_json(transport, scheduled_call) {
945
946         var netalert_dijit = dijit.byId("net-alert");
947         var netalert = false;
948
949         if (netalert_dijit) netalert = netalert_dijit.domNode;
950
951         try {
952                 var reply = JSON.parse(transport.responseText);
953
954                 if (reply) {
955
956                         var error = reply['error'];
957
958                         if (error) {
959                                 var code = error['code'];
960                                 var msg = error['msg'];
961
962                                 console.warn("[handle_rpc_json] received fatal error " + code + "/" + msg);
963
964                                 if (code != 0) {
965                                         fatalError(code, msg);
966                                         return false;
967                                 }
968                         }
969
970                         var seq = reply['seq'];
971
972                         if (seq) {
973                                 if (get_seq() != seq) {
974                                         console.log("[handle_rpc_json] sequence mismatch: " + seq +
975                                                 " (want: " + get_seq() + ")");
976                                         return true;
977                                 }
978                         }
979
980                         var message = reply['message'];
981
982                         if (message) {
983                                 if (message == "UPDATE_COUNTERS") {
984                                         console.log("need to refresh counters...");
985                                         setInitParam("last_article_id", -1);
986                                         request_counters(true);
987                                 }
988                         }
989
990                         var counters = reply['counters'];
991
992                         if (counters)
993                                 parse_counters(counters, scheduled_call);
994
995                         var runtime_info = reply['runtime-info'];
996
997                         if (runtime_info)
998                                 parse_runtime_info(runtime_info);
999
1000                         if (netalert) netalert.hide();
1001
1002                 } else {
1003                         if (netalert)
1004                                 netalert.show();
1005                         else
1006                                 notify_error("Communication problem with server.");
1007                 }
1008
1009         } catch (e) {
1010                 if (netalert)
1011                         netalert.show();
1012                 else
1013                         notify_error("Communication problem with server.");
1014
1015                 console.error(e);
1016         }
1017
1018         return true;
1019 }
1020
1021 function switchPanelMode(wide) {
1022         if (isCdmMode()) return;
1023
1024         article_id = getActiveArticleId();
1025
1026         if (wide) {
1027                 dijit.byId("headlines-wrap-inner").attr("design", 'sidebar');
1028                 dijit.byId("content-insert").attr("region", "trailing");
1029
1030                 dijit.byId("content-insert").domNode.setStyle({width: '50%',
1031                         height: 'auto',
1032                         borderTopWidth: '0px' });
1033
1034                 if (parseInt(getCookie("ttrss_ci_width")) > 0) {
1035                         dijit.byId("content-insert").domNode.setStyle(
1036                                 {width: getCookie("ttrss_ci_width") + "px" });
1037                 }
1038
1039                 $("headlines-frame").setStyle({ borderBottomWidth: '0px' });
1040                 $("headlines-frame").addClassName("wide");
1041
1042         } else {
1043
1044                 dijit.byId("content-insert").attr("region", "bottom");
1045
1046                 dijit.byId("content-insert").domNode.setStyle({width: 'auto',
1047                         height: '50%',
1048                         borderTopWidth: '0px'});
1049
1050                 if (parseInt(getCookie("ttrss_ci_height")) > 0) {
1051                         dijit.byId("content-insert").domNode.setStyle(
1052                                 {height: getCookie("ttrss_ci_height") + "px" });
1053                 }
1054
1055                 $("headlines-frame").setStyle({ borderBottomWidth: '1px' });
1056                 $("headlines-frame").removeClassName("wide");
1057
1058         }
1059
1060         closeArticlePanel();
1061
1062         if (article_id) view(article_id);
1063
1064         new Ajax.Request("backend.php", {
1065                 parameters: "op=rpc&method=setpanelmode&wide=" + (wide ? 1 : 0),
1066                 onComplete: function(transport) {
1067                         console.log(transport.responseText);
1068                 } });
1069 }
1070
1071 function update_random_feed() {
1072         console.log("in update_random_feed");
1073
1074         new Ajax.Request("backend.php", {
1075                 parameters: "op=rpc&method=updateRandomFeed",
1076                 onComplete: function(transport) {
1077                         handle_rpc_json(transport, true);
1078                         window.setTimeout(update_random_feed, 30*1000);
1079                 } });
1080 }
1081
1082 function hash_get(key) {
1083         kv = window.location.hash.substring(1).toQueryParams();
1084         return kv[key];
1085 }
1086 function hash_set(key, value) {
1087         kv = window.location.hash.substring(1).toQueryParams();
1088         kv[key] = value;
1089         window.location.hash = $H(kv).toQueryString();
1090 }