mirror of https://github.com/zulip/zulip.git
199 lines
7.2 KiB
JavaScript
199 lines
7.2 KiB
JavaScript
import $ from "jquery";
|
|
import tippy, {delegate} from "tippy.js";
|
|
|
|
import * as message_lists from "./message_lists";
|
|
import * as reactions from "./reactions";
|
|
import * as rows from "./rows";
|
|
import * as timerender from "./timerender";
|
|
|
|
// We override the defaults set by tippy library here,
|
|
// so make sure to check this too after checking tippyjs
|
|
// documentation for default properties.
|
|
tippy.setDefaultProps({
|
|
// We don't want tooltips
|
|
// to take more space than
|
|
// mobile widths ever.
|
|
maxWidth: 300,
|
|
|
|
// Some delay to showing / hiding the tooltip makes
|
|
// it look less forced and more natural.
|
|
delay: [100, 20],
|
|
placement: "auto",
|
|
|
|
// disable animations to make the
|
|
// tooltips feel snappy
|
|
animation: false,
|
|
|
|
// Show tooltips on long press on touch based
|
|
// devices.
|
|
touch: ["hold", 750],
|
|
|
|
// This has the side effect of some properties of parent applying to
|
|
// tooltips.
|
|
appendTo: "parent",
|
|
|
|
// html content is not supported by default
|
|
// enable it by passing data-tippy-allowHtml="true"
|
|
// in the tag or a parameter.
|
|
});
|
|
|
|
export function initialize() {
|
|
// Our default tooltip configuration. For this, one simply needs to:
|
|
// * Set `class="tippy-zulip-tooltip"` on an element for enable this.
|
|
// * Set `data-tippy-content="{{t 'Tooltip content' }}"`, often
|
|
// replacing a `title` attribute on an element that had both.
|
|
// * Set placement; we typically use `data-tippy-placement="top"`.
|
|
delegate("body", {
|
|
target: ".tippy-zulip-tooltip",
|
|
});
|
|
|
|
// The below definitions are for specific tooltips that require
|
|
// custom JavaScript code or configuration. Note that since the
|
|
// below specify the target directly, elements using those should
|
|
// not have the tippy-zulip-tooltip class.
|
|
|
|
// message reaction tooltip showing who reacted.
|
|
let observer;
|
|
delegate("body", {
|
|
target: ".message_reaction, .message_reactions .reaction_button",
|
|
placement: "bottom",
|
|
onShow(instance) {
|
|
const elem = $(instance.reference);
|
|
if (!instance.reference.classList.contains("reaction_button")) {
|
|
const local_id = elem.attr("data-reaction-id");
|
|
const message_id = rows.get_message_id(instance.reference);
|
|
const title = reactions.get_reaction_title_data(message_id, local_id);
|
|
instance.setContent(title);
|
|
}
|
|
|
|
// Use MutationObserver to check for removal of nodes on which tooltips
|
|
// are still active.
|
|
// We target the message table and check for removal of it, it's children
|
|
// and the reactions individually down in the subtree.
|
|
const target_node = elem.parents(".message_table.focused_table").get(0);
|
|
if (!target_node) {
|
|
// The `reaction` was removed from DOM before we reached here.
|
|
// 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);
|
|
return;
|
|
}
|
|
|
|
const nodes_to_check_for_removal = [
|
|
elem.parents(".recipient_row").get(0),
|
|
elem.parents(".message_reactions").get(0),
|
|
elem.get(0),
|
|
];
|
|
const config = {attributes: false, childList: true, subtree: true};
|
|
|
|
const callback = function (mutationsList) {
|
|
for (const mutation of mutationsList) {
|
|
for (const node of nodes_to_check_for_removal) {
|
|
// Hide instance if reference is in the removed node list.
|
|
if (Array.prototype.includes.call(mutation.removedNodes, node)) {
|
|
instance.hide();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
observer = new MutationObserver(callback);
|
|
observer.observe(target_node, config);
|
|
},
|
|
onHidden(instance) {
|
|
instance.destroy();
|
|
if (observer) {
|
|
observer.disconnect();
|
|
}
|
|
},
|
|
appendTo: () => document.body,
|
|
});
|
|
|
|
delegate("body", {
|
|
target: ".compose_control_button",
|
|
placement: "top",
|
|
// Add some additional delay when they open
|
|
// so that regular users don't have to see
|
|
// them unless they want to.
|
|
delay: [300, 20],
|
|
});
|
|
|
|
delegate("body", {
|
|
target: ".message_control_button",
|
|
placement: "top",
|
|
// Add some additional delay when they open
|
|
// so that regular users don't have to see
|
|
// them unless they want to.
|
|
delay: [300, 20],
|
|
onShow(instance) {
|
|
// Handle dynamic "starred messages" and "edit" widgets.
|
|
const elem = $(instance.reference);
|
|
let content = elem.attr("data-tippy-content");
|
|
if (content === undefined) {
|
|
// Tippy cannot get the content for message edit button
|
|
// as it is dynamically inserted based on editability.
|
|
// So, we have to manually get the i element to get the
|
|
// content from it.
|
|
//
|
|
// TODO: Change the template structure so logic is unnecessary.
|
|
const edit_button = elem.find("i.edit_content_button");
|
|
content = edit_button.attr("data-tippy-content");
|
|
}
|
|
if (content === undefined) {
|
|
// If content is still undefined it is because content
|
|
// is specified on inner i tags and is handled by our
|
|
// general tippy-zulip-tooltip class. So we return
|
|
// false here to avoid showing an extra empty tooltip
|
|
// for such cases.
|
|
return false;
|
|
}
|
|
instance.setContent(content);
|
|
return true;
|
|
},
|
|
});
|
|
|
|
delegate("body", {
|
|
target: ".message_time",
|
|
placement: "top",
|
|
appendTo: () => document.body,
|
|
onShow(instance) {
|
|
const time_elem = $(instance.reference);
|
|
const row = time_elem.closest(".message_row");
|
|
const message = message_lists.current.get(rows.id(row));
|
|
const time = new Date(message.timestamp * 1000);
|
|
instance.setContent(timerender.get_full_datetime(time));
|
|
},
|
|
onHidden(instance) {
|
|
instance.destroy();
|
|
},
|
|
});
|
|
|
|
delegate("body", {
|
|
target: ".recipient_row_date > span",
|
|
placement: "top",
|
|
appendTo: () => document.body,
|
|
onHidden(instance) {
|
|
instance.destroy();
|
|
},
|
|
});
|
|
|
|
// In case of recipient bar icons, following change
|
|
// ensures that tooltip doesn't hide behind the message
|
|
// box or it is not limited by the parent container.
|
|
delegate("body", {
|
|
target: [".recipient_bar_icon", ".sidebar-title", "#user_filter_icon"],
|
|
placement: "top",
|
|
appendTo: () => document.body,
|
|
});
|
|
|
|
delegate("body", {
|
|
target: ".rendered_markdown time",
|
|
allowHTML: true,
|
|
placement: "top",
|
|
appendTo: () => document.body,
|
|
onHidden(instance) {
|
|
instance.destroy();
|
|
},
|
|
});
|
|
}
|