From c538c151c7462ad0395ff2c15c5e11e89e362aa8 Mon Sep 17 00:00:00 2001
From: Striven <sg.striven@cutecat.club>
Date: Sat, 04 Apr 2026 19:47:16 +0000
Subject: [PATCH] Initial commit
---
quartz/components/Explorer.tsx | 175 +++++++++++++++++++++++++++++++++++-----------------------
1 files changed, 106 insertions(+), 69 deletions(-)
diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx
index ec7c48e..e4cbcab 100644
--- a/quartz/components/Explorer.tsx
+++ b/quartz/components/Explorer.tsx
@@ -1,24 +1,37 @@
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
-import explorerStyle from "./styles/explorer.scss"
+import style from "./styles/explorer.scss"
// @ts-ignore
import script from "./scripts/explorer.inline"
-import { ExplorerNode, FileNode, Options } from "./ExplorerNode"
-import { QuartzPluginData } from "../plugins/vfile"
import { classNames } from "../util/lang"
import { i18n } from "../i18n"
+import { FileTrieNode } from "../util/fileTrie"
+import OverflowListFactory from "./OverflowList"
+import { concatenateResources } from "../util/resources"
-// Options interface defined in `ExplorerNode` to avoid circular dependency
-const defaultOptions = {
- folderClickBehavior: "collapse",
+type OrderEntries = "sort" | "filter" | "map"
+
+export interface Options {
+ title?: string
+ folderDefaultState: "collapsed" | "open"
+ folderClickBehavior: "collapse" | "link"
+ useSavedState: boolean
+ sortFn: (a: FileTrieNode, b: FileTrieNode) => number
+ filterFn: (node: FileTrieNode) => boolean
+ mapFn: (node: FileTrieNode) => void
+ order: OrderEntries[]
+}
+
+const defaultOptions: Options = {
folderDefaultState: "collapsed",
+ folderClickBehavior: "link",
useSavedState: true,
mapFn: (node) => {
return node
},
sortFn: (a, b) => {
- // Sort order: folders first, then files. Sort folders and files alphabetically
- if ((!a.file && !b.file) || (a.file && b.file)) {
+ // Sort order: folders first, then files. Sort folders and files alphabeticall
+ if ((!a.isFolder && !b.isFolder) || (a.isFolder && b.isFolder)) {
// numeric: true: Whether numeric collation should be used, such that "1" < "2" < "10"
// sensitivity: "base": Only strings that differ in base letters compare as unequal. Examples: a ≠ b, a = á, a = A
return a.displayName.localeCompare(b.displayName, undefined, {
@@ -27,74 +40,68 @@
})
}
- if (a.file && !b.file) {
+ if (!a.isFolder && b.isFolder) {
return 1
} else {
return -1
}
},
- filterFn: (node) => node.name !== "tags",
+ filterFn: (node) => node.slugSegment !== "tags",
order: ["filter", "map", "sort"],
-} satisfies Options
+}
+export type FolderState = {
+ path: string
+ collapsed: boolean
+}
+
+let numExplorers = 0
export default ((userOpts?: Partial<Options>) => {
- // Parse config
const opts: Options = { ...defaultOptions, ...userOpts }
+ const { OverflowList, overflowListAfterDOMLoaded } = OverflowListFactory()
- // memoized
- let fileTree: FileNode
- let jsonTree: string
- let lastBuildId: string = ""
-
- function constructFileTree(allFiles: QuartzPluginData[]) {
- // Construct tree from allFiles
- fileTree = new FileNode("")
- allFiles.forEach((file) => fileTree.add(file))
-
- // Execute all functions (sort, filter, map) that were provided (if none were provided, only default "sort" is applied)
- if (opts.order) {
- // Order is important, use loop with index instead of order.map()
- for (let i = 0; i < opts.order.length; i++) {
- const functionName = opts.order[i]
- if (functionName === "map") {
- fileTree.map(opts.mapFn)
- } else if (functionName === "sort") {
- fileTree.sort(opts.sortFn)
- } else if (functionName === "filter") {
- fileTree.filter(opts.filterFn)
- }
- }
- }
-
- // Get all folders of tree. Initialize with collapsed state
- // Stringify to pass json tree as data attribute ([data-tree])
- const folders = fileTree.getFolderPaths(opts.folderDefaultState === "collapsed")
- jsonTree = JSON.stringify(folders)
- }
-
- const Explorer: QuartzComponent = ({
- ctx,
- cfg,
- allFiles,
- displayClass,
- fileData,
- }: QuartzComponentProps) => {
- if (ctx.buildId !== lastBuildId) {
- lastBuildId = ctx.buildId
- constructFileTree(allFiles)
- }
+ const Explorer: QuartzComponent = ({ cfg, displayClass }: QuartzComponentProps) => {
+ const id = `explorer-${numExplorers++}`
return (
- <div class={classNames(displayClass, "explorer")}>
+ <div
+ class={classNames(displayClass, "explorer")}
+ data-behavior={opts.folderClickBehavior}
+ data-collapsed={opts.folderDefaultState}
+ data-savestate={opts.useSavedState}
+ data-data-fns={JSON.stringify({
+ order: opts.order,
+ sortFn: opts.sortFn.toString(),
+ filterFn: opts.filterFn.toString(),
+ mapFn: opts.mapFn.toString(),
+ })}
+ >
<button
type="button"
- id="explorer"
- data-behavior={opts.folderClickBehavior}
- data-collapsed={opts.folderDefaultState}
- data-savestate={opts.useSavedState}
- data-tree={jsonTree}
- aria-controls="explorer-content"
- aria-expanded={opts.folderDefaultState === "open"}
+ class="explorer-toggle mobile-explorer hide-until-loaded"
+ data-mobile={true}
+ aria-controls={id}
+ >
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="24"
+ height="24"
+ viewBox="0 0 24 24"
+ stroke-width="2"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ class="lucide-menu"
+ >
+ <line x1="4" x2="20" y1="12" y2="12" />
+ <line x1="4" x2="20" y1="6" y2="6" />
+ <line x1="4" x2="20" y1="18" y2="18" />
+ </svg>
+ </button>
+ <button
+ type="button"
+ class="title-button explorer-toggle desktop-explorer"
+ data-mobile={false}
+ aria-expanded={true}
>
<h2>{opts.title ?? i18n(cfg.locale).components.explorer.title}</h2>
<svg
@@ -112,17 +119,47 @@
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
- <div id="explorer-content">
- <ul class="overflow" id="explorer-ul">
- <ExplorerNode node={fileTree} opts={opts} fileData={fileData} />
- <li id="explorer-end" />
- </ul>
+ <div id={id} class="explorer-content" aria-expanded={false} role="group">
+ <OverflowList class="explorer-ul" />
</div>
+ <template id="template-file">
+ <li>
+ <a href="#"></a>
+ </li>
+ </template>
+ <template id="template-folder">
+ <li>
+ <div class="folder-container">
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="12"
+ height="12"
+ viewBox="5 8 14 8"
+ fill="none"
+ stroke="currentColor"
+ stroke-width="2"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ class="folder-icon"
+ >
+ <polyline points="6 9 12 15 18 9"></polyline>
+ </svg>
+ <div>
+ <button class="folder-button">
+ <span class="folder-title"></span>
+ </button>
+ </div>
+ </div>
+ <div class="folder-outer">
+ <ul class="content"></ul>
+ </div>
+ </li>
+ </template>
</div>
)
}
- Explorer.css = explorerStyle
- Explorer.afterDOMLoaded = script
+ Explorer.css = style
+ Explorer.afterDOMLoaded = concatenateResources(script, overflowListAfterDOMLoaded)
return Explorer
}) satisfies QuartzComponentConstructor
--
Gitblit v1.10.0