[{"data":1,"prerenderedAt":154},["ShallowReactive",2],{"article-14":3},{"id":4,"title":5,"body":6,"create":141,"description":142,"extension":143,"labels":144,"locked":146,"meta":147,"navigation":148,"path":149,"seo":150,"stem":151,"update":152,"__hash__":153},"articles/article/14.md","VitePress 中复制功能的使用",{"type":7,"value":8,"toc":138},"minimark",[9,18,27,42,45,56,79,85,100,106,109,115,121,127,130],[10,11,12,13,17],"p",{},"在 VitePress 生成 Markdown 文档中代码块时，会在最终生成的代码块中添加复制代码的按钮。在检查这个按钮的时候发现页面中的仅仅是一个按钮，并没有添加什么点击事件，在翻阅了\nVitePress 的代码之后，发现在 ",[14,15,16],"code",{},"src\\client\\app\\composables\\copyCode.ts"," 文件中有该功能的详细实现。",[10,19,20],{},[21,22,26],"a",{"href":23,"rel":24},"https://github.com/vuejs/vitepress/blob/main/src/client/app/composables/copyCode.ts",[25],"nofollow","代码地址",[28,29,30,32],"blockquote",{},[10,31,16],{},[33,34,40],"pre",{"className":35,"code":37,"language":38,"meta":39},[36],"language-typescript","import { inBrowser } from 'vitepress'\n\nexport function useCopyCode() {\n   if (inBrowser) {\n       const timeoutIdMap: WeakMap\u003CHTMLElement, NodeJS.Timeout> = new WeakMap()\n       window.addEventListener('click', (e) => {\n          const el = e.target as HTMLElement\n          if (el.matches('div[class*=\"language-\"] > button.copy')) {\n            const parent = el.parentElement\n            const sibling = el.nextElementSibling?.nextElementSibling\n            if (!parent || !sibling) {\n              return\n            }\n\n            const isShell = /language-(shellscript|shell|bash|sh|zsh)/.test(\n              parent.className\n            )\n\n            let text = ''\n\n            sibling\n              .querySelectorAll('span.line:not(.diff.remove)')\n              .forEach((node) => (text += (node.textContent || '') + '\\n'))\n            text = text.slice(0, -1)\n\n            if (isShell) {\n              text = text.replace(/^ *(\\$|>) /gm, '').trim()\n            }\n\n            copyToClipboard(text).then(() => {\n              el.classList.add('copied')\n              clearTimeout(timeoutIdMap.get(el))\n              const timeoutId = setTimeout(() => {\n                el.classList.remove('copied')\n                el.blur()\n                timeoutIdMap.delete(el)\n              }, 2000)\n              timeoutIdMap.set(el, timeoutId)\n            })\n          }\n       })\n   }\n}\n\nasync function copyToClipboard(text: string) {\n   try {\n       return navigator.clipboard.writeText(text)\n   } catch {\n       const element = document.createElement('textarea')\n       const previouslyFocusedElement = document.activeElement\n\n       element.value = text\n\n       // Prevent keyboard from showing on mobile\n       element.setAttribute('readonly', '')\n\n       element.style.contain = 'strict'\n       element.style.position = 'absolute'\n       element.style.left = '-9999px'\n       element.style.fontSize = '12pt' // Prevent zooming on iOS\n\n       const selection = document.getSelection()\n       const originalRange = selection\n          ? selection.rangeCount > 0 && selection.getRangeAt(0)\n          : null\n\n       document.body.appendChild(element)\n       element.select()\n\n       // Explicit selection workaround for iOS\n       element.selectionStart = 0\n       element.selectionEnd = text.length\n\n       document.execCommand('copy')\n       document.body.removeChild(element)\n\n       if (originalRange) {\n          selection!.removeAllRanges() // originalRange can't be truthy when selection is falsy\n          selection!.addRange(originalRange)\n       }\n\n       // Get the focus back on the previously focused element, if any\n       if (previouslyFocusedElement) {\n          ;(previouslyFocusedElement as HTMLElement).focus()\n       }\n   }\n}\n","typescript","",[14,41,37],{"__ignoreMap":39},[10,43,44],{},"那么问题来了，如何把这个功能拿来自己使用？",[10,46,47,48,51,52,55],{},"仔细阅读一下源码，首先 ",[14,49,50],{},"useCopyCode"," 对页面中的  ",[14,53,54],{},"click","\n事件进行了监听，并且判断是否是复制代码的按钮，如果判断正确，就获取父元素和兄弟元素，父元素是用来判断语言类型，而兄弟元素用来获得所有的代码文本。",[10,57,58,59,62,63,66,67,70,71,74,75,78],{},"从 html 元素中获得文本的代码如下，首先获取 ",[14,60,61],{},"span"," 元素中类为 ",[14,64,65],{},"line"," 的元素并排除 ",[14,68,69],{},".diff.remove"," 并获取元素的文本，如果文本为\n",[14,72,73],{},"null"," 或  ",[14,76,77],{},"undefined"," 则替换为空文本，并在每行的末尾添加换行符。",[33,80,83],{"className":81,"code":82,"language":38,"meta":39},[36],"sibling.querySelectorAll('span.line:not(.diff.remove)')\n       .forEach((node) => (text += (node.textContent || '') + '\\n'))\n",[14,84,82],{"__ignoreMap":39},[10,86,87,88,91,92,95,96,99],{},"以下这段代码则是去除 ",[14,89,90],{},"shell"," 代码中的 ",[14,93,94],{},"$","、",[14,97,98],{},">"," 以及文本前后的空行：",[33,101,104],{"className":102,"code":103,"language":38,"meta":39},[36],"text = text.replace(/^ *(\\$|>) /gm, '').trim()\n",[14,105,103],{"__ignoreMap":39},[10,107,108],{},"以下这段代码是更改复制按钮的样式：",[33,110,113],{"className":111,"code":112,"language":38,"meta":39},[36],"copyToClipboard(text).then(() => {\n  el.classList.add('copied') // 为按钮添加 `copied` 类\n  clearTimeout(timeoutIdMap.get(el)) // 清除 weakmap 中的计时器\n  const timeoutId = setTimeout(() => {\n    el.classList.remove('copied') // 去除 `copied` 类\n    el.blur() // 移除元素的聚焦\n    timeoutIdMap.delete(el)\n  }, 2000)\n  timeoutIdMap.set(el, timeoutId)\n})\n",[14,114,112],{"__ignoreMap":39},[10,116,117,120],{},[14,118,119],{},"copyToClipboard"," 则是将文本复制到系统的剪贴板中。",[10,122,123,124,126],{},"如果想要自己使用，则只需要在 ",[14,125,50],{}," 代码中按照自己的要求修改即可。",[10,128,129],{},"对于复制按钮的 css 样式如下所示：",[33,131,136],{"className":132,"code":134,"language":135,"meta":39},[133],"language-css","button.copy{\n  --size: 28px;\n  height: var(--size);\n  width: var(--size);\n  background: none;\n  background-image: url('Base64 编码图像，此处省略');\n  background-repeat: no-repeat;\n  border: 1px solid #000;\n  border-radius: 2px;\n  background-position: 50%;\n  background-size: 20px;\n  padding: 5px;\n  box-sizing: border-box;\n}\n\nbutton.copied{\n  background-image: url('Base64 编码图像，此处省略');\n}\n","css",[14,137,134],{"__ignoreMap":39},{"title":39,"searchDepth":139,"depth":139,"links":140},2,[],"2024-09-22T06:00:41.000Z","在 VitePress 生成 Markdown 文档中代码块时，会在最终生成的代码块中添加复制代码的按钮。在检查这个按钮的时候发现页面中的仅仅是一个按钮，并没有添加什么点击事件，在翻阅了\nVitePress 的代码之后，发现在 src\\client\\app\\composables\\copyCode.ts 文件中有该功能的详细实现。","md",[145],"web",false,{},true,"/article/14",{"title":5,"description":142},"article/14",null,"ZhSjDkXsqyLO5bqgkQVCGFcKpfM3NLnu2utOiagBjDU",1755235549196]