svelte-streamdown 2.2.6 → 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.
- package/README.md +47 -47
- package/dist/AnimatedText.svelte +6 -8
- package/dist/Block.svelte +2 -2
- package/dist/Elements/Alert.svelte +19 -18
- package/dist/Elements/Code.svelte +20 -17
- package/dist/Elements/Image.svelte +22 -20
- package/dist/Elements/Link.svelte +1 -1
- package/dist/Elements/Math.svelte +4 -7
- package/dist/Elements/Mermaid.svelte +76 -68
- package/dist/Streamdown.svelte +4 -0
- package/dist/context.svelte.d.ts +16 -3
- package/dist/context.svelte.js +1 -3
- package/dist/marked/index.d.ts +4 -2
- package/dist/marked/index.js +10 -6
- package/dist/marked/marked-alert.d.ts +1 -1
- package/dist/marked/marked-alert.js +14 -5
- package/dist/marked/marked-br.d.ts +12 -0
- package/dist/marked/marked-br.js +21 -0
- package/dist/marked/marked-footnotes.d.ts +1 -1
- package/dist/marked/marked-footnotes.js +1 -1
- package/dist/marked/marked-hr.d.ts +12 -0
- package/dist/marked/marked-hr.js +24 -0
- package/dist/marked/marked-list.d.ts +2 -1
- package/dist/marked/marked-list.js +9 -3
- package/dist/marked/marked-math.d.ts +3 -2
- package/dist/marked/marked-math.js +41 -21
- package/dist/marked/marked-subsup.d.ts +1 -1
- package/dist/marked/marked-subsup.js +1 -1
- package/dist/marked/marked-table.d.ts +1 -0
- package/dist/marked/marked-table.js +11 -4
- package/dist/theme.d.ts +9 -1
- package/dist/theme.js +2 -0
- package/dist/utils/hightlighter.svelte.d.ts +15 -13
- package/dist/utils/hightlighter.svelte.js +101 -57
- package/dist/utils/parse-incomplete-markdown.js +352 -157
- package/package.json +4 -2
- package/dist/Elements/Table.svelte +0 -30
- package/dist/Elements/Table.svelte.d.ts +0 -9
package/README.md
CHANGED
|
@@ -20,7 +20,6 @@ Perfect for AI-powered applications that need to stream and render markdown cont
|
|
|
20
20
|
|
|
21
21
|
## ✨ Main Features
|
|
22
22
|
|
|
23
|
-
|
|
24
23
|
### 🔄 Streaming-Optimized
|
|
25
24
|
|
|
26
25
|
- **Incomplete Markdown Parsing**: Handles unterminated blocks gracefully
|
|
@@ -28,12 +27,10 @@ Perfect for AI-powered applications that need to stream and render markdown cont
|
|
|
28
27
|
- **Real-time Updates**: Optimized for dynamic content
|
|
29
28
|
- **Smooth Animations**: Animate tokens and blocks as they are streamed.
|
|
30
29
|
|
|
31
|
-
|
|
32
30
|
### 🔒 Security Hardening
|
|
33
31
|
|
|
34
32
|
- **Image Origin Control**: Whitelist allowed image sources
|
|
35
33
|
- **Link Safety**: Control link destinations
|
|
36
|
-
- **HTML Sanitization**: Prevent XSS attacks
|
|
37
34
|
|
|
38
35
|
### 🎯 Fully Customizable Components & Theming
|
|
39
36
|
|
|
@@ -49,8 +46,9 @@ Beautiful, responsive typography with **built-in Tailwind CSS classes** for head
|
|
|
49
46
|
|
|
50
47
|
### 📝 Extensive Markdown Features
|
|
51
48
|
|
|
52
|
-
Full support for
|
|
53
|
-
|
|
49
|
+
Full support for
|
|
50
|
+
|
|
51
|
+
- Basic text marks: **bold**, _italic_, `code`, ~~Strikethrough~~
|
|
54
52
|
- ~Subscript~ and ^Superscript^
|
|
55
53
|
- [Links](https://svelte-streamdown.beynar.workers.dev/)
|
|
56
54
|
- Headings (H1–H6)
|
|
@@ -58,9 +56,10 @@ Full support for
|
|
|
58
56
|
- Github alert
|
|
59
57
|
- Ordered & unordered lists (including roman, alpha, nested)
|
|
60
58
|
- Task lists ([ ] and [x])
|
|
61
|
-
- Code blocks
|
|
59
|
+
- Code blocks
|
|
62
60
|
- Mermaid diagrams
|
|
63
61
|
- Math $expressions$
|
|
62
|
+
- Escaping currency symbols ($140)
|
|
64
63
|
- Complex tables
|
|
65
64
|
- Footnotes [^1]
|
|
66
65
|
|
|
@@ -134,24 +133,27 @@ pie title Project Time Allocation
|
|
|
134
133
|
|
|
135
134
|
| H1 | H2 | H3 |
|
|
136
135
|
| ------------------------- | --- | --- |
|
|
137
|
-
| This cell spans 3 columns
|
|
136
|
+
| This cell spans 3 columns | | |
|
|
138
137
|
|
|
139
138
|
| Header 1 | Header 2 | Header 3 |
|
|
140
139
|
| ------------------------- | -------- | -------- |
|
|
141
|
-
| This cell spans 2 columns
|
|
140
|
+
| This cell spans 2 columns | | Normal |
|
|
142
141
|
| Normal | Normal | Normal |
|
|
143
142
|
|
|
144
143
|
#### Rowspan
|
|
144
|
+
|
|
145
145
|
| Header 1 | Header 2 |
|
|
146
146
|
| --------------- | -------- |
|
|
147
147
|
| This cell spans | Cell A |
|
|
148
148
|
| two rows ^ | Cell B |
|
|
149
|
+
|
|
149
150
|
#### Footer
|
|
151
|
+
|
|
150
152
|
| Header 1 | Header 2 |
|
|
151
153
|
| --------------- | -------- |
|
|
152
|
-
| Cell B
|
|
154
|
+
| Cell B | Cell A |
|
|
153
155
|
| --------------- | -------- |
|
|
154
|
-
| Footer
|
|
156
|
+
| Footer | |
|
|
155
157
|
|
|
156
158
|
#### Column alignment
|
|
157
159
|
|
|
@@ -161,7 +163,6 @@ pie title Project Time Allocation
|
|
|
161
163
|
|
|
162
164
|
#### Multiple headers and very complex layout
|
|
163
165
|
|
|
164
|
-
|
|
165
166
|
| Product Category ||| Sales Data Q1-Q4 2024 ||||
|
|
166
167
|
| Product | Region || Q1 | Q2 | Q3 | Q4 |
|
|
167
168
|
| Name | Type | Area | Revenue | Revenue | Revenue | Revenue |
|
|
@@ -179,8 +180,8 @@ pie title Project Time Allocation
|
|
|
179
180
|
|
|
180
181
|
#### decimal
|
|
181
182
|
|
|
182
|
-
1. First item
|
|
183
|
-
2. Second item
|
|
183
|
+
1. First item
|
|
184
|
+
2. Second item
|
|
184
185
|
3. Third item
|
|
185
186
|
|
|
186
187
|
#### lower-alpha
|
|
@@ -211,10 +212,9 @@ III. Third item
|
|
|
211
212
|
|
|
212
213
|
1. First level (numeric)
|
|
213
214
|
a. Second level (lowercase alpha)
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
A. Sixth level (uppercase alpha)
|
|
215
|
+
i. Third level (lowercase roman) - Fourth level (bullet)
|
|
216
|
+
I. Fifth level (uppercase roman)
|
|
217
|
+
A. Sixth level (uppercase alpha)
|
|
218
218
|
|
|
219
219
|
2. Back to the first level
|
|
220
220
|
|
|
@@ -226,13 +226,11 @@ III. Third item
|
|
|
226
226
|
- [ ] Nested uncompleted subtask
|
|
227
227
|
- [x] Nested completed subtask
|
|
228
228
|
|
|
229
|
-
|
|
230
229
|
### Alert Support
|
|
231
230
|
|
|
232
231
|
> [!IMPORTANT]
|
|
233
232
|
> Native support for Github style Alert
|
|
234
233
|
|
|
235
|
-
|
|
236
234
|
## 🔄 Differences from Original React Version
|
|
237
235
|
|
|
238
236
|
This Svelte port maintains feature parity with the original [Streamdown](https://streamdown.ai/) while adapting to Svelte's patterns:
|
|
@@ -252,7 +250,7 @@ This Svelte port maintains feature parity with the original [Streamdown](https:/
|
|
|
252
250
|
> [!NOTE]
|
|
253
251
|
> Streamdown comes with **built-in Tailwind CSS classes** for beautiful default styling. To ensure all styles are included in your build, add the following to your `app.css` or main CSS file:
|
|
254
252
|
> This setup is primarily necessary if you're using Tailwind CSS v4's new `@source` directive or if you have aggressive purging enabled in older versions. If you're using standard Tailwind CSS v3+ with default purging, Streamdown's styles should be automatically included when the component is imported and used in your application.
|
|
255
|
-
>
|
|
253
|
+
>
|
|
256
254
|
> This ensures that all Streamdown's default styling is included in your Tailwind build process.
|
|
257
255
|
|
|
258
256
|
```css
|
|
@@ -261,8 +259,6 @@ This Svelte port maintains feature parity with the original [Streamdown](https:/
|
|
|
261
259
|
@source "../node_modules/svelte-streamdown/**/*";
|
|
262
260
|
```
|
|
263
261
|
|
|
264
|
-
|
|
265
|
-
|
|
266
262
|
## 🎭 Animation System
|
|
267
263
|
|
|
268
264
|
Streamdown includes a sophisticated animation system designed specifically for streaming AI content, providing smooth and engaging visual feedback as text appears on screen.
|
|
@@ -280,18 +276,23 @@ The animation system works by:
|
|
|
280
276
|
Choose from five distinct animation styles:
|
|
281
277
|
|
|
282
278
|
#### `fade`
|
|
279
|
+
|
|
283
280
|
A clean fade-in effect where text smoothly appears from transparent to opaque.
|
|
284
281
|
|
|
285
282
|
#### `blur`
|
|
283
|
+
|
|
286
284
|
Text starts slightly blurred and comes into focus while fading in, creating a smooth reveal effect.
|
|
287
285
|
|
|
288
286
|
#### `typewriter`
|
|
287
|
+
|
|
289
288
|
A typewriter effect where text appears character by character, mimicking the look of someone typing.
|
|
290
289
|
|
|
291
290
|
#### `slideUp`
|
|
291
|
+
|
|
292
292
|
Text slides up from below while fading in, creating a dynamic upward motion.
|
|
293
293
|
|
|
294
294
|
#### `slideDown`
|
|
295
|
+
|
|
295
296
|
Text slides down from above while fading in, creating a dynamic downward motion.
|
|
296
297
|
|
|
297
298
|
> [!TIP]
|
|
@@ -300,7 +301,6 @@ Text slides down from above while fading in, creating a dynamic downward motion.
|
|
|
300
301
|
> [!WARNING]
|
|
301
302
|
> 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.
|
|
302
303
|
|
|
303
|
-
|
|
304
304
|
## 🚀 Quick Start
|
|
305
305
|
|
|
306
306
|
### Basic Usage
|
|
@@ -363,30 +363,30 @@ This heading will use a custom component!`;
|
|
|
363
363
|
|
|
364
364
|
## 📋 Props API
|
|
365
365
|
|
|
366
|
-
| Prop
|
|
367
|
-
|
|
|
368
|
-
| `content`
|
|
369
|
-
| `class`
|
|
370
|
-
| `parseIncompleteMarkdown`
|
|
371
|
-
| `defaultOrigin`
|
|
372
|
-
| `allowedLinkPrefixes`
|
|
373
|
-
| `allowedImagePrefixes`
|
|
374
|
-
| `skipHtml`
|
|
375
|
-
| `unwrapDisallowed`
|
|
376
|
-
| `urlTransform`
|
|
377
|
-
| `theme`
|
|
378
|
-
| `baseTheme`
|
|
379
|
-
| `mergeTheme`
|
|
380
|
-
| `shikiTheme`
|
|
381
|
-
| `mermaidConfig`
|
|
382
|
-
| `katexConfig`
|
|
383
|
-
| `animation`
|
|
384
|
-
| `animation.enabled`
|
|
385
|
-
| `animation.type`
|
|
386
|
-
| `animation.duration`
|
|
387
|
-
| `animation.timingFunction
|
|
388
|
-
| `animation.tokenize`
|
|
389
|
-
| `animation.animateOnMount`
|
|
366
|
+
| Prop | Type | Default | Description |
|
|
367
|
+
| -------------------------- | ---------------------------------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
368
|
+
| `content` | `string` | - | **Required.** The markdown content to render |
|
|
369
|
+
| `class` | `string` | - | CSS class names for the wrapper element |
|
|
370
|
+
| `parseIncompleteMarkdown` | `boolean` | `true` | Parse and fix incomplete markdown syntax |
|
|
371
|
+
| `defaultOrigin` | `string` | - | Default origin for relative URLs |
|
|
372
|
+
| `allowedLinkPrefixes` | `string[]` | `['*']` | Allowed URL prefixes for links |
|
|
373
|
+
| `allowedImagePrefixes` | `string[]` | `['*']` | Allowed URL prefixes for images |
|
|
374
|
+
| `skipHtml` | `boolean` | - | Skip HTML parsing entirely |
|
|
375
|
+
| `unwrapDisallowed` | `boolean` | - | Unwrap instead of removing disallowed elements |
|
|
376
|
+
| `urlTransform` | `UrlTransform \| null` | - | Custom URL transformation function |
|
|
377
|
+
| `theme` | `DeepPartial<Theme>` | - | Custom theme overrides |
|
|
378
|
+
| `baseTheme` | `'tailwind' \| 'shadcn'` | `'tailwind'` | Base theme to use before applying overrides |
|
|
379
|
+
| `mergeTheme` | `boolean` | `true` | Whether to merge theme with base theme |
|
|
380
|
+
| `shikiTheme` | `BundledTheme` | `'github-light'` | Code highlighting theme |
|
|
381
|
+
| `mermaidConfig` | `MermaidConfig` | - | Mermaid diagram configuration |
|
|
382
|
+
| `katexConfig` | `KatexOptions \| ((inline: boolean) => KatexOptions)` | - | KaTeX math rendering options |
|
|
383
|
+
| `animation` | `AnimationConfig` | - | Animation configuration for streaming content |
|
|
384
|
+
| `animation.enabled` | `boolean` | `false` | Enable/disable animations |
|
|
385
|
+
| `animation.type` | `'fade' \| 'blur' \| 'typewriter' \| 'slideUp' \| 'slideDown'` | `'blur'` | Animation style for text appearance |
|
|
386
|
+
| `animation.duration` | `number` | `500` | Animation duration in milliseconds |
|
|
387
|
+
| `animation.timingFunction` | `'ease' \| 'ease-in' \| 'ease-out' \| 'ease-in-out' \| 'linear'` | `'ease-in'` | CSS timing function for animations |
|
|
388
|
+
| `animation.tokenize` | `'word' \| 'char'` | `'word'` | Tokenization method for text animations |
|
|
389
|
+
| `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 |
|
|
390
390
|
|
|
391
391
|
### Custom Component Props
|
|
392
392
|
|
package/dist/AnimatedText.svelte
CHANGED
|
@@ -16,18 +16,16 @@
|
|
|
16
16
|
|
|
17
17
|
return text.split(splitRegex).filter((token) => token.length > 0);
|
|
18
18
|
};
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
const tokens = $derived(tokenizeNewContent(text));
|
|
21
21
|
</script>
|
|
22
22
|
|
|
23
23
|
{#if streamdown.isMounted}
|
|
24
|
-
|
|
25
|
-
{
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
{/each}
|
|
30
|
-
</span>
|
|
24
|
+
{#each tokens as token}
|
|
25
|
+
<span style={streamdown.animationTextStyle}>
|
|
26
|
+
{token}
|
|
27
|
+
</span>
|
|
28
|
+
{/each}
|
|
31
29
|
{:else}
|
|
32
30
|
{text}
|
|
33
31
|
{/if}
|
package/dist/Block.svelte
CHANGED
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
block: string;
|
|
14
14
|
insideFootnote?: boolean;
|
|
15
15
|
} = $props();
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
const tokens = $derived(lex(parseIncompleteMarkdown(block.trim())));
|
|
18
18
|
const streamdown = useStreamdown();
|
|
19
19
|
</script>
|
|
20
20
|
|
|
21
21
|
{#snippet renderChildren(tokens: StreamdownToken[])}
|
|
22
|
-
{#each tokens as token
|
|
22
|
+
{#each tokens as token}
|
|
23
23
|
{#if token}
|
|
24
24
|
{@const children = (token as any)?.tokens || []}
|
|
25
25
|
{@const isTextOnlyNode = children.length === 0}
|
|
@@ -27,9 +27,6 @@
|
|
|
27
27
|
children: Snippet;
|
|
28
28
|
token: AlertToken;
|
|
29
29
|
} = $props();
|
|
30
|
-
|
|
31
|
-
const alertType = $derived(token.variant);
|
|
32
|
-
const title = $derived(streamdown.translations?.alert?.[alertType] || alertType);
|
|
33
30
|
</script>
|
|
34
31
|
|
|
35
32
|
<Slot
|
|
@@ -39,23 +36,27 @@
|
|
|
39
36
|
}}
|
|
40
37
|
render={streamdown.snippets.alert}
|
|
41
38
|
>
|
|
42
|
-
<div class={`${streamdown.theme.alert.base} ${streamdown.theme.alert[
|
|
39
|
+
<div class={`${streamdown.theme.alert.base} ${streamdown.theme.alert[token.variant]}`}>
|
|
43
40
|
<div data-alert-title class={streamdown.theme.alert.title}>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
width="24"
|
|
47
|
-
height="24"
|
|
48
|
-
viewBox="0 0 24 24"
|
|
49
|
-
fill="none"
|
|
50
|
-
stroke-width="2"
|
|
51
|
-
stroke-linecap="round"
|
|
52
|
-
stroke-linejoin="round"
|
|
53
|
-
class={streamdown.theme.alert.icon}
|
|
54
|
-
>
|
|
55
|
-
{@html icons[alertType]}
|
|
56
|
-
</svg>
|
|
57
|
-
{title}
|
|
41
|
+
{@render (streamdown.icons?.[token.variant] || icon)()}
|
|
42
|
+
{streamdown.translations?.alert?.[token.variant] || token.variant}
|
|
58
43
|
</div>
|
|
59
44
|
{@render children()}
|
|
60
45
|
</div>
|
|
61
46
|
</Slot>
|
|
47
|
+
|
|
48
|
+
{#snippet icon()}
|
|
49
|
+
<svg
|
|
50
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
51
|
+
width="24"
|
|
52
|
+
height="24"
|
|
53
|
+
viewBox="0 0 24 24"
|
|
54
|
+
fill="none"
|
|
55
|
+
stroke-width="2"
|
|
56
|
+
stroke-linecap="round"
|
|
57
|
+
stroke-linejoin="round"
|
|
58
|
+
class={streamdown.theme.alert.icon}
|
|
59
|
+
>
|
|
60
|
+
{@html icons[token.variant]}
|
|
61
|
+
</svg>
|
|
62
|
+
{/snippet}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { HighlighterManager, languageExtensionMap } from '../utils/hightlighter.svelte.js';
|
|
6
6
|
import type { Tokens } from 'marked';
|
|
7
7
|
import { type ThemedToken } from 'shiki';
|
|
8
|
+
import { untrack } from 'svelte';
|
|
8
9
|
|
|
9
10
|
const {
|
|
10
11
|
token
|
|
@@ -14,12 +15,9 @@
|
|
|
14
15
|
|
|
15
16
|
const streamdown = useStreamdown();
|
|
16
17
|
const highlighter = HighlighterManager.create(streamdown.shikiPreloadThemes || []);
|
|
17
|
-
const theme = $derived(streamdown.shikiTheme);
|
|
18
|
-
let codeContent = $derived(token.text);
|
|
19
|
-
const language = $derived(token.lang || '');
|
|
20
18
|
const copy = useCopy({
|
|
21
19
|
get content() {
|
|
22
|
-
return
|
|
20
|
+
return token.text;
|
|
23
21
|
}
|
|
24
22
|
});
|
|
25
23
|
|
|
@@ -28,48 +26,51 @@
|
|
|
28
26
|
const downloadCode = () => {
|
|
29
27
|
try {
|
|
30
28
|
const extension =
|
|
31
|
-
|
|
32
|
-
? languageExtensionMap[
|
|
29
|
+
token.lang && token.lang in languageExtensionMap
|
|
30
|
+
? languageExtensionMap[token.lang as keyof typeof languageExtensionMap]
|
|
33
31
|
: 'txt';
|
|
34
32
|
const filename = `file.${extension}`;
|
|
35
33
|
const mimeType = 'text/plain';
|
|
36
|
-
save(filename,
|
|
34
|
+
save(filename, token.text, mimeType);
|
|
37
35
|
} catch (error) {
|
|
38
36
|
console.error('Failed to download file:', error);
|
|
39
37
|
}
|
|
40
38
|
};
|
|
41
39
|
|
|
42
40
|
$effect(() => {
|
|
43
|
-
|
|
41
|
+
const theme = streamdown.shikiTheme;
|
|
42
|
+
const lang = token.lang;
|
|
43
|
+
untrack(() => {
|
|
44
|
+
void highlighter.load(theme, lang);
|
|
45
|
+
});
|
|
44
46
|
});
|
|
45
47
|
|
|
46
48
|
const isMounted = streamdown.isMounted;
|
|
47
49
|
</script>
|
|
48
50
|
|
|
49
|
-
<div class={streamdown.theme.code.base}
|
|
50
|
-
<div class={streamdown.theme.code.header}
|
|
51
|
-
<span class={streamdown.theme.code.language}>{
|
|
51
|
+
<div class={streamdown.theme.code.base}>
|
|
52
|
+
<div class={streamdown.theme.code.header}>
|
|
53
|
+
<span class={streamdown.theme.code.language}>{token.lang}</span>
|
|
52
54
|
{#if streamdown.controls.code}
|
|
53
|
-
<div class=
|
|
54
|
-
<!-- Download button snippet -->
|
|
55
|
+
<div class={streamdown.theme.code.buttons}>
|
|
55
56
|
<button
|
|
56
57
|
class={streamdown.theme.code.button}
|
|
57
58
|
onclick={downloadCode}
|
|
58
59
|
title="Download file"
|
|
59
60
|
type="button"
|
|
60
61
|
>
|
|
61
|
-
{@render downloadIcon()}
|
|
62
|
+
{@render (streamdown.icons?.download || downloadIcon)()}
|
|
62
63
|
</button>
|
|
63
64
|
|
|
64
65
|
<button class={streamdown.theme.code.button} onclick={copy.copy} type="button">
|
|
65
|
-
{@render copyIcon()}
|
|
66
|
+
{@render (streamdown.icons?.copy || copyIcon)()}
|
|
66
67
|
</button>
|
|
67
68
|
</div>
|
|
68
69
|
{/if}
|
|
69
70
|
</div>
|
|
70
71
|
<div style="height: fit-content; width: 100%;" class={streamdown.theme.code.container}>
|
|
71
|
-
{#if highlighter.isReady(
|
|
72
|
-
{@const tokens = highlighter.highlightCode(
|
|
72
|
+
{#if highlighter.isReady(streamdown.shikiTheme, token.lang)}
|
|
73
|
+
{@const tokens = highlighter.highlightCode(token.text, token.lang, streamdown.shikiTheme)}
|
|
73
74
|
<pre class={streamdown.theme.code.pre}><code>{@render Tokens(tokens)}</code></pre>
|
|
74
75
|
{:else}
|
|
75
76
|
<pre class={streamdown.theme.code.pre}><code>{@render Skeleton(token.text.split('\n'))}</code
|
|
@@ -93,6 +94,7 @@
|
|
|
93
94
|
</span>
|
|
94
95
|
{/each}
|
|
95
96
|
{/snippet}
|
|
97
|
+
|
|
96
98
|
{#snippet Skeleton(lines: string[])}
|
|
97
99
|
{#each lines as line}
|
|
98
100
|
<span class={streamdown.theme.code.skeleton}>
|
|
@@ -100,6 +102,7 @@
|
|
|
100
102
|
</span>
|
|
101
103
|
{/each}
|
|
102
104
|
{/snippet}
|
|
105
|
+
|
|
103
106
|
{#snippet copyIcon()}
|
|
104
107
|
<svg
|
|
105
108
|
xmlns="http://www.w3.org/2000/svg"
|
|
@@ -20,25 +20,27 @@
|
|
|
20
20
|
);
|
|
21
21
|
</script>
|
|
22
22
|
|
|
23
|
-
{#if
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<
|
|
23
|
+
{#if token.href !== 'streamdown:incomplete-image'}
|
|
24
|
+
{#if transformedUrl}
|
|
25
|
+
<Slot
|
|
26
|
+
props={{
|
|
27
|
+
src: transformedUrl,
|
|
28
|
+
alt: token.text,
|
|
29
|
+
children,
|
|
30
|
+
token
|
|
31
|
+
}}
|
|
32
|
+
render={streamdown.snippets.image}
|
|
33
|
+
>
|
|
34
|
+
<span class={streamdown.theme.image.base}>
|
|
35
|
+
<img class={streamdown.theme.image.image} src={transformedUrl} alt={token.text} />
|
|
36
|
+
</span>
|
|
37
|
+
</Slot>
|
|
38
|
+
{:else}
|
|
39
|
+
<span
|
|
40
|
+
class="inline-block rounded bg-gray-200 px-3 py-1 text-sm text-gray-600 dark:bg-gray-700 dark:text-gray-400"
|
|
41
|
+
title={`Blocked URL: ${token.href}`}
|
|
42
|
+
>
|
|
43
|
+
[Image blocked: {token.text || 'No description'}]
|
|
35
44
|
</span>
|
|
36
|
-
|
|
37
|
-
{:else}
|
|
38
|
-
<span
|
|
39
|
-
class="inline-block rounded bg-gray-200 px-3 py-1 text-sm text-gray-600 dark:bg-gray-700 dark:text-gray-400"
|
|
40
|
-
title={`Blocked URL: ${token.href}`}
|
|
41
|
-
>
|
|
42
|
-
[Image blocked: {token.text || 'No description'}]
|
|
43
|
-
</span>
|
|
45
|
+
{/if}
|
|
44
46
|
{/if}
|
|
@@ -5,16 +5,13 @@
|
|
|
5
5
|
import type { KatexOptions } from 'katex';
|
|
6
6
|
import 'katex/dist/katex.min.css';
|
|
7
7
|
|
|
8
|
-
const streamdown = useStreamdown();
|
|
9
|
-
|
|
10
8
|
const {
|
|
11
9
|
token
|
|
12
10
|
}: {
|
|
13
11
|
token: MathToken;
|
|
14
12
|
} = $props();
|
|
15
13
|
|
|
16
|
-
const
|
|
17
|
-
|
|
14
|
+
const streamdown = useStreamdown();
|
|
18
15
|
let katexInstance = $state<typeof import('katex') | null>(null);
|
|
19
16
|
|
|
20
17
|
onMount(() => {
|
|
@@ -30,9 +27,9 @@
|
|
|
30
27
|
}
|
|
31
28
|
const config: KatexOptions = {
|
|
32
29
|
output: 'html',
|
|
33
|
-
displayMode: !isInline,
|
|
30
|
+
displayMode: !token.isInline,
|
|
34
31
|
...(typeof streamdown.katexConfig === 'function'
|
|
35
|
-
? streamdown.katexConfig(isInline)
|
|
32
|
+
? streamdown.katexConfig(token.isInline)
|
|
36
33
|
: streamdown.katexConfig || {})
|
|
37
34
|
};
|
|
38
35
|
const code = token.text;
|
|
@@ -47,7 +44,7 @@
|
|
|
47
44
|
});
|
|
48
45
|
</script>
|
|
49
46
|
|
|
50
|
-
{#if isInline}
|
|
47
|
+
{#if token.isInline}
|
|
51
48
|
<span
|
|
52
49
|
style={streamdown.isMounted ? streamdown.animationBlockStyle : ''}
|
|
53
50
|
bind:this={inner}
|