Skip to content

Instantly share code, notes, and snippets.

@davidnus
Last active March 26, 2025 13:15
Show Gist options
  • Select an option

  • Save davidnus/6451a141511be334b2d52615585919a7 to your computer and use it in GitHub Desktop.

Select an option

Save davidnus/6451a141511be334b2d52615585919a7 to your computer and use it in GitHub Desktop.
Interoperability Between an Extended Lexical Node and Codox

Creating an extended TextNode to work with Codox

This guide explains how to extend a foundational Lexical node, i.e. nodes found under https://github.com/facebook/lexical/blob/main/packages/lexical/src/nodes/, to work with Codox for real-time collaboration.

The following key steps will be explained:

  1. Extension: The recipes are largely similar for most Lexical nodes extensions
  2. Serialization: Converting the extended Lexical node into a Codox-compatible JSON format.
  3. Deserialization: Converting a Codox JSON node back into a Lexical-compatible format.

1. Setting Up Your Extended Node

In this example, we consider a custom node, WarningNode that extends the basic TextNode. A TextNode has the following standard properties: text, format, style, mode, detail ( see https://github.com/facebook/lexical/blob/88382127710e167cbdc608c7119edabfef503f27/packages/lexical/src/nodes/LexicalTextNode.ts#L294)). Among these properties text, format, style are directly mutable by users. For instance,

  • text contains a string that is potentially modified by multiple users concurrently.
  • format is a comma separated list of format values, e.g. bold,italic. Each of these format values are mutable individually, for instance a user might toggle the bold format where by the previous example would become italic.
  • style is a colon separate list of css property value pairs, e.g. "color: #d0021b;background-color: #f5a623". Simliar to format each css prop-value pair is potentially modifiable individually via the UX.

In this example, we will extend the original TextNode to save the the font-color and background-color information as individual properties of the node object definition. We also introduce an identifier to identify the node. The semantics of this identifier can be customized to what is appropriate for the application. The point is to illustrate how to handle two types of node properties:

  • Immutable custom metadata (e.g., __id).
  • Mutable style properties (e.g., __color and __backgroundColor).

Below we show a subset of the standard approach to extending a Lexical base node with additional properties. This is just to demonstrate that from a node property extension point of view, there is nothing that is particularly different or difficult:

/**
   * Assume you want to add the following attributes to an extended node
   */
  __id; //some custom id that's immutable
  __color; //color attribute that holds a string, and is mutable
  __backgroundColor; //background color attribute that holds a string, and is mutable

 constructor(id, color, backgroundColor, text, key) {
    // invoke parent constructor
    super(text, key);
    
    // extension: set custom attributes, with default values
    this.__id = id || 'mock_id';
    this.__color = color || '#ffffff';
    this.__backgroundColor = backgroundColor || '#ff4902';
  }
  
  exportJSON() {
    return {
      // invoke parent text node export
      ...super.exportJSON(),
      version: 1,
      
     /**
      * extension: attach custom properties to the exported node
      */
      id: this.__id,
      color: this.__color,
      backgroundColor: this.__backgroundColor,    
      type: 'warning',
      
    };
  }
  
 /*
  * Override TextNode createDOM
  */
  createDOM(config) {
    //Invoke parent createDOM
    const dom = super.createDOM(config);
    
    /**
     * extension: Compose custom css styles string from custom attributes
     */
    dom.style.cssText = `color:${this.__color};background-color:${this.__backgroundColor};`;
    dom.className = 'warning-text-frame';
    
    return dom;
  }

2. Converting from Lexical to Codox Format

Once a node has been extended, its associated interactions programmed and tested, it is time to consider its relationship to Codox. Intuitively you need to tell Codox which properties can be modified, and therefore need to be merged and synchronized, as well as properties that are pure props that should not be considered for merging and synchronization (i.e. immutable). To express this via code, you need to implement two methods:

toCodoxNode

The method toCodoxNode transforms a Lexical node into a JSON structure acceptable by Codox. Here are the steps:

  1. Extract Base JSON: Start by using the node's existing exportJSON() method. This retrieves the base text node properties.

  2. Augment with Extended Properties:

    • Mutable Properties: In the WarningNode example, text, color, backgroundColor are all mutable and therefore Codox must be aware that they are mutable. Codox is already aware that the foundational TextNode contains a text property and therefore will handle the merging of text content automatically. That then leaves color and backgroundColor as 'new' properties that must be communicated to Codox. To pass these mutable properties into Codox, we can seralize the style propertie, color and background-color, into a single CSS string and assign that string to the style property of the TextNode. Again, Codox is aware that style exists and will appropriately parse the string into individual properties for merging.
    • Immutable Metadata: Attach a codox_metadata container object to house immutable properties, such as the extended id property. Important: you should always include the extended node's type, in this case type: "warning" in the codox_metadata container object - this type ensures Codox is fully aware of the node's identity and that the extended type are preserved during the backward conversion.
  3. Construct the Codox Node: Putting it altogether the final Codox node as a JSON should include:

    • Base text attributes (e.g., text, format, mode, detail, and version).
    • A style string reflecting the current mutable properties.
    • A codox_metadata field containing immutable details.

Example Code Snippet:

 /**
   * Implement the following API methods to interoperate with Codox
   * Lexical -> Codox Node 
   * Converts lexical node to json node, acceptable by Codox.
   */
  toCodoxNode() {
    /**
     * Step 1: Get original text node json. You should/must use .exportJSON()
     */
    const originalJsonNode = this.exportJSON();

    /**
     * Step 2: Extend with additional properties.
     */
    const convertedNode = {
      type: 'text', // type property must be one of core or playground node types
      
      // preserve default text node attributes
      text: originalJsonNode.text,
      format: originalJsonNode.format,
      mode: originalJsonNode.mode,
      detail: originalJsonNode.detail,
      version: originalJsonNode.version,

      // extension: all mutable style properties should be serialized as a style string, as per TextNode definition 
      // assumption is the lexical node style is empty, if not, concat the styles. 
      style: `color:${originalJsonNode.color};background-color:${originalJsonNode.backgroundColor};`,

      /**
       * extension: codox_metadata should hold immutables:
       *  - required: your extended node type, e.g. 'warning' 
       *  - optional: any immutable properties of an original node - these fields are not merged and synchronized. 
       */
      codox_metadata: {
        type: originalJsonNode.type, //type == 'warning'
        id: originalJsonNode.id, //some custom id an immutable property
      },
    };
    console.log('[DEMO DEBUG][WarningNode][toCodoxNode]: ', { convertedNode, originalJsonNode });

    return convertedNode;
  }

Key Points

Immutable vs. Mutable

Ensure that immutable properties (like the custom id and node type) are stored in a dedicated metadata object (codox_metadata) to avoid accidental overwrites.

Style Concatenation

Combine mutable style properties into a single string. This format is expected by Codox and will be subject to synchronization.


3. Converting from Codox to Lexical Format

Once a node has been synchronized internally, Codox needs to know how to return a node representation that your application understands. To do that you need to provaide a static method that performs the opposite of the marshalling process described in the previous section.

Implementing fromCodoxNode

The reverse process involves deserializing a codoxNode back into a Lexical-compatible format. This requires the extraction of style properties and reassigning both base and extended attributes. codoxNode will contain exactly the properties produced by the toCodoxNode function.

  1. Extract the Style String Split the CSS style string which reflects the merged and synnchronized font properties (which maybe different to the original style) to retrieve the updated individual style attributes (e.g., color and background color).

  2. Rebuild the Lexical Node JSON Create a new JSON object that mirrors the Lexical node structure. Include:

  • Base attributes: text, format, mode, detail, version
  • Extended attributes: custom id, color, and background color (ensuring the type is correctly set)

Example Code Snippet

/**
   * Implement the following API methods to interoperate with Codox
   * Codox -> Lexical Node
   * Converts synchronized codox json node to lexical json node
   * must be class 'static' method
   */
  static fromCodoxNode(codoxNode) {
    /**
     * extension: all mutable properties should be deserialized from the style string, 
     */
    let color = '';
    let backgroundColor = '';
    codoxNode.style.split(';').forEach((css) => {
      if (css.startsWith('color:')) {
        color = css.split(':')[1];
      }
      if (css.startsWith('background-color:')) {
        backgroundColor = css.split(':')[1];
      }
    });

    // compose original json node from codox node
    const originalJsonNode = {
      // set original type - in this example it will be "warning"
      type: codoxNode.codox_metadata.type, 
      // set base attributes
      text: codoxNode.text,
      format: codoxNode.format,
      mode: codoxNode.mode,
      detail: codoxNode.detail,
      version: codoxNode.version,
      style: '',  // assumption here is the lexical node style is empty, if not, splice out the extended mutable properties

      /**
       * set custom attributes
       */
      id: codoxNode.codox_metadata.id,
      color: color,
      backgroundColor: backgroundColor,
    };
    return originalJsonNode;
  }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment