mirror of
https://github.com/alrayyes/wiki.git
synced 2024-11-22 03:26:22 +00:00
modern toc tweaks
This commit is contained in:
parent
9d2024b11c
commit
917d5791ac
17 changed files with 318 additions and 58 deletions
15
index.d.ts
vendored
15
index.d.ts
vendored
|
@ -1,4 +1,17 @@
|
||||||
declare module '*.scss' {
|
declare module '*.scss' {
|
||||||
const content: string
|
const content: string
|
||||||
export = content
|
export = content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dom custom event
|
||||||
|
interface CustomEventMap {
|
||||||
|
"spa_nav": CustomEvent<{ url: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Document {
|
||||||
|
addEventListener<K extends keyof CustomEventMap>(type: K,
|
||||||
|
listener: (this: Document, ev: CustomEventMap[K]) => void): void;
|
||||||
|
dispatchEvent<K extends keyof CustomEventMap>(ev: CustomEventMap[K]): void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ const config: QuartzConfig = {
|
||||||
highlight: 'rgba(143, 159, 169, 0.15)',
|
highlight: 'rgba(143, 159, 169, 0.15)',
|
||||||
},
|
},
|
||||||
darkMode: {
|
darkMode: {
|
||||||
light: '#1e1e21',
|
light: '#161618',
|
||||||
lightgray: '#292629',
|
lightgray: '#292629',
|
||||||
gray: '#343434',
|
gray: '#343434',
|
||||||
darkgray: '#d4d4d4',
|
darkgray: '#d4d4d4',
|
||||||
|
@ -41,7 +41,7 @@ const config: QuartzConfig = {
|
||||||
transformers: [
|
transformers: [
|
||||||
Plugin.FrontMatter(),
|
Plugin.FrontMatter(),
|
||||||
Plugin.Description(),
|
Plugin.Description(),
|
||||||
Plugin.TableOfContents({ showByDefault: true }),
|
Plugin.TableOfContents(),
|
||||||
Plugin.CreatedModifiedDate({
|
Plugin.CreatedModifiedDate({
|
||||||
priority: ['frontmatter', 'filesystem'] // you can add 'git' here for last modified from Git but this makes the build slower
|
priority: ['frontmatter', 'filesystem'] // you can add 'git' here for last modified from Git but this makes the build slower
|
||||||
}),
|
}),
|
||||||
|
@ -55,11 +55,23 @@ const config: QuartzConfig = {
|
||||||
Plugin.RemoveDrafts()
|
Plugin.RemoveDrafts()
|
||||||
],
|
],
|
||||||
emitters: [
|
emitters: [
|
||||||
|
Plugin.AliasRedirects(),
|
||||||
Plugin.ContentPage({
|
Plugin.ContentPage({
|
||||||
head: Component.Head(),
|
head: Component.Head(),
|
||||||
header: [Component.PageTitle(), Component.Spacer(), Component.Darkmode()],
|
header: [Component.PageTitle(), Component.Spacer(), Component.Darkmode()],
|
||||||
body: [Component.ArticleTitle(), Component.ReadingTime(), Component.TagList(), Component.TableOfContents(), Component.Content()]
|
body: [
|
||||||
})
|
Component.ArticleTitle(),
|
||||||
|
Component.ReadingTime(),
|
||||||
|
Component.TagList(),
|
||||||
|
Component.TableOfContents(),
|
||||||
|
Component.Content()
|
||||||
|
],
|
||||||
|
left: [],
|
||||||
|
right: [],
|
||||||
|
footer: []
|
||||||
|
}),
|
||||||
|
Plugin.ContentIndex(), // you can exclude this if you don't plan on using popovers, graph, or backlinks,
|
||||||
|
Plugin.CNAME({ domain: "yoursite.xyz" }) // set this to your final deployed domain
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,7 @@ export default async function buildQuartz(argv: Argv, version: string) {
|
||||||
|
|
||||||
if (argv.serve) {
|
if (argv.serve) {
|
||||||
const server = http.createServer(async (req, res) => {
|
const server = http.createServer(async (req, res) => {
|
||||||
|
console.log(chalk.grey(`[req] ${req.url}`))
|
||||||
return serveHandler(req, res, {
|
return serveHandler(req, res, {
|
||||||
public: output,
|
public: output,
|
||||||
directoryListing: false,
|
directoryListing: false,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @ts-ignore
|
||||||
import clipboardScript from './scripts/clipboard.inline'
|
import clipboardScript from './scripts/clipboard.inline'
|
||||||
import clipboardStyle from './styles/clipboard.scss'
|
import clipboardStyle from './styles/clipboard.scss'
|
||||||
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
|
|
|
@ -1,38 +1,65 @@
|
||||||
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
import style from "./styles/toc.scss"
|
import legacyStyle from "./styles/legacyToc.scss"
|
||||||
|
import modernStyle from "./styles/toc.scss"
|
||||||
|
|
||||||
interface Options {
|
interface Options {
|
||||||
layout: 'modern' | 'quartz-3'
|
layout: 'modern' | 'legacy'
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultOptions: Options = {
|
const defaultOptions: Options = {
|
||||||
layout: 'quartz-3'
|
layout: 'modern'
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ((opts?: Partial<Options>) => {
|
export default ((opts?: Partial<Options>) => {
|
||||||
const layout = opts?.layout ?? defaultOptions.layout
|
const layout = opts?.layout ?? defaultOptions.layout
|
||||||
if (layout === "modern") {
|
function TableOfContents({ fileData }: QuartzComponentProps) {
|
||||||
return function() {
|
if (!fileData.toc) {
|
||||||
return null // TODO (make this look like nextra)
|
return null
|
||||||
}
|
|
||||||
} else {
|
|
||||||
function TableOfContents({ fileData }: QuartzComponentProps) {
|
|
||||||
if (!fileData.toc) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return <details class="toc" open>
|
|
||||||
<summary><h3>Table of Contents</h3></summary>
|
|
||||||
<ul>
|
|
||||||
{fileData.toc.map(tocEntry => <li key={tocEntry.slug} class={`depth-${tocEntry.depth}`}>
|
|
||||||
<a href={`#${tocEntry.slug}`}>{tocEntry.text}</a>
|
|
||||||
</li>)}
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TableOfContents.css = style
|
return <details class="toc" open>
|
||||||
return TableOfContents
|
<summary><h3>Table of Contents</h3></summary>
|
||||||
|
<ul>
|
||||||
|
{fileData.toc.map(tocEntry => <li key={tocEntry.slug} class={`depth-${tocEntry.depth}`}>
|
||||||
|
<a href={`#${tocEntry.slug}`} data-for={tocEntry.slug}>{tocEntry.text}</a>
|
||||||
|
</li>)}
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TableOfContents.css = layout === "modern" ? modernStyle : legacyStyle
|
||||||
|
|
||||||
|
if (layout === "modern") {
|
||||||
|
TableOfContents.afterDOMLoaded = `
|
||||||
|
const bufferPx = 150
|
||||||
|
const observer = new IntersectionObserver(entries => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
const slug = entry.target.id
|
||||||
|
const tocEntryElement = document.querySelector(\`a[data-for="$\{slug\}"]\`)
|
||||||
|
const windowHeight = entry.rootBounds?.height
|
||||||
|
if (windowHeight && tocEntryElement) {
|
||||||
|
if (entry.boundingClientRect.y < windowHeight) {
|
||||||
|
tocEntryElement.classList.add("in-view")
|
||||||
|
} else {
|
||||||
|
tocEntryElement.classList.remove("in-view")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
const headers = document.querySelectorAll("h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]")
|
||||||
|
headers.forEach(header => observer.observe(header))
|
||||||
|
}
|
||||||
|
|
||||||
|
init()
|
||||||
|
|
||||||
|
document.addEventListener("spa_nav", (e) => {
|
||||||
|
observer.disconnect()
|
||||||
|
init()
|
||||||
|
})
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
return TableOfContents
|
||||||
}) satisfies QuartzComponentConstructor
|
}) satisfies QuartzComponentConstructor
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
const description = "Initialize copy for codeblocks"
|
|
||||||
export default description
|
|
||||||
|
|
||||||
const svgCopy =
|
const svgCopy =
|
||||||
'<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true"><path fill-rule="evenodd" d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z"></path><path fill-rule="evenodd" d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25h-7.5z"></path></svg>'
|
'<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true"><path fill-rule="evenodd" d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z"></path><path fill-rule="evenodd" d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25h-7.5z"></path></svg>'
|
||||||
const svgCheck =
|
const svgCheck =
|
||||||
|
|
|
@ -29,6 +29,11 @@ const getOpts = ({ target }: Event): { url: URL, scroll?: boolean } | undefined
|
||||||
return { url: new URL(href), scroll: 'routerNoscroll' in a.dataset ? false : undefined }
|
return { url: new URL(href), scroll: 'routerNoscroll' in a.dataset ? false : undefined }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function notifyNav(slug: string) {
|
||||||
|
const event = new CustomEvent("spa_nav", { detail: { slug } })
|
||||||
|
document.dispatchEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
let p: DOMParser
|
let p: DOMParser
|
||||||
async function navigate(url: URL, isBack: boolean = false) {
|
async function navigate(url: URL, isBack: boolean = false) {
|
||||||
p = p || new DOMParser()
|
p = p || new DOMParser()
|
||||||
|
@ -64,9 +69,7 @@ async function navigate(url: URL, isBack: boolean = false) {
|
||||||
const elementsToAdd = html.head.querySelectorAll(':not([spa-preserve])')
|
const elementsToAdd = html.head.querySelectorAll(':not([spa-preserve])')
|
||||||
elementsToAdd.forEach(el => document.head.appendChild(el))
|
elementsToAdd.forEach(el => document.head.appendChild(el))
|
||||||
|
|
||||||
if (!document.activeElement?.closest('[data-persist]')) {
|
notifyNav(document.body.dataset.slug!)
|
||||||
document.body.focus()
|
|
||||||
}
|
|
||||||
delete announcer.dataset.persist
|
delete announcer.dataset.persist
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
27
quartz/components/styles/legacyToc.scss
Normal file
27
quartz/components/styles/legacyToc.scss
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
details.toc {
|
||||||
|
& summary {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&::marker {
|
||||||
|
color: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
padding-left: 0.25rem;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0.5rem 1.25rem;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@for $i from 1 through 6 {
|
||||||
|
& .depth-#{$i} {
|
||||||
|
padding-left: calc(1rem * #{$i});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,24 +2,36 @@ details.toc {
|
||||||
& summary {
|
& summary {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&::marker {
|
list-style: none;
|
||||||
color: var(--dark);
|
&::marker, &::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
padding-left: 0.25rem;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& > h3 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& ul {
|
& ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0.5rem 1.25rem;
|
margin: 0.5rem 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
& > li > a {
|
||||||
|
color: var(--dark);
|
||||||
|
opacity: 0.35;
|
||||||
|
transition: 0.5s ease opacity;
|
||||||
|
&.in-view {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@for $i from 1 through 6 {
|
@for $i from 0 through 6 {
|
||||||
& .depth-#{$i} {
|
& .depth-#{$i} {
|
||||||
padding-left: calc(1rem * #{$i});
|
padding-left: calc(1rem * #{$i});
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,21 @@ function slugSegment(s: string): string {
|
||||||
return s.replace(/\s/g, '-')
|
return s.replace(/\s/g, '-')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function trimPathSuffix(fp: string): string {
|
||||||
|
let [cleanPath, anchor] = fp.split("#", 2)
|
||||||
|
anchor = anchor === undefined ? "" : "#" + anchor
|
||||||
|
|
||||||
|
if (cleanPath.endsWith("index")) {
|
||||||
|
cleanPath = cleanPath.slice(0, -"index".length)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleanPath === "") {
|
||||||
|
cleanPath = "./"
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanPath + anchor
|
||||||
|
}
|
||||||
|
|
||||||
export function slugify(s: string): string {
|
export function slugify(s: string): string {
|
||||||
const [fp, anchor] = s.split("#", 2)
|
const [fp, anchor] = s.split("#", 2)
|
||||||
const sluggedAnchor = anchor === undefined ? "" : "#" + slugAnchor(anchor)
|
const sluggedAnchor = anchor === undefined ? "" : "#" + slugAnchor(anchor)
|
||||||
|
@ -19,12 +34,9 @@ export function slugify(s: string): string {
|
||||||
|
|
||||||
// resolve /a/b/c to ../../
|
// resolve /a/b/c to ../../
|
||||||
export function resolveToRoot(slug: string): string {
|
export function resolveToRoot(slug: string): string {
|
||||||
let fp = slug
|
let fp = trimPathSuffix(slug)
|
||||||
if (fp.endsWith("index")) {
|
|
||||||
fp = fp.slice(0, -"index".length)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fp === "") {
|
if (fp === "./") {
|
||||||
return "."
|
return "."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
53
quartz/plugins/emitters/aliases.ts
Normal file
53
quartz/plugins/emitters/aliases.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import { relativeToRoot } from "../../path"
|
||||||
|
import { QuartzEmitterPlugin } from "../types"
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
export const AliasRedirects: QuartzEmitterPlugin = () => ({
|
||||||
|
name: "AliasRedirects",
|
||||||
|
getQuartzComponents() {
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
async emit(contentFolder, _cfg, content, _resources, emit): Promise<string[]> {
|
||||||
|
const fps: string[] = []
|
||||||
|
|
||||||
|
for (const [_tree, file] of content) {
|
||||||
|
const ogSlug = file.data.slug!
|
||||||
|
const dir = path.relative(contentFolder, file.dirname ?? contentFolder)
|
||||||
|
|
||||||
|
let aliases: string[] = []
|
||||||
|
if (file.data.frontmatter?.aliases) {
|
||||||
|
aliases = file.data.frontmatter?.aliases
|
||||||
|
} else if (file.data.frontmatter?.alias) {
|
||||||
|
aliases = [file.data.frontmatter?.alias]
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const alias of aliases) {
|
||||||
|
const slug = alias.startsWith("/")
|
||||||
|
? alias
|
||||||
|
: path.posix.join(dir, alias)
|
||||||
|
|
||||||
|
const fp = slug + ".html"
|
||||||
|
const redirUrl = relativeToRoot(slug, ogSlug)
|
||||||
|
await emit({
|
||||||
|
content: `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en-us">
|
||||||
|
<head>
|
||||||
|
<title>${ogSlug}</title>
|
||||||
|
<link rel="canonical" href="${redirUrl}">
|
||||||
|
<meta name="robots" content="noindex">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="refresh" content="0; url=${redirUrl}">
|
||||||
|
</head>
|
||||||
|
</html>
|
||||||
|
`,
|
||||||
|
slug,
|
||||||
|
ext: ".html",
|
||||||
|
})
|
||||||
|
|
||||||
|
fps.push(fp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fps
|
||||||
|
}
|
||||||
|
})
|
25
quartz/plugins/emitters/cname.ts
Normal file
25
quartz/plugins/emitters/cname.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { QuartzEmitterPlugin } from "../types"
|
||||||
|
|
||||||
|
interface Options {
|
||||||
|
domain: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CNAME: QuartzEmitterPlugin<Options> = (opts?: Options) => ({
|
||||||
|
name: "CNAME",
|
||||||
|
getQuartzComponents() {
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
async emit(_contentFolder, _cfg, _content, _resources, emit): Promise<string[]> {
|
||||||
|
const slug = "CNAME"
|
||||||
|
|
||||||
|
if (opts?.domain) {
|
||||||
|
await emit({
|
||||||
|
content: opts?.domain,
|
||||||
|
slug,
|
||||||
|
ext: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ["CNAME"]
|
||||||
|
}
|
||||||
|
})
|
72
quartz/plugins/emitters/contentIndex.ts
Normal file
72
quartz/plugins/emitters/contentIndex.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import { visit } from "unist-util-visit"
|
||||||
|
import { QuartzEmitterPlugin } from "../types"
|
||||||
|
import { Element } from "hast"
|
||||||
|
import path from "path"
|
||||||
|
import { trimPathSuffix } from "../../path"
|
||||||
|
|
||||||
|
interface Options {
|
||||||
|
indexAnchorLinks: boolean,
|
||||||
|
indexExternalLinks: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOptions: Options = {
|
||||||
|
indexAnchorLinks: false,
|
||||||
|
indexExternalLinks: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContentIndex = Map<string, {
|
||||||
|
title: string,
|
||||||
|
links?: string[],
|
||||||
|
tags?: string[],
|
||||||
|
content: string,
|
||||||
|
}>
|
||||||
|
|
||||||
|
export const ContentIndex: QuartzEmitterPlugin<Options> = (userOpts) => {
|
||||||
|
const opts = { ...userOpts, ...defaultOptions }
|
||||||
|
return {
|
||||||
|
name: "ContentIndex",
|
||||||
|
async emit(_contentDir, _cfg, content, _resources, emit) {
|
||||||
|
const fp = "contentIndex"
|
||||||
|
const linkIndex: ContentIndex = new Map()
|
||||||
|
for (const [tree, file] of content) {
|
||||||
|
let slug = trimPathSuffix(file.data.slug!)
|
||||||
|
|
||||||
|
const outgoing: Set<string> = new Set()
|
||||||
|
visit(tree, 'element', (node: Element) => {
|
||||||
|
if (node.tagName === 'a' && node.properties && typeof node.properties.href === 'string') {
|
||||||
|
let dest = node.properties.href
|
||||||
|
if (dest.startsWith(".")) {
|
||||||
|
const normalizedPath = path.normalize(path.join(slug, dest))
|
||||||
|
dest = trimPathSuffix(normalizedPath)
|
||||||
|
outgoing.add(dest)
|
||||||
|
} else if (dest.startsWith("#")) {
|
||||||
|
if (opts.indexAnchorLinks) {
|
||||||
|
outgoing.add(dest)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (opts.indexExternalLinks) {
|
||||||
|
outgoing.add(dest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
linkIndex.set(slug, {
|
||||||
|
title: file.data.frontmatter?.title!,
|
||||||
|
links: [...outgoing],
|
||||||
|
tags: file.data.frontmatter?.tags,
|
||||||
|
content: file.data.text ?? ""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await emit({
|
||||||
|
content: JSON.stringify(Object.fromEntries(linkIndex)),
|
||||||
|
slug: fp,
|
||||||
|
ext: ".json",
|
||||||
|
})
|
||||||
|
|
||||||
|
return [`${fp}.json`]
|
||||||
|
},
|
||||||
|
getQuartzComponents: () => [],
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,6 @@
|
||||||
import { JSResourceToScriptElement, StaticResources } from "../../resources"
|
import { JSResourceToScriptElement, StaticResources } from "../../resources"
|
||||||
import { EmitCallback, QuartzEmitterPlugin } from "../types"
|
import { QuartzEmitterPlugin } from "../types"
|
||||||
import { ProcessedContent } from "../vfile"
|
|
||||||
import { render } from "preact-render-to-string"
|
import { render } from "preact-render-to-string"
|
||||||
import { GlobalConfiguration } from "../../cfg"
|
|
||||||
import { QuartzComponent } from "../../components/types"
|
import { QuartzComponent } from "../../components/types"
|
||||||
import { resolveToRoot } from "../../path"
|
import { resolveToRoot } from "../../path"
|
||||||
import HeaderConstructor from "../../components/Header"
|
import HeaderConstructor from "../../components/Header"
|
||||||
|
@ -12,7 +10,10 @@ import BodyConstructor from "../../components/Body"
|
||||||
interface Options {
|
interface Options {
|
||||||
head: QuartzComponent
|
head: QuartzComponent
|
||||||
header: QuartzComponent[],
|
header: QuartzComponent[],
|
||||||
body: QuartzComponent[]
|
body: QuartzComponent[],
|
||||||
|
left: QuartzComponent[],
|
||||||
|
right: QuartzComponent[],
|
||||||
|
footer: QuartzComponent[],
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ContentPage: QuartzEmitterPlugin<Options> = (opts) => {
|
export const ContentPage: QuartzEmitterPlugin<Options> = (opts) => {
|
||||||
|
@ -29,7 +30,7 @@ export const ContentPage: QuartzEmitterPlugin<Options> = (opts) => {
|
||||||
getQuartzComponents() {
|
getQuartzComponents() {
|
||||||
return [opts.head, Header, ...opts.header, ...opts.body]
|
return [opts.head, Header, ...opts.header, ...opts.body]
|
||||||
},
|
},
|
||||||
async emit(cfg: GlobalConfiguration, content: ProcessedContent[], resources: StaticResources, emit: EmitCallback): Promise<string[]> {
|
async emit(_contentDir, cfg, content, resources, emit): Promise<string[]> {
|
||||||
const fps: string[] = []
|
const fps: string[] = []
|
||||||
|
|
||||||
for (const [tree, file] of content) {
|
for (const [tree, file] of content) {
|
||||||
|
@ -53,7 +54,7 @@ export const ContentPage: QuartzEmitterPlugin<Options> = (opts) => {
|
||||||
|
|
||||||
const doc = <html>
|
const doc = <html>
|
||||||
<Head {...componentData} />
|
<Head {...componentData} />
|
||||||
<body>
|
<body data-slug={file.data.slug}>
|
||||||
<div id="quartz-root" class="page">
|
<div id="quartz-root" class="page">
|
||||||
<Header {...componentData} >
|
<Header {...componentData} >
|
||||||
{header.map(HeaderComponent => <HeaderComponent {...componentData} />)}
|
{header.map(HeaderComponent => <HeaderComponent {...componentData} />)}
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
export { ContentPage } from './contentPage'
|
export { ContentPage } from './contentPage'
|
||||||
|
export { ContentIndex } from './contentIndex'
|
||||||
|
export { AliasRedirects } from './aliases'
|
||||||
|
export { CNAME } from './cname'
|
||||||
|
|
|
@ -28,13 +28,13 @@ export type QuartzFilterPluginInstance = {
|
||||||
export type QuartzEmitterPlugin<Options extends OptionType = undefined> = (opts?: Options) => QuartzEmitterPluginInstance
|
export type QuartzEmitterPlugin<Options extends OptionType = undefined> = (opts?: Options) => QuartzEmitterPluginInstance
|
||||||
export type QuartzEmitterPluginInstance = {
|
export type QuartzEmitterPluginInstance = {
|
||||||
name: string
|
name: string
|
||||||
emit(cfg: GlobalConfiguration, content: ProcessedContent[], resources: StaticResources, emitCallback: EmitCallback): Promise<string[]>
|
emit(contentDir: string, cfg: GlobalConfiguration, content: ProcessedContent[], resources: StaticResources, emitCallback: EmitCallback): Promise<string[]>
|
||||||
getQuartzComponents(): QuartzComponent[]
|
getQuartzComponents(): QuartzComponent[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmitOptions {
|
export interface EmitOptions {
|
||||||
slug: string
|
slug: string
|
||||||
ext: `.${string}`
|
ext: `.${string}` | ""
|
||||||
content: string
|
content: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ export async function emitContent(contentFolder: string, output: string, cfg: Qu
|
||||||
let emittedFiles = 0
|
let emittedFiles = 0
|
||||||
for (const emitter of cfg.plugins.emitters) {
|
for (const emitter of cfg.plugins.emitters) {
|
||||||
try {
|
try {
|
||||||
const emitted = await emitter.emit(cfg.configuration, content, staticResources, emit)
|
const emitted = await emitter.emit(contentFolder, cfg.configuration, content, staticResources, emit)
|
||||||
emittedFiles += emitted.length
|
emittedFiles += emitted.length
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
|
@ -42,24 +42,25 @@ export async function emitContent(contentFolder: string, output: string, cfg: Qu
|
||||||
const staticPath = path.join(QUARTZ, "static")
|
const staticPath = path.join(QUARTZ, "static")
|
||||||
await fs.promises.cp(staticPath, path.join(output, "static"), { recursive: true })
|
await fs.promises.cp(staticPath, path.join(output, "static"), { recursive: true })
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
console.log(`[emit:Static] ${path.join(output, "static", "**")}`)
|
console.log(`[emit:Static] ${path.join("static", "**")}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// glob all non MD/MDX/HTML files in content folder and copy it over
|
// glob all non MD/MDX/HTML files in content folder and copy it over
|
||||||
const assetsPath = path.join("public", "assets")
|
const assetsPath = path.join(output, "assets")
|
||||||
for await (const fp of globbyStream("**", {
|
for await (const fp of globbyStream("**", {
|
||||||
ignore: ["**/*.md"],
|
ignore: ["**/*.md"],
|
||||||
cwd: contentFolder,
|
cwd: contentFolder,
|
||||||
})) {
|
})) {
|
||||||
const ext = path.extname(fp as string)
|
const ext = path.extname(fp as string)
|
||||||
const src = path.join(contentFolder, fp as string)
|
const src = path.join(contentFolder, fp as string)
|
||||||
const dest = path.join(assetsPath, slugify(fp as string) + ext)
|
const name = slugify(fp as string) + ext
|
||||||
|
const dest = path.join(assetsPath, name)
|
||||||
const dir = path.dirname(dest)
|
const dir = path.dirname(dest)
|
||||||
await fs.promises.mkdir(dir, { recursive: true }) // ensure dir exists
|
await fs.promises.mkdir(dir, { recursive: true }) // ensure dir exists
|
||||||
await fs.promises.copyFile(src, dest)
|
await fs.promises.copyFile(src, dest)
|
||||||
emittedFiles += 1
|
emittedFiles += 1
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
console.log(`[emit:Assets] ${dest}`)
|
console.log(`[emit:Assets] ${path.join("assets", name)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue