@@ -41,22 +41,22 @@ export default function useEnhancedCodeBlocks(
4141 // First enhance codeblocks formatted by Jekyll + Rouge
4242 const numCodeBlocks = enhanceBlocks (
4343 mainElRef . current . querySelectorAll ( 'div.highlighter-rouge' ) ,
44- getRawContentsFromJekyllRougeCodeblock ,
44+ getCodeElFromJekyllRougeCodeblock ,
4545 0 ,
4646 ) ;
4747 // Then attempt to enhance ordinary <pre> blocks.
4848 enhanceBlocks (
4949 mainElRef . current . querySelectorAll ( 'pre' ) ,
50- getRawContentsFromPreCodeblock ,
50+ getCodeElFromPreCodeblock ,
5151 numCodeBlocks ,
5252 ) ;
5353
5454 return ( ) => { } ;
5555}
5656
57- function getRawContentsFromJekyllRougeCodeblock (
57+ function getCodeElFromJekyllRougeCodeblock (
5858 codeblock : HTMLElement ,
59- ) : string | null {
59+ ) : HTMLElement | null {
6060 // The original structure of a codeblock:
6161 // <div
6262 // class="highlighter-rouge language-[lang]"
@@ -87,13 +87,10 @@ function getRawContentsFromJekyllRougeCodeblock(
8787 return null ;
8888 }
8989
90- return ( codeEl as HTMLElement ) . innerHTML ;
90+ return codeEl as HTMLElement ;
9191}
9292
93- function getRawContentsFromPreCodeblock (
94- codeblock_ : HTMLElement ,
95- ) : string | null {
96- let codeblock = codeblock_ ;
93+ function getCodeElFromPreCodeblock ( codeblock : HTMLElement ) : HTMLElement | null {
9794 // The structure of a <pre> codeblock:
9895 // <pre>
9996 // <code> <!-- OPTIONAL -->
@@ -104,9 +101,9 @@ function getRawContentsFromPreCodeblock(
104101 codeblock . childNodes . length === 1 &&
105102 codeblock . firstElementChild ?. tagName === 'CODE'
106103 ) {
107- codeblock = codeblock . firstElementChild as HTMLElement ;
104+ return codeblock . firstElementChild as HTMLElement ;
108105 }
109- return codeblock . innerHTML . trim ( ) ;
106+ return codeblock ;
110107}
111108
112109/**
@@ -116,7 +113,7 @@ function getRawContentsFromPreCodeblock(
116113 */
117114function enhanceBlocks (
118115 codeblocks : NodeListOf < HTMLElement > ,
119- getContents : ( node : HTMLElement ) => string | null ,
116+ getCodeEl : ( node : HTMLElement ) => HTMLElement | null ,
120117 startId = 0 ,
121118) : number {
122119 let nextCodeBlockId = startId ;
@@ -141,10 +138,11 @@ function enhanceBlocks(
141138 return ;
142139 }
143140
144- const codeblockContents = getContents ( codeblock ) ;
145- if ( codeblockContents == null ) {
141+ const codeblockContentsEl = getCodeEl ( codeblock ) ;
142+ if ( codeblockContentsEl == null ) {
146143 return ;
147144 }
145+ const codeblockContents = getCodeblockContents ( codeblockContentsEl ) ;
148146
149147 const title = codeblock . dataset [ 'title' ] || null ;
150148 const anchorId = title
@@ -529,3 +527,54 @@ function createCodeBlockAnchorId(
529527) : string {
530528 return `${ slugify ( title ) } -${ codeblockNumericId } ` ;
531529}
530+
531+ /**
532+ * Given a codeblock / pre element, return a string reprensenting the HTML of
533+ * the codeblock.
534+ *
535+ * One edge case that this method handles: Lines split within a single span.
536+ * Consider the following codeblock:
537+ * ```html
538+ * <code><span class="c">Line 1</span>
539+ * <span class="c">Line 2</span>
540+ * <span class="c">Line 3
541+ * Line 4</span></code>
542+ * ```
543+ * Since the rest of the code assumes that "\n" characters separate lines, we
544+ * need to ensure that each line starts with its own span if necessary. The
545+ * output of this method should be:
546+ * ```html
547+ * <code><span class="c">Line 1</span>
548+ * <span class="c">Line 2</span>
549+ * <span class="c">Line 3</span>
550+ * <span class="c">Line 4</span></code>
551+ * ```
552+ */
553+ function getCodeblockContents ( codeEl : HTMLElement ) : string {
554+ const resultNode = codeEl . cloneNode ( ) as HTMLElement ;
555+ codeEl . childNodes . forEach ( ( childNode ) => {
556+ if ( childNode . nodeType === Node . ELEMENT_NODE ) {
557+ if (
558+ ( childNode as HTMLElement ) . tagName === 'SPAN' &&
559+ childNode . textContent != null
560+ ) {
561+ const lines = childNode . textContent . split ( '\n' ) ;
562+ lines . forEach ( ( line , i ) => {
563+ // Ignore empty lines within a span, but still insert the \n.
564+ if ( line ) {
565+ const lineEl = childNode . cloneNode ( ) as HTMLElement ;
566+ lineEl . textContent = line ;
567+ resultNode . appendChild ( lineEl ) ;
568+ }
569+ // Append a new line except after the last line in this span
570+ if ( i < lines . length - 1 ) {
571+ resultNode . appendChild ( document . createTextNode ( '\n' ) ) ;
572+ }
573+ } ) ;
574+ }
575+ } else {
576+ resultNode . appendChild ( childNode . cloneNode ( true ) ) ;
577+ }
578+ } ) ;
579+ return resultNode . innerHTML . trim ( ) ;
580+ }
0 commit comments