3 let hotkey_prefix = false;
4 let hotkey_prefix_pressed = false;
8 function notify_callback2(transport, sticky) {
9 notify_info(transport.responseText, sticky);
12 function updateFeedList() {
14 const user_search = $("feed_search");
16 if (user_search) { search = user_search.value; }
18 xhrPost("backend.php", { op: "pref-feeds", search: search }, (transport) => {
19 dijit.byId('feedConfigTab').attr('content', transport.responseText);
20 selectTab("feedConfig", true);
25 function checkInactiveFeeds() {
26 xhrPost("backend.php", { op: "pref-feeds", method: "getinactivefeeds" }, (transport) => {
27 if (parseInt(transport.responseText) > 0) {
28 Element.show(dijit.byId("pref_feeds_inactive_btn").domNode);
33 function updateUsersList(sort_key) {
34 const user_search = $("user_search");
35 const search = user_search ? user_search.value : "";
37 const query = { op: "pref-users", sort: sort_key, search: search };
39 xhrPost("backend.php", query, (transport) => {
40 dijit.byId('userConfigTab').attr('content', transport.responseText);
41 selectTab("userConfig", true)
47 const login = prompt(__("Please enter login:"), "");
54 alert(__("Can't create user: no login specified."));
58 notify_progress("Adding user...");
60 xhrPost("backend.php", { op: "pref-users", method: "add", login: login }, (transport) => {
61 notify_callback2(transport);
67 function editUser(id) {
69 const query = "backend.php?op=pref-users&method=edit&id=" +
72 if (dijit.byId("userEditDlg"))
73 dijit.byId("userEditDlg").destroyRecursive();
75 const dialog = new dijit.Dialog({
77 title: __("User Editor"),
78 style: "width: 600px",
79 execute: function () {
80 if (this.validate()) {
81 notify_progress("Saving data...", true);
83 xhrPost("backend.php", dojo.formToObject("user_edit_form"), (transport) => {
95 function editFilter(id) {
97 const query = "backend.php?op=pref-filters&method=edit&id=" + param_escape(id);
99 if (dijit.byId("feedEditDlg"))
100 dijit.byId("feedEditDlg").destroyRecursive();
102 if (dijit.byId("filterEditDlg"))
103 dijit.byId("filterEditDlg").destroyRecursive();
105 const dialog = new dijit.Dialog({
107 title: __("Edit Filter"),
108 style: "width: 600px",
111 const query = "backend.php?" + dojo.formToQuery("filter_edit_form") + "&savemode=test";
113 editFilterTest(query);
115 selectRules: function (select) {
116 $$("#filterDlg_Matches input[type=checkbox]").each(function (e) {
119 e.parentNode.addClassName("Selected");
121 e.parentNode.removeClassName("Selected");
124 selectActions: function (select) {
125 $$("#filterDlg_Actions input[type=checkbox]").each(function (e) {
129 e.parentNode.addClassName("Selected");
131 e.parentNode.removeClassName("Selected");
135 editRule: function (e) {
136 const li = e.parentNode;
137 const rule = li.getElementsByTagName("INPUT")[1].value;
138 addFilterRule(li, rule);
140 editAction: function (e) {
141 const li = e.parentNode;
142 const action = li.getElementsByTagName("INPUT")[1].value;
143 addFilterAction(li, action);
145 removeFilter: function () {
146 const msg = __("Remove filter?");
151 notify_progress("Removing filter...");
153 const query = { op: "pref-filters", method: "remove", ids: this.attr('value').id };
155 xhrPost("backend.php", query, () => {
160 addAction: function () {
163 addRule: function () {
166 deleteAction: function () {
167 $$("#filterDlg_Actions li[class*=Selected]").each(function (e) {
168 e.parentNode.removeChild(e)
171 deleteRule: function () {
172 $$("#filterDlg_Matches li[class*=Selected]").each(function (e) {
173 e.parentNode.removeChild(e)
176 execute: function () {
177 if (this.validate()) {
179 notify_progress("Saving data...", true);
181 xhrPost("backend.php", dojo.formToObject("filter_edit_form"), () => {
194 function getSelectedLabels() {
195 const tree = dijit.byId("labelTree");
196 const items = tree.model.getCheckedItems();
199 items.each(function(item) {
200 rv.push(tree.model.store.getValue(item, 'bare_id'));
206 function getSelectedUsers() {
207 return getSelectedTableRowIds("prefUserList");
210 function getSelectedFeeds() {
211 const tree = dijit.byId("feedTree");
212 const items = tree.model.getCheckedItems();
215 items.each(function(item) {
216 if (item.id[0].match("FEED:"))
217 rv.push(tree.model.store.getValue(item, 'bare_id'));
223 function getSelectedCategories() {
224 const tree = dijit.byId("feedTree");
225 const items = tree.model.getCheckedItems();
228 items.each(function(item) {
229 if (item.id[0].match("CAT:"))
230 rv.push(tree.model.store.getValue(item, 'bare_id'));
236 function getSelectedFilters() {
237 const tree = dijit.byId("filterTree");
238 const items = tree.model.getCheckedItems();
241 items.each(function(item) {
242 rv.push(tree.model.store.getValue(item, 'bare_id'));
249 function removeSelectedLabels() {
251 const sel_rows = getSelectedLabels();
253 if (sel_rows.length > 0) {
255 const ok = confirm(__("Remove selected labels?"));
258 notify_progress("Removing selected labels...");
260 const query = { op: "pref-labels", method: "remove",
261 ids: sel_rows.toString() };
263 xhrPost("backend.php", query, () => {
268 alert(__("No labels are selected."));
274 function removeSelectedUsers() {
276 const sel_rows = getSelectedUsers();
278 if (sel_rows.length > 0) {
280 const ok = confirm(__("Remove selected users? Neither default admin nor your account will be removed."));
283 notify_progress("Removing selected users...");
285 const query = { op: "pref-users", method: "remove",
286 ids: sel_rows.toString() };
288 xhrPost("backend.php", query, () => {
294 alert(__("No users are selected."));
300 function removeSelectedFilters() {
302 const sel_rows = getSelectedFilters();
304 if (sel_rows.length > 0) {
306 const ok = confirm(__("Remove selected filters?"));
309 notify_progress("Removing selected filters...");
311 const query = { op: "pref-filters", method: "remove",
312 ids: sel_rows.toString() };
314 xhrPost("backend.php", query, () => {
319 alert(__("No filters are selected."));
325 function removeSelectedFeeds() {
327 const sel_rows = getSelectedFeeds();
329 if (sel_rows.length > 0) {
331 const ok = confirm(__("Unsubscribe from selected feeds?"));
335 notify_progress("Unsubscribing from selected feeds...", true);
337 const query = { op: "pref-feeds", method: "remove",
338 ids: sel_rows.toString() };
340 xhrPost("backend.php", query, () => {
346 alert(__("No feeds are selected."));
352 function editSelectedUser() {
353 const rows = getSelectedUsers();
355 if (rows.length == 0) {
356 alert(__("No users are selected."));
360 if (rows.length > 1) {
361 alert(__("Please select only one user."));
370 function resetSelectedUserPass() {
372 const rows = getSelectedUsers();
374 if (rows.length == 0) {
375 alert(__("No users are selected."));
379 if (rows.length > 1) {
380 alert(__("Please select only one user."));
384 const ok = confirm(__("Reset password of selected user?"));
387 notify_progress("Resetting password for selected user...");
391 xhrPost("backend.php", { op: "pref-users", method: "resetPass", id: id }, (transport) => {
392 notify_info(transport.responseText, true);
398 function selectedUserDetails() {
400 const rows = getSelectedUsers();
402 if (rows.length == 0) {
403 alert(__("No users are selected."));
407 if (rows.length > 1) {
408 alert(__("Please select only one user."));
412 const query = "backend.php?op=pref-users&method=userdetails&id=" + param_escape(rows[0]);
414 if (dijit.byId("userDetailsDlg"))
415 dijit.byId("userDetailsDlg").destroyRecursive();
417 const dialog = new dijit.Dialog({
418 id: "userDetailsDlg",
419 title: __("User details"),
420 style: "width: 600px",
421 execute: function () {
431 function editSelectedFilter() {
432 const rows = getSelectedFilters();
434 if (rows.length == 0) {
435 alert(__("No filters are selected."));
439 if (rows.length > 1) {
440 alert(__("Please select only one filter."));
450 function joinSelectedFilters() {
451 const rows = getSelectedFilters();
453 if (rows.length == 0) {
454 alert(__("No filters are selected."));
458 const ok = confirm(__("Combine selected filters?"));
461 notify_progress("Joining filters...");
463 xhrPost("backend.php", { op: "pref-filters", method: "join", ids: rows.toString() }, () => {
469 function editSelectedFeed() {
470 const rows = getSelectedFeeds();
472 if (rows.length == 0) {
473 alert(__("No feeds are selected."));
477 if (rows.length > 1) {
478 return editSelectedFeeds();
483 editFeed(rows[0], {});
487 function editSelectedFeeds() {
488 const rows = getSelectedFeeds();
490 if (rows.length == 0) {
491 alert(__("No feeds are selected."));
495 notify_progress("Loading, please wait...");
497 const query = "backend.php?op=pref-feeds&method=editfeeds&ids=" +
498 param_escape(rows.toString());
502 if (dijit.byId("feedEditDlg"))
503 dijit.byId("feedEditDlg").destroyRecursive();
505 new Ajax.Request("backend.php", {
507 onComplete: function (transport) {
511 const dialog = new dijit.Dialog({
513 title: __("Edit Multiple Feeds"),
514 style: "width: 600px",
515 getChildByName: function (name) {
517 this.getChildren().each(
519 if (child.name == name) {
526 toggleField: function (checkbox, elem, label) {
527 this.getChildByName(elem).attr('disabled', !checkbox.checked);
530 if (checkbox.checked)
531 $(label).removeClassName('insensitive');
533 $(label).addClassName('insensitive');
536 execute: function () {
537 if (this.validate() && confirm(__("Save changes to selected feeds?"))) {
538 const query = this.attr('value');
540 /* normalize unchecked checkboxes because [] is not serialized */
542 Object.keys(query).each((key) => {
543 let val = query[key];
545 if (typeof val == "object" && val.length == 0)
546 query[key] = ["off"];
549 notify_progress("Saving data...", true);
551 xhrPost("backend.php", query, () => {
557 content: transport.responseText
566 function opmlImportComplete(iframe) {
567 if (!iframe.contentDocument.body.innerHTML) return false;
569 Element.show(iframe);
573 if (dijit.byId('opmlImportDlg'))
574 dijit.byId('opmlImportDlg').destroyRecursive();
576 const content = iframe.contentDocument.body.innerHTML;
578 const dialog = new dijit.Dialog({
580 title: __("OPML Import"),
581 style: "width: 600px",
582 onCancel: function () {
583 window.location.reload();
585 execute: function () {
586 window.location.reload();
594 function opmlImport() {
596 const opml_file = $("opml_file");
598 if (opml_file.value.length == 0) {
599 alert(__("Please choose an OPML file first."));
602 notify_progress("Importing, please wait...", true);
604 Element.show("upload_iframe");
611 function updateFilterList() {
612 const user_search = $("filter_search");
614 if (user_search) { search = user_search.value; }
616 new Ajax.Request("backend.php", {
617 parameters: "?op=pref-filters&search=" + param_escape(search),
618 onComplete: function(transport) {
619 dijit.byId('filterConfigTab').attr('content', transport.responseText);
624 function updateLabelList() {
625 new Ajax.Request("backend.php", {
626 parameters: "?op=pref-labels",
627 onComplete: function(transport) {
628 dijit.byId('labelConfigTab').attr('content', transport.responseText);
633 function updatePrefsList() {
634 new Ajax.Request("backend.php", {
635 parameters: "?op=pref-prefs",
636 onComplete: function(transport) {
637 dijit.byId('genConfigTab').attr('content', transport.responseText);
642 function updateSystemList() {
643 new Ajax.Request("backend.php", {
644 parameters: "?op=pref-system",
645 onComplete: function(transport) {
646 dijit.byId('systemConfigTab').attr('content', transport.responseText);
651 function selectTab(id, noupdate) {
653 notify_progress("Loading, please wait...");
655 if (id == "feedConfig") {
657 } else if (id == "filterConfig") {
659 } else if (id == "labelConfig") {
661 } else if (id == "genConfig") {
663 } else if (id == "userConfig") {
665 } else if (id == "systemConfig") {
669 const tab = dijit.byId(id + "Tab");
670 dijit.byId("pref-tabs").selectChild(tab);
675 function init_second_stage() {
676 document.onkeydown = pref_hotkey_handler;
677 loading_set_progress(50);
680 let tab = getURLParam('tab');
683 tab = dijit.byId(tab + "Tab");
684 if (tab) dijit.byId("pref-tabs").selectChild(tab);
687 const method = getURLParam('method');
689 if (method == 'editFeed') {
690 const param = getURLParam('methodparam');
692 window.setTimeout(function() { editFeed(param) }, 100);
695 setTimeout(hotkey_prefix_timeout, 5*1000);
699 window.onerror = function (message, filename, lineno, colno, error) {
700 report_error(message, filename, lineno, colno, error);
703 require(["dojo/_base/kernel",
708 "dijit/ColorPalette",
711 "dijit/form/CheckBox",
712 "dijit/form/DropDownButton",
713 "dijit/form/FilteringSelect",
714 "dijit/form/MultiSelect",
716 "dijit/form/RadioButton",
717 "dijit/form/ComboButton",
719 "dijit/form/SimpleTextarea",
720 "dijit/form/TextBox",
721 "dijit/form/ValidationTextBox",
722 "dijit/InlineEditBox",
723 "dijit/layout/AccordionContainer",
724 "dijit/layout/AccordionPane",
725 "dijit/layout/BorderContainer",
726 "dijit/layout/ContentPane",
727 "dijit/layout/TabContainer",
732 "dijit/tree/dndSource",
733 "dojo/data/ItemFileWriteStore",
734 "lib/CheckBoxStoreModel",
737 "fox/PrefFilterStore",
739 "fox/PrefFilterTree",
740 "fox/PrefLabelTree"], function (dojo, ready, parser) {
746 loading_set_progress(50);
748 const clientTzOffset = new Date().getTimezoneOffset() * 60;
750 new Ajax.Request("backend.php", {
752 op: "rpc", method: "sanityCheck",
753 clientTzOffset: clientTzOffset
755 onComplete: function (transport) {
756 backend_sanity_check_callback(transport);
767 function validatePrefsReset() {
768 const ok = confirm(__("Reset to defaults?"));
772 const query = "?op=pref-prefs&method=resetconfig";
775 new Ajax.Request("backend.php", {
777 onComplete: function(transport) {
779 notify_info(transport.responseText);
788 function pref_hotkey_handler(e) {
790 if (e.target.nodeName == "INPUT" || e.target.nodeName == "TEXTAREA") return;
793 let shift_key = false;
795 const cmdline = $('cmdline');
798 shift_key = e.shiftKey;
804 keycode = window.event.keyCode;
809 let keychar = String.fromCharCode(keycode);
811 if (keycode == 27) { // escape
812 hotkey_prefix = false;
815 if (keycode == 16) return; // ignore lone shift
816 if (keycode == 17) return; // ignore lone ctrl
818 if (!shift_key) keychar = keychar.toLowerCase();
820 var hotkeys = getInitParam("hotkeys");
822 if (!hotkey_prefix && hotkeys[0].indexOf(keychar) != -1) {
824 const date = new Date();
825 const ts = Math.round(date.getTime() / 1000);
827 hotkey_prefix = keychar;
828 hotkey_prefix_pressed = ts;
830 cmdline.innerHTML = keychar;
831 Element.show(cmdline);
836 Element.hide(cmdline);
838 let hotkey = keychar.search(/[a-zA-Z0-9]/) != -1 ? keychar : "(" + keycode + ")";
839 hotkey = hotkey_prefix ? hotkey_prefix + " " + hotkey : hotkey;
840 hotkey_prefix = false;
842 let hotkey_action = false;
843 var hotkeys = getInitParam("hotkeys");
845 for (const sequence in hotkeys[1]) {
846 if (sequence == hotkey) {
847 hotkey_action = hotkeys[1][sequence];
852 switch (hotkey_action) {
853 case "feed_subscribe":
859 case "create_filter":
863 //helpDialog("prefs");
866 console.log("unhandled action: " + hotkey_action + "; hotkey: " + hotkey);
870 function removeCategory(id, item) {
872 const ok = confirm(__("Remove category %s? Any nested feeds would be placed into Uncategorized.").replace("%s", item.name));
875 const query = "?op=pref-feeds&method=removeCat&ids=" +
878 notify_progress("Removing category...");
880 new Ajax.Request("backend.php", {
882 onComplete: function (transport) {
890 function removeSelectedCategories() {
892 const sel_rows = getSelectedCategories();
894 if (sel_rows.length > 0) {
896 const ok = confirm(__("Remove selected categories?"));
899 notify_progress("Removing selected categories...");
901 const query = "?op=pref-feeds&method=removeCat&ids="+
902 param_escape(sel_rows.toString());
904 new Ajax.Request("backend.php", {
906 onComplete: function(transport) {
912 alert(__("No categories are selected."));
918 function createCategory() {
919 const title = prompt(__("Category title:"));
923 notify_progress("Creating category...");
925 const query = "?op=pref-feeds&method=addCat&cat=" +
928 new Ajax.Request("backend.php", {
930 onComplete: function (transport) {
938 function showInactiveFeeds() {
939 const query = "backend.php?op=pref-feeds&method=inactiveFeeds";
941 if (dijit.byId("inactiveFeedsDlg"))
942 dijit.byId("inactiveFeedsDlg").destroyRecursive();
944 const dialog = new dijit.Dialog({
945 id: "inactiveFeedsDlg",
946 title: __("Feeds without recent updates"),
947 style: "width: 600px",
948 getSelectedFeeds: function () {
949 return getSelectedTableRowIds("prefInactiveFeedList");
951 removeSelected: function () {
952 const sel_rows = this.getSelectedFeeds();
954 console.log(sel_rows);
956 if (sel_rows.length > 0) {
957 const ok = confirm(__("Remove selected feeds?"));
960 notify_progress("Removing selected feeds...", true);
962 const query = "?op=pref-feeds&method=remove&ids=" +
963 param_escape(sel_rows.toString());
965 new Ajax.Request("backend.php", {
967 onComplete: function (transport) {
976 alert(__("No feeds are selected."));
979 execute: function () {
980 if (this.validate()) {
989 function opmlRegenKey() {
990 const ok = confirm(__("Replace current OPML publishing address with a new one?"));
994 notify_progress("Trying to change address...", true);
996 const query = "?op=pref-feeds&method=regenOPMLKey";
998 new Ajax.Request("backend.php", {
1000 onComplete: function (transport) {
1001 const reply = JSON.parse(transport.responseText);
1003 const new_link = reply.link;
1005 const e = $('pub_opml_url');
1009 e.innerHTML = new_link;
1011 new Effect.Highlight(e);
1016 notify_error("Could not change feed URL.");
1024 function labelColorReset() {
1025 const labels = getSelectedLabels();
1027 if (labels.length > 0) {
1028 const ok = confirm(__("Reset selected labels to default colors?"));
1031 const query = "?op=pref-labels&method=colorreset&ids=" +
1032 param_escape(labels.toString());
1034 new Ajax.Request("backend.php", {
1036 onComplete: function (transport) {
1043 alert(__("No labels are selected."));
1047 function inPreferences() {
1051 function editProfiles() {
1053 if (dijit.byId("profileEditDlg"))
1054 dijit.byId("profileEditDlg").destroyRecursive();
1056 const query = "backend.php?op=pref-prefs&method=editPrefProfiles";
1058 const dialog = new dijit.Dialog({
1059 id: "profileEditDlg",
1060 title: __("Settings Profiles"),
1061 style: "width: 600px",
1062 getSelectedProfiles: function () {
1063 return getSelectedTableRowIds("prefFeedProfileList");
1065 removeSelected: function () {
1066 const sel_rows = this.getSelectedProfiles();
1068 if (sel_rows.length > 0) {
1069 const ok = confirm(__("Remove selected profiles? Active and default profiles will not be removed."));
1072 notify_progress("Removing selected profiles...", true);
1074 const query = { op: "rpc", method: "remprofiles",
1075 ids: sel_rows.toString() };
1077 xhrPost("backend.php", query, () => {
1084 alert(__("No profiles are selected."));
1087 activateProfile: function () {
1088 const sel_rows = this.getSelectedProfiles();
1090 if (sel_rows.length == 1) {
1092 const ok = confirm(__("Activate selected profile?"));
1095 notify_progress("Loading, please wait...");
1097 xhrPost("backend.php", { op: "rpc", method: "setprofile", id: sel_rows.toString() }, () => {
1098 window.location.reload();
1103 alert(__("Please choose a profile to activate."));
1106 addProfile: function () {
1107 if (this.validate()) {
1108 notify_progress("Creating profile...", true);
1110 const query = { op: "rpc", method: "addprofile", title: dialog.attr('value').newprofile };
1112 xhrPost("backend.php", query, () => {
1119 execute: function () {
1120 if (this.validate()) {
1130 function activatePrefProfile() {
1132 const sel_rows = getSelectedFeedCats();
1134 if (sel_rows.length == 1) {
1136 const ok = confirm(__("Activate selected profile?"));
1139 notify_progress("Loading, please wait...");
1141 xhrPost("backend.php", { op: "rpc", method: "setprofile", id: sel_rows.toString() }, () => {
1142 window.location.reload();
1147 alert(__("Please choose a profile to activate."));
1153 function clearFeedAccessKeys() {
1155 const ok = confirm(__("This will invalidate all previously generated feed URLs. Continue?"));
1158 notify_progress("Clearing URLs...");
1160 xhrPost("backend.php", { op: "pref-feeds", method: "clearKeys" }, () => {
1161 notify_info("Generated URLs cleared.");
1168 function resetFilterOrder() {
1169 notify_progress("Loading, please wait...");
1171 xhrPost("backend.php", { op: "pref-filters", method: "filtersortreset" }, () => {
1177 function resetFeedOrder() {
1178 notify_progress("Loading, please wait...");
1180 xhrPost("backend.php", { op: "pref-feeds", method: "feedsortreset" }, () => {
1185 function resetCatOrder() {
1186 notify_progress("Loading, please wait...");
1188 xhrPost("backend.php", { op: "pref-feeds", method: "catsortreset" }, () => {
1193 function editCat(id, item) {
1194 const new_name = prompt(__('Rename category to:'), item.name);
1196 if (new_name && new_name != item.name) {
1198 notify_progress("Loading, please wait...");
1200 xhrPost("backend.php", { op: 'pref-feeds', method: 'renamecat', id: id, title: new_name }, () => {
1206 function editLabel(id) {
1207 const query = "backend.php?op=pref-labels&method=edit&id=" +
1210 if (dijit.byId("labelEditDlg"))
1211 dijit.byId("labelEditDlg").destroyRecursive();
1213 const dialog = new dijit.Dialog({
1215 title: __("Label Editor"),
1216 style: "width: 600px",
1217 setLabelColor: function (id, fg, bg) {
1232 const e = $("LICID-" + id);
1235 if (fg) e.style.color = fg;
1236 if (bg) e.style.backgroundColor = bg;
1239 const query = { op: "pref-labels", method: "colorset", kind: kind,
1240 ids: id, fg: fg, bg: bg, color: color };
1242 xhrPost("backend.php", query, () => {
1243 updateFilterList(); // maybe there's labels in there
1247 execute: function () {
1248 if (this.validate()) {
1249 const caption = this.attr('value').caption;
1250 const fg_color = this.attr('value').fg_color;
1251 const bg_color = this.attr('value').bg_color;
1253 dijit.byId('labelTree').setNameById(id, caption);
1254 this.setLabelColor(id, fg_color, bg_color);
1257 xhrPost("backend.php", this.attr('value'), () => {
1258 updateFilterList(); // maybe there's labels in there
1269 function customizeCSS() {
1270 const query = "backend.php?op=pref-prefs&method=customizeCSS";
1272 if (dijit.byId("cssEditDlg"))
1273 dijit.byId("cssEditDlg").destroyRecursive();
1275 const dialog = new dijit.Dialog({
1277 title: __("Customize stylesheet"),
1278 style: "width: 600px",
1279 execute: function () {
1280 notify_progress('Saving data...', true);
1282 xhrPost("backend.php", this.attr('value'), () => {
1283 window.location.reload();
1293 function insertSSLserial(value) {
1294 dijit.byId("SSL_CERT_SERIAL").attr('value', value);
1297 function gotoExportOpml(filename, settings) {
1298 const tmp = settings ? 1 : 0;
1299 document.location.href = "backend.php?op=opml&method=export&filename=" + filename + "&settings=" + tmp;
1303 function batchSubscribe() {
1304 const query = "backend.php?op=pref-feeds&method=batchSubscribe";
1306 // overlapping widgets
1307 if (dijit.byId("batchSubDlg")) dijit.byId("batchSubDlg").destroyRecursive();
1308 if (dijit.byId("feedAddDlg")) dijit.byId("feedAddDlg").destroyRecursive();
1310 const dialog = new dijit.Dialog({
1312 title: __("Batch subscribe"),
1313 style: "width: 600px",
1314 execute: function () {
1315 if (this.validate()) {
1316 notify_progress(__("Subscribing to feeds..."), true);
1318 xhrPost("backend.php", this.attr('value'), () => {
1331 function clearPluginData(name) {
1332 if (confirm(__("Clear stored data for this plugin?"))) {
1333 notify_progress("Loading, please wait...");
1335 xhrPost("backend.php", { op: "pref-prefs", method: "clearplugindata", name: name }, () => {
1342 function clearSqlLog() {
1344 if (confirm(__("Clear all messages in the error log?"))) {
1346 notify_progress("Loading, please wait...");
1348 xhrPost("backend.php", { op: "pref-system", method: "clearLog" }, () => {
1355 function updateSelectedPrompt() {
1356 // no-op shim for toggleSelectedRow()