mdx-editor / editor

A rich text editor React component for markdown
https://mdxeditor.dev
MIT License
1.75k stars 139 forks source link

[BUG] Undo/redo state is immutable #557

Open mjp0 opened 1 month ago

mjp0 commented 1 month ago

Describe the bug I can't figure out how to reset undo/redo. I'm using MDXEditor with doc browser and clicking a doc opens it in the editor. I have tried to create a separate ref for each doc but nothing seems to reset undo/redo state.

Reproduction Re-render MDXEditor with different ref or key, and undo/redo state stays.

Expected behavior I'd love to have editorsRef.current.reset() or something similar to reset everything.

Desktop (please complete the following information):

MagisterUnivers commented 3 weeks ago

@mjp0 Did you find out, if that possible to make onReset() on everything? Cause all ref is returning is { current: null }.

I am assuming we need to do something custom, like .selectAll elements in Editor currently, and then make them all look like this:

<p dir="ltr"><span data-lexical-text="true">And another text</span></p>

Thats the 1 idea i have left.

MagisterUnivers commented 3 weeks ago

@mjp0 Here, take

'use client'

import React, { forwardRef, useRef, useState } from 'react'
import dynamic from 'next/dynamic'
import { MDXEditorMethods } from '@mdxeditor/editor'
import '../../app/(main)/faq/markdown-updated.css'
import '@mdxeditor/editor/style.css'

const EditorComp = dynamic(async () => await import('../Custom/MDXEditor'), { ssr: false })

export const ForwardRefEditor = forwardRef<MDXEditorMethods, EditorProps>((props, ref) => <EditorComp {...props} editorRef={ref} />)

ForwardRefEditor.displayName = 'ForwardRefEditor'

interface Props {
  currentCollection: FAQSubSemiCollectionElement
  collectionId: number
  parentCollectionId: number
  onFieldChange: (collectionId: number, subCollectionId: number, markdown: string) => void
}

export function RichTextEditor ({ parentCollectionId, collectionId, currentCollection, onFieldChange }: Props): React.ReactNode {
  const [content, setContent] = useState(currentCollection.collectionItemMarkdown ?? '')
  const [renderKey, setRenderKey] = useState(0)
  const mkRef = useRef<MDXEditorMethods | null>(null)

  function handleEditorChange (value: string): void {
    setContent(value)
    onFieldChange(parentCollectionId, collectionId, value)
  }

  function handleResetFormatting (): void {
    const plainText = content
      .replace(/\*\*(.*?)\*\*/g, '$1')
      .replace(/\*(.*?)\*/g, '$1')
      .replace(/_(.*?)_/g, '$1')
      .replace(/~~(.*?)~~/g, '$1')
      .replace(/#+\s?(.*)/g, '$1')
      .replace(/>\s?(.*)/g, '$1')
      .replace(/\[(.*?)\]\(.*?\)/g, '$1')
      .replace(/`(.*?)`/g, '$1')
      .replace(/-|\*|\+|\d+\./g, '')
      .replace(/!\[.*?\]\(.*?\)/g, '')
      .replace(/---|\*\*\*/g, '')
      .replace(/<u>(.*?)<\/u>/g, '$1')
      .replace(/<\/?[^>]+(>|$)/g, '')
      .trim()

    handleEditorChange(plainText)
    setRenderKey(prev => prev + 1)
  }

  return (
    <div key={renderKey} className='w-full h-auto'>
      <div className='flex flex-col gap-[100px] items-center justify-center markdown'>
        <ForwardRefEditor
          markdown={content}
          editorRef={mkRef}
          onChange={handleEditorChange}
          onReset={handleResetFormatting}
        />
      </div>
    </div>
  )
}
'use client'

import {
  BlockTypeSelect,
  BoldItalicUnderlineToggles,
  CreateLink,
  InsertThematicBreak,
  ListsToggle,
  MDXEditor,
  UndoRedo,
  headingsPlugin,
  linkDialogPlugin,
  linkPlugin,
  listsPlugin,
  thematicBreakPlugin,
  toolbarPlugin
} from '@mdxeditor/editor'
import { RemoveFormatting } from 'lucide-react'

function Editor ({ markdown, editorRef, onChange, onReset }: EditorProps): React.ReactNode {
  return (
    <MDXEditor
      onChange={(e) => onChange(e)}
      ref={editorRef}
      markdown={markdown}
      plugins={[toolbarPlugin({
        toolbarContents: () => (
          <>
            {' '}
            <UndoRedo />
            <BoldItalicUnderlineToggles />
            <BlockTypeSelect />
            <InsertThematicBreak />
            <CreateLink />
            <ListsToggle />
            <button title='Reset All Formatting' type='button' className='w-auto h-auto' onClick={() => onReset()}>
              <RemoveFormatting className='w-[28px] h-[28px]' />
            </button>
          </>
        )
      }), headingsPlugin(), listsPlugin(), linkPlugin(), thematicBreakPlugin(), linkDialogPlugin()]}
    />
  )
}

export default Editor