Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions src/components/PageShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,74 @@ interface PageShellProps {
}

const PageShell: React.FC<PageShellProps> = ({ children }) => {
React.useEffect(() => {
// Select all <pre> elements inside .ps-content
const preBlocks = document.querySelectorAll('.ps-content pre');
preBlocks.forEach(pre => {
// Avoid duplicate buttons
if (
pre.querySelector('.global-copy-btn') ||
pre.parentElement?.querySelector('button')
)
return;

Comment on lines +15 to +20
const preEl = pre as HTMLElement;
preEl.style.position = 'relative';

// Create button
const btn = document.createElement('button');
btn.className =
'global-copy-btn absolute top-2 right-2 backdrop-filter backdrop-blur-xl bg-white/10 border border-white/20 p-2 hover:bg-white/30 rounded-md transition-all duration-300 z-10 text-white flex items-center justify-center';
btn.title = 'Copy to clipboard';

const copyIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>`;
const checkIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check"><path d="m5 12 5 5L20 7"/></svg>`;

btn.innerHTML = copyIcon;

btn.addEventListener('click', () => {
// Exclude the button text itself if it was copied
const textToCopy = (preEl.innerText || '')
.replace(/Copy|Copied!/g, '')
.trim();
Comment on lines +36 to +39

if (navigator.clipboard) {
navigator.clipboard
.writeText(textToCopy)
.then(() => {
btn.innerHTML = checkIcon;
setTimeout(() => {
btn.innerHTML = copyIcon;
}, 2000);
})
.catch(err => {
console.error('Failed to copy: ', err);
});
Comment on lines +50 to +52
} else {
// Fallback
const textarea = document.createElement('textarea');
textarea.value = textToCopy;
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
btn.innerHTML = checkIcon;
setTimeout(() => {
btn.innerHTML = copyIcon;
}, 2000);
} catch (e) {
console.error('Fallback copy failed', e);
}
document.body.removeChild(textarea);
}
});

preEl.appendChild(btn);
});
}, [children]);

return (
<div className='page-shell'>
{/* Animated background orbs — matches landing page */}
Expand Down