@alifd/chat 0.3.28-beta.4 → 0.3.28-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.28-beta.4';
34
+ export const version = '0.3.28-beta.6';
@@ -27,138 +27,69 @@ const Markdown = forwardRef((_a, ref) => {
27
27
  const [displayedContent, setDisplayedContent] = useState(() => (others === null || others === void 0 ? void 0 : others.typewriterEffect) ? '' : content);
28
28
  const [contentQueue, setContentQueue] = useState(() => (others === null || others === void 0 ? void 0 : others.typewriterEffect) ? content.split('') : []);
29
29
  const typeTimeoutRef = useRef(null);
30
- const [typing, setTyping] = useState(false);
31
- const previousContentRef = useRef(content);
32
- const typewriterContent = useMemo(() => {
33
- if (!(others === null || others === void 0 ? void 0 : others.typewriterEffect)) {
34
- return content;
30
+ // 清理定时器函数
31
+ const clearTypeTimer = () => {
32
+ if (typeTimeoutRef.current) {
33
+ clearTimeout(typeTimeoutRef.current);
34
+ typeTimeoutRef.current = null;
35
35
  }
36
- return displayedContent;
37
- }, [content, displayedContent, others === null || others === void 0 ? void 0 : others.typewriterEffect]);
38
- // 消息队列更新
36
+ };
37
+ // 消息队列更新 - 仅在content或typewriterEffect变化时执行
39
38
  useEffect(() => {
39
+ // 清除任何现有的定时器
40
+ clearTypeTimer();
41
+ // 如果没有启用打字机效果,直接显示全部内容
40
42
  if (!(others === null || others === void 0 ? void 0 : others.typewriterEffect)) {
41
43
  setDisplayedContent(content);
44
+ setContentQueue([]);
42
45
  return;
43
46
  }
44
- if (content === previousContentRef.current) {
45
- return; // 如果内容没有变化,不做处理
46
- }
47
- // 记录新的内容
48
- previousContentRef.current = content;
49
- // 计算当前已输出的内容+队列中待输出的内容
50
- const currentTotalContent = displayedContent + contentQueue.join('');
51
- // 如果新内容完全不同,则重置
52
- if (content.indexOf(currentTotalContent) !== 0 && currentTotalContent.indexOf(content) !== 0) {
47
+ // 将内容分成两部分:直接显示部分和打字效果部分
48
+ if (content.length <= 20) {
49
+ // 内容不超过20个字符,全部使用打字效果
53
50
  setDisplayedContent('');
54
51
  setContentQueue(content.split(''));
55
- setTyping(true);
56
- return;
57
- }
58
- // 如果新内容是当前内容的子集(内容被截断)
59
- if (content.length < currentTotalContent.length && currentTotalContent.indexOf(content) === 0) {
60
- // 计算应保留的已显示内容
61
- if (content.length <= displayedContent.length) {
62
- // 只需截断已显示内容
63
- setDisplayedContent(content);
64
- setContentQueue([]);
65
- }
66
- else {
67
- // 需要保留部分已显示内容和部分队列内容
68
- setDisplayedContent(displayedContent);
69
- setContentQueue(content.substring(displayedContent.length).split(''));
70
- }
71
- return;
72
- }
73
- // 如果新内容包含当前内容,但有新增(最常见情况)
74
- if (content.indexOf(currentTotalContent) === 0) {
75
- // 将新增的部分添加到队列中
76
- const newContent = content.substring(currentTotalContent.length);
77
- if (newContent) {
78
- setContentQueue(prevQueue => [...prevQueue, ...newContent.split('')]);
79
- if (!typing) {
80
- setTyping(true);
81
- }
82
- }
83
- return;
84
- }
85
- // 如果新内容与已显示内容有部分重叠,但不完全包含
86
- // 查找重叠的最大前缀
87
- let overlapLength = 0;
88
- for (let i = 1; i <= Math.min(displayedContent.length, content.length); i++) {
89
- if (content.substring(0, i) === displayedContent.substring(displayedContent.length - i)) {
90
- overlapLength = i;
91
- }
92
- }
93
- if (overlapLength > 0) {
94
- // 保留重叠部分,更新队列
95
- const newDisplayed = displayedContent.substring(0, displayedContent.length - overlapLength) +
96
- content.substring(0, overlapLength);
97
- const newQueue = content.substring(overlapLength).split('');
98
- setDisplayedContent(newDisplayed);
99
- setContentQueue(newQueue);
100
- if (newQueue.length > 0 && !typing) {
101
- setTyping(true);
102
- }
103
52
  }
104
53
  else {
105
- // 无法找到合适的重叠,完全重置
106
- setDisplayedContent('');
107
- setContentQueue(content.split(''));
108
- setTyping(true);
54
+ // 内容超过20个字符,前面直接显示,后20字符用打字效果
55
+ const displayPart = content.slice(0, content.length - 20);
56
+ const typingPart = content.slice(content.length - 20);
57
+ setDisplayedContent(displayPart);
58
+ setContentQueue(typingPart.split(''));
109
59
  }
110
- }, [content, others === null || others === void 0 ? void 0 : others.typewriterEffect, displayedContent, contentQueue, typing]);
111
- // 执行打字机效果
60
+ }, [content, others === null || others === void 0 ? void 0 : others.typewriterEffect]);
61
+ // 执行打字机效果 - 仅负责处理contentQueue中的字符
112
62
  useEffect(() => {
113
- if (!(others === null || others === void 0 ? void 0 : others.typewriterEffect) || contentQueue.length === 0 || !typing) {
63
+ // 如果没有启用打字机效果或没有内容需要打字
64
+ if (!(others === null || others === void 0 ? void 0 : others.typewriterEffect) || contentQueue.length === 0) {
65
+ clearTypeTimer();
114
66
  return;
115
67
  }
116
- // 清除任何现有的定时器,确保不会有并发定时器
117
- if (typeTimeoutRef.current) {
118
- clearTimeout(typeTimeoutRef.current);
119
- typeTimeoutRef.current = null;
120
- }
121
- // 动态计算输出速度,确保在2秒内完成
122
- const remainingChars = contentQueue.length;
123
- const timePerChar = Math.max(Math.min(2000 / remainingChars, 50), 10); // 最快10ms,最慢50ms每字符
68
+ // 固定打字速度为50ms一个字
69
+ const timePerChar = 50;
124
70
  const processNextChar = () => {
125
- if (contentQueue.length === 0) {
126
- if (typeTimeoutRef.current) {
127
- clearTimeout(typeTimeoutRef.current);
128
- typeTimeoutRef.current = null;
71
+ // 使用函数式更新避免依赖项问题
72
+ setContentQueue(prevQueue => {
73
+ if (prevQueue.length === 0) {
74
+ return prevQueue;
129
75
  }
130
- setTyping(false);
131
- return;
132
- }
133
- // 获取并移除队列中的第一个字符
134
- const nextChar = contentQueue[0];
135
- const newQueue = contentQueue.slice(1);
136
- // 更新状态
137
- setDisplayedContent(displayedContent + nextChar);
138
- setContentQueue(newQueue);
139
- // 如果队列还有字符,继续处理下一个
140
- if (newQueue.length > 0) {
141
- typeTimeoutRef.current = setTimeout(processNextChar, timePerChar);
142
- }
143
- else {
144
- setTyping(false);
145
- }
76
+ // 获取并移除队列中的第一个字符
77
+ const nextChar = prevQueue[0];
78
+ const newQueue = prevQueue.slice(1);
79
+ // 更新显示内容
80
+ setDisplayedContent(prev => prev + nextChar);
81
+ // 如果队列还有字符,继续处理下一个
82
+ if (newQueue.length > 0) {
83
+ typeTimeoutRef.current = setTimeout(processNextChar, timePerChar);
84
+ }
85
+ return newQueue;
86
+ });
146
87
  };
147
88
  // 开始处理第一个字符
89
+ clearTypeTimer(); // 确保没有并发定时器
148
90
  typeTimeoutRef.current = setTimeout(processNextChar, timePerChar);
149
- return () => {
150
- if (typeTimeoutRef.current) {
151
- clearTimeout(typeTimeoutRef.current);
152
- typeTimeoutRef.current = null;
153
- }
154
- };
155
- }, [others === null || others === void 0 ? void 0 : others.typewriterEffect, typing, contentQueue.length]); // 增加依赖项,确保typing状态变化时会触发
156
- // 当 typing 状态或 contentQueue 改变时重新启动打字效果
157
- useEffect(() => {
158
- if ((others === null || others === void 0 ? void 0 : others.typewriterEffect) && contentQueue.length > 0 && !typing && !typeTimeoutRef.current) {
159
- setTyping(true);
160
- }
161
- }, [contentQueue.length, others === null || others === void 0 ? void 0 : others.typewriterEffect, typing]);
91
+ return clearTypeTimer;
92
+ }, [contentQueue.length, others === null || others === void 0 ? void 0 : others.typewriterEffect]); // 仅依赖队列长度和打字机效果开关
162
93
  // 处理 HTML 标签
163
94
  const processedContent = useMemo(() => {
164
95
  // 使用 Record 类型来定义映射关系
@@ -174,7 +105,7 @@ const Markdown = forwardRef((_a, ref) => {
174
105
  common_footnote_text_style__font_size: 'span'
175
106
  };
176
107
  const regex = /<font\s+([^>]+)>([^<]+)<\/font>/g;
177
- return typewriterContent.replace(regex, (match, attributes, text) => {
108
+ return displayedContent.replace(regex, (match, attributes, text) => {
178
109
  let sizeToken = '';
179
110
  let colorTokenV2 = '';
180
111
  // 提取属性
@@ -205,7 +136,7 @@ const Markdown = forwardRef((_a, ref) => {
205
136
  return `<${tag} style="${style}" class="markdownFont">${text}</${tag}>`;
206
137
  // return `<span style="${style}">${text}</span>`;
207
138
  });
208
- }, [typewriterContent]);
139
+ }, [displayedContent]);
209
140
  // 转换表情符号
210
141
  let transformedContent = useMemo(() => {
211
142
  // 这个函数的执行会导致类似于 // 这样的符号被 转成 &#x2F; 这样的符号
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.28-beta.4';
71
+ exports.version = '0.3.28-beta.6';
@@ -29,138 +29,69 @@ const Markdown = (0, react_1.forwardRef)((_a, ref) => {
29
29
  const [displayedContent, setDisplayedContent] = (0, react_1.useState)(() => (others === null || others === void 0 ? void 0 : others.typewriterEffect) ? '' : content);
30
30
  const [contentQueue, setContentQueue] = (0, react_1.useState)(() => (others === null || others === void 0 ? void 0 : others.typewriterEffect) ? content.split('') : []);
31
31
  const typeTimeoutRef = (0, react_1.useRef)(null);
32
- const [typing, setTyping] = (0, react_1.useState)(false);
33
- const previousContentRef = (0, react_1.useRef)(content);
34
- const typewriterContent = (0, react_1.useMemo)(() => {
35
- if (!(others === null || others === void 0 ? void 0 : others.typewriterEffect)) {
36
- return content;
32
+ // 清理定时器函数
33
+ const clearTypeTimer = () => {
34
+ if (typeTimeoutRef.current) {
35
+ clearTimeout(typeTimeoutRef.current);
36
+ typeTimeoutRef.current = null;
37
37
  }
38
- return displayedContent;
39
- }, [content, displayedContent, others === null || others === void 0 ? void 0 : others.typewriterEffect]);
40
- // 消息队列更新
38
+ };
39
+ // 消息队列更新 - 仅在content或typewriterEffect变化时执行
41
40
  (0, react_1.useEffect)(() => {
41
+ // 清除任何现有的定时器
42
+ clearTypeTimer();
43
+ // 如果没有启用打字机效果,直接显示全部内容
42
44
  if (!(others === null || others === void 0 ? void 0 : others.typewriterEffect)) {
43
45
  setDisplayedContent(content);
46
+ setContentQueue([]);
44
47
  return;
45
48
  }
46
- if (content === previousContentRef.current) {
47
- return; // 如果内容没有变化,不做处理
48
- }
49
- // 记录新的内容
50
- previousContentRef.current = content;
51
- // 计算当前已输出的内容+队列中待输出的内容
52
- const currentTotalContent = displayedContent + contentQueue.join('');
53
- // 如果新内容完全不同,则重置
54
- if (content.indexOf(currentTotalContent) !== 0 && currentTotalContent.indexOf(content) !== 0) {
49
+ // 将内容分成两部分:直接显示部分和打字效果部分
50
+ if (content.length <= 20) {
51
+ // 内容不超过20个字符,全部使用打字效果
55
52
  setDisplayedContent('');
56
53
  setContentQueue(content.split(''));
57
- setTyping(true);
58
- return;
59
- }
60
- // 如果新内容是当前内容的子集(内容被截断)
61
- if (content.length < currentTotalContent.length && currentTotalContent.indexOf(content) === 0) {
62
- // 计算应保留的已显示内容
63
- if (content.length <= displayedContent.length) {
64
- // 只需截断已显示内容
65
- setDisplayedContent(content);
66
- setContentQueue([]);
67
- }
68
- else {
69
- // 需要保留部分已显示内容和部分队列内容
70
- setDisplayedContent(displayedContent);
71
- setContentQueue(content.substring(displayedContent.length).split(''));
72
- }
73
- return;
74
- }
75
- // 如果新内容包含当前内容,但有新增(最常见情况)
76
- if (content.indexOf(currentTotalContent) === 0) {
77
- // 将新增的部分添加到队列中
78
- const newContent = content.substring(currentTotalContent.length);
79
- if (newContent) {
80
- setContentQueue(prevQueue => [...prevQueue, ...newContent.split('')]);
81
- if (!typing) {
82
- setTyping(true);
83
- }
84
- }
85
- return;
86
- }
87
- // 如果新内容与已显示内容有部分重叠,但不完全包含
88
- // 查找重叠的最大前缀
89
- let overlapLength = 0;
90
- for (let i = 1; i <= Math.min(displayedContent.length, content.length); i++) {
91
- if (content.substring(0, i) === displayedContent.substring(displayedContent.length - i)) {
92
- overlapLength = i;
93
- }
94
- }
95
- if (overlapLength > 0) {
96
- // 保留重叠部分,更新队列
97
- const newDisplayed = displayedContent.substring(0, displayedContent.length - overlapLength) +
98
- content.substring(0, overlapLength);
99
- const newQueue = content.substring(overlapLength).split('');
100
- setDisplayedContent(newDisplayed);
101
- setContentQueue(newQueue);
102
- if (newQueue.length > 0 && !typing) {
103
- setTyping(true);
104
- }
105
54
  }
106
55
  else {
107
- // 无法找到合适的重叠,完全重置
108
- setDisplayedContent('');
109
- setContentQueue(content.split(''));
110
- setTyping(true);
56
+ // 内容超过20个字符,前面直接显示,后20字符用打字效果
57
+ const displayPart = content.slice(0, content.length - 20);
58
+ const typingPart = content.slice(content.length - 20);
59
+ setDisplayedContent(displayPart);
60
+ setContentQueue(typingPart.split(''));
111
61
  }
112
- }, [content, others === null || others === void 0 ? void 0 : others.typewriterEffect, displayedContent, contentQueue, typing]);
113
- // 执行打字机效果
62
+ }, [content, others === null || others === void 0 ? void 0 : others.typewriterEffect]);
63
+ // 执行打字机效果 - 仅负责处理contentQueue中的字符
114
64
  (0, react_1.useEffect)(() => {
115
- if (!(others === null || others === void 0 ? void 0 : others.typewriterEffect) || contentQueue.length === 0 || !typing) {
65
+ // 如果没有启用打字机效果或没有内容需要打字
66
+ if (!(others === null || others === void 0 ? void 0 : others.typewriterEffect) || contentQueue.length === 0) {
67
+ clearTypeTimer();
116
68
  return;
117
69
  }
118
- // 清除任何现有的定时器,确保不会有并发定时器
119
- if (typeTimeoutRef.current) {
120
- clearTimeout(typeTimeoutRef.current);
121
- typeTimeoutRef.current = null;
122
- }
123
- // 动态计算输出速度,确保在2秒内完成
124
- const remainingChars = contentQueue.length;
125
- const timePerChar = Math.max(Math.min(2000 / remainingChars, 50), 10); // 最快10ms,最慢50ms每字符
70
+ // 固定打字速度为50ms一个字
71
+ const timePerChar = 50;
126
72
  const processNextChar = () => {
127
- if (contentQueue.length === 0) {
128
- if (typeTimeoutRef.current) {
129
- clearTimeout(typeTimeoutRef.current);
130
- typeTimeoutRef.current = null;
73
+ // 使用函数式更新避免依赖项问题
74
+ setContentQueue(prevQueue => {
75
+ if (prevQueue.length === 0) {
76
+ return prevQueue;
131
77
  }
132
- setTyping(false);
133
- return;
134
- }
135
- // 获取并移除队列中的第一个字符
136
- const nextChar = contentQueue[0];
137
- const newQueue = contentQueue.slice(1);
138
- // 更新状态
139
- setDisplayedContent(displayedContent + nextChar);
140
- setContentQueue(newQueue);
141
- // 如果队列还有字符,继续处理下一个
142
- if (newQueue.length > 0) {
143
- typeTimeoutRef.current = setTimeout(processNextChar, timePerChar);
144
- }
145
- else {
146
- setTyping(false);
147
- }
78
+ // 获取并移除队列中的第一个字符
79
+ const nextChar = prevQueue[0];
80
+ const newQueue = prevQueue.slice(1);
81
+ // 更新显示内容
82
+ setDisplayedContent(prev => prev + nextChar);
83
+ // 如果队列还有字符,继续处理下一个
84
+ if (newQueue.length > 0) {
85
+ typeTimeoutRef.current = setTimeout(processNextChar, timePerChar);
86
+ }
87
+ return newQueue;
88
+ });
148
89
  };
149
90
  // 开始处理第一个字符
91
+ clearTypeTimer(); // 确保没有并发定时器
150
92
  typeTimeoutRef.current = setTimeout(processNextChar, timePerChar);
151
- return () => {
152
- if (typeTimeoutRef.current) {
153
- clearTimeout(typeTimeoutRef.current);
154
- typeTimeoutRef.current = null;
155
- }
156
- };
157
- }, [others === null || others === void 0 ? void 0 : others.typewriterEffect, typing, contentQueue.length]); // 增加依赖项,确保typing状态变化时会触发
158
- // 当 typing 状态或 contentQueue 改变时重新启动打字效果
159
- (0, react_1.useEffect)(() => {
160
- if ((others === null || others === void 0 ? void 0 : others.typewriterEffect) && contentQueue.length > 0 && !typing && !typeTimeoutRef.current) {
161
- setTyping(true);
162
- }
163
- }, [contentQueue.length, others === null || others === void 0 ? void 0 : others.typewriterEffect, typing]);
93
+ return clearTypeTimer;
94
+ }, [contentQueue.length, others === null || others === void 0 ? void 0 : others.typewriterEffect]); // 仅依赖队列长度和打字机效果开关
164
95
  // 处理 HTML 标签
165
96
  const processedContent = (0, react_1.useMemo)(() => {
166
97
  // 使用 Record 类型来定义映射关系
@@ -176,7 +107,7 @@ const Markdown = (0, react_1.forwardRef)((_a, ref) => {
176
107
  common_footnote_text_style__font_size: 'span'
177
108
  };
178
109
  const regex = /<font\s+([^>]+)>([^<]+)<\/font>/g;
179
- return typewriterContent.replace(regex, (match, attributes, text) => {
110
+ return displayedContent.replace(regex, (match, attributes, text) => {
180
111
  let sizeToken = '';
181
112
  let colorTokenV2 = '';
182
113
  // 提取属性
@@ -207,7 +138,7 @@ const Markdown = (0, react_1.forwardRef)((_a, ref) => {
207
138
  return `<${tag} style="${style}" class="markdownFont">${text}</${tag}>`;
208
139
  // return `<span style="${style}">${text}</span>`;
209
140
  });
210
- }, [typewriterContent]);
141
+ }, [displayedContent]);
211
142
  // 转换表情符号
212
143
  let transformedContent = (0, react_1.useMemo)(() => {
213
144
  // 这个函数的执行会导致类似于 // 这样的符号被 转成 &#x2F; 这样的符号
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alifd/chat",
3
- "version": "0.3.28-beta.4",
3
+ "version": "0.3.28-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",