diff --git a/web/src/emoji_picker.js b/web/src/emoji_picker.js index de28da780d..db08cd73e7 100644 --- a/web/src/emoji_picker.js +++ b/web/src/emoji_picker.js @@ -9,30 +9,27 @@ import render_emoji_showcase from "../templates/emoji_showcase.hbs"; import * as blueslip from "./blueslip"; import * as compose_ui from "./compose_ui"; +import {media_breakpoints_num} from "./css_variables"; import * as emoji from "./emoji"; import * as keydown_util from "./keydown_util"; import * as message_lists from "./message_lists"; import * as message_store from "./message_store"; import {page_params} from "./page_params"; -import * as popovers from "./popovers"; +import * as popover_menus from "./popover_menus"; import * as reactions from "./reactions"; import * as rows from "./rows"; import * as scroll_util from "./scroll_util"; import * as spectators from "./spectators"; +import * as ui_util from "./ui_util"; import {user_settings} from "./user_settings"; import * as user_status_ui from "./user_status_ui"; -// Emoji picker is of fixed width and height. Update these -// whenever these values are changed in `reactions.css`. -const APPROX_HEIGHT = 375; -const APPROX_WIDTH = 255; - // The functionalities for reacting to a message with an emoji // and composing a message with an emoji share a single widget, // implemented as the emoji_popover. export let complete_emoji_catalog = []; -let $current_message_emoji_popover_elem; +let emoji_popover_instance = null; let emoji_catalog_last_coordinates = { section: 0, index: 0, @@ -43,6 +40,7 @@ let search_is_active = false; const search_results = []; let section_head_offsets = []; let edit_message_id = null; +let current_message_id = null; const EMOJI_CATEGORIES = [ {name: "Popular", icon: "fa-star-o"}, @@ -108,6 +106,7 @@ function show_search_results() { } function show_emoji_catalog() { + reset_emoji_showcase(); $(".emoji-popover-emoji-map").show(); $(".emoji-popover-category-tabs").show(); $(".emoji-search-results-container").hide(); @@ -164,7 +163,7 @@ export function rebuild_catalog() { const generate_emoji_picker_content = function (id) { let emojis_used = []; - if (id !== undefined) { + if (id) { emojis_used = reactions.get_emojis_used_by_user_for_message_id(id); } for (const emoji_dict of emoji.emojis_by_name.values()) { @@ -189,22 +188,24 @@ function refill_section_head_offsets($popover) { } export function reactions_popped() { - return $current_message_emoji_popover_elem !== undefined; + return Boolean(emoji_popover_instance); } export function hide_emoji_popover() { - $(".has_popover").removeClass("has_popover has_emoji_popover"); + if (!reactions_popped()) { + return; + } + current_message_id = null; if (user_status_ui.user_status_picker_open()) { // Re-enable clicking events for other elements after closing // the popover. This is the inverse of the hack of in the // handler that opens the "user status modal" emoji picker. $(".app, .header, .modal__overlay, #set-user-status-modal").css("pointer-events", "all"); } - if (reactions_popped()) { - $current_message_emoji_popover_elem.popover("destroy"); - $current_message_emoji_popover_elem.removeClass("reaction_button_visible"); - $current_message_emoji_popover_elem = undefined; - } + $(emoji_popover_instance.reference).removeClass("reaction_button_visible"); + $(emoji_popover_instance.reference).parent().removeClass("reaction_button_visible"); + emoji_popover_instance.destroy(); + emoji_popover_instance = null; } function get_selected_emoji() { @@ -433,7 +434,8 @@ function get_next_emoji_coordinates(move_by) { } function change_focus_to_filter() { - $(".emoji-popover-filter").trigger("focus"); + const $popover = $(emoji_popover_instance.popper); + $popover.find(".emoji-popover-filter").trigger("focus"); // If search is active reset current selected emoji to first emoji. if (search_is_active) { current_section = 0; @@ -619,71 +621,80 @@ function register_popover_events($popover) { }); } -export function build_emoji_popover(element, id) { - const $elt = $(element); - if (id !== undefined) { - message_lists.current.select_id(id); - } - - let placement = popovers.compute_placement($elt, APPROX_HEIGHT, APPROX_WIDTH, true); - - if (placement === "viewport_center") { - // For legacy reasons `compute_placement` actually can - // return `viewport_center`, but bootstrap doesn't actually - // support that. - placement = "left"; - } - - let template = render_emoji_popover(); - - // if the window is mobile sized, add the `.popover-flex` wrapper to the emoji - // popover so that it will be wrapped in flex and centered in the screen. - if (window.innerWidth <= 768) { - template = "
" + template + "
"; - } - - $elt.popover({ - // temporary patch for handling popover placement of `viewport_center` - placement, - fix_positions: true, - template, - title: "", - content: generate_emoji_picker_content(id), - html: true, - trigger: "manual", - fixed: true, - }); - $elt.popover("show"); - $elt.addClass("reaction_button_visible"); - $elt.closest(".message_row").toggleClass("has_popover has_emoji_popover"); - - const $popover = $elt.data("popover").$tip; - $popover.find(".emoji-popover-filter").trigger("focus"); - $current_message_emoji_popover_elem = $elt; - - emoji_catalog_last_coordinates = { - section: 0, - index: 0, +function get_default_emoji_popover_options() { + return { + placement: "top", + popperOptions: { + modifiers: [ + { + // The placement is set to top, but if that placement does not fit, + // the opposite bottom or left placement will be used. + name: "flip", + options: { + // We list both bottom and top here, because + // some callers override the default + // placement. + fallbackPlacements: ["bottom", "top", "left", "right"], + }, + }, + ], + }, + onCreate(instance) { + if (current_message_id) { + message_lists.current.select_id(current_message_id); + } + emoji_popover_instance = instance; + const $popover = $(instance.popper); + $popover.addClass("emoji-popover-root"); + instance.setContent(ui_util.parse_html(render_emoji_popover())); + $popover + .find(".popover-content") + .append(generate_emoji_picker_content(current_message_id)); + emoji_catalog_last_coordinates = { + section: 0, + index: 0, + }; + }, + onShow(instance) { + const $reference = $(instance.reference); + $reference.addClass("reaction_button_visible"); + $reference.parent().addClass("reaction_button_visible"); + }, + onMount(instance) { + const $popover = $(instance.popper); + refill_section_head_offsets($popover); + register_popover_events($popover); + show_emoji_catalog(); + change_focus_to_filter(); + }, + onHidden() { + hide_emoji_popover(); + }, }; - show_emoji_catalog(); - reset_emoji_showcase(); - - $(() => refill_section_head_offsets($popover)); - register_popover_events($popover); } -export function toggle_emoji_popover(element, id) { - const $last_popover_elem = $current_message_emoji_popover_elem; - popovers.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; +export function toggle_emoji_popover(target, id, additional_popover_options) { + if (id) { + current_message_id = id; } - build_emoji_popover(element, id); -} + let show_as_overlay = false; + // If the window is mobile-sized, we will render the + // emoji popover centered on the screen with the overlay. + if (window.innerWidth <= media_breakpoints_num.md) { + show_as_overlay = true; + } + + popover_menus.toggle_popover_menu( + target, + { + ...get_default_emoji_popover_options(), + ...additional_popover_options, + }, + {show_as_overlay}, + ); +} export function register_click_handlers() { $(document).on("click", ".emoji-popover-emoji.reaction", function (e) { // When an emoji is clicked in the popover, @@ -729,7 +740,7 @@ export function register_click_handlers() { toggle_emoji_popover(compose_click_target); }); - $("#main_div").on("click", ".reaction_button", function (e) { + $("#main_div").on("click", ".emoji-message-control-button-container", function (e) { e.stopPropagation(); if (page_params.is_spectator) { @@ -738,7 +749,7 @@ export function register_click_handlers() { } const message_id = rows.get_message_id(this); - toggle_emoji_popover(this, message_id); + toggle_emoji_popover(e.currentTarget, message_id, {placement: "bottom"}); }); $("body").on("click", ".emoji-popover-tab-item", function (e) { @@ -780,12 +791,17 @@ export function register_click_handlers() { $("body").on("click", "#set-user-status-modal #selected_emoji", (e) => { e.preventDefault(); e.stopPropagation(); - toggle_emoji_popover(e.target); - // Because the emoji picker gets drawn on top of the user - // status modal, we need this hack to make clicking outside - // the emoji picker only close the emoji picker, and not the - // whole user status modal. - $(".app, .header, .modal__overlay, #set-user-status-modal").css("pointer-events", "none"); + toggle_emoji_popover(e.target, undefined, {placement: "bottom"}); + if (reactions_popped()) { + // Because the emoji picker gets drawn on top of the user + // status modal, we need this hack to make clicking outside + // the emoji picker only close the emoji picker, and not the + // whole user status modal. + $(".app, .header, .modal__overlay, #set-user-status-modal").css( + "pointer-events", + "none", + ); + } }); $(document).on("click", ".emoji-popover-emoji.status-emoji", function (e) { diff --git a/web/src/hotkey.js b/web/src/hotkey.js index 7fa9775709..fa91f1bbd1 100644 --- a/web/src/hotkey.js +++ b/web/src/hotkey.js @@ -1000,8 +1000,11 @@ export function process_hotkey(e, hotkey) { case "toggle_reactions_popover": { const $row = message_lists.current.selected_row(); emoji_picker.toggle_emoji_popover( - msg.sent_by_me ? $row.find(".actions_hover")[0] : $row.find(".reaction_button")[0], + msg.sent_by_me + ? $row.find(".message-actions-menu-button")[0] + : $row.find(".emoji-message-control-button-container")[0], msg.id, + {placement: "bottom"}, ); return true; } diff --git a/web/src/popover_menus.js b/web/src/popover_menus.js index a20226ea06..1ef6473f20 100644 --- a/web/src/popover_menus.js +++ b/web/src/popover_menus.js @@ -895,11 +895,9 @@ export function initialize() { // 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, - true, - ); + emoji_picker.toggle_emoji_popover(instance.reference.parentElement, message_id, { + placement: "bottom", + }); instance.hide(); }); diff --git a/web/styles/popovers.css b/web/styles/popovers.css index 97c83570be..d30cece393 100644 --- a/web/styles/popovers.css +++ b/web/styles/popovers.css @@ -738,7 +738,8 @@ ul { left: -1px; } -.giphy-popover { +.giphy-popover, +.emoji-popover-root { .tippy-content { /* We remove the default padding from this container as it is not necessary for the Giphy popover. */ @@ -750,6 +751,18 @@ ul { } } +.emoji-popover-root { + /* The emoji popover has a different background color for the + header and footer, so we customize the arrow to match this color. */ + .tippy-box[data-placement="top"] .tippy-arrow::before { + border-top-color: hsl(0deg 0% 93%); + } + + .tippy-box[data-placement="bottom"] .tippy-arrow::before { + border-bottom-color: hsl(0deg 0% 93%); + } +} + #giphy_grid_in_popover { /* 300px of GIPHY grid + 5px is the extra gutter space * between gif columns. */ diff --git a/web/styles/reactions.css b/web/styles/reactions.css index b4509d62dd..a5e4dc6889 100644 --- a/web/styles/reactions.css +++ b/web/styles/reactions.css @@ -81,10 +81,13 @@ background-color: hsl(0deg 0% 98%); } - .reaction_button { + .reaction_button, + .emoji-message-control-button-container { display: flex; align-items: center; + } + .reaction_button { & i { font-size: 1em; } @@ -170,7 +173,7 @@ background-color: hsl(0deg 0% 93%); - border-radius: 5px 5px 0 0; + border-radius: 3px 3px 0 0; .fa-search { position: absolute; @@ -277,6 +280,7 @@ background-color: hsl(0deg 0% 93%); min-height: 44px; width: 250px; + border-radius: 0 0 3px 3px; .emoji-preview { position: absolute; diff --git a/web/styles/zulip.css b/web/styles/zulip.css index 431101a54e..7c5b65c950 100644 --- a/web/styles/zulip.css +++ b/web/styles/zulip.css @@ -1203,7 +1203,7 @@ td.pointer { text-align: center; color: var(--color-message-action-visible); - > i { + & i { vertical-align: middle; /* TODO: Math for these values, possibly with variables. In short, the icon body is 16px square; the current diff --git a/web/templates/compose_control_buttons.hbs b/web/templates/compose_control_buttons.hbs index ba6e6b2d61..b02b8980b9 100644 --- a/web/templates/compose_control_buttons.hbs +++ b/web/templates/compose_control_buttons.hbs @@ -6,8 +6,9 @@ - - +
+ +
diff --git a/web/templates/emoji_popover.hbs b/web/templates/emoji_popover.hbs index 1421bcc07d..96e277d845 100644 --- a/web/templates/emoji_popover.hbs +++ b/web/templates/emoji_popover.hbs @@ -1,7 +1,4 @@ -
-
-
-
-
+
+
diff --git a/web/templates/message_controls.hbs b/web/templates/message_controls.hbs index 42c45a8be8..561fd11cd1 100644 --- a/web/templates/message_controls.hbs +++ b/web/templates/message_controls.hbs @@ -5,7 +5,9 @@ {{#unless msg/sent_by_me}}
- +
+ +
{{/unless}} diff --git a/web/templates/message_reactions.hbs b/web/templates/message_reactions.hbs index 6f63d3d9e4..63cd7ad4f3 100644 --- a/web/templates/message_reactions.hbs +++ b/web/templates/message_reactions.hbs @@ -2,6 +2,8 @@ {{> message_reaction}} {{/each}}
- -
+
+
+ +
+
+
diff --git a/web/tests/hotkey.test.js b/web/tests/hotkey.test.js index cb5cae0d02..b6bb76363e 100644 --- a/web/tests/hotkey.test.js +++ b/web/tests/hotkey.test.js @@ -101,7 +101,7 @@ message_lists.current = { }, selected_row() { const $row = $.create("selected-row-stub"); - $row.set_find_results(".actions_hover", []); + $row.set_find_results(".message-actions-menu-button", []); return $row; }, selected_message() {