mirror of
https://github.com/DefectingCat/DefectingCat.github.io
synced 2025-07-16 01:01:38 +00:00
Add copy hooks
Add copy button on post
This commit is contained in:
@ -1,5 +1,12 @@
|
|||||||
import classNames from 'classnames';
|
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'));
|
const CopyButton = lazy(() => import('components/post/CopyButton'));
|
||||||
|
|
||||||
@ -11,12 +18,24 @@ type Props = {} & DetailedHTMLProps<
|
|||||||
const Pre = ({ ...rest }: Props) => {
|
const Pre = ({ ...rest }: Props) => {
|
||||||
const { children, className, ...props } = rest;
|
const { children, className, ...props } = rest;
|
||||||
|
|
||||||
|
const preRef = useRef<HTMLPreElement>(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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<pre className={classNames(className, 'relative group')} {...props}>
|
<pre
|
||||||
|
ref={preRef}
|
||||||
|
className={classNames(className, 'relative group')}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
<Suspense fallback>
|
<Suspense fallback>
|
||||||
<CopyButton />
|
<CopyButton onCopy={handleCopy} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</pre>
|
</pre>
|
||||||
</>
|
</>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import RUASandpack from 'components/RUA/RUASandpack';
|
|
||||||
import Anchor from 'components/mdx/Anchor';
|
import Anchor from 'components/mdx/Anchor';
|
||||||
import Image from 'components/mdx/Image';
|
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 Tab from 'components/RUA/tab';
|
||||||
import TabItem from 'components/RUA/tab/TabItem';
|
import TabItem from 'components/RUA/tab/TabItem';
|
||||||
import RUACodeSandbox from 'components/RUA/RUACodeSandbox';
|
|
||||||
import Pre from 'components/mdx/Pre';
|
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
RUASandpack,
|
RUASandpack,
|
||||||
|
@ -3,10 +3,10 @@ import { useState } from 'react';
|
|||||||
import styles from './CopytButton.module.css';
|
import styles from './CopytButton.module.css';
|
||||||
|
|
||||||
export type CopyButtonProps = {
|
export type CopyButtonProps = {
|
||||||
onClick?: () => void;
|
onCopy?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CopyButton = ({ onClick }: CopyButtonProps) => {
|
const CopyButton = ({ onCopy: onClick }: CopyButtonProps) => {
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
onClick?.();
|
onClick?.();
|
||||||
|
57
lib/hooks/useCopyToClipboard.ts
Normal file
57
lib/hooks/useCopyToClipboard.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
|
type CopiedValue = string | null;
|
||||||
|
type CopyFn = (text: string) => Promise<boolean>; // Return success
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 clipboard API writeText 写入字符到剪贴板
|
||||||
|
* copy 方法为 memoized
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function useCopyToClipboard() {
|
||||||
|
const [copiedText, setCopiedText] = useState<CopiedValue>(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;
|
Reference in New Issue
Block a user