@messenger-box/tailwind-ui-inbox 10.0.3-alpha.71 → 10.0.3-alpha.72
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/CHANGELOG.md +4 -0
- package/lib/components/AIAgent/AIAgent.d.ts +14 -0
- package/lib/components/AIAgent/AIAgent.d.ts.map +1 -0
- package/lib/components/AIAgent/AIAgent.js +1148 -0
- package/lib/components/AIAgent/AIAgent.js.map +1 -0
- package/lib/components/AIAgent/index.d.ts +2 -0
- package/lib/components/AIAgent/index.d.ts.map +1 -0
- package/lib/components/InboxMessage/InputComponent.d.ts +9 -0
- package/lib/components/InboxMessage/InputComponent.d.ts.map +1 -0
- package/lib/components/InboxMessage/InputComponent.js +210 -0
- package/lib/components/InboxMessage/InputComponent.js.map +1 -0
- package/lib/components/InboxMessage/MessageInput.d.ts.map +1 -1
- package/lib/components/InboxMessage/MessageInput.js +14 -10
- package/lib/components/InboxMessage/MessageInput.js.map +1 -1
- package/lib/components/InboxMessage/MessageInputComponent.d.ts +9 -0
- package/lib/components/InboxMessage/MessageInputComponent.d.ts.map +1 -0
- package/lib/components/InboxMessage/MessageInputComponent.js +173 -0
- package/lib/components/InboxMessage/MessageInputComponent.js.map +1 -0
- package/lib/components/InboxMessage/Messages.d.ts.map +1 -1
- package/lib/components/InboxMessage/Messages.js +4 -54
- package/lib/components/InboxMessage/Messages.js.map +1 -1
- package/lib/components/InboxMessage/MessagesBuilderUi.d.ts +17 -0
- package/lib/components/InboxMessage/MessagesBuilderUi.d.ts.map +1 -0
- package/lib/components/InboxMessage/MessagesBuilderUi.js +162 -0
- package/lib/components/InboxMessage/MessagesBuilderUi.js.map +1 -0
- package/lib/components/InboxMessage/UploadImageButton.d.ts +1 -0
- package/lib/components/InboxMessage/UploadImageButton.d.ts.map +1 -1
- package/lib/components/InboxMessage/UploadImageButton.js +3 -3
- package/lib/components/InboxMessage/UploadImageButton.js.map +1 -1
- package/lib/components/InboxMessage/index.d.ts +3 -0
- package/lib/components/InboxMessage/index.d.ts.map +1 -1
- package/lib/components/InboxMessage/message-widgets/CommonMessage.d.ts.map +1 -1
- package/lib/components/InboxMessage/message-widgets/CommonMessage.js +11 -6
- package/lib/components/InboxMessage/message-widgets/CommonMessage.js.map +1 -1
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts +14 -0
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts.map +1 -0
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js +1525 -0
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js.map +1 -0
- package/lib/components/InboxMessage/message-widgets/PlainMessage.d.ts.map +1 -1
- package/lib/components/InboxMessage/message-widgets/PlainMessage.js +6 -3
- package/lib/components/InboxMessage/message-widgets/PlainMessage.js.map +1 -1
- package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.d.ts.map +1 -1
- package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.js +207 -12
- package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.js.map +1 -1
- package/lib/components/InboxMessage/message-widgets/index.d.ts +1 -0
- package/lib/components/InboxMessage/message-widgets/index.d.ts.map +1 -1
- package/lib/components/index.d.ts +2 -1
- package/lib/components/index.d.ts.map +1 -1
- package/lib/compute.d.ts.map +1 -1
- package/lib/compute.js +79 -3
- package/lib/compute.js.map +1 -1
- package/lib/config/env-config.d.ts +6 -0
- package/lib/config/env-config.d.ts.map +1 -1
- package/lib/config/env-config.js +19 -1
- package/lib/config/env-config.js.map +1 -1
- package/lib/container/AiInbox.d.ts +15 -0
- package/lib/container/AiInbox.d.ts.map +1 -0
- package/lib/container/AiInbox.js +1520 -0
- package/lib/container/AiInbox.js.map +1 -0
- package/lib/container/AiInboxWithLoader.d.ts +36 -0
- package/lib/container/AiInboxWithLoader.d.ts.map +1 -0
- package/lib/container/AiInboxWithLoader.js +300 -0
- package/lib/container/AiInboxWithLoader.js.map +1 -0
- package/lib/container/AiLandingInput.d.ts +4 -0
- package/lib/container/AiLandingInput.d.ts.map +1 -0
- package/lib/container/AiLandingInput.js +164 -0
- package/lib/container/AiLandingInput.js.map +1 -0
- package/lib/container/Inbox.d.ts.map +1 -1
- package/lib/container/Inbox.js +6 -4
- package/lib/container/Inbox.js.map +1 -1
- package/lib/container/InboxAiMessagesLoader.d.ts +36 -0
- package/lib/container/InboxAiMessagesLoader.d.ts.map +1 -0
- package/lib/container/InboxAiMessagesLoader.js +47 -0
- package/lib/container/InboxAiMessagesLoader.js.map +1 -0
- package/lib/container/InboxContainer.d.ts +12 -0
- package/lib/container/InboxContainer.d.ts.map +1 -0
- package/lib/container/InboxContainer.js +31 -0
- package/lib/container/InboxContainer.js.map +1 -0
- package/lib/container/InboxTemplate1.d.ts +15 -0
- package/lib/container/InboxTemplate1.d.ts.map +1 -0
- package/lib/container/InboxTemplate1.js +1375 -0
- package/lib/container/InboxTemplate1.js.map +1 -0
- package/lib/container/InboxTemplate1WithLoader.d.ts +36 -0
- package/lib/container/InboxTemplate1WithLoader.d.ts.map +1 -0
- package/lib/container/InboxTemplate2.d.ts +15 -0
- package/lib/container/InboxTemplate2.d.ts.map +1 -0
- package/lib/container/InboxTemplate2.js +1426 -0
- package/lib/container/InboxTemplate2.js.map +1 -0
- package/lib/container/InboxWithAiLoader.d.ts +15 -0
- package/lib/container/InboxWithAiLoader.d.ts.map +1 -0
- package/lib/container/InboxWithAiLoader.js +56 -0
- package/lib/container/InboxWithAiLoader.js.map +1 -0
- package/lib/container/ServiceInbox.js +1 -1
- package/lib/container/ServiceInbox.js.map +1 -1
- package/lib/container/ThreadMessages.js +1 -1
- package/lib/container/ThreadMessages.js.map +1 -1
- package/lib/container/ThreadMessagesInbox.js +1 -1
- package/lib/container/ThreadMessagesInbox.js.map +1 -1
- package/lib/container/Threads.js +1 -1
- package/lib/container/Threads.js.map +1 -1
- package/lib/container/index.d.ts +4 -1
- package/lib/container/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/machines/aiAgentMachine.d.ts +3 -0
- package/lib/machines/aiAgentMachine.d.ts.map +1 -0
- package/lib/machines/aiAgentMachine.js +1040 -0
- package/lib/machines/aiAgentMachine.js.map +1 -0
- package/lib/machines/types.d.ts +77 -0
- package/lib/machines/types.d.ts.map +1 -0
- package/lib/routes.json +40 -0
- package/lib/templates/InboxWithAi.d.ts +15 -0
- package/lib/templates/InboxWithAi.d.ts.map +1 -0
- package/lib/templates/InboxWithAi.js +405 -0
- package/lib/templates/InboxWithAi.js.map +1 -0
- package/lib/templates/InboxWithAi.tsx +502 -0
- package/package.json +7 -5
- package/src/components/AIAgent/AIAgent.tsx +1351 -0
- package/src/components/AIAgent/README.md +82 -0
- package/src/components/AIAgent/index.ts +1 -0
- package/src/components/InboxMessage/InputComponent.tsx +263 -0
- package/src/components/InboxMessage/MessageInput.tsx +73 -66
- package/src/components/InboxMessage/MessageInputComponent.tsx +245 -0
- package/src/components/InboxMessage/Messages.tsx +2 -56
- package/src/components/InboxMessage/MessagesBuilderUi.tsx +205 -0
- package/src/components/InboxMessage/UploadImageButton.tsx +3 -2
- package/src/components/InboxMessage/index.ts +3 -0
- package/src/components/InboxMessage/message-widgets/CommonMessage.tsx +39 -21
- package/src/components/InboxMessage/message-widgets/ModernMessageGroup.tsx +1968 -0
- package/src/components/InboxMessage/message-widgets/PlainMessage.tsx +6 -2
- package/src/components/InboxMessage/message-widgets/SlackLikeMessageGroup.tsx +306 -54
- package/src/components/InboxMessage/message-widgets/index.ts +1 -0
- package/src/components/index.ts +4 -0
- package/src/compute.ts +83 -2
- package/src/config/env-config.ts +6 -0
- package/src/container/AiInbox.tsx +1796 -0
- package/src/container/AiInboxWithLoader.tsx +356 -0
- package/src/container/AiLandingInput.tsx +168 -0
- package/src/container/Inbox.tsx +8 -5
- package/src/container/InboxAiMessagesLoader.tsx +68 -0
- package/src/container/InboxContainer.tsx +35 -0
- package/src/container/InboxTemplate1.tsx +1542 -0
- package/src/container/InboxTemplate1WithLoader.tsx +338 -0
- package/src/container/InboxTemplate2.tsx +1606 -0
- package/src/container/InboxWithAiLoader.tsx +76 -0
- package/src/container/index.ts +15 -1
- package/src/machines/aiAgentMachine.ts +1248 -0
- package/src/machines/types.ts +59 -0
- package/src/templates/InboxWithAi.tsx +502 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import React, { useCallback, useMemo, useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { config } from '../../config';
|
|
4
|
+
import { UploadImageButton } from './UploadImageButton';
|
|
5
|
+
import { FilesList } from '../inbox';
|
|
6
|
+
|
|
7
|
+
type MessageInputProps = {
|
|
8
|
+
channelId?: string;
|
|
9
|
+
handleSend: (message: string, files: File[]) => Promise<void>;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const MessageInputComponent = ({ handleSend: handleSendProp, placeholder }: MessageInputProps) => {
|
|
14
|
+
const [message, setMessage] = useState('');
|
|
15
|
+
const [sending, setSending] = useState(false);
|
|
16
|
+
const [files, setFiles] = useState<File[]>([]);
|
|
17
|
+
const [showToast, setShowToast] = useState(false);
|
|
18
|
+
const [toastMessage, setToastMessage] = useState('');
|
|
19
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
20
|
+
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
21
|
+
const { t } = useTranslation('translations');
|
|
22
|
+
|
|
23
|
+
// Auto-focus the textarea when component mounts
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (textareaRef.current) {
|
|
26
|
+
textareaRef.current.focus();
|
|
27
|
+
}
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
const showToastMessage = useCallback((message: string) => {
|
|
31
|
+
setToastMessage(message);
|
|
32
|
+
setShowToast(true);
|
|
33
|
+
setTimeout(() => setShowToast(false), 3000);
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
const handleSend = useCallback(() => {
|
|
37
|
+
if (!message.trim() && files.length === 0) return;
|
|
38
|
+
|
|
39
|
+
setSending(true);
|
|
40
|
+
handleSendProp(message, files)
|
|
41
|
+
.then(() => {
|
|
42
|
+
setMessage('');
|
|
43
|
+
setFiles([]);
|
|
44
|
+
// Auto-focus the textarea after sending a message
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
if (textareaRef.current) {
|
|
47
|
+
textareaRef.current.focus();
|
|
48
|
+
}
|
|
49
|
+
}, 100);
|
|
50
|
+
})
|
|
51
|
+
.finally(() => setSending(false));
|
|
52
|
+
}, [files, handleSendProp, message]);
|
|
53
|
+
|
|
54
|
+
const handleKeyDown = useCallback(
|
|
55
|
+
(e) => {
|
|
56
|
+
const keyCode = e.which || e.keyCode;
|
|
57
|
+
if (keyCode == 13 && !e.shiftKey) {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
handleSend();
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
[handleSend],
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const inputHeight = useMemo(() => {
|
|
66
|
+
const lines = message.split('\n').length;
|
|
67
|
+
return Math.max(48, Math.min(120, 48 + (lines - 1) * 20));
|
|
68
|
+
}, [message]);
|
|
69
|
+
|
|
70
|
+
const onUploadImageChange = useCallback(
|
|
71
|
+
({ target }) => {
|
|
72
|
+
let fileList = [];
|
|
73
|
+
let index = 0;
|
|
74
|
+
if (files.length + target.files.length > config.FILES_PER_MESSAGE) {
|
|
75
|
+
showToastMessage(
|
|
76
|
+
t('tailwind_ui_inbox.you_can_not_upload_more_than_files', {
|
|
77
|
+
files_per_message: config.FILES_PER_MESSAGE,
|
|
78
|
+
}),
|
|
79
|
+
);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
while (target.files[index]) {
|
|
83
|
+
fileList.push(target.files[index]);
|
|
84
|
+
index += 1;
|
|
85
|
+
}
|
|
86
|
+
setFiles((oldFiles) => [...oldFiles, ...fileList]);
|
|
87
|
+
},
|
|
88
|
+
[setFiles, files, showToastMessage, t],
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const canSend = message.trim() || files.length > 0;
|
|
92
|
+
const hasContent = message.trim().length > 0;
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className="bg-white">
|
|
96
|
+
{/* Toast notification */}
|
|
97
|
+
{showToast && (
|
|
98
|
+
<div className="fixed top-4 right-4 z-50 bg-orange-50 border border-orange-200 text-orange-800 px-4 py-3 rounded-lg shadow-lg animate-bounce">
|
|
99
|
+
<div className="flex items-center">
|
|
100
|
+
<svg className="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
101
|
+
<path
|
|
102
|
+
fillRule="evenodd"
|
|
103
|
+
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
|
|
104
|
+
clipRule="evenodd"
|
|
105
|
+
/>
|
|
106
|
+
</svg>
|
|
107
|
+
{toastMessage}
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
)}
|
|
111
|
+
|
|
112
|
+
{/* Files list */}
|
|
113
|
+
{files?.length > 0 && !sending && (
|
|
114
|
+
<div className="px-4 py-3 border-b border-gray-100 animate-fade-in">
|
|
115
|
+
<FilesList files={files} />
|
|
116
|
+
</div>
|
|
117
|
+
)}
|
|
118
|
+
|
|
119
|
+
<div className="p-4">
|
|
120
|
+
<div
|
|
121
|
+
className={`relative border-2 transition-all duration-200 ease-in-out ${
|
|
122
|
+
hasContent || isFocused
|
|
123
|
+
? 'rounded-2xl border-gray-300 bg-white'
|
|
124
|
+
: 'rounded-2xl border-gray-300 bg-white'
|
|
125
|
+
} ${isFocused ? 'border-blue-500' : ''}`}
|
|
126
|
+
>
|
|
127
|
+
{/* Image/Upload icon */}
|
|
128
|
+
<div className="absolute left-4 top-1/2 transform -translate-y-1/2 z-10">
|
|
129
|
+
{hasContent || isFocused ? (
|
|
130
|
+
<UploadImageButton onChange={onUploadImageChange} />
|
|
131
|
+
) : (
|
|
132
|
+
<svg
|
|
133
|
+
className="w-5 h-5 text-gray-400"
|
|
134
|
+
fill="none"
|
|
135
|
+
stroke="currentColor"
|
|
136
|
+
viewBox="0 0 24 24"
|
|
137
|
+
>
|
|
138
|
+
<path
|
|
139
|
+
strokeLinecap="round"
|
|
140
|
+
strokeLinejoin="round"
|
|
141
|
+
strokeWidth={2}
|
|
142
|
+
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
|
143
|
+
/>
|
|
144
|
+
</svg>
|
|
145
|
+
)}
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
{/* Textarea */}
|
|
149
|
+
<textarea
|
|
150
|
+
ref={textareaRef}
|
|
151
|
+
className={`w-full text-base bg-transparent border-none resize-none overflow-hidden text-gray-900 focus:outline-none focus:ring-0 scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-transparent ${
|
|
152
|
+
hasContent || isFocused
|
|
153
|
+
? 'pl-14 pr-20 py-3 placeholder-gray-500'
|
|
154
|
+
: 'pl-12 pr-12 py-3 placeholder-gray-600'
|
|
155
|
+
}`}
|
|
156
|
+
style={{
|
|
157
|
+
height: hasContent || isFocused ? `${inputHeight}px` : '48px',
|
|
158
|
+
minHeight: '48px',
|
|
159
|
+
maxHeight: '120px',
|
|
160
|
+
}}
|
|
161
|
+
placeholder={placeholder || 'Message'}
|
|
162
|
+
value={sending ? '' : message}
|
|
163
|
+
onKeyDown={handleKeyDown}
|
|
164
|
+
onChange={(e) => setMessage(e.target.value)}
|
|
165
|
+
onFocus={() => setIsFocused(true)}
|
|
166
|
+
onBlur={() => setIsFocused(false)}
|
|
167
|
+
disabled={sending}
|
|
168
|
+
rows={1}
|
|
169
|
+
/>
|
|
170
|
+
|
|
171
|
+
{/* Right side content */}
|
|
172
|
+
<div className="absolute right-3 top-1/2 transform -translate-y-1/2 flex items-center gap-2">
|
|
173
|
+
{hasContent && <span className="text-sm text-gray-500">{message.length}/10000</span>}
|
|
174
|
+
|
|
175
|
+
{hasContent || isFocused ? (
|
|
176
|
+
/* Send button when typing */
|
|
177
|
+
<button
|
|
178
|
+
className={`w-10 h-10 rounded-xl flex items-center justify-center transition-all duration-200 ease-in-out ${
|
|
179
|
+
canSend && !sending
|
|
180
|
+
? 'bg-gray-900 hover:bg-gray-800 text-white shadow-md hover:shadow-lg'
|
|
181
|
+
: 'bg-gray-200 text-gray-400 cursor-not-allowed'
|
|
182
|
+
}`}
|
|
183
|
+
onClick={handleSend}
|
|
184
|
+
disabled={!canSend || sending}
|
|
185
|
+
type="button"
|
|
186
|
+
>
|
|
187
|
+
{sending ? (
|
|
188
|
+
<svg className="w-5 h-5 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
189
|
+
<circle
|
|
190
|
+
className="opacity-25"
|
|
191
|
+
cx="12"
|
|
192
|
+
cy="12"
|
|
193
|
+
r="10"
|
|
194
|
+
stroke="currentColor"
|
|
195
|
+
strokeWidth="4"
|
|
196
|
+
></circle>
|
|
197
|
+
<path
|
|
198
|
+
className="opacity-75"
|
|
199
|
+
fill="currentColor"
|
|
200
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
201
|
+
></path>
|
|
202
|
+
</svg>
|
|
203
|
+
) : (
|
|
204
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
205
|
+
<path
|
|
206
|
+
strokeLinecap="round"
|
|
207
|
+
strokeLinejoin="round"
|
|
208
|
+
strokeWidth={2}
|
|
209
|
+
d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"
|
|
210
|
+
/>
|
|
211
|
+
</svg>
|
|
212
|
+
)}
|
|
213
|
+
</button>
|
|
214
|
+
) : (
|
|
215
|
+
/* Warning/Info icon when empty */
|
|
216
|
+
<svg
|
|
217
|
+
className="w-5 h-5 text-gray-400"
|
|
218
|
+
fill="none"
|
|
219
|
+
stroke="currentColor"
|
|
220
|
+
viewBox="0 0 24 24"
|
|
221
|
+
>
|
|
222
|
+
<path
|
|
223
|
+
strokeLinecap="round"
|
|
224
|
+
strokeLinejoin="round"
|
|
225
|
+
strokeWidth={2}
|
|
226
|
+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
|
|
227
|
+
/>
|
|
228
|
+
</svg>
|
|
229
|
+
)}
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
{/* Helper text - only show when focused or has content */}
|
|
234
|
+
<div className="mt-2 px-1">
|
|
235
|
+
<span className="text-xs text-gray-400">Press Enter to send, Shift+Enter for new line</span>
|
|
236
|
+
</div>
|
|
237
|
+
{/* {(isFocused || hasContent) && (
|
|
238
|
+
<div className="mt-2 px-1">
|
|
239
|
+
<span className="text-xs text-gray-400">Press Enter to send, Shift+Enter for new line</span>
|
|
240
|
+
</div>
|
|
241
|
+
)} */}
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
);
|
|
245
|
+
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { format, isToday, isYesterday } from 'date-fns';
|
|
2
2
|
import React, { useMemo, useRef, useEffect, useState } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import { UserModalContent } from './UserModalContent';
|
|
5
4
|
import { MessageSliceRenderer } from './message-widgets';
|
|
6
5
|
import { SlackLikeMessageGroup } from './message-widgets/SlackLikeMessageGroup';
|
|
7
6
|
|
|
@@ -31,19 +30,10 @@ export const Messages = ({
|
|
|
31
30
|
isDesktopView = false,
|
|
32
31
|
isSmallScreen = false,
|
|
33
32
|
}: MessagesProps) => {
|
|
34
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
35
|
-
const [selectedElement, setSelectedElement] = useState(null);
|
|
36
33
|
const { t } = useTranslation('translations');
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
setIsOpen(true);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const onClose = () => {
|
|
44
|
-
setIsOpen(false);
|
|
45
|
-
setSelectedElement(null);
|
|
46
|
-
};
|
|
35
|
+
// Disable user modal behavior: no-op handler
|
|
36
|
+
const onOpen = () => {};
|
|
47
37
|
|
|
48
38
|
useEffect(() => {
|
|
49
39
|
subscribeToNewMessages?.();
|
|
@@ -142,50 +132,6 @@ export const Messages = ({
|
|
|
142
132
|
</div>
|
|
143
133
|
))}
|
|
144
134
|
</div>
|
|
145
|
-
<ChatModal element={selectedElement} isOpen={isOpen} onClose={onClose} />
|
|
146
|
-
</>
|
|
147
|
-
);
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const ChatModal = ({ element, isOpen, onClose }) => {
|
|
151
|
-
if (!isOpen) return null;
|
|
152
|
-
|
|
153
|
-
return (
|
|
154
|
-
<>
|
|
155
|
-
{/* Modal Overlay */}
|
|
156
|
-
<div className="fixed inset-0 bg-black bg-opacity-50 z-40" onClick={onClose} />
|
|
157
|
-
|
|
158
|
-
{/* Modal Content */}
|
|
159
|
-
<div className="fixed z-50 left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2">
|
|
160
|
-
<div
|
|
161
|
-
className="bg-white w-[1036px] h-[700px] rounded-lg shadow-xl"
|
|
162
|
-
style={{
|
|
163
|
-
boxShadow: '0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)',
|
|
164
|
-
}}
|
|
165
|
-
>
|
|
166
|
-
{/* Modal Header */}
|
|
167
|
-
<div className="flex justify-between border-b border-gray-300 pb-4 pt-4">
|
|
168
|
-
<button onClick={onClose} className="w-8 ml-3 text-black hover:text-black focus:outline-none">
|
|
169
|
-
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
170
|
-
<path
|
|
171
|
-
strokeLinecap="round"
|
|
172
|
-
strokeLinejoin="round"
|
|
173
|
-
strokeWidth={2}
|
|
174
|
-
d="M6 18L18 6M6 6l12 12"
|
|
175
|
-
/>
|
|
176
|
-
</svg>
|
|
177
|
-
</button>
|
|
178
|
-
</div>
|
|
179
|
-
|
|
180
|
-
{/* Modal Body */}
|
|
181
|
-
<div className="p-4">
|
|
182
|
-
<UserModalContent
|
|
183
|
-
username={element?.author?.username}
|
|
184
|
-
image={element?.imageUrl ? element.imageUrl : ''}
|
|
185
|
-
/>
|
|
186
|
-
</div>
|
|
187
|
-
</div>
|
|
188
|
-
</div>
|
|
189
135
|
</>
|
|
190
136
|
);
|
|
191
137
|
};
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { format, isToday, isYesterday } from 'date-fns';
|
|
2
|
+
import React, { useMemo, useRef, useEffect, useState } from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { UserModalContent } from './UserModalContent';
|
|
5
|
+
import { MessageSliceRenderer } from './message-widgets';
|
|
6
|
+
import { ModernMessageGroupComponent } from './message-widgets/ModernMessageGroup';
|
|
7
|
+
|
|
8
|
+
interface MessagesProps {
|
|
9
|
+
channelId: number;
|
|
10
|
+
currentUser: any;
|
|
11
|
+
channelMessages: any[];
|
|
12
|
+
totalCount: number;
|
|
13
|
+
innerRef?: any;
|
|
14
|
+
refetchMessages?: () => void;
|
|
15
|
+
subscribeToNewMessages?: () => any;
|
|
16
|
+
subscribeToNewServiceMessages?: () => any;
|
|
17
|
+
onMessageClick: (msg) => void;
|
|
18
|
+
isDesktopView?: boolean;
|
|
19
|
+
isSmallScreen?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const MessagesBuilderUi = ({
|
|
23
|
+
currentUser,
|
|
24
|
+
channelMessages,
|
|
25
|
+
refetchMessages,
|
|
26
|
+
innerRef,
|
|
27
|
+
channelId,
|
|
28
|
+
subscribeToNewMessages,
|
|
29
|
+
subscribeToNewServiceMessages,
|
|
30
|
+
onMessageClick,
|
|
31
|
+
isDesktopView = false,
|
|
32
|
+
isSmallScreen = false,
|
|
33
|
+
}: MessagesProps) => {
|
|
34
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
35
|
+
const [selectedElement, setSelectedElement] = useState(null);
|
|
36
|
+
const { t } = useTranslation('translations');
|
|
37
|
+
|
|
38
|
+
const onOpen = (element?: any) => {
|
|
39
|
+
setSelectedElement(element);
|
|
40
|
+
setIsOpen(true);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const onClose = () => {
|
|
44
|
+
setIsOpen(false);
|
|
45
|
+
setSelectedElement(null);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
subscribeToNewMessages?.();
|
|
50
|
+
subscribeToNewServiceMessages?.();
|
|
51
|
+
}, [channelId]);
|
|
52
|
+
|
|
53
|
+
const messageListRef = useRef(null);
|
|
54
|
+
const scrollToBottom = React.useCallback(() => {
|
|
55
|
+
if (innerRef.current)
|
|
56
|
+
innerRef.current?.querySelector(':scope > :last-child')?.scrollIntoView({
|
|
57
|
+
behavior: 'smooth',
|
|
58
|
+
inline: 'start',
|
|
59
|
+
});
|
|
60
|
+
}, [innerRef]);
|
|
61
|
+
|
|
62
|
+
React.useEffect(() => {
|
|
63
|
+
if (channelMessages?.length) {
|
|
64
|
+
// scrollToBottom();
|
|
65
|
+
}
|
|
66
|
+
}, [channelMessages]);
|
|
67
|
+
|
|
68
|
+
const messageListWithDates = useMemo(() => {
|
|
69
|
+
let currentDate = '';
|
|
70
|
+
let res = [];
|
|
71
|
+
|
|
72
|
+
// Add original messages
|
|
73
|
+
channelMessages?.map((msg) => {
|
|
74
|
+
const date = new Date(msg.createdAt);
|
|
75
|
+
let msgDate;
|
|
76
|
+
if (isToday(date)) msgDate = t('tailwind_ui_inbox.today');
|
|
77
|
+
else if (isYesterday(date)) msgDate = t('tailwind_ui_inbox.yesterday');
|
|
78
|
+
else msgDate = format(new Date(msg.createdAt), 'eee, do MMMM');
|
|
79
|
+
|
|
80
|
+
if (msgDate !== currentDate) {
|
|
81
|
+
res.push({ type: 'date', content: msgDate });
|
|
82
|
+
currentDate = msgDate;
|
|
83
|
+
}
|
|
84
|
+
res.push(msg);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Add dummy messages at the end if we have existing messages
|
|
88
|
+
if (channelMessages && channelMessages.length > 0) {
|
|
89
|
+
// Add today's date if not already added
|
|
90
|
+
const todayLabel = t('tailwind_ui_inbox.today');
|
|
91
|
+
if (currentDate !== todayLabel) {
|
|
92
|
+
res.push({ type: 'date', content: todayLabel });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// dummyMessages.forEach(msg => res.push(msg));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return res;
|
|
99
|
+
}, [channelMessages, t]);
|
|
100
|
+
|
|
101
|
+
// Group messages by date sections for Slack-like rendering
|
|
102
|
+
const messagesByDate = useMemo(() => {
|
|
103
|
+
const sections = [];
|
|
104
|
+
let currentSection = { date: null, messages: [] };
|
|
105
|
+
|
|
106
|
+
messageListWithDates.forEach((item) => {
|
|
107
|
+
if (item?.type === 'date') {
|
|
108
|
+
if (currentSection.messages.length > 0) {
|
|
109
|
+
sections.push(currentSection);
|
|
110
|
+
}
|
|
111
|
+
currentSection = { date: item.content, messages: [] };
|
|
112
|
+
} else {
|
|
113
|
+
currentSection.messages.push(item);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (currentSection.messages.length > 0) {
|
|
118
|
+
sections.push(currentSection);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return sections;
|
|
122
|
+
}, [messageListWithDates]);
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<>
|
|
126
|
+
<div
|
|
127
|
+
className={`w-full pb-4 pt-2 ${
|
|
128
|
+
isDesktopView ? 'space-y-3 max-w-full mx-auto' : isSmallScreen ? 'space-y-2' : 'space-y-2'
|
|
129
|
+
}`}
|
|
130
|
+
ref={innerRef}
|
|
131
|
+
>
|
|
132
|
+
{messagesByDate?.map((section, sectionIndex) => (
|
|
133
|
+
<div key={`section-${sectionIndex}`} className="w-full">
|
|
134
|
+
{/* Date separator */}
|
|
135
|
+
{section.date && (
|
|
136
|
+
<div className="flex items-center justify-center my-3">
|
|
137
|
+
<div className="flex-grow border-t border-gray-200"></div>
|
|
138
|
+
<div className="mx-4 px-3 py-1 bg-white border border-gray-200 rounded-full text-xs font-medium text-gray-600">
|
|
139
|
+
{section.date}
|
|
140
|
+
</div>
|
|
141
|
+
<div className="flex-grow border-t border-gray-200"></div>
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
|
|
145
|
+
{/* Messages grouped by user and time */}
|
|
146
|
+
<div className={`${isDesktopView ? 'mb-2' : 'mb-1'}`}>
|
|
147
|
+
<ModernMessageGroupComponent
|
|
148
|
+
messages={section.messages}
|
|
149
|
+
currentUser={currentUser}
|
|
150
|
+
onOpen={onOpen}
|
|
151
|
+
onMessageClick={onMessageClick}
|
|
152
|
+
isDesktopView={isDesktopView}
|
|
153
|
+
isSmallScreen={isSmallScreen}
|
|
154
|
+
/>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
))}
|
|
158
|
+
</div>
|
|
159
|
+
<ChatModal element={selectedElement} isOpen={isOpen} onClose={onClose} />
|
|
160
|
+
</>
|
|
161
|
+
);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const ChatModal = ({ element, isOpen, onClose }) => {
|
|
165
|
+
if (!isOpen) return null;
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<>
|
|
169
|
+
{/* Modal Overlay */}
|
|
170
|
+
<div className="fixed inset-0 bg-black bg-opacity-50 z-40" onClick={onClose} />
|
|
171
|
+
|
|
172
|
+
{/* Modal Content */}
|
|
173
|
+
<div className="fixed z-50 left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2">
|
|
174
|
+
<div
|
|
175
|
+
className="bg-white w-[1036px] h-[700px] rounded-lg shadow-xl"
|
|
176
|
+
style={{
|
|
177
|
+
boxShadow: '0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)',
|
|
178
|
+
}}
|
|
179
|
+
>
|
|
180
|
+
{/* Modal Header */}
|
|
181
|
+
<div className="flex justify-between border-b border-gray-300 pb-4 pt-4">
|
|
182
|
+
<button onClick={onClose} className="w-8 ml-3 text-black hover:text-black focus:outline-none">
|
|
183
|
+
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
184
|
+
<path
|
|
185
|
+
strokeLinecap="round"
|
|
186
|
+
strokeLinejoin="round"
|
|
187
|
+
strokeWidth={2}
|
|
188
|
+
d="M6 18L18 6M6 6l12 12"
|
|
189
|
+
/>
|
|
190
|
+
</svg>
|
|
191
|
+
</button>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
{/* Modal Body */}
|
|
195
|
+
<div className="p-4">
|
|
196
|
+
<UserModalContent
|
|
197
|
+
username={element?.author?.username}
|
|
198
|
+
image={element?.imageUrl ? element.imageUrl : ''}
|
|
199
|
+
/>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</>
|
|
204
|
+
);
|
|
205
|
+
};
|
|
@@ -4,6 +4,7 @@ import { BiImage } from '@react-icons/all-files/bi/BiImage.js';
|
|
|
4
4
|
|
|
5
5
|
export interface IUploadImageButtonProps {
|
|
6
6
|
onChange: ChangeEventHandler<HTMLInputElement>;
|
|
7
|
+
children?: React.ReactNode;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export const UploadImageButton = (props: IUploadImageButtonProps) => {
|
|
@@ -34,11 +35,11 @@ export const UploadImageButton = (props: IUploadImageButtonProps) => {
|
|
|
34
35
|
</div>
|
|
35
36
|
<button
|
|
36
37
|
type="button"
|
|
37
|
-
className="flex items-center justify-center w-8 h-8
|
|
38
|
+
className="flex items-center justify-center w-8 h-8 bg-transparent hover:bg-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 transition-all duration-200 ease-in-out"
|
|
38
39
|
onClick={selectFile}
|
|
39
40
|
aria-label={t('tailwind_ui_inbox.upload_image')}
|
|
40
41
|
>
|
|
41
|
-
<BiImage className="w-5 h-5 text-gray-500
|
|
42
|
+
{props.children || <BiImage className="w-5 h-5 text-gray-500 hover:text-gray-700" />}
|
|
42
43
|
</button>
|
|
43
44
|
</div>
|
|
44
45
|
);
|
|
@@ -10,3 +10,6 @@ export * from './UserModalContent';
|
|
|
10
10
|
export * from './message-widgets';
|
|
11
11
|
export * from './RightSidebar';
|
|
12
12
|
export * from './ServiceInboxItem';
|
|
13
|
+
export * from './MessageInputComponent';
|
|
14
|
+
export * from './InputComponent';
|
|
15
|
+
export * from './MessagesBuilderUi';
|
|
@@ -14,34 +14,52 @@ interface ICommonMessageProps {
|
|
|
14
14
|
|
|
15
15
|
export const CommonMessage = (props: ICommonMessageProps) => {
|
|
16
16
|
const { message, index, onOpen, currentUser } = props;
|
|
17
|
+
|
|
18
|
+
// Determine if this is an AI/system message for special styling
|
|
19
|
+
const isSystemMessage =
|
|
20
|
+
message?.author?.username?.toLowerCase().includes('ai') ||
|
|
21
|
+
message?.author?.username?.toLowerCase().includes('assistant') ||
|
|
22
|
+
message?.author?.username?.toLowerCase().includes('system');
|
|
23
|
+
|
|
17
24
|
return (
|
|
18
25
|
<div className="w-full flex justify-start mb-7" key={`msgList_${index}`}>
|
|
19
26
|
<div className="flex-grow flex">
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
{/* User avatar - Only show for non-system messages */}
|
|
28
|
+
{!isSystemMessage && (
|
|
29
|
+
<img
|
|
30
|
+
className="w-8 h-8 bg-gray-400 rounded-full mr-4 cursor-pointer"
|
|
31
|
+
src={message?.author?.picture}
|
|
32
|
+
alt={message?.author?.username || 'User avatar'}
|
|
33
|
+
onClick={onOpen}
|
|
34
|
+
/>
|
|
35
|
+
)}
|
|
26
36
|
<div className="flex-grow">
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
{/* Author header - Only show for non-system messages */}
|
|
38
|
+
{!isSystemMessage && (
|
|
39
|
+
<div className="flex flex-grow mt-1.5">
|
|
40
|
+
<p className="text-sm text-gray-600 font-bold">
|
|
41
|
+
{message?.author?.familyName && message?.author?.givenName
|
|
42
|
+
? message?.author?.givenName + ' ' + message?.author?.familyName
|
|
43
|
+
: message?.author?.username}
|
|
44
|
+
</p>
|
|
45
|
+
<p className="text-sm text-gray-500 ml-2.5">
|
|
46
|
+
{formatDistance(new Date(message?.createdAt), new Date(), {
|
|
47
|
+
includeSeconds: true,
|
|
48
|
+
addSuffix: true,
|
|
49
|
+
})}
|
|
50
|
+
</p>
|
|
51
|
+
</div>
|
|
52
|
+
)}
|
|
53
|
+
{/* <p className="text-sm mt-1.5 whitespace-pre-line">{message?.message}</p> */}
|
|
54
|
+
<div
|
|
55
|
+
className="text-sm mt-1.5 whitespace-pre-wrap prose prose-sm max-w-none"
|
|
56
|
+
dangerouslySetInnerHTML={{ __html: message?.message || '' }}
|
|
57
|
+
/>
|
|
41
58
|
{message.files?.totalCount ? <FilesList uploaded files={message.files?.data} /> : null}
|
|
42
59
|
</div>
|
|
43
60
|
</div>
|
|
44
|
-
{
|
|
61
|
+
{/* Flag button - Only show for non-system messages */}
|
|
62
|
+
{!isSystemMessage && currentUser?.id !== message?.author?.id && (
|
|
45
63
|
<div className="ml-2">
|
|
46
64
|
<BsFlag className="text-gray-600 w-6 h-6" />
|
|
47
65
|
</div>
|