From 3c9c7e85467bbbe3b12ab5df078fc03d23d7d18c Mon Sep 17 00:00:00 2001 From: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Mon, 18 Sep 2023 23:32:00 +0200 Subject: [PATCH 01/16] docs: fix explorer example (#483) --- docs/features/explorer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/explorer.md b/docs/features/explorer.md index cb63e403a..6f941b871 100644 --- a/docs/features/explorer.md +++ b/docs/features/explorer.md @@ -152,7 +152,7 @@ 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()) }, }) ``` From a4c783010b824b0e2529ebe2caf7cea4b331a95a Mon Sep 17 00:00:00 2001 From: David Fischer Date: Tue, 19 Sep 2023 18:25:51 +0200 Subject: [PATCH 02/16] feat: support changes in system theme (#484) * feat: support changes in system theme * fix: run prettier * fix: add content/.gitkeep --- quartz/components/scripts/darkmode.inline.ts | 9 +++++++++ 1 file changed, 9 insertions(+) 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 + }) }) From 5bd68a44debef32bc4e997f48c51b9e200565e07 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Tue, 19 Sep 2023 10:22:39 -0700 Subject: [PATCH 03/16] fix(nit): make defaultOptions on explorer not a function --- quartz/components/Explorer.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx index 0bdb5a650..8597075d2 100644 --- a/quartz/components/Explorer.tsx +++ b/quartz/components/Explorer.tsx @@ -6,7 +6,7 @@ import script from "./scripts/explorer.inline" import { ExplorerNode, FileNode, Options } from "./ExplorerNode" // Options interface defined in `ExplorerNode` to avoid circular dependency -const defaultOptions = (): Options => ({ +const defaultOptions = { title: "Explorer", folderClickBehavior: "collapse", folderDefaultState: "collapsed", @@ -23,11 +23,12 @@ const defaultOptions = (): Options => ({ } }, order: ["filter", "map", "sort"], -}) +} satisfies Options + export default ((userOpts?: Partial) => { function Explorer({ allFiles, displayClass, fileData }: QuartzComponentProps) { // Parse config - const opts: Options = { ...defaultOptions(), ...userOpts } + const opts: Options = { ...defaultOptions, ...userOpts } // Construct tree from allFiles const fileTree = new FileNode("") From 68537a39037909ff6963a5fb719fbf3485e5ac25 Mon Sep 17 00:00:00 2001 From: rwutscher Date: Tue, 19 Sep 2023 21:26:30 +0200 Subject: [PATCH 04/16] 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 --- quartz/plugins/transformers/ofm.ts | 4 ++++ 1 file changed, 4 insertions(+) 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) From d3fa6f85989af492eddbf98050adbc7b45084a1f Mon Sep 17 00:00:00 2001 From: Adam Brangenberg Date: Wed, 20 Sep 2023 20:38:13 +0200 Subject: [PATCH 05/16] 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 --- docs/configuration.md | 2 + docs/features/offline access.md | 31 ++++++ docs/index.md | 2 +- package-lock.json | 9 +- package.json | 2 +- quartz.config.ts | 1 + quartz/cfg.ts | 1 + quartz/components/Head.tsx | 4 + .../components/pages/OfflineFallbackPage.tsx | 12 +++ quartz/plugins/emitters/componentResources.ts | 5 + quartz/plugins/emitters/index.ts | 1 + quartz/plugins/emitters/offline.ts | 97 +++++++++++++++++++ quartz/static/icon.svg | 74 ++++++++++++++ 13 files changed, 235 insertions(+), 6 deletions(-) create mode 100644 docs/features/offline access.md create mode 100644 quartz/components/pages/OfflineFallbackPage.tsx create mode 100644 quartz/plugins/emitters/offline.ts create mode 100644 quartz/static/icon.svg diff --git a/docs/configuration.md b/docs/configuration.md index 047f6ca6b..35e0b9d95 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -21,10 +21,12 @@ const config: QuartzConfig = { This part of the configuration concerns anything that can affect the whole site. The following is a list breaking down all the things you can configure: - `pageTitle`: title of the site. This is also used when generating the [[RSS Feed]] for your site. +- `description`: description of the site. This will be used when someone installs your site as an App. - `enableSPA`: whether to enable [[SPA Routing]] on your site. - `enablePopovers`: whether to enable [[popover previews]] on your site. - `analytics`: what to use for analytics on your site. Values can be - `null`: don't use analytics; + - `{ provider: "umami", websiteId: }`: easy, privacy-friendly, open source, GDPR Compliant analytics; - `{ provider: 'plausible' }`: use [Plausible](https://plausible.io/), a privacy-friendly alternative to Google Analytics; or - `{ provider: 'google', tagId: }`: use Google Analytics - `baseUrl`: this is used for sitemaps and RSS feeds that require an absolute URL to know where the canonical 'home' of your site lives. This is normally the deployed URL of your site (e.g. `quartz.jzhao.xyz` for this site). Do not include the protocol (i.e. `https://`) or any leading or trailing slashes. diff --git a/docs/features/offline access.md b/docs/features/offline access.md new file mode 100644 index 000000000..dcffdcd26 --- /dev/null +++ b/docs/features/offline access.md @@ -0,0 +1,31 @@ +--- +title: "Offline Access (PWA)" +tags: + - plugin/emitter +--- + +This plugin allows your website to be accessible offline and be installed as an app. You can use it by adding `Plugin.Offline(),` to the `emitters` in `quartz.config.ts` + +## Offline Capability + +Whenever you visit a page it gets cached for offline use. Depending on the kind of content, the process for caching is diffent: + +- **Pages** (HTML, your converted Markdown files): Quartz first tries to get them over the Network. If that fails, your browser attempts to fetch it from the cache. +- **Static Resources** (Fonts, CSS Styling, JavaScript): Quartz uses cached resources by default and updates the cache over the network in the background. +- **Images**: Images are saved once and then served from cache. Quartz uses a limited cache of 60 images and images remain in the cache for 30 days + +You can edit the fallback page by changing the `offline.md` file in the root of your `content` directory + +## Progressive Web App (PWA) + +Progressive Web Apps can have [many properties](https://developer.mozilla.org/en-US/docs/Web/Manifest). We're only going to mention the ones Quartz supports by default, however you can edit the offline plugins file to add more in case required. + +- **icons**: the `icon.svg` file in the `quartz/static` directory is used for all the icons. This makes it easier to scale the image since you don't need to provide an png for every size +- **name**, **short_name**: Uses the `pageTitle` configured in `quartz.config.ts` +- **description**: Uses the `description` configured in `quartz.config.ts` +- **background_color**, **theme_color**: Uses the `lightMode.light` color configured in `quartz.config.ts`. +- **start_url**: Uses the `baseUrl` configured in `quartz.config.ts` + +### Default values + +- **display**: this is set to `minimal-ui` diff --git a/docs/index.md b/docs/index.md index 05de2bae9..570d5b364 100644 --- a/docs/index.md +++ b/docs/index.md @@ -30,7 +30,7 @@ This will guide you through initializing your Quartz with content. Once you've d ## 🔧 Features -- [[Obsidian compatibility]], [[full-text search]], [[graph view]], note transclusion, [[wikilinks]], [[backlinks]], [[Latex]], [[syntax highlighting]], [[popover previews]], and [many more](./features) right out of the box +- [[Obsidian compatibility]], [[full-text search]], [[graph view]], note transclusion, [[wikilinks]], [[backlinks]], [[Latex]], [[syntax highlighting]], [[popover previews]], [[offline access]] and [many more](./features) right out of the box - Hot-reload for both configuration and content - Simple JSX layouts and [[creating components|page components]] - [[SPA Routing|Ridiculously fast page loads]] and tiny bundle sizes diff --git a/package-lock.json b/package-lock.json index a87907897..8ff94245d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,7 +73,7 @@ "@types/flexsearch": "^0.7.3", "@types/hast": "^2.3.4", "@types/js-yaml": "^4.0.5", - "@types/node": "^20.1.2", + "@types/node": "^20.6.2", "@types/pretty-time": "^1.1.2", "@types/source-map-support": "^0.5.6", "@types/workerpool": "^6.4.0", @@ -113,6 +113,7 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", + "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -1463,9 +1464,9 @@ } }, "node_modules/@types/node": { - "version": "20.3.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz", - "integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==", + "version": "20.6.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.2.tgz", + "integrity": "sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==", "dev": true }, "node_modules/@types/parse5": { diff --git a/package.json b/package.json index 0a2085cef..e514edfbd 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "@types/flexsearch": "^0.7.3", "@types/hast": "^2.3.4", "@types/js-yaml": "^4.0.5", - "@types/node": "^20.1.2", + "@types/node": "^20.6.2", "@types/pretty-time": "^1.1.2", "@types/source-map-support": "^0.5.6", "@types/workerpool": "^6.4.0", diff --git a/quartz.config.ts b/quartz.config.ts index f677a18f9..5a1f643aa 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -4,6 +4,7 @@ import * as Plugin from "./quartz/plugins" const config: QuartzConfig = { configuration: { pageTitle: "ðŸŠī Quartz 4.0", + description: "Quartz Documentation Page and Demo", enableSPA: true, enablePopovers: true, analytics: { diff --git a/quartz/cfg.ts b/quartz/cfg.ts index 8371b5e2b..73e959fb7 100644 --- a/quartz/cfg.ts +++ b/quartz/cfg.ts @@ -19,6 +19,7 @@ export type Analytics = export interface GlobalConfiguration { pageTitle: string + description: string /** Whether to enable single-page-app style rendering. this prevents flashes of unstyled content and improves smoothness of Quartz */ enableSPA: boolean /** Whether to display Wikipedia-style popovers when hovering over links */ diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx index 2bf263817..972f7497e 100644 --- a/quartz/components/Head.tsx +++ b/quartz/components/Head.tsx @@ -14,6 +14,8 @@ export default (() => { const iconPath = joinSegments(baseDir, "static/icon.png") const ogImagePath = `https://${cfg.baseUrl}/static/og-image.png` + const manifest = + cfg.baseUrl == undefined ? "/manifest.json" : `https://${cfg.baseUrl}/manifest.json` return ( @@ -25,7 +27,9 @@ export default (() => { {cfg.baseUrl && } + + diff --git a/quartz/components/pages/OfflineFallbackPage.tsx b/quartz/components/pages/OfflineFallbackPage.tsx new file mode 100644 index 000000000..14d4f5e9e --- /dev/null +++ b/quartz/components/pages/OfflineFallbackPage.tsx @@ -0,0 +1,12 @@ +import { QuartzComponentConstructor } from "../types" + +function OfflineFallbackPage() { + return ( +
+

Offline

+

This page isn't offline available yet.

+
+ ) +} + +export default (() => OfflineFallbackPage) satisfies QuartzComponentConstructor diff --git a/quartz/plugins/emitters/componentResources.ts b/quartz/plugins/emitters/componentResources.ts index 1290a3548..a82e7c12a 100644 --- a/quartz/plugins/emitters/componentResources.ts +++ b/quartz/plugins/emitters/componentResources.ts @@ -116,6 +116,11 @@ function addGlobalPageResources( document.dispatchEvent(event)`) } + componentResources.afterDOMLoaded.push(` + if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/sw.js'); + }`) + let wsUrl = `ws://localhost:${ctx.argv.wsPort}` if (ctx.argv.remoteDevHost) { diff --git a/quartz/plugins/emitters/index.ts b/quartz/plugins/emitters/index.ts index 99a2c54d5..6de824d5f 100644 --- a/quartz/plugins/emitters/index.ts +++ b/quartz/plugins/emitters/index.ts @@ -7,3 +7,4 @@ export { Assets } from "./assets" export { Static } from "./static" export { ComponentResources } from "./componentResources" export { NotFoundPage } from "./404" +export { Offline } from "./offline" diff --git a/quartz/plugins/emitters/offline.ts b/quartz/plugins/emitters/offline.ts new file mode 100644 index 000000000..e3c654b69 --- /dev/null +++ b/quartz/plugins/emitters/offline.ts @@ -0,0 +1,97 @@ +import { QuartzEmitterPlugin } from "../types" +import { FilePath, FullSlug } from "../../util/path" +import { FullPageLayout } from "../../cfg" +import { sharedPageComponents } from "../../../quartz.layout" +import OfflineFallbackPage from "../../components/pages/OfflineFallbackPage" +import BodyConstructor from "../../components/Body" +import { pageResources, renderPage } from "../../components/renderPage" +import { defaultProcessedContent } from "../vfile" +import { QuartzComponentProps } from "../../components/types" + +export const Offline: QuartzEmitterPlugin = () => { + const opts: FullPageLayout = { + ...sharedPageComponents, + pageBody: OfflineFallbackPage(), + beforeBody: [], + left: [], + right: [], + } + + const { head: Head, pageBody, footer: Footer } = opts + const Body = BodyConstructor() + + return { + name: "OfflineSupport", + getQuartzComponents() { + return [Head, Body, pageBody, Footer] + }, + async emit({ cfg }, _content, resources, emit): Promise { + const manifest = { + short_name: cfg.configuration.pageTitle, + name: cfg.configuration.pageTitle, + description: cfg.configuration.description, + background_color: cfg.configuration.theme.colors.lightMode.light, + theme_color: cfg.configuration.theme.colors.lightMode.light, + display: "minimal-ui", + icons: [ + { + src: "static/icon.svg", + sizes: "any", + purpose: "maskable", + }, + { + src: "static/icon.svg", + sizes: "any", + purpose: "any", + }, + ], + start_url: + cfg.configuration.baseUrl == undefined ? "/" : `https://${cfg.configuration.baseUrl}/`, + } + + const serviceWorker = + "importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js');" + + "const {pageCache, imageCache, staticResourceCache, googleFontsCache, offlineFallback} = workbox.recipes;" + + "pageCache(); googleFontsCache(); staticResourceCache(); imageCache(); offlineFallback();" + + const slug = "offline" as FullSlug + + const url = new URL(`https://${cfg.configuration.baseUrl ?? "example.com"}`) + const path = url.pathname as FullSlug + const externalResources = pageResources(path, resources) + const [tree, vfile] = defaultProcessedContent({ + slug, + text: "Offline", + description: "This page isn't offline available yet.", + frontmatter: { title: "Offline", tags: [] }, + }) + + const componentData: QuartzComponentProps = { + fileData: vfile.data, + externalResources, + cfg: cfg.configuration, + children: [], + tree, + allFiles: [], + } + + return Promise.all([ + emit({ + content: JSON.stringify(manifest), + slug: "manifest" as FullSlug, + ext: ".json", + }), + emit({ + content: serviceWorker, + slug: "sw" as FullSlug, + ext: ".js", + }), + emit({ + content: renderPage(slug, componentData, opts, externalResources), + slug, + ext: ".html", + }), + ]) + }, + } +} diff --git a/quartz/static/icon.svg b/quartz/static/icon.svg new file mode 100644 index 000000000..c6ecfa2db --- /dev/null +++ b/quartz/static/icon.svg @@ -0,0 +1,74 @@ + + + + + + + + From 09c5d42fa937675acb99fad41a065849f70018f8 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Wed, 20 Sep 2023 11:40:36 -0700 Subject: [PATCH 06/16] docs: wording changes for offline support --- docs/features/offline access.md | 2 +- quartz/components/Explorer.tsx | 1 + quartz/components/pages/OfflineFallbackPage.tsx | 2 +- quartz/plugins/emitters/offline.ts | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/features/offline access.md b/docs/features/offline access.md index dcffdcd26..885bbd501 100644 --- a/docs/features/offline access.md +++ b/docs/features/offline access.md @@ -4,7 +4,7 @@ tags: - plugin/emitter --- -This plugin allows your website to be accessible offline and be installed as an app. You can use it by adding `Plugin.Offline(),` to the `emitters` in `quartz.config.ts` +This plugin allows your website to be accessible offline and be installed as an app. You can enable it by adding `Plugin.Offline(),` to the `emitters` in `quartz.config.ts` ## Offline Capability diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx index 8597075d2..c33d37542 100644 --- a/quartz/components/Explorer.tsx +++ b/quartz/components/Explorer.tsx @@ -22,6 +22,7 @@ const defaultOptions = { return -1 } }, + filterFn: (node) => node.name !== "tags", order: ["filter", "map", "sort"], } satisfies Options diff --git a/quartz/components/pages/OfflineFallbackPage.tsx b/quartz/components/pages/OfflineFallbackPage.tsx index 14d4f5e9e..d2fede3ce 100644 --- a/quartz/components/pages/OfflineFallbackPage.tsx +++ b/quartz/components/pages/OfflineFallbackPage.tsx @@ -4,7 +4,7 @@ function OfflineFallbackPage() { return (

Offline

-

This page isn't offline available yet.

+

You're offline and this page hasn't been cached yet.

) } diff --git a/quartz/plugins/emitters/offline.ts b/quartz/plugins/emitters/offline.ts index e3c654b69..b17771a22 100644 --- a/quartz/plugins/emitters/offline.ts +++ b/quartz/plugins/emitters/offline.ts @@ -62,7 +62,7 @@ export const Offline: QuartzEmitterPlugin = () => { const [tree, vfile] = defaultProcessedContent({ slug, text: "Offline", - description: "This page isn't offline available yet.", + description: "You're offline and this page hasn't been cached yet.", frontmatter: { title: "Offline", tags: [] }, }) From 8351ce7bfaf1792655f1e82b598f86080b7d6cba Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Wed, 20 Sep 2023 11:58:52 -0700 Subject: [PATCH 07/16] docs: document enableToc --- docs/features/table of contents.md | 1 + 1 file changed, 1 insertion(+) 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. From b679d4101f4b37fac44eb13cd6d3b664743b264a Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Wed, 20 Sep 2023 13:52:29 -0700 Subject: [PATCH 08/16] Revert "docs: wording changes for offline support" This reverts commit 09c5d42fa937675acb99fad41a065849f70018f8. --- docs/features/offline access.md | 2 +- quartz/components/Explorer.tsx | 1 - quartz/components/pages/OfflineFallbackPage.tsx | 2 +- quartz/plugins/emitters/offline.ts | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/features/offline access.md b/docs/features/offline access.md index 885bbd501..dcffdcd26 100644 --- a/docs/features/offline access.md +++ b/docs/features/offline access.md @@ -4,7 +4,7 @@ tags: - plugin/emitter --- -This plugin allows your website to be accessible offline and be installed as an app. You can enable it by adding `Plugin.Offline(),` to the `emitters` in `quartz.config.ts` +This plugin allows your website to be accessible offline and be installed as an app. You can use it by adding `Plugin.Offline(),` to the `emitters` in `quartz.config.ts` ## Offline Capability diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx index c33d37542..8597075d2 100644 --- a/quartz/components/Explorer.tsx +++ b/quartz/components/Explorer.tsx @@ -22,7 +22,6 @@ const defaultOptions = { return -1 } }, - filterFn: (node) => node.name !== "tags", order: ["filter", "map", "sort"], } satisfies Options diff --git a/quartz/components/pages/OfflineFallbackPage.tsx b/quartz/components/pages/OfflineFallbackPage.tsx index d2fede3ce..14d4f5e9e 100644 --- a/quartz/components/pages/OfflineFallbackPage.tsx +++ b/quartz/components/pages/OfflineFallbackPage.tsx @@ -4,7 +4,7 @@ function OfflineFallbackPage() { return (

Offline

-

You're offline and this page hasn't been cached yet.

+

This page isn't offline available yet.

) } diff --git a/quartz/plugins/emitters/offline.ts b/quartz/plugins/emitters/offline.ts index b17771a22..e3c654b69 100644 --- a/quartz/plugins/emitters/offline.ts +++ b/quartz/plugins/emitters/offline.ts @@ -62,7 +62,7 @@ export const Offline: QuartzEmitterPlugin = () => { const [tree, vfile] = defaultProcessedContent({ slug, text: "Offline", - description: "You're offline and this page hasn't been cached yet.", + description: "This page isn't offline available yet.", frontmatter: { title: "Offline", tags: [] }, }) From 38ce96cc6e27db051ad9e15f813ea40380feb29d Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Wed, 20 Sep 2023 13:52:45 -0700 Subject: [PATCH 09/16] Revert "feat: Making Quartz available offline by making it a PWA (#465)" This reverts commit d3fa6f85989af492eddbf98050adbc7b45084a1f. --- docs/configuration.md | 2 - docs/features/offline access.md | 31 ------ docs/index.md | 2 +- package-lock.json | 9 +- package.json | 2 +- quartz.config.ts | 1 - quartz/cfg.ts | 1 - quartz/components/Head.tsx | 4 - .../components/pages/OfflineFallbackPage.tsx | 12 --- quartz/plugins/emitters/componentResources.ts | 5 - quartz/plugins/emitters/index.ts | 1 - quartz/plugins/emitters/offline.ts | 97 ------------------- quartz/static/icon.svg | 74 -------------- 13 files changed, 6 insertions(+), 235 deletions(-) delete mode 100644 docs/features/offline access.md delete mode 100644 quartz/components/pages/OfflineFallbackPage.tsx delete mode 100644 quartz/plugins/emitters/offline.ts delete mode 100644 quartz/static/icon.svg diff --git a/docs/configuration.md b/docs/configuration.md index 35e0b9d95..047f6ca6b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -21,12 +21,10 @@ const config: QuartzConfig = { This part of the configuration concerns anything that can affect the whole site. The following is a list breaking down all the things you can configure: - `pageTitle`: title of the site. This is also used when generating the [[RSS Feed]] for your site. -- `description`: description of the site. This will be used when someone installs your site as an App. - `enableSPA`: whether to enable [[SPA Routing]] on your site. - `enablePopovers`: whether to enable [[popover previews]] on your site. - `analytics`: what to use for analytics on your site. Values can be - `null`: don't use analytics; - - `{ provider: "umami", websiteId: }`: easy, privacy-friendly, open source, GDPR Compliant analytics; - `{ provider: 'plausible' }`: use [Plausible](https://plausible.io/), a privacy-friendly alternative to Google Analytics; or - `{ provider: 'google', tagId: }`: use Google Analytics - `baseUrl`: this is used for sitemaps and RSS feeds that require an absolute URL to know where the canonical 'home' of your site lives. This is normally the deployed URL of your site (e.g. `quartz.jzhao.xyz` for this site). Do not include the protocol (i.e. `https://`) or any leading or trailing slashes. diff --git a/docs/features/offline access.md b/docs/features/offline access.md deleted file mode 100644 index dcffdcd26..000000000 --- a/docs/features/offline access.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: "Offline Access (PWA)" -tags: - - plugin/emitter ---- - -This plugin allows your website to be accessible offline and be installed as an app. You can use it by adding `Plugin.Offline(),` to the `emitters` in `quartz.config.ts` - -## Offline Capability - -Whenever you visit a page it gets cached for offline use. Depending on the kind of content, the process for caching is diffent: - -- **Pages** (HTML, your converted Markdown files): Quartz first tries to get them over the Network. If that fails, your browser attempts to fetch it from the cache. -- **Static Resources** (Fonts, CSS Styling, JavaScript): Quartz uses cached resources by default and updates the cache over the network in the background. -- **Images**: Images are saved once and then served from cache. Quartz uses a limited cache of 60 images and images remain in the cache for 30 days - -You can edit the fallback page by changing the `offline.md` file in the root of your `content` directory - -## Progressive Web App (PWA) - -Progressive Web Apps can have [many properties](https://developer.mozilla.org/en-US/docs/Web/Manifest). We're only going to mention the ones Quartz supports by default, however you can edit the offline plugins file to add more in case required. - -- **icons**: the `icon.svg` file in the `quartz/static` directory is used for all the icons. This makes it easier to scale the image since you don't need to provide an png for every size -- **name**, **short_name**: Uses the `pageTitle` configured in `quartz.config.ts` -- **description**: Uses the `description` configured in `quartz.config.ts` -- **background_color**, **theme_color**: Uses the `lightMode.light` color configured in `quartz.config.ts`. -- **start_url**: Uses the `baseUrl` configured in `quartz.config.ts` - -### Default values - -- **display**: this is set to `minimal-ui` diff --git a/docs/index.md b/docs/index.md index 570d5b364..05de2bae9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -30,7 +30,7 @@ This will guide you through initializing your Quartz with content. Once you've d ## 🔧 Features -- [[Obsidian compatibility]], [[full-text search]], [[graph view]], note transclusion, [[wikilinks]], [[backlinks]], [[Latex]], [[syntax highlighting]], [[popover previews]], [[offline access]] and [many more](./features) right out of the box +- [[Obsidian compatibility]], [[full-text search]], [[graph view]], note transclusion, [[wikilinks]], [[backlinks]], [[Latex]], [[syntax highlighting]], [[popover previews]], and [many more](./features) right out of the box - Hot-reload for both configuration and content - Simple JSX layouts and [[creating components|page components]] - [[SPA Routing|Ridiculously fast page loads]] and tiny bundle sizes diff --git a/package-lock.json b/package-lock.json index 8ff94245d..a87907897 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,7 +73,7 @@ "@types/flexsearch": "^0.7.3", "@types/hast": "^2.3.4", "@types/js-yaml": "^4.0.5", - "@types/node": "^20.6.2", + "@types/node": "^20.1.2", "@types/pretty-time": "^1.1.2", "@types/source-map-support": "^0.5.6", "@types/workerpool": "^6.4.0", @@ -113,7 +113,6 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", - "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -1464,9 +1463,9 @@ } }, "node_modules/@types/node": { - "version": "20.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.2.tgz", - "integrity": "sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==", + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz", + "integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==", "dev": true }, "node_modules/@types/parse5": { diff --git a/package.json b/package.json index e514edfbd..0a2085cef 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "@types/flexsearch": "^0.7.3", "@types/hast": "^2.3.4", "@types/js-yaml": "^4.0.5", - "@types/node": "^20.6.2", + "@types/node": "^20.1.2", "@types/pretty-time": "^1.1.2", "@types/source-map-support": "^0.5.6", "@types/workerpool": "^6.4.0", diff --git a/quartz.config.ts b/quartz.config.ts index 5a1f643aa..f677a18f9 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -4,7 +4,6 @@ import * as Plugin from "./quartz/plugins" const config: QuartzConfig = { configuration: { pageTitle: "ðŸŠī Quartz 4.0", - description: "Quartz Documentation Page and Demo", enableSPA: true, enablePopovers: true, analytics: { diff --git a/quartz/cfg.ts b/quartz/cfg.ts index 73e959fb7..8371b5e2b 100644 --- a/quartz/cfg.ts +++ b/quartz/cfg.ts @@ -19,7 +19,6 @@ export type Analytics = export interface GlobalConfiguration { pageTitle: string - description: string /** Whether to enable single-page-app style rendering. this prevents flashes of unstyled content and improves smoothness of Quartz */ enableSPA: boolean /** Whether to display Wikipedia-style popovers when hovering over links */ diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx index 972f7497e..2bf263817 100644 --- a/quartz/components/Head.tsx +++ b/quartz/components/Head.tsx @@ -14,8 +14,6 @@ export default (() => { const iconPath = joinSegments(baseDir, "static/icon.png") const ogImagePath = `https://${cfg.baseUrl}/static/og-image.png` - const manifest = - cfg.baseUrl == undefined ? "/manifest.json" : `https://${cfg.baseUrl}/manifest.json` return ( @@ -27,9 +25,7 @@ export default (() => { {cfg.baseUrl && } - - diff --git a/quartz/components/pages/OfflineFallbackPage.tsx b/quartz/components/pages/OfflineFallbackPage.tsx deleted file mode 100644 index 14d4f5e9e..000000000 --- a/quartz/components/pages/OfflineFallbackPage.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { QuartzComponentConstructor } from "../types" - -function OfflineFallbackPage() { - return ( -
-

Offline

-

This page isn't offline available yet.

-
- ) -} - -export default (() => OfflineFallbackPage) satisfies QuartzComponentConstructor diff --git a/quartz/plugins/emitters/componentResources.ts b/quartz/plugins/emitters/componentResources.ts index a82e7c12a..1290a3548 100644 --- a/quartz/plugins/emitters/componentResources.ts +++ b/quartz/plugins/emitters/componentResources.ts @@ -116,11 +116,6 @@ function addGlobalPageResources( document.dispatchEvent(event)`) } - componentResources.afterDOMLoaded.push(` - if ('serviceWorker' in navigator) { - navigator.serviceWorker.register('/sw.js'); - }`) - let wsUrl = `ws://localhost:${ctx.argv.wsPort}` if (ctx.argv.remoteDevHost) { diff --git a/quartz/plugins/emitters/index.ts b/quartz/plugins/emitters/index.ts index 6de824d5f..99a2c54d5 100644 --- a/quartz/plugins/emitters/index.ts +++ b/quartz/plugins/emitters/index.ts @@ -7,4 +7,3 @@ export { Assets } from "./assets" export { Static } from "./static" export { ComponentResources } from "./componentResources" export { NotFoundPage } from "./404" -export { Offline } from "./offline" diff --git a/quartz/plugins/emitters/offline.ts b/quartz/plugins/emitters/offline.ts deleted file mode 100644 index e3c654b69..000000000 --- a/quartz/plugins/emitters/offline.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { QuartzEmitterPlugin } from "../types" -import { FilePath, FullSlug } from "../../util/path" -import { FullPageLayout } from "../../cfg" -import { sharedPageComponents } from "../../../quartz.layout" -import OfflineFallbackPage from "../../components/pages/OfflineFallbackPage" -import BodyConstructor from "../../components/Body" -import { pageResources, renderPage } from "../../components/renderPage" -import { defaultProcessedContent } from "../vfile" -import { QuartzComponentProps } from "../../components/types" - -export const Offline: QuartzEmitterPlugin = () => { - const opts: FullPageLayout = { - ...sharedPageComponents, - pageBody: OfflineFallbackPage(), - beforeBody: [], - left: [], - right: [], - } - - const { head: Head, pageBody, footer: Footer } = opts - const Body = BodyConstructor() - - return { - name: "OfflineSupport", - getQuartzComponents() { - return [Head, Body, pageBody, Footer] - }, - async emit({ cfg }, _content, resources, emit): Promise { - const manifest = { - short_name: cfg.configuration.pageTitle, - name: cfg.configuration.pageTitle, - description: cfg.configuration.description, - background_color: cfg.configuration.theme.colors.lightMode.light, - theme_color: cfg.configuration.theme.colors.lightMode.light, - display: "minimal-ui", - icons: [ - { - src: "static/icon.svg", - sizes: "any", - purpose: "maskable", - }, - { - src: "static/icon.svg", - sizes: "any", - purpose: "any", - }, - ], - start_url: - cfg.configuration.baseUrl == undefined ? "/" : `https://${cfg.configuration.baseUrl}/`, - } - - const serviceWorker = - "importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js');" + - "const {pageCache, imageCache, staticResourceCache, googleFontsCache, offlineFallback} = workbox.recipes;" + - "pageCache(); googleFontsCache(); staticResourceCache(); imageCache(); offlineFallback();" - - const slug = "offline" as FullSlug - - const url = new URL(`https://${cfg.configuration.baseUrl ?? "example.com"}`) - const path = url.pathname as FullSlug - const externalResources = pageResources(path, resources) - const [tree, vfile] = defaultProcessedContent({ - slug, - text: "Offline", - description: "This page isn't offline available yet.", - frontmatter: { title: "Offline", tags: [] }, - }) - - const componentData: QuartzComponentProps = { - fileData: vfile.data, - externalResources, - cfg: cfg.configuration, - children: [], - tree, - allFiles: [], - } - - return Promise.all([ - emit({ - content: JSON.stringify(manifest), - slug: "manifest" as FullSlug, - ext: ".json", - }), - emit({ - content: serviceWorker, - slug: "sw" as FullSlug, - ext: ".js", - }), - emit({ - content: renderPage(slug, componentData, opts, externalResources), - slug, - ext: ".html", - }), - ]) - }, - } -} diff --git a/quartz/static/icon.svg b/quartz/static/icon.svg deleted file mode 100644 index c6ecfa2db..000000000 --- a/quartz/static/icon.svg +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - From 9b0f4881a6168bfd801a5e5fffac6856907f3ccd Mon Sep 17 00:00:00 2001 From: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Wed, 20 Sep 2023 22:55:29 +0200 Subject: [PATCH 10/16] 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 --- package.json | 2 +- quartz/components/Explorer.tsx | 4 ++-- quartz/components/ExplorerNode.tsx | 10 +++++----- quartz/components/scripts/explorer.inline.ts | 8 +++++--- quartz/components/styles/explorer.scss | 13 ++++++++----- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 0a2085cef..11a68d3ad 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 8597075d2..bc4855eda 100644 --- a/quartz/components/Explorer.tsx +++ b/quartz/components/Explorer.tsx @@ -79,7 +79,7 @@ export default ((userOpts?: Partial) => { data-savestate={opts.useSavedState} data-tree={jsonTree} > -

{opts.title}

+

{opts.title}

) => {
    -
    +
diff --git a/quartz/components/ExplorerNode.tsx b/quartz/components/ExplorerNode.tsx index fd0c0823d..c55a7a0a2 100644 --- a/quartz/components/ExplorerNode.tsx +++ b/quartz/components/ExplorerNode.tsx @@ -145,7 +145,7 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro } return ( -
+
  • {node.file ? ( // Single file node
  • @@ -174,17 +174,17 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro {/* render tag if folderBehavior is "link", otherwise render )} -
  • +
    )} {/* Recursively render children of folder */} @@ -210,6 +210,6 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro )} - + ) } 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..955c269ab 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; @@ -92,7 +93,7 @@ svg { color: var(--tertiary) !important; } - & li > button { + & div > button { color: var(--dark); background-color: transparent; border: none; @@ -103,7 +104,7 @@ svg { display: flex; align-items: center; - & h3 { + & p { font-size: 0.95rem; display: inline-block; color: var(--secondary); @@ -138,5 +139,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; } From dbd242767a65a7aabbd564f7a453ca5be6181634 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Wed, 20 Sep 2023 16:08:54 -0700 Subject: [PATCH 11/16] feat: display name for folders, expand explorer a little bit (#489) * feat: display name for folders, expand explorer a little bit * update docs --- docs/advanced/index.md | 3 +++ docs/features/explorer.md | 13 +++++++------ quartz/components/Explorer.tsx | 5 +++-- quartz/components/ExplorerNode.tsx | 14 ++++++++++---- quartz/styles/base.scss | 2 +- 5 files changed, 24 insertions(+), 13 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 6f941b871..b0eb12d87 100644 --- a/docs/features/explorer.md +++ b/docs/features/explorer.md @@ -57,7 +57,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 +73,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 +121,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 +139,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() }, }) ``` @@ -172,9 +173,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/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx index bc4855eda..73c620f3b 100644 --- a/quartz/components/Explorer.tsx +++ b/quartz/components/Explorer.tsx @@ -11,10 +11,10 @@ const defaultOptions = { 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,6 +22,7 @@ const defaultOptions = { return -1 } }, + filterFn: (node) => node.name !== "tags", order: ["filter", "map", "sort"], } satisfies Options diff --git a/quartz/components/ExplorerNode.tsx b/quartz/components/ExplorerNode.tsx index c55a7a0a2..9bdd0dfcc 100644 --- a/quartz/components/ExplorerNode.tsx +++ b/quartz/components/ExplorerNode.tsx @@ -29,19 +29,25 @@ 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 { + this.displayName = file.file.frontmatter!.title + } } else { const next = file.path[0] file.path = file.path.splice(1) @@ -150,7 +156,7 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro // Single file node
    • - {node.name} + {node.displayName}
    • ) : ( @@ -177,11 +183,11 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro
      {folderBehavior === "link" ? ( - {node.name} + {node.displayName} ) : ( )}
      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 From 1f0c39046e8864eded3057cdd2712bfdd3f9b116 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Wed, 20 Sep 2023 16:09:18 -0700 Subject: [PATCH 12/16] perf: memoize filetree computation (#490) * perf: memoize filetree computation * format * var -> let --- quartz/components/Explorer.tsx | 80 +++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx index 73c620f3b..de6b5e0ae 100644 --- a/quartz/components/Explorer.tsx +++ b/quartz/components/Explorer.tsx @@ -4,6 +4,7 @@ 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 = { @@ -27,49 +28,58 @@ const defaultOptions = { } 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 (