159 lines
4.9 KiB
JavaScript
159 lines
4.9 KiB
JavaScript
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';
|
|
|
|
const input = document.createElement('input');
|
|
input.className = 'input dm-input';
|
|
input.type = 'text';
|
|
input.name = 'contact';
|
|
input.placeholder = 'Введите начало логина';
|
|
input.autocomplete = 'off';
|
|
input.maxLength = 80;
|
|
|
|
const resultsCard = document.createElement('section');
|
|
resultsCard.className = 'card stack dm-dialog-card';
|
|
resultsCard.hidden = true;
|
|
|
|
const status = document.createElement('p');
|
|
status.className = 'meta-muted';
|
|
|
|
const resultsList = document.createElement('div');
|
|
resultsList.className = 'stack dm-list';
|
|
|
|
const renderResults = (matches, query) => {
|
|
resultsList.innerHTML = '';
|
|
resultsCard.hidden = false;
|
|
|
|
if (!query.trim()) {
|
|
status.textContent = 'Введите начало логина пользователя.';
|
|
return;
|
|
}
|
|
|
|
if (!matches.length) {
|
|
status.textContent = 'Совпадений не найдено.';
|
|
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 = `
|
|
<div>
|
|
<strong>${login}</strong>
|
|
<p class="meta-muted" style="margin-top:4px;">Пользователь сервера</p>
|
|
</div>
|
|
<div class="meta-muted">Профиль</div>
|
|
`;
|
|
row.prepend(avatarEl);
|
|
row.addEventListener('click', () => {
|
|
navigate(makeProfileRoute(login));
|
|
});
|
|
resultsList.append(row);
|
|
});
|
|
};
|
|
|
|
const searchButton = document.createElement('button');
|
|
searchButton.className = 'primary-btn dm-send-btn';
|
|
searchButton.type = 'button';
|
|
searchButton.textContent = 'Поиск';
|
|
searchButton.addEventListener('click', async () => {
|
|
const query = input.value.trim();
|
|
if (!query) {
|
|
renderResults([], '');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const logins = await authService.searchUsers(query);
|
|
renderResults((logins || []).slice(0, 5), query);
|
|
} catch (e) {
|
|
status.textContent = `Ошибка поиска: ${e.message || 'unknown'}`;
|
|
resultsCard.hidden = false;
|
|
}
|
|
});
|
|
|
|
const controls = document.createElement('div');
|
|
controls.className = 'contact-search-actions';
|
|
controls.append(searchButton);
|
|
|
|
const formCard = document.createElement('section');
|
|
formCard.className = 'card stack dm-dialog-card';
|
|
formCard.append(input, controls);
|
|
|
|
resultsCard.append(status, resultsList);
|
|
|
|
screen.append(
|
|
renderHeader({
|
|
title: 'Поиск контактов',
|
|
leftAction: { label: '←', onClick: () => navigate('messages-list') },
|
|
}),
|
|
formCard,
|
|
resultsCard,
|
|
);
|
|
|
|
return screen;
|
|
}
|