@@ -114,24 +114,79 @@ function isImageFile(path) {
114114 return [ "png" , "jpg" , "jpeg" , "gif" , "svg" , "webp" ] . includes ( ext ) ;
115115}
116116
117+ function escapeHtml ( text ) {
118+ return text . replace ( / [ & < > ] / g, ( char ) => ( { "&" : "&" , "<" : "<" , ">" : ">" } [ char ] ) ) ;
119+ }
120+
121+ function renderPythonWithCommentBlocks ( text ) {
122+ const lines = text . split ( "\n" ) ;
123+ const delimiters = [ "'''" , '"""' ] ;
124+ let inBlock = false ;
125+ let blockDelim = "" ;
126+
127+ const wrapped = lines
128+ . map ( ( line , index ) => {
129+ let output = "" ;
130+ let cursor = 0 ;
131+
132+ while ( cursor < line . length ) {
133+ if ( inBlock ) {
134+ const endIndex = line . indexOf ( blockDelim , cursor ) ;
135+ if ( endIndex === - 1 ) {
136+ output += `<span class="hljs-comment">${ escapeHtml ( line . slice ( cursor ) ) } </span>` ;
137+ cursor = line . length ;
138+ } else {
139+ output += `<span class="hljs-comment">${ escapeHtml ( line . slice ( cursor , endIndex + 3 ) ) } </span>` ;
140+ cursor = endIndex + 3 ;
141+ inBlock = false ;
142+ blockDelim = "" ;
143+ }
144+ } else {
145+ const nextSingle = line . indexOf ( delimiters [ 0 ] , cursor ) ;
146+ const nextDouble = line . indexOf ( delimiters [ 1 ] , cursor ) ;
147+ const candidates = [ nextSingle , nextDouble ] . filter ( ( value ) => value !== - 1 ) ;
148+ if ( ! candidates . length ) {
149+ output += escapeHtml ( line . slice ( cursor ) ) ;
150+ cursor = line . length ;
151+ } else {
152+ const nextIndex = Math . min ( ...candidates ) ;
153+ const delim = nextIndex === nextSingle ? delimiters [ 0 ] : delimiters [ 1 ] ;
154+ output += escapeHtml ( line . slice ( cursor , nextIndex ) ) ;
155+ inBlock = true ;
156+ blockDelim = delim ;
157+ cursor = nextIndex ;
158+ }
159+ }
160+ }
161+
162+ if ( ! output ) {
163+ output = " " ;
164+ }
165+ const lineNumber = index + 1 ;
166+ return `<span class="code-line"><span class="line-number">${ lineNumber } </span><span class="line-content">${ output } </span></span>` ;
167+ } )
168+ . join ( "\n" ) ;
169+
170+ codeBlock . innerHTML = `<span class="code-lines">${ wrapped } </span>` ;
171+ }
172+
117173function renderCode ( text , languageClass ) {
174+ if ( languageClass === "python" ) {
175+ renderPythonWithCommentBlocks ( text ) ;
176+ return ;
177+ }
178+
118179 let highlighted ;
119180 try {
120181 if ( window . hljs && languageClass ) {
121182 highlighted = window . hljs . highlight ( text , { language : languageClass } ) . value ;
122183 } else if ( window . hljs ) {
123184 highlighted = window . hljs . highlightAuto ( text ) . value ;
124185 } else {
125- highlighted = text
126- . replace ( / & / g, "&" )
127- . replace ( / < / g, "<" )
128- . replace ( / > / g, ">" ) ;
186+ highlighted = escapeHtml ( text ) ;
129187 }
130188 } catch ( error ) {
131- highlighted = text
132- . replace ( / & / g, "&" )
133- . replace ( / < / g, "<" )
134- . replace ( / > / g, ">" ) ;
189+ highlighted = escapeHtml ( text ) ;
135190 }
136191
137192 const lines = highlighted . split ( "\n" ) ;
0 commit comments