UI: карточка автора в канале, профиль user и назад по истории
This commit is contained in:
parent
db2d9a666b
commit
90d10086d7
@ -0,0 +1,25 @@
|
||||
# Карточка автора в сообщении канала и стрелка «назад» по истории
|
||||
|
||||
- Краткое описание:
|
||||
- В `channel-view` в карточке сообщения добавлена вложенная плитка автора (аватар, логин, номер сообщения, дата/время).
|
||||
- Клик по плитке автора открывает профиль пользователя.
|
||||
- Клик по области сообщения (вне плитки автора и вне action-кнопок) открывает тред, как кнопка `Тред`.
|
||||
- Стрелка `назад` в `channel-view`, `channel-thread-view` и профиле переведена на реальную навигацию `history.back()`.
|
||||
- Маршрут профиля переименован с `user-profile-view` на `user`.
|
||||
|
||||
- Что проверять:
|
||||
- В канале у каждого сообщения сверху есть вложенная плитка автора.
|
||||
- Клик по вложенной плитке открывает профиль автора.
|
||||
- Клик по тексту/телу сообщения открывает тред.
|
||||
- Кнопки `Лайк`, `Ответить`, `Тред`, `Отправить` работают отдельно и не конфликтуют с кликом по карточке.
|
||||
- Стрелка `назад` возвращает на предыдущий экран по реальной истории переходов.
|
||||
- При отсутствии истории стрелка `назад` не делает переход.
|
||||
- Переходы на профиль работают по новому маршруту `user/{login}/...`.
|
||||
|
||||
- Ожидаемый результат:
|
||||
- Навигация в каналах и тредах соответствует ожидаемому UX.
|
||||
- Переходы в профиль и назад по истории работают стабильно.
|
||||
- Старый маршрут `user-profile-view` больше не используется.
|
||||
|
||||
- Статус:
|
||||
- `pending`
|
||||
@ -1,2 +1,2 @@
|
||||
client.version=1.2.66
|
||||
server.version=1.2.60
|
||||
client.version=1.2.67
|
||||
server.version=1.2.61
|
||||
|
||||
@ -101,7 +101,7 @@ const routes = {
|
||||
'messages-list': messagesList,
|
||||
'contact-search-view': contactSearchView,
|
||||
'chat-view': chatView,
|
||||
'user-profile-view': userProfileView,
|
||||
user: userProfileView,
|
||||
'channels-list': channelsList,
|
||||
'channel-view': channelView,
|
||||
'channel-thread-view': channelThreadView,
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
softHaptic,
|
||||
} from '../services/channels-ux.js';
|
||||
import { openSpeechInputModal } from '../components/speech-input-modal.js';
|
||||
import { navigateBack } from '../router.js';
|
||||
|
||||
export const pageMeta = { id: 'channel-thread-view', title: 'Тред' };
|
||||
|
||||
@ -197,17 +198,6 @@ async function resolveChannelDisplayNameFromServer(channelSelector) {
|
||||
}
|
||||
}
|
||||
|
||||
function buildBackRoute(selector) {
|
||||
if (selector?.short?.ownerBlockchainName && selector?.short?.channelName) {
|
||||
return [
|
||||
'channel',
|
||||
encodeRoutePart(selector.short.ownerBlockchainName),
|
||||
encodeRoutePart(selector.short.channelName),
|
||||
].join('/');
|
||||
}
|
||||
return 'channels-list';
|
||||
}
|
||||
|
||||
function buildThreadRouteFromTarget(target, selector) {
|
||||
if (!target) return '';
|
||||
return [
|
||||
@ -501,7 +491,6 @@ function renderSkeleton(screen) {
|
||||
|
||||
export function render({ navigate, route }) {
|
||||
const selector = parseThreadSelector(route);
|
||||
const backRoute = buildBackRoute(selector);
|
||||
const channelDisplayName = resolveChannelDisplayName(selector?.channel);
|
||||
const routeKey = `${selector?.message?.blockchainName || ''}:${selector?.message?.blockNumber || ''}:${selector?.message?.blockHash || ''}`;
|
||||
|
||||
@ -624,7 +613,7 @@ export function render({ navigate, route }) {
|
||||
screen.append(
|
||||
renderHeader({
|
||||
title: 'Тред',
|
||||
leftAction: { label: '<', onClick: () => navigate(backRoute) },
|
||||
leftAction: { label: '<', onClick: () => navigateBack() },
|
||||
}),
|
||||
);
|
||||
screen.append(channelIndicator, statusBox);
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
softHaptic,
|
||||
} from '../services/channels-ux.js';
|
||||
import { openSpeechInputModal } from '../components/speech-input-modal.js';
|
||||
import { navigateBack } from '../router.js';
|
||||
|
||||
export const pageMeta = { id: 'channel-view', title: 'Канал' };
|
||||
const CHANNEL_TYPE_PERSONAL = 100;
|
||||
@ -628,8 +629,9 @@ function renderPostCard(post, {
|
||||
const card = document.createElement('article');
|
||||
card.className = 'card stack channel-message-card';
|
||||
|
||||
const topRow = document.createElement('div');
|
||||
topRow.className = 'channel-message-top';
|
||||
const authorTile = document.createElement('button');
|
||||
authorTile.type = 'button';
|
||||
authorTile.className = 'channel-message-author-tile';
|
||||
|
||||
const avatar = document.createElement('div');
|
||||
avatar.className = 'channel-message-avatar';
|
||||
@ -654,13 +656,19 @@ function renderPostCard(post, {
|
||||
|
||||
title.append(loginEl, numberEl);
|
||||
authorBlock.append(title, timestamp);
|
||||
topRow.append(avatar, authorBlock);
|
||||
authorTile.append(avatar, authorBlock);
|
||||
authorTile.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
const cleanLogin = String(post.authorLogin || '').trim();
|
||||
if (!cleanLogin) return;
|
||||
navigate(`user/${encodeRoutePart(cleanLogin)}/channel-view`);
|
||||
});
|
||||
|
||||
const body = document.createElement('p');
|
||||
body.className = 'channel-message-body';
|
||||
body.textContent = post.body;
|
||||
|
||||
card.append(topRow, body);
|
||||
card.append(authorTile, body);
|
||||
|
||||
const refKey = messageRefKey(post.messageRef);
|
||||
if (refKey) {
|
||||
@ -688,6 +696,7 @@ function renderPostCard(post, {
|
||||
`;
|
||||
likeButton.disabled = isPending;
|
||||
likeButton.addEventListener('click', async (event) => {
|
||||
event.stopPropagation();
|
||||
animatePress(event.currentTarget);
|
||||
if (isPending) return;
|
||||
if (!isLiked) {
|
||||
@ -710,6 +719,7 @@ function renderPostCard(post, {
|
||||
<span class="channel-action-counter">${post.repliesCount || 0}</span>
|
||||
`;
|
||||
replyButton.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
animatePress(event.currentTarget);
|
||||
openReplyModal({
|
||||
navigate,
|
||||
@ -726,6 +736,7 @@ function renderPostCard(post, {
|
||||
<span class="channel-action-label">Тред</span>
|
||||
`;
|
||||
openThreadButton.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
animatePress(event.currentTarget);
|
||||
const route = buildThreadRoute(post.messageRef, selector);
|
||||
if (route) navigate(route);
|
||||
@ -747,6 +758,10 @@ function renderPostCard(post, {
|
||||
|
||||
actions.append(openThreadButton, shareButton);
|
||||
card.append(actions);
|
||||
card.addEventListener('click', () => {
|
||||
const route = buildThreadRoute(post.messageRef, selector);
|
||||
if (route) navigate(route);
|
||||
});
|
||||
return card;
|
||||
}
|
||||
|
||||
@ -978,7 +993,7 @@ export function render({ navigate, route }) {
|
||||
screen.append(
|
||||
renderHeader({
|
||||
title: '',
|
||||
leftAction: { label: '<', onClick: () => navigate('channels-list') },
|
||||
leftAction: { label: '<', onClick: () => navigateBack() },
|
||||
}),
|
||||
);
|
||||
screen.append(statusBox);
|
||||
|
||||
@ -53,7 +53,7 @@ export function render({ navigate }) {
|
||||
<div class="meta-muted">Профиль</div>
|
||||
`;
|
||||
row.addEventListener('click', () => {
|
||||
navigate(`user-profile-view/${encodeURIComponent(login)}/contact-search-view`);
|
||||
navigate(`user/${encodeURIComponent(login)}/contact-search-view`);
|
||||
});
|
||||
resultsList.append(row);
|
||||
});
|
||||
|
||||
@ -533,7 +533,7 @@ export function render({ navigate, route }) {
|
||||
const cleanLogin = normalizeLogin(login);
|
||||
if (!cleanLogin) return '';
|
||||
if (normKey(cleanLogin) === normKey(state.session.login)) return 'profile-view';
|
||||
return `user-profile-view/${encodeURIComponent(cleanLogin)}/${encodeURIComponent('network-view/keep-history')}`;
|
||||
return `user/${encodeURIComponent(cleanLogin)}/${encodeURIComponent('network-view/keep-history')}`;
|
||||
}
|
||||
|
||||
function helpText() {
|
||||
|
||||
@ -7,7 +7,9 @@ import {
|
||||
} from '../services/user-connections.js';
|
||||
import { renderUserAvatar } from '../components/avatar-image.js';
|
||||
|
||||
export const pageMeta = { id: 'user-profile-view', title: 'Чужой профиль' };
|
||||
import { navigateBack } from '../router.js';
|
||||
|
||||
export const pageMeta = { id: 'user', title: 'Чужой профиль' };
|
||||
|
||||
function escapeHtml(text) {
|
||||
return String(text || '')
|
||||
@ -147,7 +149,6 @@ function renderReadOnlyParams(card) {
|
||||
|
||||
export function render({ navigate, route }) {
|
||||
const requestedLogin = String(route.params.login || '').trim();
|
||||
const fromPage = String(route.params.fromPage || 'messages-list').trim() || 'messages-list';
|
||||
const sessionLogin = String(state.session.login || '').trim();
|
||||
|
||||
const screen = document.createElement('section');
|
||||
@ -163,7 +164,7 @@ export function render({ navigate, route }) {
|
||||
screen.append(
|
||||
renderHeader({
|
||||
title: 'Профиль пользователя',
|
||||
leftAction: { label: '←', onClick: () => navigate(fromPage) },
|
||||
leftAction: { label: '←', onClick: () => navigateBack() },
|
||||
rightActions: [{ label: 'Обновить', onClick: () => refresh() }],
|
||||
}),
|
||||
status,
|
||||
|
||||
@ -122,7 +122,7 @@ export function getRoute() {
|
||||
return { pageId, params: { sessionId: dynamicId ? decodeURIComponent(dynamicId) : '' } };
|
||||
}
|
||||
|
||||
if (pageId === 'user-profile-view') {
|
||||
if (pageId === 'user') {
|
||||
return {
|
||||
pageId,
|
||||
params: {
|
||||
@ -162,6 +162,11 @@ export function navigate(path) {
|
||||
window.dispatchEvent(new PopStateEvent('popstate'));
|
||||
}
|
||||
|
||||
export function navigateBack() {
|
||||
if (window.history.length <= 1) return;
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
export function resolveToolbarActive(pageId) {
|
||||
if (ROOT_PAGES.includes(pageId)) return pageId;
|
||||
if (
|
||||
@ -185,6 +190,6 @@ export function resolveToolbarActive(pageId) {
|
||||
}
|
||||
if (pageId === 'chat-view' || pageId === 'contact-search-view') return 'messages-list';
|
||||
if (pageId === 'channel-view' || pageId === 'channel-thread-view' || pageId === 'add-channel-view' || pageId === 'add-personal-public-chat-view') return 'channels-list';
|
||||
if (pageId === 'user-profile-view') return 'messages-list';
|
||||
if (pageId === 'user') return 'messages-list';
|
||||
return 'profile-view';
|
||||
}
|
||||
|
||||
@ -2171,6 +2171,23 @@ textarea.input {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.channel-message-author-tile {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.channel-message-author-tile:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.channel-message-avatar {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user