Обновить UI-кнопки и прокрутку

This commit is contained in:
2026-08-22 11:28:12 +03:00
parent d3e6aa2be2
commit bc8c7f318b
9 changed files with 286 additions and 26 deletions
-4
View File
@@ -1,5 +1,4 @@
import { renderHeader } from '../components/header.js';
import { attachScrollToBottomButton } from '../components/scroll-to-bottom-button.js';
import { authService, getMessageReactionState, setMessageReactionState, state } from '../state.js';
import { captureClientError } from '../services/client-error-reporter.js';
import { toUserMessage } from '../services/ui-error-texts.js';
@@ -1144,7 +1143,6 @@ export function render({ navigate, route, chrome }) {
screen.className = 'stack channels-screen channels-screen--thread';
const appScreen = document.getElementById('app-screen');
appScreen?.classList.add('channels-scroll-clean');
const scrollToBottomControl = attachScrollToBottomButton({ scrollContainer: appScreen });
const header = renderHeader({
title: '',
@@ -1356,7 +1354,6 @@ export function render({ navigate, route, chrome }) {
invalid.textContent = 'Некорректный идентификатор треда в адресе страницы.';
screen.append(invalid);
screen.cleanup = () => {
scrollToBottomControl.cleanup();
appScreen?.classList.remove('channels-scroll-clean');
};
return screen;
@@ -1545,7 +1542,6 @@ export function render({ navigate, route, chrome }) {
})();
screen.cleanup = () => {
scrollToBottomControl.cleanup();
appScreen?.classList.remove('channels-scroll-clean');
};
-3
View File
@@ -1,5 +1,4 @@
import { renderHeader } from '../components/header.js';
import { attachScrollToBottomButton } from '../components/scroll-to-bottom-button.js';
import {
authService,
getMessageReactionState,
@@ -2042,7 +2041,6 @@ export function render({ navigate, route, chrome }) {
screen.className = 'stack channels-screen channels-screen--channel';
const appScreen = document.getElementById('app-screen');
appScreen?.classList.add('channels-scroll-clean');
const scrollToBottomControl = attachScrollToBottomButton({ scrollContainer: appScreen });
const statusBox = document.createElement('div');
statusBox.className = 'card status-line is-unavailable channels-status';
@@ -2488,7 +2486,6 @@ export function render({ navigate, route, chrome }) {
})();
screen.cleanup = () => {
scrollToBottomControl.cleanup();
appScreen?.classList.remove('channels-scroll-clean');
if (typeof cleanupSeenTracking === 'function') cleanupSeenTracking();
};
-6
View File
@@ -1,5 +1,4 @@
import { renderHeader } from '../components/header.js';
import { attachScrollToBottomButton } from '../components/scroll-to-bottom-button.js';
import { directMessages } from '../mock-data.js';
import {
addAppLogEntry,
@@ -907,10 +906,6 @@ export function render({ navigate, route, chrome }) {
const wrap = document.createElement('div');
wrap.className = 'chat-wrap dm-chat-wrap';
const scrollToBottomControl = attachScrollToBottomButton({
scrollContainer: () => boundScrollContainer || wrap,
});
const historyLoader = document.createElement('div');
historyLoader.className = 'dm-history-loader';
historyLoader.hidden = true;
@@ -1563,7 +1558,6 @@ export function render({ navigate, route, chrome }) {
void loadHistoryPage({ preserveScroll: true });
});
screen.cleanup = () => {
scrollToBottomControl.cleanup();
hideUnreadSeparator({ rerender: false });
stopAllTwemojiAnimations();
window.removeEventListener('shine-chat-messages-updated', handleIncomingChatRefresh);
+38 -8
View File
@@ -306,8 +306,20 @@ export function render({ navigate, chrome } = {}) {
const tabs = document.createElement('div');
tabs.className = 'tabs';
tabs.innerHTML = `
<button class="tab-btn ${state.notificationsTab === 'replies' ? 'active' : ''}" data-tab="replies">Ответы</button>
<button class="tab-btn ${state.notificationsTab === 'events' ? 'active' : ''}" data-tab="events">События</button>
<button
type="button"
class="tab-btn notification-tab-btn ${state.notificationsTab === 'replies' ? 'active' : ''}"
data-tab="replies"
data-selected="${state.notificationsTab === 'replies' ? 'true' : 'false'}"
aria-selected="${state.notificationsTab === 'replies' ? 'true' : 'false'}"
>Ответы</button>
<button
type="button"
class="tab-btn notification-tab-btn ${state.notificationsTab === 'events' ? 'active' : ''}"
data-tab="events"
data-selected="${state.notificationsTab === 'events' ? 'true' : 'false'}"
aria-selected="${state.notificationsTab === 'events' ? 'true' : 'false'}"
>События</button>
`;
const list = document.createElement('div');
@@ -347,13 +359,31 @@ export function render({ navigate, chrome } = {}) {
}
}
tabs.querySelectorAll('.tab-btn').forEach((btn) => {
function setActiveNotificationTab(nextTab) {
const normalizedTab = nextTab === 'events' ? 'events' : 'replies';
state.notificationsTab = normalizedTab;
tabs.querySelectorAll('.notification-tab-btn').forEach((node) => {
const selected = node.dataset.tab === normalizedTab;
node.classList.toggle('active', selected);
node.dataset.selected = selected ? 'true' : 'false';
node.setAttribute('aria-selected', selected ? 'true' : 'false');
});
}
// Активная кнопка остаётся визуально нажатой до выбора второй вкладки.
// При переключении класс active и aria-selected синхронно переходят на новую кнопку.
setActiveNotificationTab(state.notificationsTab);
tabs.querySelectorAll('.notification-tab-btn').forEach((btn) => {
btn.addEventListener('click', () => {
const nextTab = String(btn.dataset.tab || 'replies');
if (state.notificationsTab === nextTab) return;
state.notificationsTab = nextTab;
tabs.querySelectorAll('.tab-btn').forEach((node) => node.classList.remove('active'));
btn.classList.add('active');
const nextTab = btn.dataset.tab === 'events' ? 'events' : 'replies';
if (state.notificationsTab === nextTab) {
setActiveNotificationTab(nextTab);
return;
}
setActiveNotificationTab(nextTab);
void load();
});
});