feat(explorer): collapsible mobile explorer (#1471)
Co-authored-by: Aaron Pham <Aaronpham0103@gmail.com>
This commit is contained in:
		@@ -27,7 +27,7 @@ export const defaultContentPageLayout: PageLayout = {
 | 
				
			|||||||
    Component.MobileOnly(Component.Spacer()),
 | 
					    Component.MobileOnly(Component.Spacer()),
 | 
				
			||||||
    Component.Search(),
 | 
					    Component.Search(),
 | 
				
			||||||
    Component.Darkmode(),
 | 
					    Component.Darkmode(),
 | 
				
			||||||
    Component.DesktopOnly(Component.Explorer()),
 | 
					    Component.Explorer(),
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  right: [
 | 
					  right: [
 | 
				
			||||||
    Component.Graph(),
 | 
					    Component.Graph(),
 | 
				
			||||||
@@ -44,7 +44,7 @@ export const defaultListPageLayout: PageLayout = {
 | 
				
			|||||||
    Component.MobileOnly(Component.Spacer()),
 | 
					    Component.MobileOnly(Component.Spacer()),
 | 
				
			||||||
    Component.Search(),
 | 
					    Component.Search(),
 | 
				
			||||||
    Component.Darkmode(),
 | 
					    Component.Darkmode(),
 | 
				
			||||||
    Component.DesktopOnly(Component.Explorer()),
 | 
					    Component.Explorer(),
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  right: [],
 | 
					  right: [],
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
 | 
					import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
 | 
				
			||||||
import explorerStyle from "./styles/explorer.scss"
 | 
					import style from "./styles/explorer.scss"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// @ts-ignore
 | 
					// @ts-ignore
 | 
				
			||||||
import script from "./scripts/explorer.inline"
 | 
					import script from "./scripts/explorer.inline"
 | 
				
			||||||
@@ -83,18 +83,46 @@ export default ((userOpts?: Partial<Options>) => {
 | 
				
			|||||||
      lastBuildId = ctx.buildId
 | 
					      lastBuildId = ctx.buildId
 | 
				
			||||||
      constructFileTree(allFiles)
 | 
					      constructFileTree(allFiles)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div class={classNames(displayClass, "explorer")}>
 | 
					      <div class={classNames(displayClass, "explorer")}>
 | 
				
			||||||
        <button
 | 
					        <button
 | 
				
			||||||
          type="button"
 | 
					          type="button"
 | 
				
			||||||
          id="explorer"
 | 
					          id="mobile-explorer"
 | 
				
			||||||
 | 
					          class="collapsed hide-until-loaded"
 | 
				
			||||||
          data-behavior={opts.folderClickBehavior}
 | 
					          data-behavior={opts.folderClickBehavior}
 | 
				
			||||||
          data-collapsed={opts.folderDefaultState}
 | 
					          data-collapsed={opts.folderDefaultState}
 | 
				
			||||||
          data-savestate={opts.useSavedState}
 | 
					          data-savestate={opts.useSavedState}
 | 
				
			||||||
          data-tree={jsonTree}
 | 
					          data-tree={jsonTree}
 | 
				
			||||||
 | 
					          data-mobile={true}
 | 
				
			||||||
          aria-controls="explorer-content"
 | 
					          aria-controls="explorer-content"
 | 
				
			||||||
          aria-expanded={opts.folderDefaultState === "open"}
 | 
					          aria-expanded={false}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <svg
 | 
				
			||||||
 | 
					            xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
					            width="24"
 | 
				
			||||||
 | 
					            height="24"
 | 
				
			||||||
 | 
					            viewBox="0 0 24 24"
 | 
				
			||||||
 | 
					            stroke-width="2"
 | 
				
			||||||
 | 
					            stroke-linecap="round"
 | 
				
			||||||
 | 
					            stroke-linejoin="round"
 | 
				
			||||||
 | 
					            class="lucide lucide-menu"
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            <line x1="4" x2="20" y1="12" y2="12" />
 | 
				
			||||||
 | 
					            <line x1="4" x2="20" y1="6" y2="6" />
 | 
				
			||||||
 | 
					            <line x1="4" x2="20" y1="18" y2="18" />
 | 
				
			||||||
 | 
					          </svg>
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					        <button
 | 
				
			||||||
 | 
					          type="button"
 | 
				
			||||||
 | 
					          id="desktop-explorer"
 | 
				
			||||||
 | 
					          class="title-button"
 | 
				
			||||||
 | 
					          data-behavior={opts.folderClickBehavior}
 | 
				
			||||||
 | 
					          data-collapsed={opts.folderDefaultState}
 | 
				
			||||||
 | 
					          data-savestate={opts.useSavedState}
 | 
				
			||||||
 | 
					          data-tree={jsonTree}
 | 
				
			||||||
 | 
					          data-mobile={false}
 | 
				
			||||||
 | 
					          aria-controls="explorer-content"
 | 
				
			||||||
 | 
					          aria-expanded={true}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <h2>{opts.title ?? i18n(cfg.locale).components.explorer.title}</h2>
 | 
					          <h2>{opts.title ?? i18n(cfg.locale).components.explorer.title}</h2>
 | 
				
			||||||
          <svg
 | 
					          <svg
 | 
				
			||||||
@@ -122,7 +150,7 @@ export default ((userOpts?: Partial<Options>) => {
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Explorer.css = explorerStyle
 | 
					  Explorer.css = style
 | 
				
			||||||
  Explorer.afterDOMLoaded = script
 | 
					  Explorer.afterDOMLoaded = script
 | 
				
			||||||
  return Explorer
 | 
					  return Explorer
 | 
				
			||||||
}) satisfies QuartzComponentConstructor
 | 
					}) satisfies QuartzComponentConstructor
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,9 @@
 | 
				
			|||||||
import { FolderState } from "../ExplorerNode"
 | 
					import { FolderState } from "../ExplorerNode"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Current state of folders
 | 
				
			||||||
type MaybeHTMLElement = HTMLElement | undefined
 | 
					type MaybeHTMLElement = HTMLElement | undefined
 | 
				
			||||||
let currentExplorerState: FolderState[]
 | 
					let currentExplorerState: FolderState[]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const observer = new IntersectionObserver((entries) => {
 | 
					const observer = new IntersectionObserver((entries) => {
 | 
				
			||||||
  // If last element is observed, remove gradient of "overflow" class so element is visible
 | 
					  // If last element is observed, remove gradient of "overflow" class so element is visible
 | 
				
			||||||
  const explorerUl = document.getElementById("explorer-ul")
 | 
					  const explorerUl = document.getElementById("explorer-ul")
 | 
				
			||||||
@@ -16,23 +18,43 @@ const observer = new IntersectionObserver((entries) => {
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function toggleExplorer(this: HTMLElement) {
 | 
					function toggleExplorer(this: HTMLElement) {
 | 
				
			||||||
 | 
					  // Toggle collapsed state of entire explorer
 | 
				
			||||||
  this.classList.toggle("collapsed")
 | 
					  this.classList.toggle("collapsed")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Toggle collapsed aria state of entire explorer
 | 
				
			||||||
  this.setAttribute(
 | 
					  this.setAttribute(
 | 
				
			||||||
    "aria-expanded",
 | 
					    "aria-expanded",
 | 
				
			||||||
    this.getAttribute("aria-expanded") === "true" ? "false" : "true",
 | 
					    this.getAttribute("aria-expanded") === "true" ? "false" : "true",
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
  const content = this.nextElementSibling as MaybeHTMLElement
 | 
					 | 
				
			||||||
  if (!content) return
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const content = (
 | 
				
			||||||
 | 
					    this.nextElementSibling?.nextElementSibling
 | 
				
			||||||
 | 
					      ? this.nextElementSibling.nextElementSibling
 | 
				
			||||||
 | 
					      : this.nextElementSibling
 | 
				
			||||||
 | 
					  ) as MaybeHTMLElement
 | 
				
			||||||
 | 
					  if (!content) return
 | 
				
			||||||
  content.classList.toggle("collapsed")
 | 
					  content.classList.toggle("collapsed")
 | 
				
			||||||
 | 
					  content.classList.toggle("explorer-viewmode")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Prevent scroll under
 | 
				
			||||||
 | 
					  if (document.querySelector("#mobile-explorer")) {
 | 
				
			||||||
 | 
					    // Disable scrolling on the page when the explorer is opened on mobile
 | 
				
			||||||
 | 
					    const bodySelector = document.querySelector("#quartz-body")
 | 
				
			||||||
 | 
					    if (bodySelector) bodySelector.classList.toggle("lock-scroll")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function toggleFolder(evt: MouseEvent) {
 | 
					function toggleFolder(evt: MouseEvent) {
 | 
				
			||||||
  evt.stopPropagation()
 | 
					  evt.stopPropagation()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Element that was clicked
 | 
				
			||||||
  const target = evt.target as MaybeHTMLElement
 | 
					  const target = evt.target as MaybeHTMLElement
 | 
				
			||||||
  if (!target) return
 | 
					  if (!target) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Check if target was svg icon or button
 | 
				
			||||||
  const isSvg = target.nodeName === "svg"
 | 
					  const isSvg = target.nodeName === "svg"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // corresponding <ul> element relative to clicked button/folder
 | 
				
			||||||
  const childFolderContainer = (
 | 
					  const childFolderContainer = (
 | 
				
			||||||
    isSvg
 | 
					    isSvg
 | 
				
			||||||
      ? target.parentElement?.nextSibling
 | 
					      ? target.parentElement?.nextSibling
 | 
				
			||||||
@@ -42,10 +64,14 @@ function toggleFolder(evt: MouseEvent) {
 | 
				
			|||||||
    isSvg ? target.nextElementSibling : target.parentElement
 | 
					    isSvg ? target.nextElementSibling : target.parentElement
 | 
				
			||||||
  ) as MaybeHTMLElement
 | 
					  ) as MaybeHTMLElement
 | 
				
			||||||
  if (!(childFolderContainer && currentFolderParent)) return
 | 
					  if (!(childFolderContainer && currentFolderParent)) return
 | 
				
			||||||
 | 
					  // <li> element of folder (stores folder-path dataset)
 | 
				
			||||||
  childFolderContainer.classList.toggle("open")
 | 
					  childFolderContainer.classList.toggle("open")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Collapse folder container
 | 
				
			||||||
  const isCollapsed = childFolderContainer.classList.contains("open")
 | 
					  const isCollapsed = childFolderContainer.classList.contains("open")
 | 
				
			||||||
  setFolderState(childFolderContainer, !isCollapsed)
 | 
					  setFolderState(childFolderContainer, !isCollapsed)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Save folder state to localStorage
 | 
				
			||||||
  const fullFolderPath = currentFolderParent.dataset.folderpath as string
 | 
					  const fullFolderPath = currentFolderParent.dataset.folderpath as string
 | 
				
			||||||
  toggleCollapsedByPath(currentExplorerState, fullFolderPath)
 | 
					  toggleCollapsedByPath(currentExplorerState, fullFolderPath)
 | 
				
			||||||
  const stringifiedFileTree = JSON.stringify(currentExplorerState)
 | 
					  const stringifiedFileTree = JSON.stringify(currentExplorerState)
 | 
				
			||||||
@@ -53,57 +79,106 @@ function toggleFolder(evt: MouseEvent) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function setupExplorer() {
 | 
					function setupExplorer() {
 | 
				
			||||||
  const explorer = document.getElementById("explorer")
 | 
					  // Set click handler for collapsing entire explorer
 | 
				
			||||||
  if (!explorer) return
 | 
					  const allExplorers = document.querySelectorAll(".explorer > button") as NodeListOf<HTMLElement>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (explorer.dataset.behavior === "collapse") {
 | 
					  for (const explorer of allExplorers) {
 | 
				
			||||||
 | 
					    // Get folder state from local storage
 | 
				
			||||||
 | 
					    const storageTree = localStorage.getItem("fileTree")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Convert to bool
 | 
				
			||||||
 | 
					    const useSavedFolderState = explorer?.dataset.savestate === "true"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (explorer) {
 | 
				
			||||||
 | 
					      // Get config
 | 
				
			||||||
 | 
					      const collapseBehavior = explorer.dataset.behavior
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Add click handlers for all folders (click handler on folder "label")
 | 
				
			||||||
 | 
					      if (collapseBehavior === "collapse") {
 | 
				
			||||||
 | 
					        for (const item of document.getElementsByClassName(
 | 
				
			||||||
 | 
					          "folder-button",
 | 
				
			||||||
 | 
					        ) as HTMLCollectionOf<HTMLElement>) {
 | 
				
			||||||
 | 
					          window.addCleanup(() => explorer.removeEventListener("click", toggleExplorer))
 | 
				
			||||||
 | 
					          item.addEventListener("click", toggleFolder)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Add click handler to main explorer
 | 
				
			||||||
 | 
					      window.addCleanup(() => explorer.removeEventListener("click", toggleExplorer))
 | 
				
			||||||
 | 
					      explorer.addEventListener("click", toggleExplorer)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Set up click handlers for each folder (click handler on folder "icon")
 | 
				
			||||||
    for (const item of document.getElementsByClassName(
 | 
					    for (const item of document.getElementsByClassName(
 | 
				
			||||||
      "folder-button",
 | 
					      "folder-icon",
 | 
				
			||||||
    ) as HTMLCollectionOf<HTMLElement>) {
 | 
					    ) as HTMLCollectionOf<HTMLElement>) {
 | 
				
			||||||
      item.addEventListener("click", toggleFolder)
 | 
					      item.addEventListener("click", toggleFolder)
 | 
				
			||||||
      window.addCleanup(() => item.removeEventListener("click", toggleFolder))
 | 
					      window.addCleanup(() => item.removeEventListener("click", toggleFolder))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Get folder state from local storage
 | 
				
			||||||
 | 
					    const oldExplorerState: FolderState[] =
 | 
				
			||||||
 | 
					      storageTree && useSavedFolderState ? JSON.parse(storageTree) : []
 | 
				
			||||||
 | 
					    const oldIndex = new Map(oldExplorerState.map((entry) => [entry.path, entry.collapsed]))
 | 
				
			||||||
 | 
					    const newExplorerState: FolderState[] = explorer.dataset.tree
 | 
				
			||||||
 | 
					      ? JSON.parse(explorer.dataset.tree)
 | 
				
			||||||
 | 
					      : []
 | 
				
			||||||
 | 
					    currentExplorerState = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const { path, collapsed } of newExplorerState) {
 | 
				
			||||||
 | 
					      currentExplorerState.push({
 | 
				
			||||||
 | 
					        path,
 | 
				
			||||||
 | 
					        collapsed: oldIndex.get(path) ?? collapsed,
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    currentExplorerState.map((folderState) => {
 | 
				
			||||||
 | 
					      const folderLi = document.querySelector(
 | 
				
			||||||
 | 
					        `[data-folderpath='${folderState.path.replace("'", "-")}']`,
 | 
				
			||||||
 | 
					      ) as MaybeHTMLElement
 | 
				
			||||||
 | 
					      const folderUl = folderLi?.parentElement?.nextElementSibling as MaybeHTMLElement
 | 
				
			||||||
 | 
					      if (folderUl) {
 | 
				
			||||||
 | 
					        setFolderState(folderUl, folderState.collapsed)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  explorer.addEventListener("click", toggleExplorer)
 | 
					function toggleExplorerFolders() {
 | 
				
			||||||
  window.addCleanup(() => explorer.removeEventListener("click", toggleExplorer))
 | 
					  const currentFile = (document.querySelector("body")?.getAttribute("data-slug") ?? "").replace(
 | 
				
			||||||
 | 
					    /\/index$/g,
 | 
				
			||||||
 | 
					    "",
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					  const allFolders = document.querySelectorAll(".folder-outer")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Set up click handlers for each folder (click handler on folder "icon")
 | 
					  allFolders.forEach((element) => {
 | 
				
			||||||
  for (const item of document.getElementsByClassName(
 | 
					    const folderUl = Array.from(element.children).find((child) =>
 | 
				
			||||||
    "folder-icon",
 | 
					      child.matches("ul[data-folderul]"),
 | 
				
			||||||
  ) as HTMLCollectionOf<HTMLElement>) {
 | 
					    )
 | 
				
			||||||
    item.addEventListener("click", toggleFolder)
 | 
					 | 
				
			||||||
    window.addCleanup(() => item.removeEventListener("click", toggleFolder))
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Get folder state from local storage
 | 
					 | 
				
			||||||
  const storageTree = localStorage.getItem("fileTree")
 | 
					 | 
				
			||||||
  const useSavedFolderState = explorer?.dataset.savestate === "true"
 | 
					 | 
				
			||||||
  const oldExplorerState: FolderState[] =
 | 
					 | 
				
			||||||
    storageTree && useSavedFolderState ? JSON.parse(storageTree) : []
 | 
					 | 
				
			||||||
  const oldIndex = new Map(oldExplorerState.map((entry) => [entry.path, entry.collapsed]))
 | 
					 | 
				
			||||||
  const newExplorerState: FolderState[] = explorer.dataset.tree
 | 
					 | 
				
			||||||
    ? JSON.parse(explorer.dataset.tree)
 | 
					 | 
				
			||||||
    : []
 | 
					 | 
				
			||||||
  currentExplorerState = []
 | 
					 | 
				
			||||||
  for (const { path, collapsed } of newExplorerState) {
 | 
					 | 
				
			||||||
    currentExplorerState.push({ path, collapsed: oldIndex.get(path) ?? collapsed })
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  currentExplorerState.map((folderState) => {
 | 
					 | 
				
			||||||
    const folderLi = document.querySelector(
 | 
					 | 
				
			||||||
      `[data-folderpath='${folderState.path}']`,
 | 
					 | 
				
			||||||
    ) as MaybeHTMLElement
 | 
					 | 
				
			||||||
    const folderUl = folderLi?.parentElement?.nextElementSibling as MaybeHTMLElement
 | 
					 | 
				
			||||||
    if (folderUl) {
 | 
					    if (folderUl) {
 | 
				
			||||||
      setFolderState(folderUl, folderState.collapsed)
 | 
					      if (currentFile.includes(folderUl.getAttribute("data-folderul") ?? "")) {
 | 
				
			||||||
 | 
					        if (!element.classList.contains("open")) {
 | 
				
			||||||
 | 
					          element.classList.add("open")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
window.addEventListener("resize", setupExplorer)
 | 
					window.addEventListener("resize", setupExplorer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
document.addEventListener("nav", () => {
 | 
					document.addEventListener("nav", () => {
 | 
				
			||||||
 | 
					  const explorer = document.querySelector("#mobile-explorer")
 | 
				
			||||||
 | 
					  if (explorer) {
 | 
				
			||||||
 | 
					    explorer.classList.add("collapsed")
 | 
				
			||||||
 | 
					    const content = explorer.nextElementSibling?.nextElementSibling as HTMLElement
 | 
				
			||||||
 | 
					    if (content) {
 | 
				
			||||||
 | 
					      content.classList.add("collapsed")
 | 
				
			||||||
 | 
					      content.classList.toggle("explorer-viewmode")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  setupExplorer()
 | 
					  setupExplorer()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  observer.disconnect()
 | 
					  observer.disconnect()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // select pseudo element at end of list
 | 
					  // select pseudo element at end of list
 | 
				
			||||||
@@ -111,6 +186,12 @@ document.addEventListener("nav", () => {
 | 
				
			|||||||
  if (lastItem) {
 | 
					  if (lastItem) {
 | 
				
			||||||
    observer.observe(lastItem)
 | 
					    observer.observe(lastItem)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Hide explorer on mobile until it is requested
 | 
				
			||||||
 | 
					  const hiddenUntilDoneLoading = document.querySelector("#mobile-explorer")
 | 
				
			||||||
 | 
					  hiddenUntilDoneLoading?.classList.remove("hide-until-loaded")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  toggleExplorerFolders()
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,70 @@
 | 
				
			|||||||
@use "../../styles/variables.scss" as *;
 | 
					@use "../../styles/variables.scss" as *;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media all and ($mobile) {
 | 
				
			||||||
 | 
					  .page > #quartz-body {
 | 
				
			||||||
 | 
					    // Shift page position when toggling Explorer on mobile.
 | 
				
			||||||
 | 
					    & > :not(.sidebar.left:has(.explorer)) {
 | 
				
			||||||
 | 
					      transform: translateX(0);
 | 
				
			||||||
 | 
					      transition: transform 300ms ease-in-out;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    &.lock-scroll > :not(.sidebar.left:has(.explorer)) {
 | 
				
			||||||
 | 
					      transform: translateX(100dvw);
 | 
				
			||||||
 | 
					      transition: transform 300ms ease-in-out;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Sticky top bar (stays in place when scrolling down on mobile).
 | 
				
			||||||
 | 
					    .sidebar.left:has(.explorer) {
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      position: sticky;
 | 
				
			||||||
 | 
					      background-color: var(--light);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Hide Explorer on mobile until done loading.
 | 
				
			||||||
 | 
					    // Prevents ugly animation on page load.
 | 
				
			||||||
 | 
					    .hide-until-loaded ~ #explorer-content {
 | 
				
			||||||
 | 
					      display: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.explorer {
 | 
					.explorer {
 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  height: 100%;
 | 
				
			||||||
  flex-direction: column;
 | 
					  flex-direction: column;
 | 
				
			||||||
  overflow-y: hidden;
 | 
					  overflow-y: hidden;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @media all and ($mobile) {
 | 
				
			||||||
 | 
					    order: -1;
 | 
				
			||||||
 | 
					    height: initial;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    flex-shrink: 0;
 | 
				
			||||||
 | 
					    align-self: flex-start;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  button#mobile-explorer {
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  button#desktop-explorer {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @media all and ($mobile) {
 | 
				
			||||||
 | 
					    button#mobile-explorer {
 | 
				
			||||||
 | 
					      display: flex;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    button#desktop-explorer {
 | 
				
			||||||
 | 
					      display: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  &.desktop-only {
 | 
					  &.desktop-only {
 | 
				
			||||||
    @media all and not ($mobile) {
 | 
					    @media all and not ($mobile) {
 | 
				
			||||||
      display: flex;
 | 
					      display: flex;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /*&:after {
 | 
					  /*&:after {
 | 
				
			||||||
    pointer-events: none;
 | 
					    pointer-events: none;
 | 
				
			||||||
    content: "";
 | 
					    content: "";
 | 
				
			||||||
@@ -23,7 +79,8 @@
 | 
				
			|||||||
  }*/
 | 
					  }*/
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
button#explorer {
 | 
					button#mobile-explorer,
 | 
				
			||||||
 | 
					button#desktop-explorer {
 | 
				
			||||||
  background-color: transparent;
 | 
					  background-color: transparent;
 | 
				
			||||||
  border: none;
 | 
					  border: none;
 | 
				
			||||||
  text-align: left;
 | 
					  text-align: left;
 | 
				
			||||||
@@ -68,19 +125,19 @@ button#explorer {
 | 
				
			|||||||
  list-style: none;
 | 
					  list-style: none;
 | 
				
			||||||
  overflow: hidden;
 | 
					  overflow: hidden;
 | 
				
			||||||
  overflow-y: auto;
 | 
					  overflow-y: auto;
 | 
				
			||||||
  max-height: 100%;
 | 
					  max-height: 0px;
 | 
				
			||||||
  transition:
 | 
					  transition:
 | 
				
			||||||
    max-height 0.35s ease,
 | 
					    max-height 0.35s ease,
 | 
				
			||||||
    visibility 0s linear 0s;
 | 
					    visibility 0s linear 0.35s;
 | 
				
			||||||
  margin-top: 0.5rem;
 | 
					  margin-top: 0.5rem;
 | 
				
			||||||
  visibility: visible;
 | 
					  visibility: hidden;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  &.collapsed {
 | 
					  &.collapsed {
 | 
				
			||||||
    max-height: 0;
 | 
					    max-height: 100%;
 | 
				
			||||||
    transition:
 | 
					    transition:
 | 
				
			||||||
      max-height 0.35s ease,
 | 
					      max-height 0.35s ease,
 | 
				
			||||||
      visibility 0s linear 0.35s;
 | 
					      visibility 0s linear 0s;
 | 
				
			||||||
    visibility: hidden;
 | 
					    visibility: visible;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  & ul {
 | 
					  & ul {
 | 
				
			||||||
@@ -91,12 +148,14 @@ button#explorer {
 | 
				
			|||||||
      max-height 0.35s ease,
 | 
					      max-height 0.35s ease,
 | 
				
			||||||
      transform 0.35s ease,
 | 
					      transform 0.35s ease,
 | 
				
			||||||
      opacity 0.2s ease;
 | 
					      opacity 0.2s ease;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    & li > a {
 | 
					    & li > a {
 | 
				
			||||||
      color: var(--dark);
 | 
					      color: var(--dark);
 | 
				
			||||||
      opacity: 0.75;
 | 
					      opacity: 0.75;
 | 
				
			||||||
      pointer-events: all;
 | 
					      pointer-events: all;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  > #explorer-ul {
 | 
					  > #explorer-ul {
 | 
				
			||||||
    max-height: none;
 | 
					    max-height: none;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -179,3 +238,80 @@ li:has(> .folder-outer:not(.open)) > .folder-container > svg {
 | 
				
			|||||||
  // remove default margin from li
 | 
					  // remove default margin from li
 | 
				
			||||||
  margin: 0;
 | 
					  margin: 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.explorer {
 | 
				
			||||||
 | 
					  @media all and ($mobile) {
 | 
				
			||||||
 | 
					    #explorer-content {
 | 
				
			||||||
 | 
					      box-sizing: border-box;
 | 
				
			||||||
 | 
					      overscroll-behavior: none;
 | 
				
			||||||
 | 
					      z-index: 100;
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      top: 0;
 | 
				
			||||||
 | 
					      background-color: var(--light);
 | 
				
			||||||
 | 
					      max-width: 100dvw;
 | 
				
			||||||
 | 
					      left: -100dvw;
 | 
				
			||||||
 | 
					      width: 100%;
 | 
				
			||||||
 | 
					      transition: transform 300ms ease-in-out;
 | 
				
			||||||
 | 
					      overflow: hidden;
 | 
				
			||||||
 | 
					      padding: $topSpacing 2rem 2rem;
 | 
				
			||||||
 | 
					      height: 100dvh;
 | 
				
			||||||
 | 
					      max-height: 100dvh;
 | 
				
			||||||
 | 
					      margin-top: 0;
 | 
				
			||||||
 | 
					      visibility: hidden;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      &:not(.collapsed) {
 | 
				
			||||||
 | 
					        transform: translateX(100dvw);
 | 
				
			||||||
 | 
					        visibility: visible;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      ul.overflow {
 | 
				
			||||||
 | 
					        max-height: 100%;
 | 
				
			||||||
 | 
					        width: 100%;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      &.collapsed {
 | 
				
			||||||
 | 
					        transform: translateX(0);
 | 
				
			||||||
 | 
					        visibility: visible;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #mobile-explorer {
 | 
				
			||||||
 | 
					      margin: 5px;
 | 
				
			||||||
 | 
					      z-index: 101;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      &:not(.collapsed) .lucide-menu {
 | 
				
			||||||
 | 
					        transform: rotate(-90deg);
 | 
				
			||||||
 | 
					        transition: transform 200ms ease-in-out;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      .lucide-menu {
 | 
				
			||||||
 | 
					        stroke: var(--darkgray);
 | 
				
			||||||
 | 
					        transition: transform 200ms ease;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        &:hover {
 | 
				
			||||||
 | 
					          stroke: var(--dark);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.no-scroll {
 | 
				
			||||||
 | 
					  opacity: 0;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					html:has(.no-scroll) {
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media all and not ($mobile) {
 | 
				
			||||||
 | 
					  .no-scroll {
 | 
				
			||||||
 | 
					    opacity: 1 !important;
 | 
				
			||||||
 | 
					    overflow: auto !important;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  html:has(.no-scroll) {
 | 
				
			||||||
 | 
					    overflow: auto !important;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user