Каналы: новый роутинг, поиск, вход-возврат, удаление просмотров и документация

This commit is contained in:
AidarKC
2026-05-08 01:15:54 +03:00
parent 6774c26ea1
commit acdd6c928b
18 changed files with 657 additions and 458 deletions
+130 -9
View File
@@ -40,6 +40,11 @@ function normalizeLoginInput(value) {
}
function buildChannelRouteFromSummary(summary, fallbackId) {
const ownerLogin = String(summary?.channel?.ownerLogin || '').trim();
const channelName = String(summary?.channel?.channelName || '').trim();
if (ownerLogin && channelName) {
return `channel/${encodeRoutePart(ownerLogin)}/${encodeRoutePart(channelName)}`;
}
const ownerBch = summary?.channel?.ownerBlockchainName;
const rootBlockNumber = summary?.channel?.channelRoot?.blockNumber;
const rootBlockHash = normalizeHash(summary?.channel?.channelRoot?.blockHash);
@@ -406,6 +411,117 @@ function openSimpleSubscribeModal({ kind, kindLabel, submitLabel, unfollow = fal
if (inputEl) inputEl.focus();
}
function openChannelFinderModal({ navigate }) {
const root = document.getElementById('modal-root');
root.innerHTML = `
<div class="modal" id="channels-find-modal">
<div class="modal-card stack" style="max-width: min(96vw, 760px); width: min(96vw, 760px);">
<h3 class="modal-title">Поиск каналов</h3>
<p class="meta-muted">Введите логин или формат login/channel</p>
<input id="channels-find-input" class="input" placeholder="login/channel" autocomplete="off" />
<div id="channels-find-suggest" class="channels-search-suggest" style="display:none"></div>
<div id="channels-find-list" class="channels-search-suggest" style="display:none"></div>
<div id="channels-find-error" class="meta-muted inline-error"></div>
<div class="form-actions-grid">
<button class="secondary-btn" id="channels-find-close" type="button">Закрыть</button>
</div>
</div>
</div>
`;
const inputEl = root.querySelector('#channels-find-input');
const suggestEl = root.querySelector('#channels-find-suggest');
const channelsEl = root.querySelector('#channels-find-list');
const errorEl = root.querySelector('#channels-find-error');
const close = () => { root.innerHTML = ''; };
const renderButtons = (container, values, onPick) => {
container.innerHTML = '';
if (!values.length) {
container.style.display = 'none';
return;
}
container.style.display = '';
values.forEach((value) => {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'channel-search-item';
btn.textContent = value.label;
btn.addEventListener('click', () => onPick(value));
container.append(btn);
});
};
const loadChannelsForLogin = async (login, filterChannel = '') => {
const ownerLogin = normalizeLoginInput(login);
if (!ownerLogin) return;
const feed = await authService.listSubscriptionsFeed(ownerLogin, 1000);
const rows = Array.isArray(feed?.ownedChannels) ? feed.ownedChannels : [];
const needle = String(filterChannel || '').trim().toLowerCase();
const channels = rows
.map((item) => String(item?.channel?.channelName || '').trim())
.filter(Boolean)
.filter((name) => !needle || name.toLowerCase().includes(needle))
.slice(0, 200)
.map((name) => ({ label: `${ownerLogin}/${name}`, ownerLogin, channelName: name }));
renderButtons(channelsEl, channels, (item) => {
close();
navigate(`channel/${encodeRoutePart(item.ownerLogin)}/${encodeRoutePart(item.channelName)}`);
});
};
const refresh = createDebounced(async () => {
const raw = String(inputEl?.value || '').trim();
errorEl.textContent = '';
if (!raw) {
suggestEl.style.display = 'none';
suggestEl.innerHTML = '';
channelsEl.style.display = 'none';
channelsEl.innerHTML = '';
return;
}
const parts = raw.split('/');
const loginPrefix = normalizeLoginInput(parts[0] || '');
const channelFilter = String(parts[1] || '').trim();
try {
if (raw.includes('/')) {
suggestEl.style.display = 'none';
suggestEl.innerHTML = '';
await loadChannelsForLogin(loginPrefix, channelFilter);
return;
}
if (loginPrefix.length < 2) {
suggestEl.style.display = 'none';
suggestEl.innerHTML = '';
channelsEl.style.display = 'none';
channelsEl.innerHTML = '';
return;
}
const logins = await authService.searchUsers(loginPrefix);
const items = (Array.isArray(logins) ? logins : []).slice(0, 15).map((login) => ({
label: login,
login,
}));
renderButtons(suggestEl, items, async (item) => {
inputEl.value = `${item.login}/`;
suggestEl.style.display = 'none';
suggestEl.innerHTML = '';
await loadChannelsForLogin(item.login, '');
});
} catch (error) {
errorEl.textContent = toUserMessage(error, 'Не удалось выполнить поиск.');
}
}, 220);
root.querySelector('#channels-find-close')?.addEventListener('click', close);
inputEl?.addEventListener('input', refresh);
if (inputEl) inputEl.focus();
}
function mapMockGroups() {
const mapRow = (channel) => ({
...channel,
@@ -527,6 +643,16 @@ function toListModel(groups) {
function renderEmptyState(activeTab, navigate) {
const wrap = document.createElement('div');
wrap.className = 'channels-empty-state channels-empty-state--compact channels-empty-state--silent';
const text = document.createElement('p');
text.className = 'meta-muted';
if (activeTab === 'subscriptions') {
text.textContent = 'Вы пока не подписаны на каналы.';
} else if (activeTab === 'my') {
text.textContent = 'У вас пока нет каналов.';
} else {
text.textContent = 'Пока нет каналов авторов.';
}
wrap.append(text);
return wrap;
}
@@ -896,14 +1022,9 @@ function updateBottomCta({ button, listState, navigate, onReload, isTabEmpty = f
}
if (tab === 'authors') {
button.textContent = 'Подписаться на автора';
button.textContent = '🔍 Поиск каналов';
button.className = baseClass;
button.onclick = () => openSimpleSubscribeModal({
kind: 'user',
kindLabel: 'Подписка на автора',
submitLabel: 'Подписаться',
onSuccess: onReload,
});
button.onclick = () => openChannelFinderModal({ navigate });
return;
}
@@ -954,7 +1075,7 @@ export function render({ navigate }) {
const notificationsState = readChannelNotificationsState();
const listState = {
activeTab: 'my',
activeTab: 'subscriptions',
openMenuId: null,
notificationsState,
revealedCounters: new Set(),
@@ -1001,8 +1122,8 @@ export function render({ navigate }) {
};
const tabItems = [
{ key: 'subscriptions', label: 'Каналы' },
{ key: 'my', label: 'Мои' },
{ key: 'subscriptions', label: 'Подписки' },
{ key: 'authors', label: 'Авторы' },
];