SHA256
Доработать оглавление канала и отображение дневника
This commit is contained in:
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
client.version=1.5.11
|
client.version=1.5.12
|
||||||
server.version=1.4.9
|
server.version=1.4.9
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const MSG_SUBTYPE_STATUS_IN_STUDY = 130;
|
|||||||
const MSG_SUBTYPE_STATUS_ABANDONED = 140;
|
const MSG_SUBTYPE_STATUS_ABANDONED = 140;
|
||||||
const MSG_SUBTYPE_STATUS_COMPLETED = 150;
|
const MSG_SUBTYPE_STATUS_COMPLETED = 150;
|
||||||
const DIARY_CHANNEL_NAME = 'diary';
|
const DIARY_CHANNEL_NAME = 'diary';
|
||||||
const DIARY_CHANNEL_DISPLAY_NAME = 'Личный дневник';
|
const DIARY_CHANNEL_DISPLAY_NAME = 'Дневник';
|
||||||
|
|
||||||
const pendingReactionActions = new Set();
|
const pendingReactionActions = new Set();
|
||||||
const pendingScrollByRoute = new Map();
|
const pendingScrollByRoute = new Map();
|
||||||
@@ -319,6 +319,10 @@ function getChannelMessageTypeMeta(msgSubType) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isEntrypointSubType(msgSubType) {
|
||||||
|
return Number(msgSubType || 0) === MSG_SUBTYPE_TEXT_ENTRYPOINT;
|
||||||
|
}
|
||||||
|
|
||||||
function isDiarySelector(selector) {
|
function isDiarySelector(selector) {
|
||||||
return String(selector?.channelName || '').trim().toLowerCase() === DIARY_CHANNEL_NAME;
|
return String(selector?.channelName || '').trim().toLowerCase() === DIARY_CHANNEL_NAME;
|
||||||
}
|
}
|
||||||
@@ -340,9 +344,9 @@ function isStatusActionSubType(msgSubType) {
|
|||||||
function getStatusActionTypeMeta(statusSubType, targetMsgSubType = 0) {
|
function getStatusActionTypeMeta(statusSubType, targetMsgSubType = 0) {
|
||||||
const status = Number(statusSubType || 0);
|
const status = Number(statusSubType || 0);
|
||||||
const target = Number(targetMsgSubType || 0);
|
const target = Number(targetMsgSubType || 0);
|
||||||
if (status === MSG_SUBTYPE_STATUS_DONE_ONCE && target === MSG_SUBTYPE_TEXT_EXERCISE) return { label: 'Выполнено' };
|
if (status === MSG_SUBTYPE_STATUS_DONE_ONCE && target === MSG_SUBTYPE_TEXT_EXERCISE) return { label: 'Выполнил упражнение' };
|
||||||
if (status === MSG_SUBTYPE_STATUS_LEARNED && target === MSG_SUBTYPE_TEXT_EXERCISE) return { label: 'Изучено' };
|
if (status === MSG_SUBTYPE_STATUS_LEARNED && target === MSG_SUBTYPE_TEXT_EXERCISE) return { label: 'Изучил упражнение' };
|
||||||
if (status === MSG_SUBTYPE_STATUS_SERVICE_PASSED && target === MSG_SUBTYPE_TEXT_SERVICE) return { label: 'Пройдено' };
|
if (status === MSG_SUBTYPE_STATUS_SERVICE_PASSED && target === MSG_SUBTYPE_TEXT_SERVICE) return { label: 'Пройденная процедура' };
|
||||||
if (status === MSG_SUBTYPE_STATUS_INTERESTED) return { label: 'Заинтересовался' };
|
if (status === MSG_SUBTYPE_STATUS_INTERESTED) return { label: 'Заинтересовался' };
|
||||||
if (status === MSG_SUBTYPE_STATUS_STARTED) return { label: 'Начал' };
|
if (status === MSG_SUBTYPE_STATUS_STARTED) return { label: 'Начал' };
|
||||||
if (status === MSG_SUBTYPE_STATUS_IN_STUDY) return { label: 'Изучаю' };
|
if (status === MSG_SUBTYPE_STATUS_IN_STUDY) return { label: 'Изучаю' };
|
||||||
@@ -833,6 +837,100 @@ function openStatusActionMenuModal({ targetLabel, options = [], onSelect }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sortPostsByTimeDesc(posts = []) {
|
||||||
|
return [...posts].sort((a, b) => {
|
||||||
|
const byTime = Number(b?.timestampMs || 0) - Number(a?.timestampMs || 0);
|
||||||
|
if (byTime !== 0) return byTime;
|
||||||
|
return Number(b?.messageRef?.blockNumber || 0) - Number(a?.messageRef?.blockNumber || 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEntrypointPosts(posts = []) {
|
||||||
|
return sortPostsByTimeDesc((Array.isArray(posts) ? posts : []).filter((post) => isEntrypointSubType(post?.msgSubType)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function flashAndScrollToMessage(messageRef) {
|
||||||
|
const key = messageRefKey(messageRef);
|
||||||
|
if (!key) return false;
|
||||||
|
const cards = Array.from(document.querySelectorAll('.channel-message-card[data-message-key]'));
|
||||||
|
const target = cards.find((card) => card.dataset.messageKey === key);
|
||||||
|
if (!target) return false;
|
||||||
|
target.classList.remove('is-focus-flash');
|
||||||
|
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
window.setTimeout(() => target.classList.add('is-focus-flash'), 60);
|
||||||
|
window.setTimeout(() => target.classList.remove('is-focus-flash'), 1800);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEntrypointMenuModal({ onShowHistory }) {
|
||||||
|
const root = document.getElementById('modal-root');
|
||||||
|
root.innerHTML = `
|
||||||
|
<div class="modal" id="channel-entrypoint-menu-modal">
|
||||||
|
<div class="modal-card stack">
|
||||||
|
<h3 class="modal-title">Оглавление канала</h3>
|
||||||
|
<div class="stack">
|
||||||
|
<button class="channel-menu-item channel-status-action-item" id="channel-entrypoint-history" type="button">Просмотреть историю изменений оглавления</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-actions-grid">
|
||||||
|
<button class="secondary-btn" id="channel-entrypoint-close" type="button">Закрыть</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
root.querySelector('#channel-entrypoint-close')?.addEventListener('click', () => {
|
||||||
|
root.innerHTML = '';
|
||||||
|
});
|
||||||
|
root.querySelector('#channel-entrypoint-history')?.addEventListener('click', () => {
|
||||||
|
root.innerHTML = '';
|
||||||
|
if (typeof onShowHistory === 'function') onShowHistory();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEntrypointHistoryModal({ channelTitle = '', posts = [], onSelect }) {
|
||||||
|
const root = document.getElementById('modal-root');
|
||||||
|
const items = getEntrypointPosts(posts);
|
||||||
|
const cleanTitle = String(channelTitle || '').trim() || 'канала';
|
||||||
|
root.innerHTML = `
|
||||||
|
<div class="modal" id="channel-entrypoint-history-modal">
|
||||||
|
<div class="modal-card stack">
|
||||||
|
<h3 class="modal-title">История оглавления</h3>
|
||||||
|
<p class="meta-muted">Показаны все версии оглавления канала ${escapeHtml(cleanTitle)}</p>
|
||||||
|
<div class="entrypoint-history-list" id="entrypoint-history-list"></div>
|
||||||
|
<button class="secondary-btn" id="entrypoint-history-close" type="button">Закрыть</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
const list = root.querySelector('#entrypoint-history-list');
|
||||||
|
if (list) {
|
||||||
|
if (!items.length) {
|
||||||
|
const empty = document.createElement('div');
|
||||||
|
empty.className = 'card meta-muted';
|
||||||
|
empty.textContent = 'Оглавление в этом канале пока не добавлялось.';
|
||||||
|
list.append(empty);
|
||||||
|
} else {
|
||||||
|
items.forEach((post) => {
|
||||||
|
const parsed = parseMessageAttachments(post.body);
|
||||||
|
const item = document.createElement('button');
|
||||||
|
item.type = 'button';
|
||||||
|
item.className = 'entrypoint-history-item';
|
||||||
|
item.innerHTML = `
|
||||||
|
<strong>${escapeHtml(post.timestampMs ? new Date(post.timestampMs).toLocaleString('ru-RU') : 'Без даты')}</strong>
|
||||||
|
<span>#${escapeHtml(post.localNumber || '—')}</span>
|
||||||
|
<p>${escapeHtml(String(parsed.text || '').trim() || 'Без текста')}</p>
|
||||||
|
`;
|
||||||
|
item.addEventListener('click', () => {
|
||||||
|
root.innerHTML = '';
|
||||||
|
if (typeof onSelect === 'function') onSelect(post);
|
||||||
|
});
|
||||||
|
list.append(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root.querySelector('#entrypoint-history-close')?.addEventListener('click', () => {
|
||||||
|
root.innerHTML = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function openRepostModal({ navigate, channels = [], onSubmit }) {
|
function openRepostModal({ navigate, channels = [], onSubmit }) {
|
||||||
const root = document.getElementById('modal-root');
|
const root = document.getElementById('modal-root');
|
||||||
const options = (Array.isArray(channels) ? channels : [])
|
const options = (Array.isArray(channels) ? channels : [])
|
||||||
@@ -1129,7 +1227,7 @@ function mapApiMessageToPost(message, selector, localNumber) {
|
|||||||
messageRef,
|
messageRef,
|
||||||
rawMessage: message,
|
rawMessage: message,
|
||||||
msgSubType,
|
msgSubType,
|
||||||
isRating: msgSubType === MSG_SUBTYPE_TEXT_RATING,
|
isRating: msgSubType === MSG_SUBTYPE_TEXT_RATING && !isStatusAction,
|
||||||
isStatusAction,
|
isStatusAction,
|
||||||
targetRef: message?.targetBlockchainName && Number.isFinite(Number(message?.targetBlockNumber))
|
targetRef: message?.targetBlockchainName && Number.isFinite(Number(message?.targetBlockNumber))
|
||||||
? {
|
? {
|
||||||
@@ -1173,7 +1271,7 @@ async function loadFromApi(route, channelId) {
|
|||||||
let selector = buildSelectorFromRoute(route, channelId);
|
let selector = buildSelectorFromRoute(route, channelId);
|
||||||
if (selector?.ownerBlockchainName && selector?.channelName && isDiarySelector(selector)) {
|
if (selector?.ownerBlockchainName && selector?.channelName && isDiarySelector(selector)) {
|
||||||
if (!isAuthorized) {
|
if (!isAuthorized) {
|
||||||
throw new Error('Личный дневник доступен только после входа.');
|
throw new Error('Дневник доступен только после входа.');
|
||||||
}
|
}
|
||||||
const diaryPayload = await authService.getPersonalDiary(currentSessionLogin, 400, 'asc');
|
const diaryPayload = await authService.getPersonalDiary(currentSessionLogin, 400, 'asc');
|
||||||
const diaryMessages = Array.isArray(diaryPayload?.messages) ? diaryPayload.messages : [];
|
const diaryMessages = Array.isArray(diaryPayload?.messages) ? diaryPayload.messages : [];
|
||||||
@@ -1192,7 +1290,7 @@ async function loadFromApi(route, channelId) {
|
|||||||
channel: {
|
channel: {
|
||||||
name: diaryPayload?.channel?.channelName || DIARY_CHANNEL_NAME,
|
name: diaryPayload?.channel?.channelName || DIARY_CHANNEL_NAME,
|
||||||
displayTitle: String(diaryPayload?.channel?.displayName || DIARY_CHANNEL_DISPLAY_NAME).trim(),
|
displayTitle: String(diaryPayload?.channel?.displayName || DIARY_CHANNEL_DISPLAY_NAME).trim(),
|
||||||
displayName: DIARY_CHANNEL_DISPLAY_NAME,
|
displayName: 'Дневника',
|
||||||
description: String(diaryPayload?.channel?.channelDescription || '').trim(),
|
description: String(diaryPayload?.channel?.channelDescription || '').trim(),
|
||||||
avaAr: '',
|
avaAr: '',
|
||||||
avaSha256: '',
|
avaSha256: '',
|
||||||
@@ -1482,6 +1580,7 @@ function renderPostCard(post, {
|
|||||||
onReply,
|
onReply,
|
||||||
onRating,
|
onRating,
|
||||||
onStatusAction,
|
onStatusAction,
|
||||||
|
onOpenEntrypointMenu,
|
||||||
onRepost,
|
onRepost,
|
||||||
onShare,
|
onShare,
|
||||||
onEdit,
|
onEdit,
|
||||||
@@ -1491,6 +1590,7 @@ function renderPostCard(post, {
|
|||||||
const card = document.createElement('article');
|
const card = document.createElement('article');
|
||||||
card.className = 'card stack channel-message-card';
|
card.className = 'card stack channel-message-card';
|
||||||
if (post.isRating) card.classList.add('is-rating');
|
if (post.isRating) card.classList.add('is-rating');
|
||||||
|
if (selector && isDiarySelector(selector)) card.classList.add('is-diary-entry');
|
||||||
|
|
||||||
const authorTile = document.createElement('button');
|
const authorTile = document.createElement('button');
|
||||||
authorTile.type = 'button';
|
authorTile.type = 'button';
|
||||||
@@ -1544,7 +1644,6 @@ function renderPostCard(post, {
|
|||||||
const typeButton = document.createElement('button');
|
const typeButton = document.createElement('button');
|
||||||
typeButton.type = 'button';
|
typeButton.type = 'button';
|
||||||
typeButton.className = 'channel-message-type-button';
|
typeButton.className = 'channel-message-type-button';
|
||||||
if (!typeMeta.actionable) typeButton.classList.add('is-static');
|
|
||||||
typeButton.textContent = typeMeta.label;
|
typeButton.textContent = typeMeta.label;
|
||||||
if (typeMeta.actionable && typeof onStatusAction === 'function') {
|
if (typeMeta.actionable && typeof onStatusAction === 'function') {
|
||||||
typeButton.addEventListener('click', (event) => {
|
typeButton.addEventListener('click', (event) => {
|
||||||
@@ -1552,7 +1651,14 @@ function renderPostCard(post, {
|
|||||||
animatePress(event.currentTarget);
|
animatePress(event.currentTarget);
|
||||||
onStatusAction(post);
|
onStatusAction(post);
|
||||||
});
|
});
|
||||||
|
} else if (isEntrypointSubType(post.msgSubType) && typeof onOpenEntrypointMenu === 'function') {
|
||||||
|
typeButton.addEventListener('click', (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
animatePress(event.currentTarget);
|
||||||
|
onOpenEntrypointMenu(post);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
|
typeButton.classList.add('is-static');
|
||||||
typeButton.disabled = true;
|
typeButton.disabled = true;
|
||||||
}
|
}
|
||||||
headRow.append(typeButton);
|
headRow.append(typeButton);
|
||||||
@@ -1597,7 +1703,7 @@ function renderPostCard(post, {
|
|||||||
ratingBadge.textContent = 'Оценка';
|
ratingBadge.textContent = 'Оценка';
|
||||||
card.append(ratingBadge);
|
card.append(ratingBadge);
|
||||||
}
|
}
|
||||||
if (post.isStatusAction) {
|
if (post.isStatusAction && selector && isDiarySelector(selector)) {
|
||||||
const statusBadge = document.createElement('span');
|
const statusBadge = document.createElement('span');
|
||||||
statusBadge.className = 'channel-message-kind-badge channel-message-kind-badge--status';
|
statusBadge.className = 'channel-message-kind-badge channel-message-kind-badge--status';
|
||||||
statusBadge.textContent = getStatusActionTypeMeta(post.msgSubType, post.targetMsgSubType).label;
|
statusBadge.textContent = getStatusActionTypeMeta(post.msgSubType, post.targetMsgSubType).label;
|
||||||
@@ -1848,6 +1954,7 @@ function renderBody(screen, navigate, routeKey, channelData, handlers) {
|
|||||||
onReply: handlers.onReply,
|
onReply: handlers.onReply,
|
||||||
onRating: handlers.onRating,
|
onRating: handlers.onRating,
|
||||||
onStatusAction: handlers.onStatusAction,
|
onStatusAction: handlers.onStatusAction,
|
||||||
|
onOpenEntrypointMenu: handlers.onOpenEntrypointMenu,
|
||||||
onRepost: handlers.onRepost,
|
onRepost: handlers.onRepost,
|
||||||
onShare: handlers.onShare,
|
onShare: handlers.onShare,
|
||||||
onEdit: handlers.onEdit,
|
onEdit: handlers.onEdit,
|
||||||
@@ -1862,7 +1969,7 @@ function renderBody(screen, navigate, routeKey, channelData, handlers) {
|
|||||||
const empty = document.createElement('div');
|
const empty = document.createElement('div');
|
||||||
empty.className = 'card meta-muted';
|
empty.className = 'card meta-muted';
|
||||||
empty.textContent = channelData.isDiary
|
empty.textContent = channelData.isDiary
|
||||||
? 'К сожалению, у вас пока еще ничего нет в личном дневнике.'
|
? 'К сожалению, у вас пока еще ничего нет в Дневнике.'
|
||||||
: 'Ждем ваших начинаний';
|
: 'Ждем ваших начинаний';
|
||||||
feed.append(empty);
|
feed.append(empty);
|
||||||
}
|
}
|
||||||
@@ -1928,13 +2035,20 @@ export function render({ navigate, route }) {
|
|||||||
const header = renderHeader({
|
const header = renderHeader({
|
||||||
title: '',
|
title: '',
|
||||||
leftAction: { label: '<', onClick: () => navigateBack() },
|
leftAction: { label: '<', onClick: () => navigateBack() },
|
||||||
rightActions: [{ label: 'Канал: ...', onClick: () => {} }],
|
rightActions: [
|
||||||
|
{ label: 'Канал: ...', className: 'channel-header-route-btn', onClick: () => {} },
|
||||||
|
{ label: 'Оглавление', className: 'channel-header-entrypoint-btn', onClick: () => {} },
|
||||||
|
],
|
||||||
});
|
});
|
||||||
const channelHeaderButton = header.querySelector('.header-actions .icon-btn');
|
const channelHeaderButton = header.querySelector('.header-actions .channel-header-route-btn');
|
||||||
|
const channelEntrypointButton = header.querySelector('.header-actions .channel-header-entrypoint-btn');
|
||||||
if (channelHeaderButton) {
|
if (channelHeaderButton) {
|
||||||
channelHeaderButton.classList.add('channel-header-route-btn');
|
|
||||||
channelHeaderButton.disabled = true;
|
channelHeaderButton.disabled = true;
|
||||||
}
|
}
|
||||||
|
if (channelEntrypointButton) {
|
||||||
|
channelEntrypointButton.disabled = true;
|
||||||
|
channelEntrypointButton.hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
const rerender = () => {
|
const rerender = () => {
|
||||||
const current = document.querySelector('section.channels-screen--channel');
|
const current = document.querySelector('section.channels-screen--channel');
|
||||||
@@ -2179,6 +2293,18 @@ export function render({ navigate, route }) {
|
|||||||
const apiData = await loadFromApi(route, channelId);
|
const apiData = await loadFromApi(route, channelId);
|
||||||
activeSelector = apiData?.selector || null;
|
activeSelector = apiData?.selector || null;
|
||||||
const titleLabel = apiData?.channel?.displayTitle || apiData?.channel?.name || 'channel';
|
const titleLabel = apiData?.channel?.displayTitle || apiData?.channel?.name || 'channel';
|
||||||
|
const entrypointPosts = getEntrypointPosts(apiData?.posts);
|
||||||
|
const openEntrypointHistory = () => {
|
||||||
|
openEntrypointHistoryModal({
|
||||||
|
channelTitle: titleLabel,
|
||||||
|
posts: apiData?.posts,
|
||||||
|
onSelect: (post) => {
|
||||||
|
if (!flashAndScrollToMessage(post?.messageRef)) {
|
||||||
|
showToast('Не удалось найти запись оглавления в ленте');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
if (channelHeaderButton) {
|
if (channelHeaderButton) {
|
||||||
channelHeaderButton.textContent = titleLabel;
|
channelHeaderButton.textContent = titleLabel;
|
||||||
channelHeaderButton.disabled = false;
|
channelHeaderButton.disabled = false;
|
||||||
@@ -2193,6 +2319,17 @@ export function render({ navigate, route }) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (channelEntrypointButton) {
|
||||||
|
const canShowEntrypointButton = !apiData?.isDiary && entrypointPosts.length > 0;
|
||||||
|
channelEntrypointButton.hidden = !canShowEntrypointButton;
|
||||||
|
channelEntrypointButton.disabled = !canShowEntrypointButton;
|
||||||
|
channelEntrypointButton.onclick = () => {
|
||||||
|
const latestEntrypoint = entrypointPosts[0];
|
||||||
|
if (!latestEntrypoint?.messageRef || !flashAndScrollToMessage(latestEntrypoint.messageRef)) {
|
||||||
|
showToast('Не удалось найти актуальное оглавление');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
skeleton.remove();
|
skeleton.remove();
|
||||||
cleanupSeenTracking = renderBody(screen, navigate, routeKey, apiData, {
|
cleanupSeenTracking = renderBody(screen, navigate, routeKey, apiData, {
|
||||||
onAddMessage: () => {
|
onAddMessage: () => {
|
||||||
@@ -2241,6 +2378,11 @@ export function render({ navigate, route }) {
|
|||||||
showStatus(toUserMessage(error, 'Не удалось записать действие.'));
|
showStatus(toUserMessage(error, 'Не удалось записать действие.'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onOpenEntrypointMenu: () => {
|
||||||
|
openEntrypointMenuModal({
|
||||||
|
onShowHistory: openEntrypointHistory,
|
||||||
|
});
|
||||||
|
},
|
||||||
onRepost: async (messageRef) => {
|
onRepost: async (messageRef) => {
|
||||||
try {
|
try {
|
||||||
await onRepost(messageRef);
|
await onRepost(messageRef);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const MENU_OVERLAY_ID = 'channels-context-menu-overlay';
|
|||||||
const CHANNEL_TYPE_STORIES = 0;
|
const CHANNEL_TYPE_STORIES = 0;
|
||||||
const CHANNEL_TYPE_PERSONAL = 100;
|
const CHANNEL_TYPE_PERSONAL = 100;
|
||||||
const DIARY_CHANNEL_NAME = 'diary';
|
const DIARY_CHANNEL_NAME = 'diary';
|
||||||
const DIARY_DISPLAY_NAME = 'Личный дневник';
|
const DIARY_DISPLAY_NAME = 'Дневник';
|
||||||
|
|
||||||
function cleanChannelMessagePreview(text) {
|
function cleanChannelMessagePreview(text) {
|
||||||
const parsed = parseMessageAttachments(text);
|
const parsed = parseMessageAttachments(text);
|
||||||
@@ -730,7 +730,7 @@ function buildDiaryChannelRow(diaryPayload, ownRows = [], notificationsState = {
|
|||||||
avatar: 'Д',
|
avatar: 'Д',
|
||||||
avaAr: '',
|
avaAr: '',
|
||||||
title: DIARY_DISPLAY_NAME,
|
title: DIARY_DISPLAY_NAME,
|
||||||
technicalLabel: 'Виртуальная лента ваших действий',
|
technicalLabel: 'Виртуальная лента Дневника',
|
||||||
channelName: DIARY_CHANNEL_NAME,
|
channelName: DIARY_CHANNEL_NAME,
|
||||||
displayTitle: DIARY_DISPLAY_NAME,
|
displayTitle: DIARY_DISPLAY_NAME,
|
||||||
channelDescription: 'История действий по упражнениям, услугам и курсам',
|
channelDescription: 'История действий по упражнениям, услугам и курсам',
|
||||||
@@ -738,7 +738,7 @@ function buildDiaryChannelRow(diaryPayload, ownRows = [], notificationsState = {
|
|||||||
channelTypeVersion: 1,
|
channelTypeVersion: 1,
|
||||||
messagePreview: String(lastMessage?.text || '').trim()
|
messagePreview: String(lastMessage?.text || '').trim()
|
||||||
? cleanChannelMessagePreview(lastMessage?.text)
|
? cleanChannelMessagePreview(lastMessage?.text)
|
||||||
: 'Новая запись в личном дневнике',
|
: 'Новая запись в Дневнике',
|
||||||
messagesCount: messages.length,
|
messagesCount: messages.length,
|
||||||
unreadCount: 0,
|
unreadCount: 0,
|
||||||
lastMessageAt: Number(lastMessage?.createdAtMs || 0),
|
lastMessageAt: Number(lastMessage?.createdAtMs || 0),
|
||||||
|
|||||||
@@ -278,12 +278,25 @@ function createMediaSlide({ item, url, kind, messageTimestampMs, slide, previewU
|
|||||||
openAttachmentViewer({ item, url, kind });
|
openAttachmentViewer({ item, url, kind });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const bindImageLayout = (img) => {
|
||||||
|
img.addEventListener('load', () => {
|
||||||
|
const naturalWidth = Number(img.naturalWidth || 0);
|
||||||
|
const naturalHeight = Number(img.naturalHeight || 0);
|
||||||
|
const isLandscape = naturalWidth > 0 && naturalHeight > 0 && naturalWidth > naturalHeight;
|
||||||
|
img.classList.toggle('is-landscape', isLandscape);
|
||||||
|
frame.classList.toggle('is-landscape', isLandscape);
|
||||||
|
slide.classList.toggle('has-landscape-media', isLandscape);
|
||||||
|
slide.parentElement?.classList.toggle('has-landscape-media', isLandscape);
|
||||||
|
}, { once: true });
|
||||||
|
};
|
||||||
|
|
||||||
if (kind === 'image') {
|
if (kind === 'image') {
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.className = 'message-attachment-media';
|
img.className = 'message-attachment-media';
|
||||||
img.src = url;
|
img.src = url;
|
||||||
img.alt = item.name;
|
img.alt = item.name;
|
||||||
img.loading = 'lazy';
|
img.loading = 'lazy';
|
||||||
|
bindImageLayout(img);
|
||||||
img.addEventListener('error', () => replaceWithUnavailable(slide, messageTimestampMs), { once: true });
|
img.addEventListener('error', () => replaceWithUnavailable(slide, messageTimestampMs), { once: true });
|
||||||
frame.append(img);
|
frame.append(img);
|
||||||
} else if (previewUrl) {
|
} else if (previewUrl) {
|
||||||
@@ -292,6 +305,7 @@ function createMediaSlide({ item, url, kind, messageTimestampMs, slide, previewU
|
|||||||
img.src = previewUrl;
|
img.src = previewUrl;
|
||||||
img.alt = `${item.name} preview`;
|
img.alt = `${item.name} preview`;
|
||||||
img.loading = 'lazy';
|
img.loading = 'lazy';
|
||||||
|
bindImageLayout(img);
|
||||||
img.addEventListener('error', () => replaceWithUnavailable(slide, messageTimestampMs), { once: true });
|
img.addEventListener('error', () => replaceWithUnavailable(slide, messageTimestampMs), { once: true });
|
||||||
const play = document.createElement('span');
|
const play = document.createElement('span');
|
||||||
play.className = 'message-attachment-play';
|
play.className = 'message-attachment-play';
|
||||||
@@ -351,6 +365,7 @@ export function createAttachmentCarouselElement(attachments = [], { gateway = ''
|
|||||||
next.setAttribute('aria-label', 'Следующее вложение');
|
next.setAttribute('aria-label', 'Следующее вложение');
|
||||||
const counter = document.createElement('div');
|
const counter = document.createElement('div');
|
||||||
counter.className = 'message-attachment-counter';
|
counter.className = 'message-attachment-counter';
|
||||||
|
wrap.classList.toggle('has-single-attachment', items.length <= 1);
|
||||||
|
|
||||||
const render = () => {
|
const render = () => {
|
||||||
stopVideos(wrap);
|
stopVideos(wrap);
|
||||||
@@ -358,6 +373,8 @@ export function createAttachmentCarouselElement(attachments = [], { gateway = ''
|
|||||||
const kind = getAttachmentKind(item);
|
const kind = getAttachmentKind(item);
|
||||||
const url = buildArweaveDataUrl({ gateway, txId: item.ar });
|
const url = buildArweaveDataUrl({ gateway, txId: item.ar });
|
||||||
const previewUrl = item.preview?.ar ? buildArweaveDataUrl({ gateway, txId: item.preview.ar }) : '';
|
const previewUrl = item.preview?.ar ? buildArweaveDataUrl({ gateway, txId: item.preview.ar }) : '';
|
||||||
|
viewport.classList.remove('has-landscape-media');
|
||||||
|
slide.className = 'message-attachment-slide';
|
||||||
slide.dataset.unavailable = '0';
|
slide.dataset.unavailable = '0';
|
||||||
slide.replaceChildren();
|
slide.replaceChildren();
|
||||||
slide.append(kind === 'file'
|
slide.append(kind === 'file'
|
||||||
@@ -365,7 +382,8 @@ export function createAttachmentCarouselElement(attachments = [], { gateway = ''
|
|||||||
: createMediaSlide({ item, url, kind, messageTimestampMs, slide, previewUrl }));
|
: createMediaSlide({ item, url, kind, messageTimestampMs, slide, previewUrl }));
|
||||||
prev.hidden = items.length <= 1 || index <= 0;
|
prev.hidden = items.length <= 1 || index <= 0;
|
||||||
next.hidden = items.length <= 1 || index >= items.length - 1;
|
next.hidden = items.length <= 1 || index >= items.length - 1;
|
||||||
counter.textContent = `${index + 1} из ${items.length}`;
|
counter.hidden = items.length <= 1;
|
||||||
|
counter.textContent = items.length > 1 ? `${index + 1} из ${items.length}` : '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const move = (delta) => {
|
const move = (delta) => {
|
||||||
|
|||||||
@@ -2305,12 +2305,21 @@ textarea.input {
|
|||||||
touch-action: pan-y;
|
touch-action: pan-y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-attachment-carousel-viewport.has-landscape-media {
|
||||||
|
align-items: start;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.message-attachment-slide {
|
.message-attachment-slide {
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
display: grid;
|
display: grid;
|
||||||
min-height: 12rem;
|
min-height: 12rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-attachment-slide.has-landscape-media {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.message-attachment-arrow {
|
.message-attachment-arrow {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: rgba(15, 23, 42, 0.62);
|
background: rgba(15, 23, 42, 0.62);
|
||||||
@@ -2363,6 +2372,10 @@ textarea.input {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-attachment-media-frame.is-landscape {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.message-attachment-media {
|
.message-attachment-media {
|
||||||
display: block;
|
display: block;
|
||||||
height: min(52vh, 20rem);
|
height: min(52vh, 20rem);
|
||||||
@@ -2371,6 +2384,10 @@ textarea.input {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-attachment-media.is-landscape {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.message-attachment-play {
|
.message-attachment-play {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: rgba(15, 23, 42, 0.72);
|
background: rgba(15, 23, 42, 0.72);
|
||||||
@@ -3960,6 +3977,14 @@ textarea.input {
|
|||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.channels-screen--channel .channel-message-card.is-diary-entry {
|
||||||
|
width: min(100%, 28rem);
|
||||||
|
max-width: min(84vw, 28rem);
|
||||||
|
padding: 16px 16px 15px;
|
||||||
|
justify-self: center;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
.channel-message-top {
|
.channel-message-top {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -4084,6 +4109,11 @@ textarea.input {
|
|||||||
box-shadow: 0 0 38px rgba(181, 136, 42, 0.14);
|
box-shadow: 0 0 38px rgba(181, 136, 42, 0.14);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.channels-screen .channel-message-card.is-focus-flash {
|
||||||
|
border-color: rgba(255, 214, 117, 0.5);
|
||||||
|
box-shadow: 0 0 0 1px rgba(255, 214, 117, 0.28), 0 0 34px rgba(255, 214, 117, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
.channel-message-body {
|
.channel-message-body {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
@@ -5460,6 +5490,58 @@ textarea.input {
|
|||||||
color: #d9e6ff;
|
color: #d9e6ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.channels-screen--channel .page-header .channel-header-entrypoint-btn {
|
||||||
|
border: 1px solid rgba(224, 190, 117, 0.38);
|
||||||
|
background: linear-gradient(180deg, rgba(57, 74, 119, 0.34), rgba(18, 29, 54, 0.34));
|
||||||
|
color: #f1d99c;
|
||||||
|
border-radius: 10px;
|
||||||
|
min-height: 36px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
transform: translateY(-40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entrypoint-history-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
max-height: min(58vh, 30rem);
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entrypoint-history-item {
|
||||||
|
appearance: none;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: rgba(17, 24, 39, 0.72);
|
||||||
|
color: #eef3ff;
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 12px 13px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entrypoint-history-item strong {
|
||||||
|
color: #f1d99c;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entrypoint-history-item span {
|
||||||
|
color: rgba(188, 208, 244, 0.74);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entrypoint-history-item p {
|
||||||
|
margin: 0;
|
||||||
|
color: #f5f8ff;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
.channel-head-actions .secondary-btn {
|
.channel-head-actions .secondary-btn {
|
||||||
background: rgba(255, 255, 255, 0.04);
|
background: rgba(255, 255, 255, 0.04);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
|||||||
Reference in New Issue
Block a user