| | |
| | | import { FilePath, FullSlug } from "../../util/path" |
| | | import { FilePath, FullSlug, joinSegments } from "../../util/path" |
| | | import { QuartzEmitterPlugin } from "../types" |
| | | |
| | | // @ts-ignore |
| | |
| | | import { Features, transform } from "lightningcss" |
| | | import { transform as transpile } from "esbuild" |
| | | import { write } from "./helpers" |
| | | import DepGraph from "../../depgraph" |
| | | |
| | | type ComponentResources = { |
| | | css: string[] |
| | |
| | | } else if (cfg.analytics?.provider === "umami") { |
| | | componentResources.afterDOMLoaded.push(` |
| | | const umamiScript = document.createElement("script") |
| | | umamiScript.src = cfg.analytics.host ?? "https://analytics.umami.is/script.js" |
| | | umamiScript.src = "${cfg.analytics.host}" ?? "https://analytics.umami.is/script.js" |
| | | umamiScript.setAttribute("data-website-id", "${cfg.analytics.websiteId}") |
| | | umamiScript.async = true |
| | | |
| | |
| | | componentResources.afterDOMLoaded.push(spaRouterScript) |
| | | } else { |
| | | componentResources.afterDOMLoaded.push(` |
| | | window.spaNavigate = (url, _) => window.location.assign(url) |
| | | const event = new CustomEvent("nav", { detail: { url: document.body.dataset.slug } }) |
| | | document.dispatchEvent(event)`) |
| | | window.spaNavigate = (url, _) => window.location.assign(url) |
| | | window.addCleanup = () => {} |
| | | const event = new CustomEvent("nav", { detail: { url: document.body.dataset.slug } }) |
| | | document.dispatchEvent(event) |
| | | `) |
| | | } |
| | | |
| | | let wsUrl = `ws://localhost:${ctx.argv.wsPort}` |
| | |
| | | contentType: "inline", |
| | | script: ` |
| | | const socket = new WebSocket('${wsUrl}') |
| | | socket.addEventListener('message', () => document.location.reload()) |
| | | // reload(true) ensures resources like images and scripts are fetched again in firefox |
| | | socket.addEventListener('message', () => document.location.reload(true)) |
| | | `, |
| | | }) |
| | | } |
| | |
| | | getQuartzComponents() { |
| | | return [] |
| | | }, |
| | | async getDependencyGraph(ctx, content, _resources) { |
| | | // This emitter adds static resources to the `resources` parameter. One |
| | | // important resource this emitter adds is the code to start a websocket |
| | | // connection and listen to rebuild messages, which triggers a page reload. |
| | | // The resources parameter with the reload logic is later used by the |
| | | // ContentPage emitter while creating the final html page. In order for |
| | | // the reload logic to be included, and so for partial rebuilds to work, |
| | | // we need to run this emitter for all markdown files. |
| | | const graph = new DepGraph<FilePath>() |
| | | |
| | | for (const [_tree, file] of content) { |
| | | const sourcePath = file.data.filePath! |
| | | const slug = file.data.slug! |
| | | graph.addEdge(sourcePath, joinSegments(ctx.argv.output, slug + ".html") as FilePath) |
| | | } |
| | | |
| | | return graph |
| | | }, |
| | | async emit(ctx, _content, resources): Promise<FilePath[]> { |
| | | const promises: Promise<FilePath>[] = [] |
| | | const cfg = ctx.cfg.configuration |
| | | // component specific scripts and styles |
| | | const componentResources = getComponentResources(ctx) |
| | | let googleFontsStyleSheet = "" |
| | | if (fontOrigin === "local") { |
| | | // let the user do it themselves in css |
| | | } else if (fontOrigin === "googleFonts") { |
| | | if (cfg.theme.cdnCaching) { |
| | | resources.css.push(googleFontHref(cfg.theme)) |
| | | } else { |
| | | let match |
| | | |
| | | const fontSourceRegex = /url\((https:\/\/fonts.gstatic.com\/s\/[^)]+\.(woff2|ttf))\)/g |
| | | |
| | | googleFontsStyleSheet = await ( |
| | | await fetch(googleFontHref(ctx.cfg.configuration.theme)) |
| | | ).text() |
| | | |
| | | while ((match = fontSourceRegex.exec(googleFontsStyleSheet)) !== null) { |
| | | // match[0] is the `url(path)`, match[1] is the `path` |
| | | const url = match[1] |
| | | // the static name of this file. |
| | | const [filename, ext] = url.split("/").pop()!.split(".") |
| | | |
| | | googleFontsStyleSheet = googleFontsStyleSheet.replace( |
| | | url, |
| | | `/static/fonts/${filename}.ttf`, |
| | | ) |
| | | |
| | | promises.push( |
| | | fetch(url) |
| | | .then((res) => { |
| | | if (!res.ok) { |
| | | throw new Error(`Failed to fetch font`) |
| | | } |
| | | return res.arrayBuffer() |
| | | }) |
| | | .then((buf) => |
| | | write({ |
| | | ctx, |
| | | slug: joinSegments("static", "fonts", filename) as FullSlug, |
| | | ext: `.${ext}`, |
| | | content: Buffer.from(buf), |
| | | }), |
| | | ), |
| | | ) |
| | | } |
| | | } |
| | | } |
| | | |
| | | // important that this goes *after* component scripts |
| | | // as the "nav" event gets triggered here and we should make sure |
| | | // that everyone else had the chance to register a listener for it |
| | | |
| | | if (fontOrigin === "googleFonts") { |
| | | resources.css.push(googleFontHref(ctx.cfg.configuration.theme)) |
| | | } else if (fontOrigin === "local") { |
| | | // let the user do it themselves in css |
| | | } |
| | | |
| | | addGlobalPageResources(ctx, resources, componentResources) |
| | | |
| | | const stylesheet = joinStyles(ctx.cfg.configuration.theme, ...componentResources.css, styles) |
| | | const stylesheet = joinStyles( |
| | | ctx.cfg.configuration.theme, |
| | | googleFontsStyleSheet, |
| | | ...componentResources.css, |
| | | styles, |
| | | ) |
| | | const [prescript, postscript] = await Promise.all([ |
| | | joinScripts(componentResources.beforeDOMLoaded), |
| | | joinScripts(componentResources.afterDOMLoaded), |
| | | ]) |
| | | |
| | | const fps = await Promise.all([ |
| | | promises.push( |
| | | write({ |
| | | ctx, |
| | | slug: "index" as FullSlug, |
| | |
| | | ext: ".js", |
| | | content: postscript, |
| | | }), |
| | | ]) |
| | | return fps |
| | | ) |
| | | |
| | | return await Promise.all(promises) |
| | | }, |
| | | } |
| | | } |