Files
zulip/static/js/giphy.js
Aman Agrawal c285e3f25e giphy: Add just space as a content to fool popover library.
Since we don't have content for GIPHY popover at the time
popover is rendered, we need to render with some fake
content otherwise popover library doesn't allows for us
to show the popover.

This came into notice after 5adc6d7297
broke show popover behaviour for GIPHY but didn't break it for
emoji popover as emoji popover already renders with some content.
Prior to this commit, popover library used `title` as content for
GIPHY popover.
2021-05-03 08:33:24 -07:00

276 lines
9.3 KiB
JavaScript

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 blueslip from "./blueslip";
import * as compose_ui from "./compose_ui";
import {media_breakpoints_num} from "./css_variables";
import {page_params} from "./page_params";
import * as popovers from "./popovers";
import * as rows from "./rows";
import * as ui_util from "./ui_util";
const giphy_fetch = new GiphyFetch(page_params.giphy_api_key);
let search_term = "";
let gifs_grid;
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() {
$(`#edit_form_${CSS.escape(edit_message_id)} .message_edit_content`).trigger("focus");
}
export function is_giphy_enabled() {
return (
page_params.giphy_api_key !== "" &&
page_params.realm_giphy_rating !== page_params.giphy_rating_options.disabled.id
);
}
// Approximate width and height of
// giphy popover as computed by chrome
// + 25px;
const APPROX_HEIGHT = 350;
const APPROX_WIDTH = 300;
export function update_giphy_rating() {
if (page_params.realm_giphy_rating === page_params.giphy_rating_options.disabled.id) {
$(".compose_giphy_link").hide();
} else {
$(".compose_giphy_link").show();
}
}
function get_rating() {
const options = page_params.giphy_rating_options;
for (const rating in page_params.giphy_rating_options) {
if (options[rating].id === page_params.realm_giphy_rating) {
return rating;
}
}
// The below should never run unless a server bug allowed a
// `giphy_rating` value not present in `giphy_rating_options`.
blueslip.error("Invalid giphy_rating value: " + page_params.realm_giphy_rating);
return "g";
}
function fetchGifs(offset) {
const config = {
offset,
limit: 25,
rating: get_rating(),
// 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 = $(
`#edit_form_${CSS.escape(edit_message_id)} .message_edit_content`,
);
}
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() {
if (!gifs_grid) {
return;
}
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;
gifs_grid.remove();
gifs_grid = renderGIPHYGrid($("#giphy_grid_in_popover .giphy-content")[0]);
return;
}
// Set to undefined to stop searching.
gifs_grid = undefined;
}
export function hide_giphy_popover() {
// Returns `true` if the popover was open.
if (active_popover_element) {
// We need to destroy the popover because when
// we hide it, bootstrap popover
// library removes `giphy-content` element
// as part of cleaning up everything inside
// `popover-content`, so we need to reinitialize
// the popover by destroying it.
active_popover_element.popover("destroy");
active_popover_element = undefined;
edit_message_id = undefined;
gifs_grid = undefined;
return true;
}
return false;
}
function get_popover_content() {
if (window.innerWidth <= media_breakpoints_num.md) {
// 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_gif_icon", ui_util.convert_enter_to_click);
$("body").on("click", "#giphy_search_clear", (e) => {
e.stopPropagation();
$("#giphy-search-query").val("");
update_grid_with_search_term();
});
$("body").on("click", ".compose_gif_icon", (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);
if ($elt.parents(".message_edit_form").length === 1) {
// Store message id in global variable edit_message_id so that
// its value can be further used to correctly find the message textarea element.
edit_message_id = rows.id($elt.parents(".message_row"));
} else {
edit_message_id = undefined;
}
active_popover_element = $elt.closest(".compose_giphy_link");
active_popover_element.popover({
animation: true,
placement: get_popover_placement(),
html: true,
trigger: "manual",
template: get_popover_content(),
/* Popovers without a content property are not displayed,
* so we need something here; but we haven't contacted the
* Giphy API yet to get the actual content to display. */
content: " ",
});
active_popover_element.popover("show");
// It takes about 1s for the popover to show; So,
// we wait for popover to display before rendering GIFs
// in it, otherwise popover is rendered with empty content.
const popover_observer = new MutationObserver(() => {
if ($("#giphy_grid_in_popover .giphy-content").is(":visible")) {
gifs_grid = renderGIPHYGrid($("#giphy_grid_in_popover .giphy-content")[0]);
popover_observer.disconnect();
}
});
const opts = {attributes: false, childList: true, characterData: false, subtree: true};
popover_observer.observe(document, opts);
$("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(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 keyboard.
$("#giphy-search-query").trigger("focus");
});
}