svelte-streamdown 2.2.7 → 2.3.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 +47 -51
- package/dist/AnimatedText.svelte +6 -8
- package/dist/Block.svelte +2 -2
- package/dist/Elements/Alert.svelte +19 -18
- package/dist/Elements/Code.svelte +20 -17
- package/dist/Elements/Image.svelte +22 -20
- package/dist/Elements/Link.svelte +1 -1
- package/dist/Elements/Math.svelte +4 -7
- package/dist/Elements/Mermaid.svelte +76 -68
- package/dist/Streamdown.svelte +4 -10
- package/dist/context.svelte.d.ts +17 -4
- package/dist/context.svelte.js +2 -1
- package/dist/marked/index.d.ts +4 -2
- package/dist/marked/index.js +10 -6
- package/dist/marked/marked-alert.d.ts +1 -1
- package/dist/marked/marked-alert.js +14 -5
- package/dist/marked/marked-br.d.ts +12 -0
- package/dist/marked/marked-br.js +21 -0
- package/dist/marked/marked-footnotes.d.ts +1 -1
- package/dist/marked/marked-footnotes.js +1 -1
- package/dist/marked/marked-hr.d.ts +12 -0
- package/dist/marked/marked-hr.js +24 -0
- package/dist/marked/marked-list.d.ts +2 -1
- package/dist/marked/marked-list.js +9 -3
- package/dist/marked/marked-math.d.ts +7 -21
- package/dist/marked/marked-math.js +88 -17
- package/dist/marked/marked-subsup.d.ts +1 -1
- package/dist/marked/marked-subsup.js +1 -1
- package/dist/marked/marked-table.d.ts +1 -0
- package/dist/marked/marked-table.js +11 -4
- package/dist/theme.d.ts +9 -1
- package/dist/theme.js +4 -2
- package/dist/utils/hightlighter.svelte.d.ts +15 -13
- package/dist/utils/hightlighter.svelte.js +101 -57
- package/dist/utils/parse-incomplete-markdown.js +352 -157
- package/package.json +4 -2
- package/dist/Elements/Table.svelte +0 -30
- package/dist/Elements/Table.svelte.d.ts +0 -9
|
@@ -1,6 +1,20 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
const blockRule = /^(
|
|
1
|
+
// Math parsing rules
|
|
2
|
+
// Block math: supports both newline format ($$\nmath\n$$) and single-line format ($$math$$)
|
|
3
|
+
const blockRule = /^(\$\$)(?:\n((?:\\[\s\S]|[^\\])+?)\n\1(?:\n|$)|([^$\n]+?)\1(?=\s|$|$))/;
|
|
4
|
+
// Inline math: handles both single ($) and double ($$) dollar delimiters
|
|
5
|
+
// Avoids matching currency by checking context and requiring proper content
|
|
6
|
+
const inlineRule = /^(\${1,2})(?!\$)((?:[^$\n]|\\\$)*?)\1(?!\d)/;
|
|
7
|
+
// Enhanced currency detection patterns
|
|
8
|
+
const currencyPatterns = {
|
|
9
|
+
// Simple price patterns: $123, $123.45, $1,234.56
|
|
10
|
+
simplePrice: /^\d{1,3}(?:,\d{3})*(?:\.\d{2})?$/,
|
|
11
|
+
// Multiple prices or numbers: "123, 456", "123.45, 678.90", "123 or 456"
|
|
12
|
+
multipleNumbers: /^\d+(?:[.,]\d+)*(?:\s*[,;]\s*\d+(?:[.,]\d+)*)+$/,
|
|
13
|
+
// Price ranges: "123-456", "123 - 456", "123 to 456"
|
|
14
|
+
priceRange: /^\d+(?:\.\d{2})?\s*(?:-|to|or)\s*\d+(?:\.\d{2})?$/i,
|
|
15
|
+
// Common currency words nearby (check surrounding context)
|
|
16
|
+
currencyContext: /(?:price|cost|dollar|euro|pound|yen|currency|pay|buy|sell|expensive|cheap)/i
|
|
17
|
+
};
|
|
4
18
|
export function markedMath() {
|
|
5
19
|
return {
|
|
6
20
|
extensions: [
|
|
@@ -10,11 +24,14 @@ export function markedMath() {
|
|
|
10
24
|
tokenizer(src) {
|
|
11
25
|
const match = src.match(blockRule);
|
|
12
26
|
if (match) {
|
|
27
|
+
// match[2] is multiline format, match[3] is single-line format
|
|
28
|
+
const content = (match[2] || match[3]).trim();
|
|
13
29
|
return {
|
|
14
30
|
type: 'math',
|
|
15
31
|
isInline: false,
|
|
32
|
+
displayMode: true,
|
|
16
33
|
raw: match[0],
|
|
17
|
-
text:
|
|
34
|
+
text: content
|
|
18
35
|
};
|
|
19
36
|
}
|
|
20
37
|
}
|
|
@@ -23,31 +40,54 @@ export function markedMath() {
|
|
|
23
40
|
name: 'math',
|
|
24
41
|
level: 'inline',
|
|
25
42
|
start(src) {
|
|
26
|
-
let index;
|
|
27
|
-
let
|
|
28
|
-
while (
|
|
29
|
-
|
|
30
|
-
if (
|
|
43
|
+
let index = 0;
|
|
44
|
+
let searchSrc = src;
|
|
45
|
+
while (searchSrc) {
|
|
46
|
+
const dollarIndex = searchSrc.indexOf('$');
|
|
47
|
+
if (dollarIndex === -1) {
|
|
31
48
|
return;
|
|
32
49
|
}
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
50
|
+
const currentIndex = index + dollarIndex;
|
|
51
|
+
const possibleMath = src.substring(currentIndex);
|
|
52
|
+
// Check if this could be math (not currency)
|
|
53
|
+
if (possibleMath.match(inlineRule)) {
|
|
54
|
+
const match = possibleMath.match(inlineRule);
|
|
55
|
+
if (match) {
|
|
56
|
+
const content = match[2];
|
|
57
|
+
const dollarCount = match[1]; // '$' or '$$'
|
|
58
|
+
// Only apply currency detection to single dollars
|
|
59
|
+
// Double dollars ($$) indicate explicit math intent
|
|
60
|
+
if (dollarCount === '$' && isCurrencyPattern(content, src, currentIndex)) {
|
|
61
|
+
// This looks like currency with single dollars, skip it
|
|
62
|
+
index += dollarIndex + 1;
|
|
63
|
+
searchSrc = src.substring(index);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
return currentIndex;
|
|
38
67
|
}
|
|
39
68
|
}
|
|
40
|
-
|
|
69
|
+
index += dollarIndex + 1;
|
|
70
|
+
searchSrc = src.substring(index);
|
|
41
71
|
}
|
|
42
72
|
},
|
|
43
73
|
tokenizer(src) {
|
|
44
74
|
const match = src.match(inlineRule);
|
|
45
75
|
if (match) {
|
|
76
|
+
const content = match[2];
|
|
77
|
+
const dollarCount = match[1]; // '$' or '$$'
|
|
78
|
+
const isDisplayMode = dollarCount === '$$';
|
|
79
|
+
// Only apply currency detection to single dollars
|
|
80
|
+
// Double dollars ($$) indicate explicit math intent
|
|
81
|
+
if (dollarCount === '$' && isCurrencyPattern(content, src, 0)) {
|
|
82
|
+
// This looks like currency with single dollars, skip it
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
46
85
|
return {
|
|
47
86
|
type: 'math',
|
|
48
|
-
isInline: true,
|
|
87
|
+
isInline: true, // Inline tokenizer always produces inline math
|
|
88
|
+
displayMode: isDisplayMode, // $$ = display mode styling, $ = inline styling
|
|
49
89
|
raw: match[0],
|
|
50
|
-
text:
|
|
90
|
+
text: content.trim()
|
|
51
91
|
};
|
|
52
92
|
}
|
|
53
93
|
}
|
|
@@ -55,3 +95,34 @@ export function markedMath() {
|
|
|
55
95
|
]
|
|
56
96
|
};
|
|
57
97
|
}
|
|
98
|
+
// Helper function to detect currency patterns
|
|
99
|
+
function isCurrencyPattern(content, fullSrc, dollarIndex) {
|
|
100
|
+
const trimmedContent = content.trim();
|
|
101
|
+
// Check for simple price patterns
|
|
102
|
+
if (currencyPatterns.simplePrice.test(trimmedContent)) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
// Check for multiple numbers/prices pattern (like "199, 199")
|
|
106
|
+
if (currencyPatterns.multipleNumbers.test(trimmedContent)) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
// Check for price ranges
|
|
110
|
+
if (currencyPatterns.priceRange.test(trimmedContent)) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
// Check surrounding context for currency-related words
|
|
114
|
+
const contextStart = Math.max(0, dollarIndex - 50);
|
|
115
|
+
const contextEnd = Math.min(fullSrc.length, dollarIndex + content.length + 50);
|
|
116
|
+
const context = fullSrc.substring(contextStart, contextEnd);
|
|
117
|
+
if (currencyPatterns.currencyContext.test(context)) {
|
|
118
|
+
// If currency context is found and content is purely numeric, likely currency
|
|
119
|
+
if (/^\d+(?:[.,]\d+)*$/.test(trimmedContent)) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Additional check: if content is just numbers with common currency formatting
|
|
124
|
+
if (/^\d{1,3}(?:,\d{3})*(?:\.\d{1,2})?$/.test(trimmedContent)) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
@@ -114,8 +114,11 @@ const processSpans = (cells, count, prevRow = [], maxColspan = null) => {
|
|
|
114
114
|
for (i = 0; i < processedCells.length; i++) {
|
|
115
115
|
const cell = processedCells[i];
|
|
116
116
|
let cellText = cell.text;
|
|
117
|
-
// Handle Rowspan - cells ending with ^
|
|
118
|
-
if (
|
|
117
|
+
// Handle Rowspan - cells ending with ^ (but not superscript ^text^)
|
|
118
|
+
// Check if it's a rowspan indicator (single ^ at end) vs superscript (^text^)
|
|
119
|
+
const isRowspanIndicator = cellText.slice(-1) === '^' &&
|
|
120
|
+
!cellText.match(/\^[^^\n\r]+\^$/); // Not a superscript pattern ^text^
|
|
121
|
+
if (isRowspanIndicator && prevRow.length > 0) {
|
|
119
122
|
// Clean the ^ indicator from the cell text
|
|
120
123
|
cell.text = cellText.slice(0, -1).trim();
|
|
121
124
|
cellText = cell.text;
|
|
@@ -179,7 +182,10 @@ const processSpans = (cells, count, prevRow = [], maxColspan = null) => {
|
|
|
179
182
|
}
|
|
180
183
|
// If no target was found but it's a rowspan cell, clean the ^ indicator
|
|
181
184
|
if (!targetFound && cell.rowspan > 0) {
|
|
182
|
-
|
|
185
|
+
// Only clean if it was actually a rowspan indicator, not superscript
|
|
186
|
+
if (isRowspanIndicator) {
|
|
187
|
+
cell.text = cell.text.slice(0, -1);
|
|
188
|
+
}
|
|
183
189
|
}
|
|
184
190
|
}
|
|
185
191
|
}
|
|
@@ -485,7 +491,8 @@ export function markedTable(options = {}) {
|
|
|
485
491
|
const item = {
|
|
486
492
|
type: 'table',
|
|
487
493
|
tokens,
|
|
488
|
-
raw: cap[0]
|
|
494
|
+
raw: cap[0],
|
|
495
|
+
align: alignment
|
|
489
496
|
};
|
|
490
497
|
return item;
|
|
491
498
|
}
|
package/dist/theme.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export declare const theme: {
|
|
|
40
40
|
base: string;
|
|
41
41
|
container: string;
|
|
42
42
|
header: string;
|
|
43
|
+
buttons: string;
|
|
43
44
|
button: string;
|
|
44
45
|
language: string;
|
|
45
46
|
skeleton: string;
|
|
@@ -168,6 +169,7 @@ export declare const shadcnTheme: {
|
|
|
168
169
|
base: string;
|
|
169
170
|
container: string;
|
|
170
171
|
header: string;
|
|
172
|
+
buttons: string;
|
|
171
173
|
button: string;
|
|
172
174
|
language: string;
|
|
173
175
|
skeleton: string;
|
|
@@ -257,7 +259,11 @@ export declare const shadcnTheme: {
|
|
|
257
259
|
};
|
|
258
260
|
};
|
|
259
261
|
export type Theme = typeof theme;
|
|
260
|
-
|
|
262
|
+
type DeepPartial<T> = {
|
|
263
|
+
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
|
|
264
|
+
};
|
|
265
|
+
export type DeepPartialTheme = DeepPartial<Theme>;
|
|
266
|
+
export declare const mergeTheme: (customTheme?: DeepPartialTheme, baseTheme?: "tailwind" | "shadcn") => {
|
|
261
267
|
link: {
|
|
262
268
|
base: string;
|
|
263
269
|
blocked: string;
|
|
@@ -297,6 +303,7 @@ export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tai
|
|
|
297
303
|
base: string;
|
|
298
304
|
container: string;
|
|
299
305
|
header: string;
|
|
306
|
+
buttons: string;
|
|
300
307
|
button: string;
|
|
301
308
|
language: string;
|
|
302
309
|
skeleton: string;
|
|
@@ -385,3 +392,4 @@ export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tai
|
|
|
385
392
|
base: string;
|
|
386
393
|
};
|
|
387
394
|
};
|
|
395
|
+
export {};
|
package/dist/theme.js
CHANGED
|
@@ -41,6 +41,7 @@ export const theme = {
|
|
|
41
41
|
base: 'my-4 w-full overflow-hidden rounded-xl border border-gray-200 flex flex-col',
|
|
42
42
|
container: ' relative overflow-visible bg-gray-100 p-2 font-mono text-sm ',
|
|
43
43
|
header: 'flex items-center justify-between bg-gray-100/80 p-2 text-gray-600 text-xs',
|
|
44
|
+
buttons: 'flex items-center gap-2',
|
|
44
45
|
button: 'cursor-pointer size-6 p-1 text-gray-600 transition-all hover:text-gray-900 rounded hover:bg-gray-100',
|
|
45
46
|
language: 'ml-1 font-mono lowercase',
|
|
46
47
|
skeleton: 'block rounded-md font-mono text-transparent bg-gray-200 scale-y-90 animate-pulse whitespace-nowrap',
|
|
@@ -120,7 +121,7 @@ export const theme = {
|
|
|
120
121
|
base: 'italic'
|
|
121
122
|
},
|
|
122
123
|
del: {
|
|
123
|
-
base: '
|
|
124
|
+
base: 'text-gray-600'
|
|
124
125
|
},
|
|
125
126
|
footnoteRef: {
|
|
126
127
|
base: 'text-gray-600 px-1 py-0.5 rounded-md bg-gray-100/80'
|
|
@@ -169,6 +170,7 @@ export const shadcnTheme = {
|
|
|
169
170
|
base: 'my-4 w-full overflow-hidden rounded-lg border border-border flex flex-col',
|
|
170
171
|
container: 'relative overflow-visible bg-muted p-2 font-mono text-sm',
|
|
171
172
|
header: 'flex items-center justify-between bg-muted/80 px-2 py-1 text-muted-foreground text-xs',
|
|
173
|
+
buttons: 'flex items-center gap-2',
|
|
172
174
|
button: 'cursor-pointer size-6 p-1 text-muted-foreground transition-all hover:text-foreground rounded hover:bg-muted',
|
|
173
175
|
language: 'ml-1 font-mono lowercase',
|
|
174
176
|
skeleton: 'block rounded-md font-mono text-transparent bg-border/80 scale-y-90 w-fit animate-pulse whitespace-nowrap',
|
|
@@ -248,7 +250,7 @@ export const shadcnTheme = {
|
|
|
248
250
|
base: 'italic text-foreground'
|
|
249
251
|
},
|
|
250
252
|
del: {
|
|
251
|
-
base: '
|
|
253
|
+
base: 'text-muted-foreground'
|
|
252
254
|
},
|
|
253
255
|
footnoteRef: {
|
|
254
256
|
base: 'text-muted-foreground px-1 text-sm inline-block rounded-full bg-muted/80 aspect-square border border-border'
|
|
@@ -1,29 +1,31 @@
|
|
|
1
|
-
import { type BundledLanguage, type BundledTheme, type ThemedToken } from 'shiki';
|
|
2
|
-
import { SvelteSet } from 'svelte/reactivity';
|
|
1
|
+
import { type BundledLanguage, type BundledTheme, type HighlighterGeneric, type ThemedToken } from 'shiki';
|
|
2
|
+
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
|
3
3
|
export declare const loadShiki: () => Promise<[any, import("shiki").CreateHighlighterFactory<BundledLanguage, BundledTheme>]>;
|
|
4
|
+
export type Highlighter = HighlighterGeneric<BundledLanguage, BundledTheme>;
|
|
4
5
|
declare class HighlighterManager {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
preloadedThemes: BundledTheme[];
|
|
10
|
-
loadedLanguages: SvelteSet<BundledLanguage>;
|
|
11
|
-
loadedThemes: SvelteSet<BundledTheme>;
|
|
6
|
+
loadedLanguages: SvelteMap<BundledLanguage, boolean | Promise<void>>;
|
|
7
|
+
loadedThemes: SvelteMap<BundledTheme, boolean | Promise<void>>;
|
|
8
|
+
preloadedThemes: SvelteSet<BundledTheme>;
|
|
9
|
+
highlighter: Highlighter | Promise<Highlighter> | null;
|
|
12
10
|
constructor(preloadedThemes: BundledTheme[]);
|
|
13
|
-
|
|
11
|
+
private loadHighlighter;
|
|
12
|
+
private loadTheme;
|
|
13
|
+
private loadLanguage;
|
|
14
|
+
private isLanguageSupported;
|
|
15
|
+
isReady(theme: BundledTheme, language?: string | undefined): boolean;
|
|
14
16
|
/**
|
|
15
17
|
* Preloads themes by creating minimal highlighter instances.
|
|
16
18
|
* This reduces flickering when switching themes.
|
|
17
19
|
*/
|
|
18
|
-
preloadThemes(
|
|
20
|
+
preloadThemes(highlighter: Highlighter): Promise<void>;
|
|
19
21
|
/**
|
|
20
22
|
* Ensures the highlighter is ready for the given theme and language.
|
|
21
23
|
*/
|
|
22
|
-
load(theme: BundledTheme, language
|
|
24
|
+
load(theme: BundledTheme, language?: string | undefined): Promise<void>;
|
|
23
25
|
/**
|
|
24
26
|
* Highlights code synchronously. Must call isReady() first.
|
|
25
27
|
*/
|
|
26
|
-
highlightCode(code: string, language:
|
|
28
|
+
highlightCode(code: string, language: string | undefined, theme: BundledTheme): ThemedToken[][];
|
|
27
29
|
static create(preloadedThemes: BundledTheme[]): HighlighterManager;
|
|
28
30
|
}
|
|
29
31
|
export { HighlighterManager };
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { bundledLanguages } from 'shiki';
|
|
2
|
-
import { untrack } from 'svelte';
|
|
3
2
|
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
|
4
|
-
const isLanguageSupported = (language) => {
|
|
5
|
-
return Object.hasOwn(bundledLanguages, language);
|
|
6
|
-
};
|
|
7
3
|
export const loadShiki = async () => {
|
|
8
4
|
return Promise.all([
|
|
9
5
|
import('shiki/engine/javascript').then((mod) => mod.createJavaScriptRegexEngine({ forgiving: true })),
|
|
@@ -11,87 +7,135 @@ export const loadShiki = async () => {
|
|
|
11
7
|
]);
|
|
12
8
|
};
|
|
13
9
|
class HighlighterManager {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
preloadedThemes;
|
|
19
|
-
loadedLanguages = new SvelteSet();
|
|
20
|
-
loadedThemes = new SvelteSet();
|
|
10
|
+
loadedLanguages = new SvelteMap();
|
|
11
|
+
loadedThemes = new SvelteMap();
|
|
12
|
+
preloadedThemes = new SvelteSet();
|
|
13
|
+
highlighter = $state(null);
|
|
21
14
|
constructor(preloadedThemes) {
|
|
22
|
-
|
|
15
|
+
preloadedThemes.forEach((theme) => this.preloadedThemes.add(theme));
|
|
23
16
|
if (typeof window !== 'undefined') {
|
|
24
17
|
Object.assign(window, {
|
|
25
18
|
STREAMDOWN_HIGHLIGHTER: this
|
|
26
19
|
});
|
|
27
20
|
}
|
|
28
21
|
}
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
async loadHighlighter() {
|
|
23
|
+
if (this.highlighter instanceof Promise) {
|
|
24
|
+
return this.highlighter;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
this.highlighter = new Promise((resolve, reject) => {
|
|
28
|
+
loadShiki().then(([engine, createHighlighter]) => {
|
|
29
|
+
return createHighlighter({
|
|
30
|
+
themes: [],
|
|
31
|
+
langs: [],
|
|
32
|
+
engine
|
|
33
|
+
}).then((highlighter) => {
|
|
34
|
+
this.highlighter = highlighter;
|
|
35
|
+
resolve(highlighter);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
return this.highlighter;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async loadTheme(theme, highlighter) {
|
|
43
|
+
const themeLoader = this.loadedThemes.get(theme);
|
|
44
|
+
if (themeLoader instanceof Promise) {
|
|
45
|
+
await themeLoader;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const themeLoaderPromise = highlighter.loadTheme(theme).then(() => {
|
|
49
|
+
this.loadedThemes.set(theme, true);
|
|
50
|
+
});
|
|
51
|
+
this.loadedThemes.set(theme, themeLoaderPromise);
|
|
52
|
+
await themeLoaderPromise;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async loadLanguage(language, highlighter) {
|
|
56
|
+
const languageLoader = this.loadedLanguages.get(language);
|
|
57
|
+
if (languageLoader instanceof Promise) {
|
|
58
|
+
await languageLoader;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const languageLoaderPromise = highlighter.loadLanguage(language).then(() => {
|
|
62
|
+
this.loadedLanguages.set(language, true);
|
|
63
|
+
});
|
|
64
|
+
this.loadedLanguages.set(language, languageLoaderPromise);
|
|
65
|
+
await languageLoaderPromise;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
isLanguageSupported = (language) => {
|
|
69
|
+
return Object.hasOwn(bundledLanguages, language);
|
|
70
|
+
};
|
|
71
|
+
isReady(theme, language = 'bash') {
|
|
72
|
+
return (!(this.highlighter instanceof Promise) &&
|
|
73
|
+
this.loadedLanguages.get(this.isLanguageSupported(language) ? language : 'bash') === true &&
|
|
74
|
+
this.loadedThemes.get(theme) === true);
|
|
31
75
|
}
|
|
32
76
|
/**
|
|
33
77
|
* Preloads themes by creating minimal highlighter instances.
|
|
34
78
|
* This reduces flickering when switching themes.
|
|
35
79
|
*/
|
|
36
|
-
async preloadThemes(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
await Promise.all(themes
|
|
41
|
-
.filter((theme) => !this.highlighters.has(`${theme}:${language}`))
|
|
42
|
-
.map(async (theme) => {
|
|
43
|
-
if (!this.createHighlighter || !this.engine) {
|
|
80
|
+
async preloadThemes(highlighter) {
|
|
81
|
+
for (const theme of this.preloadedThemes) {
|
|
82
|
+
const themeLoader = this.loadedThemes.get(theme);
|
|
83
|
+
if (themeLoader === true) {
|
|
44
84
|
return;
|
|
45
85
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
86
|
+
else if (themeLoader instanceof Promise) {
|
|
87
|
+
await themeLoader;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
await this.loadTheme(theme, highlighter);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
53
93
|
}
|
|
54
94
|
/**
|
|
55
95
|
* Ensures the highlighter is ready for the given theme and language.
|
|
56
96
|
*/
|
|
57
|
-
async load(theme, language) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
});
|
|
97
|
+
async load(theme, language = 'bash') {
|
|
98
|
+
const themeLoader = this.loadedThemes.get(theme);
|
|
99
|
+
const languageLoader = this.loadedLanguages.get(this.isLanguageSupported(language) ? language : 'bash');
|
|
100
|
+
if (this.highlighter !== null &&
|
|
101
|
+
!(this.highlighter instanceof Promise) &&
|
|
102
|
+
themeLoader === true &&
|
|
103
|
+
languageLoader === true) {
|
|
104
|
+
// already loaded
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const highlighter = await this.loadHighlighter();
|
|
108
|
+
await Promise.all([
|
|
109
|
+
this.loadTheme(theme, highlighter),
|
|
110
|
+
this.loadLanguage(this.isLanguageSupported(language) ? language : 'bash', highlighter)
|
|
111
|
+
]);
|
|
112
|
+
await this.preloadThemes(highlighter);
|
|
74
113
|
}
|
|
75
114
|
/**
|
|
76
115
|
* Highlights code synchronously. Must call isReady() first.
|
|
77
116
|
*/
|
|
78
|
-
highlightCode(code, language, theme) {
|
|
79
|
-
|
|
80
|
-
|
|
117
|
+
highlightCode(code, language = 'bash', theme) {
|
|
118
|
+
try {
|
|
119
|
+
// const highlighter = this.highlighters.get(`${theme}:${language}`);
|
|
120
|
+
const highlighter = this.highlighter;
|
|
121
|
+
if (!highlighter || highlighter instanceof Promise) {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
const tokens = highlighter.codeToTokensBase(code, {
|
|
125
|
+
lang: this.isLanguageSupported(language) ? language : 'bash',
|
|
126
|
+
theme
|
|
127
|
+
});
|
|
128
|
+
return tokens;
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
81
131
|
return [];
|
|
82
132
|
}
|
|
83
|
-
const tokens = highlighter.codeToTokensBase(code, {
|
|
84
|
-
lang: isLanguageSupported(language) ? language : 'text',
|
|
85
|
-
theme: theme
|
|
86
|
-
});
|
|
87
|
-
return tokens;
|
|
88
133
|
}
|
|
89
134
|
static create(preloadedThemes) {
|
|
90
135
|
if (typeof window !== 'undefined' && 'STREAMDOWN_HIGHLIGHTER' in window) {
|
|
91
136
|
const previousHighlighter = window.STREAMDOWN_HIGHLIGHTER;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return window.STREAMDOWN_HIGHLIGHTER;
|
|
137
|
+
preloadedThemes.forEach((theme) => previousHighlighter.preloadedThemes.add(theme));
|
|
138
|
+
return previousHighlighter;
|
|
95
139
|
}
|
|
96
140
|
return new HighlighterManager(preloadedThemes);
|
|
97
141
|
}
|