@@ -171,99 +171,205 @@ function renderInlineMarkdown(value) {
171171
172172function renderMarkdownToHtml ( markdown ) {
173173 const normalized = String ( markdown || "" ) . replace ( / \r \n / g, "\n" ) . replace ( / \r / g, "\n" ) ;
174- const lines = normalized . split ( "\n" ) ;
174+ const sourceLines = normalized . split ( "\n" ) ;
175175 const blocks = [ ] ;
176- let paragraphLines = [ ] ;
177- let listItems = [ ] ;
178- let orderedListItems = [ ] ;
179- let codeLines = [ ] ;
180- let inCodeBlock = false ;
181-
182- function flushParagraph ( ) {
183- if ( paragraphLines . length === 0 ) {
184- return ;
185- }
186- blocks . push ( `<p>${ renderInlineMarkdown ( paragraphLines . join ( " " ) ) } </p>` ) ;
187- paragraphLines = [ ] ;
176+
177+ function expandIndentation ( value ) {
178+ return String ( value || "" ) . replace ( / \t / g, " " ) ;
179+ }
180+
181+ function getIndentation ( value ) {
182+ const expanded = expandIndentation ( value ) ;
183+ const match = expanded . match ( / ^ ( \s * ) / ) ;
184+ return match ? match [ 1 ] . length : 0 ;
188185 }
189186
190- function flushList ( ) {
191- if ( listItems . length > 0 ) {
192- blocks . push ( `<ul>${ listItems . map ( ( item ) => `<li>${ renderInlineMarkdown ( item ) } </li>` ) . join ( "" ) } </ul>` ) ;
193- listItems = [ ] ;
187+ function getListMarker ( value ) {
188+ const expanded = expandIndentation ( value ) ;
189+ let match = expanded . match ( / ^ ( \s * ) ( [ - * ] ) \s + ( .+ ) $ / ) ;
190+ if ( match ) {
191+ return { indent : match [ 1 ] . length , ordered : false , content : match [ 3 ] } ;
194192 }
195193
196- if ( orderedListItems . length > 0 ) {
197- blocks . push ( `<ol> ${ orderedListItems . map ( ( item ) => `<li> ${ renderInlineMarkdown ( item ) } </li>` ) . join ( "" ) } </ol>` ) ;
198- orderedListItems = [ ] ;
194+ match = expanded . match ( / ^ ( \s * ) ( \d + ) \. \s + ( . + ) $ / ) ;
195+ if ( match ) {
196+ return { indent : match [ 1 ] . length , ordered : true , content : match [ 3 ] } ;
199197 }
198+
199+ return null ;
200200 }
201201
202- function flushCodeBlock ( ) {
203- if ( codeLines . length === 0 ) {
204- return ;
202+ function parseCodeBlock ( startIndex ) {
203+ const codeLines = [ ] ;
204+ let index = startIndex + 1 ;
205+
206+ while ( index < sourceLines . length && ! sourceLines [ index ] . startsWith ( "```" ) ) {
207+ codeLines . push ( sourceLines [ index ] ) ;
208+ index += 1 ;
205209 }
206- blocks . push ( `<pre><code>${ escapeHtml ( codeLines . join ( "\n" ) ) } </code></pre>` ) ;
207- codeLines = [ ] ;
208- }
209- for ( const line of lines ) {
210- if ( line . startsWith ( "```" ) ) {
211- flushParagraph ( ) ;
212- flushList ( ) ;
213- if ( inCodeBlock ) {
214- flushCodeBlock ( ) ;
215- }
216- inCodeBlock = ! inCodeBlock ;
217- continue ;
210+
211+ if ( index < sourceLines . length && sourceLines [ index ] . startsWith ( "```" ) ) {
212+ index += 1 ;
218213 }
219214
220- if ( inCodeBlock ) {
221- codeLines . push ( line ) ;
222- continue ;
215+ return {
216+ html : `<pre><code>${ escapeHtml ( codeLines . join ( "\n" ) ) } </code></pre>` ,
217+ nextIndex : index ,
218+ } ;
219+ }
220+
221+ function parseParagraph ( startIndex ) {
222+ const paragraphLines = [ ] ;
223+ let index = startIndex ;
224+
225+ while ( index < sourceLines . length ) {
226+ const rawLine = sourceLines [ index ] ;
227+ const trimmed = rawLine . trim ( ) ;
228+ if ( ! trimmed ) {
229+ break ;
230+ }
231+ if ( trimmed . startsWith ( "```" ) || trimmed . match ( / ^ ( # { 1 , 6 } ) \s + ( .+ ) $ / ) || getListMarker ( rawLine ) ) {
232+ break ;
233+ }
234+
235+ paragraphLines . push ( trimmed ) ;
236+ index += 1 ;
223237 }
224238
225- const trimmed = line . trim ( ) ;
239+ return {
240+ html : `<p>${ renderInlineMarkdown ( paragraphLines . join ( " " ) ) } </p>` ,
241+ nextIndex : index ,
242+ } ;
243+ }
244+
245+ function parseList ( startIndex , baseIndent , ordered ) {
246+ const tagName = ordered ? "ol" : "ul" ;
247+ const items = [ ] ;
248+ let index = startIndex ;
226249
227- if ( trimmed === "" ) {
228- flushParagraph ( ) ;
229- flushList ( ) ;
230- continue ;
250+ while ( index < sourceLines . length ) {
251+ const marker = getListMarker ( sourceLines [ index ] ) ;
252+ if ( ! marker || marker . indent !== baseIndent || marker . ordered !== ordered ) {
253+ break ;
254+ }
255+
256+ const paragraphLines = [ marker . content . trim ( ) ] ;
257+ const children = [ ] ;
258+ index += 1 ;
259+
260+ while ( index < sourceLines . length ) {
261+ const rawLine = sourceLines [ index ] ;
262+ const trimmed = rawLine . trim ( ) ;
263+
264+ if ( ! trimmed ) {
265+ let lookahead = index + 1 ;
266+ while ( lookahead < sourceLines . length && ! sourceLines [ lookahead ] . trim ( ) ) {
267+ lookahead += 1 ;
268+ }
269+ if ( lookahead >= sourceLines . length ) {
270+ index = lookahead ;
271+ break ;
272+ }
273+
274+ const nextMarker = getListMarker ( sourceLines [ lookahead ] ) ;
275+ const nextIndent = getIndentation ( sourceLines [ lookahead ] ) ;
276+ const nextTrimmed = sourceLines [ lookahead ] . trim ( ) ;
277+ if ( ( nextMarker && nextMarker . indent <= baseIndent ) || ( ! nextMarker && nextIndent <= baseIndent && ! nextTrimmed . startsWith ( "```" ) && ! nextTrimmed . match ( / ^ ( # { 1 , 6 } ) \s + ( .+ ) $ / ) ) ) {
278+ index = lookahead ;
279+ break ;
280+ }
281+
282+ index = lookahead ;
283+ continue ;
284+ }
285+
286+ if ( trimmed . startsWith ( "```" ) ) {
287+ const parsedCode = parseCodeBlock ( index ) ;
288+ children . push ( parsedCode . html ) ;
289+ index = parsedCode . nextIndex ;
290+ continue ;
291+ }
292+
293+ const nestedMarker = getListMarker ( rawLine ) ;
294+ if ( nestedMarker ) {
295+ if ( nestedMarker . indent > baseIndent ) {
296+ const parsedList = parseList ( index , nestedMarker . indent , nestedMarker . ordered ) ;
297+ children . push ( parsedList . html ) ;
298+ index = parsedList . nextIndex ;
299+ continue ;
300+ }
301+
302+ break ;
303+ }
304+
305+ const indent = getIndentation ( rawLine ) ;
306+ if ( trimmed . match ( / ^ ( # { 1 , 6 } ) \s + ( .+ ) $ / ) && indent <= baseIndent ) {
307+ break ;
308+ }
309+
310+ if ( indent > baseIndent ) {
311+ paragraphLines . push ( trimmed ) ;
312+ index += 1 ;
313+ continue ;
314+ }
315+
316+ break ;
317+ }
318+
319+ const paragraphHtml = renderInlineMarkdown ( paragraphLines . join ( " " ) ) ;
320+ if ( children . length > 0 ) {
321+ items . push ( `<li><p>${ paragraphHtml } </p>${ children . join ( "" ) } </li>` ) ;
322+ } else {
323+ items . push ( `<li>${ paragraphHtml } </li>` ) ;
324+ }
231325 }
232326
233- const unorderedMatch = trimmed . match ( / ^ [ - * ] \s + ( .+ ) $ / ) ;
234- if ( unorderedMatch ) {
235- flushParagraph ( ) ;
236- orderedListItems = [ ] ;
237- listItems . push ( unorderedMatch [ 1 ] ) ;
327+ return {
328+ html : `<${ tagName } >${ items . join ( "" ) } </${ tagName } >` ,
329+ nextIndex : index ,
330+ } ;
331+ }
332+
333+ let index = 0 ;
334+ while ( index < sourceLines . length ) {
335+ const rawLine = sourceLines [ index ] ;
336+ const trimmed = rawLine . trim ( ) ;
337+
338+ if ( ! trimmed ) {
339+ index += 1 ;
238340 continue ;
239341 }
240342
241- const orderedMatch = trimmed . match ( / ^ \d + [ . ] \s + ( .+ ) $ / ) ;
242- if ( orderedMatch ) {
243- flushParagraph ( ) ;
244- listItems = [ ] ;
245- orderedListItems . push ( orderedMatch [ 1 ] ) ;
343+ if ( trimmed . startsWith ( "```" ) ) {
344+ const parsedCode = parseCodeBlock ( index ) ;
345+ blocks . push ( parsedCode . html ) ;
346+ index = parsedCode . nextIndex ;
246347 continue ;
247348 }
248349
249350 const headingMatch = trimmed . match ( / ^ ( # { 1 , 6 } ) \s + ( .+ ) $ / ) ;
250351 if ( headingMatch ) {
251- flushParagraph ( ) ;
252- flushList ( ) ;
253352 const level = headingMatch [ 1 ] . length ;
254353 blocks . push ( `<h${ level } >${ renderInlineMarkdown ( headingMatch [ 2 ] ) } </h${ level } >` ) ;
354+ index += 1 ;
255355 continue ;
256356 }
257357
258- paragraphLines . push ( trimmed ) ;
358+ const listMarker = getListMarker ( rawLine ) ;
359+ if ( listMarker ) {
360+ const parsedList = parseList ( index , listMarker . indent , listMarker . ordered ) ;
361+ blocks . push ( parsedList . html ) ;
362+ index = parsedList . nextIndex ;
363+ continue ;
364+ }
365+
366+ const paragraph = parseParagraph ( index ) ;
367+ blocks . push ( paragraph . html ) ;
368+ index = paragraph . nextIndex ;
259369 }
260370
261- flushParagraph ( ) ;
262- flushList ( ) ;
263- flushCodeBlock ( ) ;
264371 return blocks . join ( "" ) ;
265372}
266-
267373function ensureSelectValue ( select , value ) {
268374 if ( ! Array . from ( select . options ) . some ( ( option ) => option . value === value ) ) {
269375 const option = document . createElement ( "option" ) ;
0 commit comments