@webinex/chatify 2.0.0-alpha12 → 2.0.0-alpha14

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 (144) hide show
  1. package/dist/{Chatify-CwAfN1wu.js → Chatify-CP2jgo8d.js} +191 -118
  2. package/dist/Chatify-CP2jgo8d.js.map +1 -0
  3. package/dist/Chatify.css +28 -0
  4. package/dist/audit.js +3 -2
  5. package/dist/audit.js.map +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/types/audit/index.d.ts +17 -15
  8. package/dist/types/core/api/api.d.ts +146 -0
  9. package/dist/types/ui/Chat/ChatContext.d.ts +2 -0
  10. package/dist/types/ui/Chat/ChatPanel.d.ts +7 -0
  11. package/dist/types/ui/Chat/ChatView.d.ts +4 -2
  12. package/dist/types/ui/ChatGroup/ChatGroupContext.d.ts +4 -1
  13. package/dist/types/ui/ChatGroup/ChatGroupPanel.d.ts +21 -2
  14. package/dist/types/ui/Conversation/Conversation.d.ts +1 -0
  15. package/dist/types/ui/Conversation/ConversationContext.d.ts +1 -0
  16. package/dist/types/ui/Conversation/ConversationHeader.d.ts +4 -1
  17. package/dist/types/ui/localizer.d.ts +4 -1
  18. package/dist/types/ui/useCompact.d.ts +4 -0
  19. package/package.json +81 -81
  20. package/src/audit/AuditChatList.tsx +25 -25
  21. package/src/audit/AuditConversationPanel.tsx +49 -48
  22. package/src/audit/AuditPanel.tsx +76 -76
  23. package/src/audit/AuditPanelCustomizeValue.ts +11 -11
  24. package/src/audit/AuditSearch.tsx +89 -89
  25. package/src/audit/AuditSearchValue.tsx +42 -42
  26. package/src/audit/auditApi.tsx +113 -113
  27. package/src/audit/index.d.ts +18 -18
  28. package/src/audit/index.tsx +28 -28
  29. package/src/audit/styles.scss +72 -72
  30. package/src/core/api/api.ts +675 -669
  31. package/src/core/api/baseApi.ts +38 -38
  32. package/src/core/api/index.ts +1 -1
  33. package/src/core/client.ts +298 -298
  34. package/src/core/constants.ts +1 -1
  35. package/src/core/debounce.ts +90 -90
  36. package/src/core/index.ts +4 -4
  37. package/src/core/types.tsx +182 -182
  38. package/src/index.ts +2 -2
  39. package/src/styles/aside.scss +34 -26
  40. package/src/styles/chat-list.scss +138 -138
  41. package/src/styles/chat.scss +11 -11
  42. package/src/styles/conversation.scss +199 -200
  43. package/src/styles/index.scss +227 -220
  44. package/src/styles/input.scss +148 -148
  45. package/src/styles/keyframes.scss +14 -14
  46. package/src/styles/members.scss +30 -13
  47. package/src/styles/mixins.scss +38 -25
  48. package/src/styles/variables.scss +13 -13
  49. package/src/ui/Chat/ChatContext.tsx +32 -30
  50. package/src/ui/Chat/ChatEditableHeaderName.tsx +63 -63
  51. package/src/ui/Chat/ChatHeaderActions.tsx +19 -19
  52. package/src/ui/Chat/ChatPanel.tsx +46 -30
  53. package/src/ui/Chat/ChatView.tsx +122 -115
  54. package/src/ui/Chat/Members/ChatMembersButton.tsx +9 -9
  55. package/src/ui/Chat/Members/ChatMembersPanel.tsx +128 -112
  56. package/src/ui/Chat/Members/ChatMembersPreview.tsx +25 -25
  57. package/src/ui/Chat/Members/index.ts +13 -13
  58. package/src/ui/Chat/index.ts +6 -6
  59. package/src/ui/ChatGroup/AutoReply/AutoReplyButton.tsx +21 -21
  60. package/src/ui/ChatGroup/AutoReply/AutoReplyForm.tsx +50 -50
  61. package/src/ui/ChatGroup/AutoReply/AutoReplyPanel.tsx +94 -94
  62. package/src/ui/ChatGroup/AutoReply/AutoReplyPeriodInput.tsx +22 -22
  63. package/src/ui/ChatGroup/AutoReply/AutoReplyTextInput.tsx +22 -22
  64. package/src/ui/ChatGroup/AutoReply/index.ts +19 -19
  65. package/src/ui/ChatGroup/ChatGroupContext.tsx +63 -61
  66. package/src/ui/ChatGroup/ChatGroupPanel.tsx +110 -75
  67. package/src/ui/ChatGroup/Create/CreateChatButton.tsx +15 -15
  68. package/src/ui/ChatGroup/Create/CreateChatForm.tsx +36 -36
  69. package/src/ui/ChatGroup/Create/CreateChatMemberInput.tsx +65 -65
  70. package/src/ui/ChatGroup/Create/CreateChatNameInput.tsx +25 -25
  71. package/src/ui/ChatGroup/Create/CreateChatPanel.tsx +45 -45
  72. package/src/ui/ChatGroup/Create/index.ts +19 -19
  73. package/src/ui/ChatGroup/Layout/ChatGroupActions.tsx +15 -15
  74. package/src/ui/ChatGroup/Layout/ChatGroupAside.tsx +12 -12
  75. package/src/ui/ChatGroup/Layout/ChatGroupBody.tsx +17 -10
  76. package/src/ui/ChatGroup/Layout/ChatGroupFooter.tsx +3 -3
  77. package/src/ui/ChatGroup/Layout/ChatGroupHeader.tsx +5 -5
  78. package/src/ui/ChatGroup/Layout/ChatGroupMain.tsx +21 -19
  79. package/src/ui/ChatGroup/Layout/index.ts +22 -22
  80. package/src/ui/ChatGroup/List/ChatListPanel.tsx +28 -28
  81. package/src/ui/ChatGroup/List/index.ts +8 -8
  82. package/src/ui/ChatGroup/index.ts +6 -6
  83. package/src/ui/Chatify.tsx +15 -15
  84. package/src/ui/Conversation/Conversation.tsx +96 -92
  85. package/src/ui/Conversation/ConversationActions.tsx +12 -12
  86. package/src/ui/Conversation/ConversationBody.tsx +28 -28
  87. package/src/ui/Conversation/ConversationContext.ts +31 -30
  88. package/src/ui/Conversation/ConversationHeader.tsx +35 -19
  89. package/src/ui/Conversation/ConversationName.tsx +7 -7
  90. package/src/ui/Conversation/InputBox/InputBox.tsx +126 -126
  91. package/src/ui/Conversation/InputBox/InputBoxFile.tsx +38 -38
  92. package/src/ui/Conversation/InputBox/InputBoxFileList.tsx +21 -21
  93. package/src/ui/Conversation/InputBox/InputFilesBox.tsx +78 -78
  94. package/src/ui/Conversation/InputBox/InputSubmitButtonBox.tsx +26 -26
  95. package/src/ui/Conversation/InputBox/InputTextBox.tsx +63 -61
  96. package/src/ui/Conversation/InputBox/index.ts +23 -23
  97. package/src/ui/Conversation/Message/MessageAuthor.tsx +13 -13
  98. package/src/ui/Conversation/Message/MessageBox.tsx +20 -20
  99. package/src/ui/Conversation/Message/MessageContent.tsx +15 -15
  100. package/src/ui/Conversation/Message/MessageFile.tsx +54 -54
  101. package/src/ui/Conversation/Message/MessageFileList.tsx +14 -14
  102. package/src/ui/Conversation/Message/MessageInfoBox.tsx +28 -28
  103. package/src/ui/Conversation/Message/MessageRow.tsx +28 -28
  104. package/src/ui/Conversation/Message/MessageText.tsx +7 -7
  105. package/src/ui/Conversation/Message/index.ts +28 -28
  106. package/src/ui/Conversation/NextObserver/NextMessagesObserver.tsx +47 -47
  107. package/src/ui/Conversation/NextObserver/index.ts +1 -1
  108. package/src/ui/Conversation/ReadObserver/MessageReadObserver.tsx +49 -49
  109. package/src/ui/Conversation/ReadObserver/index.ts +1 -1
  110. package/src/ui/Conversation/SendingMessage/SendingMessageBox.tsx +16 -16
  111. package/src/ui/Conversation/SendingMessage/SendingMessageContent.tsx +13 -13
  112. package/src/ui/Conversation/SendingMessage/SendingMessageInfoBox.tsx +13 -13
  113. package/src/ui/Conversation/SendingMessage/SendingMessageText.tsx +7 -7
  114. package/src/ui/Conversation/SendingMessage/index.ts +16 -16
  115. package/src/ui/Conversation/SystemMessage/SystemMessageBox.tsx +20 -20
  116. package/src/ui/Conversation/SystemMessage/SystemMessageRow.tsx +28 -28
  117. package/src/ui/Conversation/SystemMessage/index.ts +10 -10
  118. package/src/ui/Conversation/index.ts +13 -13
  119. package/src/ui/Thread/ThreadActions.tsx +22 -22
  120. package/src/ui/Thread/ThreadContext.ts +13 -13
  121. package/src/ui/Thread/ThreadPanel.tsx +128 -127
  122. package/src/ui/Thread/index.tsx +3 -3
  123. package/src/ui/color.ts +20 -20
  124. package/src/ui/common/Avatar.tsx +20 -20
  125. package/src/ui/common/ChatList/ChatList.tsx +83 -84
  126. package/src/ui/common/ChatList/ChatListItemAvatar.tsx +22 -22
  127. package/src/ui/common/ChatList/ChatListItemBox.tsx +32 -32
  128. package/src/ui/common/ChatList/ChatListItemLastMessage.tsx +15 -15
  129. package/src/ui/common/ChatList/ChatListItemLastMessageAuthor.tsx +15 -15
  130. package/src/ui/common/ChatList/ChatListItemLastMessageContent.tsx +21 -21
  131. package/src/ui/common/ChatList/ChatListItemLastMessageSentAt.tsx +22 -22
  132. package/src/ui/common/ChatList/ChatListItemName.tsx +8 -8
  133. package/src/ui/common/ChatList/ChatListItemUnreadCount.tsx +12 -12
  134. package/src/ui/common/ChatList/ChatListValue.ts +9 -9
  135. package/src/ui/common/ChatList/index.ts +32 -32
  136. package/src/ui/common/Icon.tsx +48 -48
  137. package/src/ui/common/MessageSkeleton.tsx +20 -20
  138. package/src/ui/common/index.ts +14 -14
  139. package/src/ui/customize.tsx +51 -51
  140. package/src/ui/index.ts +11 -11
  141. package/src/ui/localizer.tsx +148 -137
  142. package/src/ui/useCompact.tsx +32 -0
  143. package/src/ui/useFormatter.tsx +17 -17
  144. package/dist/Chatify-CwAfN1wu.js.map +0 -1
@@ -1,12 +1,12 @@
1
- import { Button } from 'antd';
2
- import { customize } from '../customize';
3
- import { useConversation } from './ConversationContext';
4
- import { Icon } from '../common';
5
-
6
- export const ConversationActions = customize('ConversationActions', () => {
7
- const { onClose } = useConversation();
8
-
9
- if (!onClose) return null;
10
-
11
- return <Button onClick={onClose} type="link" icon={<Icon type="close" />} />;
12
- });
1
+ import { Button } from 'antd';
2
+ import { customize } from '../customize';
3
+ import { useConversation } from './ConversationContext';
4
+ import { Icon } from '../common';
5
+
6
+ export const ConversationActions = customize('ConversationActions', () => {
7
+ const { onClose } = useConversation();
8
+
9
+ if (!onClose) return null;
10
+
11
+ return <Button onClick={onClose} type="link" icon={<Icon type="close" />} />;
12
+ });
@@ -1,28 +1,28 @@
1
- import { MessageBox } from './Message';
2
- import { NextMessagesObserver } from './NextObserver';
3
- import { SystemMessageBox } from './SystemMessage';
4
- import { MessageSkeleton } from '../common/MessageSkeleton';
5
- import { customize } from '../customize';
6
- import { SYSTEM_ID } from '../../core';
7
- import { useConversation } from './ConversationContext';
8
-
9
- // TODO: Add sending message box
10
- export const ConversationBody = customize('ConversationBody', () => {
11
- const { id, messages = [], hasMore, onNext, nextLoading } = useConversation();
12
-
13
- return (
14
- <div className="wxchtf-conversation-body">
15
- {!messages && <MessageSkeleton count={5} />}
16
-
17
- {messages?.map((x) => {
18
- if (x.sentBy.id === SYSTEM_ID) {
19
- return <SystemMessageBox key={x.id} message={x} />;
20
- }
21
-
22
- return <MessageBox message={x} key={x.id} />;
23
- })}
24
-
25
- {hasMore && <NextMessagesObserver key={messages.length + id} onLoad={onNext} loading={nextLoading} />}
26
- </div>
27
- );
28
- });
1
+ import { MessageBox } from './Message';
2
+ import { NextMessagesObserver } from './NextObserver';
3
+ import { SystemMessageBox } from './SystemMessage';
4
+ import { MessageSkeleton } from '../common/MessageSkeleton';
5
+ import { customize } from '../customize';
6
+ import { SYSTEM_ID } from '../../core';
7
+ import { useConversation } from './ConversationContext';
8
+
9
+ // TODO: Add sending message box
10
+ export const ConversationBody = customize('ConversationBody', () => {
11
+ const { id, messages = [], hasMore, onNext, nextLoading } = useConversation();
12
+
13
+ return (
14
+ <div className="wxchtf-conversation-body">
15
+ {!messages && <MessageSkeleton count={5} />}
16
+
17
+ {messages?.map((x) => {
18
+ if (x.sentBy.id === SYSTEM_ID) {
19
+ return <SystemMessageBox key={x.id} message={x} />;
20
+ }
21
+
22
+ return <MessageBox message={x} key={x.id} />;
23
+ })}
24
+
25
+ {hasMore && <NextMessagesObserver key={messages.length + id} onLoad={onNext} loading={nextLoading} />}
26
+ </div>
27
+ );
28
+ });
@@ -1,30 +1,31 @@
1
- import React from 'react';
2
- import { MessageBase, MessageBody } from '../../core';
3
- import type { Flippo } from '@webinex/flippo';
4
-
5
- export interface ConversationValue {
6
- id: string;
7
- name: string;
8
- active: boolean;
9
- lastReadMessageId: string | null;
10
- }
11
-
12
- export interface ConversationContextValue extends ConversationValue {
13
- messages?: MessageBase[];
14
- hasMore?: boolean;
15
- nextLoading: boolean;
16
- noReadTracking?: boolean;
17
-
18
- isReading?: (id: string) => boolean;
19
- onNext: () => void;
20
- onSend?: (message: MessageBody) => void;
21
- onRead?: (id: string) => void;
22
- flippo?: Flippo;
23
- onClose?: () => void;
24
- }
25
-
26
- export const ConversationContext = React.createContext<ConversationContextValue>(undefined!);
27
-
28
- export function useConversation() {
29
- return React.useContext(ConversationContext);
30
- }
1
+ import React from 'react';
2
+ import { MessageBase, MessageBody } from '../../core';
3
+ import type { Flippo } from '@webinex/flippo';
4
+
5
+ export interface ConversationValue {
6
+ id: string;
7
+ name: string;
8
+ active: boolean;
9
+ lastReadMessageId: string | null;
10
+ }
11
+
12
+ export interface ConversationContextValue extends ConversationValue {
13
+ messages?: MessageBase[];
14
+ hasMore?: boolean;
15
+ nextLoading: boolean;
16
+ noReadTracking?: boolean;
17
+ compact: boolean;
18
+
19
+ isReading?: (id: string) => boolean;
20
+ onNext: () => void;
21
+ onSend?: (message: MessageBody) => void;
22
+ onRead?: (id: string) => void;
23
+ flippo?: Flippo;
24
+ onClose?: () => void;
25
+ }
26
+
27
+ export const ConversationContext = React.createContext<ConversationContextValue>(undefined!);
28
+
29
+ export function useConversation() {
30
+ return React.useContext(ConversationContext);
31
+ }
@@ -1,19 +1,35 @@
1
- import { Col, Row } from 'antd';
2
- import { customize } from '../customize';
3
- import { ConversationName } from './ConversationName';
4
- import { ConversationActions } from './ConversationActions';
5
-
6
- export const ConversationHeader = customize('ConversationHeader', () => {
7
- return (
8
- <div className="wxchtf-conversation-header">
9
- <Row justify="space-between" align="middle">
10
- <Col>
11
- <ConversationName />
12
- </Col>
13
- <Col className="wxchtf-conversation-actions">
14
- <ConversationActions />
15
- </Col>
16
- </Row>
17
- </div>
18
- );
19
- });
1
+ import { Button, Col, Row } from 'antd';
2
+ import { customize } from '../customize';
3
+ import { ConversationName } from './ConversationName';
4
+ import { ConversationActions } from './ConversationActions';
5
+ import { LeftOutlined } from '@ant-design/icons';
6
+
7
+ export interface ConversationHeaderProps {
8
+ onBackClick?: () => void;
9
+ }
10
+
11
+ export const ConversationHeader = customize('ConversationHeader', (props: ConversationHeaderProps) => {
12
+ const { onBackClick } = props;
13
+
14
+ return (
15
+ <div className="wxchtf-conversation-header">
16
+ <Row gutter={10}>
17
+ {onBackClick && (
18
+ <Col flex="none">
19
+ <Button type="text" icon={<LeftOutlined />} onClick={onBackClick} />
20
+ </Col>
21
+ )}
22
+ <Col flex="auto">
23
+ <Row justify="space-between" align="middle">
24
+ <Col>
25
+ <ConversationName />
26
+ </Col>
27
+ <Col className="wxchtf-conversation-actions">
28
+ <ConversationActions />
29
+ </Col>
30
+ </Row>
31
+ </Col>
32
+ </Row>
33
+ </div>
34
+ );
35
+ });
@@ -1,7 +1,7 @@
1
- import { customize } from '../customize';
2
- import { useConversation } from './ConversationContext';
3
-
4
- export const ConversationName = customize('ConversationName', () => {
5
- const { name } = useConversation();
6
- return <span className="wxchtf-conversation-name">{name}</span>;
7
- });
1
+ import { customize } from '../customize';
2
+ import { useConversation } from './ConversationContext';
3
+
4
+ export const ConversationName = customize('ConversationName', () => {
5
+ const { name } = useConversation();
6
+ return <span className="wxchtf-conversation-name">{name}</span>;
7
+ });
@@ -1,126 +1,126 @@
1
- import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
2
- import { InputFilesBox } from './InputFilesBox';
3
- import { File } from '../../../core';
4
- import { InputTextBox } from './InputTextBox';
5
- import { customize, useCustomizeContext } from '../../customize';
6
- import { InputSubmitButtonBox } from './InputSubmitButtonBox';
7
- import { useConversation } from '../ConversationContext';
8
- import { ConversationCustomizeValue } from '../Conversation';
9
- import { InputBoxFileList } from './InputBoxFileList';
10
-
11
- function useFormState() {
12
- const { onSend } = useConversation();
13
- const [text, setText] = useState('');
14
- const [sending, setSending] = useState(false);
15
- const { onUpload, files, loading, setFiles, showFileBox, onDeleteFile } = useFlippoInputFileBox();
16
-
17
- const onSubmit = useCallback(() => {
18
- if (!onSend) {
19
- return;
20
- }
21
-
22
- const textValue = text.trim().length === 0 ? null : text.trim();
23
-
24
- if ((!textValue && files.length === 0) || loading) {
25
- return;
26
- }
27
-
28
- setSending(true);
29
- Promise.resolve(onSend({ text: textValue, files }))
30
- .finally(() => setSending(false))
31
- .then(() => {
32
- setFiles([]);
33
- setText('');
34
- });
35
- // eslint-disable-next-line react-hooks/exhaustive-deps
36
- }, [text, onSend, files, setFiles, loading]);
37
-
38
- return [onSubmit, { text, setText, files, onUpload, loading, showFileBox, onDeleteFile, sending }] as const;
39
- }
40
-
41
- function useFlippoInputFileBox() {
42
- const [loading, setLoading] = useState(false);
43
- const [files, setFiles] = useState<File[]>([]);
44
- const { flippo } = useCustomizeContext<ConversationCustomizeValue>();
45
-
46
- const onUpload = useCallback(
47
- (files: globalThis.File[]) => {
48
- if (!flippo) return;
49
-
50
- setLoading(true);
51
- Promise.all(files.map((file) => flippo.store(file)))
52
- .then((references) =>
53
- setFiles((prev) => [
54
- ...prev,
55
- ...references.map((reference, index) => ({
56
- name: files[index].name,
57
- ref: reference,
58
- bytes: files[index].size,
59
- })),
60
- ]),
61
- )
62
- .finally(() => setLoading(false));
63
- },
64
- [flippo],
65
- );
66
-
67
- const onDeleteFile = useCallback((file: File) => {
68
- setFiles((prev) => prev.filter((f) => f !== file));
69
- }, []);
70
-
71
- return { onUpload, files, loading, setFiles, showFileBox: !!flippo, onDeleteFile };
72
- }
73
-
74
- export const InputBox = customize('InputBox', () => {
75
- const [onSend, { text, setText, files, loading, onUpload, showFileBox, onDeleteFile, sending }] =
76
- useFormState();
77
- const inputRef = useRef<HTMLTextAreaElement>(null);
78
- const [showFileList, setShowFileList] = useState(false);
79
- const toggleShowFileList = useCallback(() => setShowFileList((prev) => !prev), []);
80
- const prevFilesLength = useRef(files.length);
81
-
82
- useLayoutEffect(() => inputRef.current?.focus(), []);
83
-
84
- useEffect(() => {
85
- if (prevFilesLength.current === 0 && files.length > 0 && !showFileList) {
86
- setShowFileList(true);
87
- }
88
-
89
- prevFilesLength.current = files.length;
90
-
91
- if (files.length === 0 && showFileList) {
92
- setShowFileList(false);
93
- }
94
- }, [files, showFileList]);
95
-
96
- return (
97
- <div className="wxchtf-input-form">
98
- <div className="wxchtf-input-box">
99
- {showFileList && (
100
- <div className="wxchtf-file-list-box">
101
- <InputBoxFileList files={files} onDelete={onDeleteFile} disabled={sending} />
102
- </div>
103
- )}
104
- <div className="wxchtf-input-box-row">
105
- {showFileBox && (
106
- <InputFilesBox
107
- onAddedClick={toggleShowFileList}
108
- onUpload={onUpload}
109
- value={files}
110
- loading={loading}
111
- disabled={sending}
112
- />
113
- )}
114
- <InputTextBox
115
- value={text}
116
- onSend={onSend}
117
- onChange={setText}
118
- disabled={sending}
119
- inputRef={inputRef}
120
- />
121
- <InputSubmitButtonBox onSubmit={onSend} disabled={loading} loading={sending} />
122
- </div>
123
- </div>
124
- </div>
125
- );
126
- });
1
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
2
+ import { InputFilesBox } from './InputFilesBox';
3
+ import { File } from '../../../core';
4
+ import { InputTextBox } from './InputTextBox';
5
+ import { customize, useCustomizeContext } from '../../customize';
6
+ import { InputSubmitButtonBox } from './InputSubmitButtonBox';
7
+ import { useConversation } from '../ConversationContext';
8
+ import { ConversationCustomizeValue } from '../Conversation';
9
+ import { InputBoxFileList } from './InputBoxFileList';
10
+
11
+ function useFormState() {
12
+ const { onSend } = useConversation();
13
+ const [text, setText] = useState('');
14
+ const [sending, setSending] = useState(false);
15
+ const { onUpload, files, loading, setFiles, showFileBox, onDeleteFile } = useFlippoInputFileBox();
16
+
17
+ const onSubmit = useCallback(() => {
18
+ if (!onSend) {
19
+ return;
20
+ }
21
+
22
+ const textValue = text.trim().length === 0 ? null : text.trim();
23
+
24
+ if ((!textValue && files.length === 0) || loading) {
25
+ return;
26
+ }
27
+
28
+ setSending(true);
29
+ Promise.resolve(onSend({ text: textValue, files }))
30
+ .finally(() => setSending(false))
31
+ .then(() => {
32
+ setFiles([]);
33
+ setText('');
34
+ });
35
+ // eslint-disable-next-line react-hooks/exhaustive-deps
36
+ }, [text, onSend, files, setFiles, loading]);
37
+
38
+ return [onSubmit, { text, setText, files, onUpload, loading, showFileBox, onDeleteFile, sending }] as const;
39
+ }
40
+
41
+ function useFlippoInputFileBox() {
42
+ const [loading, setLoading] = useState(false);
43
+ const [files, setFiles] = useState<File[]>([]);
44
+ const { flippo } = useCustomizeContext<ConversationCustomizeValue>();
45
+
46
+ const onUpload = useCallback(
47
+ (files: globalThis.File[]) => {
48
+ if (!flippo) return;
49
+
50
+ setLoading(true);
51
+ Promise.all(files.map((file) => flippo.store(file)))
52
+ .then((references) =>
53
+ setFiles((prev) => [
54
+ ...prev,
55
+ ...references.map((reference, index) => ({
56
+ name: files[index].name,
57
+ ref: reference,
58
+ bytes: files[index].size,
59
+ })),
60
+ ]),
61
+ )
62
+ .finally(() => setLoading(false));
63
+ },
64
+ [flippo],
65
+ );
66
+
67
+ const onDeleteFile = useCallback((file: File) => {
68
+ setFiles((prev) => prev.filter((f) => f !== file));
69
+ }, []);
70
+
71
+ return { onUpload, files, loading, setFiles, showFileBox: !!flippo, onDeleteFile };
72
+ }
73
+
74
+ export const InputBox = customize('InputBox', () => {
75
+ const [onSend, { text, setText, files, loading, onUpload, showFileBox, onDeleteFile, sending }] =
76
+ useFormState();
77
+ const inputRef = useRef<HTMLTextAreaElement>(null);
78
+ const [showFileList, setShowFileList] = useState(false);
79
+ const toggleShowFileList = useCallback(() => setShowFileList((prev) => !prev), []);
80
+ const prevFilesLength = useRef(files.length);
81
+
82
+ useLayoutEffect(() => inputRef.current?.focus(), []);
83
+
84
+ useEffect(() => {
85
+ if (prevFilesLength.current === 0 && files.length > 0 && !showFileList) {
86
+ setShowFileList(true);
87
+ }
88
+
89
+ prevFilesLength.current = files.length;
90
+
91
+ if (files.length === 0 && showFileList) {
92
+ setShowFileList(false);
93
+ }
94
+ }, [files, showFileList]);
95
+
96
+ return (
97
+ <div className="wxchtf-input-form">
98
+ <div className="wxchtf-input-box">
99
+ {showFileList && (
100
+ <div className="wxchtf-file-list-box">
101
+ <InputBoxFileList files={files} onDelete={onDeleteFile} disabled={sending} />
102
+ </div>
103
+ )}
104
+ <div className="wxchtf-input-box-row">
105
+ {showFileBox && (
106
+ <InputFilesBox
107
+ onAddedClick={toggleShowFileList}
108
+ onUpload={onUpload}
109
+ value={files}
110
+ loading={loading}
111
+ disabled={sending}
112
+ />
113
+ )}
114
+ <InputTextBox
115
+ value={text}
116
+ onSend={onSend}
117
+ onChange={setText}
118
+ disabled={sending}
119
+ inputRef={inputRef}
120
+ />
121
+ <InputSubmitButtonBox onSubmit={onSend} disabled={loading} loading={sending} />
122
+ </div>
123
+ </div>
124
+ </div>
125
+ );
126
+ });
@@ -1,38 +1,38 @@
1
- import { Button } from 'antd';
2
- import { File } from '../../../core';
3
- import { Icon } from '../../common';
4
- import { useCallback } from 'react';
5
- import { useFormatter } from '../../useFormatter';
6
- import { customize } from '../../customize';
7
-
8
- export interface InputBoxFileProps {
9
- file: File;
10
- disabled?: boolean;
11
- onDelete: (file: File) => void;
12
- }
13
-
14
- export const InputBoxFile = customize('InputBoxFile', (props: InputBoxFileProps) => {
15
- const { file, disabled, onDelete } = props;
16
- const formatter = useFormatter();
17
- const onDeleteClick = useCallback(() => onDelete(file), [file, onDelete]);
18
-
19
- return (
20
- <div className="wxchtf-file">
21
- <div className="wxchtf-file-info">
22
- <div className="wxchtf-file-name" title={file.name}>
23
- {file.name}
24
- </div>
25
- <div className="wxchtf-file-size">{formatter.size(file.bytes)}</div>
26
- </div>
27
- <div className="wxchtf-file-actions">
28
- <Button
29
- disabled={disabled}
30
- className="wxchtf-delete"
31
- type="link"
32
- icon={<Icon type="delete-file" />}
33
- onClick={onDeleteClick}
34
- />
35
- </div>
36
- </div>
37
- );
38
- });
1
+ import { Button } from 'antd';
2
+ import { File } from '../../../core';
3
+ import { Icon } from '../../common';
4
+ import { useCallback } from 'react';
5
+ import { useFormatter } from '../../useFormatter';
6
+ import { customize } from '../../customize';
7
+
8
+ export interface InputBoxFileProps {
9
+ file: File;
10
+ disabled?: boolean;
11
+ onDelete: (file: File) => void;
12
+ }
13
+
14
+ export const InputBoxFile = customize('InputBoxFile', (props: InputBoxFileProps) => {
15
+ const { file, disabled, onDelete } = props;
16
+ const formatter = useFormatter();
17
+ const onDeleteClick = useCallback(() => onDelete(file), [file, onDelete]);
18
+
19
+ return (
20
+ <div className="wxchtf-file">
21
+ <div className="wxchtf-file-info">
22
+ <div className="wxchtf-file-name" title={file.name}>
23
+ {file.name}
24
+ </div>
25
+ <div className="wxchtf-file-size">{formatter.size(file.bytes)}</div>
26
+ </div>
27
+ <div className="wxchtf-file-actions">
28
+ <Button
29
+ disabled={disabled}
30
+ className="wxchtf-delete"
31
+ type="link"
32
+ icon={<Icon type="delete-file" />}
33
+ onClick={onDeleteClick}
34
+ />
35
+ </div>
36
+ </div>
37
+ );
38
+ });
@@ -1,21 +1,21 @@
1
- import { File } from '../../../core';
2
- import { customize } from '../../customize';
3
- import { InputBoxFile } from './InputBoxFile';
4
-
5
- export interface InputBoxFileListProps {
6
- files: File[];
7
- disabled?: boolean;
8
- onDelete: (file: File) => void;
9
- }
10
-
11
- export const InputBoxFileList = customize('InputBoxFileList', (props: InputBoxFileListProps) => {
12
- const { files, onDelete, disabled } = props;
13
-
14
- return (
15
- <div className="wxchtf-file-list">
16
- {files.map((file) => (
17
- <InputBoxFile key={file.ref} file={file} onDelete={onDelete} disabled={disabled} />
18
- ))}
19
- </div>
20
- );
21
- });
1
+ import { File } from '../../../core';
2
+ import { customize } from '../../customize';
3
+ import { InputBoxFile } from './InputBoxFile';
4
+
5
+ export interface InputBoxFileListProps {
6
+ files: File[];
7
+ disabled?: boolean;
8
+ onDelete: (file: File) => void;
9
+ }
10
+
11
+ export const InputBoxFileList = customize('InputBoxFileList', (props: InputBoxFileListProps) => {
12
+ const { files, onDelete, disabled } = props;
13
+
14
+ return (
15
+ <div className="wxchtf-file-list">
16
+ {files.map((file) => (
17
+ <InputBoxFile key={file.ref} file={file} onDelete={onDelete} disabled={disabled} />
18
+ ))}
19
+ </div>
20
+ );
21
+ });