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;
}