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