4.0.5 • Published 6 months ago

@uiw/react-md-editor v4.0.5

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

A simple markdown editor with preview, implemented with React.js and TypeScript. This React Component aims to provide a simple Markdown editor with syntax highlighting support. This is based on textarea encapsulation, so it does not depend on any modern code editors such as Acs, CodeMirror, Monaco etc.

Features

  • 📑 Indent line or selected text by pressing tab key, with customizable indentation.
  • ♻️ Based on textarea encapsulation, does not depend on any modern code editors.
  • 🚧 Does not depend on the uiw component library.
  • 🚘 Automatic list on new lines.
  • 😻 GitHub flavored markdown support.
  • 🌒 Support dark-mode/night-mode @v3.11.0+.
  • 💡 Support next.js, Use examples in next.js.
  • Line/lines duplication (Ctrl+D) and movement (Alt+UpArrow/DownArrow) @v3.24.0+.

Quick Start

npm i @uiw/react-md-editor

Using

Open in CodeSandbox Open in Github gh-pages Open in Gitee gh-pages

import React from "react";
import MDEditor from '@uiw/react-md-editor';

export default function App() {
  const [value, setValue] = React.useState("**Hello world!!!**");
  return (
    <div className="container">
      <MDEditor
        value={value}
        onChange={setValue}
      />
      <MDEditor.Markdown source={value} style={{ whiteSpace: 'pre-wrap' }} />
    </div>
  );
}

Special Markdown syntax

Supports for CSS Style

Use HTML comments <!--rehype:xxx--> to let Markdown support style customization.

## Title
<!--rehype:style=display: flex; height: 230px; align-items: center; justify-content: center; font-size: 38px;-->

Markdown Supports **Style**<!--rehype:style=color: red;-->

Ignore content display via HTML comments

Shown in GitHub readme, excluded in HTML.

# Hello World

<!--rehype:ignore:start-->Hello World<!--rehype:ignore:end-->

Good!

Output:

<h1>Hello World</h1>

<p>Good!</p>

Security

Please note markdown needs to be sanitized if you do not completely trust your authors. Otherwise, your app is vulnerable to XSS. This can be achieved by adding rehype-sanitize as a plugin.

import React from "react";
import MDEditor from '@uiw/react-md-editor';
import rehypeSanitize from "rehype-sanitize";

export default function App() {
  const [value, setValue] = React.useState(`**Hello world!!!** <IFRAME SRC=\"javascript:javascript:alert(window.origin);\"></IFRAME>`);
  return (
    <div className="container">
      <MDEditor
        value={value}
        onChange={setValue}
        previewOptions={{
          rehypePlugins: [[rehypeSanitize]],
        }}
      />
    </div>
  );
}

Remove Code Highlight

The following example can help you exclude code highlighting code from being included in the bundle. @uiw/react-md-editor/nohighlight component does not contain the rehype-prism-plus code highlighting package, highlightEnable, showLineNumbers and highlight line functions will no longer work. (#586)

import React from "react";
import MDEditor from '@uiw/react-md-editor/nohighlight';

const code = `**Hello world!!!**
\`\`\`js
function demo() {}
\`\`\`
`

export default function App() {
  const [value, setValue] = React.useState(code);
  return (
    <div className="container">
      <MDEditor
        value={value}
        onChange={setValue}
      />
      <MDEditor.Markdown source={value} style={{ whiteSpace: 'pre-wrap' }} />
    </div>
  );
}

Placeholder & maxLength

"Below is an example that sets the placeholder for the editor and defines the maximum input character length as 10 characters."

import React from "react";
import MDEditor from '@uiw/react-md-editor';

export default function App() {
  const [value, setValue] = React.useState("");
  return (
      <MDEditor
        value={value}
        onChange={setValue}
        textareaProps={{
          placeholder: 'Please enter Markdown text',
          maxLength: 10
        }}
      />
  );
}

Custom Toolbars

Open in CodeSandbox

import React, { useState } from "react";
import MDEditor, { commands } from '@uiw/react-md-editor';

const title3 = {
  name: 'title3',
  keyCommand: 'title3',
  buttonProps: { 'aria-label': 'Insert title3' },
  icon: (
    <svg width="12" height="12" viewBox="0 0 520 520">
      <path fill="currentColor" d="M15.7083333,468 C7.03242448,468 0,462.030833 0,454.666667 L0,421.333333 C0,413.969167 7.03242448,408 15.7083333,408 L361.291667,408 C369.967576,408 377,413.969167 377,421.333333 L377,454.666667 C377,462.030833 369.967576,468 361.291667,468 L15.7083333,468 Z M21.6666667,366 C9.69989583,366 0,359.831861 0,352.222222 L0,317.777778 C0,310.168139 9.69989583,304 21.6666667,304 L498.333333,304 C510.300104,304 520,310.168139 520,317.777778 L520,352.222222 C520,359.831861 510.300104,366 498.333333,366 L21.6666667,366 Z M136.835938,64 L136.835937,126 L107.25,126 L107.25,251 L40.75,251 L40.75,126 L-5.68434189e-14,126 L-5.68434189e-14,64 L136.835938,64 Z M212,64 L212,251 L161.648438,251 L161.648438,64 L212,64 Z M378,64 L378,126 L343.25,126 L343.25,251 L281.75,251 L281.75,126 L238,126 L238,64 L378,64 Z M449.047619,189.550781 L520,189.550781 L520,251 L405,251 L405,64 L449.047619,64 L449.047619,189.550781 Z" />
    </svg>
  ),
  execute: (state, api) => {
    let modifyText = `### ${state.selectedText}\n`;
    if (!state.selectedText) {
      modifyText = `### `;
    }
    api.replaceSelection(modifyText);
  },
};

const title2 = {
  name: 'title2',
  keyCommand: 'title2',
  render: (command, disabled, executeCommand) => {
    return (
      <button 
        aria-label="Insert title2"
        disabled={disabled}
        onClick={(evn) => {
          // evn.stopPropagation();
          executeCommand(command, command.groupName)
        }}
      >
        <svg width="12" height="12" viewBox="0 0 520 520">
          <path fill="currentColor" d="M15.7083333,468 C7.03242448,468 0,462.030833 0,454.666667 L0,421.333333 C0,413.969167 7.03242448,408 15.7083333,408 L361.291667,408 C369.967576,408 377,413.969167 377,421.333333 L377,454.666667 C377,462.030833 369.967576,468 361.291667,468 L15.7083333,468 Z M21.6666667,366 C9.69989583,366 0,359.831861 0,352.222222 L0,317.777778 C0,310.168139 9.69989583,304 21.6666667,304 L498.333333,304 C510.300104,304 520,310.168139 520,317.777778 L520,352.222222 C520,359.831861 510.300104,366 498.333333,366 L21.6666667,366 Z M136.835938,64 L136.835937,126 L107.25,126 L107.25,251 L40.75,251 L40.75,126 L-5.68434189e-14,126 L-5.68434189e-14,64 L136.835938,64 Z M212,64 L212,251 L161.648438,251 L161.648438,64 L212,64 Z M378,64 L378,126 L343.25,126 L343.25,251 L281.75,251 L281.75,126 L238,126 L238,64 L378,64 Z M449.047619,189.550781 L520,189.550781 L520,251 L405,251 L405,64 L449.047619,64 L449.047619,189.550781 Z" />
        </svg>
      </button>
    )
  },
  execute: (state, api) => {
    let modifyText = `## ${state.selectedText}\n`;
    if (!state.selectedText) {
      modifyText = `## `;
    }
    api.replaceSelection(modifyText);
  },
}

function SubChildren({ close, execute, getState, textApi, dispatch }) {
  const [value, setValue] = useState('')
  const insert = () => {
    console.log('value:::', value)
    textApi.replaceSelection(value)
  }
  return (
    <div style={{ width: 120, padding: 10 }}>
      <div>My Custom Toolbar</div>
      <input type="text" onChange={(e) => setValue(e.target.value)} />
      <button
        type="button"
        onClick={() => {
          dispatch({ $value: '~~~~~~' })
          console.log('> execute: >>>>>', getState())
        }}
      >
        State
      </button>
      <button type="button" onClick={insert}>Insert</button>
      <button type="button" onClick={() => close()}>Close</button>
      <button type="button" onClick={() => execute()}>Execute</button>
    </div>
  );
}

const subChild = {
  name: 'update',
  groupName: 'update',
  icon: (
    <svg viewBox="0 0 1024 1024" width="12" height="12">
      <path fill="currentColor" d="M716.8 921.6a51.2 51.2 0 1 1 0 102.4H307.2a51.2 51.2 0 1 1 0-102.4h409.6zM475.8016 382.1568a51.2 51.2 0 0 1 72.3968 0l144.8448 144.8448a51.2 51.2 0 0 1-72.448 72.3968L563.2 541.952V768a51.2 51.2 0 0 1-45.2096 50.8416L512 819.2a51.2 51.2 0 0 1-51.2-51.2v-226.048l-57.3952 57.4464a51.2 51.2 0 0 1-67.584 4.2496l-4.864-4.2496a51.2 51.2 0 0 1 0-72.3968zM512 0c138.6496 0 253.4912 102.144 277.1456 236.288l10.752 0.3072C924.928 242.688 1024 348.0576 1024 476.5696 1024 608.9728 918.8352 716.8 788.48 716.8a51.2 51.2 0 1 1 0-102.4l8.3968-0.256C866.2016 609.6384 921.6 550.0416 921.6 476.5696c0-76.4416-59.904-137.8816-133.12-137.8816h-97.28v-51.2C691.2 184.9856 610.6624 102.4 512 102.4S332.8 184.9856 332.8 287.488v51.2H235.52c-73.216 0-133.12 61.44-133.12 137.8816C102.4 552.96 162.304 614.4 235.52 614.4l5.9904 0.3584A51.2 51.2 0 0 1 235.52 716.8C105.1648 716.8 0 608.9728 0 476.5696c0-132.1984 104.8064-239.872 234.8544-240.2816C258.5088 102.144 373.3504 0 512 0z" />
    </svg>
  ),
  children: (props) => <SubChildren {...props} />,
  execute: (state, api)  => {
    console.log('>>>>>>update>>>>>', state)
  },
  buttonProps: { 'aria-label': 'Insert title'}
}

export default function App() {
  const [value, setValue] = React.useState("Hello Markdown! `Tab` key uses default behavior");
  return (
    <div className="container">
      <MDEditor
        value={value}
        onChange={setValue}
        commands={[
          // Custom Toolbars
          title3, title2,
          commands.group([commands.title1, commands.title2, commands.title3, commands.title4, commands.title5, commands.title6], {
            name: 'title',
            groupName: 'title',
            buttonProps: { 'aria-label': 'Insert title'}
          }),
          commands.divider,
          commands.group([], subChild),
        ]}
      />
    </div>
  );
}

Customize the toolbar with commands and extraCommands props.

import React from "react";
import MDEditor, { commands } from '@uiw/react-md-editor';

export default function App() {
  const [value, setValue] = React.useState("Hello Markdown! `Tab` key uses default behavior");
  return (
    <div className="container">
      <MDEditor
        value={value}
        onChange={setValue}
        preview="edit"
        commands={[
          commands.codeEdit, commands.codePreview
        ]}
        extraCommands={[
          commands.group([commands.title1, commands.title2, commands.title3, commands.title4, commands.title5, commands.title6], {
            name: 'title',
            groupName: 'title',
            buttonProps: { 'aria-label': 'Insert title'}
          }),
          commands.divider,
          commands.group([], {
            name: 'update',
            groupName: 'update',
            icon: (
              <svg viewBox="0 0 1024 1024" width="12" height="12">
                <path fill="currentColor" d="M716.8 921.6a51.2 51.2 0 1 1 0 102.4H307.2a51.2 51.2 0 1 1 0-102.4h409.6zM475.8016 382.1568a51.2 51.2 0 0 1 72.3968 0l144.8448 144.8448a51.2 51.2 0 0 1-72.448 72.3968L563.2 541.952V768a51.2 51.2 0 0 1-45.2096 50.8416L512 819.2a51.2 51.2 0 0 1-51.2-51.2v-226.048l-57.3952 57.4464a51.2 51.2 0 0 1-67.584 4.2496l-4.864-4.2496a51.2 51.2 0 0 1 0-72.3968zM512 0c138.6496 0 253.4912 102.144 277.1456 236.288l10.752 0.3072C924.928 242.688 1024 348.0576 1024 476.5696 1024 608.9728 918.8352 716.8 788.48 716.8a51.2 51.2 0 1 1 0-102.4l8.3968-0.256C866.2016 609.6384 921.6 550.0416 921.6 476.5696c0-76.4416-59.904-137.8816-133.12-137.8816h-97.28v-51.2C691.2 184.9856 610.6624 102.4 512 102.4S332.8 184.9856 332.8 287.488v51.2H235.52c-73.216 0-133.12 61.44-133.12 137.8816C102.4 552.96 162.304 614.4 235.52 614.4l5.9904 0.3584A51.2 51.2 0 0 1 235.52 716.8C105.1648 716.8 0 608.9728 0 476.5696c0-132.1984 104.8064-239.872 234.8544-240.2816C258.5088 102.144 373.3504 0 512 0z" />
              </svg>
            ),
            children: ({ close, execute, getState, textApi }) => {
              return (
                <div style={{ width: 120, padding: 10 }}>
                  <div>My Custom Toolbar</div>
                  <button type="button" onClick={() => console.log('> execute: >>>>>', getState())}>State</button>
                  <button type="button" onClick={() => close()}>Close</button>
                  <button type="button" onClick={() => execute()}>Execute</button>
                </div>
              );
            },
            execute: (state, api)  => {
              console.log('>>>>>>update>>>>>', state)
            },
            buttonProps: { 'aria-label': 'Insert title'}
          }),
          commands.divider, commands.fullscreen
        ]}
      />
    </div>
  );
}

re-render toolbar element.

import React from "react";
import MDEditor, { commands } from '@uiw/react-md-editor';

export default function App() {
  const [value, setValue] = React.useState("Hello Markdown! `Tab` key uses default behavior");
  return (
    <div className="container">
      <MDEditor
        value={value}
        onChange={setValue}
        preview="edit"
        components={{
          toolbar: (command, disabled, executeCommand) => {
            if (command.keyCommand === 'code') {
              return (
                <button 
                  aria-label="Insert code"
                  disabled={disabled}
                  onClick={(evn) => {
                    evn.stopPropagation();
                    executeCommand(command, command.groupName)
                  }}
                >
                  Code
                </button>
              )
            }
          }
        }}
      />
    </div>
  );
}

Custom Preview Command Tool

Open in CodeSandbox

import React, { useContext } from "react";
import MDEditor, { commands, EditorContext } from "@uiw/react-md-editor";

const Button = () => {
  const { preview, dispatch } = useContext(EditorContext);
  const click = () => {
    dispatch({
      preview: preview === "edit" ? "preview" : "edit"
    });
  };
  if (preview === "edit") {
    return (
      <svg width="12" height="12" viewBox="0 0 520 520" onClick={click}>
        <polygon
          fill="currentColor"
          points="0 71.293 0 122 319 122 319 397 0 397 0 449.707 372 449.413 372 71.293"
        />
        <polygon
          fill="currentColor"
          points="429 71.293 520 71.293 520 122 481 123 481 396 520 396 520 449.707 429 449.413"
        />
      </svg>
    );
  }
  return (
    <svg width="12" height="12" viewBox="0 0 520 520" onClick={click}>
      <polygon
        fill="currentColor"
        points="0 71.293 0 122 38.023 123 38.023 398 0 397 0 449.707 91.023 450.413 91.023 72.293"
      />
      <polygon
        fill="currentColor"
        points="148.023 72.293 520 71.293 520 122 200.023 124 200.023 397 520 396 520 449.707 148.023 450.413"
      />
    </svg>
  );
};

const codePreview = {
  name: "preview",
  keyCommand: "preview",
  value: "preview",
  icon: <Button />
};

const Disable = () => {
  const { preview, dispatch } = useContext(EditorContext);
  return (
    <button disabled={preview === "preview"}>
      <svg viewBox="0 0 16 16" width="12px" height="12px">
        <path
          d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8Zm.9 13H7v-1.8h1.9V13Zm-.1-3.6v.5H7.1v-.6c.2-2.1 2-1.9 1.9-3.2.1-.7-.3-1.1-1-1.1-.8 0-1.2.7-1.2 1.6H5c0-1.7 1.2-3 2.9-3 2.3 0 3 1.4 3 2.3.1 2.3-1.9 2-2.1 3.5Z"
          fill="currentColor"
        />
      </svg>
    </button>
  )
}

const customButton = {
  name: "disable",
  keyCommand: "disable",
  value: "disable",
  icon: <Disable />
}

export default function App() {
  const [value, setValue] = React.useState("**Hello world!!!**");
  return (
    <div className="container">
      <div>The system automatically sets the theme</div>
      <MDEditor
        value={value}
        preview="edit"
        extraCommands={[codePreview, customButton, commands.fullscreen]}
        onChange={(val) => setValue(val)}
      />
    </div>
  );
}

Add Help Command Tool

Open in CodeSandbox

import React, { useContext } from "react";
import MDEditor, { commands } from "@uiw/react-md-editor";

const help = {
  name: "help",
  keyCommand: "help",
  buttonProps: { "aria-label": "Insert help" },
  icon: (
    <svg viewBox="0 0 16 16" width="12px" height="12px">
      <path
        d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8Zm.9 13H7v-1.8h1.9V13Zm-.1-3.6v.5H7.1v-.6c.2-2.1 2-1.9 1.9-3.2.1-.7-.3-1.1-1-1.1-.8 0-1.2.7-1.2 1.6H5c0-1.7 1.2-3 2.9-3 2.3 0 3 1.4 3 2.3.1 2.3-1.9 2-2.1 3.5Z"
        fill="currentColor"
      />
    </svg>
  ),
  execute: (state, api) => {
    window.open("https://www.markdownguide.org/basic-syntax/", "_blank");
  }
};

export default function App() {
  const [value, setValue] = React.useState("**Hello world!!!**");
  return (
    <MDEditor
      value={value}
      preview="edit"
      commands={[...commands.getCommands(), help]}
      onChange={(val) => setValue(val)}
    />
  );
}

Internationalization Example, You can refer to commands-cn for internationalization.

import React, { useContext } from "react";
import MDEditor, { commands } from "@uiw/react-md-editor";
import { getCommands, getExtraCommands } from "@uiw/react-md-editor/commands-cn";

export default function App() {
  const [value, setValue] = React.useState("**Hello world!!!**");
  return (
    <MDEditor
      value={value}
      preview="edit"
      commands={[...getCommands()]}
      extraCommands={[...getExtraCommands()]}
      onChange={(val) => setValue(val)}
    />
  );
}

Editor Font Size

Open in CodeSandbox #425

body .w-md-editor-text-pre > code,
body .w-md-editor-text-input {
  font-size: 23px !important;
  line-height: 24px !important;
}

Editor height adapts to text

The initial height can be adjusted through minHeight={100}. Dragbar will automatically expire. You can hide the drag button through visibleDragbar={false}

import React from "react";
import MDEditor from '@uiw/react-md-editor';

export default function App() {
  const [value, setValue] = React.useState("**Hello world!!!**");
  return (
    <div className="container">
      <MDEditor
        value={value}
        height="100%"
        // minHeight={50}
        visibleDragbar={false}
        onChange={setValue}
      />
    </div>
  );
}

Disallowed Elements

import React from "react";
import MDEditor from '@uiw/react-md-editor';

export default function App() {
  const [value, setValue] = React.useState("**Hello world!!!**  <style>body{display:none;}</style> ");
  return (
    <div className="container">
      <MDEditor
        value={value}
        height="100%"
        previewOptions={{
          disallowedElements: ['style'],
        }}
        visibleDragbar={false}
        onChange={setValue}
      />
    </div>
  );
}

Preview Markdown

Open in CodeSandbox

import React from "react";
import MDEditor from '@uiw/react-md-editor';

export default function App() {
  return (
    <div className="container">
      <MDEditor.Markdown source="Hello Markdown!" />
    </div>
  );
}

Support Custom KaTeX Preview

KaTeX is a fast, easy-to-use JavaScript library for TeX math rendering on the web, We perform math rendering through KaTeX.

The following example is preview in CodeSandbox.

Open in CodeSandbox

⚠️ Upgrade v2 to v3 d025430

npm install katex
import React from "react";
import MDEditor from '@uiw/react-md-editor';
import { getCodeString } from 'rehype-rewrite';
import katex from 'katex';
import 'katex/dist/katex.css';

const mdKaTeX = `This is to display the 
\`\$\$\c = \\pm\\sqrt{a^2 + b^2}\$\$\`
 in one line

\`\`\`KaTeX
c = \\pm\\sqrt{a^2 + b^2}
\`\`\`
`;

export default function App() {
  const [value, setValue] = React.useState(mdKaTeX);
  return (
    <MDEditor
      value={value}
      onChange={(val) => setValue(val)}
      previewOptions={{
        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>;
          },
        },
      }}
    />
  );
}

Markdown text to Image

Open in CodeSandbox

import React, { useState } from "react";
import MDEditor, { commands, ICommand, TextState, TextAreaTextApi } from "@uiw/react-md-editor";
import domToImage from "dom-to-image";

const textToImage: ICommand = {
  name: "Text To Image",
  keyCommand: "text2image",
  buttonProps: { "aria-label": "Insert title3" },
  icon: (
    <svg width="12" height="12" viewBox="0 0 20 20">
      <path fill="currentColor" d="M15 9c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm4-7H1c-.55 0-1 .45-1 1v14c0 .55.45 1 1 1h18c.55 0 1-.45 1-1V3c0-.55-.45-1-1-1zm-1 13l-6-5-2 2-4-5-4 8V4h16v11z" ></path>
    </svg>
  ),
  execute: (state: TextState, api: TextAreaTextApi) => {
    const dom = document.getElementsByClassName("gooooooooo")[0];
    if (dom) {
      domToImage.toJpeg(dom, {}).then((dataUrl) => {
        const link = document.createElement("a");
        link.download = "image.jpg";
        link.href = dataUrl;
        link.click();
      });
    }
  }
};

export default function App() {
  const [value, setValue] = useState('**Hello world!!!**');
  console.log('value::', value)
  return (
    <div className="container">
      <MDEditor
        className="gooooooooo"
        onChange={(newValue = "") => setValue(newValue)}
        value={value}
        commands={[
          textToImage,
          commands.divider
        ]}
      />
    </div>
  );
}

Support Custom Mermaid Preview

Using mermaid to generation of diagram and flowchart from text in a similar manner as markdown

Open in CodeSandbox

npm install mermaid
import React, { useState, useRef, useEffect, Fragment, useCallback } from "react";
import MDEditor from "@uiw/react-md-editor";
import { getCodeString } from 'rehype-rewrite';
import mermaid from "mermaid";

const mdMermaid = `The following are some examples of the diagrams, charts and graphs that can be made using Mermaid and the Markdown-inspired text specific to it. 

\`\`\`mermaid
graph TD
A[Hard] -->|Text| B(Round)
B --> C{Decision}
C -->|One| D[Result 1]
C -->|Two| E[Result 2]
\`\`\`

\`\`\`mermaid
sequenceDiagram
Alice->>John: Hello John, how are you?
loop Healthcheck
    John->>John: Fight against hypochondria
end
Note right of John: Rational thoughts!
John-->>Alice: Great!
John->>Bob: How about you?
Bob-->>John: Jolly good!
\`\`\`
`;

const randomid = () => parseInt(String(Math.random() * 1e15), 10).toString(36);
const Code = ({ inline, children = [], className, ...props }) => {
  const demoid = useRef(`dome${randomid()}`);
  const [container, setContainer] = useState(null);
  const isMermaid =
    className && /^language-mermaid/.test(className.toLocaleLowerCase());
  const code = children
    ? getCodeString(props.node.children)
    : children[0] || "";

  useEffect(() => {
    if (container && isMermaid && demoid.current && code) {
      mermaid
        .render(demoid.current, code)
        .then(({ svg, bindFunctions }) => {
          container.innerHTML = svg;
          if (bindFunctions) {
            bindFunctions(container);
          }
        })
        .catch((error) => {
          console.log("error:", error);
        });
    }
  }, [container, isMermaid, code, demoid]);

  const refElement = useCallback((node) => {
    if (node !== null) {
      setContainer(node);
    }
  }, []);

  if (isMermaid) {
    return (
      <Fragment>
        <code id={demoid.current} style={{ display: "none" }} />
        <code className={className} ref={refElement} data-name="mermaid" />
      </Fragment>
    );
  }
  return <code className={className}>{children}</code>;
};

export default function App() {
  const [value, setValue] = useState(mdMermaid);
  return (
    <MDEditor
      onChange={(newValue = "") => setValue(newValue)}
      textareaProps={{
        placeholder: "Please enter Markdown text"
      }}
      height={500}
      value={value}
      previewOptions={{
        components: {
          code: Code
        }
      }}
    />
  );
}

Support Nextjs

Use examples in nextjs. #52 #224

Open in CodeSandbox Open in CodeSandbox Open in StackBlitz #52 #224

npm install next-remove-imports
npm install @uiw/react-md-editor@v3.6.0
// next.config.js
const removeImports = require('next-remove-imports')();
module.exports = removeImports({});
import "@uiw/react-md-editor/markdown-editor.css";
import "@uiw/react-markdown-preview/markdown.css";
import dynamic from "next/dynamic";
import { useState } from "react";

import * as commands from "@uiw/react-md-editor/commands"

const MDEditor = dynamic(
  () => import("@uiw/react-md-editor"),
  { ssr: false }
);

function HomePage() {
  const [value, setValue] = useState("**Hello world!!!**");
  return (
    <div>
      <MDEditor value={value} onChange={setValue} />
    </div>
  );
}

export default HomePage;

Support dark-mode/night-mode

By default, the dark-mode is automatically switched according to the system. If you need to switch manually, just set the data-color-mode="dark" parameter for body.

<html data-color-mode="dark">
document.documentElement.setAttribute('data-color-mode', 'dark')
document.documentElement.setAttribute('data-color-mode', 'light')

Inherit custom color variables by adding .wmde-markdown-var selector. Setting theme styles with data-color-mode="light".

<div data-color-mode="light">
  <div className="wmde-markdown-var"> </div>
  <MDEditor source="Hello World!" />
</div>

Props

  • value: string: The Markdown value.
  • onChange?: (value?: string, event?: React.ChangeEvent<HTMLTextAreaElement>, state?: ContextStore): Event handler for the onChange event.
  • onHeightChange?: ((value?: CSSProperties['height'], oldValue?: CSSProperties['height'], state?: ContextStore): editor height change listener.
  • onStatistics?: (data: Statistics) => void; Some data on the statistics editor.
  • commands?: ICommand[]: An array of ICommand, which, each one, contain a commands property. If no commands are specified, the default will be used. Commands are explained in more details below.
  • commandsFilter?: (command: ICommand, isExtra: boolean) => false | ICommand: Filter or modify your commands.
  • extraCommands?: ICommand[]: Displayed on the right side of the toolbar.
  • autoFocus?: true: Can be used to make Markdown Editor focus itself on initialization.
  • previewOptions?: ReactMarkdown.ReactMarkdownProps: This is reset @uiw/react-markdown-preview settings.
  • textareaProps?: TextareaHTMLAttributes: Set the textarea related props.
  • renderTextarea?: (props, opts) => JSX.Element;: @deprecated Please use renderTextarea -> components. Use div to replace TextArea or re-render TextArea. #193
  • components: re-render textarea/toolbar element. #419
    • textarea Use div to replace TextArea or re-render TextArea
    • toolbar Override the default command element. toolbar < command[].render
    • preview Custom markdown preview. #429
  • height?: number=200: The height of the editor. ️⚠️ Dragbar is invalid when height parameter percentage.
  • visibleDragbar?: boolean=true: Show drag and drop tool. Set the height of the editor.
  • highlightEnable?: boolean=true: Disable editing area code highlighting. The value is false, which increases the editing speed.
  • fullscreen?: boolean=false: Show markdown preview.
  • overflow?: boolean=true: Disable fullscreen setting body styles
  • preview?: 'live' | 'edit' | 'preview': Default value live, Show markdown preview.
  • maxHeight?: number=1200: Maximum drag height. The visibleDragbar=true value is valid.
  • minHeights?: number=100: Minimum drag height. The visibleDragbar=true value is valid.
  • tabSize?: number=2: The number of characters to insert when pressing tab key. Default 2 spaces.
  • defaultTabEnable?: boolean=false: If false, the tab key inserts a tab character into the textarea. If true, the tab key executes default behavior e.g. focus shifts to next element.
  • hideToolbar?: boolean=false: Option to hide the tool bar.
  • enableScroll?: boolean=true: Whether to enable scrolling.

Development

  1. Install dependencies
$ npm install       # Installation dependencies
$ npm run build     # Compile all package
  1. Development @uiw/react-md-editor package:
$ cd core
# listen to the component compile and output the .js file
# listen for compilation output type .d.ts file
$ npm run watch # Monitor the compiled package `@uiw/react-md-editor`
  1. Launch documentation site
npm run start

Related

Contributors

As always, thanks to our amazing contributors!

Made with contributors.

License

Licensed under the MIT License.

code-documenter-client@infinitebrahmanuniverse/nolb-_uistrapi-plugin-wysiwsg-react-md-editor--img-issue-fixed@everything-registry/sub-chunk-963components-react-julsebtakeizi-cmstemplate-viewer5strapi-api-formstg-design@concordant/c-markdown-editor@cygnetops-cli/local-client@collectionscms/collections@camberi/firecms@blocklet/discuss-kit@canmorenote/local-clientjavascript-versekaiban-boardra-extstrapi-plugin-wysiwsg-react-md-editorstrapi-plugin-wysiwyg-react-md-editorstrapi-plugin-open-ai-embeddingssuperfastcmssupersonic-dashboard-sdk@akord/ui@cjnote/local-client@chenshuai2144/md-to-json-schema@cli_jsnote_test/local-client@codepadjs/local-client@codenail/local-client@cody-io/local-client@codingcatdev/markdown-convert@codingcatdev/plugin-markdown-convert@did-comment/reactyoho-medusa-plugin-ultimatezui-bundle-testunigov-sim@dxs-ts/eveli-ide@greg-online-editor/local-client@hackstrap/blink-npm@harrisfauntleroy/design-system@handcodebook/local-client@hocgin/gin-markdown@flowerforce/builder@edu-jsnote/local-client@frc-web-components/react-dashboard@instinct-prj/admin@instinct-prj/admin-rp@kurtaking/backstage-plugin-announcements@loudrr-app/widget@lentil-jsx/local-client@itecgo/blocks@mh-react-notebook/local-client@maticoapp/matico_components@nb-react-course/jsnote-local-client@edgeking810/react-showcase@k-phoen/backstage-plugin-announcements@j-book/local-client@javascript-code-book-pm/local-client@js-notepad/local-client@jsnotbook/local-client@jsnote-cygnetops/local-client@jsnote-d/local-client@jsnote-delanoi/local-client@jsnotesj/local-client@jsnotesj02/local-client@jsnoteweb/local-client@jsnote_dang_nguyen/local-client@jsnoteapp/local-client@jsnote-slavyana/local-client@jsnote-swap/local-client@jsnote-tungdv123/local-client@jsnote-victor/local-client@js-sketcher/local-client@js-run-in-browser/local-client@jsnote-oa/local-client@jsnote-rick/local-client@jsnote-shend/local-client@jsnote-jft/local-client@jsnote-lester/local-client@jsnote-logan/local-client@jsnote-file/local-client@js-handynotes/local-client@jscode-book/local-client@jscodeboard/local-client@jsbook-trial/local-client@jsketch/local-client@jsjotta/jsjotta-client@jbook/shared-components@jbook-jsnote/local-client@jsxnb/client@kevin-react-training/local-client@rdementev/heaven-help@procore-oss/backstage-plugin-announcements@rkrohne/jbook-local-client@rj-jbook/local-client@pocket-studio/local-client@nucleus-cms/components@notasjs/local-client@spotify/backstage-plugin-soundcheck@sjts/ampx-frontend
4.0.5

6 months ago

4.0.4

1 year ago

4.0.3

1 year ago

4.0.2

1 year ago

3.24.0

2 years ago

4.0.1

2 years ago

4.0.0

2 years ago

3.24.1

2 years ago

3.25.1

2 years ago

3.25.0

2 years ago

3.25.3

2 years ago

3.25.2

2 years ago

3.25.5

2 years ago

3.25.4

2 years ago

3.25.6

2 years ago

3.23.5

2 years ago

3.23.4

2 years ago

3.23.6

2 years ago

3.21.1

2 years ago

3.21.0

2 years ago

3.22.0

2 years ago

3.23.1

2 years ago

3.23.0

2 years ago

3.23.3

2 years ago

3.23.2

2 years ago

3.20.6

2 years ago

3.20.8

2 years ago

3.20.7

2 years ago

3.20.9

2 years ago

3.20.10

2 years ago

3.20.2

2 years ago

3.20.4

2 years ago

3.20.3

2 years ago

3.20.5

2 years ago

3.20.0

2 years ago

3.20.1

2 years ago

3.19.8

3 years ago

3.3.8

3 years ago

3.18.3

3 years ago

3.18.2

3 years ago

3.19.4

3 years ago

3.19.3

3 years ago

3.19.6

3 years ago

3.19.5

3 years ago

3.19.7

3 years ago

3.19.0

3 years ago

3.19.2

3 years ago

3.19.1

3 years ago

3.18.1

3 years ago

3.18.0

3 years ago

3.17.0

3 years ago

3.17.2

3 years ago

3.17.1

3 years ago

3.15.0

3 years ago

3.16.0

3 years ago

3.14.5

3 years ago

3.14.3

3 years ago

3.14.2

3 years ago

3.14.4

3 years ago

3.14.1

3 years ago

3.14.0

3 years ago

3.12.1

3 years ago

3.12.0

3 years ago

3.13.0

3 years ago

3.12.3

3 years ago

3.12.2

3 years ago

3.11.3

3 years ago

3.11.2

3 years ago

3.10.1

3 years ago

3.10.0

3 years ago

3.10.2

3 years ago

3.11.0

3 years ago

3.11.1

3 years ago

3.9.9

3 years ago

3.9.8

3 years ago

3.9.3

3 years ago

3.9.2

3 years ago

3.9.7

3 years ago

3.9.6

3 years ago

3.9.5

3 years ago

3.9.4

3 years ago

3.6.6

4 years ago

3.9.1

3 years ago

3.9.0

3 years ago

3.8.0

4 years ago

3.8.4

4 years ago

3.8.3

4 years ago

3.8.2

4 years ago

3.8.1

4 years ago

3.8.5

4 years ago

3.7.0

4 years ago

3.6.5

4 years ago

3.6.4

4 years ago

3.6.3

4 years ago

3.6.2

4 years ago

3.6.1

4 years ago

3.6.0

4 years ago

3.5.0

4 years ago

3.4.14

4 years ago

3.4.13

4 years ago

3.4.12

4 years ago

3.4.11

4 years ago

3.4.10

4 years ago

3.4.8

4 years ago

3.4.9

4 years ago

3.4.7

4 years ago

3.4.6

4 years ago

3.4.5

4 years ago

3.4.4

4 years ago

3.4.3

4 years ago

3.4.0

4 years ago

3.4.2

4 years ago

3.4.1

4 years ago

3.3.7

4 years ago

3.3.6

4 years ago

3.3.5

4 years ago

3.3.4

4 years ago

3.3.3

4 years ago

3.3.2

4 years ago

3.2.0

4 years ago

3.3.1

4 years ago

3.3.0

4 years ago

3.0.4

4 years ago

3.0.3

4 years ago

3.0.10

4 years ago

3.0.2

4 years ago

3.0.1

4 years ago

3.0.8

4 years ago

3.0.7

4 years ago

3.0.6

4 years ago

3.0.5

4 years ago

3.0.0

4 years ago

2.1.11

4 years ago

3.0.0-beta.7

4 years ago

3.0.0-beta.8

4 years ago

3.0.9

4 years ago

3.1.1

4 years ago

3.1.0

4 years ago

3.0.0-beta.6

4 years ago

3.0.0-beta.5

4 years ago

3.0.0-beta.4

4 years ago

3.0.0-beta.3

4 years ago

3.0.0-beta.2

4 years ago

3.0.0-beta.1

4 years ago

2.1.10

4 years ago

2.1.9

4 years ago

2.1.8

4 years ago

2.1.7

4 years ago

2.1.4

4 years ago

2.1.6

4 years ago

2.1.5

4 years ago

2.1.3

4 years ago

2.1.2

4 years ago

2.1.1

4 years ago

2.1.0

4 years ago

2.0.3

5 years ago

2.0.2

5 years ago

2.0.1

5 years ago

2.0.0

5 years ago

1.14.7

5 years ago

1.14.6

5 years ago

1.14.5

5 years ago

1.14.4

5 years ago

1.14.3

5 years ago

1.14.2

5 years ago

1.14.1

5 years ago

1.14.0

5 years ago

1.13.3

5 years ago

1.13.2

5 years ago

1.13.1

5 years ago

1.12.2

6 years ago

1.12.1

6 years ago

1.12.0

6 years ago

1.11.4

6 years ago

1.11.3

6 years ago

1.11.0

6 years ago

1.10.1

6 years ago

1.10.0

6 years ago

1.9.2

6 years ago

1.9.0

6 years ago

1.8.2

6 years ago

1.8.1

6 years ago

1.8.0

6 years ago

1.7.0

6 years ago

1.6.7

6 years ago

1.6.6

6 years ago

1.6.5

6 years ago

1.6.4

6 years ago

1.6.3

6 years ago

1.6.2

6 years ago

1.6.1

6 years ago

1.6.0

6 years ago

1.5.0

6 years ago

1.4.0

6 years ago

1.3.2

6 years ago

1.3.1

6 years ago

1.3.0

6 years ago

1.2.8

6 years ago

1.2.7

6 years ago

1.2.6

6 years ago

1.2.5

6 years ago

1.2.4

6 years ago

1.2.3

6 years ago

1.2.2

6 years ago

1.2.1

6 years ago

1.2.0

6 years ago

1.0.0

6 years ago