diff --git a/VERSION.properties b/VERSION.properties
index a9b1d142..306b82aa 100644
--- a/VERSION.properties
+++ b/VERSION.properties
@@ -1,2 +1,2 @@
-client.version=1.12.3
+client.version=1.12.4
server.version=1.10.1
diff --git a/Черновик_CHANNEL_MEMBERSHIP_для_каналов.md b/docs/Черновики/Черновик_CHANNEL_MEMBERSHIP_для_каналов.md
similarity index 100%
rename from Черновик_CHANNEL_MEMBERSHIP_для_каналов.md
rename to docs/Черновики/Черновик_CHANNEL_MEMBERSHIP_для_каналов.md
diff --git a/Черновик_TEXT_CHANNEL_META_для_каналов.md b/docs/Черновики/Черновик_TEXT_CHANNEL_META_для_каналов.md
similarity index 100%
rename from Черновик_TEXT_CHANNEL_META_для_каналов.md
rename to docs/Черновики/Черновик_TEXT_CHANNEL_META_для_каналов.md
diff --git a/Черновик_дополнений_каналов_и_типов_сообщений_SHiNE.md b/docs/Черновики/Черновик_дополнений_каналов_и_типов_сообщений_SHiNE.md
similarity index 100%
rename from Черновик_дополнений_каналов_и_типов_сообщений_SHiNE.md
rename to docs/Черновики/Черновик_дополнений_каналов_и_типов_сообщений_SHiNE.md
diff --git a/Черновик_формата_вложений_в_сообщениях_блокчейна.md b/docs/Черновики/Черновик_формата_вложений_в_сообщениях_блокчейна.md
similarity index 100%
rename from Черновик_формата_вложений_в_сообщениях_блокчейна.md
rename to docs/Черновики/Черновик_формата_вложений_в_сообщениях_блокчейна.md
diff --git a/docs/закомментировано/Личный_дневник.md b/docs/закомментировано/Личный_дневник.md
new file mode 100644
index 00000000..6da008e5
--- /dev/null
+++ b/docs/закомментировано/Личный_дневник.md
@@ -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)` убраны только из формы создания новой записи. Их константы и обработчики чтения сохранены для совместимости со старыми данными.
diff --git a/shine-UI/js/pages/channel-view.js b/shine-UI/js/pages/channel-view.js
index 0c6f59cc..6fe5a6cd 100644
--- a/shine-UI/js/pages/channel-view.js
+++ b/shine-UI/js/pages/channel-view.js
@@ -1217,9 +1217,6 @@ function openAddMessageModal({ channelName, onSubmit, navigate, isActive = () =>
@@ -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;
diff --git a/shine-UI/js/pages/channels-list.js b/shine-UI/js/pages/channels-list.js
index a0a6d119..08188241 100644
--- a/shine-UI/js/pages/channels-list.js
+++ b/shine-UI/js/pages/channels-list.js
@@ -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);