From 0cdb54cf65ce6c9c50fdef6c8930cf09e2411c40 Mon Sep 17 00:00:00 2001 From: N-Shar-ma Date: Sat, 7 Oct 2023 08:09:49 +0530 Subject: [PATCH] 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. --- web/src/compose_ui.js | 14 +++++++---- web/src/composebox_typeahead.js | 13 ++++------- web/tests/compose_ui.test.js | 2 ++ web/tests/composebox_typeahead.test.js | 27 +++++++++++----------- web/third/bootstrap-typeahead/typeahead.js | 3 +++ 5 files changed, 32 insertions(+), 27 deletions(-) diff --git a/web/src/compose_ui.js b/web/src/compose_ui.js index 809aa37627..10c0457504 100644 --- a/web/src/compose_ui.js +++ b/web/src/compose_ui.js @@ -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( diff --git a/web/src/composebox_typeahead.js b/web/src/composebox_typeahead.js index 53b2882eb2..c291d981e1 100644 --- a/web/src/composebox_typeahead.js +++ b/web/src/composebox_typeahead.js @@ -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 diff --git a/web/tests/compose_ui.test.js b/web/tests/compose_ui.test.js index 4e94fd5ef3..26932de68b 100644 --- a/web/tests/compose_ui.test.js +++ b/web/tests/compose_ui.test.js @@ -80,6 +80,8 @@ function make_textbox(s) { return $widget.s; }; + $widget.trigger = noop; + return $widget; } diff --git a/web/tests/composebox_typeahead.test.js b/web/tests/composebox_typeahead.test.js index 35331662a4..40cd827d1e 100644 --- a/web/tests/composebox_typeahead.test.js +++ b/web/tests/composebox_typeahead.test.js @@ -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; diff --git a/web/third/bootstrap-typeahead/typeahead.js b/web/third/bootstrap-typeahead/typeahead.js index 11033c8cb0..50634c92cc 100644 --- a/web/third/bootstrap-typeahead/typeahead.js +++ b/web/third/bootstrap-typeahead/typeahead.js @@ -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()