Fossil SCM
When approving, rejecting, or changing the status of a forum post using the new controls, disable the associated button when its underlying form is submitted so that the user is not tempted to tap those again. Successful posting redirects the page and a failure will unblock the button after a brief wait.
Commit
e0c33b0519d6000d1b49386ad3b60e1fc28de69805e1da3e50784aef61417724
Parent
071cf8cb58fd53c…
1 file changed
+26
-3
+26
-3
| --- src/fossil.page.forumpost.js | ||
| +++ src/fossil.page.forumpost.js | ||
| @@ -990,10 +990,33 @@ | ||
| 990 | 990 | state. */ |
| 991 | 991 | setTimeout(()=>{delete form.dataset.submitted}, 7000); |
| 992 | 992 | return; |
| 993 | 993 | }; |
| 994 | 994 | |
| 995 | + /** | |
| 996 | + The problem: "Approve", "Reject" and their ilk stays active | |
| 997 | + while their form post is in flight, just begging to be | |
| 998 | + clicked again. We don't really have a way to know when the | |
| 999 | + submit finishes, so we'll (A) disable them before submit and | |
| 1000 | + (B) reenable them after some long timeout in case the request | |
| 1001 | + fails. On a successful submit we're redirected, making this | |
| 1002 | + all moot. | |
| 1003 | + */ | |
| 1004 | + const deactivateButtonInFlight = (elem, form)=>{ | |
| 1005 | + if( elem.disabled ) return /* potential double-click protection */; | |
| 1006 | + elem.disabled = true; | |
| 1007 | + setTimeout( | |
| 1008 | + /* This is a stupid workaround for a failed request but none | |
| 1009 | + better come to mind except replacing the form.submit() | |
| 1010 | + with a fetch() POST, such that we can do this at | |
| 1011 | + precisely the right time. That change would not be | |
| 1012 | + difficult (we do this in /chat, /wikiedit, etc.) but | |
| 1013 | + that exceeds this morning's ambitions. */ | |
| 1014 | + ()=>elem.disabled = false, 10000 | |
| 1015 | + ); | |
| 1016 | + form.submit(); | |
| 1017 | + }; | |
| 995 | 1018 | document.querySelectorAll("form").forEach(function(form){ |
| 996 | 1019 | /* Set up controls for closing posts and setting thread |
| 997 | 1020 | status. */ |
| 998 | 1021 | form.addEventListener('submit', formSubmitted); |
| 999 | 1022 | form |
| @@ -1002,11 +1025,11 @@ | ||
| 1002 | 1025 | e.classList.remove('hidden'); |
| 1003 | 1026 | F.confirmer(e, { |
| 1004 | 1027 | confirmText: (e.classList.contains('action-reopen') |
| 1005 | 1028 | ? "Confirm re-open" |
| 1006 | 1029 | : "Confirm close"), |
| 1007 | - onconfirm: ()=>form.submit() | |
| 1030 | + onconfirm: ()=>deactivateButtonInFlight(e, form) | |
| 1008 | 1031 | }); |
| 1009 | 1032 | }); |
| 1010 | 1033 | form |
| 1011 | 1034 | .querySelectorAll("input.action-approve, input.action-reject") |
| 1012 | 1035 | .forEach(function(e){ |
| @@ -1025,11 +1048,11 @@ | ||
| 1025 | 1048 | D.input('hidden'), |
| 1026 | 1049 | 'name', isApprove ? 'approve' : 'reject', |
| 1027 | 1050 | 'value', 'ignored' |
| 1028 | 1051 | ) |
| 1029 | 1052 | ); |
| 1030 | - form.submit(); | |
| 1053 | + deactivateButtonInFlight(e, form); | |
| 1031 | 1054 | } |
| 1032 | 1055 | }); |
| 1033 | 1056 | /* We should also arguably disable the approve/reject |
| 1034 | 1057 | button's counterpart while it's counting down. */ |
| 1035 | 1058 | }); |
| @@ -1056,11 +1079,11 @@ | ||
| 1056 | 1079 | }; |
| 1057 | 1080 | sel.addEventListener('change', updateButton, true); |
| 1058 | 1081 | updateButton(); |
| 1059 | 1082 | F.confirmer(btn, { |
| 1060 | 1083 | confirmText: "Confirm status change", |
| 1061 | - onconfirm: ()=>form.submit() | |
| 1084 | + onconfirm: ()=>deactivateButtonInFlight(btn, form) | |
| 1062 | 1085 | }); |
| 1063 | 1086 | }); |
| 1064 | 1087 | }); |
| 1065 | 1088 | } |
| 1066 | 1089 | |
| 1067 | 1090 |
| --- src/fossil.page.forumpost.js | |
| +++ src/fossil.page.forumpost.js | |
| @@ -990,10 +990,33 @@ | |
| 990 | state. */ |
| 991 | setTimeout(()=>{delete form.dataset.submitted}, 7000); |
| 992 | return; |
| 993 | }; |
| 994 | |
| 995 | document.querySelectorAll("form").forEach(function(form){ |
| 996 | /* Set up controls for closing posts and setting thread |
| 997 | status. */ |
| 998 | form.addEventListener('submit', formSubmitted); |
| 999 | form |
| @@ -1002,11 +1025,11 @@ | |
| 1002 | e.classList.remove('hidden'); |
| 1003 | F.confirmer(e, { |
| 1004 | confirmText: (e.classList.contains('action-reopen') |
| 1005 | ? "Confirm re-open" |
| 1006 | : "Confirm close"), |
| 1007 | onconfirm: ()=>form.submit() |
| 1008 | }); |
| 1009 | }); |
| 1010 | form |
| 1011 | .querySelectorAll("input.action-approve, input.action-reject") |
| 1012 | .forEach(function(e){ |
| @@ -1025,11 +1048,11 @@ | |
| 1025 | D.input('hidden'), |
| 1026 | 'name', isApprove ? 'approve' : 'reject', |
| 1027 | 'value', 'ignored' |
| 1028 | ) |
| 1029 | ); |
| 1030 | form.submit(); |
| 1031 | } |
| 1032 | }); |
| 1033 | /* We should also arguably disable the approve/reject |
| 1034 | button's counterpart while it's counting down. */ |
| 1035 | }); |
| @@ -1056,11 +1079,11 @@ | |
| 1056 | }; |
| 1057 | sel.addEventListener('change', updateButton, true); |
| 1058 | updateButton(); |
| 1059 | F.confirmer(btn, { |
| 1060 | confirmText: "Confirm status change", |
| 1061 | onconfirm: ()=>form.submit() |
| 1062 | }); |
| 1063 | }); |
| 1064 | }); |
| 1065 | } |
| 1066 | |
| 1067 |
| --- src/fossil.page.forumpost.js | |
| +++ src/fossil.page.forumpost.js | |
| @@ -990,10 +990,33 @@ | |
| 990 | state. */ |
| 991 | setTimeout(()=>{delete form.dataset.submitted}, 7000); |
| 992 | return; |
| 993 | }; |
| 994 | |
| 995 | /** |
| 996 | The problem: "Approve", "Reject" and their ilk stays active |
| 997 | while their form post is in flight, just begging to be |
| 998 | clicked again. We don't really have a way to know when the |
| 999 | submit finishes, so we'll (A) disable them before submit and |
| 1000 | (B) reenable them after some long timeout in case the request |
| 1001 | fails. On a successful submit we're redirected, making this |
| 1002 | all moot. |
| 1003 | */ |
| 1004 | const deactivateButtonInFlight = (elem, form)=>{ |
| 1005 | if( elem.disabled ) return /* potential double-click protection */; |
| 1006 | elem.disabled = true; |
| 1007 | setTimeout( |
| 1008 | /* This is a stupid workaround for a failed request but none |
| 1009 | better come to mind except replacing the form.submit() |
| 1010 | with a fetch() POST, such that we can do this at |
| 1011 | precisely the right time. That change would not be |
| 1012 | difficult (we do this in /chat, /wikiedit, etc.) but |
| 1013 | that exceeds this morning's ambitions. */ |
| 1014 | ()=>elem.disabled = false, 10000 |
| 1015 | ); |
| 1016 | form.submit(); |
| 1017 | }; |
| 1018 | document.querySelectorAll("form").forEach(function(form){ |
| 1019 | /* Set up controls for closing posts and setting thread |
| 1020 | status. */ |
| 1021 | form.addEventListener('submit', formSubmitted); |
| 1022 | form |
| @@ -1002,11 +1025,11 @@ | |
| 1025 | e.classList.remove('hidden'); |
| 1026 | F.confirmer(e, { |
| 1027 | confirmText: (e.classList.contains('action-reopen') |
| 1028 | ? "Confirm re-open" |
| 1029 | : "Confirm close"), |
| 1030 | onconfirm: ()=>deactivateButtonInFlight(e, form) |
| 1031 | }); |
| 1032 | }); |
| 1033 | form |
| 1034 | .querySelectorAll("input.action-approve, input.action-reject") |
| 1035 | .forEach(function(e){ |
| @@ -1025,11 +1048,11 @@ | |
| 1048 | D.input('hidden'), |
| 1049 | 'name', isApprove ? 'approve' : 'reject', |
| 1050 | 'value', 'ignored' |
| 1051 | ) |
| 1052 | ); |
| 1053 | deactivateButtonInFlight(e, form); |
| 1054 | } |
| 1055 | }); |
| 1056 | /* We should also arguably disable the approve/reject |
| 1057 | button's counterpart while it's counting down. */ |
| 1058 | }); |
| @@ -1056,11 +1079,11 @@ | |
| 1079 | }; |
| 1080 | sel.addEventListener('change', updateButton, true); |
| 1081 | updateButton(); |
| 1082 | F.confirmer(btn, { |
| 1083 | confirmText: "Confirm status change", |
| 1084 | onconfirm: ()=>deactivateButtonInFlight(btn, form) |
| 1085 | }); |
| 1086 | }); |
| 1087 | }); |
| 1088 | } |
| 1089 | |
| 1090 |