@dbx-tools/ui-mastra 0.9.111 → 0.9.112
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/index.ts +2 -3
- package/package.json +6 -7
- package/src/react/bubbles.tsx +1 -1
- package/src/react/markdown.tsx +3 -44
- package/src/react/tool-pill.tsx +50 -16
- package/src/support/_shiki-runtime.ts +0 -27
- package/src/support/shiki-plugin.ts +0 -158
package/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Hand edits are overwritten on the next watch; this file is read-only.
|
|
4
4
|
|
|
5
5
|
export const PACKAGE_IDENTIFIER = "@dbx-tools/ui-mastra";
|
|
6
|
-
export const PACKAGE_VERSION = "0.9.
|
|
6
|
+
export const PACKAGE_VERSION = "0.9.112";
|
|
7
7
|
export * as reactBubbles from "./src/react/bubbles.tsx";
|
|
8
8
|
export * as reactChatApprovals from "./src/react/chat-approvals.ts";
|
|
9
9
|
export * as reactChatComposer from "./src/react/chat-composer.tsx";
|
|
@@ -35,7 +35,6 @@ export * as supportExport from "./src/support/export.ts";
|
|
|
35
35
|
export * as supportMastraClient from "./src/support/mastra-client.ts";
|
|
36
36
|
export * as supportModelSelection from "./src/support/model-selection.ts";
|
|
37
37
|
export * as supportRequestContext from "./src/support/request-context.ts";
|
|
38
|
-
export * as supportShikiPlugin from "./src/support/shiki-plugin.ts";
|
|
39
38
|
export * as supportThreadLabels from "./src/support/thread-labels.ts";
|
|
40
39
|
export * as supportThreadSessions from "./src/support/thread-sessions.ts";
|
|
41
40
|
export * as supportThreadTabs from "./src/support/thread-tabs.ts";
|
|
@@ -52,7 +51,7 @@ export type { DataRow } from "./src/react/data-grid.tsx";
|
|
|
52
51
|
export { MarkdownWithEmbeds } from "./src/react/embed-slots.tsx";
|
|
53
52
|
export { ExportMenu } from "./src/react/export-menu.tsx";
|
|
54
53
|
export { FeedbackControls } from "./src/react/feedback-controls.tsx";
|
|
55
|
-
export { AssistantMarkdown, ToolMarkdown, SqlBlock,
|
|
54
|
+
export { AssistantMarkdown, ToolMarkdown, SqlBlock, SourceBlock } from "./src/react/markdown.tsx";
|
|
56
55
|
export type { MastraAssistantMode, MastraAssistantSide, MastraAssistantLauncherPosition, MastraAssistantResizeOptions, MastraAssistantLauncherOptions, MastraAssistantController, MastraAssistantProps } from "./src/react/mastra-assistant.tsx";
|
|
57
56
|
export { useMastraChat, MastraChat } from "./src/react/mastra-chat.tsx";
|
|
58
57
|
export type { UseMastraChatOptions, MastraChatProps } from "./src/react/mastra-chat.tsx";
|
package/package.json
CHANGED
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
"react-dom": "~19.2.4"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@dbx-tools/shared-core": "^0.9.
|
|
33
|
-
"@dbx-tools/shared-genie": "^0.9.
|
|
34
|
-
"@dbx-tools/shared-mastra": "^0.9.
|
|
35
|
-
"@dbx-tools/shared-model": "^0.9.
|
|
36
|
-
"@dbx-tools/ui": "^0.9.
|
|
32
|
+
"@dbx-tools/shared-core": "^0.9.112",
|
|
33
|
+
"@dbx-tools/shared-genie": "^0.9.112",
|
|
34
|
+
"@dbx-tools/shared-mastra": "^0.9.112",
|
|
35
|
+
"@dbx-tools/shared-model": "^0.9.112",
|
|
36
|
+
"@dbx-tools/ui": "^0.9.112",
|
|
37
37
|
"@mastra/client-js": "1.50.0",
|
|
38
38
|
"@mastra/core": "1.71.0",
|
|
39
39
|
"@tanstack/react-table": "^8.21.3",
|
|
@@ -42,7 +42,6 @@
|
|
|
42
42
|
"echarts-for-react": "^3.0.2",
|
|
43
43
|
"lucide-react": "^0.554.0",
|
|
44
44
|
"marked": "^18.0.5",
|
|
45
|
-
"shiki": "^3.0.0",
|
|
46
45
|
"sql-formatter": "^15.6.9",
|
|
47
46
|
"streamdown": "^2.5.0"
|
|
48
47
|
},
|
|
@@ -50,7 +49,7 @@
|
|
|
50
49
|
"publishConfig": {
|
|
51
50
|
"access": "public"
|
|
52
51
|
},
|
|
53
|
-
"version": "0.9.
|
|
52
|
+
"version": "0.9.112",
|
|
54
53
|
"dbxToolsConfig": {
|
|
55
54
|
"tags": [
|
|
56
55
|
"ui"
|
package/src/react/bubbles.tsx
CHANGED
|
@@ -192,7 +192,7 @@ const ToolApprovalCard = ({
|
|
|
192
192
|
{asEmailInput(input) ? (
|
|
193
193
|
<EmailApprovalPreview email={asEmailInput(input)!} />
|
|
194
194
|
) : (
|
|
195
|
-
<RecordPreview value={input} className="rounded bg-background/40" />
|
|
195
|
+
<RecordPreview value={input} className="max-h-80 rounded bg-background/40" />
|
|
196
196
|
)}
|
|
197
197
|
{expired ? (
|
|
198
198
|
<div className="mt-3 flex items-center gap-1.5 text-xs font-medium text-muted-foreground">
|
package/src/react/markdown.tsx
CHANGED
|
@@ -10,6 +10,9 @@ import {
|
|
|
10
10
|
Tooltip,
|
|
11
11
|
TooltipContent,
|
|
12
12
|
TooltipTrigger,
|
|
13
|
+
createShikiPlugin,
|
|
14
|
+
highlightToHtml,
|
|
15
|
+
useHighlightedHtml,
|
|
13
16
|
cn,
|
|
14
17
|
} from "@dbx-tools/ui/react";
|
|
15
18
|
import { CheckIcon, CopyIcon } from "lucide-react";
|
|
@@ -17,7 +20,6 @@ import React, { useEffect, useRef, useState } from "react";
|
|
|
17
20
|
import { Streamdown } from "streamdown";
|
|
18
21
|
import { DataGrid, TABLE_WRAPPER_CLASSES, colorizeDelta, type DataRow } from "./data-grid.tsx";
|
|
19
22
|
import { copyText } from "../support/clipboard.ts";
|
|
20
|
-
import { createShikiPlugin, highlightToHtml } from "../support/shiki-plugin.ts";
|
|
21
23
|
|
|
22
24
|
// Markdown rendering for the chat: the streaming `Streamdown` engine
|
|
23
25
|
// wired with shiki highlighting and AppKit table primitives, plus the
|
|
@@ -181,27 +183,6 @@ const TOOL_MARKDOWN_COMPONENTS = {
|
|
|
181
183
|
*/
|
|
182
184
|
const SHIKI_PLUGIN = { code: createShikiPlugin() };
|
|
183
185
|
|
|
184
|
-
/** Resolve syntax-highlighted HTML while preserving plaintext during lazy load. */
|
|
185
|
-
function useHighlightedHtml(source: string, language: string): string | null {
|
|
186
|
-
const [highlighted, setHighlighted] = useState<{
|
|
187
|
-
source: string;
|
|
188
|
-
language: string;
|
|
189
|
-
html: string;
|
|
190
|
-
} | null>(null);
|
|
191
|
-
useEffect(() => {
|
|
192
|
-
let active = true;
|
|
193
|
-
void highlightToHtml(source, language).then((result) => {
|
|
194
|
-
if (active) setHighlighted({ source, language, html: result });
|
|
195
|
-
});
|
|
196
|
-
return () => {
|
|
197
|
-
active = false;
|
|
198
|
-
};
|
|
199
|
-
}, [language, source]);
|
|
200
|
-
return highlighted?.source === source && highlighted.language === language
|
|
201
|
-
? highlighted.html
|
|
202
|
-
: null;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
186
|
/**
|
|
206
187
|
* Per-word fade-in applied while a reply is still streaming. Streamdown
|
|
207
188
|
* wraps each newly arrived token in a `[data-sd-animate]` span and only
|
|
@@ -397,28 +378,6 @@ export const SqlBlock = ({ sql }: { sql: string }) => {
|
|
|
397
378
|
);
|
|
398
379
|
};
|
|
399
380
|
|
|
400
|
-
/**
|
|
401
|
-
* Render formatted JSON with the same lazily loaded Shiki runtime used by
|
|
402
|
-
* SQL and fenced Markdown code blocks.
|
|
403
|
-
*/
|
|
404
|
-
export const JsonBlock = ({ json, className }: { json: string; className?: string }) => {
|
|
405
|
-
const html = useHighlightedHtml(json, "json");
|
|
406
|
-
return (
|
|
407
|
-
<pre
|
|
408
|
-
className={cn(
|
|
409
|
-
"max-w-full overflow-auto whitespace-pre-wrap break-words font-mono text-xs leading-relaxed",
|
|
410
|
-
className,
|
|
411
|
-
)}
|
|
412
|
-
>
|
|
413
|
-
{html === null ? (
|
|
414
|
-
<code>{json}</code>
|
|
415
|
-
) : (
|
|
416
|
-
<code data-dbx-highlighted-code dangerouslySetInnerHTML={{ __html: html }} />
|
|
417
|
-
)}
|
|
418
|
-
</pre>
|
|
419
|
-
);
|
|
420
|
-
};
|
|
421
|
-
|
|
422
381
|
/** Compact syntax-highlighted source block for tool request previews. */
|
|
423
382
|
export const SourceBlock = ({
|
|
424
383
|
source,
|
package/src/react/tool-pill.tsx
CHANGED
|
@@ -4,11 +4,13 @@ import {
|
|
|
4
4
|
Collapsible,
|
|
5
5
|
CollapsibleContent,
|
|
6
6
|
CollapsibleTrigger,
|
|
7
|
+
JsonBlock,
|
|
8
|
+
RecordPreview,
|
|
7
9
|
Spinner,
|
|
8
10
|
cn,
|
|
9
11
|
} from "@dbx-tools/ui/react";
|
|
10
12
|
import { CheckIcon, ChevronDownIcon, XIcon } from "lucide-react";
|
|
11
|
-
import {
|
|
13
|
+
import { SourceBlock, SqlBlock, ToolMarkdown } from "./markdown.tsx";
|
|
12
14
|
import type { ToolEvent, ToolProgress } from "./types.ts";
|
|
13
15
|
|
|
14
16
|
// Consolidated tool-session pill and its Genie progress detail view:
|
|
@@ -388,20 +390,50 @@ export function formatRawToolPayload(value: unknown): string {
|
|
|
388
390
|
return JSON.stringify(value, null, 2) ?? String(value);
|
|
389
391
|
}
|
|
390
392
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
393
|
+
/** Return structured JSON from an object value or a complete JSON string. */
|
|
394
|
+
export function structuredToolPayload(value: unknown): unknown | undefined {
|
|
395
|
+
if (typeof value === "object" && value !== null) return value;
|
|
396
|
+
if (typeof value !== "string") return undefined;
|
|
397
|
+
const trimmed = value.trim();
|
|
398
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return undefined;
|
|
399
|
+
try {
|
|
400
|
+
const parsed: unknown = JSON.parse(trimmed);
|
|
401
|
+
return typeof parsed === "object" && parsed !== null ? parsed : undefined;
|
|
402
|
+
} catch {
|
|
403
|
+
return undefined;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const RawToolPayload = ({
|
|
408
|
+
label,
|
|
409
|
+
value,
|
|
410
|
+
}: {
|
|
411
|
+
label: "Request" | "Response" | "Output";
|
|
412
|
+
value: unknown;
|
|
413
|
+
}) => {
|
|
414
|
+
const structured = structuredToolPayload(value);
|
|
415
|
+
return (
|
|
416
|
+
<Collapsible className="rounded border border-border/60 bg-background/40">
|
|
417
|
+
<CollapsibleTrigger className="group flex w-full items-center gap-1.5 px-2 py-1 text-left text-xs uppercase tracking-wide text-muted-foreground hover:text-foreground">
|
|
418
|
+
<ChevronDownIcon className="size-3 shrink-0 transition-transform group-data-[state=closed]:-rotate-90" />
|
|
419
|
+
<span>{label}</span>
|
|
420
|
+
</CollapsibleTrigger>
|
|
421
|
+
<CollapsibleContent>
|
|
422
|
+
{structured === undefined ? (
|
|
423
|
+
<JsonBlock
|
|
424
|
+
json={formatRawToolPayload(value)}
|
|
425
|
+
className="max-h-80 border-t border-border/50 px-2 py-2 text-foreground"
|
|
426
|
+
/>
|
|
427
|
+
) : (
|
|
428
|
+
<RecordPreview
|
|
429
|
+
value={structured}
|
|
430
|
+
className="max-h-80 border-t border-border/50 bg-transparent text-foreground"
|
|
431
|
+
/>
|
|
432
|
+
)}
|
|
433
|
+
</CollapsibleContent>
|
|
434
|
+
</Collapsible>
|
|
435
|
+
);
|
|
436
|
+
};
|
|
405
437
|
|
|
406
438
|
export type ToolInputPresentation = {
|
|
407
439
|
label: "Code" | "Command" | "Output";
|
|
@@ -621,7 +653,9 @@ const ToolCallDetails = ({
|
|
|
621
653
|
) : null}
|
|
622
654
|
<WebSearchProgressDetails event={event} />
|
|
623
655
|
<ToolProgressDetails summary={summary} omitQuestion />
|
|
624
|
-
{outputPresentation ? (
|
|
656
|
+
{outputPresentation?.language === "json" ? (
|
|
657
|
+
<RawToolPayload label="Output" value={outputPresentation.source} />
|
|
658
|
+
) : outputPresentation ? (
|
|
625
659
|
<ToolInputSource presentation={outputPresentation} />
|
|
626
660
|
) : "output" in event ? (
|
|
627
661
|
<RawToolPayload label="Response" value={event.output} />
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fine-grained Shiki runtime containing only the chat's supported languages.
|
|
3
|
-
*
|
|
4
|
-
* This module is itself dynamically imported, so its grammar, theme, and WASM
|
|
5
|
-
* dependencies remain off the network until a supported code block appears.
|
|
6
|
-
*/
|
|
7
|
-
import { createHighlighterCore } from "shiki/core";
|
|
8
|
-
import { createOnigurumaEngine } from "shiki/engine/oniguruma";
|
|
9
|
-
import bash from "shiki/langs/bash.mjs";
|
|
10
|
-
import javascript from "shiki/langs/javascript.mjs";
|
|
11
|
-
import json from "shiki/langs/json.mjs";
|
|
12
|
-
import markdown from "shiki/langs/markdown.mjs";
|
|
13
|
-
import python from "shiki/langs/python.mjs";
|
|
14
|
-
import sql from "shiki/langs/sql.mjs";
|
|
15
|
-
import typescript from "shiki/langs/typescript.mjs";
|
|
16
|
-
import yaml from "shiki/langs/yaml.mjs";
|
|
17
|
-
import githubDarkHighContrast from "shiki/themes/github-dark-high-contrast.mjs";
|
|
18
|
-
import githubLightHighContrast from "shiki/themes/github-light-high-contrast.mjs";
|
|
19
|
-
|
|
20
|
-
/** Create the shared highlighter with the exact configured language set. */
|
|
21
|
-
export function createHighlighter() {
|
|
22
|
-
return createHighlighterCore({
|
|
23
|
-
themes: [githubLightHighContrast, githubDarkHighContrast],
|
|
24
|
-
langs: [sql, python, typescript, javascript, json, bash, yaml, markdown],
|
|
25
|
-
engine: createOnigurumaEngine(import("shiki/wasm")),
|
|
26
|
-
});
|
|
27
|
-
}
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { stringUtils } from "@dbx-tools/shared-core";
|
|
2
|
-
import type { BundledLanguage, BundledTheme } from "shiki";
|
|
3
|
-
import type { HighlighterCore } from "shiki/core";
|
|
4
|
-
import type { CodeHighlighterPlugin } from "streamdown";
|
|
5
|
-
|
|
6
|
-
// Streamdown 2.x ships syntax highlighting as an opt-in plugin and has
|
|
7
|
-
// no shiki dependency of its own; without a `code` plugin every fenced
|
|
8
|
-
// block renders as uncolored plaintext. This module provides a small
|
|
9
|
-
// shiki-backed highlighter to wire in via `plugins={{ code }}`.
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Languages we highlight in the chat. SQL is the primary one (Genie
|
|
13
|
-
* query previews); the rest cover code the assistant might emit. Kept
|
|
14
|
-
* to a curated set so shiki only bundles these grammars.
|
|
15
|
-
*/
|
|
16
|
-
const LANGUAGES = [
|
|
17
|
-
"text",
|
|
18
|
-
"plaintext",
|
|
19
|
-
"txt",
|
|
20
|
-
"sql",
|
|
21
|
-
"python",
|
|
22
|
-
"typescript",
|
|
23
|
-
"javascript",
|
|
24
|
-
"json",
|
|
25
|
-
"bash",
|
|
26
|
-
"yaml",
|
|
27
|
-
"markdown",
|
|
28
|
-
] as const;
|
|
29
|
-
|
|
30
|
-
/** High-contrast theme pair used for every syntax surface. */
|
|
31
|
-
const THEMES = [
|
|
32
|
-
"github-light-high-contrast",
|
|
33
|
-
"github-dark-high-contrast",
|
|
34
|
-
] as const satisfies readonly [BundledTheme, BundledTheme];
|
|
35
|
-
|
|
36
|
-
/** Languages we can tokenize, as a set for O(1) support checks. */
|
|
37
|
-
const SUPPORTED = new Set<string>(LANGUAGES);
|
|
38
|
-
|
|
39
|
-
let _highlighter: HighlighterCore | null = null;
|
|
40
|
-
let _loading: Promise<HighlighterCore> | null = null;
|
|
41
|
-
|
|
42
|
-
/** Lazily create the shared shiki highlighter (singleton, loaded once). */
|
|
43
|
-
function loadHighlighter(): Promise<HighlighterCore> {
|
|
44
|
-
_loading ??= import("./_shiki-runtime.ts")
|
|
45
|
-
.then(({ createHighlighter }) => createHighlighter())
|
|
46
|
-
.then((highlighter) => {
|
|
47
|
-
_highlighter = highlighter;
|
|
48
|
-
return highlighter;
|
|
49
|
-
});
|
|
50
|
-
return _loading;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function splitThemeColor(
|
|
54
|
-
value: string | undefined,
|
|
55
|
-
darkVariable: "--shiki-dark" | "--shiki-dark-bg",
|
|
56
|
-
): { light?: string; dark?: string } {
|
|
57
|
-
if (!value) return {};
|
|
58
|
-
const marker = `;${darkVariable}:`;
|
|
59
|
-
const index = value.indexOf(marker);
|
|
60
|
-
if (index < 0) return { light: value };
|
|
61
|
-
return {
|
|
62
|
-
light: value.slice(0, index),
|
|
63
|
-
dark: value.slice(index + marker.length),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Tokenize `code` with the active themes into Streamdown's result shape. */
|
|
68
|
-
function highlightTokens(h: HighlighterCore, code: string, language: string) {
|
|
69
|
-
const { tokens, fg, bg, rootStyle } = h.codeToTokens(code, {
|
|
70
|
-
lang: language as BundledLanguage,
|
|
71
|
-
themes: { light: THEMES[0], dark: THEMES[1] },
|
|
72
|
-
});
|
|
73
|
-
const foreground = splitThemeColor(fg, "--shiki-dark");
|
|
74
|
-
const background = splitThemeColor(bg, "--shiki-dark-bg");
|
|
75
|
-
const darkStyles = [
|
|
76
|
-
foreground.dark ? `--shiki-dark:${foreground.dark}` : "",
|
|
77
|
-
background.dark ? `--shiki-dark-bg:${background.dark}` : "",
|
|
78
|
-
]
|
|
79
|
-
.filter(Boolean)
|
|
80
|
-
.join(";");
|
|
81
|
-
return {
|
|
82
|
-
tokens,
|
|
83
|
-
fg: foreground.light,
|
|
84
|
-
bg: background.light,
|
|
85
|
-
rootStyle: [rootStyle, darkStyles].filter(Boolean).join(";"),
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** Escape HTML-significant characters (from the shared string utils). */
|
|
90
|
-
const escapeHtml = stringUtils.escapeHtml;
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Highlight `code` into minimal inline HTML: one colored `<span>` per
|
|
94
|
-
* token, lines joined by real newlines, with no line-number gutter or
|
|
95
|
-
* per-line wrapper elements. Meant to drop straight into a
|
|
96
|
-
* `<pre><code>` so the rendered text stays cleanly selectable and
|
|
97
|
-
* copyable. Falls back to plain escaped text when the language isn't
|
|
98
|
-
* supported or shiki fails to parse the snippet.
|
|
99
|
-
*/
|
|
100
|
-
export async function highlightToHtml(code: string, language: string): Promise<string> {
|
|
101
|
-
if (!SUPPORTED.has(language)) return escapeHtml(code);
|
|
102
|
-
const h = await loadHighlighter();
|
|
103
|
-
try {
|
|
104
|
-
const { tokens } = highlightTokens(h, code, language);
|
|
105
|
-
return tokens
|
|
106
|
-
.map((line) =>
|
|
107
|
-
line
|
|
108
|
-
.map((token) => {
|
|
109
|
-
const light = token.htmlStyle?.color ?? token.color ?? "inherit";
|
|
110
|
-
const dark = token.htmlStyle?.["--shiki-dark"] ?? light;
|
|
111
|
-
const style = `--sdm-c:${light};--shiki-dark:${dark}`;
|
|
112
|
-
return `<span style="${escapeHtml(style)}">${escapeHtml(token.content)}</span>`;
|
|
113
|
-
})
|
|
114
|
-
.join(""),
|
|
115
|
-
)
|
|
116
|
-
.join("\n");
|
|
117
|
-
} catch {
|
|
118
|
-
return escapeHtml(code);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* shiki-backed code highlighter plugin for Streamdown. The highlighter
|
|
124
|
-
* loads asynchronously: the first call for any block returns `null`
|
|
125
|
-
* and resolves through the `callback` once shiki is ready, after which
|
|
126
|
-
* results are synchronous. Unsupported languages return `null` so the
|
|
127
|
-
* block stays plaintext rather than throwing.
|
|
128
|
-
*/
|
|
129
|
-
export function createShikiPlugin(): CodeHighlighterPlugin {
|
|
130
|
-
const isSupported = (language: string): boolean => SUPPORTED.has(language);
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
name: "shiki",
|
|
134
|
-
type: "code-highlighter",
|
|
135
|
-
getSupportedLanguages: () => [...LANGUAGES],
|
|
136
|
-
getThemes: () => [...THEMES],
|
|
137
|
-
supportsLanguage: (language) => isSupported(language),
|
|
138
|
-
highlight: (options, callback) => {
|
|
139
|
-
if (!isSupported(options.language)) return null;
|
|
140
|
-
const language = options.language;
|
|
141
|
-
if (_highlighter) {
|
|
142
|
-
try {
|
|
143
|
-
return highlightTokens(_highlighter, options.code, language);
|
|
144
|
-
} catch {
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
void loadHighlighter().then((h) => {
|
|
149
|
-
try {
|
|
150
|
-
callback?.(highlightTokens(h, options.code, language));
|
|
151
|
-
} catch {
|
|
152
|
-
// Parse failure for this block - leave it as plaintext.
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
return null;
|
|
156
|
-
},
|
|
157
|
-
};
|
|
158
|
-
}
|