Use toast feedback for copy actions
This commit is contained in:
+32
-12
@@ -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 { 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 { 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); }
|
.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 { max-width: 880px; margin: 0 auto; padding: 18px 20px 34px; color: var(--muted); font-size: 12px; }
|
||||||
.source a { color: var(--muted); }
|
.source a { color: var(--muted); }
|
||||||
.failure { max-width: 720px; margin: 0 auto; padding: 80px 20px; }
|
.failure { max-width: 720px; margin: 0 auto; padding: 80px 20px; }
|
||||||
@@ -145,29 +148,45 @@ function pageScript(slug: string): string {
|
|||||||
function downloadShot(variant) {
|
function downloadShot(variant) {
|
||||||
location.href = "/aishare/${slug}/screenshots/" + variant + ".png";
|
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 params = new URLSearchParams(location.search);
|
||||||
var theme = params.get("theme");
|
var theme = params.get("theme");
|
||||||
if (theme === "light" || theme === "dark") document.documentElement.dataset.theme = theme;
|
if (theme === "light" || theme === "dark") document.documentElement.dataset.theme = theme;
|
||||||
var copy = document.querySelector("[data-copy]");
|
var copy = document.querySelector("[data-copy]");
|
||||||
if (copy) copy.addEventListener("click", async function () {
|
if (copy) copy.addEventListener("click", async function () {
|
||||||
await navigator.clipboard.writeText(location.href.replace(/[?#].*$/, ""));
|
await copyText(location.href.replace(/[?#].*$/, ""), "Link copied");
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
document.querySelectorAll("[data-copy-code]").forEach(function (button) {
|
document.querySelectorAll("[data-copy-code]").forEach(function (button) {
|
||||||
button.addEventListener("click", async function () {
|
button.addEventListener("click", async function () {
|
||||||
var block = button.closest(".code-block");
|
var block = button.closest(".code-block");
|
||||||
var code = block && block.querySelector("code");
|
var code = block && block.querySelector("code");
|
||||||
if (!code) return;
|
if (!code) return;
|
||||||
await navigator.clipboard.writeText(code.textContent || "");
|
await copyText(code.textContent || "", "Code copied");
|
||||||
button.textContent = "Copied";
|
|
||||||
setTimeout(function () { button.textContent = "Copy"; }, 1400);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
var defaultShot = document.querySelector("[data-shot-current]");
|
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())}.
|
Captured from ChatGPT on ${escapeHtml(new Date(conversation.capturedAt).toLocaleString())}.
|
||||||
Source: <a href="${escapeHtml(conversation.sourceUrl)}" rel="nofollow noreferrer">${escapeHtml(conversation.sourceUrl)}</a>
|
Source: <a href="${escapeHtml(conversation.sourceUrl)}" rel="nofollow noreferrer">${escapeHtml(conversation.sourceUrl)}</a>
|
||||||
</footer>
|
</footer>
|
||||||
|
<div class="toast-region" data-toast-region aria-live="polite" aria-atomic="true"></div>
|
||||||
</div>
|
</div>
|
||||||
<script>${pageScript(slug)}</script>
|
<script>${pageScript(slug)}</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user