当前位置: 首页 > 工具软件 > react-cursor > 使用案例 >

react-markdow常用配置

田仲卿
2023-12-01
import {
  ReactElement,
  useRef,
  useMemo,
  forwardRef,
  useEffect,
  useImperativeHandle,
  useCallback,
} from "react";
import styled from "styled-components";
import ReactMarkdown from "react-markdown";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { tomorrow as style } from "react-syntax-highlighter/dist/esm/styles/prism";
import gfm from "remark-gfm";
import raw from "rehype-raw";
import "github-markdown-css";
import Toc from "./Toc";

interface IProps {
  content: string;
  syncScroll: boolean;
  setEditScroll?: (y: number) => void;
}

const MarkDown = forwardRef<(y: number) => void, IProps>(
  ({ content, syncScroll, setEditScroll }, ref): ReactElement => {
    const wrap = useRef<HTMLDivElement | null>(null);

    const setScrollTop = useCallback((y: number) => {
      //@ts-ignore
      wrap.current.scrollTop = y;
    }, []);

    useImperativeHandle(ref, () => setScrollTop, [setScrollTop]);

    const onScroll = useCallback(
      e => {
        setEditScroll!(e.target.scrollTop);
      },
      [setEditScroll]
    );

    useEffect(() => {
      if (syncScroll) {
        wrap.current?.addEventListener("scroll", onScroll);
      } else {
        wrap.current?.removeEventListener("scroll", onScroll);
      }
      return () => {
        // eslint-disable-next-line react-hooks/exhaustive-deps
        wrap.current?.removeEventListener("scroll", onScroll);
      };
    }, [onScroll, syncScroll]);

    const components = useMemo(
      () => ({
        code({ node, inline, className, children, ...props }: any) {
          const match = /language-(\w+)/.exec(className || "");
          return !inline && match ? (
            <SyntaxHighlighter
              style={style}
              language={match[1]}
              PreTag='div'
              children={String(children).replace(/\n$/, "")}
              {...props}
            />
          ) : (
            <code className={className} {...props}>
              {children}
            </code>
          );
        },
        h1: ({ children }: any) => (
          <h1 className='data-toc' data-toc='1' children={children} />
        ),
        h2: ({ children }: any) => (
          <h2 className='data-toc' data-toc='2' children={children} />
        ),
        h3: ({ children }: any) => (
          <h3 className='data-toc' data-toc='3' children={children} />
        ),
        h4: ({ children }: any) => (
          <h4 className='data-toc' data-toc='4' children={children} />
        ),
        h5: ({ children }: any) => (
          <h5 className='data-toc' data-toc='5' children={children} />
        ),
        h6: ({ children }: any) => (
          <h6 className='data-toc' data-toc='6' children={children} />
        ),
        p: ({ children }: any) =>
          String(children) === "@[toc]" ? (
            <Toc
              HElements={
                wrap.current?.getElementsByClassName("data-toc") as any
              }
            />
          ) : (
            <p children={children} />
          ),
      }),
      []
    );

    return (
      <MDWrap ref={wrap}>
        <ReactMarkdown
          //@ts-ignore
          rehypePlugins={[raw]}
          //@ts-ignore
          remarkPlugins={[gfm]}
          components={components}
        >
          {content}
        </ReactMarkdown>
      </MDWrap>
    );
  }
);

export default MarkDown;

const MDWrap = styled.div.attrs({ className: "markdown-body" })`
  flex: 1;
`;

Toc.tsx

import { FC, ReactElement } from "react";
import styled from "styled-components";

const ElementToLi = (element: HTMLElement, index: number) => {
  const marginLeft =
    (Number(element?.getAttribute("data-toc")) ?? 1) * 10 + "px";
  return (
    <li style={{ marginLeft }} key={index}>
      <span
        onClick={() => {
          element.scrollIntoView();
        }}
      >
        {element.innerText}
      </span>
    </li>
  );
};

const NodeListToToc = (nodeList: Array<HTMLElement>) =>
  nodeList ? Array.from(nodeList, ElementToLi) : [];

interface Props {
  HElements: Array<HTMLElement>;
}

const Toc: FC<Props> = ({ HElements }): ReactElement => {
  return (
    <Container>
      <strong>目录</strong>
      <ul>{NodeListToToc(HElements)}</ul>
    </Container>
  );
};

export default Toc;

const Container = styled.div`
  border-bottom: 1px dashed #ccc;

  ul {
    list-style: none;
    color: #51f;
    font-weight: bold;
    li {
      margin-top: 5px;
    }
    li > span {
      cursor: pointer;
      padding: 5px;
      transition: background-color 0.2s linear;
      border-radius: 2px;
    }
    li > span:hover {
      background-color: #cccccc91;
    }
  }
`;

 类似资料: