Source: react/components/variant/textEditor.js

import { ExtensionPriority } from "remirror";
import ToggleBoldButton from "./editor/toggleBoldButton";
import ToggleStrikeButton from "./editor/toggleStrikeButton";
import ToggleItalicButton from "./editor/toggleItalicButton";
import ToggleUnderlineButton from "./editor/toggleUnderlineButton";

import { useEffect, useMemo } from "react";
import {
  // --- used for link popup ---
  // createMarkPositioner,
  // ShortcutHandlerProps,
  LinkExtension,
  // --- end - used for link popup ---
  BlockquoteExtension,
  BoldExtension,
  BulletListExtension,
  // CodeBlockExtension,
  // CodeExtension,
  UnderlineExtension,
  // DocExtension,
  HardBreakExtension,
  // HeadingExtension,
  ItalicExtension,
  ListItemExtension,
  MarkdownExtension,
  OrderedListExtension,
  StrikeExtension,
  ImageExtension,
  DropCursorExtension,
  // TableExtension,
  TrailingNodeExtension,
} from "remirror/extensions";
import { Remirror, useRemirror } from "@remirror/react";

import { FocusScope } from "react-aria";
import ToggleUndoButton from "./editor/toggleUndoButton";
import ToggleRedoButton from "./editor/toggleRedoButton";
import { ClearContentsButton } from "./editor/clearContentsButton";
import ToggleBulletListButton from "./editor/toggleBulletListButton";
import ToggleOrderedListButton from "./editor/toggleOrderedListButton";
import classNames from "classnames";

function ToolbarAria(props) {
  return (
    <div role="toolbar" className="px-2 py-1 border-b border-gray-300 flex-row isolate inline-flex w-full">
      {props.children}
    </div>
  );
}

function ToolbarGroup(props) {
  return (
    <div className="flex-row group inline-flex shadow-sm">
      <FocusScope>{props.children}</FocusScope>
    </div>
  );
}

function ToolbarDivider() {
  return <div className="border-r border-gray-300 mx-3 my-1" aria-hidden={true} />;
}

function MarkdownToolbar({ allowClear, onBlur, type }) {
  return (
    <ToolbarAria>
      <ToolbarGroup>
        <ToggleBoldButton />
        <ToggleItalicButton />
        {/* The underline functionality of the Text Editor is disabled since we set the Editor to handle input as Markdown and Markdown doesn't support underline */}
        {/* <ToggleUnderlineButton /> */}
        <ToggleStrikeButton />
      </ToolbarGroup>
      <ToolbarDivider />
      <ToolbarGroup>
        <ToggleBulletListButton />
        <ToggleOrderedListButton />
      </ToolbarGroup>
      <ToolbarDivider />
      <ToolbarGroup>
        <ToggleUndoButton />
        <ToggleRedoButton />
      </ToolbarGroup>
      <ClearContentsButton enabled={allowClear} onClick={onBlur} type={type} />
    </ToolbarAria>
  );
}

const VisualEditor = ({ value = "", readOnly = false, id, onChange, extensions, onBlur, type, allowSelfDisposal }) => {
  const visual = useRemirror({
    extensions,
    stringHandler: "markdown",
    content: value,
  });

  useEffect(() => {
    visual.getContext()?.setContent(value);
  }, [value, visual]);

  return (
    <div className={classNames(!readOnly ? "border border-gray-300 rounded-lg" : "", "prose max-w-none")}>
      <Remirror
        id={id}
        manager={visual.manager}
        autoRender="end"
        editable={!readOnly}
        onChange={({ helpers, state }) => {
          if (typeof onChange === "function") {
            const newText = helpers.getMarkdown(state);
            if (newText !== value) {
              // Don't send back the value as changed if it's the same
              onChange(newText);
            }
          } else {
            return setMarkdown(helpers.getMarkdown(state));
          }
        }}
        withoutEmotion
        initialContent={value}
      >
        {!readOnly && <MarkdownToolbar allowClear={value.trim().length === 0 && allowSelfDisposal} onBlur={onBlur} type={type} />}
      </Remirror>
    </div>
  );
};

/**
 * The editor which is used to create the annotation. Supports formatting.
 */
export const DualEditor = ({ value, readOnly, id, onChange, uploadHandler, onBlur, storeMetaData = {}, allowSelfDisposal = true }) => {
  const type = useMemo(() => {
    if (storeMetaData?.category) {
      switch (storeMetaData.category) {
        case "note": {
          return "aantekening";
        }
      }
    }
    return undefined;
  }, [storeMetaData.category]);
  const extensions = useMemo(
    () => () =>
      [
        new LinkExtension({ autoLink: true }),
        new BoldExtension(),
        new StrikeExtension(),
        new ItalicExtension(),
        // new HeadingExtension(),
        // new LinkExtension(),
        new BlockquoteExtension(),
        new BulletListExtension({ enableSpine: true }),
        new OrderedListExtension(),
        new ListItemExtension({ priority: ExtensionPriority.High, enableCollapsible: true }),
        // new CodeExtension(),
        // new CodeBlockExtension({ supportedLanguages: [] }),
        new TrailingNodeExtension(),
        // new TableExtension(),
        new MarkdownExtension({ copyAsMarkdown: false }),
        new UnderlineExtension(),
        new ImageExtension({ enableResizing: true, uploadHandler }),
        new DropCursorExtension(), // To enabled drag and drop images
        /**
         * `HardBreakExtension` allows us to create a newline inside paragraphs.
         * e.g. in a list item
         */
        new HardBreakExtension(),
      ],
    [uploadHandler]
  );

  return <VisualEditor value={value} readOnly={readOnly} id={id} onChange={onChange} extensions={extensions} onBlur={onBlur} type={type} allowSelfDisposal={allowSelfDisposal} />;
};

export default DualEditor;