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