feat: support non-singleton explorer
This commit is contained in:
		@@ -19,6 +19,7 @@ import { options } from "./util/sourcemap"
 | 
			
		||||
import { Mutex } from "async-mutex"
 | 
			
		||||
import DepGraph from "./depgraph"
 | 
			
		||||
import { getStaticResourcesFromPlugins } from "./plugins"
 | 
			
		||||
import { randomIdNonSecure } from "./util/random"
 | 
			
		||||
 | 
			
		||||
type Dependencies = Record<string, DepGraph<FilePath> | null>
 | 
			
		||||
 | 
			
		||||
@@ -38,13 +39,9 @@ type BuildData = {
 | 
			
		||||
 | 
			
		||||
type FileEvent = "add" | "change" | "delete"
 | 
			
		||||
 | 
			
		||||
function newBuildId() {
 | 
			
		||||
  return Math.random().toString(36).substring(2, 8)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) {
 | 
			
		||||
  const ctx: BuildCtx = {
 | 
			
		||||
    buildId: newBuildId(),
 | 
			
		||||
    buildId: randomIdNonSecure(),
 | 
			
		||||
    argv,
 | 
			
		||||
    cfg,
 | 
			
		||||
    allSlugs: [],
 | 
			
		||||
@@ -162,7 +159,7 @@ async function partialRebuildFromEntrypoint(
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const buildId = newBuildId()
 | 
			
		||||
  const buildId = randomIdNonSecure()
 | 
			
		||||
  ctx.buildId = buildId
 | 
			
		||||
  buildData.lastBuildMs = new Date().getTime()
 | 
			
		||||
  const release = await mut.acquire()
 | 
			
		||||
@@ -359,7 +356,7 @@ async function rebuildFromEntrypoint(
 | 
			
		||||
    toRemove.add(filePath)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const buildId = newBuildId()
 | 
			
		||||
  const buildId = randomIdNonSecure()
 | 
			
		||||
  ctx.buildId = buildId
 | 
			
		||||
  buildData.lastBuildMs = new Date().getTime()
 | 
			
		||||
  const release = await mut.acquire()
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ import style from "./styles/backlinks.scss"
 | 
			
		||||
import { resolveRelative, simplifySlug } from "../util/path"
 | 
			
		||||
import { i18n } from "../i18n"
 | 
			
		||||
import { classNames } from "../util/lang"
 | 
			
		||||
import OverflowList from "./OverflowList"
 | 
			
		||||
import OverflowListFactory from "./OverflowList"
 | 
			
		||||
 | 
			
		||||
interface BacklinksOptions {
 | 
			
		||||
  hideWhenEmpty: boolean
 | 
			
		||||
@@ -15,6 +15,7 @@ const defaultOptions: BacklinksOptions = {
 | 
			
		||||
 | 
			
		||||
export default ((opts?: Partial<BacklinksOptions>) => {
 | 
			
		||||
  const options: BacklinksOptions = { ...defaultOptions, ...opts }
 | 
			
		||||
  const { OverflowList, overflowListAfterDOMLoaded } = OverflowListFactory()
 | 
			
		||||
 | 
			
		||||
  const Backlinks: QuartzComponent = ({
 | 
			
		||||
    fileData,
 | 
			
		||||
@@ -30,7 +31,7 @@ export default ((opts?: Partial<BacklinksOptions>) => {
 | 
			
		||||
    return (
 | 
			
		||||
      <div class={classNames(displayClass, "backlinks")}>
 | 
			
		||||
        <h3>{i18n(cfg.locale).components.backlinks.title}</h3>
 | 
			
		||||
        <OverflowList id="backlinks-ul">
 | 
			
		||||
        <OverflowList>
 | 
			
		||||
          {backlinkFiles.length > 0 ? (
 | 
			
		||||
            backlinkFiles.map((f) => (
 | 
			
		||||
              <li>
 | 
			
		||||
@@ -48,7 +49,7 @@ export default ((opts?: Partial<BacklinksOptions>) => {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Backlinks.css = style
 | 
			
		||||
  Backlinks.afterDOMLoaded = OverflowList.afterDOMLoaded("backlinks-ul")
 | 
			
		||||
  Backlinks.afterDOMLoaded = overflowListAfterDOMLoaded
 | 
			
		||||
 | 
			
		||||
  return Backlinks
 | 
			
		||||
}) satisfies QuartzComponentConstructor
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,8 @@ import script from "./scripts/explorer.inline"
 | 
			
		||||
import { classNames } from "../util/lang"
 | 
			
		||||
import { i18n } from "../i18n"
 | 
			
		||||
import { FileTrieNode } from "../util/fileTrie"
 | 
			
		||||
import OverflowList from "./OverflowList"
 | 
			
		||||
import OverflowListFactory from "./OverflowList"
 | 
			
		||||
import { concatenateResources } from "../util/resources"
 | 
			
		||||
 | 
			
		||||
type OrderEntries = "sort" | "filter" | "map"
 | 
			
		||||
 | 
			
		||||
@@ -56,6 +57,7 @@ export type FolderState = {
 | 
			
		||||
 | 
			
		||||
export default ((userOpts?: Partial<Options>) => {
 | 
			
		||||
  const opts: Options = { ...defaultOptions, ...userOpts }
 | 
			
		||||
  const { OverflowList, overflowListAfterDOMLoaded } = OverflowListFactory()
 | 
			
		||||
 | 
			
		||||
  const Explorer: QuartzComponent = ({ cfg, displayClass }: QuartzComponentProps) => {
 | 
			
		||||
    return (
 | 
			
		||||
@@ -73,8 +75,7 @@ export default ((userOpts?: Partial<Options>) => {
 | 
			
		||||
      >
 | 
			
		||||
        <button
 | 
			
		||||
          type="button"
 | 
			
		||||
          id="mobile-explorer"
 | 
			
		||||
          class="explorer-toggle hide-until-loaded"
 | 
			
		||||
          class="explorer-toggle mobile-explorer hide-until-loaded"
 | 
			
		||||
          data-mobile={true}
 | 
			
		||||
          aria-controls="explorer-content"
 | 
			
		||||
        >
 | 
			
		||||
@@ -95,8 +96,7 @@ export default ((userOpts?: Partial<Options>) => {
 | 
			
		||||
        </button>
 | 
			
		||||
        <button
 | 
			
		||||
          type="button"
 | 
			
		||||
          id="desktop-explorer"
 | 
			
		||||
          class="title-button explorer-toggle"
 | 
			
		||||
          class="title-button explorer-toggle desktop-explorer"
 | 
			
		||||
          data-mobile={false}
 | 
			
		||||
          aria-expanded={true}
 | 
			
		||||
        >
 | 
			
		||||
@@ -116,8 +116,8 @@ export default ((userOpts?: Partial<Options>) => {
 | 
			
		||||
            <polyline points="6 9 12 15 18 9"></polyline>
 | 
			
		||||
          </svg>
 | 
			
		||||
        </button>
 | 
			
		||||
        <div id="explorer-content" aria-expanded={false}>
 | 
			
		||||
          <OverflowList id="explorer-ul" />
 | 
			
		||||
        <div class="explorer-content" aria-expanded={false}>
 | 
			
		||||
          <OverflowList class="explorer-ul" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <template id="template-file">
 | 
			
		||||
          <li>
 | 
			
		||||
@@ -157,6 +157,6 @@ export default ((userOpts?: Partial<Options>) => {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Explorer.css = style
 | 
			
		||||
  Explorer.afterDOMLoaded = script + OverflowList.afterDOMLoaded("explorer-ul")
 | 
			
		||||
  Explorer.afterDOMLoaded = concatenateResources(script, overflowListAfterDOMLoaded)
 | 
			
		||||
  return Explorer
 | 
			
		||||
}) satisfies QuartzComponentConstructor
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,31 @@
 | 
			
		||||
import { JSX } from "preact"
 | 
			
		||||
import { randomIdNonSecure } from "../util/random"
 | 
			
		||||
 | 
			
		||||
const OverflowList = ({
 | 
			
		||||
  children,
 | 
			
		||||
  ...props
 | 
			
		||||
}: JSX.HTMLAttributes<HTMLUListElement> & { id: string }) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <ul class="overflow" {...props}>
 | 
			
		||||
    <ul {...props} class={[props.class, "overflow"].filter(Boolean).join(" ")} id={props.id}>
 | 
			
		||||
      {children}
 | 
			
		||||
      <li class="overflow-end" />
 | 
			
		||||
    </ul>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
OverflowList.afterDOMLoaded = (id: string) => `
 | 
			
		||||
export default () => {
 | 
			
		||||
  const id = randomIdNonSecure()
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    OverflowList: (props: JSX.HTMLAttributes<HTMLUListElement>) => (
 | 
			
		||||
      <OverflowList {...props} id={id} />
 | 
			
		||||
    ),
 | 
			
		||||
    overflowListAfterDOMLoaded: `
 | 
			
		||||
document.addEventListener("nav", (e) => {
 | 
			
		||||
  const observer = new IntersectionObserver((entries) => {
 | 
			
		||||
    for (const entry of entries) {
 | 
			
		||||
      const parentUl = entry.target.parentElement
 | 
			
		||||
      if (!parentUl) return
 | 
			
		||||
      if (entry.isIntersecting) {
 | 
			
		||||
        parentUl.classList.remove("gradient-active")
 | 
			
		||||
      } else {
 | 
			
		||||
@@ -34,6 +43,6 @@ document.addEventListener("nav", (e) => {
 | 
			
		||||
  observer.observe(end)
 | 
			
		||||
  window.addCleanup(() => observer.disconnect())
 | 
			
		||||
})
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
export default OverflowList
 | 
			
		||||
`,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,8 @@ import { classNames } from "../util/lang"
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
import script from "./scripts/toc.inline"
 | 
			
		||||
import { i18n } from "../i18n"
 | 
			
		||||
import OverflowList from "./OverflowList"
 | 
			
		||||
import OverflowListFactory from "./OverflowList"
 | 
			
		||||
import { concatenateResources } from "../util/resources"
 | 
			
		||||
 | 
			
		||||
interface Options {
 | 
			
		||||
  layout: "modern" | "legacy"
 | 
			
		||||
@@ -16,11 +17,14 @@ const defaultOptions: Options = {
 | 
			
		||||
  layout: "modern",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const TableOfContents: QuartzComponent = ({
 | 
			
		||||
export default ((opts?: Partial<Options>) => {
 | 
			
		||||
  const layout = opts?.layout ?? defaultOptions.layout
 | 
			
		||||
  const { OverflowList, overflowListAfterDOMLoaded } = OverflowListFactory()
 | 
			
		||||
  const TableOfContents: QuartzComponent = ({
 | 
			
		||||
    fileData,
 | 
			
		||||
    displayClass,
 | 
			
		||||
    cfg,
 | 
			
		||||
}: QuartzComponentProps) => {
 | 
			
		||||
  }: QuartzComponentProps) => {
 | 
			
		||||
    if (!fileData.toc) {
 | 
			
		||||
      return null
 | 
			
		||||
    }
 | 
			
		||||
@@ -50,7 +54,7 @@ const TableOfContents: QuartzComponent = ({
 | 
			
		||||
          </svg>
 | 
			
		||||
        </button>
 | 
			
		||||
        <div class={fileData.collapseToc ? "collapsed toc-content" : "toc-content"}>
 | 
			
		||||
        <OverflowList id="toc-ul">
 | 
			
		||||
          <OverflowList>
 | 
			
		||||
            {fileData.toc.map((tocEntry) => (
 | 
			
		||||
              <li key={tocEntry.slug} class={`depth-${tocEntry.depth}`}>
 | 
			
		||||
                <a href={`#${tocEntry.slug}`} data-for={tocEntry.slug}>
 | 
			
		||||
@@ -62,11 +66,12 @@ const TableOfContents: QuartzComponent = ({
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
TableOfContents.css = modernStyle
 | 
			
		||||
TableOfContents.afterDOMLoaded = script + OverflowList.afterDOMLoaded("toc-ul")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
const LegacyTableOfContents: QuartzComponent = ({ fileData, cfg }: QuartzComponentProps) => {
 | 
			
		||||
  TableOfContents.css = modernStyle
 | 
			
		||||
  TableOfContents.afterDOMLoaded = concatenateResources(script, overflowListAfterDOMLoaded)
 | 
			
		||||
 | 
			
		||||
  const LegacyTableOfContents: QuartzComponent = ({ fileData, cfg }: QuartzComponentProps) => {
 | 
			
		||||
    if (!fileData.toc) {
 | 
			
		||||
      return null
 | 
			
		||||
    }
 | 
			
		||||
@@ -86,10 +91,8 @@ const LegacyTableOfContents: QuartzComponent = ({ fileData, cfg }: QuartzCompone
 | 
			
		||||
        </ul>
 | 
			
		||||
      </details>
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
LegacyTableOfContents.css = legacyStyle
 | 
			
		||||
  }
 | 
			
		||||
  LegacyTableOfContents.css = legacyStyle
 | 
			
		||||
 | 
			
		||||
export default ((opts?: Partial<Options>) => {
 | 
			
		||||
  const layout = opts?.layout ?? defaultOptions.layout
 | 
			
		||||
  return layout === "modern" ? TableOfContents : LegacyTableOfContents
 | 
			
		||||
}) satisfies QuartzComponentConstructor
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ import { htmlToJsx } from "../../util/jsx"
 | 
			
		||||
import { i18n } from "../../i18n"
 | 
			
		||||
import { QuartzPluginData } from "../../plugins/vfile"
 | 
			
		||||
import { ComponentChildren } from "preact"
 | 
			
		||||
import { concatenateResources } from "../../util/resources"
 | 
			
		||||
 | 
			
		||||
interface FolderContentOptions {
 | 
			
		||||
  /**
 | 
			
		||||
@@ -104,6 +105,6 @@ export default ((opts?: Partial<FolderContentOptions>) => {
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  FolderContent.css = style + PageList.css
 | 
			
		||||
  FolderContent.css = concatenateResources(style, PageList.css)
 | 
			
		||||
  return FolderContent
 | 
			
		||||
}) satisfies QuartzComponentConstructor
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ import { Root } from "hast"
 | 
			
		||||
import { htmlToJsx } from "../../util/jsx"
 | 
			
		||||
import { i18n } from "../../i18n"
 | 
			
		||||
import { ComponentChildren } from "preact"
 | 
			
		||||
import { concatenateResources } from "../../util/resources"
 | 
			
		||||
 | 
			
		||||
interface TagContentOptions {
 | 
			
		||||
  sort?: SortFn
 | 
			
		||||
@@ -124,6 +125,6 @@ export default ((opts?: Partial<TagContentOptions>) => {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  TagContent.css = style + PageList.css
 | 
			
		||||
  TagContent.css = concatenateResources(style, PageList.css)
 | 
			
		||||
  return TagContent
 | 
			
		||||
}) satisfies QuartzComponentConstructor
 | 
			
		||||
 
 | 
			
		||||
@@ -21,14 +21,13 @@ type FolderState = {
 | 
			
		||||
 | 
			
		||||
let currentExplorerState: Array<FolderState>
 | 
			
		||||
function toggleExplorer(this: HTMLElement) {
 | 
			
		||||
  const explorers = document.querySelectorAll(".explorer")
 | 
			
		||||
  for (const explorer of explorers) {
 | 
			
		||||
    explorer.classList.toggle("collapsed")
 | 
			
		||||
    explorer.setAttribute(
 | 
			
		||||
  const nearestExplorer = this.closest(".explorer") as HTMLElement
 | 
			
		||||
  if (!nearestExplorer) return
 | 
			
		||||
  nearestExplorer.classList.toggle("collapsed")
 | 
			
		||||
  nearestExplorer.setAttribute(
 | 
			
		||||
    "aria-expanded",
 | 
			
		||||
      explorer.getAttribute("aria-expanded") === "true" ? "false" : "true",
 | 
			
		||||
    nearestExplorer.getAttribute("aria-expanded") === "true" ? "false" : "true",
 | 
			
		||||
  )
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function toggleFolder(evt: MouseEvent) {
 | 
			
		||||
@@ -145,7 +144,7 @@ function createFolderNode(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function setupExplorer(currentSlug: FullSlug) {
 | 
			
		||||
  const allExplorers = document.querySelectorAll(".explorer") as NodeListOf<HTMLElement>
 | 
			
		||||
  const allExplorers = document.querySelectorAll("div.explorer") as NodeListOf<HTMLElement>
 | 
			
		||||
 | 
			
		||||
  for (const explorer of allExplorers) {
 | 
			
		||||
    const dataFns = JSON.parse(explorer.dataset.dataFns || "{}")
 | 
			
		||||
@@ -192,7 +191,7 @@ async function setupExplorer(currentSlug: FullSlug) {
 | 
			
		||||
      collapsed: oldIndex.get(path) === true,
 | 
			
		||||
    }))
 | 
			
		||||
 | 
			
		||||
    const explorerUl = document.getElementById("explorer-ul")
 | 
			
		||||
    const explorerUl = explorer.querySelector(".explorer-ul")
 | 
			
		||||
    if (!explorerUl) continue
 | 
			
		||||
 | 
			
		||||
    // Create and insert new content
 | 
			
		||||
@@ -219,14 +218,12 @@ async function setupExplorer(currentSlug: FullSlug) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Set up event handlers
 | 
			
		||||
    const explorerButtons = explorer.querySelectorAll(
 | 
			
		||||
      "button.explorer-toggle",
 | 
			
		||||
    ) as NodeListOf<HTMLElement>
 | 
			
		||||
    if (explorerButtons) {
 | 
			
		||||
      window.addCleanup(() =>
 | 
			
		||||
        explorerButtons.forEach((button) => button.removeEventListener("click", toggleExplorer)),
 | 
			
		||||
      )
 | 
			
		||||
      explorerButtons.forEach((button) => button.addEventListener("click", toggleExplorer))
 | 
			
		||||
    const explorerButtons = explorer.getElementsByClassName(
 | 
			
		||||
      "explorer-toggle",
 | 
			
		||||
    ) as HTMLCollectionOf<HTMLElement>
 | 
			
		||||
    for (const button of explorerButtons) {
 | 
			
		||||
      button.addEventListener("click", toggleExplorer)
 | 
			
		||||
      window.addCleanup(() => button.removeEventListener("click", toggleExplorer))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Set up folder click handlers
 | 
			
		||||
@@ -235,8 +232,8 @@ async function setupExplorer(currentSlug: FullSlug) {
 | 
			
		||||
        "folder-button",
 | 
			
		||||
      ) as HTMLCollectionOf<HTMLElement>
 | 
			
		||||
      for (const button of folderButtons) {
 | 
			
		||||
        window.addCleanup(() => button.removeEventListener("click", toggleFolder))
 | 
			
		||||
        button.addEventListener("click", toggleFolder)
 | 
			
		||||
        window.addCleanup(() => button.removeEventListener("click", toggleFolder))
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -244,15 +241,15 @@ async function setupExplorer(currentSlug: FullSlug) {
 | 
			
		||||
      "folder-icon",
 | 
			
		||||
    ) as HTMLCollectionOf<HTMLElement>
 | 
			
		||||
    for (const icon of folderIcons) {
 | 
			
		||||
      window.addCleanup(() => icon.removeEventListener("click", toggleFolder))
 | 
			
		||||
      icon.addEventListener("click", toggleFolder)
 | 
			
		||||
      window.addCleanup(() => icon.removeEventListener("click", toggleFolder))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
document.addEventListener("prenav", async (e: CustomEventMap["prenav"]) => {
 | 
			
		||||
document.addEventListener("prenav", async () => {
 | 
			
		||||
  // save explorer scrollTop position
 | 
			
		||||
  const explorer = document.getElementById("explorer-ul")
 | 
			
		||||
  const explorer = document.querySelector(".explorer-ul")
 | 
			
		||||
  if (!explorer) return
 | 
			
		||||
  sessionStorage.setItem("explorerScrollTop", explorer.scrollTop.toString())
 | 
			
		||||
})
 | 
			
		||||
@@ -262,9 +259,8 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
 | 
			
		||||
  await setupExplorer(currentSlug)
 | 
			
		||||
 | 
			
		||||
  // if mobile hamburger is visible, collapse by default
 | 
			
		||||
  const mobileExplorer = document.getElementById("mobile-explorer")
 | 
			
		||||
  if (mobileExplorer && mobileExplorer.checkVisibility()) {
 | 
			
		||||
    for (const explorer of document.querySelectorAll(".explorer")) {
 | 
			
		||||
  for (const explorer of document.getElementsByClassName("mobile-explorer")) {
 | 
			
		||||
    if (explorer.checkVisibility()) {
 | 
			
		||||
      explorer.classList.add("collapsed")
 | 
			
		||||
      explorer.setAttribute("aria-expanded", "false")
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
      margin: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .hide-until-loaded ~ #explorer-content {
 | 
			
		||||
    .hide-until-loaded ~ .explorer-content {
 | 
			
		||||
      display: none;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -30,6 +30,8 @@
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  overflow-y: hidden;
 | 
			
		||||
 | 
			
		||||
  min-height: 1.2rem;
 | 
			
		||||
  flex: 0 1 auto;
 | 
			
		||||
  &.collapsed {
 | 
			
		||||
    flex: 0 1 1.2rem;
 | 
			
		||||
@@ -52,20 +54,20 @@
 | 
			
		||||
    align-self: flex-start;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  button#mobile-explorer {
 | 
			
		||||
  button.mobile-explorer {
 | 
			
		||||
    display: none;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  button#desktop-explorer {
 | 
			
		||||
  button.desktop-explorer {
 | 
			
		||||
    display: flex;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @media all and ($mobile) {
 | 
			
		||||
    button#mobile-explorer {
 | 
			
		||||
    button.mobile-explorer {
 | 
			
		||||
      display: flex;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    button#desktop-explorer {
 | 
			
		||||
    button.desktop-explorer {
 | 
			
		||||
      display: none;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -86,8 +88,8 @@
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button#mobile-explorer,
 | 
			
		||||
button#desktop-explorer {
 | 
			
		||||
button.mobile-explorer,
 | 
			
		||||
button.desktop-explorer {
 | 
			
		||||
  background-color: transparent;
 | 
			
		||||
  border: none;
 | 
			
		||||
  text-align: left;
 | 
			
		||||
@@ -104,7 +106,7 @@ button#desktop-explorer {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#explorer-content {
 | 
			
		||||
.explorer-content {
 | 
			
		||||
  list-style: none;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  overflow-y: auto;
 | 
			
		||||
@@ -209,7 +211,7 @@ li:has(> .folder-outer:not(.open)) > .folder-container > svg {
 | 
			
		||||
    &.collapsed {
 | 
			
		||||
      flex: 0 0 34px;
 | 
			
		||||
 | 
			
		||||
      & > #explorer-content {
 | 
			
		||||
      & > .explorer-content {
 | 
			
		||||
        transform: translateX(-100vw);
 | 
			
		||||
        visibility: hidden;
 | 
			
		||||
      }
 | 
			
		||||
@@ -218,13 +220,13 @@ li:has(> .folder-outer:not(.open)) > .folder-container > svg {
 | 
			
		||||
    &:not(.collapsed) {
 | 
			
		||||
      flex: 0 0 34px;
 | 
			
		||||
 | 
			
		||||
      & > #explorer-content {
 | 
			
		||||
      & > .explorer-content {
 | 
			
		||||
        transform: translateX(0);
 | 
			
		||||
        visibility: visible;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #explorer-content {
 | 
			
		||||
    .explorer-content {
 | 
			
		||||
      box-sizing: border-box;
 | 
			
		||||
      z-index: 100;
 | 
			
		||||
      position: absolute;
 | 
			
		||||
@@ -245,7 +247,7 @@ li:has(> .folder-outer:not(.open)) > .folder-container > svg {
 | 
			
		||||
      visibility: hidden;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #mobile-explorer {
 | 
			
		||||
    .mobile-explorer {
 | 
			
		||||
      margin: 0;
 | 
			
		||||
      padding: 5px;
 | 
			
		||||
      z-index: 101;
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
 | 
			
		||||
  overflow-y: hidden;
 | 
			
		||||
  min-height: 4rem;
 | 
			
		||||
  flex: 0 1 auto;
 | 
			
		||||
  &:has(button.toc-header.collapsed) {
 | 
			
		||||
    flex: 0 1 1.2rem;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import { ComponentType, JSX } from "preact"
 | 
			
		||||
import { StaticResources } from "../util/resources"
 | 
			
		||||
import { StaticResources, StringResource } from "../util/resources"
 | 
			
		||||
import { QuartzPluginData } from "../plugins/vfile"
 | 
			
		||||
import { GlobalConfiguration } from "../cfg"
 | 
			
		||||
import { Node } from "hast"
 | 
			
		||||
@@ -19,9 +19,9 @@ export type QuartzComponentProps = {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
export type QuartzComponent = ComponentType<QuartzComponentProps> & {
 | 
			
		||||
  css?: string
 | 
			
		||||
  beforeDOMLoaded?: string
 | 
			
		||||
  afterDOMLoaded?: string
 | 
			
		||||
  css?: StringResource
 | 
			
		||||
  beforeDOMLoaded?: StringResource
 | 
			
		||||
  afterDOMLoaded?: StringResource
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type QuartzComponentConstructor<Options extends object | undefined = undefined> = (
 | 
			
		||||
 
 | 
			
		||||
@@ -36,17 +36,21 @@ function getComponentResources(ctx: BuildCtx): ComponentResources {
 | 
			
		||||
    afterDOMLoaded: new Set<string>(),
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function normalizeResource(resource: string | string[] | undefined): string[] {
 | 
			
		||||
    if (!resource) return []
 | 
			
		||||
    if (Array.isArray(resource)) return resource
 | 
			
		||||
    return [resource]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (const component of allComponents) {
 | 
			
		||||
    const { css, beforeDOMLoaded, afterDOMLoaded } = component
 | 
			
		||||
    if (css) {
 | 
			
		||||
      componentResources.css.add(css)
 | 
			
		||||
    }
 | 
			
		||||
    if (beforeDOMLoaded) {
 | 
			
		||||
      componentResources.beforeDOMLoaded.add(beforeDOMLoaded)
 | 
			
		||||
    }
 | 
			
		||||
    if (afterDOMLoaded) {
 | 
			
		||||
      componentResources.afterDOMLoaded.add(afterDOMLoaded)
 | 
			
		||||
    }
 | 
			
		||||
    const normalizedCss = normalizeResource(css)
 | 
			
		||||
    const normalizedBeforeDOMLoaded = normalizeResource(beforeDOMLoaded)
 | 
			
		||||
    const normalizedAfterDOMLoaded = normalizeResource(afterDOMLoaded)
 | 
			
		||||
 | 
			
		||||
    normalizedCss.forEach((c) => componentResources.css.add(c))
 | 
			
		||||
    normalizedBeforeDOMLoaded.forEach((b) => componentResources.beforeDOMLoaded.add(b))
 | 
			
		||||
    normalizedAfterDOMLoaded.forEach((a) => componentResources.afterDOMLoaded.add(a))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
 
 | 
			
		||||
@@ -542,7 +542,7 @@ video {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.spacer {
 | 
			
		||||
  flex: 1 1 auto;
 | 
			
		||||
  flex: 2 1 auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div:has(> .overflow) {
 | 
			
		||||
@@ -555,17 +555,14 @@ ol.overflow {
 | 
			
		||||
  max-height: 100%;
 | 
			
		||||
  overflow-y: auto;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  margin-bottom: 0;
 | 
			
		||||
 | 
			
		||||
  // clearfix
 | 
			
		||||
  content: "";
 | 
			
		||||
  clear: both;
 | 
			
		||||
 | 
			
		||||
  & > li:last-of-type {
 | 
			
		||||
    margin-bottom: 30px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  & > li.overflow-end {
 | 
			
		||||
    height: 4px;
 | 
			
		||||
    height: 1rem;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								quartz/util/random.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								quartz/util/random.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
export function randomIdNonSecure() {
 | 
			
		||||
  return Math.random().toString(36).substring(2, 8)
 | 
			
		||||
}
 | 
			
		||||
@@ -65,3 +65,10 @@ export interface StaticResources {
 | 
			
		||||
  js: JSResource[]
 | 
			
		||||
  additionalHead: (JSX.Element | ((pageData: QuartzPluginData) => JSX.Element))[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type StringResource = string | string[] | undefined
 | 
			
		||||
export function concatenateResources(...resources: StringResource[]): StringResource {
 | 
			
		||||
  return resources
 | 
			
		||||
    .filter((resource): resource is string | string[] => resource !== undefined)
 | 
			
		||||
    .flat()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user