Skip to content

Commit dc6dbf8

Browse files
authored
Merge pull request #9 from anibal/claude/draft-notes-feature-011CV2WosNE46zhNCgAHzQuJ
Add draft preview feature to be able share for feedback
2 parents f70b7d8 + d3339a6 commit dc6dbf8

File tree

12 files changed

+153
-10
lines changed

12 files changed

+153
-10
lines changed

quartz.config.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const config: QuartzConfig = {
5959
transformers: [
6060
Plugin.FrontMatter(),
6161
Plugin.CustomSlug(),
62+
Plugin.DraftPrefix(), // Publishes draft notes under draft/ prefix
6263
Plugin.CreatedModifiedDate({
6364
priority: ["frontmatter", "git", "filesystem"],
6465
}),
@@ -76,7 +77,9 @@ const config: QuartzConfig = {
7677
Plugin.Description(),
7778
Plugin.Latex({ renderEngine: "katex" }),
7879
],
79-
filters: [Plugin.RemoveDrafts()],
80+
filters: [
81+
// Plugin.RemoveDrafts(), // Disabled: drafts now published under draft/ prefix instead of excluded
82+
],
8083
emitters: [
8184
Plugin.AliasRedirects(),
8285
Plugin.ComponentResources(),

quartz.layout.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const sharedPageComponents: SharedLayout = {
1919
// components for pages that display a single page (e.g. a single note)
2020
export const defaultContentPageLayout: PageLayout = {
2121
beforeBody: [
22+
Component.DraftBanner(), // Shows "DRAFT: {title}" banner for draft pages
2223
Component.ConditionalRender({
2324
component: Component.Breadcrumbs(),
2425
condition: (page) => page.fileData.slug !== "index",
@@ -62,6 +63,7 @@ export const defaultContentPageLayout: PageLayout = {
6263
limit: 5,
6364
showTags: true,
6465
linkToMore: "recent/" as SimpleSlug,
66+
filter: (file) => !file.slug!.startsWith("draft/"), // Exclude draft pages
6567
}),
6668
],
6769
right: [
@@ -73,7 +75,12 @@ export const defaultContentPageLayout: PageLayout = {
7375

7476
// components for pages that display lists of pages (e.g. tags or folders)
7577
export const defaultListPageLayout: PageLayout = {
76-
beforeBody: [Component.Breadcrumbs(), Component.ArticleTitle(), Component.ContentMeta()],
78+
beforeBody: [
79+
Component.DraftBanner(), // Shows "DRAFT: {title}" banner for draft pages
80+
Component.Breadcrumbs(),
81+
Component.ArticleTitle(),
82+
Component.ContentMeta(),
83+
],
7784
left: [
7885
Component.PageTitle(),
7986
Component.MobileOnly(Component.Spacer()),
@@ -108,6 +115,7 @@ export const defaultListPageLayout: PageLayout = {
108115
limit: 5,
109116
showTags: true,
110117
linkToMore: "recent/" as SimpleSlug,
118+
filter: (file) => !file.slug!.startsWith("draft/"), // Exclude draft pages
111119
}),
112120
],
113121
right: [],

quartz/components/Backlinks.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ export default ((opts?: Partial<BacklinksOptions>) => {
2424
cfg,
2525
}: QuartzComponentProps) => {
2626
const slug = simplifySlug(fileData.slug!)
27-
const backlinkFiles = allFiles.filter((file) => file.links?.includes(slug))
27+
const backlinkFiles = allFiles
28+
.filter((file) => !file.slug!.startsWith("draft/")) // Exclude draft pages from backlinks
29+
.filter((file) => file.links?.includes(slug))
2830
if (options.hideWhenEmpty && backlinkFiles.length == 0) {
2931
return null
3032
}

quartz/components/DraftBanner.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
2+
import { i18n } from "../i18n"
3+
4+
export default (() => {
5+
const DraftBanner: QuartzComponent = ({ fileData, cfg }: QuartzComponentProps) => {
6+
// Check if this is a draft page
7+
const isDraft = fileData.slug?.startsWith("draft/") ?? false
8+
9+
// Don't render anything if not a draft
10+
if (!isDraft) {
11+
return null
12+
}
13+
14+
const title = fileData.frontmatter?.title ?? i18n(cfg.locale).propertyDefaults.title
15+
16+
return (
17+
<div class="draft-banner">
18+
<div class="draft-banner-content">
19+
<span class="draft-label">DRAFT:</span> {title}
20+
</div>
21+
</div>
22+
)
23+
}
24+
25+
DraftBanner.css = `
26+
.draft-banner {
27+
background-color: var(--highlight);
28+
border-left: 4px solid var(--secondary);
29+
padding: 0.75rem 1rem;
30+
margin-bottom: 1.5rem;
31+
border-radius: 4px;
32+
}
33+
34+
.draft-banner-content {
35+
font-weight: 600;
36+
color: var(--darkgray);
37+
font-size: 0.95rem;
38+
}
39+
40+
.draft-label {
41+
color: var(--secondary);
42+
font-weight: 700;
43+
text-transform: uppercase;
44+
letter-spacing: 0.5px;
45+
}
46+
`
47+
48+
return DraftBanner
49+
}) satisfies QuartzComponentConstructor

quartz/components/Head.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,14 @@ export default (() => {
3636
)
3737
const ogImageDefaultPath = `https://${cfg.baseUrl}/static/og-image.png`
3838

39+
// Check if this is a draft page (slug starts with "draft/")
40+
const isDraft = fileData.slug?.startsWith("draft/") ?? false
41+
3942
return (
4043
<head>
4144
<title>{title}</title>
4245
<meta charSet="utf-8" />
46+
{isDraft && <meta name="robots" content="noindex, nofollow" />}
4347
{cfg.theme.cdnCaching && cfg.theme.fontOrigin === "googleFonts" && (
4448
<>
4549
<link rel="preconnect" href="https://fonts.googleapis.com" />

quartz/components/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import Breadcrumbs from "./Breadcrumbs"
2323
import Comments from "./Comments"
2424
import Flex from "./Flex"
2525
import ConditionalRender from "./ConditionalRender"
26+
import DraftBanner from "./DraftBanner"
2627

2728
export {
2829
ArticleTitle,
@@ -50,4 +51,5 @@ export {
5051
Comments,
5152
Flex,
5253
ConditionalRender,
54+
DraftBanner,
5355
}

quartz/components/pages/TagContent.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ export default ((opts?: Partial<TagContentOptions>) => {
3131

3232
const tag = simplifySlug(slug.slice("tags/".length) as FullSlug)
3333
const allPagesWithTag = (tag: string) =>
34-
allFiles.filter((file) =>
35-
(file.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes).includes(tag),
36-
)
34+
allFiles
35+
.filter((file) => !file.slug!.startsWith("draft/")) // Exclude draft pages from tag listings
36+
.filter((file) =>
37+
(file.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes).includes(tag),
38+
)
3739

3840
const content = (
3941
(tree as Root).children.length === 0
@@ -45,7 +47,10 @@ export default ((opts?: Partial<TagContentOptions>) => {
4547
if (tag === "/") {
4648
const tags = [
4749
...new Set(
48-
allFiles.flatMap((data) => data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes),
50+
allFiles
51+
.filter((file) => !file.slug!.startsWith("draft/")) // Exclude drafts from tag collection
52+
.flatMap((data) => data.frontmatter?.tags ?? [])
53+
.flatMap(getAllSegmentPrefixes),
4954
),
5055
].sort((a, b) => a.localeCompare(b))
5156
const tagItemMap: Map<string, QuartzPluginData[]> = new Map()

quartz/components/scripts/graph.inline.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,16 @@ async function renderGraph(graph: HTMLElement, fullSlug: FullSlug) {
8989
enableRadial,
9090
} = JSON.parse(graph.dataset["cfg"]!) as D3Config
9191

92-
const data: Map<SimpleSlug, ContentDetails> = new Map(
92+
const rawData: Map<SimpleSlug, ContentDetails> = new Map(
9393
Object.entries<ContentDetails>(await fetchData).map(([k, v]) => [
9494
simplifySlug(k as FullSlug),
9595
v,
9696
]),
9797
)
98+
// Filter out draft pages from the graph
99+
const data: Map<SimpleSlug, ContentDetails> = new Map(
100+
[...rawData.entries()].filter(([slug]) => !slug.startsWith("draft/")),
101+
)
98102
const links: SimpleLinkData[] = []
99103
const tags: SimpleSlug[] = []
100104
const validLinks = new Set(data.keys())

quartz/plugins/emitters/contentIndex.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ export const ContentIndex: QuartzEmitterPlugin<Partial<Options>> = (opts) => {
101101
const linkIndex: ContentIndexMap = new Map()
102102
for (const [tree, file] of content) {
103103
const slug = file.data.slug!
104+
// Skip draft pages from search index, sitemap, and RSS
105+
if (slug.startsWith("draft/")) {
106+
continue
107+
}
104108
const date = getDate(ctx.cfg.configuration, file.data) ?? new Date()
105109
if (opts?.includeEmptyFiles || (file.data.text && file.data.text !== "")) {
106110
linkIndex.set(slug, {

quartz/plugins/emitters/tagPage.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,10 @@ export const TagPage: QuartzEmitterPlugin<Partial<TagPageOptions>> = (userOpts)
122122
]
123123
},
124124
async *emit(ctx, content, resources) {
125-
const allFiles = content.map((c) => c[1].data)
125+
// Exclude draft pages from tag pages
126+
const allFiles = content
127+
.map((c) => c[1].data)
128+
.filter((file) => !file.slug!.startsWith("draft/"))
126129
const cfg = ctx.cfg.configuration
127130
const [tags, tagDescriptions] = computeTagInfo(allFiles, content, cfg.locale)
128131

@@ -131,7 +134,10 @@ export const TagPage: QuartzEmitterPlugin<Partial<TagPageOptions>> = (userOpts)
131134
}
132135
},
133136
async *partialEmit(ctx, content, resources, changeEvents) {
134-
const allFiles = content.map((c) => c[1].data)
137+
// Exclude draft pages from tag pages
138+
const allFiles = content
139+
.map((c) => c[1].data)
140+
.filter((file) => !file.slug!.startsWith("draft/"))
135141
const cfg = ctx.cfg.configuration
136142

137143
// Find all tags that need to be updated based on changed files

0 commit comments

Comments
 (0)