Repository: zulip/zulip
Pull request: compose_actions: Fix misplaced caret position when qouting.
When replying with a quote, the cursor moves back to the beginning
once the quote resolves and disrupts ongoing typing.

Fixes #20071.


Problem statement:
Cursor jumps after quoted message loads
When replying with a quote, the cursor moves back to the beginning once the quote resolves, disrupting ongoing typing.

Steps to reproduce:

- Select a message.
- Make sure the text box isn't currently open (press esc if necessary).
- In quick succession, press > and then immediately start typing a reply.
- For a short time, the text box will include a note saying Quoting in square brackets, but you can keep typing while that box is present.
- After a moment, the note Quoting gets replaced by the quote. 

**Expected**: Cursor position should not change when the quote finishes resolving.
**Actual**: When the quote appears, the editing cursor moves back to the beginning of the subsequent line, disrupting the user's current typing.

Thanks @joshtriplett for the report!

Hints from issue discussion:
Hello @zulip/server-compose members, this issue was labeled with the "area: compose" label, so you may want to check it out!

<!-- areaLabelAddition -->

We certainly have logic that's intended to prevent.  

```
    compose_ui.insert_syntax_and_focus("[Quoting…]\n", textarea);                                        
                                                                                                         
    function replace_content(message) {                                                                  
        // Final message looks like:                                                                     
        //     @_**Iago|5** [said](link to message):                                                     
        //     ```quote                                                                                  
        //     message content                                                                           
        //     ```                                                                                       
        let content = $t(                                                                                
            {defaultMessage: "{username} [said]({link_to_message}):"},                                   
            {                                                                                            
                username: `@_**${message.sender_full_name}|${message.sender_id}**`,                      
                link_to_message: `${hash_util.by_conversation_and_time_uri(message)}`,                   
            },                                                                                           
        );                                                                                               
        content += "\n";                                                                                 
        const fence = fenced_code.get_unused_fence(message.raw_content);                                 
        content += `${fence}quote\n${message.raw_content}\n${fence}`;                                    
        compose_ui.replace_syntax("[Quoting…]", content, textarea);                                      
        compose_ui.autosize_textarea($("#compose-textarea"));                                            
        // Update textarea caret to point to the new line after quoted message.                          
        textarea.caret(prev_caret + content.length + 1);                                                 
    }                                                                                                    
                                                                                                         
    if (message && message.raw_content) {                                                                
        replace_content(message);                                                                        
        return;                                                                                          
    }                                                                                                    
                                                                                                         
    channel.get({                                                                                        
        url: "/json/messages/" + message_id,                                                             
        idempotent: true,                                                                                
        success(data) {                                                                                  
            message.raw_content = data.raw_content;                                                      
            replace_content(message);                                                                    
        },                                                                                               
    });                                                                                                  
```

@amanagr can you do a quick investigation into this one?  It might be the case that we can switch to using the newer libraries that support "Undo" you added for the new compose box buttons rather than debugging why this isn't working.

Commit message: compose: Fix cursor position loss in quote-and-reply.

If you used "Quote and reply" to start composing a message, and
started typing before receiving the original message body from the
server, we ended up resetting your cursor to the start of the line
after the quote for two reasons:

* We were incorrectly fetching the pre_cursor for our replacement
  operation before doing the server fetch, which meant we ignored any
  editing done while waiting for the server to respond.

* Worse, we actually fetched the original cursor position before
  inserting the "[Quoting...]" placeholder text. So we were guaranteed
  to have at least some amount of error in the cursor position.

Fixes #20379.

Patch to review:
diff --git a/static/js/compose_actions.js b/static/js/compose_actions.js
index 7bbea45264432..e44c35fa69572 100644
--- a/static/js/compose_actions.js
+++ b/static/js/compose_actions.js
@@ -482,8 +482,6 @@ export function quote_and_reply(opts) {
         respond_to_message(opts);
     }
 
-    const prev_caret = textarea.caret();
-
     compose_ui.insert_syntax_and_focus("[Quoting…]\n", textarea);
 
     function replace_content(message) {
@@ -492,6 +490,7 @@ export function quote_and_reply(opts) {
         //     ```quote
         //     message content
         //     ```
+        const prev_caret = textarea.caret();
         let content = $t(
             {defaultMessage: "{username} [said]({link_to_message}):"},
             {


Code review comments:
diff_hunk: @@ -505,7 +506,12 @@ export function quote_and_reply(opts) {
         compose_ui.replace_syntax("[Quoting…]", content, textarea);
         compose_ui.autosize_textarea($("#compose-textarea"));
         // Update textarea caret to point to the new line after quoted message.
-        textarea.caret(prev_caret + content.length + 1);
+        if (composebox_was_open) {
+            // If composebox was closed, `textbox replace`, by default, places
+            // cursor at end of content. So, we don't need to restore
+            // cursor position.
+            textarea.caret(prev_caret + content.length + 1);
+        }
path: static/js/compose_actions.js
text: @author:
The logic here is correct if you think about `open compose quote insertion`. For closed compose, we actually need `content = textbox.val()` instead of just quoted text, which is what `textbox replace` does by default, so we don't need to meddle with cursor position here.

Doing what you are saying is not solving anything.