svelte-streamdown 2.6.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -26
- package/dist/Block.svelte +8 -4
- package/dist/Block.svelte.d.ts +1 -0
- package/dist/Elements/Code.svelte +6 -1
- package/dist/Elements/Element.svelte +15 -14
- package/dist/Elements/Mermaid.svelte +2 -0
- package/dist/Elements/MermaidDownload.svelte +196 -0
- package/dist/Elements/MermaidDownload.svelte.d.ts +6 -0
- package/dist/Elements/fallbacks/CodeFallback.svelte +33 -0
- package/dist/Elements/fallbacks/CodeFallback.svelte.d.ts +8 -0
- package/dist/Elements/fallbacks/MathFallback.svelte +35 -0
- package/dist/Elements/fallbacks/MathFallback.svelte.d.ts +8 -0
- package/dist/Elements/fallbacks/MermaidFallback.svelte +34 -0
- package/dist/Elements/fallbacks/MermaidFallback.svelte.d.ts +8 -0
- package/dist/Elements/fallbacks/index.d.ts +3 -0
- package/dist/Elements/fallbacks/index.js +3 -0
- package/dist/Streamdown.svelte +41 -10
- package/dist/context.svelte.d.ts +31 -6
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/utils/bundledLanguages.d.ts +8 -0
- package/dist/utils/bundledLanguages.js +143 -0
- package/dist/utils/darkMode.svelte.d.ts +3 -0
- package/dist/utils/darkMode.svelte.js +49 -0
- package/dist/utils/hightlighter.svelte.d.ts +17 -19
- package/dist/utils/hightlighter.svelte.js +141 -61
- package/dist/utils/parse-incomplete-markdown.d.ts +52 -0
- package/dist/utils/parse-incomplete-markdown.js +4 -4
- package/package.json +15 -1
|
@@ -1,18 +1,41 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { SvelteMap } from 'svelte/reactivity';
|
|
2
|
+
import { supportedLanguages, createLanguageSet, bundledLanguagesInfo } from './bundledLanguages.js';
|
|
3
|
+
import { createHighlighterCore } from 'shiki/core';
|
|
4
|
+
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript';
|
|
5
|
+
// Import default themes directly
|
|
6
|
+
import githubDark from '@shikijs/themes/github-dark';
|
|
7
|
+
import githubLight from '@shikijs/themes/github-light';
|
|
8
|
+
// Default themes that are always loaded
|
|
9
|
+
const DEFAULT_THEMES = {
|
|
10
|
+
'github-dark': githubDark,
|
|
11
|
+
'github-light': githubLight
|
|
8
12
|
};
|
|
9
13
|
class HighlighterManager {
|
|
10
14
|
loadedLanguages = new SvelteMap();
|
|
11
|
-
loadedThemes = new SvelteMap();
|
|
12
|
-
preloadedThemes = new SvelteSet();
|
|
13
15
|
highlighter = $state(null);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
customLanguages;
|
|
17
|
+
languageLoaders;
|
|
18
|
+
additionalThemes;
|
|
19
|
+
constructor(languages, additionalThemes, additionalLanguages) {
|
|
20
|
+
// Store additional themes (will be loaded with highlighter)
|
|
21
|
+
this.additionalThemes = additionalThemes || {};
|
|
22
|
+
// Merge languages: default + additional
|
|
23
|
+
const allLanguages = additionalLanguages ? [...languages, ...additionalLanguages] : languages;
|
|
24
|
+
this.languageLoaders = new Map();
|
|
25
|
+
allLanguages.forEach((l) => {
|
|
26
|
+
this.languageLoaders.set(l.id, l.import);
|
|
27
|
+
if (l.aliases) {
|
|
28
|
+
l.aliases.forEach((alias) => this.languageLoaders.set(alias, l.import));
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
// Build custom languages set for validation
|
|
32
|
+
if (additionalLanguages) {
|
|
33
|
+
const additionalSet = createLanguageSet(additionalLanguages);
|
|
34
|
+
this.customLanguages = new Set([...supportedLanguages, ...additionalSet]);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
this.customLanguages = supportedLanguages;
|
|
38
|
+
}
|
|
16
39
|
if (typeof window !== 'undefined') {
|
|
17
40
|
Object.assign(window, {
|
|
18
41
|
STREAMDOWN_HIGHLIGHTER: this
|
|
@@ -24,17 +47,25 @@ class HighlighterManager {
|
|
|
24
47
|
return this.highlighter;
|
|
25
48
|
}
|
|
26
49
|
else if (!this.highlighter) {
|
|
27
|
-
this.highlighter = new Promise((resolve, reject) => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
50
|
+
this.highlighter = new Promise(async (resolve, reject) => {
|
|
51
|
+
try {
|
|
52
|
+
const engine = createJavaScriptRegexEngine({ forgiving: true });
|
|
53
|
+
// Load default themes + any additional themes immediately
|
|
54
|
+
const allThemes = [
|
|
55
|
+
...Object.values(DEFAULT_THEMES),
|
|
56
|
+
...Object.values(this.additionalThemes)
|
|
57
|
+
];
|
|
58
|
+
const highlighter = await createHighlighterCore({
|
|
59
|
+
themes: allThemes,
|
|
31
60
|
langs: [],
|
|
32
61
|
engine
|
|
33
|
-
}).then((highlighter) => {
|
|
34
|
-
this.highlighter = highlighter;
|
|
35
|
-
resolve(highlighter);
|
|
36
62
|
});
|
|
37
|
-
|
|
63
|
+
this.highlighter = highlighter;
|
|
64
|
+
resolve(highlighter);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
reject(error);
|
|
68
|
+
}
|
|
38
69
|
});
|
|
39
70
|
return this.highlighter;
|
|
40
71
|
}
|
|
@@ -42,94 +73,143 @@ class HighlighterManager {
|
|
|
42
73
|
return this.highlighter;
|
|
43
74
|
}
|
|
44
75
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (themeLoader instanceof Promise) {
|
|
48
|
-
await themeLoader;
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
const themeLoaderPromise = highlighter.loadTheme(theme).then(() => {
|
|
52
|
-
this.loadedThemes.set(theme, true);
|
|
53
|
-
});
|
|
54
|
-
this.loadedThemes.set(theme, themeLoaderPromise);
|
|
55
|
-
await themeLoaderPromise;
|
|
56
|
-
}
|
|
76
|
+
isThemeAvailable(theme) {
|
|
77
|
+
return theme in DEFAULT_THEMES || theme in this.additionalThemes;
|
|
57
78
|
}
|
|
58
79
|
async loadLanguage(language, highlighter) {
|
|
80
|
+
// Skip loading if language is not supported
|
|
81
|
+
if (!this.isLanguageSupported(language)) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
59
84
|
const languageLoader = this.loadedLanguages.get(language);
|
|
60
85
|
if (languageLoader instanceof Promise) {
|
|
61
86
|
await languageLoader;
|
|
62
87
|
}
|
|
63
|
-
else {
|
|
64
|
-
const
|
|
88
|
+
else if (!languageLoader) {
|
|
89
|
+
const loader = this.languageLoaders.get(language);
|
|
90
|
+
if (!loader) {
|
|
91
|
+
// Language not available, mark as failed
|
|
92
|
+
this.loadedLanguages.set(language, false);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const languageLoaderPromise = loader()
|
|
96
|
+
.then((langModule) => {
|
|
97
|
+
const langObj = langModule.default || langModule;
|
|
98
|
+
return highlighter.loadLanguage(langObj);
|
|
99
|
+
})
|
|
100
|
+
.then(() => {
|
|
65
101
|
this.loadedLanguages.set(language, true);
|
|
102
|
+
})
|
|
103
|
+
.catch((err) => {
|
|
104
|
+
this.loadedLanguages.set(language, false);
|
|
105
|
+
throw err;
|
|
66
106
|
});
|
|
67
107
|
this.loadedLanguages.set(language, languageLoaderPromise);
|
|
68
108
|
await languageLoaderPromise;
|
|
69
109
|
}
|
|
70
110
|
}
|
|
71
111
|
isLanguageSupported = (language) => {
|
|
72
|
-
return
|
|
112
|
+
return this.customLanguages.has(language);
|
|
73
113
|
};
|
|
74
|
-
isReady(theme, language
|
|
114
|
+
isReady(theme, language) {
|
|
115
|
+
// Check if theme is available
|
|
116
|
+
if (!this.isThemeAvailable(theme)) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
// For unsupported languages, we don't need to load anything (will use plaintext)
|
|
120
|
+
if (!language || !this.isLanguageSupported(language)) {
|
|
121
|
+
return !!this.highlighter && !(this.highlighter instanceof Promise);
|
|
122
|
+
}
|
|
75
123
|
return (!!this.highlighter &&
|
|
76
124
|
!(this.highlighter instanceof Promise) &&
|
|
77
|
-
this.
|
|
78
|
-
this.loadedLanguages.get(this.isLanguageSupported(language) ? language : 'bash') === true);
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Preloads themes by creating minimal highlighter instances.
|
|
82
|
-
* This reduces flickering when switching themes.
|
|
83
|
-
*/
|
|
84
|
-
async preloadThemes(highlighter) {
|
|
85
|
-
await Promise.all(Array.from(this.preloadedThemes).map((theme) => this.loadTheme(theme, highlighter)));
|
|
125
|
+
this.loadedLanguages.get(language) === true);
|
|
86
126
|
}
|
|
87
127
|
/**
|
|
88
128
|
* Ensures the highlighter is ready for the given theme and language.
|
|
89
129
|
*/
|
|
90
|
-
async load(theme, language
|
|
91
|
-
|
|
92
|
-
|
|
130
|
+
async load(theme, language) {
|
|
131
|
+
// For unsupported languages, only need to load highlighter (themes are pre-loaded)
|
|
132
|
+
if (!language || !this.isLanguageSupported(language)) {
|
|
133
|
+
if (this.highlighter !== null && !(this.highlighter instanceof Promise)) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
await this.loadHighlighter();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const languageLoader = this.loadedLanguages.get(language);
|
|
93
140
|
if (this.highlighter !== null &&
|
|
94
141
|
!(this.highlighter instanceof Promise) &&
|
|
95
|
-
themeLoader === true &&
|
|
96
142
|
languageLoader === true) {
|
|
97
143
|
return;
|
|
98
144
|
}
|
|
99
145
|
const highlighter = await this.loadHighlighter();
|
|
100
|
-
await
|
|
101
|
-
this.loadTheme(theme, highlighter),
|
|
102
|
-
this.loadLanguage(this.isLanguageSupported(language) ? language : 'bash', highlighter)
|
|
103
|
-
]);
|
|
104
|
-
await this.preloadThemes(highlighter);
|
|
146
|
+
await this.loadLanguage(language, highlighter);
|
|
105
147
|
}
|
|
106
148
|
/**
|
|
107
149
|
* Highlights code synchronously. Must call isReady() first.
|
|
150
|
+
* Returns plaintext tokens for unsupported languages.
|
|
108
151
|
*/
|
|
109
|
-
highlightCode(code, language
|
|
152
|
+
highlightCode(code, language, theme) {
|
|
110
153
|
try {
|
|
111
|
-
// const highlighter = this.highlighters.get(`${theme}:${language}`);
|
|
112
154
|
const highlighter = this.highlighter;
|
|
113
155
|
if (!highlighter || highlighter instanceof Promise) {
|
|
114
|
-
|
|
156
|
+
// Return plaintext tokens when highlighter is not ready
|
|
157
|
+
return code.split('\n').map((line) => [
|
|
158
|
+
{
|
|
159
|
+
content: line,
|
|
160
|
+
color: undefined,
|
|
161
|
+
bgColor: undefined
|
|
162
|
+
}
|
|
163
|
+
]);
|
|
164
|
+
}
|
|
165
|
+
// For unsupported languages, return plaintext tokens
|
|
166
|
+
if (!language || !this.isLanguageSupported(language)) {
|
|
167
|
+
return code.split('\n').map((line) => [
|
|
168
|
+
{
|
|
169
|
+
content: line,
|
|
170
|
+
color: undefined,
|
|
171
|
+
bgColor: undefined
|
|
172
|
+
}
|
|
173
|
+
]);
|
|
115
174
|
}
|
|
116
175
|
const tokens = highlighter.codeToTokensBase(code, {
|
|
117
|
-
lang:
|
|
176
|
+
lang: language,
|
|
118
177
|
theme
|
|
119
178
|
});
|
|
120
179
|
return tokens;
|
|
121
180
|
}
|
|
122
181
|
catch (error) {
|
|
123
|
-
|
|
182
|
+
// Return plaintext tokens on error
|
|
183
|
+
return code.split('\n').map((line) => [
|
|
184
|
+
{
|
|
185
|
+
content: line,
|
|
186
|
+
color: undefined,
|
|
187
|
+
bgColor: undefined
|
|
188
|
+
}
|
|
189
|
+
]);
|
|
124
190
|
}
|
|
125
191
|
}
|
|
126
|
-
static create(
|
|
192
|
+
static create(languages = bundledLanguagesInfo, additionalThemes, additionalLanguages) {
|
|
127
193
|
if (typeof window !== 'undefined' && 'STREAMDOWN_HIGHLIGHTER' in window) {
|
|
128
194
|
const previousHighlighter = window.STREAMDOWN_HIGHLIGHTER;
|
|
129
|
-
|
|
195
|
+
// Merge additional themes
|
|
196
|
+
if (additionalThemes) {
|
|
197
|
+
Object.assign(previousHighlighter.additionalThemes, additionalThemes);
|
|
198
|
+
}
|
|
199
|
+
// Merge additional languages with existing set
|
|
200
|
+
if (additionalLanguages) {
|
|
201
|
+
additionalLanguages.forEach((lang) => {
|
|
202
|
+
previousHighlighter.languageLoaders.set(lang.id, lang.import);
|
|
203
|
+
if (lang.aliases) {
|
|
204
|
+
lang.aliases.forEach((alias) => previousHighlighter.languageLoaders.set(alias, lang.import));
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
const additionalSet = createLanguageSet(additionalLanguages);
|
|
208
|
+
additionalSet.forEach((lang) => previousHighlighter.customLanguages.add(lang));
|
|
209
|
+
}
|
|
130
210
|
return previousHighlighter;
|
|
131
211
|
}
|
|
132
|
-
return new HighlighterManager(
|
|
212
|
+
return new HighlighterManager(languages, additionalThemes, additionalLanguages);
|
|
133
213
|
}
|
|
134
214
|
}
|
|
135
215
|
// Export the class for those who want to create their own instances
|
|
@@ -1 +1,53 @@
|
|
|
1
|
+
export interface Plugin {
|
|
2
|
+
name: string;
|
|
3
|
+
pattern?: RegExp;
|
|
4
|
+
handler?: (payload: HandlerPayload) => string;
|
|
5
|
+
skipInBlockTypes?: string[];
|
|
6
|
+
preprocess?: (payload: HookPayload) => string | {
|
|
7
|
+
text: string;
|
|
8
|
+
state: Partial<ParseState>;
|
|
9
|
+
};
|
|
10
|
+
postprocess?: (payload: HookPayload) => string;
|
|
11
|
+
}
|
|
12
|
+
interface HookPayload {
|
|
13
|
+
text: string;
|
|
14
|
+
state: ParseState;
|
|
15
|
+
setState: (state: Partial<ParseState>) => void;
|
|
16
|
+
}
|
|
17
|
+
interface HandlerPayload {
|
|
18
|
+
line: string;
|
|
19
|
+
text: string;
|
|
20
|
+
match: RegExpMatchArray;
|
|
21
|
+
state: ParseState;
|
|
22
|
+
setState: (state: Partial<ParseState>) => void;
|
|
23
|
+
}
|
|
24
|
+
interface ParseState {
|
|
25
|
+
currentLine: number;
|
|
26
|
+
context: 'normal' | 'list' | 'blockquote' | 'descriptionList';
|
|
27
|
+
blockingContexts: Set<'code' | 'math' | 'center' | 'right'>;
|
|
28
|
+
lineContexts?: Array<{
|
|
29
|
+
code: boolean;
|
|
30
|
+
math: boolean;
|
|
31
|
+
center: boolean;
|
|
32
|
+
right: boolean;
|
|
33
|
+
}>;
|
|
34
|
+
fenceInfo?: string;
|
|
35
|
+
mdxUnclosedTags?: Array<{
|
|
36
|
+
tagName: string;
|
|
37
|
+
lineIndex: number;
|
|
38
|
+
}>;
|
|
39
|
+
mdxLineStates?: Array<{
|
|
40
|
+
inMdx: boolean;
|
|
41
|
+
incompletePositions: number[];
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
44
|
+
export declare class IncompleteMarkdownParser {
|
|
45
|
+
private plugins;
|
|
46
|
+
private state;
|
|
47
|
+
setState: (state: Partial<ParseState>) => void;
|
|
48
|
+
constructor(plugins?: Plugin[]);
|
|
49
|
+
parse(text: string): string;
|
|
50
|
+
static createDefaultPlugins(): Plugin[];
|
|
51
|
+
}
|
|
1
52
|
export declare const parseIncompleteMarkdown: (text: string) => string;
|
|
53
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class IncompleteMarkdownParser {
|
|
1
|
+
export class IncompleteMarkdownParser {
|
|
2
2
|
plugins = [];
|
|
3
3
|
state = {
|
|
4
4
|
currentLine: 0,
|
|
@@ -762,7 +762,7 @@ class IncompleteMarkdownParser {
|
|
|
762
762
|
};
|
|
763
763
|
},
|
|
764
764
|
handler: ({ line, state }) => {
|
|
765
|
-
//
|
|
765
|
+
// Remove incomplete MDX syntax (don't render it)
|
|
766
766
|
const lineStates = state.mdxLineStates || [];
|
|
767
767
|
const currentState = lineStates[state.currentLine];
|
|
768
768
|
if (currentState?.incompletePositions && currentState.incompletePositions.length > 0) {
|
|
@@ -771,8 +771,8 @@ class IncompleteMarkdownParser {
|
|
|
771
771
|
for (let i = currentState.incompletePositions.length - 1; i >= 0; i--) {
|
|
772
772
|
const pos = currentState.incompletePositions[i];
|
|
773
773
|
const before = result.substring(0, pos);
|
|
774
|
-
|
|
775
|
-
result = before
|
|
774
|
+
// Simply remove the incomplete MDX tag
|
|
775
|
+
result = before;
|
|
776
776
|
}
|
|
777
777
|
return result;
|
|
778
778
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-streamdown",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"build": "vite build && npm run prepack",
|
|
@@ -30,6 +30,18 @@
|
|
|
30
30
|
".": {
|
|
31
31
|
"types": "./dist/index.d.ts",
|
|
32
32
|
"svelte": "./dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./code": {
|
|
35
|
+
"types": "./dist/Elements/Code.svelte.d.ts",
|
|
36
|
+
"svelte": "./dist/Elements/Code.svelte"
|
|
37
|
+
},
|
|
38
|
+
"./mermaid": {
|
|
39
|
+
"types": "./dist/Elements/Mermaid.svelte.d.ts",
|
|
40
|
+
"svelte": "./dist/Elements/Mermaid.svelte"
|
|
41
|
+
},
|
|
42
|
+
"./math": {
|
|
43
|
+
"types": "./dist/Elements/Math.svelte.d.ts",
|
|
44
|
+
"svelte": "./dist/Elements/Math.svelte"
|
|
33
45
|
}
|
|
34
46
|
},
|
|
35
47
|
"peerDependencies": {
|
|
@@ -72,6 +84,8 @@
|
|
|
72
84
|
},
|
|
73
85
|
"dependencies": {
|
|
74
86
|
"@floating-ui/dom": "^1.7.4",
|
|
87
|
+
"@shikijs/langs": "^3.17.1",
|
|
88
|
+
"@shikijs/themes": "^3.17.1",
|
|
75
89
|
"clsx": "^2.1.1",
|
|
76
90
|
"katex": "^0.16.22",
|
|
77
91
|
"marked": "^16.2.1",
|