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.
Files changed (38) hide show
  1. package/README.md +47 -51
  2. package/dist/AnimatedText.svelte +6 -8
  3. package/dist/Block.svelte +2 -2
  4. package/dist/Elements/Alert.svelte +19 -18
  5. package/dist/Elements/Code.svelte +20 -17
  6. package/dist/Elements/Image.svelte +22 -20
  7. package/dist/Elements/Link.svelte +1 -1
  8. package/dist/Elements/Math.svelte +4 -7
  9. package/dist/Elements/Mermaid.svelte +76 -68
  10. package/dist/Streamdown.svelte +4 -10
  11. package/dist/context.svelte.d.ts +17 -4
  12. package/dist/context.svelte.js +2 -1
  13. package/dist/marked/index.d.ts +4 -2
  14. package/dist/marked/index.js +10 -6
  15. package/dist/marked/marked-alert.d.ts +1 -1
  16. package/dist/marked/marked-alert.js +14 -5
  17. package/dist/marked/marked-br.d.ts +12 -0
  18. package/dist/marked/marked-br.js +21 -0
  19. package/dist/marked/marked-footnotes.d.ts +1 -1
  20. package/dist/marked/marked-footnotes.js +1 -1
  21. package/dist/marked/marked-hr.d.ts +12 -0
  22. package/dist/marked/marked-hr.js +24 -0
  23. package/dist/marked/marked-list.d.ts +2 -1
  24. package/dist/marked/marked-list.js +9 -3
  25. package/dist/marked/marked-math.d.ts +7 -21
  26. package/dist/marked/marked-math.js +88 -17
  27. package/dist/marked/marked-subsup.d.ts +1 -1
  28. package/dist/marked/marked-subsup.js +1 -1
  29. package/dist/marked/marked-table.d.ts +1 -0
  30. package/dist/marked/marked-table.js +11 -4
  31. package/dist/theme.d.ts +9 -1
  32. package/dist/theme.js +4 -2
  33. package/dist/utils/hightlighter.svelte.d.ts +15 -13
  34. package/dist/utils/hightlighter.svelte.js +101 -57
  35. package/dist/utils/parse-incomplete-markdown.js +352 -157
  36. package/package.json +4 -2
  37. package/dist/Elements/Table.svelte +0 -30
  38. package/dist/Elements/Table.svelte.d.ts +0 -9
@@ -1,6 +1,20 @@
1
- // Use the standard rule that requires proper spacing after math expressions
2
- const inlineRule = /^(\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n\$]))\1(?=[\s?!\.,:?!。,:]|$)/;
3
- const blockRule = /^(\${1,2})\n((?:\\[\s\S]|[^\\])+?)\n\1(?:\n|$)/;
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: match[2].trim()
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 indexSrc = src;
28
- while (indexSrc) {
29
- index = indexSrc.indexOf('$');
30
- if (index === -1) {
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 f = index > -1;
34
- if (f) {
35
- const possibleKatex = indexSrc.substring(index);
36
- if (possibleKatex.match(inlineRule)) {
37
- return index;
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
- indexSrc = indexSrc.substring(index + 1).replace(/^\$+/, '');
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: match[2].trim()
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
+ }
@@ -1,5 +1,5 @@
1
1
  import type { TokenizerExtensionFunction, TokenizerStartFunction } from 'marked';
2
- export default function markedSubSup(): {
2
+ export declare function markedSubSup(): {
3
3
  extensions: {
4
4
  name: string;
5
5
  level: 'inline';
@@ -1,6 +1,6 @@
1
1
  const subRule = /^~([^~\s](?:[^~]*[^~\s])?)~/; // ~text~
2
2
  const supRule = /^\^([^\^\s](?:[^\^]*[^\^\s])?)\^/; // ^text^
3
- export default function markedSubSup() {
3
+ export function markedSubSup() {
4
4
  return {
5
5
  extensions: [
6
6
  {
@@ -48,6 +48,7 @@ export interface TableToken {
48
48
  type: 'table';
49
49
  tokens: TableSection[];
50
50
  raw: string;
51
+ align: (string | null)[];
51
52
  }
52
53
  interface WorkingCell extends BaseCell {
53
54
  }
@@ -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 (cellText.slice(-1) === '^' && prevRow.length > 0) {
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
- cell.text = cell.text.slice(0, -1);
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
- export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tailwind" | "shadcn") => {
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: 'line-through'
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: 'line-through text-muted-foreground'
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
- initialized: boolean;
6
- private highlighters;
7
- private createHighlighter;
8
- private engine;
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
- isReady(theme: BundledTheme, language: BundledLanguage): boolean;
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(themes: BundledTheme[], language: BundledLanguage): Promise<void>;
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: BundledLanguage): Promise<void>;
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: BundledLanguage, theme: BundledTheme): ThemedToken[][];
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
- initialized = $state(false);
15
- highlighters = new SvelteMap();
16
- createHighlighter = null;
17
- engine = null;
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
- this.preloadedThemes = preloadedThemes;
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
- isReady(theme, language) {
30
- return this.highlighters.has(`${theme}:${language}`);
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(themes, language) {
37
- if (!themes.length) {
38
- return;
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
- const highlighter = await this.createHighlighter?.({
47
- themes: [theme],
48
- langs: isLanguageSupported(language) ? [language] : ['text'],
49
- engine: this.engine
50
- });
51
- this.highlighters.set(`${theme}:${language}`, highlighter);
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
- return untrack(async () => {
59
- if (!this.createHighlighter || !this.engine) {
60
- const [engine, createHighlighter] = await loadShiki();
61
- this.createHighlighter = createHighlighter;
62
- this.engine = engine;
63
- }
64
- if (!this.highlighters.has(`${theme}:${language}`)) {
65
- const highlighter = await this.createHighlighter({
66
- themes: [theme],
67
- langs: isLanguageSupported(language) ? [language] : ['text'],
68
- engine: this.engine
69
- });
70
- this.highlighters.set(`${theme}:${language}`, highlighter);
71
- }
72
- void this.preloadThemes(this.preloadedThemes || [], language);
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
- const highlighter = this.highlighters.get(`${theme}:${language}`);
80
- if (!highlighter) {
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
- previousHighlighter.preloadedThemes = Array.from(new Set([...previousHighlighter.preloadedThemes, ...preloadedThemes]));
93
- // @ts-ignore
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
  }