SHA256
UI: адаптация шапки «Связи», кнопка поиска каналов, локальный тестовый вход
- Связи: верхняя панель не выходит за границы экрана — кнопки назад/Найти/«?» не обрезаются на узких устройствах; заголовок сокращается вместо сдвига кнопок. - Каналы: emoji-иконка поиска заменена на контурную кнопку-лупу в стиле панели (подсказка «Найти канал»); окно поиска без изменений. - Локальный тестовый вход: на localhost/127.0.0.1/::1 — кнопка демо-входа (сеанс local-tester, автономные локальные состояния ЛС/каналов/профиля, без сервера и пароля); на shineup.me и тестовых доменах кнопка отсутствует. - Заметки на ручную проверку добавлены в Dev_Docs/Pending_Features. - VERSION: client 1.2.262. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,6 @@ import {
|
||||
} from '../services/user-profile-params.js';
|
||||
import { buildIdentityLines } from '../services/user-connections.js';
|
||||
import { renderUserAvatar } from '../components/avatar-image.js';
|
||||
import { makeProfileLinksRoute } from '../services/shine-routes.js';
|
||||
|
||||
export const pageMeta = { id: 'profile-view', title: 'Профиль' };
|
||||
|
||||
@@ -30,20 +29,49 @@ function escapeHtml(text) {
|
||||
.replaceAll("'", ''');
|
||||
}
|
||||
|
||||
function renderProfileInfoText(text) {
|
||||
const [intro, details] = String(text || '').split(/\n\n/, 2);
|
||||
if (!details) {
|
||||
return `<p class="profile-info-modal__lead">${escapeHtml(intro)}</p>`;
|
||||
}
|
||||
|
||||
const [sectionTitle, ...items] = details.split('\n').filter(Boolean);
|
||||
return `
|
||||
<p class="profile-info-modal__lead">${escapeHtml(intro)}</p>
|
||||
<section class="profile-info-modal__section" aria-label="${escapeHtml(sectionTitle)}">
|
||||
<p class="profile-info-modal__section-title">${escapeHtml(sectionTitle)}</p>
|
||||
<ol class="profile-info-modal__list">
|
||||
${items.map((item) => `<li>${escapeHtml(item.replace(/^\d+\)\s*/, ''))}</li>`).join('')}
|
||||
</ol>
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
function openProfileInfoModal({ title, text }) {
|
||||
const root = document.getElementById('modal-root');
|
||||
if (!root) return;
|
||||
root.innerHTML = `
|
||||
<div class="modal" id="profile-info-modal">
|
||||
<div class="modal-card stack">
|
||||
<h3 class="modal-title">${escapeHtml(title)}</h3>
|
||||
<p class="meta-muted" style="white-space: pre-wrap; line-height: 1.45;">${escapeHtml(text)}</p>
|
||||
<button class="secondary-btn" type="button" id="profile-info-close">Закрыть</button>
|
||||
</div>
|
||||
<div class="modal profile-info-modal" id="profile-info-modal" role="dialog" aria-modal="true" aria-labelledby="profile-info-title">
|
||||
<section class="modal-card profile-info-modal__card stack">
|
||||
<header class="profile-info-modal__header">
|
||||
<div>
|
||||
<p class="profile-info-modal__eyebrow">Информация</p>
|
||||
<h3 class="modal-title profile-info-modal__title" id="profile-info-title">${escapeHtml(title)}</h3>
|
||||
</div>
|
||||
<button class="profile-info-modal__close" type="button" id="profile-info-close-icon" aria-label="Закрыть" title="Закрыть">×</button>
|
||||
</header>
|
||||
<div class="profile-info-modal__content">
|
||||
${renderProfileInfoText(text)}
|
||||
</div>
|
||||
<footer class="profile-info-modal__footer">
|
||||
<button class="secondary-btn profile-info-modal__confirm" type="button" id="profile-info-close">Готово</button>
|
||||
</footer>
|
||||
</section>
|
||||
</div>
|
||||
`;
|
||||
const close = () => { root.innerHTML = ''; };
|
||||
root.querySelector('#profile-info-close')?.addEventListener('click', close);
|
||||
root.querySelector('#profile-info-close-icon')?.addEventListener('click', close);
|
||||
root.querySelector('#profile-info-modal')?.addEventListener('click', (event) => {
|
||||
if (event.target?.id === 'profile-info-modal') close();
|
||||
});
|
||||
@@ -73,22 +101,9 @@ export function render({ navigate }) {
|
||||
const topActions = document.createElement('div');
|
||||
topActions.className = 'profile-top-actions';
|
||||
topActions.innerHTML = `
|
||||
<button class="ghost-btn profile-top-action-btn" type="button" data-top-action="edit">Редактировать профиль</button>
|
||||
<button class="ghost-btn profile-top-action-btn" type="button" data-top-action="settings">Настройки</button>
|
||||
<button class="ghost-btn profile-top-action-btn profile-top-menu-btn" type="button" data-top-action="settings" aria-label="Настройки" title="Настройки">⋮</button>
|
||||
`;
|
||||
topActions.querySelector('[data-top-action="edit"]')?.addEventListener('click', () => navigate('profile-edit-view'));
|
||||
topActions.querySelector('[data-top-action="settings"]')?.addEventListener('click', () => navigate('settings-view'));
|
||||
screen.append(topActions);
|
||||
|
||||
const bottomActions = document.createElement('div');
|
||||
bottomActions.className = 'profile-bottom-actions';
|
||||
bottomActions.innerHTML = `
|
||||
<button class="ghost-btn profile-top-action-btn" type="button" data-bottom-action="wallet">Кошелёк</button>
|
||||
<button class="ghost-btn profile-top-action-btn profile-links-two-line" type="button" data-bottom-action="links">Показать\nсвязи</button>
|
||||
`;
|
||||
bottomActions.querySelector('[data-bottom-action="wallet"]')?.addEventListener('click', () => navigate('wallet-view'));
|
||||
bottomActions.querySelector('[data-bottom-action="links"]')?.addEventListener('click', () => navigate(makeProfileLinksRoute(login)));
|
||||
screen.append(bottomActions);
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card stack profile-main-card';
|
||||
@@ -203,6 +218,27 @@ export function render({ navigate }) {
|
||||
}
|
||||
|
||||
async function refreshProfileSnapshot() {
|
||||
if (state.session.isLocalDemo) {
|
||||
currentFields = [
|
||||
{ key: 'first_name', label: 'Имя', value: 'Тестовый' },
|
||||
{ key: 'last_name', label: 'Фамилия', value: 'Пользователь' },
|
||||
{ key: 'address', label: 'Адрес', value: 'Локальный режим' },
|
||||
{ key: 'website', label: 'Веб', value: '127.0.0.1' },
|
||||
{ key: 'phone', label: 'Телефон', value: profile.phone },
|
||||
];
|
||||
currentToggles = [
|
||||
{ key: 'official', enabled: false },
|
||||
{ key: 'shine', enabled: false },
|
||||
];
|
||||
currentGender = 'unknown';
|
||||
currentAvatar = { value: '', source: '', txId: '', sha256Hex: '', timeMs: 0 };
|
||||
syncIdentity();
|
||||
updateAvatarUi();
|
||||
updateTogglesUi();
|
||||
renderFields(currentFields);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const snapshot = await loadProfileSnapshot(login);
|
||||
currentFields = Array.isArray(snapshot.fields) ? snapshot.fields : [];
|
||||
@@ -218,7 +254,7 @@ export function render({ navigate }) {
|
||||
}
|
||||
}
|
||||
|
||||
card.append(topRow, badgesRow, listWrap);
|
||||
card.append(topActions, topRow, badgesRow, listWrap);
|
||||
screen.append(card);
|
||||
|
||||
updateAvatarUi();
|
||||
|
||||
Reference in New Issue
Block a user