Remove SweetAlert2 script from common layout and update Tailwind CSS configuration to include init-dialogs.js for content scanning

This commit is contained in:
Luis Eduardo
2025-10-05 23:09:22 +00:00
parent a4adc07be7
commit 6a8784daf2
10 changed files with 145 additions and 51 deletions

View File

@@ -1 +1,2 @@
internal/view/static/libs/
internal/view/static/build/

View File

@@ -1,9 +0,0 @@
/*
Fix sweetalert2 scroll issue
https://github.com/sweetalert2/sweetalert2/issues/781#issuecomment-475108658
*/
body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown),
html.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown) {
height: 100% !important;
overflow-y: visible !important;
}

View File

@@ -6,4 +6,3 @@
@import "./partials/slim-select.css";
@import "./partials/notyf.css";
@import "./partials/scrollbar.css";
@import "./partials/sweetalert2.css";

View File

@@ -1,11 +1,11 @@
import { initThemeHelper } from "./init-theme-helper.js";
import { initSweetAlert2 } from "./init-sweetalert2.js";
import { initDialogs } from "./init-dialogs.js";
import { initNotyf } from "./init-notyf.js";
import { initHTMX } from "./init-htmx.js";
import { initHelpers } from "./init-helpers.js";
initThemeHelper();
initSweetAlert2();
initDialogs();
initNotyf();
initHTMX();
initHelpers();

View File

@@ -0,0 +1,137 @@
export function initDialogs() {
/**
* Shows an alert dialog
* @param {string} text - The text to display
* @returns {Promise<{isConfirmed: boolean, isDismissed: boolean}>}
*/
async function swalAlert(text) {
return showDialog(text, false);
}
/**
* Shows a confirmation dialog
* @param {string} text - The text to display
* @returns {Promise<{isConfirmed: boolean, isDismissed: boolean}>}
*/
async function swalConfirm(text) {
return showDialog(text, true);
}
/**
* Shows a dialog
* @param {string} text - The text to display
* @param {boolean} isConfirm - True for confirm dialog, false for alert
*/
function showDialog(text, isConfirm) {
return new Promise((resolve) => {
const dialogId = "dialog-" + Date.now();
const container = createDialog(dialogId, text, isConfirm, resolve);
document.body.appendChild(container);
// Fade in
requestAnimationFrame(() => {
container.style.opacity = "0";
container.classList.remove("hidden");
requestAnimationFrame(() => {
container.style.transition = "opacity 0.15s ease-in-out";
container.style.opacity = "1";
});
});
});
}
/**
* Creates the dialog HTML
*/
function createDialog(dialogId, text, isConfirm, resolve) {
// Container
const container = document.createElement("div");
container.id = dialogId;
container.className =
"hidden !p-0 !m-0 w-[100dvw] h-[100dvh] fixed left-0 top-0 z-[1000]";
// Backdrop
const backdrop = document.createElement("div");
backdrop.className = "bg-black opacity-25 !w-full !h-full z-[1001]";
backdrop.onclick = () => closeDialog(dialogId, resolve, !isConfirm);
// Dialog box
const dialogBox = document.createElement("div");
dialogBox.className =
"absolute z-[1002] top-[50%] left-[50%] translate-y-[-50%] translate-x-[-50%] " +
"max-w-[calc(100dvw-30px)] bg-base-100 rounded-box p-6 w-[400px] shadow-xl";
// Icon
const iconPath = isConfirm
? "M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
: "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z";
const iconColor = isConfirm ? "text-warning" : "text-info";
dialogBox.innerHTML = `
<div class="flex justify-center mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="size-[100px] ${iconColor}" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${iconPath}" />
</svg>
</div>
<div class="text-center text-lg mb-6 break-words">${text}</div>
<div class="flex justify-center gap-3">
${
isConfirm
? '<button class="btn btn-error" data-action="cancel">Cancel</button><button class="btn btn-primary" data-action="confirm">Confirm</button>'
: '<button class="btn btn-primary" data-action="ok">Okay</button>'
}
</div>
`;
// Button event listeners
const buttons = dialogBox.querySelectorAll("button");
buttons.forEach((btn) => {
btn.onclick = () => {
const action = btn.getAttribute("data-action");
const confirmed = action === "confirm" || action === "ok";
closeDialog(dialogId, resolve, confirmed);
};
});
// Autofocus
setTimeout(() => {
const focusSelector = isConfirm
? '[data-action="cancel"]'
: '[data-action="ok"]';
dialogBox.querySelector(focusSelector)?.focus();
}, 150);
container.appendChild(backdrop);
container.appendChild(dialogBox);
// ESC key handler
const handleEsc = (e) => {
if (e.key === "Escape") {
closeDialog(dialogId, resolve, !isConfirm);
document.removeEventListener("keydown", handleEsc);
}
};
document.addEventListener("keydown", handleEsc);
return container;
}
/**
* Closes the dialog with fade out
*/
function closeDialog(dialogId, resolve, confirmed) {
const dialog = document.getElementById(dialogId);
if (dialog) {
dialog.style.opacity = "0";
setTimeout(() => {
dialog.remove();
resolve({ isConfirmed: confirmed, isDismissed: !confirmed });
}, 150);
} else {
resolve({ isConfirmed: confirmed, isDismissed: !confirmed });
}
}
window.swalAlert = swalAlert;
window.swalConfirm = swalConfirm;
}

View File

@@ -45,7 +45,7 @@ export function initHTMX() {
document.addEventListener(key, triggers[key]);
}
// Add trigger to use sweetalert2 for confirms
// Add trigger to use custom dialogs for confirms
document.addEventListener("htmx:confirm", function (e) {
if (!e.detail.target.hasAttribute("hx-confirm")) return;

View File

@@ -1,34 +0,0 @@
export function initSweetAlert2() {
// Docs at https://sweetalert2.github.io/#configuration
const defaultConfig = {
icon: "info",
confirmButtonText: "Okay",
cancelButtonText: "Cancel",
customClass: {
popup: "rounded-box bg-base-100 text-base-content",
confirmButton: "btn btn-primary",
denyButton: "btn btn-warning",
cancelButton: "btn btn-error",
},
};
async function swalAlert(text) {
return await Swal.fire({
...defaultConfig,
title: text,
});
}
async function swalConfirm(text) {
return await Swal.fire({
...defaultConfig,
icon: "question",
title: text,
confirmButtonText: "Confirm",
showCancelButton: true,
});
}
window.swalAlert = swalAlert;
window.swalConfirm = swalConfirm;
}

File diff suppressed because one or more lines are too long

View File

@@ -45,7 +45,6 @@ func commonHead() nodx.Node {
nodx.Script(src("/libs/htmx/htmx-2.0.1.min.js"), nodx.Defer("")),
nodx.Script(src("/libs/alpinejs/alpinejs-3.14.1.min.js"), nodx.Defer("")),
nodx.Script(src("/libs/sweetalert2/sweetalert2-11.13.1.min.js")),
nodx.Script(src("/libs/chartjs/chartjs-4.4.3.umd.min.js")),
nodx.Link(nodx.Rel("stylesheet"), href("/libs/notyf/notyf-3.10.0.min.css")),

View File

@@ -3,7 +3,10 @@ import daisyui from "daisyui";
import * as daisyuiThemes from "daisyui/src/theming/themes";
export default {
content: ["./internal/view/web/**/*.go"],
content: [
"./internal/view/web/**/*.go",
"./internal/view/static/js/init-dialogs.js",
],
plugins: [daisyui as any],
daisyui: {
logs: false,