popovers
This commit is contained in:
		
							parent
							
								
									cb89cce183
								
							
						
					
					
						commit
						8bfee04c8c
					
				
					 10 changed files with 143 additions and 16 deletions
				
			
		
							
								
								
									
										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"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function Content({ tree }: QuartzComponentProps) {
 | 
					// @ts-ignore
 | 
				
			||||||
  // @ts-ignore (preact makes it angry)
 | 
					import popoverScript from './scripts/popover.inline'
 | 
				
			||||||
  const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' })
 | 
					import popoverStyle from './styles/popover.scss'
 | 
				
			||||||
  return <article>{content}</article>
 | 
					
 | 
				
			||||||
 | 
					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() {
 | 
					function setupToc() {
 | 
				
			||||||
  const toc = document.getElementById("toc")!
 | 
					  const toc = document.getElementById("toc")
 | 
				
			||||||
  const content = toc.nextElementSibling as HTMLElement
 | 
					  if (toc) {
 | 
				
			||||||
  content.style.maxHeight = content.scrollHeight + "px"
 | 
					    const content = toc.nextElementSibling as HTMLElement
 | 
				
			||||||
  toc.removeEventListener("click", toggleToc)
 | 
					    content.style.maxHeight = content.scrollHeight + "px"
 | 
				
			||||||
  toc.addEventListener("click", toggleToc)
 | 
					    toc.removeEventListener("click", toggleToc)
 | 
				
			||||||
 | 
					    toc.addEventListener("click", toggleToc)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
window.addEventListener("resize", setupToc)
 | 
					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 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>
 | 
				
			||||||
              {beforeBody.map(BodyComponent => <BodyComponent {...componentData} />)}
 | 
					              <div class="popover-hint">
 | 
				
			||||||
 | 
					                {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…
	
	Add table
		Add a link
		
	
		Reference in a new issue