]> git.wh0rd.org - tt-rss.git/blob - prefs.js
1cf2943293cd055ff538f05be2ce14adb6dd7215
[tt-rss.git] / prefs.js
1 var init_params = new Array();
2
3 var hotkey_prefix = false;
4 var hotkey_prefix_pressed = false;
5
6 var seq = "";
7
8 function instancelist_callback2(transport) {
9 try {
10 dijit.byId('instanceConfigTab').attr('content', transport.responseText);
11 selectTab("instanceConfig", true);
12 notify("");
13 } catch (e) {
14 exception_error("instancelist_callback2", e);
15 }
16 }
17
18 function feedlist_callback2(transport) {
19 try {
20 dijit.byId('feedConfigTab').attr('content', transport.responseText);
21 selectTab("feedConfig", true);
22 notify("");
23 } catch (e) {
24 exception_error("feedlist_callback2", e);
25 }
26 }
27
28 function filterlist_callback2(transport) {
29 dijit.byId('filterConfigTab').attr('content', transport.responseText);
30 notify("");
31 }
32
33 function labellist_callback2(transport) {
34 try {
35 dijit.byId('labelConfigTab').attr('content', transport.responseText);
36 notify("");
37 } catch (e) {
38 exception_error("labellist_callback2", e);
39 }
40 }
41
42 function userlist_callback2(transport) {
43 try {
44 dijit.byId('userConfigTab').attr('content', transport.responseText);
45
46 notify("");
47 } catch (e) {
48 exception_error("userlist_callback2", e);
49 }
50 }
51
52 function prefslist_callback2(transport) {
53 try {
54 dijit.byId('genConfigTab').attr('content', transport.responseText);
55
56 notify("");
57 } catch (e) {
58 exception_error("prefslist_callback2", e);
59 }
60 }
61
62 function notify_callback2(transport) {
63 notify_info(transport.responseText);
64 }
65
66 function updateFeedList(sort_key) {
67
68 var user_search = $("feed_search");
69 var search = "";
70 if (user_search) { search = user_search.value; }
71
72 new Ajax.Request("backend.php", {
73 parameters: "?op=pref-feeds&search=" + param_escape(search),
74 onComplete: function(transport) {
75 feedlist_callback2(transport);
76 } });
77 }
78
79 function updateInstanceList(sort_key) {
80 new Ajax.Request("backend.php", {
81 parameters: "?op=pref-instances&sort=" + param_escape(sort_key),
82 onComplete: function(transport) {
83 instancelist_callback2(transport);
84 } });
85 }
86
87 function updateUsersList(sort_key) {
88
89 try {
90
91 var user_search = $("user_search");
92 var search = "";
93 if (user_search) { search = user_search.value; }
94
95 var query = "?op=pref-users&sort="
96 + param_escape(sort_key) +
97 "&search=" + param_escape(search);
98
99 new Ajax.Request("backend.php", {
100 parameters: query,
101 onComplete: function(transport) {
102 userlist_callback2(transport);
103 } });
104
105 } catch (e) {
106 exception_error("updateUsersList", e);
107 }
108 }
109
110 function addUser() {
111
112 try {
113
114 var login = prompt(__("Please enter login:"), "");
115
116 if (login == null) {
117 return false;
118 }
119
120 if (login == "") {
121 alert(__("Can't create user: no login specified."));
122 return false;
123 }
124
125 notify_progress("Adding user...");
126
127 var query = "?op=pref-users&subop=add&login=" +
128 param_escape(login);
129
130 new Ajax.Request("backend.php", {
131 parameters: query,
132 onComplete: function(transport) {
133 userlist_callback2(transport);
134 } });
135
136 } catch (e) {
137 exception_error("addUser", e);
138 }
139 }
140
141 function editUser(id, event) {
142
143 try {
144 if (!event || !event.ctrlKey) {
145
146 notify_progress("Loading, please wait...");
147
148 selectTableRows('prefUserList', 'none');
149 selectTableRowById('UMRR-'+id, 'UMCHK-'+id, true);
150
151 var query = "?op=pref-users&subop=edit&id=" +
152 param_escape(id);
153
154 new Ajax.Request("backend.php", {
155 parameters: query,
156 onComplete: function(transport) {
157 infobox_callback2(transport);
158 document.forms['user_edit_form'].login.focus();
159 } });
160
161 } else if (event.ctrlKey) {
162 var cb = $('UMCHK-' + id);
163 cb.checked = !cb.checked;
164 toggleSelectRow(cb);
165 }
166
167 } catch (e) {
168 exception_error("editUser", e);
169 }
170
171 }
172
173 function editFilter(id) {
174 try {
175
176 var query = "backend.php?op=pref-filters&subop=edit&id=" + param_escape(id);
177
178 if (dijit.byId("filterEditDlg"))
179 dijit.byId("filterEditDlg").destroyRecursive();
180
181 dialog = new dijit.Dialog({
182 id: "filterEditDlg",
183 title: __("Edit Filter"),
184 style: "width: 600px",
185 removeFilter: function() {
186 var title = this.attr('value').reg_exp;
187 var msg = __("Remove filter %s?").replace("%s", title);
188
189 if (confirm(msg)) {
190 this.hide();
191
192 notify_progress("Removing filter...");
193
194 var id = this.attr('value').id;
195
196 var query = "?op=pref-filters&subop=remove&ids="+
197 param_escape(id);
198
199 new Ajax.Request("backend.php", {
200 parameters: query,
201 onComplete: function(transport) {
202 updateFilterList();
203 } });
204 }
205 },
206 test: function() {
207 if (this.validate()) {
208
209 if (dijit.byId("filterTestDlg"))
210 dijit.byId("filterTestDlg").destroyRecursive();
211
212 tdialog = new dijit.Dialog({
213 id: "filterTestDlg",
214 title: __("Filter Test Results"),
215 style: "width: 600px",
216 href: "backend.php?savemode=test&" +
217 dojo.objectToQuery(dialog.attr('value')),
218 });
219
220 tdialog.show();
221
222 }
223 },
224 execute: function() {
225 if (this.validate()) {
226
227 var query = "?op=rpc&subop=verifyRegexp&reg_exp=" +
228 param_escape(dialog.attr('value').reg_exp);
229
230 notify_progress("Verifying regular expression...");
231
232 new Ajax.Request("backend.php", {
233 parameters: query,
234 onComplete: function(transport) {
235 var reply = JSON.parse(transport.responseText);
236
237 if (reply) {
238 notify('');
239
240 if (!reply['status']) {
241 alert("Match regular expression seems to be invalid.");
242 return;
243 } else {
244 notify_progress("Saving data...", true);
245
246 console.log(dojo.objectToQuery(dialog.attr('value')));
247
248 new Ajax.Request("backend.php", {
249 parameters: dojo.objectToQuery(dialog.attr('value')),
250 onComplete: function(transport) {
251 dialog.hide();
252 updateFilterList();
253 }})
254 }
255 }
256 }});
257 }
258 },
259 href: query});
260
261 dialog.show();
262
263
264 } catch (e) {
265 exception_error("editFilter", e);
266 }
267 }
268
269 function getSelectedLabels() {
270 var tree = dijit.byId("labelTree");
271 var items = tree.model.getCheckedItems();
272 var rv = [];
273
274 items.each(function(item) {
275 rv.push(tree.model.store.getValue(item, 'bare_id'));
276 });
277
278 return rv;
279 }
280
281 function getSelectedUsers() {
282 return getSelectedTableRowIds("prefUserList");
283 }
284
285 function getSelectedFeeds() {
286 var tree = dijit.byId("feedTree");
287 var items = tree.model.getCheckedItems();
288 var rv = [];
289
290 items.each(function(item) {
291 rv.push(tree.model.store.getValue(item, 'bare_id'));
292 });
293
294 return rv;
295 }
296
297 function getSelectedFilters() {
298 var tree = dijit.byId("filterTree");
299 var items = tree.model.getCheckedItems();
300 var rv = [];
301
302 items.each(function(item) {
303 rv.push(tree.model.store.getValue(item, 'bare_id'));
304 });
305
306 return rv;
307
308 }
309
310 /* function getSelectedFeedCats() {
311 return getSelectedTableRowIds("prefFeedCatList");
312 } */
313
314 function removeSelectedLabels() {
315
316 var sel_rows = getSelectedLabels();
317
318 if (sel_rows.length > 0) {
319
320 var ok = confirm(__("Remove selected labels?"));
321
322 if (ok) {
323 notify_progress("Removing selected labels...");
324
325 var query = "?op=pref-labels&subop=remove&ids="+
326 param_escape(sel_rows.toString());
327
328 new Ajax.Request("backend.php", {
329 parameters: query,
330 onComplete: function(transport) {
331 labellist_callback2(transport);
332 } });
333
334 }
335 } else {
336 alert(__("No labels are selected."));
337 }
338
339 return false;
340 }
341
342 function removeSelectedUsers() {
343
344 try {
345
346 var sel_rows = getSelectedUsers();
347
348 if (sel_rows.length > 0) {
349
350 var ok = confirm(__("Remove selected users? Neither default admin nor your account will be removed."));
351
352 if (ok) {
353 notify_progress("Removing selected users...");
354
355 var query = "?op=pref-users&subop=remove&ids="+
356 param_escape(sel_rows.toString());
357
358 new Ajax.Request("backend.php", {
359 parameters: query,
360 onComplete: function(transport) {
361 userlist_callback2(transport);
362 } });
363
364 }
365
366 } else {
367 alert(__("No users are selected."));
368 }
369
370 } catch (e) {
371 exception_error("removeSelectedUsers", e);
372 }
373
374 return false;
375 }
376
377 function removeSelectedFilters() {
378
379 try {
380
381 var sel_rows = getSelectedFilters();
382
383 if (sel_rows.length > 0) {
384
385 var ok = confirm(__("Remove selected filters?"));
386
387 if (ok) {
388 notify_progress("Removing selected filters...");
389
390 var query = "?op=pref-filters&subop=remove&ids="+
391 param_escape(sel_rows.toString());
392
393 new Ajax.Request("backend.php", {
394 parameters: query,
395 onComplete: function(transport) {
396 updateFilterList();
397 } });
398 }
399 } else {
400 alert(__("No filters are selected."));
401 }
402
403 } catch (e) {
404 exception_error("removeSelectedFilters", e);
405 }
406
407 return false;
408 }
409
410
411 function removeSelectedFeeds() {
412
413 try {
414
415 var sel_rows = getSelectedFeeds();
416
417 if (sel_rows.length > 0) {
418
419 var ok = confirm(__("Unsubscribe from selected feeds?"));
420
421 if (ok) {
422
423 notify_progress("Unsubscribing from selected feeds...", true);
424
425 var query = "?op=pref-feeds&subop=remove&ids="+
426 param_escape(sel_rows.toString());
427
428 console.log(query);
429
430 new Ajax.Request("backend.php", {
431 parameters: query,
432 onComplete: function(transport) {
433 updateFeedList();
434 } });
435 }
436
437 } else {
438 alert(__("No feeds are selected."));
439 }
440
441 } catch (e) {
442 exception_error("removeSelectedFeeds", e);
443 }
444
445 return false;
446 }
447
448 function clearSelectedFeeds() {
449
450 var sel_rows = getSelectedFeeds();
451
452 if (sel_rows.length > 1) {
453 alert(__("Please select only one feed."));
454 return;
455 }
456
457 if (sel_rows.length > 0) {
458
459 var ok = confirm(__("Erase all non-starred articles in selected feed?"));
460
461 if (ok) {
462 notify_progress("Clearing selected feed...");
463 clearFeedArticles(sel_rows[0]);
464 }
465
466 } else {
467
468 alert(__("No feeds are selected."));
469
470 }
471
472 return false;
473 }
474
475 function purgeSelectedFeeds() {
476
477 var sel_rows = getSelectedFeeds();
478
479 if (sel_rows.length > 0) {
480
481 var pr = prompt(__("How many days of articles to keep (0 - use default)?"), "0");
482
483 if (pr != undefined) {
484 notify_progress("Purging selected feed...");
485
486 var query = "?op=rpc&subop=purge&ids="+
487 param_escape(sel_rows.toString()) + "&days=" + pr;
488
489 console.log(query);
490
491 new Ajax.Request("prefs.php", {
492 parameters: query,
493 onComplete: function(transport) {
494 notify('');
495 } });
496 }
497
498 } else {
499
500 alert(__("No feeds are selected."));
501
502 }
503
504 return false;
505 }
506
507 function userEditCancel() {
508 closeInfoBox();
509 return false;
510 }
511
512 function userEditSave() {
513
514 try {
515
516 var login = document.forms["user_edit_form"].login.value;
517
518 if (login.length == 0) {
519 alert(__("Login field cannot be blank."));
520 return;
521 }
522
523 notify_progress("Saving user...");
524
525 closeInfoBox();
526
527 var query = Form.serialize("user_edit_form");
528
529 new Ajax.Request("backend.php", {
530 parameters: query,
531 onComplete: function(transport) {
532 userlist_callback2(transport);
533 } });
534
535 } catch (e) {
536 exception_error("userEditSave", e);
537 }
538
539 return false;
540
541 }
542
543
544 function editSelectedUser() {
545 var rows = getSelectedUsers();
546
547 if (rows.length == 0) {
548 alert(__("No users are selected."));
549 return;
550 }
551
552 if (rows.length > 1) {
553 alert(__("Please select only one user."));
554 return;
555 }
556
557 notify("");
558
559 editUser(rows[0]);
560 }
561
562 function resetSelectedUserPass() {
563
564 try {
565
566 var rows = getSelectedUsers();
567
568 if (rows.length == 0) {
569 alert(__("No users are selected."));
570 return;
571 }
572
573 if (rows.length > 1) {
574 alert(__("Please select only one user."));
575 return;
576 }
577
578 var ok = confirm(__("Reset password of selected user?"));
579
580 if (ok) {
581 notify_progress("Resetting password for selected user...");
582
583 var id = rows[0];
584
585 var query = "?op=pref-users&subop=resetPass&id=" +
586 param_escape(id);
587
588 new Ajax.Request("backend.php", {
589 parameters: query,
590 onComplete: function(transport) {
591 userlist_callback2(transport);
592 } });
593
594 }
595
596 } catch (e) {
597 exception_error("resetSelectedUserPass", e);
598 }
599 }
600
601 function selectedUserDetails() {
602
603 try {
604
605 var rows = getSelectedUsers();
606
607 if (rows.length == 0) {
608 alert(__("No users are selected."));
609 return;
610 }
611
612 if (rows.length > 1) {
613 alert(__("Please select only one user."));
614 return;
615 }
616
617 notify_progress("Loading, please wait...");
618
619 var id = rows[0];
620
621 var query = "?op=pref-users&subop=user-details&id=" + id;
622
623 new Ajax.Request("backend.php", {
624 parameters: query,
625 onComplete: function(transport) {
626 infobox_callback2(transport);
627 } });
628 } catch (e) {
629 exception_error("selectedUserDetails", e);
630 }
631 }
632
633
634 function editSelectedFilter() {
635 var rows = getSelectedFilters();
636
637 if (rows.length == 0) {
638 alert(__("No filters are selected."));
639 return;
640 }
641
642 if (rows.length > 1) {
643 alert(__("Please select only one filter."));
644 return;
645 }
646
647 notify("");
648
649 editFilter(rows[0]);
650
651 }
652
653
654 function editSelectedFeed() {
655 var rows = getSelectedFeeds();
656
657 if (rows.length == 0) {
658 alert(__("No feeds are selected."));
659 return;
660 }
661
662 if (rows.length > 1) {
663 return editSelectedFeeds();
664 }
665
666 notify("");
667
668 editFeed(rows[0], {});
669
670 }
671
672 function editSelectedFeeds() {
673
674 try {
675 var rows = getSelectedFeeds();
676
677 if (rows.length == 0) {
678 alert(__("No feeds are selected."));
679 return;
680 }
681
682 notify("");
683
684 var query = "backend.php?op=pref-feeds&subop=editfeeds&ids=" +
685 param_escape(rows.toString());
686
687 if (dijit.byId("feedEditDlg"))
688 dijit.byId("feedEditDlg").destroyRecursive();
689
690 dialog = new dijit.Dialog({
691 id: "feedEditDlg",
692 title: __("Edit Multiple Feeds"),
693 style: "width: 600px",
694 getChildByName: function (name) {
695 var rv = null
696 this.getChildren().each(
697 function(child) {
698 if (child.name == name) {
699 rv = child;
700 return;
701 }
702 });
703 return rv;
704 },
705 toggleField: function (checkbox, elem, label) {
706 this.getChildByName(elem).attr('disabled', !checkbox.checked);
707
708 if ($(label))
709 if (checkbox.checked)
710 $(label).removeClassName('insensitive');
711 else
712 $(label).addClassName('insensitive');
713
714 },
715 execute: function() {
716 if (this.validate() && confirm(__("Save changes to selected feeds?"))) {
717 var query = dojo.objectToQuery(this.attr('value'));
718
719 /* Form.serialize ignores unchecked checkboxes */
720
721 if (!query.match("&rtl_content=") &&
722 this.getChildByName('rtl_content').attr('disabled') == false) {
723 query = query + "&rtl_content=false";
724 }
725
726 if (!query.match("&private=") &&
727 this.getChildByName('private').attr('disabled') == false) {
728 query = query + "&private=false";
729 }
730
731 if (!query.match("&cache_images=") &&
732 this.getChildByName('cache_images').attr('disabled') == false) {
733 query = query + "&cache_images=false";
734 }
735
736 if (!query.match("&include_in_digest=") &&
737 this.getChildByName('include_in_digest').attr('disabled') == false) {
738 query = query + "&include_in_digest=false";
739 }
740
741 if (!query.match("&always_display_enclosures=") &&
742 this.getChildByName('always_display_enclosures').attr('disabled') == false) {
743 query = query + "&always_display_enclosures=false";
744 }
745
746 if (!query.match("&mark_unread_on_update=") &&
747 this.getChildByName('mark_unread_on_update').attr('disabled') == false) {
748 query = query + "&mark_unread_on_update=false";
749 }
750
751 if (!query.match("&update_on_checksum_change=") &&
752 this.getChildByName('update_on_checksum_change').attr('disabled') == false) {
753 query = query + "&update_on_checksum_change=false";
754 }
755
756 console.log(query);
757
758 notify_progress("Saving data...", true);
759
760 new Ajax.Request("backend.php", {
761 parameters: query,
762 onComplete: function(transport) {
763 dialog.hide();
764 updateFeedList();
765 }})
766 }
767 },
768 href: query});
769
770 dialog.show();
771
772 } catch (e) {
773 exception_error("editSelectedFeeds", e);
774 }
775 }
776
777 function piggie(enable) {
778 if (enable) {
779 console.log("I LOVEDED IT!");
780 var piggie = $("piggie");
781
782 Element.show(piggie);
783 Position.Center(piggie);
784 Effect.Puff(piggie);
785
786 }
787 }
788
789 function opmlImportComplete(iframe) {
790 try {
791 if (!iframe.contentDocument.body.innerHTML) return false;
792
793 notify('');
794
795 if (dijit.byId('opmlImportDlg'))
796 dijit.byId('opmlImportDlg').destroyRecursive();
797
798 var content = iframe.contentDocument.body.innerHTML;
799
800 dialog = new dijit.Dialog({
801 id: "opmlImportDlg",
802 title: __("OPML Import"),
803 style: "width: 600px",
804 onCancel: function() {
805 updateFeedList();
806 },
807 content: content});
808
809 dialog.show();
810
811 } catch (e) {
812 exception_error("opmlImportComplete", e);
813 }
814 }
815
816 function opmlImport() {
817
818 var opml_file = $("opml_file");
819
820 if (opml_file.value.length == 0) {
821 alert(__("Please choose an OPML file first."));
822 return false;
823 } else {
824 notify_progress("Importing, please wait...", true);
825 return true;
826 }
827 }
828
829 function updateFilterList() {
830 new Ajax.Request("backend.php", {
831 parameters: "?op=pref-filters",
832 onComplete: function(transport) {
833 filterlist_callback2(transport);
834 } });
835 }
836
837 function updateLabelList() {
838 new Ajax.Request("backend.php", {
839 parameters: "?op=pref-labels",
840 onComplete: function(transport) {
841 labellist_callback2(transport);
842 } });
843 }
844
845 function updatePrefsList() {
846 new Ajax.Request("backend.php", {
847 parameters: "?op=pref-prefs",
848 onComplete: function(transport) {
849 prefslist_callback2(transport);
850 } });
851 }
852
853 function selectTab(id, noupdate, subop) {
854 try {
855 if (!noupdate) {
856 notify_progress("Loading, please wait...");
857
858 if (id == "feedConfig") {
859 updateFeedList();
860 } else if (id == "filterConfig") {
861 updateFilterList();
862 } else if (id == "labelConfig") {
863 updateLabelList();
864 } else if (id == "genConfig") {
865 updatePrefsList();
866 } else if (id == "userConfig") {
867 updateUsersList();
868 }
869
870 var tab = dijit.byId(id + "Tab");
871 dijit.byId("pref-tabs").selectChild(tab);
872
873 }
874
875 } catch (e) {
876 exception_error("selectTab", e);
877 }
878 }
879
880 function init_second_stage() {
881 try {
882
883 document.onkeydown = pref_hotkey_handler;
884 loading_set_progress(50);
885 notify("");
886
887 dojo.addOnLoad(function() {
888 var tab = getURLParam('tab');
889
890 if (tab) {
891 tab = dijit.byId(tab + "Tab");
892 if (tab) dijit.byId("pref-tabs").selectChild(tab);
893 }
894
895 var subop = getURLParam('subop');
896
897 if (subop == 'editFeed') {
898 var param = getURLParam('subopparam');
899
900 window.setTimeout('editFeed(' + param + ')', 100);
901 }
902 });
903
904 setTimeout("hotkey_prefix_timeout()", 5*1000);
905
906 } catch (e) {
907 exception_error("init_second_stage", e);
908 }
909 }
910
911 function init() {
912
913 try {
914 dojo.registerModulePath("lib", "..");
915 dojo.registerModulePath("fox", "../..");
916
917 dojo.require("lib.CheckBoxTree");
918 dojo.require("fox.PrefFeedTree");
919 dojo.require("fox.PrefFilterTree");
920 dojo.require("fox.PrefLabelTree");
921
922 dojo.parser.parse();
923
924 dojo.addOnLoad(function() {
925 loading_set_progress(50);
926
927 new Ajax.Request("backend.php", {
928 parameters: {op: "rpc", subop: "sanityCheck"},
929 onComplete: function(transport) {
930 backend_sanity_check_callback(transport);
931 } });
932 });
933
934 } catch (e) {
935 exception_error("init", e);
936 }
937 }
938
939 function validatePrefsReset() {
940 try {
941 var ok = confirm(__("Reset to defaults?"));
942
943 if (ok) {
944
945 query = "?op=pref-prefs&subop=reset-config";
946 console.log(query);
947
948 new Ajax.Request("backend.php", {
949 parameters: query,
950 onComplete: function(transport) {
951 var msg = transport.responseText;
952 if (msg.match("PREFS_THEME_CHANGED")) {
953 window.location.reload();
954 } else {
955 notify_info(msg);
956 selectTab();
957 }
958 } });
959
960 }
961
962 } catch (e) {
963 exception_error("validatePrefsReset", e);
964 }
965
966 return false;
967
968 }
969
970
971 function pref_hotkey_handler(e) {
972 try {
973 if (e.target.nodeName == "INPUT") return;
974
975 var keycode;
976 var shift_key = false;
977
978 var cmdline = $('cmdline');
979
980 try {
981 shift_key = e.shiftKey;
982 } catch (e) {
983
984 }
985
986 if (window.event) {
987 keycode = window.event.keyCode;
988 } else if (e) {
989 keycode = e.which;
990 }
991
992 var keychar = String.fromCharCode(keycode);
993
994 if (keycode == 27) { // escape
995 if (Element.visible("hotkey_help_overlay")) {
996 Element.hide("hotkey_help_overlay");
997 }
998 hotkey_prefix = false;
999 closeInfoBox();
1000 }
1001
1002 if (keycode == 16) return; // ignore lone shift
1003 if (keycode == 17) return; // ignore lone ctrl
1004
1005 if ((keycode == 67 || keycode == 71) && !hotkey_prefix) {
1006 hotkey_prefix = keycode;
1007
1008 var date = new Date();
1009 var ts = Math.round(date.getTime() / 1000);
1010
1011 hotkey_prefix_pressed = ts;
1012
1013 cmdline.innerHTML = keychar;
1014 Element.show(cmdline);
1015
1016 console.log("KP: PREFIX=" + keycode + " CHAR=" + keychar);
1017 return;
1018 }
1019
1020 if (Element.visible("hotkey_help_overlay")) {
1021 Element.hide("hotkey_help_overlay");
1022 }
1023
1024 if (keycode == 13 || keycode == 27) {
1025 seq = "";
1026 } else {
1027 seq = seq + "" + keycode;
1028 }
1029
1030 /* Global hotkeys */
1031
1032 Element.hide(cmdline);
1033
1034 if (!hotkey_prefix) {
1035
1036 if ((keycode == 191 || keychar == '?') && shift_key) { // ?
1037 if (!Element.visible("hotkey_help_overlay")) {
1038 //Element.show("hotkey_help_overlay");
1039 Effect.Appear("hotkey_help_overlay", {duration : 0.3});
1040 } else {
1041 Element.hide("hotkey_help_overlay");
1042 }
1043 return false;
1044 }
1045
1046 if (keycode == 191 || keychar == '/') { // /
1047 var search_boxes = new Array("label_search",
1048 "feed_search", "filter_search", "user_search", "feed_browser_search");
1049
1050 for (var i = 0; i < search_boxes.length; i++) {
1051 var elem = $(search_boxes[i]);
1052 if (elem) {
1053 $(search_boxes[i]).focus();
1054 return false;
1055 }
1056 }
1057 }
1058 }
1059
1060 /* Prefix c */
1061
1062 if (hotkey_prefix == 67) { // c
1063 hotkey_prefix = false;
1064
1065 if (keycode == 70) { // f
1066 quickAddFilter();
1067 return false;
1068 }
1069
1070 if (keycode == 83) { // s
1071 quickAddFeed();
1072 return false;
1073 }
1074
1075 if (keycode == 85) { // u
1076 // no-op
1077 }
1078
1079 if (keycode == 67) { // c
1080 editFeedCats();
1081 return false;
1082 }
1083
1084 if (keycode == 84 && shift_key) { // T
1085 feedBrowser();
1086 return false;
1087 }
1088
1089 }
1090
1091 /* Prefix g */
1092
1093 if (hotkey_prefix == 71) { // g
1094
1095 hotkey_prefix = false;
1096
1097 if (keycode == 49 && $("genConfigTab")) { // 1
1098 selectTab("genConfig");
1099 return false;
1100 }
1101
1102 if (keycode == 50 && $("feedConfigTab")) { // 2
1103 selectTab("feedConfig");
1104 return false;
1105 }
1106
1107 if (keycode == 51 && $("filterConfigTab")) { // 4
1108 selectTab("filterConfig");
1109 return false;
1110 }
1111
1112 if (keycode == 52 && $("labelConfigTab")) { // 5
1113 selectTab("labelConfig");
1114 return false;
1115 }
1116
1117 if (keycode == 53 && $("userConfigTab")) { // 6
1118 selectTab("userConfig");
1119 return false;
1120 }
1121
1122 if (keycode == 88) { // x
1123 return gotoMain();
1124 }
1125
1126 }
1127
1128 if ($("piggie")) {
1129 if (seq.match("8073717369")) {
1130 seq = "";
1131 piggie(true);
1132 } else {
1133 piggie(false);
1134 }
1135 }
1136
1137 if (hotkey_prefix) {
1138 console.log("KP: PREFIX=" + hotkey_prefix + " CODE=" + keycode + " CHAR=" + keychar);
1139 } else {
1140 console.log("KP: CODE=" + keycode + " CHAR=" + keychar);
1141 }
1142
1143 } catch (e) {
1144 exception_error("pref_hotkey_handler", e);
1145 }
1146 }
1147
1148 function editFeedCats() {
1149 try {
1150 var query = "backend.php?op=pref-feeds&subop=editCats";
1151
1152 if (dijit.byId("feedCatEditDlg"))
1153 dijit.byId("feedCatEditDlg").destroyRecursive();
1154
1155 dialog = new dijit.Dialog({
1156 id: "feedCatEditDlg",
1157 title: __("Feed Categories"),
1158 style: "width: 600px",
1159 getSelectedCategories: function() {
1160 return getSelectedTableRowIds("prefFeedCatList");
1161 },
1162 removeSelected: function() {
1163 var sel_rows = this.getSelectedCategories();
1164
1165 if (sel_rows.length > 0) {
1166 var ok = confirm(__("Remove selected categories?"));
1167
1168 if (ok) {
1169 notify_progress("Removing selected categories...", true);
1170
1171 var query = "?op=pref-feeds&subop=editCats&action=remove&ids="+
1172 param_escape(sel_rows.toString());
1173
1174 new Ajax.Request("backend.php", {
1175 parameters: query,
1176 onComplete: function(transport) {
1177 notify('');
1178 dialog.attr('content', transport.responseText);
1179 updateFeedList();
1180 } });
1181
1182 }
1183
1184 } else {
1185 alert(__("No categories are selected."));
1186 }
1187 },
1188 addCategory: function() {
1189 if (this.validate()) {
1190 notify_progress("Creating category...");
1191
1192 var query = "?op=pref-feeds&subop=editCats&action=add&cat=" +
1193 param_escape(this.attr('value').newcat);
1194
1195 new Ajax.Request("backend.php", {
1196 parameters: query,
1197 onComplete: function(transport) {
1198 notify('');
1199 dialog.attr('content', transport.responseText);
1200 updateFeedList();
1201 } });
1202 }
1203 },
1204 execute: function() {
1205 if (this.validate()) {
1206 }
1207 },
1208 href: query});
1209
1210 dialog.show();
1211
1212 } catch (e) {
1213 exception_error("editFeedCats", e);
1214 }
1215 }
1216
1217 function showInactiveFeeds() {
1218 try {
1219 var query = "backend.php?op=dlg&id=inactiveFeeds";
1220
1221 if (dijit.byId("inactiveFeedsDlg"))
1222 dijit.byId("inactiveFeedsDlg").destroyRecursive();
1223
1224 dialog = new dijit.Dialog({
1225 id: "inactiveFeedsDlg",
1226 title: __("Feeds without recent updates"),
1227 style: "width: 600px",
1228 getSelectedFeeds: function() {
1229 return getSelectedTableRowIds("prefInactiveFeedList");
1230 },
1231 removeSelected: function() {
1232 var sel_rows = this.getSelectedFeeds();
1233
1234 console.log(sel_rows);
1235
1236 if (sel_rows.length > 0) {
1237 var ok = confirm(__("Remove selected feeds?"));
1238
1239 if (ok) {
1240 notify_progress("Removing selected feeds...", true);
1241
1242 var query = "?op=pref-feeds&subop=remove&ids="+
1243 param_escape(sel_rows.toString());
1244
1245 new Ajax.Request("backend.php", {
1246 parameters: query,
1247 onComplete: function(transport) {
1248 notify('');
1249 dialog.hide();
1250 updateFeedList();
1251 } });
1252 }
1253
1254 } else {
1255 alert(__("No feeds are selected."));
1256 }
1257 },
1258 execute: function() {
1259 if (this.validate()) {
1260 }
1261 },
1262 href: query});
1263
1264 dialog.show();
1265
1266 } catch (e) {
1267 exception_error("showInactiveFeeds", e);
1268 }
1269
1270 }
1271
1272 function opmlRegenKey() {
1273
1274 try {
1275 var ok = confirm(__("Replace current OPML publishing address with a new one?"));
1276
1277 if (ok) {
1278
1279 notify_progress("Trying to change address...", true);
1280
1281 var query = "?op=rpc&subop=regenOPMLKey";
1282
1283 new Ajax.Request("backend.php", {
1284 parameters: query,
1285 onComplete: function(transport) {
1286 var reply = JSON.parse(transport.responseText);
1287
1288 var new_link = reply.link;
1289
1290 var e = $('pub_opml_url');
1291
1292 if (new_link) {
1293 e.href = new_link;
1294 e.innerHTML = new_link;
1295
1296 new Effect.Highlight(e);
1297
1298 notify('');
1299
1300 } else {
1301 notify_error("Could not change feed URL.");
1302 }
1303 } });
1304 }
1305 } catch (e) {
1306 exception_error("opmlRegenKey", e);
1307 }
1308 return false;
1309 }
1310
1311 function feedActionChange() {
1312 try {
1313 var chooser = $("feedActionChooser");
1314 var opid = chooser[chooser.selectedIndex].value;
1315
1316 chooser.selectedIndex = 0;
1317 feedActionGo(opid);
1318 } catch (e) {
1319 exception_error("feedActionChange", e);
1320 }
1321 }
1322
1323 function feedActionGo(op) {
1324 try {
1325 if (op == "facEdit") {
1326
1327 var rows = getSelectedFeeds();
1328
1329 if (rows.length > 1) {
1330 editSelectedFeeds();
1331 } else {
1332 editSelectedFeed();
1333 }
1334 }
1335
1336 if (op == "facClear") {
1337 clearSelectedFeeds();
1338 }
1339
1340 if (op == "facPurge") {
1341 purgeSelectedFeeds();
1342 }
1343
1344 if (op == "facEditCats") {
1345 editFeedCats();
1346 }
1347
1348 if (op == "facRescore") {
1349 rescoreSelectedFeeds();
1350 }
1351
1352 if (op == "facUnsubscribe") {
1353 removeSelectedFeeds();
1354 }
1355
1356 } catch (e) {
1357 exception_error("feedActionGo", e);
1358
1359 }
1360 }
1361
1362 function clearFeedArticles(feed_id) {
1363
1364 notify_progress("Clearing feed...");
1365
1366 var query = "?op=pref-feeds&quiet=1&subop=clear&id=" + feed_id;
1367
1368 new Ajax.Request("backend.php", {
1369 parameters: query,
1370 onComplete: function(transport) {
1371 notify('');
1372 } });
1373
1374 return false;
1375 }
1376
1377 function rescoreSelectedFeeds() {
1378
1379 var sel_rows = getSelectedFeeds();
1380
1381 if (sel_rows.length > 0) {
1382
1383 //var ok = confirm(__("Rescore last 100 articles in selected feeds?"));
1384 var ok = confirm(__("Rescore articles in selected feeds?"));
1385
1386 if (ok) {
1387 notify_progress("Rescoring selected feeds...", true);
1388
1389 var query = "?op=pref-feeds&subop=rescore&quiet=1&ids="+
1390 param_escape(sel_rows.toString());
1391
1392 new Ajax.Request("backend.php", {
1393 parameters: query,
1394 onComplete: function(transport) {
1395 notify_callback2(transport);
1396 } });
1397
1398 }
1399 } else {
1400 alert(__("No feeds are selected."));
1401 }
1402
1403 return false;
1404 }
1405
1406 function rescore_all_feeds() {
1407 var ok = confirm(__("Rescore all articles? This operation may take a lot of time."));
1408
1409 if (ok) {
1410 notify_progress("Rescoring feeds...", true);
1411
1412 var query = "?op=pref-feeds&subop=rescoreAll&quiet=1";
1413
1414 new Ajax.Request("backend.php", {
1415 parameters: query,
1416 onComplete: function(transport) {
1417 notify_callback2(transport);
1418 } });
1419 }
1420 }
1421
1422 function labelColorReset() {
1423 try {
1424 var labels = getSelectedLabels();
1425
1426 if (labels.length > 0) {
1427 var ok = confirm(__("Reset selected labels to default colors?"));
1428
1429 if (ok) {
1430 var query = "?op=pref-labels&subop=color-reset&ids="+
1431 param_escape(labels.toString());
1432
1433 new Ajax.Request("backend.php", {
1434 parameters: query,
1435 onComplete: function(transport) {
1436 labellist_callback2(transport);
1437 } });
1438 }
1439
1440 } else {
1441 alert(__("No labels are selected."));
1442 }
1443
1444 } catch (e) {
1445 exception_error("labelColorReset", e);
1446 }
1447 }
1448
1449
1450 function inPreferences() {
1451 return true;
1452 }
1453
1454 function editProfiles() {
1455 try {
1456
1457 if (dijit.byId("profileEditDlg"))
1458 dijit.byId("profileEditDlg").destroyRecursive();
1459
1460 var query = "backend.php?op=dlg&id=editPrefProfiles";
1461
1462 dialog = new dijit.Dialog({
1463 id: "profileEditDlg",
1464 title: __("Settings Profiles"),
1465 style: "width: 600px",
1466 getSelectedProfiles: function() {
1467 return getSelectedTableRowIds("prefFeedProfileList");
1468 },
1469 removeSelected: function() {
1470 var sel_rows = this.getSelectedProfiles();
1471
1472 if (sel_rows.length > 0) {
1473 var ok = confirm(__("Remove selected profiles? Active and default profiles will not be removed."));
1474
1475 if (ok) {
1476 notify_progress("Removing selected profiles...", true);
1477
1478 var query = "?op=rpc&subop=remprofiles&ids="+
1479 param_escape(sel_rows.toString());
1480
1481 new Ajax.Request("backend.php", {
1482 parameters: query,
1483 onComplete: function(transport) {
1484 notify('');
1485 editProfiles();
1486 } });
1487
1488 }
1489
1490 } else {
1491 alert(__("No profiles are selected."));
1492 }
1493 },
1494 activateProfile: function() {
1495 var sel_rows = this.getSelectedProfiles();
1496
1497 if (sel_rows.length == 1) {
1498
1499 var ok = confirm(__("Activate selected profile?"));
1500
1501 if (ok) {
1502 notify_progress("Loading, please wait...");
1503
1504 var query = "?op=rpc&subop=setprofile&id="+
1505 param_escape(sel_rows.toString());
1506
1507 new Ajax.Request("backend.php", {
1508 parameters: query,
1509 onComplete: function(transport) {
1510 window.location.reload();
1511 } });
1512 }
1513
1514 } else {
1515 alert(__("Please choose a profile to activate."));
1516 }
1517 },
1518 addProfile: function() {
1519 if (this.validate()) {
1520 notify_progress("Creating profile...", true);
1521
1522 var query = "?op=rpc&subop=addprofile&title=" +
1523 param_escape(dialog.attr('value').newprofile);
1524
1525 new Ajax.Request("backend.php", {
1526 parameters: query,
1527 onComplete: function(transport) {
1528 notify('');
1529 editProfiles();
1530 } });
1531
1532 }
1533 },
1534 execute: function() {
1535 if (this.validate()) {
1536 }
1537 },
1538 href: query});
1539
1540 dialog.show();
1541 } catch (e) {
1542 exception_error("editProfiles", e);
1543 }
1544 }
1545
1546 function activatePrefProfile() {
1547
1548 var sel_rows = getSelectedFeedCats();
1549
1550 if (sel_rows.length == 1) {
1551
1552 var ok = confirm(__("Activate selected profile?"));
1553
1554 if (ok) {
1555 notify_progress("Loading, please wait...");
1556
1557 var query = "?op=rpc&subop=setprofile&id="+
1558 param_escape(sel_rows.toString());
1559
1560 new Ajax.Request("backend.php", {
1561 parameters: query,
1562 onComplete: function(transport) {
1563 window.location.reload();
1564 } });
1565 }
1566
1567 } else {
1568 alert(__("Please choose a profile to activate."));
1569 }
1570
1571 return false;
1572 }
1573
1574 function clearFeedAccessKeys() {
1575
1576 var ok = confirm(__("This will invalidate all previously generated feed URLs. Continue?"));
1577
1578 if (ok) {
1579 notify_progress("Clearing URLs...");
1580
1581 var query = "?op=rpc&subop=clearKeys";
1582
1583 new Ajax.Request("backend.php", {
1584 parameters: query,
1585 onComplete: function(transport) {
1586 notify_info("Generated URLs cleared.");
1587 } });
1588 }
1589
1590 return false;
1591 }
1592
1593 function resetFeedOrder() {
1594 try {
1595 notify_progress("Loading, please wait...");
1596
1597 new Ajax.Request("backend.php", {
1598 parameters: "?op=pref-feeds&subop=feedsortreset",
1599 onComplete: function(transport) {
1600 updateFeedList();
1601 } });
1602
1603
1604 } catch (e) {
1605 exception_error("resetFeedOrder");
1606 }
1607 }
1608
1609 function resetCatOrder() {
1610 try {
1611 notify_progress("Loading, please wait...");
1612
1613 new Ajax.Request("backend.php", {
1614 parameters: "?op=pref-feeds&subop=catsortreset",
1615 onComplete: function(transport) {
1616 updateFeedList();
1617 } });
1618
1619
1620 } catch (e) {
1621 exception_error("resetCatOrder");
1622 }
1623 }
1624
1625 function editCat(id, item, event) {
1626 try {
1627 var new_name = prompt(__('Rename category to:'), item.name);
1628
1629 if (new_name && new_name != item.name) {
1630
1631 notify_progress("Loading, please wait...");
1632
1633 new Ajax.Request("backend.php", {
1634 parameters: {
1635 op: 'pref-feeds',
1636 subop: 'renamecat',
1637 id: id,
1638 title: new_name,
1639 },
1640 onComplete: function(transport) {
1641 updateFeedList();
1642 } });
1643 }
1644
1645 } catch (e) {
1646 exception_error("editCat", e);
1647 }
1648 }
1649
1650 function editLabel(id, event) {
1651 try {
1652 var query = "backend.php?op=pref-labels&subop=edit&id=" +
1653 param_escape(id);
1654
1655 if (dijit.byId("labelEditDlg"))
1656 dijit.byId("labelEditDlg").destroyRecursive();
1657
1658 dialog = new dijit.Dialog({
1659 id: "labelEditDlg",
1660 title: __("Label Editor"),
1661 style: "width: 600px",
1662 setLabelColor: function(id, fg, bg) {
1663
1664 var kind = '';
1665 var color = '';
1666
1667 if (fg && bg) {
1668 kind = 'both';
1669 } else if (fg) {
1670 kind = 'fg';
1671 color = fg;
1672 } else if (bg) {
1673 kind = 'bg';
1674 color = bg;
1675 }
1676
1677 var query = "?op=pref-labels&subop=color-set&kind="+kind+
1678 "&ids=" + param_escape(id) + "&fg=" + param_escape(fg) +
1679 "&bg=" + param_escape(bg) + "&color=" + param_escape(color);
1680
1681 // console.log(query);
1682
1683 var e = $("LICID-" + id);
1684
1685 if (e) {
1686 if (fg) e.style.color = fg;
1687 if (bg) e.style.backgroundColor = bg;
1688 }
1689
1690 new Ajax.Request("backend.php", { parameters: query });
1691
1692 updateFilterList();
1693 },
1694 execute: function() {
1695 if (this.validate()) {
1696 var caption = this.attr('value').id;
1697 var caption = this.attr('value').caption;
1698 var fg_color = this.attr('value').fg_color;
1699 var bg_color = this.attr('value').bg_color;
1700 var query = dojo.objectToQuery(this.attr('value'));
1701
1702 dijit.byId('labelTree').setNameById(id, caption);
1703 this.setLabelColor(id, fg_color, bg_color);
1704 this.hide();
1705
1706 new Ajax.Request("backend.php", {
1707 parameters: query,
1708 onComplete: function(transport) {
1709 updateFilterList();
1710 } });
1711 }
1712 },
1713 href: query});
1714
1715 dialog.show();
1716
1717 } catch (e) {
1718 exception_error("editLabel", e);
1719 }
1720 }
1721
1722 function clearTwitterCredentials() {
1723 try {
1724 var ok = confirm(__("This will clear your stored authentication information for Twitter. Continue?"));
1725
1726 if (ok) {
1727 notify_progress("Clearing credentials...");
1728
1729 var query = "?op=pref-feeds&subop=remtwitterinfo";
1730
1731 new Ajax.Request("backend.php", {
1732 parameters: query,
1733 onComplete: function(transport) {
1734 notify_info("Twitter credentials have been cleared.");
1735 updateFeedList();
1736 } });
1737 }
1738
1739 } catch (e) {
1740 exception_error("clearTwitterCredentials", e);
1741 }
1742 }
1743
1744 function customizeCSS() {
1745 try {
1746 var query = "backend.php?op=dlg&id=customizeCSS";
1747
1748 if (dijit.byId("cssEditDlg"))
1749 dijit.byId("cssEditDlg").destroyRecursive();
1750
1751 dialog = new dijit.Dialog({
1752 id: "cssEditDlg",
1753 title: __("Customize stylesheet"),
1754 style: "width: 600px",
1755 execute: function() {
1756 notify_progress('Saving data...', true);
1757 new Ajax.Request("backend.php", {
1758 parameters: dojo.objectToQuery(this.attr('value')),
1759 onComplete: function(transport) {
1760 notify('');
1761 window.location.reload();
1762 } });
1763
1764 },
1765 href: query});
1766
1767 dialog.show();
1768
1769 } catch (e) {
1770 exception_error("customizeCSS", e);
1771 }
1772 }
1773
1774 function insertSSLserial(value) {
1775 try {
1776 dijit.byId("SSL_CERT_SERIAL").attr('value', value);
1777 } catch (e) {
1778 exception_error("insertSSLcerial", e);
1779 }
1780 }
1781
1782 function getSelectedInstances() {
1783 return getSelectedTableRowIds("prefInstanceList");
1784 }
1785
1786 function addInstance() {
1787 try {
1788 var query = "backend.php?op=dlg&id=addInstance";
1789
1790 if (dijit.byId("instanceAddDlg"))
1791 dijit.byId("instanceAddDlg").destroyRecursive();
1792
1793 dialog = new dijit.Dialog({
1794 id: "instanceAddDlg",
1795 title: __("Link Instance"),
1796 style: "width: 600px",
1797 regenKey: function() {
1798 new Ajax.Request("backend.php", {
1799 parameters: "?op=rpc&subop=genHash",
1800 onComplete: function(transport) {
1801 var reply = JSON.parse(transport.responseText);
1802 if (reply)
1803 dijit.byId('instance_add_key').attr('value', reply.hash);
1804
1805 } });
1806 },
1807 execute: function() {
1808 if (this.validate()) {
1809 console.warn(dojo.objectToQuery(this.attr('value')));
1810
1811 notify_progress('Saving data...', true);
1812 new Ajax.Request("backend.php", {
1813 parameters: dojo.objectToQuery(this.attr('value')),
1814 onComplete: function(transport) {
1815 dialog.hide();
1816 notify('');
1817 updateInstanceList();
1818 } });
1819 }
1820 },
1821 href: query,
1822 });
1823
1824 dialog.show();
1825
1826 } catch (e) {
1827 exception_error("addInstance", e);
1828 }
1829 }
1830
1831 function editInstance(id, event) {
1832 try {
1833 if (!event || !event.ctrlKey) {
1834
1835 selectTableRows('prefInstanceList', 'none');
1836 selectTableRowById('LIRR-'+id, 'LICHK-'+id, true);
1837
1838 var query = "backend.php?op=pref-instances&subop=edit&id=" +
1839 param_escape(id);
1840
1841 if (dijit.byId("instanceEditDlg"))
1842 dijit.byId("instanceEditDlg").destroyRecursive();
1843
1844 dialog = new dijit.Dialog({
1845 id: "instanceEditDlg",
1846 title: __("Edit Instance"),
1847 style: "width: 600px",
1848 regenKey: function() {
1849 new Ajax.Request("backend.php", {
1850 parameters: "?op=rpc&subop=genHash",
1851 onComplete: function(transport) {
1852 var reply = JSON.parse(transport.responseText);
1853 if (reply)
1854 dijit.byId('instance_edit_key').attr('value', reply.hash);
1855
1856 } });
1857 },
1858 execute: function() {
1859 if (this.validate()) {
1860 // console.warn(dojo.objectToQuery(this.attr('value')));
1861
1862 notify_progress('Saving data...', true);
1863 new Ajax.Request("backend.php", {
1864 parameters: dojo.objectToQuery(this.attr('value')),
1865 onComplete: function(transport) {
1866 dialog.hide();
1867 notify('');
1868 updateInstanceList();
1869 } });
1870 }
1871 },
1872 href: query,
1873 });
1874
1875 dialog.show();
1876
1877 } else if (event.ctrlKey) {
1878 var cb = $('LICHK-' + id);
1879 cb.checked = !cb.checked;
1880 toggleSelectRow(cb);
1881 }
1882
1883
1884 } catch (e) {
1885 exception_error("editInstance", e);
1886 }
1887 }
1888
1889 function removeSelectedInstances() {
1890 try {
1891 var sel_rows = getSelectedInstances();
1892
1893 if (sel_rows.length > 0) {
1894
1895 var ok = confirm(__("Remove selected instances?"));
1896
1897 if (ok) {
1898 notify_progress("Removing selected instances...");
1899
1900 var query = "?op=pref-instances&subop=remove&ids="+
1901 param_escape(sel_rows.toString());
1902
1903 new Ajax.Request("backend.php", {
1904 parameters: query,
1905 onComplete: function(transport) {
1906 notify('');
1907 updateInstanceList();
1908 } });
1909 }
1910
1911 } else {
1912 alert(__("No instances are selected."));
1913 }
1914
1915 } catch (e) {
1916 exception_error("removeInstance", e);
1917 }
1918 }
1919
1920 function editSelectedInstance() {
1921 var rows = getSelectedInstances();
1922
1923 if (rows.length == 0) {
1924 alert(__("No instances are selected."));
1925 return;
1926 }
1927
1928 if (rows.length > 1) {
1929 alert(__("Please select only one instance."));
1930 return;
1931 }
1932
1933 notify("");
1934
1935 editInstance(rows[0]);
1936 }
1937