Canvas stub

This commit is contained in:
saberzero1 2025-03-01 21:34:18 +01:00
parent 1175c7af87
commit 2fb4b7474d
No known key found for this signature in database
3 changed files with 195 additions and 6 deletions

View File

@ -1,12 +1,19 @@
import { ComponentChildren } from "preact" import { ComponentChildren } from "preact"
import { htmlToJsx } from "../../util/jsx" import { htmlToJsx } from "../../util/jsx"
import d3 from "d3"
import { import {
QuartzComponent, QuartzComponent,
QuartzComponentConstructor, QuartzComponentConstructor,
QuartzComponentProps, QuartzComponentProps,
QuartzCanvasComponent, QuartzCanvasComponent,
CanvasNode,
CanvasEdge,
CanvasTextNode,
CanvasFileNode,
CanvasLinkNode,
CanvasGroupNode,
} from "../types" } from "../types"
import { type FilePath } from "../../util/path" import { type FilePath, slugifyFilePath } from "../../util/path"
import fs from "fs" import fs from "fs"
function loadCanvas(file: FilePath): QuartzCanvasComponent { function loadCanvas(file: FilePath): QuartzCanvasComponent {
@ -15,13 +22,84 @@ function loadCanvas(file: FilePath): QuartzCanvasComponent {
return JSON.parse(data) as QuartzCanvasComponent return JSON.parse(data) as QuartzCanvasComponent
} }
function renderTextNodes(nodes: CanvasTextNode[]): ComponentChildren {
return nodes.map((node) => (
<div
class="canvas canvas-node canvas-text-node"
style={{
position: "fixed",
width: node.width,
height: node.height,
left: node.x,
top: node.y,
}}
>
{node["text"]}
</div>
))
}
function renderFileNodes(nodes: CanvasFileNode[]): ComponentChildren {
return nodes.map((node) => (
<div
class="internal alias internal-canvas"
data-slug={slugifyFilePath(node.file as FilePath)}
data-x={node.x}
data-y={node.y}
style={{
position: "fixed",
width: node.width,
height: node.height,
left: node.x,
top: node.y,
}}
></div>
))
}
function renderLinkNodes(nodes: CanvasLinkNode[]): ComponentChildren {
return nodes.map((node) => (
<iframe
src={node["url"]}
style={{
position: "fixed",
width: node.width,
height: node.height,
left: node.x,
top: node.y,
}}
/>
))
}
const CanvasContent: QuartzComponent = ({ fileData, tree }: QuartzComponentProps) => { const CanvasContent: QuartzComponent = ({ fileData, tree }: QuartzComponentProps) => {
//const content = htmlToJsx(fileData.filePath!, tree) as ComponentChildren //const content = htmlToJsx(fileData.filePath!, tree) as ComponentChildren
const content = loadCanvas(fileData.filePath!) const canvas = loadCanvas(fileData.filePath!)
const canvasNodes = (canvas["nodes"] ?? []) as CanvasNode[]
const canvasEdges = (canvas["edges"] ?? []) as CanvasEdge[]
const classes: string[] = fileData.frontmatter?.cssclasses ?? [] const classes: string[] = fileData.frontmatter?.cssclasses ?? []
const classString = ["popover-hint", ...classes].join(" ") const classString = ["popover-hint", ...classes].join(" ")
// Canvas parts
const textNodes = (canvasNodes.filter((node) => node["type"] === "text") ??
[]) as CanvasTextNode[]
const fileNodes = (canvasNodes.filter((node) => node["type"] === "file") ??
[]) as CanvasFileNode[]
const linkNodes = (canvasNodes.filter((node) => node["type"] === "link") ??
[]) as CanvasLinkNode[]
const groupNodes = (canvasNodes.filter((node) => node["type"] === "group") ??
[]) as CanvasGroupNode[]
const result = (
<article class={classString}>
{renderTextNodes(textNodes)}
{renderFileNodes(fileNodes)}
{renderLinkNodes(linkNodes)}
</article>
)
//TODO: Implement canvas rendering //TODO: Implement canvas rendering
return <article class={classString}>{content["nodes"]}</article> return <article class={classString}>{result}</article>
} }
export default (() => CanvasContent) satisfies QuartzComponentConstructor export default (() => CanvasContent) satisfies QuartzComponentConstructor

View File

@ -91,7 +91,7 @@ export function renderCanvas(
console.log(root) console.log(root)
// process transcludes in componentData // process transcludes in componentData
/*visit(root, "element", (node, _index, _parent) => { visit(root, "element", (node, _index, _parent) => {
if (node.tagName === "blockquote") { if (node.tagName === "blockquote") {
const classNames = (node.properties?.className ?? []) as string[] const classNames = (node.properties?.className ?? []) as string[]
if (classNames.includes("transclude")) { if (classNames.includes("transclude")) {
@ -204,7 +204,7 @@ export function renderCanvas(
} }
} }
} }
})*/ })
// set componentData.tree to the edited html that has transclusions rendered // set componentData.tree to the edited html that has transclusions rendered
componentData.tree = root componentData.tree = root

View File

@ -100,10 +100,121 @@ async function mouseEnterHandler(
} }
} }
async function navigationHandler(this: HTMLAnchorElement) {
const link = this
if (link.dataset.noPopover === "true") {
return
}
const canvasX = Number(link.getAttribute("data-x") ?? "0")
const canvasY = Number(link.getAttribute("data-y") ?? "0")
async function setPosition(popoverElement: HTMLElement) {
const { x, y } = await computePosition(link, popoverElement, {
middleware: [inline({ x: canvasX, y: canvasY }), shift(), flip()],
})
Object.assign(popoverElement.style, {
left: `${x}px`,
top: `${y}px`,
width: "100%",
height: "100%",
})
}
const hasAlreadyBeenFetched = () =>
[...link.children].some((child) => child.classList.contains("popover"))
// dont refetch if there's already a popover
if (hasAlreadyBeenFetched()) {
return setPosition(link.lastChild as HTMLElement)
}
const thisUrl = new URL(document.location.href)
thisUrl.hash = ""
thisUrl.search = ""
const targetUrl = new URL(link.href)
const hash = decodeURIComponent(targetUrl.hash)
targetUrl.hash = ""
targetUrl.search = ""
const response = await fetchCanonical(targetUrl).catch((err) => {
console.error(err)
})
// bailout if another popover exists
if (hasAlreadyBeenFetched()) {
return
}
if (!response) return
const [contentType] = response.headers.get("Content-Type")!.split(";")
const [contentTypeCategory, typeInfo] = contentType.split("/")
const popoverElement = document.createElement("div")
popoverElement.classList.add("popover")
const popoverInner = document.createElement("div")
popoverInner.classList.add("popover-inner")
popoverElement.appendChild(popoverInner)
popoverInner.dataset.contentType = contentType ?? undefined
switch (contentTypeCategory) {
case "image":
const img = document.createElement("img")
img.src = targetUrl.toString()
img.alt = targetUrl.pathname
popoverInner.appendChild(img)
break
case "application":
switch (typeInfo) {
case "pdf":
const pdf = document.createElement("iframe")
pdf.src = targetUrl.toString()
popoverInner.appendChild(pdf)
break
default:
break
}
break
default:
const contents = await response.text()
const html = p.parseFromString(contents, "text/html")
normalizeRelativeURLs(html, targetUrl)
const elts = [...html.getElementsByClassName("popover-hint")]
if (elts.length === 0) return
elts.forEach((elt) => popoverInner.appendChild(elt))
}
setPosition(popoverElement)
link.appendChild(popoverElement)
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" })
}
}
}
document.addEventListener("nav", () => { document.addEventListener("nav", () => {
const links = [...document.getElementsByClassName("internal")] as HTMLAnchorElement[] const links = [...document.getElementsByClassName("internal")] as HTMLAnchorElement[]
for (const link of links) { console.log(links)
const pageLinks = links.filter((link) =>
link.classList.contains("internal-canvas"),
) as HTMLAnchorElement[]
const canvasLinks = links.filter(
(link) => !link.classList.contains("internal-canvas"),
) as HTMLAnchorElement[]
for (const link of pageLinks) {
link.addEventListener("mouseenter", mouseEnterHandler) link.addEventListener("mouseenter", mouseEnterHandler)
window.addCleanup(() => link.removeEventListener("mouseenter", mouseEnterHandler)) window.addCleanup(() => link.removeEventListener("mouseenter", mouseEnterHandler))
} }
//TODO: Fix canvas loading of popovers
for (const link of canvasLinks) {
link.addEventListener("load", navigationHandler)
window.addCleanup(() => link.removeEventListener("load", navigationHandler))
}
}) })