v0.3.0
This commit is contained in:
@@ -8,7 +8,7 @@ import MessageInput from './MessageInput.jsx';
|
||||
import GroupInfoModal from './GroupInfoModal.jsx';
|
||||
import './ChatWindow.css';
|
||||
|
||||
export default function ChatWindow({ group, onBack, onGroupUpdated }) {
|
||||
export default function ChatWindow({ group, onBack, onGroupUpdated, onDirectMessage }) {
|
||||
const { socket } = useSocket();
|
||||
const { user } = useAuth();
|
||||
const toast = useToast();
|
||||
@@ -23,6 +23,8 @@ export default function ChatWindow({ group, onBack, onGroupUpdated }) {
|
||||
const messagesEndRef = useRef(null);
|
||||
const messagesTopRef = useRef(null);
|
||||
const typingTimers = useRef({});
|
||||
const swipeStartX = useRef(null);
|
||||
const swipeStartY = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
api.getSettings().then(({ settings }) => {
|
||||
@@ -33,6 +35,7 @@ export default function ChatWindow({ group, onBack, onGroupUpdated }) {
|
||||
return () => window.removeEventListener('jama:settings-changed', handler);
|
||||
}, []);
|
||||
|
||||
|
||||
const scrollToBottom = useCallback((smooth = false) => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: smooth ? 'smooth' : 'auto' });
|
||||
}, []);
|
||||
@@ -110,7 +113,7 @@ export default function ChatWindow({ group, onBack, onGroupUpdated }) {
|
||||
setHasMore(older.length >= 50);
|
||||
};
|
||||
|
||||
const handleSend = async ({ content, imageFile, linkPreview }) => {
|
||||
const handleSend = async ({ content, imageFile, linkPreview, emojiOnly }) => {
|
||||
if (!group) return;
|
||||
const replyId = replyTo?.id;
|
||||
setReplyTo(null);
|
||||
@@ -125,7 +128,7 @@ export default function ChatWindow({ group, onBack, onGroupUpdated }) {
|
||||
}
|
||||
} else {
|
||||
socket?.emit('message:send', {
|
||||
groupId: group.id, content, replyToId: replyId, linkPreview
|
||||
groupId: group.id, content, replyToId: replyId, linkPreview, emojiOnly
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -149,8 +152,30 @@ export default function ChatWindow({ group, onBack, onGroupUpdated }) {
|
||||
);
|
||||
}
|
||||
|
||||
const handleTouchStart = (e) => {
|
||||
swipeStartX.current = e.touches[0].clientX;
|
||||
swipeStartY.current = e.touches[0].clientY;
|
||||
};
|
||||
|
||||
const handleTouchEnd = (e) => {
|
||||
if (swipeStartX.current === null || !onBack) return;
|
||||
const dx = e.changedTouches[0].clientX - swipeStartX.current;
|
||||
const dy = Math.abs(e.changedTouches[0].clientY - swipeStartY.current);
|
||||
// Swipe right: at least 80px horizontal, less than 60px vertical drift
|
||||
if (dx > 80 && dy < 60) {
|
||||
e.preventDefault();
|
||||
onBack();
|
||||
}
|
||||
swipeStartX.current = null;
|
||||
swipeStartY.current = null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="chat-window">
|
||||
<div
|
||||
className="chat-window"
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="chat-header">
|
||||
{onBack && (
|
||||
@@ -172,12 +197,12 @@ export default function ChatWindow({ group, onBack, onGroupUpdated }) {
|
||||
) : null}
|
||||
</div>
|
||||
<span className="chat-header-sub">
|
||||
{group.type === 'public' ? 'Public group' : 'Private group'}
|
||||
{group.is_direct ? 'Direct message' : group.type === 'public' ? 'Public message' : 'Private message'}
|
||||
</span>
|
||||
</div>
|
||||
<button className="btn-icon" onClick={() => setShowInfo(true)} title="Group info">
|
||||
<button className="btn-icon" onClick={() => setShowInfo(true)} title="Message info">
|
||||
{iconGroupInfo ? (
|
||||
<img src={iconGroupInfo} alt="Group info" style={{ width: 20, height: 20, objectFit: 'contain' }} />
|
||||
<img src={iconGroupInfo} alt="Message info" style={{ width: 20, height: 20, objectFit: 'contain' }} />
|
||||
) : (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" width="24" height="24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15 9h3.75M15 12h3.75M15 15h3.75M4.5 19.5h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Zm6-10.125a1.875 1.875 0 1 1-3.75 0 1.875 1.875 0 0 1 3.75 0Zm1.294 6.336a6.721 6.721 0 0 1-3.17.789 6.721 6.721 0 0 1-3.168-.789 3.376 3.376 0 0 1 6.338 0Z" />
|
||||
@@ -203,9 +228,11 @@ export default function ChatWindow({ group, onBack, onGroupUpdated }) {
|
||||
message={msg}
|
||||
prevMessage={messages[i - 1]}
|
||||
currentUser={user}
|
||||
isDirect={!!group.is_direct}
|
||||
onReply={(m) => setReplyTo(m)}
|
||||
onDelete={(id) => socket?.emit('message:delete', { messageId: id })}
|
||||
onReact={(id, emoji) => socket?.emit('reaction:toggle', { messageId: id, emoji })}
|
||||
onDirectMessage={onDirectMessage}
|
||||
/>
|
||||
))}
|
||||
{typing.length > 0 && (
|
||||
@@ -236,7 +263,7 @@ export default function ChatWindow({ group, onBack, onGroupUpdated }) {
|
||||
) : (
|
||||
<div className="readonly-bar">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
|
||||
This channel is read-only
|
||||
This message is read-only
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -245,6 +272,7 @@ export default function ChatWindow({ group, onBack, onGroupUpdated }) {
|
||||
group={group}
|
||||
onClose={() => setShowInfo(false)}
|
||||
onUpdated={onGroupUpdated}
|
||||
onBack={onBack}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user