Импорт из телеграм
This commit is contained in:
@@ -102,12 +102,25 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
|
||||
const typingInChat = typingUsers.filter((t) => t.chatId === activeChat && t.userId !== user?.id);
|
||||
|
||||
// Refs and logic for tracking session's first unread message to show the divider exactly once per load
|
||||
const sessionUnreadRef = useRef<{ chatId: string, msgId: string | null }>({ chatId: '', msgId: null });
|
||||
|
||||
if (activeChat && activeChat !== sessionUnreadRef.current.chatId && !isLoadingMessages) {
|
||||
const firstUnreadMsg = chatMessages.find(
|
||||
(m) => m.senderId !== user?.id && !m.readBy?.some((r) => r.userId === user?.id)
|
||||
);
|
||||
sessionUnreadRef.current = { chatId: activeChat, msgId: firstUnreadMsg ? firstUnreadMsg.id : null };
|
||||
}
|
||||
const firstUnreadId = activeChat === sessionUnreadRef.current.chatId ? sessionUnreadRef.current.msgId : null;
|
||||
const lastObservedMessageIdRef = useRef<string | null>(null);
|
||||
|
||||
// Load muted state
|
||||
useEffect(() => {
|
||||
if (activeChat) {
|
||||
setMuted(isChatMuted(activeChat));
|
||||
setScrollReady(false);
|
||||
setActiveGroupCallParticipants([]);
|
||||
lastObservedMessageIdRef.current = null;
|
||||
}
|
||||
}, [activeChat]);
|
||||
|
||||
@@ -167,7 +180,23 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
// Первичная прокрутка при открытии чата или после загрузки (layout effect — до отрисовки)
|
||||
useLayoutEffect(() => {
|
||||
if (!isLoadingMessages && messagesContainerRef.current) {
|
||||
messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight;
|
||||
const container = messagesContainerRef.current;
|
||||
const unreadId = sessionUnreadRef.current.msgId;
|
||||
|
||||
if (unreadId && activeChat === sessionUnreadRef.current.chatId) {
|
||||
const dividerEl = document.getElementById('unread-divider');
|
||||
const unreadEl = document.getElementById(`msg-${unreadId}`);
|
||||
const targetEl = dividerEl || unreadEl;
|
||||
|
||||
if (targetEl) {
|
||||
// Вычитаем отступ сверху, чтобы начало непрочитанных было под шапкой
|
||||
container.scrollTop = targetEl.offsetTop - 60;
|
||||
} else {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
} else {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
setScrollReady(true);
|
||||
}
|
||||
}, [activeChat, isLoadingMessages]);
|
||||
@@ -176,17 +205,25 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
useEffect(() => {
|
||||
if (chatMessages.length > 0) {
|
||||
const lastMsg = chatMessages[chatMessages.length - 1];
|
||||
if (lastMsg.senderId === user?.id) {
|
||||
setTimeout(() => scrollToBottom(true), 50);
|
||||
} else {
|
||||
// Если пользователь внизу — прокрутить
|
||||
const container = messagesContainerRef.current;
|
||||
if (container) {
|
||||
const isNearBottom =
|
||||
container.scrollHeight - container.scrollTop - container.clientHeight < 250;
|
||||
if (isNearBottom) setTimeout(() => scrollToBottom(true), 50);
|
||||
const prevId = lastObservedMessageIdRef.current;
|
||||
lastObservedMessageIdRef.current = lastMsg.id;
|
||||
|
||||
// Scroll ONLY if a genuinely new message was added (not during initial chat load)
|
||||
if (prevId && prevId !== lastMsg.id) {
|
||||
if (lastMsg.senderId === user?.id) {
|
||||
setTimeout(() => scrollToBottom(true), 50);
|
||||
} else {
|
||||
// Если пользователь внизу — прокрутить
|
||||
const container = messagesContainerRef.current;
|
||||
if (container) {
|
||||
const isNearBottom =
|
||||
container.scrollHeight - container.scrollTop - container.clientHeight < 300;
|
||||
if (isNearBottom) setTimeout(() => scrollToBottom(true), 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lastObservedMessageIdRef.current = null;
|
||||
}
|
||||
}, [chatMessages.length, user?.id, scrollToBottom]);
|
||||
|
||||
@@ -273,7 +310,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
const container = messagesContainerRef.current;
|
||||
if (!container) return;
|
||||
const isNearBottom =
|
||||
container.scrollHeight - container.scrollTop - container.clientHeight < 200;
|
||||
container.scrollHeight - container.scrollTop - container.clientHeight < 300;
|
||||
setShowScrollDown(!isNearBottom);
|
||||
}, []);
|
||||
|
||||
@@ -321,29 +358,20 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
if (!activeChat || !chat) {
|
||||
return (
|
||||
<div className="flex-1 h-full flex items-center justify-center bg-surface-secondary/50 rounded-[2rem] overflow-hidden border border-white/5 shadow-2xl relative z-0 backdrop-blur-3xl group">
|
||||
{/* Slowly pulsing purple background as requested */}
|
||||
<div className="absolute inset-0 pointer-events-none overflow-hidden transition-opacity duration-[10000ms]">
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[60vw] h-[60vw] max-w-[800px] max-h-[800px] bg-accent/10 rounded-full blur-[120px] animate-[pulse_8s_ease-in-out_infinite]" />
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[40vw] h-[40vw] max-w-[500px] max-h-[500px] bg-purple-600/15 rounded-full blur-[100px] animate-[pulse_12s_ease-in-out_infinite_reverse]" />
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGNpcmNsZSBjeD0iMSIgY3k9IjEiIHI9IjEiIGZpbGw9InJnYmEoMjU1LDI1NSwyNTUsMC4wMSkvPjwvc3ZnPg==')] [mask-image:radial-gradient(ellipse_at_center,black_40%,transparent_100%)] opacity-20 pointer-events-none" />
|
||||
|
||||
<div className="text-center relative z-10 w-full max-w-sm px-6 mx-auto">
|
||||
<motion.div
|
||||
initial={{ scale: 0.8, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="w-28 h-28 mx-auto mb-8 rounded-[2rem] bg-gradient-to-br from-accent/20 to-purple-600/20 flex items-center justify-center shadow-[0_0_60px_-15px_var(--color-accent)] ring-1 ring-white/10 backdrop-blur-2xl relative"
|
||||
className="w-24 h-24 mx-auto mb-6 rounded-full bg-surface-hover flex items-center justify-center border border-border/40 backdrop-blur-md relative"
|
||||
>
|
||||
<div className="absolute inset-0 rounded-[2rem] bg-gradient-to-br from-white/[0.05] to-transparent pointer-events-none" />
|
||||
<MessageSquare className="w-16 h-16 text-accent shadow-2xl transform hover:scale-105 transition-transform" />
|
||||
<MessageSquare className="w-10 h-10 text-zinc-500 transform hover:scale-105 transition-transform" />
|
||||
</motion.div>
|
||||
<motion.h2
|
||||
initial={{ y: 20, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ duration: 0.6, delay: 0.1, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-accent via-fuchsia-400 to-indigo-400 mb-4 drop-shadow-lg tracking-tight"
|
||||
className="text-2xl font-semibold text-white mb-2 tracking-tight"
|
||||
>
|
||||
Knot Messenger
|
||||
</motion.h2>
|
||||
@@ -351,7 +379,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
initial={{ y: 20, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ duration: 0.6, delay: 0.2, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="text-sm font-medium text-zinc-300 bg-white/5 backdrop-blur-lg py-2.5 px-6 rounded-full inline-flex border border-white/10 shadow-lg"
|
||||
className="text-sm text-zinc-400 bg-surface-hover backdrop-blur-lg py-2 px-4 rounded-full inline-flex border border-border/50"
|
||||
>
|
||||
{t('selectChat')}
|
||||
</motion.p>
|
||||
@@ -743,8 +771,8 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
const el = document.getElementById(`msg-${msg.id}`);
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
el.classList.add('bg-knot-500/20');
|
||||
setTimeout(() => el.classList.remove('bg-knot-500/20'), 2000);
|
||||
el.classList.add('highlight-message');
|
||||
setTimeout(() => el.classList.remove('highlight-message'), 3000);
|
||||
}
|
||||
setShowSearch(false);
|
||||
setSearchText('');
|
||||
@@ -790,8 +818,8 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
const el = document.getElementById(`msg-${pinnedMsg.id}`);
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
el.classList.add('bg-knot-500/20');
|
||||
setTimeout(() => el.classList.remove('bg-knot-500/20'), 2000);
|
||||
el.classList.add('highlight-message');
|
||||
setTimeout(() => el.classList.remove('highlight-message'), 3000);
|
||||
}
|
||||
}}
|
||||
className="flex items-center gap-3 px-4 py-2 border-b border-border bg-surface-secondary/60 hover:bg-surface-hover transition-colors text-left w-full flex-shrink-0"
|
||||
@@ -839,6 +867,8 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
const showDate =
|
||||
!prevMsg ||
|
||||
new Date(msg.createdAt).toDateString() !== new Date(prevMsg.createdAt).toDateString();
|
||||
|
||||
const isFirstUnread = firstUnreadId === msg.id;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -846,6 +876,15 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
data-message-id={msg.id}
|
||||
className={`transition-colors duration-500 ${msg.senderId !== user?.id && !msg.readBy?.some(r => r.userId === user?.id) ? 'unread-detector' : ''}`}
|
||||
>
|
||||
{isFirstUnread && (
|
||||
<div id="unread-divider" className="flex items-center justify-center my-4 opacity-80 select-none">
|
||||
<div className="flex-1 h-px bg-border/50"></div>
|
||||
<span className="px-4 text-[11px] font-semibold tracking-wider uppercase text-zinc-400">
|
||||
{t('unreadMessages')}
|
||||
</span>
|
||||
<div className="flex-1 h-px bg-border/50"></div>
|
||||
</div>
|
||||
)}
|
||||
{showDate && (
|
||||
<div className="flex justify-center my-4">
|
||||
<span className="px-3 py-1 rounded-full text-xs text-zinc-400 glass">
|
||||
@@ -886,9 +925,9 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
exit={{ scale: 0, opacity: 0 }}
|
||||
onClick={() => scrollToBottom()}
|
||||
className="absolute bottom-24 right-6 w-11 h-11 rounded-full bg-surface-tertiary/90 backdrop-blur-md border border-border shadow-2xl flex items-center justify-center text-zinc-400 hover:text-white hover:bg-surface-hover hover:scale-105 transition-all z-10"
|
||||
className="absolute bottom-24 right-5 w-12 h-12 rounded-full bg-surface-secondary/95 backdrop-blur-md border border-border shadow-xl flex items-center justify-center text-zinc-400 hover:text-white hover:bg-surface-hover hover:scale-105 transition-all z-10"
|
||||
>
|
||||
<ArrowDown size={20} />
|
||||
<ArrowDown size={22} className="text-accent hover:text-accent-light transition-colors" />
|
||||
{unreadCount > 0 && (
|
||||
<motion.span
|
||||
initial={{ scale: 0 }}
|
||||
@@ -923,8 +962,8 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
const el = document.getElementById(`msg-${msgId}`);
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
el.classList.add('bg-knot-500/20');
|
||||
setTimeout(() => el.classList.remove('bg-knot-500/20'), 2000);
|
||||
el.classList.add('highlight-message');
|
||||
setTimeout(() => el.classList.remove('highlight-message'), 3000);
|
||||
setProfileUserId(null);
|
||||
}
|
||||
}}
|
||||
@@ -943,8 +982,8 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
const el = document.getElementById(`msg-${msgId}`);
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
el.classList.add('bg-knot-500/20');
|
||||
setTimeout(() => el.classList.remove('bg-knot-500/20'), 2000);
|
||||
el.classList.add('highlight-message');
|
||||
setTimeout(() => el.classList.remove('highlight-message'), 3000);
|
||||
setShowGroupSettings(false);
|
||||
}
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user