From fd3c7728fc807e96bc04d2886a8c2ffe9c61898b Mon Sep 17 00:00:00 2001 From: Varun Singh Date: Sat, 23 Mar 2024 01:00:50 +0530 Subject: [PATCH] message_list_tooltips: Convert module to TypeScript. --- tools/test-js-with-node | 2 +- ...t_tooltips.js => message_list_tooltips.ts} | 68 ++++++++++++------- 2 files changed, 43 insertions(+), 27 deletions(-) rename web/src/{message_list_tooltips.js => message_list_tooltips.ts} (87%) diff --git a/tools/test-js-with-node b/tools/test-js-with-node index 3960b08295..887c7b3b4b 100755 --- a/tools/test-js-with-node +++ b/tools/test-js-with-node @@ -138,7 +138,7 @@ EXEMPT_FILES = make_set( "web/src/message_list.js", "web/src/message_list_data.ts", "web/src/message_list_hover.js", - "web/src/message_list_tooltips.js", + "web/src/message_list_tooltips.ts", "web/src/message_list_view.js", "web/src/message_lists.ts", "web/src/message_live_update.ts", diff --git a/web/src/message_list_tooltips.js b/web/src/message_list_tooltips.ts similarity index 87% rename from web/src/message_list_tooltips.js rename to web/src/message_list_tooltips.ts index 0333cd363d..e5b0079efb 100644 --- a/web/src/message_list_tooltips.js +++ b/web/src/message_list_tooltips.ts @@ -1,6 +1,7 @@ import $ from "jquery"; import assert from "minimalistic-assert"; import {delegate} from "tippy.js"; +import type * as tippy from "tippy.js"; import render_change_visibility_policy_button_tooltip from "../templates/change_visibility_policy_button_tooltip.hbs"; import render_message_edit_notice_tooltip from "../templates/message_edit_notice_tooltip.hbs"; @@ -16,17 +17,23 @@ import * as timerender from "./timerender"; import {INTERACTIVE_HOVER_DELAY, LONG_HOVER_DELAY} from "./tippyjs"; import {parse_html} from "./ui_util"; +type Config = { + attributes: boolean; + childList: boolean; + subtree: boolean; +}; + // We need to store all message list instances together to destroy them in case of re-rendering. -const message_list_tippy_instances = new Set(); +const message_list_tippy_instances = new Set(); // This keeps track of all the instances created and destroyed. const store_message_list_instances_plugin = { fn() { return { - onCreate(instance) { + onCreate(instance: tippy.Instance) { message_list_tippy_instances.add(instance); }, - onDestroy(instance) { + onDestroy(instance: tippy.Instance) { // To make sure the `message_list_tippy_instances` contains only instances // that are present in the DOM, we need to delete instances that are destroyed message_list_tippy_instances.delete(instance); @@ -35,7 +42,7 @@ const store_message_list_instances_plugin = { }, }; -function message_list_tooltip(target, props = {}) { +function message_list_tooltip(target: string, props: Partial = {}): void { const {onShow, ...other_props} = props; delegate("body", { target, @@ -63,11 +70,11 @@ function message_list_tooltip(target, props = {}) { // Defining observer outside ensures that at max only one observer is active at all times. let observer; function hide_tooltip_if_reference_removed( - target_node, - config, - instance, - nodes_to_check_for_removal, -) { + target_node: HTMLElement, + config: Config, + instance: tippy.Instance, + nodes_to_check_for_removal: tippy.ReferenceElement[], +): void { // Use MutationObserver to check for removal of nodes on which tooltips // are still active. if (!target_node) { @@ -75,10 +82,12 @@ function hide_tooltip_if_reference_removed( // In that case, we simply hide the tooltip. // We have to be smart about hiding the instance, so we hide it as soon // as it is displayed. - setTimeout(instance.hide, 0); + setTimeout(() => { + instance.hide(); + }, 0); return; } - const callback = function (mutationsList) { + const callback = function (mutationsList: MutationRecord[]): void { for (const mutation of mutationsList) { for (const node of nodes_to_check_for_removal) { // Hide instance if reference's class changes. @@ -99,7 +108,7 @@ function hide_tooltip_if_reference_removed( // To prevent the appearance of tooltips whose reference is hidden or removed from the // DOM during re-rendering, we need to destroy all the message list present instances, // and then initialize triggers of the tooltips again after re-rendering. -export function destroy_all_message_list_tooltips() { +export function destroy_all_message_list_tooltips(): void { for (const instance of message_list_tippy_instances) { if (instance.reference === document.body) { continue; @@ -109,7 +118,7 @@ export function destroy_all_message_list_tooltips() { message_list_tippy_instances.clear(); } -export function initialize() { +export function initialize(): void { message_list_tooltip(".tippy-narrow-tooltip", { delay: LONG_HOVER_DELAY, onCreate(instance) { @@ -120,7 +129,7 @@ export function initialize() { }); // message reaction tooltip showing who reacted. - let observer; + let observer: MutationObserver; message_list_tooltip(".message_reaction", { delay: INTERACTIVE_HOVER_DELAY, placement: "bottom", @@ -132,16 +141,19 @@ export function initialize() { } const $elem = $(instance.reference); const local_id = $elem.attr("data-reaction-id"); + assert(local_id !== undefined); + assert(instance.reference instanceof HTMLElement); const message_id = rows.get_message_id(instance.reference); const title = reactions.get_reaction_title_data(message_id, local_id); instance.setContent(title); const config = {attributes: false, childList: true, subtree: true}; const target = $elem.parents(".message-list.focused-message-list").get(0); + assert(target !== undefined); const nodes_to_check_for_removal = [ - $elem.parents(".recipient_row").get(0), - $elem.parents(".message_reactions").get(0), - $elem.get(0), + $elem.parents(".recipient_row").get(0)!, + $elem.parents(".message_reactions").get(0)!, + $elem.get(0)!, ]; hide_tooltip_if_reference_removed(target, config, instance, nodes_to_check_for_removal); return undefined; @@ -174,10 +186,11 @@ export function initialize() { const config = {attributes: false, childList: true, subtree: true}; const target = $elem.parents(".message-list.focused-message-list").get(0); + assert(target !== undefined); const nodes_to_check_for_removal = [ - $elem.parents(".recipient_row").get(0), - $elem.parents(".message_reactions").get(0), - $elem.get(0), + $elem.parents(".recipient_row").get(0)!, + $elem.parents(".message_reactions").get(0)!, + $elem.get(0)!, ]; hide_tooltip_if_reference_removed(target, config, instance, nodes_to_check_for_removal); return undefined; @@ -196,7 +209,7 @@ export function initialize() { if (tippy_content !== undefined) { instance.setContent(tippy_content); } else { - const $template = $(`#${CSS.escape($elem.attr("data-tooltip-template-id"))}`); + const $template = $(`#${CSS.escape($elem.attr("data-tooltip-template-id")!)}`); instance.setContent(parse_html($template.html())); } }, @@ -218,8 +231,9 @@ export function initialize() { // We need to check for removal of local class from message_row since // .slow-send-spinner is not removed (hidden) from DOM when message is sent. const target = $elem.parents(".message_row").get(0); + assert(target !== undefined); const config = {attributes: true, childList: false, subtree: false}; - const nodes_to_check_for_removal = [$elem.get(0)]; + const nodes_to_check_for_removal = [$elem.get(0)!]; hide_tooltip_if_reference_removed(target, config, instance, nodes_to_check_for_removal); }, onHidden(instance) { @@ -230,9 +244,10 @@ export function initialize() { message_list_tooltip(".message-list .message_time", { onShow(instance) { const $time_elem = $(instance.reference); - const $row = $time_elem.closest(".message_row"); + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const $row = $time_elem.closest(".message_row") as JQuery; assert(message_lists.current !== undefined); - const message = message_lists.current.get(rows.id($row)); + const message = message_lists.current.get(rows.id($row))!; // Don't show time tooltip for locally echoed message. if (message.locally_echoed) { return false; @@ -279,7 +294,8 @@ export function initialize() { message_list_tooltip(".rendered_markdown time, .rendered_markdown .copy_codeblock", { - content: timerender.get_markdown_time_tooltip, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + content: timerender.get_markdown_time_tooltip as tippy.Content, onHidden(instance) { instance.destroy(); }, @@ -294,7 +310,7 @@ export function initialize() { // Some message_inline_images aren't actually images with a title, // for example youtube videos, so we default to the actual href const title = - $(instance.reference).parent().attr("aria-label") || + $(instance.reference).parent().attr("aria-label") ?? $(instance.reference).parent().attr("href"); instance.setContent(parse_html(render_message_inline_image_tooltip({title}))); },