feat: support non-singleton explorer
This commit is contained in:
		
							parent
							
								
									dd940a007c
								
							
						
					
					
						commit
						a8001e9554
					
				| @ -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,41 +17,70 @@ const defaultOptions: Options = { | ||||
|   layout: "modern", | ||||
| } | ||||
| 
 | ||||
| const TableOfContents: QuartzComponent = ({ | ||||
|   fileData, | ||||
|   displayClass, | ||||
|   cfg, | ||||
| }: QuartzComponentProps) => { | ||||
|   if (!fileData.toc) { | ||||
|     return null | ||||
| export default ((opts?: Partial<Options>) => { | ||||
|   const layout = opts?.layout ?? defaultOptions.layout | ||||
|   const { OverflowList, overflowListAfterDOMLoaded } = OverflowListFactory() | ||||
|   const TableOfContents: QuartzComponent = ({ | ||||
|     fileData, | ||||
|     displayClass, | ||||
|     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 ( | ||||
|     <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 id="toc-ul"> | ||||
|   TableOfContents.css = modernStyle | ||||
|   TableOfContents.afterDOMLoaded = concatenateResources(script, overflowListAfterDOMLoaded) | ||||
| 
 | ||||
|   const LegacyTableOfContents: QuartzComponent = ({ fileData, cfg }: QuartzComponentProps) => { | ||||
|     if (!fileData.toc) { | ||||
|       return null | ||||
|     } | ||||
|     return ( | ||||
|       <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}> | ||||
| @ -58,38 +88,11 @@ const TableOfContents: QuartzComponent = ({ | ||||
|               </a> | ||||
|             </li> | ||||
|           ))} | ||||
|         </OverflowList> | ||||
|       </div> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
| TableOfContents.css = modernStyle | ||||
| TableOfContents.afterDOMLoaded = script + OverflowList.afterDOMLoaded("toc-ul") | ||||
| 
 | ||||
| const LegacyTableOfContents: QuartzComponent = ({ fileData, cfg }: QuartzComponentProps) => { | ||||
|   if (!fileData.toc) { | ||||
|     return null | ||||
|         </ul> | ||||
|       </details> | ||||
|     ) | ||||
|   } | ||||
|   return ( | ||||
|     <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 | ||||
|   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( | ||||
|       "aria-expanded", | ||||
|       explorer.getAttribute("aria-expanded") === "true" ? "false" : "true", | ||||
|     ) | ||||
|   } | ||||
|   const nearestExplorer = this.closest(".explorer") as HTMLElement | ||||
|   if (!nearestExplorer) return | ||||
|   nearestExplorer.classList.toggle("collapsed") | ||||
|   nearestExplorer.setAttribute( | ||||
|     "aria-expanded", | ||||
|     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() | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user