React渲染Markdown & 代码高亮显示 & 代码块复制

使用 react-markdown 实现 markdown 的渲染,使用 @tailwindcss/typography 实现好看的样式。

项目基于 Vite + React + TypeScript搭建,包管理工具使用的是 pnpm,需要安装 TailwindCSS

首先安装 react-markdownrehype-highlighthighlight.js

pnpm install react-markdown rehype-highlight highlight.js

react-markdown 用于解析 markdown 语法,rehype-highlight 和 highlight.js 用于突出高亮显示代码块。

Markdown 渲染

封装 markdown 组件。新建 src/components/Markdown.tsx 文件:

import ReactMarkdown from "react-markdown";
import rehypeHighlight from "rehype-highlight";

import "highlight.js/styles/atom-one-dark.css";

const Markdown = ({ content }: { content: string }) => {
  return (
    <ReactMarkdown rehypePlugins={[rehypeHighlight]}>{content}</ReactMarkdown>
  );
};

export default Markdown;

App.tsx 文件中引入:

import React, { useState } from "react";
import Markdown from "@/components/Markdown";

function App() {
  const [content, setContent] = useState("");

  const handleInput = (e: React.FormEvent<HTMLTextAreaElement>) => {
    setContent(e.currentTarget.value);
  };

  return (
    <div className="mx-auto max-w-2xl space-y-4 py-4">
      <textarea
        value={content}
        onInput={handleInput}
        rows={8}
        className="block w-full rounded-md border-0 bg-zinc-100 py-1.5 text-zinc-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-inset dark:bg-zinc-900 dark:text-zinc-100 dark:ring-zinc-700 sm:text-sm sm:leading-6"
      />
      <Markdown content={content} />
    </div>
  );
}

export default App;

此时已经完成了最基本的渲染功能,但是没有任何样式。

2023-12-14_17-37-58.png

样式美化

安装 @tailwindcss/typography

pnpm install -D @tailwindcss/typography

tailwind.config.js 中配置插件:

/** @type {import('tailwindcss').Config} */
import Form from "@tailwindcss/forms";
import Typography from "@tailwindcss/typography";
export default {
  darkMode: "class",
  content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [Form, Typography],
};

此外我还安装了 @tailwindcss/forms 插件,只是为了让上面那个 textarea 更好看一些。

修改 src/components/Markdown.tsx文件:

import ReactMarkdown from "react-markdown";
import rehypeHighlight from "rehype-highlight";

import "highlight.js/styles/atom-one-dark.css";

const Markdown = ({ content }: { content: string }) => {
  return (
    <ReactMarkdown
      rehypePlugins={[rehypeHighlight]}
      className="prose prose-zinc max-w-none dark:prose-invert"
    >
      {content}
    </ReactMarkdown>
  );
};

export default Markdown;

添加 class:prose prose-zinc max-w-none dark:prose-invert,然后就实现了样式的美化,并且适配暗黑模式。

2023-12-14_17-48-33.png

2023-12-14_17-49-03.png

代码块复制

安装 lucide-react 图标库:

pnpm install lucide-react

新建 src/components/CopyButton.tsx 文件:

import { Copy, Check } from "lucide-react";
import { useState } from "react";

const CopyButton = ({ id }: { id: string }) => {
  const [copied, setCopited] = useState(false);

  const onCopy = async () => {
    try {
      setCopited(true);
      const text = document.getElementById(id)!.innerText;
      await navigator.clipboard.writeText(text);
      setTimeout(() => {
        setCopited(false);
      }, 1000);
    } catch (error) {
      console.log(error);
    }
  };

  return (
    <button
      onClick={onCopy}
      className="inline-flex rounded-md p-2 hover:bg-zinc-200 dark:hover:bg-zinc-800"
    >
      <Copy
        size={16}
        className={`transition-all
        ${copied ? "scale-0" : "scale-100"}
      `}
      />
      <Check
        size={16}
        className={`absolute transition-all ${
          copied ? "scale-100" : "scale-0"
        }`}
      />
    </button>
  );
};

export default CopyButton;

修改 src/components/Markdown.tsx 文件:

import ReactMarkdown from "react-markdown";
import rehypeHighlight from "rehype-highlight";
import { Terminal } from "lucide-react";

import "highlight.js/styles/atom-one-dark.css";
import CopyButton from "./CopyButton";

const Markdown = ({ content }: { content: string }) => {
  return (
    <ReactMarkdown
      rehypePlugins={[rehypeHighlight]}
      components={{
        pre: ({ children }) => <pre className="not-prose">{children}</pre>,
        code: ({ node, className, children, ...props }) => {
          const match = /language-(\w+)/.exec(className || "");
          if (match?.length) {
            const id = Math.random().toString(36).substr(2, 9);
            return (
              <div className="not-prose rounded-md border">
                <div className="flex h-12 items-center justify-between bg-zinc-100 px-4 dark:bg-zinc-900">
                  <div className="flex items-center gap-2">
                    <Terminal size={18} />
                    <p className="text-sm text-zinc-600 dark:text-zinc-400">
                      {node?.data?.meta}
                    </p>
                  </div>
                  <CopyButton id={id} />
                </div>
                <div className="overflow-x-auto">
                  <div id={id} className="p-4">
                    {children}
                  </div>
                </div>
              </div>
            );
          } else {
            return (
              <code
                {...props}
                className="not-prose rounded bg-gray-100 px-1 dark:bg-zinc-900"
              >
                {children}
              </code>
            );
          }
        },
      }}
      className="prose prose-zinc max-w-2xl dark:prose-invert"
    >
      {content}
    </ReactMarkdown>
  );
};

export default Markdown;

2023-12-14_17-55-54.png

此时就实现了代码块复制的功能,并且点击复制按钮还会有一个动画效果。

  • 15
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
React 中,可以使用第三方库 React Markdown,它可以将 Markdown 格式的文本转换成 HTML 格式,并且支持代码块渲染。 首先,需要安装 React Markdown: ```bash npm install react-markdown ``` 然后,可以在组件中引入 React Markdown,例如: ```jsx import ReactMarkdown from 'react-markdown'; function MyComponent() { const markdownText = ` # Hello, world! Here is a code block: \`\`\`javascript const greeting = "Hello, world!"; console.log(greeting); \`\`\` `; return ( <div> <ReactMarkdown source={markdownText} /> </div> ); } ``` 在上面的例子中,我们将 Markdown 格式的文本作为 `source` 属性传递给 `ReactMarkdown` 组件,并且在文本中使用三个反引号来标记代码块React Markdown 会自动识别代码块,并将其渲染成 `<pre>` 和 `<code>` 标签。 你还可以通过 `renderers` 属性来自定义代码块渲染方式,例如: ```jsx import ReactMarkdown from 'react-markdown'; function MyComponent() { const markdownText = ` # Hello, world! Here is a code block: \`\`\`javascript const greeting = "Hello, world!"; console.log(greeting); \`\`\` `; const CodeBlock = ({ language, value }) => { return ( <pre> <code className={`language-${language}`}>{value}</code> </pre> ); }; return ( <div> <ReactMarkdown source={markdownText} renderers={{ code: CodeBlock }} /> </div> ); } ``` 在上面的例子中,我们定义了一个名为 `CodeBlock` 的组件来自定义代码块渲染方式,并将其作为 `renderers` 属性的值传递给 `ReactMarkdown` 组件。在 `CodeBlock` 组件中,我们使用 `prismjs` 库来高亮代码块,并且根据代码块的语言来添加对应的类名。 希望这能帮助到你。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值