i2psnark/res/js/toggleConfigs.js

/**
 * @module toggleConfigs
 * @file toggleConfigs.js - Enable toggling of I2PSnark's tracker and filter configuration panels.
 * @description Provides expand/collapse functionality for the file filter and tracker
 * configuration tables in I2PSnark. Handles iframe-aware scrolling by posting messages
 * to the parent frame when embedded, or scrolling natively in standalone mode.
 * @author dr|3d
 * @license AGPL3 or later
 */

/**
 * @type {?HTMLTableElement}
 * @description The file filter configuration table element.
 */
const filterConfigTable = document.querySelector("#fileFilter table");

/**
 * @type {?HTMLTableElement}
 * @description The tracker configuration table element.
 */
const trackerConfigTable = document.querySelector("#trackers table");

/**
 * @type {boolean}
 * @description Whether the page is running inside an iframe.
 */
const isIframed = document.documentElement.classList.contains("iframed") || window.self != window.top;

/**
 * @function toggleConfig
 * @description Toggles the visibility of a configuration table. When shown, adds the
 * "expanded" class to the title and scrolls it into view. When hidden, removes the
 * class. In iframe mode, posts messages to the parent frame for scrolling and resizing.
 * @param {HTMLTableElement} table - The configuration table to show or hide.
 * @param {HTMLElement} title - The title element that acts as the toggle header.
 * @returns {void}
 */
function toggleConfig(table, title) {
  if (table.style.display === "none" || table.style.display === "") {
    table.style.display = "table";
    title.classList.add("expanded");

    if (isIframed) {
      parent.postMessage({ origin: "parent", command: "scrollToElement", id: title.id }, location.origin);
      parent.postMessage({ origin: "parent", command: "resize", iframeId: "i2psnarkframe" }, location.origin);
    } else {scrollToElement(title);}
  } else {
    table.style.display = "none";
    title.classList.remove("expanded");
  }
}

/**
 * @function scrollToElement
 * @description Smoothly scrolls the given element into view within the specified window context.
 * Calculates the element's position relative to the viewport and scrolls to it.
 * @param {HTMLElement} element - The element to scroll into view.
 * @param {Window} [windowObj=window] - The window object to scroll (useful for parent window in iframe mode).
 * @returns {void}
 */
function scrollToElement(element, windowObj = window) {
  const elementPosition = element.getBoundingClientRect().top;
  const scrollPosition = elementPosition + (windowObj !== window ? windowObj.pageYOffset : window.pageYOffset);
  windowObj.scrollTo({ top: scrollPosition, behavior: "smooth" });
}

document.addEventListener("DOMContentLoaded", () => {
  const configTitles = document.querySelectorAll(".configTitle");
  configTitles.forEach((title) => title.classList.remove("expanded"));

  const setupClickListener = async (elementId, configTable) => {
    const button = document.getElementById(elementId);
    if (!button || !configTable) {return;}
    button.addEventListener("click", async (e) => {
      const clickedTitle = e.target.closest(".configTitle");
      if (clickedTitle) {
        await toggleConfig(configTable, clickedTitle);
        setTimeout(() => {
          requestAnimationFrame(() => {
            clickedTitle.scrollIntoView({block: "center", inline: "center", behavior: "smooth"});
          });
        }, 60);
      }
    });
  };

  setupClickListener("fileFilter", filterConfigTable);
  setupClickListener("trackers", trackerConfigTable);
});