mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-19 19:04:06 -06:00
feat(contentIndex): Per-folder RSS feeds
This commit is contained in:
parent
cf7bb1fe83
commit
c65e6836b4
@ -1,10 +1,16 @@
|
||||
Quartz emits an RSS feed for all the content on your site by generating an `index.xml` file that RSS readers can subscribe to. Because of the RSS spec, this requires the `baseUrl` property in your [[configuration]] to be set properly for RSS readers to pick it up properly.
|
||||
Quartz emits an RSS feed for all the content on your site by generating an file that RSS readers can subscribe to. Because of the RSS spec, this requires the `baseUrl` property in your [[configuration]] to be set properly for RSS readers to pick it up properly.
|
||||
|
||||
> [!info]
|
||||
> After deploying, the generated RSS link will be available at `https://${baseUrl}/index.xml` by default.
|
||||
>
|
||||
> The `index.xml` path can be customized by passing the `rssSlug` option to the [[ContentIndex]] plugin.
|
||||
|
||||
Quartz also generates RSS feeds for all subdirectories on your site. Add `.rss` to the end of the directory link to download an RSS file limited to the content in that directory and its subdirectories.
|
||||
|
||||
- Subdirectories containing an `index.md` file with `noRSS: true` in the frontmatter will not generate an RSS feed.
|
||||
- The entries in that subdirectory will still be present in the default feed.
|
||||
- You can hide a file from all RSS feeds by putting `noRSS: true` in that file's frontmatter.
|
||||
|
||||
## Configuration
|
||||
|
||||
This functionality is provided by the [[ContentIndex]] plugin. See the plugin page for customization options.
|
||||
|
||||
@ -19,7 +19,7 @@ import DepGraph from "../../depgraph"
|
||||
import chalk from "chalk"
|
||||
import { ProcessedContent } from "../vfile"
|
||||
|
||||
export type ContentIndexMap = Map<FullSlug, ContentDetails>
|
||||
type ContentIndex = Tree<TreeNode>
|
||||
export type ContentDetails = {
|
||||
slug: FullSlug
|
||||
filePath: FilePath
|
||||
@ -40,7 +40,7 @@ interface Options {
|
||||
bypassIndexCheck: boolean
|
||||
rssLimit?: number
|
||||
rssFullHtml: boolean
|
||||
rssSlug: string
|
||||
rssSlug: FullSlug
|
||||
includeEmptyFiles: boolean
|
||||
titlePattern?: (cfg: GlobalConfiguration, dir: FullSlug, dirIndex?: ContentDetails) => string
|
||||
}
|
||||
@ -51,13 +51,13 @@ const defaultOptions: Options = {
|
||||
enableRSS: true,
|
||||
rssLimit: 10,
|
||||
rssFullHtml: false,
|
||||
rssSlug: "index",
|
||||
rssSlug: "index" as FullSlug,
|
||||
includeEmptyFiles: true,
|
||||
titlePattern: (cfg, dir, dirIndex) =>
|
||||
`${cfg.pageTitle} - ${dirIndex != null ? dirIndex.title : dir.split("/").pop()}`,
|
||||
}
|
||||
|
||||
function generateSiteMap(cfg: GlobalConfiguration, idx: ContentIndexMap): string {
|
||||
function generateSiteMap(cfg: GlobalConfiguration, idx: ContentIndex): string {
|
||||
const base = cfg.baseUrl ?? ""
|
||||
const createURLEntry = (content: ContentDetails): string => `
|
||||
<url>
|
||||
@ -69,7 +69,7 @@ function generateSiteMap(cfg: GlobalConfiguration, idx: ContentIndexMap): string
|
||||
</urlset>`
|
||||
}
|
||||
|
||||
function generateRSSFeed(cfg: GlobalConfiguration, idx: ContentIndexMap, limit?: number): string {
|
||||
function finishRSSFeed(cfg: GlobalConfiguration, opts: Partial<Options>, entries: Feed): string {
|
||||
const base = cfg.baseUrl ?? ""
|
||||
const feedTitle = opts.titlePattern!(cfg, entries.dir, entries.dirIndex)
|
||||
const limit = opts?.rssLimit ?? entries.raw.length
|
||||
@ -165,24 +165,26 @@ export const ContentIndex: QuartzEmitterPlugin<Partial<Options>> = (opts) => {
|
||||
}
|
||||
|
||||
const cfg = ctx.cfg.configuration
|
||||
const linkIndex: ContentIndexMap = new Map()
|
||||
for (const [tree, file] of content) {
|
||||
const slug = file.data.slug!
|
||||
const date = getDate(ctx.cfg.configuration, file.data) ?? new Date()
|
||||
if (opts?.includeEmptyFiles || (file.data.text && file.data.text !== "")) {
|
||||
linkIndex.set(slug, {
|
||||
slug,
|
||||
filePath: file.data.relativePath!,
|
||||
title: file.data.frontmatter?.title!,
|
||||
links: file.data.links ?? [],
|
||||
tags: file.data.frontmatter?.tags ?? [],
|
||||
content: file.data.text ?? "",
|
||||
richContent: opts?.rssFullHtml
|
||||
? escapeHTML(toHtml(tree as Root, { allowDangerousHtml: true }))
|
||||
: undefined,
|
||||
date: date,
|
||||
description: file.data.description ?? "",
|
||||
})
|
||||
const emitted: Promise<FilePath>[] = []
|
||||
var indexTree = new Tree<TreeNode>(defaultFeed(), compareTreeNodes)
|
||||
|
||||
// ProcessedContent[] -> Tree<TreeNode>
|
||||
// bfahrenfort: If I could finagle a Visitor pattern to cross
|
||||
// different datatypes (TransformVisitor<T, K>?), half of this pass could be
|
||||
// folded into the FeedGenerator postorder accept
|
||||
const detailsOf = ([tree, file]: ProcessedContent): ContentDetails => {
|
||||
return {
|
||||
title: file.data.frontmatter?.title!,
|
||||
links: file.data.links ?? [],
|
||||
tags: file.data.frontmatter?.tags ?? [],
|
||||
content: file.data.text ?? "",
|
||||
richContent: opts?.rssFullHtml
|
||||
? escapeHTML(toHtml(tree as Root, { allowDangerousHtml: true }))
|
||||
: undefined,
|
||||
date: getDate(ctx.cfg.configuration, file.data) ?? new Date(),
|
||||
description: file.data.description ?? "",
|
||||
slug: slugifyFilePath(file.data.relativePath!, true),
|
||||
noRSS: file.data.frontmatter?.noRSS ?? false,
|
||||
}
|
||||
}
|
||||
for (const [tree, file] of content) {
|
||||
@ -241,8 +243,8 @@ export const ContentIndex: QuartzEmitterPlugin<Partial<Options>> = (opts) => {
|
||||
emitted.push(
|
||||
write({
|
||||
ctx,
|
||||
content: generateRSSFeed(cfg, linkIndex, opts.rssLimit),
|
||||
slug: (opts?.rssSlug ?? "index") as FullSlug,
|
||||
content: finishRSSFeed(cfg, opts, feedTree.data as Feed),
|
||||
slug: opts.rssSlug!, // Safety: defaults to "index"
|
||||
ext: ".xml",
|
||||
}),
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user