svelte-streamdown 2.3.1 → 2.3.3
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 +108 -82
- package/dist/Block.svelte +1 -1
- package/dist/Elements/Element.svelte +36 -2
- package/dist/Elements/index.d.ts +0 -1
- package/dist/Elements/index.js +0 -1
- package/dist/Streamdown.svelte +9 -1
- package/dist/context.svelte.d.ts +12 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/marked/index.d.ts +17 -4
- package/dist/marked/index.js +22 -33
- package/dist/marked/marked-alert.d.ts +2 -8
- package/dist/marked/marked-alert.js +14 -20
- package/dist/marked/marked-br.d.ts +2 -8
- package/dist/marked/marked-br.js +15 -21
- package/dist/marked/marked-dl.d.ts +24 -0
- package/dist/marked/marked-dl.js +45 -0
- package/dist/marked/marked-footnotes.d.ts +2 -9
- package/dist/marked/marked-footnotes.js +55 -58
- package/dist/marked/marked-hr.d.ts +2 -8
- package/dist/marked/marked-hr.js +18 -24
- package/dist/marked/marked-list.d.ts +3 -9
- package/dist/marked/marked-list.js +176 -182
- package/dist/marked/marked-math.d.ts +2 -9
- package/dist/marked/marked-math.js +65 -69
- package/dist/marked/marked-subsup.d.ts +3 -9
- package/dist/marked/marked-subsup.js +38 -43
- package/dist/marked/marked-table.d.ts +2 -9
- package/dist/marked/marked-table.js +114 -123
- package/dist/theme.d.ts +36 -0
- package/dist/theme.js +25 -1
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -231,6 +231,13 @@ III. Third item
|
|
|
231
231
|
> [!IMPORTANT]
|
|
232
232
|
> Native support for Github style Alert
|
|
233
233
|
|
|
234
|
+
### Description List
|
|
235
|
+
|
|
236
|
+
: Topic 1 : Description 1
|
|
237
|
+
: **Topic 2** : *Description 2*
|
|
238
|
+
: Topic 3 : Description 3
|
|
239
|
+
: Topic 3 : Description 3
|
|
240
|
+
|
|
234
241
|
## 🔄 Differences from Original React Version
|
|
235
242
|
|
|
236
243
|
This Svelte port maintains feature parity with the original [Streamdown](https://streamdown.ai/) while adapting to Svelte's patterns:
|
|
@@ -261,14 +268,14 @@ This Svelte port maintains feature parity with the original [Streamdown](https:/
|
|
|
261
268
|
|
|
262
269
|
## 🎭 Animation System
|
|
263
270
|
|
|
264
|
-
Streamdown includes
|
|
271
|
+
Streamdown includes an animation system designed specifically for streaming AI content, providing smooth and engaging visual feedback as text appears on screen.
|
|
265
272
|
|
|
266
273
|
### How It Works
|
|
267
274
|
|
|
268
275
|
The animation system works by:
|
|
269
276
|
|
|
270
277
|
1. **Tokenization**: Text is broken down into tokens (words or characters) based on your configuration
|
|
271
|
-
2. **Sequential Animation**: Each token animates
|
|
278
|
+
2. **Sequential Animation**: Each token animates as it is received
|
|
272
279
|
3. **Block-level Animation**: Entire blocks (paragraphs, headings, code blocks) animate as units
|
|
273
280
|
|
|
274
281
|
### Animation Types
|
|
@@ -293,6 +300,8 @@ Text slides down from above while fading in, creating a dynamic downward motion.
|
|
|
293
300
|
|
|
294
301
|
> [!TIP]
|
|
295
302
|
> 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.
|
|
303
|
+
>
|
|
304
|
+
> If using AI SDK mind to smooth stream the content to using word-level tokenization to avoid partial words not being animated.
|
|
296
305
|
|
|
297
306
|
> [!WARNING]
|
|
298
307
|
> 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 +336,15 @@ console.log('Hello from Streamdown!');
|
|
|
327
336
|
let content = `# Custom Components Example
|
|
328
337
|
|
|
329
338
|
This heading will use a custom component!`;
|
|
330
|
-
|
|
331
|
-
// Custom heading component
|
|
332
339
|
</script>
|
|
333
340
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
+
<Streamdown {content}>
|
|
342
|
+
{#snippet heading({ children })}
|
|
343
|
+
<h1 class="mb-4 text-4xl font-bold text-blue-600">
|
|
344
|
+
{@render children()}
|
|
345
|
+
</h1>
|
|
346
|
+
{/snippet}
|
|
347
|
+
</Streamdown>
|
|
341
348
|
```
|
|
342
349
|
|
|
343
350
|
### Security Configuration
|
|
@@ -359,73 +366,32 @@ This heading will use a custom component!`;
|
|
|
359
366
|
|
|
360
367
|
## 📋 Props API
|
|
361
368
|
|
|
362
|
-
| Prop | Type
|
|
363
|
-
| -------------------------- |
|
|
364
|
-
| `content` | `string`
|
|
365
|
-
| `class` | `string`
|
|
366
|
-
| `parseIncompleteMarkdown` | `boolean`
|
|
367
|
-
| `defaultOrigin` | `string`
|
|
368
|
-
| `allowedLinkPrefixes` | `string[]`
|
|
369
|
-
| `allowedImagePrefixes` | `string[]`
|
|
370
|
-
| `skipHtml` | `boolean`
|
|
371
|
-
| `unwrapDisallowed` | `boolean`
|
|
372
|
-
| `urlTransform` | `UrlTransform \| null`
|
|
373
|
-
| `theme` | `DeepPartial<Theme>`
|
|
374
|
-
| `baseTheme` | `'tailwind' \| 'shadcn'`
|
|
375
|
-
| `mergeTheme` | `boolean`
|
|
376
|
-
| `shikiTheme` | `BundledTheme`
|
|
377
|
-
| `mermaidConfig` | `MermaidConfig`
|
|
378
|
-
| `katexConfig` | `KatexOptions \| ((inline: boolean) => KatexOptions)`
|
|
379
|
-
| `animation` | `AnimationConfig`
|
|
380
|
-
| `animation.enabled` | `boolean`
|
|
381
|
-
| `animation.type` | `'fade' \| 'blur' \| 'typewriter' \| 'slideUp' \| 'slideDown'`
|
|
382
|
-
| `animation.duration` | `number`
|
|
383
|
-
| `animation.timingFunction` | `'ease' \| 'ease-in' \| 'ease-out' \| 'ease-in-out' \| 'linear'`
|
|
384
|
-
| `animation.tokenize` | `'word' \| 'char'`
|
|
385
|
-
| `animation.animateOnMount` | `boolean`
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
```
|
|
369
|
+
| Prop | Type | Default | Description |
|
|
370
|
+
| -------------------------- | -------------------------------------------------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
371
|
+
| `content` | `string` | - | **Required.** The markdown content to render |
|
|
372
|
+
| `class` | `string` | - | CSS class names for the wrapper element |
|
|
373
|
+
| `parseIncompleteMarkdown` | `boolean` | `true` | Parse and fix incomplete markdown syntax |
|
|
374
|
+
| `defaultOrigin` | `string` | - | Default origin for relative URLs |
|
|
375
|
+
| `allowedLinkPrefixes` | `string[]` | `['*']` | Allowed URL prefixes for links |
|
|
376
|
+
| `allowedImagePrefixes` | `string[]` | `['*']` | Allowed URL prefixes for images |
|
|
377
|
+
| `skipHtml` | `boolean` | - | Skip HTML parsing entirely |
|
|
378
|
+
| `unwrapDisallowed` | `boolean` | - | Unwrap instead of removing disallowed elements |
|
|
379
|
+
| `urlTransform` | `UrlTransform \| null` | - | Custom URL transformation function |
|
|
380
|
+
| `theme` | `DeepPartial<Theme>` | - | Custom theme overrides |
|
|
381
|
+
| `baseTheme` | `'tailwind' \| 'shadcn'` | `'tailwind'` | Base theme to use before applying overrides |
|
|
382
|
+
| `mergeTheme` | `boolean` | `true` | Whether to merge theme with base theme |
|
|
383
|
+
| `shikiTheme` | `BundledTheme` | `'github-light'` | Code highlighting theme |
|
|
384
|
+
| `mermaidConfig` | `MermaidConfig` | - | Mermaid diagram configuration |
|
|
385
|
+
| `katexConfig` | `KatexOptions \| ((inline: boolean) => KatexOptions)` | - | KaTeX math rendering options |
|
|
386
|
+
| `animation` | `AnimationConfig` | - | Animation configuration for streaming content |
|
|
387
|
+
| `animation.enabled` | `boolean` | `false` | Enable/disable animations |
|
|
388
|
+
| `animation.type` | `'fade' \| 'blur' \| 'typewriter' \| 'slideUp' \| 'slideDown'` | `'blur'` | Animation style for text appearance |
|
|
389
|
+
| `animation.duration` | `number` | `500` | Animation duration in milliseconds |
|
|
390
|
+
| `animation.timingFunction` | `'ease' \| 'ease-in' \| 'ease-out' \| 'ease-in-out' \| 'linear'` | `'ease-in'` | CSS timing function for animations |
|
|
391
|
+
| `animation.tokenize` | `'word' \| 'char'` | `'word'` | Tokenization method for text animations |
|
|
392
|
+
| `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 |
|
|
393
|
+
| `extensions` | `Array<Extension>` | `[]` | Custom marked tokenizers to render special markdown blocks or inline tokens |
|
|
394
|
+
| `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
395
|
|
|
430
396
|
#### All Available Customizable Elements:
|
|
431
397
|
|
|
@@ -435,15 +401,15 @@ This heading uses a custom component with your design system!`;
|
|
|
435
401
|
|
|
436
402
|
**Lists**: `ul`, `ol`, `li`
|
|
437
403
|
|
|
438
|
-
**Code**: `code`, `
|
|
404
|
+
**Code**: `code`, `codeSpan`
|
|
439
405
|
|
|
440
|
-
**Tables**: `table`, `thead`, `tbody`, `tr`, `th`, `td`
|
|
406
|
+
**Tables**: `table`, `thead`, `tbody`, `tr`, `th`, `td`, `tfoot`
|
|
441
407
|
|
|
442
|
-
**Special Content**: `blockquote`, `hr`, `alert`, `mermaid`, `math`, `
|
|
408
|
+
**Special Content**: `blockquote`, `hr`, `alert`, `mermaid`, `math`, `footnoteRef`
|
|
443
409
|
|
|
444
410
|
**Note**: The above elements are **supported by Streamdown** and should be customized using individual props or the theme system.
|
|
445
411
|
|
|
446
|
-
## 🎨
|
|
412
|
+
## 🎨 Theming System
|
|
447
413
|
|
|
448
414
|
### Built-in Themes
|
|
449
415
|
|
|
@@ -548,6 +514,66 @@ Each component supports multiple themeable parts:
|
|
|
548
514
|
|
|
549
515
|
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
516
|
|
|
517
|
+
## 💉 Extensibility
|
|
518
|
+
|
|
519
|
+
Streamdown is extensible through the use of custom extensions.
|
|
520
|
+
|
|
521
|
+
An extension is an object that has a `name`, a `level` and a `tokenizer` function.
|
|
522
|
+
|
|
523
|
+
- `name`: The name of the extension
|
|
524
|
+
- `level`: The level of the extension, can be `block` or `inline`
|
|
525
|
+
- `tokenizer`: The tokenizer function, see [marked](https://github.com/markedjs/marked) for more information
|
|
526
|
+
|
|
527
|
+
To render the extension custom tokens, you can then simply use the `children` snippet.
|
|
528
|
+
|
|
529
|
+
### Example
|
|
530
|
+
|
|
531
|
+
```svelte
|
|
532
|
+
<script lang="ts">
|
|
533
|
+
import { Streamdown, type Extension } from 'svelte-streamdown';
|
|
534
|
+
const markedCollapsible: Extension = {
|
|
535
|
+
name: 'collapsible',
|
|
536
|
+
level: 'block',
|
|
537
|
+
tokenizer(this, src) {
|
|
538
|
+
// Match [detail]...[detail] blocks (case insensitive)
|
|
539
|
+
const detailMatch = src.match(/^\[detail\](.*?)\[detail\]/is);
|
|
540
|
+
|
|
541
|
+
if (detailMatch) {
|
|
542
|
+
const content = detailMatch[1] || '';
|
|
543
|
+
const tokens = this.lexer.blockTokens(content);
|
|
544
|
+
|
|
545
|
+
return {
|
|
546
|
+
type: 'detail',
|
|
547
|
+
raw: detailMatch[0], // The entire matched string including tags
|
|
548
|
+
tokens
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return undefined;
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
</script>
|
|
556
|
+
|
|
557
|
+
<Streamdown
|
|
558
|
+
extensions={[markedCollapsible]}
|
|
559
|
+
content={`
|
|
560
|
+
[detail]
|
|
561
|
+
This is a collapsible **section**
|
|
562
|
+
[detail]`}
|
|
563
|
+
>
|
|
564
|
+
{#snippet children({ token, streamdown, children })}
|
|
565
|
+
{#if token.type === 'detail'}
|
|
566
|
+
<details>
|
|
567
|
+
<summary> Detail </summary>
|
|
568
|
+
<div>
|
|
569
|
+
{@render children()}
|
|
570
|
+
</div>
|
|
571
|
+
</details>
|
|
572
|
+
{/if}
|
|
573
|
+
{/snippet}
|
|
574
|
+
</Streamdown>
|
|
575
|
+
```
|
|
576
|
+
|
|
551
577
|
## 🛠️ Development
|
|
552
578
|
|
|
553
579
|
### 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,40 @@
|
|
|
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 === 'descriptionList'}
|
|
229
|
+
<Slot props={{ children, token }} render={streamdown.snippets.descriptionList}>
|
|
230
|
+
<dl class={streamdown.theme.descriptionList.base}>
|
|
231
|
+
{@render children()}
|
|
232
|
+
</dl>
|
|
233
|
+
</Slot>
|
|
234
|
+
{:else if token.type === 'description'}
|
|
235
|
+
<Slot props={{ children, token }} render={streamdown.snippets.description}>
|
|
236
|
+
<div class={streamdown.theme.description.base}>
|
|
237
|
+
{@render children()}
|
|
238
|
+
</div>
|
|
239
|
+
</Slot>
|
|
240
|
+
{:else if token.type === 'descriptionTerm'}
|
|
241
|
+
<Slot props={{ children, token }} render={streamdown.snippets.descriptionTerm}>
|
|
242
|
+
<dt class={streamdown.theme.descriptionTerm.base}>
|
|
243
|
+
{@render children()}
|
|
244
|
+
</dt>
|
|
245
|
+
</Slot>
|
|
246
|
+
{:else if token.type === 'descriptionDetail'}
|
|
247
|
+
<Slot props={{ children, token }} render={streamdown.snippets.descriptionDetail}>
|
|
248
|
+
<dd class={streamdown.theme.descriptionDetail.base}>
|
|
249
|
+
{@render children()}
|
|
250
|
+
</dd>
|
|
251
|
+
</Slot>
|
|
252
|
+
{:else if token.type === 'def'}
|
|
253
|
+
<!-- TODO This does not seems to be tokenized for now -->
|
|
254
|
+
{:else if token.type === 'escape'}
|
|
255
|
+
<!-- TODO This does not seems to be tokenized for now -->
|
|
256
|
+
{:else if token.type === 'space'}
|
|
257
|
+
<!-- TODO This does not seems to be tokenized for now -->
|
|
258
|
+
{:else if token.type === 'text'}
|
|
259
|
+
{@render children()}
|
|
226
260
|
{:else if token.type === 'html'}
|
|
227
261
|
{#if streamdown.renderHtml}
|
|
228
262
|
{@const content =
|
|
@@ -230,6 +264,6 @@
|
|
|
230
264
|
{@html content}
|
|
231
265
|
{/if}
|
|
232
266
|
{:else}
|
|
233
|
-
<!-- For tokens we don't handle specifically,
|
|
234
|
-
{@render children?.()}
|
|
267
|
+
<!-- For tokens we don't handle specifically, it may certainely be a custom extension to to the children props to handle -->
|
|
268
|
+
{@render streamdown.children?.({ token, children, streamdown })}
|
|
235
269
|
{/if}
|
package/dist/Elements/index.d.ts
CHANGED
package/dist/Elements/index.js
CHANGED
package/dist/Streamdown.svelte
CHANGED
|
@@ -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}>
|
package/dist/context.svelte.d.ts
CHANGED
|
@@ -28,10 +28,11 @@ 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';
|
|
35
|
+
import type { DescriptionDetailToken, DescriptionListToken, DescriptionTermToken, DescriptionToken } from './marked/marked-dl.js';
|
|
35
36
|
type TokenSnippet = {
|
|
36
37
|
heading: Tokens.Heading;
|
|
37
38
|
paragraph: Tokens.Paragraph;
|
|
@@ -62,6 +63,10 @@ type TokenSnippet = {
|
|
|
62
63
|
footnotePopover: FootnoteToken;
|
|
63
64
|
sup: SubSupToken;
|
|
64
65
|
sub: SubSupToken;
|
|
66
|
+
descriptionList: DescriptionListToken;
|
|
67
|
+
description: DescriptionToken;
|
|
68
|
+
descriptionTerm: DescriptionTermToken;
|
|
69
|
+
descriptionDetail: DescriptionDetailToken;
|
|
65
70
|
};
|
|
66
71
|
type PredefinedElements = keyof TokenSnippet;
|
|
67
72
|
export type Snippets = {
|
|
@@ -123,5 +128,11 @@ export type StreamdownProps = {
|
|
|
123
128
|
caution?: Snippet;
|
|
124
129
|
important?: Snippet;
|
|
125
130
|
};
|
|
131
|
+
extensions?: Extension[];
|
|
132
|
+
children?: Snippet<[{
|
|
133
|
+
streamdown: StreamdownContext;
|
|
134
|
+
token: GenericToken;
|
|
135
|
+
children: Snippet;
|
|
136
|
+
}]>;
|
|
126
137
|
} & Partial<Snippets>;
|
|
127
138
|
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 {
|
|
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';
|
package/dist/marked/index.d.ts
CHANGED
|
@@ -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,21 @@ 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
|
-
|
|
10
|
+
import { type DescriptionDetailToken, type DescriptionListToken, type DescriptionTermToken, type DescriptionToken } from './marked-dl.js';
|
|
11
|
+
export type GenericToken = {
|
|
12
|
+
type: string;
|
|
13
|
+
raw: string;
|
|
14
|
+
tokens?: Token[];
|
|
15
|
+
} & Record<string, any>;
|
|
16
|
+
export type Extension = {
|
|
17
|
+
name: string;
|
|
18
|
+
level: 'block' | 'inline';
|
|
19
|
+
tokenizer: (this: TokenizerThis, src: string, tokens: Token[] | TokensList) => GenericToken | undefined;
|
|
20
|
+
start?: TokenizerStartFunction;
|
|
21
|
+
applyInBlockParsing?: boolean;
|
|
22
|
+
};
|
|
23
|
+
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 | DescriptionListToken | DescriptionToken | DescriptionDetailToken | DescriptionTermToken;
|
|
11
24
|
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[];
|
|
25
|
+
export declare const lex: (markdown: string, extensions?: Extension[]) => StreamdownToken[];
|
|
26
|
+
export declare const parseBlocks: (markdown: string, extensions?: Extension[]) => string[];
|
|
14
27
|
export type { MathToken, AlertToken, FootnoteToken, SubSupToken, BrToken, HrToken };
|
package/dist/marked/index.js
CHANGED
|
@@ -2,22 +2,13 @@ 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 {
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
markedFootnote(),
|
|
13
|
-
markedAlert(),
|
|
14
|
-
markedMath(),
|
|
15
|
-
markedSubSup(),
|
|
16
|
-
markedList(),
|
|
17
|
-
markedBr(),
|
|
18
|
-
markedHr()
|
|
19
|
-
];
|
|
20
|
-
const parseExtensions = (...ext) => {
|
|
10
|
+
import { markedDl, markedDt } from './marked-dl.js';
|
|
11
|
+
const parseExtensions = (...extensions) => {
|
|
21
12
|
const options = {
|
|
22
13
|
gfm: true,
|
|
23
14
|
extensions: {
|
|
@@ -29,35 +20,33 @@ const parseExtensions = (...ext) => {
|
|
|
29
20
|
startInline: []
|
|
30
21
|
}
|
|
31
22
|
};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if ('
|
|
35
|
-
|
|
36
|
-
options.extensions.startBlock.push(rest.start);
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
options.extensions.startInline.push(rest.start);
|
|
40
|
-
}
|
|
23
|
+
extensions.forEach(({ level, name, tokenizer, ...rest }) => {
|
|
24
|
+
if ('start' in rest && rest.start) {
|
|
25
|
+
if (level === 'block') {
|
|
26
|
+
options.extensions.startBlock.push(rest.start);
|
|
41
27
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
options.extensions.block.push(tokenizer);
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
options.extensions.inline.push(tokenizer);
|
|
48
|
-
}
|
|
28
|
+
else {
|
|
29
|
+
options.extensions.startInline.push(rest.start);
|
|
49
30
|
}
|
|
50
|
-
}
|
|
31
|
+
}
|
|
32
|
+
if (tokenizer) {
|
|
33
|
+
if (level === 'block') {
|
|
34
|
+
options.extensions.block.push(tokenizer);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
options.extensions.inline.push(tokenizer);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
51
40
|
});
|
|
52
41
|
return options;
|
|
53
42
|
};
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
return new Lexer(parseExtensions(markedHr(), markedTable(), markedFootnote(), markedAlert(), markedMath(), markedSubSup(), markedList(), markedBr()))
|
|
43
|
+
export const lex = (markdown, extensions = []) => {
|
|
44
|
+
return new Lexer(parseExtensions(markedHr, markedTable, ...markedFootnote(), markedAlert, ...markedMath, markedSub, markedSup, markedList, markedBr, markedDl, markedDt, ...extensions))
|
|
57
45
|
.lex(markdown)
|
|
58
46
|
.filter((token) => token.type !== 'space' && token.type !== 'footnote');
|
|
59
47
|
};
|
|
60
|
-
export const parseBlocks = (markdown) => {
|
|
48
|
+
export const parseBlocks = (markdown, extensions = []) => {
|
|
49
|
+
const blockLexer = new Lexer(parseExtensions(markedHr, ...markedFootnote(), markedDl, markedTable, ...extensions.filter(({ level, applyInBlockParsing }) => level === 'block' && applyInBlockParsing)));
|
|
61
50
|
return blockLexer.blockTokens(markdown, []).reduce((acc, block) => {
|
|
62
51
|
if (block.type === 'space' || block.type === 'footnote') {
|
|
63
52
|
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
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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 {
|
|
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
|
|
7
|
-
extensions: Array<{
|
|
8
|
-
name: string;
|
|
9
|
-
level: 'inline';
|
|
10
|
-
tokenizer: TokenizerExtensionFunction;
|
|
11
|
-
}>;
|
|
12
|
-
};
|
|
6
|
+
export declare const markedBr: Extension;
|
package/dist/marked/marked-br.js
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
+
};
|