MDXEditor

repository·main·Indexed 25 days ago

https://github.com/mdx-editor/editor

An open-source React component for natural markdown editing, providing a user experience similar to Notion or Google Docs. It supports standard markdown, JSX component editing, and various extensions via a plugin system, including support for headings, lists, quotes, thematic breaks, and fenced code blocks with CodeMirror integration. It also features a flexible system for implementing custom directive editors and admonitions.

Tokens
41.3K
Snippets
74
Records
247
Agent score
85%

What's inside @mdxeditor/editor

  1. Overview of MDXEditor

    main

    MDXEditor is an open-source React component designed for WYSIWYG markdown authoring. It is specifically tailored to markdown syntax constraints, meaning it does not support presentation-related styling like font size, color, or font family.

    Key characteristics:

    • Data Format: The component accepts and emits markdown as a string. It does not use an intermediate HTML representation.
    • Extensibility: The core is minimal. Features like headings, quotes, and links are enabled via a plugin system.
    • Use Case: Ideal when markdown is your persistent content format (e.g., for documentation or content intended for multi-format rendering like HTML, PDF, or Word).
  2. Understand HTML support in MDXEditor

    main

    MDXEditor supports HTML elements embedded within Markdown documents. By default, these elements are converted into generic HTML nodes that extend Lexical's Element nodes. This allows users to edit the nested Markdown content inside those HTML elements directly.

    Recommendation: While HTML is supported, it is recommended to use directives or custom JSX components instead of raw HTML to maintain Markdown's human-readable and intention-limited format.

  3. Understand MDXEditor Search and Replace behavior

    main

    MDXEditor's search and replace functionality is state-backed, meaning it uses the active editor's Lexical state as the source of truth for positions.

    Key behaviors include:

    • Positioning: While the editor uses Lexical snapshots for authoritative positioning, it provides public DOM Range and TextNodeIndex values as rebuilt projections.
    • Replace & Replace All: These operations resolve current state positions and apply matches from last-to-first in a single tagged update. This ensures that the search action itself is recorded as a single undo/redo step in the editor's history.
    • Formatting: Replacement text follows Lexical RangeSelection.insertText rules. Formatting outside of the matched spans remains unaffected.
    • Scope: Search is supported across various scopes including root, JSX, directives, and table cells.
    • Cleanup: The editor performs explicit cleanup of search states and listeners upon unmounting or switching active editors.
  4. Compatibility and Supported Environments for MDXEditor

    main

    MDXEditor is designed to be compatible with the following environments and constraints:

    • React Support: Compatible with React 18 and React 19 (including React DOM).
    • Node.js: Requires Node.js version >=16.
    • Data Format: Uses Markdown as the primary input/output contract. Upgrading the underlying Lexical engine (e.g., to 0.48) is designed to be non-breaking for existing Markdown content.
    • Browser Behavior: Implements security hardening for URLs. Dangerous or obfuscated URL schemes (e.g., javascript:, data:text/html,...) will render and preview as about:blank within the editor/dialog to prevent execution, while the authored raw URL is preserved in the Markdown and callback payloads.
  5. MDXEditor Consumer Contract and Compatibility

    main

    MDXEditor provides a stable contract for React integrators, Markdown authors, and plugin developers.

    Supported Boundaries

    • Package: The published @mdxeditor/editor package and its declarations.
    • APIs: MDXEditor props, MDXEditorMethods, and documented plugins/toolbar flows.
    • Data: Markdown input/output via callbacks or methods.
    • Extensions: Public import/export visitors and Gurx plugin registration.
    • Interactions: Root and nested contenteditable interactions.

    Compatibility Guarantees

    • React: Supports React 18 and 19.
    • Lexical: All Lexical packages are maintained in lockstep.
    • API Stability: Public MDXEditor APIs and plugin/visitor contracts are source-compatible unless a breaking change is explicitly announced.
    • Security: Retains fail-closed URL sanitization and other security fixes.

    Limitations (Not Claimed)

    • Byte-identical Markdown for syntax that MDXEditor intentionally canonicalizes.
    • Preservation of undocumented Lexical internals.
    • Complete mobile/IME certification.
    • @lexical/mdast compatibility or adoption.
  6. MDXEditor Architecture and Plugin System

    main

    MDXEditor is built using the Lexical editor framework and the MDAST family of packages.

    How it works:

    • Bi-directional Conversion: The component converts between the Markdown Abstract Syntax Tree (MDAST) and the Lexical AST using a set of visitors.
    • Plugin Mechanism: Plugins extend the editor by adding additional MDAST/Lexical AST visitors and additional Lexical nodes.
    • Markdown Processing: While it uses @lexical/markdown for shortcuts, MDXEditor implements its own markdown processing to handle advanced cases (like nested lists) that the standard Lexical implementation might not support.
  7. Use the table editor features

    main

    The table editor provides the following capabilities:

    • Insert and remove rows and columns.
    • Change column alignment.
    • Include markdown content (formatting, links, images, etc.) within individual cells.

    Note: HTML tables are not supported; only GFM markdown tables are supported.

  8. Understand the MDXEditor state management model

    main

    MDXEditor uses a reactive state management system called Gurx. The editor initializes a realm containing stateful Cells, stateless Signals, and Actions.

    • Cells: Hold stateful values (e.g., myCell$).
    • Signals: Stateless nodes used to trigger events or pass values through pipes (e.g., mySignal$).
    • Actions: Used to publish changes into the system.

    By convention, Cells and Signals are suffixed with $. You can interact with built-in state by subscribing to or publishing to exported cells/signals like rootEditor$ (the Lexical instance) or activeEditor$.

    import { realmPlugin, Cell, Signal } from '@mdxeditor/editor'
    
    // declare a stateful cell that holds a string value.
    const myCell$ = Cell('')
    
    // This is a stateless signal
    const mySignal$ = Signal<number>((r) => {
      // connect the signal node to the cell using the `pipe` operator.
      r.link(
        r.pipe(
          mySignal$,
          r.o.map((v) => `mySignal has been called ${v} times`)
        ),
        myCell$
      )
    })
  9. Plugin Lifecycle and Initialization

    main

    MDXEditor uses a RealmWithPlugins mechanism to manage the editor lifecycle:

    1. Initialization: Runs all plugin init functions, followed by all postInit functions.
    2. Root Publication: The corePlugin.postInit builds and publishes the root editor from the complete registry.
    3. Lifecycle Ownership: The editor session is managed in the commit phase to ensure that all plugins are initialized before the editor is exposed.
    4. Cleanup: Private, exactly-once disposers are invoked during cleanup to prevent memory leaks, especially under React Strict Mode.
  10. Understand the MDXEditor Extension and History Architecture

    main

    MDXEditor uses an internal extension-based composer to manage editor instances, including the root editor and nested editors (like those found in JSX/directives or tables).

    Key architectural concepts for consumers:

    • Internal Construction: Lexical extensions are internal implementation details. Consumers should continue using the supported model: realmPlugin, visitors, nodes, and ordinary React composer children.
    • History Modes: The editor manages history through several modes: root-shared, nested-shared, nested-external, and table-local. These modes control how undo/redo operations are synchronized between the root editor and nested components (like table cells).
    • Command Availability: Undo/Redo functionality is driven by public Lexical commands. The UndoRedo component observes these existing observable APIs rather than internal extension signals.
  11. Enable code block support and editing

    main

    To support fenced code blocks in MDXEditor, you must use both codeBlockPlugin (which enables the markdown structure) and codeMirrorPlugin (which provides the actual editing UI). You can also include toolbarPlugin with specific components like InsertCodeBlock and ChangeCodeMirrorLanguage to provide a user interface for managing code blocks.

    Note: codeBlockPlugin accepts a defaultCodeBlockLanguage option to set the language used when a user inserts a new block via the toolbar.

    function App() {
      return (
        <MDXEditor
          markdown="hello world"
          plugins={[
            // enables fenced code blocks
            codeBlockPlugin({ defaultCodeBlockLanguage: 'js' }),
            // enables the CodeMirror editing UI
            codeMirrorPlugin({ codeBlockLanguages: { js: 'JavaScript', css: 'CSS', tsx: 'TypeScript (React)' } }),
            toolbarPlugin({
              toolbarContents: () => (
                <ConditionalContents
                  options={[
                    { when: (editor) => editor?.editorType === 'codeblock', contents: () => <ChangeCodeMirrorLanguage /> },
                    {
                      fallback: () => (
                        <>
                          <InsertCodeBlock />
                        </>
                      )
                    }
                  ]}
                />
              )
            })
          ]}
        />
      )
    }