diff --git a/components/CopyPageButton.tsx b/components/CopyPageButton.tsx new file mode 100644 index 0000000..b3729c6 --- /dev/null +++ b/components/CopyPageButton.tsx @@ -0,0 +1,112 @@ +import { useState, useEffect, useRef } from "react"; +import { createPortal } from "react-dom"; +import Router from "next/router"; + +type State = "idle" | "loading" | "copied" | "error"; + +interface Props { + filePath: string; + filePathFallback: string; + repoBase: string; +} + +function Button({ filePath, filePathFallback, repoBase }: Props) { + const [state, setState] = useState("idle"); + + async function handleCopy() { + if (!repoBase) return; + setState("loading"); + try { + let res = await fetch(`${repoBase}/${filePath}`); + if (!res.ok) res = await fetch(`${repoBase}/${filePathFallback}`); + if (!res.ok) throw new Error(`${res.status}`); + const text = await res.text(); + await navigator.clipboard.writeText(text); + setState("copied"); + } catch { + setState("error"); + } finally { + setTimeout(() => setState("idle"), 2000); + } + } + + const label = + state === "loading" + ? "Copying…" + : state === "copied" + ? "Copied!" + : state === "error" + ? "Failed" + : "Copy page"; + + return ( + + ); +} + +export default function CopyPageButton(props: Props) { + const [mountNode, setMountNode] = useState(null); + + useEffect(() => { + function attach() { + setMountNode((prev) => { + prev?.remove(); + const breadcrumb = document.querySelector(".nextra-breadcrumb"); + if (!breadcrumb) return null; + const mount = document.createElement("div"); + mount.style.cssText = "display:contents"; + breadcrumb.appendChild(mount); + return mount; + }); + } + + attach(); + Router.events.on("routeChangeComplete", attach); + + return () => { + Router.events.off("routeChangeComplete", attach); + setMountNode((prev) => { prev?.remove(); return null; }); + }; + }, []); + + if (!mountNode) return null; + return createPortal(