Giter Club home page Giter Club logo

react-md-editor's Introduction

react-md-editor logo

Buy me a coffee Downloads npm bundle size Coverage Status
Build & Deploy Open in unpkg Gitee npm version

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>
  );
}

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/[email protected]
// 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!

小弟调调

Mend Renovate Stephen Eckels Alberto Valero Gómez Alpha Romer Coma RAR UniqueUlysees Andrea Puddu Bramus Carlene Cannon-Conner Corentin Mercier Dmitrii Ianushkevich James Finucane Kevin Nolan Lucas Sierota Michael Kramer Peter Jausovec Phillip Burch Rami Maalouf Tore Sinding Bekkedal Valentin juno tesoro juspky ryicoh wangjie

Made with contributors.

License

Licensed under the MIT License.

react-md-editor's People

Contributors

allforabit avatar alphacoma18 avatar avalero avatar bramus avatar carlenecannon-conner avatar dependabot[bot] avatar dmitriyyan avatar exmirai avatar github-actions[bot] avatar jaywcjlove avatar jmtes avatar jnishiyama avatar juspky avatar kseikyo avatar merciercorentin avatar michaelkramer avatar nuragic avatar peterj avatar phillipb avatar psycho-baller avatar rargames avatar renovate-bot avatar renovate[bot] avatar ryicoh avatar stevemk14ebr avatar toresbe avatar valenfv avatar wj0990 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-md-editor's Issues

Extending markdown syntax

Can you provide an example to extend the component to support an additional markdown syntax?. For instance, something like this __hello__ for underlined text

Formatting of paragraphs

I have issues with formatting of the paragraphs when using the editor, when using

<div className="row">
  {edit ? <MDEditor value={value} onChange={setValue} /> : <MDEditor.Markdown source={value} />}
</div>
<div className="row">
  <ReactMarkdown plugins={[gfm]} children={value} />
</div>

the result looks like this which is not what you expect. I want to show the input to my editor as two paragraphs, but the editor doesn't seem to format the paragraphs so that it can be shown as paragraphs?

image

Typo in the CodeSandbox Demo

If you go to the README.md file, and select the first link to open the code in Codesandbox
You will see on lines 3, 35 and 37 packages are being imported from MEDitor vs MDEditor.

import MEDitor from "@uiw/react-md-editor";

should be

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

Show native tooltip?

Would be useful to have a native html tooltip display when hovering over toolbar items, some icons don't clearly indicate what they do.

NextJS 10 or above and Webpack 5 issue with "react-md-editor"

Dear All,
I created a NextJS project and installed the react-markdown-editor via npm.

I have made the following configuration in my next.config.js file to enable Webpack 5:
module.exports = withCSS({ // Your configurations here future: { webpack5: true, strictPostcssConfiguration: true, }, });

When I try to run my NextJS & Webpack 5 package the following error message appears:

Error
`ready - started server on 0.0.0.0:3000, url: http://localhost:3000
info - Using webpack 5. Reason: future.webpack5 option enabled https://nextjs.org/docs/messages/webpack5
Warning: Built-in CSS support is being disabled due to custom CSS configuration being detected.
See here for more info: https://nextjs.org/docs/messages/built-in-css-disabled

(node:8240) [DEP_WEBPACK_COMPILATION_NORMAL_MODULE_LOADER_HOOK] DeprecationWarning: Compilation.hooks.normalModuleLoader was moved to NormalModule.getCompilationHooks(compilation).loader
(Use node --trace-deprecation ... to show where the warning was created)
(node:8240) [DEP_WEBPACK_MAIN_TEMPLATE_RENDER_MANIFEST] DeprecationWarning: MainTemplate.hooks.renderManifest is deprecated (use Compilation.hooks.renderManifest instead)
(node:8240) [DEP_WEBPACK_CHUNK_TEMPLATE_RENDER_MANIFEST] DeprecationWarning: ChunkTemplate.hooks.renderManifest is deprecated (use Compilation.hooks.renderManifest instead)
(node:8240) [DEP_WEBPACK_MAIN_TEMPLATE_HASH_FOR_CHUNK] DeprecationWarning: MainTemplate.hooks.hashForChunk is deprecated (use JavascriptModulesPlugin.getCompilationHooks().chunkHash instead)
(node:8240) [DEP_WEBPACK_CHUNK_MODULES_ITERABLE] DeprecationWarning: Chunk.modulesIterable: Use new ChunkGraph API
[webpack.cache.PackFileCacheStrategy/webpack.FileSystemInfo] Node.js doesn't offer a (nice) way to introspect the ESM dependency graph yet.
Until a full solution is available webpack uses an experimental ESM tracking based on parsing.
As best effort webpack parses the ESM files to guess dependencies. But this can lead to expensive and incorrect tracking.
event - compiled successfully
[webpack.cache.PackFileCacheStrategy/webpack.FileSystemInfo] Node.js doesn't offer a (nice) way to introspect the ESM dependency graph yet.
Until a full solution is available webpack uses an experimental ESM tracking based on parsing.
As best effort webpack parses the ESM files to guess dependencies. But this can lead to expensive and incorrect tracking.
`
It will be really great if "react-markdown-editor" can be ported for NextJS framework & Webpack 5.

Regards,
MHS

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Error type: undefined. Note: this is a nested preset so please contact the preset author if you are unable to fix it yourself.

Does not work with NextJS (duplicate of #52)

This is a relatively simple fix yet crucial to improve this package's popularity.

There's already an issue on this but for some reason it's closed. All the "fixes" suggested there are unreliable (edit node_modules directly) or unencouraged (using the deprecated package @zeit/next-css)

Can not access commandOrchestrator anywhere since v2

Is this feature miss since v2 ? I can not access commandOrchestrator anywhere since v2. I worked under v1.

const r = useRef();
function exec() {
  if (r.current) {
    r.current.commandOrchestrator.executeCommand({ execute(state, api) { /* ... */ } });
  }
}
return (
  <>
    <button onClick={exec}>execute command from here</button>
    <MDEditor ref={r}/>;
  </>
)

TypeError: Cannot read property 'warp' of null

Uncaught TypeError: Cannot read property 'warp' of null
    at MDEditor._this.handleScroll (MDEditor.tsx:121)
    at HTMLUnknownElement.callCallback (react-dom.development.js:188)
    at HTMLUnknownElement.sentryWrapped (helpers.ts:87)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:237)
    at invokeGuardedCallback (react-dom.development.js:292)
    at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:306)
    at executeDispatch (react-dom.development.js:389)
    at executeDispatchesInOrder (react-dom.development.js:414)
    at executeDispatchesAndRelease (react-dom.development.js:3278)
    at executeDispatchesAndReleaseTopLevel (react-dom.development.js:3287)
    at forEachAccumulated (react-dom.development.js:3259)
    at runEventsInBatch (react-dom.development.js:3304)
    at runExtractedPluginEventsInBatch (react-dom.development.js:3514)
    at handleTopLevel (react-dom.development.js:3558)
    at batchedEventUpdates$1 (react-dom.development.js:21871)
    at batchedEventUpdates (react-dom.development.js:795)
    at dispatchEventForLegacyPluginEventSystem (react-dom.development.js:3568)
    at attemptToDispatchEvent (react-dom.development.js:4267)
    at dispatchEvent (react-dom.development.js:4189)
    at unstable_runWithPriority (scheduler.development.js:653)
    at dispatchUserBlockingUpdate (react-dom.development.js:4172)
    at HTMLDocument.sentryWrapped (helpers.ts:87)
MDEditor._this.handleScroll
src/MDEditor.tsx:121
  118 | }
  119 | private handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
  120 |   const preview = this.preview.current!.mdp.current! as HTMLDivElement;
> 121 |   const textarea = this.textarea.current!.warp.current! as HTMLDivElement;
      | ^  122 |   if (textarea && preview) {
  123 |     const scale = (textarea.scrollHeight - textarea.offsetHeight) / (preview.scrollHeight - preview.offsetHeight);
  124 |     if (e.target === textarea && this.leftScroll) {
View compiled
HTMLUnknownElement.callCallback
node_modules/react-dom/cjs/react-dom.development.js:188
HTMLUnknownElement.sentryWrapped
src/helpers.ts:87
  84 | } else {
  85 |   // If config is not fetched yet, wait for all configs (we don't know which one we need) and
  86 |   // find the appId (if any) corresponding to this measurementId. If there is one, wait on
> 87 |   // that appId's initialization promise. If there is none, promise resolves and gtag
     | ^  88 |   // call goes through.
  89 |   const dynamicConfigResults = await Promise.all(dynamicConfigPromisesList);
  90 |   const foundConfig = dynamicConfigResults.find(
"@uiw/react-md-editor": "^1.14.4",

When I changed modes, preview, editor, both, This Type Error occured.

TypeError: text is undefined

Didn't found any information on stackoverflow, might be I didn't good search
I was on 2.1.2 version but with update this occured
изображение
изображение

Change color on textarea text

Hi.

I want to change the color on the textarea-text, and have tried using the prop
textareaProps={{ style: { color: "white" } }}, but something seems off. I am not that confident in passing props like this, so I do believe this may be a syntax-error.

I would be grateful if someone could have pointed me in the right direction

NextJS 10 or above Framework issue "Global CSS cannot be imported from within node_modules"

Dear All,
I created a NextJS project and installed the react-markdown-editor via npm. When I try to run my NextJS project the following error message appears:

Error
Global CSS cannot be imported from within node_modules. Read more: https://nextjs.org/docs/messages/css-npm Location: node_modules\@uiw\react-markdown-preview\lib\esm\index.js

And the possible reason according to NextJS documentation:
https://nextjs.org/docs/messages/css-npm

It will be really great if "react-markdown-editor" can be ported for NextJS framework properly.

Note:

  • Similar issue: https://github.com/uiwjs/react-md-editor/issues/52
  • As a solution to similar issue as mine current version of NextJS 10 or above have deprecated the use of "@zeit/next-css" and don't recommend to use it.
  • Warning: Built-in CSS support is being disabled due to custom CSS configuration being detected.
    Regards,
    MHS

How do I upload images?

I have an upload server (https://.../upload/img) that returns an image's URL and I found the commands.image.

I want to modify the commands that can upload an image(how to..?) and return ![](res.url)(I can do it with execute)

Can I add components like the figure?

image

Error when edit Katex flag

In demo page, the Katex paragraph Support Custom KaTeX Preview.

When I try to edit the line, delete the last character x

```Katex

Error happened and page goto blank. Here is the error log

react_devtools_backend.js:562 Error: Minified React error #152; visit https://reactjs.org/docs/error-decoder.html?invariant=152&args[]=code for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
    at react-dom.production.min.js:3539
    at Po (react-dom.production.min.js:4247)
    at Bo (react-dom.production.min.js:4296)
    at gl (react-dom.production.min.js:6697)
    at us (react-dom.production.min.js:6150)
    at ls (react-dom.production.min.js:6139)
    at Jl (react-dom.production.min.js:5880)
    at react-dom.production.min.js:2851
    at t.unstable_runWithPriority (scheduler.production.min.js:309)
    at Ha (react-dom.production.min.js:2816)
react-dom.production.min.js:3539 Uncaught Error: Minified React error #152; visit https://reactjs.org/docs/error-decoder.html?invariant=152&args[]=code for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
    at react-dom.production.min.js:3539
    at Po (react-dom.production.min.js:4247)
    at Bo (react-dom.production.min.js:4296)
    at gl (react-dom.production.min.js:6697)
    at us (react-dom.production.min.js:6150)
    at ls (react-dom.production.min.js:6139)
    at Jl (react-dom.production.min.js:5880)
    at react-dom.production.min.js:2851
    at t.unstable_runWithPriority (scheduler.production.min.js:309)
    at Ha (react-dom.production.min.js:2816)

Error when edit Katex

即使在提供的sandbox的样例中在新的一行中直接输入```即可引发错误。应该是还没有输入代码块的语言,但是已经开始渲染导致的。截图如下:
image

inlineCode error was occurred when I run the script for KaTeX preview

What

I ran the script for KaTeX preview written in README and got inlineCode error when I inserted two back quotes into editor.

code (written in README): https://codesandbox.io/s/markdown-editor-katex-for-react-7v3vl

Inserted two back quotes like this:
Screenshot from 2020-10-27 10-30-15

Found two errors:

react-dom.development.js:14348 Uncaught Error: inlineCode(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
The above error occurred in the <inlineCode> component:
    at inlineCode (https://7v3vl.csb.app/index.js:29:25)
    at p
    at Root (https://7v3vl.csb.app/node_modules/react-markdown/lib/renderers.js:45:28)
    at ReactMarkdown (https://7v3vl.csb.app/node_modules/react-markdown/lib/react-markdown.js:40:19)
    at div
    at MarkdownPreview (https://7v3vl.csb.app/node_modules/@uiw/react-md-editor/lib/esm/components/Markdown/index.js:47:33)
    at div
    at div
    at MDEditor (https://7v3vl.csb.app/node_modules/@uiw/react-md-editor/lib/esm/MDEditor.js:42:33)
    at App

Solution

I inserted the null check statement for children and got no errors.

const renderers = {
  inlineCode: ({ children }) => {
    if (/^\$\$(.*)\$\$/.test(children)) {
      const html = katex.renderToString(children.replace(/^\$\$(.*)\$\$/, '$1'), {
        throwOnError: false,
      });
      return <code dangerouslySetInnerHTML={{ __html: html }} />
    }
    if (children == null) {
        return <code />;
    }
    return children;
  },
  code: ({ children, language, value }) => {
    if (language.toLocaleLowerCase() === 'katex') {
      const html = katex.renderToString(value, {
        throwOnError: false
      });
      return (
        <pre>
          <code dangerouslySetInnerHTML={{ __html: html }} />
        </pre>
      );
    }
    if (children == null) {
        return <code />;
    }
    return children;
  }
}

I think it needs to update README and I wanna other solutions if you have.
Thanks:)

Bold text issue

Hey everyone,

I don't know if i'm the only one, but when i try using bold text in my editor it gives a strange spacing everytime and it breaks everything because i cant find myself in the text anymore.

image

has anyone seen this issue?

How do I customize Toolbar and Textarea?

I was having trouble trying to customize some of the components inside the package, namely Toolbar and Teaxarea (exported from src/components/Toolbar and src/components/Textarea respectively). When reading through the code, I happened to come across this block: (src/MDEditor.tsx, line 158-165):

<Toolbar
    active={{
    fullscreen: this.state.fullscreen,
    preview: this.state.preview,
}}
    prefixCls={prefixCls} commands={commands}
    onCommand={this.handleCommand}
/>

I'm wondering if the Toolbar component can be styled through className.

How can I set the placeholder value?

Hi, I want to use this package but I have some basic problems, like the placeholder not working. This is my code:

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




export function MarkdownArea(props) {

  const t = props.t;
  const identifier   = props.identifier;
  const labelText    = props.labelText;
  const placeholder  = props.placeholder;
  const ancestor     = props.ancestor;
  const register     = props.register;
  const errors       = props.errors;
  const isRequired   = props.isRequired;
  const minLen       = props.minLen;
  const maxLen       = props.maxLen;


  const [value, setValue] = useState("");


  const textareaProps = 
    { placeholder
    , readOnly: true
    }



  return (
    <div className={`coreInput__inputWrapper ${ancestor}__inputWrapper`}>
      <span className={`coreInput__inputLabel ${ancestor}__inputLabel`}>
        {t(labelText)}
      </span>
      <MDEditor
        value={value}
        onChange={setValue}
        height={300}
        textareaProps={textareaProps}
      />
      {errors[identifier] && <p className="coreInput__error">{errors[identifier].message}</p>}
    </div>
  )
}

And I'm using this MarkdownArea component like this:

  <MarkdownArea
        t={t}
        identifier={langCode + "-description"}
        labelText="description"
        placeholder="hola mundo"
        ancestor="ProductCreate"
        register={register}
        errors={errors}
        isRequired={true}
        minLen={config.product.minLen_description}
        maxLen={config.product.maxLen_description}
      />

But the placeholder is not working. Also any insights on how to integrate this textarea with react-hook-form will be appreciated

Add Validations (yup with/without react-hook-form)

I want to validate the user input easily using yup for minLength, maxLength, etc.
Is there a recommended way?

I also, want to abstract it more using react-hook-from, but it seems that you don't forwardRefs.

Improve toolbar button focus styling for keyboard users

The following screenshots show that, if a toolbar button has focus, it's not very clear because the styling doesn't look any different from the default state.
MDEToolbarButtonFocus

Screenshot (87)

In order to make it more accessible for people that rely on the keyboard to navigate pages, the buttons should look as they do on hover when they receive focus.

Improve Bundle Chunking

When bundled with create-react-app default settings this library produces over 400 chunked bundles. They are invidually all small, but total almost 1mb. I don't know what they all are, and they don't all appear to be loaded, but it is quite strange.

It looks like they are mostly from prism. I'm guessing they are individual language highlighting packs. Perhaps there is a way to skip them?


This tool is beautiful by the way, thank you for making it. This is easily the nicest markdown editor I've come across

Can I customize the renderer?

I checked some bugs during use.

please try below text in editor

- text
  - (tab: 1)
- text
    - (tab: 2)
- text
      - (tab: 3) 
- text
text2
  - (tab: 1) 
    text (tab:2)
1. text
1. text
1. text


1. text

I think this problem cause of react-markdown.
could you fix this bug?
If you can't fix this bug, please tell me about how to customize it.

Can I override renderers?

Thanks for sharing your awesome library.

I am adding some rendering options like katex.

const renderers = {
  inlineCode: ({ children }) => {
    if (/^\$\$(.*)\$\$/.test(children)) {
      const html = katex.renderToString(children.replace(/^\$\$(.*)\$\$/, '$1'), {
        throwOnError: false,
      });
      return <code dangerouslySetInnerHTML={{ __html: html }} />
    }
    return children;
  },
  code: ({ children, language, value }) => {
    if (language.toLocaleLowerCase() === 'katex') {
      const html = katex.renderToString(value, {
        throwOnError: false
      });
      return (
        <pre>
          <code dangerouslySetInnerHTML={{ __html: html }} />
        </pre>
      );
    }
    return children;
  }
}

However, I would like to use it with markdown's default rendering options like code block (python, C++, etc.).

int a = 10

Can I override my custom parameter options to the default renderer?
default options(codeblocks, ...) + my custom options(katex, ...)

Shifted text in edit mode

Hi everyone,

When using this awesome component in my NextJS app I ran into a problem where the text cursor is shifted from the "preview text" in the edit mode when using the title tag (the '#')

For example in the following image, the cursor is actually between the 'r' and 'k' of the word 'markdown'
Capture d’écran de 2020-11-17 22-57-18

If that is of any help, here is my next.config.js:

const withSass = require('@zeit/next-sass');
const withCSS = require('@zeit/next-css')
const withPlugins = require('next-compose-plugins');

const sassConfig = {
    cssModules: true,
    cssLoaderOptions: {
        camelCase: true
    }
};
const cssConfig = {
    cssModules: false,
};

module.exports = withPlugins([
    [withSass, sassConfig],
    [withCSS, cssConfig],
])

Thanks in advance for your help

the edit cursor is in the wrong position

After I change font family (in somewhere I don't remember), I got this UI's error:
Screen Shot 2021-04-04 at 1 32 03 PM

It only happens on the title (and other lines has special characters in begin of line), on normal lines it's doesn't happen. the edit cursor does not move to the end of the line, no matter whether I press the end of a line or use the arrow keys, it is there.

I'm found a way to fix, add some css (scss in my case) to change font family of "span.token.title.important" and "span.token.title.important .token.punctuation" like this:

// scss
span.token.title.important {
  font-family: inherit;
  .token.punctuation {
    font-family: inherit;
  }
}

// css
span.token.title.important {
      font-family: inherit;
}
  span.token.title.important .token.punctuation {
    font-family: inherit;
  }

"span.token.title.important" and ".token.punctuation" took font-family css from its parent and problem has been fix:
Screen Shot 2021-04-04 at 1 58 32 PM

Does not allow application to compile

Error Message

node_modules/@uiw/react-md-editor/lib/cjs/index.d.ts(4,1):
Declaration or statement expected.  TS1128

    2 | import * as commands from './commands';
    3 | import * as MarkdownUtil from './utils/markdownUtils';
  > 4 | export type { ICommand, CommandOrchestrator, TextRange, TextState, TextApi } from './commands';
      | ^
    5 | export type { TextSection } from './utils/markdownUtils';
    6 | export * from './Editor';
    7 | export { MarkdownUtil, commands, };

All I have done is yarn add @uiw/react-md-editor and that is the error it kicks back. Not quite sure what would cause it to explode though.

Error when npm run start

yarn start is running normally, but an error has occurred with the npm start run

Module parse failed: Unexpected token (443:21)
You may need an appropriate loader to handle this file type.
|
|             return _context.abrupt("return", Promise.all(langs.map(function (key) {
>               return import("prismjs/components/prism-".concat(key));
|             })));
|

package.json

{
  "name": "context",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@antv/g6": "^3.4.0",
    "@babel/core": "7.4.3",
    "@svgr/webpack": "4.1.0",
    "@typescript-eslint/eslint-plugin": "1.6.0",
    "@typescript-eslint/parser": "1.6.0",
    "antd": "^3.19.2",
    "axios": "^0.19.0",
    "babel-eslint": "10.0.1",
    "babel-jest": "^24.8.0",
    "babel-loader": "8.0.5",
    "babel-plugin-import": "^1.12.0",
    "babel-plugin-named-asset-import": "^0.3.2",
    "babel-plugin-transform-decorators-legacy": "^1.3.4",
    "babel-preset-react-app": "^9.0.0",
    "camelcase": "^5.2.0",
    "case-sensitive-paths-webpack-plugin": "2.2.0",
    "classnames": "^2.2.6",
    "dotenv": "6.2.0",
    "dotenv-expand": "4.2.0",
    "dva": "^2.2.3",
    "dva-loading": "^2.0.3",
    "echarts": "^4.2.1",
    "eslint": "^5.16.0",
    "eslint-config-react-app": "^4.0.1",
    "eslint-loader": "2.1.2",
    "eslint-plugin-flowtype": "2.50.1",
    "eslint-plugin-import": "2.16.0",
    "eslint-plugin-jsx-a11y": "6.2.1",
    "eslint-plugin-react": "7.12.4",
    "eslint-plugin-react-hooks": "^1.5.0",
    "file-loader": "3.0.1",
    "fs-extra": "7.0.1",
    "hashmap": "^2.3.0",
    "html-webpack-plugin": "4.0.0-beta.5",
    "identity-obj-proxy": "3.0.0",
    "is-wsl": "^1.1.0",
    "jest": "24.7.1",
    "jest-environment-jsdom-fourteen": "0.1.0",
    "jest-resolve": "24.7.1",
    "jest-watch-typeahead": "0.3.0",
    "js-md5": "^0.7.3",
    "less": "2.7.3",
    "less-loader": "^5.0.0",
    "less-vars-to-js": "^1.3.0",
    "mini-css-extract-plugin": "0.5.0",
    "moment": "^2.23.0",
    "optimize-css-assets-webpack-plugin": "5.0.1",
    "pako": "^1.0.10",
    "pnp-webpack-plugin": "1.2.1",
    "postcss-flexbugs-fixes": "4.1.0",
    "postcss-loader": "3.0.0",
    "postcss-normalize": "7.0.1",
    "postcss-preset-env": "6.6.0",
    "postcss-safe-parser": "4.0.1",
    "react": "^16.8.6",
    "react-app-polyfill": "^1.0.1",
    "react-beautiful-dnd": "^12.1.1",
    "react-cookie": "^4.0.0",
    "react-dev-utils": "^9.0.1",
    "react-dnd": "^6.0.0",
    "react-dnd-html5-backend": "^6.0.0",
    "react-dom": "^16.8.6",
    "react-highlight-words": "^0.16.0",
    "react-hot-loader": "^4.11.1",
    "react-rnd": "^9.2.0",
    "react-router-dom": "^5.0.1",
    "react-websocket": "^2.0.1",
    "redux-logger": "^3.0.6",
    "redux-thunk": "^2.3.0",
    "resolve": "1.10.0",
    "sass-loader": "7.1.0",
    "semver": "6.0.0",
    "terser-webpack-plugin": "1.2.3",
    "ts-pnp": "1.1.2",
    "url-loader": "1.1.2",
    "uuid": "^3.3.2",
    "webpack": "4.29.6",
    "webpack-dev-server": "3.2.1",
    "webpack-manifest-plugin": "2.0.4",
    "workbox-webpack-plugin": "4.2.0"
  },
  "scripts": {
    "start": "node scripts/start.js",
    "build": "node scripts/build.js",
    "test": "node scripts/test.js"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "jest": {
    "collectCoverageFrom": [
      "src/**/*.{js,jsx,ts,tsx}",
      "!src/**/*.d.ts"
    ],
    "setupFiles": [
      "react-app-polyfill/jsdom"
    ],
    "setupFilesAfterEnv": [],
    "testMatch": [
      "<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
      "<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}"
    ],
    "testEnvironment": "jest-environment-jsdom-fourteen",
    "transform": {
      "^.+\\.(js|jsx|ts|tsx)$": "<rootDir>/node_modules/babel-jest",
      "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
      "^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
    },
    "transformIgnorePatterns": [
      "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$",
      "^.+\\.module\\.(css|sass|scss)$"
    ],
    "modulePaths": [],
    "moduleNameMapper": {
      "^react-native$": "react-native-web",
      "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy"
    },
    "moduleFileExtensions": [
      "web.js",
      "js",
      "web.ts",
      "ts",
      "web.tsx",
      "tsx",
      "json",
      "web.jsx",
      "jsx",
      "node"
    ],
    "watchPlugins": [
      "jest-watch-typeahead/filename",
      "jest-watch-typeahead/testname"
    ]
  },
  "babel": {
    "presets": [
      "react-app"
    ],
    "plugins": [
      [
        "@babel/plugin-proposal-decorators",
        {
          "legacy": true
        }
      ],
      [
        "@babel/plugin-proposal-class-properties",
        {
          "loose": true
        }
      ]
    ]
  },
  "homepage": "./resource/workbench",
  "devDependencies": {
    "@babel/plugin-proposal-decorators": "^7.4.4",
    "@uiw/react-md-editor": "^1.14.3",
    "babel-plugin-dva-hmr": "^0.4.1",
    "css-loader": "^2.1.1",
    "echarts-for-react": "^2.0.15-beta.1",
    "immutability-helper": "^3.0.1",
    "jquery": "^3.4.1",
    "pako": "^1.0.10",
    "react-draggable-tags": "^0.4.0-beta.2",
    "react-resizable": "^1.8.0",
    "redux-devtools-extension": "^2.13.8",
    "style-loader": "^0.23.1",
    "xlsx": "^0.15.5"
  }
}

Error when specifying "100%" to height

See: https://github.com/uiwjs/react-md-editor/blob/master/src/MDEditor.tsx

Type of height:
height?: React.CSSProperties['height']

But when specifying "100%", there's error:

index.js:1 Warning: `NaN` is an invalid value for the `height` css style property.
    in div (created by MDEditor)
    in div (created by MDEditor)

I think at this line:

style={{ height: this.state.fullscreen ? 'calc(100% - 29px)' : (this.state.height as number) - 29 }}

If we change to below, the error can be fixed:

style={{ height: this.state.fullscreen ? 'calc(100% - 29px)' : `calc(${this.state.height} - 29px` }}

Text area doesn't update after render if we update the value by api

I'm using this package in one of my projects. Everything is ok if I render the package and start typing markdown content in it but if I just click on a button after the render to get markdown data from the internet (or anywhere else) and then send it down to the component using the props, the preview works fine but the text area would be empty.

Scroll on empty editor

Greetings!

As mentioned in the name of the issue, the scroll is always enabled in the editor. Even when there is no text in it.

I couldn't found a way to fix it from available props. Can this behavior be fixed by default in the future, or where is some way to do it right now?
Запись экрана 2020-12-05 в 02 58 03

Implementation of Shortcuts

Can you provide an example to extend the component to support custom shortcuts?

I'm looking for something like this:

  • italics (cmd + i)
  • bold (cmd + b)
  • underline (cmd + u)
  • strikethrough (cmd + shift + x)
  • code: (cmd + j)
  • code block (cmd + shift + J)
  • ordered list (cmd + shift + O)
  • unordened list (cmd + shift + L)
  • links (cmd + k)

Uncaught TypeError: Cannot read property 'toLowerCase' of undefined

I'm running into a strange issue here where the following console error is thrown anytime I modify the value feeding the MDEditor component. The component continues to function normally, just lots of console noise.

handleKeyDown.js:25 Uncaught TypeError: Cannot read property 'toLowerCase' of undefined
    at handleKeyDown.js:25
    at onKeyDown (Textarea.js:41)
    at HTMLUnknownElement.callCallback (react-dom.development.js:191)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:240)
    at invokeGuardedCallback (react-dom.development.js:293)
    at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:308)
    at executeDispatch (react-dom.development.js:393)
    at executeDispatchesInOrder (react-dom.development.js:418)
    at executeDispatchesAndRelease (react-dom.development.js:3303)
    at executeDispatchesAndReleaseTopLevel (react-dom.development.js:3312)

Code:

import MDEditor, { commands } from '@uiw/react-md-editor';
interface PropTypes extends RouterProps {}

const MyForm: FunctionComponent<PropTypes> = (props: PropTypes) => {
  const [item, setItem] = useState({title: "", body: ""})

  return (
            <MDEditor
              height={200}
              value={insight.body}
              onChange={(value) => {
                setItem(prevState => {
                  return { ...prevState, body: value}
                })
              }}/>
  )
};

Anyone else running into this? Im hitting this on the 3.0.0-beta.1 release. I have not tried going back to older releases.

Preview Props

I get an error when I try to send Preview props preview={"preview"}.

this is my full code

<MDEditor
  value={value}
  onChange={handleChange}
  visiableDragbar={false}
  autoFocus={false}
  preview={"preview"}
/>

image

prismjs dependency CVE

image
The only vulnerability I have in my project comes from prismjs which is a dependency of react-md-editor.

I think the minor version update is compatible so it should be a straightforward fix! :)

[Security] Vulnerable to XSS attacks.

Issue : The textarea doesn't do any validation or sanitize any input to it.

What is happening : This makes editor execute inline html code.

How to reproduce ? : Simply type <img src onerror="alert('hey!')"/> in input box

Close Group Command on Click

When using the group for titles like the example, it should close the group list when you click on one of the items. Right now, it stays open and covers the text area where you inserted the title:

image

[Bug] MDEditor height not responding correctly to window.innerHeight

Hello 👋

First time using react-md-editor. I'm trying to have MDEditor occupy the entire height of my window but I think I'm facing a bug now since, for example, a window.innerHeight of 900 gets me a MDEditor with an height of 871.

Here's the code that I have right now.
I'm also using tailwind but if I create a div with the height of window.innerHeight it works perfectly fine

import { useState, useRef } from 'react';
import './App.css';
import MDEditor from '@uiw/react-md-editor';

const App = () => {
  const [data, setData] = useState([{ text: 'Test note' }]);
  const [note, setNote] = useState('');

  const heightHere = window.innerHeight;

  return (
    <div className="relative bg-gray-100 h-screen">
      <div>
        <MDEditor
          className="py-2 px-5 w-10/12"
          id="noteinput"
          type="text"
          placeholder="Enter a new note"
          value={note}
          onChange={setNote}
          preview="edit"
          hideToolbar
          height={heightHere}
          visiableDragbar={false}
        />
      </div>
    </div>
  );
};

export default App;

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.