run prettier
This commit is contained in:
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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}</>
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 = `
|
||||
|
@ -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 = `
|
||||
|
@ -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 = `
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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>
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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}`)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
})
|
||||
|
@ -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)
|
||||
})
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
import Plausible from 'plausible-tracker'
|
||||
import Plausible from "plausible-tracker"
|
||||
const { trackPageview } = Plausible()
|
||||
document.addEventListener("nav", () => trackPageview())
|
||||
|
@ -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" })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -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))
|
||||
})
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -12,7 +12,7 @@ details#toc {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
& ul {
|
||||
list-style: none;
|
||||
margin: 0.5rem 1.25rem;
|
||||
|
@ -25,7 +25,7 @@ li.section-li {
|
||||
}
|
||||
|
||||
& > .desc > h3 > a {
|
||||
background-color: transparent;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
& > .meta {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user