@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 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.4';
34
+ export const version = '0.3.32-beta.6';
@@ -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.split('') : []);
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.split(''));
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 timePerChar = 100;
75
- // 动态调整一次吐字的字数,确保1s内吐完,最少吐2个字符
76
- const charsPerTime = Math.max(Math.floor(contentQueue.length / 10), 2);
77
- const processNextChar = () => {
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 nextChars = currentQueue.slice(0, charsPerTime);
84
- const newQueue = currentQueue.slice(charsPerTime);
113
+ // 获取并移除队列中的第charsPerTime个列表项
114
+ const nextItems = currentQueue.slice(0, itemsPerTime);
115
+ const newQueue = currentQueue.slice(itemsPerTime);
85
116
  // 更新显示内容
86
- setDisplayedContent(prev => prev + nextChars.join(''));
117
+ setDisplayedContent(prev => prev + nextItems.join(''));
87
118
  // 更新队列
88
119
  setContentQueue(newQueue);
89
120
  };
90
121
  // 开始处理第一个字符
91
122
  clearTypeTimer(); // 确保没有并发定时器
92
- typeTimeoutRef.current = setTimeout(processNextChar, timePerChar);
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 标签
@@ -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.4';
71
+ exports.version = '0.3.32-beta.6';
@@ -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.split('') : []);
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.split(''));
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 timePerChar = 100;
77
- // 动态调整一次吐字的字数,确保1s内吐完,最少吐2个字符
78
- const charsPerTime = Math.max(Math.floor(contentQueue.length / 10), 2);
79
- const processNextChar = () => {
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 nextChars = currentQueue.slice(0, charsPerTime);
86
- const newQueue = currentQueue.slice(charsPerTime);
115
+ // 获取并移除队列中的第charsPerTime个列表项
116
+ const nextItems = currentQueue.slice(0, itemsPerTime);
117
+ const newQueue = currentQueue.slice(itemsPerTime);
87
118
  // 更新显示内容
88
- setDisplayedContent(prev => prev + nextChars.join(''));
119
+ setDisplayedContent(prev => prev + nextItems.join(''));
89
120
  // 更新队列
90
121
  setContentQueue(newQueue);
91
122
  };
92
123
  // 开始处理第一个字符
93
124
  clearTypeTimer(); // 确保没有并发定时器
94
- typeTimeoutRef.current = setTimeout(processNextChar, timePerChar);
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 标签
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alifd/chat",
3
- "version": "0.3.32-beta.4",
3
+ "version": "0.3.32-beta.6",
4
4
  "description": "A configurable component library for chat built on React.",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.js",