Enhance Markdown Code Blocks With Copy To Clipboard

by Admin 52 views
Enhance Markdown Code Blocks with Copy to Clipboard

Hey guys! Today, we're diving into an exciting project: adding a nifty "copy to clipboard" feature to those handy markdown syntax blocks you see all over the web. This enhancement will make it super easy for your users to grab code snippets and use them without any hassle. No more tedious manual copying – just a single click and boom, the code is ready to be pasted wherever they need it!

Why Add a Copy to Clipboard Feature?

So, why should you even bother with adding this feature? Well, think about it. As developers or content creators, we often share code examples in our blogs, documentation, and tutorials. Making it simple for readers to copy these code snippets improves their experience and encourages them to engage more with your content. Here’s a breakdown of the benefits:

  • Improved User Experience: Let's face it, manually selecting and copying code can be a pain. A single-click copy button makes the process much smoother and more user-friendly.
  • Increased Engagement: When it's easy to copy and paste code, users are more likely to experiment with it, leading to deeper engagement with your content.
  • Professional Look: A copy to clipboard feature adds a touch of polish to your site, making it look more professional and well-maintained.
  • Accessibility: It makes your content more accessible to users who might have difficulty with manual selection, especially on mobile devices.

By providing this feature, you're not just adding a button; you're enhancing the overall usability and appeal of your site. Plus, it shows that you care about your audience and are willing to go the extra mile to make their lives easier.

Leveraging Shiki for Code Highlighting and More

Before we get into the nitty-gritty, let’s talk about Shiki. Shiki is a fantastic syntax highlighter that can transform your markdown code blocks into beautifully rendered and interactive elements. It's not just about making code look pretty; Shiki also provides powerful tools for adding functionality, like our copy to clipboard feature.

Shiki achieves this through something called transformer blocks. These are essentially plugins that can modify the output of Shiki's highlighting process. In our case, we'll use a transformer block to inject a copy button into each code block. When a user clicks this button, the code inside the block will be copied to their clipboard. Cool, right?

Ebacala's blog post (https://ebacala.com/blog/create-a-code-block-with-a-copy-button-using-astro-markdown-and-shiki/) provides an excellent example of how to implement this using Astro, markdown, and Shiki. We'll be adapting some of those concepts to fit our specific needs.

Step-by-Step Implementation

Alright, let’s get our hands dirty with the implementation. Here’s a breakdown of the steps we’ll be following:

  1. Create a shiki.transformer.utils.ts File: This file will house the core logic for our transformer block. It will contain the functions needed to generate the copy button and handle the clipboard functionality.
  2. Wire Up to vite.config.ts: We'll need to configure Vite (or your build tool of choice) to include our transformer block in the Shiki highlighting process. This involves modifying the vite.config.ts file to import and register our transformer.
  3. Style Transformer Markup and Support Themes: Finally, we'll add some CSS styling to make the copy button look good and ensure it integrates seamlessly with your site's theme. This includes handling different color schemes and ensuring the button is accessible.

Let's dive into each of these steps in detail.

1. Create shiki.transformer.utils.ts

First up, we need to create a utility file that will contain the logic for our Shiki transformer. This file will be responsible for generating the HTML for the copy button and attaching the necessary event listeners to handle the copy functionality. Create a new file named shiki.transformer.utils.ts in your project, and add the following code:

// shiki.transformer.utils.ts
import { h } from 'hastscript';
import { visit } from 'unist-util-visit';

export function addCopyToClipboard() {
  return (tree: any) => {
    visit(tree, 'element', (node: any) => {
      if (node.tagName === 'pre' && node.children && node.children[0].tagName === 'code') {
        const codeNode = node.children[0];
        const codeText = codeNode.children.map((child: any) => child.value).join('');

        const copyButton = h('button',
          { 
            class: 'copy-button', 
            'aria-label': 'Copy code to clipboard'
          },
          'Copy'
        );

        copyButton.data = {
          hProperties: {
            onclick: `navigator.clipboard.writeText(\'${codeText.replace(/\n/g, '\\n').replace(/'/g, '\'')}\')`,
          },
        };

        node.children.unshift(copyButton);
      }
    });
  };
}

Explanation:

  • Imports: We import h from hastscript to create HTML elements and visit from unist-util-visit to traverse the syntax tree.
  • addCopyToClipboard Function: This is our main transformer function. It takes the syntax tree as input and modifies it.
  • visit Function: We use visit to find all pre elements that contain a code element. These are our code blocks.
  • Code Extraction: We extract the code text from the code element by mapping over its children and joining their values.
  • Copy Button Creation: We create a button element with the class copy-button and an aria-label for accessibility. The text of the button is set to "Copy".
  • onclick Handler: This is where the magic happens. We set the onclick attribute of the button to a JavaScript function that uses the navigator.clipboard.writeText() method to copy the code to the clipboard. We also escape special characters in the code to prevent errors.
  • Adding the Button: Finally, we use unshift to add the copy button as the first child of the pre element, placing it above the code block.

2. Wire Up to vite.config.ts

Next, we need to tell Vite (or your bundler) to use our transformer when highlighting code with Shiki. Open your vite.config.ts file and modify it as follows:

// vite.config.ts
import { defineConfig } from 'vite'
import { sveltekit } from '@sveltejs/kit/vite';
import shiki from 'shiki';
import { addCopyToClipboard } from './src/lib/shiki.transformer.utils';

export default defineConfig({
  plugins: [
    sveltekit(),
    {
      name: 'shiki-transformer',
      async transform(code, id) {
        if (id.endsWith('.svelte.md') || id.endsWith('.md')) {
          const highlighter = await shiki.getHighlighter({
            theme: 'dracula',
          });

          highlighter.codeToHtml = (code, lang) => {
            const originalCodeToHtml = highlighter.codeToHtml.bind(highlighter);
            return originalCodeToHtml(code, lang, { transformers: [addCopyToClipboard()] });
          };

          return code;
        }
      },
    },
  ],
});

Explanation:

  • Imports: We import shiki and our addCopyToClipboard function.
  • Vite Plugin: We create a Vite plugin named shiki-transformer.
  • transform Hook: This hook is called whenever Vite processes a file. We check if the file is a markdown file (either .svelte.md or .md).
  • Shiki Highlighter: We get a Shiki highlighter instance with the dracula theme (you can choose any theme you like).
  • Overriding codeToHtml: This is the crucial part. We override the codeToHtml method of the highlighter to include our transformer. We use bind to preserve the context of the original codeToHtml method and then call it with our transformer in the transformers array.

3. Style Transformer Markup and Support Themes

Finally, we need to add some CSS to style our copy button. Create a new CSS file (e.g., src/lib/copy-button.css) and add the following styles:

/* src/lib/copy-button.css */
.copy-button {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  padding: 0.25rem 0.5rem;
  background-color: #444;
  color: #fff;
  border: none;
  border-radius: 0.25rem;
  cursor: pointer;
  font-size: 0.75rem;
  opacity: 0.7;
  transition: opacity 0.2s ease-in-out;
}

.copy-button:hover {
  opacity: 1;
}

Explanation:

  • Positioning: We use position: absolute to position the button in the top-right corner of the code block.
  • Styling: We add some basic styling to make the button look visually appealing.
  • Opacity: We use opacity to make the button slightly transparent by default and fully opaque on hover.
  • Transition: We add a transition to create a smooth hover effect.

Import this CSS file in your main layout or app component to apply the styles globally.

Wrapping Up

And there you have it! You’ve successfully added a copy to clipboard feature to your markdown code blocks using Shiki. Your users can now easily copy code snippets with a single click, making their experience much smoother and more enjoyable. This small enhancement can significantly improve the usability and appeal of your site. Keep experimenting with Shiki and transformer blocks to add even more cool features to your code blocks!