giphy: Convert module to TypeScript.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2024-11-13 00:39:27 -08:00 committed by Tim Abbott
parent c4ad9d8e09
commit e82b7641c6
8 changed files with 69 additions and 42 deletions

View File

@ -98,6 +98,7 @@
"@babel/eslint-parser": "^7.11.3", "@babel/eslint-parser": "^7.11.3",
"@babel/plugin-transform-modules-commonjs": "^7.19.6", "@babel/plugin-transform-modules-commonjs": "^7.19.6",
"@formatjs/cli": "^6.0.0", "@formatjs/cli": "^6.0.0",
"@giphy/js-types": "^5.1.0",
"@types/autosize": "^4.0.1", "@types/autosize": "^4.0.1",
"@types/blueimp-md5": "^2.18.0", "@types/blueimp-md5": "^2.18.0",
"@types/co-body": "^6.1.3", "@types/co-body": "^6.1.3",
@ -116,6 +117,7 @@
"@types/sortablejs": "^1.15.1", "@types/sortablejs": "^1.15.1",
"@types/spectrum": "^1.8.4", "@types/spectrum": "^1.8.4",
"@types/textarea-caret": "^3.0.3", "@types/textarea-caret": "^3.0.3",
"@types/throttle-debounce": "^5.0.2",
"@types/tinycolor2": "^1.4.5", "@types/tinycolor2": "^1.4.5",
"@types/turndown": "^5.0.1", "@types/turndown": "^5.0.1",
"@typescript-eslint/eslint-plugin": "^8.2.0", "@typescript-eslint/eslint-plugin": "^8.2.0",
@ -141,6 +143,7 @@
"mockdate": "^3.0.2", "mockdate": "^3.0.2",
"nyc": "^17.0.0", "nyc": "^17.0.0",
"openapi-examples-validator": "^5.0.0", "openapi-examples-validator": "^5.0.0",
"preact": "^10.24.3",
"prettier": "^3.0.0", "prettier": "^3.0.0",
"puppeteer": "^23.5.0", "puppeteer": "^23.5.0",
"source-map": "npm:source-map-js@^1.2.1", "source-map": "npm:source-map-js@^1.2.1",

View File

@ -305,6 +305,9 @@ importers:
'@formatjs/cli': '@formatjs/cli':
specifier: ^6.0.0 specifier: ^6.0.0
version: 6.3.8 version: 6.3.8
'@giphy/js-types':
specifier: ^5.1.0
version: 5.1.0
'@types/autosize': '@types/autosize':
specifier: ^4.0.1 specifier: ^4.0.1
version: 4.0.3 version: 4.0.3
@ -359,6 +362,9 @@ importers:
'@types/textarea-caret': '@types/textarea-caret':
specifier: ^3.0.3 specifier: ^3.0.3
version: 3.0.3 version: 3.0.3
'@types/throttle-debounce':
specifier: ^5.0.2
version: 5.0.2
'@types/tinycolor2': '@types/tinycolor2':
specifier: ^1.4.5 specifier: ^1.4.5
version: 1.4.6 version: 1.4.6
@ -434,6 +440,9 @@ importers:
openapi-examples-validator: openapi-examples-validator:
specifier: ^5.0.0 specifier: ^5.0.0
version: 5.0.0 version: 5.0.0
preact:
specifier: ^10.24.3
version: 10.24.3
prettier: prettier:
specifier: ^3.0.0 specifier: ^3.0.0
version: 3.3.3 version: 3.3.3
@ -2481,6 +2490,9 @@ packages:
'@types/textarea-caret@3.0.3': '@types/textarea-caret@3.0.3':
resolution: {integrity: sha512-bsA9GdXV1wQsXyDjS5+A+czz8IAR3haH5DU+KctIoXbzobRL2NOiwF/+EbB7pofAyudMytLj4ihPtbmbJT8FWw==} resolution: {integrity: sha512-bsA9GdXV1wQsXyDjS5+A+czz8IAR3haH5DU+KctIoXbzobRL2NOiwF/+EbB7pofAyudMytLj4ihPtbmbJT8FWw==}
'@types/throttle-debounce@5.0.2':
resolution: {integrity: sha512-pDzSNulqooSKvSNcksnV72nk8p7gRqN8As71Sp28nov1IgmPKWbOEIwAWvBME5pPTtaXJAvG3O4oc76HlQ4kqQ==}
'@types/tinycolor2@1.4.6': '@types/tinycolor2@1.4.6':
resolution: {integrity: sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==} resolution: {integrity: sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==}
@ -11322,6 +11334,8 @@ snapshots:
'@types/textarea-caret@3.0.3': {} '@types/textarea-caret@3.0.3': {}
'@types/throttle-debounce@5.0.2': {}
'@types/tinycolor2@1.4.6': {} '@types/tinycolor2@1.4.6': {}
'@types/triple-beam@1.3.5': {} '@types/triple-beam@1.3.5': {}

View File

@ -109,7 +109,7 @@ EXEMPT_FILES = make_set(
"web/src/fetch_status.ts", "web/src/fetch_status.ts",
"web/src/flatpickr.ts", "web/src/flatpickr.ts",
"web/src/gear_menu.js", "web/src/gear_menu.js",
"web/src/giphy.js", "web/src/giphy.ts",
"web/src/giphy_state.ts", "web/src/giphy_state.ts",
"web/src/global.ts", "web/src/global.ts",
"web/src/group_setting_pill.ts", "web/src/group_setting_pill.ts",

View File

@ -49,4 +49,4 @@ API_FEATURE_LEVEL = 319 # Last bumped for message-link class
# historical commits sharing the same major version, in which case a # historical commits sharing the same major version, in which case a
# minor version bump suffices. # minor version bump suffices.
PROVISION_VERSION = (301, 2) # bumped 2024-11-12 to patch handlebars PROVISION_VERSION = (301, 3) # bumped 2024-11-13 for @giphy/js-types

View File

@ -1,5 +1,9 @@
import type {GifsResult, GiphyFetch, Rating} from "@giphy/js-fetch-api";
import type {IGif} from "@giphy/js-types";
import $ from "jquery"; import $ from "jquery";
import _ from "lodash"; import _ from "lodash";
import assert from "minimalistic-assert";
import type * as tippy from "tippy.js";
import render_giphy_picker from "../templates/giphy_picker.hbs"; import render_giphy_picker from "../templates/giphy_picker.hbs";
@ -9,24 +13,26 @@ import * as popover_menus from "./popover_menus.ts";
import * as rows from "./rows.ts"; import * as rows from "./rows.ts";
import {realm} from "./state_data.ts"; import {realm} from "./state_data.ts";
import * as ui_util from "./ui_util.ts"; import * as ui_util from "./ui_util.ts";
import {the} from "./util.ts";
let giphy_fetch; let giphy_fetch: GiphyFetch | undefined;
let search_term = ""; let search_term = "";
let gifs_grid; let gifs_grid: {remove: () => void} | undefined;
let giphy_popover_instance = null; let giphy_popover_instance: tippy.Instance | undefined;
// Only used if popover called from edit message, otherwise it is `undefined`. // Only used if popover called from edit message, otherwise it is `undefined`.
let edit_message_id; let edit_message_id: number | undefined;
export function is_popped_from_edit_message() { export function is_popped_from_edit_message(): boolean {
return giphy_popover_instance && edit_message_id !== undefined; return giphy_popover_instance !== undefined && edit_message_id !== undefined;
} }
export function focus_current_edit_message() { export function focus_current_edit_message(): void {
$(`#edit_form_${CSS.escape(edit_message_id)} .message_edit_content`).trigger("focus"); assert(edit_message_id !== undefined);
$(`#edit_form_${CSS.escape(`${edit_message_id}`)} .message_edit_content`).trigger("focus");
} }
export function update_giphy_rating() { export function update_giphy_rating(): void {
if ( if (
realm.realm_giphy_rating === realm.giphy_rating_options.disabled.id || realm.realm_giphy_rating === realm.giphy_rating_options.disabled.id ||
realm.giphy_api_key === "" realm.giphy_api_key === ""
@ -37,10 +43,10 @@ export function update_giphy_rating() {
} }
} }
function get_rating() { function get_rating(): Rating {
const options = realm.giphy_rating_options; const options = realm.giphy_rating_options;
for (const rating in realm.giphy_rating_options) { for (const rating of ["pg", "g", "y", "pg-13", "r"] as const) {
if (options[rating].id === realm.realm_giphy_rating) { if (options[rating]?.id === realm.realm_giphy_rating) {
return rating; return rating;
} }
} }
@ -51,7 +57,7 @@ function get_rating() {
return "g"; return "g";
} }
async function renderGIPHYGrid(targetEl) { async function renderGIPHYGrid(targetEl: HTMLElement): Promise<{remove: () => void}> {
const {renderGrid} = await import(/* webpackChunkName: "giphy-sdk" */ "@giphy/js-components"); const {renderGrid} = await import(/* webpackChunkName: "giphy-sdk" */ "@giphy/js-components");
const {GiphyFetch} = await import(/* webpackChunkName: "giphy-sdk" */ "@giphy/js-fetch-api"); const {GiphyFetch} = await import(/* webpackChunkName: "giphy-sdk" */ "@giphy/js-fetch-api");
@ -59,7 +65,8 @@ async function renderGIPHYGrid(targetEl) {
giphy_fetch = new GiphyFetch(realm.giphy_api_key); giphy_fetch = new GiphyFetch(realm.giphy_api_key);
} }
function fetchGifs(offset) { async function fetchGifs(offset: number): Promise<GifsResult> {
assert(giphy_fetch !== undefined);
const config = { const config = {
offset, offset,
limit: 25, limit: 25,
@ -73,7 +80,7 @@ async function renderGIPHYGrid(targetEl) {
return giphy_fetch.search(search_term, config); return giphy_fetch.search(search_term, config);
} }
const render = () => const render = (): (() => void) =>
// See https://github.com/Giphy/giphy-js/blob/master/packages/components/README.md#grid // See https://github.com/Giphy/giphy-js/blob/master/packages/components/README.md#grid
// for detailed documentation. // for detailed documentation.
renderGrid( renderGrid(
@ -86,11 +93,11 @@ async function renderGIPHYGrid(targetEl) {
// Hide the creator attribution that appears over a // Hide the creator attribution that appears over a
// GIF; nice in principle but too distracting. // GIF; nice in principle but too distracting.
hideAttribution: true, hideAttribution: true,
onGifClick(props) { onGifClick(props: IGif) {
let $textarea = $("textarea#compose-textarea"); let $textarea = $<HTMLTextAreaElement>("textarea#compose-textarea");
if (edit_message_id !== undefined) { if (edit_message_id !== undefined) {
$textarea = $( $textarea = $(
`#edit_form_${CSS.escape(edit_message_id)} .message_edit_content`, `#edit_form_${CSS.escape(`${edit_message_id}`)} .message_edit_content`,
); );
} }
@ -120,18 +127,18 @@ async function renderGIPHYGrid(targetEl) {
}; };
} }
async function update_grid_with_search_term() { async function update_grid_with_search_term(): Promise<void> {
if (!gifs_grid) { if (!gifs_grid) {
return; return;
} }
const $search_elem = $("#giphy-search-query"); const $search_elem = $<HTMLInputElement>("input#giphy-search-query");
// GIPHY popover may have been hidden by the // GIPHY popover may have been hidden by the
// time this function is called. // time this function is called.
if ($search_elem.length) { if ($search_elem.length) {
search_term = $search_elem[0].value; search_term = the($search_elem).value;
gifs_grid.remove(); gifs_grid.remove();
gifs_grid = await renderGIPHYGrid($("#giphy_grid_in_popover .giphy-content")[0]); gifs_grid = await renderGIPHYGrid(the($("#giphy_grid_in_popover .giphy-content")));
return; return;
} }
@ -139,7 +146,7 @@ async function update_grid_with_search_term() {
gifs_grid = undefined; gifs_grid = undefined;
} }
export function hide_giphy_popover() { export function hide_giphy_popover(): boolean {
// Returns `true` if the popover was open. // Returns `true` if the popover was open.
if (giphy_popover_instance) { if (giphy_popover_instance) {
giphy_popover_instance.destroy(); giphy_popover_instance.destroy();
@ -151,7 +158,7 @@ export function hide_giphy_popover() {
return false; return false;
} }
function toggle_giphy_popover(target) { function toggle_giphy_popover(target: HTMLElement): void {
popover_menus.toggle_popover_menu( popover_menus.toggle_popover_menu(
target, target,
{ {
@ -161,10 +168,9 @@ function toggle_giphy_popover(target) {
instance.setContent(ui_util.parse_html(render_giphy_picker())); instance.setContent(ui_util.parse_html(render_giphy_picker()));
$(instance.popper).addClass("giphy-popover"); $(instance.popper).addClass("giphy-popover");
}, },
async onShow(instance) { onShow(instance) {
giphy_popover_instance = instance; giphy_popover_instance = instance;
const $popper = $(giphy_popover_instance.popper).trigger("focus"); const $popper = $(giphy_popover_instance.popper).trigger("focus");
gifs_grid = await renderGIPHYGrid($popper.find(".giphy-content")[0]);
const $click_target = $(instance.reference); const $click_target = $(instance.reference);
if ($click_target.parents(".message_edit_form").length === 1) { if ($click_target.parents(".message_edit_form").length === 1) {
@ -186,22 +192,26 @@ function toggle_giphy_popover(target) {
// every search. This makes the UX of searching pleasant // every search. This makes the UX of searching pleasant
// by allowing user to finish typing before search // by allowing user to finish typing before search
// is executed. // is executed.
_.debounce(update_grid_with_search_term, 300), _.debounce(() => void update_grid_with_search_term(), 300),
); );
$popper.on("keydown", ".giphy-gif", ui_util.convert_enter_to_click); $popper.on("keydown", ".giphy-gif", ui_util.convert_enter_to_click);
$popper.on("keydown", ".compose_gif_icon", ui_util.convert_enter_to_click); $popper.on("keydown", ".compose_gif_icon", ui_util.convert_enter_to_click);
$popper.on("click", "#giphy_search_clear", async (e) => { $popper.on("click", "#giphy_search_clear", (e) => {
e.stopPropagation(); e.stopPropagation();
$("#giphy-search-query").val(""); $("#giphy-search-query").val("");
await update_grid_with_search_term(); void update_grid_with_search_term();
}); });
void (async () => {
gifs_grid = await renderGIPHYGrid(the($popper.find(".giphy-content")));
// Focus on search box by default. // Focus on search box by default.
// This is specially helpful for users // This is specially helpful for users
// navigating via keyboard. // navigating via keyboard.
$("#giphy-search-query").trigger("focus"); $("#giphy-search-query").trigger("focus");
})();
}, },
onHidden() { onHidden() {
hide_giphy_popover(); hide_giphy_popover();
@ -214,12 +224,12 @@ function toggle_giphy_popover(target) {
); );
} }
function register_click_handlers() { function register_click_handlers(): void {
$("body").on("click", ".compose_control_button.compose_gif_icon", (e) => { $("body").on("click", ".compose_control_button.compose_gif_icon", function (this: HTMLElement) {
toggle_giphy_popover(e.currentTarget); toggle_giphy_popover(this);
}); });
} }
export function initialize() { export function initialize(): void {
register_click_handlers(); register_click_handlers();
} }

View File

@ -21,7 +21,7 @@ import * as emoji from "./emoji.ts";
import * as emoji_picker from "./emoji_picker.ts"; import * as emoji_picker from "./emoji_picker.ts";
import * as feedback_widget from "./feedback_widget.ts"; import * as feedback_widget from "./feedback_widget.ts";
import * as gear_menu from "./gear_menu.js"; import * as gear_menu from "./gear_menu.js";
import * as giphy from "./giphy.js"; import * as giphy from "./giphy.ts";
import * as hash_util from "./hash_util.ts"; import * as hash_util from "./hash_util.ts";
import * as hashchange from "./hashchange.js"; import * as hashchange from "./hashchange.js";
import * as inbox_ui from "./inbox_ui.ts"; import * as inbox_ui from "./inbox_ui.ts";

View File

@ -20,7 +20,7 @@ import {electron_bridge} from "./electron_bridge.ts";
import * as emoji from "./emoji.ts"; import * as emoji from "./emoji.ts";
import * as emoji_picker from "./emoji_picker.ts"; import * as emoji_picker from "./emoji_picker.ts";
import * as gear_menu from "./gear_menu.js"; import * as gear_menu from "./gear_menu.js";
import * as giphy from "./giphy.js"; import * as giphy from "./giphy.ts";
import * as information_density from "./information_density.ts"; import * as information_density from "./information_density.ts";
import * as left_sidebar_navigation_area from "./left_sidebar_navigation_area.ts"; import * as left_sidebar_navigation_area from "./left_sidebar_navigation_area.ts";
import * as linkifiers from "./linkifiers.ts"; import * as linkifiers from "./linkifiers.ts";

View File

@ -44,7 +44,7 @@ import * as emoji from "./emoji.ts";
import * as emoji_picker from "./emoji_picker.ts"; import * as emoji_picker from "./emoji_picker.ts";
import * as emojisets from "./emojisets.ts"; import * as emojisets from "./emojisets.ts";
import * as gear_menu from "./gear_menu.js"; import * as gear_menu from "./gear_menu.js";
import * as giphy from "./giphy.js"; import * as giphy from "./giphy.ts";
import * as giphy_state from "./giphy_state.ts"; import * as giphy_state from "./giphy_state.ts";
import * as hashchange from "./hashchange.js"; import * as hashchange from "./hashchange.js";
import * as hotkey from "./hotkey.js"; import * as hotkey from "./hotkey.js";