@@ -355,6 +355,175 @@ <h2>Where Components Are Installed</h2>
355355 </ footer >
356356
357357 <!-- Code Copy Functionality -->
358- < script src ="../code-copy.js?v=1.0 "> </ script >
358+ < script >
359+ // Code Copy Functionality for Blog Articles
360+ class CodeCopy {
361+ constructor ( ) {
362+ this . initCodeBlocks ( ) ;
363+ this . setupCopyFunctionality ( ) ;
364+ }
365+
366+ initCodeBlocks ( ) {
367+ // Convert existing pre elements to new code-block structure
368+ const preElements = document . querySelectorAll ( '.article-content-full pre:not(.converted)' ) ;
369+
370+ preElements . forEach ( pre => {
371+ const code = pre . querySelector ( 'code' ) ;
372+ if ( ! code ) return ;
373+
374+ // Detect language from class or content
375+ const language = this . detectLanguage ( code ) ;
376+ const isTerminal = language === 'bash' || language === 'terminal' ;
377+
378+ // Create new code block structure
379+ const codeBlock = document . createElement ( 'div' ) ;
380+ codeBlock . className = isTerminal ? 'code-block terminal-block' : 'code-block' ;
381+
382+ // Create header
383+ const header = document . createElement ( 'div' ) ;
384+ header . className = 'code-header' ;
385+
386+ const languageSpan = document . createElement ( 'span' ) ;
387+ languageSpan . className = 'code-language' ;
388+ languageSpan . textContent = language ;
389+
390+ const copyButton = document . createElement ( 'button' ) ;
391+ copyButton . className = 'copy-button' ;
392+ copyButton . innerHTML = `
393+ <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
394+ <path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
395+ </svg>
396+ Copy
397+ ` ;
398+
399+ header . appendChild ( languageSpan ) ;
400+ header . appendChild ( copyButton ) ;
401+
402+ // Clone and prepare the pre element
403+ const newPre = pre . cloneNode ( true ) ;
404+ newPre . classList . add ( 'converted' ) ;
405+
406+ // Assemble new structure
407+ codeBlock . appendChild ( header ) ;
408+ codeBlock . appendChild ( newPre ) ;
409+
410+ // Replace original pre
411+ pre . parentNode . replaceChild ( codeBlock , pre ) ;
412+ } ) ;
413+ }
414+
415+ detectLanguage ( codeElement ) {
416+ // Check for class-based language detection
417+ const className = codeElement . className ;
418+ if ( className . includes ( 'language-' ) ) {
419+ return className . match ( / l a n g u a g e - ( \w + ) / ) [ 1 ] ;
420+ }
421+
422+ // Check content patterns
423+ const content = codeElement . textContent ;
424+
425+ if ( content . includes ( 'npm ' ) || content . includes ( '$ ' ) || content . includes ( 'claude-code ' ) ) {
426+ return 'bash' ;
427+ }
428+ if ( content . includes ( 'import ' ) && content . includes ( 'from ' ) ) {
429+ return 'javascript' ;
430+ }
431+ if ( content . includes ( 'CREATE TABLE' ) || content . includes ( 'SELECT ' ) ) {
432+ return 'sql' ;
433+ }
434+ if ( content . includes ( '{' ) && content . includes ( '"' ) ) {
435+ return 'json' ;
436+ }
437+ if ( content . includes ( 'def ' ) || content . includes ( 'import ' ) ) {
438+ return 'python' ;
439+ }
440+
441+ return 'text' ;
442+ }
443+
444+ setupCopyFunctionality ( ) {
445+ document . addEventListener ( 'click' , async ( e ) => {
446+ if ( ! e . target . closest ( '.copy-button' ) ) return ;
447+
448+ const button = e . target . closest ( '.copy-button' ) ;
449+ const codeBlock = button . closest ( '.code-block' ) ;
450+ const pre = codeBlock . querySelector ( 'pre' ) ;
451+ const code = pre . querySelector ( 'code' ) ;
452+
453+ if ( ! code ) return ;
454+
455+ try {
456+ // Get clean text content
457+ let textToCopy = code . textContent ;
458+
459+ // Clean up terminal prompts if it's a terminal block
460+ if ( codeBlock . classList . contains ( 'terminal-block' ) ) {
461+ textToCopy = this . cleanTerminalOutput ( textToCopy ) ;
462+ }
463+
464+ await navigator . clipboard . writeText ( textToCopy ) ;
465+
466+ // Update button state
467+ const originalContent = button . innerHTML ;
468+ button . innerHTML = `
469+ <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
470+ <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
471+ </svg>
472+ Copied!
473+ ` ;
474+ button . classList . add ( 'copied' ) ;
475+
476+ // Reset after 2 seconds
477+ setTimeout ( ( ) => {
478+ button . innerHTML = originalContent ;
479+ button . classList . remove ( 'copied' ) ;
480+ } , 2000 ) ;
481+
482+ } catch ( err ) {
483+ console . error ( 'Failed to copy code:' , err ) ;
484+
485+ // Fallback: select text
486+ const selection = window . getSelection ( ) ;
487+ const range = document . createRange ( ) ;
488+ range . selectNodeContents ( code ) ;
489+ selection . removeAllRanges ( ) ;
490+ selection . addRange ( range ) ;
491+ }
492+ } ) ;
493+ }
494+
495+ cleanTerminalOutput ( text ) {
496+ // Remove common terminal prompts and clean output
497+ return text
498+ . split ( '\n' )
499+ . map ( line => {
500+ // Remove prompts like "$ ", "❯ ", "claude-code> "
501+ line = line . replace ( / ^ [ \$ ❯ ] \s * / , '' ) . replace ( / ^ c l a u d e - c o d e > \s * / , '' ) ;
502+
503+ // Remove output/result comments (lines starting with # ✓)
504+ if ( line . trim ( ) . startsWith ( '# ✓' ) ||
505+ line . trim ( ) . startsWith ( '# Start using' ) ||
506+ line . trim ( ) . startsWith ( '# Your .claude' ) ||
507+ line . trim ( ) . startsWith ( '# This will' ) ||
508+ line . includes ( 'Components will be installed' ) ||
509+ line . includes ( 'directory now contains' ) ) {
510+ return '' ;
511+ }
512+
513+ return line ;
514+ } )
515+ . filter ( line => line . trim ( ) !== '' ) // Remove empty lines
516+ . join ( '\n' )
517+ . trim ( ) ;
518+ }
519+ }
520+
521+ // Initialize when DOM is loaded
522+ if ( document . readyState === 'loading' ) {
523+ document . addEventListener ( 'DOMContentLoaded' , ( ) => new CodeCopy ( ) ) ;
524+ } else {
525+ new CodeCopy ( ) ;
526+ }
527+ </ script >
359528</ body >
360529</ html >
0 commit comments