uiwjs / react-md-editor

A simple markdown editor with preview, implemented with React.js and TypeScript.
https://uiwjs.github.io/react-md-editor
MIT License
2.17k stars 157 forks source link

About Custom Toolbar #443

Closed psn417 closed 1 year ago

psn417 commented 2 years ago

Hello everyone I am a beginner to React. I am trying to modifiy the add image command. I want a Modal to appear on screen when I click the command, so that I can use the modal to upload the image to a server, then insert the url to the editor, as is shown below:

Screenshot 2022-10-17 221015

But I don't know what's the proper way to achieve this goal. Currently I use a parent component to manage an Editor and a Modal:

import * as React from "react";
import MDEditor from "@uiw/react-md-editor";
import UploadModal from "./UploadModal";

export default function Markdown() {
  const [api, setApi] = React.useState(null);
  const [showModal, setShowModal] = React.useState(false);

  let EditorRef = React.createRef();

  const img = {
    name: "img",
    keyCommand: "img",
    buttonProps: { "aria-label": "Insert img" },
    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) => {
      setShowModal(true);
      setApi(api);
    },
  };

  const [value, setValue] = React.useState(
    "Hello Markdown! `Tab` key uses default behavior"
  );
  return (
    <div className="container">
      <MDEditor
        ref={EditorRef}
        value={value}
        onChange={setValue}
        commands={[
          // Custom Toolbars
          img,
        ]}
      />
      {showModal ? (
        <UploadModal
          onSubmit={(url) => {
            api.replaceSelection(`![description](${url})`);
          }}
          onExit={() => {
            setShowModal(false);
          }}
        />
      ) : null}
    </div>
  );
}

Here is the modal component:

import * as React from "react";
import Upload from "rc-upload";
import { Button, Image, Modal, ProgressBar } from "react-bootstrap";

export default function UploadModal(props) {
  const [showUploader, setShowUploader] = React.useState(true);
  const [showImage, setShowImage] = React.useState(false);
  const [showProcess, setShowProcess] = React.useState(false);
  const [process, setProcess] = React.useState(0);
  const [url, setUrl] = React.useState(null);

  const handleClose = () => {
    setShowUploader(true);
    setShowImage(false);
    setUrl(null);
    setProcess(0);
    setShowProcess(false);
    props.onExit();
  };

  const handleSubmit = () => {
    handleClose();
    props.onSubmit(url);
  };

  const uploaderProps = {
    action: "/upload",
    multiple: true,
    beforeUpload(file) {},
    onStart: (file) => {
      setShowProcess(true);
    },
    onSuccess(result) {
      setUrl(result.url);
      setShowImage(true);
      setShowUploader(false);
      setShowProcess(false);
    },
    onProgress(step) {
      setProcess(step.percent);
    },
    onError(err) {
      console.log("onError", err);
    },
  };

  return (
    <Modal show={true} onHide={handleClose}>
      <Modal.Header closeButton>
        <Modal.Title>Upload Image</Modal.Title>
      </Modal.Header>
      <Modal.Body className="d-flex justify-content-center">
        {showUploader ? (
          <Upload
            {...uploaderProps}
            className={
              "w-100 border border-3 border-secondary rounded bg-light"
            }
            component={"div"}
          >
            <br />
            <br />
            <p className="text-secondary text-center">
              Click or drag the image here to upload.
            </p>
            <br />
            <br />
          </Upload>
        ) : null}
        {showImage ? (
          <Image src={url} thumbnail="true" className="w-50" />
        ) : null}
        {showProcess ? <ProgressBar now={process} /> : null}
      </Modal.Body>
      <Modal.Footer>
        <Button variant="secondary" onClick={handleClose}>
          Close
        </Button>
        <Button variant="primary" onClick={handleSubmit}>
          OK
        </Button>
      </Modal.Footer>
    </Modal>
  );
}

These code can achieve exactly what I want. But I think they are very bad. It seems that the parent component needs to get the api of the editor, and I don't know how to do it.

Does anyone know how to do it properly?

psn417 commented 2 years ago

I render the image command to my custom element. It seems to be a good solution.

export default function Markdown() {
  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 === "image") {
              return (
                <UploadModal
                  onClick={() => {
                    console.log(command);
                  }}
                  onSubmit={(url) => {
                    command.execute = (state, api) => {
                      api.replaceSelection(`![description](${url})`);
                    };
                    executeCommand(command, command.groupName);
                  }}
                />
              );
            }
          },
        }}
      />
    </div>
  );
}