Skip to content

Commit fa510be

Browse files
committed
Add copy feature to each UI lesson
1 parent ddba5c9 commit fa510be

1 file changed

Lines changed: 67 additions & 1 deletion

File tree

ui/src/pages/[lang]/[...path].astro

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Layout from '../../layouts/Layout.astro';
44
import Sidebar from '../../components/Sidebar.astro';
55
import { languages } from '../../i18n/ui';
66
import lessons from '../../data/lessons.json';
7-
import { Terminal, Play } from '@lucide/astro';
7+
import { Terminal, Play, Copy, Check } from '@lucide/astro';
88
99
export async function getStaticPaths() {
1010
const paths = [];
@@ -38,6 +38,16 @@ const activeLang = lang as keyof typeof languages;
3838
<span class="filename-text">{lesson.filename}</span>
3939
</div>
4040
<div class="action-buttons">
41+
<button
42+
id="copy-code-btn"
43+
class="action-btn btn-copy"
44+
data-code={lesson.code}
45+
aria-label="Copy Code"
46+
>
47+
<span class="btn-icon btn-copy-icon"><Copy size={14} /></span>
48+
<span class="btn-icon btn-check-icon" style="display: none;"><Check size={14} /></span>
49+
<span class="btn-text">Copy Code</span>
50+
</button>
4151
<a
4252
href={`https://replit.com/github/huangsam/ultimate-python`}
4353
target="_blank"
@@ -214,6 +224,23 @@ const activeLang = lang as keyof typeof languages;
214224
font-size: 0.9rem;
215225
line-height: 1.5;
216226
}
227+
.btn-copy {
228+
background-color: #2d2d30;
229+
color: #cccccc;
230+
border: 1px solid #3e3e42;
231+
}
232+
233+
.btn-copy:hover {
234+
background-color: #3e3e42;
235+
color: #ffffff;
236+
}
237+
238+
.btn-copy.copied {
239+
background-color: var(--forest-green-light) !important;
240+
border-color: var(--forest-green-light) !important;
241+
color: #ffffff !important;
242+
}
243+
217244
@media (max-width: 900px) {
218245
/* Stack container vertically */
219246
#app-container {
@@ -227,3 +254,42 @@ const activeLang = lang as keyof typeof languages;
227254
}
228255
}
229256
</style>
257+
258+
<script>
259+
function setupCopyButton() {
260+
const copyBtn = document.getElementById('copy-code-btn');
261+
if (!copyBtn || copyBtn.dataset.copyInitialized) return;
262+
copyBtn.dataset.copyInitialized = 'true';
263+
264+
const copyIcon = copyBtn.querySelector('.btn-copy-icon') as HTMLElement;
265+
const checkIcon = copyBtn.querySelector('.btn-check-icon') as HTMLElement;
266+
const btnText = copyBtn.querySelector('.btn-text') as HTMLElement;
267+
const code = copyBtn.getAttribute('data-code') || '';
268+
269+
copyBtn.addEventListener('click', async () => {
270+
try {
271+
await navigator.clipboard.writeText(code);
272+
273+
// Show success state
274+
if (copyIcon) copyIcon.style.display = 'none';
275+
if (checkIcon) checkIcon.style.display = 'inline-flex';
276+
if (btnText) btnText.textContent = 'Copied!';
277+
copyBtn.classList.add('copied');
278+
279+
// Reset state after 2 seconds
280+
setTimeout(() => {
281+
if (copyIcon) copyIcon.style.display = 'inline-flex';
282+
if (checkIcon) checkIcon.style.display = 'none';
283+
if (btnText) btnText.textContent = 'Copy Code';
284+
copyBtn.classList.remove('copied');
285+
}, 2000);
286+
} catch (err) {
287+
console.error('Failed to copy code: ', err);
288+
}
289+
});
290+
}
291+
292+
// Run on page loads (View Transitions friendly)
293+
setupCopyButton();
294+
document.addEventListener('astro:page-load', setupCopyButton);
295+
</script>

0 commit comments

Comments
 (0)