popovers
This commit is contained in:
parent
de72a413ba
commit
659cbc4743
14
package-lock.json
generated
14
package-lock.json
generated
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -64,6 +64,7 @@ const config: QuartzConfig = {
|
|||||||
Component.ReadingTime(),
|
Component.ReadingTime(),
|
||||||
Component.TagList(),
|
Component.TagList(),
|
||||||
],
|
],
|
||||||
|
content: Component.Content(),
|
||||||
left: [
|
left: [
|
||||||
Component.TableOfContents(),
|
Component.TableOfContents(),
|
||||||
],
|
],
|
||||||
|
@ -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
|
||||||
|
41
quartz/components/scripts/popover.inline.ts
Normal file
41
quartz/components/scripts/popover.inline.ts
Normal 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"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
@ -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", () => {
|
||||||
|
43
quartz/components/styles/popover.scss
Normal file
43
quartz/components/styles/popover.scss
Normal 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;
|
||||||
|
}
|
@ -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">
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user