popovers
This commit is contained in:
parent
f078f20c08
commit
d5b51a8e29
14
package-lock.json
generated
14
package-lock.json
generated
@ -9,6 +9,7 @@
|
||||
"version": "4.0.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.4.0",
|
||||
"@inquirer/prompts": "^1.0.3",
|
||||
"@napi-rs/simple-git": "^0.1.8",
|
||||
"chalk": "^4.1.2",
|
||||
@ -393,6 +394,19 @@
|
||||
"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": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-1.2.8.tgz",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"quartz": "./quartz/bootstrap-cli.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.4.0",
|
||||
"@inquirer/prompts": "^1.0.3",
|
||||
"@napi-rs/simple-git": "^0.1.8",
|
||||
"chalk": "^4.1.2",
|
||||
|
@ -64,6 +64,7 @@ const config: QuartzConfig = {
|
||||
Component.ReadingTime(),
|
||||
Component.TagList(),
|
||||
],
|
||||
content: Component.Content(),
|
||||
left: [
|
||||
Component.TableOfContents(),
|
||||
],
|
||||
|
@ -2,10 +2,30 @@ import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||
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' })
|
||||
return <article>{content}</article>
|
||||
// @ts-ignore
|
||||
import popoverScript from './scripts/popover.inline'
|
||||
import popoverStyle from './styles/popover.scss'
|
||||
|
||||
interface Options {
|
||||
enablePopover: boolean
|
||||
}
|
||||
|
||||
export default (() => Content) satisfies QuartzComponentConstructor
|
||||
const defaultOptions: Options = {
|
||||
enablePopover: true
|
||||
}
|
||||
|
||||
export default ((opts?: Partial<Options>) => {
|
||||
function Content({ tree }: QuartzComponentProps) {
|
||||
// @ts-ignore (preact makes it angry)
|
||||
const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' })
|
||||
return <article>{content}</article>
|
||||
}
|
||||
|
||||
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,11 +22,13 @@ function toggleToc(this: HTMLElement) {
|
||||
}
|
||||
|
||||
function setupToc() {
|
||||
const toc = document.getElementById("toc")!
|
||||
const content = toc.nextElementSibling as HTMLElement
|
||||
content.style.maxHeight = content.scrollHeight + "px"
|
||||
toc.removeEventListener("click", toggleToc)
|
||||
toc.addEventListener("click", toggleToc)
|
||||
const toc = document.getElementById("toc")
|
||||
if (toc) {
|
||||
const content = toc.nextElementSibling as HTMLElement
|
||||
content.style.maxHeight = content.scrollHeight + "px"
|
||||
toc.removeEventListener("click", toggleToc)
|
||||
toc.addEventListener("click", toggleToc)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("resize", setupToc)
|
||||
|
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 { QuartzComponentProps } from "../../components/types"
|
||||
import BodyConstructor from "../../components/Body"
|
||||
import ContentConstructor from "../../components/Content"
|
||||
|
||||
interface Options {
|
||||
head: QuartzComponent
|
||||
header: QuartzComponent[],
|
||||
beforeBody: QuartzComponent[],
|
||||
content: QuartzComponent,
|
||||
left: QuartzComponent[],
|
||||
right: QuartzComponent[],
|
||||
footer: QuartzComponent[],
|
||||
@ -25,12 +25,11 @@ export const ContentPage: QuartzEmitterPlugin<Options> = (opts) => {
|
||||
const { head: Head, header, beforeBody, left, right, footer } = opts
|
||||
const Header = HeaderConstructor()
|
||||
const Body = BodyConstructor()
|
||||
const Content = ContentConstructor()
|
||||
|
||||
return {
|
||||
name: "ContentPage",
|
||||
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[]> {
|
||||
const fps: string[] = []
|
||||
@ -54,6 +53,7 @@ export const ContentPage: QuartzEmitterPlugin<Options> = (opts) => {
|
||||
tree
|
||||
}
|
||||
|
||||
const Content = opts.content
|
||||
const doc = <html>
|
||||
<Head {...componentData} />
|
||||
<body data-slug={file.data.slug}>
|
||||
@ -61,12 +61,14 @@ export const ContentPage: QuartzEmitterPlugin<Options> = (opts) => {
|
||||
<Header {...componentData} >
|
||||
{header.map(HeaderComponent => <HeaderComponent {...componentData} />)}
|
||||
</Header>
|
||||
{beforeBody.map(BodyComponent => <BodyComponent {...componentData} />)}
|
||||
<div class="popover-hint">
|
||||
{beforeBody.map(BodyComponent => <BodyComponent {...componentData} />)}
|
||||
</div>
|
||||
<Body {...componentData}>
|
||||
<div class="left">
|
||||
{left.map(BodyComponent => <BodyComponent {...componentData} />)}
|
||||
</div>
|
||||
<div class="center">
|
||||
<div class="center popover-hint">
|
||||
<Content {...componentData} />
|
||||
</div>
|
||||
<div class="right">
|
||||
|
@ -14,7 +14,8 @@ export type ComponentResources = {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -48,6 +48,8 @@ export const ResolveLinks: QuartzTransformerPlugin<Partial<Options> | undefined>
|
||||
// don't process external links or intra-document anchors
|
||||
if (!(isAbsoluteUrl(node.properties.href) || node.properties.href.startsWith("#"))) {
|
||||
node.properties.href = transformLink(node.properties.href)
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
// rewrite link internals if prettylinks is on
|
||||
|
Loading…
Reference in New Issue
Block a user