svelte-streamdown 4.0.1 → 4.1.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 +209 -41
- package/dist/Block.svelte +15 -8
- package/dist/Block.svelte.d.ts +2 -0
- package/dist/Elements/Alert.svelte +2 -1
- package/dist/Elements/Citation.svelte +7 -0
- package/dist/Elements/Code.svelte +60 -21
- package/dist/Elements/Code.svelte.d.ts +4 -0
- package/dist/Elements/Element.svelte +36 -8
- package/dist/Elements/Element.svelte.d.ts +2 -0
- package/dist/Elements/FootnoteRef.svelte +1 -0
- package/dist/Elements/Image.svelte +3 -2
- package/dist/Elements/Link.svelte +2 -2
- package/dist/Elements/Mermaid.svelte +65 -13
- package/dist/Elements/Mermaid.svelte.d.ts +3 -0
- package/dist/Elements/MermaidDownload.svelte +29 -8
- package/dist/Elements/MermaidDownload.svelte.d.ts +2 -0
- package/dist/Elements/TableDownload.svelte +57 -83
- package/dist/Elements/fallbacks/CodeFallback.svelte +25 -4
- package/dist/Elements/fallbacks/CodeFallback.svelte.d.ts +3 -0
- package/dist/Elements/fallbacks/MermaidFallback.svelte +5 -2
- package/dist/Elements/fallbacks/MermaidFallback.svelte.d.ts +2 -0
- package/dist/Elements/icons.js +10 -1
- package/dist/Elements/srOnly.d.ts +1 -0
- package/dist/Elements/srOnly.js +3 -0
- package/dist/Streamdown.svelte +78 -16
- package/dist/context.svelte.d.ts +103 -22
- package/dist/context.svelte.js +38 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/marked/index.d.ts +6 -0
- package/dist/marked/index.js +46 -11
- package/dist/marked/marked-math.js +40 -1
- package/dist/utils/fence.d.ts +27 -0
- package/dist/utils/fence.js +56 -0
- package/dist/utils/parse-incomplete-markdown.d.ts +5 -1
- package/dist/utils/parse-incomplete-markdown.js +283 -138
- package/dist/utils/table-export.d.ts +14 -0
- package/dist/utils/table-export.js +82 -0
- package/dist/utils/usePinnedScroll.svelte.d.ts +22 -0
- package/dist/utils/usePinnedScroll.svelte.js +36 -0
- package/package.json +3 -2
|
@@ -4,6 +4,13 @@ const blockRule = /^(\$\$)(?:\n((?:\\[\s\S]|[^\\])+?)\n\1(?:\n|$)|([^$\n]+?)\1(?
|
|
|
4
4
|
// Inline math: handles both single ($) and double ($$) dollar delimiters
|
|
5
5
|
// Avoids matching currency by checking context and requiring proper content
|
|
6
6
|
const inlineRule = /^(\${1,2})(?!\$)((?:[^$\n]|\\\$)*?)\1(?!\d)/;
|
|
7
|
+
// LaTeX delimiters. LLMs emit these as often as dollars, so they produce the
|
|
8
|
+
// same `math` token: `\(`/`\)` inline, `\[`/`\]` display. Only the block form
|
|
9
|
+
// may span lines; an escaped backslash (`\\(`) is not a delimiter, which the
|
|
10
|
+
// charCode dispatch below rejects before any regex runs.
|
|
11
|
+
const inlineParenRule = /^\\\(([^\n]*?)\\\)/;
|
|
12
|
+
const inlineBracketRule = /^\\\[([^\n]*?)\\\]/;
|
|
13
|
+
const blockBracketRule = /^\\\[([\s\S]+?)\\\](?:\n|$)/;
|
|
7
14
|
// Enhanced currency detection patterns
|
|
8
15
|
const currencyPatterns = {
|
|
9
16
|
// Simple price patterns: $123, $123.45, $1,234.56
|
|
@@ -20,6 +27,20 @@ export const markedMath = [
|
|
|
20
27
|
name: 'math',
|
|
21
28
|
level: 'block',
|
|
22
29
|
tokenizer(src) {
|
|
30
|
+
if (src.charCodeAt(0) === 92 /* \ */) {
|
|
31
|
+
if (src.charCodeAt(1) !== 91 /* [ */)
|
|
32
|
+
return;
|
|
33
|
+
const latex = src.match(blockBracketRule);
|
|
34
|
+
if (!latex)
|
|
35
|
+
return;
|
|
36
|
+
return {
|
|
37
|
+
type: 'math',
|
|
38
|
+
isInline: false,
|
|
39
|
+
displayMode: true,
|
|
40
|
+
raw: latex[0],
|
|
41
|
+
text: latex[1].trim()
|
|
42
|
+
};
|
|
43
|
+
}
|
|
23
44
|
if (src.charCodeAt(0) !== 36 /* $ */)
|
|
24
45
|
return;
|
|
25
46
|
const match = src.match(blockRule);
|
|
@@ -69,7 +90,25 @@ export const markedMath = [
|
|
|
69
90
|
}
|
|
70
91
|
},
|
|
71
92
|
tokenizer(src) {
|
|
72
|
-
//
|
|
93
|
+
// The rules are anchored on their opening delimiter; skip the regex
|
|
94
|
+
// otherwise (see marked-br.ts) — this runs at every inline scan position.
|
|
95
|
+
if (src.charCodeAt(0) === 92 /* \ */) {
|
|
96
|
+
const open = src.charCodeAt(1);
|
|
97
|
+
// `\\(` is an escaped backslash followed by a paren, never math.
|
|
98
|
+
const displayMode = open === 91; /* [ */
|
|
99
|
+
if (!displayMode && open !== 40 /* ( */)
|
|
100
|
+
return;
|
|
101
|
+
const latex = src.match(displayMode ? inlineBracketRule : inlineParenRule);
|
|
102
|
+
if (!latex)
|
|
103
|
+
return;
|
|
104
|
+
return {
|
|
105
|
+
type: 'math',
|
|
106
|
+
isInline: true,
|
|
107
|
+
displayMode,
|
|
108
|
+
raw: latex[0],
|
|
109
|
+
text: latex[1].trim()
|
|
110
|
+
};
|
|
111
|
+
}
|
|
73
112
|
if (src.charCodeAt(0) !== 36 /* $ */)
|
|
74
113
|
return;
|
|
75
114
|
const match = src.match(inlineRule);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An open code fence: the character it was opened with, its run length, and
|
|
3
|
+
* the prefix the opener sat behind (indentation, blockquote markers, a list
|
|
4
|
+
* marker). The prefix is what a closer's indentation is measured against.
|
|
5
|
+
*/
|
|
6
|
+
export type OpenFence = {
|
|
7
|
+
char: string;
|
|
8
|
+
length: number;
|
|
9
|
+
prefix: string;
|
|
10
|
+
} | null;
|
|
11
|
+
/**
|
|
12
|
+
* Fence tracking, one line at a time: returns the fence state after `line`.
|
|
13
|
+
* The completer's block scan and `hasUnclosedFence` both walk with this, so the
|
|
14
|
+
* "am I inside a fence" answer can never drift between them.
|
|
15
|
+
*/
|
|
16
|
+
export declare const trackFence: (line: string, open: OpenFence) => OpenFence;
|
|
17
|
+
/**
|
|
18
|
+
* The line that closes `open`, placed at the opener's depth so it closes the
|
|
19
|
+
* fence where it lives. A list marker in the prefix is blanked to spaces: a
|
|
20
|
+
* closer must sit inside the item, and a repeated marker would start a new one.
|
|
21
|
+
*/
|
|
22
|
+
export declare const closingFence: (open: NonNullable<OpenFence>) => string;
|
|
23
|
+
/**
|
|
24
|
+
* True when `raw` ends inside a fenced block — i.e. the fence is still being
|
|
25
|
+
* streamed. Only the last block of a live document can be in this state.
|
|
26
|
+
*/
|
|
27
|
+
export declare const hasUnclosedFence: (raw: string) => boolean;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// An opener may sit behind up to 3 spaces of indentation per CommonMark, behind
|
|
2
|
+
// blockquote markers ("> ```" inside a blockquote or an alert), and behind a
|
|
3
|
+
// list marker, because "- ```js" opens a fence in the item.
|
|
4
|
+
const OPENER_LINE = /^([ \t]{0,3}(?:>[ \t]*)*(?:(?:[-*+]|\d{1,9}[.)])[ \t]+)?)(`{3,}|~{3,})(.*)$/;
|
|
5
|
+
// A closer is measured against the opener, not against column 0: it may be
|
|
6
|
+
// indented up to 3 spaces more than the opener was. That is what makes a fence
|
|
7
|
+
// inside a list item close — "10. ```js" opens at column 4, so its " ```"
|
|
8
|
+
// closer is at 4, which a fixed 0–3 limit rejected.
|
|
9
|
+
const CLOSER_LINE = /^([ \t]*(?:>[ \t]*)*)(`{3,}|~{3,})[ \t]*$/;
|
|
10
|
+
/**
|
|
11
|
+
* Fence tracking, one line at a time: returns the fence state after `line`.
|
|
12
|
+
* The completer's block scan and `hasUnclosedFence` both walk with this, so the
|
|
13
|
+
* "am I inside a fence" answer can never drift between them.
|
|
14
|
+
*/
|
|
15
|
+
export const trackFence = (line, open) => {
|
|
16
|
+
if (open) {
|
|
17
|
+
// A closer uses the opener's character, is at least as long, carries no
|
|
18
|
+
// info string, and is not indented deeper than the opener plus 3 — so a
|
|
19
|
+
// shorter or deeper fence line inside the block is content.
|
|
20
|
+
const match = CLOSER_LINE.exec(line);
|
|
21
|
+
if (!match)
|
|
22
|
+
return open;
|
|
23
|
+
const run = match[2];
|
|
24
|
+
const closes = run[0] === open.char &&
|
|
25
|
+
run.length >= open.length &&
|
|
26
|
+
match[1].length <= open.prefix.length + 3;
|
|
27
|
+
return closes ? null : open;
|
|
28
|
+
}
|
|
29
|
+
const match = OPENER_LINE.exec(line);
|
|
30
|
+
if (!match)
|
|
31
|
+
return null;
|
|
32
|
+
const run = match[2];
|
|
33
|
+
// A backtick fence's info string cannot contain a backtick (marked's own rule),
|
|
34
|
+
// so such a line opens nothing.
|
|
35
|
+
if (run[0] === '`' && match[3].includes('`'))
|
|
36
|
+
return null;
|
|
37
|
+
return { char: run[0], length: run.length, prefix: match[1] };
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* The line that closes `open`, placed at the opener's depth so it closes the
|
|
41
|
+
* fence where it lives. A list marker in the prefix is blanked to spaces: a
|
|
42
|
+
* closer must sit inside the item, and a repeated marker would start a new one.
|
|
43
|
+
*/
|
|
44
|
+
export const closingFence = (open) => open.prefix.replace(/[-*+]|\d{1,9}[.)]/g, (marker) => ' '.repeat(marker.length)) +
|
|
45
|
+
open.char.repeat(open.length);
|
|
46
|
+
/**
|
|
47
|
+
* True when `raw` ends inside a fenced block — i.e. the fence is still being
|
|
48
|
+
* streamed. Only the last block of a live document can be in this state.
|
|
49
|
+
*/
|
|
50
|
+
export const hasUnclosedFence = (raw) => {
|
|
51
|
+
let open = null;
|
|
52
|
+
for (const line of raw.split('\n')) {
|
|
53
|
+
open = trackFence(line, open);
|
|
54
|
+
}
|
|
55
|
+
return open !== null;
|
|
56
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type OpenFence } from './fence.js';
|
|
1
2
|
export interface Plugin {
|
|
2
3
|
name: string;
|
|
3
4
|
pattern?: RegExp;
|
|
@@ -25,13 +26,16 @@ interface ParseState {
|
|
|
25
26
|
currentLine: number;
|
|
26
27
|
context: 'normal' | 'list' | 'blockquote' | 'descriptionList';
|
|
27
28
|
blockingContexts: Set<'code' | 'math' | 'center' | 'right'>;
|
|
29
|
+
/** Delimiter that closes the open math block: '$$' or '\]'. */
|
|
30
|
+
mathCloser?: '$$' | '\\]';
|
|
31
|
+
/** The fence still open at the end of the input, if any — what closes it. */
|
|
32
|
+
openFence?: OpenFence;
|
|
28
33
|
lineContexts?: Array<{
|
|
29
34
|
code: boolean;
|
|
30
35
|
math: boolean;
|
|
31
36
|
center: boolean;
|
|
32
37
|
right: boolean;
|
|
33
38
|
}>;
|
|
34
|
-
fenceInfo?: string;
|
|
35
39
|
mdxUnclosedTags?: Array<{
|
|
36
40
|
tagName: string;
|
|
37
41
|
lineIndex: number;
|