@@ -48,6 +48,20 @@ export function marmdownItTokensToComarkTree(
4848 let i = 0
4949 let endLine = options . startLine
5050 while ( i < tokens . length ) {
51+ const token = tokens [ i ]
52+
53+ // An html_block whose own content already closes its outer element
54+ // (e.g. `<p><img></p>`, `<div>foo</div>`, `<img>`, `<!-- ... -->`) has no
55+ // paired html_block_close later in the stream.
56+ if ( token . type === 'html_block' && htmlBlockHasOwnClose ( token . content || '' ) ) {
57+ const htmlNodes = htmlToComarkNodes ( token . content || '' )
58+ for ( const htmlNode of htmlNodes ) {
59+ nodes . push ( htmlNode )
60+ }
61+ i ++
62+ continue
63+ }
64+
5165 const result = processBlockToken ( tokens , i , false , state )
5266 if ( result . node ) {
5367 if ( options . preservePositions ) {
@@ -69,6 +83,24 @@ export function marmdownItTokensToComarkTree(
6983 return nodes
7084}
7185
86+ /**
87+ * Whether an `html_block` token's content already closes its own outer element.
88+ * The block tokeniser only emits an `html_block_close` for lines that begin with
89+ * `</`, so any block whose closer sits on the opener's line (`<p><img></p>`),
90+ * including void elements and comments, has no companion close token.
91+ */
92+ function htmlBlockHasOwnClose ( content : string ) : boolean {
93+ const trimmed = content . trim ( )
94+ if ( ! trimmed ) return false
95+ // Comments, declarations, CDATA, processing instructions: self-terminating.
96+ if ( trimmed . startsWith ( '<!' ) || trimmed . startsWith ( '<?' ) ) return true
97+ const match = trimmed . match ( / ^ < \s * ( [ a - z A - Z ] [ a - z A - Z 0 - 9 ] * ) / )
98+ if ( ! match ) return false
99+ const tag = match [ 1 ]
100+ if ( VOID_ELEMENTS . has ( tag . toLowerCase ( ) ) ) return true
101+ return new RegExp ( `</\\s*${ tag } \\s*>` , 'i' ) . test ( trimmed )
102+ }
103+
72104/**
73105 * Extract and process attributes from a token's attrs array
74106 */
@@ -289,18 +321,11 @@ function processBlockToken(
289321 return { node : [ null , { } , inner ] as unknown as ComarkNode , nextIndex : startIndex + 1 }
290322 }
291323
292- const htmlNodes = htmlToComarkNodes ( content )
293- const [ node1 ] = htmlNodes
324+ const children = processBlockChildren ( tokens , startIndex + 1 , 'html_block_close' , false , false , false , state )
325+ const [ node1 ] = htmlToComarkNodes ( content )
294326 if ( ! node1 ) {
295327 return { node : null , nextIndex : startIndex + 1 }
296328 }
297-
298- const isVoid = Array . isArray ( node1 ) && VOID_ELEMENTS . has ( node1 [ 0 ] as string )
299- if ( isVoid ) {
300- return { node : node1 , nextIndex : startIndex + 1 }
301- }
302-
303- const children = processBlockChildren ( tokens , startIndex + 1 , 'html_block_close' , false , false , false , state )
304329 const node = [ node1 [ 0 ] ! , node1 [ 1 ] ! as ComarkElementAttributes , ...children . nodes ] as ComarkNode
305330
306331 return { node, nextIndex : children . nextIndex + 1 }
0 commit comments