mirror of
https://github.com/zulip/zulip.git
synced 2025-11-11 17:36:27 +00:00
compose: Always preserve cursor position post replacement in a textarea.
Refactored (moved) the code for preserving the cursor's initial logical position from `quote_and_reply()` in `compose_actions.js` which calls `replace_syntax()` directly into `replace_syntax()` in `compose_ui.js`. This ensures that anytime text in a textarea is replaced, the original cursor position is always restored. Earlier, this was needed to be done separately, and missing that would lead to bugs with the cursor unexpectedly jumping on replacement. Fixes: #23863.
This commit is contained in:
@@ -153,22 +153,30 @@ run_test("smart_insert", ({override}) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
run_test("replace_syntax", ({override}) => {
|
run_test("replace_syntax", ({override}) => {
|
||||||
$("#compose-textarea").val("abcabc");
|
const $textbox = make_textbox("aBca$$");
|
||||||
$("#compose-textarea")[0] = "compose-textarea";
|
$textbox.caret(2);
|
||||||
override(text_field_edit, "replace", (elt, old_syntax, new_syntax) => {
|
override(text_field_edit, "replace", (elt, old_syntax, new_syntax) => {
|
||||||
assert.equal(elt, "compose-textarea");
|
assert.equal(elt, "textarea");
|
||||||
assert.equal(old_syntax, "a");
|
assert.equal(old_syntax, "a");
|
||||||
assert.equal(new_syntax(), "A");
|
assert.equal(new_syntax(), "A");
|
||||||
});
|
});
|
||||||
compose_ui.replace_syntax("a", "A");
|
let prev_carat = $textbox.caret();
|
||||||
|
compose_ui.replace_syntax("a", "A", $textbox);
|
||||||
|
assert.equal(prev_carat, $textbox.caret());
|
||||||
|
|
||||||
override(text_field_edit, "replace", (elt, old_syntax, new_syntax) => {
|
override(text_field_edit, "replace", (elt, old_syntax, new_syntax) => {
|
||||||
assert.equal(elt, "compose-textarea");
|
assert.equal(elt, "textarea");
|
||||||
assert.equal(old_syntax, "Bca");
|
assert.equal(old_syntax, "Bca");
|
||||||
assert.equal(new_syntax(), "$$\\pi$$");
|
assert.equal(new_syntax(), "$$\\pi$$");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Verify we correctly handle `$`s in the replacement syntax
|
// Verify we correctly handle `$`s in the replacement syntax
|
||||||
compose_ui.replace_syntax("Bca", "$$\\pi$$");
|
// and that on replacing with a different length string, the
|
||||||
|
// cursor is shifted accordingly as expected
|
||||||
|
$textbox.caret(5);
|
||||||
|
prev_carat = $textbox.caret();
|
||||||
|
compose_ui.replace_syntax("Bca", "$$\\pi$$", $textbox);
|
||||||
|
assert.equal(prev_carat + "$$\\pi$$".length - "Bca".length, $textbox.caret());
|
||||||
});
|
});
|
||||||
|
|
||||||
run_test("compute_placeholder_text", () => {
|
run_test("compute_placeholder_text", () => {
|
||||||
|
|||||||
@@ -559,7 +559,6 @@ export function quote_and_reply(opts) {
|
|||||||
// ```quote
|
// ```quote
|
||||||
// message content
|
// message content
|
||||||
// ```
|
// ```
|
||||||
const prev_caret = $textarea.caret();
|
|
||||||
let content = $t(
|
let content = $t(
|
||||||
{defaultMessage: "{username} [said]({link_to_message}):"},
|
{defaultMessage: "{username} [said]({link_to_message}):"},
|
||||||
{
|
{
|
||||||
@@ -571,25 +570,8 @@ export function quote_and_reply(opts) {
|
|||||||
const fence = fenced_code.get_unused_fence(message.raw_content);
|
const fence = fenced_code.get_unused_fence(message.raw_content);
|
||||||
content += `${fence}quote\n${message.raw_content}\n${fence}`;
|
content += `${fence}quote\n${message.raw_content}\n${fence}`;
|
||||||
|
|
||||||
const placeholder_offset = $textarea.val().indexOf(quoting_placeholder);
|
|
||||||
compose_ui.replace_syntax(quoting_placeholder, content, $textarea);
|
compose_ui.replace_syntax(quoting_placeholder, content, $textarea);
|
||||||
compose_ui.autosize_textarea($("#compose-textarea"));
|
compose_ui.autosize_textarea($("#compose-textarea"));
|
||||||
|
|
||||||
// When replacing content in a textarea, we need to move the
|
|
||||||
// cursor to preserve its logical position if and only if the
|
|
||||||
// content we just added was before the current cursor
|
|
||||||
// position. If we do, we need to move it by the increase in
|
|
||||||
// the length of the content before the placeholder.
|
|
||||||
if (prev_caret >= placeholder_offset + quoting_placeholder.length) {
|
|
||||||
$textarea.caret(prev_caret + content.length - quoting_placeholder.length);
|
|
||||||
} else if (prev_caret > placeholder_offset) {
|
|
||||||
/* In the rare case that our cursor was inside the
|
|
||||||
* placeholder, we treat that as though the cursor was
|
|
||||||
* just after the placeholder. */
|
|
||||||
$textarea.caret(placeholder_offset + content.length + 1);
|
|
||||||
} else {
|
|
||||||
$textarea.caret(prev_caret);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message && message.raw_content) {
|
if (message && message.raw_content) {
|
||||||
|
|||||||
@@ -75,6 +75,11 @@ export function insert_syntax_and_focus(syntax, $textarea = $("#compose-textarea
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function replace_syntax(old_syntax, new_syntax, $textarea = $("#compose-textarea")) {
|
export function replace_syntax(old_syntax, new_syntax, $textarea = $("#compose-textarea")) {
|
||||||
|
// The following couple lines are needed to later restore the initial
|
||||||
|
// logical position of the cursor after the replacement
|
||||||
|
const prev_caret = $textarea.caret();
|
||||||
|
const replacement_offset = $textarea.val().indexOf(old_syntax);
|
||||||
|
|
||||||
// Replaces `old_syntax` with `new_syntax` text in the compose box. Due to
|
// Replaces `old_syntax` with `new_syntax` text in the compose box. Due to
|
||||||
// the way that JavaScript handles string replacements, if `old_syntax` is
|
// the way that JavaScript handles string replacements, if `old_syntax` is
|
||||||
// a string it will only replace the first instance. If `old_syntax` is
|
// a string it will only replace the first instance. If `old_syntax` is
|
||||||
@@ -84,7 +89,25 @@ export function replace_syntax(old_syntax, new_syntax, $textarea = $("#compose-t
|
|||||||
// replace() function treating `$`s in new_syntax as special syntax. See
|
// replace() function treating `$`s in new_syntax as special syntax. See
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Description
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Description
|
||||||
// for details.
|
// for details.
|
||||||
|
|
||||||
replace($textarea[0], old_syntax, () => new_syntax, "after-replacement");
|
replace($textarea[0], old_syntax, () => new_syntax, "after-replacement");
|
||||||
|
|
||||||
|
// When replacing content in a textarea, we need to move the cursor
|
||||||
|
// to preserve its logical position if and only if the content we
|
||||||
|
// just added was before the current cursor position. If it was,
|
||||||
|
// we need to move the cursor forward by the increase in the
|
||||||
|
// length of the content after the replacement.
|
||||||
|
if (prev_caret >= replacement_offset + old_syntax.length) {
|
||||||
|
$textarea.caret(prev_caret + new_syntax.length - old_syntax.length);
|
||||||
|
} else if (prev_caret > replacement_offset) {
|
||||||
|
// In the rare case that our cursor was inside the
|
||||||
|
// placeholder, we treat that as though the cursor was
|
||||||
|
// just after the placeholder.
|
||||||
|
$textarea.caret(replacement_offset + new_syntax.length + 1);
|
||||||
|
} else {
|
||||||
|
// Otherwise we simply restore it to it's original position
|
||||||
|
$textarea.caret(prev_caret);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compute_placeholder_text(opts) {
|
export function compute_placeholder_text(opts) {
|
||||||
|
|||||||
Reference in New Issue
Block a user