SHA256
Ужесточение имен каналов и удаление legacy USER_PARAM для описания
This commit is contained in:
@@ -123,41 +123,6 @@ function buildAbsoluteRouteUrl(routePath = '') {
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function channelDescriptionParamKey(selector) {
|
||||
const owner = String(selector?.ownerBlockchainName || '').trim();
|
||||
const rootNo = Number(selector?.channelRootBlockNumber);
|
||||
const rootHash = normalizeRouteHash(selector?.channelRootBlockHash);
|
||||
if (!owner || !Number.isFinite(rootNo)) return '';
|
||||
return `channel_desc:${owner}:${rootNo}:${rootHash}`;
|
||||
}
|
||||
|
||||
function parseDescriptionOverride(payload) {
|
||||
if (!payload || typeof payload !== 'object') {
|
||||
return { hasOverride: false, description: '' };
|
||||
}
|
||||
|
||||
const rawValue = String(payload?.value ?? payload?.param_value ?? '').trim();
|
||||
if (!rawValue && !Number(payload?.time_ms || payload?.timeMs || 0)) {
|
||||
return { hasOverride: false, description: '' };
|
||||
}
|
||||
|
||||
if (!rawValue) {
|
||||
return { hasOverride: true, description: '' };
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(rawValue);
|
||||
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
||||
const value = typeof parsed.v === 'string' ? parsed.v : '';
|
||||
return { hasOverride: true, description: value.trim() };
|
||||
}
|
||||
} catch {
|
||||
// legacy raw string value
|
||||
}
|
||||
|
||||
return { hasOverride: true, description: rawValue };
|
||||
}
|
||||
|
||||
function buildSelectorFromRoute(route, channelId) {
|
||||
const params = route?.params || {};
|
||||
|
||||
@@ -410,88 +375,6 @@ function openAddMessageModal({ channelName, onSubmit }) {
|
||||
if (textEl) textEl.focus();
|
||||
}
|
||||
|
||||
function openEditDescriptionModal({ initialValue = '', onSubmit }) {
|
||||
const root = document.getElementById('modal-root');
|
||||
root.innerHTML = `
|
||||
<div class="modal" id="channel-edit-description-modal">
|
||||
<div class="modal-card stack">
|
||||
<h3 class="modal-title">Описание канала</h3>
|
||||
<textarea id="channel-description-text" class="input" rows="5" maxlength="400" placeholder="Коротко о канале, до 200 байт UTF-8"></textarea>
|
||||
<div class="meta-muted" id="channel-description-counter">0 / 200 байт</div>
|
||||
<div class="meta-muted inline-error" id="channel-description-error"></div>
|
||||
<div class="form-actions-grid">
|
||||
<button class="secondary-btn" id="channel-description-cancel" type="button">Отмена</button>
|
||||
<button class="primary-btn" id="channel-description-submit" type="button">Сохранить</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const textEl = root.querySelector('#channel-description-text');
|
||||
const counterEl = root.querySelector('#channel-description-counter');
|
||||
const errorEl = root.querySelector('#channel-description-error');
|
||||
const submitEl = root.querySelector('#channel-description-submit');
|
||||
const cancelEl = root.querySelector('#channel-description-cancel');
|
||||
|
||||
let inFlight = false;
|
||||
|
||||
const compute = () => {
|
||||
const value = String(textEl?.value || '').replace(/\s+/g, ' ').trim();
|
||||
const bytes = new TextEncoder().encode(value).length;
|
||||
const ok = bytes <= 200;
|
||||
return {
|
||||
value,
|
||||
bytes,
|
||||
ok,
|
||||
error: ok ? '' : 'Описание слишком длинное: максимум 200 байт UTF-8.',
|
||||
};
|
||||
};
|
||||
|
||||
const setBusy = (busy) => {
|
||||
inFlight = !!busy;
|
||||
submitEl.disabled = inFlight;
|
||||
cancelEl.disabled = inFlight;
|
||||
if (textEl) textEl.disabled = inFlight;
|
||||
submitEl.textContent = inFlight ? 'Сохраняем...' : 'Сохранить';
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
root.innerHTML = '';
|
||||
};
|
||||
|
||||
const updateValidation = () => {
|
||||
const check = compute();
|
||||
counterEl.textContent = `${check.bytes} / 200 байт`;
|
||||
errorEl.textContent = check.error;
|
||||
submitEl.disabled = inFlight || !check.ok;
|
||||
return check;
|
||||
};
|
||||
|
||||
cancelEl?.addEventListener('click', close);
|
||||
textEl?.addEventListener('input', updateValidation);
|
||||
submitEl?.addEventListener('click', async () => {
|
||||
if (inFlight) return;
|
||||
const check = updateValidation();
|
||||
if (!check.ok) return;
|
||||
|
||||
setBusy(true);
|
||||
errorEl.textContent = '';
|
||||
try {
|
||||
await onSubmit(check.value);
|
||||
close();
|
||||
} catch (error) {
|
||||
setBusy(false);
|
||||
errorEl.textContent = toUserMessage(error, 'Не удалось сохранить описание.');
|
||||
}
|
||||
});
|
||||
|
||||
if (textEl) {
|
||||
textEl.value = String(initialValue || '');
|
||||
textEl.focus();
|
||||
}
|
||||
updateValidation();
|
||||
}
|
||||
|
||||
function mapApiMessageToPost(message, selector, localNumber) {
|
||||
const blockNumber = toSafeInt(message?.messageRef?.blockNumber);
|
||||
const blockHash = normalizeMessageHash(message?.messageRef?.blockHash);
|
||||
@@ -552,27 +435,11 @@ async function loadFromApi(route, channelId) {
|
||||
const posts = messages.map((message, index) => mapApiMessageToPost(message, selector, index + 1));
|
||||
const ownerLogin = String(payload.channel?.ownerLogin || '').trim();
|
||||
|
||||
const readDescription = async () => {
|
||||
const sourceDescription = String(payload.channel?.channelDescription || '').trim();
|
||||
const paramKey = channelDescriptionParamKey(selector);
|
||||
if (!ownerLogin || !paramKey) return sourceDescription;
|
||||
|
||||
try {
|
||||
const paramPayload = await authService.getUserParam(ownerLogin, paramKey);
|
||||
const override = parseDescriptionOverride(paramPayload);
|
||||
return override.hasOverride ? override.description : sourceDescription;
|
||||
} catch {
|
||||
return sourceDescription;
|
||||
}
|
||||
};
|
||||
|
||||
const resolvedDescription = await readDescription();
|
||||
|
||||
return {
|
||||
channel: {
|
||||
name: payload.channel?.channelName || 'неизвестный канал',
|
||||
displayName: `${ownerLogin || 'неизвестно'}/${payload.channel?.channelName || 'неизвестный канал'}`,
|
||||
description: resolvedDescription,
|
||||
description: String(payload.channel?.channelDescription || '').trim(),
|
||||
ownerName: ownerLogin || 'неизвестно',
|
||||
},
|
||||
posts,
|
||||
@@ -818,22 +685,6 @@ function renderBody(screen, navigate, routeKey, channelData, handlers) {
|
||||
});
|
||||
headActions.append(aboutButton);
|
||||
|
||||
if (channelData.isOwnChannel) {
|
||||
const editButton = document.createElement('button');
|
||||
editButton.type = 'button';
|
||||
editButton.className = 'secondary-btn small-btn';
|
||||
editButton.textContent = '✎';
|
||||
editButton.title = 'Редактировать описание';
|
||||
editButton.addEventListener('click', (event) => {
|
||||
animatePress(event.currentTarget);
|
||||
openEditDescriptionModal({
|
||||
initialValue: channelData.channel.description || '',
|
||||
onSubmit: async (nextValue) => handlers.onEditDescription(nextValue),
|
||||
});
|
||||
});
|
||||
headActions.append(editButton);
|
||||
}
|
||||
|
||||
head.append(title);
|
||||
head.append(owner, headActions);
|
||||
|
||||
@@ -1024,25 +875,6 @@ export function render({ navigate, route }) {
|
||||
rerender();
|
||||
};
|
||||
|
||||
const onEditDescription = async (descriptionText) => {
|
||||
const { login, storagePwd } = requireSigningSession();
|
||||
const selector = routeSelector;
|
||||
const param = channelDescriptionParamKey(selector);
|
||||
if (!param) throw new Error('Идентификатор канала не готов для обновления описания.');
|
||||
|
||||
const value = JSON.stringify({ v: String(descriptionText || '').trim() });
|
||||
await authService.addBlockUserParam({
|
||||
login,
|
||||
storagePwd,
|
||||
param,
|
||||
value,
|
||||
});
|
||||
|
||||
softHaptic(10);
|
||||
showToast('Описание канала сохранено');
|
||||
rerender();
|
||||
};
|
||||
|
||||
screen.append(
|
||||
renderHeader({
|
||||
title: '',
|
||||
@@ -1085,14 +917,6 @@ export function render({ navigate, route }) {
|
||||
}
|
||||
},
|
||||
onShare: onShare,
|
||||
onEditDescription: async (descriptionText) => {
|
||||
try {
|
||||
await onEditDescription(descriptionText);
|
||||
showStatus('');
|
||||
} catch (error) {
|
||||
throw new Error(toUserMessage(error, 'Не удалось сохранить описание.'));
|
||||
}
|
||||
},
|
||||
onSubscribeChannel: async (event) => {
|
||||
animatePress(event?.currentTarget);
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user