Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit fa69c2a
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com>
Date:   Thu Sep 21 19:35:11 2023 +0200

    fix(explorer): increase consistency, explicitly use font-family (jackyzha0#496)

    * fix(explorer): display name for folders without `index` file

    * docs(explorer): add section for folder display names

    * docs(explorer): fix broken wikilink

    * fix(consistency): explicitly set font + label/link fix

    Use consistent styling between folders with `folderClickBehavior: "link"` and `"collapse`

    * Update quartz/components/styles/explorer.scss

    Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

    * Update quartz/components/styles/explorer.scss

    Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

    ---------

    Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

commit 8eb1554
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com>
Date:   Thu Sep 21 18:54:33 2023 +0200

    fix(explorer): display names for folders without frontmatter (jackyzha0#494)

    * fix(explorer): display name for folders without `index` file

    * docs(explorer): add section for folder display names

commit dcdeae4
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com>
Date:   Thu Sep 21 18:53:19 2023 +0200

    docs(explorer): update default config + new example (jackyzha0#493)

commit 4845223
Author: Jacky Zhao <j.zhao2k19@gmail.com>
Date:   Wed Sep 20 16:09:18 2023 -0700

    perf: memoize filetree computation (jackyzha0#490)

    * perf: memoize filetree computation

    * format

    * var -> let

commit 16d33fb
Author: Jacky Zhao <j.zhao2k19@gmail.com>
Date:   Wed Sep 20 16:08:54 2023 -0700

    feat: display name for folders, expand explorer a little bit (jackyzha0#489)

    * feat: display name for folders, expand explorer a little bit

    * update docs

commit b029eea
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com>
Date:   Wed Sep 20 22:55:29 2023 +0200

    feat(explorer): improve accessibility and consistency (+ bug fix) (jackyzha0#488)

    * feat(consistency): use `all: unset` on button

    * style: improve accessibility and consistency for explorer

    * fix: localStorage bug with folder name changes

    * chore: bump quartz version

commit 6a9e635
Author: Jacky Zhao <j.zhao2k19@gmail.com>
Date:   Wed Sep 20 13:52:45 2023 -0700

    Revert "feat: Making Quartz available offline by making it a PWA (jackyzha0#465)"

    This reverts commit d6301fa.

commit 70e029d
Author: Jacky Zhao <j.zhao2k19@gmail.com>
Date:   Wed Sep 20 13:52:29 2023 -0700

    Revert "docs: wording changes for offline support"

    This reverts commit 52a172d.

commit 0bad3ce
Author: Jacky Zhao <j.zhao2k19@gmail.com>
Date:   Wed Sep 20 11:58:52 2023 -0700

    docs: document enableToc

commit 52a172d
Author: Jacky Zhao <j.zhao2k19@gmail.com>
Date:   Wed Sep 20 11:40:36 2023 -0700

    docs: wording changes for offline support

commit d6301fa
Author: Adam Brangenberg <adambrangenberg@proton.me>
Date:   Wed Sep 20 20:38:13 2023 +0200

    feat: Making Quartz available offline by making it a PWA (jackyzha0#465)

    * Adding PWA and chaching for offline aviability

    * renamed workbox config to fit Quartz' scheme

    * Documenting new configuration

    * Added missig umami documentation

    * Fixed formatting so the build passes, thank you prettier :)

    * specified caching strategies to improve performance

    * formatting...

    * fixing "404 manifest.json not found" on subdirectories by adding a / to manifestpath

    * turning it into a plugin

    * Removed Workbox-cli and updated @types/node

    * Added Serviceworkercode to offline.ts

    * formatting

    * Removing workbox from docs

    * applied suggestions

    * Removed path.join for sw path

    Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

    * Removed path.join for manifest path

    Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

    * Removing path module import

    * Added absolute path to manifests start_url and manifest "import" using baseUrl

    * Adding protocol to baseurl

    Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

    * Adding protocol to start_url too then

    * formatting...

    * Adding fallback page

    * Documenting offline plugin

    * formatting...

    * merge suggestion

    Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

    * merge suggestion

    Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

    * merge suggestion

    Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

    * merge suggestion

    Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

    * merge suggestion

    Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

    * merge suggestion

    Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

    * merge suggestion

    Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

    * merge suggestion

    Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

    * merge suggestion

    Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

    * merge suggestion

    Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

    * merge suggestion

    Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

    * merge suggestion

    Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

    * formatting...

    * Fixing manifest path, all these nits hiding the actual issues .-.

    * Offline fallback page through plugins, most things taken from 404 Plugin

    * adding Offline Plugin to config

    * formatting...

    * Turned offline off as default and removed offline.md

    ---------

    Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

commit 27a6087
Author: rwutscher <richard.wutscher@gmail.com>
Date:   Tue Sep 19 21:26:30 2023 +0200

    fix: tag regex no longer includes purely numerical 'tags' (jackyzha0#485)

    * fix: tag regex no longer includes purely numerical 'tags'

    * fix: formatting

    * fix: use guard in findAndReplace() instead of expanding the regex

commit 1bf7e3d
Author: Jacky Zhao <j.zhao2k19@gmail.com>
Date:   Tue Sep 19 10:22:39 2023 -0700

    fix(nit): make defaultOptions on explorer not a function

commit cc31a40
Author: David Fischer <david@konst.fish>
Date:   Tue Sep 19 18:25:51 2023 +0200

    feat: support changes in system theme (jackyzha0#484)

    * feat: support changes in system theme

    * fix: run prettier

    * fix: add content/.gitkeep

commit 0d3cf29
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com>
Date:   Mon Sep 18 23:32:00 2023 +0200

    docs: fix explorer example (jackyzha0#483)
  • Loading branch information
benschlegel committed Sep 21, 2023
1 parent 7154462 commit 4310d48
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 75 deletions.
3 changes: 3 additions & 0 deletions docs/advanced/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
title: "Advanced"
---
29 changes: 21 additions & 8 deletions docs/features/explorer.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Quartz features an explorer that allows you to navigate all files and folders on

By default, it shows all folders and files on your page. To display the explorer in a different spot, you can edit the [[layout]].

Display names for folders get determined by the `title` frontmatter field in `folder/index.md` (more detail in [[authoring content | Authoring Content]]). If this file does not exist or does not contain frontmatter, the local folder name will be used instead.

> [!info]
> The explorer uses local storage by default to save the state of your explorer. This is done to ensure a smooth experience when navigating to different pages.
>
Expand All @@ -29,7 +31,7 @@ Component.Explorer({
sortFn: (a, b) => {
... // default implementation shown later
},
filterFn: undefined,
filterFn: filterFn: (node) => node.name !== "tags", // filters out 'tags' folder
mapFn: undefined,
// what order to apply functions in
order: ["filter", "map", "sort"],
Expand Down Expand Up @@ -57,7 +59,8 @@ All functions you can pass work with the `FileNode` class, which has the followi
```ts title="quartz/components/ExplorerNode.tsx" {2-5}
export class FileNode {
children: FileNode[] // children of current node
name: string // name of node (only useful for folders)
name: string // last part of slug
displayName: string // what actually should be displayed in the explorer
file: QuartzPluginData | null // set if node is a file, see `QuartzPluginData` for more detail
depth: number // depth of current node

Expand All @@ -72,7 +75,7 @@ Every function you can pass is optional. By default, only a `sort` function will
Component.Explorer({
sortFn: (a, b) => {
if ((!a.file && !b.file) || (a.file && b.file)) {
return a.name.localeCompare(b.name)
return a.displayName.localeCompare(b.displayName)
}
if (a.file && !b.file) {
return 1
Expand Down Expand Up @@ -120,7 +123,7 @@ Using this example, the explorer will alphabetically sort everything, but put al
Component.Explorer({
sortFn: (a, b) => {
if ((!a.file && !b.file) || (a.file && b.file)) {
return a.name.localeCompare(b.name)
return a.displayName.localeCompare(b.displayName)
}
if (a.file && !b.file) {
return -1
Expand All @@ -138,7 +141,7 @@ Using this example, the display names of all `FileNodes` (folders + files) will
```ts title="quartz.layout.ts"
Component.Explorer({
mapFn: (node) => {
node.name = node.name.toUpperCase()
node.displayName = node.displayName.toUpperCase()
},
})
```
Expand All @@ -152,13 +155,23 @@ Component.Explorer({
filterFn: (node) => {
// set containing names of everything you want to filter out
const omit = new Set(["authoring content", "tags", "hosting"])
return omit.has(node.name.toLowerCase())
return !omit.has(node.name.toLowerCase())
},
})
```

You can customize this by changing the entries of the `omit` set. Simply add all folder or file names you want to remove.

### Show every element in explorer

To override the default filter function that removes the `tags` folder from the explorer, you can set the filter function to `undefined`.

```ts title="quartz.layout.ts"
Component.Explorer({
filterFn: undefined, // apply no filter function, every file and folder will visible
})
```

## Advanced examples

### Add emoji prefix
Expand All @@ -172,9 +185,9 @@ Component.Explorer({
if (node.depth > 0) {
// set emoji for file/folder
if (node.file) {
node.name = "📄 " + node.name
node.displayName = "📄 " + node.displayName
} else {
node.name = "📁 " + node.name
node.displayName = "📁 " + node.displayName
}
}
},
Expand Down
1 change: 1 addition & 0 deletions docs/features/table of contents.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ tags:
Quartz can automatically generate a table of contents from a list of headings on each page. It will also show you your current scroll position on the site by marking headings you've scrolled through with a different colour.

By default, it will show all headers from H1 (`# Title`) all the way to H3 (`### Title`) and will only show the table of contents if there is more than 1 header on the page.
You can also hide the table of contents on a page by adding `showToc: false` to the frontmatter for that page.

> [!info]
> This feature requires both `Plugin.TableOfContents` in your `quartz.config.ts` and `Component.TableOfContents` in your `quartz.layout.ts` to function correctly.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@jackyzha0/quartz",
"description": "🌱 publish your digital garden and notes as a website",
"private": true,
"version": "4.0.11",
"version": "4.1.0",
"type": "module",
"author": "jackyzha0 <j.zhao2k19@gmail.com>",
"license": "MIT",
Expand Down
92 changes: 52 additions & 40 deletions quartz/components/Explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,70 +4,82 @@ import explorerStyle from "./styles/explorer.scss"
// @ts-ignore
import script from "./scripts/explorer.inline"
import { ExplorerNode, FileNode, Options } from "./ExplorerNode"
import { QuartzPluginData } from "../plugins/vfile"

// Options interface defined in `ExplorerNode` to avoid circular dependency
const defaultOptions = (): Options => ({
const defaultOptions = {
title: "Explorer",
folderClickBehavior: "collapse",
folderDefaultState: "collapsed",
useSavedState: true,
// Sort order: folders first, then files. Sort folders and files alphabetically
sortFn: (a, b) => {
// Sort order: folders first, then files. Sort folders and files alphabetically
if ((!a.file && !b.file) || (a.file && b.file)) {
return a.name.localeCompare(b.name)
return a.displayName.localeCompare(b.displayName)
}
if (a.file && !b.file) {
return 1
} else {
return -1
}
},
filterFn: (node) => node.name !== "tags",
order: ["filter", "map", "sort"],
})
} satisfies Options

export default ((userOpts?: Partial<Options>) => {
function Explorer({ allFiles, displayClass, fileData }: QuartzComponentProps) {
// Parse config
const opts: Options = { ...defaultOptions(), ...userOpts }
// Parse config
const opts: Options = { ...defaultOptions, ...userOpts }

// Construct tree from allFiles
const fileTree = new FileNode("")
allFiles.forEach((file) => fileTree.add(file, 1))
// memoized
let fileTree: FileNode
let jsonTree: string

/**
* Keys of this object must match corresponding function name of `FileNode`,
* while values must be the argument that will be passed to the function.
*
* e.g. entry for FileNode.sort: `sort: opts.sortFn` (value is sort function from options)
*/
const functions = {
map: opts.mapFn,
sort: opts.sortFn,
filter: opts.filterFn,
}
function constructFileTree(allFiles: QuartzPluginData[]) {
if (!fileTree) {
// Construct tree from allFiles
fileTree = new FileNode("")
allFiles.forEach((file) => fileTree.add(file, 1))

/**
* Keys of this object must match corresponding function name of `FileNode`,
* while values must be the argument that will be passed to the function.
*
* e.g. entry for FileNode.sort: `sort: opts.sortFn` (value is sort function from options)
*/
const functions = {
map: opts.mapFn,
sort: opts.sortFn,
filter: opts.filterFn,
}

// 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 (functions[functionName]) {
// for every entry in order, call matching function in FileNode and pass matching argument
// e.g. i = 0; functionName = "filter"
// converted to: (if opts.filterFn) => fileTree.filter(opts.filterFn)
// 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 (functions[functionName]) {
// for every entry in order, call matching function in FileNode and pass matching argument
// e.g. i = 0; functionName = "filter"
// converted to: (if opts.filterFn) => fileTree.filter(opts.filterFn)

// @ts-ignore
// typescript cant statically check these dynamic references, so manually make sure reference is valid and ignore warning
fileTree[functionName].call(fileTree, functions[functionName])
// @ts-ignore
// typescript cant statically check these dynamic references, so manually make sure reference is valid and ignore warning
fileTree[functionName].call(fileTree, functions[functionName])
}
}
}
}

// Get all folders of tree. Initialize with collapsed state
const folders = fileTree.getFolderPaths(opts.folderDefaultState === "collapsed")
// Get all folders of tree. Initialize with collapsed state
const folders = fileTree.getFolderPaths(opts.folderDefaultState === "collapsed")

// Stringify to pass json tree as data attribute ([data-tree])
const jsonTree = JSON.stringify(folders)
// Stringify to pass json tree as data attribute ([data-tree])
jsonTree = JSON.stringify(folders)
}
}

function Explorer({ allFiles, displayClass, fileData }: QuartzComponentProps) {
constructFileTree(allFiles)
return (
<div class={`explorer ${displayClass}`}>
<button
Expand All @@ -78,7 +90,7 @@ export default ((userOpts?: Partial<Options>) => {
data-savestate={opts.useSavedState}
data-tree={jsonTree}
>
<h3>{opts.title}</h3>
<h1>{opts.title}</h1>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
Expand All @@ -97,7 +109,7 @@ export default ((userOpts?: Partial<Options>) => {
<div id="explorer-content">
<ul class="overflow" id="explorer-ul">
<ExplorerNode node={fileTree} opts={opts} fileData={fileData} />
<div id="explorer-end" />
<li id="explorer-end" />
</ul>
</div>
</div>
Expand Down
25 changes: 17 additions & 8 deletions quartz/components/ExplorerNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,28 @@ export type FolderState = {
export class FileNode {
children: FileNode[]
name: string
displayName: string
file: QuartzPluginData | null
depth: number

constructor(name: string, file?: QuartzPluginData, depth?: number) {
this.children = []
this.name = name
this.displayName = name
this.file = file ? structuredClone(file) : null
this.depth = depth ?? 0
}

private insert(file: DataWrapper) {
if (file.path.length === 1) {
this.children.push(new FileNode(file.file.frontmatter!.title, file.file, this.depth + 1))
if (file.path[0] !== "index.md") {
this.children.push(new FileNode(file.file.frontmatter!.title, file.file, this.depth + 1))
} else {
const title = file.file.frontmatter?.title
if (title && title !== "index" && file.path[0] === "index.md") {
this.displayName = title
}
}
} else {
const next = file.path[0]
file.path = file.path.splice(1)
Expand Down Expand Up @@ -145,12 +154,12 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro
}

return (
<div>
<li>
{node.file ? (
// Single file node
<li key={node.file.slug}>
<a href={resolveRelative(fileData.slug!, node.file.slug!)} data-for={node.file.slug}>
{node.name}
{node.displayName}
</a>
</li>
) : (
Expand All @@ -174,17 +183,17 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
{/* render <a> tag if folderBehavior is "link", otherwise render <button> with collapse click event */}
<li key={node.name} data-folderpath={folderPath}>
<div key={node.name} data-folderpath={folderPath}>
{folderBehavior === "link" ? (
<a href={`${folderPath}`} data-for={node.name} class="folder-title">
{node.name}
{node.displayName}
</a>
) : (
<button class="folder-button">
<h3 class="folder-title">{node.name}</h3>
<p class="folder-title">{node.displayName}</p>
</button>
)}
</li>
</div>
</div>
)}
{/* Recursively render children of folder */}
Expand All @@ -210,6 +219,6 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro
</div>
</div>
)}
</div>
</li>
)
}
9 changes: 9 additions & 0 deletions quartz/components/scripts/darkmode.inline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,13 @@ document.addEventListener("nav", () => {
if (currentTheme === "dark") {
toggleSwitch.checked = true
}

// Listen for changes in prefers-color-scheme
const colorSchemeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
colorSchemeMediaQuery.addEventListener("change", (e) => {
const newTheme = e.matches ? "dark" : "light"
document.documentElement.setAttribute("saved-theme", newTheme)
localStorage.setItem("theme", newTheme)
toggleSwitch.checked = e.matches
})
})
8 changes: 5 additions & 3 deletions quartz/components/scripts/explorer.inline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,11 @@ function setupExplorer() {
) as HTMLElement

// Get corresponding content <ul> tag and set state
const folderUL = folderLi.parentElement?.nextElementSibling
if (folderUL) {
setFolderState(folderUL as HTMLElement, folderUl.collapsed)
if (folderLi) {
const folderUL = folderLi.parentElement?.nextElementSibling
if (folderUL) {
setFolderState(folderUL as HTMLElement, folderUl.collapsed)
}
}
})
} else {
Expand Down
Loading

0 comments on commit 4310d48

Please sign in to comment.