1- import React from 'react' ;
1+ import React , { useEffect } from 'react' ;
22import { BrowserRouter , Routes , Route , Navigate } from 'react-router-dom' ;
33import LessonPage from './pages/LessonPage' ;
44import { getAllLessons } from './utils/markdownloader' ;
55import './App.css' ;
66
77const App = ( ) => {
8- // Get all lessons to find the first one for the default redirect
98 const lessons = getAllLessons ( ) ;
109 const firstLesson = lessons . length > 0 ? lessons [ 0 ] : null ;
1110
11+ useEffect ( ( ) => {
12+ // Function to add copy buttons to all <pre><code> blocks
13+ const addCopyButtons = ( root = document ) => {
14+ const codeBlocks = root . querySelectorAll ( "pre" ) ;
15+
16+ codeBlocks . forEach ( ( block ) => {
17+ const code = block . querySelector ( ":scope > code" ) ; // only direct child <code>
18+ if ( ! code ) return ; // skip outer <pre> that doesn't directly contain code
19+ if ( block . querySelector ( ".copy-btn" ) ) return ; // avoid duplicates
20+
21+
22+ block . style . position = "relative" ;
23+
24+ const button = document . createElement ( "button" ) ;
25+ button . className =
26+ "copy-btn absolute top-2 right-2 bg-gray-800 text-white text-xs px-2 py-1 rounded" ;
27+ button . innerText = "Copy" ;
28+
29+ button . addEventListener ( "click" , async ( ) => {
30+ const text = code . innerText || "" ;
31+ await navigator . clipboard . writeText ( text ) ;
32+ button . innerText = "Copied!" ;
33+ setTimeout ( ( ) => ( button . innerText = "Copy" ) , 2000 ) ;
34+ } ) ;
35+
36+ block . appendChild ( button ) ;
37+ } ) ;
38+ } ;
39+
40+ // Initial run
41+ addCopyButtons ( ) ;
42+
43+ // Watch for dynamically loaded lessons
44+ const observer = new MutationObserver ( ( mutations ) => {
45+ for ( const mutation of mutations ) {
46+ mutation . addedNodes . forEach ( ( node ) => {
47+ if ( node . nodeType === 1 ) addCopyButtons ( node ) ;
48+ } ) ;
49+ }
50+ } ) ;
51+
52+ observer . observe ( document . body , { childList : true , subtree : true } ) ;
53+ return ( ) => observer . disconnect ( ) ;
54+ } , [ ] ) ;
55+
1256 return (
1357 < BrowserRouter >
1458 < Routes >
1559 { /* Redirect root to first lesson */ }
16- < Route
17- path = "/"
60+ < Route
61+ path = "/"
1862 element = {
1963 firstLesson ? (
2064 < Navigate to = { `/lessons/${ firstLesson . slug } ` } replace />
@@ -24,15 +68,15 @@ const App = () => {
2468 < p > Please add markdown files to the content folder.</ p >
2569 </ div >
2670 )
27- }
71+ }
2872 />
29-
73+
3074 { /* Dynamic lesson route */ }
3175 < Route path = "/lessons/:slug" element = { < LessonPage /> } />
32-
76+
3377 { /* 404 fallback */ }
34- < Route
35- path = "*"
78+ < Route
79+ path = "*"
3680 element = {
3781 < div style = { { padding : '2rem' , textAlign : 'center' } } >
3882 < h1 > 404 - Page Not Found</ h1 >
@@ -43,11 +87,11 @@ const App = () => {
4387 </ a >
4488 ) }
4589 </ div >
46- }
90+ }
4791 />
4892 </ Routes >
4993 </ BrowserRouter >
5094 ) ;
5195} ;
5296
53- export default App ;
97+ export default App ;
0 commit comments