прод: убрать ЛС-лабу (demo-чат, гость-доступ, isDemo, demo-avatars); мок→плейсхолдер
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
465792b2ab
commit
92791c77a9
Binary file not shown.
|
Before Width: | Height: | Size: 4.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 6.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.8 KiB |
@ -63,7 +63,6 @@ import * as appLogView from './pages/app-log-view.js';
|
|||||||
import * as pwaDiagnosticsView from './pages/pwa-diagnostics-view.js';
|
import * as pwaDiagnosticsView from './pages/pwa-diagnostics-view.js';
|
||||||
import * as solanaUsersInitView from './pages/solana-users-init-view.js';
|
import * as solanaUsersInitView from './pages/solana-users-init-view.js';
|
||||||
import * as messagesList from './pages/messages-list.js';
|
import * as messagesList from './pages/messages-list.js';
|
||||||
import * as dmLabChat from './pages/messages/dm-lab-chat.js';
|
|
||||||
import * as contactSearchView from './pages/contact-search-view.js';
|
import * as contactSearchView from './pages/contact-search-view.js';
|
||||||
import * as chatView from './pages/chat-view.js';
|
import * as chatView from './pages/chat-view.js';
|
||||||
import * as userProfileView from './pages/user-profile-view.js';
|
import * as userProfileView from './pages/user-profile-view.js';
|
||||||
@ -106,7 +105,6 @@ const routes = {
|
|||||||
'pwa-diagnostics-view': pwaDiagnosticsView,
|
'pwa-diagnostics-view': pwaDiagnosticsView,
|
||||||
'solana-users-init-view': solanaUsersInitView,
|
'solana-users-init-view': solanaUsersInitView,
|
||||||
'messages-list': messagesList,
|
'messages-list': messagesList,
|
||||||
'dm-lab-chat': dmLabChat,
|
|
||||||
'contact-search-view': contactSearchView,
|
'contact-search-view': contactSearchView,
|
||||||
'chat-view': chatView,
|
'chat-view': chatView,
|
||||||
user: userProfileView,
|
user: userProfileView,
|
||||||
@ -153,7 +151,6 @@ const GUEST_ALLOWED_PAGES = new Set([
|
|||||||
'channel-thread-view',
|
'channel-thread-view',
|
||||||
'user',
|
'user',
|
||||||
'contact-search-view',
|
'contact-search-view',
|
||||||
'dm-lab-chat', // demo-чат лаборатории ЛС (мок, без сессии)
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
setClientErrorTransport((payload) => authService.reportClientUiError(payload));
|
setClientErrorTransport((payload) => authService.reportClientUiError(payload));
|
||||||
@ -690,10 +687,7 @@ function renderApp() {
|
|||||||
const route = getRoute();
|
const route = getRoute();
|
||||||
const pageId = route.pageId || (state.session.isAuthorized ? 'messages-list' : 'start-view');
|
const pageId = route.pageId || (state.session.isAuthorized ? 'messages-list' : 'start-view');
|
||||||
|
|
||||||
// Гостю доступен ТОЛЬКО demo-маршрут ЛС (/messages-list/lab) — для оффлайн-проверки редизайна без сессии.
|
if (!state.session.isAuthorized && !PRE_AUTH_PAGES.includes(pageId) && !GUEST_ALLOWED_PAGES.has(pageId)) {
|
||||||
// Реальный /messages-list остаётся защищённым (mode пустой → редирект на start-view).
|
|
||||||
const isDmDemo = pageId === 'messages-list' && route.params?.mode === 'lab';
|
|
||||||
if (!state.session.isAuthorized && !PRE_AUTH_PAGES.includes(pageId) && !GUEST_ALLOWED_PAGES.has(pageId) && !isDmDemo) {
|
|
||||||
navigate('start-view');
|
navigate('start-view');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { state } from '../state.js';
|
|||||||
import { renderUserAvatar } from '../components/avatar-image.js';
|
import { renderUserAvatar } from '../components/avatar-image.js';
|
||||||
import { loadProfileSnapshot } from '../services/user-profile-params.js';
|
import { loadProfileSnapshot } from '../services/user-profile-params.js';
|
||||||
import { resolveDmVisualState } from './messages/dm-visual-resolver.js';
|
import { resolveDmVisualState } from './messages/dm-visual-resolver.js';
|
||||||
import { getPreview, getUnread } from './messages/dm-lab-store.js';
|
|
||||||
import { makeProfileRoute } from '../services/shine-routes.js';
|
import { makeProfileRoute } from '../services/shine-routes.js';
|
||||||
|
|
||||||
export const pageMeta = { id: 'messages-list', title: 'Личные сообщения' };
|
export const pageMeta = { id: 'messages-list', title: 'Личные сообщения' };
|
||||||
@ -80,14 +79,8 @@ export function render({ navigate, route }) {
|
|||||||
const screen = document.createElement('section');
|
const screen = document.createElement('section');
|
||||||
screen.className = 'dm-screen dm-list-screen';
|
screen.className = 'dm-screen dm-list-screen';
|
||||||
|
|
||||||
// demo/lab: гость без сессии (маршрут /messages-list/lab или ?demo=1). В demo НЕ ходим в сеть за фото
|
// Слева сверху — имя владельца аккаунта (реальный логин из сессии).
|
||||||
// профиля — иначе висящие listUserParams не дают сети уйти в idle и ломают скриншоты (остаются initials).
|
const login = String(state.session.login || '').trim();
|
||||||
const isDemo = route?.params?.mode === 'lab'
|
|
||||||
|| (typeof window !== 'undefined' && /[?&]demo=1(?:&|$)/.test(window.location.search || ''));
|
|
||||||
|
|
||||||
// Слева сверху — имя владельца аккаунта (на проде реальный логин; в demo — заглушка, НЕ «shine»,
|
|
||||||
// чтобы не дублировать центральный бренд «Shine»).
|
|
||||||
const login = String(state.session.login || '').trim() || 'Aidar007';
|
|
||||||
|
|
||||||
// DM-шапка: grid 1fr auto 1fr (бренд слева, title строго по центру, «+» справа).
|
// DM-шапка: grid 1fr auto 1fr (бренд слева, title строго по центру, «+» справа).
|
||||||
const head = document.createElement('header');
|
const head = document.createElement('header');
|
||||||
@ -111,12 +104,10 @@ export function render({ navigate, route }) {
|
|||||||
list.className = 'dm-list';
|
list.className = 'dm-list';
|
||||||
|
|
||||||
function renderRow(item) {
|
function renderRow(item) {
|
||||||
// В demo превью/непрочитанные берём из dm-lab-store (обновляются после отправки/открытия чата).
|
const v = resolveDmVisualState(item); // { tone, shining, confirmed, via, unread }
|
||||||
const resolverItem = isDemo ? { ...item, unreadCount: getUnread(item.id) } : item;
|
|
||||||
const v = resolveDmVisualState(resolverItem); // { tone, shining, confirmed, via, unread }
|
|
||||||
const cardVariant = v.tone === 'family' ? ' dm-card--family' : (v.tone === 'shining' ? ' dm-card--shining' : '');
|
const cardVariant = v.tone === 'family' ? ' dm-card--family' : (v.tone === 'shining' ? ' dm-card--shining' : '');
|
||||||
const name = item.name || item.id;
|
const name = item.name || item.id;
|
||||||
const preview = (isDemo ? getPreview(item.id, item.preview || item.lastMessage || '') : (item.preview || item.lastMessage || '')) || 'Диалог пока пуст.';
|
const preview = (item.preview || item.lastMessage || '') || 'Диалог пока пуст.';
|
||||||
|
|
||||||
const row = document.createElement('article');
|
const row = document.createElement('article');
|
||||||
row.className = `dm-dialog-card${cardVariant}`;
|
row.className = `dm-dialog-card${cardVariant}`;
|
||||||
@ -199,14 +190,12 @@ export function render({ navigate, route }) {
|
|||||||
// Аватар: фото/инициалы без кольца; у сияющего — свечение (класс is-shine).
|
// Аватар: фото/инициалы без кольца; у сияющего — свечение (класс is-shine).
|
||||||
const avWrap = document.createElement('div');
|
const avWrap = document.createElement('div');
|
||||||
avWrap.className = `dm-av dm-av--${v.tone}${v.shining ? ' is-shine' : ''}`;
|
avWrap.className = `dm-av dm-av--${v.tone}${v.shining ? ' is-shine' : ''}`;
|
||||||
const avatarEl = createDmAvatar(item.id, { upgrade: !isDemo, name, photo: isDemo ? item.photo : '' });
|
const avatarEl = createDmAvatar(item.id, { upgrade: true, name });
|
||||||
avatarEl.classList.add('avatar');
|
avatarEl.classList.add('avatar');
|
||||||
avWrap.appendChild(avatarEl);
|
avWrap.appendChild(avatarEl);
|
||||||
row.prepend(avWrap);
|
row.prepend(avWrap);
|
||||||
|
|
||||||
const go = () => navigate(isDemo
|
const go = () => navigate(`chat-view/${encodeURIComponent(item.id)}`);
|
||||||
? `messages-list/lab/chat/${encodeURIComponent(item.id)}`
|
|
||||||
: `chat-view/${encodeURIComponent(item.id)}`);
|
|
||||||
row.addEventListener('click', go);
|
row.addEventListener('click', go);
|
||||||
row.addEventListener('keydown', (e) => {
|
row.addEventListener('keydown', (e) => {
|
||||||
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); go(); }
|
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); go(); }
|
||||||
@ -214,9 +203,8 @@ export function render({ navigate, route }) {
|
|||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Оффлайн-демо: список из мока directMessages (с семантическими полями).
|
// Источник списка — мок directMessages (плейсхолдер). На проде заменяется реальными
|
||||||
// На проде источник заменяется на реальные relations (relationFlagsForTarget/shineConfirmed/shine) —
|
// relations/chats (relationFlagsForTarget/shineConfirmed/shine) — карточки и резолвер не меняются.
|
||||||
// карточки и резолвер не меняются.
|
|
||||||
const items = Array.isArray(directMessages) ? directMessages : [];
|
const items = Array.isArray(directMessages) ? directMessages : [];
|
||||||
if (!items.length) {
|
if (!items.length) {
|
||||||
const empty = document.createElement('div');
|
const empty = document.createElement('div');
|
||||||
|
|||||||
@ -1,70 +0,0 @@
|
|||||||
// Demo-чат для оффлайн-флоу /messages-list/lab/chat/:id (только demo).
|
|
||||||
// Реальный chat-view.js НЕ трогаем (он завязан на бэкенд/WS); здесь — изолированная мок-страница.
|
|
||||||
// Состояние сообщений берём из dm-lab-store (localStorage). Без сети, без авторизации.
|
|
||||||
import { directMessages } from '../../mock-data.js';
|
|
||||||
import { getThread, appendOut, markRead } from './dm-lab-store.js';
|
|
||||||
|
|
||||||
// showAppChrome:false — у чата свой низ (поле ввода), нижнее меню прячем.
|
|
||||||
export const pageMeta = { id: 'dm-lab-chat', title: 'Чат (demo)', showAppChrome: false };
|
|
||||||
|
|
||||||
function findDialog(id) {
|
|
||||||
return (Array.isArray(directMessages) ? directMessages : []).find((m) => m.id === id) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function bubble(m) {
|
|
||||||
const b = document.createElement('div');
|
|
||||||
b.className = `bubble ${m && m.from === 'out' ? 'out' : 'in'}`;
|
|
||||||
b.textContent = m ? m.text : '';
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function render({ navigate, route }) {
|
|
||||||
const chatId = String(route?.params?.chatId || '').trim();
|
|
||||||
const dialog = findDialog(chatId);
|
|
||||||
const name = (dialog && dialog.name) || chatId || 'Диалог';
|
|
||||||
|
|
||||||
// Открытие диалога сбрасывает у него непрочитанные (demo).
|
|
||||||
markRead(chatId);
|
|
||||||
|
|
||||||
const screen = document.createElement('section');
|
|
||||||
screen.className = 'dm-screen dm-chat-screen';
|
|
||||||
|
|
||||||
// Шапка чата: назад + имя собеседника + demo-метка.
|
|
||||||
const head = document.createElement('header');
|
|
||||||
head.className = 'dm-chat-head';
|
|
||||||
head.innerHTML = `
|
|
||||||
<button type="button" class="dm-chat-back" aria-label="Назад">‹</button>
|
|
||||||
<span class="dm-chat-peer">${name}</span>
|
|
||||||
<span class="dm-chat-demo-tag">demo</span>
|
|
||||||
`;
|
|
||||||
head.querySelector('.dm-chat-back').addEventListener('click', () => navigate('messages-list/lab'));
|
|
||||||
|
|
||||||
const log = document.createElement('div');
|
|
||||||
log.className = 'dm-messages-log';
|
|
||||||
getThread(chatId).forEach((m) => log.append(bubble(m)));
|
|
||||||
|
|
||||||
const inputRow = document.createElement('form');
|
|
||||||
inputRow.className = 'dm-chat-input';
|
|
||||||
inputRow.innerHTML = `
|
|
||||||
<textarea class="dm-input" rows="1" placeholder="Сообщение…" aria-label="Текст сообщения"></textarea>
|
|
||||||
<button type="submit" class="dm-send-btn dm-send-icon-btn" aria-label="Отправить">➤</button>
|
|
||||||
`;
|
|
||||||
const field = inputRow.querySelector('.dm-input');
|
|
||||||
|
|
||||||
const scrollToEnd = () => requestAnimationFrame(() => { log.scrollTop = log.scrollHeight; });
|
|
||||||
const submit = () => {
|
|
||||||
const msg = appendOut(chatId, field.value);
|
|
||||||
if (!msg) return;
|
|
||||||
field.value = '';
|
|
||||||
log.append(bubble(msg));
|
|
||||||
scrollToEnd();
|
|
||||||
};
|
|
||||||
inputRow.addEventListener('submit', (e) => { e.preventDefault(); submit(); });
|
|
||||||
field.addEventListener('keydown', (e) => {
|
|
||||||
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); submit(); }
|
|
||||||
});
|
|
||||||
|
|
||||||
screen.append(head, log, inputRow);
|
|
||||||
scrollToEnd();
|
|
||||||
return screen;
|
|
||||||
}
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
// Demo-состояние ЛС для оффлайн-флоу /messages-list/lab (только demo, на проде НЕ используется).
|
|
||||||
// Хранится в localStorage, поэтому переживает навигацию список ↔ чат и перезагрузку.
|
|
||||||
// Источник стартовых тредов — мок directMessages: последнее сообщение = preview карточки.
|
|
||||||
import { directMessages } from '../../mock-data.js';
|
|
||||||
|
|
||||||
const KEY = 'dm-lab-demo-v1';
|
|
||||||
|
|
||||||
function nowLabel() {
|
|
||||||
try {
|
|
||||||
const d = new Date();
|
|
||||||
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
|
||||||
} catch {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Стартовый набор тредов: пара входящих, последнее = preview карточки (чтобы список совпал с моком).
|
|
||||||
function seed() {
|
|
||||||
const store = {};
|
|
||||||
(Array.isArray(directMessages) ? directMessages : []).forEach((m) => {
|
|
||||||
const last = m.preview || m.lastMessage || 'Сообщение';
|
|
||||||
store[m.id] = {
|
|
||||||
unread: Math.max(0, Math.trunc(Number(m.unreadCount) || 0)),
|
|
||||||
messages: [
|
|
||||||
{ from: 'in', text: 'Привет! Это тестовый диалог demo-режима.', time: m.time || '' },
|
|
||||||
{ from: 'in', text: last, time: m.time || '' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return store;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readAll() {
|
|
||||||
try {
|
|
||||||
const raw = localStorage.getItem(KEY);
|
|
||||||
if (raw) {
|
|
||||||
const parsed = JSON.parse(raw);
|
|
||||||
if (parsed && typeof parsed === 'object') return parsed;
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
const fresh = seed();
|
|
||||||
writeAll(fresh);
|
|
||||||
return fresh;
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeAll(store) {
|
|
||||||
try { localStorage.setItem(KEY, JSON.stringify(store)); } catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getThread(id) {
|
|
||||||
const all = readAll();
|
|
||||||
const t = all[id];
|
|
||||||
return t && Array.isArray(t.messages) ? t.messages : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getUnread(id) {
|
|
||||||
const all = readAll();
|
|
||||||
return all[id] ? Math.max(0, Math.trunc(Number(all[id].unread) || 0)) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Превью для списка = текст последнего сообщения треда (или fallback из мока).
|
|
||||||
export function getPreview(id, fallback = '') {
|
|
||||||
const msgs = getThread(id);
|
|
||||||
const last = msgs[msgs.length - 1];
|
|
||||||
return last && last.text ? last.text : fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Добавить исходящее сообщение; вернуть его (или null, если текст пуст).
|
|
||||||
export function appendOut(id, text) {
|
|
||||||
const clean = String(text || '').trim();
|
|
||||||
if (!id || !clean) return null;
|
|
||||||
const all = readAll();
|
|
||||||
if (!all[id]) all[id] = { unread: 0, messages: [] };
|
|
||||||
const msg = { from: 'out', text: clean, time: nowLabel() };
|
|
||||||
all[id].messages.push(msg);
|
|
||||||
writeAll(all);
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Открытие диалога сбрасывает непрочитанные у него.
|
|
||||||
export function markRead(id) {
|
|
||||||
const all = readAll();
|
|
||||||
if (all[id]) { all[id].unread = 0; writeAll(all); }
|
|
||||||
}
|
|
||||||
|
|
||||||
// На случай отладки: сбросить demo-состояние к стартовому.
|
|
||||||
export function resetDemo() {
|
|
||||||
writeAll(seed());
|
|
||||||
}
|
|
||||||
@ -150,15 +150,6 @@ export function getRoute() {
|
|||||||
return { pageId, params: { mode: segments[1] ? decodePart(segments[1]) : '' } };
|
return { pageId, params: { mode: segments[1] ? decodePart(segments[1]) : '' } };
|
||||||
}
|
}
|
||||||
|
|
||||||
// messages-list/<mode>: ловим второй сегмент как mode (нужно для demo-маршрута /messages-list/lab).
|
|
||||||
if (pageId === 'messages-list') {
|
|
||||||
// demo-чат под лабораторным префиксом: /messages-list/lab/chat/:id → отдельная demo-страница.
|
|
||||||
if (segments[1] === 'lab' && segments[2] === 'chat' && segments[3]) {
|
|
||||||
return { pageId: 'dm-lab-chat', params: { chatId: decodePart(segments[3]) } };
|
|
||||||
}
|
|
||||||
return { pageId, params: { mode: segments[1] ? decodePart(segments[1]) : '' } };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { pageId, params: {} };
|
return { pageId, params: {} };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3730,33 +3730,6 @@ button.dm-via-node:hover { border-color: rgba(25, 229, 138, 0.5); }
|
|||||||
даёт лишний скролл. Фон НЕ меняем — клиппим overflow на уровне страницы (как просит ТЗ, п.4). */
|
даёт лишний скролл. Фон НЕ меняем — клиппим overflow на уровне страницы (как просит ТЗ, п.4). */
|
||||||
html, body { overflow-x: hidden; }
|
html, body { overflow-x: hidden; }
|
||||||
|
|
||||||
/* ===== Demo-чат лаборатории ЛС (dm-lab-chat) — только demo, прод chat-view не затрагивает ===== */
|
|
||||||
.dm-chat-screen { display: flex; flex-direction: column; min-height: 100%; }
|
|
||||||
.dm-chat-head {
|
|
||||||
position: sticky; top: 0; z-index: 12; display: flex; align-items: center; gap: 12px;
|
|
||||||
padding: 14px; border-bottom: 1px solid rgba(240, 184, 46, 0.22);
|
|
||||||
background: linear-gradient(180deg, rgba(10, 12, 18, 0.92), rgba(10, 12, 18, 0.55));
|
|
||||||
backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
|
|
||||||
}
|
|
||||||
.dm-chat-back {
|
|
||||||
flex: 0 0 auto; width: 40px; height: 40px; border-radius: 12px;
|
|
||||||
display: grid; place-items: center; font-size: 26px; line-height: 1;
|
|
||||||
color: var(--rel-family); border: 1px solid rgba(240, 184, 46, 0.4);
|
|
||||||
background: rgba(10, 12, 18, 0.6); cursor: pointer;
|
|
||||||
}
|
|
||||||
.dm-chat-peer { flex: 1 1 auto; min-width: 0; font-size: 17px; font-weight: 700; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
||||||
.dm-chat-demo-tag {
|
|
||||||
flex: 0 0 auto; font-size: 11px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
|
|
||||||
color: rgba(244, 246, 255, 0.55); padding: 3px 8px; border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.12);
|
|
||||||
}
|
|
||||||
.dm-chat-screen .dm-messages-log {
|
|
||||||
flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 10px;
|
|
||||||
padding: 14px; padding-bottom: 16px;
|
|
||||||
}
|
|
||||||
.dm-chat-screen .bubble.in { align-self: flex-start; }
|
|
||||||
.dm-chat-screen .bubble.out { align-self: flex-end; }
|
|
||||||
.dm-chat-screen .dm-chat-input { display: grid; }
|
|
||||||
|
|
||||||
.dm-chat-wrap {
|
.dm-chat-wrap {
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user