svelte-streamdown 2.4.5 → 2.5.1
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 +98 -4
- package/dist/Block.svelte +4 -4
- package/dist/Block.svelte.d.ts +0 -1
- package/dist/Elements/Alert.svelte +4 -1
- package/dist/Elements/Alert.svelte.d.ts +1 -0
- package/dist/Elements/Citation.svelte +260 -0
- package/dist/Elements/Citation.svelte.d.ts +7 -0
- package/dist/Elements/Code.svelte +13 -41
- package/dist/Elements/Code.svelte.d.ts +1 -0
- package/dist/Elements/Element.svelte +65 -33
- package/dist/Elements/FootnoteRef.svelte +41 -89
- package/dist/Elements/Image.svelte +5 -1
- package/dist/Elements/Image.svelte.d.ts +1 -0
- package/dist/Elements/Link.svelte +5 -1
- package/dist/Elements/Link.svelte.d.ts +1 -0
- package/dist/Elements/Math.svelte +10 -2
- package/dist/Elements/Math.svelte.d.ts +1 -0
- package/dist/Elements/Mermaid.svelte +87 -175
- package/dist/Elements/Mermaid.svelte.d.ts +1 -0
- package/dist/Elements/TableDownload.svelte +216 -0
- package/dist/Elements/TableDownload.svelte.d.ts +8 -0
- package/dist/Elements/icons.d.ts +9 -0
- package/dist/Elements/icons.js +175 -0
- package/dist/Elements/popover.svelte.d.ts +8 -0
- package/dist/Elements/popover.svelte.js +40 -0
- package/dist/Elements/stepperState.svelte.d.ts +35 -0
- package/dist/Elements/stepperState.svelte.js +98 -0
- package/dist/Streamdown.svelte +13 -4
- package/dist/Streamdown.svelte.d.ts +23 -2
- package/dist/context.svelte.d.ts +28 -11
- package/dist/context.svelte.js +14 -6
- package/dist/marked/index.d.ts +4 -2
- package/dist/marked/index.js +4 -2
- package/dist/marked/marked-align.d.ts +10 -0
- package/dist/marked/marked-align.js +36 -0
- package/dist/marked/marked-citations.d.ts +8 -0
- package/dist/marked/marked-citations.js +49 -0
- package/dist/marked/marked-table.js +36 -13
- package/dist/theme.d.ts +66 -21
- package/dist/theme.js +47 -17
- package/dist/utils/get.d.ts +1 -0
- package/dist/utils/get.js +25 -0
- package/dist/utils/panzoom.svelte.d.ts +1 -2
- package/dist/utils/panzoom.svelte.js +87 -43
- package/dist/utils/parse-incomplete-markdown.js +70 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@ Full support for
|
|
|
62
62
|
- Escaping currency symbols ($140)
|
|
63
63
|
- Complex tables
|
|
64
64
|
- Footnotes [^1]
|
|
65
|
+
- Inline citations [ref] [ref2]
|
|
65
66
|
|
|
66
67
|
[^1]:
|
|
67
68
|
Reference render in a popover by default.
|
|
@@ -133,11 +134,11 @@ pie title Project Time Allocation
|
|
|
133
134
|
|
|
134
135
|
| H1 | H2 | H3 |
|
|
135
136
|
| ------------------------- | --- | --- |
|
|
136
|
-
| This cell spans 3 columns
|
|
137
|
+
| This cell spans 3 columns |||
|
|
137
138
|
|
|
138
139
|
| Header 1 | Header 2 | Header 3 |
|
|
139
140
|
| ------------------------- | -------- | -------- |
|
|
140
|
-
| This cell spans 2 columns
|
|
141
|
+
| This cell spans 2 columns || Normal |
|
|
141
142
|
| Normal | Normal | Normal |
|
|
142
143
|
|
|
143
144
|
#### Rowspan
|
|
@@ -153,7 +154,7 @@ pie title Project Time Allocation
|
|
|
153
154
|
| --------------- | -------- |
|
|
154
155
|
| Cell B | Cell A |
|
|
155
156
|
| --------------- | -------- |
|
|
156
|
-
| Footer
|
|
157
|
+
| Footer ||
|
|
157
158
|
|
|
158
159
|
#### Column alignment
|
|
159
160
|
|
|
@@ -238,6 +239,98 @@ III. Third item
|
|
|
238
239
|
: Topic 3 : Description 3
|
|
239
240
|
: Topic 3 : Description 3
|
|
240
241
|
|
|
242
|
+
### Citation Support
|
|
243
|
+
|
|
244
|
+
Streamdown supports inline citations that allow you to reference external sources and display them in interactive popovers. Citations work out-of-the-box with a simple object structure and support nested references like this `[cloudflare.website, vercel]` will render into [cloudflare.website, vercel]
|
|
245
|
+
|
|
246
|
+
To enable inline citations, pass a `sources` object as a prop to the `Streamdown` component.
|
|
247
|
+
|
|
248
|
+
#### Basic Usage
|
|
249
|
+
|
|
250
|
+
```svelte
|
|
251
|
+
<script>
|
|
252
|
+
import { Streamdown } from 'svelte-streamdown';
|
|
253
|
+
|
|
254
|
+
let content = `According to [smith2023], AI is advancing rapidly. See also [nested.subsection] for related work.`;
|
|
255
|
+
|
|
256
|
+
let sources = {
|
|
257
|
+
"smith2023": {
|
|
258
|
+
title: "AI Research Paper",
|
|
259
|
+
url: "https://example.com/paper",
|
|
260
|
+
content: "Detailed content of the citation..."
|
|
261
|
+
},
|
|
262
|
+
"nested": {
|
|
263
|
+
"subsection": {
|
|
264
|
+
title: "Nested Citation",
|
|
265
|
+
url: "https://example.com/nested"
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
</script>
|
|
270
|
+
|
|
271
|
+
<Streamdown {content} {sources} />
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
#### Default Citation Structure
|
|
275
|
+
|
|
276
|
+
Citations work with objects containing these properties:
|
|
277
|
+
|
|
278
|
+
- `title (or name or author)`: Display title for the citation
|
|
279
|
+
- `url (or href, url, link or source)`: Link to the source
|
|
280
|
+
- `content (or text, summary or excerpt)`: Rich content to display in carousel mode
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
#### Display Modes
|
|
284
|
+
|
|
285
|
+
Streamdown offers two ways to display citations:
|
|
286
|
+
|
|
287
|
+
- **List View**: Shows all citations in a compact list format
|
|
288
|
+
- **Carousel View** (default): Step-through navigation for multiple citations with full content display
|
|
289
|
+
|
|
290
|
+
You can control the display mode using the `inlineCitationsMode` prop:
|
|
291
|
+
|
|
292
|
+
```svelte
|
|
293
|
+
<!-- List view -->
|
|
294
|
+
<Streamdown {content} {sources} inlineCitationsMode="list" />
|
|
295
|
+
|
|
296
|
+
<!-- Carousel view (default) -->
|
|
297
|
+
<Streamdown {content} {sources} inlineCitationsMode="carousel" />
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
#### Citation Popovers
|
|
301
|
+
|
|
302
|
+
Citations appear as clickable buttons that open popovers when clicked. The popover shows:
|
|
303
|
+
- Source title and URL (when available)
|
|
304
|
+
- Favicon from the source domain
|
|
305
|
+
- Rich content (in carousel mode)
|
|
306
|
+
- Navigation controls (in carousel mode for multiple citations)
|
|
307
|
+
|
|
308
|
+
#### Custom Citation Rendering
|
|
309
|
+
|
|
310
|
+
If your citation data structure doesn't match the default format, you can customize how citations are rendered using `inlineCitationPreview`, `inlineCitationContent` or `inlineCitationPopover` snippets:
|
|
311
|
+
|
|
312
|
+
```svelte
|
|
313
|
+
<Streamdown {content} {sources}>
|
|
314
|
+
{#snippet inlineCitationPreview({ token })}
|
|
315
|
+
<!-- Customize the clickable citation button -->
|
|
316
|
+
{token.keys[0]}
|
|
317
|
+
{/snippet}
|
|
318
|
+
|
|
319
|
+
{#snippet inlineCitationContent({ source, key, token })}
|
|
320
|
+
<!-- Customize content displayed in popover -->
|
|
321
|
+
<div class="custom-content">
|
|
322
|
+
<h4>{source.customTitle || key}</h4>
|
|
323
|
+
<p>{source.customDescription}</p>
|
|
324
|
+
</div>
|
|
325
|
+
{/snippet}
|
|
326
|
+
</Streamdown>
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
These snippets allow you to:
|
|
330
|
+
- **`inlineCitationPreview`**: Customize the content of the clickable button that appears in the text
|
|
331
|
+
- **`inlineCitationContent`**: Customize how individual citation content is displayed within popovers
|
|
332
|
+
- **`inlineCitationPopover`**: Completely customize the list of citations
|
|
333
|
+
|
|
241
334
|
## 🔄 Differences from Original React Version
|
|
242
335
|
|
|
243
336
|
This Svelte port maintains feature parity with the original [Streamdown](https://streamdown.ai/) while adapting to Svelte's patterns:
|
|
@@ -369,6 +462,7 @@ This heading will use a custom component!`;
|
|
|
369
462
|
| Prop | Type | Default | Description |
|
|
370
463
|
| -------------------------- | -------------------------------------------------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
371
464
|
| `content` | `string` | - | **Required.** The markdown content to render |
|
|
465
|
+
| `sources` | `Record<string, any>` | - | Citation data object for inline citations |
|
|
372
466
|
| `class` | `string` | - | CSS class names for the wrapper element |
|
|
373
467
|
| `parseIncompleteMarkdown` | `boolean` | `true` | Parse and fix incomplete markdown syntax |
|
|
374
468
|
| `defaultOrigin` | `string` | - | Default origin for relative URLs |
|
|
@@ -405,7 +499,7 @@ This heading will use a custom component!`;
|
|
|
405
499
|
|
|
406
500
|
**Tables**: `table`, `thead`, `tbody`, `tr`, `th`, `td`, `tfoot`
|
|
407
501
|
|
|
408
|
-
**Special Content**: `blockquote`, `hr`, `alert`, `mermaid`, `math`, `footnoteRef`
|
|
502
|
+
**Special Content**: `blockquote`, `hr`, `alert`, `mermaid`, `math`, `footnoteRef`, `inlineCitation`
|
|
409
503
|
|
|
410
504
|
**Note**: The above elements are **supported by Streamdown** and should be customized using individual props or the theme system.
|
|
411
505
|
|
package/dist/Block.svelte
CHANGED
|
@@ -4,17 +4,17 @@
|
|
|
4
4
|
import { lex, type StreamdownToken } from './marked/index.js';
|
|
5
5
|
import AnimatedText from './AnimatedText.svelte';
|
|
6
6
|
import { useStreamdown } from './context.svelte.js';
|
|
7
|
+
import { getContext } from 'svelte';
|
|
7
8
|
|
|
8
9
|
let {
|
|
9
|
-
block
|
|
10
|
-
insideFootnote = false
|
|
10
|
+
block
|
|
11
11
|
}: {
|
|
12
12
|
block: string;
|
|
13
|
-
insideFootnote?: boolean;
|
|
14
13
|
} = $props();
|
|
15
14
|
|
|
16
15
|
const streamdown = useStreamdown();
|
|
17
16
|
const tokens = $derived(lex(parseIncompleteMarkdown(block.trim()), streamdown.extensions));
|
|
17
|
+
const insidePopover = getContext('POPOVER');
|
|
18
18
|
</script>
|
|
19
19
|
|
|
20
20
|
{#snippet renderChildren(tokens: StreamdownToken[])}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
{@const isTextOnlyNode = children.length === 0}
|
|
25
25
|
<Element {token}>
|
|
26
26
|
{#if isTextOnlyNode}
|
|
27
|
-
{#if streamdown.animation.enabled && !
|
|
27
|
+
{#if streamdown.animation.enabled && !insidePopover}
|
|
28
28
|
<AnimatedText text={'text' in token ? token.text : ''} />
|
|
29
29
|
{:else}
|
|
30
30
|
{'text' in token ? token.text : ''}
|
package/dist/Block.svelte.d.ts
CHANGED
|
@@ -22,8 +22,10 @@
|
|
|
22
22
|
|
|
23
23
|
const {
|
|
24
24
|
children,
|
|
25
|
-
token
|
|
25
|
+
token,
|
|
26
|
+
id
|
|
26
27
|
}: {
|
|
28
|
+
id: string;
|
|
27
29
|
children: Snippet;
|
|
28
30
|
token: AlertToken;
|
|
29
31
|
} = $props();
|
|
@@ -37,6 +39,7 @@
|
|
|
37
39
|
render={streamdown.snippets.alert}
|
|
38
40
|
>
|
|
39
41
|
<div
|
|
42
|
+
data-streamdown-alert={id}
|
|
40
43
|
style={streamdown.isMounted ? streamdown.animationBlockStyle : ''}
|
|
41
44
|
class={`${streamdown.theme.alert.base} ${streamdown.theme.alert[token.variant]}`}
|
|
42
45
|
>
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useStreamdown } from '../context.svelte.js';
|
|
3
|
+
import Slot from './Slot.svelte';
|
|
4
|
+
import { useClickOutside } from '../utils/useClickOutside.svelte.js';
|
|
5
|
+
import { scale } from 'svelte/transition';
|
|
6
|
+
import Block from '../Block.svelte';
|
|
7
|
+
import { useKeyDown } from '../utils/useKeyDown.svelte.js';
|
|
8
|
+
import type { CitationToken } from '../marked/marked-citations.js';
|
|
9
|
+
import { StepperState } from './stepperState.svelte.js';
|
|
10
|
+
import { Popover } from './popover.svelte.js';
|
|
11
|
+
import { get } from '../utils/get.js';
|
|
12
|
+
import { chevronLeft, chevronRight } from './icons.js';
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
token
|
|
16
|
+
}: {
|
|
17
|
+
token: CitationToken;
|
|
18
|
+
} = $props();
|
|
19
|
+
const streamdown = useStreamdown();
|
|
20
|
+
const id = $props.id();
|
|
21
|
+
const popover = new Popover();
|
|
22
|
+
|
|
23
|
+
const citationWithSources = $derived.by(() => {
|
|
24
|
+
return token.keys.reduce((acc, key) => {
|
|
25
|
+
const source = get<Record<string, any>>(streamdown.sources, key);
|
|
26
|
+
const safeUrl = (url: string | null) => {
|
|
27
|
+
if (url == null) return null;
|
|
28
|
+
try {
|
|
29
|
+
return new URL(url);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
if (source) {
|
|
35
|
+
const url = safeUrl(source.url ?? source.href ?? source.link ?? source.source);
|
|
36
|
+
acc.push({
|
|
37
|
+
key,
|
|
38
|
+
title: source.title ?? source.name ?? source.author ?? null,
|
|
39
|
+
url,
|
|
40
|
+
host: url ? url.host.replace('www.', '') : null,
|
|
41
|
+
favicon: url ? `https://www.google.com/s2/favicons?domain=${url.hostname}&sz=128` : null,
|
|
42
|
+
content: source.content ?? source.text ?? source.summary ?? source.excerpt ?? null,
|
|
43
|
+
source
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return acc;
|
|
47
|
+
}, [] as CitationWithSource[]);
|
|
48
|
+
});
|
|
49
|
+
useKeyDown({
|
|
50
|
+
keys: ['Escape'],
|
|
51
|
+
get isActive() {
|
|
52
|
+
return popover.isOpen;
|
|
53
|
+
},
|
|
54
|
+
callback: () => {
|
|
55
|
+
popover.isOpen = false;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
const clickOutside = useClickOutside({
|
|
59
|
+
get isActive() {
|
|
60
|
+
return popover.isOpen;
|
|
61
|
+
},
|
|
62
|
+
callback: () => {
|
|
63
|
+
popover.isOpen = false;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const stepper = new StepperState({
|
|
68
|
+
get items() {
|
|
69
|
+
return citationWithSources;
|
|
70
|
+
},
|
|
71
|
+
keyFramesOptions: { duration: 200, easing: 'ease-in-out', fill: 'forwards' }
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
type CitationWithSource = Record<string, any> & {
|
|
75
|
+
key: string;
|
|
76
|
+
title: string | null;
|
|
77
|
+
url: URL | null;
|
|
78
|
+
favicon: string | null;
|
|
79
|
+
content: string | null;
|
|
80
|
+
source: Record<string, any>;
|
|
81
|
+
};
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
{#if popover.isOpen}
|
|
85
|
+
<dialog
|
|
86
|
+
data-streamdown-citation-popover={id}
|
|
87
|
+
id={'citation-popover-' + id}
|
|
88
|
+
aria-modal="false"
|
|
89
|
+
transition:scale|global={{ start: 0.95, duration: 200 }}
|
|
90
|
+
{@attach clickOutside.attachment}
|
|
91
|
+
{@attach popover.popoverAttachment}
|
|
92
|
+
open
|
|
93
|
+
class={`${streamdown.theme.components.popover}`}
|
|
94
|
+
style:max-width="400px"
|
|
95
|
+
>
|
|
96
|
+
<Slot
|
|
97
|
+
props={{
|
|
98
|
+
token,
|
|
99
|
+
isOpen: popover.isOpen
|
|
100
|
+
}}
|
|
101
|
+
render={streamdown.snippets.inlineCitationPopover}
|
|
102
|
+
>
|
|
103
|
+
{#if streamdown.inlineCitationsMode === 'carousel'}
|
|
104
|
+
{@render carouselView()}
|
|
105
|
+
{:else}
|
|
106
|
+
{@render listView()}
|
|
107
|
+
{/if}
|
|
108
|
+
</Slot>
|
|
109
|
+
</dialog>
|
|
110
|
+
{/if}
|
|
111
|
+
|
|
112
|
+
{#snippet listView()}
|
|
113
|
+
<div class={streamdown.theme.inlineCitation.list.base}>
|
|
114
|
+
{#each citationWithSources as { favicon, key, source, title, url }}
|
|
115
|
+
<Slot render={streamdown.snippets.inlineCitationContent} props={{ source, key, token }}>
|
|
116
|
+
<a
|
|
117
|
+
class={streamdown.theme.inlineCitation.list.item}
|
|
118
|
+
href={url?.toString()}
|
|
119
|
+
target="_blank"
|
|
120
|
+
rel="noopener noreferrer"
|
|
121
|
+
>
|
|
122
|
+
{#if title}
|
|
123
|
+
<span class={streamdown.theme.inlineCitation.list.title}>{title}</span>
|
|
124
|
+
{/if}
|
|
125
|
+
{#if url}
|
|
126
|
+
<span class={streamdown.theme.inlineCitation.list.url}>
|
|
127
|
+
{#if favicon}
|
|
128
|
+
<img
|
|
129
|
+
src={favicon}
|
|
130
|
+
alt={title || url.host}
|
|
131
|
+
class={streamdown.theme.inlineCitation.list.favicon}
|
|
132
|
+
/>
|
|
133
|
+
{/if}
|
|
134
|
+
{url.host}{url.pathname === '/' ? '' : url.pathname}
|
|
135
|
+
</span>
|
|
136
|
+
{/if}
|
|
137
|
+
</a>
|
|
138
|
+
</Slot>
|
|
139
|
+
{/each}
|
|
140
|
+
</div>
|
|
141
|
+
{/snippet}
|
|
142
|
+
|
|
143
|
+
{#snippet carouselView()}
|
|
144
|
+
{#if citationWithSources.length > 1}
|
|
145
|
+
<div class={streamdown.theme.inlineCitation.carousel.header}>
|
|
146
|
+
<div class={streamdown.theme.inlineCitation.carousel.stepCounter}>
|
|
147
|
+
{stepper.activeStep + 1}
|
|
148
|
+
/ {citationWithSources.length}
|
|
149
|
+
</div>
|
|
150
|
+
<div class={streamdown.theme.inlineCitation.carousel.buttons}>
|
|
151
|
+
<button
|
|
152
|
+
disabled={!stepper.canGoPrevious}
|
|
153
|
+
class={streamdown.theme.components.button}
|
|
154
|
+
onclick={() => stepper.previous()}
|
|
155
|
+
>
|
|
156
|
+
{@render (streamdown.icons?.chevronLeft || chevronLeft)()}
|
|
157
|
+
</button>
|
|
158
|
+
<button
|
|
159
|
+
disabled={!stepper.canGoNext}
|
|
160
|
+
class={streamdown.theme.components.button}
|
|
161
|
+
onclick={() => stepper.next()}
|
|
162
|
+
>
|
|
163
|
+
{@render (streamdown.icons?.chevronRight || chevronRight)()}
|
|
164
|
+
</button>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
{/if}
|
|
168
|
+
<div
|
|
169
|
+
use:stepper.scroller
|
|
170
|
+
style:height={Number.isFinite(stepper.stepHeights[stepper.activeStep])
|
|
171
|
+
? `${stepper.stepHeights[stepper.activeStep]}px`
|
|
172
|
+
: 'auto'}
|
|
173
|
+
style:overflow="hidden"
|
|
174
|
+
style:position="relative"
|
|
175
|
+
style:transition-duration="200ms"
|
|
176
|
+
style:transition-timing-function="ease-in-out"
|
|
177
|
+
aria-label="Citations-${id}"
|
|
178
|
+
>
|
|
179
|
+
<div
|
|
180
|
+
bind:this={stepper.stepContainer}
|
|
181
|
+
style:display="grid"
|
|
182
|
+
style:transition-duration={`${200}ms`}
|
|
183
|
+
style:width="100%"
|
|
184
|
+
style:grid-template-columns="repeat({citationWithSources.length}, 100%)"
|
|
185
|
+
>
|
|
186
|
+
{#each citationWithSources as { favicon, key, source, title, url, content }, index}
|
|
187
|
+
<div
|
|
188
|
+
bind:offsetHeight={stepper.stepHeights[index]}
|
|
189
|
+
data-step={index}
|
|
190
|
+
tabindex={stepper.activeStep === index ? 0 : -1}
|
|
191
|
+
inert={stepper.activeStep !== index}
|
|
192
|
+
role="tabpanel"
|
|
193
|
+
style:height="fit-content"
|
|
194
|
+
style:width="100%"
|
|
195
|
+
style:flex-grow="1"
|
|
196
|
+
aria-label="Citation-${id}"
|
|
197
|
+
>
|
|
198
|
+
<Slot render={streamdown.snippets.inlineCitationContent} props={{ source, key, token }}>
|
|
199
|
+
{#if url || title}
|
|
200
|
+
<div class="mb-4">
|
|
201
|
+
{#if title}
|
|
202
|
+
<div class={streamdown.theme.inlineCitation.carousel.title}>
|
|
203
|
+
{title}
|
|
204
|
+
</div>
|
|
205
|
+
{/if}
|
|
206
|
+
{#if url}
|
|
207
|
+
<a
|
|
208
|
+
href={url.toString()}
|
|
209
|
+
target="_blank"
|
|
210
|
+
rel="noopener noreferrer"
|
|
211
|
+
class={streamdown.theme.inlineCitation.carousel.url}
|
|
212
|
+
>
|
|
213
|
+
{#if favicon}
|
|
214
|
+
<img
|
|
215
|
+
src={favicon}
|
|
216
|
+
alt={title || url.host}
|
|
217
|
+
class={streamdown.theme.inlineCitation.carousel.favicon}
|
|
218
|
+
/>
|
|
219
|
+
{/if}
|
|
220
|
+
<!-- Skip search params -->
|
|
221
|
+
{url.host}{url.pathname === '/' ? '' : url.pathname}
|
|
222
|
+
</a>
|
|
223
|
+
{/if}
|
|
224
|
+
</div>
|
|
225
|
+
{/if}
|
|
226
|
+
{#if content}
|
|
227
|
+
<Block block={content} />
|
|
228
|
+
{/if}
|
|
229
|
+
</Slot>
|
|
230
|
+
</div>
|
|
231
|
+
{/each}
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
{/snippet}
|
|
235
|
+
|
|
236
|
+
{#if citationWithSources.length > 0}
|
|
237
|
+
<button
|
|
238
|
+
data-streamdown-citation-preview={id}
|
|
239
|
+
style={streamdown.animationBlockStyle}
|
|
240
|
+
bind:this={popover.reference}
|
|
241
|
+
class={streamdown.theme.inlineCitation.preview}
|
|
242
|
+
onclick={() => (popover.isOpen = !popover.isOpen)}
|
|
243
|
+
aria-expanded={popover.isOpen}
|
|
244
|
+
aria-haspopup="dialog"
|
|
245
|
+
aria-controls={'citation-popover-' + id}
|
|
246
|
+
{@attach clickOutside.attachment}
|
|
247
|
+
>
|
|
248
|
+
<Slot
|
|
249
|
+
render={streamdown.snippets.inlineCitationPreview}
|
|
250
|
+
props={{
|
|
251
|
+
token
|
|
252
|
+
}}
|
|
253
|
+
>
|
|
254
|
+
{citationWithSources[0]?.url?.host ??
|
|
255
|
+
citationWithSources[0]?.title ??
|
|
256
|
+
citationWithSources[0]?.key}
|
|
257
|
+
{citationWithSources.length > 1 ? ` +${citationWithSources.length - 1}` : ''}
|
|
258
|
+
</Slot>
|
|
259
|
+
</button>
|
|
260
|
+
{/if}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CitationToken } from '../marked/marked-citations.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
token: CitationToken;
|
|
4
|
+
};
|
|
5
|
+
declare const Citation: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
6
|
+
type Citation = ReturnType<typeof Citation>;
|
|
7
|
+
export default Citation;
|
|
@@ -6,11 +6,14 @@
|
|
|
6
6
|
import type { Tokens } from 'marked';
|
|
7
7
|
import { type ThemedToken } from 'shiki';
|
|
8
8
|
import { untrack } from 'svelte';
|
|
9
|
+
import { checkIcon, copyIcon, downloadIcon } from './icons.js';
|
|
9
10
|
|
|
10
11
|
const {
|
|
11
|
-
token
|
|
12
|
+
token,
|
|
13
|
+
id
|
|
12
14
|
}: {
|
|
13
15
|
token: Tokens.Code;
|
|
16
|
+
id: string;
|
|
14
17
|
} = $props();
|
|
15
18
|
|
|
16
19
|
const streamdown = useStreamdown();
|
|
@@ -48,6 +51,7 @@
|
|
|
48
51
|
</script>
|
|
49
52
|
|
|
50
53
|
<div
|
|
54
|
+
data-streamdown-code={id}
|
|
51
55
|
style={streamdown.isMounted ? streamdown.animationBlockStyle : ''}
|
|
52
56
|
class={streamdown.theme.code.base}
|
|
53
57
|
>
|
|
@@ -56,16 +60,20 @@
|
|
|
56
60
|
{#if streamdown.controls.code}
|
|
57
61
|
<div class={streamdown.theme.code.buttons}>
|
|
58
62
|
<button
|
|
59
|
-
class={streamdown.theme.
|
|
63
|
+
class={streamdown.theme.components.button}
|
|
60
64
|
onclick={downloadCode}
|
|
61
|
-
title="Download
|
|
65
|
+
title="Download code"
|
|
62
66
|
type="button"
|
|
63
67
|
>
|
|
64
68
|
{@render (streamdown.icons?.download || downloadIcon)()}
|
|
65
69
|
</button>
|
|
66
70
|
|
|
67
|
-
<button class={streamdown.theme.
|
|
68
|
-
{
|
|
71
|
+
<button class={streamdown.theme.components.button} onclick={copy.copy} type="button">
|
|
72
|
+
{#if copy.isCopied}
|
|
73
|
+
{@render (streamdown.icons?.check || checkIcon)()}
|
|
74
|
+
{:else}
|
|
75
|
+
{@render (streamdown.icons?.copy || copyIcon)()}
|
|
76
|
+
{/if}
|
|
69
77
|
</button>
|
|
70
78
|
</div>
|
|
71
79
|
{/if}
|
|
@@ -107,39 +115,3 @@
|
|
|
107
115
|
</span>
|
|
108
116
|
{/each}
|
|
109
117
|
{/snippet}
|
|
110
|
-
|
|
111
|
-
{#snippet copyIcon()}
|
|
112
|
-
<svg
|
|
113
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
114
|
-
width="100%"
|
|
115
|
-
height="100%"
|
|
116
|
-
viewBox="0 0 24 24"
|
|
117
|
-
fill="none"
|
|
118
|
-
stroke="currentColor"
|
|
119
|
-
stroke-width="2"
|
|
120
|
-
stroke-linecap="round"
|
|
121
|
-
stroke-linejoin="round"
|
|
122
|
-
class="lucide lucide-clipboard-icon lucide-clipboard"
|
|
123
|
-
><rect width="8" height="4" x="8" y="2" rx="1" ry="1" /><path
|
|
124
|
-
d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"
|
|
125
|
-
/></svg
|
|
126
|
-
>
|
|
127
|
-
{/snippet}
|
|
128
|
-
|
|
129
|
-
{#snippet downloadIcon()}
|
|
130
|
-
<svg
|
|
131
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
132
|
-
width="100%"
|
|
133
|
-
height="100%"
|
|
134
|
-
viewBox="0 0 24 24"
|
|
135
|
-
fill="none"
|
|
136
|
-
stroke="currentColor"
|
|
137
|
-
stroke-width="2"
|
|
138
|
-
stroke-linecap="round"
|
|
139
|
-
stroke-linejoin="round"
|
|
140
|
-
class="lucide lucide-download-icon lucide-download"
|
|
141
|
-
><path d="M12 15V3" /><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><path
|
|
142
|
-
d="m7 10 5 5 5-5"
|
|
143
|
-
/></svg
|
|
144
|
-
>
|
|
145
|
-
{/snippet}
|