zulip/static/js/spoilers.js

94 lines
3.3 KiB
JavaScript

function collapse_spoiler(spoiler) {
const spoiler_height = spoiler.height();
// Set height to rendered height on next frame, then to zero on following
// frame to allow CSS transition animation to work
requestAnimationFrame(() => {
spoiler.height(spoiler_height + "px");
spoiler.removeClass("spoiler-content-open");
requestAnimationFrame(() => {
spoiler.height("0px");
});
});
}
function expand_spoiler(spoiler) {
// Normally, the height of the spoiler block is not defined absolutely on
// the `spoiler-content-open` class, but just set to `auto` (i.e. the height
// of the content). CSS animations do not work with properties set to
// `auto`, so we get the actual height of the content here and temporarily
// put it explicitly on the element styling to allow the transition to work.
const spoiler_height = spoiler.prop("scrollHeight");
spoiler.height(spoiler_height + "px");
// The `spoiler-content-open` class has CSS animations defined on it which
// will trigger on the frame after this class change.
spoiler.addClass("spoiler-content-open");
spoiler.on("transitionend", () => {
spoiler.off("transitionend");
// When the CSS transition is over, reset the height to auto
// This keeps things working if, e.g., the viewport is resized
spoiler.height("");
});
}
exports.hide_spoilers_in_notification = (content) => {
content.find(".spoiler-block").each((i, elem) => {
$(elem).find(".spoiler-content").remove();
let text = $(elem).find(".spoiler-header").text().trim();
if (text.length > 0) {
text = `${text} `;
}
text = `${text}(…)`;
$(elem).find(".spoiler-header").text(text);
});
return content;
};
exports.initialize = function () {
$("body").on("click", ".spoiler-header", function (e) {
const button = $(this).children(".spoiler-button");
const arrow = button.children(".spoiler-arrow");
const spoiler_content = $(this).siblings(".spoiler-content");
const target = $(e.target);
// Spoiler headers can contain markdown, including links. We
// return so that clicking such links will be be processed by
// the browser rather than opening the header.
if (target.closest("a").length > 0) {
return;
}
// Allow selecting text inside a spoiler header.
if (document.getSelection().type === "Range") {
return;
}
e.preventDefault();
e.stopPropagation();
if (spoiler_content.hasClass("spoiler-content-open")) {
// Content was open, we are collapsing
arrow.removeClass("spoiler-button-open");
// Modify ARIA roles for screen readers
button.attr("aria-expanded", "false");
spoiler_content.attr("aria-hidden", "true");
collapse_spoiler(spoiler_content);
} else {
// Content was closed, we are expanding
arrow.addClass("spoiler-button-open");
// Modify ARIA roles for screen readers
button.attr("aria-expanded", "true");
spoiler_content.attr("aria-hidden", "false");
expand_spoiler(spoiler_content);
}
});
};
window.spoilers = exports;