From d446ee1f7d916d25e381a5a1f940244ccd33ea92 Mon Sep 17 00:00:00 2001 From: Harv Date: Wed, 15 Oct 2025 20:26:33 +0000 Subject: [PATCH 1/7] first draft at unlisted files --- quartz.config.ts | 1 + quartz/cfg.ts | 2 ++ quartz/components/Breadcrumbs.tsx | 2 +- quartz/components/pages/FolderContent.tsx | 2 +- quartz/plugins/emitters/contentIndex.tsx | 6 +++++ quartz/plugins/emitters/contentPage.tsx | 5 ++-- quartz/plugins/emitters/folderPage.tsx | 5 ++-- quartz/plugins/emitters/tagPage.tsx | 5 ++-- quartz/plugins/filters/unlisted.ts | 27 ++++++++++++++++++++++ quartz/plugins/transformers/frontmatter.ts | 1 + quartz/util/ctx.ts | 9 +++++++- 11 files changed, 56 insertions(+), 9 deletions(-) create mode 100644 quartz/plugins/filters/unlisted.ts diff --git a/quartz.config.ts b/quartz.config.ts index b3db3d60d..de17574fa 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -18,6 +18,7 @@ const config: QuartzConfig = { locale: "en-US", baseUrl: "quartz.jzhao.xyz", ignorePatterns: ["private", "templates", ".obsidian"], + unlistedPatterns: [], defaultDateType: "modified", theme: { fontOrigin: "googleFonts", diff --git a/quartz/cfg.ts b/quartz/cfg.ts index 57dff5c75..12d3e3457 100644 --- a/quartz/cfg.ts +++ b/quartz/cfg.ts @@ -62,6 +62,8 @@ export interface GlobalConfiguration { analytics: Analytics /** Glob patterns to not search */ ignorePatterns: string[] + /** Glob patterns to mark files as unlisted (hidden from listings but still accessible via direct link) */ + unlistedPatterns?: string[] /** Whether to use created, modified, or published as the default type of date */ defaultDateType: ValidDateType /** Base URL to use for CNAME files, sitemaps, and RSS feeds that require an absolute URL. diff --git a/quartz/components/Breadcrumbs.tsx b/quartz/components/Breadcrumbs.tsx index 5144a314d..17a32104e 100644 --- a/quartz/components/Breadcrumbs.tsx +++ b/quartz/components/Breadcrumbs.tsx @@ -50,7 +50,7 @@ export default ((opts?: Partial) => { displayClass, ctx, }: QuartzComponentProps) => { - const trie = (ctx.trie ??= trieFromAllFiles(allFiles)) + const trie = (ctx.trie ??= trieFromAllFiles(allFiles, ctx.cfg)) const slugParts = fileData.slug!.split("/") const pathNodes = trie.ancestryChain(slugParts) diff --git a/quartz/components/pages/FolderContent.tsx b/quartz/components/pages/FolderContent.tsx index afd4f5d7e..16cfad202 100644 --- a/quartz/components/pages/FolderContent.tsx +++ b/quartz/components/pages/FolderContent.tsx @@ -30,7 +30,7 @@ export default ((opts?: Partial) => { const FolderContent: QuartzComponent = (props: QuartzComponentProps) => { const { tree, fileData, allFiles, cfg } = props - const trie = (props.ctx.trie ??= trieFromAllFiles(allFiles)) + const trie = (props.ctx.trie ??= trieFromAllFiles(allFiles, props.ctx.cfg)) const folder = trie.findNode(fileData.slug!.split("/")) if (!folder) { return null diff --git a/quartz/plugins/emitters/contentIndex.tsx b/quartz/plugins/emitters/contentIndex.tsx index 56392b358..138831358 100644 --- a/quartz/plugins/emitters/contentIndex.tsx +++ b/quartz/plugins/emitters/contentIndex.tsx @@ -7,6 +7,7 @@ import { QuartzEmitterPlugin } from "../types" import { toHtml } from "hast-util-to-html" import { write } from "./helpers" import { i18n } from "../../i18n" +import { isUnlisted } from "../filters/unlisted" export type ContentIndexMap = Map export type ContentDetails = { @@ -102,6 +103,11 @@ export const ContentIndex: QuartzEmitterPlugin> = (opts) => { for (const [tree, file] of content) { const slug = file.data.slug! const date = getDate(ctx.cfg.configuration, file.data) ?? new Date() + + if(isUnlisted(file.data, cfg)) { + continue + } + if (opts?.includeEmptyFiles || (file.data.text && file.data.text !== "")) { linkIndex.set(slug, { slug, diff --git a/quartz/plugins/emitters/contentPage.tsx b/quartz/plugins/emitters/contentPage.tsx index c3410ecc3..b67cff207 100644 --- a/quartz/plugins/emitters/contentPage.tsx +++ b/quartz/plugins/emitters/contentPage.tsx @@ -14,6 +14,7 @@ import { BuildCtx } from "../../util/ctx" import { Node } from "unist" import { StaticResources } from "../../util/resources" import { QuartzPluginData } from "../vfile" +import { isUnlisted } from "../filters/unlisted" async function processContent( ctx: BuildCtx, @@ -74,7 +75,7 @@ export const ContentPage: QuartzEmitterPlugin> = (userOp ] }, async *emit(ctx, content, resources) { - const allFiles = content.map((c) => c[1].data) + const allFiles = content.map((c) => c[1].data).filter(f => !isUnlisted(f, ctx.cfg.configuration)) let containsIndex = false for (const [tree, file] of content) { @@ -98,7 +99,7 @@ export const ContentPage: QuartzEmitterPlugin> = (userOp } }, async *partialEmit(ctx, content, resources, changeEvents) { - const allFiles = content.map((c) => c[1].data) + const allFiles = content.map((c) => c[1].data).filter(f => !isUnlisted(f, ctx.cfg.configuration)) // find all slugs that changed or were added const changedSlugs = new Set() diff --git a/quartz/plugins/emitters/folderPage.tsx b/quartz/plugins/emitters/folderPage.tsx index f9b181dff..ef610a5bd 100644 --- a/quartz/plugins/emitters/folderPage.tsx +++ b/quartz/plugins/emitters/folderPage.tsx @@ -20,6 +20,7 @@ import { write } from "./helpers" import { i18n, TRANSLATIONS } from "../../i18n" import { BuildCtx } from "../../util/ctx" import { StaticResources } from "../../util/resources" +import { isUnlisted } from "../filters/unlisted" interface FolderPageOptions extends FullPageLayout { sort?: (f1: QuartzPluginData, f2: QuartzPluginData) => number } @@ -129,7 +130,7 @@ export const FolderPage: QuartzEmitterPlugin> = (user ] }, async *emit(ctx, content, resources) { - const allFiles = content.map((c) => c[1].data) + const allFiles = content.map((c) => c[1].data).filter(f => !isUnlisted(f, ctx.cfg.configuration)) const cfg = ctx.cfg.configuration const folders: Set = new Set( @@ -146,7 +147,7 @@ export const FolderPage: QuartzEmitterPlugin> = (user yield* processFolderInfo(ctx, folderInfo, allFiles, opts, resources) }, async *partialEmit(ctx, content, resources, changeEvents) { - const allFiles = content.map((c) => c[1].data) + const allFiles = content.map((c) => c[1].data).filter(f => !isUnlisted(f, ctx.cfg.configuration)) const cfg = ctx.cfg.configuration // Find all folders that need to be updated based on changed files diff --git a/quartz/plugins/emitters/tagPage.tsx b/quartz/plugins/emitters/tagPage.tsx index 5f238932d..04d2363db 100644 --- a/quartz/plugins/emitters/tagPage.tsx +++ b/quartz/plugins/emitters/tagPage.tsx @@ -12,6 +12,7 @@ import { write } from "./helpers" import { i18n, TRANSLATIONS } from "../../i18n" import { BuildCtx } from "../../util/ctx" import { StaticResources } from "../../util/resources" +import { isUnlisted } from "../filters/unlisted" interface TagPageOptions extends FullPageLayout { sort?: (f1: QuartzPluginData, f2: QuartzPluginData) => number @@ -122,7 +123,7 @@ export const TagPage: QuartzEmitterPlugin> = (userOpts) ] }, async *emit(ctx, content, resources) { - const allFiles = content.map((c) => c[1].data) + const allFiles = content.map((c) => c[1].data).filter(f => !isUnlisted(f, ctx.cfg.configuration)) const cfg = ctx.cfg.configuration const [tags, tagDescriptions] = computeTagInfo(allFiles, content, cfg.locale) @@ -131,7 +132,7 @@ export const TagPage: QuartzEmitterPlugin> = (userOpts) } }, async *partialEmit(ctx, content, resources, changeEvents) { - const allFiles = content.map((c) => c[1].data) + const allFiles = content.map((c) => c[1].data).filter(f => !isUnlisted(f, ctx.cfg.configuration)) const cfg = ctx.cfg.configuration // Find all tags that need to be updated based on changed files diff --git a/quartz/plugins/filters/unlisted.ts b/quartz/plugins/filters/unlisted.ts new file mode 100644 index 000000000..07d6e0856 --- /dev/null +++ b/quartz/plugins/filters/unlisted.ts @@ -0,0 +1,27 @@ +import { minimatch } from "minimatch" +import { QuartzPluginData } from "../vfile" +import { GlobalConfiguration } from "../../cfg" + +export function isUnlisted( + fileData: QuartzPluginData, + cfg: GlobalConfiguration, + unlistedPatterns?: string[] +): boolean { + const unlistedFlag: boolean = + fileData?.frontmatter?.unlisted === true || + fileData?.frontmatter?.unlisted === "true" + + if (unlistedFlag) return true + + const patterns = unlistedPatterns ?? cfg.unlistedPatterns + if (patterns && patterns.length > 0 && fileData.slug) { + const slug = fileData.slug as string + for (const pattern of patterns) { + if (minimatch(slug, pattern)) { + return true + } + } + } + + return false +} \ No newline at end of file diff --git a/quartz/plugins/transformers/frontmatter.ts b/quartz/plugins/transformers/frontmatter.ts index 1103900c5..6bd04c782 100644 --- a/quartz/plugins/transformers/frontmatter.ts +++ b/quartz/plugins/transformers/frontmatter.ts @@ -146,6 +146,7 @@ declare module "vfile" { socialDescription: string publish: boolean | string draft: boolean | string + unlisted: boolean | string lang: string enableToc: string cssclasses: string[] diff --git a/quartz/util/ctx.ts b/quartz/util/ctx.ts index 80115ec27..d1eb4a5ba 100644 --- a/quartz/util/ctx.ts +++ b/quartz/util/ctx.ts @@ -1,4 +1,5 @@ import { QuartzConfig } from "../cfg" +import { isUnlisted } from "../plugins/filters/unlisted" import { QuartzPluginData } from "../plugins/vfile" import { FileTrieNode } from "./fileTrie" import { FilePath, FullSlug } from "./path" @@ -31,10 +32,16 @@ export interface BuildCtx { incremental: boolean } -export function trieFromAllFiles(allFiles: QuartzPluginData[]): FileTrieNode { +export function trieFromAllFiles( + allFiles: QuartzPluginData[], + cfg?: QuartzConfig +): FileTrieNode { const trie = new FileTrieNode([]) allFiles.forEach((file) => { if (file.frontmatter) { + if (cfg && isUnlisted(file, cfg.configuration)) { + return + } trie.add({ ...file, slug: file.slug!, From 74cda8b1ffebe101f717374cd7c2c11deb1c0e12 Mon Sep 17 00:00:00 2001 From: Harv Date: Wed, 15 Oct 2025 20:45:21 +0000 Subject: [PATCH 2/7] running prettier --- quartz/plugins/emitters/contentIndex.tsx | 2 +- quartz/plugins/emitters/contentPage.tsx | 8 ++++-- quartz/plugins/emitters/folderPage.tsx | 8 ++++-- quartz/plugins/emitters/tagPage.tsx | 8 ++++-- quartz/plugins/filters/unlisted.ts | 35 ++++++++++++------------ quartz/util/ctx.ts | 2 +- 6 files changed, 37 insertions(+), 26 deletions(-) diff --git a/quartz/plugins/emitters/contentIndex.tsx b/quartz/plugins/emitters/contentIndex.tsx index 138831358..779fc0210 100644 --- a/quartz/plugins/emitters/contentIndex.tsx +++ b/quartz/plugins/emitters/contentIndex.tsx @@ -104,7 +104,7 @@ export const ContentIndex: QuartzEmitterPlugin> = (opts) => { const slug = file.data.slug! const date = getDate(ctx.cfg.configuration, file.data) ?? new Date() - if(isUnlisted(file.data, cfg)) { + if (isUnlisted(file.data, cfg)) { continue } diff --git a/quartz/plugins/emitters/contentPage.tsx b/quartz/plugins/emitters/contentPage.tsx index b67cff207..d8d39546d 100644 --- a/quartz/plugins/emitters/contentPage.tsx +++ b/quartz/plugins/emitters/contentPage.tsx @@ -75,7 +75,9 @@ export const ContentPage: QuartzEmitterPlugin> = (userOp ] }, async *emit(ctx, content, resources) { - const allFiles = content.map((c) => c[1].data).filter(f => !isUnlisted(f, ctx.cfg.configuration)) + const allFiles = content + .map((c) => c[1].data) + .filter((f) => !isUnlisted(f, ctx.cfg.configuration)) let containsIndex = false for (const [tree, file] of content) { @@ -99,7 +101,9 @@ export const ContentPage: QuartzEmitterPlugin> = (userOp } }, async *partialEmit(ctx, content, resources, changeEvents) { - const allFiles = content.map((c) => c[1].data).filter(f => !isUnlisted(f, ctx.cfg.configuration)) + const allFiles = content + .map((c) => c[1].data) + .filter((f) => !isUnlisted(f, ctx.cfg.configuration)) // find all slugs that changed or were added const changedSlugs = new Set() diff --git a/quartz/plugins/emitters/folderPage.tsx b/quartz/plugins/emitters/folderPage.tsx index ef610a5bd..4fb47c54f 100644 --- a/quartz/plugins/emitters/folderPage.tsx +++ b/quartz/plugins/emitters/folderPage.tsx @@ -130,7 +130,9 @@ export const FolderPage: QuartzEmitterPlugin> = (user ] }, async *emit(ctx, content, resources) { - const allFiles = content.map((c) => c[1].data).filter(f => !isUnlisted(f, ctx.cfg.configuration)) + const allFiles = content + .map((c) => c[1].data) + .filter((f) => !isUnlisted(f, ctx.cfg.configuration)) const cfg = ctx.cfg.configuration const folders: Set = new Set( @@ -147,7 +149,9 @@ export const FolderPage: QuartzEmitterPlugin> = (user yield* processFolderInfo(ctx, folderInfo, allFiles, opts, resources) }, async *partialEmit(ctx, content, resources, changeEvents) { - const allFiles = content.map((c) => c[1].data).filter(f => !isUnlisted(f, ctx.cfg.configuration)) + const allFiles = content + .map((c) => c[1].data) + .filter((f) => !isUnlisted(f, ctx.cfg.configuration)) const cfg = ctx.cfg.configuration // Find all folders that need to be updated based on changed files diff --git a/quartz/plugins/emitters/tagPage.tsx b/quartz/plugins/emitters/tagPage.tsx index 04d2363db..bb58d1586 100644 --- a/quartz/plugins/emitters/tagPage.tsx +++ b/quartz/plugins/emitters/tagPage.tsx @@ -123,7 +123,9 @@ export const TagPage: QuartzEmitterPlugin> = (userOpts) ] }, async *emit(ctx, content, resources) { - const allFiles = content.map((c) => c[1].data).filter(f => !isUnlisted(f, ctx.cfg.configuration)) + const allFiles = content + .map((c) => c[1].data) + .filter((f) => !isUnlisted(f, ctx.cfg.configuration)) const cfg = ctx.cfg.configuration const [tags, tagDescriptions] = computeTagInfo(allFiles, content, cfg.locale) @@ -132,7 +134,9 @@ export const TagPage: QuartzEmitterPlugin> = (userOpts) } }, async *partialEmit(ctx, content, resources, changeEvents) { - const allFiles = content.map((c) => c[1].data).filter(f => !isUnlisted(f, ctx.cfg.configuration)) + const allFiles = content + .map((c) => c[1].data) + .filter((f) => !isUnlisted(f, ctx.cfg.configuration)) const cfg = ctx.cfg.configuration // Find all tags that need to be updated based on changed files diff --git a/quartz/plugins/filters/unlisted.ts b/quartz/plugins/filters/unlisted.ts index 07d6e0856..c87d3d1ac 100644 --- a/quartz/plugins/filters/unlisted.ts +++ b/quartz/plugins/filters/unlisted.ts @@ -3,25 +3,24 @@ import { QuartzPluginData } from "../vfile" import { GlobalConfiguration } from "../../cfg" export function isUnlisted( - fileData: QuartzPluginData, - cfg: GlobalConfiguration, - unlistedPatterns?: string[] + fileData: QuartzPluginData, + cfg: GlobalConfiguration, + unlistedPatterns?: string[], ): boolean { - const unlistedFlag: boolean = - fileData?.frontmatter?.unlisted === true || - fileData?.frontmatter?.unlisted === "true" - - if (unlistedFlag) return true + const unlistedFlag: boolean = + fileData?.frontmatter?.unlisted === true || fileData?.frontmatter?.unlisted === "true" - const patterns = unlistedPatterns ?? cfg.unlistedPatterns - if (patterns && patterns.length > 0 && fileData.slug) { - const slug = fileData.slug as string - for (const pattern of patterns) { - if (minimatch(slug, pattern)) { - return true - } - } + if (unlistedFlag) return true + + const patterns = unlistedPatterns ?? cfg.unlistedPatterns + if (patterns && patterns.length > 0 && fileData.slug) { + const slug = fileData.slug as string + for (const pattern of patterns) { + if (minimatch(slug, pattern)) { + return true + } } + } - return false -} \ No newline at end of file + return false +} diff --git a/quartz/util/ctx.ts b/quartz/util/ctx.ts index d1eb4a5ba..d0bbbf6a6 100644 --- a/quartz/util/ctx.ts +++ b/quartz/util/ctx.ts @@ -34,7 +34,7 @@ export interface BuildCtx { export function trieFromAllFiles( allFiles: QuartzPluginData[], - cfg?: QuartzConfig + cfg?: QuartzConfig, ): FileTrieNode { const trie = new FileTrieNode([]) allFiles.forEach((file) => { From 5cc9b3cea4ae0a0b644ef3eaa99eb802d7c09028 Mon Sep 17 00:00:00 2001 From: Harv Date: Sat, 18 Oct 2025 10:32:34 +0100 Subject: [PATCH 3/7] Update quartz/plugins/filters/unlisted.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- quartz/plugins/filters/unlisted.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartz/plugins/filters/unlisted.ts b/quartz/plugins/filters/unlisted.ts index c87d3d1ac..3a3594e0e 100644 --- a/quartz/plugins/filters/unlisted.ts +++ b/quartz/plugins/filters/unlisted.ts @@ -14,7 +14,7 @@ export function isUnlisted( const patterns = unlistedPatterns ?? cfg.unlistedPatterns if (patterns && patterns.length > 0 && fileData.slug) { - const slug = fileData.slug as string + const slug = fileData.slug for (const pattern of patterns) { if (minimatch(slug, pattern)) { return true From a18c1139e8059b955438d05c106afb15a954abb4 Mon Sep 17 00:00:00 2001 From: Harv Date: Sat, 18 Oct 2025 16:39:08 +0100 Subject: [PATCH 4/7] Update quartz/plugins/filters/unlisted.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- quartz/plugins/filters/unlisted.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/quartz/plugins/filters/unlisted.ts b/quartz/plugins/filters/unlisted.ts index 3a3594e0e..fbb6230fd 100644 --- a/quartz/plugins/filters/unlisted.ts +++ b/quartz/plugins/filters/unlisted.ts @@ -5,14 +5,13 @@ import { GlobalConfiguration } from "../../cfg" export function isUnlisted( fileData: QuartzPluginData, cfg: GlobalConfiguration, - unlistedPatterns?: string[], ): boolean { const unlistedFlag: boolean = fileData?.frontmatter?.unlisted === true || fileData?.frontmatter?.unlisted === "true" if (unlistedFlag) return true - const patterns = unlistedPatterns ?? cfg.unlistedPatterns + const patterns = cfg.unlistedPatterns if (patterns && patterns.length > 0 && fileData.slug) { const slug = fileData.slug for (const pattern of patterns) { From 3810eda4cb70902a7626f0cb12d9e34669b5cfb9 Mon Sep 17 00:00:00 2001 From: Harv Date: Sat, 18 Oct 2025 15:38:27 +0000 Subject: [PATCH 5/7] make unlistedPatterns nonoptional --- quartz/cfg.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartz/cfg.ts b/quartz/cfg.ts index 12d3e3457..1e8f96e9f 100644 --- a/quartz/cfg.ts +++ b/quartz/cfg.ts @@ -63,7 +63,7 @@ export interface GlobalConfiguration { /** Glob patterns to not search */ ignorePatterns: string[] /** Glob patterns to mark files as unlisted (hidden from listings but still accessible via direct link) */ - unlistedPatterns?: string[] + unlistedPatterns: string[] /** Whether to use created, modified, or published as the default type of date */ defaultDateType: ValidDateType /** Base URL to use for CNAME files, sitemaps, and RSS feeds that require an absolute URL. From 37d4c7aebf4cf761fa88284c53da14bea66f106f Mon Sep 17 00:00:00 2001 From: Harv Date: Sat, 18 Oct 2025 15:38:44 +0000 Subject: [PATCH 6/7] add unlisted pages docs to private pages md --- docs/features/private pages.md | 45 ++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/docs/features/private pages.md b/docs/features/private pages.md index eed6d3c1a..75ab8e6ad 100644 --- a/docs/features/private pages.md +++ b/docs/features/private pages.md @@ -1,12 +1,21 @@ --- -title: Private Pages +title: Private and Unlisted Pages tags: - feature/filter --- -There may be some notes you want to avoid publishing as a website. Quartz supports this through two mechanisms which can be used in conjunction: +You may want to control which notes appear publicly on your site. Quartz supports two complementary mechanisms to achieve this: -## Filter Plugins +- **Private Pages** — fully exclude notes from the published site. +- **Unlisted Pages** — published and accessible via direct link, but hidden from navigation and listings. + +--- + +## Private Pages + +Quarts supports **Private Pages**, which allow you to **prevent certain notes from being published as a website**. There are two mechanisms for this which can be used in conjunction: + +### Filter Plugins [[making plugins#Filters|Filter plugins]] are plugins that filter out content based off of certain criteria. By default, Quartz uses the [[RemoveDrafts]] plugin which filters out any note that has `draft: true` in the frontmatter. @@ -15,7 +24,7 @@ If you'd like to only publish a select number of notes, you can instead use [[Ex > [!warning] > Regardless of the filter plugin used, **all non-markdown files will be emitted and available publically in the final build.** This includes files such as images, voice recordings, PDFs, etc. -## `ignorePatterns` +### Quartz Config This is a field in `quartz.config.ts` under the main [[configuration]] which allows you to specify a list of patterns to effectively exclude from parsing all together. Any valid [fast-glob](https://github.com/mrmlnc/fast-glob#pattern-syntax) pattern works here. @@ -26,8 +35,34 @@ Common examples include: - `some/folder`: exclude the entire of `some/folder` - `*.md`: exclude all files with a `.md` extension -- `!(*.md)` exclude all files that _don't_ have a `.md` extension. Note that negations _must_ parenthesize the rest of the pattern! +- `!(*.md)`: exclude all files that _don't_ have a `.md` extension. Note that negations _must_ parenthesize the rest of the pattern! - `**/private`: exclude any files or folders named `private` at any level of nesting > [!warning] > Marking something as private via either a plugin or through the `ignorePatterns` pattern will only prevent a page from being included in the final built site. If your GitHub repository is public, also be sure to include an ignore for those in the `.gitignore` of your Quartz. See the `git` [documentation](https://git-scm.com/docs/gitignore#_pattern_format) for more information. + +--- + +## Unlisted Pages + +Quartz supports **Unlisted Pages**, which allow you to publish notes that remain **accessible by direct link** but **hidden from navigation components** such as: + +- the explorer sidebar +- recent notes lists +- tag or folder listings + +This is useful for sharing content privately with collaborators, collecting feedback, or keeping drafts semi-private without fully unpublishing them. + +There are two mechanisms provided to enable unlisted pages: + +### Frontmatter Flags + +To mark a single page as unlisted, add `unlisted: true` to its frontmatter. + +### Quartz Config + +If you want to apply this behavior to multiple files or folders, you can use the `unlistedPatterns` field in your `quartz.config.ts`. +This accepts an array of fast-glob patterns that identify which pages should be treated as unlisted. + +> [!note] +> As with `ignorePatterns`, fast-glob syntax differs slightly from Bash glob syntax. Using Bash-style patterns may lead to unexpected results. \ No newline at end of file From 50632a06e980d5793590ce3c2b303866d40b74e7 Mon Sep 17 00:00:00 2001 From: Harv Date: Sat, 18 Oct 2025 15:43:03 +0000 Subject: [PATCH 7/7] pretty --- docs/features/private pages.md | 8 ++++---- quartz/plugins/filters/unlisted.ts | 5 +---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/docs/features/private pages.md b/docs/features/private pages.md index 75ab8e6ad..1ee6311ec 100644 --- a/docs/features/private pages.md +++ b/docs/features/private pages.md @@ -47,9 +47,9 @@ Common examples include: Quartz supports **Unlisted Pages**, which allow you to publish notes that remain **accessible by direct link** but **hidden from navigation components** such as: -- the explorer sidebar -- recent notes lists -- tag or folder listings +- the explorer sidebar +- recent notes lists +- tag or folder listings This is useful for sharing content privately with collaborators, collecting feedback, or keeping drafts semi-private without fully unpublishing them. @@ -65,4 +65,4 @@ If you want to apply this behavior to multiple files or folders, you can use the This accepts an array of fast-glob patterns that identify which pages should be treated as unlisted. > [!note] -> As with `ignorePatterns`, fast-glob syntax differs slightly from Bash glob syntax. Using Bash-style patterns may lead to unexpected results. \ No newline at end of file +> As with `ignorePatterns`, fast-glob syntax differs slightly from Bash glob syntax. Using Bash-style patterns may lead to unexpected results. diff --git a/quartz/plugins/filters/unlisted.ts b/quartz/plugins/filters/unlisted.ts index fbb6230fd..2e42af348 100644 --- a/quartz/plugins/filters/unlisted.ts +++ b/quartz/plugins/filters/unlisted.ts @@ -2,10 +2,7 @@ import { minimatch } from "minimatch" import { QuartzPluginData } from "../vfile" import { GlobalConfiguration } from "../../cfg" -export function isUnlisted( - fileData: QuartzPluginData, - cfg: GlobalConfiguration, -): boolean { +export function isUnlisted(fileData: QuartzPluginData, cfg: GlobalConfiguration): boolean { const unlistedFlag: boolean = fileData?.frontmatter?.unlisted === true || fileData?.frontmatter?.unlisted === "true"