─── ❖ ── ✦ ── ❖ ───

Associated notes

This note is part of a series about how this blog is set up. See here for an overview.

My own config

This should help to rebuild the blog, if needed.

Modified files

[Click me] custom quartz.config.ts
quartz.config.ts
import { QuartzConfig } from "./quartz/cfg"
import * as Plugin from "./quartz/plugins"
 
/**
* Quartz 4.0 Configuration
*
* See https://quartz.jzhao.xyz/configuration for more information.
*/
const config: QuartzConfig = {
configuration: {
  pageTitle: "🍺ZoyBlog📚",
  enableSPA: true,
  enablePopovers: true,
  analytics: {
    provider: "null",
  },
  locale: "en-US",
  baseUrl: "zoylendt.github.io",
  ignorePatterns: ["private", "templates", ".obsidian"],
  defaultDateType: "created",
  theme: {
    fontOrigin: "googleFonts",
    cdnCaching: true,
    typography: {
      header: "Sedan SC",
      body: "Lexend",
      code: "JetBrains Mono",
    },
    colors: {
      lightMode: {
        light: "#faf8f8",
        lightgray: "#e5e5e5",
        gray: "#b8b8b8",
        darkgray: "#4e4e4e",
        dark: "#2b2b2b",
        secondary: "#284b63",
        tertiary: "#84a59d",
        highlight: "rgba(143, 159, 169, 0.15)",
      },
      darkMode: {
        light: "#161618",
        lightgray: "#393639",
        gray: "#646464",
        darkgray: "#d4d4d4",
        dark: "#ebebec",
        secondary: "#7b97aa",
        tertiary: "#84a59d",
        highlight: "rgba(143, 159, 169, 0.15)",
      },
    },
  },
},
plugins: {
  transformers: [
    Plugin.FrontMatter(),
    Plugin.CreatedModifiedDate({
      priority: ["frontmatter", "filesystem"],
    }),
    Plugin.Latex({ renderEngine: "katex" }),
    Plugin.SyntaxHighlighting({
      theme: {
        light: "github-light",
        dark: "github-dark",
      },
      keepBackground: false,
    }),
    Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }),
    Plugin.GitHubFlavoredMarkdown(),
    Plugin.TableOfContents(),
    Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
    Plugin.Description(),
  ],
  filters: [Plugin.RemoveDrafts()],
  emitters: [
    Plugin.AliasRedirects(),
    Plugin.ComponentResources(),
    Plugin.ContentPage(),
    Plugin.FolderPage(),
    Plugin.TagPage(),
    Plugin.ContentIndex({
      enableSiteMap: true,
      enableRSS: true,
    }),
    Plugin.Assets(),
    Plugin.Static(),
    Plugin.NotFoundPage(),
  ],
},
}
 
export default config
[Click me] custom quartz.layout.ts
quartz.layout.ts
import { PageLayout, SharedLayout } from "./quartz/cfg"
import * as Component from "./quartz/components"
import { SimpleSlug } from "./quartz/util/path"
 
// components shared across all pages
export const sharedPageComponents: SharedLayout = {
head: Component.Head(),
header: [],
footer: Component.Footer({
  links: {
    GitHub: "https://github.com/jackyzha0/quartz",
    "Discord Community": "https://discord.gg/cRFFHYye7t",
  },
}),
}
 
// components for pages that display a single page (e.g. a single note)
export const defaultContentPageLayout: PageLayout = {
beforeBody: [
  Component.Breadcrumbs(),
  Component.ArticleTitle(),
  Component.ContentMeta(),
  Component.TagList(),
],
left: [
  Component.PageTitle(),
  Component.MobileOnly(Component.Spacer()),
  Component.Search(),
  Component.Darkmode(),
  Component.DesktopOnly(Component.RecentNotes({
      title: "Recent Notes",
      limit: 4,
      filter: (f) =>
        !f.frontmatter?.noindex,
      linkToMore: "tags/note" as SimpleSlug,
    }),),
  Component.DesktopOnly(Component.Explorer({
mapFn: (node) => {
  // dont change name of root node
  if (node.depth > 0) {
    // set emoji for file/folder
    if (node.file) {
      node.displayName = "📄 " + node.displayName
    } else {
      node.displayName = "📁 " + node.displayName
    }
  }
},
})),
],
right: [
  Component.Graph({
    localGraph: {
      showTags: false,
    },
    globalGraph: {
      showTags: false,
    },
  }),
  Component.DesktopOnly(Component.TableOfContents()),
  Component.Backlinks(),
],
}
 
// components for pages that display lists of pages  (e.g. tags or folders)
export const defaultListPageLayout: PageLayout = {
beforeBody: [Component.Breadcrumbs(), Component.ArticleTitle(), Component.ContentMeta()],
left: [
  Component.PageTitle(),
  Component.MobileOnly(Component.Spacer()),
  Component.Search(),
  Component.Darkmode(),
  Component.DesktopOnly(Component.Explorer()),
],
right: [],
}
[Click me] custom /quartz/components/ContentMeta.tsx
/quartz/components/ContentMeta.tsx
import { formatDate, getDate } from "./Date"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import readingTime from "reading-time"
import { classNames } from "../util/lang"
import { i18n } from "../i18n"
import { JSX } from "preact"
import style from "./styles/contentMeta.scss"
 
interface ContentMetaOptions {
/**
 * Whether to display reading time
 */
showReadingTime: boolean
showComma: boolean
}
 
const defaultOptions: ContentMetaOptions = {
showReadingTime: true,
showComma: true,
}
 
export default ((opts?: Partial<ContentMetaOptions>) => {
// Merge options with defaults
const options: ContentMetaOptions = { ...defaultOptions, ...opts }
 
function ContentMetadata({ cfg, fileData, displayClass }: QuartzComponentProps) {
  const text = fileData.text
 
  if (text) {
    var modifiedSegment: string = ""
    var createdSegment: string = ""
    const fileRelativePath = fileData.filePath
    //const segments: (string | JSX.Element)[] = []
 
    if (fileData.dates) {
      const cfgDefaultDataType = cfg.defaultDateType // For backward compatibility, just in case this is used somewhere else by the original author
 
      if (fileData.dates.created) {
        cfg.defaultDateType = "created"
        createdSegment = formatDate(getDate(cfg, fileData)!)
      }
 
      if (fileData.dates.modified) {
        cfg.defaultDateType = "modified"
        modifiedSegment = formatDate(getDate(cfg, fileData)!)
      }
 
      cfg.defaultDateType = cfgDefaultDataType
    }
 
 
    // Display reading time if enabled
    var readingTimeStr: string = ""
    if (options.showReadingTime) {
      const { minutes, words: _words } = readingTime(text)
      const displayedTime = i18n(cfg.locale).components.contentMeta.readingTime({
        minutes: Math.ceil(minutes),
      })
      // segments.push(displayedTime)
      readingTimeStr = `${_words} words, ${displayedTime}`
    }
 
    //Created: &nbsp;{createdSegment} <br /> 
    return (
      <p class={classNames(displayClass, "content-meta")}>
        {readingTimeStr} <br />
        Created {createdSegment} & updated {modifiedSegment} <br /> 
        🌟 <a href={`https://github.com/zoylendt/zoylendt.github.io/blame/v4/${fileRelativePath}`} class={classNames(displayClass, "external")} target={"_blank"} style={"font-weight:400"}>
          View Source<svg class="external-icon" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg>
        </a> &nbsp;
        🗓️ <a href={`https://github.com/zoylendt/zoylendt.github.io/commits/v4/${fileRelativePath}`} class={classNames(displayClass, "external")} target={"_blank"} style={"font-weight:400"}>
          Commit history<svg class="external-icon" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg>
        </a>
      </p>
    )
 
    /*const segmentsElements = segments.map((segment) => <span>{segment}</span>)
    return (
      <p show-comma={options.showComma} class={classNames(displayClass, "content-meta")}>
        {segmentsElements}
      </p>
    )*/
  } else {
    return null
  }
}
 
ContentMetadata.css = style
 
return ContentMetadata
}) satisfies QuartzComponentConstructor
[Click me] custom /quartz/components/index.ts
/quartz/components/index.ts
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"
import ContentMeta from "./ContentMeta"
import Spacer from "./Spacer"
import TableOfContents from "./TableOfContents"
import Explorer from "./Explorer"
import TagList from "./TagList"
import Graph from "./Graph"
import Backlinks from "./Backlinks"
import Search from "./Search"
import Footer from "./Footer"
import DesktopOnly from "./DesktopOnly"
import MobileOnly from "./MobileOnly"
import RecentNotes from "./RecentNotes"
import Breadcrumbs from "./Breadcrumbs"
import LinksHeader from "./LinksHeader"
 
export {
ArticleTitle,
Content,
TagContent,
FolderContent,
Darkmode,
Head,
PageTitle,
ContentMeta,
Spacer,
TableOfContents,
Explorer,
TagList,
Graph,
Backlinks,
Search,
Footer,
DesktopOnly,
MobileOnly,
RecentNotes,
NotFound,
Breadcrumbs,
LinksHeader,
}
[Click me] new /quartz/components/LinksHeader.tsx
/quartz/components/LinksHeader.tsx
import { QuartzComponentConstructor } from "./types"
import style from "./styles/linksHeader.scss"
 
interface Options {
links: Record<string, string>
}
 
export default (() => {
function LinksHeader() {
  return (
    <div>
      <div id="links-header">
        <span>
          <img src="https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Brain/Color/brain_color.svg"></img>
          <a href="/Braindump">Braindump</a>
        </span>
        <span>
          <img src="https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Beer%20mug/Color/beer_mug_color.svg"></img>
          <a href="/Brewing">Brewing</a>
        </span>
        <span>
          <img src="https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Keyboard/Color/keyboard_color.svg"></img>
          <a href="/Coding">Coding</a>
        </span>
        <span>
          <img src="https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Atom%20symbol/Color/atom_symbol_color.svg"></img>
          <a href="/Physics">Physics</a>
        </span>
        <span>
          <img src="https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Open%20book/Color/open_book_color.svg"></img>
          <a href="/Media">Media</a>
        </span>
        <span>
          <img src="https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Spouting%20whale/Color/spouting_whale_color.svg"></img>
          <a href="/Selfhosted">Selfhosted</a>
        </span>
      </div>
    <hr style="background-color: var(--gray); border-top: 1px var(--gray) solid; margin-top: 1.3rem"></hr>
    </div>
  )
}
 
LinksHeader.css = style
return LinksHeader
}) satisfies QuartzComponentConstructor
[Click me] new /quartz/components/styles/linksHeader.scss
/quartz/components/styles/linksHeader.scss
@use "../../styles/variables.scss" as *;
 
header {
display: block;
margin-top: 1rem;
}
 
#links-header {
display: flex;
flex-wrap: wrap;
column-gap: 1em;
font-size: 1.2em;
justify-content: space-between;
margin: 0 0.2em;
 
span {
  margin-top: 0.75em;
}
 
img {
  height: 1em;
  margin: 0 0.3em 0 0;
  vertical-align: sub;
}
 
a {
  color: var(--dark);
}
}
 
@media (max-width: $mobileBreakpoint) {
#links-header > * {
  width: calc(33.33% - 0.67em); /* 33.33% width with some gap */
}
 
#links-header > *:nth-child(3n-1) {
  text-align: center;
}
 
#links-header > *:nth-child(3n) {
  text-align: right;
}
}

Original files

The unaltered config files are included here, in order to make it easier to compare them.

[Click me] default quartz.config.ts
quartz.config.ts
import { QuartzConfig } from "./quartz/cfg"
import * as Plugin from "./quartz/plugins"
 
/**
* Quartz 4.0 Configuration
*
* See https://quartz.jzhao.xyz/configuration for more information.
*/
const config: QuartzConfig = {
configuration: {
  pageTitle: "🪴 Quartz 4.0",
  enableSPA: true,
  enablePopovers: true,
  analytics: {
    provider: "plausible",
  },
  locale: "en-US",
  baseUrl: "quartz.jzhao.xyz",
  ignorePatterns: ["private", "templates", ".obsidian"],
  defaultDateType: "created",
  theme: {
    fontOrigin: "googleFonts",
    cdnCaching: true,
    typography: {
      header: "Schibsted Grotesk",
      body: "Source Sans Pro",
      code: "IBM Plex Mono",
    },
    colors: {
      lightMode: {
        light: "#faf8f8",
        lightgray: "#e5e5e5",
        gray: "#b8b8b8",
        darkgray: "#4e4e4e",
        dark: "#2b2b2b",
        secondary: "#284b63",
        tertiary: "#84a59d",
        highlight: "rgba(143, 159, 169, 0.15)",
      },
      darkMode: {
        light: "#161618",
        lightgray: "#393639",
        gray: "#646464",
        darkgray: "#d4d4d4",
        dark: "#ebebec",
        secondary: "#7b97aa",
        tertiary: "#84a59d",
        highlight: "rgba(143, 159, 169, 0.15)",
      },
    },
  },
},
plugins: {
  transformers: [
    Plugin.FrontMatter(),
    Plugin.CreatedModifiedDate({
      priority: ["frontmatter", "filesystem"],
    }),
    Plugin.Latex({ renderEngine: "katex" }),
    Plugin.SyntaxHighlighting({
      theme: {
        light: "github-light",
        dark: "github-dark",
      },
      keepBackground: false,
    }),
    Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }),
    Plugin.GitHubFlavoredMarkdown(),
    Plugin.TableOfContents(),
    Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
    Plugin.Description(),
  ],
  filters: [Plugin.RemoveDrafts()],
  emitters: [
    Plugin.AliasRedirects(),
    Plugin.ComponentResources(),
    Plugin.ContentPage(),
    Plugin.FolderPage(),
    Plugin.TagPage(),
    Plugin.ContentIndex({
      enableSiteMap: true,
      enableRSS: true,
    }),
    Plugin.Assets(),
    Plugin.Static(),
    Plugin.NotFoundPage(),
  ],
},
}
 
export default config
[Click me] default quartz.layout.ts
quartz.layout.ts
import { PageLayout, SharedLayout } from "./quartz/cfg"
import * as Component from "./quartz/components"
 
// components shared across all pages
export const sharedPageComponents: SharedLayout = {
head: Component.Head(),
header: [],
footer: Component.Footer({
  links: {
    GitHub: "https://github.com/jackyzha0/quartz",
    "Discord Community": "https://discord.gg/cRFFHYye7t",
  },
}),
}
 
// components for pages that display a single page (e.g. a single note)
export const defaultContentPageLayout: PageLayout = {
beforeBody: [
  Component.Breadcrumbs(),
  Component.ArticleTitle(),
  Component.ContentMeta(),
  Component.TagList(),
],
left: [
  Component.PageTitle(),
  Component.MobileOnly(Component.Spacer()),
  Component.Search(),
  Component.Darkmode(),
  Component.DesktopOnly(Component.Explorer()),
],
right: [
  Component.Graph(),
  Component.DesktopOnly(Component.TableOfContents()),
  Component.Backlinks(),
],
}
 
// components for pages that display lists of pages  (e.g. tags or folders)
export const defaultListPageLayout: PageLayout = {
beforeBody: [Component.Breadcrumbs(), Component.ArticleTitle(), Component.ContentMeta()],
left: [
  Component.PageTitle(),
  Component.MobileOnly(Component.Spacer()),
  Component.Search(),
  Component.Darkmode(),
  Component.DesktopOnly(Component.Explorer()),
],
right: [],
}
[Click me] default /quartz/components/ContentMeta.tsx
/quartz/components/ContentMeta.tsx
import { formatDate, getDate } from "./Date"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import readingTime from "reading-time"
import { classNames } from "../util/lang"
import { i18n } from "../i18n"
import { JSX } from "preact"
import style from "./styles/contentMeta.scss"
 
interface ContentMetaOptions {
/**
 * Whether to display reading time
 */
showReadingTime: boolean
showComma: boolean
}
 
const defaultOptions: ContentMetaOptions = {
showReadingTime: true,
showComma: true,
}
 
export default ((opts?: Partial<ContentMetaOptions>) => {
// Merge options with defaults
const options: ContentMetaOptions = { ...defaultOptions, ...opts }
 
function ContentMetadata({ cfg, fileData, displayClass }: QuartzComponentProps) {
  const text = fileData.text
 
  if (text) {
    const segments: (string | JSX.Element)[] = []
 
    if (fileData.dates) {
      segments.push(formatDate(getDate(cfg, fileData)!, cfg.locale))
    }
 
    // Display reading time if enabled
    if (options.showReadingTime) {
      const { minutes, words: _words } = readingTime(text)
      const displayedTime = i18n(cfg.locale).components.contentMeta.readingTime({
        minutes: Math.ceil(minutes),
      })
      segments.push(displayedTime)
    }
 
    const segmentsElements = segments.map((segment) => <span>{segment}</span>)
 
    return (
      <p show-comma={options.showComma} class={classNames(displayClass, "content-meta")}>
        {segmentsElements}
      </p>
    )
  } else {
    return null
  }
}
 
ContentMetadata.css = style
 
return ContentMetadata
}) satisfies QuartzComponentConstructor
[Click me] default /quartz/components/index.ts
/quartz/components/index.ts
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"
import ContentMeta from "./ContentMeta"
import Spacer from "./Spacer"
import TableOfContents from "./TableOfContents"
import Explorer from "./Explorer"
import TagList from "./TagList"
import Graph from "./Graph"
import Backlinks from "./Backlinks"
import Search from "./Search"
import Footer from "./Footer"
import DesktopOnly from "./DesktopOnly"
import MobileOnly from "./MobileOnly"
import RecentNotes from "./RecentNotes"
import Breadcrumbs from "./Breadcrumbs"
 
export {
ArticleTitle,
Content,
TagContent,
FolderContent,
Darkmode,
Head,
PageTitle,
ContentMeta,
Spacer,
TableOfContents,
Explorer,
TagList,
Graph,
Backlinks,
Search,
Footer,
DesktopOnly,
MobileOnly,
RecentNotes,
NotFound,
Breadcrumbs,
}

─── ❖ ── ✦ ── ❖ ───