| ... | @@ -36,6 +36,7 @@ interface TailState { | ... | @@ -36,6 +36,7 @@ interface TailState { |
| 36 | exclusive: ExclusiveState | null; | 36 | exclusive: ExclusiveState | null; |
| 37 | links: LinkState[]; | 37 | links: LinkState[]; |
| 38 | pendingDelim: DelimState | null; | 38 | pendingDelim: DelimState | null; |
| | 39 | pendingHtml: number | null; |
| 39 | } | 40 | } |
| 40 | | 41 | |
| 41 | const NESTABLE_DELIMS = new Set<DelimToken>(["*", "**", "_", "__"]); | 42 | const NESTABLE_DELIMS = new Set<DelimToken>(["*", "**", "_", "__"]); |
| ... | @@ -47,6 +48,7 @@ function parseTail(tail: string): TailState { | ... | @@ -47,6 +48,7 @@ function parseTail(tail: string): TailState { |
| 47 | exclusive: null, | 48 | exclusive: null, |
| 48 | links: [], | 49 | links: [], |
| 49 | pendingDelim: null, | 50 | pendingDelim: null, |
| | 51 | pendingHtml: null, |
| 50 | }; | 52 | }; |
| 51 | | 53 | |
| 52 | for (let i = 0; i < tail.length; i++) { | 54 | for (let i = 0; i < tail.length; i++) { |
| ... | @@ -71,6 +73,12 @@ function parseTail(tail: string): TailState { | ... | @@ -71,6 +73,12 @@ function parseTail(tail: string): TailState { |
| 71 | continue; | 73 | continue; |
| 72 | } | 74 | } |
| 73 | | 75 | |
| | 76 | const htmlEnd = consumeHtml(state, tail, i); |
| | 77 | if (htmlEnd > i) { |
| | 78 | i = htmlEnd - 1; |
| | 79 | continue; |
| | 80 | } |
| | 81 | |
| 74 | const delimEnd = consumeDelim(state, tail, i); | 82 | const delimEnd = consumeDelim(state, tail, i); |
| 75 | if (delimEnd > i) { | 83 | if (delimEnd > i) { |
| 76 | i = delimEnd - 1; | 84 | i = delimEnd - 1; |
| ... | @@ -169,6 +177,23 @@ function consumeTildes(state: TailState, tail: string, start: number) { | ... | @@ -169,6 +177,23 @@ function consumeTildes(state: TailState, tail: string, start: number) { |
| 169 | return start + length; | 177 | return start + length; |
| 170 | } | 178 | } |
| 171 | | 179 | |
| | 180 | function consumeHtml(state: TailState, tail: string, start: number) { |
| | 181 | if (tail[start] !== "<" || isEscapedAt(tail, start)) return start; |
| | 182 | |
| | 183 | const next = tail[start + 1]; |
| | 184 | if (next !== undefined && !isAsciiLetter(next)) return start; |
| | 185 | |
| | 186 | state.pendingHtml = start; |
| | 187 | for (let i = start + 1; i < tail.length; i++) { |
| | 188 | if (tail[i] === ">" || tail[i] === "\n") { |
| | 189 | state.pendingHtml = null; |
| | 190 | return i + 1; |
| | 191 | } |
| | 192 | } |
| | 193 | |
| | 194 | return tail.length; |
| | 195 | } |
| | 196 | |
| 172 | function consumeDelim(state: TailState, tail: string, start: number) { | 197 | function consumeDelim(state: TailState, tail: string, start: number) { |
| 173 | const token = matchDelim(tail, start); | 198 | const token = matchDelim(tail, start); |
| 174 | if (!token) return start; | 199 | if (!token) return start; |
| ... | @@ -277,6 +302,8 @@ function inLinkUrlMode(state: TailState) { | ... | @@ -277,6 +302,8 @@ function inLinkUrlMode(state: TailState) { |
| 277 | } | 302 | } |
| 278 | | 303 | |
| 279 | function renderTail(tail: string, state: TailState) { | 304 | function renderTail(tail: string, state: TailState) { |
| | 305 | if (state.pendingHtml !== null) tail = tail.slice(0, state.pendingHtml); |
| | 306 | |
| 280 | const link = state.links.at(-1); | 307 | const link = state.links.at(-1); |
| 281 | if (link) return renderOpenLink(tail, link); | 308 | if (link) return renderOpenLink(tail, link); |
| 282 | | 309 | |
| ... | @@ -386,3 +413,7 @@ function isEscapedAt(text: string, index: number) { | ... | @@ -386,3 +413,7 @@ function isEscapedAt(text: string, index: number) { |
| 386 | function isWordChar(char?: string) { | 413 | function isWordChar(char?: string) { |
| 387 | return !!char && /[A-Za-z0-9]/.test(char); | 414 | return !!char && /[A-Za-z0-9]/.test(char); |
| 388 | } | 415 | } |
| | 416 | |
| | 417 | function isAsciiLetter(char?: string) { |
| | 418 | return !!char && /[A-Za-z]/.test(char); |
| | 419 | } |