message_edit: Restore focus to the message being edited.

When user is editing a message and we rerender it, we should always
set focus back to the textarea.
This commit is contained in:
Aman Agrawal 2024-05-16 08:22:54 +00:00 committed by Tim Abbott
parent 753b8e1f6f
commit b8cec0ff2a
1 changed files with 71 additions and 0 deletions

View File

@ -205,6 +205,67 @@ function get_users_for_recipient_row(message) {
return users.sort(compare_by_name); return users.sort(compare_by_name);
} }
let message_id_to_focus_after_processing_message_events = {
id: undefined,
selectionStart: undefined,
selectionEnd: undefined,
};
function reset_restore_message_edit_focus_state() {
message_id_to_focus_after_processing_message_events = {
id: undefined,
selectionStart: undefined,
selectionEnd: undefined,
};
}
function capture_user_message_editing_state() {
if (document.activeElement?.classList.contains("message_edit_content")) {
message_id_to_focus_after_processing_message_events = {
id: rows.get_message_id(document.activeElement),
selectionStart: document.activeElement.selectionStart,
selectionEnd: document.activeElement.selectionEnd,
};
} else {
reset_restore_message_edit_focus_state();
}
}
function maybe_restore_focus_to_message_edit_form() {
if (
// It is possible that selected message might not be the one
// user was editing but is less likely the case. It makes
// things complicated to think about with selected message being different
// from the message edit form we are trying to restore focus to.
// So, we simply only restore focus if the selected message is the
// one user was editing.
message_lists.current?.data.selected_id() !==
message_id_to_focus_after_processing_message_events.id
) {
// If user has navigated away from the message they were editing,
// we don't want to restore focus to the message edit form since
// we don't want to capture hotkey inside the message edit form
// when they navigate back to the message they were editing.
reset_restore_message_edit_focus_state();
}
setTimeout(() => {
const $message_edit_content = message_lists.current
?.selected_row()
.find(".message_edit_content");
if (!$message_edit_content || $message_edit_content.length === 0) {
return;
}
$message_edit_content.trigger("focus");
$message_edit_content[0].setSelectionRange(
message_id_to_focus_after_processing_message_events.selectionStart,
message_id_to_focus_after_processing_message_events.selectionEnd,
);
reset_restore_message_edit_focus_state();
}, 0);
}
function populate_group_from_message_container(group, message_container) { function populate_group_from_message_container(group, message_container) {
group.is_stream = message_container.msg.is_stream; group.is_stream = message_container.msg.is_stream;
group.is_private = message_container.msg.is_private; group.is_private = message_container.msg.is_private;
@ -827,6 +888,8 @@ export class MessageListView {
return undefined; return undefined;
} }
capture_user_message_editing_state();
const list = this.list; // for convenience const list = this.list; // for convenience
let orig_scrolltop_offset; let orig_scrolltop_offset;
@ -982,6 +1045,7 @@ export class MessageListView {
// After all the messages are rendered, resize any message edit textarea if required. // After all the messages are rendered, resize any message edit textarea if required.
autosize.update(this.$list.find(".message_edit_content")); autosize.update(this.$list.find(".message_edit_content"));
maybe_restore_focus_to_message_edit_form();
restore_scroll_position(); restore_scroll_position();
@ -1390,6 +1454,11 @@ export class MessageListView {
} }
rerender_messages(messages, message_content_edited) { rerender_messages(messages, message_content_edited) {
// this.render is never called in this code path, we use
// `_rerender_message` instead which is optimized for this use
// case.
capture_user_message_editing_state();
// We need to destroy all the tippy instances from the DOM before re-rendering to // We need to destroy all the tippy instances from the DOM before re-rendering to
// prevent the appearance of tooltips whose reference has been removed. // prevent the appearance of tooltips whose reference has been removed.
message_list_tooltips.destroy_all_message_list_tooltips(); message_list_tooltips.destroy_all_message_list_tooltips();
@ -1426,6 +1495,7 @@ export class MessageListView {
if (message_lists.current === this.list && narrow_state.is_message_feed_visible()) { if (message_lists.current === this.list && narrow_state.is_message_feed_visible()) {
this.update_sticky_recipient_headers(); this.update_sticky_recipient_headers();
maybe_restore_focus_to_message_edit_form();
} }
} }
@ -1485,6 +1555,7 @@ export class MessageListView {
} }
clear_table() { clear_table() {
capture_user_message_editing_state();
// We do not want to call .empty() because that also clears // We do not want to call .empty() because that also clears
// jQuery data. This does mean, however, that we need to be // jQuery data. This does mean, however, that we need to be
// mindful of memory leaks. // mindful of memory leaks.