@webinex/chatify 1.0.1-rc3 → 1.0.3-rc3

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.
Files changed (48) hide show
  1. package/dist/css/chatify.css +249 -143
  2. package/dist/esm/index.js +1 -1
  3. package/dist/esm/index.js.map +1 -1
  4. package/dist/esm/types/core/action.d.ts +11 -4
  5. package/dist/esm/types/core/state.d.ts +2 -1
  6. package/dist/esm/types/core/useAddChatMutation.d.ts +1 -1
  7. package/dist/esm/types/core/useGetAccountsQuery.d.ts +3 -1
  8. package/dist/esm/types/core/useGetChatListQuery.d.ts +3 -1
  9. package/dist/esm/types/core/useGetChatQuery.d.ts +3 -1
  10. package/dist/esm/types/ui/ChatList/ChatListItemLastMessageSentAt.d.ts +3 -0
  11. package/dist/esm/types/ui/ChatList/index.d.ts +1 -0
  12. package/dist/esm/types/ui/Chatify.d.ts +2 -1
  13. package/dist/esm/types/ui/Icon.d.ts +9 -8
  14. package/dist/esm/types/ui/localizer.d.ts +2 -0
  15. package/dist/index.d.ts +29 -15
  16. package/package.json +1 -1
  17. package/src/core/action.tsx +33 -7
  18. package/src/core/state.ts +3 -2
  19. package/src/core/useAddChatMutation.tsx +9 -3
  20. package/src/core/useGetAccountsQuery.tsx +11 -14
  21. package/src/core/useGetChatListQuery.tsx +10 -13
  22. package/src/core/useGetChatQuery.tsx +11 -14
  23. package/src/core/useGetMessagesQuery.tsx +16 -16
  24. package/src/styles/aside.scss +59 -21
  25. package/src/styles/chat.scss +81 -92
  26. package/src/styles/index.scss +34 -5
  27. package/src/styles/input.scss +72 -0
  28. package/src/styles/variables.scss +4 -4
  29. package/src/ui/AddChatButton.tsx +5 -3
  30. package/src/ui/ChatList/ChatListItemLastMessage.tsx +2 -0
  31. package/src/ui/ChatList/ChatListItemLastMessageSentAt.tsx +22 -0
  32. package/src/ui/ChatList/index.ts +1 -0
  33. package/src/ui/ChatPanel/ChatHeader.tsx +3 -5
  34. package/src/ui/ChatPanel/ChatHeaderMembers.tsx +6 -4
  35. package/src/ui/ChatPanel/ChatHeaderName.tsx +17 -15
  36. package/src/ui/ChatPanel/ChatMembersButton.tsx +1 -1
  37. package/src/ui/ChatPanel/ChatMembersPanel.tsx +8 -4
  38. package/src/ui/Chatify.tsx +11 -5
  39. package/src/ui/CreateChatPanel/CreateChatPanel.tsx +20 -4
  40. package/src/ui/Icon.tsx +7 -5
  41. package/src/ui/InputBox/InputTextBox.tsx +2 -1
  42. package/src/ui/Layout/Aside.tsx +2 -0
  43. package/src/ui/Layout/Header.tsx +1 -6
  44. package/src/ui/Message/MessageContent.tsx +0 -2
  45. package/src/ui/Message/MessageInfoBox.tsx +1 -0
  46. package/src/ui/Message/MessageRow.tsx +5 -1
  47. package/src/ui/localizer.tsx +4 -0
  48. package/src/styles/settings.scss +0 -5
@@ -6,11 +6,12 @@ export const ACTIONS = {
6
6
  mutation(state);
7
7
  },
8
8
 
9
- messages_fetch: (state: State, { chatId }: { chatId: string }) => {
9
+ messages_fetch: (state: State, { chatId, requestId }: { chatId: string; requestId: string }) => {
10
10
  state.query.messages[chatId] = {
11
11
  ...state.query.messages[chatId],
12
12
  uninitialized: false,
13
13
  isFetching: true,
14
+ requestId,
14
15
  };
15
16
  },
16
17
 
@@ -23,6 +24,7 @@ export const ACTIONS = {
23
24
  qState.error = undefined;
24
25
  qState.isFetching = false;
25
26
  qState.data = messages.map((x) => ({ type: 'message', ...x }));
27
+ qState.requestId = undefined;
26
28
 
27
29
  if (hasMore) {
28
30
  qState.data!.push({ type: 'next' });
@@ -33,6 +35,7 @@ export const ACTIONS = {
33
35
  const qState = state.query.messages[chatId];
34
36
  qState.error = error;
35
37
  qState.isFetching = false;
38
+ qState.requestId = undefined;
36
39
  },
37
40
 
38
41
  fetchNext_fetch: (state: State, { chatId }: { chatId: string }) => {
@@ -54,9 +57,10 @@ export const ACTIONS = {
54
57
  state.mutation.fetchNext = { isFetching: false, error };
55
58
  },
56
59
 
57
- chatList_fetch: (state: State) => {
60
+ chatList_fetch: (state: State, { requestId }: { requestId: string }) => {
58
61
  state.query.chatList.isFetching = true;
59
62
  state.query.chatList.uninitialized = false;
63
+ state.query.chatList.requestId = requestId;
60
64
  },
61
65
 
62
66
  chatList_fulfilled: (state: State, { chats }: { chats: ChatListItem[] }) => {
@@ -65,6 +69,7 @@ export const ACTIONS = {
65
69
  state.query.chatList.isFetching = false;
66
70
  state.query.chatList.error = undefined;
67
71
  state.query.chatList.data = chats;
72
+ state.query.chatList.requestId = undefined;
68
73
 
69
74
  if (state.ui.chatId === undefined && chats.length > 0) {
70
75
  state.ui.view = 'chat';
@@ -75,6 +80,7 @@ export const ACTIONS = {
75
80
  chatList_rejected: (state: State, { error }: { error: any }) => {
76
81
  state.query.chatList.isFetching = false;
77
82
  state.query.chatList.error = error;
83
+ state.query.chatList.requestId = undefined;
78
84
  },
79
85
 
80
86
  removeMember_fetch: (state: State) => {
@@ -125,19 +131,21 @@ export const ACTIONS = {
125
131
  state.mutation.addChat = { isFetching: false, error };
126
132
  },
127
133
 
128
- accounts_fetch: (state: State) => {
129
- state.query.accounts = { isFetching: true, uninitialized: false };
134
+ accounts_fetch: (state: State, { requestId }: { requestId: string }) => {
135
+ state.query.accounts = { isFetching: true, uninitialized: false, requestId };
130
136
  },
131
137
 
132
138
  accounts_fulfilled: (state: State, { accounts }: { accounts: Account[] }) => {
133
139
  state.query.accounts.data = accounts;
134
140
  state.query.accounts.isFetching = false;
135
141
  state.query.accounts.error = undefined;
142
+ state.query.accounts.requestId = undefined;
136
143
  },
137
144
 
138
145
  accounts_rejected: (state: State, { error }: { error: any }) => {
139
146
  state.query.accounts.isFetching = false;
140
147
  state.query.accounts.error = error;
148
+ state.query.accounts.requestId = undefined;
141
149
  },
142
150
 
143
151
  chat_open: (state: State, { chatId }: { chatId: string }) => {
@@ -146,10 +154,20 @@ export const ACTIONS = {
146
154
  },
147
155
 
148
156
  new_chat_open: (state: State) => {
149
- state.ui.chatId = undefined;
150
157
  state.ui.view = 'new-chat';
151
158
  },
152
159
 
160
+ new_chat_close: (state: State) => {
161
+ if (state.ui.chatId) {
162
+ state.ui.view = 'chat';
163
+ } else if (state.query.chatList.data?.length) {
164
+ state.ui.view = 'chat';
165
+ state.ui.chatId = state.query.chatList.data[0]?.id;
166
+ } else {
167
+ state.ui.view = undefined;
168
+ }
169
+ },
170
+
153
171
  toggle_members_view: (state: State) => {
154
172
  state.ui.showMembers = !state.ui.showMembers;
155
173
  },
@@ -327,8 +345,14 @@ export const ACTIONS = {
327
345
  }
328
346
  },
329
347
 
330
- chat_fetch: (state: State, { chatId }: { chatId: string }) => {
331
- state.query.chats[chatId] = { isFetching: true, uninitialized: false, data: undefined, error: undefined };
348
+ chat_fetch: (state: State, { chatId, requestId }: { chatId: string; requestId: string }) => {
349
+ state.query.chats[chatId] = {
350
+ isFetching: true,
351
+ uninitialized: false,
352
+ data: undefined,
353
+ error: undefined,
354
+ requestId,
355
+ };
332
356
  },
333
357
 
334
358
  chat_fulfilled: (state: State, { chat }: { chat: Chat }) => {
@@ -336,12 +360,14 @@ export const ACTIONS = {
336
360
  chatState.isFetching = false;
337
361
  chatState.error = undefined;
338
362
  chatState.data = chat;
363
+ chatState.requestId = undefined;
339
364
  },
340
365
 
341
366
  chat_rejected: (state: State, { chatId, error }: { chatId: string; error: any }) => {
342
367
  const chatState = state.query.chats[chatId];
343
368
  chatState.isFetching = false;
344
369
  chatState.error = error;
370
+ chatState.requestId = undefined;
345
371
  },
346
372
 
347
373
  send_fetch: (
package/src/core/state.ts CHANGED
@@ -16,6 +16,7 @@ export interface QueryState<TData> {
16
16
  isFetching: boolean;
17
17
  error?: any;
18
18
  data?: TData;
19
+ requestId?: string;
19
20
  }
20
21
 
21
22
  export interface MutationState {
@@ -51,12 +52,12 @@ export interface State {
51
52
  };
52
53
  }
53
54
 
54
- export const INITIAL_QUERY_STATE: QueryState<any> = {
55
+ export const FETCH_QUERY_STATE: QueryState<any> = Object.freeze({
55
56
  uninitialized: false,
56
57
  isFetching: true,
57
58
  data: undefined,
58
59
  error: undefined,
59
- };
60
+ });
60
61
 
61
62
  export const INITIAL_STATE: State = {
62
63
  ui: {
@@ -10,10 +10,16 @@ export function useAddChatMutation() {
10
10
 
11
11
  const invoke = useCallback((args: AddChatRequest) => {
12
12
  dispatch({ type: 'addChat_fetch', data: void 0 });
13
- client
13
+ return client
14
14
  .addChat(args)
15
- .then(() => dispatch({ type: 'addChat_fulfilled', data: void 0 }))
16
- .catch((error) => dispatch({ type: 'addChat_rejected', data: { error } }));
15
+ .then((id) => {
16
+ dispatch({ type: 'addChat_fulfilled', data: void 0 });
17
+ return id;
18
+ })
19
+ .catch((error) => {
20
+ dispatch({ type: 'addChat_rejected', data: { error } });
21
+ throw error;
22
+ });
17
23
 
18
24
  // eslint-disable-next-line react-hooks/exhaustive-deps
19
25
  }, []);
@@ -1,33 +1,30 @@
1
- import { useEffect, useMemo } from 'react';
1
+ import { useEffect, useRef } from 'react';
2
2
  import { useClient } from '../ChatifyContext';
3
3
  import { useDispatch, useSelect, useStateRef } from './reducer';
4
+ import { FETCH_QUERY_STATE, QueryState } from './state';
5
+ import { uniqId } from '../util';
6
+ import { Account } from './models';
4
7
 
5
- export function useGetAccountsQuery() {
8
+ export function useGetAccountsQuery(): QueryState<Account[]> {
9
+ const id = useRef(uniqId());
6
10
  const client = useClient();
7
11
  const stateRef = useStateRef();
8
- useSelect((x) => x.query.accounts, []);
12
+ const queryState = useSelect((x) => x.query.accounts, []) ?? FETCH_QUERY_STATE;
9
13
  const dispatch = useDispatch();
10
14
 
11
- const uninitialized = useMemo(() => {
12
- const uninitialized = stateRef.current.query.accounts.uninitialized !== false;
13
- if (uninitialized) dispatch({ type: 'accounts_fetch', data: undefined });
14
- return uninitialized;
15
-
16
- // eslint-disable-next-line react-hooks/exhaustive-deps
17
- }, []);
18
-
19
15
  useEffect(() => {
20
- if (!uninitialized) {
16
+ if (stateRef.current.query.accounts.uninitialized === false) {
21
17
  return;
22
18
  }
23
19
 
20
+ dispatch({ type: 'accounts_fetch', data: { requestId: id.current } });
24
21
  client
25
22
  .accounts()
26
23
  .then((accounts) => dispatch({ type: 'accounts_fulfilled', data: { accounts } }))
27
24
  .catch((error) => dispatch({ type: 'accounts_rejected', data: { error } }));
28
25
 
29
26
  // eslint-disable-next-line react-hooks/exhaustive-deps
30
- }, [uninitialized]);
27
+ }, []);
31
28
 
32
- return stateRef.current.query.accounts;
29
+ return queryState ?? FETCH_QUERY_STATE;
33
30
  }
@@ -1,26 +1,23 @@
1
- import { useEffect, useMemo } from 'react';
1
+ import { useEffect, useRef } from 'react';
2
2
  import { useClient } from '../ChatifyContext';
3
3
  import { useDispatch, useSelect, useStateRef } from './reducer';
4
+ import { uniqId } from '../util';
5
+ import { FETCH_QUERY_STATE, QueryState } from './state';
6
+ import { ChatListItem } from './models';
4
7
 
5
- export function useGetChatListQuery() {
8
+ export function useGetChatListQuery(): QueryState<ChatListItem[]> {
9
+ const id = useRef(uniqId());
6
10
  const stateRef = useStateRef();
7
11
  const client = useClient();
8
- useSelect((x) => x.query.chatList, []);
12
+ const queryState = useSelect((x) => x.query.chatList, []) ?? FETCH_QUERY_STATE;
9
13
  const dispatch = useDispatch();
10
14
 
11
- const uninitialized = useMemo(() => {
12
- const uninitialized = stateRef.current.query.chatList.uninitialized !== false;
13
- if (uninitialized) dispatch({ type: 'chatList_fetch', data: undefined });
14
- return uninitialized;
15
-
16
- // eslint-disable-next-line react-hooks/exhaustive-deps
17
- }, []);
18
-
19
15
  useEffect(() => {
20
- if (!uninitialized) {
16
+ if (stateRef.current.query.chatList.uninitialized === false) {
21
17
  return;
22
18
  }
23
19
 
20
+ dispatch({ type: 'chatList_fetch', data: { requestId: id.current } });
24
21
  client
25
22
  .chats()
26
23
  .then((chats) => dispatch({ type: 'chatList_fulfilled', data: { chats } }))
@@ -29,5 +26,5 @@ export function useGetChatListQuery() {
29
26
  // eslint-disable-next-line react-hooks/exhaustive-deps
30
27
  }, []);
31
28
 
32
- return stateRef.current.query.chatList;
29
+ return queryState ?? FETCH_QUERY_STATE;
33
30
  }
@@ -1,34 +1,31 @@
1
- import { useEffect, useMemo } from 'react';
1
+ import { useEffect, useRef } from 'react';
2
2
  import { useClient } from '../ChatifyContext';
3
3
  import { useDispatch, useSelect, useStateRef } from './reducer';
4
+ import { uniqId } from '../util';
5
+ import { FETCH_QUERY_STATE, QueryState } from './state';
6
+ import { Chat } from './models';
4
7
 
5
- export function useGetChatQuery(args: { chatId: string }) {
8
+ export function useGetChatQuery(args: { chatId: string }): QueryState<Chat> {
9
+ const id = useRef(uniqId());
6
10
  const { chatId } = args;
7
11
  const client = useClient();
8
12
  const stateRef = useStateRef();
9
- useSelect((x) => x.query.chats[chatId], [chatId]);
13
+ const queryState = useSelect((x) => x.query.chats[chatId], [chatId]) ?? FETCH_QUERY_STATE;
10
14
  const dispatch = useDispatch();
11
15
 
12
- const uninitialized = useMemo(() => {
13
- const uninitialized = stateRef.current.query.chats[chatId]?.uninitialized !== false;
14
- if (uninitialized) dispatch({ type: 'chat_fetch', data: { chatId } });
15
- return uninitialized;
16
-
17
- // eslint-disable-next-line react-hooks/exhaustive-deps
18
- }, [chatId]);
19
-
20
16
  useEffect(() => {
21
- if (!uninitialized) {
17
+ if (stateRef.current.query.chats[chatId]?.uninitialized === false) {
22
18
  return;
23
19
  }
24
20
 
21
+ dispatch({ type: 'chat_fetch', data: { chatId, requestId: id.current } });
25
22
  client
26
23
  .chat(chatId)
27
24
  .then((chat) => dispatch({ type: 'chat_fulfilled', data: { chat } }))
28
25
  .catch((error) => dispatch({ type: 'chat_rejected', data: { chatId, error } }));
29
26
 
30
27
  // eslint-disable-next-line react-hooks/exhaustive-deps
31
- }, [uninitialized, chatId]);
28
+ }, [chatId]);
32
29
 
33
- return stateRef.current.query.chats[chatId];
30
+ return queryState ?? FETCH_QUERY_STATE;
34
31
  }
@@ -1,35 +1,35 @@
1
- import { useEffect, useMemo } from 'react';
1
+ import { useEffect, useRef } from 'react';
2
2
  import { useClient } from '../ChatifyContext';
3
- import { MessageState, QueryState } from './state';
3
+ import { FETCH_QUERY_STATE, MessageState, QueryState } from './state';
4
4
  import { useDispatch, useSelect, useStateRef } from './reducer';
5
+ import { uniqId } from '../util';
5
6
 
6
7
  export function useGetMessagesQuery(args: { chatId: string }): QueryState<MessageState[]> {
7
8
  const { chatId } = args;
9
+ const id = useRef(uniqId());
8
10
  const stateRef = useStateRef();
9
11
  const dispatch = useDispatch();
10
12
  useSelect((x) => x.query.messages[chatId], [chatId]);
11
13
  const client = useClient();
12
14
 
13
- const uninitialized = useMemo(() => {
14
- const uninitialized = stateRef.current.query.messages[chatId]?.uninitialized !== false;
15
- if (uninitialized) dispatch({ type: 'messages_fetch', data: { chatId } });
16
- return uninitialized;
17
- }, [chatId, dispatch, stateRef]);
18
-
19
15
  useEffect(() => {
20
- if (!uninitialized) {
16
+ if (stateRef.current.query.messages[chatId]?.uninitialized === false) {
21
17
  return;
22
18
  }
23
19
 
24
- client.messages({ chatId, skip: 0, take: 16 }).then((messages) =>
25
- dispatch({
26
- type: 'messages_fulfilled',
27
- data: { chatId, messages: messages.slice(0, 15), hasMore: messages.length === 16 },
28
- }),
29
- );
20
+ dispatch({ type: 'messages_fetch', data: { requestId: id.current, chatId } });
21
+ client
22
+ .messages({ chatId, skip: 0, take: 16 })
23
+ .then((messages) =>
24
+ dispatch({
25
+ type: 'messages_fulfilled',
26
+ data: { chatId, messages: messages.slice(0, 15), hasMore: messages.length === 16 },
27
+ }),
28
+ )
29
+ .catch((error) => dispatch({ type: 'messages_rejected', data: { chatId, error } }));
30
30
 
31
31
  // eslint-disable-next-line react-hooks/exhaustive-deps
32
32
  }, [chatId]);
33
33
 
34
- return stateRef.current.query.messages[chatId];
34
+ return stateRef.current.query.messages[chatId] ?? FETCH_QUERY_STATE;
35
35
  }
@@ -1,67 +1,98 @@
1
1
  .wxchtf-aside {
2
2
  flex: none;
3
- width: 300px;
4
- border-right: $border;
5
3
  display: flex;
6
4
  flex-flow: column nowrap;
5
+ background: #ffffff;
6
+ border-top-left-radius: $radius;
7
+ border-bottom-left-radius: $radius;
8
+ padding-top: 15px;
9
+ width: 320px;
10
+ position: relative;
11
+ overflow: hidden;
12
+
13
+ &::before {
14
+ position: absolute;
15
+ content: '';
16
+ left: 0;
17
+ bottom: 0;
18
+ width: calc(100% - 10px);
19
+ height: 20px;
20
+ z-index: 1;
21
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, #ffffff 100%);
22
+ }
7
23
 
8
24
  .wxchtf-chat-list.ant-menu {
9
25
  flex: 1 1 auto;
10
26
  height: 100px;
11
27
  overflow-y: auto;
28
+ border-inline-end: none;
29
+ padding-bottom: 2rem;
12
30
 
13
31
  @include scrollbar();
14
32
 
33
+ .ant-menu-item + .ant-menu-item {
34
+ margin-top: 8px;
35
+ }
36
+
15
37
  .ant-menu-item {
16
38
  padding: 0;
17
39
  height: unset;
18
40
  margin: 0;
19
- width: 100%;
20
- border-radius: 0;
41
+ margin-left: 8px;
42
+ width: calc(100% - 16px);
43
+ border-radius: 8px;
21
44
  line-height: 1.5;
22
- border-top: $border;
23
45
 
24
- &:first-child {
25
- border-top: none;
26
- }
46
+ &.ant-menu-item-selected {
47
+ background-color: #e4f7f7;
27
48
 
28
- &.ant-menu-item-selected .wxchtf-chat-name {
29
- font-weight: 600;
49
+ .wxchtf-chat-name {
50
+ font-weight: 600;
51
+ }
30
52
  }
31
53
 
32
54
  .wxchtf-chat {
33
- padding: 0.5rem 1rem;
55
+ padding-inline: 10px;
56
+ padding-block: 8.5px;
34
57
  position: relative;
35
58
 
36
59
  &.--unread {
37
60
  .wxchtf-chat-last {
38
61
  font-weight: bold;
39
62
  }
63
+
64
+ .wxchtf-chat-name {
65
+ font-weight: bold;
66
+ max-width: 100%;
67
+ }
40
68
  }
41
69
 
42
70
  .wxchtf-chat-name {
43
- font-weight: bold;
44
71
  display: inline-block;
72
+ font-weight: 500;
45
73
  }
46
74
 
47
75
  .wxchtf-unread-count {
48
- display: inline-block;
49
- padding: 0.2rem;
50
- min-width: 1rem;
51
- height: 1.2rem;
52
- line-height: 0.7rem;
53
- margin-left: 0.5rem;
76
+ background-color: #ff4d4f;
54
77
  border-radius: 50%;
78
+ width: 16px;
79
+ height: 16px;
80
+ padding: 0;
81
+ font-size: 10px;
82
+ position: absolute;
83
+ top: 32px;
84
+ right: 13px;
85
+ color: white;
86
+ line-height: 16px;
87
+ font-weight: bold;
55
88
  text-align: center;
56
- font-size: 0.7rem;
57
- background: $unreadCountBackground;
58
89
  }
59
90
 
60
91
  .wxchtf-chat-last {
61
92
  $size: 25px;
93
+ color: #0e0f1799;
62
94
 
63
95
  font-style: italic;
64
- opacity: 0.75;
65
96
  overflow: hidden;
66
97
  text-overflow: ellipsis;
67
98
  white-space: nowrap;
@@ -87,6 +118,13 @@
87
118
  .wxchtf-chat-last-message {
88
119
  display: inline;
89
120
  }
121
+
122
+ .wxchtf-chat-last-sent-at {
123
+ position: absolute;
124
+ top: 7px;
125
+ right: 13px;
126
+ font-size: 0.8em;
127
+ }
90
128
  }
91
129
  }
92
130
  }