uiwjs / react-markdown-preview

React component preview markdown text in web browser. The minimal amount of CSS to replicate the GitHub Markdown style. Support dark-mode/night mode.
https://uiwjs.github.io/react-markdown-preview
MIT License
271 stars 48 forks source link

remarkDirectiveRehype doesn't, but in pure ReactMarkdown does #274

Closed jsilva-js closed 4 days ago

jsilva-js commented 1 week ago

Hello I am with difficulties to comprehend what the @uiwjs react markdown preview wrapper does that can't render my custom directive component


'use client';
import MarkdownPreview from '@uiw/react-markdown-preview';
import remarkDirective from 'remark-directive';
import remarkDirectiveRehype from 'remark-directive-rehype';

import { useChildRefContext } from '@/hooks/note/context/childRef';
import type { INote } from '@/libs/prisma';

import { NoteTagWrapper } from './NoteTagWrapper';
import styles from './styles.module.scss';

const EditorPreview = ({ note }: { note: INote }) => {
  const { childRefs } = useChildRefContext();
  const plugins = [remarkDirective, remarkDirectiveRehype];

  return (
    <MarkdownPreview
      source={note.content}
      style={{ whiteSpace: 'pre-wrap' }}
      remarkPlugins={plugins}
      components={{ 'note-tag': NoteTagWrapper }}
      className={styles.markdown}
    />
  );
};```

export default EditorPreview;

// output is correct except for the directive component 
jaywcjlove commented 1 week ago

@jsilva-js Do you not understand the components props?

import type {Element} from 'hast'

type Components = Partial<{
  [TagName in keyof JSX.IntrinsicElements]:
    // Class component:
    | (new (props: JSX.IntrinsicElements[TagName] & ExtraProps) => JSX.ElementClass)
    // Function component:
    | ((props: JSX.IntrinsicElements[TagName] & ExtraProps) => JSX.Element | string | null | undefined)
    // Tag name:
    | keyof JSX.IntrinsicElements
}>

For example:

<MarkdownPreview
  source={source}
  style={{ padding: 16 }}
  components={{
    code: ({ children = [], className, ...props }) => {
      if (typeof children === 'string' && /^\$\$(.*)\$\$/.test(children)) {
        const html = katex.renderToString(children.replace(/^\$\$(.*)\$\$/, '$1'), {
          throwOnError: false,
        });
        return <code dangerouslySetInnerHTML={{ __html: html }} style={{ background: 'transparent' }} />;
      }
      const code = props.node && props.node.children ? getCodeString(props.node.children) : children;
      if (
        typeof code === 'string' &&
        typeof className === 'string' &&
        /^language-katex/.test(className.toLocaleLowerCase())
      ) {
        const html = katex.renderToString(code, {
          throwOnError: false,
        });
        return <code style={{ fontSize: '150%' }} dangerouslySetInnerHTML={{ __html: html }} />;
      }
      return <code className={String(className)}>{children}</code>;
    },
  }}
/>
jsilva-js commented 4 days ago

I already got it, my solution stayed like this.

I needed to use remark-directive and remark-directive-rehype to create custom complex react components


'use client';
import MarkdownPreview from '@uiw/react-markdown-preview';
import React, { useMemo } from 'react';
import remarkDirective from 'remark-directive';
import remarkDirectiveRehype from 'remark-directive-rehype';

import { useChildRefContext } from '@/hooks/note/context/childRef';
import type { INote } from '@/libs/prisma';

import { NoteTagWrapper } from './directive/NoteTagWrapper';
import styles from './styles.module.scss';

const EditorPreview = ({ note }: { note: INote }) => {
  const plugins = [remarkDirective, remarkDirectiveRehype];
  const { childRefs } = useChildRefContext();

  // Memoize the NoteTag component based on the note prop
  const NoteTag = useMemo(() => NoteTagWrapper(note, childRefs), [note, childRefs]);

  return (
    <MarkdownPreview
      source={note.content}
      style={{ whiteSpace: 'pre-wrap' }}
      remarkPlugins={plugins}
      components={{ notetag: NoteTag }}
      className={styles.markdown}
    />
  );
};

// Memoize EditorPreview to prevent re-renders unless the note changes
const MemoizedEditorPreview = React.memo(EditorPreview, (prevProps, nextProps) => {
  return prevProps.note.id === nextProps.note.id && prevProps.note.content === nextProps.note.content;
});

MemoizedEditorPreview.displayName = 'EditorPreview';

export default MemoizedEditorPreview;