Source: clientModules/typesenseHandleSearchModal.js

/**
 * @file This file makes the state of the search modal (open or closed) persistent in the url.
 * @author Kor Dwarshuis
 * @version 1.0.0
 * @since 2023-06-21
 */

import paths from "../docusaurus.paths";

// The search Modal is a modal that opens when the user clicks on the search icon in the top right corner of the screen.
let searchModalStatus = '';

// Some things need to be done only once, when the app is initialized
let appInitialized = false;

function router() {
  function getParam(key) {
    const searchParams = new URLSearchParams(window.location.search);
    return searchParams.get(key);
  }

  // Function to set the value of a search parameter
  function setParam(key, value) {
    const searchParams = new URLSearchParams(window.location.search);
    searchParams.set(key, value);

    // Update the URL with the modified search parameters
    const newUrl = window.location.pathname + '?' + searchParams.toString();
    window.history.replaceState(null, '', newUrl);
  }

  return {
    getParam,
    setParam
  };
}

// Create an instance of the router
const myRouter = router();

function hideModal() {
  document.querySelector('.search-modal-backdrop').classList.add('hidden');
  document.querySelector('#search').classList.add('hidden');
  searchModalStatus = 'closed';
}

function showModal() {
  document.querySelector('.search-modal-backdrop').classList.remove('hidden');
  document.querySelector('#search').classList.remove('hidden');
  document.querySelector('.ais-SearchBox-input').focus();
}

// Runs on onRouteDidUpdate
const typesenseHandleSearchModal = () => {
  // Only first time when app loads
  if (appInitialized === false) {
    document.addEventListener('eventSearchModalCloses', function () {
      hideModal();

      // Add to url that the modal is closed
      myRouter.setParam('searchModalStatus', 'closed');
    }, false);


    document.addEventListener('eventSearchModalOpens', function () {
      showModal();

      searchModalStatus = 'open';

      // Add to url that the modal is open
      myRouter.setParam('searchModalStatus', 'open');

      // Hide the search results on the home page if the search box is empty
      if (window.location.pathname === paths.baseUrl && document.querySelector('.ais-SearchBox-input').value === '') {
        document.querySelector('.search-results-container').classList.add('d-none');
        document.querySelector('.to-search-results').classList.add('d-none');
      } else {
        document.querySelector('.search-results-container').classList.remove('d-none');
        document.querySelector('.to-search-results').classList.remove('d-none');
      }
    }, false);

  }

  // on new page load, check if the search modal should be open or closed
  if (myRouter.getParam('searchModalStatus') === 'open') {
    showModal();
    searchModalStatus = 'open';
  } else {
    hideModal();
    searchModalStatus = 'closed';
  }

  // // on new page load, if search box is empty, hide results. There seems to be no out of the box solution for this
  // if (document.querySelector('.ais-SearchBox-input').value === '') {
  //   document.querySelector('#hits').classList.add('hidden');
  //   document.querySelector('#pagination').classList.add('hidden');
  //   document.querySelector('.ais-SearchBox-input').addEventListener('input', function () {
  //     document.querySelector('#hits').classList.remove('hidden');
  //     document.querySelector('#pagination').classList.remove('hidden');
  //   })
  // }

  function setSearchModalStatusInUrl() {
    setTimeout(() => {
      myRouter.setParam('searchModalStatus', searchModalStatus);
    }, 1000);//TODO: typesense removes all query params so we need to wait for that to happen and the re-add the searchModalStatus param. Find out how typesense can be configured to not remove all query params
  }

  function toggleSearchResultsVisibility() {
    if (window.location.pathname === paths.baseUrl && document.querySelector('.ais-SearchBox-input').value === '') {
      document.querySelector('.search-results-container').classList.add('d-none');
      document.querySelector('.to-search-results').classList.add('d-none');
    } else {
      document.querySelector('.search-results-container').classList.remove('d-none');
      document.querySelector('.to-search-results').classList.remove('d-none');
    }
  }

  document.querySelector('.ais-SearchBox-input').addEventListener('input', function (e) {// Should be “input”, not “change”
    setSearchModalStatusInUrl();
    toggleSearchResultsVisibility();
  }, false);

  // event delegation, for the filters
  const on = (selector, eventType, childSelector, eventHandler) => {
    const elements = document.querySelectorAll(selector);
    for (let element of elements) {
      element.addEventListener(eventType, eventOnElement => {
        if (eventOnElement.target.matches(childSelector)) {
          eventHandler(eventOnElement);
        }
      }, true);
    }
  };

  // When a filter is clicked
  on('#filters-section', 'click', '.ais-RefinementList-checkbox', event => {
    setSearchModalStatusInUrl();
  });


  // create event for opening and closing the search modal
  var eventSearchModalOpens = new Event('eventSearchModalOpens');
  var eventSearchModalCloses = new Event('eventSearchModalCloses');

  function handleSearchModalCloseClick(e) {
    document.dispatchEvent(eventSearchModalCloses);

  }
  function handleSearchModalOpenClick(e) {
    document.dispatchEvent(eventSearchModalOpens);

  }

  // Dispatch the event.
  // Only first time when app loads, since the Modal DOM structure is apparently preserved between pages
  if (appInitialized === false) {
    document.querySelector('#search-close').addEventListener('click', handleSearchModalCloseClick);
  }

  // Dispatch the event.
  // This DOM element is not preserved between pages, so we need to add the event listener every time the page loads
  document.querySelector('#search-start').addEventListener('click', handleSearchModalOpenClick);

  if (window.location.pathname === paths.baseUrl) {
    handleSearchModalOpenClick();
  }
  // Close the search modal when the backdrop is clicked
  document.querySelector('.search-modal-backdrop').addEventListener('click', handleSearchModalCloseClick);

  // Close the search modal when the escape key is pressed
  document.addEventListener('keyup', (event) => {
    switch (event.key) {
      // escape
      case 'Escape':
        document.dispatchEvent(eventSearchModalCloses);
        break;
    }
  });

  appInitialized = true;
};

export function onRouteDidUpdate({ location, previousLocation }) {
  // Don't execute if we are still on the same page; the lifecycle may be fired
  // because the hash changes (e.g. when navigating between headings)
  if (location.pathname === previousLocation?.pathname) return;

  typesenseHandleSearchModal();
}

// export function onRouteUpdate({ location, previousLocation }) {
//   // Only first time when app loads
//   if (previousLocation === null) {
//     console.log("Only first time when app loads");
//   }
// }