text: 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;
  