Skip to Content
🎉 How to create page like this?. Check it out →
DevWebNextComponents

Components

Copy to Clipboard

Thêm nút lệnh copy vào code block

Version (old)

components/copyToClipboard.tsx
const copyToClipboard = (str: string) => { const el = document.createElement('textarea') el.value = str el.setAttribute('readonly', '') el.style.position = 'absolute' el.style.left = '-9999px' document.body.appendChild(el) const selected = document.getSelection().rangeCount > 0 ? document.getSelection().getRangeAt(0) : false el.select() document.execCommand('copy') document.body.removeChild(el) if (selected) { document.getSelection().removeAllRanges() document.getSelection().addRange(selected) } } export default copyToClipboard
lib/addCopyCodeButtons.ts
import toast from 'react-hot-toast' import copyToClipboard from 'components/copyToClipboard' function handleCopyClick(evt) { const { children } = evt.target.parentElement const { innerText } = Array.from(children)[0] as HTMLElement copyToClipboard(innerText) toast.success('Đã copy vào clipboard!') // Thông báo khi nhấn vào nút copy } function copyCodeListener() { const codeBlocks = document.querySelectorAll('pre[data-language]') // Thêm vào thẻ <pre> codeBlocks.forEach((codeBlock) => { const copy = document.createElement('div') copy.classList.add('copy-code-button') copy.innerHTML = 'Copy' copy.addEventListener('click', handleCopyClick) codeBlock.append(copy) }) } export default copyCodeListener
blog/[slug].tsx
import { useEffect, useRef } from 'react' import { useMDXComponent } from 'next-contentlayer/hooks' import { getTweets } from 'lib/twitter' import addCopyCodeButtons from 'lib/addCopyCodeButtons' import components from 'components/MDXComponents' import BlogLayout from 'layouts/blog' import Tweet from 'components/Tweet' import { allBlogs } from 'contentlayer/generated' import { getPartialPost } from 'lib/contentlayer' export default function Post({ post, tweets, }: { post: ReturnType<typeof getPartialPost> tweets: any[] }) { const Component = useMDXComponent(post.body.code) const StaticTweet = ({ id }) => { const tweet = tweets.find((tweet) => tweet.id === id) return <Tweet {...tweet} /> } // https://phannhatchanh.com/snippets/javascript/them-nut-copy-vao-code-blocks-bang-javascript/ const effectRan = useRef(false) useEffect(() => { if (effectRan.current === true || process.env.NODE_ENV !== 'development') { addCopyCodeButtons() } return () => { effectRan.current = true } }, []) return ( <BlogLayout post={post}> <Component components={ { ...components, StaticTweet, } as any } /> </BlogLayout> ) } export async function getStaticPaths() { return { paths: allBlogs.map((p) => ({ params: { slug: p.slug } })), fallback: false, } } export async function getStaticProps({ params }) { const post = allBlogs.find((post) => post.slug === params.slug) const tweets = await getTweets(post.tweetIds) return { props: { post: getPartialPost(post, allBlogs), tweets, }, } }

Back To Top

components/BackToTop.tsx
import { useState, useEffect, FC } from 'react' import { ChevronDoubleUpIcon } from '@heroicons/react/24/outline' import Tippy from '@tippyjs/react' const BackToTop: FC = () => { const [showButton, setShowButton] = useState(false) useEffect(() => { const toggleVisibility = () => { setShowButton(window.pageYOffset > 500) } window.addEventListener('scroll', toggleVisibility) return () => window.removeEventListener('scroll', toggleVisibility) }, []) return ( <div className="fixed flex items-center justify-center p-0 text-xl rounded-full cursor-pointer bottom-2 right-2"> {showButton && ( <Tippy delay={[0, 50]} content="Trở lên trên" theme="tooltip"> <ChevronDoubleUpIcon className="w-7 transition-colors text-accent-600 dark:text-accent-400 hover:text-[#db2777] dark:hover:text-[#db2777]" onClick={() => { window.scrollTo({ top: 0, behavior: 'smooth', }) }} /> </Tippy> )} </div> ) } export default BackToTop
Last updated on