]> git.wh0rd.org Git - tt-rss.git/blob - js/prefs.js
some more eslint-related stuff
[tt-rss.git] / js / prefs.js
1 /* global dijit, __ */
2
3 let init_params = [];
4
5 let hotkey_prefix = false;
6 let hotkey_prefix_pressed = false;
7
8 let seq = "";
9
10 function notify_callback2(transport, sticky) {
11         notify_info(transport.responseText, sticky);
12 }
13
14 function updateFeedList() {
15
16         const user_search = $("feed_search");
17         let search = "";
18         if (user_search) { search = user_search.value; }
19
20         new Ajax.Request("backend.php", {
21                 parameters: "?op=pref-feeds&search=" + param_escape(search),
22                 onComplete: function(transport) {
23                         dijit.byId('feedConfigTab').attr('content', transport.responseText);
24                         selectTab("feedConfig", true);
25                         notify("");
26                 } });
27 }
28
29 function checkInactiveFeeds() {
30         new Ajax.Request("backend.php", {
31                 parameters: "?op=pref-feeds&method=getinactivefeeds",
32                 onComplete: function (transport) {
33                         if (parseInt(transport.responseText) > 0) {
34                                 Element.show(dijit.byId("pref_feeds_inactive_btn").domNode);
35                         }
36                 }
37         });
38 }
39
40 function updateUsersList(sort_key) {
41         const user_search = $("user_search");
42         let search = "";
43         if (user_search) {
44                 search = user_search.value;
45         }
46
47         const query = "?op=pref-users&sort=" +
48                 param_escape(sort_key) +
49                 "&search=" + param_escape(search);
50
51         new Ajax.Request("backend.php", {
52                 parameters: query,
53                 onComplete: function (transport) {
54                         dijit.byId('userConfigTab').attr('content', transport.responseText);
55                         selectTab("userConfig", true)
56                         notify("");
57                 }
58         });
59 }
60
61 function addUser() {
62         const login = prompt(__("Please enter login:"), "");
63
64         if (login == null) {
65                 return false;
66         }
67
68         if (login == "") {
69                 alert(__("Can't create user: no login specified."));
70                 return false;
71         }
72
73         notify_progress("Adding user...");
74
75         const query = "?op=pref-users&method=add&login=" +
76                 param_escape(login);
77
78         new Ajax.Request("backend.php", {
79                 parameters: query,
80                 onComplete: function (transport) {
81                         notify_callback2(transport);
82                         updateUsersList();
83                 }
84         });
85
86 }
87
88 function editUser(id) {
89
90         const query = "backend.php?op=pref-users&method=edit&id=" +
91                 param_escape(id);
92
93         if (dijit.byId("userEditDlg"))
94                 dijit.byId("userEditDlg").destroyRecursive();
95
96         var dialog = new dijit.Dialog({
97                 id: "userEditDlg",
98                 title: __("User Editor"),
99                 style: "width: 600px",
100                 execute: function () {
101                         if (this.validate()) {
102                                 notify_progress("Saving data...", true);
103
104                                 const query = dojo.formToQuery("user_edit_form");
105
106                                 new Ajax.Request("backend.php", {
107                                         parameters: query,
108                                         onComplete: function (transport) {
109                                                 dialog.hide();
110                                                 updateUsersList();
111                                         }
112                                 });
113                         }
114                 },
115                 href: query
116         });
117
118         dialog.show();
119 }
120
121 function editFilter(id) {
122
123         const query = "backend.php?op=pref-filters&method=edit&id=" + param_escape(id);
124
125         if (dijit.byId("feedEditDlg"))
126                 dijit.byId("feedEditDlg").destroyRecursive();
127
128         if (dijit.byId("filterEditDlg"))
129                 dijit.byId("filterEditDlg").destroyRecursive();
130
131         var dialog = new dijit.Dialog({
132                 id: "filterEditDlg",
133                 title: __("Edit Filter"),
134                 style: "width: 600px",
135
136                 test: function () {
137                         const query = "backend.php?" + dojo.formToQuery("filter_edit_form") + "&savemode=test";
138
139                         editFilterTest(query);
140                 },
141                 selectRules: function (select) {
142                         $$("#filterDlg_Matches input[type=checkbox]").each(function (e) {
143                                 e.checked = select;
144                                 if (select)
145                                         e.parentNode.addClassName("Selected");
146                                 else
147                                         e.parentNode.removeClassName("Selected");
148                         });
149                 },
150                 selectActions: function (select) {
151                         $$("#filterDlg_Actions input[type=checkbox]").each(function (e) {
152                                 e.checked = select;
153
154                                 if (select)
155                                         e.parentNode.addClassName("Selected");
156                                 else
157                                         e.parentNode.removeClassName("Selected");
158
159                         });
160                 },
161                 editRule: function (e) {
162                         const li = e.parentNode;
163                         const rule = li.getElementsByTagName("INPUT")[1].value;
164                         addFilterRule(li, rule);
165                 },
166                 editAction: function (e) {
167                         const li = e.parentNode;
168                         const action = li.getElementsByTagName("INPUT")[1].value;
169                         addFilterAction(li, action);
170                 },
171                 removeFilter: function () {
172                         const msg = __("Remove filter?");
173
174                         if (confirm(msg)) {
175                                 this.hide();
176
177                                 notify_progress("Removing filter...");
178
179                                 const id = this.attr('value').id;
180
181                                 const query = "?op=pref-filters&method=remove&ids=" +
182                                         param_escape(id);
183
184                                 new Ajax.Request("backend.php", {
185                                         parameters: query,
186                                         onComplete: function (transport) {
187                                                 updateFilterList();
188                                         }
189                                 });
190                         }
191                 },
192                 addAction: function () {
193                         addFilterAction();
194                 },
195                 addRule: function () {
196                         addFilterRule();
197                 },
198                 deleteAction: function () {
199                         $$("#filterDlg_Actions li[class*=Selected]").each(function (e) {
200                                 e.parentNode.removeChild(e)
201                         });
202                 },
203                 deleteRule: function () {
204                         $$("#filterDlg_Matches li[class*=Selected]").each(function (e) {
205                                 e.parentNode.removeChild(e)
206                         });
207                 },
208                 execute: function () {
209                         if (this.validate()) {
210
211                                 notify_progress("Saving data...", true);
212
213                                 const query = dojo.formToQuery("filter_edit_form");
214
215                                 console.log(query);
216
217                                 new Ajax.Request("backend.php", {
218                                         parameters: query,
219                                         onComplete: function (transport) {
220                                                 dialog.hide();
221                                                 updateFilterList();
222                                         }
223                                 });
224                         }
225                 },
226                 href: query
227         });
228
229         dialog.show();
230 }
231
232
233 function getSelectedLabels() {
234         const tree = dijit.byId("labelTree");
235         const items = tree.model.getCheckedItems();
236         const rv = [];
237
238         items.each(function(item) {
239                 rv.push(tree.model.store.getValue(item, 'bare_id'));
240         });
241
242         return rv;
243 }
244
245 function getSelectedUsers() {
246         return getSelectedTableRowIds("prefUserList");
247 }
248
249 function getSelectedFeeds() {
250         const tree = dijit.byId("feedTree");
251         const items = tree.model.getCheckedItems();
252         const rv = [];
253
254         items.each(function(item) {
255                 if (item.id[0].match("FEED:"))
256                         rv.push(tree.model.store.getValue(item, 'bare_id'));
257         });
258
259         return rv;
260 }
261
262 function getSelectedCategories() {
263         const tree = dijit.byId("feedTree");
264         const items = tree.model.getCheckedItems();
265         const rv = [];
266
267         items.each(function(item) {
268                 if (item.id[0].match("CAT:"))
269                         rv.push(tree.model.store.getValue(item, 'bare_id'));
270         });
271
272         return rv;
273 }
274
275 function getSelectedFilters() {
276         const tree = dijit.byId("filterTree");
277         const items = tree.model.getCheckedItems();
278         const rv = [];
279
280         items.each(function(item) {
281                 rv.push(tree.model.store.getValue(item, 'bare_id'));
282         });
283
284         return rv;
285
286 }
287
288 function removeSelectedLabels() {
289
290         const sel_rows = getSelectedLabels();
291
292         if (sel_rows.length > 0) {
293
294                 const ok = confirm(__("Remove selected labels?"));
295
296                 if (ok) {
297                         notify_progress("Removing selected labels...");
298
299                         const query = "?op=pref-labels&method=remove&ids="+
300                                 param_escape(sel_rows.toString());
301
302                         new Ajax.Request("backend.php", {
303                                 parameters: query,
304                                 onComplete: function(transport) {
305                                                 updateLabelList();
306                                         } });
307
308                 }
309         } else {
310                 alert(__("No labels are selected."));
311         }
312
313         return false;
314 }
315
316 function removeSelectedUsers() {
317
318         const sel_rows = getSelectedUsers();
319
320         if (sel_rows.length > 0) {
321
322                 const ok = confirm(__("Remove selected users? Neither default admin nor your account will be removed."));
323
324                 if (ok) {
325                         notify_progress("Removing selected users...");
326
327                         const query = "?op=pref-users&method=remove&ids=" +
328                                 param_escape(sel_rows.toString());
329
330                         new Ajax.Request("backend.php", {
331                                 parameters: query,
332                                 onComplete: function (transport) {
333                                         updateUsersList();
334                                 }
335                         });
336
337                 }
338
339         } else {
340                 alert(__("No users are selected."));
341         }
342
343         return false;
344 }
345
346 function removeSelectedFilters() {
347
348         const sel_rows = getSelectedFilters();
349
350         if (sel_rows.length > 0) {
351
352                 const ok = confirm(__("Remove selected filters?"));
353
354                 if (ok) {
355                         notify_progress("Removing selected filters...");
356
357                         const query = "?op=pref-filters&method=remove&ids=" +
358                                 param_escape(sel_rows.toString());
359
360                         new Ajax.Request("backend.php", {
361                                 parameters: query,
362                                 onComplete: function (transport) {
363                                         updateFilterList();
364                                 }
365                         });
366                 }
367         } else {
368                 alert(__("No filters are selected."));
369         }
370
371         return false;
372 }
373
374 function removeSelectedFeeds() {
375
376         const sel_rows = getSelectedFeeds();
377
378         if (sel_rows.length > 0) {
379
380                 const ok = confirm(__("Unsubscribe from selected feeds?"));
381
382                 if (ok) {
383
384                         notify_progress("Unsubscribing from selected feeds...", true);
385
386                         const query = "?op=pref-feeds&method=remove&ids=" +
387                                 param_escape(sel_rows.toString());
388
389                         console.log(query);
390
391                         new Ajax.Request("backend.php", {
392                                 parameters: query,
393                                 onComplete: function (transport) {
394                                         updateFeedList();
395                                 }
396                         });
397                 }
398
399         } else {
400                 alert(__("No feeds are selected."));
401         }
402
403         return false;
404 }
405
406 function editSelectedUser() {
407         const rows = getSelectedUsers();
408
409         if (rows.length == 0) {
410                 alert(__("No users are selected."));
411                 return;
412         }
413
414         if (rows.length > 1) {
415                 alert(__("Please select only one user."));
416                 return;
417         }
418
419         notify("");
420
421         editUser(rows[0]);
422 }
423
424 function resetSelectedUserPass() {
425
426         const rows = getSelectedUsers();
427
428         if (rows.length == 0) {
429                 alert(__("No users are selected."));
430                 return;
431         }
432
433         if (rows.length > 1) {
434                 alert(__("Please select only one user."));
435                 return;
436         }
437
438         const ok = confirm(__("Reset password of selected user?"));
439
440         if (ok) {
441                 notify_progress("Resetting password for selected user...");
442
443                 const id = rows[0];
444
445                 const query = "?op=pref-users&method=resetPass&id=" +
446                         param_escape(id);
447
448                 new Ajax.Request("backend.php", {
449                         parameters: query,
450                         onComplete: function (transport) {
451                                 notify_info(transport.responseText, true);
452                         }
453                 });
454
455         }
456 }
457
458 function selectedUserDetails() {
459
460         const rows = getSelectedUsers();
461
462         if (rows.length == 0) {
463                 alert(__("No users are selected."));
464                 return;
465         }
466
467         if (rows.length > 1) {
468                 alert(__("Please select only one user."));
469                 return;
470         }
471
472         const id = rows[0];
473
474         const query = "backend.php?op=pref-users&method=userdetails&id=" + id;
475
476         if (dijit.byId("userDetailsDlg"))
477                 dijit.byId("userDetailsDlg").destroyRecursive();
478
479         var dialog = new dijit.Dialog({
480                 id: "userDetailsDlg",
481                 title: __("User details"),
482                 style: "width: 600px",
483                 execute: function () {
484                         dialog.hide();
485                 },
486                 href: query
487         });
488
489         dialog.show();
490 }
491
492
493 function editSelectedFilter() {
494         const rows = getSelectedFilters();
495
496         if (rows.length == 0) {
497                 alert(__("No filters are selected."));
498                 return;
499         }
500
501         if (rows.length > 1) {
502                 alert(__("Please select only one filter."));
503                 return;
504         }
505
506         notify("");
507
508         editFilter(rows[0]);
509
510 }
511
512 function joinSelectedFilters() {
513         const rows = getSelectedFilters();
514
515         if (rows.length == 0) {
516                 alert(__("No filters are selected."));
517                 return;
518         }
519
520         const ok = confirm(__("Combine selected filters?"));
521
522         if (ok) {
523                 notify_progress("Joining filters...");
524
525                 const query = "?op=pref-filters&method=join&ids="+
526                         param_escape(rows.toString());
527
528                 console.log(query);
529
530                 new Ajax.Request("backend.php", {
531                         parameters: query,
532                         onComplete: function(transport) {
533                                         updateFilterList();
534                         } });
535         }
536 }
537
538 function editSelectedFeed() {
539         const rows = getSelectedFeeds();
540
541         if (rows.length == 0) {
542                 alert(__("No feeds are selected."));
543                 return;
544         }
545
546         if (rows.length > 1) {
547                 return editSelectedFeeds();
548         }
549
550         notify("");
551
552         editFeed(rows[0], {});
553
554 }
555
556 function editSelectedFeeds() {
557         const rows = getSelectedFeeds();
558
559         if (rows.length == 0) {
560                 alert(__("No feeds are selected."));
561                 return;
562         }
563
564         notify_progress("Loading, please wait...");
565
566         const query = "backend.php?op=pref-feeds&method=editfeeds&ids=" +
567                 param_escape(rows.toString());
568
569         console.log(query);
570
571         if (dijit.byId("feedEditDlg"))
572                 dijit.byId("feedEditDlg").destroyRecursive();
573
574         new Ajax.Request("backend.php", {
575                 parameters: query,
576                 onComplete: function (transport) {
577
578                         notify("");
579
580                         var dialog = new dijit.Dialog({
581                                 id: "feedEditDlg",
582                                 title: __("Edit Multiple Feeds"),
583                                 style: "width: 600px",
584                                 getChildByName: function (name) {
585                                         let rv = null;
586                                         this.getChildren().each(
587                                                 function (child) {
588                                                         if (child.name == name) {
589                                                                 rv = child;
590                                                                 return;
591                                                         }
592                                                 });
593                                         return rv;
594                                 },
595                                 toggleField: function (checkbox, elem, label) {
596                                         this.getChildByName(elem).attr('disabled', !checkbox.checked);
597
598                                         if ($(label))
599                                                 if (checkbox.checked)
600                                                         $(label).removeClassName('insensitive');
601                                                 else
602                                                         $(label).addClassName('insensitive');
603
604                                 },
605                                 execute: function () {
606                                         if (this.validate() && confirm(__("Save changes to selected feeds?"))) {
607                                                 let query = dojo.objectToQuery(this.attr('value'));
608
609                                                 /* Form.serialize ignores unchecked checkboxes */
610
611                                                 if (!query.match("&private=") &&
612                                                         this.getChildByName('private').attr('disabled') == false) {
613                                                         query = query + "&private=false";
614                                                 }
615
616                                                 try {
617                                                         if (!query.match("&cache_images=") &&
618                                                                 this.getChildByName('cache_images').attr('disabled') == false) {
619                                                                 query = query + "&cache_images=false";
620                                                         }
621                                                 } catch (e) {
622                                                 }
623
624                                                 try {
625                                                         if (!query.match("&hide_images=") &&
626                                                                 this.getChildByName('hide_images').attr('disabled') == false) {
627                                                                 query = query + "&hide_images=false";
628                                                         }
629                                                 } catch (e) {
630                                                 }
631
632                                                 if (!query.match("&include_in_digest=") &&
633                                                         this.getChildByName('include_in_digest').attr('disabled') == false) {
634                                                         query = query + "&include_in_digest=false";
635                                                 }
636
637                                                 if (!query.match("&always_display_enclosures=") &&
638                                                         this.getChildByName('always_display_enclosures').attr('disabled') == false) {
639                                                         query = query + "&always_display_enclosures=false";
640                                                 }
641
642                                                 if (!query.match("&mark_unread_on_update=") &&
643                                                         this.getChildByName('mark_unread_on_update').attr('disabled') == false) {
644                                                         query = query + "&mark_unread_on_update=false";
645                                                 }
646
647                                                 console.log(query);
648
649                                                 notify_progress("Saving data...", true);
650
651                                                 new Ajax.Request("backend.php", {
652                                                         parameters: query,
653                                                         onComplete: function (transport) {
654                                                                 dialog.hide();
655                                                                 updateFeedList();
656                                                         }
657                                                 });
658                                         }
659                                 },
660                                 content: transport.responseText
661                         });
662
663                         dialog.show();
664
665                 }
666         });
667 }
668
669 function opmlImportComplete(iframe) {
670         if (!iframe.contentDocument.body.innerHTML) return false;
671
672         Element.show(iframe);
673
674         notify('');
675
676         if (dijit.byId('opmlImportDlg'))
677                 dijit.byId('opmlImportDlg').destroyRecursive();
678
679         const content = iframe.contentDocument.body.innerHTML;
680
681         const dialog = new dijit.Dialog({
682                 id: "opmlImportDlg",
683                 title: __("OPML Import"),
684                 style: "width: 600px",
685                 onCancel: function () {
686             window.location.reload();
687                 },
688                 execute: function () {
689                         window.location.reload();
690                 },
691                 content: content
692         });
693
694         dialog.show();
695 }
696
697 function opmlImport() {
698
699         const opml_file = $("opml_file");
700
701         if (opml_file.value.length == 0) {
702                 alert(__("Please choose an OPML file first."));
703                 return false;
704         } else {
705                 notify_progress("Importing, please wait...", true);
706
707                 Element.show("upload_iframe");
708
709                 return true;
710         }
711 }
712
713
714 function updateFilterList() {
715         const user_search = $("filter_search");
716         let search = "";
717         if (user_search) { search = user_search.value; }
718
719         new Ajax.Request("backend.php", {
720                 parameters: "?op=pref-filters&search=" + param_escape(search),
721                 onComplete: function(transport) {
722                         dijit.byId('filterConfigTab').attr('content', transport.responseText);
723                         notify("");
724                 } });
725 }
726
727 function updateLabelList() {
728         new Ajax.Request("backend.php", {
729                 parameters: "?op=pref-labels",
730                 onComplete: function(transport) {
731                         dijit.byId('labelConfigTab').attr('content', transport.responseText);
732                         notify("");
733                 } });
734 }
735
736 function updatePrefsList() {
737         new Ajax.Request("backend.php", {
738                 parameters: "?op=pref-prefs",
739                 onComplete: function(transport) {
740                         dijit.byId('genConfigTab').attr('content', transport.responseText);
741                         notify("");
742                 } });
743 }
744
745 function updateSystemList() {
746         new Ajax.Request("backend.php", {
747                 parameters: "?op=pref-system",
748                 onComplete: function(transport) {
749                         dijit.byId('systemConfigTab').attr('content', transport.responseText);
750                         notify("");
751                 } });
752 }
753
754 function selectTab(id, noupdate) {
755         if (!noupdate) {
756                 notify_progress("Loading, please wait...");
757
758                 if (id == "feedConfig") {
759                         updateFeedList();
760                 } else if (id == "filterConfig") {
761                         updateFilterList();
762                 } else if (id == "labelConfig") {
763                         updateLabelList();
764                 } else if (id == "genConfig") {
765                         updatePrefsList();
766                 } else if (id == "userConfig") {
767                         updateUsersList();
768                 } else if (id == "systemConfig") {
769                         updateSystemList();
770                 }
771
772                 const tab = dijit.byId(id + "Tab");
773                 dijit.byId("pref-tabs").selectChild(tab);
774
775         }
776 }
777
778 function init_second_stage() {
779         document.onkeydown = pref_hotkey_handler;
780         loading_set_progress(50);
781         notify("");
782
783         let tab = getURLParam('tab');
784
785         if (tab) {
786                 tab = dijit.byId(tab + "Tab");
787                 if (tab) dijit.byId("pref-tabs").selectChild(tab);
788         }
789
790         const method = getURLParam('method');
791
792         if (method == 'editFeed') {
793                 const param = getURLParam('methodparam');
794
795                 window.setTimeout(function() { editFeed(param) }, 100);
796         }
797
798         setTimeout(hotkey_prefix_timeout, 5*1000);
799 }
800
801 function init() {
802         window.onerror = function (message, filename, lineno, colno, error) {
803                 report_error(message, filename, lineno, colno, error);
804         };
805
806         require(["dojo/_base/kernel",
807                 "dojo/ready",
808                 "dojo/parser",
809                 "dojo/_base/loader",
810                 "dojo/_base/html",
811                 "dijit/ColorPalette",
812                 "dijit/Dialog",
813                 "dijit/form/Button",
814                 "dijit/form/CheckBox",
815                 "dijit/form/DropDownButton",
816                 "dijit/form/FilteringSelect",
817         "dijit/form/MultiSelect",
818                 "dijit/form/Form",
819                 "dijit/form/RadioButton",
820                 "dijit/form/ComboButton",
821                 "dijit/form/Select",
822                 "dijit/form/SimpleTextarea",
823                 "dijit/form/TextBox",
824                 "dijit/form/ValidationTextBox",
825                 "dijit/InlineEditBox",
826                 "dijit/layout/AccordionContainer",
827                 "dijit/layout/AccordionPane",
828                 "dijit/layout/BorderContainer",
829                 "dijit/layout/ContentPane",
830                 "dijit/layout/TabContainer",
831                 "dijit/Menu",
832                 "dijit/ProgressBar",
833                 "dijit/Toolbar",
834                 "dijit/Tree",
835                 "dijit/tree/dndSource",
836                 "dojo/data/ItemFileWriteStore",
837                 "lib/CheckBoxStoreModel",
838                 "lib/CheckBoxTree",
839                 "fox/PrefFeedStore",
840                 "fox/PrefFilterStore",
841                 "fox/PrefFeedTree",
842                 "fox/PrefFilterTree",
843                 "fox/PrefLabelTree"], function (dojo, ready, parser) {
844
845                 ready(function () {
846                         try {
847                                 parser.parse();
848
849                                 loading_set_progress(50);
850
851                                 const clientTzOffset = new Date().getTimezoneOffset() * 60;
852
853                                 new Ajax.Request("backend.php", {
854                                         parameters: {
855                                                 op: "rpc", method: "sanityCheck",
856                                                 clientTzOffset: clientTzOffset
857                                         },
858                                         onComplete: function (transport) {
859                                                 backend_sanity_check_callback(transport);
860                                         }
861                                 });
862                         } catch (e) {
863                                 exception_error(e);
864                         }
865                 });
866         });
867 }
868
869
870 function validatePrefsReset() {
871         const ok = confirm(__("Reset to defaults?"));
872
873         if (ok) {
874
875                 const query = "?op=pref-prefs&method=resetconfig";
876                 console.log(query);
877
878                 new Ajax.Request("backend.php", {
879                         parameters: query,
880                         onComplete: function(transport) {
881                                 updatePrefsList();
882                                 notify_info(transport.responseText);
883                         } });
884
885         }
886
887         return false;
888
889 }
890
891 function pref_hotkey_handler(e) {
892
893         if (e.target.nodeName == "INPUT" || e.target.nodeName == "TEXTAREA") return;
894
895         let keycode = false;
896         let shift_key = false;
897
898         const cmdline = $('cmdline');
899
900         try {
901                 shift_key = e.shiftKey;
902         } catch (e) {
903
904         }
905
906         if (window.event) {
907                 keycode = window.event.keyCode;
908         } else if (e) {
909                 keycode = e.which;
910         }
911
912         let keychar = String.fromCharCode(keycode);
913
914         if (keycode == 27) { // escape
915                 hotkey_prefix = false;
916         }
917
918         if (keycode == 16) return; // ignore lone shift
919         if (keycode == 17) return; // ignore lone ctrl
920
921         if (!shift_key) keychar = keychar.toLowerCase();
922
923         var hotkeys = getInitParam("hotkeys");
924
925         if (!hotkey_prefix && hotkeys[0].indexOf(keychar) != -1) {
926
927                 const date = new Date();
928                 const ts = Math.round(date.getTime() / 1000);
929
930                 hotkey_prefix = keychar;
931                 hotkey_prefix_pressed = ts;
932
933                 cmdline.innerHTML = keychar;
934                 Element.show(cmdline);
935
936                 return true;
937         }
938
939         Element.hide(cmdline);
940
941         let hotkey = keychar.search(/[a-zA-Z0-9]/) != -1 ? keychar : "(" + keycode + ")";
942         hotkey = hotkey_prefix ? hotkey_prefix + " " + hotkey : hotkey;
943         hotkey_prefix = false;
944
945         let hotkey_action = false;
946         var hotkeys = getInitParam("hotkeys");
947
948         for (const sequence in hotkeys[1]) {
949                 if (sequence == hotkey) {
950                         hotkey_action = hotkeys[1][sequence];
951                         break;
952                 }
953         }
954
955         switch (hotkey_action) {
956                 case "feed_subscribe":
957                         quickAddFeed();
958                         return false;
959                 case "create_label":
960                         addLabel();
961                         return false;
962                 case "create_filter":
963                         quickAddFilter();
964                         return false;
965                 case "help_dialog":
966                         //helpDialog("prefs");
967                         return false;
968                 default:
969                         console.log("unhandled action: " + hotkey_action + "; hotkey: " + hotkey);
970         }
971 }
972
973 function removeCategory(id, item) {
974
975         const ok = confirm(__("Remove category %s? Any nested feeds would be placed into Uncategorized.").replace("%s", item.name));
976
977         if (ok) {
978                 const query = "?op=pref-feeds&method=removeCat&ids=" +
979                         param_escape(id);
980
981                 notify_progress("Removing category...");
982
983                 new Ajax.Request("backend.php", {
984                         parameters: query,
985                         onComplete: function (transport) {
986                                 notify('');
987                                 updateFeedList();
988                         }
989                 });
990         }
991 }
992
993 function removeSelectedCategories() {
994
995         const sel_rows = getSelectedCategories();
996
997         if (sel_rows.length > 0) {
998
999                 const ok = confirm(__("Remove selected categories?"));
1000
1001                 if (ok) {
1002                         notify_progress("Removing selected categories...");
1003
1004                         const query = "?op=pref-feeds&method=removeCat&ids="+
1005                                 param_escape(sel_rows.toString());
1006
1007                         new Ajax.Request("backend.php", {
1008                                 parameters: query,
1009                                 onComplete: function(transport) {
1010                                                 updateFeedList();
1011                                         } });
1012
1013                 }
1014         } else {
1015                 alert(__("No categories are selected."));
1016         }
1017
1018         return false;
1019 }
1020
1021 function createCategory() {
1022         const title = prompt(__("Category title:"));
1023
1024         if (title) {
1025
1026                 notify_progress("Creating category...");
1027
1028                 const query = "?op=pref-feeds&method=addCat&cat=" +
1029                         param_escape(title);
1030
1031                 new Ajax.Request("backend.php", {
1032                         parameters: query,
1033                         onComplete: function (transport) {
1034                                 notify('');
1035                                 updateFeedList();
1036                         }
1037                 });
1038         }
1039 }
1040
1041 function showInactiveFeeds() {
1042         const query = "backend.php?op=pref-feeds&method=inactiveFeeds";
1043
1044         if (dijit.byId("inactiveFeedsDlg"))
1045                 dijit.byId("inactiveFeedsDlg").destroyRecursive();
1046
1047         var dialog = new dijit.Dialog({
1048                 id: "inactiveFeedsDlg",
1049                 title: __("Feeds without recent updates"),
1050                 style: "width: 600px",
1051                 getSelectedFeeds: function () {
1052                         return getSelectedTableRowIds("prefInactiveFeedList");
1053                 },
1054                 removeSelected: function () {
1055                         const sel_rows = this.getSelectedFeeds();
1056
1057                         console.log(sel_rows);
1058
1059                         if (sel_rows.length > 0) {
1060                                 const ok = confirm(__("Remove selected feeds?"));
1061
1062                                 if (ok) {
1063                                         notify_progress("Removing selected feeds...", true);
1064
1065                                         const query = "?op=pref-feeds&method=remove&ids=" +
1066                                                 param_escape(sel_rows.toString());
1067
1068                                         new Ajax.Request("backend.php", {
1069                                                 parameters: query,
1070                                                 onComplete: function (transport) {
1071                                                         notify('');
1072                                                         dialog.hide();
1073                                                         updateFeedList();
1074                                                 }
1075                                         });
1076                                 }
1077
1078                         } else {
1079                                 alert(__("No feeds are selected."));
1080                         }
1081                 },
1082                 execute: function () {
1083                         if (this.validate()) {
1084                         }
1085                 },
1086                 href: query
1087         });
1088
1089         dialog.show();
1090 }
1091
1092 function opmlRegenKey() {
1093         const ok = confirm(__("Replace current OPML publishing address with a new one?"));
1094
1095         if (ok) {
1096
1097                 notify_progress("Trying to change address...", true);
1098
1099                 const query = "?op=pref-feeds&method=regenOPMLKey";
1100
1101                 new Ajax.Request("backend.php", {
1102                         parameters: query,
1103                         onComplete: function (transport) {
1104                                 const reply = JSON.parse(transport.responseText);
1105
1106                                 const new_link = reply.link;
1107
1108                                 const e = $('pub_opml_url');
1109
1110                                 if (new_link) {
1111                                         e.href = new_link;
1112                                         e.innerHTML = new_link;
1113
1114                                         new Effect.Highlight(e);
1115
1116                                         notify('');
1117
1118                                 } else {
1119                                         notify_error("Could not change feed URL.");
1120                                 }
1121                         }
1122                 });
1123         }
1124         return false;
1125 }
1126
1127 function labelColorReset() {
1128         const labels = getSelectedLabels();
1129
1130         if (labels.length > 0) {
1131                 const ok = confirm(__("Reset selected labels to default colors?"));
1132
1133                 if (ok) {
1134                         const query = "?op=pref-labels&method=colorreset&ids=" +
1135                                 param_escape(labels.toString());
1136
1137                         new Ajax.Request("backend.php", {
1138                                 parameters: query,
1139                                 onComplete: function (transport) {
1140                                         updateLabelList();
1141                                 }
1142                         });
1143                 }
1144
1145         } else {
1146                 alert(__("No labels are selected."));
1147         }
1148 }
1149
1150 function inPreferences() {
1151         return true;
1152 }
1153
1154 function editProfiles() {
1155
1156         if (dijit.byId("profileEditDlg"))
1157                 dijit.byId("profileEditDlg").destroyRecursive();
1158
1159         const query = "backend.php?op=pref-prefs&method=editPrefProfiles";
1160
1161         var dialog = new dijit.Dialog({
1162                 id: "profileEditDlg",
1163                 title: __("Settings Profiles"),
1164                 style: "width: 600px",
1165                 getSelectedProfiles: function () {
1166                         return getSelectedTableRowIds("prefFeedProfileList");
1167                 },
1168                 removeSelected: function () {
1169                         const sel_rows = this.getSelectedProfiles();
1170
1171                         if (sel_rows.length > 0) {
1172                                 const ok = confirm(__("Remove selected profiles? Active and default profiles will not be removed."));
1173
1174                                 if (ok) {
1175                                         notify_progress("Removing selected profiles...", true);
1176
1177                                         const query = "?op=rpc&method=remprofiles&ids=" +
1178                                                 param_escape(sel_rows.toString());
1179
1180                                         new Ajax.Request("backend.php", {
1181                                                 parameters: query,
1182                                                 onComplete: function (transport) {
1183                                                         notify('');
1184                                                         editProfiles();
1185                                                 }
1186                                         });
1187
1188                                 }
1189
1190                         } else {
1191                                 alert(__("No profiles are selected."));
1192                         }
1193                 },
1194                 activateProfile: function () {
1195                         const sel_rows = this.getSelectedProfiles();
1196
1197                         if (sel_rows.length == 1) {
1198
1199                                 const ok = confirm(__("Activate selected profile?"));
1200
1201                                 if (ok) {
1202                                         notify_progress("Loading, please wait...");
1203
1204                                         const query = "?op=rpc&method=setprofile&id=" +
1205                                                 param_escape(sel_rows.toString());
1206
1207                                         new Ajax.Request("backend.php", {
1208                                                 parameters: query,
1209                                                 onComplete: function (transport) {
1210                                                         window.location.reload();
1211                                                 }
1212                                         });
1213                                 }
1214
1215                         } else {
1216                                 alert(__("Please choose a profile to activate."));
1217                         }
1218                 },
1219                 addProfile: function () {
1220                         if (this.validate()) {
1221                                 notify_progress("Creating profile...", true);
1222
1223                                 const query = "?op=rpc&method=addprofile&title=" +
1224                                         param_escape(dialog.attr('value').newprofile);
1225
1226                                 new Ajax.Request("backend.php", {
1227                                         parameters: query,
1228                                         onComplete: function (transport) {
1229                                                 notify('');
1230                                                 editProfiles();
1231                                         }
1232                                 });
1233
1234                         }
1235                 },
1236                 execute: function () {
1237                         if (this.validate()) {
1238                         }
1239                 },
1240                 href: query
1241         });
1242
1243         dialog.show();
1244 }
1245
1246 function activatePrefProfile() {
1247
1248         const sel_rows = getSelectedFeedCats();
1249
1250         if (sel_rows.length == 1) {
1251
1252                 const ok = confirm(__("Activate selected profile?"));
1253
1254                 if (ok) {
1255                         notify_progress("Loading, please wait...");
1256
1257                         const query = "?op=rpc&method=setprofile&id="+
1258                                 param_escape(sel_rows.toString());
1259
1260                         new Ajax.Request("backend.php", {
1261                                 parameters: query,
1262                                 onComplete: function(transport) {
1263                                         window.location.reload();
1264                                 } });
1265                 }
1266
1267         } else {
1268                 alert(__("Please choose a profile to activate."));
1269         }
1270
1271         return false;
1272 }
1273
1274 function clearFeedAccessKeys() {
1275
1276         const ok = confirm(__("This will invalidate all previously generated feed URLs. Continue?"));
1277
1278         if (ok) {
1279                 notify_progress("Clearing URLs...");
1280
1281                 const query = "?op=pref-feeds&method=clearKeys";
1282
1283                 new Ajax.Request("backend.php", {
1284                         parameters: query,
1285                         onComplete: function(transport) {
1286                                 notify_info("Generated URLs cleared.");
1287                         } });
1288         }
1289
1290         return false;
1291 }
1292
1293 function resetFilterOrder() {
1294         notify_progress("Loading, please wait...");
1295
1296         new Ajax.Request("backend.php", {
1297                 parameters: "?op=pref-filters&method=filtersortreset",
1298                 onComplete: function (transport) {
1299                         updateFilterList();
1300                 }
1301         });
1302 }
1303
1304
1305 function resetFeedOrder() {
1306         notify_progress("Loading, please wait...");
1307
1308         new Ajax.Request("backend.php", {
1309                 parameters: "?op=pref-feeds&method=feedsortreset",
1310                 onComplete: function (transport) {
1311                         updateFeedList();
1312                 }
1313         });
1314 }
1315
1316 function resetCatOrder() {
1317         notify_progress("Loading, please wait...");
1318
1319         new Ajax.Request("backend.php", {
1320                 parameters: "?op=pref-feeds&method=catsortreset",
1321                 onComplete: function (transport) {
1322                         updateFeedList();
1323                 }
1324         });
1325 }
1326
1327 function editCat(id, item) {
1328         const new_name = prompt(__('Rename category to:'), item.name);
1329
1330         if (new_name && new_name != item.name) {
1331
1332                 notify_progress("Loading, please wait...");
1333
1334                 new Ajax.Request("backend.php", {
1335                         parameters: {
1336                                 op: 'pref-feeds',
1337                                 method: 'renamecat',
1338                                 id: id,
1339                                 title: new_name,
1340                         },
1341                         onComplete: function (transport) {
1342                                 updateFeedList();
1343                         }
1344                 });
1345         }
1346 }
1347
1348 function editLabel(id) {
1349         const query = "backend.php?op=pref-labels&method=edit&id=" +
1350                 param_escape(id);
1351
1352         if (dijit.byId("labelEditDlg"))
1353                 dijit.byId("labelEditDlg").destroyRecursive();
1354
1355         const dialog = new dijit.Dialog({
1356                 id: "labelEditDlg",
1357                 title: __("Label Editor"),
1358                 style: "width: 600px",
1359                 setLabelColor: function (id, fg, bg) {
1360
1361                         let kind = '';
1362                         let color = '';
1363
1364                         if (fg && bg) {
1365                                 kind = 'both';
1366                         } else if (fg) {
1367                                 kind = 'fg';
1368                                 color = fg;
1369                         } else if (bg) {
1370                                 kind = 'bg';
1371                                 color = bg;
1372                         }
1373
1374                         const query = "?op=pref-labels&method=colorset&kind=" + kind +
1375                                 "&ids=" + param_escape(id) + "&fg=" + param_escape(fg) +
1376                                 "&bg=" + param_escape(bg) + "&color=" + param_escape(color);
1377
1378                         //              console.log(query);
1379
1380                         const e = $("LICID-" + id);
1381
1382                         if (e) {
1383                                 if (fg) e.style.color = fg;
1384                                 if (bg) e.style.backgroundColor = bg;
1385                         }
1386
1387                         new Ajax.Request("backend.php", {parameters: query});
1388
1389                         updateFilterList();
1390                 },
1391                 execute: function () {
1392                         if (this.validate()) {
1393                                 const caption = this.attr('value').caption;
1394                                 const fg_color = this.attr('value').fg_color;
1395                                 const bg_color = this.attr('value').bg_color;
1396                                 const query = dojo.objectToQuery(this.attr('value'));
1397
1398                                 dijit.byId('labelTree').setNameById(id, caption);
1399                                 this.setLabelColor(id, fg_color, bg_color);
1400                                 this.hide();
1401
1402                                 new Ajax.Request("backend.php", {
1403                                         parameters: query,
1404                                         onComplete: function (transport) {
1405                                                 updateFilterList();
1406                                         }
1407                                 });
1408                         }
1409                 },
1410                 href: query
1411         });
1412
1413         dialog.show();
1414 }
1415
1416
1417 function customizeCSS() {
1418         const query = "backend.php?op=pref-prefs&method=customizeCSS";
1419
1420         if (dijit.byId("cssEditDlg"))
1421                 dijit.byId("cssEditDlg").destroyRecursive();
1422
1423         const dialog = new dijit.Dialog({
1424                 id: "cssEditDlg",
1425                 title: __("Customize stylesheet"),
1426                 style: "width: 600px",
1427                 execute: function () {
1428                         notify_progress('Saving data...', true);
1429                         new Ajax.Request("backend.php", {
1430                                 parameters: dojo.objectToQuery(this.attr('value')),
1431                                 onComplete: function (transport) {
1432                                         notify('');
1433                                         window.location.reload();
1434                                 }
1435                         });
1436
1437                 },
1438                 href: query
1439         });
1440
1441         dialog.show();
1442 }
1443
1444 function insertSSLserial(value) {
1445         dijit.byId("SSL_CERT_SERIAL").attr('value', value);
1446 }
1447
1448 function gotoExportOpml(filename, settings) {
1449         const tmp = settings ? 1 : 0;
1450         document.location.href = "backend.php?op=opml&method=export&filename=" + filename + "&settings=" + tmp;
1451 }
1452
1453
1454 function batchSubscribe() {
1455         const query = "backend.php?op=pref-feeds&method=batchSubscribe";
1456
1457         // overlapping widgets
1458         if (dijit.byId("batchSubDlg")) dijit.byId("batchSubDlg").destroyRecursive();
1459         if (dijit.byId("feedAddDlg"))    dijit.byId("feedAddDlg").destroyRecursive();
1460
1461         var dialog = new dijit.Dialog({
1462                 id: "batchSubDlg",
1463                 title: __("Batch subscribe"),
1464                 style: "width: 600px",
1465                 execute: function () {
1466                         if (this.validate()) {
1467                                 console.log(dojo.objectToQuery(this.attr('value')));
1468
1469                                 notify_progress(__("Subscribing to feeds..."), true);
1470
1471                                 new Ajax.Request("backend.php", {
1472                                         parameters: dojo.objectToQuery(this.attr('value')),
1473                                         onComplete: function (transport) {
1474                                                 notify("");
1475                                                 updateFeedList();
1476                                                 dialog.hide();
1477                                         }
1478                                 });
1479                         }
1480                 },
1481                 href: query
1482         });
1483
1484         dialog.show();
1485 }
1486
1487 function clearPluginData(name) {
1488         if (confirm(__("Clear stored data for this plugin?"))) {
1489                 notify_progress("Loading, please wait...");
1490
1491                 new Ajax.Request("backend.php", {
1492                         parameters: "?op=pref-prefs&method=clearplugindata&name=" + param_escape(name),
1493                         onComplete: function(transport) {
1494                                 notify('');
1495                                 updatePrefsList();
1496                         } });
1497         }
1498 }
1499
1500 function clearSqlLog() {
1501
1502         if (confirm(__("Clear all messages in the error log?"))) {
1503
1504                 notify_progress("Loading, please wait...");
1505                 const query = "?op=pref-system&method=clearLog";
1506
1507                 new Ajax.Request("backend.php", {
1508                         parameters: query,
1509                         onComplete: function(transport) {
1510                                 updateSystemList();
1511                         } });
1512
1513         }
1514 }
1515
1516 function updateSelectedPrompt() {
1517         // no-op shim for toggleSelectedRow()
1518 }
1519