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