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); } }); }