@@ -3,8 +3,13 @@ import { fromMarkdown as wikiLinkFromMarkdown } from "mdast-util-wiki-link";
33import { gfm as gfmSyntax } from "micromark-extension-gfm" ;
44import { math as mathSyntax } from "micromark-extension-math" ;
55import { syntax as wikiLinkSyntax } from "micromark-extension-wiki-link" ;
6+ import { htmlBlockNames , htmlRawNames } from "micromark-util-html-tag-name" ;
67import parseFrontMatter from "../../main/front-matter/parse.js" ;
7- import { fromMarkdown } from "./micromark/html-flow-hack.js" ;
8+ import { mapAst } from "../utilities.js" ;
9+ import {
10+ fromMarkdown ,
11+ htmlFlowHackSyntax ,
12+ } from "./micromark/html-flow-hack.js" ;
813import { gfmFromMarkdown } from "./micromark/mdast-util-gfm.js" ;
914import {
1015 liquidFromMarkdown ,
@@ -14,7 +19,13 @@ import {
1419let markdownParseOptions ;
1520function getMarkdownParseOptions ( ) {
1621 return ( markdownParseOptions ??= {
17- extensions : [ gfmSyntax ( ) , mathSyntax ( ) , wikiLinkSyntax ( ) , liquidSyntax ( ) ] ,
22+ extensions : [
23+ gfmSyntax ( ) ,
24+ mathSyntax ( ) ,
25+ wikiLinkSyntax ( ) ,
26+ liquidSyntax ( ) ,
27+ htmlFlowHackSyntax ( ) ,
28+ ] ,
1829 mdastExtensions : [
1930 gfmFromMarkdown ( ) ,
2031 mathFromMarkdown ( ) ,
@@ -26,7 +37,7 @@ function getMarkdownParseOptions() {
2637
2738function parseMarkdown ( text ) {
2839 const { frontMatter, content } = parseFrontMatter ( text ) ;
29- const ast = fromMarkdown ( content , getMarkdownParseOptions ( ) ) ;
40+ let ast = fromMarkdown ( content , getMarkdownParseOptions ( ) ) ;
3041
3142 if ( frontMatter ) {
3243 const [ start , end ] = [ frontMatter . start , frontMatter . end ] . map (
@@ -45,7 +56,123 @@ function parseMarkdown(text) {
4556 } ) ;
4657 }
4758
59+ ast = transformInlineHtml ( ast ) ;
60+
4861 return ast ;
4962}
5063
64+ // with html-flow hack, inline HTML can interrupt paragraphs against commonmark spec
65+ // this function merges such inline HTML back into paragraphs
66+ function transformInlineHtml ( ast ) {
67+ return mapAst ( ast , ( node ) => {
68+ if ( ! node . children ) {
69+ return node ;
70+ }
71+
72+ const { children } = node ;
73+ for ( let i = 0 ; i < children . length ; i ++ ) {
74+ const child = children [ i ] ;
75+ if ( child . type !== "html" ) {
76+ continue ;
77+ }
78+ const tagName = child . value
79+ . match ( / ^ < \/ ? ( [ a - z 0 - 9 - ] + ) / iu) ?. [ 1 ]
80+ . toLowerCase ( ) ;
81+ if ( ! tagName ) {
82+ continue ;
83+ }
84+ if (
85+ [ ...htmlBlockNames ] . includes ( tagName ) ||
86+ htmlRawNames . includes ( tagName )
87+ ) {
88+ continue ;
89+ }
90+ const prev = children [ i - 1 ] ;
91+ const next = children [ i + 1 ] ;
92+
93+ const previousLineDifference =
94+ prev ?. type !== "paragraph"
95+ ? null
96+ : child . position . start . line - prev . position . end . line ;
97+ /** @type {"immediate" | "with-new-line" | "none" } */
98+ const mergePrevious =
99+ previousLineDifference === null
100+ ? "none"
101+ : previousLineDifference === 0
102+ ? "immediate"
103+ : previousLineDifference === 1
104+ ? "with-new-line"
105+ : "none" ;
106+
107+ const nextLineDifference =
108+ next ?. type !== "paragraph"
109+ ? null
110+ : next . position . start . line - child . position . end . line ;
111+ /** @type {"immediate" | "with-new-line" | "none" } */
112+ const mergeNext =
113+ previousLineDifference === null
114+ ? "none"
115+ : nextLineDifference === 0
116+ ? "immediate"
117+ : nextLineDifference === 1
118+ ? "with-new-line"
119+ : "none" ;
120+
121+ if ( mergePrevious === "none" && mergeNext === "none" ) {
122+ continue ;
123+ }
124+
125+ if ( mergePrevious !== "none" ) {
126+ if ( mergePrevious === "with-new-line" ) {
127+ prev . children . push ( newlineTextAfter ( prev ) ) ;
128+ }
129+ prev . children . push ( child ) ;
130+ prev . position . end = child . position . end ;
131+ children . splice ( i , 1 ) ;
132+ i -- ;
133+
134+ if ( mergeNext === "none" ) {
135+ continue ;
136+ }
137+ if ( mergeNext === "with-new-line" ) {
138+ prev . children . push ( newlineTextAfter ( child ) ) ;
139+ }
140+ prev . children . push ( ...next . children ) ;
141+ prev . position . end = next . position . end ;
142+ children . splice ( i + 1 , 1 ) ;
143+ continue ;
144+ }
145+
146+ // mergeNext must be not "none" here
147+ if ( mergeNext === "with-new-line" ) {
148+ next . children . unshift ( newlineTextAfter ( child ) ) ;
149+ }
150+ next . children . unshift ( child ) ;
151+ next . position . start = child . position . start ;
152+ children . splice ( i , 1 ) ;
153+ }
154+ return node ;
155+ } ) ;
156+
157+ function newlineTextAfter ( node ) {
158+ return {
159+ type : "newLineHack" ,
160+ value : "\n" ,
161+ raw : "\n" ,
162+ position : {
163+ start : {
164+ line : node . position . end . line ,
165+ column : node . position . end . column ,
166+ offset : node . position . end . offset ,
167+ } ,
168+ end : {
169+ line : node . position . end . line + 1 ,
170+ column : 1 ,
171+ offset : node . position . end . offset + 1 ,
172+ } ,
173+ } ,
174+ } ;
175+ }
176+ }
177+
51178export { parseMarkdown } ;
0 commit comments