run prettier

This commit is contained in:
Jacky Zhao
2023-07-22 17:27:41 -07:00
parent 3079d003cc
commit adec32227f
101 changed files with 1810 additions and 1405 deletions

View File

@ -4,15 +4,25 @@ import { canonicalizeServer, resolveRelative } from "../path"
function Backlinks({ fileData, allFiles }: QuartzComponentProps) {
const slug = canonicalizeServer(fileData.slug!)
const backlinkFiles = allFiles.filter(file => file.links?.includes(slug))
return <div class="backlinks">
<h3>Backlinks</h3>
<ul class="overflow">
{backlinkFiles.length > 0 ?
backlinkFiles.map(f => <li><a href={resolveRelative(slug, canonicalizeServer(f.slug!))} class="internal">{f.frontmatter?.title}</a></li>)
: <li>No backlinks found</li>}
</ul>
</div>
const backlinkFiles = allFiles.filter((file) => file.links?.includes(slug))
return (
<div class="backlinks">
<h3>Backlinks</h3>
<ul class="overflow">
{backlinkFiles.length > 0 ? (
backlinkFiles.map((f) => (
<li>
<a href={resolveRelative(slug, canonicalizeServer(f.slug!))} class="internal">
{f.frontmatter?.title}
</a>
</li>
))
) : (
<li>No backlinks found</li>
)}
</ul>
</div>
)
}
Backlinks.css = style

View File

@ -1,16 +1,13 @@
// @ts-ignore
import clipboardScript from './scripts/clipboard.inline'
import clipboardStyle from './styles/clipboard.scss'
import clipboardScript from "./scripts/clipboard.inline"
import clipboardStyle from "./styles/clipboard.scss"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
function Body({ children }: QuartzComponentProps) {
return <div id="quartz-body">
{children}
</div>
return <div id="quartz-body">{children}</div>
}
Body.afterDOMLoaded = clipboardScript
Body.css = clipboardStyle
export default (() => Body) satisfies QuartzComponentConstructor

View File

@ -1,50 +1,48 @@
// @ts-ignore: this is safe, we don't want to actually make darkmode.inline.ts a module as
// @ts-ignore: this is safe, we don't want to actually make darkmode.inline.ts a module as
// modules are automatically deferred and we don't want that to happen for critical beforeDOMLoads
// see: https://v8.dev/features/modules#defer
import darkmodeScript from "./scripts/darkmode.inline"
import styles from './styles/darkmode.scss'
import styles from "./styles/darkmode.scss"
import { QuartzComponentConstructor } from "./types"
function Darkmode() {
return <div class="darkmode">
<input class="toggle" id="darkmode-toggle" type="checkbox" tabIndex={-1} />
<label id="toggle-label-light" for="darkmode-toggle" tabIndex={-1}>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
version="1.1"
id="dayIcon"
x="0px"
y="0px"
viewBox="0 0 35 35"
style="enable-background:new 0 0 35 35;"
xmlSpace="preserve"
>
<title>Light mode</title>
<path
d="M6,17.5C6,16.672,5.328,16,4.5,16h-3C0.672,16,0,16.672,0,17.5 S0.672,19,1.5,19h3C5.328,19,6,18.328,6,17.5z M7.5,26c-0.414,0-0.789,0.168-1.061,0.439l-2,2C4.168,28.711,4,29.086,4,29.5 C4,30.328,4.671,31,5.5,31c0.414,0,0.789-0.168,1.06-0.44l2-2C8.832,28.289,9,27.914,9,27.5C9,26.672,8.329,26,7.5,26z M17.5,6 C18.329,6,19,5.328,19,4.5v-3C19,0.672,18.329,0,17.5,0S16,0.672,16,1.5v3C16,5.328,16.671,6,17.5,6z M27.5,9 c0.414,0,0.789-0.168,1.06-0.439l2-2C30.832,6.289,31,5.914,31,5.5C31,4.672,30.329,4,29.5,4c-0.414,0-0.789,0.168-1.061,0.44 l-2,2C26.168,6.711,26,7.086,26,7.5C26,8.328,26.671,9,27.5,9z M6.439,8.561C6.711,8.832,7.086,9,7.5,9C8.328,9,9,8.328,9,7.5 c0-0.414-0.168-0.789-0.439-1.061l-2-2C6.289,4.168,5.914,4,5.5,4C4.672,4,4,4.672,4,5.5c0,0.414,0.168,0.789,0.439,1.06 L6.439,8.561z M33.5,16h-3c-0.828,0-1.5,0.672-1.5,1.5s0.672,1.5,1.5,1.5h3c0.828,0,1.5-0.672,1.5-1.5S34.328,16,33.5,16z M28.561,26.439C28.289,26.168,27.914,26,27.5,26c-0.828,0-1.5,0.672-1.5,1.5c0,0.414,0.168,0.789,0.439,1.06l2,2 C28.711,30.832,29.086,31,29.5,31c0.828,0,1.5-0.672,1.5-1.5c0-0.414-0.168-0.789-0.439-1.061L28.561,26.439z M17.5,29 c-0.829,0-1.5,0.672-1.5,1.5v3c0,0.828,0.671,1.5,1.5,1.5s1.5-0.672,1.5-1.5v-3C19,29.672,18.329,29,17.5,29z M17.5,7 C11.71,7,7,11.71,7,17.5S11.71,28,17.5,28S28,23.29,28,17.5S23.29,7,17.5,7z M17.5,25c-4.136,0-7.5-3.364-7.5-7.5 c0-4.136,3.364-7.5,7.5-7.5c4.136,0,7.5,3.364,7.5,7.5C25,21.636,21.636,25,17.5,25z"
></path>
</svg>
</label>
<label id="toggle-label-dark" for="darkmode-toggle" tabIndex={-1}>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
version="1.1"
id="nightIcon"
x="0px"
y="0px"
viewBox="0 0 100 100"
style="enable-background='new 0 0 100 100'"
xmlSpace="preserve"
>
<title>Dark mode</title>
<path
d="M96.76,66.458c-0.853-0.852-2.15-1.064-3.23-0.534c-6.063,2.991-12.858,4.571-19.655,4.571 C62.022,70.495,50.88,65.88,42.5,57.5C29.043,44.043,25.658,23.536,34.076,6.47c0.532-1.08,0.318-2.379-0.534-3.23 c-0.851-0.852-2.15-1.064-3.23-0.534c-4.918,2.427-9.375,5.619-13.246,9.491c-9.447,9.447-14.65,22.008-14.65,35.369 c0,13.36,5.203,25.921,14.65,35.368s22.008,14.65,35.368,14.65c13.361,0,25.921-5.203,35.369-14.65 c3.872-3.871,7.064-8.328,9.491-13.246C97.826,68.608,97.611,67.309,96.76,66.458z"
></path>
</svg>
</label>
</div>
return (
<div class="darkmode">
<input class="toggle" id="darkmode-toggle" type="checkbox" tabIndex={-1} />
<label id="toggle-label-light" for="darkmode-toggle" tabIndex={-1}>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
version="1.1"
id="dayIcon"
x="0px"
y="0px"
viewBox="0 0 35 35"
style="enable-background:new 0 0 35 35;"
xmlSpace="preserve"
>
<title>Light mode</title>
<path d="M6,17.5C6,16.672,5.328,16,4.5,16h-3C0.672,16,0,16.672,0,17.5 S0.672,19,1.5,19h3C5.328,19,6,18.328,6,17.5z M7.5,26c-0.414,0-0.789,0.168-1.061,0.439l-2,2C4.168,28.711,4,29.086,4,29.5 C4,30.328,4.671,31,5.5,31c0.414,0,0.789-0.168,1.06-0.44l2-2C8.832,28.289,9,27.914,9,27.5C9,26.672,8.329,26,7.5,26z M17.5,6 C18.329,6,19,5.328,19,4.5v-3C19,0.672,18.329,0,17.5,0S16,0.672,16,1.5v3C16,5.328,16.671,6,17.5,6z M27.5,9 c0.414,0,0.789-0.168,1.06-0.439l2-2C30.832,6.289,31,5.914,31,5.5C31,4.672,30.329,4,29.5,4c-0.414,0-0.789,0.168-1.061,0.44 l-2,2C26.168,6.711,26,7.086,26,7.5C26,8.328,26.671,9,27.5,9z M6.439,8.561C6.711,8.832,7.086,9,7.5,9C8.328,9,9,8.328,9,7.5 c0-0.414-0.168-0.789-0.439-1.061l-2-2C6.289,4.168,5.914,4,5.5,4C4.672,4,4,4.672,4,5.5c0,0.414,0.168,0.789,0.439,1.06 L6.439,8.561z M33.5,16h-3c-0.828,0-1.5,0.672-1.5,1.5s0.672,1.5,1.5,1.5h3c0.828,0,1.5-0.672,1.5-1.5S34.328,16,33.5,16z M28.561,26.439C28.289,26.168,27.914,26,27.5,26c-0.828,0-1.5,0.672-1.5,1.5c0,0.414,0.168,0.789,0.439,1.06l2,2 C28.711,30.832,29.086,31,29.5,31c0.828,0,1.5-0.672,1.5-1.5c0-0.414-0.168-0.789-0.439-1.061L28.561,26.439z M17.5,29 c-0.829,0-1.5,0.672-1.5,1.5v3c0,0.828,0.671,1.5,1.5,1.5s1.5-0.672,1.5-1.5v-3C19,29.672,18.329,29,17.5,29z M17.5,7 C11.71,7,7,11.71,7,17.5S11.71,28,17.5,28S28,23.29,28,17.5S23.29,7,17.5,7z M17.5,25c-4.136,0-7.5-3.364-7.5-7.5 c0-4.136,3.364-7.5,7.5-7.5c4.136,0,7.5,3.364,7.5,7.5C25,21.636,21.636,25,17.5,25z"></path>
</svg>
</label>
<label id="toggle-label-dark" for="darkmode-toggle" tabIndex={-1}>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
version="1.1"
id="nightIcon"
x="0px"
y="0px"
viewBox="0 0 100 100"
style="enable-background='new 0 0 100 100'"
xmlSpace="preserve"
>
<title>Dark mode</title>
<path d="M96.76,66.458c-0.853-0.852-2.15-1.064-3.23-0.534c-6.063,2.991-12.858,4.571-19.655,4.571 C62.022,70.495,50.88,65.88,42.5,57.5C29.043,44.043,25.658,23.536,34.076,6.47c0.532-1.08,0.318-2.379-0.534-3.23 c-0.851-0.852-2.15-1.064-3.23-0.534c-4.918,2.427-9.375,5.619-13.246,9.491c-9.447,9.447-14.65,22.008-14.65,35.369 c0,13.36,5.203,25.921,14.65,35.368s22.008,14.65,35.368,14.65c13.361,0,25.921-5.203,35.369-14.65 c3.872-3.871,7.064-8.328,9.491-13.246C97.826,68.608,97.611,67.309,96.76,66.458z"></path>
</svg>
</label>
</div>
)
}
Darkmode.beforeDOMLoaded = darkmodeScript

View File

@ -3,10 +3,10 @@ interface Props {
}
export function Date({ date }: Props) {
const formattedDate = date.toLocaleDateString('en-US', {
const formattedDate = date.toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: '2-digit'
day: "2-digit",
})
return <>{formattedDate}</>
}

View File

@ -1,6 +1,6 @@
import { QuartzComponentConstructor } from "./types"
import style from "./styles/footer.scss"
import {version} from "../../package.json"
import { version } from "../../package.json"
interface Options {
links: Record<string, string>
@ -10,13 +10,21 @@ export default ((opts?: Options) => {
function Footer() {
const year = new Date().getFullYear()
const links = opts?.links ?? []
return <footer>
<hr />
<p>Created with <a href="https://quartz.jzhao.xyz/">Quartz v{version}</a>, © {year}</p>
<ul>{Object.entries(links).map(([text, link]) => <li>
<a href={link}>{text}</a>
</li>)}</ul>
</footer>
return (
<footer>
<hr />
<p>
Created with <a href="https://quartz.jzhao.xyz/">Quartz v{version}</a>, © {year}
</p>
<ul>
{Object.entries(links).map(([text, link]) => (
<li>
<a href={link}>{text}</a>
</li>
))}
</ul>
</footer>
)
}
Footer.css = style

View File

@ -4,19 +4,19 @@ import script from "./scripts/graph.inline"
import style from "./styles/graph.scss"
export interface D3Config {
drag: boolean,
zoom: boolean,
depth: number,
scale: number,
repelForce: number,
centerForce: number,
linkDistance: number,
fontSize: number,
drag: boolean
zoom: boolean
depth: number
scale: number
repelForce: number
centerForce: number
linkDistance: number
fontSize: number
opacityScale: number
}
interface GraphOptions {
localGraph: Partial<D3Config> | undefined,
localGraph: Partial<D3Config> | undefined
globalGraph: Partial<D3Config> | undefined
}
@ -30,7 +30,7 @@ const defaultOptions: GraphOptions = {
centerForce: 0.3,
linkDistance: 30,
fontSize: 0.6,
opacityScale: 1
opacityScale: 1,
},
globalGraph: {
drag: true,
@ -41,21 +41,32 @@ const defaultOptions: GraphOptions = {
centerForce: 0.3,
linkDistance: 30,
fontSize: 0.6,
opacityScale: 1
}
opacityScale: 1,
},
}
export default ((opts?: GraphOptions) => {
function Graph() {
const localGraph = { ...opts?.localGraph, ...defaultOptions.localGraph }
const globalGraph = { ...opts?.globalGraph, ...defaultOptions.globalGraph }
return <div class="graph">
<h3>Graph View</h3>
<div class="graph-outer">
<div id="graph-container" data-cfg={JSON.stringify(localGraph)}></div>
<svg version="1.1" id="global-graph-icon" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 55 55" fill="currentColor" xmlSpace="preserve">
<path d="M49,0c-3.309,0-6,2.691-6,6c0,1.035,0.263,2.009,0.726,2.86l-9.829,9.829C32.542,17.634,30.846,17,29,17
return (
<div class="graph">
<h3>Graph View</h3>
<div class="graph-outer">
<div id="graph-container" data-cfg={JSON.stringify(localGraph)}></div>
<svg
version="1.1"
id="global-graph-icon"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 55 55"
fill="currentColor"
xmlSpace="preserve"
>
<path
d="M49,0c-3.309,0-6,2.691-6,6c0,1.035,0.263,2.009,0.726,2.86l-9.829,9.829C32.542,17.634,30.846,17,29,17
s-3.542,0.634-4.898,1.688l-7.669-7.669C16.785,10.424,17,9.74,17,9c0-2.206-1.794-4-4-4S9,6.794,9,9s1.794,4,4,4
c0.74,0,1.424-0.215,2.019-0.567l7.669,7.669C21.634,21.458,21,23.154,21,25s0.634,3.542,1.688,4.897L10.024,42.562
C8.958,41.595,7.549,41,6,41c-3.309,0-6,2.691-6,6s2.691,6,6,6s6-2.691,6-6c0-1.035-0.263-2.009-0.726-2.86l12.829-12.829
@ -65,13 +76,15 @@ export default ((opts?: GraphOptions) => {
C46.042,11.405,47.451,12,49,12c3.309,0,6-2.691,6-6S52.309,0,49,0z M11,9c0-1.103,0.897-2,2-2s2,0.897,2,2s-0.897,2-2,2
S11,10.103,11,9z M6,51c-2.206,0-4-1.794-4-4s1.794-4,4-4s4,1.794,4,4S8.206,51,6,51z M33,49c0,2.206-1.794,4-4,4s-4-1.794-4-4
s1.794-4,4-4S33,46.794,33,49z M29,31c-3.309,0-6-2.691-6-6s2.691-6,6-6s6,2.691,6,6S32.309,31,29,31z M47,41c0,1.103-0.897,2-2,2
s-2-0.897-2-2s0.897-2,2-2S47,39.897,47,41z M49,10c-2.206,0-4-1.794-4-4s1.794-4,4-4s4,1.794,4,4S51.206,10,49,10z"/>
</svg>
s-2-0.897-2-2s0.897-2,2-2S47,39.897,47,41z M49,10c-2.206,0-4-1.794-4-4s1.794-4,4-4s4,1.794,4,4S51.206,10,49,10z"
/>
</svg>
</div>
<div id="global-graph-outer">
<div id="global-graph-container" data-cfg={JSON.stringify(globalGraph)}></div>
</div>
</div>
<div id="global-graph-outer">
<div id="global-graph-container" data-cfg={JSON.stringify(globalGraph)}></div>
</div>
</div>
)
}
Graph.css = style

View File

@ -12,23 +12,29 @@ export default (() => {
const iconPath = baseDir + "/static/icon.png"
const ogImagePath = baseDir + "/static/og-image.png"
return <head>
<title>{title}</title>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta property="og:title" content={title} />
<meta property="og:description" content={title} />
<meta property="og:image" content={ogImagePath} />
<meta property="og:width" content="1200" />
<meta property="og:height" content="675" />
<link rel="icon" href={iconPath} />
<meta name="description" content={description} />
<meta name="generator" content="Quartz" />
<link rel="preconnect" href="https://fonts.googleapis.com"/>
<link rel="preconnect" href="https://fonts.gstatic.com"/>
{css.map(href => <link key={href} href={href} rel="stylesheet" type="text/css" spa-preserve />)}
{js.filter(resource => resource.loadTime === "beforeDOMReady").map(res => JSResourceToScriptElement(res, true))}
</head>
return (
<head>
<title>{title}</title>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta property="og:title" content={title} />
<meta property="og:description" content={title} />
<meta property="og:image" content={ogImagePath} />
<meta property="og:width" content="1200" />
<meta property="og:height" content="675" />
<link rel="icon" href={iconPath} />
<meta name="description" content={description} />
<meta name="generator" content="Quartz" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
{css.map((href) => (
<link key={href} href={href} rel="stylesheet" type="text/css" spa-preserve />
))}
{js
.filter((resource) => resource.loadTime === "beforeDOMReady")
.map((res) => JSResourceToScriptElement(res, true))}
</head>
)
}
return Head

View File

@ -1,9 +1,7 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
function Header({ children }: QuartzComponentProps) {
return (children.length > 0) ? <header>
{children}
</header> : null
return children.length > 0 ? <header>{children}</header> : null
}
Header.css = `

View File

@ -17,32 +17,51 @@ function byDateAndAlphabetical(f1: QuartzPluginData, f2: QuartzPluginData): numb
// otherwise, sort lexographically by title
const f1Title = f1.frontmatter?.title.toLowerCase() ?? ""
const f2Title = f2.frontmatter?.title.toLowerCase() ?? ""
return f1Title.localeCompare(f2Title)
return f1Title.localeCompare(f2Title)
}
export function PageList({ fileData, allFiles }: QuartzComponentProps) {
const slug = canonicalizeServer(fileData.slug!)
return <ul class="section-ul">
{allFiles.sort(byDateAndAlphabetical).map(page => {
const title = page.frontmatter?.title
const pageSlug = canonicalizeServer(page.slug!)
const tags = page.frontmatter?.tags ?? []
return (
<ul class="section-ul">
{allFiles.sort(byDateAndAlphabetical).map((page) => {
const title = page.frontmatter?.title
const pageSlug = canonicalizeServer(page.slug!)
const tags = page.frontmatter?.tags ?? []
return <li class="section-li">
<div class="section">
{page.dates && <p class="meta">
<Date date={page.dates.modified} />
</p>}
<div class="desc">
<h3><a href={resolveRelative(slug, pageSlug)} class="internal">{title}</a></h3>
</div>
<ul class="tags">
{tags.map(tag => <li><a class="internal" href={resolveRelative(slug, `tags/${tag}` as CanonicalSlug)}>#{tag}</a></li>)}
</ul>
</div>
</li>
})}
</ul>
return (
<li class="section-li">
<div class="section">
{page.dates && (
<p class="meta">
<Date date={page.dates.modified} />
</p>
)}
<div class="desc">
<h3>
<a href={resolveRelative(slug, pageSlug)} class="internal">
{title}
</a>
</h3>
</div>
<ul class="tags">
{tags.map((tag) => (
<li>
<a
class="internal"
href={resolveRelative(slug, `tags/${tag}` as CanonicalSlug)}
>
#{tag}
</a>
</li>
))}
</ul>
</div>
</li>
)
})}
</ul>
)
}
PageList.css = `

View File

@ -5,7 +5,11 @@ function PageTitle({ fileData, cfg }: QuartzComponentProps) {
const title = cfg?.pageTitle ?? "Untitled Quartz"
const slug = canonicalizeServer(fileData.slug!)
const baseDir = pathToRoot(slug)
return <h1 class="page-title"><a href={baseDir}>{title}</a></h1>
return (
<h1 class="page-title">
<a href={baseDir}>{title}</a>
</h1>
)
}
PageTitle.css = `

View File

@ -5,7 +5,11 @@ function ReadingTime({ fileData }: QuartzComponentProps) {
const text = fileData.text
if (text) {
const { text: timeTaken, words } = readingTime(text)
return <p class="reading-time">{words} words, {timeTaken}</p>
return (
<p class="reading-time">
{words} words, {timeTaken}
</p>
)
} else {
return null
}

View File

@ -5,27 +5,41 @@ import script from "./scripts/search.inline"
export default (() => {
function Search() {
return <div class="search">
<div id="search-icon">
<p>Search</p>
<div></div>
<svg tabIndex={0} aria-labelledby="title desc" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.9 19.7">
<title id="title">Search</title>
<desc id="desc">Search</desc>
<g class="search-path" fill="none">
<path stroke-linecap="square" d="M18.5 18.3l-5.4-5.4" />
<circle cx="8" cy="8" r="7" />
</g>
</svg>
</div>
<div id="search-container">
<div id="search-space">
<input autocomplete="off" id="search-bar" name="search" type="text" aria-label="Search for something" placeholder="Search for something" />
<div id="results-container">
return (
<div class="search">
<div id="search-icon">
<p>Search</p>
<div></div>
<svg
tabIndex={0}
aria-labelledby="title desc"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 19.9 19.7"
>
<title id="title">Search</title>
<desc id="desc">Search</desc>
<g class="search-path" fill="none">
<path stroke-linecap="square" d="M18.5 18.3l-5.4-5.4" />
<circle cx="8" cy="8" r="7" />
</g>
</svg>
</div>
<div id="search-container">
<div id="search-space">
<input
autocomplete="off"
id="search-bar"
name="search"
type="text"
aria-label="Search for something"
placeholder="Search for something"
/>
<div id="results-container"></div>
</div>
</div>
</div>
</div>
)
}
Search.afterDOMLoaded = script

View File

@ -6,11 +6,11 @@ import modernStyle from "./styles/toc.scss"
import script from "./scripts/toc.inline"
interface Options {
layout: 'modern' | 'legacy'
layout: "modern" | "legacy"
}
const defaultOptions: Options = {
layout: 'modern'
layout: "modern",
}
function TableOfContents({ fileData }: QuartzComponentProps) {
@ -18,21 +18,38 @@ function TableOfContents({ fileData }: QuartzComponentProps) {
return null
}
return <div class="desktop-only">
<button type="button" id="toc">
<h3>Table of Contents</h3>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="fold">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
<div id="toc-content">
<ul class="overflow">
{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>
return (
<div class="desktop-only">
<button type="button" id="toc">
<h3>Table of Contents</h3>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="fold"
>
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
<div id="toc-content">
<ul class="overflow">
{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>
</div>
</div>
</div>
)
}
TableOfContents.css = modernStyle
TableOfContents.afterDOMLoaded = script
@ -42,16 +59,22 @@ function LegacyTableOfContents({ fileData }: QuartzComponentProps) {
return null
}
return <details id="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}`} data-for={tocEntry.slug}>{tocEntry.text}</a>
</li>)}
</ul>
</details>
return (
<details id="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}`} data-for={tocEntry.slug}>
{tocEntry.text}
</a>
</li>
))}
</ul>
</details>
)
}
LegacyTableOfContents.css = legacyStyle

View File

@ -1,19 +1,27 @@
import { canonicalizeServer, pathToRoot } from "../path"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { slug as slugAnchor } from 'github-slugger'
import { slug as slugAnchor } from "github-slugger"
function TagList({ fileData }: QuartzComponentProps) {
const tags = fileData.frontmatter?.tags
const slug = canonicalizeServer(fileData.slug!)
const baseDir = pathToRoot(slug)
if (tags && tags.length > 0) {
return <ul class="tags">{tags.map(tag => {
const display = `#${tag}`
const linkDest = baseDir + `/tags/${slugAnchor(tag)}`
return <li>
<a href={linkDest} class="internal">{display}</a>
</li>
})}</ul>
return (
<ul class="tags">
{tags.map((tag) => {
const display = `#${tag}`
const linkDest = baseDir + `/tags/${slugAnchor(tag)}`
return (
<li>
<a href={linkDest} class="internal">
{display}
</a>
</li>
)
})}
</ul>
)
} else {
return null
}

View File

@ -9,7 +9,7 @@ import ReadingTime from "./ReadingTime"
import Spacer from "./Spacer"
import TableOfContents from "./TableOfContents"
import TagList from "./TagList"
import Graph from "./Graph"
import Graph from "./Graph"
import Backlinks from "./Backlinks"
import Search from "./Search"
import Footer from "./Footer"
@ -33,5 +33,5 @@ export {
Search,
Footer,
DesktopOnly,
MobileOnly
}
MobileOnly,
}

View File

@ -1,10 +1,10 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
import { Fragment, jsx, jsxs } from 'preact/jsx-runtime'
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
function Content({ tree }: QuartzComponentProps) {
// @ts-ignore (preact makes it angry)
const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' })
const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
return <article class="popover-hint">{content}</article>
}

View File

@ -1,16 +1,16 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
import { Fragment, jsx, jsxs } from 'preact/jsx-runtime'
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
import path from "path"
import style from '../styles/listPage.scss'
import style from "../styles/listPage.scss"
import { PageList } from "../PageList"
import { canonicalizeServer } from "../../path"
function FolderContent(props: QuartzComponentProps) {
const { tree, fileData, allFiles } = props
const folderSlug = canonicalizeServer(fileData.slug!)
const allPagesInFolder = allFiles.filter(file => {
const allPagesInFolder = allFiles.filter((file) => {
const fileSlug = file.slug ?? ""
const prefixed = fileSlug.startsWith(folderSlug)
const folderParts = folderSlug.split(path.posix.sep)
@ -21,18 +21,20 @@ function FolderContent(props: QuartzComponentProps) {
const listProps = {
...props,
allFiles: allPagesInFolder
allFiles: allPagesInFolder,
}
// @ts-ignore
const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' })
return <div class="popover-hint">
<article>{content}</article>
<p>{allPagesInFolder.length} items under this folder.</p>
<div>
<PageList {...listProps} />
const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
return (
<div class="popover-hint">
<article>{content}</article>
<p>{allPagesInFolder.length} items under this folder.</p>
<div>
<PageList {...listProps} />
</div>
</div>
</div>
)
}
FolderContent.css = style + PageList.css

View File

@ -1,7 +1,7 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
import { Fragment, jsx, jsxs } from 'preact/jsx-runtime'
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
import style from '../styles/listPage.scss'
import style from "../styles/listPage.scss"
import { PageList } from "../PageList"
import { ServerSlug, canonicalizeServer } from "../../path"
@ -11,21 +11,23 @@ function TagContent(props: QuartzComponentProps) {
if (slug?.startsWith("tags/")) {
const tag = canonicalizeServer(slug.slice("tags/".length) as ServerSlug)
const allPagesWithTag = allFiles.filter(file => (file.frontmatter?.tags ?? []).includes(tag))
const allPagesWithTag = allFiles.filter((file) => (file.frontmatter?.tags ?? []).includes(tag))
const listProps = {
...props,
allFiles: allPagesWithTag
allFiles: allPagesWithTag,
}
// @ts-ignore
const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' })
return <div class="popover-hint">
<article>{content}</article>
<p>{allPagesWithTag.length} items with this tag.</p>
<div>
<PageList {...listProps} />
const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
return (
<div class="popover-hint">
<article>{content}</article>
<p>{allPagesWithTag.length} items with this tag.</p>
<div>
<PageList {...listProps} />
</div>
</div>
</div>
)
} else {
throw new Error(`Component "TagContent" tried to render a non-tag page: ${slug}`)
}

View File

@ -1,21 +1,24 @@
import { render } from "preact-render-to-string";
import { QuartzComponent, QuartzComponentProps } from "./types";
import { render } from "preact-render-to-string"
import { QuartzComponent, QuartzComponentProps } from "./types"
import HeaderConstructor from "./Header"
import BodyConstructor from "./Body"
import { JSResourceToScriptElement, StaticResources } from "../resources";
import { CanonicalSlug, pathToRoot } from "../path";
import { JSResourceToScriptElement, StaticResources } from "../resources"
import { CanonicalSlug, pathToRoot } from "../path"
interface RenderComponents {
head: QuartzComponent
header: QuartzComponent[],
beforeBody: QuartzComponent[],
pageBody: QuartzComponent,
left: QuartzComponent[],
right: QuartzComponent[],
footer: QuartzComponent,
header: QuartzComponent[]
beforeBody: QuartzComponent[]
pageBody: QuartzComponent
left: QuartzComponent[]
right: QuartzComponent[]
footer: QuartzComponent
}
export function pageResources(slug: CanonicalSlug, staticResources: StaticResources): StaticResources {
export function pageResources(
slug: CanonicalSlug,
staticResources: StaticResources,
): StaticResources {
const baseDir = pathToRoot(slug)
const contentIndexPath = baseDir + "/static/contentIndex.json"
@ -25,52 +28,89 @@ export function pageResources(slug: CanonicalSlug, staticResources: StaticResour
css: [baseDir + "/index.css", ...staticResources.css],
js: [
{ src: baseDir + "/prescript.js", loadTime: "beforeDOMReady", contentType: "external" },
{ loadTime: "beforeDOMReady", contentType: "inline", spaPreserve: true, script: contentIndexScript },
{
loadTime: "beforeDOMReady",
contentType: "inline",
spaPreserve: true,
script: contentIndexScript,
},
...staticResources.js,
{ src: baseDir + "/postscript.js", loadTime: "afterDOMReady", moduleType: 'module', contentType: "external" }
]
{
src: baseDir + "/postscript.js",
loadTime: "afterDOMReady",
moduleType: "module",
contentType: "external",
},
],
}
}
export function renderPage(slug: CanonicalSlug, componentData: QuartzComponentProps, components: RenderComponents, pageResources: StaticResources): string {
const { head: Head, header, beforeBody, pageBody: Content, left, right, footer: Footer } = components
export function renderPage(
slug: CanonicalSlug,
componentData: QuartzComponentProps,
components: RenderComponents,
pageResources: StaticResources,
): string {
const {
head: Head,
header,
beforeBody,
pageBody: Content,
left,
right,
footer: Footer,
} = components
const Header = HeaderConstructor()
const Body = BodyConstructor()
const LeftComponent =
const LeftComponent = (
<div class="left sidebar">
{left.map(BodyComponent => <BodyComponent {...componentData} />)}
{left.map((BodyComponent) => (
<BodyComponent {...componentData} />
))}
</div>
)
const RightComponent =
const RightComponent = (
<div class="right sidebar">
{right.map(BodyComponent => <BodyComponent {...componentData} />)}
{right.map((BodyComponent) => (
<BodyComponent {...componentData} />
))}
</div>
)
const doc = <html>
<Head {...componentData} />
<body data-slug={slug}>
<div id="quartz-root" class="page">
<Body {...componentData}>
{LeftComponent}
<div class="center">
<div class="page-header">
<Header {...componentData} >
{header.map(HeaderComponent => <HeaderComponent {...componentData} />)}
</Header>
<div class="popover-hint">
{beforeBody.map(BodyComponent => <BodyComponent {...componentData} />)}
const doc = (
<html>
<Head {...componentData} />
<body data-slug={slug}>
<div id="quartz-root" class="page">
<Body {...componentData}>
{LeftComponent}
<div class="center">
<div class="page-header">
<Header {...componentData}>
{header.map((HeaderComponent) => (
<HeaderComponent {...componentData} />
))}
</Header>
<div class="popover-hint">
{beforeBody.map((BodyComponent) => (
<BodyComponent {...componentData} />
))}
</div>
</div>
<Content {...componentData} />
</div>
<Content {...componentData} />
</div>
{RightComponent}
</Body>
<Footer {...componentData} />
</div>
</body>
{pageResources.js.filter(resource => resource.loadTime === "afterDOMReady").map(res => JSResourceToScriptElement(res))}
</html>
{RightComponent}
</Body>
<Footer {...componentData} />
</div>
</body>
{pageResources.js
.filter((resource) => resource.loadTime === "afterDOMReady")
.map((res) => JSResourceToScriptElement(res))}
</html>
)
return "<!DOCTYPE html>\n" + render(doc)
}

View File

@ -7,7 +7,9 @@ function toggleCallout(this: HTMLElement) {
}
function setupCallout() {
const collapsible = document.getElementsByClassName(`callout is-collapsible`) as HTMLCollectionOf<HTMLElement>
const collapsible = document.getElementsByClassName(
`callout is-collapsible`,
) as HTMLCollectionOf<HTMLElement>
for (const div of collapsible) {
const title = div.firstElementChild

View File

@ -1,24 +1,23 @@
const userPref = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'
const currentTheme = localStorage.getItem('theme') ?? userPref
document.documentElement.setAttribute('saved-theme', currentTheme)
const userPref = window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark"
const currentTheme = localStorage.getItem("theme") ?? userPref
document.documentElement.setAttribute("saved-theme", currentTheme)
document.addEventListener("nav", () => {
const switchTheme = (e: any) => {
if (e.target.checked) {
document.documentElement.setAttribute('saved-theme', 'dark')
localStorage.setItem('theme', 'dark')
}
else {
document.documentElement.setAttribute('saved-theme', 'light')
localStorage.setItem('theme', 'light')
document.documentElement.setAttribute("saved-theme", "dark")
localStorage.setItem("theme", "dark")
} else {
document.documentElement.setAttribute("saved-theme", "light")
localStorage.setItem("theme", "light")
}
}
// Darkmode toggle
const toggleSwitch = document.querySelector('#darkmode-toggle') as HTMLInputElement
toggleSwitch.removeEventListener('change', switchTheme)
toggleSwitch.addEventListener('change', switchTheme)
if (currentTheme === 'dark') {
const toggleSwitch = document.querySelector("#darkmode-toggle") as HTMLInputElement
toggleSwitch.removeEventListener("change", switchTheme)
toggleSwitch.addEventListener("change", switchTheme)
if (currentTheme === "dark") {
toggleSwitch.checked = true
}
})

View File

@ -1,16 +1,16 @@
import { ContentDetails } from "../../plugins/emitters/contentIndex"
import * as d3 from 'd3'
import * as d3 from "d3"
import { registerEscapeHandler, removeAllChildren } from "./util"
import { CanonicalSlug, getCanonicalSlug, getClientSlug, resolveRelative } from "../../path"
type NodeData = {
id: CanonicalSlug,
text: string,
id: CanonicalSlug
text: string
tags: string[]
} & d3.SimulationNodeDatum
type LinkData = {
source: CanonicalSlug,
source: CanonicalSlug
target: CanonicalSlug
}
@ -40,7 +40,7 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
centerForce,
linkDistance,
fontSize,
opacityScale
opacityScale,
} = JSON.parse(graph.dataset["cfg"]!)
const data = await fetchData
@ -66,18 +66,22 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
wl.push("__SENTINEL")
} else {
neighbourhood.add(cur)
const outgoing = links.filter(l => l.source === cur)
const incoming = links.filter(l => l.target === cur)
const outgoing = links.filter((l) => l.source === cur)
const incoming = links.filter((l) => l.target === cur)
wl.push(...outgoing.map((l) => l.target), ...incoming.map((l) => l.source))
}
}
} else {
Object.keys(data).forEach(id => neighbourhood.add(id as CanonicalSlug))
Object.keys(data).forEach((id) => neighbourhood.add(id as CanonicalSlug))
}
const graphData: { nodes: NodeData[], links: LinkData[] } = {
nodes: [...neighbourhood].map(url => ({ id: url, text: data[url]?.title ?? url, tags: data[url]?.tags ?? [] })),
links: links.filter((l) => neighbourhood.has(l.source) && neighbourhood.has(l.target))
const graphData: { nodes: NodeData[]; links: LinkData[] } = {
nodes: [...neighbourhood].map((url) => ({
id: url,
text: data[url]?.title ?? url,
tags: data[url]?.tags ?? [],
})),
links: links.filter((l) => neighbourhood.has(l.source) && neighbourhood.has(l.target)),
}
const simulation: d3.Simulation<NodeData, LinkData> = d3
@ -96,11 +100,11 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
const width = graph.offsetWidth
const svg = d3
.select<HTMLElement, NodeData>('#' + container)
.select<HTMLElement, NodeData>("#" + container)
.append("svg")
.attr("width", width)
.attr("height", height)
.attr('viewBox', [-width / 2 / scale, -height / 2 / scale, width / scale, height / scale])
.attr("viewBox", [-width / 2 / scale, -height / 2 / scale, width / scale, height / scale])
// draw links between nodes
const link = svg
@ -145,7 +149,7 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
d.fy = null
}
const noop = () => { }
const noop = () => {}
return d3
.drag<Element, NodeData>()
.on("start", enableDrag ? dragstarted : noop)
@ -170,9 +174,11 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
const targ = resolveRelative(slug, d.id)
window.spaNavigate(new URL(targ, getClientSlug(window)))
})
.on("mouseover", function(_, d) {
.on("mouseover", function (_, d) {
const neighbours: CanonicalSlug[] = data[slug].links ?? []
const neighbourNodes = d3.selectAll<HTMLElement, NodeData>(".node").filter((d) => neighbours.includes(d.id))
const neighbourNodes = d3
.selectAll<HTMLElement, NodeData>(".node")
.filter((d) => neighbours.includes(d.id))
console.log(neighbourNodes)
const currentId = d.id
const linkNodes = d3
@ -183,12 +189,7 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
neighbourNodes.transition().duration(200).attr("fill", color)
// highlight links
linkNodes
.transition()
.duration(200)
.attr("stroke", "var(--gray)")
.attr("stroke-width", 1)
linkNodes.transition().duration(200).attr("stroke", "var(--gray)").attr("stroke-width", 1)
const bigFont = fontSize * 1.5
@ -199,11 +200,11 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
.select("text")
.transition()
.duration(200)
.attr('opacityOld', d3.select(parent).select('text').style("opacity"))
.style('opacity', 1)
.style('font-size', bigFont + 'em')
.attr("opacityOld", d3.select(parent).select("text").style("opacity"))
.style("opacity", 1)
.style("font-size", bigFont + "em")
})
.on("mouseleave", function(_, d) {
.on("mouseleave", function (_, d) {
const currentId = d.id
const linkNodes = d3
.selectAll(".link")
@ -216,8 +217,8 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
.select("text")
.transition()
.duration(200)
.style('opacity', d3.select(parent).select('text').attr("opacityOld"))
.style('font-size', fontSize + 'em')
.style("opacity", d3.select(parent).select("text").attr("opacityOld"))
.style("font-size", fontSize + "em")
})
// @ts-ignore
.call(drag(simulation))
@ -228,10 +229,12 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
.attr("dx", 0)
.attr("dy", (d) => -nodeRadius(d) + "px")
.attr("text-anchor", "middle")
.text((d) => data[d.id]?.title || (d.id.charAt(1).toUpperCase() + d.id.slice(2)).replace("-", " "))
.style('opacity', (opacityScale - 1) / 3.75)
.text(
(d) => data[d.id]?.title || (d.id.charAt(1).toUpperCase() + d.id.slice(2)).replace("-", " "),
)
.style("opacity", (opacityScale - 1) / 3.75)
.style("pointer-events", "none")
.style('font-size', fontSize + 'em')
.style("font-size", fontSize + "em")
.raise()
// @ts-ignore
.call(drag(simulation))
@ -249,7 +252,7 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
.on("zoom", ({ transform }) => {
link.attr("transform", transform)
node.attr("transform", transform)
const scale = transform.k * opacityScale;
const scale = transform.k * opacityScale
const scaledOpacity = Math.max((scale - 1) / 3.75, 0)
labels.attr("transform", transform).style("opacity", scaledOpacity)
}),
@ -263,17 +266,13 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
.attr("y1", (d: any) => d.source.y)
.attr("x2", (d: any) => d.target.x)
.attr("y2", (d: any) => d.target.y)
node
.attr("cx", (d: any) => d.x)
.attr("cy", (d: any) => d.y)
labels
.attr("x", (d: any) => d.x)
.attr("y", (d: any) => d.y)
node.attr("cx", (d: any) => d.x).attr("cy", (d: any) => d.y)
labels.attr("x", (d: any) => d.x).attr("y", (d: any) => d.y)
})
}
function renderGlobalGraph() {
const slug = getCanonicalSlug(window)
const slug = getCanonicalSlug(window)
const container = document.getElementById("global-graph-outer")
const sidebar = container?.closest(".sidebar") as HTMLElement
container?.classList.add("active")
@ -305,4 +304,3 @@ document.addEventListener("nav", async (e: unknown) => {
containerIcon?.removeEventListener("click", renderGlobalGraph)
containerIcon?.addEventListener("click", renderGlobalGraph)
})

View File

@ -1,3 +1,3 @@
import Plausible from 'plausible-tracker'
import Plausible from "plausible-tracker"
const { trackPageview } = Plausible()
document.addEventListener("nav", () => trackPageview())

View File

@ -2,33 +2,25 @@ import { computePosition, flip, inline, shift } from "@floating-ui/dom"
// from micromorph/src/utils.ts
// https://github.com/natemoo-re/micromorph/blob/main/src/utils.ts#L5
export function normalizeRelativeURLs(
el: Element | Document,
base: string | URL
) {
export function normalizeRelativeURLs(el: Element | Document, base: string | URL) {
const update = (el: Element, attr: string, base: string | URL) => {
el.setAttribute(attr, new URL(el.getAttribute(attr)!, base).pathname)
}
el.querySelectorAll('[href^="./"], [href^="../"]').forEach((item) =>
update(item, 'href', base)
)
el.querySelectorAll('[href^="./"], [href^="../"]').forEach((item) => update(item, "href", base))
el.querySelectorAll('[src^="./"], [src^="../"]').forEach((item) =>
update(item, 'src', base)
)
el.querySelectorAll('[src^="./"], [src^="../"]').forEach((item) => update(item, "src", base))
}
const p = new DOMParser()
async function mouseEnterHandler(this: HTMLLinkElement, { clientX, clientY }: { clientX: number, clientY: number }) {
async function mouseEnterHandler(
this: HTMLLinkElement,
{ clientX, clientY }: { clientX: number; clientY: number },
) {
const link = this
async function setPosition(popoverElement: HTMLElement) {
const { x, y } = await computePosition(link, popoverElement, {
middleware: [
inline({ x: clientX, y: clientY }),
shift(),
flip()
]
middleware: [inline({ x: clientX, y: clientY }), shift(), flip()],
})
Object.assign(popoverElement.style, {
left: `${x}px`,
@ -37,7 +29,7 @@ async function mouseEnterHandler(this: HTMLLinkElement, { clientX, clientY }: {
}
// dont refetch if there's already a popover
if ([...link.children].some(child => child.classList.contains("popover"))) {
if ([...link.children].some((child) => child.classList.contains("popover"))) {
return setPosition(link.lastChild as HTMLElement)
}
@ -68,7 +60,7 @@ async function mouseEnterHandler(this: HTMLLinkElement, { clientX, clientY }: {
const popoverInner = document.createElement("div")
popoverInner.classList.add("popover-inner")
popoverElement.appendChild(popoverInner)
elts.forEach(elt => popoverInner.appendChild(elt))
elts.forEach((elt) => popoverInner.appendChild(elt))
setPosition(popoverElement)
link.appendChild(popoverElement)
@ -77,7 +69,7 @@ async function mouseEnterHandler(this: HTMLLinkElement, { clientX, clientY }: {
const heading = popoverInner.querySelector(hash) as HTMLElement | null
if (heading) {
// leave ~12px of buffer when scrolling to a heading
popoverInner.scroll({ top: heading.offsetTop - 12, behavior: 'instant' })
popoverInner.scroll({ top: heading.offsetTop - 12, behavior: "instant" })
}
}
}

View File

@ -4,9 +4,9 @@ import { registerEscapeHandler, removeAllChildren } from "./util"
import { CanonicalSlug, getClientSlug, resolveRelative } from "../../path"
interface Item {
slug: CanonicalSlug,
title: string,
content: string,
slug: CanonicalSlug
title: string
content: string
}
let index: Document<Item> | undefined = undefined
@ -15,15 +15,17 @@ const contextWindowWords = 30
const numSearchResults = 5
function highlight(searchTerm: string, text: string, trim?: boolean) {
// try to highlight longest tokens first
const tokenizedTerms = searchTerm.split(/\s+/).filter(t => t !== "").sort((a, b) => b.length - a.length)
let tokenizedText = text
const tokenizedTerms = searchTerm
.split(/\s+/)
.filter(t => t !== "")
.filter((t) => t !== "")
.sort((a, b) => b.length - a.length)
let tokenizedText = text.split(/\s+/).filter((t) => t !== "")
let startIndex = 0
let endIndex = tokenizedText.length - 1
if (trim) {
const includesCheck = (tok: string) => tokenizedTerms.some((term) => tok.toLowerCase().startsWith(term.toLowerCase()))
const includesCheck = (tok: string) =>
tokenizedTerms.some((term) => tok.toLowerCase().startsWith(term.toLowerCase()))
const occurencesIndices = tokenizedText.map(includesCheck)
let bestSum = 0
@ -42,19 +44,22 @@ function highlight(searchTerm: string, text: string, trim?: boolean) {
tokenizedText = tokenizedText.slice(startIndex, endIndex)
}
const slice = tokenizedText.map(tok => {
// see if this tok is prefixed by any search terms
for (const searchTok of tokenizedTerms) {
if (tok.toLowerCase().includes(searchTok.toLowerCase())) {
const regex = new RegExp(searchTok.toLowerCase(), "gi")
return tok.replace(regex, `<span class="highlight">$&</span>`)
const slice = tokenizedText
.map((tok) => {
// see if this tok is prefixed by any search terms
for (const searchTok of tokenizedTerms) {
if (tok.toLowerCase().includes(searchTok.toLowerCase())) {
const regex = new RegExp(searchTok.toLowerCase(), "gi")
return tok.replace(regex, `<span class="highlight">$&</span>`)
}
}
}
return tok
})
return tok
})
.join(" ")
return `${startIndex === 0 ? "" : "..."}${slice}${endIndex === tokenizedText.length - 1 ? "" : "..."}`
return `${startIndex === 0 ? "" : "..."}${slice}${
endIndex === tokenizedText.length - 1 ? "" : "..."
}`
}
const encoder = (str: string) => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])/)
@ -113,7 +118,7 @@ document.addEventListener("nav", async (e: unknown) => {
button.classList.add("result-card")
button.id = slug
button.innerHTML = `<h3>${title}</h3><p>${content}</p>`
button.addEventListener('click', () => {
button.addEventListener("click", () => {
const targ = resolveRelative(currentSlug, slug)
window.spaNavigate(new URL(targ, getClientSlug(window)))
})
@ -132,7 +137,6 @@ document.addEventListener("nav", async (e: unknown) => {
} else {
results.append(...finalResults.map(resultToHTML))
}
}
function onType(e: HTMLElementEventMap["input"]) {
@ -140,12 +144,12 @@ document.addEventListener("nav", async (e: unknown) => {
const searchResults = index?.search(term, numSearchResults) ?? []
const getByField = (field: string): CanonicalSlug[] => {
const results = searchResults.filter((x) => x.field === field)
return results.length === 0 ? [] : [...results[0].result] as CanonicalSlug[]
return results.length === 0 ? [] : ([...results[0].result] as CanonicalSlug[])
}
// order titles ahead of content
const allIds: Set<CanonicalSlug> = new Set([...getByField("title"), ...getByField("content")])
const finalResults = [...allIds].map(id => formatForDisplay(term, id))
const finalResults = [...allIds].map((id) => formatForDisplay(term, id))
displayResults(finalResults)
}
@ -160,7 +164,7 @@ document.addEventListener("nav", async (e: unknown) => {
if (!index) {
index = new Document({
cache: true,
charset: 'latin:extra',
charset: "latin:extra",
optimize: true,
encode: encoder,
document: {
@ -174,7 +178,7 @@ document.addEventListener("nav", async (e: unknown) => {
field: "content",
tokenize: "reverse",
},
]
],
},
})
@ -182,7 +186,7 @@ document.addEventListener("nav", async (e: unknown) => {
await index.addAsync(slug, {
slug: slug as CanonicalSlug,
title: fileData.title,
content: fileData.content
content: fileData.content,
})
}
}

View File

@ -5,8 +5,9 @@ import { CanonicalSlug, RelativeURL, getCanonicalSlug } from "../../path"
// https://github.com/natemoo-re/micromorph
const NODE_TYPE_ELEMENT = 1
let announcer = document.createElement('route-announcer')
const isElement = (target: EventTarget | null): target is Element => (target as Node)?.nodeType === NODE_TYPE_ELEMENT
let announcer = document.createElement("route-announcer")
const isElement = (target: EventTarget | null): target is Element =>
(target as Node)?.nodeType === NODE_TYPE_ELEMENT
const isLocalUrl = (href: string) => {
try {
const url = new URL(href)
@ -16,18 +17,18 @@ const isLocalUrl = (href: string) => {
}
return true
}
} catch (e) { }
} catch (e) {}
return false
}
const getOpts = ({ target }: Event): { url: URL, scroll?: boolean } | undefined => {
const getOpts = ({ target }: Event): { url: URL; scroll?: boolean } | undefined => {
if (!isElement(target)) return
const a = target.closest("a")
if (!a) return
if ('routerIgnore' in a.dataset) return
if ("routerIgnore" in a.dataset) return
const { href } = a
if (!isLocalUrl(href)) return
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(url: CanonicalSlug) {
@ -44,7 +45,7 @@ async function navigate(url: URL, isBack: boolean = false) {
window.location.assign(url)
})
if (!contents) return;
if (!contents) return
if (!isBack) {
history.pushState({}, "", url)
window.scrollTo({ top: 0 })
@ -54,22 +55,22 @@ async function navigate(url: URL, isBack: boolean = false) {
if (title) {
document.title = title
} else {
const h1 = document.querySelector('h1')
const h1 = document.querySelector("h1")
title = h1?.innerText ?? h1?.textContent ?? url.pathname
}
if (announcer.textContent !== title) {
announcer.textContent = title
}
announcer.dataset.persist = ''
announcer.dataset.persist = ""
html.body.appendChild(announcer)
micromorph(document.body, html.body)
// now, patch head
const elementsToRemove = document.head.querySelectorAll(':not([spa-preserve])')
elementsToRemove.forEach(el => el.remove())
const elementsToAdd = html.head.querySelectorAll(':not([spa-preserve])')
elementsToAdd.forEach(el => document.head.appendChild(el))
// now, patch head
const elementsToRemove = document.head.querySelectorAll(":not([spa-preserve])")
elementsToRemove.forEach((el) => el.remove())
const elementsToAdd = html.head.querySelectorAll(":not([spa-preserve])")
elementsToAdd.forEach((el) => document.head.appendChild(el))
notifyNav(getCanonicalSlug(window))
delete announcer.dataset.persist
@ -101,7 +102,7 @@ function createRouter() {
})
}
return new class Router {
return new (class Router {
go(pathname: RelativeURL) {
const url = new URL(pathname, window.location.toString())
return navigate(url, false)
@ -114,26 +115,30 @@ function createRouter() {
forward() {
return window.history.forward()
}
}
})()
}
createRouter()
notifyNav(getCanonicalSlug(window))
if (!customElements.get('route-announcer')) {
if (!customElements.get("route-announcer")) {
const attrs = {
'aria-live': 'assertive',
'aria-atomic': 'true',
'style': 'position: absolute; left: 0; top: 0; clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap; width: 1px; height: 1px'
"aria-live": "assertive",
"aria-atomic": "true",
style:
"position: absolute; left: 0; top: 0; clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap; width: 1px; height: 1px",
}
customElements.define('route-announcer', class RouteAnnouncer extends HTMLElement {
constructor() {
super()
}
connectedCallback() {
for (const [key, value] of Object.entries(attrs)) {
this.setAttribute(key, value)
customElements.define(
"route-announcer",
class RouteAnnouncer extends HTMLElement {
constructor() {
super()
}
}
})
connectedCallback() {
for (const [key, value] of Object.entries(attrs)) {
this.setAttribute(key, value)
}
}
},
)
}

View File

@ -1,5 +1,5 @@
const bufferPx = 150
const observer = new IntersectionObserver(entries => {
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
const slug = entry.target.id
const tocEntryElement = document.querySelector(`a[data-for="${slug}"]`)
@ -38,5 +38,5 @@ document.addEventListener("nav", () => {
// update toc entry highlighting
observer.disconnect()
const headers = document.querySelectorAll("h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]")
headers.forEach(header => observer.observe(header))
headers.forEach((header) => observer.observe(header))
})

View File

@ -15,7 +15,7 @@ export function registerEscapeHandler(outsideContainer: HTMLElement | null, cb:
outsideContainer?.removeEventListener("click", click)
outsideContainer?.addEventListener("click", click)
document.removeEventListener("keydown", esc)
document.addEventListener('keydown', esc)
document.addEventListener("keydown", esc)
}
export function removeAllChildren(node: HTMLElement) {

View File

@ -3,7 +3,7 @@
.graph {
& > h3 {
font-size: 1rem;
margin: 0
margin: 0;
}
& > .graph-outer {
@ -26,7 +26,7 @@
top: 0;
right: 0;
border-radius: 4px;
background-color: transparent;
background-color: transparent;
transition: background-color 0.5s ease;
cursor: pointer;
&:hover {
@ -52,7 +52,7 @@
& > #global-graph-container {
border: 1px solid var(--lightgray);
background-color: var(--light);
background-color: var(--light);
border-radius: 5px;
box-sizing: border-box;
position: fixed;

View File

@ -12,7 +12,7 @@ details#toc {
margin: 0;
}
}
& ul {
list-style: none;
margin: 0.5rem 1.25rem;

View File

@ -25,7 +25,7 @@ li.section-li {
}
& > .desc > h3 > a {
background-color: transparent;
background-color: transparent;
}
& > .meta {

View File

@ -32,7 +32,7 @@
border: 1px solid var(--lightgray);
background-color: var(--light);
border-radius: 5px;
box-shadow: 6px 6px 36px 0 rgba(0,0,0,0.25);
box-shadow: 6px 6px 36px 0 rgba(0, 0, 0, 0.25);
overflow: auto;
}
@ -42,14 +42,17 @@
visibility: hidden;
opacity: 0;
transition: opacity 0.3s ease, visibility 0.3s ease;
transition:
opacity 0.3s ease,
visibility 0.3s ease;
@media all and (max-width: $mobileBreakpoint) {
display: none !important;
}
}
a:hover .popover, .popover:hover {
a:hover .popover,
.popover:hover {
animation: dropin 0.3s ease;
animation-fill-mode: forwards;
animation-delay: 0.2s;

View File

@ -67,7 +67,9 @@
width: 100%;
border-radius: 5px;
background: var(--light);
box-shadow: 0 14px 50px rgba(27, 33, 48, 0.12), 0 10px 30px rgba(27, 33, 48, 0.16);
box-shadow:
0 14px 50px rgba(27, 33, 48, 0.12),
0 10px 30px rgba(27, 33, 48, 0.16);
margin-bottom: 2em;
}
@ -108,7 +110,8 @@
font-weight: 700;
}
&:hover, &:focus {
&:hover,
&:focus {
background: var(--lightgray);
}
@ -127,12 +130,11 @@
margin: 0;
}
& > p {
& > p {
margin-bottom: 0;
}
}
}
}
}
}

View File

@ -15,16 +15,16 @@ button#toc {
}
& .fold {
margin-left: 0.5rem;
margin-left: 0.5rem;
transition: transform 0.3s ease;
opacity: 0.8;
}
&.collapsed .fold {
transform: rotateZ(-90deg)
transform: rotateZ(-90deg);
}
}
#toc-content {
list-style: none;
overflow: hidden;
@ -42,7 +42,9 @@ button#toc {
& > li > a {
color: var(--dark);
opacity: 0.35;
transition: 0.5s ease opacity, 0.3s ease color;
transition:
0.5s ease opacity,
0.3s ease color;
&.in-view {
opacity: 0.75;
}
@ -55,4 +57,3 @@ button#toc {
}
}
}

View File

@ -11,15 +11,17 @@ export type QuartzComponentProps = {
children: (QuartzComponent | JSX.Element)[]
tree: Node<QuartzPluginData>
allFiles: QuartzPluginData[]
displayClass?: 'mobile-only' | 'desktop-only'
displayClass?: "mobile-only" | "desktop-only"
} & JSX.IntrinsicAttributes & {
[key: string]: any
}
[key: string]: any
}
export type QuartzComponent = ComponentType<QuartzComponentProps> & {
css?: string,
beforeDOMLoaded?: string,
afterDOMLoaded?: string,
css?: string
beforeDOMLoaded?: string
afterDOMLoaded?: string
}
export type QuartzComponentConstructor<Options extends object | undefined = undefined> = (opts: Options) => QuartzComponent
export type QuartzComponentConstructor<Options extends object | undefined = undefined> = (
opts: Options,
) => QuartzComponent