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
|
@@ -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,186 +185,86 @@
|
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
<
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
{
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
<svg
|
|
255
|
-
class={streamdown.theme.mermaid.icon}
|
|
256
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
257
|
-
viewBox="0 0 24 24"
|
|
258
|
-
fill="none"
|
|
259
|
-
stroke="currentColor"
|
|
260
|
-
stroke-width="2"
|
|
261
|
-
stroke-linecap="round"
|
|
262
|
-
stroke-linejoin="round"
|
|
263
|
-
>
|
|
264
|
-
<circle cx="11" cy="11" r="8" /><line x1="21" x2="16.65" y1="21" y2="16.65" />
|
|
265
|
-
<line x1="11" x2="11" y1="8" y2="14" />
|
|
266
|
-
<line x1="8" x2="14" y1="11" y2="11" />
|
|
267
|
-
</svg>
|
|
268
|
-
{/snippet}
|
|
269
|
-
|
|
270
|
-
{#snippet zoomOutIcon()}
|
|
271
|
-
<svg
|
|
272
|
-
class={streamdown.theme.mermaid.icon}
|
|
273
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
274
|
-
viewBox="0 0 24 24"
|
|
275
|
-
fill="none"
|
|
276
|
-
stroke="currentColor"
|
|
277
|
-
stroke-width="2"
|
|
278
|
-
stroke-linecap="round"
|
|
279
|
-
stroke-linejoin="round"
|
|
280
|
-
>
|
|
281
|
-
<circle cx="11" cy="11" r="8" /><line x1="21" x2="16.65" y1="21" y2="16.65" /><line
|
|
282
|
-
x1="8"
|
|
283
|
-
x2="14"
|
|
284
|
-
y1="11"
|
|
285
|
-
y2="11"
|
|
286
|
-
/>
|
|
287
|
-
</svg>
|
|
288
|
-
{/snippet}
|
|
289
|
-
|
|
290
|
-
{#snippet fitViewIcon()}
|
|
291
|
-
<svg
|
|
292
|
-
class={streamdown.theme.mermaid.icon}
|
|
293
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
294
|
-
viewBox="0 0 24 24"
|
|
295
|
-
fill="none"
|
|
296
|
-
stroke="currentColor"
|
|
297
|
-
stroke-width="2"
|
|
298
|
-
stroke-linecap="round"
|
|
299
|
-
stroke-linejoin="round"
|
|
300
|
-
>
|
|
301
|
-
<path d="M3 7V5a2 2 0 0 1 2-2h2" />
|
|
302
|
-
<path d="M17 3h2a2 2 0 0 1 2 2v2" /><path d="M21 17v2a2 2 0 0 1-2 2h-2" />
|
|
303
|
-
<path d="M7 21H5a2 2 0 0 1-2-2v-2" /><rect width="10" height="8" x="7" y="8" rx="1" />
|
|
304
|
-
</svg>
|
|
305
|
-
{/snippet}
|
|
306
|
-
|
|
307
|
-
{#snippet fullscreenIcon()}
|
|
308
|
-
<svg
|
|
309
|
-
class={streamdown.theme.mermaid.icon}
|
|
310
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
311
|
-
viewBox="0 0 24 24"
|
|
312
|
-
fill="none"
|
|
313
|
-
stroke="currentColor"
|
|
314
|
-
stroke-width="2"
|
|
315
|
-
stroke-linecap="round"
|
|
316
|
-
stroke-linejoin="round"
|
|
317
|
-
>
|
|
318
|
-
<path d="m15 15 6 6" /><path d="m15 9 6-6" /><path d="M21 16v5h-5" /><path d="M21 8V3h-5" />
|
|
319
|
-
<path d="M3 16v5h5" /><path d="m3 21 6-6" /><path d="M3 8V3h5" /><path d="M9 9 3 3" />
|
|
320
|
-
</svg>
|
|
321
|
-
{/snippet}
|
|
214
|
+
<div data-streamdown-mermaid={id}>
|
|
215
|
+
{#if mermaid}
|
|
216
|
+
<div
|
|
217
|
+
style={streamdown.isMounted ? streamdown.animationBlockStyle : ''}
|
|
218
|
+
class={streamdown.theme.mermaid.base}
|
|
219
|
+
{@attach (node) => renderMermaid(token.text, node)}
|
|
220
|
+
{@attach insider.attach}
|
|
221
|
+
data-expanded={'false'}
|
|
222
|
+
>
|
|
223
|
+
{#if streamdown.controls.mermaid}
|
|
224
|
+
<div class={streamdown.theme.mermaid.buttons}>
|
|
225
|
+
<button
|
|
226
|
+
class={streamdown.theme.components.button}
|
|
227
|
+
aria-label="Zoom to fit"
|
|
228
|
+
onclick={() => panzoom.zoomToFit()}
|
|
229
|
+
data-panzoom-ignore
|
|
230
|
+
>
|
|
231
|
+
{@render (streamdown.icons?.fitView || fitViewIcon)()}
|
|
232
|
+
</button>
|
|
233
|
+
<button
|
|
234
|
+
class={streamdown.theme.components.button}
|
|
235
|
+
aria-label="Zoom in"
|
|
236
|
+
onclick={() => panzoom.zoomIn()}
|
|
237
|
+
data-panzoom-ignore
|
|
238
|
+
>
|
|
239
|
+
{@render (streamdown.icons?.zoomIn || zoomInIcon)()}
|
|
240
|
+
</button>
|
|
241
|
+
<button
|
|
242
|
+
class={streamdown.theme.components.button}
|
|
243
|
+
aria-label="Zoom out"
|
|
244
|
+
onclick={() => panzoom.zoomOut()}
|
|
245
|
+
data-panzoom-ignore
|
|
246
|
+
>
|
|
247
|
+
{@render (streamdown.icons?.zoomOut || zoomOutIcon)()}
|
|
248
|
+
</button>
|
|
249
|
+
<button
|
|
250
|
+
class={streamdown.theme.components.button}
|
|
251
|
+
aria-label="Toggle expand"
|
|
252
|
+
onclick={() => panzoom.toggleExpand()}
|
|
253
|
+
data-panzoom-ignore
|
|
254
|
+
>
|
|
255
|
+
{@render (streamdown.icons?.fullscreen || fullscreenIcon)()}
|
|
256
|
+
</button>
|
|
257
|
+
</div>
|
|
258
|
+
{/if}
|
|
259
|
+
<svg {@attach panzoom.attach} data-mermaid-svg></svg>
|
|
260
|
+
</div>
|
|
261
|
+
{:else}
|
|
262
|
+
<div class={streamdown.theme.mermaid.base}></div>
|
|
263
|
+
{/if}
|
|
264
|
+
</div>
|
|
322
265
|
|
|
323
266
|
<style>
|
|
324
|
-
/* View Transition styles for zoom effect */
|
|
325
|
-
::view-transition-old(panzoom-element),
|
|
326
|
-
::view-transition-new(panzoom-element) {
|
|
327
|
-
/* Animate both scale and opacity for smooth zoom */
|
|
328
|
-
animation-duration: 0.3s;
|
|
329
|
-
animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/* Zoom in animation (expand) */
|
|
333
|
-
::view-transition-old(panzoom-element) {
|
|
334
|
-
animation-name: zoom-out;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
::view-transition-new(panzoom-element) {
|
|
338
|
-
animation-name: zoom-in;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
@keyframes zoom-out {
|
|
342
|
-
from {
|
|
343
|
-
transform: scale(1);
|
|
344
|
-
}
|
|
345
|
-
to {
|
|
346
|
-
transform: scale(0.8);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
@keyframes zoom-in {
|
|
351
|
-
from {
|
|
352
|
-
transform: scale(1.2);
|
|
353
|
-
}
|
|
354
|
-
to {
|
|
355
|
-
transform: scale(1);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
267
|
:global([data-expanded='true']) {
|
|
360
|
-
::view-transition-old(panzoom-element),
|
|
361
|
-
::view-transition-new(panzoom-element) {
|
|
362
|
-
animation-duration: 0.3s;
|
|
363
|
-
animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
|
|
364
|
-
}
|
|
365
268
|
position: fixed;
|
|
366
269
|
top: 16px;
|
|
367
270
|
left: 16px;
|
|
@@ -370,4 +273,13 @@
|
|
|
370
273
|
z-index: 2147483647;
|
|
371
274
|
margin: 0px;
|
|
372
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
|
+
}
|
|
373
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<[]>;
|