Обновить каналы и перенести черновики

This commit is contained in:
AidarKC
2026-09-09 15:17:31 +03:00
parent f827c3e493
commit 9f77c54955
8 changed files with 168 additions and 85 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
client.version=1.12.3
client.version=1.12.4
server.version=1.10.1
@@ -0,0 +1,68 @@
# Личный дневник — временно отключён в UI
Статус: **закомментировано / скрыто из интерфейса**.
Личный дневник SHiNE не удалён из протокола и серверной части. На текущем этапе он только перестал отображаться пользователю в списке каналов.
## Что это за функция
«Дневник» — виртуальный персональный канал пользователя. Он формируется не как обычный канал с отдельной цепочкой постов, а собирается сервером из `STATUS_ACTION` записей пользователя.
Основной read API:
- `GetPersonalDiary`
Связанная документация:
- `docs/API/06_Channels_Read_API.md` — раздел `GetPersonalDiary`;
- `docs/API/09_Operations_Index.md` — операция `GetPersonalDiary`;
- `docs/Blockchain/15_STATUS_ACTION_Blocks.md` — действия, из которых собирается дневник;
- `docs/Blockchain/CHANGELOG.md` — история добавления функции.
## Что отключено сейчас
В `shine-UI/js/pages/channels-list.js` отключён запрос `authService.getPersonalDiary(...)` при построении списка каналов.
Код оставлен рядом в комментариях, а в `mapApiFeed(...)` передаётся `diaryPayload = null`. Благодаря этому карточка «Дневник» не появляется в разделе каналов.
## Что намеренно НЕ удалено
Чтобы не ломать обратную совместимость и сохранить возможность вернуть функцию позже, оставлены:
- серверная операция `GetPersonalDiary`;
- `authService.getPersonalDiary(...)`;
- обработка diary-route в `channel-view.js`;
- форматы `STATUS_ACTION`;
- чтение старых типов сообщений и действий.
То есть функция сохранена технически, но скрыта из обычной навигации.
## Как вернуть дневник
В `shine-UI/js/pages/channels-list.js` вернуть получение `diaryPayload`:
```js
let diaryPayload = null;
try {
diaryPayload = await authService.getPersonalDiary(state.session.login, 200, 'asc');
} catch {
diaryPayload = null;
}
```
и убрать временное:
```js
const diaryPayload = null;
```
После этого `mapApiFeed(...)` снова сможет добавить виртуальную карточку дневника в список каналов.
## Связанные типы контента
В интерфейсе создания новой записи канала сейчас доступны только:
- `POST (10)` — Пост;
- `TEXT_ENTRYPOINT (100)` — Оглавление канала.
Варианты `TEXT_EXERCISE (110)`, `TEXT_SERVICE (120)` и `TEXT_COURSE (130)` убраны только из формы создания новой записи. Их константы и обработчики чтения сохранены для совместимости со старыми данными.
+87 -78
View File
@@ -1217,9 +1217,6 @@ function openAddMessageModal({ channelName, onSubmit, navigate, isActive = () =>
<div class="channel-message-tools">
<select id="channel-message-type" class="input channel-message-type-select">
<option value="${10}">Пост</option>
<option value="${MSG_SUBTYPE_TEXT_EXERCISE}">Упражнение</option>
<option value="${MSG_SUBTYPE_TEXT_SERVICE}">Услуга</option>
<option value="${MSG_SUBTYPE_TEXT_COURSE}">Курс</option>
<option value="${MSG_SUBTYPE_TEXT_ENTRYPOINT}">Оглавление канала</option>
</select>
<button class="secondary-btn attachment-trigger-btn" id="channel-message-attach" type="button" aria-label="Добавить вложение" title="Добавить вложение">▣ 📎</button>
@@ -2211,9 +2208,9 @@ function renderBody(screen, navigate, routeKey, channelData, handlers) {
} else if (channelData.isOwnChannel) {
screen.append(feed, addMessageButton);
} else if (!channelData.isSubscribed && !isStoriesChannel(channelData.channel)) {
screen.append(actionButton, feed, backButton);
screen.append(feed, actionButton);
} else {
screen.append(feed, backButton);
screen.append(feed);
}
const hasPendingScrollTarget = pendingScrollByRoute.has(routeKey);
@@ -2319,32 +2316,19 @@ export function render({ navigate, route, chrome }) {
const items = [
{ label: 'О канале', action: () => { if (aboutRoute) navigate(aboutRoute); } },
];
if (apiData?.isSubscribed && !apiData?.isOwnChannel) {
items.push({
label: 'Отписаться от канала',
danger: true,
action: async () => {
try {
const { login, storagePwd } = requireSigningSession();
await authService.addBlockFollowChannel({
login,
storagePwd,
targetBlockchainName: apiData.selector.ownerBlockchainName,
targetBlockNumber: apiData.selector.channelRootBlockNumber,
targetBlockHashHex: apiData.selector.channelRootBlockHash,
unfollow: true,
});
if (disposed) return;
const feed = await authService.listSubscriptionsFeed(login, 200);
if (disposed) return;
setChannelsFeed(feed, state.channelsIndex);
showToast('Вы отписались от канала');
void refresh();
} catch (error) {
showStatus(toUserMessage(error, 'Не удалось отписаться от канала.'));
}
},
});
if (!apiData?.isOwnChannel && !isStoriesChannel(apiData?.channel)) {
if (apiData?.isSubscribed) {
items.push({
label: 'Отписаться от канала',
danger: true,
action: () => unsubscribeFromChannel(apiData),
});
} else {
items.push({
label: 'Подписаться на канал',
action: () => subscribeToChannel(apiData),
});
}
}
return items;
},
@@ -2373,6 +2357,77 @@ export function render({ navigate, route, chrome }) {
return { login, storagePwd };
};
const subscribeToChannel = async (apiData, event = null) => {
animatePress(event?.currentTarget);
try {
const { login, storagePwd } = requireSigningSession();
if (!apiData?.selector) throw new Error('Не удалось определить канал для подписки.');
const targetName = `${apiData.channel?.ownerName || 'user'}/${apiData.channel?.name || 'channel'}`;
const ok = window.confirm(`Подписаться на канал ${targetName}?`);
if (!ok) return;
await authService.addBlockFollowChannel({
login,
storagePwd,
targetBlockchainName: apiData.selector.ownerBlockchainName,
targetBlockNumber: apiData.selector.channelRootBlockNumber,
targetBlockHashHex: apiData.selector.channelRootBlockHash,
unfollow: false,
});
if (disposed) return;
const readSettingKey = buildChannelSettingsKey(
apiData.channel?.ownerBlockchainName || apiData.selector?.ownerBlockchainName,
apiData.channel?.name || apiData.channel?.channelName,
);
try {
await authService.upsertUserSetting({
login,
settingType: 1,
settingKey: readSettingKey,
timeMs: Date.now(),
valueText: '',
valueNum: Math.max(0, Number(apiData.messagesCount || 0)),
storagePwd,
});
} catch (readStateError) {
showStatus(toUserMessage(readStateError, 'Подписка выполнена, но не удалось сохранить, сколько сообщений уже прочитано.'));
}
const feed = await authService.listSubscriptionsFeed(login, 200);
if (disposed) return;
setChannelsFeed(feed, state.channelsIndex);
softHaptic(15);
showToast('Подписка на канал выполнена');
void refresh();
} catch (error) {
showStatus(toUserMessage(error, 'Не удалось подписаться на канал.'));
}
};
const unsubscribeFromChannel = async (apiData) => {
try {
const { login, storagePwd } = requireSigningSession();
if (!apiData?.selector) throw new Error('Не удалось определить канал для отписки.');
await authService.addBlockFollowChannel({
login,
storagePwd,
targetBlockchainName: apiData.selector.ownerBlockchainName,
targetBlockNumber: apiData.selector.channelRootBlockNumber,
targetBlockHashHex: apiData.selector.channelRootBlockHash,
unfollow: true,
});
if (disposed) return;
const feed = await authService.listSubscriptionsFeed(login, 200);
if (disposed) return;
setChannelsFeed(feed, state.channelsIndex);
showToast('Вы отписались от канала');
void refresh();
} catch (error) {
showStatus(toUserMessage(error, 'Не удалось отписаться от канала.'));
}
};
const onToggleLike = async (messageRef, action) => {
const actionKey = makeReactionActionKey(messageRef);
if (!actionKey) {
@@ -2774,53 +2829,7 @@ export function render({ navigate, route, chrome }) {
throw new Error(toUserMessage(error, 'Не удалось изменить сообщение.'));
}
},
onSubscribeChannel: async (event) => {
animatePress(event?.currentTarget);
try {
const { login, storagePwd } = requireSigningSession();
if (!apiData.selector) throw new Error('Не удалось определить канал для подписки.');
const targetName = `${apiData.channel?.ownerName || 'user'}/${apiData.channel?.name || 'channel'}`;
const ok = window.confirm(`Подписаться на канал ${targetName}?`);
if (!ok) return;
await authService.addBlockFollowChannel({
login,
storagePwd,
targetBlockchainName: apiData.selector.ownerBlockchainName,
targetBlockNumber: apiData.selector.channelRootBlockNumber,
targetBlockHashHex: apiData.selector.channelRootBlockHash,
unfollow: false,
});
if (disposed) return;
const readSettingKey = buildChannelSettingsKey(
apiData.channel?.ownerBlockchainName || apiData.selector?.ownerBlockchainName,
apiData.channel?.name || apiData.channel?.channelName,
);
try {
await authService.upsertUserSetting({
login,
settingType: 1,
settingKey: readSettingKey,
timeMs: Date.now(),
valueText: '',
valueNum: Math.max(0, Number(apiData.messagesCount || 0)),
storagePwd,
});
} catch (readStateError) {
showStatus(toUserMessage(readStateError, 'Подписка выполнена, но не удалось сохранить, сколько сообщений уже прочитано.'));
}
const feed = await authService.listSubscriptionsFeed(login, 200);
if (disposed) return;
setChannelsFeed(feed, state.channelsIndex);
softHaptic(15);
showToast('Подписка на канал выполнена');
void refresh();
} catch (error) {
showStatus(toUserMessage(error, 'Не удалось подписаться на канал.'));
}
},
onSubscribeChannel: (event) => subscribeToChannel(apiData, event),
});
} catch (error) {
if (disposed || seq !== refreshSeq) return;
+12 -6
View File
@@ -1067,12 +1067,18 @@ async function loadFeedAndRender({ screen, listState, contentEl, navigate }) {
try {
const feed = await authService.listSubscriptionsFeed(state.session.login, 200);
let diaryPayload = null;
try {
diaryPayload = await authService.getPersonalDiary(state.session.login, 200, 'asc');
} catch {
diaryPayload = null;
}
// FEATURE DISABLED: personal Diary is intentionally hidden from the Channels UI.
// The server/API implementation is preserved so the feature can be restored later.
// See: docs/закомментировано/Личный_дневник.md
// let diaryPayload = null;
// try {
// diaryPayload = await authService.getPersonalDiary(state.session.login, 200, 'asc');
// } catch {
// diaryPayload = null;
// }
const diaryPayload = null;
const groups = mapApiFeed(feed, listState.notificationsState, diaryPayload);
listState.channels = toListModel(groups);