diff --git a/web/src/components/friends/FriendRow.tsx b/web/src/components/friends/FriendRow.tsx index e1dac59..79e472b 100644 --- a/web/src/components/friends/FriendRow.tsx +++ b/web/src/components/friends/FriendRow.tsx @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'; import { removeRelationship, type RelationshipUser } from '@/api/friends'; import { Avatar } from '@/components/ui/Avatar'; +import { ErrorNotice } from '@/components/ui/ErrorNotice'; import { Menu, MenuItem, MenuSeparator } from '@/components/ui/Menu'; import { Popover } from '@/components/ui/Popover'; import { Button } from '@/components/ui/primitives'; @@ -131,6 +132,12 @@ export function FriendRow({ friend }: { friend: RelationshipUser }) {

{hint}

+ {openDm.isError || remove.isError ? ( + + + + ) : null} + setSearchOpen((value) => !value)} > - {t('friends.empty.search')} + {t('friends.search.open')} diff --git a/web/src/components/friends/useDmUnread.ts b/web/src/components/friends/useDmUnread.ts index 0d5a81f..dc2934b 100644 --- a/web/src/components/friends/useDmUnread.ts +++ b/web/src/components/friends/useDmUnread.ts @@ -6,10 +6,14 @@ import { unreadCount, useMessagesStore, type ChannelMessagesState } from '@/stor /** * Непрочитанные личной беседы. * - * Для беседы с загруженной историей счётчик считается как у комнат - * (`unreadCount`). Пока история не загружена, сравниваем `last_message_id` - * канала с отметкой чтения из READY/`READ_STATE_UPDATE`: точного числа в этом - * случае нет, поэтому `exact = false` — в сайдбаре рисуем точку, а не цифру. + * Два сигнала, как у комнат: + * - загруженная история — точное число (`unreadCount`); + * - `last_message_id` канала против отметки чтения из READY / + * `READ_STATE_UPDATE` — когда история ещё не загружена, точного числа нет, + * поэтому в сайдбаре рисуем точку (`exact = false`), а не цифру. + * + * Сигналы объединяются через максимум: сообщение в уже открытой беседе могло + * прийти событием, когда история загружена, а отметки чтения ещё нет. */ export function useDmUnread(channel: Channel): { unread: number; @@ -22,14 +26,17 @@ export function useDmUnread(channel: Channel): { [channel.id], ), ); + // Отметки чтения известны после снапшота READY: отсутствие записи для + // беседы означает «не читалась». + const snapshotApplied = useMessagesStore((state) => state.snapshotApplied); const mentions = record?.mentionCount ?? 0; - if (record !== undefined && record.messages.length > 0) { - return { unread: unreadCount(record), mentions, exact: true }; - } - - const known = record?.readStateKnown === true; + const hasHistory = record !== undefined && record.messages.length > 0; + const fromHistory = hasHistory ? unreadCount(record) : 0; + const known = record?.readStateKnown === true || snapshotApplied; const marker = record?.lastReadMessageId ?? null; const hasNewer = channel.last_message_id !== undefined && channel.last_message_id !== marker; - return { unread: known && hasNewer ? 1 : 0, mentions, exact: false }; + const unread = Math.max(fromHistory, known && hasNewer ? 1 : 0); + + return { unread, mentions, exact: fromHistory > 0 }; } diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 56dce3c..c1cc698 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -176,7 +176,8 @@ }, "empty": { "title": "You have no servers yet", - "body": "Create your own server or join one with an invite." + "body": "Create your own server or join one with an invite.", + "create": "Create your own server" }, "leave": "Leave server", "leaveConfirm": "Leave the server “{{name}}”?", @@ -249,7 +250,8 @@ "adding": "Sending…", "requested": "Request sent", "friend": "Already friends", - "message": "Message" + "message": "Message", + "open": "Search friends" }, "requests": { "incoming": "Incoming requests", diff --git a/web/src/i18n/locales/ru.json b/web/src/i18n/locales/ru.json index 506aec1..f1986c1 100644 --- a/web/src/i18n/locales/ru.json +++ b/web/src/i18n/locales/ru.json @@ -176,7 +176,8 @@ }, "empty": { "title": "У вас пока нет серверов", - "body": "Создайте свой сервер или присоединитесь по приглашению." + "body": "Создайте свой сервер или присоединитесь по приглашению.", + "create": "Создать свой сервер" }, "leave": "Покинуть сервер", "leaveConfirm": "Покинуть сервер «{{name}}»?", @@ -249,7 +250,8 @@ "adding": "Отправляем…", "requested": "Заявка отправлена", "friend": "Уже в друзьях", - "message": "Написать" + "message": "Написать", + "open": "Поиск друзей" }, "requests": { "incoming": "Входящие заявки", diff --git a/web/src/pages/app/FriendsSidebar.tsx b/web/src/pages/app/FriendsSidebar.tsx index bbd227e..5de10a3 100644 --- a/web/src/pages/app/FriendsSidebar.tsx +++ b/web/src/pages/app/FriendsSidebar.tsx @@ -137,7 +137,9 @@ export function FriendsSidebar({ channelId }: { channelId: string | null }) { {t('friends.dmSection')} {direct.length === 0 ? ( -

{t('friends.dmEmpty')}

+

+ {t('friends.dmEmpty')} +

) : (