svelte-streamdown 1.0.4 → 1.0.6
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.
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { setContext } from 'svelte';
|
|
3
|
-
import type { Snippet } from 'svelte';
|
|
4
|
-
import type { BundledLanguage, BundledTheme } from 'shiki';
|
|
5
2
|
import { useStreamdown } from '../Streamdown.svelte';
|
|
6
3
|
import { save } from '../utils/save.js';
|
|
7
4
|
import { useCopy } from '../utils/copy.svelte.js';
|
|
@@ -11,8 +8,8 @@
|
|
|
11
8
|
|
|
12
9
|
let { node, className, props }: ElementProps = $props();
|
|
13
10
|
|
|
14
|
-
const highlighter = HighlighterManager.create();
|
|
15
11
|
const streamdown = useStreamdown();
|
|
12
|
+
const highlighter = HighlighterManager.create(streamdown.shikiPreloadThemes || []);
|
|
16
13
|
const theme = $derived(streamdown.shikiTheme);
|
|
17
14
|
let codeContent = $derived((node.children[0] as any).value);
|
|
18
15
|
const language = $derived(node.properties.language as string);
|
|
@@ -41,8 +38,6 @@
|
|
|
41
38
|
$effect(() => {
|
|
42
39
|
void highlighter.load(theme, language as any);
|
|
43
40
|
});
|
|
44
|
-
|
|
45
|
-
$inspect(highlighter.isReady(theme, language as any));
|
|
46
41
|
</script>
|
|
47
42
|
|
|
48
43
|
<div
|
package/dist/Streamdown.svelte
CHANGED
|
@@ -7,15 +7,16 @@ declare class HighlighterManager {
|
|
|
7
7
|
private highlighters;
|
|
8
8
|
private createHighlighter;
|
|
9
9
|
private engine;
|
|
10
|
+
preloadedThemes: BundledTheme[];
|
|
10
11
|
loadedLanguages: SvelteSet<BundledLanguage>;
|
|
11
12
|
loadedThemes: SvelteSet<BundledTheme>;
|
|
12
|
-
constructor();
|
|
13
|
+
constructor(preloadedThemes: BundledTheme[]);
|
|
13
14
|
isReady(theme: BundledTheme, language: BundledLanguage): boolean;
|
|
14
15
|
/**
|
|
15
16
|
* Preloads themes by creating minimal highlighter instances.
|
|
16
17
|
* This reduces flickering when switching themes.
|
|
17
18
|
*/
|
|
18
|
-
preloadThemes(themes: BundledTheme[]): Promise<void>;
|
|
19
|
+
preloadThemes(themes: BundledTheme[], language: BundledLanguage): Promise<void>;
|
|
19
20
|
/**
|
|
20
21
|
* Ensures the highlighter is ready for the given theme and language.
|
|
21
22
|
*/
|
|
@@ -24,7 +25,7 @@ declare class HighlighterManager {
|
|
|
24
25
|
* Highlights code synchronously. Must call isReady() first.
|
|
25
26
|
*/
|
|
26
27
|
highlightCode(code: string, language: BundledLanguage, theme: BundledTheme, preClassName?: string): string;
|
|
27
|
-
static create(): HighlighterManager;
|
|
28
|
+
static create(preloadedThemes: BundledTheme[]): HighlighterManager;
|
|
28
29
|
}
|
|
29
30
|
export { HighlighterManager };
|
|
30
31
|
export declare const languageExtensionMap: Record<string, string>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {} from 'shiki';
|
|
2
2
|
import { untrack } from 'svelte';
|
|
3
3
|
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
|
4
|
+
import { StreamdownContext, useStreamdown } from './Streamdown.svelte';
|
|
4
5
|
// Remove background styles from <pre> tags (inline style)
|
|
5
6
|
const removePreBackground = (html) => {
|
|
6
7
|
return html.replace(/<pre[^>]*style="[^"]*background[^";]*;?[^"]*"[^>]*>/g, (match) => match.replace(/style="[^"]*background[^";]*;?[^"]*"/, ''));
|
|
@@ -17,9 +18,11 @@ class HighlighterManager {
|
|
|
17
18
|
highlighters = new SvelteMap();
|
|
18
19
|
createHighlighter = null;
|
|
19
20
|
engine = null;
|
|
21
|
+
preloadedThemes;
|
|
20
22
|
loadedLanguages = new SvelteSet();
|
|
21
23
|
loadedThemes = new SvelteSet();
|
|
22
|
-
constructor() {
|
|
24
|
+
constructor(preloadedThemes) {
|
|
25
|
+
this.preloadedThemes = preloadedThemes;
|
|
23
26
|
if (typeof window !== 'undefined') {
|
|
24
27
|
Object.assign(window, {
|
|
25
28
|
STREAMDOWN_HIGHLIGHTER: this
|
|
@@ -33,29 +36,29 @@ class HighlighterManager {
|
|
|
33
36
|
* Preloads themes by creating minimal highlighter instances.
|
|
34
37
|
* This reduces flickering when switching themes.
|
|
35
38
|
*/
|
|
36
|
-
async preloadThemes(themes) {
|
|
37
|
-
if (!
|
|
39
|
+
async preloadThemes(themes, language) {
|
|
40
|
+
if (!themes.length) {
|
|
38
41
|
return;
|
|
39
42
|
}
|
|
40
|
-
|
|
41
|
-
.filter((theme) => !this.
|
|
43
|
+
await Promise.all(themes
|
|
44
|
+
.filter((theme) => !this.highlighters.has(`${theme}:${language}`))
|
|
42
45
|
.map(async (theme) => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
this.loadedThemes.add(theme);
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
console.warn(`Failed to preload theme ${theme}:`, error);
|
|
46
|
+
if (!this.createHighlighter || !this.engine) {
|
|
47
|
+
return;
|
|
49
48
|
}
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
const highlighter = await this.createHighlighter?.({
|
|
50
|
+
themes: [theme],
|
|
51
|
+
langs: [language],
|
|
52
|
+
engine: this.engine
|
|
53
|
+
});
|
|
54
|
+
this.highlighters.set(`${theme}:${language}`, highlighter);
|
|
55
|
+
}));
|
|
52
56
|
}
|
|
53
57
|
/**
|
|
54
58
|
* Ensures the highlighter is ready for the given theme and language.
|
|
55
59
|
*/
|
|
56
60
|
async load(theme, language) {
|
|
57
61
|
return untrack(async () => {
|
|
58
|
-
console.log('load', theme, language);
|
|
59
62
|
if (!this.createHighlighter || !this.engine) {
|
|
60
63
|
const [engine, createHighlighter] = await loadShiki();
|
|
61
64
|
this.createHighlighter = createHighlighter;
|
|
@@ -69,41 +72,16 @@ class HighlighterManager {
|
|
|
69
72
|
});
|
|
70
73
|
this.highlighters.set(`${theme}:${language}`, highlighter);
|
|
71
74
|
}
|
|
72
|
-
|
|
73
|
-
// highlighter = await this.createHighlighter({
|
|
74
|
-
// themes: [theme],
|
|
75
|
-
// langs: [language],
|
|
76
|
-
// engine: this.engine
|
|
77
|
-
// });
|
|
78
|
-
// this.highlighter = highlighter;
|
|
79
|
-
// this.loadedThemes.add(theme);
|
|
80
|
-
// this.loadedLanguages.add(language);
|
|
81
|
-
// }
|
|
82
|
-
// if (!this.loadedThemes.has(theme)) {
|
|
83
|
-
// console.log('loading theme', theme);
|
|
84
|
-
// await this.highlighter.loadTheme(theme);
|
|
85
|
-
// }
|
|
86
|
-
// if (!this.loadedLanguages.has(language)) {
|
|
87
|
-
// console.log('loading language', language);
|
|
88
|
-
// await this.highlighter.loadLanguage(language);
|
|
89
|
-
// }
|
|
90
|
-
// this.loadedThemes.add(theme);
|
|
91
|
-
// this.loadedLanguages.add(language);
|
|
75
|
+
void this.preloadThemes(this.preloadedThemes || [], language);
|
|
92
76
|
});
|
|
93
77
|
}
|
|
94
78
|
/**
|
|
95
79
|
* Highlights code synchronously. Must call isReady() first.
|
|
96
80
|
*/
|
|
97
81
|
highlightCode(code, language, theme, preClassName) {
|
|
98
|
-
console.log('highlightCode', theme, language, this.isReady(theme, language));
|
|
99
|
-
if (!this.isReady(theme, language)) {
|
|
100
|
-
throw new Error(`Highlighter not ready for theme "${theme}" and language "${language}". ` +
|
|
101
|
-
`Call await highlighter.isReady(theme, language) first.`);
|
|
102
|
-
}
|
|
103
82
|
const highlighter = this.highlighters.get(`${theme}:${language}`);
|
|
104
83
|
if (!highlighter) {
|
|
105
|
-
|
|
106
|
-
`Call await highlighter.load(theme, language) first.`);
|
|
84
|
+
return '';
|
|
107
85
|
}
|
|
108
86
|
let html = highlighter.codeToHtml(code, {
|
|
109
87
|
lang: language,
|
|
@@ -116,12 +94,14 @@ class HighlighterManager {
|
|
|
116
94
|
}
|
|
117
95
|
return html;
|
|
118
96
|
}
|
|
119
|
-
static create() {
|
|
97
|
+
static create(preloadedThemes) {
|
|
120
98
|
if (typeof window !== 'undefined' && 'STREAMDOWN_HIGHLIGHTER' in window) {
|
|
99
|
+
const previousHighlighter = window.STREAMDOWN_HIGHLIGHTER;
|
|
100
|
+
previousHighlighter.preloadedThemes = Array.from(new Set([...previousHighlighter.preloadedThemes, ...preloadedThemes]));
|
|
121
101
|
// @ts-ignore
|
|
122
102
|
return window.STREAMDOWN_HIGHLIGHTER;
|
|
123
103
|
}
|
|
124
|
-
return new HighlighterManager();
|
|
104
|
+
return new HighlighterManager(preloadedThemes);
|
|
125
105
|
}
|
|
126
106
|
}
|
|
127
107
|
// Export the class for those who want to create their own instances
|
package/dist/utils/parse.js
CHANGED
|
@@ -1,26 +1,14 @@
|
|
|
1
1
|
import { urlAttributes } from 'html-url-attributes';
|
|
2
2
|
import remarkParse from 'remark-parse';
|
|
3
|
-
import remarkRehype from 'remark-rehype';
|
|
3
|
+
import remarkRehype, {} from 'remark-rehype';
|
|
4
4
|
import { unified } from 'unified';
|
|
5
5
|
import { visit } from 'unist-util-visit';
|
|
6
|
-
import { VFile } from 'vfile';
|
|
7
6
|
import remarkGfm from 'remark-gfm';
|
|
8
7
|
import remarkMath from 'remark-math';
|
|
9
8
|
import { rehypeGithubAlerts } from './alerts.js';
|
|
10
|
-
import { squeezeParagraphs } from 'mdast-squeeze-paragraphs';
|
|
11
9
|
const emptyPlugins = [];
|
|
12
10
|
const emptyRemarkRehypeOptions = { allowDangerousHtml: true };
|
|
13
11
|
const safeProtocol = /^(https?|ircs?|mailto|xmpp)$/i;
|
|
14
|
-
function createFile(content) {
|
|
15
|
-
const file = new VFile();
|
|
16
|
-
if (typeof content === 'string') {
|
|
17
|
-
file.value = content;
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
throw new Error('Unexpected value `' + content + '` for content, expected `string`');
|
|
21
|
-
}
|
|
22
|
-
return file;
|
|
23
|
-
}
|
|
24
12
|
export function parseMarkdown(context, content) {
|
|
25
13
|
const rehypePlugins = context.rehypePlugins || emptyPlugins;
|
|
26
14
|
const remarkPlugins = context.remarkPlugins || emptyPlugins;
|
|
@@ -35,9 +23,8 @@ export function parseMarkdown(context, content) {
|
|
|
35
23
|
.use(remarkRehype, remarkRehypeOptions)
|
|
36
24
|
.use(rehypePlugins)
|
|
37
25
|
.use([rehypeGithubAlerts]);
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
return post(processor.runSync(tree, file), {
|
|
26
|
+
const tree = processor.runSync(processor.parse(content));
|
|
27
|
+
return post(tree, {
|
|
41
28
|
allowElement: context.allowElement,
|
|
42
29
|
allowedElements: context.allowedElements,
|
|
43
30
|
disallowedElements: context.disallowedElements,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-streamdown",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"build": "vite build && npm run prepack",
|
|
@@ -76,7 +76,6 @@
|
|
|
76
76
|
"katex": "^0.16.22",
|
|
77
77
|
"marked": "^16.2.1",
|
|
78
78
|
"mdast": "^3.0.0",
|
|
79
|
-
"mdast-squeeze-paragraphs": "^6.0.0",
|
|
80
79
|
"mermaid": "^11.11.0",
|
|
81
80
|
"rehype-github-alerts": "^4.1.1",
|
|
82
81
|
"rehype-katex": "^7.0.1",
|
|
@@ -87,7 +86,6 @@
|
|
|
87
86
|
"shiki": "^3.12.2",
|
|
88
87
|
"tailwind-merge": "^3.3.1",
|
|
89
88
|
"unified": "^11.0.5",
|
|
90
|
-
"unist-util-visit": "^5.0.0"
|
|
91
|
-
"vfile": "^6.0.3"
|
|
89
|
+
"unist-util-visit": "^5.0.0"
|
|
92
90
|
}
|
|
93
91
|
}
|