@alifd/chat 0.3.32-beta.4 → 0.3.32-beta.6
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/index.js +1 -1
- package/es/markdown/index.js +43 -12
- package/es/markdown/types.d.ts +5 -0
- 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/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-beta.
|
|
34
|
+
export const version = '0.3.32-beta.6';
|
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/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-beta.
|
|
71
|
+
exports.version = '0.3.32-beta.6';
|
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
|
}
|