svelte-streamdown 2.5.0 → 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/dist/Elements/Alert.svelte +4 -1
- package/dist/Elements/Alert.svelte.d.ts +1 -0
- package/dist/Elements/Citation.svelte +3 -28
- package/dist/Elements/Code.svelte +11 -37
- package/dist/Elements/Code.svelte.d.ts +1 -0
- package/dist/Elements/Element.svelte +57 -32
- package/dist/Elements/FootnoteRef.svelte +2 -0
- 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 +37 -90
- 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/Streamdown.svelte +3 -1
- package/dist/context.svelte.d.ts +3 -0
- package/dist/marked/index.d.ts +1 -1
- package/dist/theme.js +2 -2
- package/dist/utils/panzoom.svelte.js +14 -1
- package/package.json +1 -1
|
@@ -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
|
>
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { StepperState } from './stepperState.svelte.js';
|
|
10
10
|
import { Popover } from './popover.svelte.js';
|
|
11
11
|
import { get } from '../utils/get.js';
|
|
12
|
+
import { chevronLeft, chevronRight } from './icons.js';
|
|
12
13
|
|
|
13
14
|
const {
|
|
14
15
|
token
|
|
@@ -82,6 +83,7 @@
|
|
|
82
83
|
|
|
83
84
|
{#if popover.isOpen}
|
|
84
85
|
<dialog
|
|
86
|
+
data-streamdown-citation-popover={id}
|
|
85
87
|
id={'citation-popover-' + id}
|
|
86
88
|
aria-modal="false"
|
|
87
89
|
transition:scale|global={{ start: 0.95, duration: 200 }}
|
|
@@ -231,36 +233,9 @@
|
|
|
231
233
|
</div>
|
|
232
234
|
{/snippet}
|
|
233
235
|
|
|
234
|
-
{#snippet chevronRight()}
|
|
235
|
-
<svg
|
|
236
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
237
|
-
width="100%"
|
|
238
|
-
height="100%"
|
|
239
|
-
viewBox="0 0 24 24"
|
|
240
|
-
fill="none"
|
|
241
|
-
stroke="currentColor"
|
|
242
|
-
stroke-width="2"
|
|
243
|
-
stroke-linecap="round"
|
|
244
|
-
stroke-linejoin="round"><path d="m9 18 6-6-6-6" /></svg
|
|
245
|
-
>
|
|
246
|
-
{/snippet}
|
|
247
|
-
|
|
248
|
-
{#snippet chevronLeft()}
|
|
249
|
-
<svg
|
|
250
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
251
|
-
width="100%"
|
|
252
|
-
height="100%"
|
|
253
|
-
viewBox="0 0 24 24"
|
|
254
|
-
fill="none"
|
|
255
|
-
stroke="currentColor"
|
|
256
|
-
stroke-width="2"
|
|
257
|
-
stroke-linecap="round"
|
|
258
|
-
stroke-linejoin="round"><path d="m15 18-6-6 6-6" /></svg
|
|
259
|
-
>
|
|
260
|
-
{/snippet}
|
|
261
|
-
|
|
262
236
|
{#if citationWithSources.length > 0}
|
|
263
237
|
<button
|
|
238
|
+
data-streamdown-citation-preview={id}
|
|
264
239
|
style={streamdown.animationBlockStyle}
|
|
265
240
|
bind:this={popover.reference}
|
|
266
241
|
class={streamdown.theme.inlineCitation.preview}
|
|
@@ -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
|
>
|
|
@@ -58,14 +62,18 @@
|
|
|
58
62
|
<button
|
|
59
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
71
|
<button class={streamdown.theme.components.button} onclick={copy.copy} type="button">
|
|
68
|
-
{
|
|
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,37 +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
|
-
><rect width="8" height="4" x="8" y="2" rx="1" ry="1" /><path
|
|
123
|
-
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"
|
|
124
|
-
/></svg
|
|
125
|
-
>
|
|
126
|
-
{/snippet}
|
|
127
|
-
|
|
128
|
-
{#snippet downloadIcon()}
|
|
129
|
-
<svg
|
|
130
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
131
|
-
width="100%"
|
|
132
|
-
height="100%"
|
|
133
|
-
viewBox="0 0 24 24"
|
|
134
|
-
fill="none"
|
|
135
|
-
stroke="currentColor"
|
|
136
|
-
stroke-width="2"
|
|
137
|
-
stroke-linecap="round"
|
|
138
|
-
stroke-linejoin="round"
|
|
139
|
-
><path d="M12 15V3" /><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><path
|
|
140
|
-
d="m7 10 5 5 5-5"
|
|
141
|
-
/></svg
|
|
142
|
-
>
|
|
143
|
-
{/snippet}
|
|
@@ -11,11 +11,13 @@
|
|
|
11
11
|
import { useStreamdown } from '../context.svelte.js';
|
|
12
12
|
import FootnoteRef from './FootnoteRef.svelte';
|
|
13
13
|
import Citation from './Citation.svelte';
|
|
14
|
+
import TableDownload from './TableDownload.svelte';
|
|
14
15
|
let { token, children }: { token: StreamdownToken; children: Snippet } = $props();
|
|
15
16
|
const streamdown = useStreamdown();
|
|
16
17
|
|
|
17
18
|
// Only apply animation on block level elements. Leaves text elements to be animated by their text children.
|
|
18
19
|
const style = $derived(streamdown.isMounted ? streamdown.animationBlockStyle : '');
|
|
20
|
+
const id = $props.id();
|
|
19
21
|
</script>
|
|
20
22
|
|
|
21
23
|
{#if token.type === 'heading'}
|
|
@@ -27,67 +29,71 @@
|
|
|
27
29
|
render={streamdown.snippets.heading}
|
|
28
30
|
>
|
|
29
31
|
{#if token.depth === 1}
|
|
30
|
-
<h1 {style} class={streamdown.theme[`h${token.depth}`].base}>
|
|
32
|
+
<h1 data-streamdown-heading-1={id} {style} class={streamdown.theme[`h${token.depth}`].base}>
|
|
31
33
|
{@render children()}
|
|
32
34
|
</h1>
|
|
33
35
|
{:else if token.depth === 2}
|
|
34
|
-
<h2 {style} class={streamdown.theme[`h${token.depth}`].base}>
|
|
36
|
+
<h2 data-streamdown-heading-2={id} {style} class={streamdown.theme[`h${token.depth}`].base}>
|
|
35
37
|
{@render children()}
|
|
36
38
|
</h2>
|
|
37
39
|
{:else if token.depth === 3}
|
|
38
|
-
<h3 {style} class={streamdown.theme[`h${token.depth}`].base}>
|
|
40
|
+
<h3 data-streamdown-heading-3={id} {style} class={streamdown.theme[`h${token.depth}`].base}>
|
|
39
41
|
{@render children()}
|
|
40
42
|
</h3>
|
|
41
43
|
{:else if token.depth === 4}
|
|
42
|
-
<h4 {style} class={streamdown.theme[`h${token.depth}`].base}>
|
|
44
|
+
<h4 data-streamdown-heading-4={id} {style} class={streamdown.theme[`h${token.depth}`].base}>
|
|
43
45
|
{@render children()}
|
|
44
46
|
</h4>
|
|
45
47
|
{:else if token.depth === 5}
|
|
46
|
-
<h5 {style} class={streamdown.theme[`h${token.depth}`].base}>
|
|
48
|
+
<h5 data-streamdown-heading-5={id} {style} class={streamdown.theme[`h${token.depth}`].base}>
|
|
47
49
|
{@render children()}
|
|
48
50
|
</h5>
|
|
49
51
|
{:else if token.depth === 6}
|
|
50
|
-
<h6 {style} class={streamdown.theme[`h${token.depth}`].base}>
|
|
52
|
+
<h6 data-streamdown-heading-6={id} {style} class={streamdown.theme[`h${token.depth}`].base}>
|
|
51
53
|
{@render children()}
|
|
52
54
|
</h6>
|
|
53
55
|
{/if}
|
|
54
56
|
</Slot>
|
|
55
57
|
{:else if token.type === 'paragraph'}
|
|
56
58
|
<Slot props={{ children, token }} render={streamdown.snippets.paragraph}>
|
|
57
|
-
<p {style} class={streamdown.theme.paragraph.base}>
|
|
59
|
+
<p data-streamdown-paragraph={id} {style} class={streamdown.theme.paragraph.base}>
|
|
58
60
|
{@render children()}
|
|
59
61
|
</p>
|
|
60
62
|
</Slot>
|
|
61
63
|
{:else if token.type === 'blockquote'}
|
|
62
64
|
<Slot props={{ children, token }} render={streamdown.snippets.blockquote}>
|
|
63
|
-
<blockquote {style} class={streamdown.theme.blockquote.base}>
|
|
65
|
+
<blockquote data-streamdown-blockquote={id} {style} class={streamdown.theme.blockquote.base}>
|
|
64
66
|
{@render children()}
|
|
65
67
|
</blockquote>
|
|
66
68
|
</Slot>
|
|
67
69
|
{:else if token.type === 'code' && token.lang === 'mermaid'}
|
|
68
70
|
<Slot props={{ children, token }} render={streamdown.snippets.code}>
|
|
69
|
-
<Mermaid {token} />
|
|
71
|
+
<Mermaid {id} {token} />
|
|
70
72
|
</Slot>
|
|
71
73
|
{:else if token.type === 'code'}
|
|
72
74
|
<Slot props={{ children, token }} render={streamdown.snippets.code}>
|
|
73
|
-
<Code {token} />
|
|
75
|
+
<Code {id} {token} />
|
|
74
76
|
</Slot>
|
|
75
77
|
{:else if token.type === 'codespan'}
|
|
76
78
|
<Slot props={{ children, token }} render={streamdown.snippets.codespan}>
|
|
77
|
-
<code class={streamdown.theme.codespan.base}>
|
|
79
|
+
<code data-streamdown-codespan={id} class={streamdown.theme.codespan.base}>
|
|
78
80
|
{@render children()}
|
|
79
81
|
</code>
|
|
80
82
|
</Slot>
|
|
81
83
|
{:else if token.type === 'list'}
|
|
82
84
|
{#if token.ordered}
|
|
83
85
|
<Slot props={{ children, token }} render={streamdown.snippets.ol}>
|
|
84
|
-
<ol
|
|
86
|
+
<ol
|
|
87
|
+
data-streamdown-ol={id}
|
|
88
|
+
style:list-style-type={token.listType}
|
|
89
|
+
class={streamdown.theme.ol.base}
|
|
90
|
+
>
|
|
85
91
|
{@render children()}
|
|
86
92
|
</ol>
|
|
87
93
|
</Slot>
|
|
88
94
|
{:else}
|
|
89
95
|
<Slot props={{ children, token }} render={streamdown.snippets.ul}>
|
|
90
|
-
<ul class={streamdown.theme.ul.base}>
|
|
96
|
+
<ul data-streamdown-ul={id} class={streamdown.theme.ul.base}>
|
|
91
97
|
{@render children()}
|
|
92
98
|
</ul>
|
|
93
99
|
</Slot>
|
|
@@ -95,6 +101,7 @@
|
|
|
95
101
|
{:else if token.type === 'list_item'}
|
|
96
102
|
<Slot props={{ children, token }} render={streamdown.snippets.li}>
|
|
97
103
|
<li
|
|
104
|
+
data-streamdown-li={id}
|
|
98
105
|
{style}
|
|
99
106
|
style:list-style-type={token.task ? 'none' : undefined}
|
|
100
107
|
{...token.value && !token.task ? { value: token.value } : {}}
|
|
@@ -113,7 +120,15 @@
|
|
|
113
120
|
</Slot>
|
|
114
121
|
{:else if token.type === 'table'}
|
|
115
122
|
<Slot props={{ token, children }} render={streamdown.snippets.table}>
|
|
116
|
-
|
|
123
|
+
{#if streamdown.controls.table}
|
|
124
|
+
<TableDownload {id} {token} />
|
|
125
|
+
{/if}
|
|
126
|
+
<div
|
|
127
|
+
data-streamdown-table={id}
|
|
128
|
+
{style}
|
|
129
|
+
class={`${streamdown.theme.table.base} group`}
|
|
130
|
+
style:overscroll-behavior-x="none"
|
|
131
|
+
>
|
|
117
132
|
<table class={streamdown.theme.table.table}>
|
|
118
133
|
{@render children()}
|
|
119
134
|
</table>
|
|
@@ -121,25 +136,25 @@
|
|
|
121
136
|
</Slot>
|
|
122
137
|
{:else if token.type === 'thead'}
|
|
123
138
|
<Slot props={{ token, children }} render={streamdown.snippets.thead}>
|
|
124
|
-
<thead {style} class={streamdown.theme.thead.base}>
|
|
139
|
+
<thead data-streamdown-thead={id} {style} class={streamdown.theme.thead.base}>
|
|
125
140
|
{@render children?.()}
|
|
126
141
|
</thead>
|
|
127
142
|
</Slot>
|
|
128
143
|
{:else if token.type === 'tbody'}
|
|
129
144
|
<Slot props={{ token, children }} render={streamdown.snippets.tbody}>
|
|
130
|
-
<tbody {style} class={streamdown.theme.tbody.base}>
|
|
145
|
+
<tbody data-streamdown-tbody={id} {style} class={streamdown.theme.tbody.base}>
|
|
131
146
|
{@render children?.()}
|
|
132
147
|
</tbody>
|
|
133
148
|
</Slot>
|
|
134
149
|
{:else if token.type === 'tfoot'}
|
|
135
150
|
<Slot props={{ token, children }} render={streamdown.snippets.tfoot}>
|
|
136
|
-
<tfoot {style} class={streamdown.theme.tfoot.base}>
|
|
151
|
+
<tfoot data-streamdown-tfoot={id} {style} class={streamdown.theme.tfoot.base}>
|
|
137
152
|
{@render children?.()}
|
|
138
153
|
</tfoot>
|
|
139
154
|
</Slot>
|
|
140
155
|
{:else if token.type === 'tr'}
|
|
141
156
|
<Slot props={{ token, children }} render={streamdown.snippets.tr}>
|
|
142
|
-
<tr {style} class={streamdown.theme.tr.base}>
|
|
157
|
+
<tr data-streamdown-tr={id} {style} class={streamdown.theme.tr.base}>
|
|
143
158
|
{@render children?.()}
|
|
144
159
|
</tr>
|
|
145
160
|
</Slot>
|
|
@@ -148,6 +163,7 @@
|
|
|
148
163
|
<Slot props={{ children, token }} render={streamdown.snippets.td}>
|
|
149
164
|
<td
|
|
150
165
|
{style}
|
|
166
|
+
data-streamdown-td={id}
|
|
151
167
|
class={streamdown.theme.td.base}
|
|
152
168
|
{...token.colspan > 1 ? { colspan: token.colspan } : {}}
|
|
153
169
|
{...token.rowspan > 1 ? { rowspan: token.rowspan } : {}}
|
|
@@ -164,6 +180,7 @@
|
|
|
164
180
|
<Slot props={{ children, token }} render={streamdown.snippets.th}>
|
|
165
181
|
<th
|
|
166
182
|
{style}
|
|
183
|
+
data-streamdown-th={id}
|
|
167
184
|
class={streamdown.theme.th.base}
|
|
168
185
|
{...token.colspan > 1 ? { colspan: token.colspan } : {}}
|
|
169
186
|
{...token.rowspan > 1 ? { rowspan: token.rowspan } : {}}
|
|
@@ -176,45 +193,45 @@
|
|
|
176
193
|
</Slot>
|
|
177
194
|
{/if}
|
|
178
195
|
{:else if token.type === 'image'}
|
|
179
|
-
<Image {token} {children} />
|
|
196
|
+
<Image {id} {token} {children} />
|
|
180
197
|
{:else if token.type === 'link'}
|
|
181
|
-
<Link {token} {children} />
|
|
198
|
+
<Link {id} {token} {children} />
|
|
182
199
|
{:else if token.type === 'sub'}
|
|
183
200
|
<Slot props={{ children, token }} render={streamdown.snippets.sub}>
|
|
184
|
-
<sub class={streamdown.theme.sub.base}>
|
|
201
|
+
<sub data-streamdown-sub={id} class={streamdown.theme.sub.base}>
|
|
185
202
|
{@render children()}
|
|
186
203
|
</sub>
|
|
187
204
|
</Slot>
|
|
188
205
|
{:else if token.type === 'sup'}
|
|
189
206
|
<Slot props={{ children, token }} render={streamdown.snippets.sup}>
|
|
190
|
-
<sup class={streamdown.theme.sup.base}>
|
|
207
|
+
<sup data-streamdown-sup={id} class={streamdown.theme.sup.base}>
|
|
191
208
|
{@render children()}
|
|
192
209
|
</sup>
|
|
193
210
|
</Slot>
|
|
194
211
|
{:else if token.type === 'strong'}
|
|
195
212
|
<Slot props={{ children, token }} render={streamdown.snippets.strong}>
|
|
196
|
-
<strong class={streamdown.theme.strong.base}>
|
|
213
|
+
<strong data-streamdown-strong={id} class={streamdown.theme.strong.base}>
|
|
197
214
|
{@render children()}
|
|
198
215
|
</strong>
|
|
199
216
|
</Slot>
|
|
200
217
|
{:else if token.type === 'em'}
|
|
201
218
|
<Slot props={{ children, token }} render={streamdown.snippets.em}>
|
|
202
|
-
<em class={streamdown.theme.em.base}>
|
|
219
|
+
<em data-streamdown-em={id} class={streamdown.theme.em.base}>
|
|
203
220
|
{@render children()}
|
|
204
221
|
</em>
|
|
205
222
|
</Slot>
|
|
206
223
|
{:else if token.type === 'del'}
|
|
207
224
|
<Slot props={{ children, token }} render={streamdown.snippets.del}>
|
|
208
|
-
<del class={streamdown.theme.del.base}>
|
|
225
|
+
<del data-streamdown-del={id} class={streamdown.theme.del.base}>
|
|
209
226
|
{@render children()}
|
|
210
227
|
</del>
|
|
211
228
|
</Slot>
|
|
212
229
|
{:else if token.type === 'hr'}
|
|
213
230
|
<Slot props={{ children, token }} render={streamdown.snippets.hr}>
|
|
214
|
-
<hr {style} class={streamdown.theme.hr.base} />
|
|
231
|
+
<hr data-streamdown-hr={id} {style} class={streamdown.theme.hr.base} />
|
|
215
232
|
</Slot>
|
|
216
233
|
{:else if token.type === 'br'}
|
|
217
|
-
<br />
|
|
234
|
+
<br data-streamdown-br={id} />
|
|
218
235
|
{:else if token.type === 'math'}
|
|
219
236
|
<Slot
|
|
220
237
|
props={{
|
|
@@ -223,10 +240,10 @@
|
|
|
223
240
|
}}
|
|
224
241
|
render={streamdown.snippets.math}
|
|
225
242
|
>
|
|
226
|
-
<Math {token} />
|
|
243
|
+
<Math {id} {token} />
|
|
227
244
|
</Slot>
|
|
228
245
|
{:else if token.type === 'alert'}
|
|
229
|
-
<Alert {token} {children} />
|
|
246
|
+
<Alert {id} {token} {children} />
|
|
230
247
|
{:else if token.type === 'footnoteRef'}
|
|
231
248
|
<Slot props={{ token }} render={streamdown.snippets.footnoteRef}>
|
|
232
249
|
<FootnoteRef {token} />
|
|
@@ -239,7 +256,11 @@
|
|
|
239
256
|
<!-- TODO Footnotes are rendered inside the FootnoteRef popover -->
|
|
240
257
|
{:else if token.type === 'descriptionList'}
|
|
241
258
|
<Slot props={{ children, token }} render={streamdown.snippets.descriptionList}>
|
|
242
|
-
<dl
|
|
259
|
+
<dl
|
|
260
|
+
data-streamdown-description-list={id}
|
|
261
|
+
style={streamdown.animationBlockStyle}
|
|
262
|
+
class={streamdown.theme.descriptionList.base}
|
|
263
|
+
>
|
|
243
264
|
{@render children()}
|
|
244
265
|
</dl>
|
|
245
266
|
</Slot>
|
|
@@ -249,13 +270,17 @@
|
|
|
249
270
|
</Slot>
|
|
250
271
|
{:else if token.type === 'descriptionTerm'}
|
|
251
272
|
<Slot props={{ children, token }} render={streamdown.snippets.descriptionTerm}>
|
|
252
|
-
<dt {style} class={streamdown.theme.descriptionTerm.base}>
|
|
273
|
+
<dt data-streamdown-description-term={id} {style} class={streamdown.theme.descriptionTerm.base}>
|
|
253
274
|
{@render children()}
|
|
254
275
|
</dt>
|
|
255
276
|
</Slot>
|
|
256
277
|
{:else if token.type === 'descriptionDetail'}
|
|
257
278
|
<Slot props={{ children, token }} render={streamdown.snippets.descriptionDetail}>
|
|
258
|
-
<dd
|
|
279
|
+
<dd
|
|
280
|
+
data-streamdown-description-detail={id}
|
|
281
|
+
{style}
|
|
282
|
+
class={streamdown.theme.descriptionDetail.base}
|
|
283
|
+
>
|
|
259
284
|
{@render children()}
|
|
260
285
|
</dd>
|
|
261
286
|
</Slot>
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
{#if popover.isOpen}
|
|
43
43
|
<dialog
|
|
44
|
+
data-streamdown-footnote-popover={id}
|
|
44
45
|
id={'footnote-popover-' + id}
|
|
45
46
|
aria-modal="false"
|
|
46
47
|
transition:scale|global={{ start: 0.95, duration: 100 }}
|
|
@@ -65,6 +66,7 @@
|
|
|
65
66
|
|
|
66
67
|
{#if token.label !== 'streamdown:footnote'}
|
|
67
68
|
<button
|
|
69
|
+
data-streamdown-footnote-ref={id}
|
|
68
70
|
style={streamdown.animationBlockStyle}
|
|
69
71
|
bind:this={popover.reference}
|
|
70
72
|
class={streamdown.theme.footnoteRef.base}
|
|
@@ -9,10 +9,12 @@
|
|
|
9
9
|
|
|
10
10
|
const {
|
|
11
11
|
children,
|
|
12
|
-
token
|
|
12
|
+
token,
|
|
13
|
+
id
|
|
13
14
|
}: {
|
|
14
15
|
children: Snippet;
|
|
15
16
|
token: Tokens.Image;
|
|
17
|
+
id: string;
|
|
16
18
|
} = $props();
|
|
17
19
|
|
|
18
20
|
const transformedUrl = $derived(
|
|
@@ -32,6 +34,7 @@
|
|
|
32
34
|
render={streamdown.snippets.image}
|
|
33
35
|
>
|
|
34
36
|
<span
|
|
37
|
+
data-streamdown-image={id}
|
|
35
38
|
style={streamdown.isMounted ? streamdown.animationBlockStyle : ''}
|
|
36
39
|
class={streamdown.theme.image.base}
|
|
37
40
|
>
|
|
@@ -40,6 +43,7 @@
|
|
|
40
43
|
</Slot>
|
|
41
44
|
{:else}
|
|
42
45
|
<span
|
|
46
|
+
data-streamdown-image-blocked={id}
|
|
43
47
|
class="inline-block rounded bg-gray-200 px-3 py-1 text-sm text-gray-600 dark:bg-gray-700 dark:text-gray-400"
|
|
44
48
|
title={`Blocked URL: ${token.href}`}
|
|
45
49
|
>
|
|
@@ -9,10 +9,12 @@
|
|
|
9
9
|
|
|
10
10
|
const {
|
|
11
11
|
children,
|
|
12
|
-
token
|
|
12
|
+
token,
|
|
13
|
+
id
|
|
13
14
|
}: {
|
|
14
15
|
children: Snippet;
|
|
15
16
|
token: Tokens.Link;
|
|
17
|
+
id: string;
|
|
16
18
|
} = $props();
|
|
17
19
|
|
|
18
20
|
const transformedUrl = $derived(
|
|
@@ -33,6 +35,7 @@
|
|
|
33
35
|
render={streamdown.snippets.link}
|
|
34
36
|
>
|
|
35
37
|
<a
|
|
38
|
+
data-streamdown-link={id}
|
|
36
39
|
class={streamdown.theme.link.base}
|
|
37
40
|
href={transformedUrl}
|
|
38
41
|
target="_blank"
|
|
@@ -43,6 +46,7 @@
|
|
|
43
46
|
</Slot>
|
|
44
47
|
{:else}
|
|
45
48
|
<span
|
|
49
|
+
data-streamdown-link-blocked={id}
|
|
46
50
|
class={streamdown.theme.link.blocked}
|
|
47
51
|
title={token.title ? `Blocked URL: ${token.href}` : undefined}
|
|
48
52
|
>
|
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
import 'katex/dist/katex.min.css';
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
|
-
token
|
|
9
|
+
token,
|
|
10
|
+
id
|
|
10
11
|
}: {
|
|
11
12
|
token: MathToken;
|
|
13
|
+
id: string;
|
|
12
14
|
} = $props();
|
|
13
15
|
|
|
14
16
|
const streamdown = useStreamdown();
|
|
@@ -46,6 +48,7 @@
|
|
|
46
48
|
|
|
47
49
|
{#if token.isInline}
|
|
48
50
|
<span
|
|
51
|
+
data-streamdown-inline-math={id}
|
|
49
52
|
style={streamdown.isMounted ? streamdown.animationBlockStyle : ''}
|
|
50
53
|
bind:this={inner}
|
|
51
54
|
class={streamdown.theme.math.inline}
|
|
@@ -53,7 +56,12 @@
|
|
|
53
56
|
{@html html}
|
|
54
57
|
</span>
|
|
55
58
|
{:else}
|
|
56
|
-
<div
|
|
59
|
+
<div
|
|
60
|
+
data-streamdown-block-math={id}
|
|
61
|
+
style={streamdown.isMounted ? streamdown.animationBlockStyle : ''}
|
|
62
|
+
style:height="fit-content"
|
|
63
|
+
style:width="100%"
|
|
64
|
+
>
|
|
57
65
|
<div class="overflow-x-auto">
|
|
58
66
|
<div bind:this={inner} class={streamdown.theme.math.block}>
|
|
59
67
|
{@html html}
|
|
@@ -2,6 +2,7 @@ import type { MathToken } from '../marked/index.js';
|
|
|
2
2
|
import 'katex/dist/katex.min.css';
|
|
3
3
|
type $$ComponentProps = {
|
|
4
4
|
token: MathToken;
|
|
5
|
+
id: string;
|
|
5
6
|
};
|
|
6
7
|
declare const Math: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
8
|
type Math = ReturnType<typeof Math>;
|
|
@@ -5,13 +5,16 @@
|
|
|
5
5
|
import type { MermaidConfig } from 'mermaid';
|
|
6
6
|
import { on } from 'svelte/events';
|
|
7
7
|
import { usePanzoom } from '../utils/panzoom.svelte';
|
|
8
|
+
import { fitViewIcon, fullscreenIcon, zoomInIcon, zoomOutIcon } from './icons.js';
|
|
8
9
|
|
|
9
10
|
const streamdown = useStreamdown();
|
|
10
11
|
|
|
11
12
|
const {
|
|
12
|
-
token
|
|
13
|
+
token,
|
|
14
|
+
id
|
|
13
15
|
}: {
|
|
14
16
|
token: Tokens.Code;
|
|
17
|
+
id: string;
|
|
15
18
|
} = $props();
|
|
16
19
|
|
|
17
20
|
let mermaid = $state<any>(null);
|
|
@@ -154,6 +157,7 @@
|
|
|
154
157
|
const renderMermaid = async (code: string, element: HTMLElement) => {
|
|
155
158
|
try {
|
|
156
159
|
// Sanitize the code first
|
|
160
|
+
const sanitizedCode = sanitizeMermaidCode(code);
|
|
157
161
|
|
|
158
162
|
// Default configuration
|
|
159
163
|
const defaultConfig: MermaidConfig = {
|
|
@@ -172,8 +176,7 @@
|
|
|
172
176
|
};
|
|
173
177
|
|
|
174
178
|
// Initialize mermaid with merged config
|
|
175
|
-
|
|
176
|
-
mermaid.initialize(mergedConfig);
|
|
179
|
+
mermaid.initialize(defaultConfig);
|
|
177
180
|
|
|
178
181
|
const chartHash = code.split('').reduce((acc, char) => {
|
|
179
182
|
return ((acc << 5) - acc + char.charCodeAt(0)) | 0;
|
|
@@ -182,25 +185,33 @@
|
|
|
182
185
|
const uniqueId = `mermaid-${Math.abs(chartHash)}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
183
186
|
|
|
184
187
|
// Render the diagram
|
|
185
|
-
const { svg: svgString } = await mermaid.render(uniqueId,
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const svgTarget = element.querySelector('[data-mermaid-svg]')
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
svgTarget.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
188
|
+
const { svg: svgString } = await mermaid.render(uniqueId, sanitizedCode);
|
|
189
|
+
|
|
190
|
+
// Insert the SVG into the target element
|
|
191
|
+
const svgTarget = element.querySelector('[data-mermaid-svg]') as SVGElement;
|
|
192
|
+
if (svgTarget) {
|
|
193
|
+
svgTarget.innerHTML = svgString;
|
|
194
|
+
svgTarget.id = uniqueId;
|
|
195
|
+
|
|
196
|
+
// Apply any additional attributes from the rendered SVG
|
|
197
|
+
const tempSvg = new DOMParser().parseFromString(svgString, 'image/svg+xml').documentElement;
|
|
198
|
+
Array.from(tempSvg.attributes).forEach((attribute) => {
|
|
199
|
+
if (attribute.name !== 'id') {
|
|
200
|
+
svgTarget.setAttribute(attribute.name, attribute.value);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
panzoom.zoomToFit();
|
|
205
|
+
panzoom.zoomToFit();
|
|
206
|
+
}
|
|
197
207
|
} catch (err) {
|
|
198
|
-
|
|
208
|
+
console.warn('Mermaid rendering error:', err);
|
|
209
|
+
// Could show error state in UI here
|
|
199
210
|
}
|
|
200
211
|
};
|
|
201
212
|
</script>
|
|
202
213
|
|
|
203
|
-
<div>
|
|
214
|
+
<div data-streamdown-mermaid={id}>
|
|
204
215
|
{#if mermaid}
|
|
205
216
|
<div
|
|
206
217
|
style={streamdown.isMounted ? streamdown.animationBlockStyle : ''}
|
|
@@ -251,79 +262,6 @@
|
|
|
251
262
|
<div class={streamdown.theme.mermaid.base}></div>
|
|
252
263
|
{/if}
|
|
253
264
|
</div>
|
|
254
|
-
{#snippet zoomInIcon()}
|
|
255
|
-
<svg
|
|
256
|
-
class={streamdown.theme.mermaid.icon}
|
|
257
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
258
|
-
width="100%"
|
|
259
|
-
height="100%"
|
|
260
|
-
viewBox="0 0 24 24"
|
|
261
|
-
fill="none"
|
|
262
|
-
stroke="currentColor"
|
|
263
|
-
stroke-width="2"
|
|
264
|
-
stroke-linecap="round"
|
|
265
|
-
stroke-linejoin="round"
|
|
266
|
-
>
|
|
267
|
-
<circle cx="11" cy="11" r="8" /><line x1="21" x2="16.65" y1="21" y2="16.65" />
|
|
268
|
-
<line x1="11" x2="11" y1="8" y2="14" />
|
|
269
|
-
<line x1="8" x2="14" y1="11" y2="11" />
|
|
270
|
-
</svg>
|
|
271
|
-
{/snippet}
|
|
272
|
-
|
|
273
|
-
{#snippet zoomOutIcon()}
|
|
274
|
-
<svg
|
|
275
|
-
class={streamdown.theme.mermaid.icon}
|
|
276
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
277
|
-
width="100%"
|
|
278
|
-
height="100%"
|
|
279
|
-
viewBox="0 0 24 24"
|
|
280
|
-
fill="none"
|
|
281
|
-
stroke="currentColor"
|
|
282
|
-
stroke-width="2"
|
|
283
|
-
stroke-linecap="round"
|
|
284
|
-
stroke-linejoin="round"
|
|
285
|
-
>
|
|
286
|
-
<circle cx="11" cy="11" r="8" /><line x1="21" x2="16.65" y1="21" y2="16.65" /><line
|
|
287
|
-
x1="8"
|
|
288
|
-
x2="14"
|
|
289
|
-
y1="11"
|
|
290
|
-
y2="11"
|
|
291
|
-
/>
|
|
292
|
-
</svg>
|
|
293
|
-
{/snippet}
|
|
294
|
-
|
|
295
|
-
{#snippet fitViewIcon()}
|
|
296
|
-
<svg
|
|
297
|
-
class={streamdown.theme.mermaid.icon}
|
|
298
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
299
|
-
viewBox="0 0 24 24"
|
|
300
|
-
fill="none"
|
|
301
|
-
stroke="currentColor"
|
|
302
|
-
stroke-width="2"
|
|
303
|
-
stroke-linecap="round"
|
|
304
|
-
stroke-linejoin="round"
|
|
305
|
-
>
|
|
306
|
-
<path d="M3 7V5a2 2 0 0 1 2-2h2" />
|
|
307
|
-
<path d="M17 3h2a2 2 0 0 1 2 2v2" /><path d="M21 17v2a2 2 0 0 1-2 2h-2" />
|
|
308
|
-
<path d="M7 21H5a2 2 0 0 1-2-2v-2" /><rect width="10" height="8" x="7" y="8" rx="1" />
|
|
309
|
-
</svg>
|
|
310
|
-
{/snippet}
|
|
311
|
-
|
|
312
|
-
{#snippet fullscreenIcon()}
|
|
313
|
-
<svg
|
|
314
|
-
class={streamdown.theme.mermaid.icon}
|
|
315
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
316
|
-
viewBox="0 0 24 24"
|
|
317
|
-
fill="none"
|
|
318
|
-
stroke="currentColor"
|
|
319
|
-
stroke-width="2"
|
|
320
|
-
stroke-linecap="round"
|
|
321
|
-
stroke-linejoin="round"
|
|
322
|
-
>
|
|
323
|
-
<path d="m15 15 6 6" /><path d="m15 9 6-6" /><path d="M21 16v5h-5" /><path d="M21 8V3h-5" />
|
|
324
|
-
<path d="M3 16v5h5" /><path d="m3 21 6-6" /><path d="M3 8V3h5" /><path d="M9 9 3 3" />
|
|
325
|
-
</svg>
|
|
326
|
-
{/snippet}
|
|
327
265
|
|
|
328
266
|
<style>
|
|
329
267
|
:global([data-expanded='true']) {
|
|
@@ -335,4 +273,13 @@
|
|
|
335
273
|
z-index: 2147483647;
|
|
336
274
|
margin: 0px;
|
|
337
275
|
}
|
|
276
|
+
|
|
277
|
+
/* Hide Mermaid's temporary rendering containers */
|
|
278
|
+
:global(div[id^='dmermaid-']) {
|
|
279
|
+
position: absolute !important;
|
|
280
|
+
left: -9999px !important;
|
|
281
|
+
top: -9999px !important;
|
|
282
|
+
visibility: hidden !important;
|
|
283
|
+
pointer-events: none !important;
|
|
284
|
+
}
|
|
338
285
|
</style>
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useStreamdown } from '../context.svelte.js';
|
|
3
|
+
import { scale } from 'svelte/transition';
|
|
4
|
+
import { checkIcon, copyIcon, downloadIcon } from './icons.js';
|
|
5
|
+
import { Popover } from './popover.svelte.js';
|
|
6
|
+
import { useClickOutside } from '../utils/useClickOutside.svelte.js';
|
|
7
|
+
import { useKeyDown } from '../utils/useKeyDown.svelte.js';
|
|
8
|
+
import type { TableToken } from '../marked/marked-table.js';
|
|
9
|
+
import { useCopy } from '../utils/copy.svelte.js';
|
|
10
|
+
import { save } from '../utils/save.js';
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
token,
|
|
14
|
+
id
|
|
15
|
+
}: {
|
|
16
|
+
token: TableToken;
|
|
17
|
+
id: string;
|
|
18
|
+
} = $props();
|
|
19
|
+
const streamdown = useStreamdown();
|
|
20
|
+
const popover = new Popover();
|
|
21
|
+
let modeState = $state<'download' | 'copy'>('download');
|
|
22
|
+
|
|
23
|
+
useKeyDown({
|
|
24
|
+
keys: ['Escape'],
|
|
25
|
+
get isActive() {
|
|
26
|
+
return popover.isOpen;
|
|
27
|
+
},
|
|
28
|
+
callback: () => {
|
|
29
|
+
popover.isOpen = false;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
const clickOutside = useClickOutside({
|
|
33
|
+
get isActive() {
|
|
34
|
+
return popover.isOpen;
|
|
35
|
+
},
|
|
36
|
+
callback: () => {
|
|
37
|
+
popover.isOpen = false;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
let copyValue = $state(token.raw);
|
|
42
|
+
|
|
43
|
+
const copy = useCopy({
|
|
44
|
+
get content() {
|
|
45
|
+
return copyValue;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const copyOrDownload = (type: 'Markdown' | 'HTML' | 'CSV') => {
|
|
50
|
+
if (type === 'Markdown') {
|
|
51
|
+
copyValue = token.raw;
|
|
52
|
+
if (modeState === 'copy') {
|
|
53
|
+
copy.copy();
|
|
54
|
+
} else {
|
|
55
|
+
save('table.md', copyValue, 'text/markdown');
|
|
56
|
+
}
|
|
57
|
+
} else if (type === 'HTML') {
|
|
58
|
+
const table = document.querySelector(`[data-streamdown-table=${id}]`);
|
|
59
|
+
|
|
60
|
+
if (table) {
|
|
61
|
+
let html = (table.cloneNode(true) as HTMLElement).outerHTML;
|
|
62
|
+
// remove comments
|
|
63
|
+
html = html.replace(/<!--[\s\S]*?-->/g, '');
|
|
64
|
+
copyValue = html;
|
|
65
|
+
// Remove class and style attributes
|
|
66
|
+
html = html.replace(/class="[^"]*"/g, '');
|
|
67
|
+
html = html.replace(/style="[^"]*"/g, '');
|
|
68
|
+
// remove space between the tag name and the closing chrevron like <table > -> <table> for every tagnmae
|
|
69
|
+
html = html.replace(/<([^>]+)>\s+</g, '<$1><');
|
|
70
|
+
// Remove spaces before closing bracket >
|
|
71
|
+
html = html.replace(/\s+>/g, '>');
|
|
72
|
+
// Remove spaces after opening bracket <
|
|
73
|
+
html = html.replace(/<\s+/g, '<');
|
|
74
|
+
// Collapse multiple spaces within tags to single space
|
|
75
|
+
html = html.replace(/<([^>]+)>/g, (match) => match.replace(/\s+/g, ' '));
|
|
76
|
+
|
|
77
|
+
copyValue = html;
|
|
78
|
+
|
|
79
|
+
if (modeState === 'copy') {
|
|
80
|
+
copy.copy();
|
|
81
|
+
} else {
|
|
82
|
+
save('table.html', copyValue, 'text/html');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} else if (type === 'CSV') {
|
|
86
|
+
const table = document.querySelector(`[data-streamdown-table=${id}]`);
|
|
87
|
+
|
|
88
|
+
if (table) {
|
|
89
|
+
const rows = table.querySelectorAll('tr');
|
|
90
|
+
const rowSpanFills: Array<{ rowIndex: number; colIndex: number; colSpan: number }> = [];
|
|
91
|
+
|
|
92
|
+
const matrix = Array.from(rows).reduce((acc, row, rowIndex) => {
|
|
93
|
+
const cells = row.querySelectorAll('td, th');
|
|
94
|
+
const rowData: string[] = [];
|
|
95
|
+
let actualCol = 0; // Track actual column position in the output matrix
|
|
96
|
+
|
|
97
|
+
Array.from(cells).forEach((cell) => {
|
|
98
|
+
const colSpan = parseInt(cell.getAttribute('colspan') || '1');
|
|
99
|
+
const rowSpan = parseInt(cell.getAttribute('rowspan') || '1');
|
|
100
|
+
|
|
101
|
+
// Add the cell content
|
|
102
|
+
// Add the cell content, quoting if it contains commas, quotes, or newlines
|
|
103
|
+
const content = cell.textContent || '';
|
|
104
|
+
const needsQuoting = /[,"\n]/.test(content);
|
|
105
|
+
const escapedContent = content.replace(/"/g, '""');
|
|
106
|
+
rowData.push(needsQuoting ? `"${escapedContent}"` : content);
|
|
107
|
+
|
|
108
|
+
// Add empty cells for colspan
|
|
109
|
+
for (let i = 0; i < colSpan - 1; i++) {
|
|
110
|
+
rowData.push('');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Track rowspan fills needed in future rows
|
|
114
|
+
if (rowSpan > 1) {
|
|
115
|
+
for (let r = 1; r < rowSpan; r++) {
|
|
116
|
+
rowSpanFills.push({
|
|
117
|
+
rowIndex: rowIndex + r,
|
|
118
|
+
colIndex: actualCol,
|
|
119
|
+
colSpan: colSpan
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
actualCol += colSpan;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
acc.push(rowData);
|
|
128
|
+
return acc;
|
|
129
|
+
}, [] as string[][]);
|
|
130
|
+
|
|
131
|
+
// Process rowspan fills - insert empty cells at correct positions
|
|
132
|
+
rowSpanFills.forEach(({ rowIndex, colIndex, colSpan }) => {
|
|
133
|
+
if (matrix[rowIndex]) {
|
|
134
|
+
matrix[rowIndex].splice(colIndex, 0, ...Array(colSpan).fill(''));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const csv = matrix.map((row) => row.join(',')).join('\n');
|
|
139
|
+
copyValue = csv;
|
|
140
|
+
if (modeState === 'copy') {
|
|
141
|
+
copy.copy();
|
|
142
|
+
} else {
|
|
143
|
+
save('table.csv', copyValue, 'text/csv');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
popover.isOpen = false;
|
|
148
|
+
};
|
|
149
|
+
</script>
|
|
150
|
+
|
|
151
|
+
{#if popover.isOpen}
|
|
152
|
+
<dialog
|
|
153
|
+
id={'table-download-popover'}
|
|
154
|
+
aria-modal="false"
|
|
155
|
+
transition:scale|global={{ start: 0.95, duration: 100 }}
|
|
156
|
+
{@attach clickOutside.attachment}
|
|
157
|
+
{@attach popover.popoverAttachment}
|
|
158
|
+
open
|
|
159
|
+
style:width="fit-content !important"
|
|
160
|
+
style:min-width="fit-content !important"
|
|
161
|
+
class={streamdown.theme.components.popover}
|
|
162
|
+
>
|
|
163
|
+
{#each ['Markdown', 'HTML', 'CSV'] as type}
|
|
164
|
+
<button
|
|
165
|
+
style="width: 100%; text-align: left; justify-content: flex-start; padding: 1rem 1rem; margin: 0.2rem 0;"
|
|
166
|
+
onclick={() => copyOrDownload(type as 'Markdown' | 'HTML' | 'CSV')}
|
|
167
|
+
class={streamdown.theme.components.button}
|
|
168
|
+
>
|
|
169
|
+
{type}
|
|
170
|
+
</button>
|
|
171
|
+
{/each}
|
|
172
|
+
</dialog>
|
|
173
|
+
{/if}
|
|
174
|
+
|
|
175
|
+
<div
|
|
176
|
+
data-streamdown-table-download
|
|
177
|
+
class=" right-0 ml-auto flex items-center justify-end gap-2 p-1"
|
|
178
|
+
>
|
|
179
|
+
{#each ['download', 'copy'] as mode (mode)}
|
|
180
|
+
<button
|
|
181
|
+
class={streamdown.theme.components.button}
|
|
182
|
+
onclick={async (e: MouseEvent) => {
|
|
183
|
+
if (modeState === mode && popover.isOpen) {
|
|
184
|
+
popover.isOpen = false;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (popover.isOpen && modeState !== mode) {
|
|
188
|
+
popover.isOpen = false;
|
|
189
|
+
const wait = new Promise((resolve) => {
|
|
190
|
+
setTimeout(resolve, 80);
|
|
191
|
+
});
|
|
192
|
+
await wait;
|
|
193
|
+
}
|
|
194
|
+
popover.reference = e.target as HTMLButtonElement;
|
|
195
|
+
popover.isOpen = true;
|
|
196
|
+
modeState = mode as 'download' | 'copy';
|
|
197
|
+
}}
|
|
198
|
+
{@attach clickOutside.attachment}
|
|
199
|
+
title={mode === 'download' ? 'Download table' : 'Copy table'}
|
|
200
|
+
>
|
|
201
|
+
{#if mode === 'download'}
|
|
202
|
+
{@render (streamdown.icons?.download || downloadIcon)()}
|
|
203
|
+
{:else if copy.isCopied}
|
|
204
|
+
{@render (streamdown.icons?.check || checkIcon)()}
|
|
205
|
+
{:else}
|
|
206
|
+
{@render (streamdown.icons?.copy || copyIcon)()}
|
|
207
|
+
{/if}
|
|
208
|
+
</button>
|
|
209
|
+
{/each}
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
<style>
|
|
213
|
+
:global([data-streamdown-table-download] + div) {
|
|
214
|
+
margin-top: 0px;
|
|
215
|
+
}
|
|
216
|
+
</style>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TableToken } from '../marked/marked-table.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
token: TableToken;
|
|
4
|
+
id: string;
|
|
5
|
+
};
|
|
6
|
+
declare const TableDownload: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type TableDownload = ReturnType<typeof TableDownload>;
|
|
8
|
+
export default TableDownload;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const copyIcon: import("svelte").Snippet<[]>;
|
|
2
|
+
export declare const downloadIcon: import("svelte").Snippet<[]>;
|
|
3
|
+
export declare const checkIcon: import("svelte").Snippet<[]>;
|
|
4
|
+
export declare const zoomInIcon: import("svelte").Snippet<[]>;
|
|
5
|
+
export declare const zoomOutIcon: import("svelte").Snippet<[]>;
|
|
6
|
+
export declare const fitViewIcon: import("svelte").Snippet<[]>;
|
|
7
|
+
export declare const fullscreenIcon: import("svelte").Snippet<[]>;
|
|
8
|
+
export declare const chevronRight: import("svelte").Snippet<[]>;
|
|
9
|
+
export declare const chevronLeft: import("svelte").Snippet<[]>;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { createRawSnippet } from 'svelte';
|
|
2
|
+
export const copyIcon = createRawSnippet(() => {
|
|
3
|
+
return {
|
|
4
|
+
render: () => {
|
|
5
|
+
return `
|
|
6
|
+
<svg
|
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
8
|
+
width="100%"
|
|
9
|
+
height="100%"
|
|
10
|
+
viewBox="0 0 24 24"
|
|
11
|
+
fill="none"
|
|
12
|
+
stroke="currentColor"
|
|
13
|
+
stroke-width="2"
|
|
14
|
+
stroke-linecap="round"
|
|
15
|
+
stroke-linejoin="round"
|
|
16
|
+
><rect width="8" height="4" x="8" y="2" rx="1" ry="1" /><path
|
|
17
|
+
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"
|
|
18
|
+
/></svg>
|
|
19
|
+
`;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
export const downloadIcon = createRawSnippet(() => {
|
|
24
|
+
return {
|
|
25
|
+
render: () => {
|
|
26
|
+
return `<svg
|
|
27
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
28
|
+
width="100%"
|
|
29
|
+
height="100%"
|
|
30
|
+
viewBox="0 0 24 24"
|
|
31
|
+
fill="none"
|
|
32
|
+
stroke="currentColor"
|
|
33
|
+
stroke-width="2"
|
|
34
|
+
stroke-linecap="round"
|
|
35
|
+
stroke-linejoin="round"
|
|
36
|
+
><path d="M12 15V3" /><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><path
|
|
37
|
+
d="m7 10 5 5 5-5"
|
|
38
|
+
/></svg
|
|
39
|
+
>`;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
export const checkIcon = createRawSnippet(() => {
|
|
44
|
+
return {
|
|
45
|
+
render: () => {
|
|
46
|
+
return `
|
|
47
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
|
|
48
|
+
`;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
export const zoomInIcon = createRawSnippet(() => {
|
|
53
|
+
return {
|
|
54
|
+
render: () => `
|
|
55
|
+
<svg
|
|
56
|
+
width="100%"
|
|
57
|
+
height="100%"
|
|
58
|
+
viewBox="0 0 24 24"
|
|
59
|
+
fill="none"
|
|
60
|
+
stroke="currentColor"
|
|
61
|
+
stroke-width="2"
|
|
62
|
+
stroke-linecap="round"
|
|
63
|
+
stroke-linejoin="round"
|
|
64
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
65
|
+
>
|
|
66
|
+
<circle cx="11" cy="11" r="8" />
|
|
67
|
+
<line x1="21" x2="16.65" y1="21" y2="16.65" />
|
|
68
|
+
<line x1="11" x2="11" y1="8" y2="14" />
|
|
69
|
+
<line x1="8" x2="14" y1="11" y2="11" />
|
|
70
|
+
</svg>
|
|
71
|
+
`
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
export const zoomOutIcon = createRawSnippet(() => {
|
|
75
|
+
return {
|
|
76
|
+
render: () => `
|
|
77
|
+
<svg
|
|
78
|
+
width="100%"
|
|
79
|
+
height="100%"
|
|
80
|
+
viewBox="0 0 24 24"
|
|
81
|
+
fill="none"
|
|
82
|
+
stroke="currentColor"
|
|
83
|
+
stroke-width="2"
|
|
84
|
+
stroke-linecap="round"
|
|
85
|
+
stroke-linejoin="round"
|
|
86
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
87
|
+
>
|
|
88
|
+
<circle cx="11" cy="11" r="8" />
|
|
89
|
+
<line x1="21" x2="16.65" y1="21" y2="16.65" />
|
|
90
|
+
<line x1="8" x2="14" y1="11" y2="11" />
|
|
91
|
+
</svg>
|
|
92
|
+
`
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
export const fitViewIcon = createRawSnippet(() => {
|
|
96
|
+
return {
|
|
97
|
+
render: () => `
|
|
98
|
+
<svg
|
|
99
|
+
width="100%"
|
|
100
|
+
height="100%"
|
|
101
|
+
viewBox="0 0 24 24"
|
|
102
|
+
fill="none"
|
|
103
|
+
stroke="currentColor"
|
|
104
|
+
stroke-width="2"
|
|
105
|
+
stroke-linecap="round"
|
|
106
|
+
stroke-linejoin="round"
|
|
107
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
108
|
+
>
|
|
109
|
+
<path d="M3 7V5a2 2 0 0 1 2-2h2" />
|
|
110
|
+
<path d="M17 3h2a2 2 0 0 1 2 2v2" />
|
|
111
|
+
<path d="M21 17v2a2 2 0 0 1-2 2h-2" />
|
|
112
|
+
<path d="M7 21H5a2 2 0 0 1-2-2v-2" />
|
|
113
|
+
<rect width="10" height="8" x="7" y="8" rx="1" />
|
|
114
|
+
</svg>
|
|
115
|
+
`
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
export const fullscreenIcon = createRawSnippet(() => {
|
|
119
|
+
return {
|
|
120
|
+
render: () => `
|
|
121
|
+
<svg
|
|
122
|
+
width="100%"
|
|
123
|
+
height="100%"
|
|
124
|
+
viewBox="0 0 24 24"
|
|
125
|
+
fill="none"
|
|
126
|
+
stroke="currentColor"
|
|
127
|
+
stroke-width="2"
|
|
128
|
+
stroke-linecap="round"
|
|
129
|
+
stroke-linejoin="round"
|
|
130
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
131
|
+
>
|
|
132
|
+
<path d="m15 15 6 6" /><path d="m15 9 6-6" /><path d="M21 16v5h-5" /><path d="M21 8V3h-5" />
|
|
133
|
+
<path d="M3 16v5h5" /><path d="m3 21 6-6" /><path d="M3 8V3h5" /><path d="M9 9 3 3" />
|
|
134
|
+
</svg>
|
|
135
|
+
`
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
export const chevronRight = createRawSnippet(() => {
|
|
139
|
+
return {
|
|
140
|
+
render: () => `
|
|
141
|
+
<svg
|
|
142
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
143
|
+
width="100%"
|
|
144
|
+
height="100%"
|
|
145
|
+
viewBox="0 0 24 24"
|
|
146
|
+
fill="none"
|
|
147
|
+
stroke="currentColor"
|
|
148
|
+
stroke-width="2"
|
|
149
|
+
stroke-linecap="round"
|
|
150
|
+
stroke-linejoin="round"
|
|
151
|
+
>
|
|
152
|
+
<path d="m9 18 6-6-6-6" />
|
|
153
|
+
</svg>
|
|
154
|
+
`
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
export const chevronLeft = createRawSnippet(() => {
|
|
158
|
+
return {
|
|
159
|
+
render: () => `
|
|
160
|
+
<svg
|
|
161
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
162
|
+
width="100%"
|
|
163
|
+
height="100%"
|
|
164
|
+
viewBox="0 0 24 24"
|
|
165
|
+
fill="none"
|
|
166
|
+
stroke="currentColor"
|
|
167
|
+
stroke-width="2"
|
|
168
|
+
stroke-linecap="round"
|
|
169
|
+
stroke-linejoin="round"
|
|
170
|
+
>
|
|
171
|
+
<path d="m15 18-6-6 6-6" />
|
|
172
|
+
</svg>
|
|
173
|
+
`
|
|
174
|
+
};
|
|
175
|
+
});
|
package/dist/Streamdown.svelte
CHANGED
|
@@ -103,9 +103,11 @@
|
|
|
103
103
|
get controls() {
|
|
104
104
|
const codeControls = controls?.code ?? true;
|
|
105
105
|
const mermaidControls = controls?.mermaid ?? true;
|
|
106
|
+
const tableControls = controls?.table ?? true;
|
|
106
107
|
return {
|
|
107
108
|
code: codeControls,
|
|
108
|
-
mermaid: mermaidControls
|
|
109
|
+
mermaid: mermaidControls,
|
|
110
|
+
table: tableControls
|
|
109
111
|
};
|
|
110
112
|
},
|
|
111
113
|
get children() {
|
package/dist/context.svelte.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface StreamdownContext extends Omit<StreamdownProps, keyof Snippets
|
|
|
10
10
|
controls: {
|
|
11
11
|
code: boolean;
|
|
12
12
|
mermaid: boolean;
|
|
13
|
+
table: boolean;
|
|
13
14
|
};
|
|
14
15
|
inlineCitationsMode: 'list' | 'carousel';
|
|
15
16
|
animation: {
|
|
@@ -117,6 +118,7 @@ export type StreamdownProps<Source extends Record<string, any> = Record<string,
|
|
|
117
118
|
controls?: {
|
|
118
119
|
code?: boolean;
|
|
119
120
|
mermaid?: boolean;
|
|
121
|
+
table?: boolean;
|
|
120
122
|
};
|
|
121
123
|
renderHtml?: boolean | ((token: Tokens.HTML | Tokens.Tag) => string);
|
|
122
124
|
animation?: {
|
|
@@ -141,6 +143,7 @@ export type StreamdownProps<Source extends Record<string, any> = Record<string,
|
|
|
141
143
|
important?: Snippet;
|
|
142
144
|
chevronLeft?: Snippet;
|
|
143
145
|
chevronRight?: Snippet;
|
|
146
|
+
check?: Snippet;
|
|
144
147
|
};
|
|
145
148
|
extensions?: Extension[];
|
|
146
149
|
children?: Snippet<[{
|
package/dist/marked/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export type Extension = {
|
|
|
22
22
|
start?: TokenizerStartFunction;
|
|
23
23
|
applyInBlockParsing?: boolean;
|
|
24
24
|
};
|
|
25
|
-
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 | AlignToken | CitationToken;
|
|
25
|
+
export type StreamdownToken = Exclude<MarkedToken, Tokens.List | Tokens.ListItem | Tokens.Table> | ListToken | ListItemToken | MathToken | AlertToken | FootnoteToken | SubSupToken | BrToken | HrToken | TableToken | THead | TBody | TFoot | THeadRow | TRow | TH | TD | DescriptionListToken | DescriptionToken | DescriptionDetailToken | DescriptionTermToken | AlignToken | CitationToken;
|
|
26
26
|
export type { TableToken, THead, TBody, TFoot, THeadRow, TRow, TH, TD } from './marked-table.js';
|
|
27
27
|
export declare const lex: (markdown: string, extensions?: Extension[]) => StreamdownToken[];
|
|
28
28
|
export declare const parseBlocks: (markdown: string, extensions?: Extension[]) => string[];
|
package/dist/theme.js
CHANGED
|
@@ -151,7 +151,7 @@ export const theme = {
|
|
|
151
151
|
},
|
|
152
152
|
components: {
|
|
153
153
|
button: 'disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer p-1 text-gray-600 transition-all hover:text-gray-900 rounded hover:bg-gray-100 w-6 h-6',
|
|
154
|
-
popover: '
|
|
154
|
+
popover: 'min-w-[250px] max-w-md fixed z-[1000] max-h-md overflow-y-auto rounded-lg bg-white p-4 shadow'
|
|
155
155
|
}
|
|
156
156
|
};
|
|
157
157
|
export const shadcnTheme = {
|
|
@@ -304,7 +304,7 @@ export const shadcnTheme = {
|
|
|
304
304
|
},
|
|
305
305
|
components: {
|
|
306
306
|
button: 'disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer p-1 text-muted-foreground transition-all hover:text-foreground rounded hover:bg-border flex items-center justify-center w-6 h-6',
|
|
307
|
-
popover: '
|
|
307
|
+
popover: 'min-w-[250px] max-w-md fixed z-[1000] max-h-md overflow-y-auto rounded-lg bg-popover border border-border p-2 shadow'
|
|
308
308
|
}
|
|
309
309
|
};
|
|
310
310
|
export const mergeTheme = (customTheme, baseTheme) => {
|
|
@@ -198,6 +198,11 @@ export const usePanzoom = (opts = {}) => {
|
|
|
198
198
|
dragOffMove = dragOffUp = null;
|
|
199
199
|
}
|
|
200
200
|
function onTouchStart(e) {
|
|
201
|
+
const hasButton = e
|
|
202
|
+
.composedPath()
|
|
203
|
+
.some((el) => el.tagName?.toLowerCase?.() === 'button');
|
|
204
|
+
if (hasButton)
|
|
205
|
+
return;
|
|
201
206
|
if (!node)
|
|
202
207
|
return;
|
|
203
208
|
if (animating) {
|
|
@@ -352,7 +357,15 @@ export const usePanzoom = (opts = {}) => {
|
|
|
352
357
|
const first = eventTarget.getBoundingClientRect();
|
|
353
358
|
if (expand) {
|
|
354
359
|
// Expanding: immediately apply expanded state
|
|
355
|
-
|
|
360
|
+
// We should add margin to the parent element to account for the expanded height
|
|
361
|
+
if (eventTarget.parentElement) {
|
|
362
|
+
const styleAttributes = ['margin-block', 'height'];
|
|
363
|
+
styleAttributes.forEach((attribute) => {
|
|
364
|
+
if (eventTarget?.parentElement) {
|
|
365
|
+
eventTarget.parentElement.style.setProperty(attribute, getComputedStyle(eventTarget).getPropertyValue(attribute));
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
}
|
|
356
369
|
eventTarget.dataset.expanded = 'true';
|
|
357
370
|
isExpanded = true;
|
|
358
371
|
zoomToFit();
|