@lobehub/ui
Version:
Lobe UI is an open-source UI component library for building AIGC web apps
115 lines (114 loc) • 3.55 kB
JavaScript
import { visit } from "../../node_modules/unist-util-visit/lib/index.mjs";
import { getNow } from "../../utils/getNow.mjs";
//#region src/Markdown/plugins/rehypeStreamAnimated.ts
const WORD_SEGMENT_RE = /\s+|\S+/g;
const wordSegmenter = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter(void 0, { granularity: "word" }) : null;
const segmentWords = (value) => {
if (!wordSegmenter) return value.match(WORD_SEGMENT_RE) ?? [];
const segments = [];
for (const item of wordSegmenter.segment(value)) segments.push(item.segment);
return segments;
};
const BLOCK_TAGS = new Set([
"p",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"li"
]);
const SKIP_TAGS = new Set([
"pre",
"code",
"table",
"svg"
]);
function hasClass(node, cls) {
const cn = node.properties?.className;
if (Array.isArray(cn)) return cn.some((c) => String(c).includes(cls));
if (typeof cn === "string") return cn.includes(cls);
return false;
}
const rehypeStreamAnimated = (options = {}) => {
const { births, fadeDuration = 150, granularity = "char", nowMs, revealed = false, runtime } = options;
const resolvedRuntime = revealed ? void 0 : runtime ?? (Array.isArray(births) && typeof nowMs === "number" ? {
births,
styles: []
} : void 0);
const nowOverride = runtime ? void 0 : nowMs;
return (tree) => {
let globalCharIndex = 0;
const now = nowOverride ?? (resolvedRuntime ? getNow() : 0);
const shouldSkip = (node) => {
return SKIP_TAGS.has(node.tagName) || hasClass(node, "katex");
};
const resolveStyle = (index) => {
const styles = resolvedRuntime.styles;
const cached = styles[index];
if (cached !== void 0) return cached;
const birthTs = resolvedRuntime.births[index];
let resolved;
if (birthTs === void 0) resolved = null;
else {
const elapsed = now - birthTs;
resolved = elapsed >= fadeDuration ? null : `animation-delay:${-elapsed}ms`;
}
styles[index] = resolved;
return resolved;
};
const buildSpan = (value, startIndex) => {
let className = "stream-char";
let style;
if (revealed) className = "stream-char stream-char-revealed";
else if (resolvedRuntime) {
const resolved = resolveStyle(startIndex);
if (resolved === null) className = "stream-char stream-char-revealed";
else style = resolved;
}
const properties = { className };
if (style !== void 0) properties.style = style;
return {
children: [{
type: "text",
value
}],
properties,
tagName: "span",
type: "element"
};
};
const wrapText = (node) => {
const newChildren = [];
for (const child of node.children) if (child.type === "text") if (granularity === "word") for (const segment of segmentWords(child.value)) {
const startIndex = globalCharIndex;
for (const _char of segment) globalCharIndex++;
if (segment.trim() === "") newChildren.push({
type: "text",
value: segment
});
else newChildren.push(buildSpan(segment, startIndex));
}
else for (const char of child.value) {
newChildren.push(buildSpan(char, globalCharIndex));
globalCharIndex++;
}
else if (child.type === "element") {
if (!shouldSkip(child)) wrapText(child);
newChildren.push(child);
} else newChildren.push(child);
node.children = newChildren;
};
visit(tree, "element", ((node) => {
if (shouldSkip(node)) return "skip";
if (BLOCK_TAGS.has(node.tagName)) {
wrapText(node);
return "skip";
}
}));
};
};
//#endregion
export { rehypeStreamAnimated };
//# sourceMappingURL=rehypeStreamAnimated.mjs.map