SHA256
Добавить просмотр лайков сообщений канала
This commit is contained in:
@@ -1790,6 +1790,169 @@ function renderChannelMetaEventCard(event) {
|
||||
return card;
|
||||
}
|
||||
|
||||
|
||||
function likeCategoryCounts(post) {
|
||||
const total = Math.max(0, Number(post?.likesCount || 0));
|
||||
const primary = Math.max(0, Math.min(total, Number(post?.primaryLikesCount || 0)));
|
||||
const shining = Math.max(0, Math.min(primary, Number(post?.shiningLikesCount || 0)));
|
||||
return {
|
||||
shining,
|
||||
official: Math.max(0, primary - shining),
|
||||
others: Math.max(0, total - primary),
|
||||
total,
|
||||
};
|
||||
}
|
||||
|
||||
function openMessageLikesListModal({ navigate, messageRef, initialTab = 'shining' }) {
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'channel-likes-modal-overlay';
|
||||
overlay.innerHTML = `
|
||||
<section class="channel-likes-modal" role="dialog" aria-modal="true" aria-label="Кто поставил лайк">
|
||||
<header class="channel-likes-modal__header">
|
||||
<h3>Лайки</h3>
|
||||
<button type="button" class="ui-button channel-likes-modal__close" aria-label="Закрыть">×</button>
|
||||
</header>
|
||||
<div class="channel-likes-tabs" role="tablist">
|
||||
<button type="button" class="ui-button" data-like-tab="shining">Сияющие <span data-like-tab-count="shining"></span></button>
|
||||
<button type="button" class="ui-button" data-like-tab="official">Официальные <span data-like-tab-count="official"></span></button>
|
||||
<button type="button" class="ui-button" data-like-tab="others">Остальные <span data-like-tab-count="others"></span></button>
|
||||
</div>
|
||||
<div class="channel-likes-modal__status">Загрузка...</div>
|
||||
<div class="channel-likes-user-list"></div>
|
||||
</section>
|
||||
`;
|
||||
document.body.append(overlay);
|
||||
|
||||
const modal = overlay.querySelector('.channel-likes-modal');
|
||||
const status = overlay.querySelector('.channel-likes-modal__status');
|
||||
const list = overlay.querySelector('.channel-likes-user-list');
|
||||
const close = () => overlay.remove();
|
||||
overlay.querySelector('.channel-likes-modal__close')?.addEventListener('click', close);
|
||||
overlay.addEventListener('click', (event) => { if (event.target === overlay) close(); });
|
||||
modal?.addEventListener('click', (event) => event.stopPropagation());
|
||||
|
||||
let activeTab = ['shining', 'official', 'others'].includes(initialTab) ? initialTab : 'shining';
|
||||
let payload = null;
|
||||
|
||||
const renderTab = () => {
|
||||
overlay.querySelectorAll('[data-like-tab]').forEach((button) => {
|
||||
const isActive = button.dataset.likeTab === activeTab;
|
||||
button.classList.toggle('is-active', isActive);
|
||||
button.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
||||
});
|
||||
if (!payload) return;
|
||||
const rows = Array.isArray(payload?.[activeTab]) ? payload[activeTab] : [];
|
||||
list.innerHTML = '';
|
||||
rows.forEach((row) => {
|
||||
const userButton = document.createElement('button');
|
||||
userButton.type = 'button';
|
||||
userButton.className = 'ui-button card row profile-list-row channel-like-user-row';
|
||||
const avatarRaw = String(row?.avatarAr || '').trim();
|
||||
userButton.append(renderUserAvatar({
|
||||
login: String(row?.login || '').trim() || 'unknown',
|
||||
firstName: String(row?.firstName || ''),
|
||||
lastName: String(row?.lastName || ''),
|
||||
avatar: avatarRaw ? { ar: avatarRaw } : null,
|
||||
size: 'md',
|
||||
}));
|
||||
const firstName = String(row?.firstName || '').trim();
|
||||
const lastName = String(row?.lastName || '').trim();
|
||||
const login = String(row?.login || '').trim();
|
||||
const name = [firstName, lastName].filter(Boolean).join(' ') || login;
|
||||
const text = document.createElement('div');
|
||||
text.className = 'profile-list-row-text';
|
||||
text.innerHTML = `<b>${escapeHtml(name)}</b><small>${escapeHtml(login)}</small>`;
|
||||
userButton.append(text);
|
||||
userButton.addEventListener('click', () => {
|
||||
close();
|
||||
navigate(makeProfileRoute(login));
|
||||
});
|
||||
list.append(userButton);
|
||||
});
|
||||
status.textContent = rows.length ? (payload?.truncated ? 'Показаны первые 1000 лайков.' : '') : 'В этом списке пока никого нет.';
|
||||
};
|
||||
|
||||
overlay.querySelectorAll('[data-like-tab]').forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
activeTab = button.dataset.likeTab;
|
||||
renderTab();
|
||||
});
|
||||
});
|
||||
renderTab();
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
payload = await authService.getMessageLikes(messageRef, 1000);
|
||||
if (!overlay.isConnected) return;
|
||||
['shining', 'official', 'others'].forEach((key) => {
|
||||
const countEl = overlay.querySelector(`[data-like-tab-count="${key}"]`);
|
||||
if (countEl) countEl.textContent = String(Array.isArray(payload?.[key]) ? payload[key].length : 0);
|
||||
});
|
||||
renderTab();
|
||||
} catch (error) {
|
||||
if (!overlay.isConnected) return;
|
||||
status.className = 'channel-likes-modal__status is-error';
|
||||
status.textContent = toUserMessage(error, 'Не удалось загрузить список лайков.');
|
||||
list.innerHTML = '';
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
function openMessageLikePopup({ anchor, post, navigate, onToggleLike }) {
|
||||
document.querySelectorAll('.channel-like-popup-layer').forEach((el) => el.remove());
|
||||
const counts = likeCategoryCounts(post);
|
||||
const layer = document.createElement('div');
|
||||
layer.className = 'channel-like-popup-layer';
|
||||
layer.innerHTML = `
|
||||
<section class="channel-like-popup" role="dialog" aria-label="Лайки сообщения">
|
||||
<div class="channel-like-popup__title">Лайки</div>
|
||||
<div class="channel-like-popup__counts">
|
||||
<button type="button" class="ui-button channel-like-count" data-like-list="shining"><b>${counts.shining}</b><span>Сияющие</span></button>
|
||||
<button type="button" class="ui-button channel-like-count" data-like-list="official"><b>${counts.official}</b><span>Официальные</span></button>
|
||||
<button type="button" class="ui-button channel-like-count" data-like-list="others"><b>${counts.others}</b><span>Остальные</span></button>
|
||||
</div>
|
||||
<div class="channel-like-popup__total">Всего лайков: <b>${counts.total}</b></div>
|
||||
<button type="button" class="ui-button channel-like-popup__action">${post.reactionState === 'liked' ? 'Убрать свой лайк' : 'Добавить свой лайк'}</button>
|
||||
<button type="button" class="ui-button channel-like-popup__close">Закрыть</button>
|
||||
</section>
|
||||
`;
|
||||
document.body.append(layer);
|
||||
const popup = layer.querySelector('.channel-like-popup');
|
||||
const rect = anchor.getBoundingClientRect();
|
||||
const width = Math.min(330, Math.max(270, window.innerWidth - 24));
|
||||
const left = Math.max(12, Math.min(window.innerWidth - width - 12, rect.left + rect.width / 2 - width / 2));
|
||||
const estimatedHeight = 255;
|
||||
const top = rect.bottom + estimatedHeight < window.innerHeight - 8
|
||||
? rect.bottom + 8
|
||||
: Math.max(8, rect.top - estimatedHeight - 8);
|
||||
popup.style.width = `${width}px`;
|
||||
popup.style.left = `${left}px`;
|
||||
popup.style.top = `${top}px`;
|
||||
|
||||
const close = () => layer.remove();
|
||||
layer.addEventListener('click', (event) => { if (event.target === layer) close(); });
|
||||
popup.addEventListener('click', (event) => event.stopPropagation());
|
||||
layer.querySelector('.channel-like-popup__close')?.addEventListener('click', close);
|
||||
layer.querySelectorAll('[data-like-list]').forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
const initialTab = button.dataset.likeList;
|
||||
close();
|
||||
openMessageLikesListModal({ navigate, messageRef: post.messageRef, initialTab });
|
||||
});
|
||||
});
|
||||
layer.querySelector('.channel-like-popup__action')?.addEventListener('click', async (event) => {
|
||||
const button = event.currentTarget;
|
||||
button.disabled = true;
|
||||
try {
|
||||
await onToggleLike(post.messageRef, post.reactionState === 'liked' ? 'unlike' : 'like');
|
||||
close();
|
||||
} catch (error) {
|
||||
button.disabled = false;
|
||||
showToast(toUserMessage(error, 'Не удалось изменить лайк.'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderPostCard(post, {
|
||||
navigate,
|
||||
selector,
|
||||
@@ -1976,18 +2139,11 @@ function renderPostCard(post, {
|
||||
`;
|
||||
setActionTitle(likeButton, isPending ? 'Лайк...' : 'Лайк');
|
||||
likeButton.disabled = isPending;
|
||||
likeButton.addEventListener('click', async (event) => {
|
||||
likeButton.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
animatePress(event.currentTarget);
|
||||
if (isPending) return;
|
||||
if (!isLiked) {
|
||||
const ok = window.confirm('Поставить лайк?');
|
||||
if (!ok) return;
|
||||
}
|
||||
await longPressFeel(event.currentTarget, 130);
|
||||
likeButton.disabled = true;
|
||||
setActionTitle(likeButton, 'Лайк...');
|
||||
await onToggleLike(post.messageRef, isLiked ? 'unlike' : 'like', { likeButton });
|
||||
openMessageLikePopup({ anchor: event.currentTarget, post, navigate, onToggleLike });
|
||||
});
|
||||
|
||||
const replyButton = document.createElement('button');
|
||||
|
||||
@@ -1595,6 +1595,17 @@ export class AuthService {
|
||||
return response.payload || {};
|
||||
}
|
||||
|
||||
async getMessageLikes(message, limit = 1000) {
|
||||
const normalizedMessage = {
|
||||
blockchainName: String(message?.blockchainName || '').trim(),
|
||||
blockNumber: Number(message?.blockNumber),
|
||||
blockHash: String(message?.blockHash || '').trim(),
|
||||
};
|
||||
const response = await this.ws.request('GetMessageLikes', { message: normalizedMessage, limit });
|
||||
if (response.status !== 200) throw opError('GetMessageLikes', response);
|
||||
return response.payload || {};
|
||||
}
|
||||
|
||||
async getMessageThread(message, depthUp = 20, depthDown = 2, limitChildrenPerNode = 50, login = '') {
|
||||
const normalizedMessage = {
|
||||
blockchainName: String(message?.blockchainName || '').trim(),
|
||||
|
||||
@@ -683,3 +683,143 @@
|
||||
color: #ffb7c5;
|
||||
background: rgba(var(--shine-action-blue-rgb), 0.12);
|
||||
}
|
||||
|
||||
/* Channel message likes: compact popup + full user list modal. */
|
||||
.channel-like-popup-layer,
|
||||
.channel-likes-modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1600;
|
||||
background: rgba(5, 9, 18, 0.26);
|
||||
}
|
||||
|
||||
.channel-like-popup {
|
||||
position: fixed;
|
||||
z-index: 1601;
|
||||
padding: 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 18px;
|
||||
background: rgba(20, 27, 44, 0.96);
|
||||
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.34);
|
||||
backdrop-filter: blur(18px);
|
||||
-webkit-backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
.channel-like-popup__title {
|
||||
margin-bottom: 10px;
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.channel-like-popup__counts {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.channel-like-count {
|
||||
min-width: 0;
|
||||
padding: 10px 6px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: inherit;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.channel-like-count b { font-size: 18px; }
|
||||
.channel-like-count span { font-size: 10px; opacity: .72; overflow-wrap: anywhere; }
|
||||
|
||||
.channel-like-popup__total {
|
||||
padding: 10px 2px 8px;
|
||||
font-size: 12px;
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
.channel-like-popup__action,
|
||||
.channel-like-popup__close {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
margin-top: 7px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.channel-like-popup__close { opacity: .72; }
|
||||
|
||||
.channel-likes-modal-overlay {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 14px;
|
||||
background: rgba(5, 9, 18, 0.58);
|
||||
}
|
||||
|
||||
.channel-likes-modal {
|
||||
width: min(560px, 100%);
|
||||
max-height: min(720px, calc(100vh - 28px));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 20px;
|
||||
background: rgba(18, 25, 42, 0.98);
|
||||
box-shadow: 0 24px 70px rgba(0, 0, 0, 0.42);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
}
|
||||
|
||||
.channel-likes-modal__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 14px 16px 8px;
|
||||
}
|
||||
|
||||
.channel-likes-modal__header h3 { margin: 0; }
|
||||
.channel-likes-modal__close { font-size: 24px; min-width: 38px; min-height: 38px; }
|
||||
|
||||
.channel-likes-tabs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 6px;
|
||||
padding: 8px 12px 10px;
|
||||
}
|
||||
|
||||
.channel-likes-tabs > button {
|
||||
min-width: 0;
|
||||
min-height: 40px;
|
||||
padding-inline: 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.channel-likes-tabs > button.is-active {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
.channel-likes-tabs span { margin-left: 3px; opacity: .68; }
|
||||
|
||||
.channel-likes-modal__status {
|
||||
min-height: 20px;
|
||||
padding: 0 16px 8px;
|
||||
font-size: 12px;
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.channel-likes-modal__status.is-error { opacity: 1; }
|
||||
|
||||
.channel-likes-user-list {
|
||||
overflow-y: auto;
|
||||
padding: 0 10px 14px;
|
||||
}
|
||||
|
||||
.channel-like-user-row {
|
||||
width: 100%;
|
||||
margin: 4px 0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user