import { GlobalConfiguration } from "../../cfg" import { CanonicalSlug, ClientSlug, FilePath, ServerSlug, canonicalizeServer, } from "../../util/path" import { QuartzEmitterPlugin } from "../types" import path from "path" export type ContentIndex = Map export type ContentDetails = { title: string links: CanonicalSlug[] tags: string[] content: string date?: Date description?: string } interface Options { enableSiteMap: boolean enableRSS: boolean includeEmptyFiles: boolean } const defaultOptions: Options = { enableSiteMap: true, enableRSS: true, includeEmptyFiles: false, } function generateSiteMap(cfg: GlobalConfiguration, idx: ContentIndex): string { const base = cfg.baseUrl ?? "" const createURLEntry = (slug: CanonicalSlug, content: ContentDetails): string => ` https://${base}/${slug} ${content.date?.toISOString()} ` const urls = Array.from(idx) .map(([slug, content]) => createURLEntry(slug, content)) .join("") return `${urls}` } function generateRSSFeed(cfg: GlobalConfiguration, idx: ContentIndex): string { const base = cfg.baseUrl ?? "" const root = `https://${base}` as ClientSlug const createURLEntry = (slug: CanonicalSlug, content: ContentDetails): string => ` ${content.title} ${root}/${slug} ${root}/${slug} ${content.description} ${content.date?.toUTCString()} ` const items = Array.from(idx) .map(([slug, content]) => createURLEntry(slug, content)) .join("") return ` ${cfg.pageTitle} ${root} Recent content on ${cfg.pageTitle} Quartz -- quartz.jzhao.xyz ${items} ` } export const ContentIndex: QuartzEmitterPlugin> = (opts) => { opts = { ...defaultOptions, ...opts } return { name: "ContentIndex", async emit(ctx, content, _resources, emit) { const cfg = ctx.cfg.configuration const emitted: FilePath[] = [] const linkIndex: ContentIndex = new Map() for (const [_tree, file] of content) { const slug = canonicalizeServer(file.data.slug!) const date = file.data.dates?.modified ?? new Date() if (opts?.includeEmptyFiles || (file.data.text && file.data.text !== "")) { linkIndex.set(slug, { title: file.data.frontmatter?.title!, links: file.data.links ?? [], tags: file.data.frontmatter?.tags ?? [], content: file.data.text ?? "", date: date, description: file.data.description ?? "", }) } } if (opts?.enableSiteMap) { emitted.push( await emit({ content: generateSiteMap(cfg, linkIndex), slug: "sitemap" as ServerSlug, ext: ".xml", }), ) } if (opts?.enableRSS) { emitted.push( await emit({ content: generateRSSFeed(cfg, linkIndex), slug: "index" as ServerSlug, ext: ".xml", }), ) } const fp = path.join("static", "contentIndex") as ServerSlug const simplifiedIndex = Object.fromEntries( Array.from(linkIndex).map(([slug, content]) => { // remove description and from content index as nothing downstream // actually uses it. we only keep it in the index as we need it // for the RSS feed delete content.description delete content.date return [slug, content] }), ) emitted.push( await emit({ content: JSON.stringify(simplifiedIndex), slug: fp, ext: ".json", }), ) return emitted }, getQuartzComponents: () => [], } }