LobeHub Editor

repository·master·Indexed 41 days ago

https://github.com/lobehub/lobe-editor

A powerful and extensible rich text editor built on Meta's Lexical framework, featuring a dual-architecture design (Kernel + React) optimized for AI applications, chat interfaces, and complex content editing. It supports Yjs for real-time collaboration, a plugin-based system for custom functionality, and provides both a React-integrated layer and a framework-agnostic Kernel API. Version 4.27.3.

Tokens
134K
Snippets
480
Records
617
Agent score
50%

What's inside LobeHub Editor

  1. Choose the right Lobe Editor component for your use case

    master

    Lobe Editor provides different component types depending on whether you need an interactive editor, a static renderer, or a diff viewer:

    • Interactive Editing: Use the Editor component for a full, editable experience with React UI building blocks.
    • Static Rendering / SSR: Use LexicalRenderer for a headless JSON-to-React renderer. This is ideal for displaying content without editing capabilities or for Server-Side Rendering (SSR).
    • Document Comparison: Use LexicalDiff to view a read-only diff between two SerializedEditorState values.
    • Extending Functionality: Use the Plugins system to add new capabilities or modify editor behavior.
  2. Use the List plugin for ordered and unordered lists

    master

    The List plugin provides comprehensive list functionality, including multi-level nesting, list item manipulation, and markdown shortcuts. It leverages Lexical's ListNode and ListItemNode to support both bulleted and numbered lists.

    Core Capabilities

    • Nesting: Supports deeply nested list structures.
    • Markdown Shortcuts: Automatically creates lists using patterns like -, *, +, or 1..
    • Keyboard Navigation: Supports Tab/Shift+Tab for indentation and Enter for new items.
    • Type Conversion: Easily switch between ordered and unordered lists.
    const listPlugin = new ListPlugin(kernel, {
      theme: {
        list: 'custom-list',
        listItem: 'custom-list-item',
        nestedListItem: 'custom-nested-item',
      },
      maxNestingLevel: 5,
      enableMarkdown: true,
      autoIndent: true,
    });
  3. Use the Table plugin for full-featured table editing

    master
    The Table plugin provides comprehensive table editing capabilities built on top of Lexical. It supports table creation, manipulation, custom styling, and internationalization. It includes a specialized node system for enhanced table operations and React components for seamless integration.
  4. Use LexicalDiff for side-by-side document comparison

    master
    LexicalDiff is a read-only component designed to compare two SerializedEditorState values and render a side-by-side diff view. It aligns root blocks and provides character-level highlighting for inline text modifications. It reuses the existing LexicalRenderer to ensure that standard block nodes render correctly without additional configuration.
  5. Collaboration constraints for Collapsible blocks

    master

    To prevent UI conflicts during multi-user editing, the plugin implements a guard on the collapse action:

    An expanded collapsible cannot be collapsed if a remote collaborator has a cursor inside it.

    The plugin validates this by checking both Yjs relative positions and the materialized Lexical cursor selection to ensure accuracy even during pending updates.

  6. Implement custom block renderers in LexicalDiff

    master

    To customize how specific block types are displayed during a diff, you can provide custom rendering logic via two main extension points:

    1. blockRenderers[type]: Use this to provide a custom renderer for rows where both the original and the modified blocks are of the same type.
    2. renderBlockDiff: Use this as a fallback renderer for rows where the block types differ (cross-type changes) or when no specific type-based renderer is provided.
  7. Compare LexicalRenderer and Editor for read-only scenarios

    master

    When deciding between using the full <Editor> component with editable={false} versus using <LexicalRenderer>, consider the following:

    • <Editor editable={false}>: Loads the full editor engine, plugins, and DOM bindings. It is more heavy-weight but provides the exact same environment as the editor.
    • <LexicalRenderer>: A lightweight, headless renderer that only performs JSON-to-React conversion. It is significantly more efficient for displaying content in read-only modes, especially in server-side environments where a full editor instance is unnecessary or impossible to instantiate.
  8. How keyboard navigation and boundary handling works in Collapsible

    master

    The Collapsible plugin manages arrow navigation and Backspace behavior through two layers to ensure consistent selection movement around nested contentEditable and empty block boundaries:

    1. Lexical Commands: Handles standard editor keyboard dispatch for critical-priority operations.
    2. DOM keydown Listeners: A capture-phase listener scoped to the editor root handles native selection movement that might bypass Lexical commands.

    Key behaviors:

    • Edge Navigation: When navigating near an edge collapsible or decorator block, the plugin may create a temporary paragraph to facilitate movement, removing it once the selection leaves.
    • Collapsed State Selection: If a selection attempt resolves into a hidden child (within a collapsed section), the plugin redirects the selection back to the visible title.
  9. Customize search matching strategies

    master

    You can control how users find commands by adjusting matchingStrategy and matchingFields within SlashOptions:

    • startsWith (Default): Matches the beginning of the string. Use matchingFields: ['key'] for standard behavior or ['title', 'description'] for more descriptive searches.
    • fuzzy: Uses Fuse.js for flexible matching, allowing users to find items even with typos or partial character sequences.

    Example of fuzzy matching:

    const fuzzyMatching = {
      trigger: '#',
      matchingStrategy: 'fuzzy',
      matchingFields: ['key', 'title'],
      items: [
        { key: 'react-component', title: 'React Component' },
        { key: 'vue-template', title: 'Vue Template' },
      ],
    };
  10. Markdown serialization for LinkHighlight

    master

    The plugin supports automatic markdown serialization and deserialization using the <url> format. This allows for seamless persistence and editing.

    Supported Formats:

    • Standard URLs: <https://example.com>
    • Email: <mailto:user@example.com>
    • Phone: <tel:+1234567890>

    Automatic Conversion:

    • Typing: Typing a URL inside angle brackets automatically converts it to a LinkHighlightNode.
    • Pasting: Pasting a URL (HTTP, Mailto, or Tel) automatically wraps it in a LinkHighlightNode if enablePasteAutoHighlight is enabled.
    • Markdown Content: Pasting markdown containing <url> syntax will be converted to LinkHighlightNode instead of a standard LinkNode.
  11. Manage Editor lifecycle and destruction

    master

    The useEditor hook manages the editor's kernel reference. By default, it uses a reversible root detach during React cleanup. This detaches the editor from the global registry and removes DOM/dragon listeners while preserving the Lexical state, history, and plugin configuration. This is ideal for use with <Activity> components where a subtree is hidden but not destroyed.

    Destruction Strategies

    1. Activity-safe (Default): Use useEditor() without options. This preserves state so the editor can be reattached when the component becomes visible again.
    2. Permanent Unmount: Use useEditor({ autoDestroy: true }). This destroys all runtime resources when the hook owner unmounts. Warning: This is mutually exclusive with <Activity> because the editor will be destroyed when the subtree is hidden.
    3. Manual Destruction: Call editor.destroy() explicitly to release resources. This is useful when an editor is passed to other components or stored in long-lived references (like Zustand or Providers).

    Note: Reversible detach does not guarantee garbage collection if you maintain a reference to the IEditor instance in a global store. You must remove that reference or call .destroy() to reclaim memory.

    // Activity-safe default: detach while hidden, preserve state, and reattach.
    const editor = useEditor();
    
    // Permanent owner only: destroy all runtime resources on unmount.
    const editor = useEditor({ autoDestroy: true });
  12. How the Math plugin works

    master

    The Math plugin enables LaTeX-style mathematical expressions in the editor. It supports two primary node types: MathInlineNode for inline math (e.g., $E=mc^2$) and MathBlockNode for standalone block math (e.g., $$E=mc^2$$).

    Key features include:

    • Real-time rendering: Uses decorators to render LaTeX syntax into visual notation.
    • Markdown Integration: Automatically converts LaTeX-style shortcuts ($...$ and $$) into math nodes and supports seamless markdown serialization.
    • Command System: Allows programmatic insertion, updating, and navigation of math nodes.
    • Customizable Rendering: Developers can provide custom React components or decorators to handle how math is displayed (e.g., using KaTeX or MathJax).
    import { MathPlugin, ReactMathPlugin } from '@lobehub/editor';
    
    // Register the plugin with custom renderer
    const mathPlugin = new MathPlugin(kernel, {
      decorator: (node, editor) => {
        return <KaTeXRenderer code={node.code} />;
      },
      theme: {
        mathInline: 'custom-inline-math',
        mathBlock: 'custom-block-math'
      }
    });