@alifd/chat 0.3.32-beta.5 → 0.3.32
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/html-render/index.js +7 -19
- package/es/index.js +1 -1
- package/es/markdown/index.js +43 -12
- package/es/markdown/types.d.ts +5 -0
- package/lib/html-render/index.js +7 -19
- package/lib/index.js +1 -1
- package/lib/markdown/index.js +43 -12
- package/lib/markdown/types.d.ts +5 -0
- package/package.json +1 -1
package/es/html-render/index.js
CHANGED
|
@@ -103,7 +103,7 @@ const HTMLRenderer = memo(function HTMLRenderer({ className, children, imagePrev
|
|
|
103
103
|
const parserOptions = {
|
|
104
104
|
// @ts-ignore
|
|
105
105
|
replace: domNode => {
|
|
106
|
-
var _a, _b, _c, _d, _e, _f
|
|
106
|
+
var _a, _b, _c, _d, _e, _f;
|
|
107
107
|
// 处理文本节点
|
|
108
108
|
if ((domNode.type === 'text' || domNode.nodeType === 3) && typewriterEffect) {
|
|
109
109
|
return processTextNode(domNode);
|
|
@@ -112,22 +112,10 @@ const HTMLRenderer = memo(function HTMLRenderer({ className, children, imagePrev
|
|
|
112
112
|
const { name } = domNode;
|
|
113
113
|
if (name === 'link-reference') {
|
|
114
114
|
const element = (domToReact([domNode]));
|
|
115
|
-
// 检查是否有子元素
|
|
116
|
-
if (!((_a = element.props) === null || _a === void 0 ? void 0 : _a.children)) {
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
115
|
// link-reference 的子元素一定是 a 标签
|
|
120
|
-
const aElement = (
|
|
121
|
-
// 检查a标签是否存在
|
|
122
|
-
if (!aElement || !aElement.props) {
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
116
|
+
const aElement = (_a = element.props) === null || _a === void 0 ? void 0 : _a.children;
|
|
125
117
|
// a 标签的子元素一定是 span 节点
|
|
126
|
-
const spanElement = (
|
|
127
|
-
// 检查span元素是否存在
|
|
128
|
-
if (!spanElement || !spanElement.props) {
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
118
|
+
const spanElement = (_b = aElement === null || aElement === void 0 ? void 0 : aElement.props) === null || _b === void 0 ? void 0 : _b.children;
|
|
131
119
|
const handleUrlClick = () => {
|
|
132
120
|
var _a;
|
|
133
121
|
if (!((_a = aElement === null || aElement === void 0 ? void 0 : aElement.props) === null || _a === void 0 ? void 0 : _a.href)) {
|
|
@@ -140,10 +128,10 @@ const HTMLRenderer = memo(function HTMLRenderer({ className, children, imagePrev
|
|
|
140
128
|
defaultOpenLink(aElement.props.href);
|
|
141
129
|
}
|
|
142
130
|
};
|
|
143
|
-
return (React.createElement(Balloon, { v2: true, align: "b", className: "link-reference-balloon", closable: false, offset: [0, -8], triggerType: ['hover'], trigger: React.createElement("span", { className: "link-reference-index", onClick: handleUrlClick }, (
|
|
131
|
+
return (React.createElement(Balloon, { v2: true, align: "b", className: "link-reference-balloon", closable: false, offset: [0, -8], triggerType: ['hover'], trigger: React.createElement("span", { className: "link-reference-index", onClick: handleUrlClick }, (_c = spanElement === null || spanElement === void 0 ? void 0 : spanElement.props) === null || _c === void 0 ? void 0 : _c.children) },
|
|
144
132
|
React.createElement("div", { className: "link-reference-content" },
|
|
145
|
-
React.createElement("img", { className: "link-reference-source-icon", src: ((
|
|
146
|
-
React.createElement("a", { className: "link-reference-title", onClick: handleUrlClick }, ((
|
|
133
|
+
React.createElement("img", { className: "link-reference-source-icon", src: ((_d = element.props) === null || _d === void 0 ? void 0 : _d['data-source-icon']) || '' }),
|
|
134
|
+
React.createElement("a", { className: "link-reference-title", onClick: handleUrlClick }, ((_e = element.props) === null || _e === void 0 ? void 0 : _e['data-title']) || ''))));
|
|
147
135
|
}
|
|
148
136
|
if (name === 'a') {
|
|
149
137
|
const element = (domToReact([domNode]));
|
|
@@ -197,7 +185,7 @@ const HTMLRenderer = memo(function HTMLRenderer({ className, children, imagePrev
|
|
|
197
185
|
return renderImage(element.props);
|
|
198
186
|
}
|
|
199
187
|
// 换成统一的图片渲染
|
|
200
|
-
return React.createElement(Img, Object.assign({}, element.props, { imageClassName: (
|
|
188
|
+
return React.createElement(Img, Object.assign({}, element.props, { imageClassName: (_f = element.props) === null || _f === void 0 ? void 0 : _f.className, enablePreview: imagePreview, onImageClick: () => {
|
|
201
189
|
handleImageClick === null || handleImageClick === void 0 ? void 0 : handleImageClick(element.props.src);
|
|
202
190
|
} }));
|
|
203
191
|
}
|
package/es/index.js
CHANGED
|
@@ -31,4 +31,4 @@ export { default as RadioGroup } from './radio-group';
|
|
|
31
31
|
export { default as CheckboxGroup } from './checkbox-group';
|
|
32
32
|
export { default as Select } from './select';
|
|
33
33
|
export { default as Flip } from './flip';
|
|
34
|
-
export const version = '0.3.32
|
|
34
|
+
export const version = '0.3.32';
|
package/es/markdown/index.js
CHANGED
|
@@ -10,6 +10,8 @@ import hljs from 'highlight.js'; // 引入 highlight.js
|
|
|
10
10
|
import 'highlight.js/styles/github.css'; // 引入代码高亮样式
|
|
11
11
|
let displayedContentCache = '';
|
|
12
12
|
const DEFAULT_LOADING_ICON = 'https://img.alicdn.com/imgextra/i1/O1CN01wdlADU1WCvOLNSB3w_!!6000000002753-1-tps-530-255.gif';
|
|
13
|
+
// 默认白名单标签
|
|
14
|
+
const DEFAULT_WHITELIST_TAGS = ['img', 'code', 'pre', 'link-reference'];
|
|
13
15
|
/**
|
|
14
16
|
* @component Markdown
|
|
15
17
|
* @en Markdown
|
|
@@ -26,8 +28,37 @@ const Markdown = forwardRef((_a, ref) => {
|
|
|
26
28
|
}, [onReady]);
|
|
27
29
|
const containerRef = useRef(null);
|
|
28
30
|
const [displayedContent, setDisplayedContent] = useState(() => (others === null || others === void 0 ? void 0 : others.typewriterEffect) ? displayedContentCache : content);
|
|
29
|
-
const [contentQueue, setContentQueue] = useState(() => (others === null || others === void 0 ? void 0 : others.typewriterEffect) ? content.
|
|
31
|
+
const [contentQueue, setContentQueue] = useState(() => (others === null || others === void 0 ? void 0 : others.typewriterEffect) ? createContentQueue(content, others.whitelistTags) : []);
|
|
30
32
|
const typeTimeoutRef = useRef(null);
|
|
33
|
+
// 创建打字机内容队列,处理白名单标签
|
|
34
|
+
function createContentQueue(text, customWhitelist) {
|
|
35
|
+
if (!text)
|
|
36
|
+
return [];
|
|
37
|
+
const whitelist = [...DEFAULT_WHITELIST_TAGS, ...(customWhitelist || [])];
|
|
38
|
+
if (whitelist.length === 0) {
|
|
39
|
+
return text.split('');
|
|
40
|
+
}
|
|
41
|
+
// 创建匹配白名单标签的正则表达式
|
|
42
|
+
const tagPattern = new RegExp(`(<(${whitelist.join('|')}).*?>.*?<\\/\\2>)`, 'gs');
|
|
43
|
+
const parts = [];
|
|
44
|
+
let lastIndex = 0;
|
|
45
|
+
let match;
|
|
46
|
+
// 分割文本,将白名单标签作为整体,其他文本按字符分割
|
|
47
|
+
while ((match = tagPattern.exec(text)) !== null) {
|
|
48
|
+
if (match.index > lastIndex) {
|
|
49
|
+
// 添加标签前的普通文本,按字符分割
|
|
50
|
+
parts.push(...text.substring(lastIndex, match.index).split(''));
|
|
51
|
+
}
|
|
52
|
+
// 添加整个标签内容作为一项
|
|
53
|
+
parts.push(match[1]);
|
|
54
|
+
lastIndex = match.index + match[1].length;
|
|
55
|
+
}
|
|
56
|
+
// 添加剩余的普通文本,按字符分割
|
|
57
|
+
if (lastIndex < text.length) {
|
|
58
|
+
parts.push(...text.substring(lastIndex).split(''));
|
|
59
|
+
}
|
|
60
|
+
return parts;
|
|
61
|
+
}
|
|
31
62
|
// 清理定时器函数
|
|
32
63
|
const clearTypeTimer = () => {
|
|
33
64
|
if (typeTimeoutRef.current) {
|
|
@@ -60,9 +91,9 @@ const Markdown = forwardRef((_a, ref) => {
|
|
|
60
91
|
const displayPart = content.slice(0, contentLength - diffLength);
|
|
61
92
|
const typingPart = content.slice(contentLength - diffLength);
|
|
62
93
|
setDisplayedContent(displayPart);
|
|
63
|
-
setContentQueue(typingPart.
|
|
94
|
+
setContentQueue(createContentQueue(typingPart, others.whitelistTags));
|
|
64
95
|
}
|
|
65
|
-
}, [content, others === null || others === void 0 ? void 0 : others.typewriterEffect]);
|
|
96
|
+
}, [content, others === null || others === void 0 ? void 0 : others.typewriterEffect, others === null || others === void 0 ? void 0 : others.whitelistTags]);
|
|
66
97
|
// 执行打字机效果 - 仅负责处理contentQueue中的字符
|
|
67
98
|
useEffect(() => {
|
|
68
99
|
// 如果没有启用打字机效果或没有内容需要打字
|
|
@@ -71,25 +102,25 @@ const Markdown = forwardRef((_a, ref) => {
|
|
|
71
102
|
return;
|
|
72
103
|
}
|
|
73
104
|
// 固定吐字速度为50ms一次
|
|
74
|
-
const
|
|
75
|
-
// 动态调整一次吐字的字数,确保1s内吐完,最少吐2
|
|
76
|
-
const
|
|
77
|
-
const
|
|
105
|
+
const timePerItem = 100;
|
|
106
|
+
// 动态调整一次吐字的字数,确保1s内吐完,最少吐2个列表项
|
|
107
|
+
const itemsPerTime = Math.max(Math.floor(contentQueue.length / 10), 2);
|
|
108
|
+
const processNextItem = () => {
|
|
78
109
|
const currentQueue = contentQueue;
|
|
79
110
|
if (currentQueue.length === 0) {
|
|
80
111
|
return;
|
|
81
112
|
}
|
|
82
|
-
// 获取并移除队列中的第charsPerTime
|
|
83
|
-
const
|
|
84
|
-
const newQueue = currentQueue.slice(
|
|
113
|
+
// 获取并移除队列中的第charsPerTime个列表项
|
|
114
|
+
const nextItems = currentQueue.slice(0, itemsPerTime);
|
|
115
|
+
const newQueue = currentQueue.slice(itemsPerTime);
|
|
85
116
|
// 更新显示内容
|
|
86
|
-
setDisplayedContent(prev => prev +
|
|
117
|
+
setDisplayedContent(prev => prev + nextItems.join(''));
|
|
87
118
|
// 更新队列
|
|
88
119
|
setContentQueue(newQueue);
|
|
89
120
|
};
|
|
90
121
|
// 开始处理第一个字符
|
|
91
122
|
clearTypeTimer(); // 确保没有并发定时器
|
|
92
|
-
typeTimeoutRef.current = setTimeout(
|
|
123
|
+
typeTimeoutRef.current = setTimeout(processNextItem, timePerItem);
|
|
93
124
|
return clearTypeTimer;
|
|
94
125
|
}, [contentQueue.length, others === null || others === void 0 ? void 0 : others.typewriterEffect]);
|
|
95
126
|
// 处理 HTML 标签
|
package/es/markdown/types.d.ts
CHANGED
|
@@ -71,4 +71,9 @@ export interface MarkdownProps extends React.HTMLAttributes<HTMLElement> {
|
|
|
71
71
|
* 是否启用打字机效果
|
|
72
72
|
*/
|
|
73
73
|
typewriterEffect?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* 白名单标签列表,这些标签及其内容会作为整体放入打字机队列
|
|
76
|
+
* @en Whitelist tags that will be put into typewriter queue as a whole
|
|
77
|
+
*/
|
|
78
|
+
whitelistTags?: string[];
|
|
74
79
|
}
|
package/lib/html-render/index.js
CHANGED
|
@@ -106,7 +106,7 @@ const HTMLRenderer = (0, react_1.memo)(function HTMLRenderer({ className, childr
|
|
|
106
106
|
const parserOptions = {
|
|
107
107
|
// @ts-ignore
|
|
108
108
|
replace: domNode => {
|
|
109
|
-
var _a, _b, _c, _d, _e, _f
|
|
109
|
+
var _a, _b, _c, _d, _e, _f;
|
|
110
110
|
// 处理文本节点
|
|
111
111
|
if ((domNode.type === 'text' || domNode.nodeType === 3) && typewriterEffect) {
|
|
112
112
|
return processTextNode(domNode);
|
|
@@ -115,22 +115,10 @@ const HTMLRenderer = (0, react_1.memo)(function HTMLRenderer({ className, childr
|
|
|
115
115
|
const { name } = domNode;
|
|
116
116
|
if (name === 'link-reference') {
|
|
117
117
|
const element = ((0, html_react_parser_1.domToReact)([domNode]));
|
|
118
|
-
// 检查是否有子元素
|
|
119
|
-
if (!((_a = element.props) === null || _a === void 0 ? void 0 : _a.children)) {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
118
|
// link-reference 的子元素一定是 a 标签
|
|
123
|
-
const aElement = (
|
|
124
|
-
// 检查a标签是否存在
|
|
125
|
-
if (!aElement || !aElement.props) {
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
119
|
+
const aElement = (_a = element.props) === null || _a === void 0 ? void 0 : _a.children;
|
|
128
120
|
// a 标签的子元素一定是 span 节点
|
|
129
|
-
const spanElement = (
|
|
130
|
-
// 检查span元素是否存在
|
|
131
|
-
if (!spanElement || !spanElement.props) {
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
121
|
+
const spanElement = (_b = aElement === null || aElement === void 0 ? void 0 : aElement.props) === null || _b === void 0 ? void 0 : _b.children;
|
|
134
122
|
const handleUrlClick = () => {
|
|
135
123
|
var _a;
|
|
136
124
|
if (!((_a = aElement === null || aElement === void 0 ? void 0 : aElement.props) === null || _a === void 0 ? void 0 : _a.href)) {
|
|
@@ -143,10 +131,10 @@ const HTMLRenderer = (0, react_1.memo)(function HTMLRenderer({ className, childr
|
|
|
143
131
|
defaultOpenLink(aElement.props.href);
|
|
144
132
|
}
|
|
145
133
|
};
|
|
146
|
-
return (react_1.default.createElement(balloon_1.default, { v2: true, align: "b", className: "link-reference-balloon", closable: false, offset: [0, -8], triggerType: ['hover'], trigger: react_1.default.createElement("span", { className: "link-reference-index", onClick: handleUrlClick }, (
|
|
134
|
+
return (react_1.default.createElement(balloon_1.default, { v2: true, align: "b", className: "link-reference-balloon", closable: false, offset: [0, -8], triggerType: ['hover'], trigger: react_1.default.createElement("span", { className: "link-reference-index", onClick: handleUrlClick }, (_c = spanElement === null || spanElement === void 0 ? void 0 : spanElement.props) === null || _c === void 0 ? void 0 : _c.children) },
|
|
147
135
|
react_1.default.createElement("div", { className: "link-reference-content" },
|
|
148
|
-
react_1.default.createElement("img", { className: "link-reference-source-icon", src: ((
|
|
149
|
-
react_1.default.createElement("a", { className: "link-reference-title", onClick: handleUrlClick }, ((
|
|
136
|
+
react_1.default.createElement("img", { className: "link-reference-source-icon", src: ((_d = element.props) === null || _d === void 0 ? void 0 : _d['data-source-icon']) || '' }),
|
|
137
|
+
react_1.default.createElement("a", { className: "link-reference-title", onClick: handleUrlClick }, ((_e = element.props) === null || _e === void 0 ? void 0 : _e['data-title']) || ''))));
|
|
150
138
|
}
|
|
151
139
|
if (name === 'a') {
|
|
152
140
|
const element = ((0, html_react_parser_1.domToReact)([domNode]));
|
|
@@ -200,7 +188,7 @@ const HTMLRenderer = (0, react_1.memo)(function HTMLRenderer({ className, childr
|
|
|
200
188
|
return renderImage(element.props);
|
|
201
189
|
}
|
|
202
190
|
// 换成统一的图片渲染
|
|
203
|
-
return react_1.default.createElement(img_1.default, Object.assign({}, element.props, { imageClassName: (
|
|
191
|
+
return react_1.default.createElement(img_1.default, Object.assign({}, element.props, { imageClassName: (_f = element.props) === null || _f === void 0 ? void 0 : _f.className, enablePreview: imagePreview, onImageClick: () => {
|
|
204
192
|
handleImageClick === null || handleImageClick === void 0 ? void 0 : handleImageClick(element.props.src);
|
|
205
193
|
} }));
|
|
206
194
|
}
|
package/lib/index.js
CHANGED
|
@@ -68,4 +68,4 @@ var select_1 = require("./select");
|
|
|
68
68
|
Object.defineProperty(exports, "Select", { enumerable: true, get: function () { return tslib_1.__importDefault(select_1).default; } });
|
|
69
69
|
var flip_1 = require("./flip");
|
|
70
70
|
Object.defineProperty(exports, "Flip", { enumerable: true, get: function () { return tslib_1.__importDefault(flip_1).default; } });
|
|
71
|
-
exports.version = '0.3.32
|
|
71
|
+
exports.version = '0.3.32';
|
package/lib/markdown/index.js
CHANGED
|
@@ -12,6 +12,8 @@ const highlight_js_1 = tslib_1.__importDefault(require("highlight.js")); // 引
|
|
|
12
12
|
require("highlight.js/styles/github.css"); // 引入代码高亮样式
|
|
13
13
|
let displayedContentCache = '';
|
|
14
14
|
const DEFAULT_LOADING_ICON = 'https://img.alicdn.com/imgextra/i1/O1CN01wdlADU1WCvOLNSB3w_!!6000000002753-1-tps-530-255.gif';
|
|
15
|
+
// 默认白名单标签
|
|
16
|
+
const DEFAULT_WHITELIST_TAGS = ['img', 'code', 'pre', 'link-reference'];
|
|
15
17
|
/**
|
|
16
18
|
* @component Markdown
|
|
17
19
|
* @en Markdown
|
|
@@ -28,8 +30,37 @@ const Markdown = (0, react_1.forwardRef)((_a, ref) => {
|
|
|
28
30
|
}, [onReady]);
|
|
29
31
|
const containerRef = (0, react_1.useRef)(null);
|
|
30
32
|
const [displayedContent, setDisplayedContent] = (0, react_1.useState)(() => (others === null || others === void 0 ? void 0 : others.typewriterEffect) ? displayedContentCache : content);
|
|
31
|
-
const [contentQueue, setContentQueue] = (0, react_1.useState)(() => (others === null || others === void 0 ? void 0 : others.typewriterEffect) ? content.
|
|
33
|
+
const [contentQueue, setContentQueue] = (0, react_1.useState)(() => (others === null || others === void 0 ? void 0 : others.typewriterEffect) ? createContentQueue(content, others.whitelistTags) : []);
|
|
32
34
|
const typeTimeoutRef = (0, react_1.useRef)(null);
|
|
35
|
+
// 创建打字机内容队列,处理白名单标签
|
|
36
|
+
function createContentQueue(text, customWhitelist) {
|
|
37
|
+
if (!text)
|
|
38
|
+
return [];
|
|
39
|
+
const whitelist = [...DEFAULT_WHITELIST_TAGS, ...(customWhitelist || [])];
|
|
40
|
+
if (whitelist.length === 0) {
|
|
41
|
+
return text.split('');
|
|
42
|
+
}
|
|
43
|
+
// 创建匹配白名单标签的正则表达式
|
|
44
|
+
const tagPattern = new RegExp(`(<(${whitelist.join('|')}).*?>.*?<\\/\\2>)`, 'gs');
|
|
45
|
+
const parts = [];
|
|
46
|
+
let lastIndex = 0;
|
|
47
|
+
let match;
|
|
48
|
+
// 分割文本,将白名单标签作为整体,其他文本按字符分割
|
|
49
|
+
while ((match = tagPattern.exec(text)) !== null) {
|
|
50
|
+
if (match.index > lastIndex) {
|
|
51
|
+
// 添加标签前的普通文本,按字符分割
|
|
52
|
+
parts.push(...text.substring(lastIndex, match.index).split(''));
|
|
53
|
+
}
|
|
54
|
+
// 添加整个标签内容作为一项
|
|
55
|
+
parts.push(match[1]);
|
|
56
|
+
lastIndex = match.index + match[1].length;
|
|
57
|
+
}
|
|
58
|
+
// 添加剩余的普通文本,按字符分割
|
|
59
|
+
if (lastIndex < text.length) {
|
|
60
|
+
parts.push(...text.substring(lastIndex).split(''));
|
|
61
|
+
}
|
|
62
|
+
return parts;
|
|
63
|
+
}
|
|
33
64
|
// 清理定时器函数
|
|
34
65
|
const clearTypeTimer = () => {
|
|
35
66
|
if (typeTimeoutRef.current) {
|
|
@@ -62,9 +93,9 @@ const Markdown = (0, react_1.forwardRef)((_a, ref) => {
|
|
|
62
93
|
const displayPart = content.slice(0, contentLength - diffLength);
|
|
63
94
|
const typingPart = content.slice(contentLength - diffLength);
|
|
64
95
|
setDisplayedContent(displayPart);
|
|
65
|
-
setContentQueue(typingPart.
|
|
96
|
+
setContentQueue(createContentQueue(typingPart, others.whitelistTags));
|
|
66
97
|
}
|
|
67
|
-
}, [content, others === null || others === void 0 ? void 0 : others.typewriterEffect]);
|
|
98
|
+
}, [content, others === null || others === void 0 ? void 0 : others.typewriterEffect, others === null || others === void 0 ? void 0 : others.whitelistTags]);
|
|
68
99
|
// 执行打字机效果 - 仅负责处理contentQueue中的字符
|
|
69
100
|
(0, react_1.useEffect)(() => {
|
|
70
101
|
// 如果没有启用打字机效果或没有内容需要打字
|
|
@@ -73,25 +104,25 @@ const Markdown = (0, react_1.forwardRef)((_a, ref) => {
|
|
|
73
104
|
return;
|
|
74
105
|
}
|
|
75
106
|
// 固定吐字速度为50ms一次
|
|
76
|
-
const
|
|
77
|
-
// 动态调整一次吐字的字数,确保1s内吐完,最少吐2
|
|
78
|
-
const
|
|
79
|
-
const
|
|
107
|
+
const timePerItem = 100;
|
|
108
|
+
// 动态调整一次吐字的字数,确保1s内吐完,最少吐2个列表项
|
|
109
|
+
const itemsPerTime = Math.max(Math.floor(contentQueue.length / 10), 2);
|
|
110
|
+
const processNextItem = () => {
|
|
80
111
|
const currentQueue = contentQueue;
|
|
81
112
|
if (currentQueue.length === 0) {
|
|
82
113
|
return;
|
|
83
114
|
}
|
|
84
|
-
// 获取并移除队列中的第charsPerTime
|
|
85
|
-
const
|
|
86
|
-
const newQueue = currentQueue.slice(
|
|
115
|
+
// 获取并移除队列中的第charsPerTime个列表项
|
|
116
|
+
const nextItems = currentQueue.slice(0, itemsPerTime);
|
|
117
|
+
const newQueue = currentQueue.slice(itemsPerTime);
|
|
87
118
|
// 更新显示内容
|
|
88
|
-
setDisplayedContent(prev => prev +
|
|
119
|
+
setDisplayedContent(prev => prev + nextItems.join(''));
|
|
89
120
|
// 更新队列
|
|
90
121
|
setContentQueue(newQueue);
|
|
91
122
|
};
|
|
92
123
|
// 开始处理第一个字符
|
|
93
124
|
clearTypeTimer(); // 确保没有并发定时器
|
|
94
|
-
typeTimeoutRef.current = setTimeout(
|
|
125
|
+
typeTimeoutRef.current = setTimeout(processNextItem, timePerItem);
|
|
95
126
|
return clearTypeTimer;
|
|
96
127
|
}, [contentQueue.length, others === null || others === void 0 ? void 0 : others.typewriterEffect]);
|
|
97
128
|
// 处理 HTML 标签
|
package/lib/markdown/types.d.ts
CHANGED
|
@@ -71,4 +71,9 @@ export interface MarkdownProps extends React.HTMLAttributes<HTMLElement> {
|
|
|
71
71
|
* 是否启用打字机效果
|
|
72
72
|
*/
|
|
73
73
|
typewriterEffect?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* 白名单标签列表,这些标签及其内容会作为整体放入打字机队列
|
|
76
|
+
* @en Whitelist tags that will be put into typewriter queue as a whole
|
|
77
|
+
*/
|
|
78
|
+
whitelistTags?: string[];
|
|
74
79
|
}
|