tag and folder pages
This commit is contained in:
		@@ -1,90 +1,49 @@
 | 
			
		||||
import { JSResourceToScriptElement, StaticResources } from "../../resources"
 | 
			
		||||
import { QuartzEmitterPlugin } from "../types"
 | 
			
		||||
import { render } from "preact-render-to-string"
 | 
			
		||||
import { QuartzComponent } from "../../components/types"
 | 
			
		||||
import { resolveToRoot, trimPathSuffix } from "../../path"
 | 
			
		||||
import HeaderConstructor from "../../components/Header"
 | 
			
		||||
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"
 | 
			
		||||
 | 
			
		||||
interface Options {
 | 
			
		||||
  head: QuartzComponent
 | 
			
		||||
  header: QuartzComponent[],
 | 
			
		||||
  beforeBody: QuartzComponent[],
 | 
			
		||||
  content: QuartzComponent,
 | 
			
		||||
  left: QuartzComponent[],
 | 
			
		||||
  right: QuartzComponent[],
 | 
			
		||||
  footer: QuartzComponent[],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const ContentPage: QuartzEmitterPlugin<Options> = (opts) => {
 | 
			
		||||
export const ContentPage: QuartzEmitterPlugin<FullPageLayout> = (opts) => {
 | 
			
		||||
  if (!opts) {
 | 
			
		||||
    throw new Error("ContentPage must be initialized with options specifiying the components to use")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const { head: Head, header, beforeBody, left, right, footer } = opts
 | 
			
		||||
  const { head: Head, header, beforeBody, pageBody: Content, left, right, footer: Footer } = opts
 | 
			
		||||
  const Header = HeaderConstructor()
 | 
			
		||||
  const Body = BodyConstructor()
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    name: "ContentPage",
 | 
			
		||||
    getQuartzComponents() {
 | 
			
		||||
      return [opts.head, Header, Body, ...opts.header, ...opts.beforeBody, opts.content, ...opts.left, ...opts.right, ...opts.footer]
 | 
			
		||||
      return [Head, Header, Body, ...header, ...beforeBody, Content, ...left, ...right, Footer]
 | 
			
		||||
    },
 | 
			
		||||
    async emit(_contentDir, cfg, content, resources, emit): Promise<string[]> {
 | 
			
		||||
      const fps: string[] = []
 | 
			
		||||
      const allFiles = content.map(c => c[1].data)
 | 
			
		||||
      for (const [tree, file] of content) {
 | 
			
		||||
        const baseDir = resolveToRoot(file.data.slug!)
 | 
			
		||||
        const pageResources: StaticResources = {
 | 
			
		||||
          css: [baseDir + "/index.css", ...resources.css],
 | 
			
		||||
          js: [
 | 
			
		||||
            { src: baseDir + "/prescript.js", loadTime: "beforeDOMReady", contentType: "external" },
 | 
			
		||||
            ...resources.js,
 | 
			
		||||
            { src: baseDir + "/postscript.js", loadTime: "afterDOMReady", moduleType: 'module', contentType: "external" }
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const slug = file.data.slug!
 | 
			
		||||
        const externalResources = pageResources(slug, resources)
 | 
			
		||||
        const componentData: QuartzComponentProps = {
 | 
			
		||||
          fileData: file.data,
 | 
			
		||||
          externalResources: pageResources,
 | 
			
		||||
          externalResources,
 | 
			
		||||
          cfg,
 | 
			
		||||
          children: [],
 | 
			
		||||
          tree,
 | 
			
		||||
          allFiles
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const Content = opts.content
 | 
			
		||||
        const doc = <html>
 | 
			
		||||
          <Head {...componentData} />
 | 
			
		||||
          <body data-slug={file.data.slug ?? ""}>
 | 
			
		||||
            <div id="quartz-root" class="page">
 | 
			
		||||
              <Header {...componentData} >
 | 
			
		||||
                {header.map(HeaderComponent => <HeaderComponent {...componentData} />)}
 | 
			
		||||
              </Header>
 | 
			
		||||
              <div class="popover-hint">
 | 
			
		||||
                {beforeBody.map(BodyComponent => <BodyComponent {...componentData} />)}
 | 
			
		||||
              </div>
 | 
			
		||||
              <Body {...componentData}>
 | 
			
		||||
                <div class="left">
 | 
			
		||||
                  {left.map(BodyComponent => <BodyComponent {...componentData} />)}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center popover-hint">
 | 
			
		||||
                  <Content {...componentData} />
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="right">
 | 
			
		||||
                  {right.map(BodyComponent => <BodyComponent {...componentData} />)}
 | 
			
		||||
                </div>
 | 
			
		||||
              </Body>
 | 
			
		||||
 | 
			
		||||
            </div>
 | 
			
		||||
          </body>
 | 
			
		||||
          {pageResources.js.filter(resource => resource.loadTime === "afterDOMReady").map(res => JSResourceToScriptElement(res))}
 | 
			
		||||
        </html>
 | 
			
		||||
        const content = renderPage(
 | 
			
		||||
          slug,
 | 
			
		||||
          componentData,
 | 
			
		||||
          opts,
 | 
			
		||||
          externalResources
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        const fp = file.data.slug + ".html"
 | 
			
		||||
        await emit({
 | 
			
		||||
          content: "<!DOCTYPE html>\n" + render(doc),
 | 
			
		||||
          content,
 | 
			
		||||
          slug: file.data.slug!,
 | 
			
		||||
          ext: ".html",
 | 
			
		||||
        })
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										77
									
								
								quartz/plugins/emitters/folderPage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								quartz/plugins/emitters/folderPage.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
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 { ProcessedContent, defaultProcessedContent } from "../vfile"
 | 
			
		||||
import { FullPageLayout } from "../../cfg"
 | 
			
		||||
import path from "path"
 | 
			
		||||
 | 
			
		||||
export const FolderPage: QuartzEmitterPlugin<FullPageLayout> = (opts) => {
 | 
			
		||||
  if (!opts) {
 | 
			
		||||
    throw new Error("ErrorPage must be initialized with options specifiying the components to use")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const { head: Head, header, beforeBody, pageBody: Content, 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]
 | 
			
		||||
    },
 | 
			
		||||
    async emit(_contentDir, cfg, content, resources, emit): Promise<string[]> {
 | 
			
		||||
      const fps: string[] = []
 | 
			
		||||
      const allFiles = content.map(c => c[1].data)
 | 
			
		||||
 | 
			
		||||
      const folders: Set<string> = new Set(allFiles.flatMap(data => data.slug ? [path.dirname(data.slug)] : []))
 | 
			
		||||
 | 
			
		||||
      // remove special prefixes
 | 
			
		||||
      folders.delete(".")
 | 
			
		||||
      folders.delete("tags")
 | 
			
		||||
 | 
			
		||||
      const folderDescriptions: Record<string, ProcessedContent> = Object.fromEntries([...folders].map(folder => ([
 | 
			
		||||
        folder, defaultProcessedContent({ slug: folder, frontmatter: { title: `Folder: ${folder}`, tags: [] } })
 | 
			
		||||
      ])))
 | 
			
		||||
 | 
			
		||||
      for (const [tree, file] of content) {
 | 
			
		||||
        const slug = 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
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        const fp = file.data.slug + ".html"
 | 
			
		||||
        await emit({
 | 
			
		||||
          content,
 | 
			
		||||
          slug: file.data.slug!,
 | 
			
		||||
          ext: ".html",
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        fps.push(fp)
 | 
			
		||||
      }
 | 
			
		||||
      return fps
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
export { ContentPage } from './contentPage'
 | 
			
		||||
export { TagPage } from './tagPage'
 | 
			
		||||
export { FolderPage } from './folderPage'
 | 
			
		||||
export { ContentIndex } from './contentIndex'
 | 
			
		||||
export { AliasRedirects } from './aliases'
 | 
			
		||||
export { CNAME } from './cname'
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										74
									
								
								quartz/plugins/emitters/tagPage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								quartz/plugins/emitters/tagPage.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
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 { ProcessedContent, defaultProcessedContent } from "../vfile"
 | 
			
		||||
import { FullPageLayout } from "../../cfg"
 | 
			
		||||
 | 
			
		||||
export const TagPage: QuartzEmitterPlugin<FullPageLayout> = (opts) => {
 | 
			
		||||
  if (!opts) {
 | 
			
		||||
    throw new Error("TagPage must be initialized with options specifiying the components to use")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const { head: Head, header, beforeBody, pageBody: Content, left, right, footer: Footer } = opts
 | 
			
		||||
  const Header = HeaderConstructor()
 | 
			
		||||
  const Body = BodyConstructor()
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    name: "TagPage",
 | 
			
		||||
    getQuartzComponents() {
 | 
			
		||||
      return [Head, Header, Body, ...header, ...beforeBody, Content, ...left, ...right, Footer]
 | 
			
		||||
    },
 | 
			
		||||
    async emit(_contentDir, cfg, content, resources, emit): Promise<string[]> {
 | 
			
		||||
      const fps: string[] = []
 | 
			
		||||
      const allFiles = content.map(c => c[1].data)
 | 
			
		||||
 | 
			
		||||
      const tags: Set<string> = new Set(allFiles.flatMap(data => data.frontmatter?.tags ?? []))
 | 
			
		||||
      const tagDescriptions: Record<string, ProcessedContent> = Object.fromEntries([...tags].map(tag => ([
 | 
			
		||||
        tag, defaultProcessedContent({ slug: `tags/${tag}`, frontmatter: { title: `Tag: ${tag}`, tags: [] } })
 | 
			
		||||
      ])))
 | 
			
		||||
 | 
			
		||||
      for (const [tree, file] of content) {
 | 
			
		||||
        const slug = file.data.slug!
 | 
			
		||||
        if (slug.startsWith("tags/")) {
 | 
			
		||||
          const tag = slug.slice("tags/".length)
 | 
			
		||||
          if (tags.has(tag)) {
 | 
			
		||||
            tagDescriptions[tag] = [tree, file]
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      for (const tag of tags) {
 | 
			
		||||
        const slug = `tags/${tag}`
 | 
			
		||||
        const externalResources = pageResources(slug, resources)
 | 
			
		||||
        const [tree, file] = tagDescriptions[tag]
 | 
			
		||||
        const componentData: QuartzComponentProps = {
 | 
			
		||||
          fileData: file.data,
 | 
			
		||||
          externalResources,
 | 
			
		||||
          cfg,
 | 
			
		||||
          children: [],
 | 
			
		||||
          tree,
 | 
			
		||||
          allFiles
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const content = renderPage(
 | 
			
		||||
          slug,
 | 
			
		||||
          componentData,
 | 
			
		||||
          opts,
 | 
			
		||||
          externalResources
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        const fp = file.data.slug + ".html"
 | 
			
		||||
        await emit({
 | 
			
		||||
          content,
 | 
			
		||||
          slug: file.data.slug!,
 | 
			
		||||
          ext: ".html",
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        fps.push(fp)
 | 
			
		||||
      }
 | 
			
		||||
      return fps
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user