From 97a2bee81aec6daa69c366ca4bcdbbf919d238e7daca11a4f946976c072af4cb Mon Sep 17 00:00:00 2001 From: AidarKC Date: Wed, 22 Apr 2026 17:23:20 +0300 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=D1=81=D1=82=D0=B0=D1=80=D1=82=20?= =?UTF-8?q?=D1=81=20=D0=BB=D0=B8=D1=87=D0=BD=D1=8B=D1=85=20=D1=81=D0=BE?= =?UTF-8?q?=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B8=20=D0=B1?= =?UTF-8?q?=D0=B5=D0=B9=D0=B4=D0=B6=20=D0=BD=D0=B5=D0=BF=D1=80=D0=BE=D1=87?= =?UTF-8?q?=D0=B8=D1=82=D0=B0=D0=BD=D0=BD=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shine-UI/js/app.js | 13 +++++++++---- shine-UI/js/components/toolbar.js | 24 +++++++++++++++++++++++- shine-UI/styles/components.css | 25 +++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/shine-UI/js/app.js b/shine-UI/js/app.js index 79d46ea..80d6f01 100644 --- a/shine-UI/js/app.js +++ b/shine-UI/js/app.js @@ -448,7 +448,7 @@ function renderPageFailureFallback(pageId, error) { function renderApp() { const route = getRoute(); - const pageId = route.pageId || (state.session.isAuthorized ? 'profile-view' : 'start-view'); + const pageId = route.pageId || (state.session.isAuthorized ? 'messages-list' : 'start-view'); if (!state.session.isAuthorized && !PRE_AUTH_PAGES.includes(pageId)) { navigate('start-view'); @@ -456,7 +456,7 @@ function renderApp() { } if (state.session.isAuthorized && PRE_AUTH_PAGES.includes(pageId)) { - navigate('profile-view'); + navigate('messages-list'); return; } @@ -629,6 +629,8 @@ async function init() { ? new TextDecoder().decode(parsed.payloadBytes || new Uint8Array(0)) : ''; + let shouldRefreshToolbarUnread = false; + if (messageType === 1 || messageType === 2) { const isIncomingForCurrent = messageType === 1; const added = addSignedMessageToChat({ @@ -651,6 +653,9 @@ async function init() { details: { messageKey, baseKey: parsed.baseKey, messageType }, }); } + if (added && isIncomingForCurrent) { + shouldRefreshToolbarUnread = true; + } if (added && isIncomingForCurrent && Notification.permission === 'granted' && !payload.backlog) { try { new Notification(`Сообщение от ${fromLogin}`, { body: text || '' }); @@ -691,7 +696,7 @@ async function init() { } const pageId = getRoute().pageId || ''; - if (pageId === 'chat-view' || pageId === 'messages-list') { + if (pageId === 'chat-view' || pageId === 'messages-list' || shouldRefreshToolbarUnread) { renderApp(); } }); @@ -773,7 +778,7 @@ async function init() { await ensureSessionRuntimeStarted(); if (!window.location.hash) { - navigate(state.session.isAuthorized ? 'profile-view' : 'start-view'); + navigate(state.session.isAuthorized ? 'messages-list' : 'start-view'); } else { renderApp(); } diff --git a/shine-UI/js/components/toolbar.js b/shine-UI/js/components/toolbar.js index ee71320..7b3e907 100644 --- a/shine-UI/js/components/toolbar.js +++ b/shine-UI/js/components/toolbar.js @@ -1,4 +1,5 @@ import { resolveToolbarActive } from '../router.js'; +import { state } from '../state.js'; const ITEMS = [ { pageId: 'messages-list', label: 'Личные сообщения', icon: '💬' }, @@ -8,15 +9,29 @@ const ITEMS = [ { pageId: 'profile-view', label: 'Профиль', icon: '👤' }, ]; +function getTotalUnreadMessages() { + const chats = Object.values(state.chats || {}); + let total = 0; + chats.forEach((messages) => { + if (!Array.isArray(messages)) return; + messages.forEach((msg) => { + if (msg?.from === 'in' && msg?.unread) total += 1; + }); + }); + return total; +} + export function renderToolbar(currentPageId, navigate) { const root = document.createElement('nav'); root.className = 'toolbar'; const active = resolveToolbarActive(currentPageId); + const unreadTotal = getTotalUnreadMessages(); ITEMS.forEach((item) => { const btn = document.createElement('button'); const isProfile = item.pageId === 'profile-view'; - btn.className = `toolbar-btn${item.pageId === active ? ' active' : ''}${isProfile ? ' toolbar-btn-profile' : ''}`; + const isMessages = item.pageId === 'messages-list'; + btn.className = `toolbar-btn${item.pageId === active ? ' active' : ''}${isProfile ? ' toolbar-btn-profile' : ''}${isMessages ? ' toolbar-btn-messages' : ''}`; if (isProfile) { btn.innerHTML = ` ${item.icon} @@ -31,6 +46,13 @@ export function renderToolbar(currentPageId, navigate) { } else { btn.innerHTML = `${item.icon}${item.label}`; } + if (isMessages && unreadTotal > 0) { + const badge = document.createElement('span'); + badge.className = 'toolbar-unread-badge'; + badge.textContent = unreadTotal > 99 ? '99+' : String(unreadTotal); + badge.setAttribute('aria-label', `Непрочитанных сообщений: ${badge.textContent}`); + btn.append(badge); + } btn.addEventListener('click', () => navigate(item.pageId)); root.append(btn); }); diff --git a/shine-UI/styles/components.css b/shine-UI/styles/components.css index 7914932..a481b7d 100644 --- a/shine-UI/styles/components.css +++ b/shine-UI/styles/components.css @@ -621,6 +621,31 @@ position: relative; } +.toolbar-btn-messages { + position: relative; +} + +.toolbar-unread-badge { + position: absolute; + top: 2px; + right: 6px; + min-width: 16px; + height: 16px; + border-radius: 999px; + padding: 0 5px; + display: inline-flex; + align-items: center; + justify-content: center; + background: #f07f8a; + color: #fff2f4; + border: 1px solid rgba(255, 222, 227, 0.55); + box-shadow: 0 4px 10px rgba(152, 36, 52, 0.35); + font-size: 10px; + font-weight: 700; + line-height: 1; + pointer-events: none; +} + .toolbar-btn-profile .toolbar-label-wrap { padding-bottom: 10px; }