UI: DM список метаданных и Enter/Ctrl+Enter в чате
This commit is contained in:
parent
c6d310184b
commit
8325cbec84
@ -0,0 +1,24 @@
|
|||||||
|
# Личные сообщения: правая мета-колонка и Enter/Ctrl+Enter
|
||||||
|
|
||||||
|
- Краткое описание:
|
||||||
|
- В списке `Личные сообщения` обновлена правая колонка карточки диалога:
|
||||||
|
- сверху отображается бейдж количества непрочитанных (если есть);
|
||||||
|
- снизу маленьким шрифтом отображается дата/время последнего сообщения;
|
||||||
|
- если сообщений нет, вместо времени отображается `-`.
|
||||||
|
- В экране чата нижний блок ввода закреплён (sticky) и остаётся на месте при прокрутке.
|
||||||
|
- В поле ввода чата изменено поведение клавиш:
|
||||||
|
- `Enter` отправляет сообщение;
|
||||||
|
- `Ctrl+Enter` добавляет перенос строки и не отправляет сообщение.
|
||||||
|
|
||||||
|
- Что проверять:
|
||||||
|
- В карточках диалогов справа корректно показываются непрочитанные/время/прочерк.
|
||||||
|
- В чате нижний блок ввода не уезжает при прокрутке истории.
|
||||||
|
- `Enter` отправляет сообщение из textarea.
|
||||||
|
- `Ctrl+Enter` вставляет новую строку в textarea.
|
||||||
|
|
||||||
|
- Ожидаемый результат:
|
||||||
|
- Список диалогов показывает полезную мета-информацию в стабильном формате.
|
||||||
|
- Ввод сообщений в чате работает в привычной схеме Enter/многострочность.
|
||||||
|
|
||||||
|
- Статус:
|
||||||
|
- `pending`
|
||||||
@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.71
|
client.version=1.2.72
|
||||||
server.version=1.2.65
|
server.version=1.2.66
|
||||||
|
|||||||
@ -416,6 +416,16 @@ export function render({ navigate, route }) {
|
|||||||
const input = form.elements.message;
|
const input = form.elements.message;
|
||||||
autoResizeComposer(input);
|
autoResizeComposer(input);
|
||||||
input?.addEventListener('input', () => autoResizeComposer(input));
|
input?.addEventListener('input', () => autoResizeComposer(input));
|
||||||
|
input?.addEventListener('keydown', async (event) => {
|
||||||
|
if (event.key !== 'Enter') return;
|
||||||
|
if (event.ctrlKey) return;
|
||||||
|
event.preventDefault();
|
||||||
|
const text = String(input.value || '').trim();
|
||||||
|
if (!text) return;
|
||||||
|
input.value = '';
|
||||||
|
autoResizeComposer(input);
|
||||||
|
await sendTextMessage(text);
|
||||||
|
});
|
||||||
|
|
||||||
form.querySelector('#chat-voice-input')?.addEventListener('click', async () => {
|
form.querySelector('#chat-voice-input')?.addEventListener('click', async () => {
|
||||||
await openSpeechInputModal({
|
await openSpeechInputModal({
|
||||||
|
|||||||
@ -11,6 +11,17 @@ import { loadCurrentRelations } from '../services/user-connections.js';
|
|||||||
|
|
||||||
export const pageMeta = { id: 'messages-list', title: 'Личные сообщения' };
|
export const pageMeta = { id: 'messages-list', title: 'Личные сообщения' };
|
||||||
|
|
||||||
|
function formatChatRowTime(ts) {
|
||||||
|
const value = Number(ts || 0);
|
||||||
|
if (!Number.isFinite(value) || value <= 0) return '-';
|
||||||
|
return new Intl.DateTimeFormat('ru-RU', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
}).format(new Date(value));
|
||||||
|
}
|
||||||
|
|
||||||
export function render({ navigate }) {
|
export function render({ navigate }) {
|
||||||
const screen = document.createElement('section');
|
const screen = document.createElement('section');
|
||||||
screen.className = 'stack dm-screen dm-list-screen';
|
screen.className = 'stack dm-screen dm-list-screen';
|
||||||
@ -38,9 +49,9 @@ export function render({ navigate }) {
|
|||||||
</div>
|
</div>
|
||||||
<p class="meta-muted" style="margin-top:4px;">${item.lastMessage}</p>
|
<p class="meta-muted" style="margin-top:4px;">${item.lastMessage}</p>
|
||||||
</div>
|
</div>
|
||||||
<div style="display:grid; justify-items:end; gap:6px;">
|
<div class="dm-row-meta-col">
|
||||||
<span class="meta-muted">${item.time}</span>
|
|
||||||
${item.unread ? `<span class="unread">${item.unread}</span>` : '<span></span>'}
|
${item.unread ? `<span class="unread">${item.unread}</span>` : '<span></span>'}
|
||||||
|
<span class="meta-muted dm-row-time">${item.time}</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
row.addEventListener('click', () => navigate(`chat-view/${encodeURIComponent(item.id)}`));
|
row.addEventListener('click', () => navigate(`chat-view/${encodeURIComponent(item.id)}`));
|
||||||
@ -59,12 +70,13 @@ export function render({ navigate }) {
|
|||||||
const chat = getChatMessages(login);
|
const chat = getChatMessages(login);
|
||||||
const lastChat = chat[chat.length - 1];
|
const lastChat = chat[chat.length - 1];
|
||||||
const unread = chat.filter((m) => m?.from === 'in' && m?.unread).length;
|
const unread = chat.filter((m) => m?.from === 'in' && m?.unread).length;
|
||||||
|
const lastTimeMs = Number(lastChat?.createdAtMs || 0);
|
||||||
return {
|
return {
|
||||||
id: login,
|
id: login,
|
||||||
initials: (login[0] || '?').toUpperCase(),
|
initials: (login[0] || '?').toUpperCase(),
|
||||||
name: preview?.name || login,
|
name: preview?.name || login,
|
||||||
lastMessage: lastChat?.text || preview?.lastMessage || 'Диалог пока пуст.',
|
lastMessage: lastChat?.text || preview?.lastMessage || 'Диалог пока пуст.',
|
||||||
time: preview?.time || '—',
|
time: formatChatRowTime(lastTimeMs),
|
||||||
unread,
|
unread,
|
||||||
notInContacts: false,
|
notInContacts: false,
|
||||||
};
|
};
|
||||||
@ -81,12 +93,13 @@ export function render({ navigate }) {
|
|||||||
const chat = getChatMessages(login);
|
const chat = getChatMessages(login);
|
||||||
const lastChat = chat[chat.length - 1];
|
const lastChat = chat[chat.length - 1];
|
||||||
const unread = chat.filter((m) => m?.from === 'in' && m?.unread).length;
|
const unread = chat.filter((m) => m?.from === 'in' && m?.unread).length;
|
||||||
|
const lastTimeMs = Number(lastChat?.createdAtMs || 0);
|
||||||
return {
|
return {
|
||||||
id: login,
|
id: login,
|
||||||
initials: (login[0] || '?').toUpperCase(),
|
initials: (login[0] || '?').toUpperCase(),
|
||||||
name: login,
|
name: login,
|
||||||
lastMessage: lastChat?.text || 'Диалог пока пуст.',
|
lastMessage: lastChat?.text || 'Диалог пока пуст.',
|
||||||
time: 'сейчас',
|
time: formatChatRowTime(lastTimeMs),
|
||||||
unread,
|
unread,
|
||||||
notInContacts: true,
|
notInContacts: true,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3427,6 +3427,19 @@ textarea.input {
|
|||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.32);
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dm-row-meta-col {
|
||||||
|
display: grid;
|
||||||
|
justify-items: end;
|
||||||
|
align-content: start;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-row-time {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
.dm-chat-wrap {
|
.dm-chat-wrap {
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
@ -3465,6 +3478,14 @@ textarea.input {
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
align-items: end;
|
align-items: end;
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 10;
|
||||||
|
padding: 10px;
|
||||||
|
border-top: 1px solid rgba(212, 175, 55, 0.22);
|
||||||
|
background: rgba(8, 12, 20, 0.9);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dm-voice-btn {
|
.dm-voice-btn {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user