susidns/src/js/togglePanels.js

/**
 * @module togglePanels
 * @file I2P+ SusiDNS panel toggler.
 * Provides show/hide functionality for the "Add New Host" and "Import Hosts"
 * panels, including scroll-lock and iframe-aware behavior.
 * @author dr|z3d
 * @license AGPL3 or later
 */

(function () {
  /**
   * Initialises panel toggle handlers on DOM load. Detects the active theme,
   * wires up click listeners on toggle buttons, and manages CSS classes and
   * scroll behavior.
   *
   * @listens DOMContentLoaded
   */
  document.addEventListener("DOMContentLoaded", function () {
    const darkTheme = document.body.classList.contains("dark");
    const lightTheme = document.body.classList.contains("light");
    if (!darkTheme && !lightTheme) {return;}

    const addContainer = document.getElementById("add");
    const addReset = document.querySelector("#add input[type=reset]");
    const addSubmits = document.querySelectorAll("#addDestForm input[type=submit]");
    const addTextInputs = document.querySelectorAll("#add input[type=text]");
    const addToggle = document.getElementById("addNewDest");
    const importContainer = document.getElementById("import");
    const importReset = document.querySelector("#import input[type=reset]");
    const importSubmit = document.querySelector("#importHostsForm input[type=submit]");
    const importToggle = document.getElementById("importFromFile");
    const messages = document.querySelector("#messages p");
    const isIframed = document.documentElement.classList.contains("iframed") || window.top !== window.parent.top;

    [addContainer, importContainer].forEach((container) => {
      if (container) {container.hidden = true;}
    });

    addTextInputs.forEach(input => {input.value = "";});

    [addToggle, importToggle].forEach((button) => {
      if (button) {
        button.removeAttribute("href");
        button.removeAttribute("style");
        button.hidden = false;
      }
    });

    /**
     * Shows one panel container and hides the other, adjusting page
     * layout and scroll behavior accordingly.
     *
     * @function toggleVisibility
     * @param {HTMLElement|null} showContainer - The container to reveal.
     * @param {HTMLElement|null} hideContainer - The container to conceal.
     * @returns {void}
     */
    function toggleVisibility(showContainer, hideContainer) {
      if (showContainer) {showContainer.hidden = false;}
      if (hideContainer) {hideContainer.hidden = true;}
      toggleBodyClass();
      if (!showContainer.hidden) {
        document.documentElement.classList.add("noscroll");
        const page = document.getElementById("page");
        if (lightTheme) {page.style.minHeight = "230px";}
        else {page.style.minHeight = "255px";}
        if (isIframed) {
          window.parent.document.documentElement.style.overflow = "hidden";
        }
      }
    }

    /**
     * Adds or removes the "displayPanels" class on `<body>` based on the
     * visibility state of the add and import containers. Applies a short
     * delay when panels are transitioning out.
     *
     * @function toggleBodyClass
     * @returns {void}
     */
    function toggleBodyClass() {
      const shouldHide = addContainer?.classList.contains("isHidden") || importContainer?.classList.contains("isHidden");
      const hidden = addContainer.hidden && importContainer.hidden || shouldHide;
      if (!shouldHide) {document.body.classList.toggle("displayPanels", !hidden);}
      else {
        setTimeout(() => {
          document.body.classList.remove("displayPanels");
          document.documentElement.classList.remove("noscroll");
          page.style.minHeight = null;
          if (isIframed) {window.parent.document.documentElement.style.overflow = null;}
        }, 240);
      }
    }

    /**
     * Clears all text input values inside the "Add" panel and hides the
     * container with a brief CSS transition.
     *
     * @function resetAddInputs
     * @returns {void}
     */
    function resetAddInputs() {
      addTextInputs.forEach(input => {
        input.removeAttribute("value");
        input.setAttribute("value", "");
      });
      addContainer.classList.add("isHidden");
      setTimeout(() => {
        toggleBodyClass();
        addContainer.hidden = true;
        addContainer.classList.remove("isHidden");
      }, 240);
    }

    addToggle?.addEventListener("click", () =>
      toggleVisibility(addContainer, importContainer)
    );

    importToggle?.addEventListener("click", () =>
      toggleVisibility(importContainer, addContainer)
    );

    addReset?.addEventListener("click", resetAddInputs);

    importReset?.addEventListener("click", () => {
      importContainer.classList.add("isHidden");
      toggleBodyClass();
      setTimeout(() => {
        importContainer.hidden = true;
        importContainer.classList.remove("isHidden");
      }, 240);
    });

  });
})();