diff --git a/client-web/src/core/infrastructure/i18n.ts b/client-web/src/core/infrastructure/i18n.ts index 547d5d3..9143940 100644 --- a/client-web/src/core/infrastructure/i18n.ts +++ b/client-web/src/core/infrastructure/i18n.ts @@ -194,7 +194,8 @@ const translations = { clearChatConfirm: 'Очистить историю чата для себя? Собеседник сохранит свою историю.', clearHistory: 'Очистить историю', clearHistoryConfirm: 'Очистить историю?', - deleteChatConfirm: 'Удалить чат? Это действие нельзя отменить.', + deleteChatConfirm: 'Удалить чат? Это действие нельзя отменить. Чат будет удалён у всех участников.', + deleteGroupChatConfirm: 'Удалить чат? Это действие нельзя отменить. Чат будет удалён у всех участников.', pinChat: 'Закрепить чат', unpinChat: 'Открепить чат', chatCleared: 'Очищено', @@ -575,6 +576,7 @@ const translations = { clearHistory: 'Clear history', clearHistoryConfirm: 'Clear history?', deleteChatConfirm: 'Delete this chat? This action cannot be undone.', + deleteGroupChatConfirm: 'Delete this chat? This action cannot be undone. The chat will be removed for all participants.', pinChat: 'Pin chat', unpinChat: 'Unpin chat', chatCleared: 'Chat cleared', diff --git a/client-web/src/modules/chats/presentation/components/ChatListItem.tsx b/client-web/src/modules/chats/presentation/components/ChatListItem.tsx index 93d35bc..6bcfcca 100644 --- a/client-web/src/modules/chats/presentation/components/ChatListItem.tsx +++ b/client-web/src/modules/chats/presentation/components/ChatListItem.tsx @@ -283,7 +283,11 @@ function ChatListItem({ chat, isActive }: ChatListItemProps) { setShowDeleteConfirm(false)} /> diff --git a/client-web/src/modules/chats/presentation/components/ChatView.tsx b/client-web/src/modules/chats/presentation/components/ChatView.tsx index dcb1991..ae66ab1 100644 --- a/client-web/src/modules/chats/presentation/components/ChatView.tsx +++ b/client-web/src/modules/chats/presentation/components/ChatView.tsx @@ -98,13 +98,13 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal NotificationStore.useNotificationStore.getState().addNotification('info', (t as any)('searchingHistory') || 'Searching message in history...'); const chatStore = useChatStore.getState(); - + if (sequenceId !== undefined) { await chatStore.jumpToMessage(activeChat, sequenceId); // Wait a bit for React to render setTimeout(() => { if (!tryScroll()) { - NotificationStore.useNotificationStore.getState().addNotification('warning', (t as any)('messageNotFound') || 'Message not found'); + NotificationStore.useNotificationStore.getState().addNotification('warning', (t as any)('messageNotFound') || 'Message not found'); } }, 300); } else { @@ -137,25 +137,25 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal useEffect(() => { if (!chat?.isImporting || !chat?.importJobId) { - setImportStatus(null); - return; + setImportStatus(null); + return; } const poll = async () => { - try { - const data = await httpClient.request(`/import/telegram/status/${chat.importJobId}`); - setImportStatus({ processed: data.processedMessages, total: data.totalMessages, status: data.status }); - if (data.status === 'Completed' || data.status === 'Failed') { - setImportStatus(null); - loadChats(); - } - } catch (e: any) { - if (e.status === 404) { - console.warn('Import job not found'); - } else { - console.error('Failed to poll status', e); - } + try { + const data = await httpClient.request(`/import/telegram/status/${chat.importJobId}`); + setImportStatus({ processed: data.processedMessages, total: data.totalMessages, status: data.status }); + if (data.status === 'Completed' || data.status === 'Failed') { + setImportStatus(null); + loadChats(); } + } catch (e: any) { + if (e.status === 404) { + console.warn('Import job not found'); + } else { + console.error('Failed to poll status', e); + } + } }; poll(); @@ -270,19 +270,19 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal const saveScrollPosition = useCallback((targetChatId?: string) => { const container = scrollContainerRef.current; const chatId = targetChatId || activeChat; - + if (!container || !chatId || !scrollReady || isInitializingRef.current || isScrollingToBottomRef.current) return; // Проверка: сообщения в стейте должны быть от целевого чата if (chatMessages.length > 0 && chatMessages[0].chatId !== chatId) return; - + const isAtBottomNow = container.scrollHeight - container.scrollTop - container.clientHeight < 40; isAtBottomRef.current = isAtBottomNow; - + if (isAtBottomNow) { - localStorage.setItem(`chat_at_bottom_${chatId}`, 'true'); - localStorage.removeItem(`chat_anchor_${chatId}`); - return; + localStorage.setItem(`chat_at_bottom_${chatId}`, 'true'); + localStorage.removeItem(`chat_anchor_${chatId}`); + return; } const messageElements = container.querySelectorAll('[data-message-id]'); @@ -304,8 +304,8 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal } if (anchor && anchor.id) { - localStorage.setItem(`chat_anchor_${chatId}`, JSON.stringify(anchor)); - localStorage.removeItem(`chat_at_bottom_${chatId}`); + localStorage.setItem(`chat_anchor_${chatId}`, JSON.stringify(anchor)); + localStorage.removeItem(`chat_at_bottom_${chatId}`); } }, [activeChat, scrollReady, chatMessages]); @@ -313,7 +313,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal const restoreScrollPosition = useCallback(() => { const container = scrollContainerRef.current; if (!container || !activeChat) return false; - + if (chatMessages.length > 0 && chatMessages[0].chatId !== activeChat) return false; if (chatMessages.length === 0) return true; @@ -330,16 +330,16 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal try { const { id, offset } = JSON.parse(saved); let el = container.querySelector(`[data-message-id="${id}"]`) as HTMLElement; - + // Поиск ближайшего, если точное сообщение еще не загружено if (!el) { - const msgIndex = chatMessages.findIndex(m => m.id === id); - if (msgIndex !== -1) { - for (let i = msgIndex; i < chatMessages.length; i++) { - const nextEl = container.querySelector(`[data-message-id="${chatMessages[i].id}"]`) as HTMLElement; - if (nextEl) { el = nextEl; break; } - } - } + const msgIndex = chatMessages.findIndex(m => m.id === id); + if (msgIndex !== -1) { + for (let i = msgIndex; i < chatMessages.length; i++) { + const nextEl = container.querySelector(`[data-message-id="${chatMessages[i].id}"]`) as HTMLElement; + if (nextEl) { el = nextEl; break; } + } + } } if (el) { @@ -368,7 +368,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal useEffect(() => { if (isLoadingMessages || !scrollContainerRef.current || !activeChat) return; const container = scrollContainerRef.current; - + const observer = new ResizeObserver(() => { if (isInitializingRef.current) return; @@ -405,18 +405,18 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal // Сохраняем позицию старого чата if (prevChatIdRef.current && scrollContainerRef.current && scrollReady) { - saveScrollPosition(prevChatIdRef.current); + saveScrollPosition(prevChatIdRef.current); } - + setScrollReady(false); prevChatIdRef.current = activeChat; - + if (scrollContainerRef.current) { - scrollContainerRef.current.scrollTop = 0; + scrollContainerRef.current.scrollTop = 0; } const timer = setTimeout(() => { - isInitializingRef.current = false; + isInitializingRef.current = false; }, 600); return () => clearTimeout(timer); @@ -443,7 +443,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current); scrollTimeoutRef.current = setTimeout(() => saveScrollPosition(), 150); - + const st = container.scrollTop; const isScrollingUp = st < lastScrollTopRef.current; const stChanged = st !== lastScrollTopRef.current; @@ -473,7 +473,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal const containerRect = container.getBoundingClientRect(); const messageElements = container.querySelectorAll('[data-message-id]'); - + let currentTopMsgId = null; for (const el of messageElements) { const rect = el.getBoundingClientRect(); @@ -506,15 +506,15 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal // Принудительный сброс режима (Второй Клик) localStorage.setItem(`chat_at_bottom_${activeChat}`, 'true'); localStorage.removeItem(`chat_anchor_${activeChat}`); - + isScrollingToBottomRef.current = true; if (scrollContainerRef.current) { - scrollContainerRef.current.scrollTo({ - top: scrollContainerRef.current.scrollHeight, - behavior: 'smooth' - }); + scrollContainerRef.current.scrollTo({ + top: scrollContainerRef.current.scrollHeight, + behavior: 'smooth' + }); } - + setTimeout(() => { isScrollingToBottomRef.current = false; }, 1000); @@ -646,7 +646,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
{/* Background Knot Texture */}
- cloud_download + cloud_download
{/* Chat Empty State View */} @@ -1060,7 +1060,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal setShowTopMenu(false); if (activeChat) { setConfirmAction({ - message: t('deleteChatConfirm'), + message: chat.type === 'group' ? t('deleteGroupChatConfirm') : t('deleteChatConfirm'), action: async () => { try { await ChatApi.deleteChat(activeChat); @@ -1152,16 +1152,16 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
- {importStatus?.status === 'Processing' ? 'Обработка' : - importStatus?.status === 'Queued' ? 'В очереди' : 'Загрузка'} + {importStatus?.status === 'Processing' ? 'Обработка' : + importStatus?.status === 'Queued' ? 'В очереди' : 'Загрузка'} {importStatus?.processed || 0} / {importStatus?.total || 0}
- +
- 1 && (
{chatPinnedMessages.map((_, idx) => ( -
))}
@@ -1226,8 +1226,8 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal {t('pinnedMessage')} {chatPinnedMessages.length > 1 ? `#${(pinnedIndex % chatPinnedMessages.length) + 1}` : ''}

- {chatPinnedMessages[pinnedIndex % chatPinnedMessages.length]?.content || - (chatPinnedMessages[pinnedIndex % chatPinnedMessages.length]?.media?.length > 0 ? t('media') : '...')} + {chatPinnedMessages[pinnedIndex % chatPinnedMessages.length]?.content || + (chatPinnedMessages[pinnedIndex % chatPinnedMessages.length]?.media?.length > 0 ? t('media') : '...')}