import {renderGrid} from "@giphy/js-components"; import {GiphyFetch} from "@giphy/js-fetch-api"; import $ from "jquery"; import _ from "lodash"; import render_giphy_picker from "../templates/giphy_picker.hbs"; import render_giphy_picker_mobile from "../templates/giphy_picker_mobile.hbs"; import * as compose_ui from "./compose_ui"; import {media_breakpoints} from "./css_variables"; import {page_params} from "./page_params"; import * as popovers from "./popovers"; import * as ui_util from "./ui_util"; const giphy_fetch = new GiphyFetch(page_params.giphy_api_key); let search_term = ""; let active_popover_element; // Only used if popover called from edit message, otherwise it is `undefined`. let edit_message_id; export function is_popped_from_edit_messsage() { return active_popover_element && edit_message_id !== undefined; } export function focus_current_edit_message() { $(`#message_edit_content_${CSS.escape(edit_message_id)}`).trigger("focus"); } // Approximate width and heigh of // giphy popover as computed by chrome // + 25px; const APPROX_HEIGHT = 350; const APPROX_WIDTH = 300; function fetchGifs(offset) { const config = { offset, limit: 25, // Default rating to 'g' until we can make this configurable. rating: "g", // We don't pass random_id here, for privacy reasons. }; if (search_term === "") { // Get the trending gifs by default. return giphy_fetch.trending(config); } return giphy_fetch.search(search_term, config); } function renderGIPHYGrid(targetEl) { const render = () => // See https://github.com/Giphy/giphy-js/blob/master/packages/components/README.md#grid // for detailed documentation. renderGrid( { width: 300, fetchGifs, columns: 3, gutter: 6, noLink: true, // Hide the creator attribution that appears over a // GIF; nice in principle but too distracting. hideAttribution: true, onGifClick: (props) => { let textarea = $("#compose-textarea"); if (edit_message_id !== undefined) { textarea = $(`#message_edit_content_${CSS.escape(edit_message_id)}`); } compose_ui.insert_syntax_and_focus( `[](${props.images.downsized_medium.url})`, textarea, ); hide_giphy_popover(); }, onGifVisible: (gif, e) => { // Set tabindex for all the GIFs that // are visible to the user. This allows // user to navigate the GIFs using tab. // TODO: Remove this after https://github.com/Giphy/giphy-js/issues/174 // is closed. e.target.tabIndex = 0; }, }, targetEl, ); // Limit the rate at which we do queries to the GIPHY API to // one per 300ms, in line with animation timing, basically to avoid // content appearing while the user is typing. const resizeRender = _.throttle(render, 300); window.addEventListener("resize", resizeRender, false); const remove = render(); return { remove: () => { remove(); window.removeEventListener("resize", resizeRender, false); }, }; } function update_grid_with_search_term() { const search_elem = $("#giphy-search-query"); // GIPHY popover may have been hidden by the // time this function is called. if (search_elem.length) { search_term = search_elem[0].value; return renderGIPHYGrid($("#giphy_grid_in_popover .popover-content")[0]); } // Return undefined to stop searching. return undefined; } export function hide_giphy_popover() { // Returns `true` if the popover was open. if (active_popover_element) { active_popover_element.popover("hide"); active_popover_element = undefined; edit_message_id = undefined; return true; } return false; } function get_popover_content() { if (window.innerWidth <= Number(media_breakpoints.md_min.slice(0, -2))) { // Show as modal in the center for small screens. return render_giphy_picker_mobile(); } return render_giphy_picker(); } function get_popover_placement() { let placement = popovers.compute_placement( active_popover_element, APPROX_HEIGHT, APPROX_WIDTH, true, ); if (placement === "viewport_center") { // For legacy reasons `compute_placement` actually can // return `viewport_center` which used to place popover in // the center of the screen, but bootstrap doesn't actually // support that and we already handle it on small screen sizes // by placing it in center using `popover-flex`. placement = "left"; } return placement; } export function initialize() { $("body").on("keydown", ".giphy-gif", ui_util.convert_enter_to_click); $("body").on("keydown", ".compose_giphy_logo", ui_util.convert_enter_to_click); $("body").on("click", ".compose_giphy_logo", (e) => { e.preventDefault(); e.stopPropagation(); if (active_popover_element && $.contains(active_popover_element.get()[0], e.target)) { // Hide giphy popover if already active. hide_giphy_popover(); return; } popovers.hide_all(); const $elt = $(e.target); // Store data-message-id value in global variable edit_message_id so that // its value can be further used to correctly find the message textarea element. // This will store `undefined` when called from compose box, by design. edit_message_id = $elt.attr("data-message-id"); active_popover_element = $elt.closest("#compose_box_giphy_grid"); active_popover_element.popover({ animation: true, placement: get_popover_placement(), html: true, trigger: "manual", template: get_popover_content(), }); active_popover_element.popover("show"); let gifs_grid = renderGIPHYGrid($("#giphy_grid_in_popover .popover-content")[0]); $("body").on( "keyup", "#giphy-search-query", // Use debounce to create a 300ms interval between // every search. This makes the UX of searching pleasant // by allowing user to finish typing before search // is executed. _.debounce(() => { // GIPHY popover may have been hidden by the // time this function is called. if (gifs_grid) { gifs_grid.remove(); gifs_grid = update_grid_with_search_term(); } }, 300), ); $(document).one("compose_canceled.zulip compose_finished.zulip", () => { hide_giphy_popover(); }); // Focus on search box by default. // This is specially helpful for users // navigating via keybaord. $("#giphy-search-query").trigger("focus"); }); }