From cee2883c0889a65e2786d70eb81932f5ed017e59 Mon Sep 17 00:00:00 2001
From: Jacky Zhao <j.zhao2k19@gmail.com>
Date: Wed, 26 Jul 2023 04:10:37 +0000
Subject: [PATCH] nested tag support and tag index page
---
quartz/components/PageList.tsx | 13 +++
quartz/plugins/transformers/frontmatter.ts | 4
quartz/path.ts | 22 +++++++
quartz/components/TagList.tsx | 5 -
quartz/plugins/emitters/componentResources.ts | 2
content/features/upcoming features.md | 2
content/features/syntax highlighting.md | 2
quartz/plugins/emitters/tagPage.tsx | 34 ++++++++---
quartz/plugins/transformers/ofm.ts | 4
quartz/theme.ts | 1
content/features/Latex.md | 5 +
quartz/components/pages/TagContent.tsx | 66 ++++++++++++++++++----
quartz/components/styles/popover.scss | 2
13 files changed, 125 insertions(+), 37 deletions(-)
diff --git a/content/features/Latex.md b/content/features/Latex.md
index 3f523d6..0dbc113 100644
--- a/content/features/Latex.md
+++ b/content/features/Latex.md
@@ -1,3 +1,8 @@
+---
+tags:
+ - plugin/transformer
+---
+
Quartz uses [Katex](https://katex.org/) by default to typeset both inline and block math expressions at build time.
## Formatting
diff --git a/content/features/syntax highlighting.md b/content/features/syntax highlighting.md
index 16d3fa9..68436c2 100644
--- a/content/features/syntax highlighting.md
+++ b/content/features/syntax highlighting.md
@@ -1,5 +1,7 @@
---
title: Syntax Highlighting
+tags:
+ - plugin/transformer
---
Syntax highlighting in Quartz is completely done at build-time. This means that Quartz only ships pre-calculated CSS to highlight the right words so there is no heavy client-side bundle that does the syntax highlighting.
diff --git a/content/features/upcoming features.md b/content/features/upcoming features.md
index 676bb71..dab75c6 100644
--- a/content/features/upcoming features.md
+++ b/content/features/upcoming features.md
@@ -4,9 +4,7 @@
## high priority
-- local fonts
- images in same folder are broken on shortest path mode
-- https://help.obsidian.md/Editing+and+formatting/Tags#Nested+tags nested tags?? and big tag listing
- watch mode for config/source code
- https://help.obsidian.md/Editing+and+formatting/Basic+formatting+syntax#Task+lists task list styling
- publish metadata https://help.obsidian.md/Editing+and+formatting/Metadata#Metadata+for+Obsidian+Publish
diff --git a/quartz/components/PageList.tsx b/quartz/components/PageList.tsx
index 8ef40c5..7183acb 100644
--- a/quartz/components/PageList.tsx
+++ b/quartz/components/PageList.tsx
@@ -20,11 +20,20 @@
return f1Title.localeCompare(f2Title)
}
-export function PageList({ fileData, allFiles }: QuartzComponentProps) {
+type Props = {
+ limit?: number
+} & QuartzComponentProps
+
+export function PageList({ fileData, allFiles, limit }: Props) {
const slug = canonicalizeServer(fileData.slug!)
+ let list = allFiles.sort(byDateAndAlphabetical)
+ if (limit) {
+ list = list.slice(0, limit)
+ }
+
return (
<ul class="section-ul">
- {allFiles.sort(byDateAndAlphabetical).map((page) => {
+ {list.map((page) => {
const title = page.frontmatter?.title
const pageSlug = canonicalizeServer(page.slug!)
const tags = page.frontmatter?.tags ?? []
diff --git a/quartz/components/TagList.tsx b/quartz/components/TagList.tsx
index 0bffb0d..6560035 100644
--- a/quartz/components/TagList.tsx
+++ b/quartz/components/TagList.tsx
@@ -1,6 +1,5 @@
-import { canonicalizeServer, pathToRoot } from "../path"
+import { canonicalizeServer, pathToRoot, slugTag } from "../path"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
-import { slug as slugAnchor } from "github-slugger"
function TagList({ fileData }: QuartzComponentProps) {
const tags = fileData.frontmatter?.tags
@@ -11,7 +10,7 @@
<ul class="tags">
{tags.map((tag) => {
const display = `#${tag}`
- const linkDest = baseDir + `/tags/${slugAnchor(tag)}`
+ const linkDest = baseDir + `/tags/${slugTag(tag)}`
return (
<li>
<a href={linkDest} class="internal tag-link">
diff --git a/quartz/components/pages/TagContent.tsx b/quartz/components/pages/TagContent.tsx
index 6a3d827..2a9dfca 100644
--- a/quartz/components/pages/TagContent.tsx
+++ b/quartz/components/pages/TagContent.tsx
@@ -3,33 +3,75 @@
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
import style from "../styles/listPage.scss"
import { PageList } from "../PageList"
-import { ServerSlug, canonicalizeServer } from "../../path"
+import { ServerSlug, canonicalizeServer, getAllSegmentPrefixes } from "../../path"
+import { QuartzPluginData } from "../../plugins/vfile"
+const numPages = 10
function TagContent(props: QuartzComponentProps) {
const { tree, fileData, allFiles } = props
const slug = fileData.slug
- if (slug?.startsWith("tags/")) {
- const tag = canonicalizeServer(slug.slice("tags/".length) as ServerSlug)
- const allPagesWithTag = allFiles.filter((file) => (file.frontmatter?.tags ?? []).includes(tag))
- const listProps = {
- ...props,
- allFiles: allPagesWithTag,
+ if (!slug?.startsWith("tags/")) {
+ throw new Error(`Component "TagContent" tried to render a non-tag page: ${slug}`)
+ }
+
+ const tag = canonicalizeServer(slug.slice("tags/".length) as ServerSlug)
+ const allPagesWithTag = (tag: string) =>
+ allFiles.filter((file) =>
+ (file.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes).includes(tag),
+ )
+
+ // @ts-ignore
+ const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
+ if (tag === "") {
+ const tags = [...new Set(allFiles.flatMap((data) => data.frontmatter?.tags ?? []))]
+ const tagItemMap: Map<string, QuartzPluginData[]> = new Map()
+ for (const tag of tags) {
+ tagItemMap.set(tag, allPagesWithTag(tag))
}
- // @ts-ignore
- const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
return (
<div class="popover-hint">
<article>{content}</article>
- <p>{allPagesWithTag.length} items with this tag.</p>
+ <p>Found {tags.length} total tags.</p>
+ <div>
+ {tags.map((tag) => {
+ const pages = tagItemMap.get(tag)!
+ const listProps = {
+ ...props,
+ allFiles: pages,
+ }
+ return (
+ <div>
+ <h2>
+ <a class="internal tag-link" href={`./tags/${tag}`}>
+ #{tag}
+ </a>
+ </h2>
+ <p>{pages.length} items with this tag. {pages.length > numPages && `Showing first ${numPages}.`}</p>
+ <PageList limit={numPages} {...listProps} />
+ </div>
+ )
+ })}
+ </div>
+ </div>
+ )
+ } else {
+ const pages = allPagesWithTag(tag)
+ const listProps = {
+ ...props,
+ allFiles: pages,
+ }
+
+ return (
+ <div class="popover-hint">
+ <article>{content}</article>
+ <p>{pages.length} items with this tag.</p>
<div>
<PageList {...listProps} />
</div>
</div>
)
- } else {
- throw new Error(`Component "TagContent" tried to render a non-tag page: ${slug}`)
}
}
diff --git a/quartz/components/styles/popover.scss b/quartz/components/styles/popover.scss
index 3616e51..21e6b72 100644
--- a/quartz/components/styles/popover.scss
+++ b/quartz/components/styles/popover.scss
@@ -24,7 +24,7 @@
position: relative;
width: 30rem;
max-height: 20rem;
- padding: 0 1rem 2rem 1rem;
+ padding: 0 1rem 1rem 1rem;
font-weight: initial;
line-height: normal;
font-size: initial;
diff --git a/quartz/path.ts b/quartz/path.ts
index 0d3a0c6..fca2c05 100644
--- a/quartz/path.ts
+++ b/quartz/path.ts
@@ -1,4 +1,4 @@
-import { slug as slugAnchor } from "github-slugger"
+import { slug } from "github-slugger"
import { trace } from "./trace"
// Quartz Paths
@@ -197,10 +197,30 @@
return [fp, anchor]
}
+export function slugAnchor(anchor: string) {
+ return slug(anchor)
+}
+
+export function slugTag(tag: string) {
+ return tag
+ .split("/")
+ .map((tagSegment) => slug(tagSegment))
+ .join("/")
+}
+
export function joinSegments(...args: string[]): string {
return args.filter((segment) => segment !== "").join("/")
}
+export function getAllSegmentPrefixes(tags: string): string[] {
+ const segments = tags.split("/")
+ const results: string[] = []
+ for (let i = 0; i < segments.length; i++) {
+ results.push(segments.slice(0, i + 1).join("/"))
+ }
+ return results
+}
+
export const QUARTZ = "quartz"
function _canonicalize(fp: string): string {
diff --git a/quartz/plugins/emitters/componentResources.ts b/quartz/plugins/emitters/componentResources.ts
index 72a8841..69c9dd5 100644
--- a/quartz/plugins/emitters/componentResources.ts
+++ b/quartz/plugins/emitters/componentResources.ts
@@ -160,7 +160,7 @@
content: transform({
filename: "index.css",
code: Buffer.from(stylesheet),
- minify: true
+ minify: true,
}).code.toString(),
}),
emit({
diff --git a/quartz/plugins/emitters/tagPage.tsx b/quartz/plugins/emitters/tagPage.tsx
index 69b0180..2154851 100644
--- a/quartz/plugins/emitters/tagPage.tsx
+++ b/quartz/plugins/emitters/tagPage.tsx
@@ -5,7 +5,13 @@
import { pageResources, renderPage } from "../../components/renderPage"
import { ProcessedContent, defaultProcessedContent } from "../vfile"
import { FullPageLayout } from "../../cfg"
-import { CanonicalSlug, FilePath, ServerSlug, joinSegments } from "../../path"
+import {
+ CanonicalSlug,
+ FilePath,
+ ServerSlug,
+ getAllSegmentPrefixes,
+ joinSegments,
+} from "../../path"
export const TagPage: QuartzEmitterPlugin<FullPageLayout> = (opts) => {
if (!opts) {
@@ -26,15 +32,23 @@
const allFiles = content.map((c) => c[1].data)
const cfg = ctx.cfg.configuration
- const tags: Set<string> = new Set(allFiles.flatMap((data) => data.frontmatter?.tags ?? []))
+ const tags: Set<string> = new Set(
+ allFiles.flatMap((data) => data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes),
+ )
+ // add base tag
+ tags.add("")
+
const tagDescriptions: Record<string, ProcessedContent> = Object.fromEntries(
- [...tags].map((tag) => [
- tag,
- defaultProcessedContent({
- slug: `tags/${tag}/index` as ServerSlug,
- frontmatter: { title: `Tag: ${tag}`, tags: [] },
- }),
- ]),
+ [...tags].map((tag) => {
+ const title = tag === "" ? "Tag Index" : `Tag: #${tag}`
+ return [
+ tag,
+ defaultProcessedContent({
+ slug: joinSegments("tags", tag, "index") as ServerSlug,
+ frontmatter: { title, tags: [] },
+ }),
+ ]
+ }),
)
for (const [tree, file] of content) {
@@ -48,7 +62,7 @@
}
for (const tag of tags) {
- const slug = `tags/${tag}/index` as CanonicalSlug
+ const slug = joinSegments("tags", tag) as CanonicalSlug
const externalResources = pageResources(slug, resources)
const [tree, file] = tagDescriptions[tag]
const componentData: QuartzComponentProps = {
diff --git a/quartz/plugins/transformers/frontmatter.ts b/quartz/plugins/transformers/frontmatter.ts
index 29b0b4b..58ae7c7 100644
--- a/quartz/plugins/transformers/frontmatter.ts
+++ b/quartz/plugins/transformers/frontmatter.ts
@@ -2,7 +2,7 @@
import remarkFrontmatter from "remark-frontmatter"
import { QuartzTransformerPlugin } from "../types"
import yaml from "js-yaml"
-import { slug as slugAnchor } from "github-slugger"
+import { slugTag } from "../../path"
export interface Options {
language: "yaml" | "toml"
@@ -43,7 +43,7 @@
}
// slug them all!!
- data.tags = data.tags?.map((tag: string) => slugAnchor(tag)) ?? []
+ data.tags = data.tags?.map((tag: string) => slugTag(tag)) ?? []
// fill in frontmatter
file.data.frontmatter = {
diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts
index 9840ea8..4a45b02 100644
--- a/quartz/plugins/transformers/ofm.ts
+++ b/quartz/plugins/transformers/ofm.ts
@@ -9,7 +9,7 @@
import { JSResource } from "../../resources"
// @ts-ignore
import calloutScript from "../../components/scripts/callout.inline.ts"
-import { FilePath, canonicalizeServer, pathToRoot, slugifyFilePath } from "../../path"
+import { FilePath, canonicalizeServer, pathToRoot, slugTag, slugifyFilePath } from "../../path"
export interface Options {
comments: boolean
@@ -337,7 +337,7 @@
return {
type: "link",
- url: base + `/tags/${slugAnchor(tag)}`,
+ url: base + `/tags/${slugTag(tag)}`,
data: {
hProperties: {
className: ["tag-link"],
diff --git a/quartz/theme.ts b/quartz/theme.ts
index b01bfdc..8d7b727 100644
--- a/quartz/theme.ts
+++ b/quartz/theme.ts
@@ -60,5 +60,4 @@
--highlight: ${theme.colors.darkMode.highlight};
}
`
-
}
--
Gitblit v1.10.0