svelte-streamdown 2.6.1 → 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 +43 -1
- 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 +10 -6
- 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 +37 -10
- package/dist/context.svelte.d.ts +21 -4
- 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 +1 -1
- package/package.json +15 -1
package/dist/context.svelte.d.ts
CHANGED
|
@@ -2,10 +2,11 @@ import type { Component, Snippet } from 'svelte';
|
|
|
2
2
|
import type { DeepPartialTheme, Theme } from './theme.js';
|
|
3
3
|
import type { MermaidConfig } from 'mermaid';
|
|
4
4
|
import type { KatexOptions } from 'katex';
|
|
5
|
-
import type {
|
|
5
|
+
import type { LanguageInfo } from './utils/bundledLanguages.js';
|
|
6
|
+
import type { ThemeRegistration } from 'shiki';
|
|
6
7
|
export interface StreamdownContext extends Omit<StreamdownProps, keyof Snippets | 'class' | 'theme' | 'shikiTheme' | 'inlineCitationsMode'> {
|
|
7
8
|
snippets: Snippets;
|
|
8
|
-
shikiTheme:
|
|
9
|
+
shikiTheme: string;
|
|
9
10
|
theme: Theme;
|
|
10
11
|
controls: {
|
|
11
12
|
code: boolean;
|
|
@@ -91,6 +92,7 @@ export type Snippets<Source extends Record<string, any> = Record<string, any>> =
|
|
|
91
92
|
};
|
|
92
93
|
export type StreamdownProps<Source extends Record<string, any> = Record<string, any>> = {
|
|
93
94
|
streamdown?: StreamdownContext;
|
|
95
|
+
static?: boolean;
|
|
94
96
|
sources?: {
|
|
95
97
|
[key: string]: Source;
|
|
96
98
|
};
|
|
@@ -105,8 +107,9 @@ export type StreamdownProps<Source extends Record<string, any> = Record<string,
|
|
|
105
107
|
theme?: DeepPartialTheme;
|
|
106
108
|
baseTheme?: 'tailwind' | 'shadcn';
|
|
107
109
|
mergeTheme?: boolean;
|
|
108
|
-
shikiTheme?:
|
|
109
|
-
|
|
110
|
+
shikiTheme?: string;
|
|
111
|
+
shikiLanguages?: LanguageInfo[];
|
|
112
|
+
shikiThemes?: Record<string, ThemeRegistration>;
|
|
110
113
|
mermaidConfig?: MermaidConfig;
|
|
111
114
|
katexConfig?: KatexOptions | ((inline: boolean) => KatexOptions);
|
|
112
115
|
translations?: {
|
|
@@ -159,5 +162,19 @@ export type StreamdownProps<Source extends Record<string, any> = Record<string,
|
|
|
159
162
|
children: Snippet;
|
|
160
163
|
props: any;
|
|
161
164
|
}, any, any>>;
|
|
165
|
+
components?: {
|
|
166
|
+
code?: Component<{
|
|
167
|
+
token: Tokens.Code;
|
|
168
|
+
id: string;
|
|
169
|
+
}, any, any>;
|
|
170
|
+
mermaid?: Component<{
|
|
171
|
+
token: Tokens.Code;
|
|
172
|
+
id: string;
|
|
173
|
+
}, any, any>;
|
|
174
|
+
math?: Component<{
|
|
175
|
+
token: MathToken;
|
|
176
|
+
id: string;
|
|
177
|
+
}, any, any>;
|
|
178
|
+
};
|
|
162
179
|
} & Partial<Snippets<Source>>;
|
|
163
180
|
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { default as Streamdown } from './Streamdown.svelte';
|
|
2
2
|
export { useStreamdown, type StreamdownProps } from './context.svelte.js';
|
|
3
3
|
export { theme, shadcnTheme, mergeTheme, type Theme } from './theme.js';
|
|
4
|
-
export { type Extension, type StreamdownToken } from './marked/index.js';
|
|
5
|
-
export {
|
|
4
|
+
export { type Extension, type StreamdownToken, lex, parseBlocks } from './marked/index.js';
|
|
5
|
+
export { parseIncompleteMarkdown, type Plugin, IncompleteMarkdownParser } from './utils/parse-incomplete-markdown.js';
|
|
6
|
+
export { bundledLanguagesInfo, createLanguageSet, type LanguageInfo } from './utils/bundledLanguages.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { default as Streamdown } from './Streamdown.svelte';
|
|
2
2
|
export { useStreamdown } from './context.svelte.js';
|
|
3
3
|
export { theme, shadcnTheme, mergeTheme } from './theme.js';
|
|
4
|
-
export {} from './marked/index.js';
|
|
5
4
|
export { lex, parseBlocks } from './marked/index.js';
|
|
5
|
+
export { parseIncompleteMarkdown, IncompleteMarkdownParser } from './utils/parse-incomplete-markdown.js';
|
|
6
|
+
export { bundledLanguagesInfo, createLanguageSet } from './utils/bundledLanguages.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type LanguageInfo = {
|
|
2
|
+
id: string;
|
|
3
|
+
aliases?: string[];
|
|
4
|
+
import: () => Promise<any>;
|
|
5
|
+
};
|
|
6
|
+
export declare const bundledLanguagesInfo: LanguageInfo[];
|
|
7
|
+
export declare function createLanguageSet(languages: LanguageInfo[]): Set<string>;
|
|
8
|
+
export declare const supportedLanguages: Set<string>;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
export const bundledLanguagesInfo = [
|
|
2
|
+
// Web essentials
|
|
3
|
+
{
|
|
4
|
+
id: 'javascript',
|
|
5
|
+
aliases: ['js'],
|
|
6
|
+
import: () => import('@shikijs/langs/javascript')
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
id: 'typescript',
|
|
10
|
+
aliases: ['ts'],
|
|
11
|
+
import: () => import('@shikijs/langs/typescript')
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: 'html',
|
|
15
|
+
import: () => import('@shikijs/langs/html')
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: 'css',
|
|
19
|
+
import: () => import('@shikijs/langs/css')
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'json',
|
|
23
|
+
import: () => import('@shikijs/langs/json')
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'jsx',
|
|
27
|
+
import: () => import('@shikijs/langs/jsx')
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'tsx',
|
|
31
|
+
import: () => import('@shikijs/langs/tsx')
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'markdown',
|
|
35
|
+
aliases: ['md'],
|
|
36
|
+
import: () => import('@shikijs/langs/markdown')
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'yaml',
|
|
40
|
+
aliases: ['yml'],
|
|
41
|
+
import: () => import('@shikijs/langs/yaml')
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'xml',
|
|
45
|
+
import: () => import('@shikijs/langs/xml')
|
|
46
|
+
},
|
|
47
|
+
// Backend languages
|
|
48
|
+
{
|
|
49
|
+
id: 'python',
|
|
50
|
+
aliases: ['py'],
|
|
51
|
+
import: () => import('@shikijs/langs/python')
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 'java',
|
|
55
|
+
import: () => import('@shikijs/langs/java')
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'go',
|
|
59
|
+
import: () => import('@shikijs/langs/go')
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'rust',
|
|
63
|
+
aliases: ['rs'],
|
|
64
|
+
import: () => import('@shikijs/langs/rust')
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'ruby',
|
|
68
|
+
aliases: ['rb'],
|
|
69
|
+
import: () => import('@shikijs/langs/ruby')
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'php',
|
|
73
|
+
import: () => import('@shikijs/langs/php')
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'c',
|
|
77
|
+
import: () => import('@shikijs/langs/c')
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'cpp',
|
|
81
|
+
aliases: ['c++'],
|
|
82
|
+
import: () => import('@shikijs/langs/cpp')
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'csharp',
|
|
86
|
+
aliases: ['c#', 'cs'],
|
|
87
|
+
import: () => import('@shikijs/langs/csharp')
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'sql',
|
|
91
|
+
import: () => import('@shikijs/langs/sql')
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'swift',
|
|
95
|
+
import: () => import('@shikijs/langs/swift')
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: 'kotlin',
|
|
99
|
+
aliases: ['kt', 'kts'],
|
|
100
|
+
import: () => import('@shikijs/langs/kotlin')
|
|
101
|
+
},
|
|
102
|
+
// Config/Shell
|
|
103
|
+
{
|
|
104
|
+
id: 'shellscript',
|
|
105
|
+
aliases: ['bash', 'sh', 'shell', 'zsh'],
|
|
106
|
+
import: () => import('@shikijs/langs/shellscript')
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: 'docker',
|
|
110
|
+
aliases: ['dockerfile'],
|
|
111
|
+
import: () => import('@shikijs/langs/docker')
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'toml',
|
|
115
|
+
import: () => import('@shikijs/langs/toml')
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: 'graphql',
|
|
119
|
+
aliases: ['gql'],
|
|
120
|
+
import: () => import('@shikijs/langs/graphql')
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: 'svelte',
|
|
124
|
+
import: () => import('@shikijs/langs/svelte')
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: 'vue',
|
|
128
|
+
import: () => import('@shikijs/langs/vue')
|
|
129
|
+
}
|
|
130
|
+
];
|
|
131
|
+
// Helper function to create a Set of all language IDs and aliases from language info
|
|
132
|
+
export function createLanguageSet(languages) {
|
|
133
|
+
const set = new Set();
|
|
134
|
+
languages.forEach((lang) => {
|
|
135
|
+
set.add(lang.id);
|
|
136
|
+
if (lang.aliases) {
|
|
137
|
+
lang.aliases.forEach((alias) => set.add(alias));
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
return set;
|
|
141
|
+
}
|
|
142
|
+
// Create a Set of all supported language IDs and aliases for fast lookup (default set)
|
|
143
|
+
export const supportedLanguages = createLanguageSet(bundledLanguagesInfo);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { onMount } from 'svelte';
|
|
2
|
+
export const useDarkMode = () => {
|
|
3
|
+
let isDark = $state(false);
|
|
4
|
+
const checkTheme = () => {
|
|
5
|
+
const elements = [document.documentElement, document.body];
|
|
6
|
+
// First check for explicit light mode
|
|
7
|
+
for (const element of elements) {
|
|
8
|
+
if (element.classList.contains('light') ||
|
|
9
|
+
element.dataset.theme === 'light' ||
|
|
10
|
+
element.style.colorScheme === 'light') {
|
|
11
|
+
isDark = false;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
// Then check for dark mode
|
|
16
|
+
for (const element of elements) {
|
|
17
|
+
if (element.classList.contains('dark') ||
|
|
18
|
+
element.dataset.theme === 'dark' ||
|
|
19
|
+
element.style.colorScheme === 'dark') {
|
|
20
|
+
isDark = true;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Default to light mode if no explicit theme is set
|
|
25
|
+
isDark = false;
|
|
26
|
+
};
|
|
27
|
+
onMount(() => {
|
|
28
|
+
// Initial check
|
|
29
|
+
checkTheme();
|
|
30
|
+
// Watch for class, attribute, and style changes on html and body
|
|
31
|
+
const observer = new MutationObserver(() => {
|
|
32
|
+
checkTheme();
|
|
33
|
+
});
|
|
34
|
+
const observerOptions = {
|
|
35
|
+
attributes: true,
|
|
36
|
+
attributeFilter: ['class', 'data-theme', 'style']
|
|
37
|
+
};
|
|
38
|
+
observer.observe(document.documentElement, observerOptions);
|
|
39
|
+
observer.observe(document.body, observerOptions);
|
|
40
|
+
return () => {
|
|
41
|
+
observer.disconnect();
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
get current() {
|
|
46
|
+
return isDark;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
};
|
|
@@ -1,32 +1,30 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import type { ThemedToken, ThemeRegistration } from 'shiki';
|
|
2
|
+
import type { HighlighterCore } from 'shiki/core';
|
|
3
|
+
import { SvelteMap } from 'svelte/reactivity';
|
|
4
|
+
import { type LanguageInfo } from './bundledLanguages.js';
|
|
5
|
+
export type Highlighter = HighlighterCore;
|
|
5
6
|
declare class HighlighterManager {
|
|
6
|
-
loadedLanguages: SvelteMap<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
loadedLanguages: SvelteMap<string, boolean | Promise<void>>;
|
|
8
|
+
highlighter: any;
|
|
9
|
+
customLanguages: Set<string>;
|
|
10
|
+
languageLoaders: Map<string, () => Promise<any>>;
|
|
11
|
+
additionalThemes: Record<string, ThemeRegistration>;
|
|
12
|
+
constructor(languages: LanguageInfo[], additionalThemes?: Record<string, ThemeRegistration>, additionalLanguages?: LanguageInfo[]);
|
|
11
13
|
private loadHighlighter;
|
|
12
|
-
private
|
|
14
|
+
private isThemeAvailable;
|
|
13
15
|
private loadLanguage;
|
|
14
16
|
private isLanguageSupported;
|
|
15
|
-
isReady(theme:
|
|
16
|
-
/**
|
|
17
|
-
* Preloads themes by creating minimal highlighter instances.
|
|
18
|
-
* This reduces flickering when switching themes.
|
|
19
|
-
*/
|
|
20
|
-
preloadThemes(highlighter: Highlighter): Promise<void>;
|
|
17
|
+
isReady(theme: string, language: string | undefined): boolean;
|
|
21
18
|
/**
|
|
22
19
|
* Ensures the highlighter is ready for the given theme and language.
|
|
23
20
|
*/
|
|
24
|
-
load(theme:
|
|
21
|
+
load(theme: string, language: string | undefined): Promise<void>;
|
|
25
22
|
/**
|
|
26
23
|
* Highlights code synchronously. Must call isReady() first.
|
|
24
|
+
* Returns plaintext tokens for unsupported languages.
|
|
27
25
|
*/
|
|
28
|
-
highlightCode(code: string, language: string | undefined, theme:
|
|
29
|
-
static create(
|
|
26
|
+
highlightCode(code: string, language: string | undefined, theme: string): ThemedToken[][];
|
|
27
|
+
static create(languages?: LanguageInfo[], additionalThemes?: Record<string, ThemeRegistration>, additionalLanguages?: LanguageInfo[]): HighlighterManager;
|
|
30
28
|
}
|
|
31
29
|
export { HighlighterManager };
|
|
32
30
|
export declare const languageExtensionMap: Record<string, string>;
|
|
@@ -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 {};
|