run prettier
This commit is contained in:
		@@ -7,7 +7,9 @@ function toggleCallout(this: HTMLElement) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function setupCallout() {
 | 
			
		||||
  const collapsible = document.getElementsByClassName(`callout is-collapsible`) as HTMLCollectionOf<HTMLElement>
 | 
			
		||||
  const collapsible = document.getElementsByClassName(
 | 
			
		||||
    `callout is-collapsible`,
 | 
			
		||||
  ) as HTMLCollectionOf<HTMLElement>
 | 
			
		||||
  for (const div of collapsible) {
 | 
			
		||||
    const title = div.firstElementChild
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,23 @@
 | 
			
		||||
const userPref = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'
 | 
			
		||||
const currentTheme = localStorage.getItem('theme') ?? userPref
 | 
			
		||||
document.documentElement.setAttribute('saved-theme', currentTheme)
 | 
			
		||||
const userPref = window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark"
 | 
			
		||||
const currentTheme = localStorage.getItem("theme") ?? userPref
 | 
			
		||||
document.documentElement.setAttribute("saved-theme", currentTheme)
 | 
			
		||||
 | 
			
		||||
document.addEventListener("nav", () => {
 | 
			
		||||
  const switchTheme = (e: any) => {
 | 
			
		||||
    if (e.target.checked) {
 | 
			
		||||
      document.documentElement.setAttribute('saved-theme', 'dark')
 | 
			
		||||
      localStorage.setItem('theme', 'dark')
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      document.documentElement.setAttribute('saved-theme', 'light')
 | 
			
		||||
      localStorage.setItem('theme', 'light')
 | 
			
		||||
      document.documentElement.setAttribute("saved-theme", "dark")
 | 
			
		||||
      localStorage.setItem("theme", "dark")
 | 
			
		||||
    } else {
 | 
			
		||||
      document.documentElement.setAttribute("saved-theme", "light")
 | 
			
		||||
      localStorage.setItem("theme", "light")
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Darkmode toggle
 | 
			
		||||
  const toggleSwitch = document.querySelector('#darkmode-toggle') as HTMLInputElement
 | 
			
		||||
  toggleSwitch.removeEventListener('change', switchTheme)
 | 
			
		||||
  toggleSwitch.addEventListener('change', switchTheme)
 | 
			
		||||
  if (currentTheme === 'dark') {
 | 
			
		||||
  const toggleSwitch = document.querySelector("#darkmode-toggle") as HTMLInputElement
 | 
			
		||||
  toggleSwitch.removeEventListener("change", switchTheme)
 | 
			
		||||
  toggleSwitch.addEventListener("change", switchTheme)
 | 
			
		||||
  if (currentTheme === "dark") {
 | 
			
		||||
    toggleSwitch.checked = true
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,16 @@
 | 
			
		||||
import { ContentDetails } from "../../plugins/emitters/contentIndex"
 | 
			
		||||
import * as d3 from 'd3'
 | 
			
		||||
import * as d3 from "d3"
 | 
			
		||||
import { registerEscapeHandler, removeAllChildren } from "./util"
 | 
			
		||||
import { CanonicalSlug, getCanonicalSlug, getClientSlug, resolveRelative } from "../../path"
 | 
			
		||||
 | 
			
		||||
type NodeData = {
 | 
			
		||||
  id: CanonicalSlug,
 | 
			
		||||
  text: string,
 | 
			
		||||
  id: CanonicalSlug
 | 
			
		||||
  text: string
 | 
			
		||||
  tags: string[]
 | 
			
		||||
} & d3.SimulationNodeDatum
 | 
			
		||||
 | 
			
		||||
type LinkData = {
 | 
			
		||||
  source: CanonicalSlug,
 | 
			
		||||
  source: CanonicalSlug
 | 
			
		||||
  target: CanonicalSlug
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -40,7 +40,7 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
 | 
			
		||||
    centerForce,
 | 
			
		||||
    linkDistance,
 | 
			
		||||
    fontSize,
 | 
			
		||||
    opacityScale
 | 
			
		||||
    opacityScale,
 | 
			
		||||
  } = JSON.parse(graph.dataset["cfg"]!)
 | 
			
		||||
 | 
			
		||||
  const data = await fetchData
 | 
			
		||||
@@ -66,18 +66,22 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
 | 
			
		||||
        wl.push("__SENTINEL")
 | 
			
		||||
      } else {
 | 
			
		||||
        neighbourhood.add(cur)
 | 
			
		||||
        const outgoing = links.filter(l => l.source === cur)
 | 
			
		||||
        const incoming = links.filter(l => l.target === cur)
 | 
			
		||||
        const outgoing = links.filter((l) => l.source === cur)
 | 
			
		||||
        const incoming = links.filter((l) => l.target === cur)
 | 
			
		||||
        wl.push(...outgoing.map((l) => l.target), ...incoming.map((l) => l.source))
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    Object.keys(data).forEach(id => neighbourhood.add(id as CanonicalSlug))
 | 
			
		||||
    Object.keys(data).forEach((id) => neighbourhood.add(id as CanonicalSlug))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const graphData: { nodes: NodeData[], links: LinkData[] } = {
 | 
			
		||||
    nodes: [...neighbourhood].map(url => ({ id: url, text: data[url]?.title ?? url, tags: data[url]?.tags ?? [] })),
 | 
			
		||||
    links: links.filter((l) => neighbourhood.has(l.source) && neighbourhood.has(l.target))
 | 
			
		||||
  const graphData: { nodes: NodeData[]; links: LinkData[] } = {
 | 
			
		||||
    nodes: [...neighbourhood].map((url) => ({
 | 
			
		||||
      id: url,
 | 
			
		||||
      text: data[url]?.title ?? url,
 | 
			
		||||
      tags: data[url]?.tags ?? [],
 | 
			
		||||
    })),
 | 
			
		||||
    links: links.filter((l) => neighbourhood.has(l.source) && neighbourhood.has(l.target)),
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const simulation: d3.Simulation<NodeData, LinkData> = d3
 | 
			
		||||
@@ -96,11 +100,11 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
 | 
			
		||||
  const width = graph.offsetWidth
 | 
			
		||||
 | 
			
		||||
  const svg = d3
 | 
			
		||||
    .select<HTMLElement, NodeData>('#' + container)
 | 
			
		||||
    .select<HTMLElement, NodeData>("#" + container)
 | 
			
		||||
    .append("svg")
 | 
			
		||||
    .attr("width", width)
 | 
			
		||||
    .attr("height", height)
 | 
			
		||||
    .attr('viewBox', [-width / 2 / scale, -height / 2 / scale, width / scale, height / scale])
 | 
			
		||||
    .attr("viewBox", [-width / 2 / scale, -height / 2 / scale, width / scale, height / scale])
 | 
			
		||||
 | 
			
		||||
  // draw links between nodes
 | 
			
		||||
  const link = svg
 | 
			
		||||
@@ -145,7 +149,7 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
 | 
			
		||||
      d.fy = null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const noop = () => { }
 | 
			
		||||
    const noop = () => {}
 | 
			
		||||
    return d3
 | 
			
		||||
      .drag<Element, NodeData>()
 | 
			
		||||
      .on("start", enableDrag ? dragstarted : noop)
 | 
			
		||||
@@ -170,9 +174,11 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
 | 
			
		||||
      const targ = resolveRelative(slug, d.id)
 | 
			
		||||
      window.spaNavigate(new URL(targ, getClientSlug(window)))
 | 
			
		||||
    })
 | 
			
		||||
    .on("mouseover", function(_, d) {
 | 
			
		||||
    .on("mouseover", function (_, d) {
 | 
			
		||||
      const neighbours: CanonicalSlug[] = data[slug].links ?? []
 | 
			
		||||
      const neighbourNodes = d3.selectAll<HTMLElement, NodeData>(".node").filter((d) => neighbours.includes(d.id))
 | 
			
		||||
      const neighbourNodes = d3
 | 
			
		||||
        .selectAll<HTMLElement, NodeData>(".node")
 | 
			
		||||
        .filter((d) => neighbours.includes(d.id))
 | 
			
		||||
      console.log(neighbourNodes)
 | 
			
		||||
      const currentId = d.id
 | 
			
		||||
      const linkNodes = d3
 | 
			
		||||
@@ -183,12 +189,7 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
 | 
			
		||||
      neighbourNodes.transition().duration(200).attr("fill", color)
 | 
			
		||||
 | 
			
		||||
      // highlight links
 | 
			
		||||
      linkNodes
 | 
			
		||||
        .transition()
 | 
			
		||||
        .duration(200)
 | 
			
		||||
        .attr("stroke", "var(--gray)")
 | 
			
		||||
        .attr("stroke-width", 1)
 | 
			
		||||
 | 
			
		||||
      linkNodes.transition().duration(200).attr("stroke", "var(--gray)").attr("stroke-width", 1)
 | 
			
		||||
 | 
			
		||||
      const bigFont = fontSize * 1.5
 | 
			
		||||
 | 
			
		||||
@@ -199,11 +200,11 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
 | 
			
		||||
        .select("text")
 | 
			
		||||
        .transition()
 | 
			
		||||
        .duration(200)
 | 
			
		||||
        .attr('opacityOld', d3.select(parent).select('text').style("opacity"))
 | 
			
		||||
        .style('opacity', 1)
 | 
			
		||||
        .style('font-size', bigFont + 'em')
 | 
			
		||||
        .attr("opacityOld", d3.select(parent).select("text").style("opacity"))
 | 
			
		||||
        .style("opacity", 1)
 | 
			
		||||
        .style("font-size", bigFont + "em")
 | 
			
		||||
    })
 | 
			
		||||
    .on("mouseleave", function(_, d) {
 | 
			
		||||
    .on("mouseleave", function (_, d) {
 | 
			
		||||
      const currentId = d.id
 | 
			
		||||
      const linkNodes = d3
 | 
			
		||||
        .selectAll(".link")
 | 
			
		||||
@@ -216,8 +217,8 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
 | 
			
		||||
        .select("text")
 | 
			
		||||
        .transition()
 | 
			
		||||
        .duration(200)
 | 
			
		||||
        .style('opacity', d3.select(parent).select('text').attr("opacityOld"))
 | 
			
		||||
        .style('font-size', fontSize + 'em')
 | 
			
		||||
        .style("opacity", d3.select(parent).select("text").attr("opacityOld"))
 | 
			
		||||
        .style("font-size", fontSize + "em")
 | 
			
		||||
    })
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    .call(drag(simulation))
 | 
			
		||||
@@ -228,10 +229,12 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
 | 
			
		||||
    .attr("dx", 0)
 | 
			
		||||
    .attr("dy", (d) => -nodeRadius(d) + "px")
 | 
			
		||||
    .attr("text-anchor", "middle")
 | 
			
		||||
    .text((d) => data[d.id]?.title || (d.id.charAt(1).toUpperCase() + d.id.slice(2)).replace("-", " "))
 | 
			
		||||
    .style('opacity', (opacityScale - 1) / 3.75)
 | 
			
		||||
    .text(
 | 
			
		||||
      (d) => data[d.id]?.title || (d.id.charAt(1).toUpperCase() + d.id.slice(2)).replace("-", " "),
 | 
			
		||||
    )
 | 
			
		||||
    .style("opacity", (opacityScale - 1) / 3.75)
 | 
			
		||||
    .style("pointer-events", "none")
 | 
			
		||||
    .style('font-size', fontSize + 'em')
 | 
			
		||||
    .style("font-size", fontSize + "em")
 | 
			
		||||
    .raise()
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    .call(drag(simulation))
 | 
			
		||||
@@ -249,7 +252,7 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
 | 
			
		||||
        .on("zoom", ({ transform }) => {
 | 
			
		||||
          link.attr("transform", transform)
 | 
			
		||||
          node.attr("transform", transform)
 | 
			
		||||
          const scale = transform.k * opacityScale;
 | 
			
		||||
          const scale = transform.k * opacityScale
 | 
			
		||||
          const scaledOpacity = Math.max((scale - 1) / 3.75, 0)
 | 
			
		||||
          labels.attr("transform", transform).style("opacity", scaledOpacity)
 | 
			
		||||
        }),
 | 
			
		||||
@@ -263,17 +266,13 @@ async function renderGraph(container: string, slug: CanonicalSlug) {
 | 
			
		||||
      .attr("y1", (d: any) => d.source.y)
 | 
			
		||||
      .attr("x2", (d: any) => d.target.x)
 | 
			
		||||
      .attr("y2", (d: any) => d.target.y)
 | 
			
		||||
    node
 | 
			
		||||
      .attr("cx", (d: any) => d.x)
 | 
			
		||||
      .attr("cy", (d: any) => d.y)
 | 
			
		||||
    labels
 | 
			
		||||
      .attr("x", (d: any) => d.x)
 | 
			
		||||
      .attr("y", (d: any) => d.y)
 | 
			
		||||
    node.attr("cx", (d: any) => d.x).attr("cy", (d: any) => d.y)
 | 
			
		||||
    labels.attr("x", (d: any) => d.x).attr("y", (d: any) => d.y)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function renderGlobalGraph() {
 | 
			
		||||
  const slug = getCanonicalSlug(window) 
 | 
			
		||||
  const slug = getCanonicalSlug(window)
 | 
			
		||||
  const container = document.getElementById("global-graph-outer")
 | 
			
		||||
  const sidebar = container?.closest(".sidebar") as HTMLElement
 | 
			
		||||
  container?.classList.add("active")
 | 
			
		||||
@@ -305,4 +304,3 @@ document.addEventListener("nav", async (e: unknown) => {
 | 
			
		||||
  containerIcon?.removeEventListener("click", renderGlobalGraph)
 | 
			
		||||
  containerIcon?.addEventListener("click", renderGlobalGraph)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
import Plausible from 'plausible-tracker'
 | 
			
		||||
import Plausible from "plausible-tracker"
 | 
			
		||||
const { trackPageview } = Plausible()
 | 
			
		||||
document.addEventListener("nav", () => trackPageview())
 | 
			
		||||
 
 | 
			
		||||
@@ -2,33 +2,25 @@ import { computePosition, flip, inline, shift } from "@floating-ui/dom"
 | 
			
		||||
 | 
			
		||||
// from micromorph/src/utils.ts
 | 
			
		||||
// https://github.com/natemoo-re/micromorph/blob/main/src/utils.ts#L5
 | 
			
		||||
export function normalizeRelativeURLs(
 | 
			
		||||
  el: Element | Document,
 | 
			
		||||
  base: string | URL
 | 
			
		||||
) {
 | 
			
		||||
export function normalizeRelativeURLs(el: Element | Document, base: string | URL) {
 | 
			
		||||
  const update = (el: Element, attr: string, base: string | URL) => {
 | 
			
		||||
    el.setAttribute(attr, new URL(el.getAttribute(attr)!, base).pathname)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  el.querySelectorAll('[href^="./"], [href^="../"]').forEach((item) =>
 | 
			
		||||
    update(item, 'href', base)
 | 
			
		||||
  )
 | 
			
		||||
  el.querySelectorAll('[href^="./"], [href^="../"]').forEach((item) => update(item, "href", base))
 | 
			
		||||
 | 
			
		||||
  el.querySelectorAll('[src^="./"], [src^="../"]').forEach((item) =>
 | 
			
		||||
    update(item, 'src', base)
 | 
			
		||||
  )
 | 
			
		||||
  el.querySelectorAll('[src^="./"], [src^="../"]').forEach((item) => update(item, "src", base))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const p = new DOMParser()
 | 
			
		||||
async function mouseEnterHandler(this: HTMLLinkElement, { clientX, clientY }: { clientX: number, clientY: number }) {
 | 
			
		||||
async function mouseEnterHandler(
 | 
			
		||||
  this: HTMLLinkElement,
 | 
			
		||||
  { clientX, clientY }: { clientX: number; clientY: number },
 | 
			
		||||
) {
 | 
			
		||||
  const link = this
 | 
			
		||||
  async function setPosition(popoverElement: HTMLElement) {
 | 
			
		||||
    const { x, y } = await computePosition(link, popoverElement, {
 | 
			
		||||
      middleware: [
 | 
			
		||||
        inline({ x: clientX, y: clientY }),
 | 
			
		||||
        shift(),
 | 
			
		||||
        flip()
 | 
			
		||||
      ]
 | 
			
		||||
      middleware: [inline({ x: clientX, y: clientY }), shift(), flip()],
 | 
			
		||||
    })
 | 
			
		||||
    Object.assign(popoverElement.style, {
 | 
			
		||||
      left: `${x}px`,
 | 
			
		||||
@@ -37,7 +29,7 @@ async function mouseEnterHandler(this: HTMLLinkElement, { clientX, clientY }: {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // dont refetch if there's already a popover
 | 
			
		||||
  if ([...link.children].some(child => child.classList.contains("popover"))) {
 | 
			
		||||
  if ([...link.children].some((child) => child.classList.contains("popover"))) {
 | 
			
		||||
    return setPosition(link.lastChild as HTMLElement)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -68,7 +60,7 @@ async function mouseEnterHandler(this: HTMLLinkElement, { clientX, clientY }: {
 | 
			
		||||
  const popoverInner = document.createElement("div")
 | 
			
		||||
  popoverInner.classList.add("popover-inner")
 | 
			
		||||
  popoverElement.appendChild(popoverInner)
 | 
			
		||||
  elts.forEach(elt => popoverInner.appendChild(elt))
 | 
			
		||||
  elts.forEach((elt) => popoverInner.appendChild(elt))
 | 
			
		||||
 | 
			
		||||
  setPosition(popoverElement)
 | 
			
		||||
  link.appendChild(popoverElement)
 | 
			
		||||
@@ -77,7 +69,7 @@ async function mouseEnterHandler(this: HTMLLinkElement, { clientX, clientY }: {
 | 
			
		||||
    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' })
 | 
			
		||||
      popoverInner.scroll({ top: heading.offsetTop - 12, behavior: "instant" })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,9 @@ import { registerEscapeHandler, removeAllChildren } from "./util"
 | 
			
		||||
import { CanonicalSlug, getClientSlug, resolveRelative } from "../../path"
 | 
			
		||||
 | 
			
		||||
interface Item {
 | 
			
		||||
  slug: CanonicalSlug,
 | 
			
		||||
  title: string,
 | 
			
		||||
  content: string,
 | 
			
		||||
  slug: CanonicalSlug
 | 
			
		||||
  title: string
 | 
			
		||||
  content: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let index: Document<Item> | undefined = undefined
 | 
			
		||||
@@ -15,15 +15,17 @@ const contextWindowWords = 30
 | 
			
		||||
const numSearchResults = 5
 | 
			
		||||
function highlight(searchTerm: string, text: string, trim?: boolean) {
 | 
			
		||||
  // try to highlight longest tokens first
 | 
			
		||||
  const tokenizedTerms = searchTerm.split(/\s+/).filter(t => t !== "").sort((a, b) => b.length - a.length)
 | 
			
		||||
  let tokenizedText = text
 | 
			
		||||
  const tokenizedTerms = searchTerm
 | 
			
		||||
    .split(/\s+/)
 | 
			
		||||
    .filter(t => t !== "")
 | 
			
		||||
    .filter((t) => t !== "")
 | 
			
		||||
    .sort((a, b) => b.length - a.length)
 | 
			
		||||
  let tokenizedText = text.split(/\s+/).filter((t) => t !== "")
 | 
			
		||||
 | 
			
		||||
  let startIndex = 0
 | 
			
		||||
  let endIndex = tokenizedText.length - 1
 | 
			
		||||
  if (trim) {
 | 
			
		||||
    const includesCheck = (tok: string) => tokenizedTerms.some((term) => tok.toLowerCase().startsWith(term.toLowerCase()))
 | 
			
		||||
    const includesCheck = (tok: string) =>
 | 
			
		||||
      tokenizedTerms.some((term) => tok.toLowerCase().startsWith(term.toLowerCase()))
 | 
			
		||||
    const occurencesIndices = tokenizedText.map(includesCheck)
 | 
			
		||||
 | 
			
		||||
    let bestSum = 0
 | 
			
		||||
@@ -42,19 +44,22 @@ function highlight(searchTerm: string, text: string, trim?: boolean) {
 | 
			
		||||
    tokenizedText = tokenizedText.slice(startIndex, endIndex)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const slice = tokenizedText.map(tok => {
 | 
			
		||||
    // see if this tok is prefixed by any search terms 
 | 
			
		||||
    for (const searchTok of tokenizedTerms) {
 | 
			
		||||
      if (tok.toLowerCase().includes(searchTok.toLowerCase())) {
 | 
			
		||||
        const regex = new RegExp(searchTok.toLowerCase(), "gi")
 | 
			
		||||
        return tok.replace(regex, `<span class="highlight">$&</span>`)
 | 
			
		||||
  const slice = tokenizedText
 | 
			
		||||
    .map((tok) => {
 | 
			
		||||
      // see if this tok is prefixed by any search terms
 | 
			
		||||
      for (const searchTok of tokenizedTerms) {
 | 
			
		||||
        if (tok.toLowerCase().includes(searchTok.toLowerCase())) {
 | 
			
		||||
          const regex = new RegExp(searchTok.toLowerCase(), "gi")
 | 
			
		||||
          return tok.replace(regex, `<span class="highlight">$&</span>`)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return tok
 | 
			
		||||
  })
 | 
			
		||||
      return tok
 | 
			
		||||
    })
 | 
			
		||||
    .join(" ")
 | 
			
		||||
 | 
			
		||||
  return `${startIndex === 0 ? "" : "..."}${slice}${endIndex === tokenizedText.length - 1 ? "" : "..."}`
 | 
			
		||||
  return `${startIndex === 0 ? "" : "..."}${slice}${
 | 
			
		||||
    endIndex === tokenizedText.length - 1 ? "" : "..."
 | 
			
		||||
  }`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const encoder = (str: string) => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])/)
 | 
			
		||||
@@ -113,7 +118,7 @@ document.addEventListener("nav", async (e: unknown) => {
 | 
			
		||||
    button.classList.add("result-card")
 | 
			
		||||
    button.id = slug
 | 
			
		||||
    button.innerHTML = `<h3>${title}</h3><p>${content}</p>`
 | 
			
		||||
    button.addEventListener('click', () => {
 | 
			
		||||
    button.addEventListener("click", () => {
 | 
			
		||||
      const targ = resolveRelative(currentSlug, slug)
 | 
			
		||||
      window.spaNavigate(new URL(targ, getClientSlug(window)))
 | 
			
		||||
    })
 | 
			
		||||
@@ -132,7 +137,6 @@ document.addEventListener("nav", async (e: unknown) => {
 | 
			
		||||
    } else {
 | 
			
		||||
      results.append(...finalResults.map(resultToHTML))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function onType(e: HTMLElementEventMap["input"]) {
 | 
			
		||||
@@ -140,12 +144,12 @@ document.addEventListener("nav", async (e: unknown) => {
 | 
			
		||||
    const searchResults = index?.search(term, numSearchResults) ?? []
 | 
			
		||||
    const getByField = (field: string): CanonicalSlug[] => {
 | 
			
		||||
      const results = searchResults.filter((x) => x.field === field)
 | 
			
		||||
      return results.length === 0 ? [] : [...results[0].result] as CanonicalSlug[]
 | 
			
		||||
      return results.length === 0 ? [] : ([...results[0].result] as CanonicalSlug[])
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // order titles ahead of content
 | 
			
		||||
    const allIds: Set<CanonicalSlug> = new Set([...getByField("title"), ...getByField("content")])
 | 
			
		||||
    const finalResults = [...allIds].map(id => formatForDisplay(term, id))
 | 
			
		||||
    const finalResults = [...allIds].map((id) => formatForDisplay(term, id))
 | 
			
		||||
    displayResults(finalResults)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -160,7 +164,7 @@ document.addEventListener("nav", async (e: unknown) => {
 | 
			
		||||
  if (!index) {
 | 
			
		||||
    index = new Document({
 | 
			
		||||
      cache: true,
 | 
			
		||||
      charset: 'latin:extra',
 | 
			
		||||
      charset: "latin:extra",
 | 
			
		||||
      optimize: true,
 | 
			
		||||
      encode: encoder,
 | 
			
		||||
      document: {
 | 
			
		||||
@@ -174,7 +178,7 @@ document.addEventListener("nav", async (e: unknown) => {
 | 
			
		||||
            field: "content",
 | 
			
		||||
            tokenize: "reverse",
 | 
			
		||||
          },
 | 
			
		||||
        ]
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
@@ -182,7 +186,7 @@ document.addEventListener("nav", async (e: unknown) => {
 | 
			
		||||
      await index.addAsync(slug, {
 | 
			
		||||
        slug: slug as CanonicalSlug,
 | 
			
		||||
        title: fileData.title,
 | 
			
		||||
        content: fileData.content
 | 
			
		||||
        content: fileData.content,
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,9 @@ import { CanonicalSlug, RelativeURL, getCanonicalSlug } from "../../path"
 | 
			
		||||
// https://github.com/natemoo-re/micromorph
 | 
			
		||||
 | 
			
		||||
const NODE_TYPE_ELEMENT = 1
 | 
			
		||||
let announcer = document.createElement('route-announcer')
 | 
			
		||||
const isElement = (target: EventTarget | null): target is Element => (target as Node)?.nodeType === NODE_TYPE_ELEMENT
 | 
			
		||||
let announcer = document.createElement("route-announcer")
 | 
			
		||||
const isElement = (target: EventTarget | null): target is Element =>
 | 
			
		||||
  (target as Node)?.nodeType === NODE_TYPE_ELEMENT
 | 
			
		||||
const isLocalUrl = (href: string) => {
 | 
			
		||||
  try {
 | 
			
		||||
    const url = new URL(href)
 | 
			
		||||
@@ -16,18 +17,18 @@ const isLocalUrl = (href: string) => {
 | 
			
		||||
      }
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
  } catch (e) { }
 | 
			
		||||
  } catch (e) {}
 | 
			
		||||
  return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getOpts = ({ target }: Event): { url: URL, scroll?: boolean } | undefined => {
 | 
			
		||||
const getOpts = ({ target }: Event): { url: URL; scroll?: boolean } | undefined => {
 | 
			
		||||
  if (!isElement(target)) return
 | 
			
		||||
  const a = target.closest("a")
 | 
			
		||||
  if (!a) return
 | 
			
		||||
  if ('routerIgnore' in a.dataset) return
 | 
			
		||||
  if ("routerIgnore" in a.dataset) return
 | 
			
		||||
  const { href } = a
 | 
			
		||||
  if (!isLocalUrl(href)) return
 | 
			
		||||
  return { url: new URL(href), scroll: 'routerNoscroll' in a.dataset ? false : undefined }
 | 
			
		||||
  return { url: new URL(href), scroll: "routerNoscroll" in a.dataset ? false : undefined }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function notifyNav(url: CanonicalSlug) {
 | 
			
		||||
@@ -44,7 +45,7 @@ async function navigate(url: URL, isBack: boolean = false) {
 | 
			
		||||
      window.location.assign(url)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
  if (!contents) return;
 | 
			
		||||
  if (!contents) return
 | 
			
		||||
  if (!isBack) {
 | 
			
		||||
    history.pushState({}, "", url)
 | 
			
		||||
    window.scrollTo({ top: 0 })
 | 
			
		||||
@@ -54,22 +55,22 @@ async function navigate(url: URL, isBack: boolean = false) {
 | 
			
		||||
  if (title) {
 | 
			
		||||
    document.title = title
 | 
			
		||||
  } else {
 | 
			
		||||
    const h1 = document.querySelector('h1')
 | 
			
		||||
    const h1 = document.querySelector("h1")
 | 
			
		||||
    title = h1?.innerText ?? h1?.textContent ?? url.pathname
 | 
			
		||||
  }
 | 
			
		||||
  if (announcer.textContent !== title) {
 | 
			
		||||
    announcer.textContent = title
 | 
			
		||||
  }
 | 
			
		||||
  announcer.dataset.persist = ''
 | 
			
		||||
  announcer.dataset.persist = ""
 | 
			
		||||
  html.body.appendChild(announcer)
 | 
			
		||||
 | 
			
		||||
  micromorph(document.body, html.body)
 | 
			
		||||
 | 
			
		||||
  // now, patch head 
 | 
			
		||||
  const elementsToRemove = document.head.querySelectorAll(':not([spa-preserve])')
 | 
			
		||||
  elementsToRemove.forEach(el => el.remove())
 | 
			
		||||
  const elementsToAdd = html.head.querySelectorAll(':not([spa-preserve])')
 | 
			
		||||
  elementsToAdd.forEach(el => document.head.appendChild(el))
 | 
			
		||||
  // now, patch head
 | 
			
		||||
  const elementsToRemove = document.head.querySelectorAll(":not([spa-preserve])")
 | 
			
		||||
  elementsToRemove.forEach((el) => el.remove())
 | 
			
		||||
  const elementsToAdd = html.head.querySelectorAll(":not([spa-preserve])")
 | 
			
		||||
  elementsToAdd.forEach((el) => document.head.appendChild(el))
 | 
			
		||||
 | 
			
		||||
  notifyNav(getCanonicalSlug(window))
 | 
			
		||||
  delete announcer.dataset.persist
 | 
			
		||||
@@ -101,7 +102,7 @@ function createRouter() {
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return new class Router {
 | 
			
		||||
  return new (class Router {
 | 
			
		||||
    go(pathname: RelativeURL) {
 | 
			
		||||
      const url = new URL(pathname, window.location.toString())
 | 
			
		||||
      return navigate(url, false)
 | 
			
		||||
@@ -114,26 +115,30 @@ function createRouter() {
 | 
			
		||||
    forward() {
 | 
			
		||||
      return window.history.forward()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  })()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
createRouter()
 | 
			
		||||
notifyNav(getCanonicalSlug(window))
 | 
			
		||||
 | 
			
		||||
if (!customElements.get('route-announcer')) {
 | 
			
		||||
if (!customElements.get("route-announcer")) {
 | 
			
		||||
  const attrs = {
 | 
			
		||||
    'aria-live': 'assertive',
 | 
			
		||||
    'aria-atomic': 'true',
 | 
			
		||||
    'style': 'position: absolute; left: 0; top: 0; clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap; width: 1px; height: 1px'
 | 
			
		||||
    "aria-live": "assertive",
 | 
			
		||||
    "aria-atomic": "true",
 | 
			
		||||
    style:
 | 
			
		||||
      "position: absolute; left: 0; top: 0; clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap; width: 1px; height: 1px",
 | 
			
		||||
  }
 | 
			
		||||
  customElements.define('route-announcer', class RouteAnnouncer extends HTMLElement {
 | 
			
		||||
    constructor() {
 | 
			
		||||
      super()
 | 
			
		||||
    }
 | 
			
		||||
    connectedCallback() {
 | 
			
		||||
      for (const [key, value] of Object.entries(attrs)) {
 | 
			
		||||
        this.setAttribute(key, value)
 | 
			
		||||
  customElements.define(
 | 
			
		||||
    "route-announcer",
 | 
			
		||||
    class RouteAnnouncer extends HTMLElement {
 | 
			
		||||
      constructor() {
 | 
			
		||||
        super()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
      connectedCallback() {
 | 
			
		||||
        for (const [key, value] of Object.entries(attrs)) {
 | 
			
		||||
          this.setAttribute(key, value)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
const bufferPx = 150
 | 
			
		||||
const observer = new IntersectionObserver(entries => {
 | 
			
		||||
const observer = new IntersectionObserver((entries) => {
 | 
			
		||||
  for (const entry of entries) {
 | 
			
		||||
    const slug = entry.target.id
 | 
			
		||||
    const tocEntryElement = document.querySelector(`a[data-for="${slug}"]`)
 | 
			
		||||
@@ -38,5 +38,5 @@ document.addEventListener("nav", () => {
 | 
			
		||||
  // update toc entry highlighting
 | 
			
		||||
  observer.disconnect()
 | 
			
		||||
  const headers = document.querySelectorAll("h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]")
 | 
			
		||||
  headers.forEach(header => observer.observe(header))
 | 
			
		||||
  headers.forEach((header) => observer.observe(header))
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ export function registerEscapeHandler(outsideContainer: HTMLElement | null, cb:
 | 
			
		||||
  outsideContainer?.removeEventListener("click", click)
 | 
			
		||||
  outsideContainer?.addEventListener("click", click)
 | 
			
		||||
  document.removeEventListener("keydown", esc)
 | 
			
		||||
  document.addEventListener('keydown', esc)
 | 
			
		||||
  document.addEventListener("keydown", esc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function removeAllChildren(node: HTMLElement) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user