网站首页 > 基础教程 正文
我的博客中使用了rehype-pretty-code?加?shiki?来美化代码块,rehype-pretty-code提供了一个shiki的?transformer?来自动给代码块加上复制按钮,它会生成这样的代码:
<button
data="code内的代码"
onclick=\"navigator.clipboard.writeText(this.attributes.data.value);this.classList.add('rehype-pretty-copied');window.setTimeout(() => this.classList.remove('rehype-pretty-copied'), 3000);\"
>
<span class=\"ready\"></span>
<span class=\"success\"></span>
</button>
但是在NextJS中目前想要不做额外处理地使用它,只能使用React Server Components,将生成的HTML文本传入?dangerouslySetInnerHTML?:
function MyComponent() {
return <div dangerouslySetInnerHTML={{ __html: html }} />
}
但在某些场景下没法直接用服务端组件,下面给出对应的解决办法。
MDX
如果要结合MDX使用,MDX会把生成的?button?当成React组件处理,而React组件的?onClick?属性需要的是函数对象而不是字符串,为了防止XSS这类安全问题又不能将字符串直接eval成函数,这里就会报错。
解决办法是通过MDX自定义components的方式,先自定义一个复制按钮组件:
'use client'
import { type PropsWithoutRef, useState } from 'react'
export default function CopyCodeButton({
code,
}: PropsWithoutRef<{ code: string }>) {
const [isCopied, setIsCopied] = useState(false)
const copy = async () => {
await navigator.clipboard.writeText(code)
setIsCopied(true)
setTimeout(() => {
setIsCopied(false)
}, 2500)
}
return (
<button
className="rehype-pretty-copy"
title="Copy code"
aria-label="Copy code"
onClick={copy}
>
{isCopied ? CheckIcon : CopyIcon}
</button>
)
}
这个组件不是服务端组件,所以在开头第一行要加?"use client"?,?className?可以复用一下,子组件切换复用有点麻烦,干脆直接自定义的图标了。
下一步就是通过MDX的API替换生成的button:
<MDXContent
components={{
button(props) {
const { children, className, ...rest } = props
// 判断一下是否是插件生成的
if (className === 'rehype-pretty-copy') {
return <CopyCodeButton code={rest.data} />
} else {
return <button {...props} />
}
},
}}
/>
这样就可以实现复制代码按钮了。
Org
直接使用我的@docube/org通常来说是没有问题的,但是由于我的文章页面的结构大致是这样的:
function Post() {
return (
<article>
<header></header>
{content}
<address></address>
</article>
)
}
React的?dangerouslySetInnerHTML?不能直接作用到?Fragment?上,也就是必须要给content加个父元素,我个人有点受不了……
为了能不加额外的父元素,我使用了?html-react-parser?这个库,它又带来了新的问题,也就是为了安全,它会直接忽略?onclick?属性,导致只能渲染按钮却没有复制的功能。
解决办法如下:
import reactParse from 'html-react-parser'
function Content() {
return (
<>
{reactParse(post.body, {
replace: (dom) => {
if (
'attribs' in dom &&
dom.name === 'button' &&
dom.attribs['class'] === 'rehype-pretty-copy'
) {
delete dom.attribs.onclick
return <CopyCodeButton code={dom.attribs.data} />
}
},
})}
<>
)
}
猜你喜欢
- 2024-10-09 基于JavaScript实现的步骤引导进度条
- 2024-10-09 ?? ??????前端开发,实现B站首页动态banner
- 2024-10-09 有条件地启用和禁用DOM元素 在有条件使用
- 2024-10-09 详解Web导航菜单吸顶效果 详解web导航菜单吸顶效果怎么设置
- 2024-10-09 vue实践中的常见知识漏洞001 vue常见问题总结
- 2024-10-09 很多人不知道 Vue 中的组件就是一个函数
- 2024-10-09 javascript一个可以让你学会很多东西的实例「312」
- 2024-10-09 Webpack基础应用篇 -「9」管理资源(下)
- 2024-10-09 移动开发第三天 移动开发什么意思
- 2024-10-09 读 Vue3 文档的时候我只学会了 Vue3?
- 最近发表
- 标签列表
-
- gitpush (61)
- pythonif (68)
- location.href (57)
- tail-f (57)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- css3动画 (57)
- c#event (59)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- exec命令 (59)
- canvasfilltext (58)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- node教程 (59)
- console.table (62)
- c++time_t (58)
- phpcookie (58)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)