@lobehub/ui
Version:
Lobe UI is an open-source UI component library for building AIGC web apps
132 lines (129 loc) • 4.32 kB
JavaScript
'use client';
import { getCodeLanguageByInput } from "../Highlighter/const.mjs";
import lobe_theme_default from "../Highlighter/theme/lobe-theme.mjs";
import { useEffect, useMemo, useState } from "react";
import { transformerNotationDiff, transformerNotationErrorLevel, transformerNotationFocus, transformerNotationHighlight, transformerNotationWordHighlight } from "@shikijs/transformers";
import { Md5 } from "ts-md5";
//#region src/hooks/useHighlight.ts
const MD5_LENGTH_THRESHOLD = 1e4;
const highlightCache = /* @__PURE__ */ new Map();
const MAX_CACHE_SIZE = 1e3;
const cleanupCache = () => {
if (highlightCache.size > MAX_CACHE_SIZE) {
const entriesToRemove = Math.floor(MAX_CACHE_SIZE * .2);
const keysToRemove = Array.from(highlightCache.keys()).slice(0, entriesToRemove);
for (const key of keysToRemove) highlightCache.delete(key);
}
};
let codeToHtmlPromise = null;
const loadCodeToHtml = () => {
if (typeof window === "undefined") return Promise.resolve(null);
if (!codeToHtmlPromise) codeToHtmlPromise = import("shiki").then((mod) => mod.codeToHtml ?? null);
return codeToHtmlPromise;
};
const loadShikiModule = () => {
if (typeof window === "undefined") return Promise.resolve(null);
return import("shiki");
};
const shikiModulePromise = loadShikiModule();
const escapeHtml = (str) => {
return str.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
};
const customThemes = { "lobe-theme": lobe_theme_default };
const useHighlight = (text, { language, enableTransformer, theme: builtinTheme, streaming }) => {
const safeText = text ?? "";
const lang = (language ?? "plaintext").toLowerCase();
const matchedLanguage = useMemo(() => getCodeLanguageByInput(lang), [lang]);
const transformers = useMemo(() => {
if (!enableTransformer) return;
return [
transformerNotationDiff(),
transformerNotationHighlight(),
transformerNotationWordHighlight(),
transformerNotationFocus(),
transformerNotationErrorLevel()
];
}, [enableTransformer]);
const cacheKey = useMemo(() => {
if (streaming) return null;
return [
matchedLanguage,
builtinTheme,
safeText.length < MD5_LENGTH_THRESHOLD ? safeText : Md5.hashStr(safeText)
].filter(Boolean).join("-");
}, [
safeText,
matchedLanguage,
builtinTheme,
streaming
]);
const [data, setData] = useState();
useEffect(() => {
if (!cacheKey) {
setData(void 0);
return;
}
const cachedPromise = highlightCache.get(cacheKey);
if (cachedPromise) {
cachedPromise.then((html) => {
setData(html);
}).catch(() => {});
return;
}
const highlightPromise = (async () => {
try {
const shikiModule = await shikiModulePromise;
if (!shikiModule) return safeText;
const effectiveTheme = builtinTheme || "lobe-theme";
if (!builtinTheme && effectiveTheme === "lobe-theme") {
const customTheme = customThemes[effectiveTheme];
if (customTheme) return await (await shikiModule.getSingletonHighlighter({
langs: [matchedLanguage],
themes: [customTheme]
})).codeToHtml(safeText, {
lang: matchedLanguage,
theme: effectiveTheme,
transformers
});
}
const codeToHtml = await loadCodeToHtml();
if (!codeToHtml) return safeText;
return await codeToHtml(safeText, {
lang: matchedLanguage,
theme: effectiveTheme,
transformers
});
} catch (error_) {
console.error("Advanced rendering failed:", error_);
try {
const codeToHtml = await loadCodeToHtml();
if (!codeToHtml) return safeText;
return await codeToHtml(safeText, {
lang: matchedLanguage,
theme: "lobe-theme"
});
} catch {
return `<pre class="fallback"><code>${escapeHtml(safeText)}</code></pre>`;
}
}
})();
highlightCache.set(cacheKey, highlightPromise);
cleanupCache();
highlightPromise.then((html) => {
if (highlightCache.get(cacheKey) === highlightPromise) setData(html);
}).catch(() => {
if (highlightCache.get(cacheKey) === highlightPromise) highlightCache.delete(cacheKey);
});
}, [
cacheKey,
safeText,
matchedLanguage,
builtinTheme,
transformers,
customThemes
]);
return data || "";
};
//#endregion
export { shikiModulePromise, useHighlight };
//# sourceMappingURL=useHighlight.mjs.map