@@ -361,6 +361,123 @@ function hookSearchModal() {
361361 }
362362}
363363
364+ /* ===== Code Block Copy Functionality ===== */
365+
366+ function createCopyButton ( ) {
367+ var button = document . createElement ( 'button' ) ;
368+ button . className = 'copy-code-button' ;
369+ button . type = 'button' ;
370+ button . setAttribute ( 'aria-label' , 'Copy code to clipboard' ) ;
371+ button . setAttribute ( 'title' , 'Copy code' ) ;
372+
373+ // Create clipboard icon SVG
374+ var clipboardIcon = `
375+ <svg viewBox="0 0 24 24">
376+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
377+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
378+ </svg>
379+ ` ;
380+
381+ // Create checkmark icon SVG (for copied state)
382+ var checkIcon = `
383+ <svg viewBox="0 0 24 24">
384+ <polyline points="20 6 9 17 4 12"></polyline>
385+ </svg>
386+ ` ;
387+
388+ button . innerHTML = clipboardIcon ;
389+ button . dataset . clipboardIcon = clipboardIcon ;
390+ button . dataset . checkIcon = checkIcon ;
391+
392+ return button ;
393+ }
394+
395+ function wrapCodeBlocksWithCopyButton ( ) {
396+ // Find all pre elements that are not already wrapped
397+ var preElements = document . querySelectorAll ( 'main pre:not(.code-block-wrapper pre)' ) ;
398+
399+ preElements . forEach ( function ( pre ) {
400+ // Skip if already wrapped
401+ if ( pre . parentElement . classList . contains ( 'code-block-wrapper' ) ) {
402+ return ;
403+ }
404+
405+ // Create wrapper
406+ var wrapper = document . createElement ( 'div' ) ;
407+ wrapper . className = 'code-block-wrapper' ;
408+
409+ // Insert wrapper before pre
410+ pre . parentNode . insertBefore ( wrapper , pre ) ;
411+
412+ // Move pre into wrapper
413+ wrapper . appendChild ( pre ) ;
414+
415+ // Create and add copy button
416+ var copyButton = createCopyButton ( ) ;
417+ wrapper . appendChild ( copyButton ) ;
418+
419+ // Add click handler
420+ copyButton . addEventListener ( 'click' , function ( ) {
421+ copyCodeToClipboard ( pre , copyButton ) ;
422+ } ) ;
423+ } ) ;
424+ }
425+
426+ function copyCodeToClipboard ( preElement , button ) {
427+ var code = preElement . textContent ;
428+
429+ // Use modern clipboard API if available
430+ if ( navigator . clipboard && navigator . clipboard . writeText ) {
431+ navigator . clipboard . writeText ( code ) . then ( function ( ) {
432+ showCopySuccess ( button ) ;
433+ } ) . catch ( function ( err ) {
434+ // Fallback to old method
435+ fallbackCopyToClipboard ( code , button ) ;
436+ } ) ;
437+ } else {
438+ // Fallback for older browsers
439+ fallbackCopyToClipboard ( code , button ) ;
440+ }
441+ }
442+
443+ function fallbackCopyToClipboard ( text , button ) {
444+ var textArea = document . createElement ( 'textarea' ) ;
445+ textArea . value = text ;
446+ textArea . style . position = 'fixed' ;
447+ textArea . style . left = '-999999px' ;
448+ textArea . style . top = '-999999px' ;
449+ document . body . appendChild ( textArea ) ;
450+ textArea . focus ( ) ;
451+ textArea . select ( ) ;
452+
453+ try {
454+ var successful = document . execCommand ( 'copy' ) ;
455+ if ( successful ) {
456+ showCopySuccess ( button ) ;
457+ }
458+ } catch ( err ) {
459+ console . error ( 'Failed to copy code:' , err ) ;
460+ }
461+
462+ document . body . removeChild ( textArea ) ;
463+ }
464+
465+ function showCopySuccess ( button ) {
466+ // Change icon to checkmark
467+ button . innerHTML = button . dataset . checkIcon ;
468+ button . classList . add ( 'copied' ) ;
469+ button . setAttribute ( 'aria-label' , 'Copied!' ) ;
470+ button . setAttribute ( 'title' , 'Copied!' ) ;
471+
472+ // Revert back after 2 seconds
473+ setTimeout ( function ( ) {
474+ button . innerHTML = button . dataset . clipboardIcon ;
475+ button . classList . remove ( 'copied' ) ;
476+ button . setAttribute ( 'aria-label' , 'Copy code to clipboard' ) ;
477+ button . setAttribute ( 'title' , 'Copy code' ) ;
478+ } , 2000 ) ;
479+ }
480+
364481/* ===== Initialization ===== */
365482
366483document . addEventListener ( 'DOMContentLoaded' , function ( ) {
@@ -371,4 +488,5 @@ document.addEventListener('DOMContentLoaded', function() {
371488 generateToc ( ) ;
372489 hookTocActiveHighlighting ( ) ;
373490 hookSearchModal ( ) ;
491+ wrapCodeBlocksWithCopyButton ( ) ;
374492} ) ;
0 commit comments