2023-10-03 11:48:40 +02:00
|
|
|
import ClipboardJS from "clipboard";
|
|
|
|
import $ from "jquery";
|
2024-02-05 19:03:29 +01:00
|
|
|
import assert from "minimalistic-assert";
|
2023-10-03 11:48:40 +02:00
|
|
|
|
2024-05-18 08:36:20 +02:00
|
|
|
import render_message_actions_popover from "../templates/popovers/message_actions_popover.hbs";
|
2023-10-03 11:48:40 +02:00
|
|
|
|
|
|
|
import * as blueslip from "./blueslip";
|
2023-10-06 01:43:50 +02:00
|
|
|
import * as compose_reply from "./compose_reply";
|
2023-10-03 11:48:40 +02:00
|
|
|
import * as condense from "./condense";
|
|
|
|
import {show_copied_confirmation} from "./copied_tooltip";
|
|
|
|
import * as emoji_picker from "./emoji_picker";
|
|
|
|
import * as message_edit from "./message_edit";
|
|
|
|
import * as message_lists from "./message_lists";
|
|
|
|
import * as message_viewport from "./message_viewport";
|
|
|
|
import * as popover_menus from "./popover_menus";
|
|
|
|
import * as popover_menus_data from "./popover_menus_data";
|
2023-10-21 11:38:49 +02:00
|
|
|
import * as popovers from "./popovers";
|
2023-10-03 11:48:40 +02:00
|
|
|
import * as read_receipts from "./read_receipts";
|
|
|
|
import * as rows from "./rows";
|
|
|
|
import * as stream_popover from "./stream_popover";
|
|
|
|
import {parse_html} from "./ui_util";
|
|
|
|
import * as unread_ops from "./unread_ops";
|
|
|
|
|
|
|
|
let message_actions_popover_keyboard_toggle = false;
|
|
|
|
|
|
|
|
function get_action_menu_menu_items() {
|
2024-02-28 08:37:49 +01:00
|
|
|
const $current_actions_popover_elem = $("[data-tippy-root] #message-actions-menu-dropdown");
|
2023-10-03 11:48:40 +02:00
|
|
|
if (!$current_actions_popover_elem) {
|
|
|
|
blueslip.error("Trying to get menu items when action popover is closed.");
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $current_actions_popover_elem.find("li:not(.divider):visible a");
|
|
|
|
}
|
|
|
|
|
|
|
|
function focus_first_action_popover_item() {
|
|
|
|
// For now I recommend only calling this when the user opens the menu with a hotkey.
|
|
|
|
// Our popup menus act kind of funny when you mix keyboard and mouse.
|
|
|
|
const $items = get_action_menu_menu_items();
|
|
|
|
popover_menus.focus_first_popover_item($items);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function toggle_message_actions_menu(message) {
|
2023-10-21 11:38:49 +02:00
|
|
|
if (popover_menus.is_message_actions_popover_displayed()) {
|
|
|
|
popovers.hide_all();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-05-14 08:18:15 +02:00
|
|
|
if (message.locally_echoed || message_edit.currently_editing_messages.has(message.id)) {
|
2023-10-03 11:48:40 +02:00
|
|
|
// Don't open the popup for locally echoed messages for now.
|
|
|
|
// It creates bugs with things like keyboard handlers when
|
|
|
|
// we get the server response.
|
|
|
|
// We also suppress the popup for messages in an editing state,
|
|
|
|
// including previews, when a user tries to reach them from the
|
|
|
|
// keyboard.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-10-21 11:38:49 +02:00
|
|
|
// Since this can be called via hotkey, we need to
|
|
|
|
// hide any other popovers that may be open before.
|
|
|
|
if (popovers.any_active()) {
|
|
|
|
popovers.hide_all();
|
|
|
|
}
|
|
|
|
|
2023-10-03 11:48:40 +02:00
|
|
|
message_viewport.maybe_scroll_to_show_message_top();
|
|
|
|
const $popover_reference = $(".selected_message .actions_hover .message-actions-menu-button");
|
|
|
|
message_actions_popover_keyboard_toggle = true;
|
|
|
|
$popover_reference.trigger("click");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function initialize() {
|
|
|
|
popover_menus.register_popover_menu(".actions_hover .message-actions-menu-button", {
|
2024-02-28 08:37:49 +01:00
|
|
|
theme: "popover-menu",
|
2023-10-03 11:48:40 +02:00
|
|
|
placement: "bottom",
|
|
|
|
popperOptions: {
|
|
|
|
modifiers: [
|
|
|
|
{
|
|
|
|
// The placement is set to bottom, but if that placement does not fit,
|
|
|
|
// the opposite top placement will be used.
|
|
|
|
name: "flip",
|
|
|
|
options: {
|
|
|
|
fallbackPlacements: ["top", "left"],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
onShow(instance) {
|
|
|
|
popover_menus.on_show_prep(instance);
|
|
|
|
const $row = $(instance.reference).closest(".message_row");
|
|
|
|
const message_id = rows.id($row);
|
|
|
|
const args = popover_menus_data.get_actions_popover_content_context(message_id);
|
2024-05-18 08:36:20 +02:00
|
|
|
instance.setContent(parse_html(render_message_actions_popover(args)));
|
2023-10-03 11:48:40 +02:00
|
|
|
$row.addClass("has_actions_popover");
|
|
|
|
},
|
|
|
|
onMount(instance) {
|
message_view: Allow quoting selected message text, via hotkey and menu.
Whenever the user has text selected within a single message, and uses
the hotkey to quote and reply, this selection will be quoted. In case of
no selection or selection not within a single message, the entirety of
the currently focused message will be quoted like before.
Similarly, when the user selects text within a single message, opens
that message's actions menu, and clicks the "Quote and reply" option,
the selected text will be quoted. In case of no selection or selection
containing any other message/s, the entirety of the message whose menu
was opened will be quoted like before.
When partially quoting a message, it is the markdown version of the
selection that is quoted, hence preserving any formatting. Any other
elements of the message, outside of the content, selected presumably by
accident (like the timestamp or sender name), are ignored.
Fixes: #19712.
2024-02-01 19:23:17 +01:00
|
|
|
const $row = $(instance.reference).closest(".message_row");
|
|
|
|
const message_id = rows.id($row);
|
|
|
|
let quote_content;
|
|
|
|
if (compose_reply.selection_within_message_id() === message_id) {
|
|
|
|
// If the user has selected text within this message, quote only that.
|
|
|
|
// We track the selection right now, before the popover option for Quote
|
|
|
|
// and reply is clicked, since by then the selection is lost, due to the
|
|
|
|
// change in focus.
|
|
|
|
quote_content = compose_reply.get_message_selection();
|
|
|
|
}
|
2023-10-03 11:48:40 +02:00
|
|
|
if (message_actions_popover_keyboard_toggle) {
|
|
|
|
focus_first_action_popover_item();
|
|
|
|
message_actions_popover_keyboard_toggle = false;
|
|
|
|
}
|
|
|
|
popover_menus.popover_instances.message_actions = instance;
|
|
|
|
|
|
|
|
// We want click events to propagate to `instance` so that
|
|
|
|
// instance.hide gets called.
|
|
|
|
const $popper = $(instance.popper);
|
|
|
|
$popper.one("click", ".respond_button", (e) => {
|
message_view: Allow quoting selected message text, via hotkey and menu.
Whenever the user has text selected within a single message, and uses
the hotkey to quote and reply, this selection will be quoted. In case of
no selection or selection not within a single message, the entirety of
the currently focused message will be quoted like before.
Similarly, when the user selects text within a single message, opens
that message's actions menu, and clicks the "Quote and reply" option,
the selected text will be quoted. In case of no selection or selection
containing any other message/s, the entirety of the message whose menu
was opened will be quoted like before.
When partially quoting a message, it is the markdown version of the
selection that is quoted, hence preserving any formatting. Any other
elements of the message, outside of the content, selected presumably by
accident (like the timestamp or sender name), are ignored.
Fixes: #19712.
2024-02-01 19:23:17 +01:00
|
|
|
compose_reply.quote_and_reply({
|
|
|
|
trigger: "popover respond",
|
|
|
|
message_id,
|
|
|
|
quote_content,
|
|
|
|
});
|
2023-10-03 11:48:40 +02:00
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
2024-04-02 09:29:56 +02:00
|
|
|
popover_menus.hide_current_popover_if_visible(instance);
|
2023-10-03 11:48:40 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
$popper.one("click", ".popover_edit_message, .popover_view_source", (e) => {
|
2024-05-03 02:05:20 +02:00
|
|
|
const message_id = Number($(e.currentTarget).attr("data-message-id"));
|
2024-02-05 19:03:29 +01:00
|
|
|
assert(message_lists.current !== undefined);
|
2023-10-03 11:48:40 +02:00
|
|
|
const $row = message_lists.current.get_row(message_id);
|
|
|
|
message_edit.start($row);
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
2024-04-02 09:29:56 +02:00
|
|
|
popover_menus.hide_current_popover_if_visible(instance);
|
2023-10-03 11:48:40 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
$popper.one("click", ".popover_move_message", (e) => {
|
2024-05-03 02:05:20 +02:00
|
|
|
const message_id = Number($(e.currentTarget).attr("data-message-id"));
|
2024-02-05 19:03:29 +01:00
|
|
|
assert(message_lists.current !== undefined);
|
2024-02-20 17:20:29 +01:00
|
|
|
message_lists.current.select_id(message_id);
|
2023-10-03 11:48:40 +02:00
|
|
|
const message = message_lists.current.get(message_id);
|
|
|
|
stream_popover.build_move_topic_to_stream_popover(
|
|
|
|
message.stream_id,
|
|
|
|
message.topic,
|
|
|
|
false,
|
|
|
|
message,
|
|
|
|
);
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
2024-04-02 09:29:56 +02:00
|
|
|
popover_menus.hide_current_popover_if_visible(instance);
|
2023-10-03 11:48:40 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
$popper.one("click", ".mark_as_unread", (e) => {
|
2024-05-03 02:05:20 +02:00
|
|
|
const message_id = Number($(e.currentTarget).attr("data-message-id"));
|
2023-10-03 11:48:40 +02:00
|
|
|
unread_ops.mark_as_unread_from_here(message_id);
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
2024-04-02 09:29:56 +02:00
|
|
|
popover_menus.hide_current_popover_if_visible(instance);
|
2023-10-03 11:48:40 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
$popper.one("click", ".popover_toggle_collapse", (e) => {
|
2024-05-03 02:05:20 +02:00
|
|
|
const message_id = Number($(e.currentTarget).attr("data-message-id"));
|
2024-02-05 19:03:29 +01:00
|
|
|
assert(message_lists.current !== undefined);
|
2024-03-06 23:14:37 +01:00
|
|
|
const message = message_lists.current.get(message_id);
|
|
|
|
assert(message !== undefined);
|
|
|
|
if (message.collapsed) {
|
|
|
|
condense.uncollapse(message);
|
|
|
|
} else {
|
|
|
|
condense.collapse(message);
|
2023-10-03 11:48:40 +02:00
|
|
|
}
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
2024-04-02 09:29:56 +02:00
|
|
|
popover_menus.hide_current_popover_if_visible(instance);
|
2023-10-03 11:48:40 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
$popper.one("click", ".rehide_muted_user_message", (e) => {
|
2024-05-03 02:05:20 +02:00
|
|
|
const message_id = Number($(e.currentTarget).attr("data-message-id"));
|
2024-02-05 19:03:29 +01:00
|
|
|
assert(message_lists.current !== undefined);
|
2023-10-03 11:48:40 +02:00
|
|
|
const $row = message_lists.current.get_row(message_id);
|
|
|
|
const message = message_lists.current.get(rows.id($row));
|
|
|
|
const message_container = message_lists.current.view.message_containers.get(
|
|
|
|
message.id,
|
|
|
|
);
|
|
|
|
if ($row && !message_container.is_hidden) {
|
|
|
|
message_lists.current.view.hide_revealed_message(message_id);
|
|
|
|
}
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
2024-04-02 09:29:56 +02:00
|
|
|
popover_menus.hide_current_popover_if_visible(instance);
|
2023-10-03 11:48:40 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
$popper.one("click", ".view_read_receipts", (e) => {
|
2024-05-03 02:05:20 +02:00
|
|
|
const message_id = Number($(e.currentTarget).attr("data-message-id"));
|
2023-10-03 11:48:40 +02:00
|
|
|
read_receipts.show_user_list(message_id);
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
2024-04-02 09:29:56 +02:00
|
|
|
popover_menus.hide_current_popover_if_visible(instance);
|
2023-10-03 11:48:40 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
$popper.one("click", ".delete_message", (e) => {
|
2024-05-03 02:05:20 +02:00
|
|
|
const message_id = Number($(e.currentTarget).attr("data-message-id"));
|
2023-10-03 11:48:40 +02:00
|
|
|
message_edit.delete_message(message_id);
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
2024-04-02 09:29:56 +02:00
|
|
|
popover_menus.hide_current_popover_if_visible(instance);
|
2023-10-03 11:48:40 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
$popper.one("click", ".reaction_button", (e) => {
|
2024-05-03 02:05:20 +02:00
|
|
|
const message_id = Number($(e.currentTarget).attr("data-message-id"));
|
2023-10-03 11:48:40 +02:00
|
|
|
// Don't propagate the click event since `toggle_emoji_popover` opens a
|
|
|
|
// emoji_picker which we don't want to hide after actions popover is hidden.
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
emoji_picker.toggle_emoji_popover(instance.reference.parentElement, message_id, {
|
|
|
|
placement: "bottom",
|
|
|
|
});
|
2024-04-02 09:29:56 +02:00
|
|
|
popover_menus.hide_current_popover_if_visible(instance);
|
2023-10-03 11:48:40 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
new ClipboardJS($popper.find(".copy_link")[0]).on("success", () => {
|
|
|
|
show_copied_confirmation($(instance.reference).closest(".message_controls")[0]);
|
|
|
|
setTimeout(() => {
|
|
|
|
// The Clipboard library works by focusing to a hidden textarea.
|
|
|
|
// We unfocus this so keyboard shortcuts, etc., will work again.
|
|
|
|
$(":focus").trigger("blur");
|
|
|
|
}, 0);
|
2024-04-02 09:29:56 +02:00
|
|
|
popover_menus.hide_current_popover_if_visible(instance);
|
2023-10-03 11:48:40 +02:00
|
|
|
});
|
|
|
|
},
|
|
|
|
onHidden(instance) {
|
|
|
|
const $row = $(instance.reference).closest(".message_row");
|
|
|
|
$row.removeClass("has_actions_popover");
|
|
|
|
instance.destroy();
|
|
|
|
popover_menus.popover_instances.message_actions = undefined;
|
|
|
|
message_actions_popover_keyboard_toggle = false;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|