compose: Ensure cursor is scrolled into view after content is inserted.

This fixes the bug where on pressing enter after the last line in a
textarea, the cursor would go to the new line but the textarea would
not scroll it into view unless more was typed. This was observed on
chromium browsers.

A new function `insert_and_scroll_into_view` is added to `compose_ui.js`
which blurs and refocuses the textarea after inserting the content, then
autosizes the textarea.
This commit is contained in:
N-Shar-ma 2023-10-07 08:09:49 +05:30 committed by Tim Abbott
parent acdae9eae2
commit 0cdb54cf65
5 changed files with 32 additions and 27 deletions

View File

@ -35,6 +35,14 @@ export function autosize_textarea($textarea) {
}
}
export function insert_and_scroll_into_view(content, $textarea) {
insert($textarea[0], content);
// Blurring and refocusing ensures the cursor / selection is in view.
$textarea.trigger("blur");
$textarea.trigger("focus");
autosize_textarea($textarea);
}
function get_focus_area(msg_type, opts) {
// Set focus to "Topic" when narrowed to a stream+topic and "New topic" button clicked.
if (msg_type === "stream" && opts.stream_id && !opts.topic) {
@ -99,8 +107,7 @@ export function smart_insert_inline($textarea, syntax) {
syntax += " ";
}
insert($textarea[0], syntax);
autosize_textarea($textarea);
insert_and_scroll_into_view(syntax, $textarea);
}
export function smart_insert_block($textarea, syntax, padding_newlines = 2) {
@ -146,8 +153,7 @@ export function smart_insert_block($textarea, syntax, padding_newlines = 2) {
const new_lines_needed_after_count = padding_newlines - new_lines_after_count;
syntax = syntax + "\n".repeat(new_lines_needed_after_count);
insert($textarea[0], syntax);
autosize_textarea($textarea);
insert_and_scroll_into_view(syntax, $textarea);
}
export function insert_syntax_and_focus(

View File

@ -1,6 +1,5 @@
import $ from "jquery";
import _ from "lodash";
import {insert} from "text-field-edit";
import * as typeahead from "../shared/src/typeahead";
import render_topic_typeahead_hint from "../templates/topic_typeahead_hint.hbs";
@ -165,8 +164,7 @@ function handle_bulleting_or_numbering($textarea, e) {
// below we select and replace the last 2 characters in the textarea before
// the cursor - the bullet syntax - with an empty string
$textarea[0].setSelectionRange($textarea.caret() - 2, $textarea.caret());
insert($textarea[0], "");
compose_ui.autosize_textarea($textarea);
compose_ui.insert_and_scroll_into_view("", $textarea);
e.preventDefault();
return;
}
@ -183,8 +181,7 @@ function handle_bulleting_or_numbering($textarea, e) {
$textarea.caret() - previous_number_string.length - 2,
$textarea.caret(),
);
insert($textarea[0], "");
compose_ui.autosize_textarea($textarea);
compose_ui.insert_and_scroll_into_view("", $textarea);
e.preventDefault();
return;
}
@ -194,8 +191,7 @@ function handle_bulleting_or_numbering($textarea, e) {
// if previous line was neither numbered nor bulleted, only add
// a new line to emulate default behaviour (to_append is blank)
// else we add the bulleting / numbering syntax to the new line
insert($textarea[0], "\n" + to_append);
compose_ui.autosize_textarea($textarea);
compose_ui.insert_and_scroll_into_view("\n" + to_append, $textarea);
e.preventDefault();
}
@ -217,8 +213,7 @@ export function handle_enter($textarea, e) {
if ($textarea[0].selectionStart !== $textarea[0].selectionEnd) {
// Replace it with the newline, remembering to resize the
// textarea if needed.
insert($textarea[0], "\n");
compose_ui.autosize_textarea($textarea);
compose_ui.insert_and_scroll_into_view("\n", $textarea);
e.preventDefault();
} else {
// if nothing had been selected in the texarea we

View File

@ -80,6 +80,8 @@ function make_textbox(s) {
return $widget.s;
};
$widget.trigger = noop;
return $widget;
}

View File

@ -13,7 +13,7 @@ const noop = () => {};
let autosize_called;
mock_esm("../src/compose_ui", {
const compose_ui = mock_esm("../src/compose_ui", {
autosize_textarea() {
autosize_called = true;
},
@ -52,7 +52,6 @@ const compose_pm_pill = zrequire("compose_pm_pill");
const compose_recipient = zrequire("compose_recipient");
const composebox_typeahead = zrequire("composebox_typeahead");
const settings_config = zrequire("settings_config");
const text_field_edit = mock_esm("text-field-edit");
const pygments_data = zrequire("../generated/pygments_data.json");
const ct = composebox_typeahead;
@ -1161,8 +1160,8 @@ test("initialize", ({override, override_rewire, mock_template}) => {
selectionStart: 0,
selectionEnd: 0,
};
override(text_field_edit, "insert", (_textarea, syntax) => {
assert.equal(syntax, "\n");
override(compose_ui, "insert_and_scroll_into_view", (content, _textarea) => {
assert.equal(content, "\n");
});
$("#compose-textarea").caret = () => $("#compose-textarea")[0].selectionStart;
@ -1189,8 +1188,8 @@ test("initialize", ({override, override_rewire, mock_template}) => {
$("#compose-textarea").val("- List item 1\n- List item 2");
$("#compose-textarea")[0].selectionStart = 27;
$("#compose-textarea")[0].selectionEnd = 27;
override(text_field_edit, "insert", (_textarea, syntax) => {
assert.equal(syntax, "\n- ");
override(compose_ui, "insert_and_scroll_into_view", (content, _textarea) => {
assert.equal(content, "\n- ");
});
$("form#send_message_form").trigger(event);
@ -1202,8 +1201,8 @@ test("initialize", ({override, override_rewire, mock_template}) => {
assert.equal(start, 28);
assert.equal(end, 30);
};
override(text_field_edit, "insert", (_textarea, syntax) => {
assert.equal(syntax, "");
override(compose_ui, "insert_and_scroll_into_view", (content, _textarea) => {
assert.equal(content, "");
});
$("form#send_message_form").trigger(event);
@ -1211,8 +1210,8 @@ test("initialize", ({override, override_rewire, mock_template}) => {
$("#compose-textarea").val("1. List item 1\n2. List item 2");
$("#compose-textarea")[0].selectionStart = 29;
$("#compose-textarea")[0].selectionEnd = 29;
override(text_field_edit, "insert", (_textarea, syntax) => {
assert.equal(syntax, "\n3. ");
override(compose_ui, "insert_and_scroll_into_view", (content, _textarea) => {
assert.equal(content, "\n3. ");
});
$("form#send_message_form").trigger(event);
@ -1224,16 +1223,16 @@ test("initialize", ({override, override_rewire, mock_template}) => {
assert.equal(start, 30);
assert.equal(end, 33);
};
override(text_field_edit, "insert", (_textarea, syntax) => {
assert.equal(syntax, "");
override(compose_ui, "insert_and_scroll_into_view", (content, _textarea) => {
assert.equal(content, "");
});
$("form#send_message_form").trigger(event);
$("#compose-textarea").val("A");
$("#compose-textarea")[0].selectionStart = 4;
$("#compose-textarea")[0].selectionEnd = 4;
override(text_field_edit, "insert", (_textarea, syntax) => {
assert.equal(syntax, "\n");
override(compose_ui, "insert_and_scroll_into_view", (content, _textarea) => {
assert.equal(content, "\n");
});
event.altKey = false;
event.metaKey = true;

View File

@ -199,6 +199,9 @@ import {get_string_diff} from "../../src/util";
// select / highlight the minimal text to be replaced
this.$element[0].setSelectionRange(from, to_before);
insert(this.$element[0], replacement);
// Blurring and refocusing ensures the cursor is in view.
this.$element.trigger("blur");
this.$element.trigger("focus");
}
return this.hide()