import { renderHeader } from '../components/header.js'; import { authService } from '../state.js'; import { renderUserAvatar } from '../components/avatar-image.js'; import { loadProfileSnapshot } from '../services/user-profile-params.js'; import { makeProfileRoute } from '../services/shine-routes.js'; export const pageMeta = { id: 'contact-search-view', title: 'Поиск контактов' }; const searchAvatarSnapshotCache = new Map(); const searchAvatarPendingByLogin = new Map(); async function loadSearchAvatarSnapshot(login) { const cleanLogin = String(login || '').trim(); if (!cleanLogin) return null; const key = cleanLogin.toLowerCase(); if (searchAvatarSnapshotCache.has(key)) return searchAvatarSnapshotCache.get(key); if (searchAvatarPendingByLogin.has(key)) return searchAvatarPendingByLogin.get(key); const pending = loadProfileSnapshot(cleanLogin) .then((snapshot) => { searchAvatarSnapshotCache.set(key, snapshot || null); searchAvatarPendingByLogin.delete(key); return snapshot || null; }) .catch(() => { searchAvatarSnapshotCache.set(key, null); searchAvatarPendingByLogin.delete(key); return null; }); searchAvatarPendingByLogin.set(key, pending); return pending; } function createSearchAvatar(login) { const cleanLogin = String(login || '').trim(); const title = cleanLogin ? `Профиль ${cleanLogin}` : ''; const avatarEl = renderUserAvatar({ login: cleanLogin || 'unknown', size: 'small', className: 'avatar', title, }); if (!cleanLogin) return avatarEl; void loadSearchAvatarSnapshot(cleanLogin).then((snapshot) => { if (!avatarEl.isConnected) return; const upgraded = renderUserAvatar({ login: cleanLogin, avatar: snapshot?.avatar?.txId ? { ar: String(snapshot.avatar.txId || '').trim(), sha256Hex: String(snapshot?.avatar?.sha256Hex || '').trim().toLowerCase(), } : null, size: 'small', className: 'avatar', title, }); avatarEl.replaceWith(upgraded); }); return avatarEl; } export function render({ navigate }) { const screen = document.createElement('section'); screen.className = 'stack dm-screen dm-search-screen'; let searchTimer = 0; let searchSeq = 0; const input = document.createElement('input'); input.className = 'input dm-input contact-search-input'; input.type = 'text'; input.name = 'contact'; input.placeholder = 'Введите начало логина'; input.autocomplete = 'off'; input.maxLength = 80; const resultsCard = document.createElement('section'); resultsCard.className = 'card stack contact-search-results-card'; resultsCard.hidden = true; const status = document.createElement('p'); status.className = 'contact-search-results-title'; const resultsList = document.createElement('div'); resultsList.className = 'stack dm-list'; const renderResults = (matches, query) => { resultsList.innerHTML = ''; if (!query.trim()) { status.textContent = ''; resultsCard.hidden = true; return; } resultsCard.hidden = false; if (!matches.length) { status.textContent = 'Найдено пользователей: 0'; return; } status.textContent = `Найдено пользователей: ${matches.length}`; matches.forEach((login) => { const row = document.createElement('article'); row.className = 'list-item dm-dialog-card'; const avatarEl = createSearchAvatar(login); row.innerHTML = `
${login}
`; row.prepend(avatarEl); row.addEventListener('click', () => { navigate(makeProfileRoute(login)); }); resultsList.append(row); }); }; const runSearch = async () => { const query = input.value.trim(); const seq = ++searchSeq; if (!query) { renderResults([], ''); return; } try { const logins = await authService.searchUsers(query); if (seq !== searchSeq) return; renderResults((logins || []).slice(0, 5), query); } catch (e) { if (seq !== searchSeq) return; status.textContent = `Ошибка поиска: ${e.message || 'unknown'}`; resultsCard.hidden = false; resultsList.innerHTML = ''; } }; const scheduleSearch = () => { if (searchTimer) window.clearTimeout(searchTimer); searchTimer = window.setTimeout(() => { searchTimer = 0; void runSearch(); }, 2000); }; const searchButton = document.createElement('button'); searchButton.className = 'primary-btn dm-send-btn'; searchButton.type = 'button'; searchButton.textContent = 'Поиск'; searchButton.addEventListener('click', async () => { if (searchTimer) { window.clearTimeout(searchTimer); searchTimer = 0; } await runSearch(); }); input.addEventListener('input', () => { scheduleSearch(); }); const controls = document.createElement('div'); controls.className = 'contact-search-actions'; controls.append(searchButton); const formCard = document.createElement('section'); formCard.className = 'card stack contact-search-form-card'; formCard.append(input, controls); resultsCard.append(status, resultsList); screen.append( renderHeader({ title: 'Поиск контактов', leftAction: { label: '←', onClick: () => navigate('messages-list') }, }), formCard, resultsCard, ); screen.cleanup = () => { if (searchTimer) window.clearTimeout(searchTimer); }; return screen; }