SHA256
Каналы: новый роутинг, поиск, вход-возврат, удаление просмотров и документация
This commit is contained in:
@@ -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: 'Авторы' },
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user