* fix(table of contents): multiple scrollbars (https://github.com/jackyzha0/quartz/issues/1388) * fix(center): Main content mininum width (https://github.com/jackyzha0/quartz/issues/1439) * fix(code block): Horizontal overflow fix (https://github.com/jackyzha0/quartz/issues/1438, https://github.com/jackyzha0/quartz/issues/1353) * WIP fix for ul/ol .overflow * Fix: restore former scrollbar behavior for overflow lists (https://github.com/jackyzha0/quartz/issues/1437) * Fix: code block overflow-x * fix: Table of Content overflow (https://github.com/jackyzha0/quartz/issues/1437) * Address feedback * Move max-height toggle from js to css
		
			
				
	
	
		
			136 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			136 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { FolderState } from "../ExplorerNode"
 | 
						|
 | 
						|
type MaybeHTMLElement = HTMLElement | undefined
 | 
						|
let currentExplorerState: FolderState[]
 | 
						|
const observer = new IntersectionObserver((entries) => {
 | 
						|
  // If last element is observed, remove gradient of "overflow" class so element is visible
 | 
						|
  const explorerUl = document.getElementById("explorer-ul")
 | 
						|
  if (!explorerUl) return
 | 
						|
  for (const entry of entries) {
 | 
						|
    if (entry.isIntersecting) {
 | 
						|
      explorerUl.classList.add("no-background")
 | 
						|
    } else {
 | 
						|
      explorerUl.classList.remove("no-background")
 | 
						|
    }
 | 
						|
  }
 | 
						|
})
 | 
						|
 | 
						|
function toggleExplorer(this: HTMLElement) {
 | 
						|
  this.classList.toggle("collapsed")
 | 
						|
  this.setAttribute(
 | 
						|
    "aria-expanded",
 | 
						|
    this.getAttribute("aria-expanded") === "true" ? "false" : "true",
 | 
						|
  )
 | 
						|
  const content = this.nextElementSibling as MaybeHTMLElement
 | 
						|
  if (!content) return
 | 
						|
 | 
						|
  content.classList.toggle("collapsed")
 | 
						|
}
 | 
						|
 | 
						|
function toggleFolder(evt: MouseEvent) {
 | 
						|
  evt.stopPropagation()
 | 
						|
  const target = evt.target as MaybeHTMLElement
 | 
						|
  if (!target) return
 | 
						|
 | 
						|
  const isSvg = target.nodeName === "svg"
 | 
						|
  const childFolderContainer = (
 | 
						|
    isSvg
 | 
						|
      ? target.parentElement?.nextSibling
 | 
						|
      : target.parentElement?.parentElement?.nextElementSibling
 | 
						|
  ) as MaybeHTMLElement
 | 
						|
  const currentFolderParent = (
 | 
						|
    isSvg ? target.nextElementSibling : target.parentElement
 | 
						|
  ) as MaybeHTMLElement
 | 
						|
  if (!(childFolderContainer && currentFolderParent)) return
 | 
						|
 | 
						|
  childFolderContainer.classList.toggle("open")
 | 
						|
  const isCollapsed = childFolderContainer.classList.contains("open")
 | 
						|
  setFolderState(childFolderContainer, !isCollapsed)
 | 
						|
  const fullFolderPath = currentFolderParent.dataset.folderpath as string
 | 
						|
  toggleCollapsedByPath(currentExplorerState, fullFolderPath)
 | 
						|
  const stringifiedFileTree = JSON.stringify(currentExplorerState)
 | 
						|
  localStorage.setItem("fileTree", stringifiedFileTree)
 | 
						|
}
 | 
						|
 | 
						|
function setupExplorer() {
 | 
						|
  const explorer = document.getElementById("explorer")
 | 
						|
  if (!explorer) return
 | 
						|
 | 
						|
  if (explorer.dataset.behavior === "collapse") {
 | 
						|
    for (const item of document.getElementsByClassName(
 | 
						|
      "folder-button",
 | 
						|
    ) as HTMLCollectionOf<HTMLElement>) {
 | 
						|
      item.addEventListener("click", toggleFolder)
 | 
						|
      window.addCleanup(() => item.removeEventListener("click", toggleFolder))
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  explorer.addEventListener("click", toggleExplorer)
 | 
						|
  window.addCleanup(() => explorer.removeEventListener("click", toggleExplorer))
 | 
						|
 | 
						|
  // Set up click handlers for each folder (click handler on folder "icon")
 | 
						|
  for (const item of document.getElementsByClassName(
 | 
						|
    "folder-icon",
 | 
						|
  ) 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) {
 | 
						|
      setFolderState(folderUl, folderState.collapsed)
 | 
						|
    }
 | 
						|
  })
 | 
						|
}
 | 
						|
 | 
						|
window.addEventListener("resize", setupExplorer)
 | 
						|
document.addEventListener("nav", () => {
 | 
						|
  setupExplorer()
 | 
						|
  observer.disconnect()
 | 
						|
 | 
						|
  // select pseudo element at end of list
 | 
						|
  const lastItem = document.getElementById("explorer-end")
 | 
						|
  if (lastItem) {
 | 
						|
    observer.observe(lastItem)
 | 
						|
  }
 | 
						|
})
 | 
						|
 | 
						|
/**
 | 
						|
 * Toggles the state of a given folder
 | 
						|
 * @param folderElement <div class="folder-outer"> Element of folder (parent)
 | 
						|
 * @param collapsed if folder should be set to collapsed or not
 | 
						|
 */
 | 
						|
function setFolderState(folderElement: HTMLElement, collapsed: boolean) {
 | 
						|
  return collapsed ? folderElement.classList.remove("open") : folderElement.classList.add("open")
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Toggles visibility of a folder
 | 
						|
 * @param array array of FolderState (`fileTree`, either get from local storage or data attribute)
 | 
						|
 * @param path path to folder (e.g. 'advanced/more/more2')
 | 
						|
 */
 | 
						|
function toggleCollapsedByPath(array: FolderState[], path: string) {
 | 
						|
  const entry = array.find((item) => item.path === path)
 | 
						|
  if (entry) {
 | 
						|
    entry.collapsed = !entry.collapsed
 | 
						|
  }
 | 
						|
}
 |