svelte-streamdown 2.3.1 → 2.3.2

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
@@ -261,14 +261,14 @@ This Svelte port maintains feature parity with the original [Streamdown](https:/
261
261
 
262
262
  ## 🎭 Animation System
263
263
 
264
- Streamdown includes a sophisticated animation system designed specifically for streaming AI content, providing smooth and engaging visual feedback as text appears on screen.
264
+ Streamdown includes an animation system designed specifically for streaming AI content, providing smooth and engaging visual feedback as text appears on screen.
265
265
 
266
266
  ### How It Works
267
267
 
268
268
  The animation system works by:
269
269
 
270
270
  1. **Tokenization**: Text is broken down into tokens (words or characters) based on your configuration
271
- 2. **Sequential Animation**: Each token animates in sequence with configurable timing
271
+ 2. **Sequential Animation**: Each token animates as it is received
272
272
  3. **Block-level Animation**: Entire blocks (paragraphs, headings, code blocks) animate as units
273
273
 
274
274
  ### Animation Types
@@ -293,6 +293,8 @@ Text slides down from above while fading in, creating a dynamic downward motion.
293
293
 
294
294
  > [!TIP]
295
295
  > For production applications where the LLM is not streaming (static content), disable animations entirely by setting `animation.enabled = false` to minimize DOM elements and improve performance.
296
+ >
297
+ > If using AI SDK mind to smooth stream the content to using word-level tokenization to avoid partial words not being animated.
296
298
 
297
299
  > [!WARNING]
298
300
  > Character-level tokenization (`tokenize: 'char'`) creates significantly more DOM elements than word-level tokenization. Use character tokenization sparingly and only when the typewriter effect is essential for your user experience.
@@ -327,17 +329,15 @@ console.log('Hello from Streamdown!');
327
329
  let content = `# Custom Components Example
328
330
 
329
331
  This heading will use a custom component!`;
330
-
331
- // Custom heading component
332
332
  </script>
333
333
 
334
- {#snippet customHeading({ children, token })}
335
- <h1 class="mb-4 text-4xl font-bold text-blue-600" {...token.props}>
336
- {@render children()}
337
- </h1>
338
- {/snippet}
339
-
340
- <Streamdown {content} heading={customHeading} />
334
+ <Streamdown {content}>
335
+ {#snippet heading({ children })}
336
+ <h1 class="mb-4 text-4xl font-bold text-blue-600">
337
+ {@render children()}
338
+ </h1>
339
+ {/snippet}
340
+ </Streamdown>
341
341
  ```
342
342
 
343
343
  ### Security Configuration
@@ -359,73 +359,32 @@ This heading will use a custom component!`;
359
359
 
360
360
  ## 📋 Props API
361
361
 
362
- | Prop | Type | Default | Description |
363
- | -------------------------- | ---------------------------------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
364
- | `content` | `string` | - | **Required.** The markdown content to render |
365
- | `class` | `string` | - | CSS class names for the wrapper element |
366
- | `parseIncompleteMarkdown` | `boolean` | `true` | Parse and fix incomplete markdown syntax |
367
- | `defaultOrigin` | `string` | - | Default origin for relative URLs |
368
- | `allowedLinkPrefixes` | `string[]` | `['*']` | Allowed URL prefixes for links |
369
- | `allowedImagePrefixes` | `string[]` | `['*']` | Allowed URL prefixes for images |
370
- | `skipHtml` | `boolean` | - | Skip HTML parsing entirely |
371
- | `unwrapDisallowed` | `boolean` | - | Unwrap instead of removing disallowed elements |
372
- | `urlTransform` | `UrlTransform \| null` | - | Custom URL transformation function |
373
- | `theme` | `DeepPartial<Theme>` | - | Custom theme overrides |
374
- | `baseTheme` | `'tailwind' \| 'shadcn'` | `'tailwind'` | Base theme to use before applying overrides |
375
- | `mergeTheme` | `boolean` | `true` | Whether to merge theme with base theme |
376
- | `shikiTheme` | `BundledTheme` | `'github-light'` | Code highlighting theme |
377
- | `mermaidConfig` | `MermaidConfig` | - | Mermaid diagram configuration |
378
- | `katexConfig` | `KatexOptions \| ((inline: boolean) => KatexOptions)` | - | KaTeX math rendering options |
379
- | `animation` | `AnimationConfig` | - | Animation configuration for streaming content |
380
- | `animation.enabled` | `boolean` | `false` | Enable/disable animations |
381
- | `animation.type` | `'fade' \| 'blur' \| 'typewriter' \| 'slideUp' \| 'slideDown'` | `'blur'` | Animation style for text appearance |
382
- | `animation.duration` | `number` | `500` | Animation duration in milliseconds |
383
- | `animation.timingFunction` | `'ease' \| 'ease-in' \| 'ease-out' \| 'ease-in-out' \| 'linear'` | `'ease-in'` | CSS timing function for animations |
384
- | `animation.tokenize` | `'word' \| 'char'` | `'word'` | Tokenization method for text animations |
385
- | `animation.animateOnMount` | `boolean` | `false` | Run the token animation on mount or not, useful if you render the Streamdown component in the same time as the first token is receive from the LLM |
386
-
387
- ### Custom Component Props
388
-
389
- **Every single markdown element** can be customized with Svelte snippets, giving you
390
- complete control over styling and behavior:
391
-
392
- Each snippet receives `{ children, token }` where `token` is a typed token object containing the parsed markdown token with its properties and children is a snippet to be rendered.
393
-
394
- ```svelte
395
- <script>
396
- import { Streamdown } from 'svelte-streamdown';
397
-
398
- let content = `# Fully Customizable
399
-
400
- This heading uses a custom component with your design system!`;
401
- </script>
402
-
403
- {#snippet customHeading({ children, token })}
404
- <h1
405
- class="text-gradient mb-6 bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-4xl font-bold text-transparent"
406
- {...token.props}
407
- >
408
- {@render children()}
409
- </h1>
410
- {/snippet}
411
-
412
- {#snippet customCode({ children, token })}
413
- <code class="rounded bg-gray-100 px-2 py-1 font-mono text-sm dark:bg-gray-800" {...token.props}>
414
- {@render children()}
415
- </code>
416
- {/snippet}
417
-
418
- {#snippet customBlockquote({ children, token })}
419
- <blockquote
420
- class="border-l-4 border-blue-500 pl-4 text-gray-600 italic dark:text-gray-300"
421
- {...token.props}
422
- >
423
- {@render children()}
424
- </blockquote>
425
- {/snippet}
426
-
427
- <Streamdown {content} heading={customHeading} code={customCode} blockquote={customBlockquote} />
428
- ```
362
+ | Prop | Type | Default | Description |
363
+ | -------------------------- | -------------------------------------------------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
364
+ | `content` | `string` | - | **Required.** The markdown content to render |
365
+ | `class` | `string` | - | CSS class names for the wrapper element |
366
+ | `parseIncompleteMarkdown` | `boolean` | `true` | Parse and fix incomplete markdown syntax |
367
+ | `defaultOrigin` | `string` | - | Default origin for relative URLs |
368
+ | `allowedLinkPrefixes` | `string[]` | `['*']` | Allowed URL prefixes for links |
369
+ | `allowedImagePrefixes` | `string[]` | `['*']` | Allowed URL prefixes for images |
370
+ | `skipHtml` | `boolean` | - | Skip HTML parsing entirely |
371
+ | `unwrapDisallowed` | `boolean` | - | Unwrap instead of removing disallowed elements |
372
+ | `urlTransform` | `UrlTransform \| null` | - | Custom URL transformation function |
373
+ | `theme` | `DeepPartial<Theme>` | - | Custom theme overrides |
374
+ | `baseTheme` | `'tailwind' \| 'shadcn'` | `'tailwind'` | Base theme to use before applying overrides |
375
+ | `mergeTheme` | `boolean` | `true` | Whether to merge theme with base theme |
376
+ | `shikiTheme` | `BundledTheme` | `'github-light'` | Code highlighting theme |
377
+ | `mermaidConfig` | `MermaidConfig` | - | Mermaid diagram configuration |
378
+ | `katexConfig` | `KatexOptions \| ((inline: boolean) => KatexOptions)` | - | KaTeX math rendering options |
379
+ | `animation` | `AnimationConfig` | - | Animation configuration for streaming content |
380
+ | `animation.enabled` | `boolean` | `false` | Enable/disable animations |
381
+ | `animation.type` | `'fade' \| 'blur' \| 'typewriter' \| 'slideUp' \| 'slideDown'` | `'blur'` | Animation style for text appearance |
382
+ | `animation.duration` | `number` | `500` | Animation duration in milliseconds |
383
+ | `animation.timingFunction` | `'ease' \| 'ease-in' \| 'ease-out' \| 'ease-in-out' \| 'linear'` | `'ease-in'` | CSS timing function for animations |
384
+ | `animation.tokenize` | `'word' \| 'char'` | `'word'` | Tokenization method for text animations |
385
+ | `animation.animateOnMount` | `boolean` | `false` | Run the token animation on mount or not, useful if you render the Streamdown component in the same time as the first token is receive from the LLM |
386
+ | `extensions` | `Array<Extension>` | `[]` | Custom marked tokenizers to render special markdown blocks or inline tokens |
387
+ | `children` | `Snippet<[{token:GenericToken, streamdown: StreamdownContext, children: Snippet` | `undefined` | Snippet used to render element that are not supported by Streamdown and tokenized by your custom extensions |
429
388
 
430
389
  #### All Available Customizable Elements:
431
390
 
@@ -435,15 +394,15 @@ This heading uses a custom component with your design system!`;
435
394
 
436
395
  **Lists**: `ul`, `ol`, `li`
437
396
 
438
- **Code**: `code`, `inlineCode`, `pre`
397
+ **Code**: `code`, `codeSpan`
439
398
 
440
- **Tables**: `table`, `thead`, `tbody`, `tr`, `th`, `td`
399
+ **Tables**: `table`, `thead`, `tbody`, `tr`, `th`, `td`, `tfoot`
441
400
 
442
- **Special Content**: `blockquote`, `hr`, `alert`, `mermaid`, `math`, `inlineMath`
401
+ **Special Content**: `blockquote`, `hr`, `alert`, `mermaid`, `math`, `footnoteRef`
443
402
 
444
403
  **Note**: The above elements are **supported by Streamdown** and should be customized using individual props or the theme system.
445
404
 
446
- ## 🎨 Advanced Theming System
405
+ ## 🎨 Theming System
447
406
 
448
407
  ### Built-in Themes
449
408
 
@@ -548,6 +507,66 @@ Each component supports multiple themeable parts:
548
507
 
549
508
  Themes are intelligently merged using Tailwind's class merging utility, so you only need to override the specific parts you want to customize while keeping the default styling for everything else.
550
509
 
510
+ ## 💉 Extensibility
511
+
512
+ Streamdown is extensible through the use of custom extensions.
513
+
514
+ An extension is an object that has a `name`, a `level` and a `tokenizer` function.
515
+
516
+ - `name`: The name of the extension
517
+ - `level`: The level of the extension, can be `block` or `inline`
518
+ - `tokenizer`: The tokenizer function, see [marked](https://github.com/markedjs/marked) for more information
519
+
520
+ To render the extension custom tokens, you can then simply use the `children` snippet.
521
+
522
+ ### Example
523
+
524
+ ```svelte
525
+ <script lang="ts">
526
+ import { Streamdown, type Extension } from 'svelte-streamdown';
527
+ const markedCollapsible: Extension = {
528
+ name: 'collapsible',
529
+ level: 'block',
530
+ tokenizer(this, src) {
531
+ // Match [detail]...[detail] blocks (case insensitive)
532
+ const detailMatch = src.match(/^\[detail\](.*?)\[detail\]/is);
533
+
534
+ if (detailMatch) {
535
+ const content = detailMatch[1] || '';
536
+ const tokens = this.lexer.blockTokens(content);
537
+
538
+ return {
539
+ type: 'detail',
540
+ raw: detailMatch[0], // The entire matched string including tags
541
+ tokens
542
+ };
543
+ }
544
+
545
+ return undefined;
546
+ }
547
+ };
548
+ </script>
549
+
550
+ <Streamdown
551
+ extensions={[markedCollapsible]}
552
+ content={`
553
+ [detail]
554
+ This is a collapsible **section**
555
+ [detail]`}
556
+ >
557
+ {#snippet children({ token, streamdown, children })}
558
+ {#if token.type === 'detail'}
559
+ <details>
560
+ <summary> Detail </summary>
561
+ <div>
562
+ {@render children()}
563
+ </div>
564
+ </details>
565
+ {/if}
566
+ {/snippet}
567
+ </Streamdown>
568
+ ```
569
+
551
570
  ## 🛠️ Development
552
571
 
553
572
  ### Setup
package/dist/Block.svelte CHANGED
@@ -14,8 +14,8 @@
14
14
  insideFootnote?: boolean;
15
15
  } = $props();
16
16
 
17
- const tokens = $derived(lex(parseIncompleteMarkdown(block.trim())));
18
17
  const streamdown = useStreamdown();
18
+ const tokens = $derived(lex(parseIncompleteMarkdown(block.trim()), streamdown.extensions));
19
19
  </script>
20
20
 
21
21
  {#snippet renderChildren(tokens: StreamdownToken[])}
@@ -223,6 +223,16 @@
223
223
  <Alert {token} {children} />
224
224
  {:else if token.type === 'footnoteRef'}
225
225
  <FootnoteRef {token} />
226
+ {:else if token.type === 'footnote'}
227
+ <!-- TODO Footnotes are rendered inside the FootnoteRef popover -->
228
+ {:else if token.type === 'def'}
229
+ <!-- TODO This does not seems to be tokenized for now -->
230
+ {:else if token.type === 'escape'}
231
+ <!-- TODO This does not seems to be tokenized for now -->
232
+ {:else if token.type === 'space'}
233
+ <!-- TODO This does not seems to be tokenized for now -->
234
+ {:else if token.type === 'text'}
235
+ {@render children()}
226
236
  {:else if token.type === 'html'}
227
237
  {#if streamdown.renderHtml}
228
238
  {@const content =
@@ -230,6 +240,6 @@
230
240
  {@html content}
231
241
  {/if}
232
242
  {:else}
233
- <!-- For tokens we don't handle specifically, render children or fallback -->
234
- {@render children?.()}
243
+ <!-- For tokens we don't handle specifically, it may certainely be a custom extension to to the children props to handle -->
244
+ {@render streamdown.children?.({ token, children, streamdown })}
235
245
  {/if}
@@ -6,4 +6,3 @@ export { default as Link } from './Link.svelte';
6
6
  export { default as Math } from './Math.svelte';
7
7
  export { default as Mermaid } from './Mermaid.svelte';
8
8
  export { default as Slot } from './Slot.svelte';
9
- export { default as Table } from './Table.svelte';
@@ -6,4 +6,3 @@ export { default as Link } from './Link.svelte';
6
6
  export { default as Math } from './Math.svelte';
7
7
  export { default as Mermaid } from './Mermaid.svelte';
8
8
  export { default as Slot } from './Slot.svelte';
9
- export { default as Table } from './Table.svelte';
@@ -25,6 +25,8 @@
25
25
  animation,
26
26
  element = $bindable(),
27
27
  icons,
28
+ children,
29
+ extensions,
28
30
  ...snippets
29
31
  }: StreamdownProps = $props();
30
32
 
@@ -99,6 +101,12 @@
99
101
  mermaid: mermaidControls
100
102
  };
101
103
  },
104
+ get children() {
105
+ return children;
106
+ },
107
+ get extensions() {
108
+ return extensions;
109
+ },
102
110
  get icons() {
103
111
  return icons;
104
112
  }
@@ -106,7 +114,7 @@
106
114
 
107
115
  const id = $props.id();
108
116
 
109
- const blocks = $derived(parseBlocks(content));
117
+ const blocks = $derived(parseBlocks(content, streamdown.extensions));
110
118
  </script>
111
119
 
112
120
  <div bind:this={element} class={className}>
@@ -28,7 +28,7 @@ export declare class StreamdownContext {
28
28
  });
29
29
  }
30
30
  export declare const useStreamdown: () => StreamdownContext;
31
- import type { AlertToken, MathToken, SubSupToken, TableToken, THead, TBody, TFoot, THeadRow, TRow, TD, TH } from './marked/index.js';
31
+ import type { AlertToken, MathToken, SubSupToken, TableToken, THead, TBody, TFoot, THeadRow, TRow, TD, TH, Extension, GenericToken } from './marked/index.js';
32
32
  import type { Tokens } from 'marked';
33
33
  import type { ListItemToken, ListToken } from './marked/marked-list.js';
34
34
  import type { Footnote, FootnoteRef, FootnoteToken } from './marked/marked-footnotes.js';
@@ -123,5 +123,11 @@ export type StreamdownProps = {
123
123
  caution?: Snippet;
124
124
  important?: Snippet;
125
125
  };
126
+ extensions?: Extension[];
127
+ children?: Snippet<[{
128
+ streamdown: StreamdownContext;
129
+ token: GenericToken;
130
+ children: Snippet;
131
+ }]>;
126
132
  } & Partial<Snippets>;
127
133
  export {};
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { default as Streamdown } from './Streamdown.svelte';
2
2
  export { useStreamdown, type StreamdownProps } from './context.svelte.js';
3
3
  export { theme, shadcnTheme, mergeTheme, type Theme } from './theme.js';
4
- export { lex, parseBlocks, type StreamdownToken } from './marked/index.js';
4
+ export { type Extension, type StreamdownToken } from './marked/index.js';
5
+ export { lex, parseBlocks } from './marked/index.js';
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { default as Streamdown } from './Streamdown.svelte';
2
2
  export { useStreamdown } from './context.svelte.js';
3
3
  export { theme, shadcnTheme, mergeTheme } from './theme.js';
4
+ export {} from './marked/index.js';
4
5
  export { lex, parseBlocks } from './marked/index.js';
@@ -1,4 +1,4 @@
1
- import { type MarkedToken, type Tokens } from 'marked';
1
+ import { type MarkedToken, type Token, type TokenizerStartFunction, type TokenizerThis, type Tokens, type TokensList } from 'marked';
2
2
  import { type AlertToken } from './marked-alert.js';
3
3
  import { type FootnoteToken } from './marked-footnotes.js';
4
4
  import { type MathToken } from './marked-math.js';
@@ -7,8 +7,20 @@ import { type ListItemToken, type ListToken } from './marked-list.js';
7
7
  import { type BrToken } from './marked-br.js';
8
8
  import { type HrToken } from './marked-hr.js';
9
9
  import { type TableToken, type THead, type TBody, type TFoot, type THeadRow, type TRow, type TH, type TD } from './marked-table.js';
10
+ export type GenericToken = {
11
+ type: string;
12
+ raw: string;
13
+ tokens?: Token[];
14
+ } & Record<string, any>;
15
+ export type Extension = {
16
+ name: string;
17
+ level: 'block' | 'inline';
18
+ tokenizer: (this: TokenizerThis, src: string, tokens: Token[] | TokensList) => GenericToken | undefined;
19
+ start?: TokenizerStartFunction;
20
+ applyInBlockParsing?: boolean;
21
+ };
10
22
  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;
11
23
  export type { TableToken, THead, TBody, TFoot, THeadRow, TRow, TH, TD } from './marked-table.js';
12
- export declare const lex: (markdown: string) => StreamdownToken[];
13
- export declare const parseBlocks: (markdown: string) => string[];
24
+ export declare const lex: (markdown: string, extensions?: Extension[]) => StreamdownToken[];
25
+ export declare const parseBlocks: (markdown: string, extensions?: Extension[]) => string[];
14
26
  export type { MathToken, AlertToken, FootnoteToken, SubSupToken, BrToken, HrToken };
@@ -2,22 +2,12 @@ import { Lexer } from 'marked';
2
2
  import { markedAlert } from './marked-alert.js';
3
3
  import { markedFootnote } from './marked-footnotes.js';
4
4
  import { markedMath } from './marked-math.js';
5
- import { markedSubSup } from './marked-subsup.js';
5
+ import { markedSub, markedSup } from './marked-subsup.js';
6
6
  import { markedList } from './marked-list.js';
7
7
  import { markedBr } from './marked-br.js';
8
8
  import { markedHr } from './marked-hr.js';
9
9
  import { markedTable } from './marked-table.js';
10
- const extensions = [
11
- markedTable(),
12
- markedFootnote(),
13
- markedAlert(),
14
- markedMath(),
15
- markedSubSup(),
16
- markedList(),
17
- markedBr(),
18
- markedHr()
19
- ];
20
- const parseExtensions = (...ext) => {
10
+ const parseExtensions = (...extensions) => {
21
11
  const options = {
22
12
  gfm: true,
23
13
  extensions: {
@@ -29,35 +19,33 @@ const parseExtensions = (...ext) => {
29
19
  startInline: []
30
20
  }
31
21
  };
32
- ext.forEach(({ extensions }) => {
33
- extensions.forEach(({ level, name, tokenizer, ...rest }) => {
34
- if ('start' in rest && rest.start) {
35
- if (level === 'block') {
36
- options.extensions.startBlock.push(rest.start);
37
- }
38
- else {
39
- options.extensions.startInline.push(rest.start);
40
- }
22
+ extensions.forEach(({ level, name, tokenizer, ...rest }) => {
23
+ if ('start' in rest && rest.start) {
24
+ if (level === 'block') {
25
+ options.extensions.startBlock.push(rest.start);
41
26
  }
42
- if (tokenizer) {
43
- if (level === 'block') {
44
- options.extensions.block.push(tokenizer);
45
- }
46
- else {
47
- options.extensions.inline.push(tokenizer);
48
- }
27
+ else {
28
+ options.extensions.startInline.push(rest.start);
49
29
  }
50
- });
30
+ }
31
+ if (tokenizer) {
32
+ if (level === 'block') {
33
+ options.extensions.block.push(tokenizer);
34
+ }
35
+ else {
36
+ options.extensions.inline.push(tokenizer);
37
+ }
38
+ }
51
39
  });
52
40
  return options;
53
41
  };
54
- const blockLexer = new Lexer(parseExtensions(markedHr(), markedFootnote(), markedTable()));
55
- export const lex = (markdown) => {
56
- return new Lexer(parseExtensions(markedHr(), markedTable(), markedFootnote(), markedAlert(), markedMath(), markedSubSup(), markedList(), markedBr()))
42
+ export const lex = (markdown, extensions = []) => {
43
+ return new Lexer(parseExtensions(markedHr, markedTable, ...markedFootnote(), markedAlert, ...markedMath, markedSub, markedSup, markedList, markedBr, ...extensions))
57
44
  .lex(markdown)
58
45
  .filter((token) => token.type !== 'space' && token.type !== 'footnote');
59
46
  };
60
- export const parseBlocks = (markdown) => {
47
+ export const parseBlocks = (markdown, extensions = []) => {
48
+ const blockLexer = new Lexer(parseExtensions(markedHr, ...markedFootnote(), markedTable, ...extensions.filter(({ level, applyInBlockParsing }) => level === 'block' && applyInBlockParsing)));
61
49
  return blockLexer.blockTokens(markdown, []).reduce((acc, block) => {
62
50
  if (block.type === 'space' || block.type === 'footnote') {
63
51
  return acc;
@@ -1,14 +1,8 @@
1
+ import type { Extension } from './index.js';
1
2
  import type { Tokenizer, Tokens } from 'marked';
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 declare function markedAlert(): {
6
- extensions: Array<{
7
- name: string;
8
- level: 'block' | 'inline';
9
- tokenizer: TokenizerExtensionFunction;
10
- }>;
11
- };
5
+ export declare const markedAlert: Extension;
12
6
  export declare function processAlertToken(token: Tokens.Blockquote, tokenizer: Tokenizer): void;
13
7
  export type AlertToken = {
14
8
  type: 'alert';
@@ -3,26 +3,20 @@ const variants = ['note', 'tip', 'important', 'warning', 'caution'];
3
3
  export function createSyntaxPattern(type) {
4
4
  return `^\\s*[\\*_]*\\[!${type.toUpperCase()}\\][\\*_]*\\s*`;
5
5
  }
6
- export function markedAlert() {
7
- const defaultLexer = new Lexer({ gfm: true });
8
- const defaultTokenizer = defaultLexer.options.tokenizer;
9
- return {
10
- extensions: [
11
- {
12
- name: 'alert',
13
- level: 'block',
14
- tokenizer(src) {
15
- const cap = defaultTokenizer.rules.block.blockquote.exec(src);
16
- if (cap) {
17
- const blockquoteToken = defaultTokenizer.blockquote(src);
18
- blockquoteToken && processAlertToken(blockquoteToken, this.lexer.options.tokenizer);
19
- return blockquoteToken;
20
- }
21
- }
22
- }
23
- ]
24
- };
25
- }
6
+ const defaultLexer = new Lexer({ gfm: true });
7
+ const defaultTokenizer = defaultLexer.options.tokenizer;
8
+ export const markedAlert = {
9
+ name: 'alert',
10
+ level: 'block',
11
+ tokenizer(src) {
12
+ const cap = defaultTokenizer.rules.block.blockquote.exec(src);
13
+ if (cap) {
14
+ const blockquoteToken = defaultTokenizer.blockquote(src);
15
+ blockquoteToken && processAlertToken(blockquoteToken, this.lexer.options.tokenizer);
16
+ return blockquoteToken;
17
+ }
18
+ }
19
+ };
26
20
  export function processAlertToken(token, tokenizer) {
27
21
  const matchedVariant = variants.find((type) => new RegExp(createSyntaxPattern(type), 'i').test(('text' in token && token.text) || ''));
28
22
  if (!matchedVariant) {
@@ -1,12 +1,6 @@
1
- import type { TokenizerExtensionFunction } from 'marked';
1
+ import type { Extension } from './index.js';
2
2
  export interface BrToken {
3
3
  type: 'br';
4
4
  raw: string;
5
5
  }
6
- export declare function markedBr(): {
7
- extensions: Array<{
8
- name: string;
9
- level: 'inline';
10
- tokenizer: TokenizerExtensionFunction;
11
- }>;
12
- };
6
+ export declare const markedBr: Extension;
@@ -1,21 +1,15 @@
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
+ export const markedBr = {
2
+ name: 'br',
3
+ level: 'inline',
4
+ tokenizer(src) {
5
+ // Match HTML <br> tags (with or without closing slash, case insensitive)
6
+ const match = src.match(/^<br\s*\/?>/i);
7
+ if (match) {
8
+ return {
9
+ type: 'br',
10
+ raw: match[0]
11
+ };
12
+ }
13
+ return undefined;
14
+ }
15
+ };
@@ -1,12 +1,5 @@
1
- import type { TokenizerExtensionFunction } from 'marked';
2
- import { type StreamdownToken } from './index.js';
3
- export declare function markedFootnote(): {
4
- extensions: {
5
- name: string;
6
- level: 'block' | 'inline';
7
- tokenizer: TokenizerExtensionFunction;
8
- }[];
9
- };
1
+ import { type StreamdownToken, type Extension } from './index.js';
2
+ export declare function markedFootnote(): Extension[];
10
3
  /**
11
4
  * Represents a single footnote.
12
5
  */