Skip to content

Commit a3fd3b5

Browse files
committed
fix: update stream-markdown-parser to version 0.0.30 and enhance math-like detection logic
1 parent ef3a3e3 commit a3fd3b5

File tree

9 files changed

+42
-81
lines changed

9 files changed

+42
-81
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
},
113113
"dependencies": {
114114
"@floating-ui/dom": "^1.7.4",
115-
"stream-markdown-parser": "^0.0.29"
115+
"stream-markdown-parser": "^0.0.30"
116116
},
117117
"devDependencies": {
118118
"@antfu/eslint-config": "^5.4.1",

packages/markdown-parser/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "stream-markdown-parser",
33
"type": "module",
4-
"version": "0.0.29",
4+
"version": "0.0.30",
55
"packageManager": "[email protected]",
66
"description": "Pure markdown parser and renderer utilities with streaming support - framework agnostic",
77
"author": "Simon He",

packages/markdown-parser/src/plugins/isMathLike.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ const TEX_SPECIFIC_RE = /\\(?:text|frac|left|right|times)/
3737
// regex literal on some platforms/linters.
3838
// eslint-disable-next-line prefer-regex-literals
3939
const OPS_RE = new RegExp('(?<!\\+)\\+(?!\\+)|[=\\-*/^<>]|\\\\times|\\\\pm|\\\\cdot|\\\\le|\\\\ge|\\\\neq')
40+
// Hyphenated multi-word (like "Quasi-Streaming") should not be treated
41+
// as a math operator. But single-letter-variable hyphens (e.g. "x-y") are
42+
// still math; so only ignore hyphens between multi-letter words.
43+
const HYPHENATED_MULTIWORD_RE = /\b[A-Z]{2,}-[A-Z]{2,}\b/i
4044
const FUNC_CALL_RE = /[A-Z]+\s*\([^)]+\)/i
4145
const WORDS_RE = /\b(?:sin|cos|tan|log|ln|exp|sqrt|frac|sum|lim|int|prod)\b/
4246
// Heuristic to detect common date/time patterns like 2025/9/30 21:37:24 and
@@ -79,14 +83,16 @@ export function isMathLike(s: string) {
7983
const superscriptPattern = /(?:^|[^\w\\])(?:[A-Z]|\\[A-Z]+)\^(?:\{[^}]+\}|[A-Z0-9\\])/i
8084
const superSub = subscriptPattern.test(norm) || superscriptPattern.test(norm)
8185
// common math operator symbols or named commands
82-
const ops = OPS_RE.test(norm)
86+
// Ignore operator-like hyphens that are clearly word compound separators
87+
// (e.g. "Quasi-Streaming"). Keep hyphens for math like "x-y".
88+
const ops = OPS_RE.test(norm) && !HYPHENATED_MULTIWORD_RE.test(norm)
8389
// function-like patterns: f(x), sin(x)
8490
const funcCall = FUNC_CALL_RE.test(norm)
8591
// common math words
8692
const words = WORDS_RE.test(norm)
87-
// 纯单个英文字命,也渲染成数学公式
93+
// 纯单个英文字母也渲染成数学公式
8894
// e.g. (w) (x) (y) (z)
89-
// const pureWord = /^\([a-zA-Z]\)$/i.test(stripped)
95+
const pureWord = /^\([a-z]\)$/i.test(stripped)
9096

91-
return texCmd || texCmdWithBraces || texBraceStart || texSpecific || superSub || ops || funcCall || words
97+
return texCmd || texCmdWithBraces || texBraceStart || texSpecific || superSub || ops || funcCall || words || pureWord
9298
}

packages/markdown-parser/src/plugins/math.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -439,19 +439,7 @@ export function applyMath(md: MarkdownIt, mathOpts?: MathOptions) {
439439
let closeDelim = ''
440440
for (const [open, close] of delimiters) {
441441
// 这里其实不应该只匹配 startWith的情况因为很可能前面还有 text
442-
const m = lineText.indexOf(open)
443-
if (m !== -1) {
444-
const beforeText = lineText.slice(0, m)
445-
if (beforeText) {
446-
if (beforeText.endsWith('!')) {
447-
// image-node need break
448-
return false
449-
}
450-
const inline = s.push('inline', '', 0)
451-
inline.content = beforeText
452-
inline.map = [startLine, startLine]
453-
inline.children = []
454-
}
442+
if (lineText.startsWith(open)) {
455443
if (open.includes('[')) {
456444
if (lineText.replace('\\', '') === '[') {
457445
if (startLine + 1 < endLine) {

playground/src/pages/markdown.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Icon } from '@iconify/vue'
33
import MarkdownRender from 'vue-renderer-markdown'
44
import { useRouter } from 'vue-router'
55
import { getUseMonaco } from '../../../src/components/CodeBlockNode/monaco'
6+
import MarkdownCodeBlockNode from '../../../src/components/MarkdownCodeBlockNode'
67
import { setCustomComponents } from '../../../src/utils/nodeComponents'
78
import KatexWorker from '../../../src/workers/katexRenderer.worker?worker&inline'
89
import { setKaTeXWorker } from '../../../src/workers/katexWorkerClient'
@@ -11,7 +12,6 @@ import { setMermaidWorker } from '../../../src/workers/mermaidWorkerClient'
1112
import ThinkingNode from '../components/ThinkingNode.vue'
1213
import { streamContent } from '../const/markdown'
1314
import 'katex/dist/katex.min.css'
14-
import MarkdownCodeBlockNode from '../../../src/components/MarkdownCodeBlockNode'
1515
1616
// 每隔 10 毫秒输出一部分内容
1717
const content = ref<string>('')

pnpm-lock.yaml

Lines changed: 19 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/__snapshots__/e2e.markdown.test.ts.snap

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,6 @@ exports[`e2e markdown parsing (fixtures) > parses fixture code-diff.md > code-di
5454

5555
exports[`e2e markdown parsing (fixtures) > parses fixture escaped-brackets.md > escaped-brackets.md 1`] = `
5656
[
57-
{
58-
"children": undefined,
59-
"firstText": "A line with an escaped bracket: ",
60-
"type": "text",
61-
},
62-
{
63-
"children": undefined,
64-
"firstText": "A line with an escaped bracket: ",
65-
"type": "text",
66-
},
6757
{
6858
"children": [
6959
"text",
@@ -106,11 +96,6 @@ print('ok')
10696

10797
exports[`e2e markdown parsing (fixtures) > parses fixture footnotes.md > footnotes.md 1`] = `
10898
[
109-
{
110-
"children": undefined,
111-
"firstText": "This is a sentence with a footnote",
112-
"type": "text",
113-
},
11499
{
115100
"children": [
116101
"text",
@@ -177,11 +162,6 @@ exports[`e2e markdown parsing (fixtures) > parses fixture image-link.md > image-
177162
"firstText": "Here is an image: ",
178163
"type": "paragraph",
179164
},
180-
{
181-
"children": undefined,
182-
"firstText": "And a link: ",
183-
"type": "text",
184-
},
185165
{
186166
"children": [
187167
"text",
@@ -214,9 +194,8 @@ exports[`e2e markdown parsing (fixtures) > parses fixture math.md > math.md 1`]
214194
{
215195
"children": [
216196
"text",
217-
"math_inline",
218197
],
219-
"firstText": "A line with parentheses that should not break: ",
198+
"firstText": "A line with parentheses that should not break: (not-math)",
220199
"type": "paragraph",
221200
},
222201
]
@@ -315,23 +294,13 @@ exports[`e2e markdown parsing (fixtures) > parses fixture unclosed-fence.md > un
315294

316295
exports[`e2e markdown parsing (fixtures) > parses fixture unmatched-brackets.md > unmatched-brackets.md 1`] = `
317296
[
318-
{
319-
"children": undefined,
320-
"firstText": "This line has an opening bracket ",
321-
"type": "text",
322-
},
323297
{
324298
"children": [
325299
"text",
326300
],
327301
"firstText": "This line has an opening bracket [ without a closing.",
328302
"type": "paragraph",
329303
},
330-
{
331-
"children": undefined,
332-
"firstText": "Another line with a link-like ",
333-
"type": "text",
334-
},
335304
{
336305
"children": [
337306
"text",

test/debug/isMathLike.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { expect, it } from 'vitest'
44
it('recognizes \"\\boldsymbol{...}\" as math-like', () => {
55
expect(isMathLike('\\boldsymbol{\\beta}')).toBe(true)
66
expect(isMathLike('\\(C++\\)')).toBe(false)
7-
expect(isMathLike('\\(W\\)')).toBe(false)
7+
expect(isMathLike('(W)')).toBe(true)
88
expect(isMathLike('\\(f^{(k)}(a)\\)')).toBe(true)
99
expect(isMathLike('\\(W^\perp\\)')).toBe(true)
1010
expect(isMathLike('\\(2025/9/30 21:37:24\\)')).toBe(false)
@@ -13,4 +13,5 @@ it('recognizes \"\\boldsymbol{...}\" as math-like', () => {
1313
expect(isMathLike('served from vue-markdown-icon.1')).toBe(false)
1414
expect(isMathLike('get_time')).toBe(false)
1515
expect(isMathLike('or **matrix of coefficients**')).toBe(false)
16+
expect(isMathLike('Quasi-Streaming')).toBe(false)
1617
})

test/e2e/__snapshots__/node-renderer.e2e.test.ts.snap

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -261,13 +261,7 @@ exports[`markdownRender node e2e coverage > renders footnote nodes 1`] = `
261261
"<div data-v-8dfe8a80="" class="markdown-renderer">
262262
<div data-v-8dfe8a80="">
263263
<div data-v-8dfe8a80="" class="node-spacer" style="height: 0px;" aria-hidden="true"></div>
264-
<div data-v-8dfe8a80="" class="node-slot" data-node-index="0" data-node-type="text">
265-
<div data-v-8dfe8a80="" class="node-content">
266-
<!-- Skip wrapping code_block nodes in transitions to avoid touching Monaco editor internals -->
267-
<transition-stub data-v-8dfe8a80="" name="typewriter" appear="true" persisted="false" css="true"><span data-v-a84b8095="" data-v-8dfe8a80="" class="whitespace-pre-wrap break-words text-node" index-key="markdown-renderer-0" typewriter="true" is-dark="false">A footnote reference</span></transition-stub>
268-
</div>
269-
</div>
270-
<div data-v-8dfe8a80="" class="node-slot" data-node-index="1" data-node-type="paragraph">
264+
<div data-v-8dfe8a80="" class="node-slot" data-node-index="0" data-node-type="paragraph">
271265
<div data-v-8dfe8a80="" class="node-content">
272266
<!-- Skip wrapping code_block nodes in transitions to avoid touching Monaco editor internals -->
273267
<transition-stub data-v-8dfe8a80="" name="typewriter" appear="true" persisted="false" css="true">
@@ -277,7 +271,7 @@ exports[`markdownRender node e2e coverage > renders footnote nodes 1`] = `
277271
</transition-stub>
278272
</div>
279273
</div>
280-
<div data-v-8dfe8a80="" class="node-slot" data-node-index="2" data-node-type="footnote">
274+
<div data-v-8dfe8a80="" class="node-slot" data-node-index="1" data-node-type="footnote">
281275
<div data-v-8dfe8a80="" class="node-content">
282276
<!-- Skip wrapping code_block nodes in transitions to avoid touching Monaco editor internals -->
283277
<transition-stub data-v-8dfe8a80="" name="typewriter" appear="true" persisted="false" css="true">
@@ -436,17 +430,11 @@ exports[`markdownRender node e2e coverage > renders link node 1`] = `
436430
"<div data-v-8dfe8a80="" class="markdown-renderer">
437431
<div data-v-8dfe8a80="">
438432
<div data-v-8dfe8a80="" class="node-spacer" style="height: 0px;" aria-hidden="true"></div>
439-
<div data-v-8dfe8a80="" class="node-slot" data-node-index="0" data-node-type="text">
440-
<div data-v-8dfe8a80="" class="node-content">
441-
<!-- Skip wrapping code_block nodes in transitions to avoid touching Monaco editor internals -->
442-
<transition-stub data-v-8dfe8a80="" name="typewriter" appear="true" persisted="false" css="true"><span data-v-a84b8095="" data-v-8dfe8a80="" class="whitespace-pre-wrap break-words text-node" index-key="markdown-renderer-0" typewriter="true" is-dark="false">Visit </span></transition-stub>
443-
</div>
444-
</div>
445-
<div data-v-8dfe8a80="" class="node-slot" data-node-index="1" data-node-type="paragraph">
433+
<div data-v-8dfe8a80="" class="node-slot" data-node-index="0" data-node-type="paragraph">
446434
<div data-v-8dfe8a80="" class="node-content">
447435
<!-- Skip wrapping code_block nodes in transitions to avoid touching Monaco editor internals -->
448436
<transition-stub data-v-8dfe8a80="" name="typewriter" appear="true" persisted="false" css="true">
449-
<p data-v-93ee5c2d="" data-v-8dfe8a80="" dir="auto" class="paragraph-node" typewriter="true" is-dark="false"><span data-v-93ee5c2d="" class="whitespace-pre-wrap break-words text-node">Visit </span><a data-v-994bfd08="" data-v-93ee5c2d="" class="link-node" href="https://vuejs.org" aria-label="Link: https://vuejs.org" aria-hidden="false" target="_blank" rel="noopener noreferrer" style="--link-color: #0366d6; --underline-height: 2px; --underline-bottom: -3px; --underline-opacity: 0.9; --underline-duration: 0.8s; --underline-timing: linear; --underline-iteration: infinite;"><span data-v-a84b8095="" data-v-994bfd08="" class="whitespace-pre-wrap break-words text-node" index-key="markdown-renderer-1-1-0">Vue</span></a><span data-v-93ee5c2d="" class="whitespace-pre-wrap break-words text-node"> now.</span></p>
437+
<p data-v-93ee5c2d="" data-v-8dfe8a80="" dir="auto" class="paragraph-node" typewriter="true" is-dark="false"><span data-v-93ee5c2d="" class="whitespace-pre-wrap break-words text-node">Visit </span><a data-v-994bfd08="" data-v-93ee5c2d="" class="link-node" href="https://vuejs.org" aria-label="Link: https://vuejs.org" aria-hidden="false" target="_blank" rel="noopener noreferrer" style="--link-color: #0366d6; --underline-height: 2px; --underline-bottom: -3px; --underline-opacity: 0.9; --underline-duration: 0.8s; --underline-timing: linear; --underline-iteration: infinite;"><span data-v-a84b8095="" data-v-994bfd08="" class="whitespace-pre-wrap break-words text-node" index-key="markdown-renderer-0-1-0">Vue</span></a><span data-v-93ee5c2d="" class="whitespace-pre-wrap break-words text-node"> now.</span></p>
450438
</transition-stub>
451439
</div>
452440
</div>
@@ -543,17 +531,11 @@ exports[`markdownRender node e2e coverage > renders reference node 1`] = `
543531
"<div data-v-8dfe8a80="" class="markdown-renderer">
544532
<div data-v-8dfe8a80="">
545533
<div data-v-8dfe8a80="" class="node-spacer" style="height: 0px;" aria-hidden="true"></div>
546-
<div data-v-8dfe8a80="" class="node-slot" data-node-index="0" data-node-type="text">
547-
<div data-v-8dfe8a80="" class="node-content">
548-
<!-- Skip wrapping code_block nodes in transitions to avoid touching Monaco editor internals -->
549-
<transition-stub data-v-8dfe8a80="" name="typewriter" appear="true" persisted="false" css="true"><span data-v-a84b8095="" data-v-8dfe8a80="" class="whitespace-pre-wrap break-words text-node" index-key="markdown-renderer-0" typewriter="true" is-dark="false">Cite research </span></transition-stub>
550-
</div>
551-
</div>
552-
<div data-v-8dfe8a80="" class="node-slot" data-node-index="1" data-node-type="paragraph">
534+
<div data-v-8dfe8a80="" class="node-slot" data-node-index="0" data-node-type="paragraph">
553535
<div data-v-8dfe8a80="" class="node-content">
554536
<!-- Skip wrapping code_block nodes in transitions to avoid touching Monaco editor internals -->
555537
<transition-stub data-v-8dfe8a80="" name="typewriter" appear="true" persisted="false" css="true">
556-
<p data-v-93ee5c2d="" data-v-8dfe8a80="" dir="auto" class="paragraph-node" typewriter="true" is-dark="false"><span data-v-93ee5c2d="" class="whitespace-pre-wrap break-words text-node">Cite research </span><span data-v-93ee5c2d="" class="reference-node cursor-pointer bg-accent text-xs rounded-md px-1.5 mx-0.5 hover:bg-secondary" role="button" tabindex="0" index-key="markdown-renderer-1-1">1</span><span data-v-93ee5c2d="" class="whitespace-pre-wrap break-words text-node"> for details.</span></p>
538+
<p data-v-93ee5c2d="" data-v-8dfe8a80="" dir="auto" class="paragraph-node" typewriter="true" is-dark="false"><span data-v-93ee5c2d="" class="whitespace-pre-wrap break-words text-node">Cite research </span><span data-v-93ee5c2d="" class="reference-node cursor-pointer bg-accent text-xs rounded-md px-1.5 mx-0.5 hover:bg-secondary" role="button" tabindex="0" index-key="markdown-renderer-0-1">1</span><span data-v-93ee5c2d="" class="whitespace-pre-wrap break-words text-node"> for details.</span></p>
557539
</transition-stub>
558540
</div>
559541
</div>

0 commit comments

Comments
 (0)