SHiNE-server/shine-UI/js/pages/contact-search-view.js

190 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 = `
<div class="contact-search-result-main">
<strong class="dm-row-title">${login}</strong>
</div>
<span class="dm-chevron" aria-hidden="true"></span>
`;
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;
}