@@ -4,7 +4,7 @@ import Layout from '../../layouts/Layout.astro';
44import Sidebar from ' ../../components/Sidebar.astro' ;
55import { languages } from ' ../../i18n/ui' ;
66import lessons from ' ../../data/lessons.json' ;
7- import { Terminal , Play } from ' @lucide/astro' ;
7+ import { Terminal , Play , Copy , Check } from ' @lucide/astro' ;
88
99export 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