diff --git a/components/mdx/Pre.tsx b/components/mdx/Pre.tsx index 443c484..270ad05 100644 --- a/components/mdx/Pre.tsx +++ b/components/mdx/Pre.tsx @@ -1,5 +1,12 @@ import classNames from 'classnames'; -import { DetailedHTMLProps, HTMLAttributes, lazy, Suspense } from 'react'; +import useCopyToClipboard from 'lib/hooks/useCopyToClipboard'; +import { + DetailedHTMLProps, + HTMLAttributes, + lazy, + Suspense, + useRef, +} from 'react'; const CopyButton = lazy(() => import('components/post/CopyButton')); @@ -11,12 +18,24 @@ type Props = {} & DetailedHTMLProps< const Pre = ({ ...rest }: Props) => { const { children, className, ...props } = rest; + const preRef = useRef(null); + const { copy } = useCopyToClipboard(); + const handleCopy = () => { + if (!preRef.current) throw new Error('Can not access pre element.'); + if (preRef.current.textContent == null) return; + copy(preRef.current.textContent); + }; + return ( <> -
+      
         {children}
         
-          
+          
         
       
diff --git a/components/mdx/components.ts b/components/mdx/components.ts index db89725..be1bb46 100644 --- a/components/mdx/components.ts +++ b/components/mdx/components.ts @@ -1,10 +1,10 @@ -import RUASandpack from 'components/RUA/RUASandpack'; import Anchor from 'components/mdx/Anchor'; import Image from 'components/mdx/Image'; +import Pre from 'components/mdx/Pre'; +import RUACodeSandbox from 'components/RUA/RUACodeSandbox'; +import RUASandpack from 'components/RUA/RUASandpack'; import Tab from 'components/RUA/tab'; import TabItem from 'components/RUA/tab/TabItem'; -import RUACodeSandbox from 'components/RUA/RUACodeSandbox'; -import Pre from 'components/mdx/Pre'; const components = { RUASandpack, diff --git a/components/post/CopyButton.tsx b/components/post/CopyButton.tsx index 281d411..39ba714 100644 --- a/components/post/CopyButton.tsx +++ b/components/post/CopyButton.tsx @@ -3,10 +3,10 @@ import { useState } from 'react'; import styles from './CopytButton.module.css'; export type CopyButtonProps = { - onClick?: () => void; + onCopy?: () => void; }; -const CopyButton = ({ onClick }: CopyButtonProps) => { +const CopyButton = ({ onCopy: onClick }: CopyButtonProps) => { const [copied, setCopied] = useState(false); const handleClick = () => { onClick?.(); diff --git a/lib/hooks/useCopyToClipboard.ts b/lib/hooks/useCopyToClipboard.ts new file mode 100644 index 0000000..e4dd699 --- /dev/null +++ b/lib/hooks/useCopyToClipboard.ts @@ -0,0 +1,57 @@ +import { useCallback, useState } from 'react'; + +type CopiedValue = string | null; +type CopyFn = (text: string) => Promise; // Return success + +/** + * 使用 clipboard API writeText 写入字符到剪贴板 + * copy 方法为 memoized + * @returns + */ +function useCopyToClipboard() { + const [copiedText, setCopiedText] = useState(null); + + const copy: CopyFn = useCallback(async (text) => { + const copyWithOldWay = () => { + try { + const el = document.createElement('textarea'); + el.value = text; + el.setAttribute('readonly', ''); + el.style.position = 'absolute'; + el.style.left = '-9999px'; + document.body.appendChild(el); + el.select(); + document.execCommand('copy'); + document.body.removeChild(el); + return true; + } catch (e) { + console.warn('Copy failed', e); + return false; + } + }; + + const copyWithClipboardAPI = async () => { + // Try to save to clipboard then save it in the state if worked + try { + await navigator.clipboard.writeText(text); + setCopiedText(text); + return true; + } catch (error) { + console.warn('Copy failed', error); + setCopiedText(null); + return false; + } + }; + + if (!navigator?.clipboard) { + console.warn('Clipboard not supported'); + return copyWithOldWay(); + } else { + return await copyWithClipboardAPI(); + } + }, []); + + return { copiedText, copy }; +} + +export default useCopyToClipboard;