svelte-streamdown 2.2.7 → 2.3.0

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.
Files changed (38) hide show
  1. package/README.md +47 -47
  2. package/dist/AnimatedText.svelte +6 -8
  3. package/dist/Block.svelte +2 -2
  4. package/dist/Elements/Alert.svelte +19 -18
  5. package/dist/Elements/Code.svelte +20 -17
  6. package/dist/Elements/Image.svelte +22 -20
  7. package/dist/Elements/Link.svelte +1 -1
  8. package/dist/Elements/Math.svelte +4 -7
  9. package/dist/Elements/Mermaid.svelte +76 -68
  10. package/dist/Streamdown.svelte +4 -0
  11. package/dist/context.svelte.d.ts +16 -3
  12. package/dist/context.svelte.js +1 -3
  13. package/dist/marked/index.d.ts +4 -2
  14. package/dist/marked/index.js +10 -6
  15. package/dist/marked/marked-alert.d.ts +1 -1
  16. package/dist/marked/marked-alert.js +14 -5
  17. package/dist/marked/marked-br.d.ts +12 -0
  18. package/dist/marked/marked-br.js +21 -0
  19. package/dist/marked/marked-footnotes.d.ts +1 -1
  20. package/dist/marked/marked-footnotes.js +1 -1
  21. package/dist/marked/marked-hr.d.ts +12 -0
  22. package/dist/marked/marked-hr.js +24 -0
  23. package/dist/marked/marked-list.d.ts +2 -1
  24. package/dist/marked/marked-list.js +9 -3
  25. package/dist/marked/marked-math.d.ts +7 -21
  26. package/dist/marked/marked-math.js +38 -17
  27. package/dist/marked/marked-subsup.d.ts +1 -1
  28. package/dist/marked/marked-subsup.js +1 -1
  29. package/dist/marked/marked-table.d.ts +1 -0
  30. package/dist/marked/marked-table.js +11 -4
  31. package/dist/theme.d.ts +9 -1
  32. package/dist/theme.js +2 -0
  33. package/dist/utils/hightlighter.svelte.d.ts +15 -13
  34. package/dist/utils/hightlighter.svelte.js +101 -57
  35. package/dist/utils/parse-incomplete-markdown.js +352 -157
  36. package/package.json +4 -2
  37. package/dist/Elements/Table.svelte +0 -30
  38. package/dist/Elements/Table.svelte.d.ts +0 -9
@@ -14,7 +14,6 @@
14
14
  token: Tokens.Code;
15
15
  } = $props();
16
16
 
17
- const code = $derived(token.text);
18
17
  let mermaid = $state<any>(null);
19
18
  onMount(async () => {
20
19
  mermaid = (await import('mermaid')).default;
@@ -204,7 +203,7 @@
204
203
  {#if mermaid}
205
204
  <div
206
205
  class={streamdown.theme.mermaid.base}
207
- {@attach (node) => renderMermaid(code, node)}
206
+ {@attach (node) => renderMermaid(token.text, node)}
208
207
  {@attach insider.attach}
209
208
  data-expanded={'false'}
210
209
  >
@@ -216,25 +215,7 @@
216
215
  onclick={() => panzoom.zoomToFit()}
217
216
  data-panzoom-ignore
218
217
  >
219
- <svg
220
- class={streamdown.theme.mermaid.icon}
221
- xmlns="http://www.w3.org/2000/svg"
222
- viewBox="0 0 24 24"
223
- fill="none"
224
- stroke="currentColor"
225
- stroke-width="2"
226
- stroke-linecap="round"
227
- stroke-linejoin="round"
228
- ><path d="M3 7V5a2 2 0 0 1 2-2h2" /><path d="M17 3h2a2 2 0 0 1 2 2v2" /><path
229
- d="M21 17v2a2 2 0 0 1-2 2h-2"
230
- /><path d="M7 21H5a2 2 0 0 1-2-2v-2" /><rect
231
- width="10"
232
- height="8"
233
- x="7"
234
- y="8"
235
- rx="1"
236
- /></svg
237
- >
218
+ {@render (streamdown.icons?.fitView || fitViewIcon)()}
238
219
  </button>
239
220
  <button
240
221
  class={streamdown.theme.mermaid.button}
@@ -242,66 +223,23 @@
242
223
  onclick={() => panzoom.zoomIn()}
243
224
  data-panzoom-ignore
244
225
  >
245
- <svg
246
- class={streamdown.theme.mermaid.icon}
247
- xmlns="http://www.w3.org/2000/svg"
248
- viewBox="0 0 24 24"
249
- fill="none"
250
- stroke="currentColor"
251
- stroke-width="2"
252
- stroke-linecap="round"
253
- stroke-linejoin="round"
254
- ><circle cx="11" cy="11" r="8" /><line x1="21" x2="16.65" y1="21" y2="16.65" /><line
255
- x1="11"
256
- x2="11"
257
- y1="8"
258
- y2="14"
259
- /><line x1="8" x2="14" y1="11" y2="11" /></svg
260
- >
226
+ {@render (streamdown.icons?.zoomIn || zoomInIcon)()}
261
227
  </button>
262
228
  <button
263
229
  class={streamdown.theme.mermaid.button}
264
230
  aria-label="Zoom out"
265
231
  onclick={() => panzoom.zoomOut()}
266
232
  data-panzoom-ignore
267
- ><svg
268
- class={streamdown.theme.mermaid.icon}
269
- xmlns="http://www.w3.org/2000/svg"
270
- viewBox="0 0 24 24"
271
- fill="none"
272
- stroke="currentColor"
273
- stroke-width="2"
274
- stroke-linecap="round"
275
- stroke-linejoin="round"
276
- ><circle cx="11" cy="11" r="8" /><line x1="21" x2="16.65" y1="21" y2="16.65" /><line
277
- x1="8"
278
- x2="14"
279
- y1="11"
280
- y2="11"
281
- /></svg
282
- ></button
283
233
  >
234
+ {@render (streamdown.icons?.zoomOut || zoomOutIcon)()}
235
+ </button>
284
236
  <button
285
237
  class={streamdown.theme.mermaid.button}
286
238
  aria-label="Toggle expand"
287
239
  onclick={() => panzoom.toggleExpand()}
288
240
  data-panzoom-ignore
289
241
  >
290
- <svg
291
- class={streamdown.theme.mermaid.icon}
292
- xmlns="http://www.w3.org/2000/svg"
293
- viewBox="0 0 24 24"
294
- fill="none"
295
- stroke="currentColor"
296
- stroke-width="2"
297
- stroke-linecap="round"
298
- stroke-linejoin="round"
299
- ><path d="m15 15 6 6" /><path d="m15 9 6-6" /><path d="M21 16v5h-5" /><path
300
- d="M21 8V3h-5"
301
- /><path d="M3 16v5h5" /><path d="m3 21 6-6" /><path d="M3 8V3h5" /><path
302
- d="M9 9 3 3"
303
- /></svg
304
- >
242
+ {@render (streamdown.icons?.fullscreen || fullscreenIcon)()}
305
243
  </button>
306
244
  </div>
307
245
  {/if}
@@ -311,6 +249,76 @@
311
249
  <div class={streamdown.theme.mermaid.base}></div>
312
250
  {/if}
313
251
 
252
+ {#snippet zoomInIcon()}
253
+ <svg
254
+ class={streamdown.theme.mermaid.icon}
255
+ xmlns="http://www.w3.org/2000/svg"
256
+ viewBox="0 0 24 24"
257
+ fill="none"
258
+ stroke="currentColor"
259
+ stroke-width="2"
260
+ stroke-linecap="round"
261
+ stroke-linejoin="round"
262
+ >
263
+ <circle cx="11" cy="11" r="8" /><line x1="21" x2="16.65" y1="21" y2="16.65" />
264
+ <line x1="11" x2="11" y1="8" y2="14" />
265
+ <line x1="8" x2="14" y1="11" y2="11" />
266
+ </svg>
267
+ {/snippet}
268
+
269
+ {#snippet zoomOutIcon()}
270
+ <svg
271
+ class={streamdown.theme.mermaid.icon}
272
+ xmlns="http://www.w3.org/2000/svg"
273
+ viewBox="0 0 24 24"
274
+ fill="none"
275
+ stroke="currentColor"
276
+ stroke-width="2"
277
+ stroke-linecap="round"
278
+ stroke-linejoin="round"
279
+ >
280
+ <circle cx="11" cy="11" r="8" /><line x1="21" x2="16.65" y1="21" y2="16.65" /><line
281
+ x1="8"
282
+ x2="14"
283
+ y1="11"
284
+ y2="11"
285
+ />
286
+ </svg>
287
+ {/snippet}
288
+
289
+ {#snippet fitViewIcon()}
290
+ <svg
291
+ class={streamdown.theme.mermaid.icon}
292
+ xmlns="http://www.w3.org/2000/svg"
293
+ viewBox="0 0 24 24"
294
+ fill="none"
295
+ stroke="currentColor"
296
+ stroke-width="2"
297
+ stroke-linecap="round"
298
+ stroke-linejoin="round"
299
+ >
300
+ <path d="M3 7V5a2 2 0 0 1 2-2h2" />
301
+ <path d="M17 3h2a2 2 0 0 1 2 2v2" /><path d="M21 17v2a2 2 0 0 1-2 2h-2" />
302
+ <path d="M7 21H5a2 2 0 0 1-2-2v-2" /><rect width="10" height="8" x="7" y="8" rx="1" />
303
+ </svg>
304
+ {/snippet}
305
+
306
+ {#snippet fullscreenIcon()}
307
+ <svg
308
+ class={streamdown.theme.mermaid.icon}
309
+ xmlns="http://www.w3.org/2000/svg"
310
+ viewBox="0 0 24 24"
311
+ fill="none"
312
+ stroke="currentColor"
313
+ stroke-width="2"
314
+ stroke-linecap="round"
315
+ stroke-linejoin="round"
316
+ >
317
+ <path d="m15 15 6 6" /><path d="m15 9 6-6" /><path d="M21 16v5h-5" /><path d="M21 8V3h-5" />
318
+ <path d="M3 16v5h5" /><path d="m3 21 6-6" /><path d="M3 8V3h5" /><path d="M9 9 3 3" />
319
+ </svg>
320
+ {/snippet}
321
+
314
322
  <style>
315
323
  /* View Transition styles for zoom effect */
316
324
  ::view-transition-old(panzoom-element),
@@ -24,6 +24,7 @@
24
24
  controls,
25
25
  animation,
26
26
  element = $bindable(),
27
+ icons,
27
28
  ...snippets
28
29
  }: StreamdownProps = $props();
29
30
 
@@ -97,6 +98,9 @@
97
98
  code: codeControls,
98
99
  mermaid: mermaidControls
99
100
  };
101
+ },
102
+ get icons() {
103
+ return icons;
100
104
  }
101
105
  });
102
106
 
@@ -1,8 +1,8 @@
1
- import type { BundledTheme } from 'shiki';
2
1
  import type { Snippet } from 'svelte';
3
- import type { Theme } from './theme.js';
2
+ import type { DeepPartialTheme, Theme } from './theme.js';
4
3
  import type { MermaidConfig } from 'mermaid';
5
4
  import type { KatexOptions } from 'katex';
5
+ import type { BundledTheme } from 'shiki';
6
6
  export interface StreamdownContext extends Omit<StreamdownProps, keyof Snippets | 'class' | 'theme' | 'shikiTheme'> {
7
7
  snippets: Snippets;
8
8
  shikiTheme: BundledTheme;
@@ -81,7 +81,7 @@ export type StreamdownProps = {
81
81
  defaultOrigin?: string;
82
82
  allowedLinkPrefixes?: string[];
83
83
  allowedImagePrefixes?: string[];
84
- theme?: Partial<Theme>;
84
+ theme?: DeepPartialTheme;
85
85
  baseTheme?: 'tailwind' | 'shadcn';
86
86
  mergeTheme?: boolean;
87
87
  shikiTheme?: BundledTheme;
@@ -110,5 +110,18 @@ export type StreamdownProps = {
110
110
  timingFunction?: 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'linear';
111
111
  tokenize?: 'word' | 'char';
112
112
  };
113
+ icons?: {
114
+ copy?: Snippet;
115
+ download?: Snippet;
116
+ fullscreen?: Snippet;
117
+ zoomIn?: Snippet;
118
+ zoomOut?: Snippet;
119
+ fitView?: Snippet;
120
+ note?: Snippet;
121
+ tip?: Snippet;
122
+ warning?: Snippet;
123
+ caution?: Snippet;
124
+ important?: Snippet;
125
+ };
113
126
  } & Partial<Snippets>;
114
127
  export {};
@@ -10,9 +10,7 @@ export class StreamdownContext {
10
10
  animation-duration: ${this.animation.duration}ms;
11
11
  animation-timing-function: ${this.animation.timingFunction};
12
12
  animation-iteration-count: 1;
13
- animation-fill-mode: forwards;
14
- white-space: pre-wrap;
15
- display: inline-block;`
13
+ animation-fill-mode: forwards;`
16
14
  : undefined);
17
15
  animationBlockStyle = $derived(this.animation.enabled
18
16
  ? `animation-name: sd-${this.animation.type};
@@ -4,9 +4,11 @@ import { type FootnoteToken } from './marked-footnotes.js';
4
4
  import { type MathToken } from './marked-math.js';
5
5
  import { type SubSupToken } from './marked-subsup.js';
6
6
  import { type ListItemToken, type ListToken } from './marked-list.js';
7
+ import { type BrToken } from './marked-br.js';
8
+ import { type HrToken } from './marked-hr.js';
7
9
  import { type TableToken, type THead, type TBody, type TFoot, type THeadRow, type TRow, type TH, type TD } from './marked-table.js';
8
- export type StreamdownToken = Exclude<MarkedToken, Tokens.List | Tokens.ListItem> | ListToken | ListItemToken | MathToken | AlertToken | FootnoteToken | SubSupToken | TableToken | THead | TBody | TFoot | THeadRow | TRow | TH | TD;
10
+ export type StreamdownToken = Exclude<MarkedToken, Tokens.List | Tokens.ListItem> | ListToken | ListItemToken | MathToken | AlertToken | FootnoteToken | SubSupToken | BrToken | HrToken | TableToken | THead | TBody | TFoot | THeadRow | TRow | TH | TD;
9
11
  export type { TableToken, THead, TBody, TFoot, THeadRow, TRow, TH, TD } from './marked-table.js';
10
12
  export declare const lex: (markdown: string) => StreamdownToken[];
11
13
  export declare const parseBlocks: (markdown: string) => string[];
12
- export type { MathToken, AlertToken, FootnoteToken, SubSupToken };
14
+ export type { MathToken, AlertToken, FootnoteToken, SubSupToken, BrToken, HrToken };
@@ -1,9 +1,11 @@
1
1
  import { Lexer } from 'marked';
2
- import markedAlert, {} from './marked-alert.js';
3
- import markedFootnote, {} from './marked-footnotes.js';
2
+ import { markedAlert } from './marked-alert.js';
3
+ import { markedFootnote } from './marked-footnotes.js';
4
4
  import { markedMath } from './marked-math.js';
5
- import markedSubSup, {} from './marked-subsup.js';
5
+ import { markedSubSup } from './marked-subsup.js';
6
6
  import { markedList } from './marked-list.js';
7
+ import { markedBr } from './marked-br.js';
8
+ import { markedHr } from './marked-hr.js';
7
9
  import { markedTable } from './marked-table.js';
8
10
  const extensions = [
9
11
  markedTable(),
@@ -11,7 +13,9 @@ const extensions = [
11
13
  markedAlert(),
12
14
  markedMath(),
13
15
  markedSubSup(),
14
- markedList()
16
+ markedList(),
17
+ markedBr(),
18
+ markedHr()
15
19
  ];
16
20
  const parseExtensions = (...ext) => {
17
21
  const options = {
@@ -47,9 +51,9 @@ const parseExtensions = (...ext) => {
47
51
  });
48
52
  return options;
49
53
  };
50
- const blockLexer = new Lexer(parseExtensions(markedFootnote(), markedTable()));
54
+ const blockLexer = new Lexer(parseExtensions(markedHr(), markedFootnote(), markedTable()));
51
55
  export const lex = (markdown) => {
52
- return new Lexer(parseExtensions(markedTable(), markedFootnote(), markedAlert(), markedMath(), markedSubSup(), markedList()))
56
+ return new Lexer(parseExtensions(markedHr(), markedTable(), markedFootnote(), markedAlert(), markedMath(), markedSubSup(), markedList(), markedBr()))
53
57
  .lex(markdown)
54
58
  .filter((token) => token.type !== 'space' && token.type !== 'footnote');
55
59
  };
@@ -2,7 +2,7 @@ import type { Tokenizer, Tokens } from 'marked';
2
2
  import type { TokenizerExtensionFunction } from 'marked';
3
3
  type variantType = 'note' | 'tip' | 'important' | 'warning' | 'caution';
4
4
  export declare function createSyntaxPattern(type: variantType): string;
5
- export default function markedAlert(): {
5
+ export declare function markedAlert(): {
6
6
  extensions: Array<{
7
7
  name: string;
8
8
  level: 'block' | 'inline';
@@ -3,7 +3,7 @@ const variants = ['note', 'tip', 'important', 'warning', 'caution'];
3
3
  export function createSyntaxPattern(type) {
4
4
  return `^\\s*[\\*_]*\\[!${type.toUpperCase()}\\][\\*_]*\\s*`;
5
5
  }
6
- export default function markedAlert() {
6
+ export function markedAlert() {
7
7
  const defaultLexer = new Lexer({ gfm: true });
8
8
  const defaultTokenizer = defaultLexer.options.tokenizer;
9
9
  return {
@@ -24,14 +24,22 @@ export default function markedAlert() {
24
24
  };
25
25
  }
26
26
  export function processAlertToken(token, tokenizer) {
27
- const matchedVariant = variants.find((type) => new RegExp(createSyntaxPattern(type)).test(('text' in token && token.text) || ''));
28
- if (!matchedVariant)
27
+ const matchedVariant = variants.find((type) => new RegExp(createSyntaxPattern(type), 'i').test(('text' in token && token.text) || ''));
28
+ if (!matchedVariant) {
29
+ Object.assign(token, {
30
+ tokens: token.tokens
31
+ .map((token) => {
32
+ return tokenizer.lexer.blockTokens(token.raw, [])[0];
33
+ })
34
+ .filter(Boolean)
35
+ });
29
36
  return;
37
+ }
38
+ const alertPattern = new RegExp(`[\\*_]*\\[!${matchedVariant.toUpperCase()}\\][\\*_]*`, 'g');
30
39
  const tokens = token.tokens
31
40
  .map((token) => {
32
41
  let cleanedRaw = token.raw;
33
42
  // Remove alert markers with any markdown formatting (asterisks/underscores)
34
- const alertPattern = new RegExp(`[\\*_]*\\[!${matchedVariant.toUpperCase()}\\][\\*_]*`, 'g');
35
43
  cleanedRaw = cleanedRaw.replaceAll(alertPattern, '').trim();
36
44
  return tokenizer.lexer.blockTokens(cleanedRaw, [])[0];
37
45
  })
@@ -39,6 +47,7 @@ export function processAlertToken(token, tokenizer) {
39
47
  Object.assign(token, {
40
48
  type: 'alert',
41
49
  variant: matchedVariant,
42
- tokens
50
+ tokens,
51
+ text: token.text.replace(alertPattern, '').trim()
43
52
  });
44
53
  }
@@ -0,0 +1,12 @@
1
+ import type { TokenizerExtensionFunction } from 'marked';
2
+ export interface BrToken {
3
+ type: 'br';
4
+ raw: string;
5
+ }
6
+ export declare function markedBr(): {
7
+ extensions: Array<{
8
+ name: string;
9
+ level: 'inline';
10
+ tokenizer: TokenizerExtensionFunction;
11
+ }>;
12
+ };
@@ -0,0 +1,21 @@
1
+ export function markedBr() {
2
+ return {
3
+ extensions: [
4
+ {
5
+ name: 'br',
6
+ level: 'inline',
7
+ tokenizer(src) {
8
+ // Match HTML <br> tags (with or without closing slash, case insensitive)
9
+ const match = src.match(/^<br\s*\/?>/i);
10
+ if (match) {
11
+ return {
12
+ type: 'br',
13
+ raw: match[0]
14
+ };
15
+ }
16
+ return undefined;
17
+ }
18
+ }
19
+ ]
20
+ };
21
+ }
@@ -1,6 +1,6 @@
1
1
  import type { TokenizerExtensionFunction } from 'marked';
2
2
  import { type StreamdownToken } from './index.js';
3
- export default function markedFootnote(): {
3
+ export declare function markedFootnote(): {
4
4
  extensions: {
5
5
  name: string;
6
6
  level: 'block' | 'inline';
@@ -9,7 +9,7 @@ const safeGetContext = () => {
9
9
  return null;
10
10
  }
11
11
  };
12
- export default function markedFootnote() {
12
+ export function markedFootnote() {
13
13
  const ensureMaps = (tokenizer) => {
14
14
  const streamdown = safeGetContext();
15
15
  if (!streamdown) {
@@ -0,0 +1,12 @@
1
+ import type { TokenizerExtensionFunction } from 'marked';
2
+ export interface HrToken {
3
+ type: 'hr';
4
+ raw: string;
5
+ }
6
+ export declare function markedHr(): {
7
+ extensions: Array<{
8
+ name: string;
9
+ level: 'block';
10
+ tokenizer: TokenizerExtensionFunction;
11
+ }>;
12
+ };
@@ -0,0 +1,24 @@
1
+ export function markedHr() {
2
+ return {
3
+ extensions: [
4
+ {
5
+ name: 'hr',
6
+ level: 'block',
7
+ tokenizer(src) {
8
+ // Match horizontal rules according to CommonMark spec:
9
+ // 3 or more matching -, _, or * characters, with optional spaces between
10
+ // Must be at start of string and match entire line
11
+ const match = src.match(/^[ \t]*(-[ \t]*-[ \t]*-+|_[ \t]*_[ \t]*_+|\*[ \t]*\*[ \t]*\*+)[ \t]*(?:\n|$)/);
12
+ if (match) {
13
+ const raw = match[0].replace(/\n$/, ''); // Remove trailing newline from raw
14
+ return {
15
+ type: 'hr',
16
+ raw: raw
17
+ };
18
+ }
19
+ return undefined;
20
+ }
21
+ }
22
+ ]
23
+ };
24
+ }
@@ -4,7 +4,7 @@ export declare function romanToInt(roman: string): number;
4
4
  export declare const romanUpper = "(?:C|XC|L?X{0,3}(?:IX|IV|V?I{0,3}))";
5
5
  export declare const romanLower = "(?:c|xc|l?x{0,3}(?:ix|iv|v?i{0,3}))";
6
6
  export declare const bulletPattern = "(?:[*+-]|(?:\\d{1,9}|[a-zA-Z]|(?:C|XC|L?X{0,3}(?:IX|IV|V?I{0,3}))|(?:c|xc|l?x{0,3}(?:ix|iv|v?i{0,3})))[.)])";
7
- export declare const rule = "^( {0,3}(?:[*+-]|(?:\\d{1,9}|[a-zA-Z]|(?:C|XC|L?X{0,3}(?:IX|IV|V?I{0,3}))|(?:c|xc|l?x{0,3}(?:ix|iv|v?i{0,3})))[.)]))([ \\t][^\\n]+?)?(?:\\n|$)";
7
+ export declare const rule = "^( {0,3}(?:[*+-]|(?:\\d{1,9}|[a-zA-Z]|(?:C|XC|L?X{0,3}(?:IX|IV|V?I{0,3}))|(?:c|xc|l?x{0,3}(?:ix|iv|v?i{0,3})))[.)]))([ \\t][^\\n]*|[ \\t])?(?:\\n|$)";
8
8
  export declare function markedList(): {
9
9
  extensions: Array<{
10
10
  name: string;
@@ -18,6 +18,7 @@ export interface ListToken {
18
18
  ordered: boolean;
19
19
  listType: 'decimal' | 'lower-alpha' | 'upper-alpha' | 'lower-roman' | 'upper-roman' | null;
20
20
  loose: boolean;
21
+ start?: number;
21
22
  tokens: ListItemToken[];
22
23
  }
23
24
  /**
@@ -25,7 +25,7 @@ export const romanUpper = '(?:C|XC|L?X{0,3}(?:IX|IV|V?I{0,3}))';
25
25
  export const romanLower = '(?:c|xc|l?x{0,3}(?:ix|iv|v?i{0,3}))';
26
26
  // Fixed regex pattern - carefully balanced parentheses
27
27
  export const bulletPattern = `(?:[*+-]|(?:\\d{1,9}|[a-zA-Z]|${romanUpper}|${romanLower})[.)])`;
28
- export const rule = `^( {0,3}${bulletPattern})([ \\t][^\\n]+?)?(?:\\n|$)`;
28
+ export const rule = `^( {0,3}${bulletPattern})([ \\t][^\\n]*|[ \\t])?(?:\\n|$)`;
29
29
  function finalizeList(list, lexer) {
30
30
  if (list.tokens.length === 0)
31
31
  return;
@@ -97,10 +97,12 @@ export function markedList() {
97
97
  ordered: isOrdered,
98
98
  listType: isOrdered ? type : null,
99
99
  loose: false,
100
+ start: undefined, // Will be set when first item is processed
100
101
  tokens: []
101
102
  };
102
103
  // Get next list item
103
- const itemRegex = new RegExp(`^( {0,3}${bull})((?:[\t ][^\\n]*)?(?:\\n|$))`);
104
+ // Updated regex to properly handle empty list items (space after bullet, then newline)
105
+ const itemRegex = new RegExp(`^( {0,3}${bull})([\t ][^\\n]*|[\t ])?(?:\\n|$)`);
104
106
  let endsWithBlankLine = false;
105
107
  // Check if current bullet point can start a new List Item
106
108
  while (src) {
@@ -112,7 +114,9 @@ export function markedList() {
112
114
  raw = cap[0];
113
115
  const bullet = cap[1].trim();
114
116
  src = src.substring(raw.length);
115
- const line = cap[2].split('\n', 1)[0].replace(/^\t+/, (t) => ' '.repeat(3 * t.length));
117
+ const line = cap[2]
118
+ ? cap[2].split('\n', 1)[0].replace(/^\t+/, (t) => ' '.repeat(3 * t.length))
119
+ : '';
116
120
  const nextLine = src.split('\n', 1)[0];
117
121
  const blankLine = !line.trim();
118
122
  let indent = 0;
@@ -199,6 +203,8 @@ export function markedList() {
199
203
  if (expectedValue === null) {
200
204
  // First item: set expectedValue to this item's value (or 1 if parsing failed)
201
205
  expectedValue = value ?? 1;
206
+ // Set the start property for ordered lists
207
+ list.start = expectedValue;
202
208
  }
203
209
  else {
204
210
  // Subsequent items: check if value matches expected
@@ -1,30 +1,16 @@
1
- import type { TokenizerThis } from 'marked';
1
+ import type { TokenizerExtensionFunction, TokenizerStartFunction } from 'marked';
2
2
  export declare function markedMath(): {
3
- extensions: ({
3
+ extensions: Array<{
4
4
  name: string;
5
- level: string;
6
- tokenizer(this: TokenizerThis, src: string): {
7
- type: "math";
8
- isInline: false;
9
- raw: string;
10
- text: string;
11
- } | undefined;
12
- start?: undefined;
13
- } | {
14
- name: string;
15
- level: string;
16
- start(src: string): number | undefined;
17
- tokenizer(this: TokenizerThis, src: string): {
18
- type: "math";
19
- isInline: true;
20
- raw: string;
21
- text: string;
22
- } | undefined;
23
- })[];
5
+ level: 'block' | 'inline';
6
+ tokenizer: TokenizerExtensionFunction;
7
+ start?: TokenizerStartFunction;
8
+ }>;
24
9
  };
25
10
  export type MathToken = {
26
11
  type: 'math';
27
12
  raw: string;
28
13
  text: string;
29
14
  isInline: boolean;
15
+ displayMode: boolean;
30
16
  };
@@ -1,6 +1,9 @@
1
- // Use the standard rule that requires proper spacing after math expressions
2
- const inlineRule = /^(\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n\$]))\1(?=[\s?!\.,:?!。,:]|$)/;
3
- const blockRule = /^(\${1,2})\n((?:\\[\s\S]|[^\\])+?)\n\1(?:\n|$)/;
1
+ // Math parsing rules
2
+ // Block math: supports both newline format ($$\nmath\n$$) and single-line format ($$math$$)
3
+ const blockRule = /^(\$\$)(?:\n((?:\\[\s\S]|[^\\])+?)\n\1(?:\n|$)|([^$\n]+?)\1(?=\s|$|$))/;
4
+ // Inline math: handles both single ($) and double ($$) dollar delimiters
5
+ // Avoids matching currency by checking context and requiring proper content
6
+ const inlineRule = /^(\${1,2})(?!\$)((?:[^$\n]|\\\$)*?)\1(?!\d)/;
4
7
  export function markedMath() {
5
8
  return {
6
9
  extensions: [
@@ -10,11 +13,14 @@ export function markedMath() {
10
13
  tokenizer(src) {
11
14
  const match = src.match(blockRule);
12
15
  if (match) {
16
+ // match[2] is multiline format, match[3] is single-line format
17
+ const content = (match[2] || match[3]).trim();
13
18
  return {
14
19
  type: 'math',
15
20
  isInline: false,
21
+ displayMode: true,
16
22
  raw: match[0],
17
- text: match[2].trim()
23
+ text: content
18
24
  };
19
25
  }
20
26
  }
@@ -23,31 +29,46 @@ export function markedMath() {
23
29
  name: 'math',
24
30
  level: 'inline',
25
31
  start(src) {
26
- let index;
27
- let indexSrc = src;
28
- while (indexSrc) {
29
- index = indexSrc.indexOf('$');
30
- if (index === -1) {
32
+ let index = 0;
33
+ let searchSrc = src;
34
+ while (searchSrc) {
35
+ const dollarIndex = searchSrc.indexOf('$');
36
+ if (dollarIndex === -1) {
31
37
  return;
32
38
  }
33
- const f = index > -1;
34
- if (f) {
35
- const possibleKatex = indexSrc.substring(index);
36
- if (possibleKatex.match(inlineRule)) {
37
- return index;
39
+ const currentIndex = index + dollarIndex;
40
+ const possibleMath = src.substring(currentIndex);
41
+ // Check if this could be math (not currency)
42
+ if (possibleMath.match(inlineRule)) {
43
+ // Additional check: avoid currency patterns like $5.00
44
+ const beforeChar = currentIndex > 0 ? src[currentIndex - 1] : '';
45
+ const afterDollar = possibleMath.substring(1, 6); // Check first few chars after $
46
+ // Skip if it looks like currency (digit immediately after $ or decimal pattern)
47
+ if (!/^\d+(\.\d{2})?\s/.test(afterDollar)) {
48
+ return currentIndex;
38
49
  }
39
50
  }
40
- indexSrc = indexSrc.substring(index + 1).replace(/^\$+/, '');
51
+ index += dollarIndex + 1;
52
+ searchSrc = src.substring(index);
41
53
  }
42
54
  },
43
55
  tokenizer(src) {
44
56
  const match = src.match(inlineRule);
45
57
  if (match) {
58
+ // Additional validation: avoid currency patterns
59
+ const content = match[2];
60
+ if (/^\d+(\.\d{2})?$/.test(content.trim())) {
61
+ // This looks like currency, skip it
62
+ return;
63
+ }
64
+ // Double dollars are display mode, single dollars are inline
65
+ const isDisplayMode = match[1] === '$$';
46
66
  return {
47
67
  type: 'math',
48
- isInline: true,
68
+ isInline: !isDisplayMode,
69
+ displayMode: isDisplayMode,
49
70
  raw: match[0],
50
- text: match[2].trim()
71
+ text: content.trim()
51
72
  };
52
73
  }
53
74
  }
@@ -1,5 +1,5 @@
1
1
  import type { TokenizerExtensionFunction, TokenizerStartFunction } from 'marked';
2
- export default function markedSubSup(): {
2
+ export declare function markedSubSup(): {
3
3
  extensions: {
4
4
  name: string;
5
5
  level: 'inline';