From 65c1e8bddf331261b45fc9eb40f7eee51e6146c7 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 8 Jul 2026 06:18:14 +0000 Subject: [PATCH] Use toast feedback for copy actions --- src/render.ts | 44 ++++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/src/render.ts b/src/render.ts index a0ca671..7822332 100644 --- a/src/render.ts +++ b/src/render.ts @@ -115,6 +115,9 @@ h1 { margin: 0; font-size: clamp(28px, 5vw, 44px); line-height: 1.12; letter-spa .shot-menu { position: absolute; left: 0; bottom: calc(100% + 8px); z-index: 20; min-width: 150px; padding: 6px; border: 1px solid var(--border); border-radius: 8px; background: var(--panel); box-shadow: 0 12px 30px rgba(0,0,0,.12); } .shot-menu button { width: 100%; border: 0; border-radius: 6px; background: transparent; color: var(--text); padding: 8px 10px; font: inherit; font-size: 13px; text-align: left; cursor: pointer; } .shot-menu button:hover, .shot-menu button[data-current="true"] { background: var(--soft); } +.toast-region { position: fixed; left: 50%; bottom: 22px; z-index: 40; display: grid; justify-items: center; pointer-events: none; transform: translateX(-50%); } +.toast { max-width: min(calc(100vw - 32px), 320px); border: 1px solid var(--border); border-radius: 8px; background: var(--panel); color: var(--text); padding: 9px 12px; font-size: 13px; line-height: 1.35; box-shadow: 0 12px 30px rgba(0,0,0,.14); opacity: 0; transform: translateY(8px); transition: opacity .16s ease, transform .16s ease; } +.toast[data-visible="true"] { opacity: 1; transform: translateY(0); } .source { max-width: 880px; margin: 0 auto; padding: 18px 20px 34px; color: var(--muted); font-size: 12px; } .source a { color: var(--muted); } .failure { max-width: 720px; margin: 0 auto; padding: 80px 20px; } @@ -145,29 +148,45 @@ function pageScript(slug: string): string { function downloadShot(variant) { location.href = "/aishare/${slug}/screenshots/" + variant + ".png"; } + var toastTimer; + function showToast(message) { + var region = document.querySelector("[data-toast-region]"); + if (!region) return; + var toast = region.querySelector("[data-toast]"); + if (!toast) { + toast = document.createElement("div"); + toast.className = "toast"; + toast.dataset.toast = "true"; + region.appendChild(toast); + } + toast.textContent = message; + toast.dataset.visible = "true"; + clearTimeout(toastTimer); + toastTimer = setTimeout(function () { + delete toast.dataset.visible; + }, 1600); + } + async function copyText(text, successMessage) { + try { + await navigator.clipboard.writeText(text); + showToast(successMessage); + } catch (error) { + showToast("Copy failed"); + } + } var params = new URLSearchParams(location.search); var theme = params.get("theme"); if (theme === "light" || theme === "dark") document.documentElement.dataset.theme = theme; var copy = document.querySelector("[data-copy]"); if (copy) copy.addEventListener("click", async function () { - await navigator.clipboard.writeText(location.href.replace(/[?#].*$/, "")); - copy.dataset.copied = "true"; - copy.setAttribute("aria-label", "Copied"); - copy.setAttribute("title", "Copied"); - setTimeout(function () { - delete copy.dataset.copied; - copy.setAttribute("aria-label", "Copy link"); - copy.setAttribute("title", "Copy link"); - }, 1400); + await copyText(location.href.replace(/[?#].*$/, ""), "Link copied"); }); document.querySelectorAll("[data-copy-code]").forEach(function (button) { button.addEventListener("click", async function () { var block = button.closest(".code-block"); var code = block && block.querySelector("code"); if (!code) return; - await navigator.clipboard.writeText(code.textContent || ""); - button.textContent = "Copied"; - setTimeout(function () { button.textContent = "Copy"; }, 1400); + await copyText(code.textContent || "", "Code copied"); }); }); var defaultShot = document.querySelector("[data-shot-current]"); @@ -241,6 +260,7 @@ export function renderConversationPage(slug: string, conversation: NormalizedCon Captured from ChatGPT on ${escapeHtml(new Date(conversation.capturedAt).toLocaleString())}. Source: ${escapeHtml(conversation.sourceUrl)} +