From c9ddec07aa1dd47a0dd1498e8f6b6aea0e919699 Mon Sep 17 00:00:00 2001
From: Jacky Zhao <j.zhao2k19@gmail.com>
Date: Wed, 6 Sep 2023 21:02:21 -0700
Subject: [PATCH] feat: 404 page emitter

---
 docs/features/OxHugo compatibility.md |  2 +-
 docs/features/upcoming features.md    |  3 +-
 quartz.config.ts                      |  1 +
 quartz/components/index.ts            |  4 +-
 quartz/components/pages/404.tsx       | 12 ++++++
 quartz/plugins/emitters/404.tsx       | 56 +++++++++++++++++++++++++++
 quartz/plugins/emitters/index.ts      |  1 +
 7 files changed, 75 insertions(+), 4 deletions(-)
 create mode 100644 quartz/components/pages/404.tsx
 create mode 100644 quartz/plugins/emitters/404.tsx

diff --git a/docs/features/OxHugo compatibility.md b/docs/features/OxHugo compatibility.md
index 7801f0c..b25167f 100644
--- a/docs/features/OxHugo compatibility.md	
+++ b/docs/features/OxHugo compatibility.md	
@@ -3,7 +3,7 @@ tags:
   - plugin/transformer
 ---
 
-[org-roam](https://www.orgroam.com/) is a plain-text(`org`) personal knowledge management system for [emacs](https://en.wikipedia.org/wiki/Emacs). [ox-hugo](https://github.com/kaushalmodi/ox-hugo) is org exporter backend that exports `org-mode` files to [Hugo](https://gohugo.io/) compatible Markdown.
+[org-roam](https://www.orgroam.com/) is a plain-text personal knowledge management system for [emacs](https://en.wikipedia.org/wiki/Emacs). [ox-hugo](https://github.com/kaushalmodi/ox-hugo) is org exporter backend that exports `org-mode` files to [Hugo](https://gohugo.io/) compatible Markdown.
 
 Because the Markdown generated by ox-hugo is not pure Markdown but Hugo specific, we need to transform it to fit into Quartz. This is done by `Plugin.OxHugoFlavouredMarkdown`. Even though this [[making plugins|plugin]] was written with `ox-hugo` in mind, it should work for any Hugo specific Markdown.
 
diff --git a/docs/features/upcoming features.md b/docs/features/upcoming features.md
index fbfdbc9..76adda0 100644
--- a/docs/features/upcoming features.md	
+++ b/docs/features/upcoming features.md	
@@ -4,15 +4,14 @@ draft: true
 
 ## high priority backlog
 
+- static dead link detection
 - block links: https://help.obsidian.md/Linking+notes+and+files/Internal+links#Link+to+a+block+in+a+note
 - note/header/block transcludes: https://help.obsidian.md/Linking+notes+and+files/Embedding+files
-- static dead link detection
 - docker support
 
 ## misc backlog
 
 - breadcrumbs component
-- filetree component
 - cursor chat extension
 - https://giscus.app/ extension
 - sidenotes? https://github.com/capnfabs/paperesque
diff --git a/quartz.config.ts b/quartz.config.ts
index 31d5bcf..f677a18 100644
--- a/quartz.config.ts
+++ b/quartz.config.ts
@@ -69,6 +69,7 @@ const config: QuartzConfig = {
       }),
       Plugin.Assets(),
       Plugin.Static(),
+      Plugin.NotFoundPage(),
     ],
   },
 }
diff --git a/quartz/components/index.ts b/quartz/components/index.ts
index a83f078..10a43ac 100644
--- a/quartz/components/index.ts
+++ b/quartz/components/index.ts
@@ -1,7 +1,8 @@
-import ArticleTitle from "./ArticleTitle"
 import Content from "./pages/Content"
 import TagContent from "./pages/TagContent"
 import FolderContent from "./pages/FolderContent"
+import NotFound from "./pages/404"
+import ArticleTitle from "./ArticleTitle"
 import Darkmode from "./Darkmode"
 import Head from "./Head"
 import PageTitle from "./PageTitle"
@@ -36,4 +37,5 @@ export {
   DesktopOnly,
   MobileOnly,
   RecentNotes,
+  NotFound,
 }
diff --git a/quartz/components/pages/404.tsx b/quartz/components/pages/404.tsx
new file mode 100644
index 0000000..c276f56
--- /dev/null
+++ b/quartz/components/pages/404.tsx
@@ -0,0 +1,12 @@
+import { QuartzComponentConstructor } from "../types"
+
+function NotFound() {
+  return (
+    <article class="popover-hint">
+      <h1>404</h1>
+      <p>Either this page is private or doesn't exist.</p>
+    </article>
+  )
+}
+
+export default (() => NotFound) satisfies QuartzComponentConstructor
diff --git a/quartz/plugins/emitters/404.tsx b/quartz/plugins/emitters/404.tsx
new file mode 100644
index 0000000..785c873
--- /dev/null
+++ b/quartz/plugins/emitters/404.tsx
@@ -0,0 +1,56 @@
+import { QuartzEmitterPlugin } from "../types"
+import { QuartzComponentProps } from "../../components/types"
+import BodyConstructor from "../../components/Body"
+import { pageResources, renderPage } from "../../components/renderPage"
+import { FullPageLayout } from "../../cfg"
+import { FilePath, FullSlug } from "../../util/path"
+import { sharedPageComponents } from "../../../quartz.layout"
+import { NotFound } from "../../components"
+import { defaultProcessedContent } from "../vfile"
+
+export const NotFoundPage: QuartzEmitterPlugin = () => {
+  const opts: FullPageLayout = {
+    ...sharedPageComponents,
+    pageBody: NotFound(),
+    beforeBody: [],
+    left: [],
+    right: [],
+  }
+
+  const { head: Head, pageBody, footer: Footer } = opts
+  const Body = BodyConstructor()
+
+  return {
+    name: "404Page",
+    getQuartzComponents() {
+      return [Head, Body, pageBody, Footer]
+    },
+    async emit(ctx, _content, resources, emit): Promise<FilePath[]> {
+      const cfg = ctx.cfg.configuration
+      const slug = "404" as FullSlug
+      const externalResources = pageResources(slug, resources)
+      const [tree, vfile] = defaultProcessedContent({
+        slug,
+        text: "Not Found",
+        description: "Not Found",
+        frontmatter: { title: "Not Found", tags: [] },
+      })
+      const componentData: QuartzComponentProps = {
+        fileData: vfile.data,
+        externalResources,
+        cfg,
+        children: [],
+        tree,
+        allFiles: [],
+      }
+
+      return [
+        await emit({
+          content: renderPage(slug, componentData, opts, externalResources),
+          slug,
+          ext: ".html",
+        }),
+      ]
+    },
+  }
+}
diff --git a/quartz/plugins/emitters/index.ts b/quartz/plugins/emitters/index.ts
index da95d49..99a2c54 100644
--- a/quartz/plugins/emitters/index.ts
+++ b/quartz/plugins/emitters/index.ts
@@ -6,3 +6,4 @@ export { AliasRedirects } from "./aliases"
 export { Assets } from "./assets"
 export { Static } from "./static"
 export { ComponentResources } from "./componentResources"
+export { NotFoundPage } from "./404"