This commit is contained in:
Jacky Zhao 2023-06-17 16:05:46 -07:00
parent cb89cce183
commit 8bfee04c8c
10 changed files with 143 additions and 16 deletions

14
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "4.0.3", "version": "4.0.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@floating-ui/dom": "^1.4.0",
"@inquirer/prompts": "^1.0.3", "@inquirer/prompts": "^1.0.3",
"@napi-rs/simple-git": "^0.1.8", "@napi-rs/simple-git": "^0.1.8",
"chalk": "^4.1.2", "chalk": "^4.1.2",
@ -393,6 +394,19 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@floating-ui/core": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.3.1.tgz",
"integrity": "sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g=="
},
"node_modules/@floating-ui/dom": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.4.0.tgz",
"integrity": "sha512-b4F0iWffLiqb/TpP2PWVOixrZqE6ni+6VT64AmFH7sJIF3SFPLbe6/h3jQ5Cwffs+HaC9A8V0TQzCPBwVvziIA==",
"dependencies": {
"@floating-ui/core": "^1.3.1"
}
},
"node_modules/@inquirer/checkbox": { "node_modules/@inquirer/checkbox": {
"version": "1.2.8", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-1.2.8.tgz", "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-1.2.8.tgz",

View File

@ -25,6 +25,7 @@
"quartz": "./quartz/bootstrap-cli.mjs" "quartz": "./quartz/bootstrap-cli.mjs"
}, },
"dependencies": { "dependencies": {
"@floating-ui/dom": "^1.4.0",
"@inquirer/prompts": "^1.0.3", "@inquirer/prompts": "^1.0.3",
"@napi-rs/simple-git": "^0.1.8", "@napi-rs/simple-git": "^0.1.8",
"chalk": "^4.1.2", "chalk": "^4.1.2",

View File

@ -64,6 +64,7 @@ const config: QuartzConfig = {
Component.ReadingTime(), Component.ReadingTime(),
Component.TagList(), Component.TagList(),
], ],
content: Component.Content(),
left: [ left: [
Component.TableOfContents(), Component.TableOfContents(),
], ],

View File

@ -2,10 +2,30 @@ 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 { toJsxRuntime } from "hast-util-to-jsx-runtime"
// @ts-ignore
import popoverScript from './scripts/popover.inline'
import popoverStyle from './styles/popover.scss'
interface Options {
enablePopover: boolean
}
const defaultOptions: Options = {
enablePopover: true
}
export default ((opts?: Partial<Options>) => {
function Content({ tree }: QuartzComponentProps) { function Content({ tree }: QuartzComponentProps) {
// @ts-ignore (preact makes it angry) // @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>{content}</article> return <article>{content}</article>
} }
export default (() => Content) satisfies QuartzComponentConstructor const enablePopover = opts?.enablePopover ?? defaultOptions.enablePopover
if (enablePopover) {
Content.afterDOMLoaded = popoverScript
Content.css = popoverStyle
}
return Content
}) satisfies QuartzComponentConstructor

View File

@ -0,0 +1,41 @@
import { computePosition, inline, shift, autoPlacement } from "@floating-ui/dom"
document.addEventListener("nav", () => {
const links = [...document.getElementsByClassName("internal")] as HTMLLinkElement[]
const p = new DOMParser()
for (const link of links) {
link.addEventListener("mouseenter", async ({ clientX, clientY }) => {
if (link.dataset.fetchedPopover === "true") return
const url = link.href
const contents = await fetch(`${url}`)
.then((res) => res.text())
.catch((err) => {
console.error(err)
})
if (!contents) return
const html = p.parseFromString(contents, "text/html")
const elts = [...html.getElementsByClassName("popover-hint")]
if (elts.length === 0) return
const popoverElement = document.createElement("div")
popoverElement.classList.add("popover")
elts.forEach(elt => popoverElement.appendChild(elt))
const { x, y } = await computePosition(link, popoverElement, {
middleware: [inline({
x: clientX,
y: clientY
}), shift(), autoPlacement()]
})
Object.assign(popoverElement.style, {
left: `${x}px`,
top: `${y}px`,
})
link.appendChild(popoverElement)
link.dataset.fetchedPopover = "true"
})
}
})

View File

@ -22,12 +22,14 @@ function toggleToc(this: HTMLElement) {
} }
function setupToc() { function setupToc() {
const toc = document.getElementById("toc")! const toc = document.getElementById("toc")
if (toc) {
const content = toc.nextElementSibling as HTMLElement const content = toc.nextElementSibling as HTMLElement
content.style.maxHeight = content.scrollHeight + "px" content.style.maxHeight = content.scrollHeight + "px"
toc.removeEventListener("click", toggleToc) toc.removeEventListener("click", toggleToc)
toc.addEventListener("click", toggleToc) toc.addEventListener("click", toggleToc)
} }
}
window.addEventListener("resize", setupToc) window.addEventListener("resize", setupToc)
document.addEventListener("nav", () => { document.addEventListener("nav", () => {

View File

@ -0,0 +1,43 @@
@keyframes dropin {
0% {
opacity: 0;
visibility: hidden;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
visibility: visible;
}
}
.popover {
z-index: 999;
position: absolute;
overflow: scroll;
width: 30rem;
height: 20rem;
padding: 0 1rem;
margin-top: -1rem;
border: 1px solid var(--lightgray);
background-color: var(--light);
border-radius: 5px;
box-shadow: 6px 6px 36px 0 rgba(0,0,0,0.25);
font-weight: initial;
visibility: hidden;
opacity: 0;
transition: opacity 0.2s ease, visibility 0.2s ease;
@media all and (max-width: 600px) {
display: none !important;
}
}
a:hover .popover, .popover:hover {
animation: dropin 0.5s ease;
opacity: 1;
visibility: visible;
}

View File

@ -6,12 +6,12 @@ import { resolveToRoot } from "../../path"
import HeaderConstructor from "../../components/Header" import HeaderConstructor from "../../components/Header"
import { QuartzComponentProps } from "../../components/types" import { QuartzComponentProps } from "../../components/types"
import BodyConstructor from "../../components/Body" import BodyConstructor from "../../components/Body"
import ContentConstructor from "../../components/Content"
interface Options { interface Options {
head: QuartzComponent head: QuartzComponent
header: QuartzComponent[], header: QuartzComponent[],
beforeBody: QuartzComponent[], beforeBody: QuartzComponent[],
content: QuartzComponent,
left: QuartzComponent[], left: QuartzComponent[],
right: QuartzComponent[], right: QuartzComponent[],
footer: QuartzComponent[], footer: QuartzComponent[],
@ -25,12 +25,11 @@ export const ContentPage: QuartzEmitterPlugin<Options> = (opts) => {
const { head: Head, header, beforeBody, left, right, footer } = opts const { head: Head, header, beforeBody, left, right, footer } = opts
const Header = HeaderConstructor() const Header = HeaderConstructor()
const Body = BodyConstructor() const Body = BodyConstructor()
const Content = ContentConstructor()
return { return {
name: "ContentPage", name: "ContentPage",
getQuartzComponents() { getQuartzComponents() {
return [opts.head, Header, Body, ...opts.header, ...opts.beforeBody, ...opts.left, ...opts.right, ...opts.footer] return [opts.head, Header, Body, ...opts.header, ...opts.beforeBody, opts.content, ...opts.left, ...opts.right, ...opts.footer]
}, },
async emit(_contentDir, cfg, content, resources, emit): Promise<string[]> { async emit(_contentDir, cfg, content, resources, emit): Promise<string[]> {
const fps: string[] = [] const fps: string[] = []
@ -54,6 +53,7 @@ export const ContentPage: QuartzEmitterPlugin<Options> = (opts) => {
tree tree
} }
const Content = opts.content
const doc = <html> const doc = <html>
<Head {...componentData} /> <Head {...componentData} />
<body data-slug={file.data.slug}> <body data-slug={file.data.slug}>
@ -61,12 +61,14 @@ export const ContentPage: QuartzEmitterPlugin<Options> = (opts) => {
<Header {...componentData} > <Header {...componentData} >
{header.map(HeaderComponent => <HeaderComponent {...componentData} />)} {header.map(HeaderComponent => <HeaderComponent {...componentData} />)}
</Header> </Header>
<div class="popover-hint">
{beforeBody.map(BodyComponent => <BodyComponent {...componentData} />)} {beforeBody.map(BodyComponent => <BodyComponent {...componentData} />)}
</div>
<Body {...componentData}> <Body {...componentData}>
<div class="left"> <div class="left">
{left.map(BodyComponent => <BodyComponent {...componentData} />)} {left.map(BodyComponent => <BodyComponent {...componentData} />)}
</div> </div>
<div class="center"> <div class="center popover-hint">
<Content {...componentData} /> <Content {...componentData} />
</div> </div>
<div class="right"> <div class="right">

View File

@ -14,7 +14,8 @@ export type ComponentResources = {
} }
function joinScripts(scripts: string[]): string { function joinScripts(scripts: string[]): string {
return scripts.join("\n") // wrap with iife to prevent scope collision
return scripts.map(script => `(function () {${script}})();`).join("\n")
} }
export function emitComponentResources(cfg: GlobalConfiguration, resources: StaticResources, plugins: PluginTypes, emit: EmitCallback) { export function emitComponentResources(cfg: GlobalConfiguration, resources: StaticResources, plugins: PluginTypes, emit: EmitCallback) {

View File

@ -48,6 +48,8 @@ export const ResolveLinks: QuartzTransformerPlugin<Partial<Options> | undefined>
// don't process external links or intra-document anchors // don't process external links or intra-document anchors
if (!(isAbsoluteUrl(node.properties.href) || node.properties.href.startsWith("#"))) { if (!(isAbsoluteUrl(node.properties.href) || node.properties.href.startsWith("#"))) {
node.properties.href = transformLink(node.properties.href) node.properties.href = transformLink(node.properties.href)
} else {
} }
// rewrite link internals if prettylinks is on // rewrite link internals if prettylinks is on