CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-udecode--plate-image

Image plugin for Plate rich text editor that enables embedding, uploading, and managing images with advanced features like drag-and-drop, resizing, and captions.

Pending
Overview
Eval results
Files

transforms-utilities.mddocs/

Transforms and Utilities

Functions for programmatically inserting images and utility functions for image URL validation.

Capabilities

Image Insertion Transform

Function for programmatically inserting image elements into the Plate editor.

/**
 * Inserts an image element at the current cursor position
 * Creates a properly structured image element with the provided URL
 * @param editor - Plate editor instance
 * @param url - Image URL (string) or data (ArrayBuffer) to insert
 */
function insertImage<V extends Value>(
  editor: PlateEditor<V>,
  url: string | ArrayBuffer
): void;

Element Creation:

  • Creates TImageElement with proper type and structure
  • Sets URL from provided parameter
  • Adds empty text child node (required by Slate)
  • Inserts at current selection or cursor position

Usage Examples:

import { insertImage } from "@udecode/plate-image";
import { useEditorRef } from "@udecode/plate-core";

// Insert image from URL
function InsertImageButton({ imageUrl }: { imageUrl: string }) {
  const editor = useEditorRef();
  
  const handleInsert = () => {
    insertImage(editor, imageUrl);
  };
  
  return (
    <button onClick={handleInsert}>
      Insert Image
    </button>
  );
}

// Insert multiple images
function InsertImageGallery({ imageUrls }: { imageUrls: string[] }) {
  const editor = useEditorRef();
  
  const insertGallery = () => {
    imageUrls.forEach((url, index) => {
      // Insert with slight delay to ensure proper positioning
      setTimeout(() => {
        insertImage(editor, url);
      }, index * 100);
    });
  };
  
  return (
    <button onClick={insertGallery}>
      Insert Gallery ({imageUrls.length} images)
    </button>
  );
}

// Insert image from file upload
function FileUploadImageInserter() {
  const editor = useEditorRef();
  
  const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (file && file.type.startsWith('image/')) {
      const reader = new FileReader();
      reader.onload = (e) => {
        if (e.target?.result) {
          insertImage(editor, e.target.result as string);
        }
      };
      reader.readAsDataURL(file);
    }
  };
  
  return (
    <input 
      type="file" 
      accept="image/*" 
      onChange={handleFileUpload}
    />
  );
}

// Insert with custom image processing
async function insertProcessedImage(
  editor: PlateEditor, 
  rawImageData: string | ArrayBuffer
) {
  try {
    // Process or upload image first
    const processedUrl = await processImageData(rawImageData);
    insertImage(editor, processedUrl);
  } catch (error) {
    console.error('Failed to process image:', error);
    // Fallback to raw data
    insertImage(editor, rawImageData);
  }
}

Image URL Validation Utility

Utility function to determine if a given URL points to an image file based on file extension.

/**
 * Checks if a URL points to an image file
 * Validates both URL format and file extension against comprehensive image format list
 * @param url - URL string to validate
 * @returns Boolean indicating whether URL is a valid image URL
 */
function isImageUrl(url: string): boolean;

Validation Process:

  1. URL Format: Checks if string is a valid URL using isUrl helper
  2. Extension Extraction: Extracts file extension from URL pathname
  3. Format Check: Compares extension against comprehensive list of image formats

Supported Image Formats:

  • Common formats: jpg, jpeg, png, gif, webp, svg, bmp, tiff, ico
  • Raw formats: cr2, raw, dng, nef, arw
  • Professional formats: psd, ai, eps, xcf
  • Specialized formats: Many additional formats (120+ total)

Usage Examples:

import { isImageUrl } from "@udecode/plate-image";

// Basic URL validation
const urls = [
  'https://example.com/image.jpg',      // true
  'https://example.com/image.png',      // true
  'https://example.com/document.pdf',   // false
  'not-a-url',                          // false
  'https://example.com/image.webp',     // true
];

urls.forEach(url => {
  console.log(`${url}: ${isImageUrl(url)}`);
});

// Use in paste handler
function handlePaste(event: ClipboardEvent) {
  const text = event.clipboardData?.getData('text/plain');
  
  if (text && isImageUrl(text)) {
    console.log('Pasted text is an image URL:', text);
    insertImage(editor, text);
    event.preventDefault();
  }
}

// Filter image URLs from mixed content
function filterImageUrls(urls: string[]): string[] {
  return urls.filter(isImageUrl);
}

// Validate before insertion
function safeInsertImage(editor: PlateEditor, url: string) {
  if (isImageUrl(url)) {
    insertImage(editor, url);
    return true;
  } else {
    console.warn('Invalid image URL:', url);
    return false;
  }
}

// Custom validation with fallback
function validateAndInsertImage(editor: PlateEditor, url: string) {
  if (isImageUrl(url)) {
    insertImage(editor, url);
  } else {
    // Try to detect image by MIME type or other means
    fetch(url, { method: 'HEAD' })
      .then(response => {
        const contentType = response.headers.get('content-type');
        if (contentType?.startsWith('image/')) {
          insertImage(editor, url);
        } else {
          console.error('URL does not point to an image');
        }
      })
      .catch(error => {
        console.error('Failed to validate URL:', error);
      });
  }
}

Advanced Transform Patterns

Batch Image Operations

import { insertImage, isImageUrl } from "@udecode/plate-image";
import { Transforms } from "slate";

// Insert multiple images with spacing
async function insertImageBatch(
  editor: PlateEditor, 
  urls: string[],
  spacing: boolean = true
) {
  const validUrls = urls.filter(isImageUrl);
  
  for (let i = 0; i < validUrls.length; i++) {
    insertImage(editor, validUrls[i]);
    
    if (spacing && i < validUrls.length - 1) {
      // Insert paragraph between images
      Transforms.insertNodes(editor, {
        type: 'p',
        children: [{ text: '' }]
      });
    }
  }
}

// Replace selected content with image
function replaceSelectionWithImage(editor: PlateEditor, url: string) {
  if (isImageUrl(url)) {
    // Delete current selection
    if (editor.selection) {
      Transforms.delete(editor);
    }
    insertImage(editor, url);
  }
}

Integration with Upload Systems

// Insert image with upload progress tracking
async function insertImageWithUpload(
  editor: PlateEditor,
  file: File,
  onProgress?: (percent: number) => void
) {
  try {
    // Create temporary data URL for immediate display
    const reader = new FileReader();
    reader.onload = (e) => {
      if (e.target?.result) {
        insertImage(editor, e.target.result as string);
      }
    };
    reader.readAsDataURL(file);
    
    // Upload file and replace with final URL
    const uploadedUrl = await uploadWithProgress(file, onProgress);
    
    // Replace temporary image with uploaded version
    // (Implementation would need to track and update the specific image)
    
  } catch (error) {
    console.error('Upload failed:', error);
  }
}

// Utility for drag-and-drop integration
function handleImageDrop(
  editor: PlateEditor,
  event: DragEvent,
  uploadFunction?: (file: File) => Promise<string>
) {
  event.preventDefault();
  
  const files = Array.from(event.dataTransfer?.files || []);
  const imageFiles = files.filter(file => file.type.startsWith('image/'));
  
  imageFiles.forEach(async (file) => {
    if (uploadFunction) {
      try {
        const url = await uploadFunction(file);
        insertImage(editor, url);
      } catch (error) {
        // Fallback to data URL
        const reader = new FileReader();
        reader.onload = (e) => {
          if (e.target?.result) {
            insertImage(editor, e.target.result as string);
          }
        };
        reader.readAsDataURL(file);
      }
    } else {
      // Direct data URL insertion
      const reader = new FileReader();
      reader.onload = (e) => {
        if (e.target?.result) {
          insertImage(editor, e.target.result as string);
        }
      };
      reader.readAsDataURL(file);
    }
  });
}

Error Handling and Validation

// Robust image insertion with validation and error handling
async function safeInsertImage(
  editor: PlateEditor,
  urlOrData: string | ArrayBuffer,
  options?: {
    validateUrl?: boolean;
    maxSize?: number;
    allowedFormats?: string[];
  }
) {
  try {
    // Validate URL if it's a string
    if (typeof urlOrData === 'string') {
      if (options?.validateUrl && !isImageUrl(urlOrData)) {
        throw new Error('Invalid image URL format');
      }
      
      // Optional: Check if URL is accessible
      if (options?.validateUrl) {
        const response = await fetch(urlOrData, { method: 'HEAD' });
        if (!response.ok) {
          throw new Error(`Image URL not accessible: ${response.status}`);
        }
      }
    }
    
    // Insert the image
    insertImage(editor, urlOrData);
    return true;
    
  } catch (error) {
    console.error('Failed to insert image:', error);
    
    // Optional: Show user-friendly error message
    if (typeof urlOrData === 'string') {
      console.log('Failed URL:', urlOrData);
    }
    
    return false;
  }
}

Install with Tessl CLI

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

docs

editor-enhancements.md

hooks-state.md

index.md

plugin-configuration.md

react-components.md

transforms-utilities.md

types-interfaces.md

tile.json