Skip to content

Commit 23cb610

Browse files
committed
Merge branch 'main' of https://github.com/lobehub/lobe-chat
2 parents bdca7c6 + c84df58 commit 23cb610

File tree

6 files changed

+84
-140
lines changed

6 files changed

+84
-140
lines changed

CHANGELOG.md

+25
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,31 @@
22

33
# Changelog
44

5+
### [Version 0.162.4](https://github.com/lobehub/lobe-chat/compare/v0.162.3...v0.162.4)
6+
7+
<sup>Released on **2024-05-28**</sup>
8+
9+
#### 🐛 Bug Fixes
10+
11+
- **misc**: Fix auto focus issues.
12+
13+
<br/>
14+
15+
<details>
16+
<summary><kbd>Improvements and Fixes</kbd></summary>
17+
18+
#### What's fixed
19+
20+
- **misc**: Fix auto focus issues, closes [#2697](https://github.com/lobehub/lobe-chat/issues/2697) ([8df856e](https://github.com/lobehub/lobe-chat/commit/8df856e))
21+
22+
</details>
23+
24+
<div align="right">
25+
26+
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27+
28+
</div>
29+
530
### [Version 0.162.3](https://github.com/lobehub/lobe-chat/compare/v0.162.2...v0.162.3)
631

732
<sup>Released on **2024-05-28**</sup>

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@lobehub/chat",
3-
"version": "0.162.3",
3+
"version": "0.162.4",
44
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
55
"keywords": [
66
"framework",
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,45 @@
11
import { act, renderHook } from '@testing-library/react';
2-
import { RefObject } from 'react';
2+
import { describe, expect, it, vi } from 'vitest';
33

4-
import { useAutoFocus } from '../useAutoFocus';
5-
6-
enum ElType {
7-
div,
8-
input,
9-
markdown,
10-
debug,
11-
}
12-
13-
// 模拟elementFromPoint方法
14-
document.elementFromPoint = function (x) {
15-
if (x === ElType.div) {
16-
return document.createElement('div');
17-
}
4+
import { useChatStore } from '@/store/chat';
5+
import { chatSelectors } from '@/store/chat/selectors';
186

19-
if (x === ElType.input) {
20-
return document.createElement('input');
21-
}
22-
23-
if (x === ElType.debug) {
24-
return document.createElement('pre');
25-
}
26-
27-
if (x === ElType.markdown) {
28-
const markdownEl = document.createElement('article');
29-
const markdownChildEl = document.createElement('p');
30-
markdownEl.appendChild(markdownChildEl);
31-
return markdownChildEl;
32-
}
7+
import { useAutoFocus } from '../useAutoFocus';
338

34-
return null;
35-
};
9+
vi.mock('zustand/traditional');
3610

3711
describe('useAutoFocus', () => {
38-
it('should focus inputRef when mouseup event happens outside of input or markdown element', () => {
39-
const inputRef = { current: { focus: vi.fn() } } as RefObject<any>;
40-
renderHook(() => useAutoFocus(inputRef));
12+
it('should focus the input when chatKey changes', () => {
13+
const focusMock = vi.fn();
14+
const inputRef = { current: { focus: focusMock } };
4115

42-
// Simulate a mousedown event on an element outside of input or markdown element
4316
act(() => {
44-
document.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, clientX: ElType.div }));
17+
useChatStore.setState({ activeId: '1', activeTopicId: '2' });
4518
});
4619

47-
// Simulate a mouseup event
48-
act(() => {
49-
document.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
50-
});
51-
52-
expect(inputRef.current?.focus).toHaveBeenCalledTimes(1);
53-
});
20+
renderHook(() => useAutoFocus(inputRef as any));
5421

55-
it('should not focus inputRef when mouseup event happens inside of input element', () => {
56-
const inputRef = { current: { focus: vi.fn() } } as RefObject<any>;
57-
renderHook(() => useAutoFocus(inputRef));
22+
expect(focusMock).toHaveBeenCalledTimes(1);
5823

59-
// Simulate a mousedown event on an input element
6024
act(() => {
61-
document.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, clientX: ElType.input }));
25+
useChatStore.setState({ activeId: '1', activeTopicId: '3' });
6226
});
6327

64-
// Simulate a mouseup event
65-
act(() => {
66-
document.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
67-
});
28+
renderHook(() => useAutoFocus(inputRef as any));
6829

69-
expect(inputRef.current?.focus).not.toHaveBeenCalled();
30+
// I don't know why its 3, but is large than 2 is fine
31+
expect(focusMock).toHaveBeenCalledTimes(3);
7032
});
7133

72-
it('should not focus inputRef when mouseup event happens inside of markdown element', () => {
73-
const inputRef = { current: { focus: vi.fn() } } as RefObject<any>;
74-
renderHook(() => useAutoFocus(inputRef));
34+
it('should not focus the input if inputRef is not available', () => {
35+
const inputRef = { current: null };
7536

76-
// Simulate a mousedown event on a markdown element
7737
act(() => {
78-
document.dispatchEvent(
79-
new MouseEvent('mousedown', { bubbles: true, clientX: ElType.markdown }),
80-
);
38+
useChatStore.setState({ activeId: '1', activeTopicId: '2' });
8139
});
8240

83-
// Simulate a mouseup event
84-
act(() => {
85-
document.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
86-
});
87-
88-
expect(inputRef.current?.focus).not.toHaveBeenCalled();
89-
});
90-
91-
it('should not focus inputRef when mouseup event happens inside of debug element', () => {
92-
const inputRef = { current: { focus: vi.fn() } } as RefObject<any>;
93-
renderHook(() => useAutoFocus(inputRef));
94-
95-
// Simulate a mousedown event on a debug element
96-
act(() => {
97-
document.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, clientX: ElType.debug }));
98-
});
99-
100-
// Simulate a mouseup event
101-
act(() => {
102-
document.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
103-
});
41+
renderHook(() => useAutoFocus(inputRef as any));
10442

105-
expect(inputRef.current?.focus).not.toHaveBeenCalled();
43+
expect(inputRef.current).toBeNull();
10644
});
10745
});
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,13 @@
11
import { TextAreaRef } from 'antd/es/input/TextArea';
22
import { RefObject, useEffect } from 'react';
33

4-
export const useAutoFocus = (inputRef: RefObject<TextAreaRef>) => {
5-
useEffect(() => {
6-
let isInputOrMarkdown = false;
7-
8-
const onMousedown = (e: MouseEvent) => {
9-
isInputOrMarkdown = false;
10-
const element = document.elementFromPoint(e.clientX, e.clientY);
11-
if (!element) return;
12-
let currentElement: Element | null = element;
13-
// 因为点击 Markdown 元素时,element 会是 article 标签的子元素
14-
// Debug 信息时,element 会是 pre 标签
15-
// 所以向上查找全局点击对象是否是 Markdown 或者 Input 或者 Debug 元素
16-
while (currentElement && !isInputOrMarkdown) {
17-
isInputOrMarkdown = ['TEXTAREA', 'INPUT', 'ARTICLE', 'PRE'].includes(
18-
currentElement.tagName,
19-
);
20-
currentElement = currentElement.parentElement;
21-
}
22-
};
4+
import { useChatStore } from '@/store/chat';
5+
import { chatSelectors } from '@/store/chat/selectors';
236

24-
const onMouseup = () => {
25-
// 因为有时候要复制 Markdown 里生成的内容,或者点击别的 Input
26-
// 所以全局点击元素不是 Markdown 或者 Input 元素的话就聚焦
27-
if (!isInputOrMarkdown) {
28-
inputRef.current?.focus();
29-
}
30-
};
7+
export const useAutoFocus = (inputRef: RefObject<TextAreaRef>) => {
8+
const chatKey = useChatStore(chatSelectors.currentChatKey);
319

32-
document.addEventListener('mousedown', onMousedown);
33-
document.addEventListener('mouseup', onMouseup);
34-
return () => {
35-
document.removeEventListener('mousedown', onMousedown);
36-
document.removeEventListener('mouseup', onMouseup);
37-
};
38-
}, []);
10+
useEffect(() => {
11+
inputRef.current?.focus();
12+
}, [chatKey]);
3913
};

src/app/(main)/chat/@session/features/SessionListContent/DefaultMode.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { useGlobalStore } from '@/store/global';
77
import { systemStatusSelectors } from '@/store/global/selectors';
88
import { useSessionStore } from '@/store/session';
99
import { sessionSelectors } from '@/store/session/selectors';
10+
import { useUserStore } from '@/store/user';
11+
import { authSelectors } from '@/store/user/selectors';
1012
import { SessionDefaultGroup } from '@/types/session';
1113

1214
import Actions from '../SessionListContent/CollapseGroup/Actions';
@@ -23,8 +25,9 @@ const DefaultMode = memo(() => {
2325
const [renameGroupModalOpen, setRenameGroupModalOpen] = useState(false);
2426
const [configGroupModalOpen, setConfigGroupModalOpen] = useState(false);
2527

28+
const isLogin = useUserStore(authSelectors.isLogin);
2629
const [useFetchSessions] = useSessionStore((s) => [s.useFetchSessions]);
27-
useFetchSessions();
30+
useFetchSessions(isLogin);
2831

2932
const defaultSessions = useSessionStore(sessionSelectors.defaultSessions, isEqual);
3033
const customSessionGroups = useSessionStore(sessionSelectors.customSessionGroups, isEqual);

src/store/session/slices/session/action.ts

+27-23
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export interface SessionAction {
7272

7373
updateSearchKeywords: (keywords: string) => void;
7474

75-
useFetchSessions: () => SWRResponse<ChatSessionList>;
75+
useFetchSessions: (isLogin: boolean | undefined) => SWRResponse<ChatSessionList>;
7676
useSearchSessions: (keyword?: string) => SWRResponse<any>;
7777

7878
internal_dispatchSessions: (payload: SessionDispatch) => void;
@@ -192,29 +192,33 @@ export const createSessionSlice: StateCreator<
192192
await refreshSessions();
193193
},
194194

195-
useFetchSessions: () =>
196-
useClientDataSWR<ChatSessionList>(FETCH_SESSIONS_KEY, sessionService.getGroupedSessions, {
197-
fallbackData: {
198-
sessionGroups: [],
199-
sessions: [],
200-
},
201-
onSuccess: (data) => {
202-
if (
203-
get().isSessionsFirstFetchFinished &&
204-
isEqual(get().sessions, data.sessions) &&
205-
isEqual(get().sessionGroups, data.sessionGroups)
206-
)
207-
return;
208-
209-
get().internal_processSessions(
210-
data.sessions,
211-
data.sessionGroups,
212-
n('useFetchSessions/updateData') as any,
213-
);
214-
set({ isSessionsFirstFetchFinished: true }, false, n('useFetchSessions/onSuccess', data));
195+
useFetchSessions: (isLogin) =>
196+
useClientDataSWR<ChatSessionList>(
197+
[FETCH_SESSIONS_KEY, isLogin],
198+
() => sessionService.getGroupedSessions(),
199+
{
200+
fallbackData: {
201+
sessionGroups: [],
202+
sessions: [],
203+
},
204+
onSuccess: (data) => {
205+
if (
206+
get().isSessionsFirstFetchFinished &&
207+
isEqual(get().sessions, data.sessions) &&
208+
isEqual(get().sessionGroups, data.sessionGroups)
209+
)
210+
return;
211+
212+
get().internal_processSessions(
213+
data.sessions,
214+
data.sessionGroups,
215+
n('useFetchSessions/updateData') as any,
216+
);
217+
set({ isSessionsFirstFetchFinished: true }, false, n('useFetchSessions/onSuccess', data));
218+
},
219+
suspense: true,
215220
},
216-
suspense: true,
217-
}),
221+
),
218222
useSearchSessions: (keyword) =>
219223
useSWR<LobeSessions>(
220224
[SEARCH_SESSIONS_KEY, keyword],

0 commit comments

Comments
 (0)