Ionaru / easy-markdown-editor

EasyMDE: A simple, beautiful, and embeddable JavaScript Markdown editor. Delightful editing for beginners and experts alike. Features built-in autosaving and spell checking.
https://stackblitz.com/edit/easymde
MIT License
2.41k stars 316 forks source link

Custom toolbar button to open a separate React component (or any kind of div input from where I can accept input) #506

Closed tanmayaBiswalOdiware closed 1 year ago

tanmayaBiswalOdiware commented 1 year ago

So I am trying to integrate MathQuill into EasyMDE and in my head I thought of a sleek way of allowing users to input Math by letting them type LaTeX inside a separate MathQuill component and then having an insert button to let the raw math be accepted and appended to the textbox of EasyMDE.

So far, no success on my side. Is it possible to achieve this kind of functionality with custom toolbar?

Link to the CodeSandbox

tanmayaBiswalOdiware commented 1 year ago

Used React.createPortal to do the job.

This code shows the main component

import React, { useRef, useCallback } from "react";
import ReactDOM from "react-dom";
import { SimpleMdeReact } from "react-simplemde-editor";
import MathQuillContainer from "./MathQuillContainer";
import "easymde/dist/easymde.min.css";

import "./math.css";

const SimpleEditor = (props) => {
  const [modalOpen, setModalOpen] = React.useState(false);
  const editorRef = useRef();

  const [value, setValue] = React.useState(
    "Initial ***value*** $\\frac{10}{20}$ $$\\frac {a}{b}$$"
  );

  const onChange = React.useCallback((value) => {
    setValue(value);
  }, []);

  const easyMde = useRef(null);

  const getMdeInstanceCallback = useCallback((simpleMde) => {
    easyMde.current = simpleMde;
  }, []);

  const Options = React.useMemo(() => {
    return {
      // autofocus: true,
      spellChecker: false,
      status: false,
      placeholder: "Hold the door",
      previewRender: (text) => {
        // first process the markdown
        const markdownHtml = easyMde.current.markdown(text);

        // typeset with MathJax
        const container = document.createElement("div");
        container.innerHTML = markdownHtml;
        props.mathJax.current.typesetClear([container]);
        props.mathJax.current.typeset([container]);
        return container.innerHTML;
      },
      toolbar: [
        // usual toolbar options here like bold, italics etc,
        // new toolbar option below
        {
          name: "formula-button",
          title: "Math Expression",
          action: (editor) => {
            const cm = editor.codemirror;
            editorRef.current = cm;
            setModalOpen(true);
          },
          className: "fa fa-calculator"
        }
      ]
      // toolbar: false
    };
  }, []);

  return (
    <>
      <SimpleMdeReact
        value={value}
        onChange={onChange}
        options={Options}
        getMdeInstance={getMdeInstanceCallback}
      />
      {modalOpen && (
        <MathQuillContainer onClose={setModalOpen} codemirror={editorRef} />
      )}
    </>
  );
};

export default SimpleEditor;

The code below shows the portal component:

import React, { useRef, useState } from "react";
import ReactDOM from "react-dom";
import { addStyles, EditableMathField } from "react-mathquill";

addStyles();

function MathQuillContainer(props) {
  const [latex, setLatex] = useState("\\frac{1}{\\sqrt{2}}\\cdot 2");
  const mathRaw = useRef(null);

  function insertIntoCM() {
    let cm = props.codemirror.current;
    // const text = cm.getSelection(); // You can also implement your reference picker here.
    cm.replaceSelection("$" + latex + "$");
    props.onClose(false);
  }

  function insertIntoCMBlock() {
    let cm = props.codemirror.current;
    cm.replaceSelection("$$" + latex + "$$");
    props.onClose(false);
  }

  return ReactDOM.createPortal(
    <div
      style={{
        position: "absolute", top: "0", bottom: "0", left: "0", right: "0", display: "grid", justifyContent: "center", alignItems: "center",
        backgroundColor: "rgba(0,0,0,0.3)"
      }}
      onClick={(e) => {
        e.stopPropagation();
        props.onClose(false);
      }}
    >
      <div
        style={{ padding: 20, background: "#fff", borderRadius: "2px", display: "inline-block", minHeight: "300px", margin: "1rem",
          position: "relative", minWidth: "300px", boxShadow: "0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)", 
          justifySelf: "center" }}
        onClick={(e) => {
          e.stopPropagation();
        }}
      >
        <EditableMathField
          latex={latex}
          onChange={(mathField) => setLatex(mathField.latex())}
          mathquillDidMount={(mathraw) => (mathRaw.current = mathraw.latex())}
        />
        <hr />
        <p>{latex}</p>
        <button onClick={insertIntoCM}> Insert Inline </button>
        <button onClick={insertIntoCMBlock}> Insert Block </button>
      </div>
    </div>,
    document.querySelector("#modal-root-test")
  );
}

export default MathQuillContainer;

The styles have not been finalized so it will look pretty rough, but you get the idea.