various polish
This commit is contained in:
@ -2,9 +2,8 @@ import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||
|
||||
function ArticleTitle({ fileData }: QuartzComponentProps) {
|
||||
const title = fileData.frontmatter?.title
|
||||
const displayTitle = fileData.slug === "index" ? undefined : title
|
||||
if (displayTitle) {
|
||||
return <h1 class="article-title">{displayTitle}</h1>
|
||||
if (title) {
|
||||
return <h1 class="article-title">{title}</h1>
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ export default ((opts?: Options) => {
|
||||
return <>
|
||||
<hr />
|
||||
<footer>
|
||||
<p>Made by {name} using <a>Quartz</a>, © {year}</p>
|
||||
<p>Made by {name} using <a href="https://quartz.jzhao.xyz/">Quartz</a>, © {year}</p>
|
||||
<ul>{Object.entries(links).map(([text, link]) => <li>
|
||||
<a href={link}>{text}</a>
|
||||
</li>)}</ul>
|
||||
|
@ -2,15 +2,7 @@ import { resolveToRoot } from "../path"
|
||||
import { JSResourceToScriptElement } from "../resources"
|
||||
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||
|
||||
interface Options {
|
||||
prefetchContentIndex: boolean
|
||||
}
|
||||
|
||||
const defaultOptions: Options = {
|
||||
prefetchContentIndex: true
|
||||
}
|
||||
|
||||
export default ((opts?: Options) => {
|
||||
export default (() => {
|
||||
function Head({ fileData, externalResources }: QuartzComponentProps) {
|
||||
const slug = fileData.slug!
|
||||
const title = fileData.frontmatter?.title ?? "Untitled"
|
||||
@ -20,10 +12,6 @@ export default ((opts?: Options) => {
|
||||
const iconPath = baseDir + "/static/icon.png"
|
||||
const ogImagePath = baseDir + "/static/og-image.png"
|
||||
|
||||
const prefetchContentIndex = opts?.prefetchContentIndex ?? defaultOptions.prefetchContentIndex
|
||||
const contentIndexPath = baseDir + "/static/contentIndex.json"
|
||||
const contentIndexScript = `const fetchData = fetch(\`${contentIndexPath}\`).then(data => data.json())`
|
||||
|
||||
return <head>
|
||||
<title>{title}</title>
|
||||
<meta charSet="utf-8" />
|
||||
@ -36,9 +24,8 @@ export default ((opts?: Options) => {
|
||||
<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" />
|
||||
{prefetchContentIndex && <script spa-preserve>{contentIndexScript}</script>}
|
||||
<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>
|
||||
|
@ -12,6 +12,7 @@ header {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin: 2em 0;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
|
@ -23,7 +23,7 @@ function byDateAndAlphabetical(f1: QuartzPluginData, f2: QuartzPluginData): numb
|
||||
|
||||
export function PageList({ fileData, allFiles }: QuartzComponentProps) {
|
||||
const slug = fileData.slug!
|
||||
return <ul class="section-ul">
|
||||
return <ul class="section-ul popover-hint">
|
||||
{allFiles.sort(byDateAndAlphabetical).map(page => {
|
||||
const title = page.frontmatter?.title
|
||||
const pageSlug = page.slug!
|
||||
@ -36,9 +36,8 @@ export function PageList({ fileData, allFiles }: QuartzComponentProps) {
|
||||
<div class="desc">
|
||||
<h3><a href={stripIndex(relativeToRoot(slug, pageSlug))} class="internal">{title}</a></h3>
|
||||
</div>
|
||||
<div class="spacer"></div>
|
||||
<ul class="tags">
|
||||
{tags.map(tag => <li><a href={relativeToRoot(slug, `tags/${tag}`)}>#{tag}</a></li>)}
|
||||
{tags.map(tag => <li><a class="internal" href={relativeToRoot(slug, `tags/${tag}`)}>#{tag}</a></li>)}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
|
@ -11,7 +11,7 @@ function TagList({ fileData }: QuartzComponentProps) {
|
||||
const display = `#${tag}`
|
||||
const linkDest = baseDir + `/tags/${slugAnchor(tag)}`
|
||||
return <li>
|
||||
<a href={linkDest}>{display}</a>
|
||||
<a href={linkDest} class="internal">{display}</a>
|
||||
</li>
|
||||
})}</ul>
|
||||
} else {
|
||||
@ -25,17 +25,18 @@ TagList.css = `
|
||||
display: flex;
|
||||
padding-left: 0;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.tags > li {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
overflow-wrap: normal;
|
||||
}
|
||||
|
||||
& > li {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
|
||||
& > a {
|
||||
border-radius: 8px;
|
||||
border: var(--lightgray) 1px solid;
|
||||
padding: 0.2rem 0.5rem;
|
||||
}
|
||||
}
|
||||
.tags > li > a {
|
||||
border-radius: 8px;
|
||||
background-color: var(--highlight);
|
||||
padding: 0.2rem 0.5rem;
|
||||
}
|
||||
`
|
||||
|
||||
|
@ -5,7 +5,7 @@ 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' })
|
||||
return <article>{content}</article>
|
||||
return <article class="popover-hint">{content}</article>
|
||||
}
|
||||
|
||||
export default (() => Content) satisfies QuartzComponentConstructor
|
||||
export default (() => Content) satisfies QuartzComponentConstructor
|
||||
|
@ -17,10 +17,15 @@ interface RenderComponents {
|
||||
|
||||
export function pageResources(slug: string, staticResources: StaticResources): StaticResources {
|
||||
const baseDir = resolveToRoot(slug)
|
||||
|
||||
const contentIndexPath = baseDir + "/static/contentIndex.json"
|
||||
const contentIndexScript = `const fetchData = fetch(\`${contentIndexPath}\`).then(data => data.json())`
|
||||
|
||||
return {
|
||||
css: [baseDir + "/index.css", ...staticResources.css],
|
||||
js: [
|
||||
{ src: baseDir + "/prescript.js", loadTime: "beforeDOMReady", contentType: "external" },
|
||||
{ loadTime: "afterDOMReady", contentType: "inline", spaPreserve: true, script: contentIndexScript },
|
||||
...staticResources.js,
|
||||
{ src: baseDir + "/postscript.js", loadTime: "afterDOMReady", moduleType: 'module', contentType: "external" }
|
||||
]
|
||||
@ -32,28 +37,40 @@ export function renderPage(slug: string, componentData: QuartzComponentProps, co
|
||||
const Header = HeaderConstructor()
|
||||
const Body = BodyConstructor()
|
||||
|
||||
const LeftComponent =
|
||||
<div class="left">
|
||||
<div class="left-inner">
|
||||
{left.map(BodyComponent => <BodyComponent {...componentData} />)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
const RightComponent =
|
||||
<div class="right">
|
||||
<div class="right-inner">
|
||||
{right.map(BodyComponent => <BodyComponent {...componentData} />)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
const doc = <html>
|
||||
<Head {...componentData} />
|
||||
<body data-slug={slug}>
|
||||
<div id="quartz-root" class="page">
|
||||
<Header {...componentData} >
|
||||
{header.map(HeaderComponent => <HeaderComponent {...componentData} />)}
|
||||
</Header>
|
||||
<div class="popover-hint">
|
||||
{beforeBody.map(BodyComponent => <BodyComponent {...componentData} />)}
|
||||
<div class="page-header">
|
||||
<Header {...componentData} >
|
||||
{header.map(HeaderComponent => <HeaderComponent {...componentData} />)}
|
||||
</Header>
|
||||
<div class="popover-hint">
|
||||
{beforeBody.map(BodyComponent => <BodyComponent {...componentData} />)}
|
||||
</div>
|
||||
</div>
|
||||
<Body {...componentData}>
|
||||
<div class="left">
|
||||
{left.map(BodyComponent => <BodyComponent {...componentData} />)}
|
||||
</div>
|
||||
<div class="center popover-hint">
|
||||
{LeftComponent}
|
||||
<div class="center">
|
||||
<Content {...componentData} />
|
||||
<Footer {...componentData} />
|
||||
</div>
|
||||
<div class="right">
|
||||
{right.map(BodyComponent => <BodyComponent {...componentData} />)}
|
||||
</div>
|
||||
{RightComponent}
|
||||
</Body>
|
||||
<Footer {...componentData} />
|
||||
</div>
|
||||
</body>
|
||||
{pageResources.js.filter(resource => resource.loadTime === "afterDOMReady").map(res => JSResourceToScriptElement(res))}
|
||||
|
@ -2,7 +2,7 @@ const userPref = window.matchMedia('(prefers-color-scheme: light)').matches ? 'l
|
||||
const currentTheme = localStorage.getItem('theme') ?? userPref
|
||||
document.documentElement.setAttribute('saved-theme', currentTheme)
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
document.addEventListener("nav", () => {
|
||||
const switchTheme = (e: any) => {
|
||||
if (e.target.checked) {
|
||||
document.documentElement.setAttribute('saved-theme', 'dark')
|
||||
@ -16,7 +16,8 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Darkmode toggle
|
||||
const toggleSwitch = document.querySelector('#darkmode-toggle') as HTMLInputElement
|
||||
toggleSwitch.addEventListener('change', switchTheme, false)
|
||||
toggleSwitch.removeEventListener('change', switchTheme)
|
||||
toggleSwitch.addEventListener('change', switchTheme)
|
||||
if (currentTheme === 'dark') {
|
||||
toggleSwitch.checked = true
|
||||
}
|
||||
|
@ -266,9 +266,9 @@ async function renderGraph(container: string, slug: string) {
|
||||
})
|
||||
}
|
||||
|
||||
function renderGlobalGraph() {
|
||||
async function renderGlobalGraph() {
|
||||
const slug = document.body.dataset["slug"]!
|
||||
renderGraph("global-graph-container", slug)
|
||||
await renderGraph("global-graph-container", slug)
|
||||
const container = document.getElementById("global-graph-outer")
|
||||
container?.classList.add("active")
|
||||
|
||||
@ -293,7 +293,14 @@ document.addEventListener("nav", async (e: unknown) => {
|
||||
containerIcon?.addEventListener("click", renderGlobalGraph)
|
||||
})
|
||||
|
||||
window.addEventListener('resize', async () => {
|
||||
const slug = document.body.dataset["slug"]!
|
||||
await renderGraph("graph-container", slug)
|
||||
let resizeEventDebounce: number | undefined = undefined
|
||||
window.addEventListener('resize', () => {
|
||||
if (resizeEventDebounce) {
|
||||
clearTimeout(resizeEventDebounce)
|
||||
}
|
||||
|
||||
resizeEventDebounce = window.setTimeout(async () => {
|
||||
const slug = document.body.dataset["slug"]!
|
||||
await renderGraph("graph-container", slug)
|
||||
}, 50)
|
||||
})
|
||||
|
3
quartz/components/scripts/plausible.inline.ts
Normal file
3
quartz/components/scripts/plausible.inline.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import Plausible from 'plausible-tracker'
|
||||
const { trackPageview } = Plausible()
|
||||
document.addEventListener("nav", () => trackPageview())
|
@ -1,5 +1,24 @@
|
||||
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
|
||||
) {
|
||||
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('[src^="./"], [src^="../"]').forEach((item) =>
|
||||
update(item, 'src', base)
|
||||
)
|
||||
}
|
||||
|
||||
document.addEventListener("nav", () => {
|
||||
const links = [...document.getElementsByClassName("internal")] as HTMLLinkElement[]
|
||||
const p = new DOMParser()
|
||||
@ -41,6 +60,7 @@ document.addEventListener("nav", () => {
|
||||
|
||||
if (!contents) return
|
||||
const html = p.parseFromString(contents, "text/html")
|
||||
normalizeRelativeURLs(html, targetUrl)
|
||||
const elts = [...html.getElementsByClassName("popover-hint")]
|
||||
if (elts.length === 0) return
|
||||
|
||||
@ -54,11 +74,13 @@ document.addEventListener("nav", () => {
|
||||
setPosition(popoverElement)
|
||||
link.appendChild(popoverElement)
|
||||
link.dataset.fetchedPopover = "true"
|
||||
|
||||
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' })
|
||||
|
||||
if (hash !== "") {
|
||||
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' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -7,13 +7,9 @@
|
||||
& > ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin: 0.5rem 0;
|
||||
|
||||
& > li {
|
||||
margin: 0.5rem 0;
|
||||
padding: 0.25rem 1rem;
|
||||
border: var(--lightgray) 1px solid;
|
||||
border-radius: 5px;
|
||||
& > a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
footer {
|
||||
text-align: left;
|
||||
opacity: 0.8;
|
||||
margin-bottom: 4rem;
|
||||
|
||||
& ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
|
@ -11,6 +11,7 @@
|
||||
height: 250px;
|
||||
margin: 0.5em 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
& > #global-graph-icon {
|
||||
color: var(--dark);
|
||||
@ -30,10 +31,6 @@
|
||||
background-color: var(--lightgray);
|
||||
}
|
||||
}
|
||||
|
||||
& > #graph-container > svg {
|
||||
margin-bottom: -5px;
|
||||
}
|
||||
}
|
||||
|
||||
& > #global-graph-outer {
|
||||
|
@ -8,29 +8,36 @@ li.section-li {
|
||||
margin-bottom: 1em;
|
||||
|
||||
& > .section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: 6em 3fr 1fr;
|
||||
|
||||
@media all and (max-width: 600px) {
|
||||
& .tags {
|
||||
& > .tags {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
& h3 > a {
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
background-color: transparent;
|
||||
& > .tags {
|
||||
justify-self: end;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
& p {
|
||||
& > .desc a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
& > .meta {
|
||||
margin: 0;
|
||||
padding-right: 1em;
|
||||
flex-basis: 6em;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
& .meta {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
// modifications in popover context
|
||||
.popover .section {
|
||||
grid-template-columns: 6em 1fr !important;
|
||||
& > .tags {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@
|
||||
height: 20rem;
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
font-weight: initial;
|
||||
line-height: initial;
|
||||
line-height: normal;
|
||||
font-size: initial;
|
||||
font-family: var(--bodyFont);
|
||||
border: 1px solid var(--gray);
|
||||
|
@ -1,8 +1,7 @@
|
||||
.search {
|
||||
min-width: 5rem;
|
||||
max-width: 12rem;
|
||||
max-width: 14rem;
|
||||
flex-grow: 0.3;
|
||||
margin: 0 1.5rem;
|
||||
|
||||
& > #search-icon {
|
||||
background-color: var(--lightgray);
|
||||
|
@ -8,7 +8,7 @@ export type QuartzComponentProps = {
|
||||
externalResources: StaticResources
|
||||
fileData: QuartzPluginData
|
||||
cfg: GlobalConfiguration
|
||||
children: QuartzComponent[] | JSX.Element[]
|
||||
children: (QuartzComponent | JSX.Element)[]
|
||||
tree: Node<QuartzPluginData>
|
||||
allFiles: QuartzPluginData[]
|
||||
}
|
||||
|
Reference in New Issue
Block a user