mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-20 03:14: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]
|
> [!info]
|
||||||
> After deploying, the generated RSS link will be available at `https://${baseUrl}/index.xml` by default.
|
> 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.
|
> 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
|
## Configuration
|
||||||
|
|
||||||
This functionality is provided by the [[ContentIndex]] plugin. See the plugin page for customization options.
|
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 chalk from "chalk"
|
||||||
import { ProcessedContent } from "../vfile"
|
import { ProcessedContent } from "../vfile"
|
||||||
|
|
||||||
export type ContentIndexMap = Map<FullSlug, ContentDetails>
|
type ContentIndex = Tree<TreeNode>
|
||||||
export type ContentDetails = {
|
export type ContentDetails = {
|
||||||
slug: FullSlug
|
slug: FullSlug
|
||||||
filePath: FilePath
|
filePath: FilePath
|
||||||
@ -40,7 +40,7 @@ interface Options {
|
|||||||
bypassIndexCheck: boolean
|
bypassIndexCheck: boolean
|
||||||
rssLimit?: number
|
rssLimit?: number
|
||||||
rssFullHtml: boolean
|
rssFullHtml: boolean
|
||||||
rssSlug: string
|
rssSlug: FullSlug
|
||||||
includeEmptyFiles: boolean
|
includeEmptyFiles: boolean
|
||||||
titlePattern?: (cfg: GlobalConfiguration, dir: FullSlug, dirIndex?: ContentDetails) => string
|
titlePattern?: (cfg: GlobalConfiguration, dir: FullSlug, dirIndex?: ContentDetails) => string
|
||||||
}
|
}
|
||||||
@ -51,13 +51,13 @@ const defaultOptions: Options = {
|
|||||||
enableRSS: true,
|
enableRSS: true,
|
||||||
rssLimit: 10,
|
rssLimit: 10,
|
||||||
rssFullHtml: false,
|
rssFullHtml: false,
|
||||||
rssSlug: "index",
|
rssSlug: "index" as FullSlug,
|
||||||
includeEmptyFiles: true,
|
includeEmptyFiles: true,
|
||||||
titlePattern: (cfg, dir, dirIndex) =>
|
titlePattern: (cfg, dir, dirIndex) =>
|
||||||
`${cfg.pageTitle} - ${dirIndex != null ? dirIndex.title : dir.split("/").pop()}`,
|
`${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 base = cfg.baseUrl ?? ""
|
||||||
const createURLEntry = (content: ContentDetails): string => `
|
const createURLEntry = (content: ContentDetails): string => `
|
||||||
<url>
|
<url>
|
||||||
@ -69,7 +69,7 @@ function generateSiteMap(cfg: GlobalConfiguration, idx: ContentIndexMap): string
|
|||||||
</urlset>`
|
</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 base = cfg.baseUrl ?? ""
|
||||||
const feedTitle = opts.titlePattern!(cfg, entries.dir, entries.dirIndex)
|
const feedTitle = opts.titlePattern!(cfg, entries.dir, entries.dirIndex)
|
||||||
const limit = opts?.rssLimit ?? entries.raw.length
|
const limit = opts?.rssLimit ?? entries.raw.length
|
||||||
@ -165,14 +165,15 @@ export const ContentIndex: QuartzEmitterPlugin<Partial<Options>> = (opts) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cfg = ctx.cfg.configuration
|
const cfg = ctx.cfg.configuration
|
||||||
const linkIndex: ContentIndexMap = new Map()
|
const emitted: Promise<FilePath>[] = []
|
||||||
for (const [tree, file] of content) {
|
var indexTree = new Tree<TreeNode>(defaultFeed(), compareTreeNodes)
|
||||||
const slug = file.data.slug!
|
|
||||||
const date = getDate(ctx.cfg.configuration, file.data) ?? new Date()
|
// ProcessedContent[] -> Tree<TreeNode>
|
||||||
if (opts?.includeEmptyFiles || (file.data.text && file.data.text !== "")) {
|
// bfahrenfort: If I could finagle a Visitor pattern to cross
|
||||||
linkIndex.set(slug, {
|
// different datatypes (TransformVisitor<T, K>?), half of this pass could be
|
||||||
slug,
|
// folded into the FeedGenerator postorder accept
|
||||||
filePath: file.data.relativePath!,
|
const detailsOf = ([tree, file]: ProcessedContent): ContentDetails => {
|
||||||
|
return {
|
||||||
title: file.data.frontmatter?.title!,
|
title: file.data.frontmatter?.title!,
|
||||||
links: file.data.links ?? [],
|
links: file.data.links ?? [],
|
||||||
tags: file.data.frontmatter?.tags ?? [],
|
tags: file.data.frontmatter?.tags ?? [],
|
||||||
@ -180,9 +181,10 @@ export const ContentIndex: QuartzEmitterPlugin<Partial<Options>> = (opts) => {
|
|||||||
richContent: opts?.rssFullHtml
|
richContent: opts?.rssFullHtml
|
||||||
? escapeHTML(toHtml(tree as Root, { allowDangerousHtml: true }))
|
? escapeHTML(toHtml(tree as Root, { allowDangerousHtml: true }))
|
||||||
: undefined,
|
: undefined,
|
||||||
date: date,
|
date: getDate(ctx.cfg.configuration, file.data) ?? new Date(),
|
||||||
description: file.data.description ?? "",
|
description: file.data.description ?? "",
|
||||||
})
|
slug: slugifyFilePath(file.data.relativePath!, true),
|
||||||
|
noRSS: file.data.frontmatter?.noRSS ?? false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const [tree, file] of content) {
|
for (const [tree, file] of content) {
|
||||||
@ -241,8 +243,8 @@ export const ContentIndex: QuartzEmitterPlugin<Partial<Options>> = (opts) => {
|
|||||||
emitted.push(
|
emitted.push(
|
||||||
write({
|
write({
|
||||||
ctx,
|
ctx,
|
||||||
content: generateRSSFeed(cfg, linkIndex, opts.rssLimit),
|
content: finishRSSFeed(cfg, opts, feedTree.data as Feed),
|
||||||
slug: (opts?.rssSlug ?? "index") as FullSlug,
|
slug: opts.rssSlug!, // Safety: defaults to "index"
|
||||||
ext: ".xml",
|
ext: ".xml",
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user