SHA256
Исправить профиль и список каналов
This commit is contained in:
@@ -119,14 +119,14 @@ public final class UserProfileStateDAO {
|
||||
sql="""
|
||||
SELECT cn.owner_login, cn.slug, cn.display_name, cn.ava_ar, cn.owner_bch_name, cn.channel_root_block_number, encode(cn.channel_root_block_hash,'hex') root_hash
|
||||
FROM channel_names_state cn WHERE LOWER(cn.owner_login)=LOWER(?) AND cn.channel_type_code=1
|
||||
ORDER BY LOWER(cn.display_name), cn.slug LIMIT ? OFFSET ?
|
||||
ORDER BY cn.display_name, cn.slug LIMIT ? OFFSET ?
|
||||
""";
|
||||
} else if ("following".equals(mode)) {
|
||||
sql="""
|
||||
SELECT DISTINCT cn.owner_login, cn.slug, cn.display_name, cn.ava_ar, cn.owner_bch_name, cn.channel_root_block_number, encode(cn.channel_root_block_hash,'hex') root_hash
|
||||
FROM connections_state cs JOIN channel_names_state cn ON cn.owner_bch_name=cs.to_bch_name AND cn.channel_root_block_number=cs.to_block_number AND cn.channel_root_block_hash=cs.to_block_hash
|
||||
WHERE LOWER(cs.login)=LOWER(?) AND cs.rel_type=30 AND cn.channel_type_code=1
|
||||
ORDER BY LOWER(cn.display_name), cn.slug LIMIT ? OFFSET ?
|
||||
ORDER BY cn.display_name, cn.slug LIMIT ? OFFSET ?
|
||||
""";
|
||||
} else throw new IllegalArgumentException("Unsupported channel mode: "+mode);
|
||||
List<ChannelCard> out=new ArrayList<>();
|
||||
|
||||
@@ -13,8 +13,8 @@ function parseAvatar(raw) {
|
||||
}
|
||||
|
||||
const TITLES = {
|
||||
friends: 'Друзья', close_friends: 'Близкие друзья', primary_received: 'Подтвердили основной аккаунт',
|
||||
primary_given: 'Подтверждённые аккаунты', shine_received: 'Считают сияющим', shine_given: 'Подтверждённые сияющие',
|
||||
friends: 'Друзья', close_friends: 'Близкие друзья', primary_received: 'Подтвердили аккаунт',
|
||||
primary_given: 'Подтверждённые аккаунты', shine_received: 'Подтвердили сияние', shine_given: 'Подтверждённые сияющие',
|
||||
channels_owned: 'Каналы', channels_following: 'Подписки на каналы',
|
||||
};
|
||||
|
||||
|
||||
@@ -38,31 +38,12 @@ function metricHtml({ kind, label, value, glow = false, positionClass = '' }) {
|
||||
</button>`;
|
||||
}
|
||||
|
||||
function openProfileSheet(title, html) {
|
||||
const root = document.getElementById('modal-root');
|
||||
if (!root) return;
|
||||
root.innerHTML = `
|
||||
<div class="user-profile-sheet-backdrop" id="user-profile-sheet-backdrop">
|
||||
<section class="user-profile-sheet" role="dialog" aria-modal="true" aria-label="${escapeHtml(title)}">
|
||||
<div class="user-profile-sheet-handle" aria-hidden="true"></div>
|
||||
<div class="user-profile-sheet-title">${escapeHtml(title)}</div>
|
||||
<div class="user-profile-sheet-content">${html}</div>
|
||||
</section>
|
||||
</div>`;
|
||||
|
||||
const close = () => {
|
||||
if (root.querySelector('#user-profile-sheet-backdrop')) root.innerHTML = '';
|
||||
};
|
||||
root.querySelector('#user-profile-sheet-backdrop')?.addEventListener('click', (event) => {
|
||||
if (event.target?.id === 'user-profile-sheet-backdrop') close();
|
||||
});
|
||||
function spiritualPathDetailHtml(card) {
|
||||
const value = String(card?.spiritualPath || '').trim();
|
||||
return `<div class="user-profile-detail-copy${value ? '' : ' is-muted'}">${escapeHtml(value || 'Не заполнено')}</div>`;
|
||||
}
|
||||
|
||||
function spiritualPathSheetHtml(card) {
|
||||
return `<div class="user-profile-sheet-copy">${escapeHtml(card?.spiritualPath || 'Не заполнено')}</div>`;
|
||||
}
|
||||
|
||||
function contactsSheetHtml(card) {
|
||||
function contactsDetailHtml(card) {
|
||||
const rows = [
|
||||
['Ссылки', card?.web],
|
||||
['Телефон', card?.phone],
|
||||
@@ -70,7 +51,7 @@ function contactsSheetHtml(card) {
|
||||
].filter(([, value]) => String(value || '').trim());
|
||||
|
||||
if (!rows.length) {
|
||||
return '<div class="user-profile-sheet-copy is-muted">Не заполнено</div>';
|
||||
return '<div class="user-profile-detail-copy is-muted">Не заполнено</div>';
|
||||
}
|
||||
return rows.map(([label, value]) => `
|
||||
<div class="user-profile-contact-row">
|
||||
@@ -232,10 +213,15 @@ export function render({ navigate, route, chrome }) {
|
||||
</div>
|
||||
</div>` : ''}
|
||||
|
||||
<div class="user-profile-detail-links">
|
||||
<button type="button" data-profile-detail="spiritual-path">Духовный путь</button>
|
||||
<button type="button" data-profile-detail="contacts">Контакты</button>
|
||||
</div>`;
|
||||
<div class="user-profile-detail-links" aria-label="Дополнительная информация о пользователе">
|
||||
<button type="button" data-profile-detail="spiritual-path" aria-pressed="false" aria-controls="user-profile-detail-panel">
|
||||
<span class="user-profile-detail-tab-label">Духовный путь</span>
|
||||
</button>
|
||||
<button type="button" data-profile-detail="contacts" aria-pressed="false" aria-controls="user-profile-detail-panel">
|
||||
<span class="user-profile-detail-tab-label">Контакты</span>
|
||||
</button>
|
||||
</div>
|
||||
<section class="user-profile-detail-panel" id="user-profile-detail-panel" aria-live="polite" hidden></section>`;
|
||||
|
||||
const avatarSlot = body.querySelector('.user-profile-avatar-slot');
|
||||
avatarSlot?.append(renderUserAvatar({
|
||||
@@ -268,12 +254,27 @@ export function render({ navigate, route, chrome }) {
|
||||
}
|
||||
|
||||
const detailButton = event.target.closest('[data-profile-detail]');
|
||||
if (detailButton?.dataset.profileDetail === 'spiritual-path') {
|
||||
openProfileSheet('Духовный путь', spiritualPathSheetHtml(card));
|
||||
if (detailButton) {
|
||||
const detailKind = detailButton.dataset.profileDetail;
|
||||
const detailPanel = body.querySelector('#user-profile-detail-panel');
|
||||
if (!detailPanel) return;
|
||||
|
||||
body.querySelectorAll('[data-profile-detail]').forEach((button) => {
|
||||
const active = button === detailButton;
|
||||
button.classList.toggle('is-active', active);
|
||||
button.setAttribute('aria-pressed', active ? 'true' : 'false');
|
||||
});
|
||||
|
||||
if (detailKind === 'spiritual-path') {
|
||||
detailPanel.innerHTML = spiritualPathDetailHtml(card);
|
||||
} else if (detailKind === 'contacts') {
|
||||
detailPanel.innerHTML = contactsDetailHtml(card);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (detailButton?.dataset.profileDetail === 'contacts') {
|
||||
openProfileSheet('Контакты', contactsSheetHtml(card));
|
||||
|
||||
detailPanel.hidden = false;
|
||||
detailPanel.dataset.activeDetail = detailKind;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -340,8 +341,6 @@ export function render({ navigate, route, chrome }) {
|
||||
|
||||
screen.cleanup = () => {
|
||||
document.removeEventListener('pointerdown', handleOutsidePointer);
|
||||
const root = document.getElementById('modal-root');
|
||||
if (root?.querySelector('#user-profile-sheet-backdrop')) root.innerHTML = '';
|
||||
};
|
||||
|
||||
return screen;
|
||||
|
||||
@@ -9912,7 +9912,7 @@ body.chat-topbar-overlay .composer-slot > * {
|
||||
|
||||
.user-profile-full-name {
|
||||
width: min(88%, 340px);
|
||||
margin: 0 auto clamp(8px, 1.4vh, 12px);
|
||||
margin: -5px auto clamp(13px, 2vh, 17px);
|
||||
color: rgba(239, 246, 255, 0.94);
|
||||
font-size: clamp(16px, 4.5vw, 19px);
|
||||
font-weight: 700;
|
||||
@@ -11340,8 +11340,8 @@ body.chat-topbar-overlay .composer-slot {
|
||||
.profile-stats-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
|
||||
.profile-stat-card{display:flex;flex-direction:column;align-items:flex-start;gap:3px;text-align:left;cursor:pointer}
|
||||
.profile-stat-card b{font-size:22px}.profile-stat-card span{font-size:12px;opacity:.78}
|
||||
.profile-list-row{width:100%;align-items:center;gap:12px;text-align:left;cursor:pointer}
|
||||
.profile-list-row-text{display:flex;flex-direction:column;gap:3px;min-width:0}.profile-list-row-text small{opacity:.68}
|
||||
.profile-list-row{width:100%;align-items:center;justify-content:flex-start;gap:12px;text-align:left;cursor:pointer}
|
||||
.profile-list-row-text{display:flex;flex:1 1 auto;flex-direction:column;align-items:flex-start;gap:3px;min-width:0;overflow:hidden}.profile-list-row-text b,.profile-list-row-text small{display:block;width:100%;text-align:left;overflow-wrap:anywhere}.profile-list-row-text small{opacity:.68}
|
||||
.profile-bottom-actions{justify-content:center;gap:12px;position:sticky;bottom:12px;z-index:3}
|
||||
.profile-about:empty{display:none}
|
||||
|
||||
@@ -11527,3 +11527,152 @@ body.chat-topbar-overlay .page-header.app-topbar-shell .header-center {
|
||||
flex: 1 1 0 !important;
|
||||
min-width: 0 !important;
|
||||
}
|
||||
|
||||
/* ===== Профиль другого пользователя: встроенные вкладки деталей (2026-09-02) =====
|
||||
* «Духовный путь» и «Контакты» переключают содержимое прямо в свободной области
|
||||
* страницы. Активная кнопка отмечается отдельным сияющим эллипсом вокруг текста. */
|
||||
.user-profile-screen {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.user-profile-body {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.user-profile-detail-links {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.user-profile-detail-links button {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.user-profile-detail-tab-label {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
display: inline-flex;
|
||||
min-height: 34px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgba(229, 239, 253, 0.86);
|
||||
transition: color 150ms ease, filter 150ms ease;
|
||||
}
|
||||
|
||||
.user-profile-detail-tab-label::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: calc(100% + 24px);
|
||||
min-width: 112px;
|
||||
height: 34px;
|
||||
transform: translate(-50%, -50%);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 50%;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 150ms ease, border-color 150ms ease, box-shadow 150ms ease;
|
||||
}
|
||||
|
||||
.user-profile-detail-links button.is-active .user-profile-detail-tab-label {
|
||||
color: #f4f9ff;
|
||||
filter: brightness(1.08);
|
||||
}
|
||||
|
||||
.user-profile-detail-links button.is-active .user-profile-detail-tab-label::before {
|
||||
opacity: 1;
|
||||
border-color: rgba(144, 218, 255, 0.9);
|
||||
box-shadow:
|
||||
0 0 5px rgba(139, 220, 255, 0.82),
|
||||
0 0 13px rgba(72, 145, 255, 0.58),
|
||||
0 0 26px rgba(72, 145, 255, 0.28),
|
||||
inset 0 0 9px rgba(139, 220, 255, 0.16);
|
||||
}
|
||||
|
||||
.user-profile-detail-panel {
|
||||
width: min(92%, 360px);
|
||||
flex: 1 1 auto;
|
||||
min-height: clamp(104px, 18vh, 170px);
|
||||
margin: clamp(7px, 1.5vh, 12px) auto 0;
|
||||
padding: clamp(10px, 2vh, 16px) 4px 4px;
|
||||
color: rgba(225, 234, 248, 0.88);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.user-profile-detail-panel[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.user-profile-detail-copy {
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
color: rgba(225, 234, 248, 0.88);
|
||||
font-size: 14px;
|
||||
line-height: 1.58;
|
||||
}
|
||||
|
||||
.user-profile-detail-copy.is-muted {
|
||||
color: rgba(194, 207, 229, 0.56);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.user-profile-detail-panel .user-profile-contact-row {
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
@media (max-height: 700px) {
|
||||
.user-profile-detail-panel {
|
||||
min-height: 88px;
|
||||
margin-top: 5px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Профиль другого пользователя: сияние активной вкладки от букв (2026-09-02) =====
|
||||
* Без жёсткой рамки: синий свет рождается на глифах текста и мягко затухает
|
||||
* в вытянутом эллиптическом ореоле вокруг подписи. */
|
||||
.user-profile-detail-tab-label::before {
|
||||
width: calc(100% + 34px);
|
||||
min-width: 118px;
|
||||
height: 40px;
|
||||
border: 0 !important;
|
||||
background: radial-gradient(
|
||||
ellipse at center,
|
||||
rgba(105, 218, 255, 0.20) 0%,
|
||||
rgba(72, 160, 255, 0.14) 34%,
|
||||
rgba(54, 110, 255, 0.07) 56%,
|
||||
transparent 76%
|
||||
);
|
||||
box-shadow: none !important;
|
||||
filter: blur(5px);
|
||||
transform: translate(-50%, -50%) scale(0.88);
|
||||
transition: opacity 160ms ease, transform 180ms ease, filter 180ms ease;
|
||||
}
|
||||
|
||||
.user-profile-detail-links button.is-active .user-profile-detail-tab-label {
|
||||
color: #effbff;
|
||||
filter: none;
|
||||
text-shadow:
|
||||
0 0 1px rgba(245, 253, 255, 0.98),
|
||||
0 0 4px rgba(126, 222, 255, 0.98),
|
||||
0 0 8px rgba(78, 181, 255, 0.92),
|
||||
0 0 15px rgba(55, 126, 255, 0.72),
|
||||
0 0 25px rgba(55, 126, 255, 0.42);
|
||||
}
|
||||
|
||||
.user-profile-detail-links button.is-active .user-profile-detail-tab-label::before {
|
||||
opacity: 1;
|
||||
border: 0 !important;
|
||||
background: radial-gradient(
|
||||
ellipse at center,
|
||||
rgba(112, 224, 255, 0.22) 0%,
|
||||
rgba(74, 166, 255, 0.16) 34%,
|
||||
rgba(51, 107, 255, 0.08) 57%,
|
||||
transparent 78%
|
||||
);
|
||||
box-shadow: none !important;
|
||||
filter: blur(6px);
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user