Skip to content
Merged
Show file tree
Hide file tree
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
47 changes: 47 additions & 0 deletions docs/step.css
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@
padding: 1.25rem;
overflow-x: auto;
margin: 1.5rem 0;
position: relative;
padding-top: 3.25rem;
}

.markdown pre code {
Expand All @@ -346,6 +348,51 @@
padding: 0;
}

.code-copy-btn {
position: absolute;
top: 0.9rem;
right: 1rem;
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 4.75rem;
padding: 0.35rem 0.75rem;
border: 1px solid rgba(0, 245, 255, 0.25);
border-radius: 999px;
background: rgba(10, 10, 15, 0.88);
color: var(--neon-cyan);
font-family: 'Space Grotesk', sans-serif;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
cursor: pointer;
transition: transform 0.2s ease, border-color 0.2s ease, color 0.2s ease, background 0.2s ease;
}

.code-copy-btn:hover {
transform: translateY(-1px);
border-color: var(--neon-cyan);
background: rgba(0, 245, 255, 0.12);
}

.code-copy-btn:focus-visible {
outline: 2px solid var(--neon-cyan);
outline-offset: 2px;
}

.code-copy-btn.copied {
color: var(--success-green);
border-color: rgba(78, 201, 176, 0.45);
background: rgba(78, 201, 176, 0.12);
}

.code-copy-btn.failed {
color: var(--warning-yellow);
border-color: rgba(220, 220, 170, 0.45);
background: rgba(220, 220, 170, 0.12);
}

/* Blockquotes / Tips */
.markdown blockquote {
background: var(--bg-card);
Expand Down
75 changes: 75 additions & 0 deletions docs/step.html
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@
? '../workshop/'
: 'https://raw.githubusercontent.com/copilot-dev-days/agent-lab-python/main/workshop/';
const CHECKBOX_STORAGE_KEY = 'agent-lab-checkboxes-v1';
const COPY_FEEDBACK_DURATION_MS = 1600;
let checkboxSaveTimeout;

// Get current step from URL
Expand Down Expand Up @@ -255,6 +256,21 @@
checkboxSaveTimeout = setTimeout(() => writeCheckboxState(state), 100);
}

function applyVisuallyHiddenStyles(element) {
Object.assign(element.style, {
position: 'absolute',
width: '1px',
height: '1px',
padding: '0',
margin: '-1px',
border: '0',
overflow: 'hidden',
clip: 'rect(0 0 0 0)',
clipPath: 'inset(50%)',
whiteSpace: 'nowrap'
});
}

function initializeTaskListCheckboxes(stepId) {
const container = document.getElementById('markdown-content');
if (!container) return;
Expand Down Expand Up @@ -282,6 +298,64 @@
});
}

async function copyTextToClipboard(text) {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
return;
}

const textArea = document.createElement('textarea');
textArea.value = text;
textArea.setAttribute('readonly', '');
applyVisuallyHiddenStyles(textArea);
document.body.appendChild(textArea);
textArea.select();

const copySucceeded = document.execCommand('copy');
textArea.remove();

if (!copySucceeded) {
throw new Error('Copy failed');
}
}

function initializeCodeBlockCopyButtons() {
const container = document.getElementById('markdown-content');
if (!container) return;

const codeBlocks = container.querySelectorAll('pre');
codeBlocks.forEach((block) => {
const codeElement = block.querySelector('code');
if (!codeElement || block.querySelector('.code-copy-btn')) return;

const button = document.createElement('button');
button.type = 'button';
button.className = 'code-copy-btn';
button.textContent = 'Copy';
button.setAttribute('aria-label', 'Copy code block');

let resetTimeoutId = null;
button.addEventListener('click', async () => {
try {
await copyTextToClipboard(codeElement.textContent ?? '');
button.textContent = 'Copied!';
button.classList.add('copied');
} catch {
button.textContent = 'Failed';
button.classList.add('failed');
}

clearTimeout(resetTimeoutId);
resetTimeoutId = setTimeout(() => {
button.textContent = 'Copy';
button.classList.remove('copied', 'failed');
}, COPY_FEEDBACK_DURATION_MS);
});

block.appendChild(button);
});
}

// Build sidebar navigation
function buildSidebar() {
const nav = document.getElementById('stepNav');
Expand Down Expand Up @@ -390,6 +464,7 @@

document.getElementById('markdown-content').innerHTML = marked.parse(md);
initializeTaskListCheckboxes(step.id);
initializeCodeBlockCopyButtons();

// If this is the completion page, add confetti!
if (step.id === '05-complete') {
Expand Down