Исправить мобильный ввод в личном чате
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
|
server.version=1.2.248
|
||||||
|
|||||||
@ -281,6 +281,30 @@ function scrollToLatestMessage(list) {
|
|||||||
window.setTimeout(apply, 260);
|
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) {
|
function scrollToUnreadSeparator(list) {
|
||||||
if (!list) return false;
|
if (!list) return false;
|
||||||
const separator = list.querySelector('.chat-unread-separator');
|
const separator = list.querySelector('.chat-unread-separator');
|
||||||
@ -619,7 +643,7 @@ export function render({ navigate, route }) {
|
|||||||
const editing = activeEdit;
|
const editing = activeEdit;
|
||||||
const tempId = editing ? '' : addOutgoingPendingMessage(chatId, text);
|
const tempId = editing ? '' : addOutgoingPendingMessage(chatId, text);
|
||||||
renderLog(log, chatId, { onOpenActions: handleOpenActions });
|
renderLog(log, chatId, { onOpenActions: handleOpenActions });
|
||||||
scrollToLatestMessage(log);
|
scrollToLatestMessageSmart(log, { smoothIfNearBottom: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let result;
|
let result;
|
||||||
@ -657,10 +681,16 @@ export function render({ navigate, route }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderLog(log, chatId, { onOpenActions: handleOpenActions });
|
renderLog(log, chatId, { onOpenActions: handleOpenActions });
|
||||||
scrollToLatestMessage(log);
|
scrollToLatestMessageSmart(log, { smoothIfNearBottom: true });
|
||||||
window.requestAnimationFrame(() => scrollToLatestMessage(log));
|
window.requestAnimationFrame(() => scrollToLatestMessageSmart(log, { smoothIfNearBottom: true }));
|
||||||
window.setTimeout(() => scrollToLatestMessage(log), 90);
|
window.setTimeout(() => scrollToLatestMessageSmart(log, { smoothIfNearBottom: true }), 220);
|
||||||
window.setTimeout(() => scrollToLatestMessage(log), 220);
|
if (input) {
|
||||||
|
try {
|
||||||
|
input.focus({ preventScroll: true });
|
||||||
|
} catch {
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
addAppLogEntry({
|
addAppLogEntry({
|
||||||
level: 'info',
|
level: 'info',
|
||||||
source: 'outgoing-dm',
|
source: 'outgoing-dm',
|
||||||
@ -735,27 +765,7 @@ export function render({ navigate, route }) {
|
|||||||
});
|
});
|
||||||
input?.addEventListener('keydown', async (event) => {
|
input?.addEventListener('keydown', async (event) => {
|
||||||
if (event.key !== 'Enter') return;
|
if (event.key !== 'Enter') return;
|
||||||
if (event.ctrlKey) {
|
window.requestAnimationFrame(() => autoResizeComposer(input));
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
form.querySelector('#chat-voice-input')?.addEventListener('click', async () => {
|
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) => {
|
form.addEventListener('submit', async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const text = String(input.value || '').trim();
|
const text = String(input.value || '').trim();
|
||||||
@ -780,6 +799,7 @@ export function render({ navigate, route }) {
|
|||||||
input.value = '';
|
input.value = '';
|
||||||
autoResizeComposer(input);
|
autoResizeComposer(input);
|
||||||
await sendTextMessage(text);
|
await sendTextMessage(text);
|
||||||
|
focusInputToEnd();
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleIncomingChatRefresh = async (event) => {
|
const handleIncomingChatRefresh = async (event) => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user