SHA256
Compare commits
2
Commits
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
f0b8fac598 | ||
|
|
3ae9d9175f |
@@ -1,3 +1,5 @@
|
|||||||
|
import org.bouncycastle.util.Properties
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'application'
|
id 'application'
|
||||||
|
|||||||
@@ -952,6 +952,9 @@ function closeTopChannelsMenu(listState) {
|
|||||||
listState.topMenuCleanup();
|
listState.topMenuCleanup();
|
||||||
}
|
}
|
||||||
listState.topMenuCleanup = null;
|
listState.topMenuCleanup = null;
|
||||||
|
listState.topMenuAnchor?.setAttribute('aria-expanded', 'false');
|
||||||
|
listState.topMenuAnchor?.classList.remove('menu-open-pressed');
|
||||||
|
listState.topMenuAnchor = null;
|
||||||
const root = document.getElementById('modal-root');
|
const root = document.getElementById('modal-root');
|
||||||
if (root) {
|
if (root) {
|
||||||
const overlay = root.querySelector(`#${TOP_MENU_OVERLAY_ID}`);
|
const overlay = root.querySelector(`#${TOP_MENU_OVERLAY_ID}`);
|
||||||
@@ -1002,9 +1005,6 @@ function openTopChannelsMenu({
|
|||||||
if (item.divider) {
|
if (item.divider) {
|
||||||
const divider = document.createElement('div');
|
const divider = document.createElement('div');
|
||||||
divider.className = 'channel-menu-divider';
|
divider.className = 'channel-menu-divider';
|
||||||
divider.style.height = '1px';
|
|
||||||
divider.style.background = 'rgba(255,255,255,0.08)';
|
|
||||||
divider.style.margin = '6px 0';
|
|
||||||
menu.append(divider);
|
menu.append(divider);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1021,6 +1021,9 @@ function openTopChannelsMenu({
|
|||||||
|
|
||||||
overlay.append(menu);
|
overlay.append(menu);
|
||||||
root.append(overlay);
|
root.append(overlay);
|
||||||
|
anchorEl.setAttribute('aria-expanded', 'true');
|
||||||
|
anchorEl.classList.add('menu-open-pressed');
|
||||||
|
listState.topMenuAnchor = anchorEl;
|
||||||
|
|
||||||
const onOverlayClick = (event) => {
|
const onOverlayClick = (event) => {
|
||||||
if (event.target === overlay) closeTopChannelsMenu(listState);
|
if (event.target === overlay) closeTopChannelsMenu(listState);
|
||||||
@@ -1354,6 +1357,7 @@ export function render({ navigate, route, chrome }) {
|
|||||||
const listState = {
|
const listState = {
|
||||||
openMenuId: null,
|
openMenuId: null,
|
||||||
topMenuCleanup: null,
|
topMenuCleanup: null,
|
||||||
|
topMenuAnchor: null,
|
||||||
notificationsState,
|
notificationsState,
|
||||||
revealedCounters: new Set(),
|
revealedCounters: new Set(),
|
||||||
channels: [],
|
channels: [],
|
||||||
@@ -1393,11 +1397,16 @@ export function render({ navigate, route, chrome }) {
|
|||||||
topMenuBtn.type = 'button';
|
topMenuBtn.type = 'button';
|
||||||
topMenuBtn.className = 'icon-btn channels-top-more-btn';
|
topMenuBtn.className = 'icon-btn channels-top-more-btn';
|
||||||
topMenuBtn.setAttribute('aria-label', 'Ещё действия');
|
topMenuBtn.setAttribute('aria-label', 'Ещё действия');
|
||||||
|
topMenuBtn.setAttribute('aria-haspopup', 'menu');
|
||||||
|
topMenuBtn.setAttribute('aria-expanded', 'false');
|
||||||
topMenuBtn.title = 'Ещё действия';
|
topMenuBtn.title = 'Ещё действия';
|
||||||
topMenuBtn.append(createOverflowDots());
|
topMenuBtn.append(createOverflowDots());
|
||||||
topMenuBtn.addEventListener('click', (event) => {
|
topMenuBtn.addEventListener('click', (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
animatePress(topMenuBtn);
|
if (listState.topMenuAnchor === topMenuBtn) {
|
||||||
|
closeTopChannelsMenu(listState);
|
||||||
|
return;
|
||||||
|
}
|
||||||
openTopChannelsMenu({
|
openTopChannelsMenu({
|
||||||
listState,
|
listState,
|
||||||
anchorEl: topMenuBtn,
|
anchorEl: topMenuBtn,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { renderHeader } from '../components/header.js';
|
import { renderHeader } from '../components/header.js';
|
||||||
|
import { createOverflowDots } from '../components/overflow-dots.js';
|
||||||
import { authService, state } from '../state.js';
|
import { authService, state } from '../state.js';
|
||||||
import { makeProfileRoute } from '../services/shine-routes.js';
|
import { makeProfileRoute } from '../services/shine-routes.js';
|
||||||
import { makeProfileLinksRoute } from '../services/shine-routes.js';
|
import { makeProfileLinksRoute } from '../services/shine-routes.js';
|
||||||
@@ -457,18 +458,113 @@ export function render({ navigate, route, chrome } = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let topMenuPortal = null;
|
||||||
|
let topMenuBackdrop = null;
|
||||||
|
let topMenuButton = null;
|
||||||
|
|
||||||
|
const closeTopMenu = () => {
|
||||||
|
topMenuPortal?.remove();
|
||||||
|
topMenuBackdrop?.remove();
|
||||||
|
topMenuPortal = null;
|
||||||
|
topMenuBackdrop = null;
|
||||||
|
topMenuButton?.setAttribute('aria-expanded', 'false');
|
||||||
|
topMenuButton?.classList.remove('menu-open-pressed');
|
||||||
|
};
|
||||||
|
|
||||||
|
const positionTopMenu = () => {
|
||||||
|
if (!topMenuPortal || !topMenuButton) return;
|
||||||
|
const rect = topMenuButton.getBoundingClientRect();
|
||||||
|
const margin = 10;
|
||||||
|
const menuRect = topMenuPortal.getBoundingClientRect();
|
||||||
|
const menuWidth = menuRect.width || topMenuPortal.offsetWidth || 190;
|
||||||
|
const menuHeight = menuRect.height || topMenuPortal.offsetHeight || 56;
|
||||||
|
const left = Math.max(margin, Math.min(window.innerWidth - menuWidth - margin, rect.right - menuWidth));
|
||||||
|
const below = rect.bottom + 7;
|
||||||
|
const top = below + menuHeight <= window.innerHeight - margin
|
||||||
|
? below
|
||||||
|
: Math.max(margin, rect.top - menuHeight - 7);
|
||||||
|
topMenuPortal.style.left = `${Math.round(left)}px`;
|
||||||
|
topMenuPortal.style.top = `${Math.round(top)}px`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openTopMenu = () => {
|
||||||
|
if (!topMenuButton || topMenuPortal) return;
|
||||||
|
const portal = document.createElement('div');
|
||||||
|
portal.className = 'dm-head-menu dm-head-menu--portal network-head-menu';
|
||||||
|
portal.setAttribute('role', 'menu');
|
||||||
|
portal.innerHTML = `
|
||||||
|
<button type="button" class="dm-head-menu-item network-head-menu-item" role="menuitem" data-action="find-person">
|
||||||
|
<span>Найти человека</span>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
portal.querySelector('[data-action="find-person"]')?.addEventListener('click', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
closeTopMenu();
|
||||||
|
openSearchModal();
|
||||||
|
});
|
||||||
|
portal.addEventListener('click', (event) => event.stopPropagation());
|
||||||
|
const backdrop = document.createElement('div');
|
||||||
|
backdrop.className = 'ui-menu-dim-layer';
|
||||||
|
backdrop.addEventListener('click', closeTopMenu);
|
||||||
|
document.body.append(backdrop, portal);
|
||||||
|
topMenuBackdrop = backdrop;
|
||||||
|
topMenuPortal = portal;
|
||||||
|
topMenuButton.setAttribute('aria-expanded', 'true');
|
||||||
|
topMenuButton.classList.add('menu-open-pressed');
|
||||||
|
positionTopMenu();
|
||||||
|
requestAnimationFrame(positionTopMenu);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleTopMenu = (event) => {
|
||||||
|
event?.preventDefault?.();
|
||||||
|
event?.stopPropagation?.();
|
||||||
|
if (topMenuPortal) closeTopMenu();
|
||||||
|
else openTopMenu();
|
||||||
|
};
|
||||||
|
|
||||||
const header = renderHeader({
|
const header = renderHeader({
|
||||||
title: 'Связи',
|
title: 'Связи',
|
||||||
rightActions: [
|
rightActions: [
|
||||||
{ label: 'Найти', onClick: openSearchModal },
|
{
|
||||||
|
iconNode: createOverflowDots(),
|
||||||
|
className: 'network-top-more-btn',
|
||||||
|
title: 'Ещё действия',
|
||||||
|
ariaLabel: 'Меню связей',
|
||||||
|
onClick: toggleTopMenu,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
// «Связи» используют тот же общий topbar, что и остальные страницы.
|
// «Связи» используют тот же общий topbar, что и остальные страницы.
|
||||||
// Отдельный класс нужен только для page-specific fade графа, не для геометрии header.
|
// Отдельный класс нужен только для page-specific fade графа, не для геометрии header.
|
||||||
header.classList.add('network-topbar');
|
header.classList.add('network-topbar');
|
||||||
|
topMenuButton = header.querySelector('.network-top-more-btn');
|
||||||
|
topMenuButton?.setAttribute('aria-haspopup', 'menu');
|
||||||
|
topMenuButton?.setAttribute('aria-expanded', 'false');
|
||||||
|
|
||||||
|
const onTopMenuOutsideClick = (event) => {
|
||||||
|
if (!topMenuPortal) return;
|
||||||
|
if (topMenuPortal.contains(event.target) || topMenuButton?.contains(event.target)) return;
|
||||||
|
closeTopMenu();
|
||||||
|
};
|
||||||
|
const onTopMenuKeydown = (event) => {
|
||||||
|
if (event.key !== 'Escape' || !topMenuPortal) return;
|
||||||
|
closeTopMenu();
|
||||||
|
topMenuButton?.focus();
|
||||||
|
};
|
||||||
|
const onTopMenuViewportChange = () => positionTopMenu();
|
||||||
|
document.addEventListener('click', onTopMenuOutsideClick);
|
||||||
|
document.addEventListener('keydown', onTopMenuKeydown);
|
||||||
|
window.addEventListener('resize', onTopMenuViewportChange, { passive: true });
|
||||||
|
window.addEventListener('scroll', onTopMenuViewportChange, { passive: true, capture: true });
|
||||||
|
|
||||||
// Ресайз и перерисовку рёбер движок обрабатывает сам (window resize + ResizeObserver внутри).
|
// Ресайз и перерисовку рёбер движок обрабатывает сам (window resize + ResizeObserver внутри).
|
||||||
screen.cleanup = () => {
|
screen.cleanup = () => {
|
||||||
|
closeTopMenu();
|
||||||
|
document.removeEventListener('click', onTopMenuOutsideClick);
|
||||||
|
document.removeEventListener('keydown', onTopMenuKeydown);
|
||||||
|
window.removeEventListener('resize', onTopMenuViewportChange);
|
||||||
|
window.removeEventListener('scroll', onTopMenuViewportChange, true);
|
||||||
if (engine) engine.destroy();
|
if (engine) engine.destroy();
|
||||||
engine = null;
|
engine = null;
|
||||||
appScreenEl?.classList.remove('network-scroll-lock');
|
appScreenEl?.classList.remove('network-scroll-lock');
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ body::before {
|
|||||||
--toolbar-height: 78px;
|
--toolbar-height: 78px;
|
||||||
--keyboard-offset: 0px;
|
--keyboard-offset: 0px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-left: 1px solid rgba(211, 170, 86, 0.2);
|
border-left: 0;
|
||||||
border-right: 1px solid rgba(211, 170, 86, 0.2);
|
border-right: 0;
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user