Archived
88 lines
2.9 KiB
JavaScript
88 lines
2.9 KiB
JavaScript
const legacyCopy = (value) => {
|
|
const textarea = document.createElement("textarea");
|
|
textarea.value = value;
|
|
textarea.setAttribute("readonly", "");
|
|
textarea.style.position = "fixed";
|
|
textarea.style.top = "-9999px";
|
|
textarea.style.left = "-9999px";
|
|
document.body.append(textarea);
|
|
textarea.select();
|
|
textarea.setSelectionRange(0, textarea.value.length);
|
|
try {
|
|
return document.execCommand("copy");
|
|
} finally {
|
|
textarea.remove();
|
|
}
|
|
};
|
|
|
|
const copyText = async (value) => {
|
|
if (navigator.clipboard && window.isSecureContext) {
|
|
await navigator.clipboard.writeText(value);
|
|
return true;
|
|
}
|
|
return legacyCopy(value);
|
|
};
|
|
|
|
const showCopyStatus = (button, text, className, duration = 1200) => {
|
|
const label = button.querySelector("[data-copy-label]");
|
|
const oldLabel = label ? label.textContent : "";
|
|
button.classList.add(className);
|
|
if (label) {
|
|
label.setAttribute("aria-live", "polite");
|
|
label.textContent = text;
|
|
}
|
|
setTimeout(() => {
|
|
button.classList.remove(className);
|
|
if (label) label.textContent = oldLabel;
|
|
}, duration);
|
|
};
|
|
|
|
document.addEventListener("click", async (event) => {
|
|
document.querySelectorAll("details.copy-menu[open]").forEach((menu) => {
|
|
if (!menu.contains(event.target)) menu.removeAttribute("open");
|
|
});
|
|
|
|
const toggle = event.target.closest("[data-secret-toggle]");
|
|
if (toggle) {
|
|
const group = toggle.closest(".copy-field");
|
|
const field = group ? group.querySelector("[data-secret-input], [data-secret-value]") : null;
|
|
if (!field) return;
|
|
if (field.matches("[data-secret-input]")) {
|
|
const masked = field.type === "password";
|
|
field.type = masked ? "text" : "password";
|
|
toggle.textContent = masked ? "Hide" : "View";
|
|
toggle.setAttribute("aria-label", masked ? "Hide secret value" : "Show secret value");
|
|
} else {
|
|
const masked = field.dataset.secretMasked !== "false";
|
|
field.dataset.secretMasked = masked ? "false" : "true";
|
|
field.textContent = masked ? (field.dataset.secretValue || "") : "********";
|
|
toggle.textContent = masked ? "Hide" : "View";
|
|
toggle.setAttribute("aria-label", masked ? "Hide secret value" : "Show secret value");
|
|
}
|
|
return;
|
|
}
|
|
|
|
const button = event.target.closest("[data-copy]");
|
|
if (!button) return;
|
|
const menu = button.closest("details.copy-menu");
|
|
try {
|
|
const copied = await copyText(button.dataset.copy || "");
|
|
if (!copied) throw new Error("copy failed");
|
|
showCopyStatus(button, "Copied", "is-copied");
|
|
if (menu) {
|
|
setTimeout(() => {
|
|
menu.removeAttribute("open");
|
|
}, 250);
|
|
}
|
|
} catch (_) {
|
|
showCopyStatus(button, "Copy failed", "is-copy-error", 1800);
|
|
}
|
|
});
|
|
|
|
document.addEventListener("keydown", (event) => {
|
|
if (event.key !== "Escape") return;
|
|
document.querySelectorAll("details.copy-menu[open]").forEach((menu) => {
|
|
menu.removeAttribute("open");
|
|
});
|
|
});
|