From 538bcf3845c0370825568b4db2cb50f37dcc9911 Mon Sep 17 00:00:00 2001 From: Mendon Kissling <59585235+mendonk@users.noreply.github.com> Date: Fri, 12 Jun 2026 12:58:50 -0400 Subject: [PATCH] feat: docs page copy-as-markdown keyboard shortcut (#13628) * add-copy-page-keyboard-shortcut * peer-review --- docs/src/components/CopyPageButton.module.css | 12 ++++ docs/src/components/CopyPageButton.tsx | 59 ++++++++++++++++++- 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/docs/src/components/CopyPageButton.module.css b/docs/src/components/CopyPageButton.module.css index 9e07c2623c..83cc91c651 100644 --- a/docs/src/components/CopyPageButton.module.css +++ b/docs/src/components/CopyPageButton.module.css @@ -32,3 +32,15 @@ .label { font: inherit; } + +.kbd { + font-family: inherit; + font-size: 90%; + font-weight: var(--ifm-font-weight-normal); + line-height: 1; + padding: 0.1rem 0.3rem; + border-radius: 3px; + border: 1px solid var(--ifm-color-secondary-dark); + background-color: var(--ifm-color-emphasis-100); + color: var(--ifm-color-emphasis-700); +} diff --git a/docs/src/components/CopyPageButton.tsx b/docs/src/components/CopyPageButton.tsx index da7a7b5712..dfc028f573 100644 --- a/docs/src/components/CopyPageButton.tsx +++ b/docs/src/components/CopyPageButton.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { Copy as CopyIcon, Check as CheckIcon } from "lucide-react"; import styles from "./CopyPageButton.module.css"; @@ -136,10 +136,18 @@ async function copyCurrentPageAsMarkdown(): Promise { return true; } +function isEditableElement(el: Element | null): boolean { + if (!el) return false; + const tag = el.tagName.toLowerCase(); + if (tag === "input" || tag === "textarea" || tag === "select") return true; + return (el as HTMLElement).isContentEditable; +} + export function CopyPageButton(): JSX.Element | null { const [copied, setCopied] = useState(false); + const [isMac, setIsMac] = useState(true); - const handleClick = useCallback(async () => { + const handleCopy = useCallback(async () => { const ok = await copyCurrentPageAsMarkdown(); if (ok) { setCopied(true); @@ -147,9 +155,51 @@ export function CopyPageButton(): JSX.Element | null { } }, []); + useEffect(() => { + const platform = + (navigator as { userAgentData?: { platform?: string } }).userAgentData + ?.platform ?? navigator.platform; + setIsMac(/Mac|iPhone|iPad|iPod/i.test(platform)); + + const onKeyDown = (event: KeyboardEvent) => { + // Option/Alt+C copies the whole page as Markdown. Unlike Cmd/Ctrl+C, + // this never shadows the native copy gesture or clobbers the clipboard + // on a reflexive copy with a collapsed selection. + // event.code is required here: on macOS, Option+C produces + // event.key === "ç", and key-based checks also break on non-Latin + // layouts. + const isCopyShortcut = + event.altKey && + !event.metaKey && + !event.ctrlKey && + !event.shiftKey && + event.code === "KeyC"; + if (!isCopyShortcut) return; + if (event.repeat) return; + + // Don't fire while typing in a form field, where Option+C may insert a + // character (e.g. "ç" on macOS). + if (isEditableElement(document.activeElement)) return; + + event.preventDefault(); + void handleCopy(); + }; + + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }, [handleCopy]); + + const shortcutLabel = isMac ? "⌥C" : "Alt+C"; + return (
-
);