or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

conversion-utilities.mddata-models.mdeditor-components.mdentity-system.mdindex.mdkey-bindings-utilities.mdtext-modification.md
tile.json

entity-system.mddocs/

Entity System

System for managing rich content entities like links, images, and embedded media, along with text decoration capabilities.

Capabilities

DraftEntity (exported as Entity)

Global entity storage and management system (legacy API). Entities represent rich content like links, images, or any data associated with text ranges.

/**
 * Global entity management (legacy API)
 */
const Entity: {
  /**
   * Create new entity
   * @param type - Entity type string
   * @param mutability - How the entity behaves during editing
   * @param data - Associated data object
   * @returns Entity key string
   */
  create(type: DraftEntityType, mutability: DraftEntityMutability, data?: any): string;
  
  /**
   * Add entity instance to storage
   * @param instance - DraftEntityInstance to add
   * @returns Entity key string
   */
  add(instance: DraftEntityInstance): string;
  
  /**
   * Get entity by key
   * @param key - Entity key to retrieve
   * @returns DraftEntityInstance
   */
  get(key: string): DraftEntityInstance;
  
  /**
   * Merge data into existing entity
   * @param key - Entity key to update
   * @param toMerge - Data to merge
   */
  mergeData(key: string, toMerge: {[key: string]: any}): void;
  
  /**
   * Replace entity data
   * @param key - Entity key to update
   * @param newData - New data to replace existing
   */
  replaceData(key: string, newData: {[key: string]: any}): void;
  
  /**
   * Get last created entity key
   * @returns Most recent entity key
   */
  getLastCreatedEntityKey(): string;
};

Usage Examples:

import { Entity, Modifier, EditorState } from "draft-js";

// Create link entity
function createLink(editorState, url, text) {
  const contentState = editorState.getCurrentContent();
  const selection = editorState.getSelection();
  
  // Create entity
  const entityKey = Entity.create('LINK', 'MUTABLE', { 
    url: url,
    title: text 
  });
  
  // Apply entity to selected text
  const newContentState = Modifier.applyEntity(
    contentState,
    selection,
    entityKey
  );
  
  return EditorState.push(editorState, newContentState, 'apply-entity');
}

// Create image entity
function insertImage(editorState, imageUrl, altText) {
  const entityKey = Entity.create('IMAGE', 'IMMUTABLE', {
    src: imageUrl,
    alt: altText,
    width: 'auto',
    height: 'auto'
  });
  
  return AtomicBlockUtils.insertAtomicBlock(
    editorState,
    entityKey,
    ' '
  );
}

// Update entity data
function updateLinkUrl(entityKey, newUrl) {
  Entity.mergeData(entityKey, { url: newUrl });
}

// Get entity data
function getLinkUrl(entityKey) {
  const entity = Entity.get(entityKey);
  return entity.getData().url;
}

DraftEntityInstance (exported as EntityInstance)

Immutable representation of a single entity instance containing type, mutability, and associated data.

/**
 * Immutable entity instance
 */
class DraftEntityInstance {
  /**
   * Get entity type
   * @returns Entity type string
   */
  getType(): DraftEntityType;
  
  /**
   * Get entity mutability
   * @returns Mutability setting
   */
  getMutability(): DraftEntityMutability;
  
  /**
   * Get entity data
   * @returns Associated data object
   */
  getData(): any;
  
  /**
   * Create new instance with merged data
   * @param toMerge - Data to merge
   * @returns New DraftEntityInstance
   */
  mergeData(toMerge: {[key: string]: any}): DraftEntityInstance;
  
  /**
   * Create new instance with replaced data
   * @param newData - New data to replace existing
   * @returns New DraftEntityInstance
   */
  replaceData(newData: {[key: string]: any}): DraftEntityInstance;
};

Usage Examples:

import { Entity } from "draft-js";

// Working with entity instances
function analyzeEntity(entityKey) {
  const entity = Entity.get(entityKey);
  
  const type = entity.getType(); // 'LINK', 'IMAGE', etc.
  const mutability = entity.getMutability(); // 'MUTABLE', 'IMMUTABLE', 'SEGMENTED'
  const data = entity.getData(); // { url: '...', title: '...' }
  
  return { type, mutability, data };
}

// Update entity immutably
function createUpdatedEntity(entityKey, newData) {
  const entity = Entity.get(entityKey);
  return entity.mergeData(newData);
}

CompositeDraftDecorator (exported as CompositeDecorator)

Decorator system for applying React components to text ranges based on content patterns (like highlighting, mentions, hashtags).

/**
 * Decorator for applying React components to text ranges
 */
class CompositeDraftDecorator {
  /**
   * Create composite decorator
   * @param decorators - Array of decorator configurations
   */
  constructor(decorators: Array<{
    strategy: (contentBlock: ContentBlock, callback: (start: number, end: number) => void, contentState: ContentState) => void,
    component: React.ComponentType<any>,
    props?: {[key: string]: any}
  }>);
  
  /**
   * Get decorations for a content block
   * @param block - Content block to analyze
   * @param contentState - Full content state
   * @returns List of decoration keys
   */
  getDecorations(block: ContentBlock, contentState: ContentState): List<string>;
  
  /**
   * Get React component for decoration
   * @param key - Decoration key
   * @returns React component
   */
  getComponentForKey(key: string): Function;
  
  /**
   * Get props for decoration component
   * @param key - Decoration key
   * @returns Props object
   */
  getPropsForKey(key: string): any;
};

Usage Examples:

import React from "react";
import { CompositeDecorator, EditorState } from "draft-js";

// Link decorator component
function Link(props) {
  const { contentState, entityKey, children } = props;
  const entity = contentState.getEntity(entityKey);
  const { url } = entity.getData();
  
  return (
    <a href={url} style={{ color: 'blue', textDecoration: 'underline' }}>
      {children}
    </a>
  );
}

// Hashtag decorator component
function Hashtag(props) {
  return (
    <span style={{ color: '#1DA1F2', fontWeight: 'bold' }}>
      {props.children}
    </span>
  );
}

// Mention decorator component
function Mention(props) {
  return (
    <span style={{ color: '#1DA1F2', backgroundColor: '#E8F4FD' }}>
      {props.children}
    </span>
  );
}

// Strategy functions
function findLinkEntities(contentBlock, callback, contentState) {
  contentBlock.findEntityRanges(
    (character) => {
      const entityKey = character.getEntity();
      return (
        entityKey !== null &&
        contentState.getEntity(entityKey).getType() === 'LINK'
      );
    },
    callback
  );
}

function findHashtagEntities(contentBlock, callback) {
  const text = contentBlock.getText();
  const regex = /#[\w]+/g;
  let matchArr, start;
  
  while ((matchArr = regex.exec(text)) !== null) {
    start = matchArr.index;
    callback(start, start + matchArr[0].length);
  }
}

function findMentionEntities(contentBlock, callback) {
  const text = contentBlock.getText();
  const regex = /@[\w]+/g;
  let matchArr, start;
  
  while ((matchArr = regex.exec(text)) !== null) {
    start = matchArr.index;
    callback(start, start + matchArr[0].length);
  }
}

// Create decorator
const decorator = new CompositeDecorator([
  {
    strategy: findLinkEntities,
    component: Link,
  },
  {
    strategy: findHashtagEntities,
    component: Hashtag,
  },
  {
    strategy: findMentionEntities,
    component: Mention,
  },
]);

// Use with editor state
const editorState = EditorState.createEmpty(decorator);

// Update decorator
function updateDecorator(currentEditorState, newDecorator) {
  return EditorState.set(currentEditorState, {
    decorator: newDecorator
  });
}

Working with Entities in Content

/**
 * Utility functions for working with entities in content
 */

// Find all entities of a specific type
function findEntitiesByType(contentState: ContentState, entityType: DraftEntityType): Array<{
  blockKey: string,
  start: number,
  end: number,
  entityKey: string
}> {
  const entities = [];
  const blocks = contentState.getBlocksAsArray();
  
  blocks.forEach(block => {
    block.findEntityRanges(
      character => {
        const entityKey = character.getEntity();
        return entityKey !== null && 
               contentState.getEntity(entityKey).getType() === entityType;
      },
      (start, end) => {
        const entityKey = block.getEntityAt(start);
        entities.push({
          blockKey: block.getKey(),
          start,
          end,
          entityKey
        });
      }
    );
  });
  
  return entities;
}

// Remove all entities of a specific type
function removeEntitiesByType(editorState: EditorState, entityType: DraftEntityType): EditorState {
  const contentState = editorState.getCurrentContent();
  const entities = findEntitiesByType(contentState, entityType);
  
  let newContentState = contentState;
  
  entities.forEach(({ blockKey, start, end }) => {
    const selection = SelectionState.createEmpty(blockKey).merge({
      anchorOffset: start,
      focusOffset: end
    });
    
    newContentState = Modifier.applyEntity(newContentState, selection, null);
  });
  
  return EditorState.push(editorState, newContentState, 'apply-entity');
}

Complete Entity Example:

import React, { useState } from "react";
import { Editor, EditorState, Entity, RichUtils, CompositeDecorator } from "draft-js";

// Link component with editing capability
function EditableLink(props) {
  const { contentState, entityKey, children } = props;
  const entity = contentState.getEntity(entityKey);
  const { url, title } = entity.getData();
  
  const handleClick = (e) => {
    e.preventDefault();
    const newUrl = prompt('Enter URL:', url);
    if (newUrl) {
      Entity.mergeData(entityKey, { url: newUrl });
    }
  };
  
  return (
    <a 
      href={url} 
      title={title}
      onClick={handleClick}
      style={{ color: 'blue', textDecoration: 'underline', cursor: 'pointer' }}
    >
      {children}
    </a>
  );
}

function MyEditor() {
  const decorator = new CompositeDecorator([
    {
      strategy: (contentBlock, callback, contentState) => {
        contentBlock.findEntityRanges(
          (character) => {
            const entityKey = character.getEntity();
            return entityKey !== null && 
                   contentState.getEntity(entityKey).getType() === 'LINK';
          },
          callback
        );
      },
      component: EditableLink,
    },
  ]);
  
  const [editorState, setEditorState] = useState(
    EditorState.createEmpty(decorator)
  );
  
  const addLink = () => {
    const selection = editorState.getSelection();
    if (!selection.isCollapsed()) {
      const url = prompt('Enter URL:');
      if (url) {
        const entityKey = Entity.create('LINK', 'MUTABLE', { url });
        const newEditorState = RichUtils.toggleLink(editorState, selection, entityKey);
        setEditorState(newEditorState);
      }
    }
  };
  
  return (
    <div>
      <button onClick={addLink}>Add Link</button>
      <Editor
        editorState={editorState}
        onChange={setEditorState}
      />
    </div>
  );
}

Types

Entity-Related Types

// Entity types
type DraftEntityType = string; // 'LINK', 'IMAGE', 'MENTION', etc.

// Entity mutability
type DraftEntityMutability = 'MUTABLE' | 'IMMUTABLE' | 'SEGMENTED';

// Decorator interface
interface DraftDecoratorType {
  getDecorations(block: ContentBlock, contentState: ContentState): ?Array<?string>;
  getComponentForKey(key: string): Function;
  getPropsForKey(key: string): any;
}

// Decorator strategy function
type DecoratorStrategy = (
  contentBlock: ContentBlock,
  callback: (start: number, end: number) => void,
  contentState: ContentState
) => void;

// Decorator configuration
interface DecoratorConfig {
  strategy: DecoratorStrategy;
  component: React.ComponentType<any>;
  props?: {[key: string]: any};
}