技术工具与实践

在Astro markdown代码块中添加复制代码按钮

在这篇博文中,我们将讨论如何使用 Astro.build 框架向 HTML 代码块添加复制代码按钮。但是,提供的 JavaScript 代码也可以在任何其他 HTML 项目中使用。

• 6分钟

原文地址 Adding a Copy Code Button in Astro Markdown Code Blocks

原文作者 Tim Neubauer

Contents

介绍

在网站上的代码块中添加Copy按钮可以极大地增强访问者的用户体验。它允许用户快速轻松地从您的网站复制代码片段,而无需手动选择、复制和粘贴代码。这可以节省用户时间并改善他们在您网站上的整体体验。

准备Astro布局

首先,我们将创建一个名为BlogPostLayout.astro的新布局。 此布局将用于显示我们的 Markdown 文件。 在此布局中,我们稍后将添加一些客户端 JavaScript,这些 JavaScript 将在 Markdown 文件生成的 HTML 的每个 <pre> 标记内添加复制代码按钮。

---
---

<script></script>
<slot />

由于 Astro 为 Markdown 和 MDX 页面提供了特殊的 frontmatter 布局属性,因此我们可以在 Markdown 页面中指定或新创建 Astro 布局。

---
layout: ./layouts/BlogPostLayout.astro
---

# Post Title

post content

添加复制按钮

在我们新创建的布局中,我们添加一些客户端 JavaScript 将按钮渲染到 dom 上。 我们首先定义一个名为copyButtonLabel的变量并将其值设置为Copy。 该变量将用于设置我们将添加到代码块的复制按钮的标签。

let copyButtonLabel = 'Copy'

接下来,我们使用 document.querySelectorAll 函数选择页面的所有 pre 元素。 为了使选定的 elmenets 可迭代,我们使用 Array.from 方法将它们转换为数组,并将其存储在codeBlock变量中。

let codeBlocks = Array.from(document.querySelectorAll('pre'))

对于每个代码块,我们创建一个新的div元素并将其position属性设置为relative。 这将用作代码块的包装器,并将确保复制按钮相对于代码块的位置正确。

for (let codeBlock of codeBlocks) {
  let wrapper = document.createElement('div')
  wrapper.style.position = 'relative'
}

然后,我们创建一个button元素,为其分配copy-code类,并将其内部 HTML 设置为copyButtonLabel变量的值。

let copyButton = document.createElement('button')
copyButton.className = 'copy-code'
copyButton.innerHTML = copyButtonLabel

复制代码类用于设置按钮的样式。 由于我们使用position:relative属性创建了包装元素,因此我们将复制代码的位置设置为绝对位置。

.copy-code {
  position: absolute;
  top: 0;
  right: 0;
  background-color: #2563eb;
  padding: 0.25rem 0.5rem;
  font-size: 0.75rem;
  line-height: 1rem;
}

.copy-code:hover {
  background-color: #3b82f6;
}

回到 JavaScript。 我们将代码块的tabindex属性设置为0,并将复制按钮附加到其上。 这将使代码块可聚焦并允许用户使用键盘与其交互。

codeBlock.setAttribute('tabindex', '0')
codeBlock.appendChild(copyButton)

然后,我们用新创建的div元素包装代码块,并将其插入到其父元素中的代码块之前。

codeBlock.parentNode.insertBefore(wrapper, codeBlock)
wrapper.appendChild(codeBlock)

最后,我们为复制按钮添加一个单击事件监听器,并在单击时调用copyCode函数。

copyButton.addEventListener('click', async () => {
  await copyCode(codeBlock, copyButton)
})

copyCode函数是一个异步函数,它将代码块和按钮作为参数。 它选择代码块中的code元素,获取其内部文本,并使用navigator.clipboard.writeText方法将其写入剪贴板。

async function copyCode(block, button) {
  let code = block.querySelector('code')
  let text = code.innerText

  await navigator.clipboard.writeText(text)
}

为了提供代码已被复制的视觉反馈,按钮的内部文本更改为Code Copied,并在 700 毫秒后设置为默认值。

async function copyCode(block, button) {
  let code = block.querySelector('code')
  let text = code.innerText

  await navigator.clipboard.writeText(text)

  // visual feedback that task is completed
  button.innerText = 'Code Copied'

  setTimeout(() => {
    button.innerText = copyButtonLabel
  }, 700)
}

就是这样! 你的 astro 布局应该是这样的。

---
---

<script>
  let copyButtonLabel = "Copy";
  let codeBlocks = Array.from(document.querySelectorAll("pre"));

  for (let codeBlock of codeBlocks) {
    let wrapper = document.createElement("div");
    wrapper.style.position = "relative";

    let copyButton = document.createElement("button");
    copyButton.className = "copy-code";
    copyButton.innerHTML = copyButtonLabel;

    codeBlock.setAttribute("tabindex", "0");
    codeBlock.appendChild(copyButton);
    // wrap codebock with relative parent element
    codeBlock.parentNode.insertBefore(wrapper, codeBlock);
    wrapper.appendChild(codeBlock);

    copyButton.addEventListener("click", async () => {
      await copyCode(codeBlock, copyButton);
    });
  }

  async function copyCode(block, button) {
    let code = block.querySelector("code");
    let text = code.innerText;

    await navigator.clipboard.writeText(text);

    // visual feedback that task is completed
    button.innerText = "Copied";

    setTimeout(() => {
      button.innerText = copyButtonLabel;
    }, 700);
  }
</script>

<slot />

使用此 JavaScript 代码,您可以轻松地将Copy按钮添加到网站上的任何代码块,使其对您的用户来说更加用户友好和高效。 该代码很容易理解,您可以对其进行自定义以满足您的需求。