From 236130ac221f7d254ec9881f529f4ca567e15234 Mon Sep 17 00:00:00 2001
From: Jacky Zhao <j.zhao2k19@gmail.com>
Date: Sun, 20 Aug 2023 12:46:37 -0700
Subject: [PATCH] css fixes, add recent notes, more robust quartz update

---
 quartz/bootstrap-cli.mjs                  | 11 +--
 quartz/components/PageList.tsx            |  2 +-
 quartz/components/RecentNotes.tsx         | 81 +++++++++++++++++++++++
 quartz/components/index.ts                |  2 +
 quartz/components/pages/FolderContent.tsx |  9 +--
 quartz/components/pages/TagContent.tsx    | 13 ++--
 quartz/components/styles/recentNotes.scss | 23 +++++++
 quartz/styles/base.scss                   |  1 -
 8 files changed, 128 insertions(+), 14 deletions(-)
 create mode 100644 quartz/components/RecentNotes.tsx
 create mode 100644 quartz/components/styles/recentNotes.scss

diff --git a/quartz/bootstrap-cli.mjs b/quartz/bootstrap-cli.mjs
index 2c82955..ab63286 100755
--- a/quartz/bootstrap-cli.mjs
+++ b/quartz/bootstrap-cli.mjs
@@ -136,7 +136,10 @@ async function popContentFolder(contentFolder) {
 
 function gitPull(origin, branch) {
   const flags = ["--no-rebase", "--autostash", "-s", "recursive", "-X", "ours", "--no-edit"]
-  spawnSync("git", ["pull", ...flags, origin, branch], { stdio: "inherit" })
+  const out = spawnSync("git", ["pull", ...flags, origin, branch], { stdio: "inherit" })
+  if (out.stderr) {
+    throw new Error(`Error while pulling updates: ${out.stderr}`)
+  }
 }
 
 yargs(hideBin(process.argv))
@@ -258,13 +261,13 @@ See the [documentation](https://quartz.jzhao.xyz) for how to get started.
     const contentFolder = path.join(cwd, argv.directory)
     console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`))
     console.log("Backing up your content")
+    execSync(
+      `git remote show upstream || git remote add upstream https://github.com/jackyzha0/quartz.git`,
+    )
     await stashContentFolder(contentFolder)
     console.log(
       "Pulling updates... you may need to resolve some `git` conflicts if you've made changes to components or plugins.",
     )
-    execSync(
-      `git remote show upstream || git remote add upstream https://github.com/jackyzha0/quartz.git`,
-    )
     gitPull(UPSTREAM_NAME, QUARTZ_SOURCE_BRANCH)
     await popContentFolder(contentFolder)
     console.log("Ensuring dependencies are up to date")
diff --git a/quartz/components/PageList.tsx b/quartz/components/PageList.tsx
index d7aca08..c55b534 100644
--- a/quartz/components/PageList.tsx
+++ b/quartz/components/PageList.tsx
@@ -3,7 +3,7 @@ import { QuartzPluginData } from "../plugins/vfile"
 import { Date } from "./Date"
 import { QuartzComponentProps } from "./types"
 
-function byDateAndAlphabetical(f1: QuartzPluginData, f2: QuartzPluginData): number {
+export function byDateAndAlphabetical(f1: QuartzPluginData, f2: QuartzPluginData): number {
   if (f1.dates && f2.dates) {
     // sort descending by last modified
     return f2.dates.modified.getTime() - f1.dates.modified.getTime()
diff --git a/quartz/components/RecentNotes.tsx b/quartz/components/RecentNotes.tsx
new file mode 100644
index 0000000..bb7ba19
--- /dev/null
+++ b/quartz/components/RecentNotes.tsx
@@ -0,0 +1,81 @@
+import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
+import { FullSlug, SimpleSlug, resolveRelative } from "../util/path"
+import { QuartzPluginData } from "../plugins/vfile"
+import { byDateAndAlphabetical } from "./PageList"
+import style from "./styles/recentNotes.scss"
+import { Date } from "./Date"
+
+interface Options {
+  title: string
+  limit: number
+  linkToMore: SimpleSlug | false
+  filter: (f: QuartzPluginData) => boolean
+  sort: (f1: QuartzPluginData, f2: QuartzPluginData) => number
+}
+
+const defaultOptions: Options = {
+  title: "Recent Notes",
+  limit: 3,
+  linkToMore: false,
+  filter: () => true,
+  sort: byDateAndAlphabetical,
+}
+
+export default ((userOpts?: Partial<Options>) => {
+  const opts = { ...defaultOptions, ...userOpts }
+  function RecentNotes(props: QuartzComponentProps) {
+    const { allFiles, fileData } = props
+    const pages = allFiles.filter(opts.filter).sort(opts.sort).slice(0, opts.limit)
+    const remaining = Math.max(0, pages.length - opts.limit)
+    return (
+      <div class="recent-notes">
+        <h3>{opts.title}</h3>
+        <ul class="recent-ul">
+          {pages.map((page) => {
+            const title = page.frontmatter?.title
+            const tags = page.frontmatter?.tags ?? []
+
+            return (
+              <li class="recent-li">
+                <div class="section">
+                  <div class="desc">
+                    <h3>
+                      <a href={resolveRelative(fileData.slug!, page.slug!)} class="internal">
+                        {title}
+                      </a>
+                    </h3>
+                  </div>
+                  {page.dates && (
+                    <p class="meta">
+                      <Date date={page.dates.modified} />
+                    </p>
+                  )}
+                  <ul class="tags">
+                    {tags.map((tag) => (
+                      <li>
+                        <a
+                          class="internal tag-link"
+                          href={resolveRelative(fileData.slug!, `tags/${tag}` as FullSlug)}
+                        >
+                          #{tag}
+                        </a>
+                      </li>
+                    ))}
+                  </ul>
+                </div>
+              </li>
+            )
+          })}
+        </ul>
+        {opts.linkToMore && remaining > 0 && (
+          <p>
+            <a href={resolveRelative(fileData.slug!, opts.linkToMore)}>See {remaining} more →</a>
+          </p>
+        )}
+      </div>
+    )
+  }
+
+  RecentNotes.css = style
+  return RecentNotes
+}) satisfies QuartzComponentConstructor
diff --git a/quartz/components/index.ts b/quartz/components/index.ts
index caaf416..a83f078 100644
--- a/quartz/components/index.ts
+++ b/quartz/components/index.ts
@@ -15,6 +15,7 @@ import Search from "./Search"
 import Footer from "./Footer"
 import DesktopOnly from "./DesktopOnly"
 import MobileOnly from "./MobileOnly"
+import RecentNotes from "./RecentNotes"
 
 export {
   ArticleTitle,
@@ -34,4 +35,5 @@ export {
   Footer,
   DesktopOnly,
   MobileOnly,
+  RecentNotes,
 }
diff --git a/quartz/components/pages/FolderContent.tsx b/quartz/components/pages/FolderContent.tsx
index c1083ba..1528aea 100644
--- a/quartz/components/pages/FolderContent.tsx
+++ b/quartz/components/pages/FolderContent.tsx
@@ -25,10 +25,11 @@ function FolderContent(props: QuartzComponentProps) {
     allFiles: allPagesInFolder,
   }
 
-  const content = (tree as Root).children.length === 0 ?
-    fileData.description :
-    // @ts-ignore
-    toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
+  const content =
+    (tree as Root).children.length === 0
+      ? fileData.description
+      : // @ts-ignore
+        toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
 
   return (
     <div class="popover-hint">
diff --git a/quartz/components/pages/TagContent.tsx b/quartz/components/pages/TagContent.tsx
index 654e576..73ee465 100644
--- a/quartz/components/pages/TagContent.tsx
+++ b/quartz/components/pages/TagContent.tsx
@@ -22,10 +22,11 @@ function TagContent(props: QuartzComponentProps) {
       (file.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes).includes(tag),
     )
 
-  const content = (tree as Root).children.length === 0 ?
-    fileData.description :
-    // @ts-ignore
-    toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
+  const content =
+    (tree as Root).children.length === 0
+      ? fileData.description
+      : // @ts-ignore
+        toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
 
   if (tag === "") {
     const tags = [...new Set(allFiles.flatMap((data) => data.frontmatter?.tags ?? []))]
@@ -45,6 +46,9 @@ function TagContent(props: QuartzComponentProps) {
               ...props,
               allFiles: pages,
             }
+
+            const contentPage = allFiles.filter((file) => file.slug === `tags/${tag}`)[0]
+            const content = contentPage?.description
             return (
               <div>
                 <h2>
@@ -52,6 +56,7 @@ function TagContent(props: QuartzComponentProps) {
                     #{tag}
                   </a>
                 </h2>
+                {content && <p>{content}</p>}
                 <p>
                   {pages.length} items with this tag.{" "}
                   {pages.length > numPages && `Showing first ${numPages}.`}
diff --git a/quartz/components/styles/recentNotes.scss b/quartz/components/styles/recentNotes.scss
new file mode 100644
index 0000000..05871f5
--- /dev/null
+++ b/quartz/components/styles/recentNotes.scss
@@ -0,0 +1,23 @@
+.recent-notes {
+  & > h3 {
+    font-size: 1rem;
+  }
+
+  & > ul.recent-ul {
+    list-style: none;
+    margin-top: 1.5rem;
+    padding-left: 0;
+
+    & > li {
+      margin: 1rem 0;
+      .section > .desc > h3 > a {
+        background-color: transparent;
+      }
+
+      .section > .meta {
+        margin: 0 0 0.5rem 0;
+        opacity: 0.6;
+      }
+    }
+  }
+}
diff --git a/quartz/styles/base.scss b/quartz/styles/base.scss
index fad5dc0..16a0de3 100644
--- a/quartz/styles/base.scss
+++ b/quartz/styles/base.scss
@@ -320,7 +320,6 @@ pre {
   border-radius: 5px;
   overflow-x: auto;
   border: 1px solid var(--lightgray);
-  position: relative;
 
   &:has(> code.mermaid) {
     border: none;