Prevent duplicate post submissions
CI / test (pull_request) Successful in 6m16s

This commit is contained in:
2026-08-27 09:16:05 -07:00
parent 17e73ad2d3
commit eb4cc08a69
7 changed files with 116 additions and 4 deletions
+35
View File
@@ -41,6 +41,31 @@ body {
background-size: 100% 100%, 48px 48px;
}
.submit-progress {
position: fixed;
inset: 0 0 auto;
z-index: 100;
height: 3px;
overflow: hidden;
pointer-events: none;
}
.submit-progress[hidden] { display: none; }
.submit-progress-bar {
display: block;
width: 35%;
height: 100%;
background: var(--signal);
box-shadow: 0 0 12px var(--signal);
animation: submit-progress 900ms ease-in-out infinite;
}
@keyframes submit-progress {
from { transform: translateX(-100%); }
to { transform: translateX(300%); }
}
img, svg { display: block; }
a {
@@ -253,6 +278,12 @@ a {
.btn-primary:hover { filter: brightness(1.08); }
.btn:disabled {
cursor: wait;
opacity: 0.65;
filter: none;
}
.btn-ghost {
background: transparent;
color: var(--ink);
@@ -763,6 +794,10 @@ input:focus, textarea:focus, .btn:focus-visible, .chip:focus-visible, .vote-btn:
@media (prefers-reduced-motion: reduce) {
.btn-primary:hover { filter: none; }
.submit-progress-bar {
width: 100%;
animation: none;
}
}
.admin-users { margin-top: 20px; overflow-x: auto; }
+58
View File
@@ -0,0 +1,58 @@
(() => {
const formSelector = "form[data-submit-once]";
function progressIndicator() {
return document.getElementById("submit-progress");
}
function resetForm(form) {
form.removeAttribute("aria-busy");
delete form.dataset.submitting;
const button = form.querySelector("[data-submit-button]");
if (!button) {
return;
}
button.disabled = false;
button.removeAttribute("aria-disabled");
if (button.dataset.idleLabel) {
button.textContent = button.dataset.idleLabel;
delete button.dataset.idleLabel;
}
}
document.addEventListener("submit", (event) => {
const form = event.target.closest(formSelector);
if (!form) {
return;
}
if (form.dataset.submitting === "true") {
event.preventDefault();
return;
}
form.dataset.submitting = "true";
form.setAttribute("aria-busy", "true");
const button = event.submitter || form.querySelector("[data-submit-button]");
if (button) {
button.dataset.idleLabel = button.textContent;
button.textContent = form.dataset.submittingLabel || "Posting…";
button.disabled = true;
button.setAttribute("aria-disabled", "true");
}
const progress = progressIndicator();
if (progress) {
progress.hidden = false;
}
});
window.addEventListener("pageshow", () => {
document.querySelectorAll(formSelector).forEach(resetForm);
const progress = progressIndicator();
if (progress) {
progress.hidden = true;
}
});
})();