mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	actions_popover: Use tippyjs instead of bootstrap to display popover.
Fixes #23494 Popover now automatically displays on left when there is not enough space top or bottom of the reference element.
This commit is contained in:
		@@ -59,12 +59,14 @@ const overlays = mock_esm("../../static/js/overlays", {
 | 
			
		||||
    is_overlay_or_modal_open: () => overlays.is_modal_open() || overlays.is_active(),
 | 
			
		||||
});
 | 
			
		||||
const popovers = mock_esm("../../static/js/popovers", {
 | 
			
		||||
    actions_popped: () => false,
 | 
			
		||||
    user_info_manage_menu_popped: () => false,
 | 
			
		||||
    message_info_popped: () => false,
 | 
			
		||||
    user_sidebar_popped: () => false,
 | 
			
		||||
    user_info_popped: () => false,
 | 
			
		||||
});
 | 
			
		||||
const popover_menus = mock_esm("../../static/js/popover_menus", {
 | 
			
		||||
    actions_popped: () => false,
 | 
			
		||||
});
 | 
			
		||||
const reactions = mock_esm("../../static/js/reactions");
 | 
			
		||||
const search = mock_esm("../../static/js/search");
 | 
			
		||||
const settings_data = mock_esm("../../static/js/settings_data");
 | 
			
		||||
@@ -366,7 +368,7 @@ run_test("misc", ({override}) => {
 | 
			
		||||
    assert_mapping("s", narrow, "by_recipient");
 | 
			
		||||
    assert_mapping("S", narrow, "by_topic");
 | 
			
		||||
    assert_mapping("u", popovers, "show_sender_info");
 | 
			
		||||
    assert_mapping("i", popovers, "open_message_menu");
 | 
			
		||||
    assert_mapping("i", popover_menus, "toggle_message_actions_menu");
 | 
			
		||||
    assert_mapping(":", reactions, "open_reactions_popover", true);
 | 
			
		||||
    assert_mapping(">", compose_actions, "quote_and_reply");
 | 
			
		||||
    assert_mapping("e", message_edit, "start");
 | 
			
		||||
 
 | 
			
		||||
@@ -29,9 +29,6 @@ const message_lists = mock_esm("../../static/js/message_lists", {
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
mock_esm("../../static/js/message_viewport", {
 | 
			
		||||
    height: () => 500,
 | 
			
		||||
});
 | 
			
		||||
mock_esm("../../static/js/stream_popover", {
 | 
			
		||||
    hide_stream_popover: noop,
 | 
			
		||||
    hide_topic_popover: noop,
 | 
			
		||||
@@ -43,7 +40,6 @@ mock_esm("../../static/js/stream_popover", {
 | 
			
		||||
 | 
			
		||||
const people = zrequire("people");
 | 
			
		||||
const user_status = zrequire("user_status");
 | 
			
		||||
const message_edit = zrequire("message_edit");
 | 
			
		||||
const popovers = zrequire("popovers");
 | 
			
		||||
 | 
			
		||||
const alice = {
 | 
			
		||||
@@ -218,54 +214,3 @@ test_ui("sender_hover", ({override, mock_template}) => {
 | 
			
		||||
 | 
			
		||||
    // todo: load image
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test_ui("actions_popover", ({override, mock_template}) => {
 | 
			
		||||
    override($.fn, "popover", noop);
 | 
			
		||||
 | 
			
		||||
    const $target = $.create("click target");
 | 
			
		||||
 | 
			
		||||
    const handler = $("#main_div").get_on_handler("click", ".actions_hover");
 | 
			
		||||
 | 
			
		||||
    const message = {
 | 
			
		||||
        id: 999,
 | 
			
		||||
        topic: "Actions (1)",
 | 
			
		||||
        type: "stream",
 | 
			
		||||
        stream_id: 123,
 | 
			
		||||
        sent_by_me: true,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    message_lists.current.get = (msg_id) => {
 | 
			
		||||
        assert.equal(msg_id, message.id);
 | 
			
		||||
        return message;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    message_lists.current.view.message_containers.get = (msg_id) => {
 | 
			
		||||
        assert.equal(msg_id, message.id);
 | 
			
		||||
        return {
 | 
			
		||||
            is_hidden: false,
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    override(page_params, "realm_allow_message_editing", true);
 | 
			
		||||
    override(page_params, "realm_message_content_edit_limit_seconds", null);
 | 
			
		||||
    assert.equal(message_edit.get_editability(message), message_edit.editability_types.FULL);
 | 
			
		||||
 | 
			
		||||
    $target.closest = (sel) => {
 | 
			
		||||
        assert.equal(sel, ".message_row");
 | 
			
		||||
        return {
 | 
			
		||||
            toggleClass: noop,
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    mock_template("actions_popover_template.hbs", false, () => "actions-template");
 | 
			
		||||
    mock_template("actions_popover_content.hbs", false, (opts) => {
 | 
			
		||||
        // TODO: Test all the properties of the popover
 | 
			
		||||
        assert.equal(
 | 
			
		||||
            opts.conversation_time_uri,
 | 
			
		||||
            "http://zulip.zulipdev.com/#narrow/stream/123-unknown/topic/Actions.20.281.29/near/999",
 | 
			
		||||
        );
 | 
			
		||||
        return "actions-content";
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    handler.call($target, e);
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -242,11 +242,6 @@ export function initialize() {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $("body").on("click", ".reveal_hidden_message", (e) => {
 | 
			
		||||
        // Hide actions popover to keep its options
 | 
			
		||||
        // in sync with revealed/hidden state of
 | 
			
		||||
        // muted user's message.
 | 
			
		||||
        popovers.hide_actions_popover();
 | 
			
		||||
 | 
			
		||||
        const message_id = rows.id($(e.currentTarget).closest(".message_row"));
 | 
			
		||||
        message_lists.current.view.reveal_hidden_message(message_id);
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
 
 | 
			
		||||
@@ -666,7 +666,7 @@ export function build_emoji_popover($elt, id) {
 | 
			
		||||
    register_popover_events($popover);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function toggle_emoji_popover(element, id) {
 | 
			
		||||
export function toggle_emoji_popover(element, id, coming_from_actions_popover) {
 | 
			
		||||
    const $last_popover_elem = $current_message_emoji_popover_elem;
 | 
			
		||||
    popovers.hide_all();
 | 
			
		||||
    if ($last_popover_elem !== undefined && $last_popover_elem.get()[0] === element) {
 | 
			
		||||
@@ -683,7 +683,7 @@ export function toggle_emoji_popover(element, id) {
 | 
			
		||||
 | 
			
		||||
    if (user_status_ui.user_status_picker_open()) {
 | 
			
		||||
        build_emoji_popover($elt, id, true);
 | 
			
		||||
    } else if ($elt.data("popover") === undefined) {
 | 
			
		||||
    } else if ($elt.data("popover") === undefined || coming_from_actions_popover) {
 | 
			
		||||
        // Keep the element over which the popover is based off visible.
 | 
			
		||||
        $elt.addClass("reaction_button_visible");
 | 
			
		||||
        build_emoji_popover($elt, id);
 | 
			
		||||
@@ -748,19 +748,6 @@ export function register_click_handlers() {
 | 
			
		||||
        toggle_emoji_popover(this, message_id);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $("body").on("click", ".actions_popover .reaction_button", (e) => {
 | 
			
		||||
        const message_id = $(e.currentTarget).data("message-id");
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        // HACK: Because we need the popover to be based off an
 | 
			
		||||
        // element that definitely exists in the page even if the
 | 
			
		||||
        // message wasn't sent by us and thus the .reaction_hover
 | 
			
		||||
        // element is not present, we use the message's
 | 
			
		||||
        // .fa-chevron-down element as the base for the popover.
 | 
			
		||||
        const elem = $(".selected_message .actions_hover")[0];
 | 
			
		||||
        toggle_emoji_popover(elem, message_id);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $("body").on("click", ".emoji-popover-tab-item", function (e) {
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,7 @@ import * as narrow from "./narrow";
 | 
			
		||||
import * as navigate from "./navigate";
 | 
			
		||||
import * as overlays from "./overlays";
 | 
			
		||||
import {page_params} from "./page_params";
 | 
			
		||||
import * as popover_menus from "./popover_menus";
 | 
			
		||||
import * as popovers from "./popovers";
 | 
			
		||||
import * as reactions from "./reactions";
 | 
			
		||||
import * as recent_topics_ui from "./recent_topics_ui";
 | 
			
		||||
@@ -351,7 +352,7 @@ export function process_escape_key(e) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handle_popover_events(event_name) {
 | 
			
		||||
    if (popovers.actions_popped()) {
 | 
			
		||||
    if (popover_menus.actions_popped()) {
 | 
			
		||||
        popovers.actions_menu_handle_keyboard(event_name);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
@@ -938,7 +939,7 @@ export function process_hotkey(e, hotkey) {
 | 
			
		||||
    // Shortcuts that operate on a message
 | 
			
		||||
    switch (event_name) {
 | 
			
		||||
        case "message_actions":
 | 
			
		||||
            return popovers.open_message_menu(msg);
 | 
			
		||||
            return popover_menus.toggle_message_actions_menu(msg);
 | 
			
		||||
        case "star_message":
 | 
			
		||||
            message_flags.toggle_starred_and_update_server(msg);
 | 
			
		||||
            return true;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,9 +2,11 @@
 | 
			
		||||
   TippyJS/Popper popover library from the legacy Bootstrap
 | 
			
		||||
   popovers system in popovers.js. */
 | 
			
		||||
 | 
			
		||||
import ClipboardJS from "clipboard";
 | 
			
		||||
import $ from "jquery";
 | 
			
		||||
import tippy, {delegate} from "tippy.js";
 | 
			
		||||
 | 
			
		||||
import render_actions_popover_content from "../templates/actions_popover_content.hbs";
 | 
			
		||||
import render_compose_control_buttons_popover from "../templates/compose_control_buttons_popover.hbs";
 | 
			
		||||
import render_compose_select_enter_behaviour_popover from "../templates/compose_select_enter_behaviour_popover.hbs";
 | 
			
		||||
import render_left_sidebar_stream_setting_popover from "../templates/left_sidebar_stream_setting_popover.hbs";
 | 
			
		||||
@@ -13,17 +15,34 @@ import render_mobile_message_buttons_popover_content from "../templates/mobile_m
 | 
			
		||||
import * as channel from "./channel";
 | 
			
		||||
import * as common from "./common";
 | 
			
		||||
import * as compose_actions from "./compose_actions";
 | 
			
		||||
import * as condense from "./condense";
 | 
			
		||||
import * as emoji_picker from "./emoji_picker";
 | 
			
		||||
import * as giphy from "./giphy";
 | 
			
		||||
import {$t} from "./i18n";
 | 
			
		||||
import * as message_edit from "./message_edit";
 | 
			
		||||
import * as message_edit_history from "./message_edit_history";
 | 
			
		||||
import * as message_lists from "./message_lists";
 | 
			
		||||
import * as narrow_state from "./narrow_state";
 | 
			
		||||
import * as popover_menus_data from "./popover_menus_data";
 | 
			
		||||
import * as popovers from "./popovers";
 | 
			
		||||
import * as read_receipts from "./read_receipts";
 | 
			
		||||
import * as rows from "./rows";
 | 
			
		||||
import * as settings_data from "./settings_data";
 | 
			
		||||
import * as stream_popover from "./stream_popover";
 | 
			
		||||
import {parse_html} from "./ui_util";
 | 
			
		||||
import * as unread_ops from "./unread_ops";
 | 
			
		||||
import {user_settings} from "./user_settings";
 | 
			
		||||
 | 
			
		||||
let left_sidebar_stream_setting_popover_displayed = false;
 | 
			
		||||
let compose_mobile_button_popover_displayed = false;
 | 
			
		||||
export let compose_enter_sends_popover_displayed = false;
 | 
			
		||||
let compose_control_buttons_popover_instance;
 | 
			
		||||
let message_actions_popover_displayed = false;
 | 
			
		||||
let message_actions_popover_keyboard_toggle = false;
 | 
			
		||||
 | 
			
		||||
export function actions_popped() {
 | 
			
		||||
    return message_actions_popover_displayed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function get_compose_control_buttons_popover() {
 | 
			
		||||
    return compose_control_buttons_popover_instance;
 | 
			
		||||
@@ -47,7 +66,8 @@ export function any_active() {
 | 
			
		||||
        left_sidebar_stream_setting_popover_displayed ||
 | 
			
		||||
        compose_mobile_button_popover_displayed ||
 | 
			
		||||
        compose_control_buttons_popover_instance ||
 | 
			
		||||
        compose_enter_sends_popover_displayed
 | 
			
		||||
        compose_enter_sends_popover_displayed ||
 | 
			
		||||
        message_actions_popover_displayed
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -98,6 +118,20 @@ function tippy_no_propagation(target, popover_props) {
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function toggle_message_actions_menu(message) {
 | 
			
		||||
    if (message.locally_echoed) {
 | 
			
		||||
        // 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.
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const $popover_reference = $(".selected_message .actions_hover .zulip-icon-ellipsis-v-solid");
 | 
			
		||||
    message_actions_popover_keyboard_toggle = true;
 | 
			
		||||
    $popover_reference.trigger("click");
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function initialize() {
 | 
			
		||||
    tippy_no_propagation("#streams_inline_icon", {
 | 
			
		||||
        onShow(instance) {
 | 
			
		||||
@@ -228,4 +262,183 @@ export function initialize() {
 | 
			
		||||
            compose_enter_sends_popover_displayed = false;
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    tippy_no_propagation(".actions_hover .zulip-icon-ellipsis-v-solid", {
 | 
			
		||||
        // The is our minimum supported width for mobile. We shouldn't
 | 
			
		||||
        // make the popover wider than this.
 | 
			
		||||
        maxWidth: "320px",
 | 
			
		||||
        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) {
 | 
			
		||||
            on_show_prep(instance);
 | 
			
		||||
            const $row = $(instance.reference).closest(".message_row");
 | 
			
		||||
            const message_id = rows.id($row);
 | 
			
		||||
            message_lists.current.select_id(message_id);
 | 
			
		||||
            const args = popover_menus_data.get_actions_popover_content_context(message_id);
 | 
			
		||||
            instance.setContent(parse_html(render_actions_popover_content(args)));
 | 
			
		||||
            $row.addClass("has_popover has_actions_popover");
 | 
			
		||||
            message_actions_popover_displayed = true;
 | 
			
		||||
        },
 | 
			
		||||
        onMount(instance) {
 | 
			
		||||
            if (message_actions_popover_keyboard_toggle) {
 | 
			
		||||
                popovers.focus_first_action_popover_item();
 | 
			
		||||
            }
 | 
			
		||||
            message_actions_popover_keyboard_toggle = false;
 | 
			
		||||
 | 
			
		||||
            // We want click events to propagate to `instance` so that
 | 
			
		||||
            // instance.hide gets called.
 | 
			
		||||
            const $popper = $(instance.popper);
 | 
			
		||||
            $popper.one("click", ".respond_button", (e) => {
 | 
			
		||||
                // Arguably, we should fetch the message ID to respond to from
 | 
			
		||||
                // e.target, but that should always be the current selected
 | 
			
		||||
                // 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"});
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                e.stopPropagation();
 | 
			
		||||
                instance.hide();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $popper.one("click", ".popover_edit_message, .popover_view_source", (e) => {
 | 
			
		||||
                const message_id = $(e.currentTarget).data("message-id");
 | 
			
		||||
                const $row = message_lists.current.get_row(message_id);
 | 
			
		||||
                message_edit.start($row);
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                e.stopPropagation();
 | 
			
		||||
                instance.hide();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $popper.one("click", ".popover_move_message", (e) => {
 | 
			
		||||
                const message_id = $(e.currentTarget).data("message-id");
 | 
			
		||||
                const message = message_lists.current.get(message_id);
 | 
			
		||||
                stream_popover.build_move_topic_to_stream_popover(
 | 
			
		||||
                    message.stream_id,
 | 
			
		||||
                    message.topic,
 | 
			
		||||
                    message,
 | 
			
		||||
                );
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                e.stopPropagation();
 | 
			
		||||
                instance.hide();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $popper.one("click", ".mark_as_unread", (e) => {
 | 
			
		||||
                const message_id = $(e.currentTarget).data("message-id");
 | 
			
		||||
                unread_ops.mark_as_unread_from_here(message_id);
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                e.stopPropagation();
 | 
			
		||||
                instance.hide();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $popper.one("click", ".popover_toggle_collapse", (e) => {
 | 
			
		||||
                const message_id = $(e.currentTarget).data("message-id");
 | 
			
		||||
                const $row = message_lists.current.get_row(message_id);
 | 
			
		||||
                const message = message_lists.current.get(rows.id($row));
 | 
			
		||||
                if ($row) {
 | 
			
		||||
                    if (message.collapsed) {
 | 
			
		||||
                        condense.uncollapse($row);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        condense.collapse($row);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                e.stopPropagation();
 | 
			
		||||
                instance.hide();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $popper.one("click", ".rehide_muted_user_message", (e) => {
 | 
			
		||||
                const message_id = $(e.currentTarget).data("message-id");
 | 
			
		||||
                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();
 | 
			
		||||
                instance.hide();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $popper.one("click", ".view_edit_history", (e) => {
 | 
			
		||||
                const message_id = $(e.currentTarget).data("message-id");
 | 
			
		||||
                const $row = message_lists.current.get_row(message_id);
 | 
			
		||||
                const message = message_lists.current.get(rows.id($row));
 | 
			
		||||
                message_edit_history.show_history(message);
 | 
			
		||||
                $("#message-history-cancel").trigger("focus");
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                e.stopPropagation();
 | 
			
		||||
                instance.hide();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $popper.one("click", ".view_read_receipts", (e) => {
 | 
			
		||||
                const message_id = $(e.currentTarget).data("message-id");
 | 
			
		||||
                read_receipts.show_user_list(message_id);
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                e.stopPropagation();
 | 
			
		||||
                instance.hide();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $popper.one("click", ".delete_message", (e) => {
 | 
			
		||||
                const message_id = $(e.currentTarget).data("message-id");
 | 
			
		||||
                message_edit.delete_message(message_id);
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                e.stopPropagation();
 | 
			
		||||
                instance.hide();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $popper.one("click", ".reaction_button", (e) => {
 | 
			
		||||
                const message_id = $(e.currentTarget).data("message-id");
 | 
			
		||||
                // 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();
 | 
			
		||||
                // HACK: Because we need the popover to be based off an
 | 
			
		||||
                // element that definitely exists in the page even if the
 | 
			
		||||
                // message wasn't sent by us and thus the .reaction_hover
 | 
			
		||||
                // element is not present, we use the message's
 | 
			
		||||
                // .fa-chevron-down element as the base for the popover.
 | 
			
		||||
                const elem = $(".selected_message .actions_hover")[0];
 | 
			
		||||
                emoji_picker.toggle_emoji_popover(elem, message_id, true);
 | 
			
		||||
                instance.hide();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            new ClipboardJS($popper.find(".copy_link")[0]).on("success", (e) => {
 | 
			
		||||
                // e.trigger returns the DOM element triggering the copy action
 | 
			
		||||
                const message_id = e.trigger.dataset.messageId;
 | 
			
		||||
                const $row = $(`[zid='${CSS.escape(message_id)}']`);
 | 
			
		||||
                $row.find(".alert-msg")
 | 
			
		||||
                    .text($t({defaultMessage: "Copied!"}))
 | 
			
		||||
                    .css("display", "block")
 | 
			
		||||
                    .delay(1000)
 | 
			
		||||
                    .fadeOut(300);
 | 
			
		||||
 | 
			
		||||
                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);
 | 
			
		||||
                instance.hide();
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        onHidden(instance) {
 | 
			
		||||
            const $row = $(instance.reference).closest(".message_row");
 | 
			
		||||
            $row.removeClass("has_popover has_actions_popover");
 | 
			
		||||
            instance.destroy();
 | 
			
		||||
            message_actions_popover_displayed = false;
 | 
			
		||||
            message_actions_popover_keyboard_toggle = false;
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,10 @@
 | 
			
		||||
import ClipboardJS from "clipboard";
 | 
			
		||||
import {add, formatISO, parseISO, set} from "date-fns";
 | 
			
		||||
import ConfirmDatePlugin from "flatpickr/dist/plugins/confirmDate/confirmDate";
 | 
			
		||||
import $ from "jquery";
 | 
			
		||||
import tippy, {hideAll} from "tippy.js";
 | 
			
		||||
 | 
			
		||||
import render_actions_popover_content from "../templates/actions_popover_content.hbs";
 | 
			
		||||
import render_actions_popover_template from "../templates/actions_popover_template.hbs";
 | 
			
		||||
import render_no_arrow_popover from "../templates/no_arrow_popover.hbs";
 | 
			
		||||
import render_playground_links_popover_content from "../templates/playground_links_popover_content.hbs";
 | 
			
		||||
import render_remind_me_popover_content from "../templates/remind_me_popover_content.hbs";
 | 
			
		||||
import render_user_group_info_popover from "../templates/user_group_info_popover.hbs";
 | 
			
		||||
import render_user_group_info_popover_content from "../templates/user_group_info_popover_content.hbs";
 | 
			
		||||
import render_user_info_popover_content from "../templates/user_info_popover_content.hbs";
 | 
			
		||||
@@ -21,15 +17,11 @@ import * as channel from "./channel";
 | 
			
		||||
import * as compose_actions from "./compose_actions";
 | 
			
		||||
import * as compose_state from "./compose_state";
 | 
			
		||||
import * as compose_ui from "./compose_ui";
 | 
			
		||||
import * as condense from "./condense";
 | 
			
		||||
import {media_breakpoints_num} from "./css_variables";
 | 
			
		||||
import * as dialog_widget from "./dialog_widget";
 | 
			
		||||
import * as emoji_picker from "./emoji_picker";
 | 
			
		||||
import * as giphy from "./giphy";
 | 
			
		||||
import * as hash_util from "./hash_util";
 | 
			
		||||
import {$t, $t_html} from "./i18n";
 | 
			
		||||
import * as message_edit from "./message_edit";
 | 
			
		||||
import * as message_edit_history from "./message_edit_history";
 | 
			
		||||
import * as message_lists from "./message_lists";
 | 
			
		||||
import * as message_viewport from "./message_viewport";
 | 
			
		||||
import * as muted_users from "./muted_users";
 | 
			
		||||
@@ -39,8 +31,6 @@ import * as overlays from "./overlays";
 | 
			
		||||
import {page_params} from "./page_params";
 | 
			
		||||
import * as people from "./people";
 | 
			
		||||
import * as popover_menus from "./popover_menus";
 | 
			
		||||
import * as popover_menus_data from "./popover_menus_data";
 | 
			
		||||
import * as read_receipts from "./read_receipts";
 | 
			
		||||
import * as realm_playground from "./realm_playground";
 | 
			
		||||
import * as reminder from "./reminder";
 | 
			
		||||
import * as resize from "./resize";
 | 
			
		||||
@@ -51,7 +41,6 @@ import * as settings_data from "./settings_data";
 | 
			
		||||
import * as settings_users from "./settings_users";
 | 
			
		||||
import * as stream_popover from "./stream_popover";
 | 
			
		||||
import * as ui_report from "./ui_report";
 | 
			
		||||
import * as unread_ops from "./unread_ops";
 | 
			
		||||
import * as user_groups from "./user_groups";
 | 
			
		||||
import * as user_profile from "./user_profile";
 | 
			
		||||
import {user_settings} from "./user_settings";
 | 
			
		||||
@@ -59,8 +48,6 @@ import * as user_status from "./user_status";
 | 
			
		||||
import * as user_status_ui from "./user_status_ui";
 | 
			
		||||
import * as util from "./util";
 | 
			
		||||
 | 
			
		||||
let $current_actions_popover_elem;
 | 
			
		||||
let current_flatpickr_instance;
 | 
			
		||||
let $current_message_info_popover_elem;
 | 
			
		||||
let $current_user_info_popover_elem;
 | 
			
		||||
let $current_user_info_popover_manage_menu;
 | 
			
		||||
@@ -70,8 +57,6 @@ let userlist_placement = "right";
 | 
			
		||||
let list_of_popovers = [];
 | 
			
		||||
 | 
			
		||||
export function clear_for_testing() {
 | 
			
		||||
    $current_actions_popover_elem = undefined;
 | 
			
		||||
    current_flatpickr_instance = undefined;
 | 
			
		||||
    $current_message_info_popover_elem = undefined;
 | 
			
		||||
    $current_user_info_popover_elem = undefined;
 | 
			
		||||
    $current_user_info_popover_manage_menu = undefined;
 | 
			
		||||
@@ -519,55 +504,14 @@ function show_user_group_info_popover(element, group, message) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function toggle_actions_popover(element, id) {
 | 
			
		||||
    const $last_popover_elem = $current_actions_popover_elem;
 | 
			
		||||
    hide_all();
 | 
			
		||||
    if ($last_popover_elem !== undefined && $last_popover_elem.get()[0] === element) {
 | 
			
		||||
        // We want it to be the case that a user can dismiss a popover
 | 
			
		||||
        // by clicking on the same element that caused the popover.
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $(element).closest(".message_row").toggleClass("has_popover has_actions_popover");
 | 
			
		||||
    message_lists.current.select_id(id);
 | 
			
		||||
    const $elt = $(element);
 | 
			
		||||
    if ($elt.data("popover") === undefined) {
 | 
			
		||||
        const args = popover_menus_data.get_actions_popover_content_context(id);
 | 
			
		||||
 | 
			
		||||
        const ypos = $elt.offset().top;
 | 
			
		||||
        $elt.popover({
 | 
			
		||||
            // Popover height with 7 items in it is ~190 px
 | 
			
		||||
            placement: message_viewport.height() - ypos < 220 ? "top" : "bottom",
 | 
			
		||||
            title: "",
 | 
			
		||||
            content: render_actions_popover_content(args),
 | 
			
		||||
            template: render_actions_popover_template(),
 | 
			
		||||
            html: true,
 | 
			
		||||
            trigger: "manual",
 | 
			
		||||
        });
 | 
			
		||||
        $elt.popover("show");
 | 
			
		||||
        $current_actions_popover_elem = $elt;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (window.innerWidth < media_breakpoints_num.xl) {
 | 
			
		||||
        // This ensures that the popover doesn't overflow to the right of the window.
 | 
			
		||||
        const actions_popover_left = window.innerWidth - $(".actions_popover_wrapper").outerWidth();
 | 
			
		||||
        $(".actions_popover_wrapper").css("left", actions_popover_left);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function get_action_menu_menu_items() {
 | 
			
		||||
    const $current_actions_popover_elem = $("[data-tippy-root] .actions_popover");
 | 
			
		||||
    if (!$current_actions_popover_elem) {
 | 
			
		||||
        blueslip.error("Trying to get menu items when action popover is closed.");
 | 
			
		||||
        return undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const popover_data = $current_actions_popover_elem.data("popover");
 | 
			
		||||
    if (!popover_data) {
 | 
			
		||||
        blueslip.error("Cannot find popover data for actions menu.");
 | 
			
		||||
        return undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $("li:not(.divider):visible a", popover_data.$tip);
 | 
			
		||||
    return $current_actions_popover_elem.find("li:not(.divider):visible a");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function focus_first_popover_item($items, index = 0) {
 | 
			
		||||
@@ -607,50 +551,18 @@ export function popover_items_handle_keyboard(key, $items) {
 | 
			
		||||
    $items.eq(index).trigger("focus");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function focus_first_action_popover_item() {
 | 
			
		||||
export 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();
 | 
			
		||||
    focus_first_popover_item($items);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function open_message_menu(message) {
 | 
			
		||||
    if (message.locally_echoed) {
 | 
			
		||||
        // 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.
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const message_id = message.id;
 | 
			
		||||
    toggle_actions_popover($(".selected_message .actions_hover")[0], message_id);
 | 
			
		||||
    if ($current_actions_popover_elem) {
 | 
			
		||||
        focus_first_action_popover_item();
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function actions_menu_handle_keyboard(key) {
 | 
			
		||||
    const $items = get_action_menu_menu_items();
 | 
			
		||||
    popover_items_handle_keyboard(key, $items);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function actions_popped() {
 | 
			
		||||
    return $current_actions_popover_elem !== undefined;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function hide_actions_popover() {
 | 
			
		||||
    if (actions_popped()) {
 | 
			
		||||
        $(".has_popover").removeClass("has_popover has_actions_popover");
 | 
			
		||||
        $current_actions_popover_elem.popover("destroy");
 | 
			
		||||
        $current_actions_popover_elem = undefined;
 | 
			
		||||
    }
 | 
			
		||||
    if (current_flatpickr_instance !== undefined) {
 | 
			
		||||
        current_flatpickr_instance.destroy();
 | 
			
		||||
        current_flatpickr_instance = undefined;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function message_info_popped() {
 | 
			
		||||
    return $current_message_info_popover_elem !== undefined;
 | 
			
		||||
}
 | 
			
		||||
@@ -818,12 +730,6 @@ export function hide_playground_links_popover() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function register_click_handlers() {
 | 
			
		||||
    $("#main_div").on("click", ".actions_hover", function (e) {
 | 
			
		||||
        const $row = $(this).closest(".message_row");
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        toggle_actions_popover(this, rows.id($row));
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $("#main_div").on(
 | 
			
		||||
        "click",
 | 
			
		||||
        ".sender_name, .sender_name-in-status, .inline_profile_picture",
 | 
			
		||||
@@ -1086,28 +992,6 @@ export function register_click_handlers() {
 | 
			
		||||
        current_user_sidebar_popover = $target.data("popover");
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $("body").on("click", ".mark_as_unread", (e) => {
 | 
			
		||||
        hide_actions_popover();
 | 
			
		||||
        const message_id = $(e.currentTarget).data("message-id");
 | 
			
		||||
 | 
			
		||||
        unread_ops.mark_as_unread_from_here(message_id);
 | 
			
		||||
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $("body").on("click", ".respond_button", (e) => {
 | 
			
		||||
        // Arguably, we should fetch the message ID to respond to from
 | 
			
		||||
        // e.target, but that should always be the current selected
 | 
			
		||||
        // 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"});
 | 
			
		||||
        hide_actions_popover();
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $("body").on("click", ".remind.custom", (e) => {
 | 
			
		||||
        $(e.currentTarget)[0]._flatpickr.toggle();
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
@@ -1177,104 +1061,6 @@ export function register_click_handlers() {
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    });
 | 
			
		||||
    $("body").on("click", ".popover_toggle_collapse", (e) => {
 | 
			
		||||
        const message_id = $(e.currentTarget).data("message-id");
 | 
			
		||||
        const $row = message_lists.current.get_row(message_id);
 | 
			
		||||
        const message = message_lists.current.get(rows.id($row));
 | 
			
		||||
 | 
			
		||||
        hide_actions_popover();
 | 
			
		||||
 | 
			
		||||
        if ($row) {
 | 
			
		||||
            if (message.collapsed) {
 | 
			
		||||
                condense.uncollapse($row);
 | 
			
		||||
            } else {
 | 
			
		||||
                condense.collapse($row);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    });
 | 
			
		||||
    $("body").on("click", ".popover_edit_message, .popover_view_source", (e) => {
 | 
			
		||||
        const message_id = $(e.currentTarget).data("message-id");
 | 
			
		||||
        const $row = message_lists.current.get_row(message_id);
 | 
			
		||||
        hide_actions_popover();
 | 
			
		||||
        message_edit.start($row);
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    });
 | 
			
		||||
    $("body").on("click", ".popover_move_message", (e) => {
 | 
			
		||||
        const message_id = $(e.currentTarget).data("message-id");
 | 
			
		||||
        const message = message_lists.current.get(message_id);
 | 
			
		||||
        hide_actions_popover();
 | 
			
		||||
        stream_popover.build_move_topic_to_stream_popover(
 | 
			
		||||
            message.stream_id,
 | 
			
		||||
            message.topic,
 | 
			
		||||
            message,
 | 
			
		||||
        );
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    });
 | 
			
		||||
    $("body").on("click", ".rehide_muted_user_message", (e) => {
 | 
			
		||||
        const message_id = $(e.currentTarget).data("message-id");
 | 
			
		||||
        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);
 | 
			
		||||
 | 
			
		||||
        hide_actions_popover();
 | 
			
		||||
 | 
			
		||||
        if ($row && !message_container.is_hidden) {
 | 
			
		||||
            message_lists.current.view.hide_revealed_message(message_id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    });
 | 
			
		||||
    $("body").on("click", ".view_edit_history", (e) => {
 | 
			
		||||
        const message_id = $(e.currentTarget).data("message-id");
 | 
			
		||||
        const $row = message_lists.current.get_row(message_id);
 | 
			
		||||
        const message = message_lists.current.get(rows.id($row));
 | 
			
		||||
 | 
			
		||||
        hide_actions_popover();
 | 
			
		||||
        message_edit_history.show_history(message);
 | 
			
		||||
        $("#message-history-cancel").trigger("focus");
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $("body").on("click", ".view_read_receipts", (e) => {
 | 
			
		||||
        const message_id = $(e.currentTarget).data("message-id");
 | 
			
		||||
        hide_actions_popover();
 | 
			
		||||
        read_receipts.show_user_list(message_id);
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $("body").on("click", ".delete_message", (e) => {
 | 
			
		||||
        const message_id = $(e.currentTarget).data("message-id");
 | 
			
		||||
        hide_actions_popover();
 | 
			
		||||
        message_edit.delete_message(message_id);
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    clipboard_enable(".copy_link").on("success", (e) => {
 | 
			
		||||
        hide_actions_popover();
 | 
			
		||||
        // e.trigger returns the DOM element triggering the copy action
 | 
			
		||||
        const message_id = e.trigger.dataset.messageId;
 | 
			
		||||
        const $row = $(`[zid='${CSS.escape(message_id)}']`);
 | 
			
		||||
        $row.find(".alert-msg")
 | 
			
		||||
            .text($t({defaultMessage: "Copied!"}))
 | 
			
		||||
            .css("display", "block")
 | 
			
		||||
            .delay(1000)
 | 
			
		||||
            .fadeOut(300);
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    clipboard_enable(".copy_mention_syntax");
 | 
			
		||||
 | 
			
		||||
@@ -1332,7 +1118,6 @@ export function any_active() {
 | 
			
		||||
    // Expanded sidebars on mobile view count as popovers as well.
 | 
			
		||||
    return (
 | 
			
		||||
        popover_menus.any_active() ||
 | 
			
		||||
        actions_popped() ||
 | 
			
		||||
        user_sidebar_popped() ||
 | 
			
		||||
        stream_popover.stream_popped() ||
 | 
			
		||||
        stream_popover.topic_popped() ||
 | 
			
		||||
@@ -1353,7 +1138,6 @@ export function hide_all_except_sidebars(opts) {
 | 
			
		||||
    } else if (opts.exclude_tippy_instance) {
 | 
			
		||||
        hideAll({exclude: opts.exclude_tippy_instance});
 | 
			
		||||
    }
 | 
			
		||||
    hide_actions_popover();
 | 
			
		||||
    emoji_picker.hide_emoji_popover();
 | 
			
		||||
    giphy.hide_giphy_popover();
 | 
			
		||||
    stream_popover.hide_stream_popover();
 | 
			
		||||
 
 | 
			
		||||
@@ -205,21 +205,20 @@ ul {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.actions_popover_wrapper {
 | 
			
		||||
    .arrow {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @media (width < $xl_min) {
 | 
			
		||||
        width: max-content;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @media (width <= $mm_min) {
 | 
			
		||||
        width: auto;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.actions_popover {
 | 
			
		||||
    /* Override default padding of tippyjs */
 | 
			
		||||
    margin: 0 -9px;
 | 
			
		||||
 | 
			
		||||
    /* Override bootstrap defaults */
 | 
			
		||||
    .nav-list > li > a {
 | 
			
		||||
        padding: 3px 15px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Override bootstrap defaults */
 | 
			
		||||
    hr {
 | 
			
		||||
        margin: 5px 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .mark_as_unread {
 | 
			
		||||
        .unread_count {
 | 
			
		||||
            /* The icon for this menu item is in the form of an unread count from
 | 
			
		||||
 
 | 
			
		||||
@@ -71,6 +71,7 @@ EXEMPT_FILES = make_set(
 | 
			
		||||
        "static/js/confirm_dialog.js",
 | 
			
		||||
        "static/js/copy_and_paste.js",
 | 
			
		||||
        "static/js/csrf.ts",
 | 
			
		||||
        "static/js/css_variables.js",
 | 
			
		||||
        "static/js/dark_theme.js",
 | 
			
		||||
        "static/js/debug.js",
 | 
			
		||||
        "static/js/deprecated_feature_notice.js",
 | 
			
		||||
@@ -82,6 +83,7 @@ EXEMPT_FILES = make_set(
 | 
			
		||||
        "static/js/emoji_picker.js",
 | 
			
		||||
        "static/js/emojisets.js",
 | 
			
		||||
        "static/js/favicon.js",
 | 
			
		||||
        "static/js/feature_flags.ts",
 | 
			
		||||
        "static/js/feedback_widget.js",
 | 
			
		||||
        "static/js/flatpickr.js",
 | 
			
		||||
        "static/js/floating_recipient_bar.js",
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user