CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-udecode--plate-media

Comprehensive media handling capabilities for the Plate rich text editor framework, supporting images, videos, audio files, and embeddable content

Pending
Overview
Eval results
Files

upload-system.mddocs/

Upload System

Advanced placeholder-based upload system with file validation, progress tracking, error handling, and drag & drop support. Provides a comprehensive solution for handling file uploads in rich text editing contexts.

Capabilities

Base Placeholder Plugin

Core plugin providing placeholder functionality for file uploads with validation and progress tracking.

/**
 * Base plugin with insert methods for all media types
 * Provides placeholder elements that can be replaced with actual media
 */
const BasePlaceholderPlugin: TSlatePlugin<PlaceholderConfig>;

interface PlaceholderConfig extends MediaPluginOptions {
  rules?: PlaceholderRule[];
}

interface PlaceholderRule {
  /** Media type this rule applies to */
  mediaType: string;
}

Usage Example:

import { BasePlaceholderPlugin } from "@udecode/plate-media";
import { createSlateEditor } from "@udecode/plate";

const editor = createSlateEditor({
  plugins: [
    BasePlaceholderPlugin.configure({
      options: {
        rules: [
          { mediaType: 'image' },
          { mediaType: 'video' },
          { mediaType: 'audio' },
          { mediaType: 'file' }
        ]
      }
    })
  ]
});

Placeholder Insertion

Functions for inserting placeholder elements of different media types.

Generic Placeholder Insertion

/**
 * Inserts a placeholder element for the specified media type
 * @param editor - Slate editor instance
 * @param mediaType - Type of media placeholder to insert
 * @param options - Optional insertion configuration
 */
function insertPlaceholder(
  editor: SlateEditor, 
  mediaType: string, 
  options?: InsertNodesOptions
): void;

Typed Placeholder Functions

/**
 * Insert image placeholder element
 * @param editor - Slate editor instance
 * @param options - Optional insertion configuration
 */
function insertImagePlaceholder(
  editor: SlateEditor, 
  options?: InsertNodesOptions
): void;

/**
 * Insert video placeholder element
 * @param editor - Slate editor instance
 * @param options - Optional insertion configuration
 */
function insertVideoPlaceholder(
  editor: SlateEditor, 
  options?: InsertNodesOptions
): void;

/**
 * Insert audio placeholder element
 * @param editor - Slate editor instance
 * @param options - Optional insertion configuration
 */
function insertAudioPlaceholder(
  editor: SlateEditor, 
  options?: InsertNodesOptions
): void;

/**
 * Insert file placeholder element
 * @param editor - Slate editor instance
 * @param options - Optional insertion configuration
 */
function insertFilePlaceholder(
  editor: SlateEditor, 
  options?: InsertNodesOptions
): void;

Usage Examples:

import { 
  insertImagePlaceholder, 
  insertVideoPlaceholder,
  insertPlaceholder 
} from "@udecode/plate-media";

// Insert specific placeholder types
insertImagePlaceholder(editor);
insertVideoPlaceholder(editor);

// Insert generic placeholder
insertPlaceholder(editor, 'audio');
insertPlaceholder(editor, 'file', { at: [0, 0] });

Media Node Updates

Function for updating placeholder elements with actual media data after upload.

/**
 * Updates a placeholder or media element with new properties
 * Used to replace placeholders with actual media after upload completion
 * @param editor - Slate editor instance
 * @param props - Media properties to set
 * @param options - Optional node update configuration
 */
function setMediaNode(
  editor: SlateEditor, 
  props: MediaNodeProps, 
  options?: SetNodesOptions
): void;

interface MediaNodeProps {
  /** Media element type */
  type: string;
  /** Media URL */
  url: string;
  /** Optional media ID */
  id?: string;
  /** Initial height for sizing */
  initialHeight?: number;
  /** Initial width for sizing */
  initialWidth?: number;
  /** Whether this is an upload operation */
  isUpload?: boolean;
  /** File name for downloads */
  name?: string;
  /** Placeholder ID to replace */
  placeholderId?: string;
  /** Current width for responsive sizing */
  width?: number;
}

Usage Example:

import { setMediaNode, insertImagePlaceholder } from "@udecode/plate-media";

// Insert placeholder first
insertImagePlaceholder(editor);

// Later, after upload completes
const uploadResult = await uploadFile(file);

setMediaNode(editor, {
  type: 'image',
  url: uploadResult.url,
  name: uploadResult.fileName,
  initialWidth: uploadResult.width,
  initialHeight: uploadResult.height,
  isUpload: true
});

React Upload System

Enhanced React integration with comprehensive file handling, validation, and UI components.

Placeholder Plugin

React-enhanced plugin with file drop, paste handling, and upload management.

/**
 * Enhanced React plugin with file drop/paste handling and upload management
 * Provides comprehensive file validation and progress tracking
 */
const PlaceholderPlugin: TPlatePlugin<PlaceholderConfig>;

interface PlaceholderConfig extends MediaPluginOptions {
  /** Disable empty placeholder insertion */
  disableEmptyPlaceholder?: boolean;
  /** Disable drag & drop functionality */
  disableFileDrop?: boolean;
  /** File type configuration */
  uploadConfig?: UploadConfig;
  /** Maximum files allowed globally */
  maxFileCount?: number;
  /** Allow multiple file selection */
  multiple?: boolean;
  /** Current upload error */
  error?: UploadError | null;
}

File Type Configuration

Comprehensive file type support with validation and limits.

Upload Configuration

/**
 * Configuration mapping for different file types
 */
type UploadConfig = Partial<Record<AllowedFileType, MediaItemConfig>>;

interface MediaItemConfig {
  /** Target media type for this file type */
  mediaType: MediaKeys;
  /** Maximum files allowed for this type */
  maxFileCount?: number;
  /** Maximum file size (e.g., "4MB", "100KB") */
  maxFileSize?: FileSize;
  /** Minimum files required */
  minFileCount?: number;
}

type AllowedFileType = 'image' | 'video' | 'audio' | 'pdf' | 'text' | 'blob';
type FileSize = `${number}${'B' | 'KB' | 'MB' | 'GB'}`;
type MediaKeys = 'image' | 'video' | 'audio' | 'file';

File Type Constants

/**
 * Array of all supported file type categories
 */
const ALLOWED_FILE_TYPES: AllowedFileType[] = [
  'image', 
  'video', 
  'audio', 
  'pdf', 
  'text', 
  'blob'
];

Usage Example:

import { PlaceholderPlugin, type UploadConfig } from "@udecode/plate-media/react";

const uploadConfig: UploadConfig = {
  image: {
    mediaType: 'image',
    maxFileCount: 10,
    maxFileSize: '5MB'
  },
  video: {
    mediaType: 'video', 
    maxFileCount: 3,
    maxFileSize: '50MB'
  },
  audio: {
    mediaType: 'audio',
    maxFileCount: 5,
    maxFileSize: '10MB'
  },
  pdf: {
    mediaType: 'file',
    maxFileCount: 2,
    maxFileSize: '25MB'
  }
};

const editor = createPlateEditor({
  plugins: [
    PlaceholderPlugin.configure({
      options: {
        uploadConfig,
        maxFileCount: 20,
        multiple: true,
        disableFileDrop: false
      }
    })
  ]
});

Error Handling

Comprehensive error system for upload validation and user feedback.

/**
 * Upload error codes enumeration
 */
enum UploadErrorCode {
  INVALID_FILE_TYPE = 400,
  TOO_MANY_FILES = 402,
  INVALID_FILE_SIZE = 403,
  TOO_LESS_FILES = 405,
  TOO_LARGE = 413,
}

/**
 * Discriminated union of upload error types with detailed data structures
 */
type UploadError = 
  | {
      code: UploadErrorCode.INVALID_FILE_TYPE;
      data: {
        allowedTypes: string[];
        files: File[];
      };
    }
  | {
      code: UploadErrorCode.TOO_MANY_FILES;
      data: {
        files: File[];
        fileType: AllowedFileType | null;
        maxFileCount: number;
      };
    }
  | {
      code: UploadErrorCode.INVALID_FILE_SIZE;
      data: {
        files: File[];
      };
    }
  | {
      code: UploadErrorCode.TOO_LESS_FILES;
      data: {
        files: File[];
        fileType: AllowedFileType;
        minFileCount: number;
      };
    }
  | {
      code: UploadErrorCode.TOO_LARGE;
      data: {
        files: File[];
        fileType: AllowedFileType;
        maxFileSize: string;
      };
    };

/**
 * Creates typed upload errors with detailed error data
 * @param code - Error code from UploadErrorCode enum
 * @param data - Error-specific data structure
 * @returns Formatted upload error
 */
function createUploadError<T extends UploadErrorCode>(
  code: T, 
  data: Extract<UploadError, { code: T }>['data']
): UploadError;

/**
 * Type guard to check if an unknown value is an UploadError
 * @param error - Value to check
 * @returns Boolean indicating if value is UploadError
 */
function isUploadError(error: unknown): error is UploadError;

Usage Example:

import { 
  createUploadError, 
  isUploadError, 
  UploadErrorCode,
  type UploadError 
} from "@udecode/plate-media/react";

const handleUploadError = (error: UploadError) => {
  switch (error.code) {
    case UploadErrorCode.INVALID_FILE_TYPE:
      console.error(`Invalid file types. Allowed: ${error.data.allowedTypes.join(', ')}`);
      console.error(`Invalid files:`, error.data.files.map(f => f.name));
      break;
    case UploadErrorCode.TOO_MANY_FILES:
      console.error(`Too many files. Maximum allowed: ${error.data.maxFileCount}`);
      console.error(`File type: ${error.data.fileType}`);
      break;
    case UploadErrorCode.INVALID_FILE_SIZE:
      console.error(`Invalid file size for files:`, error.data.files.map(f => f.name));
      break;
    case UploadErrorCode.TOO_LESS_FILES:
      console.error(`Not enough ${error.data.fileType} files. Minimum required: ${error.data.minFileCount}`);
      console.error(`Current files:`, error.data.files.map(f => f.name));
      break;
    case UploadErrorCode.TOO_LARGE:
      console.error(`Files too large for ${error.data.fileType}. Max size: ${error.data.maxFileSize}`);
      console.error(`Large files:`, error.data.files.map(f => f.name));
      break;
  }
};

File Validation

Comprehensive file validation system with type detection and size checking.

Validation Functions

/**
 * Validates file selection against upload configuration
 * @param files - FileList to validate
 * @param config - Upload configuration
 * @returns Validation result with errors if any
 */
function validateFiles(
  files: FileList, 
  config: UploadConfig
): { isValid: boolean; error?: UploadError };

/**
 * Validates individual file against configuration
 * @param file - File to validate
 * @param config - Media item configuration
 * @returns Validation result
 */
function validateFileItem(
  file: File, 
  config: MediaItemConfig
): { isValid: boolean; error?: UploadError };

/**
 * Determines file type category from File object
 * @param file - File to categorize
 * @returns File type category or undefined if not supported
 */
function matchFileType(file: File): AllowedFileType | undefined;

/**
 * Groups files by their detected media type
 * @param files - Array of files to group
 * @returns Object mapping media types to file arrays
 */
function groupFilesByType(files: File[]): Record<string, File[]>;

Utility Functions

/**
 * Converts file size string to bytes
 * @param size - Size string like "4MB", "100KB"
 * @returns Size in bytes
 */
function fileSizeToBytes(size: FileSize): number;

/**
 * Determines appropriate media type for a file
 * @param file - File to analyze
 * @returns Corresponding media type
 */
function getMediaType(file: File): MediaKeys;

Usage Examples:

import { 
  validateFiles, 
  matchFileType, 
  fileSizeToBytes,
  groupFilesByType 
} from "@udecode/plate-media/react";

// Validate file selection
const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
  if (event.target.files) {
    const validation = validateFiles(event.target.files, uploadConfig);
    
    if (!validation.isValid && validation.error) {
      setError(validation.error);
      return;
    }
    
    // Process valid files
    processFiles(event.target.files);
  }
};

// Check file types
const checkFileType = (file: File) => {
  const fileType = matchFileType(file);
  console.log(`File type: ${fileType}`); // "image", "video", etc.
};

// Convert file sizes
const maxSizeBytes = fileSizeToBytes("5MB"); // 5242880

// Group files by type
const files = Array.from(fileList);
const grouped = groupFilesByType(files);
// { image: [file1, file2], video: [file3], audio: [file4] }

State Management

Upload progress and state tracking with React integration.

Placeholder Store

/**
 * Atom store for upload state management
 */
interface PlaceholderStore {
  /** Upload operation in progress */
  isUploading: boolean;
  /** Progress by placeholder ID */
  progresses: Record<string, number>;
  /** Size constraints for uploads */
  size: { width: number; height: number } | null;
  /** Files currently being processed */
  updatedFiles: File[];
}

/**
 * Store access hooks
 */
function usePlaceholderStore(): PlaceholderStore;
function usePlaceholderValue<T>(selector: (store: PlaceholderStore) => T): T;
function usePlaceholderSet(): (updates: Partial<PlaceholderStore>) => void;

API Extensions

/**
 * Extended API for file tracking during uploads
 */
interface PlaceholderApi {
  /** Add file to upload tracking */
  addUploadingFile(id: string, file: File): void;
  /** Retrieve uploading file by ID */
  getUploadingFile(id: string): File | undefined;
  /** Remove file from tracking */
  removeUploadingFile(id: string): void;
}

/**
 * Extended transforms for media insertion
 */
interface PlaceholderTransforms {
  /** Insert media from file list */
  insertMedia(files: FileList, options?: InsertNodesOptions): void;
}

React Hooks

Specialized hooks for placeholder and upload functionality.

/**
 * Provides placeholder element state and handlers
 * @returns Placeholder element props and interaction handlers
 */
function usePlaceholderElement(): {
  element: TPlaceholderElement;
  isUploading: boolean;
  progress: number;
  error: UploadError | null;
};

/**
 * Manages placeholder popover interactions
 * @returns Popover state and control functions
 */
function usePlaceholderPopover(): {
  isOpen: boolean;
  open: () => void;
  close: () => void;
  toggle: () => void;
};

Usage Example:

import { 
  usePlaceholderElement, 
  usePlaceholderPopover 
} from "@udecode/plate-media/react";

const PlaceholderComponent = () => {
  const { element, isUploading, progress, error } = usePlaceholderElement();
  const popover = usePlaceholderPopover();
  
  return (
    <div>
      {isUploading && (
        <div>
          <div>Uploading... {Math.round(progress * 100)}%</div>
          <progress value={progress} max={1} />
        </div>
      )}
      
      {error && (
        <div style={{ color: 'red' }}>
          Error: {error.message}
        </div>
      )}
      
      <button onClick={popover.toggle}>
        {popover.isOpen ? 'Close' : 'Open'} Upload Options
      </button>
      
      {popover.isOpen && (
        <div>
          {/* Upload options UI */}
        </div>
      )}
    </div>
  );
};

MIME Type Support

Comprehensive MIME type definitions organized by category for accurate file type detection.

MIME Type Categories

The upload system supports extensive MIME type mappings:

  • Image: JPG, PNG, GIF, SVG, WebP, HEIC, AVIF, and many RAW formats
  • Video: MP4, WebM, AVI, MOV, MKV, and other common video formats
  • Audio: MP3, WAV, FLAC, AAC, OGG, and professional audio formats
  • PDF: Application/PDF documents
  • Text: Plain text, markdown, CSV, and code files
  • Blob: Generic file support for other formats

File type detection uses both MIME type and file extension for maximum compatibility across different browsers and systems.

Install with Tessl CLI

npx tessl i tessl/npm-udecode--plate-media

docs

base-plugins.md

image.md

index.md

media-embeds.md

react-components.md

upload-system.md

tile.json