message_list_tooltips: Convert module to TypeScript.

This commit is contained in:
Varun Singh 2024-03-23 01:00:50 +05:30 committed by Tim Abbott
parent b0377d5d6d
commit fd3c7728fc
2 changed files with 43 additions and 27 deletions

View File

@ -138,7 +138,7 @@ EXEMPT_FILES = make_set(
"web/src/message_list.js", "web/src/message_list.js",
"web/src/message_list_data.ts", "web/src/message_list_data.ts",
"web/src/message_list_hover.js", "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_list_view.js",
"web/src/message_lists.ts", "web/src/message_lists.ts",
"web/src/message_live_update.ts", "web/src/message_live_update.ts",

View File

@ -1,6 +1,7 @@
import $ from "jquery"; import $ from "jquery";
import assert from "minimalistic-assert"; import assert from "minimalistic-assert";
import {delegate} from "tippy.js"; 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_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"; 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 {INTERACTIVE_HOVER_DELAY, LONG_HOVER_DELAY} from "./tippyjs";
import {parse_html} from "./ui_util"; 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. // 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<tippy.Instance>();
// This keeps track of all the instances created and destroyed. // This keeps track of all the instances created and destroyed.
const store_message_list_instances_plugin = { const store_message_list_instances_plugin = {
fn() { fn() {
return { return {
onCreate(instance) { onCreate(instance: tippy.Instance) {
message_list_tippy_instances.add(instance); message_list_tippy_instances.add(instance);
}, },
onDestroy(instance) { onDestroy(instance: tippy.Instance) {
// To make sure the `message_list_tippy_instances` contains only instances // 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 // that are present in the DOM, we need to delete instances that are destroyed
message_list_tippy_instances.delete(instance); 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<tippy.Props> = {}): void {
const {onShow, ...other_props} = props; const {onShow, ...other_props} = props;
delegate("body", { delegate("body", {
target, 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. // Defining observer outside ensures that at max only one observer is active at all times.
let observer; let observer;
function hide_tooltip_if_reference_removed( function hide_tooltip_if_reference_removed(
target_node, target_node: HTMLElement,
config, config: Config,
instance, instance: tippy.Instance,
nodes_to_check_for_removal, nodes_to_check_for_removal: tippy.ReferenceElement[],
) { ): void {
// Use MutationObserver to check for removal of nodes on which tooltips // Use MutationObserver to check for removal of nodes on which tooltips
// are still active. // are still active.
if (!target_node) { if (!target_node) {
@ -75,10 +82,12 @@ function hide_tooltip_if_reference_removed(
// In that case, we simply hide the tooltip. // In that case, we simply hide the tooltip.
// We have to be smart about hiding the instance, so we hide it as soon // We have to be smart about hiding the instance, so we hide it as soon
// as it is displayed. // as it is displayed.
setTimeout(instance.hide, 0); setTimeout(() => {
instance.hide();
}, 0);
return; return;
} }
const callback = function (mutationsList) { const callback = function (mutationsList: MutationRecord[]): void {
for (const mutation of mutationsList) { for (const mutation of mutationsList) {
for (const node of nodes_to_check_for_removal) { for (const node of nodes_to_check_for_removal) {
// Hide instance if reference's class changes. // 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 // 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, // 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. // 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) { for (const instance of message_list_tippy_instances) {
if (instance.reference === document.body) { if (instance.reference === document.body) {
continue; continue;
@ -109,7 +118,7 @@ export function destroy_all_message_list_tooltips() {
message_list_tippy_instances.clear(); message_list_tippy_instances.clear();
} }
export function initialize() { export function initialize(): void {
message_list_tooltip(".tippy-narrow-tooltip", { message_list_tooltip(".tippy-narrow-tooltip", {
delay: LONG_HOVER_DELAY, delay: LONG_HOVER_DELAY,
onCreate(instance) { onCreate(instance) {
@ -120,7 +129,7 @@ export function initialize() {
}); });
// message reaction tooltip showing who reacted. // message reaction tooltip showing who reacted.
let observer; let observer: MutationObserver;
message_list_tooltip(".message_reaction", { message_list_tooltip(".message_reaction", {
delay: INTERACTIVE_HOVER_DELAY, delay: INTERACTIVE_HOVER_DELAY,
placement: "bottom", placement: "bottom",
@ -132,16 +141,19 @@ export function initialize() {
} }
const $elem = $(instance.reference); const $elem = $(instance.reference);
const local_id = $elem.attr("data-reaction-id"); 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 message_id = rows.get_message_id(instance.reference);
const title = reactions.get_reaction_title_data(message_id, local_id); const title = reactions.get_reaction_title_data(message_id, local_id);
instance.setContent(title); instance.setContent(title);
const config = {attributes: false, childList: true, subtree: true}; const config = {attributes: false, childList: true, subtree: true};
const target = $elem.parents(".message-list.focused-message-list").get(0); const target = $elem.parents(".message-list.focused-message-list").get(0);
assert(target !== undefined);
const nodes_to_check_for_removal = [ const nodes_to_check_for_removal = [
$elem.parents(".recipient_row").get(0), $elem.parents(".recipient_row").get(0)!,
$elem.parents(".message_reactions").get(0), $elem.parents(".message_reactions").get(0)!,
$elem.get(0), $elem.get(0)!,
]; ];
hide_tooltip_if_reference_removed(target, config, instance, nodes_to_check_for_removal); hide_tooltip_if_reference_removed(target, config, instance, nodes_to_check_for_removal);
return undefined; return undefined;
@ -174,10 +186,11 @@ export function initialize() {
const config = {attributes: false, childList: true, subtree: true}; const config = {attributes: false, childList: true, subtree: true};
const target = $elem.parents(".message-list.focused-message-list").get(0); const target = $elem.parents(".message-list.focused-message-list").get(0);
assert(target !== undefined);
const nodes_to_check_for_removal = [ const nodes_to_check_for_removal = [
$elem.parents(".recipient_row").get(0), $elem.parents(".recipient_row").get(0)!,
$elem.parents(".message_reactions").get(0), $elem.parents(".message_reactions").get(0)!,
$elem.get(0), $elem.get(0)!,
]; ];
hide_tooltip_if_reference_removed(target, config, instance, nodes_to_check_for_removal); hide_tooltip_if_reference_removed(target, config, instance, nodes_to_check_for_removal);
return undefined; return undefined;
@ -196,7 +209,7 @@ export function initialize() {
if (tippy_content !== undefined) { if (tippy_content !== undefined) {
instance.setContent(tippy_content); instance.setContent(tippy_content);
} else { } 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())); 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 // 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. // .slow-send-spinner is not removed (hidden) from DOM when message is sent.
const target = $elem.parents(".message_row").get(0); const target = $elem.parents(".message_row").get(0);
assert(target !== undefined);
const config = {attributes: true, childList: false, subtree: false}; 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); hide_tooltip_if_reference_removed(target, config, instance, nodes_to_check_for_removal);
}, },
onHidden(instance) { onHidden(instance) {
@ -230,9 +244,10 @@ export function initialize() {
message_list_tooltip(".message-list .message_time", { message_list_tooltip(".message-list .message_time", {
onShow(instance) { onShow(instance) {
const $time_elem = $(instance.reference); 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); 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. // Don't show time tooltip for locally echoed message.
if (message.locally_echoed) { if (message.locally_echoed) {
return false; return false;
@ -279,7 +294,8 @@ export function initialize() {
message_list_tooltip(".rendered_markdown time, .rendered_markdown .copy_codeblock", 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) { onHidden(instance) {
instance.destroy(); instance.destroy();
}, },
@ -294,7 +310,7 @@ export function initialize() {
// Some message_inline_images aren't actually images with a title, // Some message_inline_images aren't actually images with a title,
// for example youtube videos, so we default to the actual href // for example youtube videos, so we default to the actual href
const title = const title =
$(instance.reference).parent().attr("aria-label") || $(instance.reference).parent().attr("aria-label") ??
$(instance.reference).parent().attr("href"); $(instance.reference).parent().attr("href");
instance.setContent(parse_html(render_message_inline_image_tooltip({title}))); instance.setContent(parse_html(render_message_inline_image_tooltip({title})));
}, },