Add site copy feedback and command block fixes (#200)

* Add copy confirmation animation

* Add live mode copy confirmation

* Add Neo Mirai copy confirmation

* Fix Neo Mirai command overflow on narrow screens

* Link footer logo to homepage

* Fix copy feedback helper

---------

Co-authored-by: Paul Bakaus <43004+pbakaus@users.noreply.github.com>
This commit is contained in:
Vyctor H. Brzezowski
2026-06-05 15:47:17 -07:00
committed by GitHub
co-authored by Paul Bakaus
parent 08d50f215b
commit 397d3cb4b7
9 changed files with 348 additions and 57 deletions
+2 -20
View File
@@ -5,6 +5,7 @@ import {
import { initFrameworkViz } from "./components/framework-viz.js";
import { initScrollReveal } from "./utils/reveal.js";
import { initAnchorScroll, initHashTracking } from "./utils/scroll.js";
import { initCopyFeedback } from "./utils/copy-feedback.js";
import { initSectionNav } from "./components/section-nav.js";
import { initFoundationGrid } from "./components/foundation-grid.js";
import { initLiveDemo, initGbarPageChat } from "./components/live-demo.js";
@@ -208,26 +209,6 @@ document.addEventListener("click", (e) => {
const bundleName = bundleBtn.dataset.bundle;
window.location.href = `/api/download/bundle/${bundleName}`;
}
// Handle copy button clicks
const copyBtn = e.target.closest("[data-copy]");
if (copyBtn) {
const textToCopy = copyBtn.dataset.copy;
const onCopied = () => {
copyBtn.classList.add('copied');
setTimeout(() => copyBtn.classList.remove('copied'), 1500);
};
if (navigator.clipboard?.writeText) {
navigator.clipboard.writeText(textToCopy).then(onCopied).catch(() => {});
} else {
// Fallback for non-HTTPS or older browsers
const ta = Object.assign(document.createElement('textarea'), { value: textToCopy, style: 'position:fixed;left:-9999px' });
document.body.appendChild(ta);
ta.select();
try { document.execCommand('copy'); onCopied(); } catch {}
ta.remove();
}
}
});
@@ -261,6 +242,7 @@ function initHeaderScroll() {
function init() {
initAnchorScroll();
initHashTracking();
initCopyFeedback();
initHeaderScroll();
initScrollReveal();
initGlassTerminal();
+62
View File
@@ -0,0 +1,62 @@
const copyResetTimers = new WeakMap();
function copyWithTextareaFallback(text) {
const ta = Object.assign(document.createElement('textarea'), {
value: text,
style: 'position:fixed;left:-9999px',
});
document.body.appendChild(ta);
ta.focus();
ta.select();
try {
return document.execCommand('copy');
} catch {
return false;
} finally {
ta.remove();
}
}
function showCopiedState(button, { copiedClass = 'copied', resetMs = 1200 } = {}) {
window.clearTimeout(copyResetTimers.get(button));
button.classList.remove(copiedClass);
void button.offsetWidth;
button.classList.add(copiedClass);
copyResetTimers.set(
button,
window.setTimeout(() => button.classList.remove(copiedClass), resetMs),
);
}
async function copyText(text) {
if (navigator.clipboard?.writeText) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch {}
}
return copyWithTextareaFallback(text);
}
export function initCopyFeedback({
selector = '[data-copy]',
copiedClass = 'copied',
resetMs = 1200,
} = {}) {
document.addEventListener('click', async (event) => {
if (!(event.target instanceof Element)) return;
const button = event.target.closest(selector);
if (!(button instanceof HTMLElement)) return;
const text = button.getAttribute('data-copy');
if (!text) return;
if (await copyText(text)) {
showCopiedState(button, { copiedClass, resetMs });
}
});
}