diff --git a/docs/configuration.md b/docs/configuration.md index e29dc807b..f2f003d5f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -130,3 +130,11 @@ typography: { ... } ``` + +## Home Page + +The home page is the main web page of your Quartz. The content for the home page lives in `content/index.md`, to change it see [[authoring content|the authoring content guide]]. + +To enable easy customization, the `homePage` emitter allows you to fully rearrange the layout of the home page. The default home page layout is called `defaultHomePageLayout{:ts}` and can be found in `quartz.layout.ts`. The layout of the home page and all other content pages is the same by default. See [[layout|the layout documentation]] for further guidance. + +A differnet method is used to configure the comment box on the home page, see [[Comments#Conditionally display comments|conditionally display comments]]. diff --git a/quartz.config.ts b/quartz.config.ts index f54060908..4bb07e16f 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -78,6 +78,7 @@ const config: QuartzConfig = { Plugin.AliasRedirects(), Plugin.ComponentResources(), Plugin.ContentPage(), + Plugin.HomePage(), Plugin.FolderPage(), Plugin.TagPage(), Plugin.ContentIndex({ diff --git a/quartz.layout.ts b/quartz.layout.ts index 1c601a2b6..d9917511b 100644 --- a/quartz.layout.ts +++ b/quartz.layout.ts @@ -1,4 +1,4 @@ -import { PageLayout, SharedLayout } from "./quartz/cfg" +import { PageLayout, SharedLayout, HomePageLayout } from "./quartz/cfg" import * as Component from "./quartz/components" // components shared across all pages @@ -62,3 +62,16 @@ export const defaultListPageLayout: PageLayout = { ], right: [], } + +// components for the home page +export const defaultHomePageLayout: HomePageLayout = { + ...sharedPageComponents, + ...defaultContentPageLayout, + // head: Component.Head(), + // header: [], + // left: [], + // beforeBody: [], + // afterBody: [], + // right: [], + // footer: Component.Footer(), +} diff --git a/quartz/cfg.ts b/quartz/cfg.ts index 1c98b9363..d60e0bc14 100644 --- a/quartz/cfg.ts +++ b/quartz/cfg.ts @@ -91,3 +91,7 @@ export interface FullPageLayout { export type PageLayout = Pick export type SharedLayout = Pick +export type HomePageLayout = Pick< + FullPageLayout, + "beforeBody" | "left" | "right" | "head" | "header" | "footer" | "afterBody" +> diff --git a/quartz/plugins/emitters/contentPage.tsx b/quartz/plugins/emitters/contentPage.tsx index f83393054..4801f1604 100644 --- a/quartz/plugins/emitters/contentPage.tsx +++ b/quartz/plugins/emitters/contentPage.tsx @@ -103,6 +103,7 @@ export const ContentPage: QuartzEmitterPlugin> = (userOp const slug = file.data.slug! if (slug === "index") { containsIndex = true + continue } if (file.data.slug?.endsWith("/index")) { diff --git a/quartz/plugins/emitters/homePage.tsx b/quartz/plugins/emitters/homePage.tsx new file mode 100644 index 000000000..c086c9129 --- /dev/null +++ b/quartz/plugins/emitters/homePage.tsx @@ -0,0 +1,127 @@ +import path from "path" +import { visit } from "unist-util-visit" +import { Root } from "hast" +import { VFile } from "vfile" +import { QuartzEmitterPlugin } from "../types" +import { QuartzComponentProps } from "../../components/types" +import HeaderConstructor from "../../components/Header" +import BodyConstructor from "../../components/Body" +import { pageResources, renderPage } from "../../components/renderPage" +import { FullPageLayout } from "../../cfg" +import { Argv } from "../../util/ctx" +import { FilePath, isRelativeURL, joinSegments, pathToRoot } from "../../util/path" +import { defaultHomePageLayout, sharedPageComponents } from "../../../quartz.layout" +import { Content } from "../../components" +import chalk from "chalk" +import { write } from "./helpers" +import DepGraph from "../../depgraph" + +// get all the dependencies for the markdown file +// eg. images, scripts, stylesheets, transclusions +const parseDependencies = (argv: Argv, hast: Root, file: VFile): string[] => { + const dependencies: string[] = [] + + visit(hast, "element", (elem): void => { + let ref: string | null = null + + if ( + ["script", "img", "audio", "video", "source", "iframe"].includes(elem.tagName) && + elem?.properties?.src + ) { + ref = elem.properties.src.toString() + } else if (["a", "link"].includes(elem.tagName) && elem?.properties?.href) { + // transclusions will create a tags with relative hrefs + ref = elem.properties.href.toString() + } + + // if it is a relative url, its a local file and we need to add + // it to the dependency graph. otherwise, ignore + if (ref === null || !isRelativeURL(ref)) { + return + } + + let fp = path.join(file.data.filePath!, path.relative(argv.directory, ref)).replace(/\\/g, "/") + // markdown files have the .md extension stripped in hrefs, add it back here + if (!fp.split("/").pop()?.includes(".")) { + fp += ".md" + } + dependencies.push(fp) + }) + + return dependencies +} + +export const HomePage: QuartzEmitterPlugin> = (userOpts) => { + const opts: FullPageLayout = { + ...defaultHomePageLayout, + pageBody: Content(), + ...userOpts, + } + + const { head: Head, header, beforeBody, pageBody, afterBody, left, right, footer: Footer } = opts + const Header = HeaderConstructor() + const Body = BodyConstructor() + + return { + name: "HomePage", + getQuartzComponents() { + return [ + Head, + Header, + Body, + ...header, + ...beforeBody, + pageBody, + ...afterBody, + ...left, + ...right, + Footer, + ] + }, + async getDependencyGraph(ctx, content, _resources) { + const graph = new DepGraph() + + for (const [tree, file] of content) { + const sourcePath = file.data.filePath! + const slug = file.data.slug! + graph.addEdge(sourcePath, joinSegments(ctx.argv.output, slug + ".html") as FilePath) + + parseDependencies(ctx.argv, tree as Root, file).forEach((dep) => { + graph.addEdge(dep as FilePath, sourcePath) + }) + } + + return graph + }, + async *emit(ctx, content, resources) { + const cfg = ctx.cfg.configuration + const allFiles = content.map((c) => c[1].data) + + for (const [tree, file] of content) { + const slug = file.data.slug! + if (slug !== "index") { + continue + } + + const externalResources = pageResources(pathToRoot(slug), file.data, 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", + }) + } + }, + } +} diff --git a/quartz/plugins/emitters/index.ts b/quartz/plugins/emitters/index.ts index 842ffb083..6700448f5 100644 --- a/quartz/plugins/emitters/index.ts +++ b/quartz/plugins/emitters/index.ts @@ -1,4 +1,5 @@ export { ContentPage } from "./contentPage" +export { HomePage } from "./homePage" export { TagPage } from "./tagPage" export { FolderPage } from "./folderPage" export { ContentIndex as ContentIndex } from "./contentIndex"