stream-markdown-parser 0.0.34 → 0.0.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,6 +21,15 @@ This package contains the core markdown parsing logic extracted from `stream-mar
21
21
 
22
22
  > ℹ️ We now build on top of [`markdown-it-ts`](https://www.npmjs.com/package/markdown-it-ts), a TypeScript-first distribution of markdown-it. The API stays the same, but we only rely on its parsing pipeline and ship richer typings for tokens and hooks.
23
23
 
24
+ ## Documentation
25
+
26
+ The full usage guide lives alongside the markstream-vue docs:
27
+
28
+ - English: https://markstream-vue-docs.simonhe.me/guide/api
29
+ - 中文: https://markstream-vue-docs.simonhe.me/zh/guide/api
30
+
31
+ This README highlights the parser-specific APIs; visit the docs for end-to-end integration tutorials (VitePress, workers, Tailwind, troubleshooting, etc.).
32
+
24
33
  ## Installation
25
34
 
26
35
  ```bash
@@ -31,9 +40,47 @@ npm install stream-markdown-parser
31
40
  yarn add stream-markdown-parser
32
41
  ```
33
42
 
43
+ ## Quick API (TL;DR)
44
+
45
+ - `getMarkdown(options)` — create a `markdown-it-ts` instance with built-in plugins (task lists, sub/sup, math helpers, etc.). Also accepts `plugin`, `apply`, and `i18n` options.
46
+ - `registerMarkdownPlugin(plugin)` / `clearRegisteredMarkdownPlugins()` — add or remove global plugins that run for every `getMarkdown()` call (useful for feature flags or tests).
47
+ - `parseMarkdownToStructure(markdown, md, parseOptions)` — convert Markdown into the streaming-friendly AST consumed by `markstream-vue` and other renderers.
48
+ - `processTokens(tokens)` / `parseInlineTokens(children, content)` — low-level helpers if you want to bypass the built-in AST pipeline.
49
+ - `applyMath`, `applyContainers`, `normalizeStandaloneBackslashT`, `findMatchingClose`, etc. — utilities for custom parsing or linting workflows.
50
+
34
51
  ## Usage
35
52
 
36
- ### Basic Example
53
+ ### Streaming-friendly pipeline
54
+
55
+ ```
56
+ Markdown string
57
+ ↓ getMarkdown() → markdown-it-ts instance with plugins
58
+ parseMarkdownToStructure() → AST (ParsedNode[])
59
+ ↓ feed into your renderer (markstream-vue, custom UI, workers)
60
+ ```
61
+
62
+ Reuse the same `md` instance when parsing multiple documents—plugin setup is the heaviest step. When integrating with [`markstream-vue`](../../README.md), pass the AST to `<MarkdownRender :nodes="nodes" />` or supply raw `content` and hand it the same parser options.
63
+
64
+ ### Incremental / streaming example
65
+
66
+ When consuming an AI or SSE stream you can keep appending to a buffer, parse with the same `md` instance, and send the AST to the UI (e.g., `markstream-vue`) on every chunk:
67
+
68
+ ```ts
69
+ import { getMarkdown, parseMarkdownToStructure } from 'stream-markdown-parser'
70
+
71
+ const md = getMarkdown()
72
+ let buffer = ''
73
+
74
+ async function handleChunk(chunk: string) {
75
+ buffer += chunk
76
+ const nodes = parseMarkdownToStructure(buffer, md)
77
+ postMessage({ type: 'markdown:update', nodes })
78
+ }
79
+ ```
80
+
81
+ In the UI layer, render `nodes` with `<MarkdownRender :nodes="nodes" />` to avoid re-parsing. See the [docs usage guide](../../docs/guide/usage.md) for end-to-end wiring.
82
+
83
+ ### Basic example
37
84
 
38
85
  ```typescript
39
86
  import { getMarkdown, parseMarkdownToStructure } from 'stream-markdown-parser'
@@ -114,6 +161,29 @@ const md = getMarkdown('editor-1', {
114
161
  })
115
162
  ```
116
163
 
164
+ ### Extending globally
165
+
166
+ Need to add a plugin everywhere without touching each call site? Use the helper exports:
167
+
168
+ ```ts
169
+ import {
170
+ clearRegisteredMarkdownPlugins,
171
+ registerMarkdownPlugin,
172
+ } from 'stream-markdown-parser'
173
+
174
+ registerMarkdownPlugin(myPlugin)
175
+
176
+ const md = getMarkdown()
177
+ // md now has `myPlugin` enabled in addition to anything passed via options
178
+
179
+ // For tests or teardown flows:
180
+ clearRegisteredMarkdownPlugins()
181
+ ```
182
+
183
+ - `plugin` option → array of `md.use` invocations scoped to a single `getMarkdown` call.
184
+ - `apply` option → imperatively mutate the instance (`md.inline.ruler.before(...)`). Wrap in try/catch if you need to surface errors differently; the helper logs to console to preserve legacy behaviour.
185
+ - `registerMarkdownPlugin` → global singleton registry (handy in SSR or worker contexts where you want feature flags to apply everywhere).
186
+
117
187
  ## API
118
188
 
119
189
  ### Main Functions
@@ -175,6 +245,34 @@ interface MathOptions {
175
245
  }
176
246
  ```
177
247
 
248
+ ### Parse hooks (fine-grained transforms)
249
+
250
+ Both `parseMarkdownToStructure()` and `<MarkdownRender :parse-options>` accept the same hook signature:
251
+
252
+ ```ts
253
+ interface ParseOptions {
254
+ preTransformTokens?: (tokens: Token[]) => Token[]
255
+ postTransformTokens?: (tokens: Token[]) => Token[]
256
+ postTransformNodes?: (nodes: ParsedNode[]) => ParsedNode[]
257
+ }
258
+ ```
259
+
260
+ Example — flag AI “thinking” blocks:
261
+
262
+ ```ts
263
+ const parseOptions = {
264
+ postTransformNodes(nodes) {
265
+ return nodes.map(node =>
266
+ node.type === 'html_block' && /<thinking>/.test(node.value)
267
+ ? { ...node, meta: { type: 'thinking' } }
268
+ : node,
269
+ )
270
+ },
271
+ }
272
+ ```
273
+
274
+ Use the metadata in your renderer to show custom UI without mangling the original Markdown.
275
+
178
276
  ### Utility Functions
179
277
 
180
278
  #### `isMathLike(content)`
@@ -198,6 +296,14 @@ Find the matching closing delimiter in a string, handling nested pairs.
198
296
 
199
297
  **Returns:** `number` - Index of matching close, or -1 if not found
200
298
 
299
+ ## Tips & troubleshooting
300
+
301
+ - **Reuse parser instances**: cache `getMarkdown()` results per worker/request to avoid re-registering plugins.
302
+ - **Server-side parsing**: run `parseMarkdownToStructure` on the server, ship the AST to the client, and hydrate with `markstream-vue` for deterministic output.
303
+ - **Custom HTML widgets**: pre-extract `<MyWidget>` blocks before parsing (replace with placeholders) and reinject them during rendering instead of mutating `html_block` nodes post-parse.
304
+ - **Styling**: when piping nodes into `markstream-vue`, follow the docs [CSS checklist](../../docs/guide/troubleshooting.md#css-looks-wrong-start-here) so Tailwind/UnoCSS don’t override library styles.
305
+ - **Error handling**: the `apply` hook swallows exceptions to maintain backwards compatibility. If you want strict mode, wrap your mutators before passing them in and rethrow/log as needed.
306
+
201
307
  #### `parseFenceToken(token)`
202
308
 
203
309
  Parse a code fence token into a CodeBlockNode.
@@ -217,6 +323,26 @@ Normalize backslash-t sequences in math content.
217
323
 
218
324
  **Returns:** `string`
219
325
 
326
+ ### Lower-level helpers
327
+
328
+ If you need full control over how tokens are transformed, you can import the primitive builders directly:
329
+
330
+ ```ts
331
+ import type { MarkdownToken } from 'stream-markdown-parser'
332
+ import {
333
+
334
+ parseInlineTokens,
335
+ processTokens
336
+ } from 'stream-markdown-parser'
337
+
338
+ const tokens: MarkdownToken[] = md.parse(markdown, {})
339
+ const nodes = processTokens(tokens)
340
+ // or operate at inline granularity:
341
+ const inlineNodes = parseInlineTokens(tokens[0].children ?? [], tokens[0].content ?? '')
342
+ ```
343
+
344
+ `processTokens` is what `parseMarkdownToStructure` uses internally, so you can remix the AST pipeline without reimplementing the Markdown-it loop.
345
+
220
346
  ### Plugin Functions
221
347
 
222
348
  #### `applyMath(md, options?)`
@@ -303,6 +429,41 @@ While this package is framework-agnostic, it's designed to work seamlessly with:
303
429
  - ✅ **Vanilla JS** - Direct HTML rendering
304
430
  - ✅ **Any framework** - Parse to AST and render as needed
305
431
 
432
+ ### Example — dedicated worker feeding markstream-vue
433
+
434
+ Offload parsing to a Web Worker while the UI renders via `markstream-vue`:
435
+
436
+ ```ts
437
+ // worker.ts
438
+ import { getMarkdown, parseMarkdownToStructure } from 'stream-markdown-parser'
439
+
440
+ const md = getMarkdown()
441
+ let buffer = ''
442
+
443
+ globalThis.addEventListener('message', (event) => {
444
+ if (event.data.type === 'chunk') {
445
+ buffer += event.data.value
446
+ const nodes = parseMarkdownToStructure(buffer, md)
447
+ globalThis.postMessage({ type: 'update', nodes })
448
+ }
449
+ })
450
+ ```
451
+
452
+ ```ts
453
+ // ui.ts
454
+ const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' })
455
+ worker.addEventListener('message', (event) => {
456
+ if (event.data.type === 'update')
457
+ nodes.value = event.data.nodes
458
+ })
459
+ ```
460
+
461
+ ```vue
462
+ <MarkdownRender :nodes="nodes" />
463
+ ```
464
+
465
+ This pattern keeps Markdown-it work off the main thread and lets you reuse the same AST in any framework.
466
+
306
467
  ## Migration from `stream-markdown-parser`
307
468
 
308
469
  If you're migrating from using the markdown utils in `stream-markdown-parser`:
package/README.zh-CN.md CHANGED
@@ -21,6 +21,15 @@
21
21
 
22
22
  > ℹ️ 自当前版本起我们基于 [`markdown-it-ts`](https://www.npmjs.com/package/markdown-it-ts)(一个 TypeScript 优先的 markdown-it 发行版)进行构建。API 与 markdown-it 保持一致,但内部仅依赖其解析流程,并提供更丰富的 token 类型定义。
23
23
 
24
+ ## 文档
25
+
26
+ 完整的使用说明与集成教程见 markstream-vue 文档站:
27
+
28
+ - English: https://markstream-vue-docs.simonhe.me/guide/api
29
+ - 中文: https://markstream-vue-docs.simonhe.me/zh/guide/api
30
+
31
+ 本 README 聚焦解析器 API;如需 VitePress/Vite/Nuxt 集成、Worker 流式解析、Tailwind/UnoCSS 配置等指南,请查阅上述文档。
32
+
24
33
  ## 安装
25
34
 
26
35
  ```bash
@@ -31,8 +40,46 @@ npm install stream-markdown-parser
31
40
  yarn add stream-markdown-parser
32
41
  ```
33
42
 
43
+ ## 快速 API 速览
44
+
45
+ - `getMarkdown(options)` — 返回一个预配置的 `markdown-it-ts` 实例;支持 `plugin`、`apply`、`i18n` 等选项(内置任务列表、上下标、数学等插件)。
46
+ - `registerMarkdownPlugin(plugin)` / `clearRegisteredMarkdownPlugins()` — 全局注册/清除插件,在所有 `getMarkdown()` 调用中生效(适合特性开关或测试环境)。
47
+ - `parseMarkdownToStructure(markdown, md, parseOptions)` — 将 Markdown 转换为可供 `markstream-vue` 等渲染器使用的 AST。
48
+ - `processTokens(tokens)` / `parseInlineTokens(children, content)` — 更底层的 token → 节点工具,方便自定义管线。
49
+ - `applyMath`、`applyContainers`、`normalizeStandaloneBackslashT`、`findMatchingClose` 等 — 用于构建自定义解析、lint 或内容清洗流程。
50
+
34
51
  ## 使用
35
52
 
53
+ ### 流式解析流程
54
+
55
+ ```
56
+ Markdown 字符串
57
+ ↓ getMarkdown() → 带插件的 markdown-it-ts 实例
58
+ parseMarkdownToStructure() → AST (ParsedNode[])
59
+ ↓ 交给你的渲染器(markstream-vue、自定义 UI、Worker 等)
60
+ ```
61
+
62
+ 多次解析时复用同一个 `md` 实例可以避免重复注册插件。与 [`markstream-vue`](../../README.zh-CN.md) 一起使用时,你可以把 AST 传给 `<MarkdownRender :nodes="nodes" />`,或仅传入原始 `content` 并共享同一套解析配置。
63
+
64
+ ### 增量 / 流式示例
65
+
66
+ 处理 AI/SSE 流时,可以复用同一个 `md` 实例不停地对累积缓冲区解析,并把 AST 推送给 UI(例如 `markstream-vue`):
67
+
68
+ ```ts
69
+ import { getMarkdown, parseMarkdownToStructure } from 'stream-markdown-parser'
70
+
71
+ const md = getMarkdown()
72
+ let buffer = ''
73
+
74
+ async function handleChunk(chunk: string) {
75
+ buffer += chunk
76
+ const nodes = parseMarkdownToStructure(buffer, md)
77
+ postMessage({ type: 'markdown:update', nodes })
78
+ }
79
+ ```
80
+
81
+ 在前端通过 `<MarkdownRender :nodes="nodes" />` 渲染即可避免重复解析。具体串联示例见[文档用法指南](../../docs/zh/guide/usage.md)。
82
+
36
83
  ### 基础示例
37
84
 
38
85
  ```typescript
@@ -113,6 +160,29 @@ const md = getMarkdown('editor-1', {
113
160
  })
114
161
  ```
115
162
 
163
+ ### 全局扩展
164
+
165
+ 想在所有 `getMarkdown()` 实例上启用同一个插件,而无需修改调用点?可使用内置 helper:
166
+
167
+ ```ts
168
+ import {
169
+ clearRegisteredMarkdownPlugins,
170
+ registerMarkdownPlugin,
171
+ } from 'stream-markdown-parser'
172
+
173
+ registerMarkdownPlugin(myPlugin)
174
+
175
+ const md = getMarkdown()
176
+ // 现在 md 会自动包含 myPlugin
177
+
178
+ // 测试或清理阶段:
179
+ clearRegisteredMarkdownPlugins()
180
+ ```
181
+
182
+ - `plugin` 选项 → 针对单次 `getMarkdown` 调用传入 `md.use(...)`。
183
+ - `apply` 选项 → 直接操作实例(如 `md.inline.ruler.before(...)`)。如果需要严格模式,可自行包裹 try/catch;默认会打印错误保持兼容。
184
+ - `registerMarkdownPlugin` → 全局注册表,适用于 SSR / Worker 等场景统一开启功能。
185
+
116
186
  ## API
117
187
 
118
188
  ### 主要函数
@@ -174,6 +244,34 @@ interface MathOptions {
174
244
  }
175
245
  ```
176
246
 
247
+ ### 解析钩子(精细化变换)
248
+
249
+ `parseMarkdownToStructure()` 与 `<MarkdownRender :parse-options>` 可使用相同的钩子:
250
+
251
+ ```ts
252
+ interface ParseOptions {
253
+ preTransformTokens?: (tokens: Token[]) => Token[]
254
+ postTransformTokens?: (tokens: Token[]) => Token[]
255
+ postTransformNodes?: (nodes: ParsedNode[]) => ParsedNode[]
256
+ }
257
+ ```
258
+
259
+ 示例 —— 标记 AI “思考” 块:
260
+
261
+ ```ts
262
+ const parseOptions = {
263
+ postTransformNodes(nodes) {
264
+ return nodes.map(node =>
265
+ node.type === 'html_block' && /<thinking>/.test(node.value)
266
+ ? { ...node, meta: { type: 'thinking' } }
267
+ : node,
268
+ )
269
+ },
270
+ }
271
+ ```
272
+
273
+ 在渲染器中读取 `node.meta` 即可渲染自定义 UI,而无需直接修改 Markdown 文本。
274
+
177
275
  ### 工具函数
178
276
 
179
277
  #### `isMathLike(content)`
@@ -197,6 +295,14 @@ interface MathOptions {
197
295
 
198
296
  **返回值:** `number` - 匹配闭合的索引,如果未找到则返回 -1
199
297
 
298
+ ## 使用建议与排障
299
+
300
+ - **复用解析实例**:缓存 `getMarkdown()` 的结果,避免重复注册插件。
301
+ - **服务端解析**:在服务端运行 `parseMarkdownToStructure` 后把 AST 下发给客户端,配合 `markstream-vue` 实现确定性输出。
302
+ - **自定义 HTML 组件**:在解析前先把 `<MyWidget>` 这类片段替换为占位符,渲染时再注入,避免在 `html_block` 上进行脆弱的字符串操作。
303
+ - **样式提示**:如果将节点交给 `markstream-vue`,务必按照文档的 [CSS 排查清单](../../docs/zh/guide/troubleshooting.md#css-looks-wrong-start-here) 调整 reset / layer,防止 Tailwind/UnoCSS 覆盖样式。
304
+ - **错误处理**:`apply` 钩子内部默认捕获异常后打印日志,如需在 CI/生产中抛出错误,可在传入前自行封装并 rethrow。
305
+
200
306
  #### `parseFenceToken(token)`
201
307
 
202
308
  将代码围栏 token 解析为 CodeBlockNode。
@@ -216,6 +322,26 @@ interface MathOptions {
216
322
 
217
323
  **返回值:** `string`
218
324
 
325
+ ### 低阶辅助函数
326
+
327
+ 需要更细粒度地控制 token → AST 流程时,可直接使用以下导出:
328
+
329
+ ```ts
330
+ import type { MarkdownToken } from 'stream-markdown-parser'
331
+ import {
332
+
333
+ parseInlineTokens,
334
+ processTokens
335
+ } from 'stream-markdown-parser'
336
+
337
+ const tokens: MarkdownToken[] = md.parse(markdown, {})
338
+ const nodes = processTokens(tokens)
339
+ // 或仅解析内联内容:
340
+ const inlineNodes = parseInlineTokens(tokens[0].children ?? [], tokens[0].content ?? '')
341
+ ```
342
+
343
+ `processTokens` 即 `parseMarkdownToStructure` 内部使用的同一个转换器,可在自定义管线中复用,避免重复实现 Markdown-it 遍历。
344
+
219
345
  ### 插件函数
220
346
 
221
347
  #### `applyMath(md, options?)`
package/dist/index.js CHANGED
@@ -601,7 +601,7 @@ var require_markdown_it_task_checkbox = /* @__PURE__ */ __commonJS({ "../../node
601
601
  }) });
602
602
 
603
603
  //#endregion
604
- //#region ../../node_modules/.pnpm/markdown-it-ts@0.0.2-beta.8/node_modules/markdown-it-ts/dist/chunk-Bp6m_JJh.js
604
+ //#region ../../node_modules/.pnpm/markdown-it-ts@0.0.2-beta.9/node_modules/markdown-it-ts/dist/chunk-Bp6m_JJh.js
605
605
  var import_markdown_it_task_checkbox = /* @__PURE__ */ __toESM(require_markdown_it_task_checkbox(), 1);
606
606
  var __defProp = Object.defineProperty;
607
607
  var __export = (all) => {
@@ -2187,7 +2187,7 @@ var require_punycode = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/pu
2187
2187
  }) });
2188
2188
 
2189
2189
  //#endregion
2190
- //#region ../../node_modules/.pnpm/markdown-it-ts@0.0.2-beta.8/node_modules/markdown-it-ts/dist/index.js
2190
+ //#region ../../node_modules/.pnpm/markdown-it-ts@0.0.2-beta.9/node_modules/markdown-it-ts/dist/index.js
2191
2191
  var import_punycode = /* @__PURE__ */ __toESM(require_punycode(), 1);
2192
2192
  var utils_exports = /* @__PURE__ */ __export({
2193
2193
  arrayReplaceAt: () => arrayReplaceAt,
@@ -5708,12 +5708,10 @@ var Renderer = class {
5708
5708
  rules;
5709
5709
  baseOptions;
5710
5710
  normalizedBase;
5711
- bufferPool;
5712
5711
  constructor(options = {}) {
5713
5712
  this.baseOptions = { ...options };
5714
5713
  this.normalizedBase = this.buildNormalizedBase();
5715
5714
  this.rules = { ...defaultRules };
5716
- this.bufferPool = [];
5717
5715
  }
5718
5716
  set(options) {
5719
5717
  this.baseOptions = {
@@ -5723,65 +5721,54 @@ var Renderer = class {
5723
5721
  this.normalizedBase = this.buildNormalizedBase();
5724
5722
  return this;
5725
5723
  }
5726
- render(tokens, options = {}, env = {}) {
5724
+ render(tokens, options, env) {
5727
5725
  if (!Array.isArray(tokens)) throw new TypeError("render expects token array as first argument");
5728
5726
  const merged = this.mergeOptions(options);
5729
- const chunks = this.acquireBuffer();
5727
+ const envRef = env ?? {};
5728
+ let output = "";
5730
5729
  for (let i = 0; i < tokens.length; i++) {
5731
5730
  const token = tokens[i];
5732
5731
  if (token.type === "inline") {
5733
- this.pushInlineTokens(token.children || [], merged, env, chunks);
5732
+ output += this.renderInlineTokens(token.children || [], merged, envRef);
5734
5733
  continue;
5735
5734
  }
5736
5735
  const rule = this.rules[token.type];
5737
- if (rule) chunks.push(ensureSyncResult(rule(tokens, i, merged, env, this), token.type));
5738
- else chunks.push(this.renderToken(tokens, i, merged));
5736
+ if (rule) output += ensureSyncResult(rule(tokens, i, merged, envRef, this), token.type);
5737
+ else output += this.renderToken(tokens, i, merged);
5739
5738
  }
5740
- const output = chunks.join("");
5741
- this.releaseBuffer(chunks);
5742
5739
  return output;
5743
5740
  }
5744
- async renderAsync(tokens, options = {}, env = {}) {
5741
+ async renderAsync(tokens, options, env) {
5745
5742
  if (!Array.isArray(tokens)) throw new TypeError("render expects token array as first argument");
5746
5743
  const merged = this.mergeOptions(options);
5747
- const chunks = this.acquireBuffer();
5744
+ const envRef = env ?? {};
5745
+ let output = "";
5748
5746
  for (let i = 0; i < tokens.length; i++) {
5749
5747
  const token = tokens[i];
5750
5748
  if (token.type === "inline") {
5751
- await this.pushInlineTokensAsync(token.children || [], merged, env, chunks);
5749
+ output += await this.renderInlineTokensAsync(token.children || [], merged, envRef);
5752
5750
  continue;
5753
5751
  }
5754
5752
  const rule = this.rules[token.type];
5755
- if (rule) chunks.push(await resolveResult(rule(tokens, i, merged, env, this)));
5756
- else chunks.push(this.renderToken(tokens, i, merged));
5753
+ if (rule) output += await resolveResult(rule(tokens, i, merged, envRef, this));
5754
+ else output += this.renderToken(tokens, i, merged);
5757
5755
  }
5758
- const output = chunks.join("");
5759
- this.releaseBuffer(chunks);
5760
5756
  return output;
5761
5757
  }
5762
- renderInline(tokens, options = {}, env = {}) {
5758
+ renderInline(tokens, options, env) {
5763
5759
  const merged = this.mergeOptions(options);
5764
- const chunks = this.acquireBuffer();
5765
- this.pushInlineTokens(tokens, merged, env, chunks);
5766
- const output = chunks.join("");
5767
- this.releaseBuffer(chunks);
5768
- return output;
5760
+ const envRef = env ?? {};
5761
+ return this.renderInlineTokens(tokens, merged, envRef);
5769
5762
  }
5770
- async renderInlineAsync(tokens, options = {}, env = {}) {
5763
+ async renderInlineAsync(tokens, options, env) {
5771
5764
  const merged = this.mergeOptions(options);
5772
- const chunks = this.acquireBuffer();
5773
- await this.pushInlineTokensAsync(tokens, merged, env, chunks);
5774
- const output = chunks.join("");
5775
- this.releaseBuffer(chunks);
5776
- return output;
5765
+ const envRef = env ?? {};
5766
+ return this.renderInlineTokensAsync(tokens, merged, envRef);
5777
5767
  }
5778
- renderInlineAsText(tokens, options = {}, env = {}) {
5768
+ renderInlineAsText(tokens, options, env) {
5779
5769
  const merged = this.mergeOptions(options);
5780
- const chunks = this.acquireBuffer();
5781
- this.renderInlineAsTextInternal(tokens, merged, env, chunks);
5782
- const output = chunks.join("");
5783
- this.releaseBuffer(chunks);
5784
- return output;
5770
+ const envRef = env ?? {};
5771
+ return this.renderInlineAsTextInternal(tokens, merged, envRef);
5785
5772
  }
5786
5773
  renderAttrs(token) {
5787
5774
  if (!token.attrs || token.attrs.length === 0) return "";
@@ -5841,56 +5828,53 @@ var Renderer = class {
5841
5828
  ...this.baseOptions
5842
5829
  });
5843
5830
  }
5844
- pushInlineTokens(tokens, options, env, buffer) {
5831
+ renderInlineTokens(tokens, options, env) {
5832
+ if (!tokens || tokens.length === 0) return "";
5833
+ let output = "";
5845
5834
  for (let i = 0; i < tokens.length; i++) {
5846
5835
  const token = tokens[i];
5847
5836
  const rule = this.rules[token.type];
5848
- if (rule) buffer.push(ensureSyncResult(rule(tokens, i, options, env, this), token.type));
5849
- else buffer.push(this.renderToken(tokens, i, options));
5837
+ if (rule) output += ensureSyncResult(rule(tokens, i, options, env, this), token.type);
5838
+ else output += this.renderToken(tokens, i, options);
5850
5839
  }
5840
+ return output;
5851
5841
  }
5852
- async pushInlineTokensAsync(tokens, options, env, buffer) {
5842
+ async renderInlineTokensAsync(tokens, options, env) {
5843
+ if (!tokens || tokens.length === 0) return "";
5844
+ let output = "";
5853
5845
  for (let i = 0; i < tokens.length; i++) {
5854
5846
  const token = tokens[i];
5855
5847
  const rule = this.rules[token.type];
5856
- if (rule) buffer.push(await resolveResult(rule(tokens, i, options, env, this)));
5857
- else buffer.push(this.renderToken(tokens, i, options));
5848
+ if (rule) output += await resolveResult(rule(tokens, i, options, env, this));
5849
+ else output += this.renderToken(tokens, i, options);
5858
5850
  }
5851
+ return output;
5859
5852
  }
5860
- renderInlineAsTextInternal(tokens, options, env, buffer) {
5853
+ renderInlineAsTextInternal(tokens, options, env) {
5854
+ if (!tokens || tokens.length === 0) return "";
5855
+ let output = "";
5861
5856
  for (let i = 0; i < tokens.length; i++) {
5862
5857
  const token = tokens[i];
5863
5858
  switch (token.type) {
5864
5859
  case "text":
5865
5860
  case "text_special":
5866
- buffer.push(token.content);
5861
+ output += token.content;
5867
5862
  break;
5868
5863
  case "image":
5869
- this.renderInlineAsTextInternal(token.children || [], options, env, buffer);
5864
+ output += this.renderInlineAsTextInternal(token.children || [], options, env);
5870
5865
  break;
5871
5866
  case "html_inline":
5872
5867
  case "html_block":
5873
- buffer.push(token.content);
5868
+ output += token.content;
5874
5869
  break;
5875
5870
  case "softbreak":
5876
5871
  case "hardbreak":
5877
- buffer.push("\n");
5872
+ output += "\n";
5878
5873
  break;
5879
5874
  default: break;
5880
5875
  }
5881
5876
  }
5882
- }
5883
- acquireBuffer() {
5884
- const buf = this.bufferPool.pop();
5885
- if (buf) {
5886
- buf.length = 0;
5887
- return buf;
5888
- }
5889
- return [];
5890
- }
5891
- releaseBuffer(buffer) {
5892
- buffer.length = 0;
5893
- this.bufferPool.push(buffer);
5877
+ return output;
5894
5878
  }
5895
5879
  };
5896
5880
  var renderer_default = Renderer;
@@ -7273,7 +7257,6 @@ function fixLinkToken(tokens) {
7273
7257
  i -= replacerTokens.length - 1;
7274
7258
  continue;
7275
7259
  } else if (curToken.content?.startsWith("](") && tokens[i - 1].type === "strong_close" && tokens[i - 4]?.type === "text" && tokens[i - 4]?.content?.includes("**[")) {
7276
- i++;
7277
7260
  const replacerTokens = [];
7278
7261
  const beforeText = tokens[i - 4].content.split("**[")[0];
7279
7262
  if (beforeText) replacerTokens.push(textToken(beforeText));
@@ -8047,7 +8030,7 @@ function applyMath(md, mathOpts) {
8047
8030
  }
8048
8031
  foundAny = true;
8049
8032
  if (!silent) {
8050
- const before = src.slice(0, index);
8033
+ const before = src.slice(s.pos - s.pending.length, index);
8051
8034
  let toPushBefore = src.slice(0, searchPos) ? src.slice(preMathPos, index) : before;
8052
8035
  const isStrongPrefix = countUnescapedStrong(toPushBefore) % 2 === 1;
8053
8036
  if (index !== s.pos && isStrongPrefix) toPushBefore = s.pending + src.slice(s.pos, index);
@@ -8453,6 +8436,10 @@ function parseHtmlInlineCodeToken(token, tokens, i) {
8453
8436
  const m = html.match(/>([\s\S]*?)<\s*\/\s*[\w-]+>/);
8454
8437
  return m ? m[1] : "";
8455
8438
  }
8439
+ if (tag === "br") return [{
8440
+ type: "hardbreak",
8441
+ raw: code$1
8442
+ }, i + 1];
8456
8443
  if (tag === "a") {
8457
8444
  let loading = false;
8458
8445
  if (!nextToken || nextToken?.type === "text" && (!nnextToken || nnextToken.type !== "html_inline") || !nextToken) loading = true;
@@ -8606,11 +8593,13 @@ function parseLinkToken(tokens, startIndex, options) {
8606
8593
  //#endregion
8607
8594
  //#region src/parser/inline-parsers/math-inline-parser.ts
8608
8595
  function parseMathInlineToken(token) {
8596
+ const content = token.content ?? "";
8597
+ const raw = token.raw === "$$" ? `$${content}$` : token.raw || "";
8609
8598
  return {
8610
8599
  type: "math_inline",
8611
- content: String(token.content ?? ""),
8600
+ content,
8612
8601
  loading: !!token.loading,
8613
- raw: token.raw
8602
+ raw
8614
8603
  };
8615
8604
  }
8616
8605
 
@@ -9726,11 +9715,13 @@ function parseHtmlBlock(token) {
9726
9715
  //#endregion
9727
9716
  //#region src/parser/node-parsers/math-block-parser.ts
9728
9717
  function parseMathBlock(token) {
9718
+ const content = String(token.content ?? "");
9719
+ const raw = token.raw === "$$" ? `$$${content}$$` : String(token.raw ?? "");
9729
9720
  return {
9730
9721
  type: "math_block",
9731
- content: String(token.content ?? ""),
9722
+ content,
9732
9723
  loading: !!token.loading,
9733
- raw: String(token.raw ?? "")
9724
+ raw
9734
9725
  };
9735
9726
  }
9736
9727