Channels UI surgical cleanup and create description save fix

This commit is contained in:
DrygMira 2026-04-14 10:36:50 +03:00
parent 126b4ba3a1
commit 7bdb3118ae
6 changed files with 61 additions and 56 deletions

View File

@ -132,8 +132,8 @@ export function render({ navigate }) {
}); });
const baseMessage = `Канал "${normalizeChannelDisplayName(check.name)}" создан.`; const baseMessage = `Канал "${normalizeChannelDisplayName(check.name)}" создан.`;
const successMessage = created?.usedLegacyDescriptionFallback const successMessage = created?.usedLegacyDescriptionFallback && created?.savedDescriptionViaUserParam
? `${baseMessage} Описание не сохранено: на текущем сервере включен legacy-формат create-channel.` ? `${baseMessage} Описание сохранено через блок параметра.`
: baseMessage; : baseMessage;
persistCreateSuccessFlash(successMessage); persistCreateSuccessFlash(successMessage);
navigate('channels-list'); navigate('channels-list');

View File

@ -363,6 +363,8 @@ export function render({ navigate, route }) {
const screen = document.createElement('section'); const screen = document.createElement('section');
screen.className = 'stack channels-screen channels-screen--thread'; screen.className = 'stack channels-screen channels-screen--thread';
const appScreen = document.getElementById('app-screen');
appScreen?.classList.add('channels-scroll-clean');
const userIndicator = document.createElement('div'); const userIndicator = document.createElement('div');
userIndicator.className = 'card channels-user-chip'; userIndicator.className = 'card channels-user-chip';
@ -538,5 +540,9 @@ export function render({ navigate, route }) {
} }
})(); })();
screen.cleanup = () => {
appScreen?.classList.remove('channels-scroll-clean');
};
return screen; return screen;
} }

View File

@ -605,21 +605,12 @@ function renderBody(screen, navigate, routeKey, channelData, handlers) {
title.className = 'channel-head-title'; title.className = 'channel-head-title';
title.textContent = channelData.channel.displayName || channelData.channel.name; title.textContent = channelData.channel.displayName || channelData.channel.name;
const description = document.createElement('p');
description.className = 'channel-head-description';
const hasDescription = !!String(channelData.channel.description || '').trim();
if (hasDescription) {
description.textContent = channelData.channel.description;
} else if (channelData.isOwnChannel) {
description.textContent = 'Описание пока не задано.';
}
const owner = document.createElement('p'); const owner = document.createElement('p');
owner.className = 'channel-head-meta'; owner.className = 'channel-head-meta';
owner.textContent = `Владелец: ${channelData.channel.ownerName}`; owner.textContent = `Владелец: ${channelData.channel.ownerName}`;
const headActions = document.createElement('div'); const headActions = document.createElement('div');
headActions.className = 'row'; headActions.className = 'channel-head-actions';
const aboutButton = document.createElement('button'); const aboutButton = document.createElement('button');
aboutButton.type = 'button'; aboutButton.type = 'button';
aboutButton.className = 'secondary-btn small-btn'; aboutButton.className = 'secondary-btn small-btn';
@ -646,20 +637,7 @@ function renderBody(screen, navigate, routeKey, channelData, handlers) {
headActions.append(editButton); headActions.append(editButton);
} }
if (hasDescription) {
const moreButton = document.createElement('button');
moreButton.type = 'button';
moreButton.className = 'channel-head-more';
moreButton.textContent = 'ещё';
moreButton.addEventListener('click', () => {
description.classList.toggle('is-expanded');
moreButton.textContent = description.classList.contains('is-expanded') ? 'скрыть' : 'ещё';
});
headActions.append(moreButton);
}
head.append(title); head.append(title);
if (hasDescription || channelData.isOwnChannel) head.append(description);
head.append(owner, headActions); head.append(owner, headActions);
const actionButton = document.createElement('button'); const actionButton = document.createElement('button');
@ -723,16 +701,8 @@ export function render({ navigate, route }) {
const screen = document.createElement('section'); const screen = document.createElement('section');
screen.className = 'stack channels-screen channels-screen--channel'; screen.className = 'stack channels-screen channels-screen--channel';
const appScreen = document.getElementById('app-screen');
const titleFromIndex = state.channelsIndex[channelId]?.channel?.channelName; appScreen?.classList.add('channels-scroll-clean');
const ownerFromIndex = state.channelsIndex[channelId]?.channel?.ownerLogin;
const titleFromIndexDisplay = (ownerFromIndex && titleFromIndex) ? `${ownerFromIndex}/${titleFromIndex}` : titleFromIndex;
const titleFromRoute = route.params.ownerBlockchainName ? String(route.params.ownerBlockchainName) : '';
const headerTitle = `Канал: ${titleFromIndexDisplay || titleFromRoute || 'Канал'}`;
const userIndicator = document.createElement('div');
userIndicator.className = 'card channels-user-chip';
userIndicator.textContent = `Вы вошли как @${state.session.login || 'неизвестно'}`;
const statusBox = document.createElement('div'); const statusBox = document.createElement('div');
statusBox.className = 'card status-line is-unavailable channels-status'; statusBox.className = 'card status-line is-unavailable channels-status';
@ -846,11 +816,11 @@ export function render({ navigate, route }) {
screen.append( screen.append(
renderHeader({ renderHeader({
title: headerTitle, title: '',
leftAction: { label: '<', onClick: () => navigate('channels-list') }, leftAction: { label: '<', onClick: () => navigate('channels-list') },
}), }),
); );
screen.append(userIndicator, statusBox); screen.append(statusBox);
const skeleton = renderSkeleton(screen); const skeleton = renderSkeleton(screen);
@ -924,5 +894,9 @@ export function render({ navigate, route }) {
} }
})(); })();
screen.cleanup = () => {
appScreen?.classList.remove('channels-scroll-clean');
};
return screen; return screen;
} }

View File

@ -39,20 +39,6 @@ export function render({ navigate }) {
const card = document.createElement('div'); const card = document.createElement('div');
card.className = 'card stack'; card.className = 'card stack';
const nextStepCard = document.createElement('div');
nextStepCard.className = 'card stack';
nextStepCard.innerHTML = `
<strong>Вы вошли как @${login}</strong>
<p class="meta-muted">Следующий шаг для ручной проверки: откройте вкладку «Каналы» в нижнем меню.</p>
`;
const openChannelsButton = document.createElement('button');
openChannelsButton.className = 'primary-btn';
openChannelsButton.type = 'button';
openChannelsButton.textContent = 'Открыть каналы';
openChannelsButton.addEventListener('click', () => navigate('channels-list'));
nextStepCard.append(openChannelsButton);
const topRow = document.createElement('div'); const topRow = document.createElement('div');
topRow.className = 'row'; topRow.className = 'row';
topRow.innerHTML = ` topRow.innerHTML = `
@ -215,7 +201,7 @@ export function render({ navigate }) {
shineBtn.addEventListener('click', () => onToggleClick('shine')); shineBtn.addEventListener('click', () => onToggleClick('shine'));
card.append(topRow, badgesRow, status, listWrap); card.append(topRow, badgesRow, status, listWrap);
screen.append(nextStepCard, card); screen.append(card);
refreshProfileSnapshot(); refreshProfileSnapshot();

View File

@ -89,6 +89,16 @@ function isLegacyCreateChannelFormatError(error) {
); );
} }
function channelDescriptionParamKeyFromSelector(selector) {
const owner = String(selector?.ownerBlockchainName || '').trim();
const rootNo = Number(selector?.channelRootBlockNumber);
const rootHash = String(selector?.channelRootBlockHash || '').trim().toLowerCase();
if (!owner || !Number.isFinite(rootNo) || rootNo < 0 || !/^[0-9a-f]{64}$/.test(rootHash)) {
return '';
}
return `channel_desc:${owner}:${rootNo}:${rootHash}`;
}
function makeClientInfo() { function makeClientInfo() {
const ua = navigator.userAgent || 'unknown'; const ua = navigator.userAgent || 'unknown';
return ua.slice(0, 50); return ua.slice(0, 50);
@ -891,6 +901,7 @@ export class AuthService {
const check = validateChannelDisplayName(channelName); const check = validateChannelDisplayName(channelName);
if (!check.ok) throw new Error(channelNameErrorText(check.code)); if (!check.ok) throw new Error(channelNameErrorText(check.code));
const cleanChannelName = normalizeChannelDisplayName(check.normalized); const cleanChannelName = normalizeChannelDisplayName(check.normalized);
const cleanChannelDescription = normalizeChannelDescription(channelDescription);
const channelSlug = toCanonicalChannelSlug(cleanChannelName); const channelSlug = toCanonicalChannelSlug(cleanChannelName);
const key = `create-channel:${cleanLogin}:${channelSlug || cleanChannelName.toLowerCase()}`; const key = `create-channel:${cleanLogin}:${channelSlug || cleanChannelName.toLowerCase()}`;
@ -930,7 +941,7 @@ export class AuthService {
prevLineHashHex, prevLineHashHex,
thisLineNumber, thisLineNumber,
channelName: cleanChannelName, channelName: cleanChannelName,
channelDescription, channelDescription: cleanChannelDescription,
}) })
: makeCreateChannelBodyBytes({ : makeCreateChannelBodyBytes({
lineCode: 0, lineCode: 0,
@ -952,6 +963,7 @@ export class AuthService {
let payload; let payload;
let usedLegacyDescriptionFallback = false; let usedLegacyDescriptionFallback = false;
let savedDescriptionViaUserParam = false;
try { try {
payload = await submitCreate(true); payload = await submitCreate(true);
} catch (error) { } catch (error) {
@ -960,13 +972,32 @@ export class AuthService {
usedLegacyDescriptionFallback = true; usedLegacyDescriptionFallback = true;
} }
return { const selector = {
...payload,
usedLegacyDescriptionFallback,
channel: {
ownerBlockchainName: blockchainName, ownerBlockchainName: blockchainName,
channelRootBlockNumber: Number(payload?.serverLastGlobalNumber), channelRootBlockNumber: Number(payload?.serverLastGlobalNumber),
channelRootBlockHash: normalizeHex32(payload?.serverLastGlobalHash, ZERO64), channelRootBlockHash: normalizeHex32(payload?.serverLastGlobalHash, ZERO64),
};
if (usedLegacyDescriptionFallback && cleanChannelDescription) {
const param = channelDescriptionParamKeyFromSelector(selector);
if (!param) {
throw new Error('Не удалось сохранить описание канала: некорректный идентификатор канала.');
}
await this.addBlockUserParam({
login: cleanLogin,
storagePwd,
param,
value: JSON.stringify({ v: cleanChannelDescription }),
});
savedDescriptionViaUserParam = true;
}
return {
...payload,
usedLegacyDescriptionFallback,
savedDescriptionViaUserParam,
channel: {
...selector,
}, },
}; };
}); });

View File

@ -1293,6 +1293,14 @@ textarea.input {
gap: 6px; gap: 6px;
} }
.channel-head-actions {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 8px;
flex-wrap: wrap;
}
.channel-head-title { .channel-head-title {
font-size: 24px; font-size: 24px;
color: #f0d089; color: #f0d089;