]> git.wh0rd.org Git - tt-rss.git/blob - js/tt-rss.js
1270f6e3337f6c8e044aa1be01702eb33fd8ffc5
[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["search_dialog"] = function() {
303                 search();
304         };
305         hotkey_actions["toggle_mark"] = function() {
306                 selectionToggleMarked(undefined, false, true);
307         };
308         hotkey_actions["toggle_publ"] = function() {
309                 selectionTogglePublished(undefined, false, true);
310         };
311         hotkey_actions["toggle_unread"] = function() {
312                 selectionToggleUnread(undefined, false, true);
313         };
314         hotkey_actions["edit_tags"] = function() {
315                 const id = getActiveArticleId();
316                 if (id) {
317                         editArticleTags(id);
318                 }
319         }
320         hotkey_actions["open_in_new_window"] = function() {
321                 if (getActiveArticleId()) {
322                         openArticleInNewWindow(getActiveArticleId());
323                 }
324         };
325         hotkey_actions["catchup_below"] = function() {
326                 catchupRelativeToArticle(1);
327         };
328         hotkey_actions["catchup_above"] = function() {
329                 catchupRelativeToArticle(0);
330         };
331         hotkey_actions["article_scroll_down"] = function() {
332                 scrollArticle(40);
333         };
334         hotkey_actions["article_scroll_up"] = function() {
335                 scrollArticle(-40);
336         };
337         hotkey_actions["close_article"] = function() {
338                 closeArticlePanel();
339         };
340         hotkey_actions["email_article"] = function() {
341                 if (typeof emailArticle != "undefined") {
342                         emailArticle();
343                 } else if (typeof mailtoArticle != "undefined") {
344                         mailtoArticle();
345                 } else {
346                         alert(__("Please enable mail plugin first."));
347                 }
348         };
349         hotkey_actions["select_all"] = function() {
350                 selectArticles('all');
351         };
352         hotkey_actions["select_unread"] = function() {
353                 selectArticles('unread');
354         };
355         hotkey_actions["select_marked"] = function() {
356                 selectArticles('marked');
357         };
358         hotkey_actions["select_published"] = function() {
359                 selectArticles('published');
360         };
361         hotkey_actions["select_invert"] = function() {
362                 selectArticles('invert');
363         };
364         hotkey_actions["select_none"] = function() {
365                 selectArticles('none');
366         };
367         hotkey_actions["feed_refresh"] = function() {
368                 if (getActiveFeedId() != undefined) {
369                         viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat()});
370                         return;
371                 }
372         };
373         hotkey_actions["feed_unhide_read"] = function() {
374                 toggleDispRead();
375         };
376         hotkey_actions["feed_subscribe"] = function() {
377                 quickAddFeed();
378         };
379         hotkey_actions["feed_debug_update"] = function() {
380                 if (!activeFeedIsCat() && parseInt(getActiveFeedId()) > 0) {
381                         window.open("backend.php?op=feeds&method=update_debugger&feed_id=" + getActiveFeedId() +
382                                 "&csrf_token=" + getInitParam("csrf_token"));
383                 } else {
384                         alert("You can't debug this kind of feed.");
385                 }
386         };
387
388         hotkey_actions["feed_debug_viewfeed"] = function() {
389                 viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat(), viewfeed_debug: true});
390         };
391
392         hotkey_actions["feed_edit"] = function() {
393                 if (activeFeedIsCat())
394                         alert(__("You can't edit this kind of feed."));
395                 else
396                         editFeed(getActiveFeedId());
397         };
398         hotkey_actions["feed_catchup"] = function() {
399                 if (getActiveFeedId() != undefined) {
400                         catchupCurrentFeed();
401                         return;
402                 }
403         };
404         hotkey_actions["feed_reverse"] = function() {
405                 reverseHeadlineOrder();
406         };
407         hotkey_actions["feed_toggle_vgroup"] = function() {
408                 xhrPost("backend.php", {op: "rpc", method: "togglepref", key: "VFEED_GROUP_BY_FEED"}, () => {
409                         viewCurrentFeed();
410                 })
411         };
412         hotkey_actions["catchup_all"] = function() {
413                 catchupAllFeeds();
414         };
415         hotkey_actions["cat_toggle_collapse"] = function() {
416                 if (activeFeedIsCat()) {
417                         dijit.byId("feedTree").collapseCat(getActiveFeedId());
418                         return;
419                 }
420         };
421         hotkey_actions["goto_all"] = function() {
422                 viewfeed({feed: -4});
423         };
424         hotkey_actions["goto_fresh"] = function() {
425                 viewfeed({feed: -3});
426         };
427         hotkey_actions["goto_marked"] = function() {
428                 viewfeed({feed: -1});
429         };
430         hotkey_actions["goto_published"] = function() {
431                 viewfeed({feed: -2});
432         };
433         hotkey_actions["goto_tagcloud"] = function() {
434                 displayDlg(__("Tag cloud"), "printTagCloud");
435         };
436         hotkey_actions["goto_prefs"] = function() {
437                 gotoPreferences();
438         };
439         hotkey_actions["select_article_cursor"] = function() {
440                 const id = getArticleUnderPointer();
441                 if (id) {
442                         const row = $("RROW-" + id);
443
444                         if (row) {
445                                 const cb = dijit.getEnclosingWidget(
446                                         row.getElementsByClassName("rchk")[0]);
447
448                                 if (cb) {
449                                         cb.attr("checked", !cb.attr("checked"));
450                                         toggleSelectRowById(cb, "RROW-" + id);
451                                         return false;
452                                 }
453                         }
454                 }
455         };
456         hotkey_actions["create_label"] = function() {
457                 addLabel();
458         };
459         hotkey_actions["create_filter"] = function() {
460                 quickAddFilter();
461         };
462         hotkey_actions["collapse_sidebar"] = function() {
463                 collapse_feedlist();
464         };
465         hotkey_actions["toggle_embed_original"] = function() {
466                 if (typeof embedOriginalArticle != "undefined") {
467                         if (getActiveArticleId())
468                                 embedOriginalArticle(getActiveArticleId());
469                 } else {
470                         alert(__("Please enable embed_original plugin first."));
471                 }
472         };
473         hotkey_actions["toggle_widescreen"] = function() {
474                 if (!isCdmMode()) {
475                         _widescreen_mode = !_widescreen_mode;
476
477                         // reset stored sizes because geometry changed
478                         setCookie("ttrss_ci_width", 0);
479                         setCookie("ttrss_ci_height", 0);
480
481                         switchPanelMode(_widescreen_mode);
482                 } else {
483                         alert(__("Widescreen is not available in combined mode."));
484                 }
485         };
486         hotkey_actions["help_dialog"] = function() {
487                 helpDialog("main");
488         };
489         hotkey_actions["toggle_combined_mode"] = function() {
490                 notify_progress("Loading, please wait...");
491
492                 const value = isCdmMode() ? "false" : "true";
493
494                 xhrPost("backend.php", {op: "rpc", method: "setpref", key: "COMBINED_DISPLAY_MODE", value: value}, () => {
495             setInitParam("combined_display_mode",
496                 !getInitParam("combined_display_mode"));
497
498             closeArticlePanel();
499             viewCurrentFeed();
500                 })
501         };
502 }
503
504 function init_second_stage() {
505         updateFeedList();
506         closeArticlePanel();
507
508         if (parseInt(getCookie("ttrss_fh_width")) > 0) {
509                 dijit.byId("feeds-holder").domNode.setStyle(
510                         {width: getCookie("ttrss_fh_width") + "px" });
511         }
512
513         dijit.byId("main").resize();
514
515         var tmph = dojo.connect(dijit.byId('feeds-holder'), 'resize',
516                 function (args) {
517                         if (args && args.w >= 0) {
518                                 setCookie("ttrss_fh_width", args.w, getInitParam("cookie_lifetime"));
519                         }
520         });
521
522         var tmph = dojo.connect(dijit.byId('content-insert'), 'resize',
523                 function (args) {
524                         if (args && args.w >= 0 && args.h >= 0) {
525                                 setCookie("ttrss_ci_width", args.w, getInitParam("cookie_lifetime"));
526                                 setCookie("ttrss_ci_height", args.h, getInitParam("cookie_lifetime"));
527                         }
528         });
529
530         delCookie("ttrss_test");
531
532         const toolbar = document.forms["main_toolbar_form"];
533
534         dijit.getEnclosingWidget(toolbar.view_mode).attr('value',
535                 getInitParam("default_view_mode"));
536
537         dijit.getEnclosingWidget(toolbar.order_by).attr('value',
538                 getInitParam("default_view_order_by"));
539
540         const hash_feed_id = hash_get('f');
541         const hash_feed_is_cat = hash_get('c') == "1";
542
543         if (hash_feed_id != undefined) {
544                 setActiveFeedId(hash_feed_id, hash_feed_is_cat);
545         }
546
547         loading_set_progress(50);
548
549         // can't use cache_clear() here because viewfeed might not have initialized yet
550         if ('sessionStorage' in window && window['sessionStorage'] !== null)
551                 sessionStorage.clear();
552
553         /*const hotkeys = getInitParam("hotkeys");
554         const tmp = [];
555
556         for (const sequence in hotkeys[1]) {
557                 const filtered = sequence.replace(/\|.*$/, "");
558                 tmp[filtered] = hotkeys[1][sequence];
559         }
560
561         hotkeys[1] = tmp;
562         setInitParam("hotkeys", hotkeys);*/
563
564         _widescreen_mode = getInitParam("widescreen");
565         switchPanelMode(_widescreen_mode);
566
567         console.log("second stage ok");
568
569         if (getInitParam("simple_update")) {
570                 console.log("scheduling simple feed updater...");
571                 window.setTimeout(update_random_feed, 30*1000);
572         }
573 }
574
575 function quickMenuGo(opid) {
576         switch (opid) {
577         case "qmcPrefs":
578                 gotoPreferences();
579                 break;
580         case "qmcLogout":
581                 gotoLogout();
582                 break;
583         case "qmcTagCloud":
584                 displayDlg(__("Tag cloud"), "printTagCloud");
585                 break;
586         case "qmcSearch":
587                 search();
588                 break;
589         case "qmcAddFeed":
590                 quickAddFeed();
591                 break;
592         case "qmcDigest":
593                 window.location.href = "backend.php?op=digest";
594                 break;
595         case "qmcEditFeed":
596                 if (activeFeedIsCat())
597                         alert(__("You can't edit this kind of feed."));
598                 else
599                         editFeed(getActiveFeedId());
600                 break;
601         case "qmcRemoveFeed":
602                 var actid = getActiveFeedId();
603
604                 if (activeFeedIsCat()) {
605                         alert(__("You can't unsubscribe from the category."));
606                         return;
607                 }
608
609                 if (!actid) {
610                         alert(__("Please select some feed first."));
611                         return;
612                 }
613
614                 var fn = getFeedName(actid);
615
616                 var pr = __("Unsubscribe from %s?").replace("%s", fn);
617
618                 if (confirm(pr)) {
619                         unsubscribeFeed(actid);
620                 }
621                 break;
622         case "qmcCatchupAll":
623                 catchupAllFeeds();
624                 break;
625         case "qmcShowOnlyUnread":
626                 toggleDispRead();
627                 break;
628         case "qmcToggleWidescreen":
629                 if (!isCdmMode()) {
630                         _widescreen_mode = !_widescreen_mode;
631
632                         // reset stored sizes because geometry changed
633                         setCookie("ttrss_ci_width", 0);
634                         setCookie("ttrss_ci_height", 0);
635
636                         switchPanelMode(_widescreen_mode);
637                 } else {
638                         alert(__("Widescreen is not available in combined mode."));
639                 }
640                 break;
641         case "qmcHKhelp":
642                 helpDialog("main");
643                 break;
644         default:
645                 console.log("quickMenuGo: unknown action: " + opid);
646         }
647 }
648
649 function toggleDispRead() {
650
651         const hide = !(getInitParam("hide_read_feeds") == "1");
652
653         xhrPost("backend.php", {op: "rpc", method: "setpref", key: "HIDE_READ_FEEDS", value: hide}, () => {
654         hideOrShowFeeds(hide);
655         setInitParam("hide_read_feeds", hide);
656         });
657 }
658
659 function parse_runtime_info(data) {
660
661         //console.log("parsing runtime info...");
662
663         for (const k in data) {
664                 const v = data[k];
665
666 //              console.log("RI: " + k + " => " + v);
667
668                 if (k == "dep_ts" && parseInt(getInitParam("dep_ts")) > 0) {
669                         if (parseInt(getInitParam("dep_ts")) < parseInt(v) && getInitParam("reload_on_ts_change")) {
670                                 window.location.reload();
671                         }
672                 }
673
674                 if (k == "daemon_is_running" && v != 1) {
675                         notify_error("<span onclick=\"explainError(1)\">Update daemon is not running.</span>", true);
676                         return;
677                 }
678
679                 if (k == "update_result") {
680                         const updatesIcon = dijit.byId("updatesIcon").domNode;
681
682                         if (v) {
683                                 Element.show(updatesIcon);
684                         } else {
685                                 Element.hide(updatesIcon);
686                         }
687                 }
688
689                 if (k == "daemon_stamp_ok" && v != 1) {
690                         notify_error("<span onclick=\"explainError(3)\">Update daemon is not updating feeds.</span>", true);
691                         return;
692                 }
693
694                 if (k == "max_feed_id" || k == "num_feeds") {
695                         if (init_params[k] != v) {
696                                 console.log("feed count changed, need to reload feedlist.");
697                                 updateFeedList();
698                         }
699                 }
700
701                 init_params[k] = v;
702                 notify('');
703         }
704
705         PluginHost.run(PluginHost.HOOK_RUNTIME_INFO_LOADED, data);
706 }
707
708 function collapse_feedlist() {
709         Element.toggle("feeds-holder");
710
711         const splitter = $("feeds-holder_splitter");
712
713         Element.visible("feeds-holder") ? splitter.show() : splitter.hide();
714
715         dijit.byId("main").resize();
716 }
717
718 function viewModeChanged() {
719         cache_clear();
720         return viewCurrentFeed('');
721 }
722
723 function hotkey_handler(e) {
724         if (e.target.nodeName == "INPUT" || e.target.nodeName == "TEXTAREA") return;
725
726     const action_name = keyevent_to_action(e);
727
728     if (action_name) {
729         const action_func = hotkey_actions[action_name];
730
731         if (action_func != null) {
732             action_func();
733             e.stopPropagation();
734             return false;
735         }
736         }
737 }
738
739 function inPreferences() {
740         return false;
741 }
742
743 function reverseHeadlineOrder() {
744
745         const toolbar = document.forms["main_toolbar_form"];
746         const order_by = dijit.getEnclosingWidget(toolbar.order_by);
747
748         let value = order_by.attr('value');
749
750         if (value == "date_reverse")
751                 value = "default";
752         else
753                 value = "date_reverse";
754
755         order_by.attr('value', value);
756
757         viewCurrentFeed();
758
759 }
760
761 function handle_rpc_json(transport, scheduled_call) {
762
763         const netalert_dijit = dijit.byId("net-alert");
764         let netalert = false;
765
766         if (netalert_dijit) netalert = netalert_dijit.domNode;
767
768         try {
769                 const reply = JSON.parse(transport.responseText);
770
771                 if (reply) {
772
773                         const error = reply['error'];
774
775                         if (error) {
776                                 const code = error['code'];
777                                 const msg = error['msg'];
778
779                                 console.warn("[handle_rpc_json] received fatal error " + code + "/" + msg);
780
781                                 if (code != 0) {
782                                         fatalError(code, msg);
783                                         return false;
784                                 }
785                         }
786
787                         const seq = reply['seq'];
788
789                         if (seq && get_seq() != seq) {
790                                 console.log("[handle_rpc_json] sequence mismatch: " + seq +
791                                         " (want: " + get_seq() + ")");
792                                 return true;
793                         }
794
795                         const message = reply['message'];
796
797                         if (message == "UPDATE_COUNTERS") {
798                                 console.log("need to refresh counters...");
799                                 setInitParam("last_article_id", -1);
800                                 request_counters(true);
801                         }
802
803                         const counters = reply['counters'];
804
805                         if (counters)
806                                 parse_counters(counters, scheduled_call);
807
808                         const runtime_info = reply['runtime-info'];
809
810                         if (runtime_info)
811                                 parse_runtime_info(runtime_info);
812
813                         if (netalert) netalert.hide();
814
815                         return reply;
816
817                 } else {
818             if (netalert)
819                 netalert.show();
820             else
821                 notify_error("Communication problem with server.");
822         }
823
824         } catch (e) {
825                 if (netalert)
826                         netalert.show();
827                 else
828                         notify_error("Communication problem with server.");
829
830                 console.error(e);
831         }
832
833         return false;
834 }
835
836 function switchPanelMode(wide) {
837         if (isCdmMode()) return;
838
839         const article_id = getActiveArticleId();
840
841         if (wide) {
842                 dijit.byId("headlines-wrap-inner").attr("design", 'sidebar');
843                 dijit.byId("content-insert").attr("region", "trailing");
844
845                 dijit.byId("content-insert").domNode.setStyle({width: '50%',
846                         height: 'auto',
847                         borderTopWidth: '0px' });
848
849                 if (parseInt(getCookie("ttrss_ci_width")) > 0) {
850                         dijit.byId("content-insert").domNode.setStyle(
851                                 {width: getCookie("ttrss_ci_width") + "px" });
852                 }
853
854                 $("headlines-frame").setStyle({ borderBottomWidth: '0px' });
855                 $("headlines-frame").addClassName("wide");
856
857         } else {
858
859                 dijit.byId("content-insert").attr("region", "bottom");
860
861                 dijit.byId("content-insert").domNode.setStyle({width: 'auto',
862                         height: '50%',
863                         borderTopWidth: '0px'});
864
865                 if (parseInt(getCookie("ttrss_ci_height")) > 0) {
866                         dijit.byId("content-insert").domNode.setStyle(
867                                 {height: getCookie("ttrss_ci_height") + "px" });
868                 }
869
870                 $("headlines-frame").setStyle({ borderBottomWidth: '1px' });
871                 $("headlines-frame").removeClassName("wide");
872
873         }
874
875         closeArticlePanel();
876
877         if (article_id) view(article_id);
878
879         xhrPost("backend.php", {op: "rpc", method: "setpanelmode", wide: wide ? 1 : 0});
880 }
881
882 function update_random_feed() {
883         console.log("in update_random_feed");
884
885         xhrPost("backend.php", { op: "rpc", method: "updateRandomFeed" }, (transport) => {
886                 handle_rpc_json(transport, true);
887                 window.setTimeout(update_random_feed, 30*1000);
888         });
889 }
890
891 function hash_get(key) {
892         const kv = window.location.hash.substring(1).toQueryParams();
893         return kv[key];
894 }
895 function hash_set(key, value) {
896         const kv = window.location.hash.substring(1).toQueryParams();
897         kv[key] = value;
898         window.location.hash = $H(kv).toQueryString();
899 }