remarkjs / react-markdown

Markdown component for React
https://remarkjs.github.io/react-markdown/
MIT License
13.2k stars 874 forks source link

remark-toc doens't working #757

Closed joisun closed 1 year ago

joisun commented 1 year ago

Initial checklist

Affected packages and versions

"react-markdown": "^8.0.7",

Link to runnable example

No response

Steps to reproduce

I'm writting a ArticleViewer React Component , and used remark-toc as a plugin , expect to generate Table of content for me. But it's not working...

import remarkGfm from 'remark-gfm';
import remarkToc from 'remark-toc';
import rehypeRaw from "rehype-raw"
import ReactMarkdown from 'react-markdown';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import remarkSlug from 'remark-slug';
import Image from 'next/image';
import { useState } from 'react';
import { oneDark, oneLight } from '@/styles/react-syntax-highlighter';
import styles from "./markdown-styles.module.scss"

// 图片加载时的闪耀效果图

function generateShimmer(w: number, h: number, isLight = false) {
  const shimmer = (w: number, h: number) => `
<svg style="opacity: 0.3;" width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <defs>
    <linearGradient id="g">
      <stop stop-color="#333" offset="20%" />
      <stop stop-color="#111" offset="50%" />
      <stop stop-color="#333" offset="70%" />
    </linearGradient>
  </defs>
  <rect width="${w}" height="${h}" fill="#333" />
  <rect id="r" width="${w}" height="${h}" fill="url(#g)" />
  <animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite"  />
</svg>`;

  const toBase64 = (str: string) => window.btoa(str);
  return `data:image/svg+xml;base64,${toBase64(shimmer(w, h))}`;
}
interface ArticleViewerType {
  isLight: boolean // 是否暗色主题
  contentStr: string // markdown 文本
}
const ArticleViewer: React.FC<ArticleViewerType> = ({ isLight, contentStr }) => {
  return (
    <div className={styles['markdown-style']}>
      <ReactMarkdown
        className="markdown-body"
        children={contentStr || ''}
        rehypePlugins={[rehypeRaw]}
        remarkPlugins={[[remarkSlug],[remarkToc,{prefix: 'user-content-',singleTilde:false}],[remarkGfm]]}
        components={{
          code({ node, inline, className, children, ...props }) {
            const match = /language-(\w+)/.exec(className || '');
            return !inline && match
              ? (
              <SyntaxHighlighter
                {...props}
                children={String(children).replace(/\n$/, '')}
                style={(isLight ? oneLight : oneDark) as any}
                language={match[1]}
                PreTag="div"
              />
                )
              : (
              <code {...props} className={className}>
                {children}
              </code>
                );
          },
          p({ node, className, children, ...props }) {
            // const { node } = paragraph
            if ((node.children[0] as any).tagName === 'img') {
              const image = node.children[0] as any;
              const metastring = image.properties.alt;
              const alt = metastring?.replace(/ *\{[^)]*\} */g, '') || ' '; // fix:图片加载失败的时候,不显示默认border https://stackoverflow.com/a/33470333/12261182
              const metaWidth = metastring.match(/{([^}]+)x/);
              const metaHeight = metastring.match(/x([^}]+)}/);
              const width = metaWidth ? metaWidth[1] : '768';
              const height = metaHeight ? metaHeight[1] : '432';
              const isPriority = metastring?.toLowerCase().match('{priority}');
              const hasCaption = metastring?.toLowerCase().includes('{caption:');
              const caption = metastring?.match(/{caption: (.*?)}/)?.pop();

              const [imgSrc, setImgSrc] = useState(image.properties.src);
              const isValidSrc  = /^(?:https?:\/\/|\/|data:image\/[a-z]+;base64,)[^\s]+\.(?:jpg|jpeg|gif|png|bmp)$/.test(imgSrc)

              return (
                <>
                {/* Image 的src 如果是 xxx.assets/image-20230130110529790.png 这种错误的数据结构, 将无法被捕获错误,会报错, 而不会进入到 onError */}
                  <Image
                    src={isValidSrc ? imgSrc : 'data:image/svg+xml;base64,'}
                    width={width}
                    height={height}
                    className="postImg"
                    alt={alt}
                    priority={isPriority}
                    loading="lazy"
                    placeholder="blur"
                    onError={e => setImgSrc(generateShimmer(width, height, isLight))}
                    blurDataURL={generateShimmer(width, height, isLight)}
                  />
                  {hasCaption
                    ? (
                    <div className="caption" aria-label={caption}>
                      {caption}
                    </div>
                      )
                    : null}
                </>
              );
            }
            return <p>{children}</p>;
          },
        }}
      />
    </div>
  );
};

export default ArticleViewer;

Expected behavior

remark-toc work well

Actual behavior

-

Runtime

No response

Package manager

No response

OS

No response

Build and bundle tools

No response

Murderlon commented 1 year ago

Hi, here you can see remark-toc working as expected: https://codesandbox.io/s/confident-volhard-dnnmtz?file=/src/app.tsx

You probably don't have "Table of Contents" in your file.

joisun commented 1 year ago

Hi, here you can see remark-toc working as expected: codesandbox.io/s/confident-volhard-dnnmtz?file=/src/app.tsx

You probably don't have "Table of Contents" in your file.

Okay ,thank you pretty much, I guess I've missed the doc :

options.heading Pattern text of heading to look for (string, default: 'toc|table[ -]of[ -]contents?'). Wrapped in new RegExp('^(' + options.heading + ')$', 'i'), so it’s case-insensitive and matches the whole heading text.

My render content do not contain "Table of Contents", there is only a "[toc]", which can use directlly in Typora.

By the way , Can I do some config to match "[toc]" content ? instead of Headling ?

and What I've tried as a Headling still not work:

...
## [toc]
...

with the headling config {headling:"\\[toc\\]"} or {headling:"\[toc\]"}

Appreciate ! @Murderlon

Murderlon commented 1 year ago

Why are you wrapping toc in []? I would just use toc, and with that: {heading: 'toc'} (no regex).

https://github.com/remarkjs/remark-toc#example-a-different-heading

github-actions[bot] commented 1 year ago

Hi! This was closed. Team: If this was fixed, please add phase/solved. Otherwise, please add one of the no/* labels.

github-actions[bot] commented 1 year ago

Hi! Thanks for reaching out! Because we treat issues as our backlog, we close issues that are questions since they don’t represent a task to be completed.

See our support docs for how and where to ask questions.

Thanks, — bb

joisun commented 1 year ago

Why are you wrapping toc in []? I would just use toc, and with that: {heading: 'toc'} (no regex).

remarkjs/remark-toc#example-a-different-heading

I usually write post on typora, and the TOC rule here: https://support.typora.io/Markdown-Reference/

Enter [toc] and press the Return key to create a “Table of Contents” section. The TOC extracts all headings from the document, and its contents are updated automatically as you add to the document.

so it's convenient for me to copy the content directly without change.

and I've tried succesfully like this: https://codesandbox.io/s/priceless-ben-65h2mc?file=/src/app.tsx

But, is there anyway to match body content instead of Headling?

# match this 
[toc]

# instead of this
## [toc]
Murderlon commented 1 year ago

But, is there anyway to match body content instead of Headling?

No it has to be a heading. But you can fork this plugin and adjust it where needed.

joisun commented 1 year ago

But, is there anyway to match body content instead of Headling?

No it has to be a heading. But you can fork this plugin and adjust it where needed.

Okay, thanks again