@webinex/chatify 2.0.0-alpha13 → 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 (143) 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/ui/Chat/ChatContext.d.ts +2 -0
  9. package/dist/types/ui/Chat/ChatPanel.d.ts +7 -0
  10. package/dist/types/ui/Chat/ChatView.d.ts +4 -2
  11. package/dist/types/ui/ChatGroup/ChatGroupContext.d.ts +4 -1
  12. package/dist/types/ui/ChatGroup/ChatGroupPanel.d.ts +21 -2
  13. package/dist/types/ui/Conversation/Conversation.d.ts +1 -0
  14. package/dist/types/ui/Conversation/ConversationContext.d.ts +1 -0
  15. package/dist/types/ui/Conversation/ConversationHeader.d.ts +4 -1
  16. package/dist/types/ui/localizer.d.ts +4 -1
  17. package/dist/types/ui/useCompact.d.ts +4 -0
  18. package/package.json +81 -81
  19. package/src/audit/AuditChatList.tsx +25 -25
  20. package/src/audit/AuditConversationPanel.tsx +49 -48
  21. package/src/audit/AuditPanel.tsx +76 -76
  22. package/src/audit/AuditPanelCustomizeValue.ts +11 -11
  23. package/src/audit/AuditSearch.tsx +89 -89
  24. package/src/audit/AuditSearchValue.tsx +42 -42
  25. package/src/audit/auditApi.tsx +113 -113
  26. package/src/audit/index.d.ts +18 -18
  27. package/src/audit/index.tsx +28 -28
  28. package/src/audit/styles.scss +72 -72
  29. package/src/core/api/api.ts +675 -675
  30. package/src/core/api/baseApi.ts +38 -38
  31. package/src/core/api/index.ts +1 -1
  32. package/src/core/client.ts +298 -298
  33. package/src/core/constants.ts +1 -1
  34. package/src/core/debounce.ts +90 -90
  35. package/src/core/index.ts +4 -4
  36. package/src/core/types.tsx +182 -182
  37. package/src/index.ts +2 -2
  38. package/src/styles/aside.scss +34 -26
  39. package/src/styles/chat-list.scss +138 -138
  40. package/src/styles/chat.scss +11 -11
  41. package/src/styles/conversation.scss +199 -200
  42. package/src/styles/index.scss +227 -220
  43. package/src/styles/input.scss +148 -148
  44. package/src/styles/keyframes.scss +14 -14
  45. package/src/styles/members.scss +30 -13
  46. package/src/styles/mixins.scss +38 -25
  47. package/src/styles/variables.scss +13 -13
  48. package/src/ui/Chat/ChatContext.tsx +32 -30
  49. package/src/ui/Chat/ChatEditableHeaderName.tsx +63 -63
  50. package/src/ui/Chat/ChatHeaderActions.tsx +19 -19
  51. package/src/ui/Chat/ChatPanel.tsx +46 -30
  52. package/src/ui/Chat/ChatView.tsx +122 -115
  53. package/src/ui/Chat/Members/ChatMembersButton.tsx +9 -9
  54. package/src/ui/Chat/Members/ChatMembersPanel.tsx +128 -112
  55. package/src/ui/Chat/Members/ChatMembersPreview.tsx +25 -25
  56. package/src/ui/Chat/Members/index.ts +13 -13
  57. package/src/ui/Chat/index.ts +6 -6
  58. package/src/ui/ChatGroup/AutoReply/AutoReplyButton.tsx +21 -21
  59. package/src/ui/ChatGroup/AutoReply/AutoReplyForm.tsx +50 -50
  60. package/src/ui/ChatGroup/AutoReply/AutoReplyPanel.tsx +94 -94
  61. package/src/ui/ChatGroup/AutoReply/AutoReplyPeriodInput.tsx +22 -22
  62. package/src/ui/ChatGroup/AutoReply/AutoReplyTextInput.tsx +22 -22
  63. package/src/ui/ChatGroup/AutoReply/index.ts +19 -19
  64. package/src/ui/ChatGroup/ChatGroupContext.tsx +63 -61
  65. package/src/ui/ChatGroup/ChatGroupPanel.tsx +110 -75
  66. package/src/ui/ChatGroup/Create/CreateChatButton.tsx +15 -15
  67. package/src/ui/ChatGroup/Create/CreateChatForm.tsx +36 -36
  68. package/src/ui/ChatGroup/Create/CreateChatMemberInput.tsx +65 -65
  69. package/src/ui/ChatGroup/Create/CreateChatNameInput.tsx +25 -25
  70. package/src/ui/ChatGroup/Create/CreateChatPanel.tsx +45 -45
  71. package/src/ui/ChatGroup/Create/index.ts +19 -19
  72. package/src/ui/ChatGroup/Layout/ChatGroupActions.tsx +15 -15
  73. package/src/ui/ChatGroup/Layout/ChatGroupAside.tsx +12 -12
  74. package/src/ui/ChatGroup/Layout/ChatGroupBody.tsx +17 -10
  75. package/src/ui/ChatGroup/Layout/ChatGroupFooter.tsx +3 -3
  76. package/src/ui/ChatGroup/Layout/ChatGroupHeader.tsx +5 -5
  77. package/src/ui/ChatGroup/Layout/ChatGroupMain.tsx +21 -19
  78. package/src/ui/ChatGroup/Layout/index.ts +22 -22
  79. package/src/ui/ChatGroup/List/ChatListPanel.tsx +28 -28
  80. package/src/ui/ChatGroup/List/index.ts +8 -8
  81. package/src/ui/ChatGroup/index.ts +6 -6
  82. package/src/ui/Chatify.tsx +15 -15
  83. package/src/ui/Conversation/Conversation.tsx +96 -92
  84. package/src/ui/Conversation/ConversationActions.tsx +12 -12
  85. package/src/ui/Conversation/ConversationBody.tsx +28 -28
  86. package/src/ui/Conversation/ConversationContext.ts +31 -30
  87. package/src/ui/Conversation/ConversationHeader.tsx +35 -19
  88. package/src/ui/Conversation/ConversationName.tsx +7 -7
  89. package/src/ui/Conversation/InputBox/InputBox.tsx +126 -126
  90. package/src/ui/Conversation/InputBox/InputBoxFile.tsx +38 -38
  91. package/src/ui/Conversation/InputBox/InputBoxFileList.tsx +21 -21
  92. package/src/ui/Conversation/InputBox/InputFilesBox.tsx +78 -78
  93. package/src/ui/Conversation/InputBox/InputSubmitButtonBox.tsx +26 -26
  94. package/src/ui/Conversation/InputBox/InputTextBox.tsx +63 -61
  95. package/src/ui/Conversation/InputBox/index.ts +23 -23
  96. package/src/ui/Conversation/Message/MessageAuthor.tsx +13 -13
  97. package/src/ui/Conversation/Message/MessageBox.tsx +20 -20
  98. package/src/ui/Conversation/Message/MessageContent.tsx +15 -15
  99. package/src/ui/Conversation/Message/MessageFile.tsx +54 -54
  100. package/src/ui/Conversation/Message/MessageFileList.tsx +14 -14
  101. package/src/ui/Conversation/Message/MessageInfoBox.tsx +28 -28
  102. package/src/ui/Conversation/Message/MessageRow.tsx +28 -28
  103. package/src/ui/Conversation/Message/MessageText.tsx +7 -7
  104. package/src/ui/Conversation/Message/index.ts +28 -28
  105. package/src/ui/Conversation/NextObserver/NextMessagesObserver.tsx +47 -47
  106. package/src/ui/Conversation/NextObserver/index.ts +1 -1
  107. package/src/ui/Conversation/ReadObserver/MessageReadObserver.tsx +49 -49
  108. package/src/ui/Conversation/ReadObserver/index.ts +1 -1
  109. package/src/ui/Conversation/SendingMessage/SendingMessageBox.tsx +16 -16
  110. package/src/ui/Conversation/SendingMessage/SendingMessageContent.tsx +13 -13
  111. package/src/ui/Conversation/SendingMessage/SendingMessageInfoBox.tsx +13 -13
  112. package/src/ui/Conversation/SendingMessage/SendingMessageText.tsx +7 -7
  113. package/src/ui/Conversation/SendingMessage/index.ts +16 -16
  114. package/src/ui/Conversation/SystemMessage/SystemMessageBox.tsx +20 -20
  115. package/src/ui/Conversation/SystemMessage/SystemMessageRow.tsx +28 -28
  116. package/src/ui/Conversation/SystemMessage/index.ts +10 -10
  117. package/src/ui/Conversation/index.ts +13 -13
  118. package/src/ui/Thread/ThreadActions.tsx +22 -22
  119. package/src/ui/Thread/ThreadContext.ts +13 -13
  120. package/src/ui/Thread/ThreadPanel.tsx +128 -127
  121. package/src/ui/Thread/index.tsx +3 -3
  122. package/src/ui/color.ts +20 -20
  123. package/src/ui/common/Avatar.tsx +20 -20
  124. package/src/ui/common/ChatList/ChatList.tsx +83 -84
  125. package/src/ui/common/ChatList/ChatListItemAvatar.tsx +22 -22
  126. package/src/ui/common/ChatList/ChatListItemBox.tsx +32 -32
  127. package/src/ui/common/ChatList/ChatListItemLastMessage.tsx +15 -15
  128. package/src/ui/common/ChatList/ChatListItemLastMessageAuthor.tsx +15 -15
  129. package/src/ui/common/ChatList/ChatListItemLastMessageContent.tsx +21 -21
  130. package/src/ui/common/ChatList/ChatListItemLastMessageSentAt.tsx +22 -22
  131. package/src/ui/common/ChatList/ChatListItemName.tsx +8 -8
  132. package/src/ui/common/ChatList/ChatListItemUnreadCount.tsx +12 -12
  133. package/src/ui/common/ChatList/ChatListValue.ts +9 -9
  134. package/src/ui/common/ChatList/index.ts +32 -32
  135. package/src/ui/common/Icon.tsx +48 -48
  136. package/src/ui/common/MessageSkeleton.tsx +20 -20
  137. package/src/ui/common/index.ts +14 -14
  138. package/src/ui/customize.tsx +51 -51
  139. package/src/ui/index.ts +11 -11
  140. package/src/ui/localizer.tsx +148 -137
  141. package/src/ui/useCompact.tsx +32 -0
  142. package/src/ui/useFormatter.tsx +17 -17
  143. 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
+ });