diff --git a/docs/.vitepress/ext/vite.ts b/docs/.vitepress/ext/vite.ts index b86a41388..1c36d6201 100644 --- a/docs/.vitepress/ext/vite.ts +++ b/docs/.vitepress/ext/vite.ts @@ -1,13 +1,11 @@ import { fileURLToPath } from "url"; import { UserConfig, Alias } from "vite"; -import imgit from "../imgit/plugin/vite"; -import svg from "../imgit/plugin/svg"; -import youtube from "../imgit/plugin/youtube"; +import imgit from "imgit/vite"; +import svg from "imgit/svg"; +import youtube from "imgit/youtube"; export const vite: UserConfig = { plugins: [imgit({ - enforce: "pre", - skip: (_, id) => !id.endsWith(".md"), width: 720, root: "./docs/public", cache: { root: "./docs/public/imgit" }, @@ -15,12 +13,7 @@ export const vite: UserConfig = { encode: { root: "./docs/public/imgit/encoded" }, plugins: [svg(), youtube()] })], - resolve: { - alias: [ - override("NotFound", "not-found"), - { find: /\.(:?tif|tiff|bmp|tga|psd|avi|mkv|mov)$/, replacement: ".webp" } - ] - } + resolve: { alias: [override("NotFound", "not-found")] } }; // https://vitepress.dev/guide/extending-default-theme#overriding-internal-components diff --git a/docs/.vitepress/imgit/client/index.ts b/docs/.vitepress/imgit/client/index.ts deleted file mode 100644 index 66bf38544..000000000 --- a/docs/.vitepress/imgit/client/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { observeMutations } from "./mutation"; -import "./styles.css"; - -observeMutations(); diff --git a/docs/.vitepress/imgit/client/intersection.ts b/docs/.vitepress/imgit/client/intersection.ts deleted file mode 100644 index 9e1223810..000000000 --- a/docs/.vitepress/imgit/client/intersection.ts +++ /dev/null @@ -1,45 +0,0 @@ -const observer = canObserve() ? new IntersectionObserver(handleIntersections) : undefined; -const edge = observer && isEdge(); - -export function observeVideo(video: HTMLVideoElement) { - observer?.observe(video); -} - -export function unobserveVideo(video: HTMLVideoElement) { - observer?.unobserve(video); -} - -function canObserve() { - return typeof document === "object" && "IntersectionObserver" in window; -} - -function handleIntersections(entries: IntersectionObserverEntry[], observer: IntersectionObserver) { - for (const entry of entries) - if (entry.isIntersecting) - handleIntersection(entry, observer); -} - -function handleIntersection(entry: IntersectionObserverEntry, observer: IntersectionObserver) { - for (const child of entry.target.children) - if (isSource(child) && child.dataset.imgitSrc && !av1OnEdge(child)) - child.src = child.dataset.imgitSrc; - (entry.target).load(); - observer.unobserve(entry.target); -} - - -function isSource(element: Element): element is HTMLSourceElement { - return "src" in element; -} - -function av1OnEdge(source: HTMLSourceElement) { - // Edge has bug where it thinks it can play av01, while actually can't. - // https://stackoverflow.com/questions/64212993 TODO: Find if there is a tracking issue for this. - return edge && source.type.includes("codecs=av01"); -} - -function isEdge() { - // https://learn.microsoft.com/en-us/microsoft-edge/web-platform/user-agent-guidance - const agent = window.navigator.userAgent; - return agent.includes("Edg/") || agent.includes("EdgiOS/") || agent.includes("EdgA/"); -} diff --git a/docs/.vitepress/imgit/client/mutation.ts b/docs/.vitepress/imgit/client/mutation.ts deleted file mode 100644 index dd04d3236..000000000 --- a/docs/.vitepress/imgit/client/mutation.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { observeVideo, unobserveVideo } from "./intersection"; - -const IMAGE_LOADED_EVENT = "load"; -const VIDEO_LOADED_EVENT = "loadeddata"; - -/** External mutation handlers. */ -export type Handler = [(added: Element) => void, (removed: Element) => void]; - -const observer = canObserve() ? new MutationObserver(handleMutations) : undefined; -const handlers: Array = []; - -export function observeMutations() { - observer?.observe(document.body, { childList: true, subtree: true }); - if (canObserve()) handleAdded(document.body); -} - -export function addHandler(handler: Handler) { - handlers.push(handler); -} - -function canObserve() { - return typeof document === "object" && "MutationObserver" in window; -} - -function handleMutations(mutations: MutationRecord[]) { - for (const mutation of mutations) - handleMutation(mutation); -} - -function handleMutation(mutation: MutationRecord) { - for (const node of mutation.addedNodes) - if (isElement(node)) handleAdded(node); - for (const node of mutation.removedNodes) - if (isElement(node)) handleRemoved(node); -} - -function handleAdded(added: Element) { - for (const element of added.querySelectorAll("[data-imgit-loadable]")) { - if (isImage(element)) { - // Height check is a hack for firefox, which returns false-positive on complete. - if (element.complete && element.naturalHeight > 0) signalLoaded(element); - else element.addEventListener(IMAGE_LOADED_EVENT, handleLoaded); - } else if (isVideo(element)) { - observeVideo(element); - if (element.readyState >= 2) signalLoaded(element); - else element.addEventListener(VIDEO_LOADED_EVENT, handleLoaded); - } - } - - for (const handler of handlers) - handler[0](added); -} - -function handleRemoved(removed: Element) { - for (const element of removed.querySelectorAll("[data-imgit-loadable]")) { - if (isVideo(element)) unobserveVideo(element); - element.removeEventListener(IMAGE_LOADED_EVENT, handleLoaded); - element.removeEventListener(VIDEO_LOADED_EVENT, handleLoaded); - } - - for (const handler of handlers) - handler[1](removed); -} - -function isElement(obj: Node | EventTarget): obj is Element { - return "querySelector" in obj; -} - -function isVideo(element: Element): element is HTMLVideoElement { - return "getVideoPlaybackQuality" in element; -} - -function isImage(element: Element): element is HTMLImageElement { - return "complete" in element; -} - -function handleLoaded(event: Event) { - if (event.currentTarget && isElement(event.currentTarget)) - signalLoaded(event.currentTarget); -} - -function signalLoaded(element: Element) { - const container = element.closest("[data-imgit-container]"); - if (!container) return; - (container).dataset.imgitLoaded = ""; -} diff --git a/docs/.vitepress/imgit/client/styles.css b/docs/.vitepress/imgit/client/styles.css deleted file mode 100644 index 3dfcad42a..000000000 --- a/docs/.vitepress/imgit/client/styles.css +++ /dev/null @@ -1,20 +0,0 @@ -.imgit-picture, .imgit-video { - display: grid; -} - -.imgit-picture > *, .imgit-video > * { - grid-area: 1 / 1; -} - -.imgit-cover { - display: flex; -} - -.imgit-cover img { - contain: size; -} - -[data-imgit-loaded] .imgit-cover { - opacity: 0; - transition: opacity 0.3s ease-out; -} diff --git a/docs/.vitepress/imgit/plugin/cloudflare.ts b/docs/.vitepress/imgit/plugin/cloudflare.ts deleted file mode 100644 index 36dc44c51..000000000 --- a/docs/.vitepress/imgit/plugin/cloudflare.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { EncodedAsset } from "../server/asset"; - -export default function (credentials: unknown): (path: string, asset: EncodedAsset) => Promise { - return async (path, asset) => ""; -} diff --git a/docs/.vitepress/imgit/plugin/svg.ts b/docs/.vitepress/imgit/plugin/svg.ts deleted file mode 100644 index ff2fe6256..000000000 --- a/docs/.vitepress/imgit/plugin/svg.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Plugin } from "../server"; -import { CapturedAsset, BuiltAsset } from "../server/asset"; -import { std } from "../server"; - -/** Adds support for inlining SVG assets with imgit. - * @example ![](/assets/diagram.svg) */ -export default function (): Plugin { - // Skipping probe and encode stages; we'll just embed SVG content into HTML. - return { probe: isSvg, encode: isSvg, build }; -}; - -function isSvg(asset: CapturedAsset): boolean { - return asset.syntax.url.endsWith(".svg"); -} - -async function build(asset: BuiltAsset): Promise { - if (!isSvg(asset)) return false; - const cls = `imgit-svg ${asset.spec.class ?? ""}`; - const svg = await std.fs.read(asset.content.local, "utf8"); - asset.html = `
${svg}
`; - return true; -} diff --git a/docs/.vitepress/imgit/plugin/turbopack.ts b/docs/.vitepress/imgit/plugin/turbopack.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/.vitepress/imgit/plugin/vite.ts b/docs/.vitepress/imgit/plugin/vite.ts deleted file mode 100644 index 0c848915e..000000000 --- a/docs/.vitepress/imgit/plugin/vite.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Platform, Prefs, boot, exit, transform } from "../server"; - -/** Configures vite plugin behaviour. */ -export type VitePrefs = Prefs & { - /** Force the plugin to run either before are after other plugins. */ - enforce?: "pre" | "post"; - /** Specify condition when document shouldn't be transformed by the plugin. */ - skip?: (code: string, id: string, options?: { ssr?: boolean; }) => boolean; -}; - -declare type VitePlugin = { - name: string; - enforce?: "pre" | "post"; - buildStart?: (options: unknown) => Promise | void; - transform?: (code: string, id: string, options?: { ssr?: boolean; }) => Promise | string; - buildEnd?: (error?: Error) => Promise | void; -} - -/** Creates imgit plugin instance for vite. - * @param prefs Plugin preferences; will use pre-defined defaults when not assigned. - * @param platform Runtime APIs to use; will attempt to detect automatically when not assigned. */ -export default function (prefs?: VitePrefs, platform?: Platform): VitePlugin { - return { - name: "imgit", - enforce: prefs?.enforce, - buildStart: _ => boot(prefs, platform), - transform: (code, id, opt) => prefs?.skip?.(code, id, opt) ? code : transform(id, code), - buildEnd: exit - }; -}; diff --git a/docs/.vitepress/imgit/plugin/youtube/client.ts b/docs/.vitepress/imgit/plugin/youtube/client.ts deleted file mode 100644 index 9c05f7be6..000000000 --- a/docs/.vitepress/imgit/plugin/youtube/client.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { addHandler } from "../../client/mutation"; -import "./styles.css"; - -addHandler([handleAdded, handleRemoved]); -typeof document === "object" && handleAdded(document.body); - -function handleAdded(added: Element) { - for (const banner of added.querySelectorAll("button.imgit-youtube-banner")) - banner.addEventListener("click", handleBannerClick); - for (const poster of added.querySelectorAll("div.imgit-youtube-poster")) - poster.addEventListener("click", handlePlayClick); -} - -function handleRemoved(removed: Element) { - for (const banner of removed.querySelectorAll("button.imgit-youtube-banner")) - banner.removeEventListener("click", handleBannerClick); - for (const poster of removed.querySelectorAll("div.imgit-youtube-poster")) - poster.removeEventListener("click", handlePlayClick); -} - -function handleBannerClick(event: Event) { - const button = event.currentTarget; - const href = button.dataset.href!; - window.open(href, "_blank"); -} - -function handlePlayClick(event: Event) { - const poster = event.currentTarget; - const container = poster.parentElement; - const iframe = container.lastChild!.firstChild; - container.classList.add("imgit-youtube-loading"); - iframe.src = iframe.dataset.src!; - iframe.addEventListener("load", handlePlayerLoaded); -} - -function handlePlayerLoaded(event: Event) { - const iframe = event.currentTarget; - const player = iframe.parentElement; - const container = player.parentElement; - container.classList.add("imgit-youtube-playing"); - player.hidden = false; -} diff --git a/docs/.vitepress/imgit/plugin/youtube/index.ts b/docs/.vitepress/imgit/plugin/youtube/index.ts deleted file mode 100644 index 867c2edab..000000000 --- a/docs/.vitepress/imgit/plugin/youtube/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import server from "./server"; - -export default server; diff --git a/docs/.vitepress/imgit/plugin/youtube/server.ts b/docs/.vitepress/imgit/plugin/youtube/server.ts deleted file mode 100644 index 5d2037d72..000000000 --- a/docs/.vitepress/imgit/plugin/youtube/server.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Plugin } from "../../server"; -import { BuiltAsset, ResolvedAsset, AssetSyntax } from "../../server/asset"; -import { Cache, cache as $cache, cfg, std } from "../../server"; -import { resolveSpec } from "../../server/transform/2-resolve"; -import { build as buildDefault } from "../../server/transform/6-build"; - -/** YouTube plugin preferences. */ -export type Prefs = { - /** Whether to show captured alt syntax as video title; enabled by default. */ - title?: boolean; - /** Whether to show "Watch on YouTube" banner; enabled by default. */ - banner?: boolean; -} - -type YouTubeCache = Cache & { - /** Resolved thumbnail URLs mapped by YouTube video ID. */ - youtube: Record; -} - -/** YouTube thumbnail variants; each video is supposed to have at least "0". */ -const thumbs = ["maxresdefault", "mqdefault", "0"]; -const cache = $cache; -const prefs: Prefs = {}; - -/** Adds support for embedding YouTube videos with imgit. - * @example ![](https://www.youtube.com/watch?v=arbuYnJoLtU) */ -export default function ($prefs?: Prefs): Plugin { - if (!cache.hasOwnProperty("youtube")) cache.youtube = {}; - Object.assign(prefs, $prefs); - return { resolve, build }; -}; - -async function resolve(asset: ResolvedAsset): Promise { - if (!isYouTube(asset.syntax.url)) return false; - const id = getYouTubeId(asset.syntax.url); - asset.content = { src: await resolveThumbnailUrl(id) }; - asset.spec = asset.syntax.spec ? resolveSpec(asset.syntax.spec) : {}; - return true; -} - -async function build(asset: BuiltAsset): Promise { - if (!isYouTube(asset.syntax.url)) return false; - const id = getYouTubeId(asset.syntax.url); - const cls = `imgit-youtube ${asset.spec.class ?? ""}`; - const source = `https://www.youtube-nocookie.com/embed/${id}?autoplay=1&playsinline=1`; - const allow = "accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"; - const banner = buildBanner(asset.syntax); - const title = buildTitle(asset.syntax); - asset.html = ` -
${title}${banner} -
-
- ${await buildPoster(asset)} -
-