rendering, link resolution, asset copying
This commit is contained in:
		@@ -1,26 +0,0 @@
 | 
			
		||||
import { resolveToRoot } from "../../path"
 | 
			
		||||
import { EmitCallback, QuartzEmitterPlugin } from "../types"
 | 
			
		||||
import { ProcessedContent } from "../vfile"
 | 
			
		||||
 | 
			
		||||
export class ContentPage extends QuartzEmitterPlugin {
 | 
			
		||||
  name = "ContentPage"
 | 
			
		||||
  async emit(content: ProcessedContent[], emit: EmitCallback): Promise<string[]> {
 | 
			
		||||
    const fps: string[] = []
 | 
			
		||||
    for (const [tree, file] of content) {
 | 
			
		||||
      const pathToRoot = resolveToRoot(file.data.slug!)
 | 
			
		||||
 | 
			
		||||
      const fp = file.data.slug + ".html"
 | 
			
		||||
      await emit({
 | 
			
		||||
        title: file.data.frontmatter?.title ?? "Untitled",
 | 
			
		||||
        description: file.data.description ?? "",
 | 
			
		||||
        slug: file.data.slug!,
 | 
			
		||||
        ext: ".html",
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      // TODO: process aliases
 | 
			
		||||
 | 
			
		||||
      fps.push(fp)
 | 
			
		||||
    }
 | 
			
		||||
    return fps
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										62
									
								
								quartz/plugins/emitters/contentPage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								quartz/plugins/emitters/contentPage.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
 | 
			
		||||
import { resolveToRoot } from "../../path"
 | 
			
		||||
import { StaticResources } from "../../resources"
 | 
			
		||||
import { EmitCallback, QuartzEmitterPlugin } from "../types"
 | 
			
		||||
import { ProcessedContent } from "../vfile"
 | 
			
		||||
import { Fragment, jsx, jsxs } from 'preact/jsx-runtime'
 | 
			
		||||
import { render } from "preact-render-to-string"
 | 
			
		||||
import { ComponentType } from "preact"
 | 
			
		||||
import { HeadProps } from "../../components/Head"
 | 
			
		||||
 | 
			
		||||
interface Options {
 | 
			
		||||
  Head: ComponentType<HeadProps>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ContentPage extends QuartzEmitterPlugin {
 | 
			
		||||
  name = "ContentPage"
 | 
			
		||||
  opts: Options
 | 
			
		||||
 | 
			
		||||
  constructor(opts: Options) {
 | 
			
		||||
    super()
 | 
			
		||||
    this.opts = opts
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async emit(content: ProcessedContent[], resources: StaticResources, emit: EmitCallback): Promise<string[]> {
 | 
			
		||||
    const fps: string[] = []
 | 
			
		||||
    for (const [tree, file] of content) {
 | 
			
		||||
 | 
			
		||||
      // @ts-ignore (preact makes it angry)
 | 
			
		||||
      const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' })
 | 
			
		||||
 | 
			
		||||
      const { Head } = this.opts
 | 
			
		||||
      const doc = <html>
 | 
			
		||||
        <Head
 | 
			
		||||
          title={file.data.frontmatter?.title ?? "Untitled"}
 | 
			
		||||
          description={file.data.description ?? "No description provided"}
 | 
			
		||||
          slug={file.data.slug!}
 | 
			
		||||
          externalResources={resources} />
 | 
			
		||||
        <body>
 | 
			
		||||
          <div id="quartz-root">
 | 
			
		||||
            <header>
 | 
			
		||||
              <h1>{file.data.frontmatter?.title}</h1>
 | 
			
		||||
            </header>
 | 
			
		||||
            <article>
 | 
			
		||||
              {content}
 | 
			
		||||
            </article>
 | 
			
		||||
          </div>
 | 
			
		||||
        </body>
 | 
			
		||||
        {resources.js.filter(resource => resource.loadTime === "afterDOMReady").map(resource => <script key={resource.src} src={resource.src} />)}
 | 
			
		||||
      </html>
 | 
			
		||||
 | 
			
		||||
      const fp = file.data.slug + ".html"
 | 
			
		||||
      await emit({
 | 
			
		||||
        content: "<!DOCTYPE html>\n" + render(doc),
 | 
			
		||||
        slug: file.data.slug!,
 | 
			
		||||
        ext: ".html",
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      fps.push(fp)
 | 
			
		||||
    }
 | 
			
		||||
    return fps
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -45,12 +45,11 @@ export class CreatedModifiedDate extends QuartzTransformerPlugin {
 | 
			
		||||
              modified ||= file.data.frontmatter["last-modified"]
 | 
			
		||||
              published ||= file.data.frontmatter.publishDate
 | 
			
		||||
            } else if (source === "git") {
 | 
			
		||||
              console.log(file)
 | 
			
		||||
              if (!repo) {
 | 
			
		||||
                repo = new Repository(file.cwd)
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              modified ||= new Date(await repo.getFileLatestModifiedDateAsync(fp))
 | 
			
		||||
              modified ||= new Date(await repo.getFileLatestModifiedDateAsync(file.data.filePath!))
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										85
									
								
								quartz/plugins/transformers/links.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								quartz/plugins/transformers/links.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
			
		||||
import { PluggableList } from "unified"
 | 
			
		||||
import { QuartzTransformerPlugin } from "../types"
 | 
			
		||||
import { remarkWikiLink } from "@flowershow/remark-wiki-link"
 | 
			
		||||
import { relative, relativeToRoot, slugify } from "../../path"
 | 
			
		||||
import path from "path"
 | 
			
		||||
import { visit } from 'unist-util-visit'
 | 
			
		||||
import isAbsoluteUrl from "is-absolute-url"
 | 
			
		||||
 | 
			
		||||
interface Options {
 | 
			
		||||
  /** How to resolve Markdown paths */
 | 
			
		||||
  markdownLinkResolution: 'absolute' | 'relative'
 | 
			
		||||
  /** Strips folders from a link so that it looks nice */
 | 
			
		||||
  prettyLinks: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const defaultOptions: Options = {
 | 
			
		||||
  markdownLinkResolution: 'absolute',
 | 
			
		||||
  prettyLinks: true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class LinkProcessing extends QuartzTransformerPlugin {
 | 
			
		||||
  name = "LinkProcessing"
 | 
			
		||||
  opts: Options
 | 
			
		||||
 | 
			
		||||
  constructor(opts?: Options) {
 | 
			
		||||
    super()
 | 
			
		||||
    this.opts = { ...defaultOptions, ...opts }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  markdownPlugins(): PluggableList {
 | 
			
		||||
    return [[remarkWikiLink, {
 | 
			
		||||
      pathFormat: this.opts.markdownLinkResolution === "absolute" ? 'obsidian-absolute' : 'raw'
 | 
			
		||||
    }]]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  htmlPlugins(): PluggableList {
 | 
			
		||||
    return [() => {
 | 
			
		||||
      return (tree, file) => {
 | 
			
		||||
        const curSlug = file.data.slug! 
 | 
			
		||||
        const transformLink = (target: string) => {
 | 
			
		||||
          const targetSlug = slugify(decodeURI(target))
 | 
			
		||||
          if (this.opts.markdownLinkResolution === 'relative' && !path.isAbsolute(targetSlug)) {
 | 
			
		||||
            return './' + relative(curSlug, targetSlug)
 | 
			
		||||
          } else {
 | 
			
		||||
            return './' + relativeToRoot(curSlug, targetSlug)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // rewrite all links
 | 
			
		||||
        visit(tree, 'element', (node, _index, _parent) => {
 | 
			
		||||
          if (
 | 
			
		||||
            node.tagName === 'a' &&
 | 
			
		||||
            node.properties &&
 | 
			
		||||
            typeof node.properties.href === 'string'
 | 
			
		||||
          ) {
 | 
			
		||||
            node.properties.className = isAbsoluteUrl(node.properties.href) ? "external" : "internal"
 | 
			
		||||
 | 
			
		||||
            // don't process external links or intra-document anchors
 | 
			
		||||
            if (!(isAbsoluteUrl(node.properties.href) || node.properties.href.startsWith("#"))) {
 | 
			
		||||
              node.properties.href = transformLink(node.properties.href)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (this.opts.prettyLinks && node.children.length === 1 && node.children[0].type === 'text') {
 | 
			
		||||
              node.children[0].value = path.basename(node.children[0].value)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        // transform all images
 | 
			
		||||
        visit(tree, 'element', (node, _index, _parent) => {
 | 
			
		||||
          if (
 | 
			
		||||
            node.tagName === 'img' &&
 | 
			
		||||
            node.properties &&
 | 
			
		||||
            typeof node.properties.src === 'string'
 | 
			
		||||
          ) {
 | 
			
		||||
            if (!isAbsoluteUrl(node.properties.src)) {
 | 
			
		||||
              const ext = path.extname(node.properties.src)
 | 
			
		||||
              node.properties.src = transformLink("/assets/" + node.properties.src) + ext
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
    }]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -15,20 +15,15 @@ export abstract class QuartzFilterPlugin {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface EmitOptions {
 | 
			
		||||
  // meta
 | 
			
		||||
  title: string
 | 
			
		||||
  description: string
 | 
			
		||||
  slug: string
 | 
			
		||||
  ext: `.${string}`
 | 
			
		||||
  
 | 
			
		||||
  // rendering related 
 | 
			
		||||
  content: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type EmitCallback = (data: EmitOptions) => Promise<void>
 | 
			
		||||
export type EmitCallback = (data: EmitOptions) => Promise<string>
 | 
			
		||||
export abstract class QuartzEmitterPlugin {
 | 
			
		||||
  abstract name: string
 | 
			
		||||
  abstract emit(content: ProcessedContent[], emitCallback: EmitCallback): Promise<string[]>
 | 
			
		||||
  abstract emit(content: ProcessedContent[], resources: StaticResources, emitCallback: EmitCallback): Promise<string[]>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface PluginTypes {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user