feat: support non-singleton explorer
This commit is contained in:
parent
dd940a007c
commit
a8001e9554
15 changed files with 168 additions and 146 deletions
|
@ -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()
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue