Исправить мобильный ввод в личном чате
This commit is contained in:
parent
827d2e9c3e
commit
8768e142e3
@ -6,6 +6,9 @@
|
||||
- при открытии уже существующего личного чата стартовая позиция выбирается по хвосту переписки:
|
||||
- если есть непрочитанные сообщения, открытие происходит на линии `Новые сообщения`;
|
||||
- если непрочитанных нет, чат открывается сразу в самом низу.
|
||||
- на мобильной экранной клавиатуре `Enter` больше не отправляет сообщение, а создаёт новую строку;
|
||||
- отправка выполняется только кнопкой `Отправить`;
|
||||
- после отправки фокус остаётся в поле ввода, чтобы экранная клавиатура не закрывалась автоматически.
|
||||
|
||||
## Что проверять
|
||||
|
||||
@ -17,6 +20,11 @@
|
||||
- проверить то же поведение после прихода подтверждения отправки/перерисовки списка.
|
||||
- открыть существующий чат с непрочитанными сообщениями и убедиться, что видна линия `Новые сообщения` и сообщения ниже неё;
|
||||
- открыть существующий чат без непрочитанных сообщений и убедиться, что открыт конец переписки, а не начало.
|
||||
- на телефоне нажать кнопку `Enter` на экранной клавиатуре и убедиться, что появляется новая строка, а сообщение не уходит;
|
||||
- нажать кнопку отправки и убедиться, что сообщение отправилось, осталось видимым внизу и клавиатура не закрылась;
|
||||
- отдельно проверить два сценария прокрутки после отправки:
|
||||
- пользователь уже почти внизу и прокрутка идёт плавно;
|
||||
- пользователь был заметно выше и чат догоняет новый хвост без лишних скачков.
|
||||
|
||||
## Ожидаемый результат
|
||||
|
||||
@ -24,6 +32,7 @@
|
||||
- при наборе доступно больше вертикального места;
|
||||
- собственное только что отправленное сообщение сразу попадает в видимую область.
|
||||
- при открытии чата пользователь сразу попадает в актуальную часть переписки.
|
||||
- мобильный ввод не конфликтует с привычным поведением экранной клавиатуры.
|
||||
|
||||
## Статус
|
||||
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
client.version=1.2.265
|
||||
client.version=1.2.266
|
||||
server.version=1.2.248
|
||||
|
||||
@ -281,6 +281,30 @@ function scrollToLatestMessage(list) {
|
||||
window.setTimeout(apply, 260);
|
||||
}
|
||||
|
||||
function scrollToLatestMessageSmart(list, { smoothIfNearBottom = false } = {}) {
|
||||
if (!list) return;
|
||||
const scrollContainer = list.closest('.screen-content') || list.parentElement || list;
|
||||
const lastBubble = list.lastElementChild;
|
||||
const maxScrollTop = Math.max(0, scrollContainer.scrollHeight - scrollContainer.clientHeight);
|
||||
const distanceToBottom = Math.max(0, maxScrollTop - scrollContainer.scrollTop);
|
||||
const useSmooth = smoothIfNearBottom && distanceToBottom <= 180;
|
||||
|
||||
const apply = (behavior = useSmooth ? 'smooth' : 'auto') => {
|
||||
if (lastBubble?.scrollIntoView) {
|
||||
lastBubble.scrollIntoView({ block: 'end', inline: 'nearest', behavior });
|
||||
}
|
||||
if (behavior === 'smooth' && typeof scrollContainer.scrollTo === 'function') {
|
||||
scrollContainer.scrollTo({ top: scrollContainer.scrollHeight, behavior: 'smooth' });
|
||||
} else {
|
||||
scrollContainer.scrollTop = scrollContainer.scrollHeight;
|
||||
}
|
||||
};
|
||||
|
||||
apply(useSmooth ? 'smooth' : 'auto');
|
||||
window.requestAnimationFrame(() => apply('auto'));
|
||||
window.setTimeout(() => apply('auto'), useSmooth ? 220 : 90);
|
||||
}
|
||||
|
||||
function scrollToUnreadSeparator(list) {
|
||||
if (!list) return false;
|
||||
const separator = list.querySelector('.chat-unread-separator');
|
||||
@ -619,7 +643,7 @@ export function render({ navigate, route }) {
|
||||
const editing = activeEdit;
|
||||
const tempId = editing ? '' : addOutgoingPendingMessage(chatId, text);
|
||||
renderLog(log, chatId, { onOpenActions: handleOpenActions });
|
||||
scrollToLatestMessage(log);
|
||||
scrollToLatestMessageSmart(log, { smoothIfNearBottom: true });
|
||||
|
||||
try {
|
||||
let result;
|
||||
@ -657,10 +681,16 @@ export function render({ navigate, route }) {
|
||||
}
|
||||
|
||||
renderLog(log, chatId, { onOpenActions: handleOpenActions });
|
||||
scrollToLatestMessage(log);
|
||||
window.requestAnimationFrame(() => scrollToLatestMessage(log));
|
||||
window.setTimeout(() => scrollToLatestMessage(log), 90);
|
||||
window.setTimeout(() => scrollToLatestMessage(log), 220);
|
||||
scrollToLatestMessageSmart(log, { smoothIfNearBottom: true });
|
||||
window.requestAnimationFrame(() => scrollToLatestMessageSmart(log, { smoothIfNearBottom: true }));
|
||||
window.setTimeout(() => scrollToLatestMessageSmart(log, { smoothIfNearBottom: true }), 220);
|
||||
if (input) {
|
||||
try {
|
||||
input.focus({ preventScroll: true });
|
||||
} catch {
|
||||
input.focus();
|
||||
}
|
||||
}
|
||||
addAppLogEntry({
|
||||
level: 'info',
|
||||
source: 'outgoing-dm',
|
||||
@ -735,27 +765,7 @@ export function render({ navigate, route }) {
|
||||
});
|
||||
input?.addEventListener('keydown', async (event) => {
|
||||
if (event.key !== 'Enter') return;
|
||||
if (event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
const start = Number(input.selectionStart ?? input.value.length);
|
||||
const end = Number(input.selectionEnd ?? input.value.length);
|
||||
const value = String(input.value || '');
|
||||
input.value = `${value.slice(0, start)}\n${value.slice(end)}`;
|
||||
const nextPos = start + 1;
|
||||
try {
|
||||
input.setSelectionRange(nextPos, nextPos);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
autoResizeComposer(input);
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
const text = String(input.value || '').trim();
|
||||
if (!text) return;
|
||||
input.value = '';
|
||||
autoResizeComposer(input);
|
||||
await sendTextMessage(text);
|
||||
window.requestAnimationFrame(() => autoResizeComposer(input));
|
||||
});
|
||||
|
||||
form.querySelector('#chat-voice-input')?.addEventListener('click', async () => {
|
||||
@ -773,6 +783,15 @@ export function render({ navigate, route }) {
|
||||
});
|
||||
});
|
||||
|
||||
form.querySelector('.dm-send-btn')?.addEventListener('pointerdown', (event) => {
|
||||
event.preventDefault();
|
||||
try {
|
||||
input?.focus({ preventScroll: true });
|
||||
} catch {
|
||||
input?.focus();
|
||||
}
|
||||
});
|
||||
|
||||
form.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
const text = String(input.value || '').trim();
|
||||
@ -780,6 +799,7 @@ export function render({ navigate, route }) {
|
||||
input.value = '';
|
||||
autoResizeComposer(input);
|
||||
await sendTextMessage(text);
|
||||
focusInputToEnd();
|
||||
});
|
||||
|
||||
const handleIncomingChatRefresh = async (event) => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user