@lobehub/ui 5.1.1 → 5.2.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.
- package/es/Accordion/Accordion.d.mts +2 -2
- package/es/Accordion/AccordionItem.d.mts +2 -2
- package/es/ActionIcon/ActionIcon.d.mts +2 -2
- package/es/Alert/Alert.d.mts +2 -2
- package/es/Avatar/AvatarGroup/index.d.mts +2 -2
- package/es/Burger/Burger.d.mts +2 -2
- package/es/CodeDiff/CodeDiff.d.mts +2 -2
- package/es/CodeDiff/PatchDiff.d.mts +2 -2
- package/es/CodeEditor/CodeEditor.d.mts +2 -2
- package/es/Collapse/Collapse.d.mts +2 -2
- package/es/ConfigProvider/index.d.mts +2 -2
- package/es/CopyButton/CopyButton.d.mts +2 -2
- package/es/DatePicker/DatePicker.d.mts +2 -2
- package/es/DraggablePanel/components/DraggablePanelBody.d.mts +2 -2
- package/es/DraggablePanel/components/DraggablePanelContainer.d.mts +2 -2
- package/es/DraggablePanel/components/DraggablePanelFooter.d.mts +2 -2
- package/es/DraggablePanel/components/DraggablePanelHeader.d.mts +2 -2
- package/es/DraggableSideNav/DraggableSideNav.d.mts +2 -2
- package/es/Drawer/Drawer.d.mts +2 -2
- package/es/Dropdown/Dropdown.d.mts +2 -2
- package/es/EditableText/EditableText.d.mts +2 -2
- package/es/EditorSlashMenu/atoms.d.mts +13 -13
- package/es/EmojiPicker/EmojiPicker.d.mts +2 -2
- package/es/Flex/FlexBasic.d.mts +2 -2
- package/es/FontLoader/index.d.mts +2 -2
- package/es/Footer/Footer.d.mts +2 -2
- package/es/Form/components/FormGroup.d.mts +2 -2
- package/es/Form/components/FormItem.d.mts +2 -2
- package/es/Form/components/FormSubmitFooter.d.mts +2 -2
- package/es/FormModal/FormModal.d.mts +2 -2
- package/es/Freeze/Freeze.d.mts +2 -2
- package/es/GuideCard/GuideCard.d.mts +2 -2
- package/es/Highlighter/Highlighter.d.mts +2 -2
- package/es/Highlighter/SyntaxHighlighter/index.d.mts +2 -2
- package/es/Hotkey/Hotkey.d.mts +2 -2
- package/es/HotkeyInput/HotkeyInput.d.mts +2 -2
- package/es/Icon/Icon.d.mts +2 -2
- package/es/Icon/components/IconProvider.d.mts +3 -3
- package/es/Image/PreviewGroup.d.mts +2 -2
- package/es/ImageSelect/ImageSelect.d.mts +2 -2
- package/es/Input/Input.d.mts +2 -2
- package/es/Input/InputNumber.d.mts +2 -2
- package/es/Input/InputOPT.d.mts +2 -2
- package/es/Input/InputPassword.d.mts +2 -2
- package/es/Input/TextArea.d.mts +2 -2
- package/es/Layout/components/LayoutFooter.d.mts +2 -2
- package/es/Layout/components/LayoutHeader.d.mts +2 -2
- package/es/Layout/components/LayoutMain.d.mts +2 -2
- package/es/Layout/components/LayoutSidebar.d.mts +2 -2
- package/es/Layout/components/LayoutSidebarInner.d.mts +2 -2
- package/es/Layout/components/LayoutToc.d.mts +2 -2
- package/es/List/ListItem/index.d.mts +2 -2
- package/es/Markdown/Markdown.d.mts +2 -2
- package/es/Markdown/SyntaxMarkdown/StreamdownRender.mjs +99 -15
- package/es/Markdown/SyntaxMarkdown/StreamdownRender.mjs.map +1 -1
- package/es/Markdown/SyntaxMarkdown/style.mjs +11 -6
- package/es/Markdown/SyntaxMarkdown/style.mjs.map +1 -1
- package/es/Markdown/SyntaxMarkdown/useStreamQueue.mjs +89 -0
- package/es/Markdown/SyntaxMarkdown/useStreamQueue.mjs.map +1 -0
- package/es/Markdown/Typography.d.mts +2 -2
- package/es/Markdown/components/SearchResultCards/index.d.mts +2 -2
- package/es/Markdown/plugins/rehypeStreamAnimated.d.mts +6 -1
- package/es/Markdown/plugins/rehypeStreamAnimated.mjs +57 -26
- package/es/Markdown/plugins/rehypeStreamAnimated.mjs.map +1 -1
- package/es/MaskShadow/MaskShadow.d.mts +2 -2
- package/es/Menu/Menu.d.mts +2 -2
- package/es/Mermaid/Mermaid.d.mts +2 -2
- package/es/Mermaid/SyntaxMermaid/index.d.mts +2 -2
- package/es/Modal/Modal.d.mts +2 -2
- package/es/Modal/ModalProvider.d.mts +2 -2
- package/es/Modal/imperative.d.mts +2 -2
- package/es/MotionProvider/index.d.mts +2 -2
- package/es/Popover/ArrowIcon.d.mts +2 -2
- package/es/Popover/atoms.d.mts +9 -9
- package/es/Popover/context.d.mts +2 -2
- package/es/SearchBar/SearchBar.d.mts +2 -2
- package/es/Segmented/Segmented.d.mts +2 -2
- package/es/Select/Select.d.mts +2 -2
- package/es/SideNav/SideNav.d.mts +2 -2
- package/es/SliderWithInput/SliderWithInput.d.mts +2 -2
- package/es/SortableList/components/DragHandle.d.mts +2 -2
- package/es/SortableList/components/SortableItem.d.mts +2 -2
- package/es/ThemeProvider/ThemeProvider.d.mts +2 -2
- package/es/Toc/Toc.d.mts +2 -2
- package/es/Video/index.d.mts +2 -2
- package/es/awesome/AuroraBackground/AuroraBackground.d.mts +2 -2
- package/es/awesome/BottomGradientButton/BottomGradientButton.d.mts +2 -2
- package/es/awesome/Features/Features.d.mts +2 -2
- package/es/awesome/Giscus/Giscus.d.mts +2 -2
- package/es/awesome/GradientButton/GradientButton.d.mts +2 -2
- package/es/awesome/GridBackground/GridBackground.d.mts +2 -2
- package/es/awesome/GridBackground/GridShowcase.d.mts +2 -2
- package/es/awesome/Hero/Hero.d.mts +2 -2
- package/es/awesome/Spline/Spine.d.mts +2 -2
- package/es/awesome/Spotlight/Spotlight.d.mts +2 -2
- package/es/awesome/SpotlightCard/SpotlightCard.d.mts +2 -2
- package/es/awesome/TypewriterEffect/TypewriterEffect.d.mts +2 -2
- package/es/base-ui/DropdownMenu/DropdownMenu.d.mts +2 -2
- package/es/base-ui/Modal/Modal.mjs +16 -4
- package/es/base-ui/Modal/Modal.mjs.map +1 -1
- package/es/base-ui/Modal/atoms.mjs +7 -4
- package/es/base-ui/Modal/atoms.mjs.map +1 -1
- package/es/base-ui/Modal/context.d.mts +2 -2
- package/es/base-ui/Modal/imperative.d.mts +2 -2
- package/es/base-ui/Modal/style.mjs +16 -0
- package/es/base-ui/Modal/style.mjs.map +1 -1
- package/es/base-ui/Select/Select.d.mts +2 -2
- package/es/base-ui/Select/atoms.d.mts +3 -3
- package/es/base-ui/Switch/Switch.d.mts +2 -2
- package/es/base-ui/Toast/imperative.d.mts +2 -2
- package/es/brand/LobeChat/index.d.mts +2 -2
- package/es/brand/LobeHub/index.d.mts +2 -2
- package/es/brand/LogoThree/LogoSpline.d.mts +2 -2
- package/es/brand/LogoThree/index.d.mts +2 -2
- package/es/chat/BackBottom/BackBottom.d.mts +2 -2
- package/es/chat/ChatInputArea/components/ChatInputAreaInner.d.mts +2 -2
- package/es/chat/ChatItem/ChatItem.d.mts +2 -2
- package/es/chat/ChatList/ChatList.d.mts +2 -2
- package/es/chat/EditableMessage/EditableMessage.d.mts +2 -2
- package/es/chat/EditableMessageList/EditableMessageList.d.mts +2 -2
- package/es/chat/MessageInput/MessageInput.d.mts +2 -2
- package/es/chat/MessageModal/MessageModal.d.mts +2 -2
- package/es/color/ColorScales/index.d.mts +2 -2
- package/es/color/CssVar/index.d.mts +2 -2
- package/es/hooks/useMarkdown/useMarkdownRehypePlugins.mjs +2 -5
- package/es/hooks/useMarkdown/useMarkdownRehypePlugins.mjs.map +1 -1
- package/es/i18n/context.d.mts +2 -2
- package/es/icons/lucideExtra/BotPromptIcon.d.mts +2 -2
- package/es/icons/lucideExtra/CreateBotIcon.d.mts +2 -2
- package/es/icons/lucideExtra/DiscordIcon.d.mts +3 -3
- package/es/icons/lucideExtra/GlobeOffIcon.d.mts +3 -3
- package/es/icons/lucideExtra/GroupBotIcon.d.mts +3 -3
- package/es/icons/lucideExtra/GroupBotSquareIcon.d.mts +3 -3
- package/es/icons/lucideExtra/LeftClickIcon.d.mts +3 -3
- package/es/icons/lucideExtra/LeftDoubleClickIcon.d.mts +3 -3
- package/es/icons/lucideExtra/McpIcon.d.mts +3 -3
- package/es/icons/lucideExtra/ProviderIcon.d.mts +3 -3
- package/es/icons/lucideExtra/RightClickIcon.d.mts +3 -3
- package/es/icons/lucideExtra/RightDoubleClickIcon.d.mts +3 -3
- package/es/icons/lucideExtra/ShapesUploadIcon.d.mts +3 -3
- package/es/icons/lucideExtra/SkillsIcon.d.mts +3 -3
- package/es/icons/lucideExtra/TreeDownRightIcon.d.mts +3 -3
- package/es/icons/lucideExtra/TreeUpDownRightIcon.d.mts +2 -2
- package/es/index.mjs +1 -1
- package/es/mdx/Mdx/index.d.mts +2 -2
- package/es/mobile/ChatHeader/ChatHeaderTitle.d.mts +2 -2
- package/es/mobile/ChatInputArea/components/ChatSendButton.d.mts +2 -2
- package/es/mobile/TabBar/TabBar.d.mts +2 -2
- package/es/storybook/StoryBook/index.d.mts +2 -2
- package/package.json +1 -1
package/es/Input/InputOPT.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { InputOPTProps } from "./type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react147 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Input/InputOPT.d.ts
|
|
5
|
-
declare const InputOPT:
|
|
5
|
+
declare const InputOPT: react147.NamedExoticComponent<InputOPTProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { InputOPT };
|
|
8
8
|
//# sourceMappingURL=InputOPT.d.mts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { InputPasswordProps } from "./type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react148 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Input/InputPassword.d.ts
|
|
5
|
-
declare const InputPassword:
|
|
5
|
+
declare const InputPassword: react148.NamedExoticComponent<InputPasswordProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { InputPassword };
|
|
8
8
|
//# sourceMappingURL=InputPassword.d.mts.map
|
package/es/Input/TextArea.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { TextAreaProps } from "./type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react146 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Input/TextArea.d.ts
|
|
5
|
-
declare const TextArea:
|
|
5
|
+
declare const TextArea: react146.NamedExoticComponent<TextAreaProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { TextArea };
|
|
8
8
|
//# sourceMappingURL=TextArea.d.mts.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { DivProps } from "../../types/index.mjs";
|
|
2
2
|
import "../../index.mjs";
|
|
3
|
-
import * as
|
|
3
|
+
import * as react23 from "react";
|
|
4
4
|
|
|
5
5
|
//#region src/Layout/components/LayoutFooter.d.ts
|
|
6
|
-
declare const LayoutFooter:
|
|
6
|
+
declare const LayoutFooter: react23.NamedExoticComponent<DivProps>;
|
|
7
7
|
//#endregion
|
|
8
8
|
export { LayoutFooter };
|
|
9
9
|
//# sourceMappingURL=LayoutFooter.d.mts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { LayoutHeaderProps } from "../type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react24 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Layout/components/LayoutHeader.d.ts
|
|
5
|
-
declare const LayoutHeader:
|
|
5
|
+
declare const LayoutHeader: react24.NamedExoticComponent<LayoutHeaderProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { LayoutHeader };
|
|
8
8
|
//# sourceMappingURL=LayoutHeader.d.mts.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { DivProps } from "../../types/index.mjs";
|
|
2
2
|
import "../../index.mjs";
|
|
3
|
-
import * as
|
|
3
|
+
import * as react25 from "react";
|
|
4
4
|
|
|
5
5
|
//#region src/Layout/components/LayoutMain.d.ts
|
|
6
|
-
declare const LayoutMain:
|
|
6
|
+
declare const LayoutMain: react25.NamedExoticComponent<DivProps>;
|
|
7
7
|
//#endregion
|
|
8
8
|
export { LayoutMain };
|
|
9
9
|
//# sourceMappingURL=LayoutMain.d.mts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { LayoutSidebarProps } from "../type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react26 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Layout/components/LayoutSidebar.d.ts
|
|
5
|
-
declare const LayoutSidebar:
|
|
5
|
+
declare const LayoutSidebar: react26.NamedExoticComponent<LayoutSidebarProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { LayoutSidebar };
|
|
8
8
|
//# sourceMappingURL=LayoutSidebar.d.mts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { LayoutSidebarInnerProps } from "../type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react27 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Layout/components/LayoutSidebarInner.d.ts
|
|
5
|
-
declare const LayoutSidebarInner:
|
|
5
|
+
declare const LayoutSidebarInner: react27.NamedExoticComponent<LayoutSidebarInnerProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { LayoutSidebarInner };
|
|
8
8
|
//# sourceMappingURL=LayoutSidebarInner.d.mts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { LayoutTocProps } from "../type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react28 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Layout/components/LayoutToc.d.ts
|
|
5
|
-
declare const LayoutToc:
|
|
5
|
+
declare const LayoutToc: react28.NamedExoticComponent<LayoutTocProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { LayoutToc };
|
|
8
8
|
//# sourceMappingURL=LayoutToc.d.mts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ListItemProps } from "../type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react31 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/List/ListItem/index.d.ts
|
|
5
|
-
declare const ListItem:
|
|
5
|
+
declare const ListItem: react31.NamedExoticComponent<ListItemProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { ListItem };
|
|
8
8
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { MarkdownProps } from "./type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react21 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Markdown/Markdown.d.ts
|
|
5
|
-
declare const Markdown:
|
|
5
|
+
declare const Markdown: react21.NamedExoticComponent<MarkdownProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { Markdown };
|
|
8
8
|
//# sourceMappingURL=Markdown.d.mts.map
|
|
@@ -4,45 +4,129 @@ import { useMarkdownComponents } from "../../hooks/useMarkdown/useMarkdownCompon
|
|
|
4
4
|
import { useMarkdownContent } from "../../hooks/useMarkdown/useMarkdownContent.mjs";
|
|
5
5
|
import { useMarkdownRehypePlugins } from "../../hooks/useMarkdown/useMarkdownRehypePlugins.mjs";
|
|
6
6
|
import { useMarkdownRemarkPlugins } from "../../hooks/useMarkdown/useMarkdownRemarkPlugins.mjs";
|
|
7
|
+
import { rehypeStreamAnimated } from "../plugins/rehypeStreamAnimated.mjs";
|
|
7
8
|
import { styles } from "./style.mjs";
|
|
8
|
-
import {
|
|
9
|
+
import { useStreamQueue } from "./useStreamQueue.mjs";
|
|
10
|
+
import { createElement, memo, useEffect, useId, useMemo, useRef } from "react";
|
|
9
11
|
import { jsx } from "react/jsx-runtime";
|
|
10
12
|
import Markdown from "react-markdown";
|
|
11
13
|
import { marked } from "marked";
|
|
12
14
|
import remend from "remend";
|
|
13
15
|
|
|
14
16
|
//#region src/Markdown/SyntaxMarkdown/StreamdownRender.tsx
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
+
const STREAM_CHAR_DELAY = 15;
|
|
18
|
+
function countChars(text) {
|
|
19
|
+
return [...text].length;
|
|
20
|
+
}
|
|
21
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
22
|
+
const isDeepEqualValue = (a, b) => {
|
|
23
|
+
if (a === b) return true;
|
|
24
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
25
|
+
if (!Array.isArray(a) || !Array.isArray(b)) return false;
|
|
26
|
+
if (a.length !== b.length) return false;
|
|
27
|
+
for (let i = 0; i < a.length; i++) if (!isDeepEqualValue(a[i], b[i])) return false;
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
if (!isRecord(a) || !isRecord(b)) return false;
|
|
31
|
+
const keysA = Object.keys(a);
|
|
32
|
+
const keysB = Object.keys(b);
|
|
33
|
+
if (keysA.length !== keysB.length) return false;
|
|
34
|
+
for (const key of keysA) if (!isDeepEqualValue(a[key], b[key])) return false;
|
|
35
|
+
return true;
|
|
36
|
+
};
|
|
37
|
+
const isSamePlugin = (prevPlugin, nextPlugin) => {
|
|
38
|
+
const prevTuple = Array.isArray(prevPlugin) ? prevPlugin : [prevPlugin];
|
|
39
|
+
const nextTuple = Array.isArray(nextPlugin) ? nextPlugin : [nextPlugin];
|
|
40
|
+
if (prevTuple.length !== nextTuple.length) return false;
|
|
41
|
+
if (prevTuple[0] !== nextTuple[0]) return false;
|
|
42
|
+
return isDeepEqualValue(prevTuple.slice(1), nextTuple.slice(1));
|
|
43
|
+
};
|
|
44
|
+
const isSamePlugins = (prevPlugins, nextPlugins) => {
|
|
45
|
+
if (prevPlugins === nextPlugins) return true;
|
|
46
|
+
if (!prevPlugins || !nextPlugins) return !prevPlugins && !nextPlugins;
|
|
47
|
+
if (prevPlugins.length !== nextPlugins.length) return false;
|
|
48
|
+
for (let i = 0; i < prevPlugins.length; i++) if (!isSamePlugin(prevPlugins[i], nextPlugins[i])) return false;
|
|
49
|
+
return true;
|
|
50
|
+
};
|
|
51
|
+
const useStablePlugins = (plugins) => {
|
|
52
|
+
const stableRef = useRef(plugins);
|
|
53
|
+
if (!isSamePlugins(stableRef.current, plugins)) stableRef.current = plugins;
|
|
54
|
+
return stableRef.current;
|
|
17
55
|
};
|
|
18
56
|
const StreamdownBlock = memo(({ children, ...rest }) => {
|
|
19
57
|
return /* @__PURE__ */ jsx(Markdown, {
|
|
20
58
|
...rest,
|
|
21
59
|
children
|
|
22
60
|
});
|
|
23
|
-
}, (prevProps, nextProps) => prevProps.children === nextProps.children);
|
|
61
|
+
}, (prevProps, nextProps) => prevProps.children === nextProps.children && prevProps.components === nextProps.components && isSamePlugins(prevProps.rehypePlugins, nextProps.rehypePlugins) && isSamePlugins(prevProps.remarkPlugins, nextProps.remarkPlugins));
|
|
24
62
|
StreamdownBlock.displayName = "StreamdownBlock";
|
|
25
63
|
const StreamdownRender = memo(({ children, ...rest }) => {
|
|
26
64
|
const escapedContent = useMarkdownContent(children || "");
|
|
27
65
|
const components = useMarkdownComponents();
|
|
28
|
-
const
|
|
29
|
-
const
|
|
66
|
+
const baseRehypePlugins = useStablePlugins(useMarkdownRehypePlugins());
|
|
67
|
+
const remarkPlugins = useStablePlugins(useMarkdownRemarkPlugins());
|
|
30
68
|
const generatedId = useId();
|
|
31
69
|
const processedContent = useMemo(() => {
|
|
32
70
|
return remend(typeof escapedContent === "string" ? escapedContent : "");
|
|
33
71
|
}, [escapedContent]);
|
|
34
|
-
const blocks = useMemo(() =>
|
|
72
|
+
const blocks = useMemo(() => {
|
|
73
|
+
const tokens = marked.lexer(processedContent);
|
|
74
|
+
let offset = 0;
|
|
75
|
+
return tokens.map((token) => {
|
|
76
|
+
const block = {
|
|
77
|
+
content: token.raw,
|
|
78
|
+
startOffset: offset
|
|
79
|
+
};
|
|
80
|
+
offset += token.raw.length;
|
|
81
|
+
return block;
|
|
82
|
+
});
|
|
83
|
+
}, [processedContent]);
|
|
84
|
+
const { getBlockState, charDelay } = useStreamQueue(blocks);
|
|
85
|
+
const staggerPlugins = useMemo(() => [...baseRehypePlugins, [rehypeStreamAnimated, {
|
|
86
|
+
baseCharCount: 0,
|
|
87
|
+
charDelay
|
|
88
|
+
}]], [baseRehypePlugins, charDelay]);
|
|
89
|
+
const revealedPlugins = useMemo(() => [...baseRehypePlugins, [rehypeStreamAnimated, { revealed: true }]], [baseRehypePlugins]);
|
|
90
|
+
const prevCharCountRef = useRef(0);
|
|
91
|
+
const prevStreamOffsetRef = useRef(-1);
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
const tailIdx = blocks.length - 1;
|
|
94
|
+
if (tailIdx >= 0) {
|
|
95
|
+
const tail = blocks[tailIdx];
|
|
96
|
+
if (tail.startOffset !== prevStreamOffsetRef.current) {
|
|
97
|
+
prevStreamOffsetRef.current = tail.startOffset;
|
|
98
|
+
prevCharCountRef.current = 0;
|
|
99
|
+
}
|
|
100
|
+
prevCharCountRef.current = countChars(tail.content);
|
|
101
|
+
} else {
|
|
102
|
+
prevCharCountRef.current = 0;
|
|
103
|
+
prevStreamOffsetRef.current = -1;
|
|
104
|
+
}
|
|
105
|
+
}, [blocks]);
|
|
35
106
|
return /* @__PURE__ */ jsx("div", {
|
|
36
107
|
className: styles.animated,
|
|
37
|
-
children: blocks.map((block, index) =>
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
108
|
+
children: blocks.map((block, index) => {
|
|
109
|
+
const state = getBlockState(index);
|
|
110
|
+
if (state === "queued") return null;
|
|
111
|
+
let plugins;
|
|
112
|
+
if (state === "streaming") {
|
|
113
|
+
const baseCharCount = block.startOffset === prevStreamOffsetRef.current ? prevCharCountRef.current : 0;
|
|
114
|
+
plugins = [...baseRehypePlugins, [rehypeStreamAnimated, {
|
|
115
|
+
baseCharCount,
|
|
116
|
+
charDelay: STREAM_CHAR_DELAY
|
|
117
|
+
}]];
|
|
118
|
+
} else if (state === "animating") plugins = staggerPlugins;
|
|
119
|
+
else plugins = revealedPlugins;
|
|
120
|
+
return /* @__PURE__ */ createElement(StreamdownBlock, {
|
|
121
|
+
...rest,
|
|
122
|
+
components,
|
|
123
|
+
key: `${generatedId}-${block.startOffset}`,
|
|
124
|
+
rehypePlugins: plugins,
|
|
125
|
+
remarkPlugins
|
|
126
|
+
}, block.content);
|
|
127
|
+
})
|
|
44
128
|
});
|
|
45
|
-
}
|
|
129
|
+
});
|
|
46
130
|
StreamdownRender.displayName = "StreamdownRender";
|
|
47
131
|
var StreamdownRender_default = StreamdownRender;
|
|
48
132
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StreamdownRender.mjs","names":[],"sources":["../../../src/Markdown/SyntaxMarkdown/StreamdownRender.tsx"],"sourcesContent":["'use client';\n\nimport { marked } from 'marked';\nimport { memo, useId, useMemo } from 'react';\nimport Markdown, { type Options } from 'react-markdown';\nimport remend from 'remend';\n\nimport {\n useMarkdownComponents,\n useMarkdownContent,\n useMarkdownRehypePlugins,\n useMarkdownRemarkPlugins,\n} from '@/hooks/useMarkdown';\n\nimport { styles } from './style';\n\nconst
|
|
1
|
+
{"version":3,"file":"StreamdownRender.mjs","names":["blocks: BlockInfo[]","staggerPlugins: Pluggable[]","revealedPlugins: Pluggable[]","plugins: Pluggable[]"],"sources":["../../../src/Markdown/SyntaxMarkdown/StreamdownRender.tsx"],"sourcesContent":["'use client';\n\nimport { marked } from 'marked';\nimport { memo, useEffect, useId, useMemo, useRef } from 'react';\nimport Markdown, { type Options } from 'react-markdown';\nimport remend from 'remend';\nimport type { Pluggable, PluggableList } from 'unified';\n\nimport {\n useMarkdownComponents,\n useMarkdownContent,\n useMarkdownRehypePlugins,\n useMarkdownRemarkPlugins,\n} from '@/hooks/useMarkdown';\nimport { rehypeStreamAnimated } from '@/Markdown/plugins/rehypeStreamAnimated';\n\nimport { styles } from './style';\nimport { type BlockInfo, useStreamQueue } from './useStreamQueue';\n\nconst STREAM_CHAR_DELAY = 15;\n\nfunction countChars(text: string): number {\n return [...text].length;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null;\n\nconst isDeepEqualValue = (a: unknown, b: unknown): boolean => {\n if (a === b) return true;\n\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!isDeepEqualValue(a[i], b[i])) return false;\n }\n return true;\n }\n\n if (!isRecord(a) || !isRecord(b)) return false;\n\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n if (keysA.length !== keysB.length) return false;\n\n for (const key of keysA) {\n if (!isDeepEqualValue(a[key], b[key])) return false;\n }\n\n return true;\n};\n\nconst isSamePlugin = (prevPlugin: Pluggable, nextPlugin: Pluggable): boolean => {\n const prevTuple = Array.isArray(prevPlugin) ? prevPlugin : [prevPlugin];\n const nextTuple = Array.isArray(nextPlugin) ? nextPlugin : [nextPlugin];\n\n if (prevTuple.length !== nextTuple.length) return false;\n if (prevTuple[0] !== nextTuple[0]) return false;\n\n return isDeepEqualValue(prevTuple.slice(1), nextTuple.slice(1));\n};\n\nconst isSamePlugins = (\n prevPlugins?: PluggableList | null,\n nextPlugins?: PluggableList | null,\n): boolean => {\n if (prevPlugins === nextPlugins) return true;\n if (!prevPlugins || !nextPlugins) return !prevPlugins && !nextPlugins;\n if (prevPlugins.length !== nextPlugins.length) return false;\n\n for (let i = 0; i < prevPlugins.length; i++) {\n if (!isSamePlugin(prevPlugins[i], nextPlugins[i])) return false;\n }\n\n return true;\n};\n\nconst useStablePlugins = (plugins: PluggableList): PluggableList => {\n const stableRef = useRef<PluggableList>(plugins);\n\n if (!isSamePlugins(stableRef.current, plugins)) {\n stableRef.current = plugins;\n }\n\n return stableRef.current;\n};\n\nconst StreamdownBlock = memo<Options>(\n ({ children, ...rest }) => {\n return <Markdown {...rest}>{children}</Markdown>;\n },\n (prevProps, nextProps) =>\n prevProps.children === nextProps.children &&\n prevProps.components === nextProps.components &&\n isSamePlugins(prevProps.rehypePlugins, nextProps.rehypePlugins) &&\n isSamePlugins(prevProps.remarkPlugins, nextProps.remarkPlugins),\n);\n\nStreamdownBlock.displayName = 'StreamdownBlock';\n\nexport const StreamdownRender = memo<Options>(({ children, ...rest }) => {\n const escapedContent = useMarkdownContent(children || '');\n const components = useMarkdownComponents();\n const baseRehypePlugins = useStablePlugins(useMarkdownRehypePlugins());\n const remarkPlugins = useStablePlugins(useMarkdownRemarkPlugins());\n const generatedId = useId();\n\n const processedContent = useMemo(() => {\n const content = typeof escapedContent === 'string' ? escapedContent : '';\n return remend(content);\n }, [escapedContent]);\n\n const blocks: BlockInfo[] = useMemo(() => {\n const tokens = marked.lexer(processedContent);\n let offset = 0;\n return tokens.map((token) => {\n const block = { content: token.raw, startOffset: offset };\n offset += token.raw.length;\n return block;\n });\n }, [processedContent]);\n\n const { getBlockState, charDelay } = useStreamQueue(blocks);\n\n const staggerPlugins: Pluggable[] = useMemo(\n () => [...baseRehypePlugins, [rehypeStreamAnimated, { baseCharCount: 0, charDelay }]],\n [baseRehypePlugins, charDelay],\n );\n\n const revealedPlugins: Pluggable[] = useMemo(\n () => [...baseRehypePlugins, [rehypeStreamAnimated, { revealed: true }]],\n [baseRehypePlugins],\n );\n\n // prevCharCount tracks the PREVIOUS render's streaming char count.\n // Updated in useEffect (after render) to avoid stale reads during\n // synchronous re-renders (e.g. useLayoutEffect auto-reveal).\n const prevCharCountRef = useRef(0);\n const prevStreamOffsetRef = useRef(-1);\n\n useEffect(() => {\n const tailIdx = blocks.length - 1;\n if (tailIdx >= 0) {\n const tail = blocks[tailIdx];\n if (tail.startOffset !== prevStreamOffsetRef.current) {\n prevStreamOffsetRef.current = tail.startOffset;\n prevCharCountRef.current = 0;\n }\n prevCharCountRef.current = countChars(tail.content);\n } else {\n prevCharCountRef.current = 0;\n prevStreamOffsetRef.current = -1;\n }\n }, [blocks]);\n\n return (\n <div className={styles.animated}>\n {blocks.map((block, index) => {\n const state = getBlockState(index);\n if (state === 'queued') return null;\n\n let plugins: Pluggable[];\n if (state === 'streaming') {\n const baseCharCount =\n block.startOffset === prevStreamOffsetRef.current ? prevCharCountRef.current : 0;\n plugins = [\n ...baseRehypePlugins,\n [rehypeStreamAnimated, { baseCharCount, charDelay: STREAM_CHAR_DELAY }],\n ];\n } else if (state === 'animating') {\n plugins = staggerPlugins;\n } else {\n plugins = revealedPlugins;\n }\n\n return (\n <StreamdownBlock\n {...rest}\n components={components}\n key={`${generatedId}-${block.startOffset}`}\n rehypePlugins={plugins}\n remarkPlugins={remarkPlugins}\n >\n {block.content}\n </StreamdownBlock>\n );\n })}\n </div>\n );\n});\n\nStreamdownRender.displayName = 'StreamdownRender';\n\nexport default StreamdownRender;\n"],"mappings":"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"}
|
|
@@ -4,15 +4,20 @@ import { createStaticStyles } from "antd-style";
|
|
|
4
4
|
//#region src/Markdown/SyntaxMarkdown/style.ts
|
|
5
5
|
const styles = createStaticStyles(({ css: css$1 }) => {
|
|
6
6
|
return { animated: css$1`
|
|
7
|
-
.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
.stream-char {
|
|
8
|
+
opacity: 0;
|
|
9
|
+
|
|
10
|
+
animation-name: ${fadeIn};
|
|
11
|
+
animation-duration: 150ms;
|
|
12
|
+
animation-timing-function: ease-out;
|
|
13
|
+
animation-fill-mode: forwards;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.stream-char-revealed {
|
|
11
17
|
opacity: 1;
|
|
12
|
-
animation:
|
|
18
|
+
animation: none;
|
|
13
19
|
}
|
|
14
20
|
|
|
15
|
-
/* 只对 .base 级别的 span 应用流式动画,不要穿透到内部 */
|
|
16
21
|
.katex-display .katex-html span {
|
|
17
22
|
mask: none !important;
|
|
18
23
|
animation: none !important;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"style.mjs","names":[],"sources":["../../../src/Markdown/SyntaxMarkdown/style.ts"],"sourcesContent":["import { createStaticStyles } from 'antd-style';\n\nimport { fadeIn } from '@/styles/animations';\n\nexport const styles = createStaticStyles(({ css }) => {\n return {\n animated: css`\n .
|
|
1
|
+
{"version":3,"file":"style.mjs","names":[],"sources":["../../../src/Markdown/SyntaxMarkdown/style.ts"],"sourcesContent":["import { createStaticStyles } from 'antd-style';\n\nimport { fadeIn } from '@/styles/animations';\n\nexport const styles = createStaticStyles(({ css }) => {\n return {\n animated: css`\n .stream-char {\n opacity: 0;\n\n animation-name: ${fadeIn};\n animation-duration: 150ms;\n animation-timing-function: ease-out;\n animation-fill-mode: forwards;\n }\n\n .stream-char-revealed {\n opacity: 1;\n animation: none;\n }\n\n .katex-display .katex-html span {\n mask: none !important;\n animation: none !important;\n }\n `,\n };\n});\n"],"mappings":";;;;AAIA,MAAa,SAAS,oBAAoB,EAAE,iBAAU;AACpD,QAAO,EACL,UAAU,KAAG;;;;0BAIS,OAAO;;;;;;;;;;;;;;;OAgB9B;EACD"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/Markdown/SyntaxMarkdown/useStreamQueue.ts
|
|
4
|
+
const BASE_DELAY = 20;
|
|
5
|
+
const ACCELERATION_FACTOR = .3;
|
|
6
|
+
const MAX_BLOCK_DURATION = 3e3;
|
|
7
|
+
const FADE_DURATION = 150;
|
|
8
|
+
function countChars(text) {
|
|
9
|
+
return [...text].length;
|
|
10
|
+
}
|
|
11
|
+
function computeCharDelay(queueLength, charCount) {
|
|
12
|
+
let delay = BASE_DELAY / (1 + queueLength * ACCELERATION_FACTOR);
|
|
13
|
+
delay = Math.min(delay, MAX_BLOCK_DURATION / Math.max(charCount, 1));
|
|
14
|
+
return delay;
|
|
15
|
+
}
|
|
16
|
+
function useStreamQueue(blocks) {
|
|
17
|
+
const [revealedCount, setRevealedCount] = useState(0);
|
|
18
|
+
const timerRef = useRef(null);
|
|
19
|
+
const prevBlocksLenRef = useRef(0);
|
|
20
|
+
const minRevealedRef = useRef(0);
|
|
21
|
+
if (blocks.length === 0 && prevBlocksLenRef.current !== 0) minRevealedRef.current = 0;
|
|
22
|
+
if (blocks.length > prevBlocksLenRef.current && prevBlocksLenRef.current > 0) {
|
|
23
|
+
const prevTail = prevBlocksLenRef.current - 1;
|
|
24
|
+
minRevealedRef.current = Math.max(minRevealedRef.current, prevTail + 1);
|
|
25
|
+
}
|
|
26
|
+
prevBlocksLenRef.current = blocks.length;
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (blocks.length === 0) {
|
|
29
|
+
setRevealedCount(0);
|
|
30
|
+
minRevealedRef.current = 0;
|
|
31
|
+
if (timerRef.current) {
|
|
32
|
+
clearTimeout(timerRef.current);
|
|
33
|
+
timerRef.current = null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}, [blocks.length]);
|
|
37
|
+
const effectiveRevealedCount = Math.max(revealedCount, minRevealedRef.current);
|
|
38
|
+
const tailIndex = blocks.length - 1;
|
|
39
|
+
const getBlockState = useCallback((index) => {
|
|
40
|
+
if (index < effectiveRevealedCount) return "revealed";
|
|
41
|
+
if (index === effectiveRevealedCount && index < tailIndex) return "animating";
|
|
42
|
+
if (index === effectiveRevealedCount && index === tailIndex) return "streaming";
|
|
43
|
+
return "queued";
|
|
44
|
+
}, [effectiveRevealedCount, tailIndex]);
|
|
45
|
+
const queueLength = Math.max(0, tailIndex - effectiveRevealedCount - 1);
|
|
46
|
+
const animatingIndex = effectiveRevealedCount < tailIndex ? effectiveRevealedCount : -1;
|
|
47
|
+
const animatingCharCount = animatingIndex >= 0 ? countChars(blocks[animatingIndex]?.content ?? "") : 0;
|
|
48
|
+
const frozenRef = useRef({
|
|
49
|
+
delay: BASE_DELAY,
|
|
50
|
+
index: -1
|
|
51
|
+
});
|
|
52
|
+
if (animatingIndex >= 0 && animatingIndex !== frozenRef.current.index) frozenRef.current = {
|
|
53
|
+
delay: computeCharDelay(queueLength, animatingCharCount),
|
|
54
|
+
index: animatingIndex
|
|
55
|
+
};
|
|
56
|
+
const charDelay = animatingIndex >= 0 ? frozenRef.current.delay : BASE_DELAY;
|
|
57
|
+
const onAnimationDone = useCallback(() => {
|
|
58
|
+
setRevealedCount(effectiveRevealedCount + 1);
|
|
59
|
+
}, [effectiveRevealedCount]);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (timerRef.current) {
|
|
62
|
+
clearTimeout(timerRef.current);
|
|
63
|
+
timerRef.current = null;
|
|
64
|
+
}
|
|
65
|
+
if (animatingIndex < 0) return;
|
|
66
|
+
const totalTime = Math.max(0, (animatingCharCount - 1) * charDelay) + FADE_DURATION;
|
|
67
|
+
timerRef.current = setTimeout(onAnimationDone, totalTime);
|
|
68
|
+
return () => {
|
|
69
|
+
if (timerRef.current) {
|
|
70
|
+
clearTimeout(timerRef.current);
|
|
71
|
+
timerRef.current = null;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}, [
|
|
75
|
+
animatingIndex,
|
|
76
|
+
animatingCharCount,
|
|
77
|
+
charDelay,
|
|
78
|
+
onAnimationDone
|
|
79
|
+
]);
|
|
80
|
+
return {
|
|
81
|
+
charDelay,
|
|
82
|
+
getBlockState,
|
|
83
|
+
queueLength
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
export { useStreamQueue };
|
|
89
|
+
//# sourceMappingURL=useStreamQueue.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useStreamQueue.mjs","names":[],"sources":["../../../src/Markdown/SyntaxMarkdown/useStreamQueue.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\nexport interface BlockInfo {\n content: string;\n startOffset: number;\n}\n\nexport type BlockState = 'revealed' | 'animating' | 'streaming' | 'queued';\n\nconst BASE_DELAY = 20;\nconst ACCELERATION_FACTOR = 0.3;\nconst MAX_BLOCK_DURATION = 3000;\nconst FADE_DURATION = 150;\n\nfunction countChars(text: string): number {\n return [...text].length;\n}\n\nfunction computeCharDelay(queueLength: number, charCount: number): number {\n const acceleration = 1 + queueLength * ACCELERATION_FACTOR;\n let delay = BASE_DELAY / acceleration;\n delay = Math.min(delay, MAX_BLOCK_DURATION / Math.max(charCount, 1));\n return delay;\n}\n\nexport interface UseStreamQueueReturn {\n charDelay: number;\n getBlockState: (index: number) => BlockState;\n queueLength: number;\n}\n\nexport function useStreamQueue(blocks: BlockInfo[]): UseStreamQueueReturn {\n const [revealedCount, setRevealedCount] = useState(0);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const prevBlocksLenRef = useRef(0);\n const minRevealedRef = useRef(0);\n\n // Synchronous auto-reveal during render.\n // When blocks grow, the previous tail (streaming block) is instantly\n // promoted to revealed — its chars are already visible via stream-mode\n // animation. This runs during render (not in effect) so there is NO\n // intermediate frame where the old streaming block enters 'animating'\n // state and gets stagger plugins that would restart its animations.\n if (blocks.length === 0 && prevBlocksLenRef.current !== 0) {\n minRevealedRef.current = 0;\n }\n if (blocks.length > prevBlocksLenRef.current && prevBlocksLenRef.current > 0) {\n const prevTail = prevBlocksLenRef.current - 1;\n minRevealedRef.current = Math.max(minRevealedRef.current, prevTail + 1);\n }\n prevBlocksLenRef.current = blocks.length;\n\n // State reset when stream restarts (blocks empty)\n useEffect(() => {\n if (blocks.length === 0) {\n setRevealedCount(0);\n minRevealedRef.current = 0;\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }\n }, [blocks.length]);\n\n const effectiveRevealedCount = Math.max(revealedCount, minRevealedRef.current);\n const tailIndex = blocks.length - 1;\n\n const getBlockState = useCallback(\n (index: number): BlockState => {\n if (index < effectiveRevealedCount) return 'revealed';\n if (index === effectiveRevealedCount && index < tailIndex) return 'animating';\n if (index === effectiveRevealedCount && index === tailIndex) return 'streaming';\n return 'queued';\n },\n [effectiveRevealedCount, tailIndex],\n );\n\n const queueLength = Math.max(0, tailIndex - effectiveRevealedCount - 1);\n\n const animatingIndex = effectiveRevealedCount < tailIndex ? effectiveRevealedCount : -1;\n const animatingCharCount =\n animatingIndex >= 0 ? countChars(blocks[animatingIndex]?.content ?? '') : 0;\n\n // Freeze charDelay when a new block starts animating\n const frozenRef = useRef({ delay: BASE_DELAY, index: -1 });\n if (animatingIndex >= 0 && animatingIndex !== frozenRef.current.index) {\n frozenRef.current = {\n delay: computeCharDelay(queueLength, animatingCharCount),\n index: animatingIndex,\n };\n }\n const charDelay = animatingIndex >= 0 ? frozenRef.current.delay : BASE_DELAY;\n\n const onAnimationDone = useCallback(() => {\n setRevealedCount(effectiveRevealedCount + 1);\n }, [effectiveRevealedCount]);\n\n useEffect(() => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n\n if (animatingIndex < 0) return;\n\n const totalTime = Math.max(0, (animatingCharCount - 1) * charDelay) + FADE_DURATION;\n timerRef.current = setTimeout(onAnimationDone, totalTime);\n\n return () => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n };\n }, [animatingIndex, animatingCharCount, charDelay, onAnimationDone]);\n\n return { charDelay, getBlockState, queueLength };\n}\n"],"mappings":";;;AASA,MAAM,aAAa;AACnB,MAAM,sBAAsB;AAC5B,MAAM,qBAAqB;AAC3B,MAAM,gBAAgB;AAEtB,SAAS,WAAW,MAAsB;AACxC,QAAO,CAAC,GAAG,KAAK,CAAC;;AAGnB,SAAS,iBAAiB,aAAqB,WAA2B;CAExE,IAAI,QAAQ,cADS,IAAI,cAAc;AAEvC,SAAQ,KAAK,IAAI,OAAO,qBAAqB,KAAK,IAAI,WAAW,EAAE,CAAC;AACpE,QAAO;;AAST,SAAgB,eAAe,QAA2C;CACxE,MAAM,CAAC,eAAe,oBAAoB,SAAS,EAAE;CACrD,MAAM,WAAW,OAA6C,KAAK;CACnE,MAAM,mBAAmB,OAAO,EAAE;CAClC,MAAM,iBAAiB,OAAO,EAAE;AAQhC,KAAI,OAAO,WAAW,KAAK,iBAAiB,YAAY,EACtD,gBAAe,UAAU;AAE3B,KAAI,OAAO,SAAS,iBAAiB,WAAW,iBAAiB,UAAU,GAAG;EAC5E,MAAM,WAAW,iBAAiB,UAAU;AAC5C,iBAAe,UAAU,KAAK,IAAI,eAAe,SAAS,WAAW,EAAE;;AAEzE,kBAAiB,UAAU,OAAO;AAGlC,iBAAgB;AACd,MAAI,OAAO,WAAW,GAAG;AACvB,oBAAiB,EAAE;AACnB,kBAAe,UAAU;AACzB,OAAI,SAAS,SAAS;AACpB,iBAAa,SAAS,QAAQ;AAC9B,aAAS,UAAU;;;IAGtB,CAAC,OAAO,OAAO,CAAC;CAEnB,MAAM,yBAAyB,KAAK,IAAI,eAAe,eAAe,QAAQ;CAC9E,MAAM,YAAY,OAAO,SAAS;CAElC,MAAM,gBAAgB,aACnB,UAA8B;AAC7B,MAAI,QAAQ,uBAAwB,QAAO;AAC3C,MAAI,UAAU,0BAA0B,QAAQ,UAAW,QAAO;AAClE,MAAI,UAAU,0BAA0B,UAAU,UAAW,QAAO;AACpE,SAAO;IAET,CAAC,wBAAwB,UAAU,CACpC;CAED,MAAM,cAAc,KAAK,IAAI,GAAG,YAAY,yBAAyB,EAAE;CAEvE,MAAM,iBAAiB,yBAAyB,YAAY,yBAAyB;CACrF,MAAM,qBACJ,kBAAkB,IAAI,WAAW,OAAO,iBAAiB,WAAW,GAAG,GAAG;CAG5E,MAAM,YAAY,OAAO;EAAE,OAAO;EAAY,OAAO;EAAI,CAAC;AAC1D,KAAI,kBAAkB,KAAK,mBAAmB,UAAU,QAAQ,MAC9D,WAAU,UAAU;EAClB,OAAO,iBAAiB,aAAa,mBAAmB;EACxD,OAAO;EACR;CAEH,MAAM,YAAY,kBAAkB,IAAI,UAAU,QAAQ,QAAQ;CAElE,MAAM,kBAAkB,kBAAkB;AACxC,mBAAiB,yBAAyB,EAAE;IAC3C,CAAC,uBAAuB,CAAC;AAE5B,iBAAgB;AACd,MAAI,SAAS,SAAS;AACpB,gBAAa,SAAS,QAAQ;AAC9B,YAAS,UAAU;;AAGrB,MAAI,iBAAiB,EAAG;EAExB,MAAM,YAAY,KAAK,IAAI,IAAI,qBAAqB,KAAK,UAAU,GAAG;AACtE,WAAS,UAAU,WAAW,iBAAiB,UAAU;AAEzD,eAAa;AACX,OAAI,SAAS,SAAS;AACpB,iBAAa,SAAS,QAAQ;AAC9B,aAAS,UAAU;;;IAGtB;EAAC;EAAgB;EAAoB;EAAW;EAAgB,CAAC;AAEpE,QAAO;EAAE;EAAW;EAAe;EAAa"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { TypographyProps } from "./type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react20 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Markdown/Typography.d.ts
|
|
5
|
-
declare const Typography:
|
|
5
|
+
declare const Typography: react20.NamedExoticComponent<TypographyProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { Typography };
|
|
8
8
|
//# sourceMappingURL=Typography.d.mts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FlexboxProps } from "../../../Flex/type.mjs";
|
|
2
2
|
import "../../../Flex/index.mjs";
|
|
3
|
-
import * as
|
|
3
|
+
import * as react66 from "react";
|
|
4
4
|
import { Ref } from "react";
|
|
5
5
|
|
|
6
6
|
//#region src/Markdown/components/SearchResultCards/index.d.ts
|
|
@@ -14,7 +14,7 @@ interface SearchResultCardsProps extends FlexboxProps {
|
|
|
14
14
|
dataSource: string[] | SearchResultItem[];
|
|
15
15
|
ref?: Ref<HTMLDivElement>;
|
|
16
16
|
}
|
|
17
|
-
declare const SearchResultCards:
|
|
17
|
+
declare const SearchResultCards: react66.NamedExoticComponent<SearchResultCardsProps>;
|
|
18
18
|
//#endregion
|
|
19
19
|
export { SearchResultCards, SearchResultCardsProps };
|
|
20
20
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { Root } from "hast";
|
|
2
2
|
|
|
3
3
|
//#region src/Markdown/plugins/rehypeStreamAnimated.d.ts
|
|
4
|
-
|
|
4
|
+
interface StreamAnimatedOptions {
|
|
5
|
+
baseCharCount?: number;
|
|
6
|
+
charDelay?: number;
|
|
7
|
+
revealed?: boolean;
|
|
8
|
+
}
|
|
9
|
+
declare const rehypeStreamAnimated: (options?: StreamAnimatedOptions) => (tree: Root) => void;
|
|
5
10
|
//#endregion
|
|
6
11
|
export { rehypeStreamAnimated };
|
|
7
12
|
//# sourceMappingURL=rehypeStreamAnimated.d.mts.map
|
|
@@ -1,34 +1,65 @@
|
|
|
1
1
|
import { visit } from "../../node_modules/unist-util-visit/lib/index.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/Markdown/plugins/rehypeStreamAnimated.ts
|
|
4
|
-
const
|
|
4
|
+
const BLOCK_TAGS = new Set([
|
|
5
|
+
"p",
|
|
6
|
+
"h1",
|
|
7
|
+
"h2",
|
|
8
|
+
"h3",
|
|
9
|
+
"h4",
|
|
10
|
+
"h5",
|
|
11
|
+
"h6",
|
|
12
|
+
"li"
|
|
13
|
+
]);
|
|
14
|
+
const SKIP_TAGS = new Set([
|
|
15
|
+
"pre",
|
|
16
|
+
"code",
|
|
17
|
+
"table",
|
|
18
|
+
"svg"
|
|
19
|
+
]);
|
|
20
|
+
function hasClass(node, cls) {
|
|
21
|
+
const cn = node.properties?.className;
|
|
22
|
+
if (Array.isArray(cn)) return cn.some((c) => String(c).includes(cls));
|
|
23
|
+
if (typeof cn === "string") return cn.includes(cls);
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
const rehypeStreamAnimated = (options = {}) => {
|
|
27
|
+
const { charDelay = 20, baseCharCount = 0, revealed = false } = options;
|
|
5
28
|
return (tree) => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
tagName: "span",
|
|
27
|
-
type: "element"
|
|
28
|
-
});
|
|
29
|
+
let globalCharIndex = 0;
|
|
30
|
+
const shouldSkip = (node) => {
|
|
31
|
+
return SKIP_TAGS.has(node.tagName) || hasClass(node, "katex");
|
|
32
|
+
};
|
|
33
|
+
const wrapText = (node) => {
|
|
34
|
+
const newChildren = [];
|
|
35
|
+
for (const child of node.children) if (child.type === "text") for (const char of child.value) {
|
|
36
|
+
const properties = { className: revealed ? "stream-char stream-char-revealed" : "stream-char" };
|
|
37
|
+
if (!revealed) {
|
|
38
|
+
const delay = Math.max(0, globalCharIndex - baseCharCount) * charDelay;
|
|
39
|
+
if (delay > 0) properties.style = `animation-delay:${delay}ms`;
|
|
40
|
+
}
|
|
41
|
+
newChildren.push({
|
|
42
|
+
children: [{
|
|
43
|
+
type: "text",
|
|
44
|
+
value: char
|
|
45
|
+
}],
|
|
46
|
+
properties,
|
|
47
|
+
tagName: "span",
|
|
48
|
+
type: "element"
|
|
29
49
|
});
|
|
30
|
-
|
|
31
|
-
|
|
50
|
+
globalCharIndex++;
|
|
51
|
+
}
|
|
52
|
+
else if (child.type === "element") {
|
|
53
|
+
if (!shouldSkip(child)) wrapText(child);
|
|
54
|
+
newChildren.push(child);
|
|
55
|
+
} else newChildren.push(child);
|
|
56
|
+
node.children = newChildren;
|
|
57
|
+
};
|
|
58
|
+
visit(tree, "element", ((node) => {
|
|
59
|
+
if (shouldSkip(node)) return "skip";
|
|
60
|
+
if (BLOCK_TAGS.has(node.tagName)) {
|
|
61
|
+
wrapText(node);
|
|
62
|
+
return "skip";
|
|
32
63
|
}
|
|
33
64
|
}));
|
|
34
65
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rehypeStreamAnimated.mjs","names":["newChildren:
|
|
1
|
+
{"version":3,"file":"rehypeStreamAnimated.mjs","names":["newChildren: ElementContent[]","properties: Record<string, any>"],"sources":["../../../src/Markdown/plugins/rehypeStreamAnimated.ts"],"sourcesContent":["import { type Element, type ElementContent, type Root } from 'hast';\nimport { type BuildVisitor } from 'unist-util-visit';\nimport { visit } from 'unist-util-visit';\n\nexport interface StreamAnimatedOptions {\n baseCharCount?: number;\n charDelay?: number;\n revealed?: boolean;\n}\n\nconst BLOCK_TAGS = new Set(['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li']);\nconst SKIP_TAGS = new Set(['pre', 'code', 'table', 'svg']);\n\nfunction hasClass(node: Element, cls: string): boolean {\n const cn = node.properties?.className;\n if (Array.isArray(cn)) return cn.some((c) => String(c).includes(cls));\n if (typeof cn === 'string') return cn.includes(cls);\n return false;\n}\n\nexport const rehypeStreamAnimated = (options: StreamAnimatedOptions = {}) => {\n const { charDelay = 20, baseCharCount = 0, revealed = false } = options;\n\n return (tree: Root) => {\n let globalCharIndex = 0;\n\n const shouldSkip = (node: Element): boolean => {\n return SKIP_TAGS.has(node.tagName) || hasClass(node, 'katex');\n };\n\n const wrapText = (node: Element) => {\n const newChildren: ElementContent[] = [];\n for (const child of node.children) {\n if (child.type === 'text') {\n for (const char of child.value) {\n const properties: Record<string, any> = {\n className: revealed ? 'stream-char stream-char-revealed' : 'stream-char',\n };\n if (!revealed) {\n const relativeIndex = Math.max(0, globalCharIndex - baseCharCount);\n const delay = relativeIndex * charDelay;\n if (delay > 0) {\n properties.style = `animation-delay:${delay}ms`;\n }\n }\n newChildren.push({\n children: [{ type: 'text', value: char }],\n properties,\n tagName: 'span',\n type: 'element',\n });\n globalCharIndex++;\n }\n } else if (child.type === 'element') {\n if (!shouldSkip(child)) {\n wrapText(child);\n }\n newChildren.push(child);\n } else {\n newChildren.push(child);\n }\n }\n node.children = newChildren;\n };\n\n visit(tree, 'element', ((node: Element) => {\n if (shouldSkip(node)) return 'skip';\n if (BLOCK_TAGS.has(node.tagName)) {\n wrapText(node);\n return 'skip';\n }\n }) as BuildVisitor<Root, 'element'>);\n };\n};\n"],"mappings":";;;AAUA,MAAM,aAAa,IAAI,IAAI;CAAC;CAAK;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAK,CAAC;AAC3E,MAAM,YAAY,IAAI,IAAI;CAAC;CAAO;CAAQ;CAAS;CAAM,CAAC;AAE1D,SAAS,SAAS,MAAe,KAAsB;CACrD,MAAM,KAAK,KAAK,YAAY;AAC5B,KAAI,MAAM,QAAQ,GAAG,CAAE,QAAO,GAAG,MAAM,MAAM,OAAO,EAAE,CAAC,SAAS,IAAI,CAAC;AACrE,KAAI,OAAO,OAAO,SAAU,QAAO,GAAG,SAAS,IAAI;AACnD,QAAO;;AAGT,MAAa,wBAAwB,UAAiC,EAAE,KAAK;CAC3E,MAAM,EAAE,YAAY,IAAI,gBAAgB,GAAG,WAAW,UAAU;AAEhE,SAAQ,SAAe;EACrB,IAAI,kBAAkB;EAEtB,MAAM,cAAc,SAA2B;AAC7C,UAAO,UAAU,IAAI,KAAK,QAAQ,IAAI,SAAS,MAAM,QAAQ;;EAG/D,MAAM,YAAY,SAAkB;GAClC,MAAMA,cAAgC,EAAE;AACxC,QAAK,MAAM,SAAS,KAAK,SACvB,KAAI,MAAM,SAAS,OACjB,MAAK,MAAM,QAAQ,MAAM,OAAO;IAC9B,MAAMC,aAAkC,EACtC,WAAW,WAAW,qCAAqC,eAC5D;AACD,QAAI,CAAC,UAAU;KAEb,MAAM,QADgB,KAAK,IAAI,GAAG,kBAAkB,cAAc,GACpC;AAC9B,SAAI,QAAQ,EACV,YAAW,QAAQ,mBAAmB,MAAM;;AAGhD,gBAAY,KAAK;KACf,UAAU,CAAC;MAAE,MAAM;MAAQ,OAAO;MAAM,CAAC;KACzC;KACA,SAAS;KACT,MAAM;KACP,CAAC;AACF;;YAEO,MAAM,SAAS,WAAW;AACnC,QAAI,CAAC,WAAW,MAAM,CACpB,UAAS,MAAM;AAEjB,gBAAY,KAAK,MAAM;SAEvB,aAAY,KAAK,MAAM;AAG3B,QAAK,WAAW;;AAGlB,QAAM,MAAM,aAAa,SAAkB;AACzC,OAAI,WAAW,KAAK,CAAE,QAAO;AAC7B,OAAI,WAAW,IAAI,KAAK,QAAQ,EAAE;AAChC,aAAS,KAAK;AACd,WAAO;;KAEyB"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { MaskShadowProps } from "./type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react79 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/MaskShadow/MaskShadow.d.ts
|
|
5
|
-
declare const MaskShadow:
|
|
5
|
+
declare const MaskShadow: react79.NamedExoticComponent<MaskShadowProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { MaskShadow };
|
|
8
8
|
//# sourceMappingURL=MaskShadow.d.mts.map
|