import { GlobalConfiguration } from "../../cfg" import { getDate } from "../../components/Date" import { escapeHTML } from "../../util/escape" import { FilePath, FullSlug, SimpleSlug, simplifySlug } from "../../util/path" import { QuartzEmitterPlugin } from "../types" import path from "path" export type ContentIndex = Map export type ContentDetails = { title: string links: SimpleSlug[] tags: string[] content: string date?: Date description?: string } interface Options { enableSiteMap: boolean enableRSS: boolean rssLimit?: number includeEmptyFiles: boolean } const defaultOptions: Options = { enableSiteMap: true, enableRSS: true, rssLimit: 10, includeEmptyFiles: true, } function generateSiteMap(cfg: GlobalConfiguration, idx: ContentIndex): string { const base = cfg.baseUrl ?? "" const createURLEntry = (slug: SimpleSlug, content: ContentDetails): string => ` https://${base}/${encodeURI(slug)} ${content.date?.toISOString()} ` const urls = Array.from(idx) .map(([slug, content]) => createURLEntry(simplifySlug(slug), content)) .join("") return `${urls}` } function generateRSSFeed(cfg: GlobalConfiguration, idx: ContentIndex, limit?: number): string { const base = cfg.baseUrl ?? "" const root = `https://${base}` const createURLEntry = (slug: SimpleSlug, content: ContentDetails): string => ` ${escapeHTML(content.title)} ${root}/${encodeURI(slug)} ${root}/${encodeURI(slug)} ${content.description} ${content.date?.toUTCString()} ` const items = Array.from(idx) .map(([slug, content]) => createURLEntry(simplifySlug(slug), content)) .slice(0, limit ?? idx.size) .join("") return ` ${escapeHTML(cfg.pageTitle)} ${root} ${!!limit ? `Last ${limit} notes` : "Recent notes"} 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 = 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, { 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 FullSlug, ext: ".xml", }), ) } if (opts?.enableRSS) { emitted.push( await emit({ content: generateRSSFeed(cfg, linkIndex, opts.rssLimit), slug: "index" as FullSlug, ext: ".xml", }), ) } const fp = path.join("static", "contentIndex") as FullSlug 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: () => [], } }