Skip to content

Commit

Permalink
quote-and-reply: Quote part of a message
Browse files Browse the repository at this point in the history
Summary
- When nothing is highlighted, fully quote the currently selected message
- When part of a message is highlighted, quote only the highlighted text
- If the user highlights multiple messages, same behavior as nothing highlighted
- The quote-and-reply menu option has the same behavior as the > hotkey

Issues
- Formatting is not preserved when quoting only part of a message

Fixes #19712, Fixes #8951
  • Loading branch information
jonnyktran committed Sep 14, 2021
1 parent 6651842 commit db1ffcf
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 15 deletions.
10 changes: 6 additions & 4 deletions frontend_tests/node_tests/compose_ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -340,8 +340,10 @@ run_test("quote_and_reply", ({override}) => {
$("#compose-textarea").trigger("blur");
}

const opts = {};

set_compose_content_with_caret("hello %there"); // "%" is used to encode/display position of focus before change
compose_actions.quote_and_reply();
compose_actions.quote_and_reply(opts);
assert.equal(get_compose_content_with_caret(), "hello \n[Quoting…]\n%there");

success_function({
Expand All @@ -357,7 +359,7 @@ run_test("quote_and_reply", ({override}) => {
// If the caret is initially positioned at 0, it should not
// add a newline before the quoted message.
set_compose_content_with_caret("%hello there");
compose_actions.quote_and_reply();
compose_actions.quote_and_reply(opts);
assert.equal(get_compose_content_with_caret(), "[Quoting…]\n%hello there");

success_function({
Expand All @@ -380,7 +382,7 @@ run_test("quote_and_reply", ({override}) => {
// If the compose-box is close, or open with no content while
// quoting a message, the quoted message should be placed
// at the beginning of compose-box.
compose_actions.quote_and_reply();
compose_actions.quote_and_reply(opts);
assert.equal(get_compose_content_with_caret(), "[Quoting…]\n%");

success_function({
Expand All @@ -398,7 +400,7 @@ run_test("quote_and_reply", ({override}) => {
// newlines), the compose-box should re-open and thus the quoted
// message should start from the beginning of compose-box.
set_compose_content_with_caret(" \n\n \n %");
compose_actions.quote_and_reply();
compose_actions.quote_and_reply(opts);
assert.equal(get_compose_content_with_caret(), "[Quoting…]\n%");

success_function({
Expand Down
2 changes: 1 addition & 1 deletion frontend_tests/node_tests/hotkey.js
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ run_test("misc", () => {
assert_mapping("u", popovers, "show_sender_info");
assert_mapping("i", popovers, "open_message_menu");
assert_mapping(":", reactions, "open_reactions_popover", true);
assert_mapping(">", compose_actions, "quote_and_reply");
assert_mapping(">", compose_actions, "quote_selected_text_and_reply");
assert_mapping("e", message_edit, "start");
});

Expand Down
44 changes: 36 additions & 8 deletions static/js/compose_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import * as compose_pm_pill from "./compose_pm_pill";
import * as compose_state from "./compose_state";
import * as compose_ui from "./compose_ui";
import * as compose_validate from "./compose_validate";
import * as copy_and_paste from "./copy_and_paste";
import * as drafts from "./drafts";
import * as hash_util from "./hash_util";
import {$t} from "./i18n";
Expand Down Expand Up @@ -460,8 +461,13 @@ export function on_topic_narrow() {

export function quote_and_reply(opts) {
const textarea = $("#compose-textarea");
const message_id = message_lists.current.selected_id();
const message = message_lists.current.selected_message();
let message_id = message_lists.current.selected_id();
let message = message_lists.current.selected_message();

if (opts.start_message_id) {
message_id = opts.start_message_id;
message = message_lists.current.get(message_id);
}

if (compose_state.has_message_content()) {
// The user already started typing a message,
Expand All @@ -485,7 +491,7 @@ export function quote_and_reply(opts) {

compose_ui.insert_syntax_and_focus("[Quoting…]\n", textarea);

function replace_content(message) {
function replace_content(message, msg_content) {
// Final message looks like:
// @_**Iago|5** [said](link to message):
// ```quote
Expand All @@ -499,16 +505,21 @@ export function quote_and_reply(opts) {
},
);
content += "\n";
const fence = fenced_code.get_unused_fence(message.raw_content);
content += `${fence}quote\n${message.raw_content}\n${fence}`;
const fence = fenced_code.get_unused_fence(msg_content);
content += `${fence}quote\n${msg_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);
if (message && opts.selected_text !== undefined) {
// The copied text is plain text, therefore, the quoted text
// is not stylized as well.
replace_content(message, opts.selected_text);
return;
} else if (message && message.raw_content) {
replace_content(message, message.raw_content);
return;
}

Expand All @@ -517,11 +528,28 @@ export function quote_and_reply(opts) {
idempotent: true,
success(data) {
message.raw_content = data.raw_content;
replace_content(message);
replace_content(message, message.raw_content);
},
});
}

export function quote_selected_text_and_reply(opts) {
const selected_text = window.getSelection().toString();

if (selected_text !== "") {
const analysis = copy_and_paste.analyze_selection(window.getSelection());

if (!analysis.skip_same_td_check && analysis.start_id === analysis.end_id) {
// Quote the selected text iff a single message's text is selected.
// Otherwise, just quote the selected message.
opts.selected_text = selected_text.trim();
opts.start_message_id = analysis.start_id;
}
}

quote_and_reply(opts);
}

export function on_narrow(opts) {
// We use force_close when jumping between PM narrows with the "p" key,
// so that we don't have an open compose box that makes it difficult
Expand Down
2 changes: 1 addition & 1 deletion static/js/hotkey.js
Original file line number Diff line number Diff line change
Expand Up @@ -897,7 +897,7 @@ export function process_hotkey(e, hotkey) {
condense.toggle_collapse(msg);
return true;
case "compose_quote_reply": // > : respond to selected message with quote
compose_actions.quote_and_reply({trigger: "hotkey"});
compose_actions.quote_selected_text_and_reply({trigger: "hotkey"});
return true;
case "edit_message": {
const row = message_lists.current.get_row(msg.id);
Expand Down
2 changes: 1 addition & 1 deletion static/js/popovers.js
Original file line number Diff line number Diff line change
Expand Up @@ -1052,7 +1052,7 @@ export function register_click_handlers() {
// message in the current message list (and
// compose_actions.respond_to_message doesn't take a message
// argument).
compose_actions.quote_and_reply({trigger: "popover respond"});
compose_actions.quote_selected_text_and_reply({trigger: "popover respond"});
hide_actions_popover();
e.stopPropagation();
e.preventDefault();
Expand Down

0 comments on commit db1ffcf

Please sign in to comment.