import $ from "jquery"; function collapse_spoiler(spoiler: JQuery): void { const spoiler_height = spoiler.height() || 0; // 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: JQuery): void { // 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: number = 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(""); }); } export const hide_spoilers_in_notification = (content: JQuery): JQuery => { 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; }; export function initialize(): void { $("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. const selection = document.getSelection(); if (selection && selection.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); } }); }