From c538c151c7462ad0395ff2c15c5e11e89e362aa8 Mon Sep 17 00:00:00 2001
From: Striven <sg.striven@cutecat.club>
Date: Sat, 04 Apr 2026 19:47:16 +0000
Subject: [PATCH] Initial commit

---
 quartz/plugins/emitters/folderPage.tsx |  203 ++++++++++++++++++++++++++++++++++++--------------
 1 files changed, 146 insertions(+), 57 deletions(-)

diff --git a/quartz/plugins/emitters/folderPage.tsx b/quartz/plugins/emitters/folderPage.tsx
index 2f6bcbf..f9b181d 100644
--- a/quartz/plugins/emitters/folderPage.tsx
+++ b/quartz/plugins/emitters/folderPage.tsx
@@ -3,79 +3,168 @@
 import HeaderConstructor from "../../components/Header"
 import BodyConstructor from "../../components/Body"
 import { pageResources, renderPage } from "../../components/renderPage"
-import { ProcessedContent, defaultProcessedContent } from "../vfile"
+import { ProcessedContent, QuartzPluginData, defaultProcessedContent } from "../vfile"
 import { FullPageLayout } from "../../cfg"
 import path from "path"
-import { CanonicalSlug, FilePath, ServerSlug, canonicalizeServer, joinSegments } from "../../path"
+import {
+  FullSlug,
+  SimpleSlug,
+  stripSlashes,
+  joinSegments,
+  pathToRoot,
+  simplifySlug,
+} from "../../util/path"
+import { defaultListPageLayout, sharedPageComponents } from "../../../quartz.layout"
+import { FolderContent } from "../../components"
+import { write } from "./helpers"
+import { i18n, TRANSLATIONS } from "../../i18n"
+import { BuildCtx } from "../../util/ctx"
+import { StaticResources } from "../../util/resources"
+interface FolderPageOptions extends FullPageLayout {
+  sort?: (f1: QuartzPluginData, f2: QuartzPluginData) => number
+}
 
-export const FolderPage: QuartzEmitterPlugin<FullPageLayout> = (opts) => {
-  if (!opts) {
-    throw new Error("ErrorPage must be initialized with options specifiying the components to use")
+async function* processFolderInfo(
+  ctx: BuildCtx,
+  folderInfo: Record<SimpleSlug, ProcessedContent>,
+  allFiles: QuartzPluginData[],
+  opts: FullPageLayout,
+  resources: StaticResources,
+) {
+  for (const [folder, folderContent] of Object.entries(folderInfo) as [
+    SimpleSlug,
+    ProcessedContent,
+  ][]) {
+    const slug = joinSegments(folder, "index") as FullSlug
+    const [tree, file] = folderContent
+    const cfg = ctx.cfg.configuration
+    const externalResources = pageResources(pathToRoot(slug), resources)
+    const componentData: QuartzComponentProps = {
+      ctx,
+      fileData: file.data,
+      externalResources,
+      cfg,
+      children: [],
+      tree,
+      allFiles,
+    }
+
+    const content = renderPage(cfg, slug, componentData, opts, externalResources)
+    yield write({
+      ctx,
+      content,
+      slug,
+      ext: ".html",
+    })
+  }
+}
+
+function computeFolderInfo(
+  folders: Set<SimpleSlug>,
+  content: ProcessedContent[],
+  locale: keyof typeof TRANSLATIONS,
+): Record<SimpleSlug, ProcessedContent> {
+  // Create default folder descriptions
+  const folderInfo: Record<SimpleSlug, ProcessedContent> = Object.fromEntries(
+    [...folders].map((folder) => [
+      folder,
+      defaultProcessedContent({
+        slug: joinSegments(folder, "index") as FullSlug,
+        frontmatter: {
+          title: `${i18n(locale).pages.folderContent.folder}: ${folder}`,
+          tags: [],
+        },
+      }),
+    ]),
+  )
+
+  // Update with actual content if available
+  for (const [tree, file] of content) {
+    const slug = stripSlashes(simplifySlug(file.data.slug!)) as SimpleSlug
+    if (folders.has(slug)) {
+      folderInfo[slug] = [tree, file]
+    }
   }
 
-  const { head: Head, header, beforeBody, pageBody: Content, left, right, footer: Footer } = opts
+  return folderInfo
+}
+
+function _getFolders(slug: FullSlug): SimpleSlug[] {
+  var folderName = path.dirname(slug ?? "") as SimpleSlug
+  const parentFolderNames = [folderName]
+
+  while (folderName !== ".") {
+    folderName = path.dirname(folderName ?? "") as SimpleSlug
+    parentFolderNames.push(folderName)
+  }
+  return parentFolderNames
+}
+
+export const FolderPage: QuartzEmitterPlugin<Partial<FolderPageOptions>> = (userOpts) => {
+  const opts: FullPageLayout = {
+    ...sharedPageComponents,
+    ...defaultListPageLayout,
+    pageBody: FolderContent({ sort: userOpts?.sort }),
+    ...userOpts,
+  }
+
+  const { head: Head, header, beforeBody, pageBody, afterBody, left, right, footer: Footer } = opts
   const Header = HeaderConstructor()
   const Body = BodyConstructor()
 
   return {
     name: "FolderPage",
     getQuartzComponents() {
-      return [Head, Header, Body, ...header, ...beforeBody, Content, ...left, ...right, Footer]
+      return [
+        Head,
+        Header,
+        Body,
+        ...header,
+        ...beforeBody,
+        pageBody,
+        ...afterBody,
+        ...left,
+        ...right,
+        Footer,
+      ]
     },
-    async emit(_contentDir, cfg, content, resources, emit): Promise<FilePath[]> {
-      const fps: FilePath[] = []
-      const allFiles = content.map(c => c[1].data)
+    async *emit(ctx, content, resources) {
+      const allFiles = content.map((c) => c[1].data)
+      const cfg = ctx.cfg.configuration
 
-      const folders: Set<CanonicalSlug> = new Set(allFiles.flatMap(data => {
-        const slug = data.slug
-        const folderName = path.dirname(slug ?? "") as CanonicalSlug
-        if (slug && folderName !== "." && folderName !== "tags") {
-          return [folderName]
-        }
-        return []
-      }))
+      const folders: Set<SimpleSlug> = new Set(
+        allFiles.flatMap((data) => {
+          return data.slug
+            ? _getFolders(data.slug).filter(
+                (folderName) => folderName !== "." && folderName !== "tags",
+              )
+            : []
+        }),
+      )
 
-      const folderDescriptions: Record<string, ProcessedContent> = Object.fromEntries([...folders].map(folder => ([
-        folder, defaultProcessedContent({ slug: joinSegments(folder, "index") as ServerSlug, frontmatter: { title: `Folder: ${folder}`, tags: [] } })
-      ])))
+      const folderInfo = computeFolderInfo(folders, content, cfg.locale)
+      yield* processFolderInfo(ctx, folderInfo, allFiles, opts, resources)
+    },
+    async *partialEmit(ctx, content, resources, changeEvents) {
+      const allFiles = content.map((c) => c[1].data)
+      const cfg = ctx.cfg.configuration
 
-      for (const [tree, file] of content) {
-        const slug = canonicalizeServer(file.data.slug!)
-        if (folders.has(slug)) {
-          folderDescriptions[slug] = [tree, file]
-        }
-      }
-
-      for (const folder of folders) {
-        const slug = folder
-        const externalResources = pageResources(slug, resources)
-        const [tree, file] = folderDescriptions[folder]
-        const componentData: QuartzComponentProps = {
-          fileData: file.data,
-          externalResources,
-          cfg,
-          children: [],
-          tree,
-          allFiles
-        }
-
-        const content = renderPage(
-          slug,
-          componentData,
-          opts,
-          externalResources
+      // Find all folders that need to be updated based on changed files
+      const affectedFolders: Set<SimpleSlug> = new Set()
+      for (const changeEvent of changeEvents) {
+        if (!changeEvent.file) continue
+        const slug = changeEvent.file.data.slug!
+        const folders = _getFolders(slug).filter(
+          (folderName) => folderName !== "." && folderName !== "tags",
         )
-
-        const fp = file.data.slug! + ".html" as FilePath
-        await emit({
-          content,
-          slug: file.data.slug!,
-          ext: ".html",
-        })
-
-        fps.push(fp)
+        folders.forEach((folder) => affectedFolders.add(folder))
       }
-      return fps
-    }
+
+      // If there are affected folders, rebuild their pages
+      if (affectedFolders.size > 0) {
+        const folderInfo = computeFolderInfo(affectedFolders, content, cfg.locale)
+        yield* processFolderInfo(ctx, folderInfo, allFiles, opts, resources)
+      }
+    },
   }
 }

--
Gitblit v1.10.0