From 4310d48f1f103081693482295a9ea2afeb4fc94e Mon Sep 17 00:00:00 2001 From: Ben Schlegel Date: Thu, 21 Sep 2023 21:40:37 +0200 Subject: [PATCH] Squashed commit of the following: commit fa69c2a5656254251b74dbd5545bef000f67af2f 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 (#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 * Update quartz/components/styles/explorer.scss Co-authored-by: Jacky Zhao --------- Co-authored-by: Jacky Zhao commit 8eb1554b13532a2441b41d2018800c56cfa84ce9 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 (#494) * fix(explorer): display name for folders without `index` file * docs(explorer): add section for folder display names commit dcdeae4e7bd527945b887ca347b3b4408c03055b 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 (#493) commit 48452231d5fcd14ef218928bde9ae7e5bc745f4a Author: Jacky Zhao Date: Wed Sep 20 16:09:18 2023 -0700 perf: memoize filetree computation (#490) * perf: memoize filetree computation * format * var -> let commit 16d33fb77193710bede887d6a177d2144b78fb67 Author: Jacky Zhao Date: Wed Sep 20 16:08:54 2023 -0700 feat: display name for folders, expand explorer a little bit (#489) * feat: display name for folders, expand explorer a little bit * update docs commit b029eeadabe0877df6ec11443c68743f1494bc40 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) (#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 6a9e6352e88aa9ff18e5b33cf2de442a250bd960 Author: Jacky Zhao Date: Wed Sep 20 13:52:45 2023 -0700 Revert "feat: Making Quartz available offline by making it a PWA (#465)" This reverts commit d6301fae90d9f922618bf0f413e273156731eef7. commit 70e029d151ccbb9aeab30a0f811b9f529b7f8818 Author: Jacky Zhao Date: Wed Sep 20 13:52:29 2023 -0700 Revert "docs: wording changes for offline support" This reverts commit 52a172d1a4911080444ff797183e29ba8175741e. commit 0bad3ce7990aa4ef417128f9d74c2947fe5117fd Author: Jacky Zhao Date: Wed Sep 20 11:58:52 2023 -0700 docs: document enableToc commit 52a172d1a4911080444ff797183e29ba8175741e Author: Jacky Zhao Date: Wed Sep 20 11:40:36 2023 -0700 docs: wording changes for offline support commit d6301fae90d9f922618bf0f413e273156731eef7 Author: Adam Brangenberg Date: Wed Sep 20 20:38:13 2023 +0200 feat: Making Quartz available offline by making it a PWA (#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 * Removed path.join for manifest path Co-authored-by: Jacky Zhao * 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 * Adding protocol to start_url too then * formatting... * Adding fallback page * Documenting offline plugin * formatting... * merge suggestion Co-authored-by: Jacky Zhao * merge suggestion Co-authored-by: Jacky Zhao * merge suggestion Co-authored-by: Jacky Zhao * merge suggestion Co-authored-by: Jacky Zhao * merge suggestion Co-authored-by: Jacky Zhao * merge suggestion Co-authored-by: Jacky Zhao * merge suggestion Co-authored-by: Jacky Zhao * merge suggestion Co-authored-by: Jacky Zhao * merge suggestion Co-authored-by: Jacky Zhao * merge suggestion Co-authored-by: Jacky Zhao * merge suggestion Co-authored-by: Jacky Zhao * merge suggestion Co-authored-by: Jacky Zhao * 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 commit 27a6087dd5a25dd5031b86b9917adde6ef4b211a Author: rwutscher Date: Tue Sep 19 21:26:30 2023 +0200 fix: tag regex no longer includes purely numerical 'tags' (#485) * fix: tag regex no longer includes purely numerical 'tags' * fix: formatting * fix: use guard in findAndReplace() instead of expanding the regex commit 1bf7e3d8b3966590ebfa3418d6fb2ce6a520c846 Author: Jacky Zhao Date: Tue Sep 19 10:22:39 2023 -0700 fix(nit): make defaultOptions on explorer not a function commit cc31a40b0cb53cba7f51187cb6d68076c3f54c0f Author: David Fischer Date: Tue Sep 19 18:25:51 2023 +0200 feat: support changes in system theme (#484) * feat: support changes in system theme * fix: run prettier * fix: add content/.gitkeep commit 0d3cf2922618774fc397dca8cb92fcf76fb0db02 Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Mon Sep 18 23:32:00 2023 +0200 docs: fix explorer example (#483) --- docs/advanced/index.md | 3 + docs/features/explorer.md | 29 ++++-- docs/features/table of contents.md | 1 + package.json | 2 +- quartz/components/Explorer.tsx | 94 +++++++++++--------- quartz/components/ExplorerNode.tsx | 25 ++++-- quartz/components/scripts/darkmode.inline.ts | 9 ++ quartz/components/scripts/explorer.inline.ts | 8 +- quartz/components/styles/explorer.scss | 32 ++++--- quartz/plugins/transformers/ofm.ts | 4 + quartz/styles/base.scss | 2 +- 11 files changed, 133 insertions(+), 76 deletions(-) create mode 100644 docs/advanced/index.md diff --git a/docs/advanced/index.md b/docs/advanced/index.md new file mode 100644 index 000000000..482258900 --- /dev/null +++ b/docs/advanced/index.md @@ -0,0 +1,3 @@ +--- +title: "Advanced" +--- diff --git a/docs/features/explorer.md b/docs/features/explorer.md index cb63e403a..91766a999 100644 --- a/docs/features/explorer.md +++ b/docs/features/explorer.md @@ -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. > @@ -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"], @@ -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 @@ -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 @@ -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 @@ -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() }, }) ``` @@ -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 @@ -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 } } }, diff --git a/docs/features/table of contents.md b/docs/features/table of contents.md index f05857368..a66c85017 100644 --- a/docs/features/table of contents.md +++ b/docs/features/table of contents.md @@ -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. diff --git a/package.json b/package.json index 4bf721f66..8cb21ca41 100644 --- a/package.json +++ b/package.json @@ -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 ", "license": "MIT", diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx index 0bdb5a650..de6b5e0ae 100644 --- a/quartz/components/Explorer.tsx +++ b/quartz/components/Explorer.tsx @@ -4,17 +4,18 @@ 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 @@ -22,52 +23,63 @@ const defaultOptions = (): Options => ({ return -1 } }, + filterFn: (node) => node.name !== "tags", order: ["filter", "map", "sort"], -}) +} satisfies Options + export default ((userOpts?: Partial) => { - 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)) - // 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) + /** + * 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, + } - // @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]) + // 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]) + } } } + + // 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]) + jsonTree = JSON.stringify(folders) } + } - // 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) - + function Explorer({ allFiles, displayClass, fileData }: QuartzComponentProps) { + constructFileTree(allFiles) return (
diff --git a/quartz/components/ExplorerNode.tsx b/quartz/components/ExplorerNode.tsx index fd0c0823d..e5ceb0bf3 100644 --- a/quartz/components/ExplorerNode.tsx +++ b/quartz/components/ExplorerNode.tsx @@ -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) @@ -145,12 +154,12 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro } return ( -
+
  • {node.file ? ( // Single file node
  • - {node.name} + {node.displayName}
  • ) : ( @@ -174,17 +183,17 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro {/* render tag if folderBehavior is "link", otherwise render )} - +
    )} {/* Recursively render children of folder */} @@ -210,6 +219,6 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro )} - + ) } diff --git a/quartz/components/scripts/darkmode.inline.ts b/quartz/components/scripts/darkmode.inline.ts index e16f4f845..c42a367c9 100644 --- a/quartz/components/scripts/darkmode.inline.ts +++ b/quartz/components/scripts/darkmode.inline.ts @@ -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 + }) }) diff --git a/quartz/components/scripts/explorer.inline.ts b/quartz/components/scripts/explorer.inline.ts index 2b7df7d35..9fe18654f 100644 --- a/quartz/components/scripts/explorer.inline.ts +++ b/quartz/components/scripts/explorer.inline.ts @@ -113,9 +113,11 @@ function setupExplorer() { ) as HTMLElement // Get corresponding content
      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 { diff --git a/quartz/components/styles/explorer.scss b/quartz/components/styles/explorer.scss index 776a5ae6e..28e9f9bb2 100644 --- a/quartz/components/styles/explorer.scss +++ b/quartz/components/styles/explorer.scss @@ -1,4 +1,5 @@ button#explorer { + all: unset; background-color: transparent; border: none; text-align: left; @@ -8,7 +9,7 @@ button#explorer { display: flex; align-items: center; - & h3 { + & h1 { font-size: 1rem; display: inline-block; margin: 0; @@ -58,7 +59,7 @@ button#explorer { max-height 0.35s ease, transform 0.35s ease, opacity 0.2s ease; - & div > li > a { + & li > a { color: var(--dark); opacity: 0.75; pointer-events: all; @@ -80,19 +81,20 @@ svg { align-items: center; user-select: none; - & li > a { - // other selector is more specific, needs important - color: var(--secondary) !important; - opacity: 1 !important; - font-size: 1.05rem !important; + & div > a { + color: var(--secondary); + font-family: var(--headerFont); + font-size: 0.95rem; + font-weight: 600; + line-height: 1.5rem; + display: inline-block; } - & li > a:hover { - // other selector is more specific, needs important - color: var(--tertiary) !important; + & div > a:hover { + color: var(--tertiary); } - & li > button { + & div > button { color: var(--dark); background-color: transparent; border: none; @@ -102,15 +104,15 @@ svg { padding-right: 0; display: flex; align-items: center; + font-family: var(--headerFont); - & h3 { + & p { font-size: 0.95rem; display: inline-block; color: var(--secondary); font-weight: 600; margin: 0; line-height: 1.5rem; - font-weight: bold; pointer-events: none; } } @@ -138,5 +140,7 @@ div:has(> .folder-outer:not(.open)) > .folder-container > svg { #explorer-end { // needs height so IntersectionObserver gets triggered - height: 1px; + height: 4px; + // remove default margin from li + margin: 0; } diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts index 8306f40d8..4d55edad8 100644 --- a/quartz/plugins/transformers/ofm.ts +++ b/quartz/plugins/transformers/ofm.ts @@ -400,6 +400,10 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin return (tree: Root, file) => { const base = pathToRoot(file.data.slug!) findAndReplace(tree, tagRegex, (_value: string, tag: string) => { + // Check if the tag only includes numbers + if (/^\d+$/.test(tag)) { + return false + } tag = slugTag(tag) if (file.data.frontmatter && !file.data.frontmatter.tags.includes(tag)) { file.data.frontmatter.tags.push(tag) diff --git a/quartz/styles/base.scss b/quartz/styles/base.scss index c6925fbe5..9d553622d 100644 --- a/quartz/styles/base.scss +++ b/quartz/styles/base.scss @@ -446,7 +446,7 @@ video { ul.overflow, ol.overflow { - max-height: 300; + max-height: 400; overflow-y: auto; // clearfix