Custom modals for inline confirm on submit buttons

This commit is contained in:
Noah Petherbridge 2024-12-25 15:38:17 -08:00
parent 1c013aa8d8
commit ee1a555b6a
15 changed files with 81 additions and 49 deletions

View File

@ -30,6 +30,8 @@ document.addEventListener('DOMContentLoaded', () => {
$cancel = $modal.querySelector("button.nonshy-alert-cancel-button"), $cancel = $modal.querySelector("button.nonshy-alert-cancel-button"),
$title = $modal.querySelector("#nonshy-alert-modal-title"), $title = $modal.querySelector("#nonshy-alert-modal-title"),
$body = $modal.querySelector("#nonshy-alert-modal-body"), $body = $modal.querySelector("#nonshy-alert-modal-body"),
alertIcon = `<i class="fa fa-exclamation-triangle mr-2"></i>`,
confirmIcon = `<i class="fa fa-question-circle mr-2"></i>`,
cls = 'is-active'; cls = 'is-active';
// Current caller's promise. // Current caller's promise.
@ -55,12 +57,18 @@ document.addEventListener('DOMContentLoaded', () => {
message = message.replace(/>/g, "&gt;"); message = message.replace(/>/g, "&gt;");
message = message.replace(/\n/g, "<br>"); message = message.replace(/\n/g, "<br>");
$title.innerHTML = title; $title.innerHTML = (isConfirm ? confirmIcon : alertIcon) + title;
$body.innerHTML = message; $body.innerHTML = message;
// Show the modal. // Show the modal.
$modal.classList.add(cls); $modal.classList.add(cls);
// Focus the OK button, e.g. so hitting Enter doesn't accidentally (re)click the same
// link/button which prompted the alert box in the first place.
window.requestAnimationFrame(() => {
$ok.focus();
});
// Return as a promise. // Return as a promise.
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
currentPromise = resolve; currentPromise = resolve;
@ -89,6 +97,27 @@ document.addEventListener('DOMContentLoaded', () => {
} }
}); });
// Inline submit button confirmation prompts, e.g.: many submit buttons have name="intent"
// and want the user to confirm before submitting, and had inline onclick handlers.
(document.querySelectorAll('.nonshy-confirm-submit') || []).forEach(button => {
const message = button.dataset.confirm;
if (!message) return;
const onclick = (e) => {
e.preventDefault();
modalConfirm({
message: message.replace(/\\n/g, '\n'),
}).then(() => {
button.removeEventListener('click', onclick);
window.requestAnimationFrame(() => {
button.click();
});
});
}
button.addEventListener('click', onclick);
});
// Exported global functions to invoke the modal. // Exported global functions to invoke the modal.
window.modalAlert = async ({ message, title="Alert" }) => { window.modalAlert = async ({ message, title="Alert" }) => {
return showModal({ return showModal({

View File

@ -283,8 +283,8 @@
</div> </div>
<div class="column is-narrow has-text-right"> <div class="column is-narrow has-text-right">
<button type="submit" name="intent" value="clear-all" <button type="submit" name="intent" value="clear-all"
class="button is-danger is-light is-small" class="button is-danger is-light is-small nonshy-confirm-submit"
onclick="return window.confirm('Are you sure you want to REMOVE all notifications?')" data-confirm="Are you sure you want to REMOVE all notifications?"
title="Remove all notifications from your feed"> title="Remove all notifications from your feed">
<i class="fa fa-xmark mr-1"></i> <i class="fa fa-xmark mr-1"></i>
Clear all Clear all
@ -313,8 +313,8 @@
{{InputCSRF}} {{InputCSRF}}
<div class="my-2 has-text-right"> <div class="my-2 has-text-right">
<button type="submit" name="intent" value="clear-all" <button type="submit" name="intent" value="clear-all"
class="button is-danger is-light is-small" class="button is-danger is-light is-small nonshy-confirm-submit"
onclick="return window.confirm('Are you sure you want to REMOVE all notifications?')"> data-confirm="Are you sure you want to REMOVE all notifications?">
<i class="fa fa-xmark mr-1"></i> <i class="fa fa-xmark mr-1"></i>
Clear all Clear all
</button> </button>
@ -649,8 +649,8 @@
{{if and ($Root.CurrentUser.IsAdmin) (not $Body.Photo.Explicit)}} {{if and ($Root.CurrentUser.IsAdmin) (not $Body.Photo.Explicit)}}
<div class="mt-2"> <div class="mt-2">
<a href="/admin/photo/mark-explicit?photo_id={{$Body.Photo.ID}}&next={{$Root.Request.URL}}" <a href="/admin/photo/mark-explicit?photo_id={{$Body.Photo.ID}}&next={{$Root.Request.URL}}"
class="has-text-danger is-size-7" class="has-text-danger is-size-7 nonshy-confirm-submit"
onclick="return confirm('Do you want to mark this photo as Explicit?')"> data-confirm="Do you want to mark this photo as Explicit?">
<i class="fa fa-peace mr-1"></i> <i class="fa fa-peace mr-1"></i>
Mark photo as Explicit Mark photo as Explicit
</a> </a>

View File

@ -152,8 +152,8 @@
<!-- Delete button --> <!-- Delete button -->
{{if eq $Root.CurrentUser.ID .UserID}} {{if eq $Root.CurrentUser.ID .UserID}}
<button type="submit" class="button is-small is-outlined is-danger" <button type="submit" class="button is-small is-outlined is-danger nonshy-confirm-submit"
onclick="return confirm('Do you want to delete this note?')"> data-confirm="Do you want to delete this note?">
<i class="fa fa-trash mr-1"></i> <i class="fa fa-trash mr-1"></i>
Delete Delete
</button> </button>

View File

@ -158,9 +158,9 @@
<input type="hidden" name="verdict" value="remove"> <input type="hidden" name="verdict" value="remove">
{{end}} {{end}}
<button type="submit" class="button is-fullwidth" <button type="submit" class="button is-fullwidth nonshy-confirm-submit"
{{if not (eq .IsFriend "none")}}title="Friendship {{.IsFriend}}"{{end}} {{if not (eq .IsFriend "none")}}title="Friendship {{.IsFriend}}"{{end}}
{{if eq .IsFriend "approved"}}onclick="return confirm('Do you want to remove this friendship?')"{{end}}> {{if eq .IsFriend "approved"}}data-confirm="Do you want to remove this friendship?"{{end}}>
<span class="icon-text"> <span class="icon-text">
<span class="icon"> <span class="icon">
{{if eq .IsFriend "approved"}} {{if eq .IsFriend "approved"}}

View File

@ -71,8 +71,8 @@
<form action="{{.Request.URL.Path}}" method="POST"> <form action="{{.Request.URL.Path}}" method="POST">
{{InputCSRF}} {{InputCSRF}}
<input type="hidden" name="intent" value="regenerate-backup-codes"> <input type="hidden" name="intent" value="regenerate-backup-codes">
<button type="submit" class="button is-warning" <button type="submit" class="button is-warning nonshy-confirm-submit"
onclick="return window.confirm('Are you sure you want to re-generate all of your Backup Codes? This will remove the current set of Backup Codes and replace them with a new set.')"> data-confirm="Are you sure you want to re-generate all of your Backup Codes? This will remove the current set of Backup Codes and replace them with a new set.">
<i class="fa fa-rotate mr-2"></i> <i class="fa fa-rotate mr-2"></i>
Generate all-new backup codes Generate all-new backup codes
</button> </button>

View File

@ -213,8 +213,8 @@
{{end}} {{end}}
{{if not (eq .Status "approved")}} {{if not (eq .Status "approved")}}
<button type="submit" name="verdict" value="approve" class="card-footer-item button is-success" <button type="submit" name="verdict" value="approve" class="card-footer-item button is-success nonshy-confirm-submit"
{{if eq $Root.View "rejected"}}onclick="return confirm('Are you SURE you want to mark this photo as Approved?\n\nKeep in mind you are currently viewing the Rejected list of photos!')"{{end}}> {{if eq $Root.View "rejected"}}data-confirm="Are you SURE you want to mark this photo as Approved?\n\nKeep in mind you are currently viewing the Rejected list of photos!"{{end}}>
<span class="icon"><i class="fa fa-check"></i></span> <span class="icon"><i class="fa fa-check"></i></span>
<span>Approve</span> <span>Approve</span>
</button> </button>

View File

@ -24,15 +24,15 @@
</p> </p>
<p class="block"> <p class="block">
<button type="submit" class="button is-danger mr-2" <button type="submit" class="button is-danger mr-2 nonshy-confirm-submit"
name="intent" value="everything" name="intent" value="everything"
onclick="return confirm('Do you want to lock down EVERYTHING?')"> data-confirm="Do you want to lock down EVERYTHING?">
<i class="fa fa-exclamation-triangle mr-2"></i> Lock Down Everything <i class="fa fa-exclamation-triangle mr-2"></i> Lock Down Everything
</button> </button>
<button type="submit" class="button is-success" <button type="submit" class="button is-success nonshy-confirm-submit"
name="intent" value="nothing" name="intent" value="nothing"
onclick="return confirm('Do you want to RESTORE ALL site functionality?')"> data-confirm="Do you want to RESTORE ALL site functionality?">
<i class="fa fa-exclamation-triangle mr-2"></i> Restore Everything <i class="fa fa-exclamation-triangle mr-2"></i> Restore Everything
</button> </button>
</p> </p>

View File

@ -72,10 +72,10 @@
</button> </button>
{{if .EditGroupID}} {{if .EditGroupID}}
<button type="submit" class="button is-danger" <button type="submit" class="button is-danger nonshy-confirm-submit"
name="intent" name="intent"
value="delete" value="delete"
onclick="return window.confirm('Are you sure you want to delete this group?')"> data-confirm="Are you sure you want to delete this group?">
<i class="fa fa-trash mr-1"></i> <i class="fa fa-trash mr-1"></i>
Delete Delete
</button> </button>

View File

@ -20,9 +20,9 @@
<input type="hidden" name="fragment" value="{{.Forum.Fragment}}"> <input type="hidden" name="fragment" value="{{.Forum.Fragment}}">
{{if .IsForumSubscribed}} {{if .IsForumSubscribed}}
<button type="submit" class="button" <button type="submit" class="button nonshy-confirm-submit"
name="intent" value="unfollow" name="intent" value="unfollow"
onclick="return confirm('Do you want to remove this forum from your list?')"> data-confirm="Do you want to remove this forum from your list?">
<span class="icon"><i class="fa fa-bookmark"></i></span> <span class="icon"><i class="fa fa-bookmark"></i></span>
<span>Followed</span> <span>Followed</span>
</button> </button>
@ -220,9 +220,9 @@
<input type="hidden" name="fragment" value="{{.Forum.Fragment}}"> <input type="hidden" name="fragment" value="{{.Forum.Fragment}}">
{{if .IsForumSubscribed}} {{if .IsForumSubscribed}}
<button type="submit" class="button is-small ml-2" <button type="submit" class="button is-small ml-2 nonshy-confirm-submit"
name="intent" value="unfollow" name="intent" value="unfollow"
onclick="return confirm('Do you want to remove this forum from your list?')"> data-confirm="Do you want to remove this forum from your list?">
<span class="icon"><i class="fa fa-bookmark"></i></span> <span class="icon"><i class="fa fa-bookmark"></i></span>
<span>Unfollow</span> <span>Unfollow</span>
</button> </button>

View File

@ -20,9 +20,9 @@
<input type="hidden" name="fragment" value="{{.Forum.Fragment}}"> <input type="hidden" name="fragment" value="{{.Forum.Fragment}}">
{{if .IsForumSubscribed}} {{if .IsForumSubscribed}}
<button type="submit" class="button" <button type="submit" class="button nonshy-confirm-submit"
name="intent" value="unfollow" name="intent" value="unfollow"
onclick="return confirm('Do you want to remove this forum from your list?')"> data-confirm="Do you want to remove this forum from your list?">
<span class="icon"><i class="fa fa-bookmark"></i></span> <span class="icon"><i class="fa fa-bookmark"></i></span>
<span>Followed</span> <span>Followed</span>
</button> </button>
@ -349,7 +349,9 @@
{{if or $Root.CanModerate ($Root.CurrentUser.HasAdminScope "social.moderator.forum") (eq $Root.CurrentUser.ID .User.ID)}} {{if or $Root.CanModerate ($Root.CurrentUser.HasAdminScope "social.moderator.forum") (eq $Root.CurrentUser.ID .User.ID)}}
<div class="column is-narrow"> <div class="column is-narrow">
<a href="/forum/post?to={{$Root.Forum.Fragment}}&thread={{$Root.Thread.ID}}&edit={{.ID}}&delete=true" onclick="return confirm('Are you sure you want to delete this comment?')" class="has-text-dark"> <a href="/forum/post?to={{$Root.Forum.Fragment}}&thread={{$Root.Thread.ID}}&edit={{.ID}}&delete=true"
data-confirm="Are you sure you want to delete this comment?"
class="has-text-dark nonshy-confirm-submit">
<span class="icon"><i class="fa fa-trash"></i></span> <span class="icon"><i class="fa fa-trash"></i></span>
<span>Delete</span> <span>Delete</span>
</a> </a>
@ -414,10 +416,10 @@
<!-- Pin/Unpin --> <!-- Pin/Unpin -->
{{if or (eq .Forum.OwnerID .CurrentUser.ID) (.CurrentUser.HasAdminScope "admin.forum.manage")}} {{if or (eq .Forum.OwnerID .CurrentUser.ID) (.CurrentUser.HasAdminScope "admin.forum.manage")}}
<p class="control"> <p class="control">
<button type="submit" class="button is-small has-text-success" <button type="submit" class="button is-small has-text-success nonshy-confirm-submit"
name="intent" name="intent"
value="{{if .Thread.Pinned}}un{{end}}pin" value="{{if .Thread.Pinned}}un{{end}}pin"
onclick="return confirm('Are you sure you want to {{if .Thread.Pinned}}un{{end}}pin this thread to the top of the forum?')"> data-confirm="Are you sure you want to {{if .Thread.Pinned}}un{{end}}pin this thread to the top of the forum?">
<span class="icon is-small"> <span class="icon is-small">
<i class="fas fa-thumbtack{{if .Thread.Pinned}}-slash{{end}}"></i> <i class="fas fa-thumbtack{{if .Thread.Pinned}}-slash{{end}}"></i>
</span> </span>
@ -428,10 +430,10 @@
<!-- Lock/Unlock --> <!-- Lock/Unlock -->
<p class="control"> <p class="control">
<button type="submit" class="button is-small has-text-warning" <button type="submit" class="button is-small has-text-warning nonshy-confirm-submit"
name="intent" name="intent"
value="{{if .Thread.NoReply}}un{{end}}lock" value="{{if .Thread.NoReply}}un{{end}}lock"
onclick="return confirm('Do you want to {{if .Thread.NoReply}}UN{{end}}LOCK this thread?\n\nA locked thread will not accept any new replies.')"> data-confirm="Do you want to {{if .Thread.NoReply}}UN{{end}}LOCK this thread?\n\nA locked thread will not accept any new replies.">
<span class="icon is-small"> <span class="icon is-small">
<i class="fa fa-ban"></i> <i class="fa fa-ban"></i>
</span> </span>

View File

@ -139,8 +139,8 @@
</footer> </footer>
{{else}} {{else}}
<footer class="card-footer"> <footer class="card-footer">
<button type="submit" name="verdict" value="remove" class="card-footer-item button is-danger is-outlined" <button type="submit" name="verdict" value="remove" class="card-footer-item button is-danger is-outlined nonshy-confirm-submit"
onclick="return confirm('Are you sure you want to remove this friendship?')"> data-confirm="Are you sure you want to remove this friendship{{if $Root.IsPending}} request{{end}}?">
<span class="icon"><i class="fa fa-xmark"></i></span> <span class="icon"><i class="fa fa-xmark"></i></span>
<span>{{if $Root.IsPending}}Cancel{{else}}Remove{{end}}</span> <span>{{if $Root.IsPending}}Cancel{{else}}Remove{{end}}</span>
</button> </button>

View File

@ -140,8 +140,8 @@
<!-- Our message? We can delete it. --> <!-- Our message? We can delete it. -->
{{if eq $Root.CurrentUser.ID $SourceUser.ID}} {{if eq $Root.CurrentUser.ID $SourceUser.ID}}
<form action="/messages/delete" method="POST" class="is-inline" <form action="/messages/delete" method="POST" class="is-inline nonshy-confirm-submit"
onsubmit="return confirm('Do you want to DELETE this message?')"> data-confirm="Do you want to DELETE this message?">
{{InputCSRF}} {{InputCSRF}}
<input type="hidden" name="id" value="{{.ID}}"> <input type="hidden" name="id" value="{{.ID}}">
<input type="hidden" name="next" value="{{$Root.Request.URL.Path}}"> <input type="hidden" name="next" value="{{$Root.Request.URL.Path}}">
@ -172,14 +172,13 @@
</div> </div>
<!-- "Delete ALL" Form --> <!-- "Delete ALL" Form -->
<form action="/messages/delete" method="POST" <form action="/messages/delete" method="POST" class="is-inline">
class="is-inline"
onsubmit="return confirm('Are you sure you want to delete this whole entire thread for both of you? It will be like you two had never chatted before at all.')">
{{InputCSRF}} {{InputCSRF}}
<input type="hidden" name="intent" value="delete-thread"> <input type="hidden" name="intent" value="delete-thread">
<input type="hidden" name="id" value="{{$Root.MessageID}}"> <input type="hidden" name="id" value="{{$Root.MessageID}}">
<input type="hidden" name="next" value="/messages"> <input type="hidden" name="next" value="/messages">
<button class="button has-text-danger is-outline is-small p-1 ml-4"> <button class="button has-text-danger is-outline is-small p-1 ml-4 nonshy-confirm-submit"
data-confirm="Are you sure you want to delete this whole entire thread for both of you? It will be like you two had never chatted before at all.">
<i class="fa fa-trash mr-2"></i> <i class="fa fa-trash mr-2"></i>
Delete whole thread Delete whole thread
</button> </button>

View File

@ -180,9 +180,9 @@
re-approved with a new Certification Photo to be recertified. re-approved with a new Certification Photo to be recertified.
</p> </p>
<button type="submit" class="button is-danger" <button type="submit" class="button is-danger nonshy-confirm-submit"
{{if eq .CertificationPhoto.Status "approved"}} {{if eq .CertificationPhoto.Status "approved"}}
onclick="return window.confirm('Removing this photo will mark your account as Not Certified.\n\nYou will then need to upload a new certification photo for approval to certify your account again.\n\nAre you sure you want to do this?')" data-confirm="Removing this photo will mark your account as Not Certified.\n\nYou will then need to upload a new certification photo for approval to certify your account again.\n\nAre you sure you want to do this?"
{{end}}> {{end}}>
<span class="icon"><i class="fa fa-trash"></i></span> <span class="icon"><i class="fa fa-trash"></i></span>
<span>Delete My Certification Photo</span> <span>Delete My Certification Photo</span>

View File

@ -379,7 +379,9 @@
<!-- The poster, the photo owner, and the admin can delete the comment --> <!-- The poster, the photo owner, and the admin can delete the comment -->
{{if or $Root.CurrentUser.IsAdmin (eq $Root.CurrentUser.ID .User.ID) $Root.IsOwnPhoto}} {{if or $Root.CurrentUser.IsAdmin (eq $Root.CurrentUser.ID .User.ID) $Root.IsOwnPhoto}}
<div class="column is-narrow"> <div class="column is-narrow">
<a href="/comments?table_name=photos&table_id={{$Root.Photo.ID}}&edit={{.ID}}&delete=true&next={{UrlEncode $Root.Request.URL.Path "?id=" $Root.Photo.ID}}" onclick="return confirm('Are you sure you want to delete this comment?')" class="has-text-dark"> <a href="/comments?table_name=photos&table_id={{$Root.Photo.ID}}&edit={{.ID}}&delete=true&next={{UrlEncode $Root.Request.URL.Path "?id=" $Root.Photo.ID}}"
data-confirm="Are you sure you want to delete this comment?"
class="has-text-dark nonshy-confirm-submit">
<span class="icon"><i class="fa fa-trash"></i></span> <span class="icon"><i class="fa fa-trash"></i></span>
<span>Delete</span> <span>Delete</span>
</a> </a>

View File

@ -74,8 +74,8 @@
</a> </a>
</div> </div>
<div class="column is-narrow mx-1 my-2"> <div class="column is-narrow mx-1 my-2">
<a href="/photo/private/share?intent=revoke-all" class="button is-danger is-outlined is-fullwidth" <a href="/photo/private/share?intent=revoke-all" class="button is-danger is-outlined is-fullwidth nonshy-confirm-submit"
onclick="return confirm('Are you sure you want to lock your Private Photos from ALL users?')"> data-confirm="Are you sure you want to lock your Private Photos from ALL users?">
<span class="icon"><i class="fa fa-lock"></i></span> <span class="icon"><i class="fa fa-lock"></i></span>
<span>Revoke ALL Shares</span> <span>Revoke ALL Shares</span>
</a> </a>
@ -202,16 +202,16 @@
<!-- Card Footers --> <!-- Card Footers -->
{{if $Root.IsGrantee}} {{if $Root.IsGrantee}}
<footer class="card-footer"> <footer class="card-footer">
<button type="submit" name="intent" value="decline" class="card-footer-item button is-danger is-outlined" <button type="submit" name="intent" value="decline" class="card-footer-item button is-danger is-outlined nonshy-confirm-submit"
onclick="return confirm('Do you want to decline access to this person\'s private photos? Doing so will remove them from your Shared With Me list and you will no longer see their private photos unless they share with you again in the future.')"> data-confirm="Do you want to decline access to this person's private photos? Doing so will remove them from your Shared With Me list and you will no longer see their private photos unless they share with you again in the future.">
<span class="icon"><i class="fa fa-thumbs-down"></i></span> <span class="icon"><i class="fa fa-thumbs-down"></i></span>
<span>Decline</span> <span>Decline</span>
</button> </button>
</footer> </footer>
{{else}} {{else}}
<footer class="card-footer"> <footer class="card-footer">
<button type="submit" name="intent" value="revoke" class="card-footer-item button is-danger is-outlined" <button type="submit" name="intent" value="revoke" class="card-footer-item button is-danger is-outlined nonshy-confirm-submit"
onclick="return confirm('Are you sure you want to revoke private photo access to this user?')"> data-confirm="Are you sure you want to revoke private photo access to this user?">
<span class="icon"><i class="fa fa-xmark"></i></span> <span class="icon"><i class="fa fa-xmark"></i></span>
<span>Revoke Access</span> <span>Revoke Access</span>
</button> </button>