Skip to main content

Overview

The getPlainText method extracts plain text content from the editor, stripping all formatting, structure, and metadata. It’s useful for character counts, search indexing, or plain text exports.

Usage

import { useYooptaEditor } from '@yoopta/editor';

function CharacterCount() {
  const editor = useYooptaEditor();
  const text = editor.getPlainText(editor.children);

  return <div>{text.length} characters</div>;
}

Signature

function getPlainText(
  editor: YooEditor,
  content: YooptaContentValue
): string

Parameters

editor
YooEditor
required
The editor instance (automatically provided when called as editor.getPlainText())
content
YooptaContentValue
required
The content to convert to plain text. Typically editor.children for current content.

Returns

string - Plain text representation with all formatting removed

How It Works

Under the hood, getPlainText converts content to HTML first, then extracts the text:
export function getPlainText(editor: YooEditor, content: YooptaContentValue) {
  const htmlString = getHTML(editor, content);
  const div = document.createElement('div');
  div.innerHTML = htmlString;
  return div.innerText;
}
This approach:
  • Uses browser’s native text extraction
  • Preserves line breaks and spacing
  • Removes all HTML tags and attributes
  • Handles special characters correctly

Examples

Character Counter

function CharacterCounter() {
  const editor = useYooptaEditor();
  const [count, setCount] = useState(0);

  useEffect(() => {
    const handleChange = () => {
      const text = editor.getPlainText(editor.children);
      setCount(text.length);
    };

    handleChange(); // Initial count
    editor.on('change', handleChange);
    return () => editor.off('change', handleChange);
  }, [editor]);

  return (
    <div className="character-count">
      {count.toLocaleString()} characters
    </div>
  );
}

Word Counter

function WordCounter() {
  const editor = useYooptaEditor();
  const [wordCount, setWordCount] = useState(0);

  useEffect(() => {
    const handleChange = () => {
      const text = editor.getPlainText(editor.children);
      const words = text.trim().split(/\s+/).filter(Boolean);
      setWordCount(words.length);
    };

    handleChange();
    editor.on('change', handleChange);
    return () => editor.off('change', handleChange);
  }, [editor]);

  return <div>{wordCount} words</div>;
}

Reading Time Estimate

function ReadingTime() {
  const editor = useYooptaEditor();
  const [minutes, setMinutes] = useState(0);

  useEffect(() => {
    const handleChange = () => {
      const text = editor.getPlainText(editor.children);
      const words = text.trim().split(/\s+/).filter(Boolean).length;
      const wordsPerMinute = 200;
      const readingMinutes = Math.ceil(words / wordsPerMinute);
      setMinutes(readingMinutes);
    };

    handleChange();
    editor.on('change', handleChange);
    return () => editor.off('change', handleChange);
  }, [editor]);

  return (
    <div className="reading-time">
      {minutes} min read
    </div>
  );
}

Copy Plain Text

function CopyPlainTextButton() {
  const editor = useYooptaEditor();
  const [copied, setCopied] = useState(false);

  const handleCopy = async () => {
    const text = editor.getPlainText(editor.children);
    await navigator.clipboard.writeText(text);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };

  return (
    <button onClick={handleCopy}>
      {copied ? '✓ Copied' : 'Copy Text'}
    </button>
  );
}

Search/Filter Content

function SearchableContent() {
  const editor = useYooptaEditor();
  const [searchTerm, setSearchTerm] = useState('');
  const [matches, setMatches] = useState(0);

  useEffect(() => {
    if (!searchTerm) {
      setMatches(0);
      return;
    }

    const text = editor.getPlainText(editor.children);
    const regex = new RegExp(searchTerm, 'gi');
    const found = text.match(regex);
    setMatches(found ? found.length : 0);
  }, [searchTerm, editor.children]);

  return (
    <div>
      <input
        type="text"
        placeholder="Search content..."
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      {searchTerm && <p>{matches} matches found</p>}
    </div>
  );
}

Export to Text File

function ExportTextButton() {
  const editor = useYooptaEditor();

  const handleExport = () => {
    const text = editor.getPlainText(editor.children);
    
    const blob = new Blob([text], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = 'document.txt';
    link.click();
    URL.revokeObjectURL(url);
  };

  return <button onClick={handleExport}>Download as Text</button>;
}

Content Length Validator

function ContentLengthValidator({ maxLength = 1000 }) {
  const editor = useYooptaEditor();
  const [length, setLength] = useState(0);
  const [isValid, setIsValid] = useState(true);

  useEffect(() => {
    const handleChange = () => {
      const text = editor.getPlainText(editor.children);
      const currentLength = text.length;
      setLength(currentLength);
      setIsValid(currentLength <= maxLength);
    };

    handleChange();
    editor.on('change', handleChange);
    return () => editor.off('change', handleChange);
  }, [editor, maxLength]);

  return (
    <div className={isValid ? 'valid' : 'invalid'}>
      {length} / {maxLength} characters
      {!isValid && <span> (exceeds limit)</span>}
    </div>
  );
}

Analytics Tracking

function ContentAnalytics() {
  const editor = useYooptaEditor();

  useEffect(() => {
    const handleChange = () => {
      const text = editor.getPlainText(editor.children);
      
      // Track metrics
      analytics.track('content_updated', {
        characterCount: text.length,
        wordCount: text.split(/\s+/).filter(Boolean).length,
        timestamp: Date.now(),
      });
    };

    editor.on('change', handleChange);
    return () => editor.off('change', handleChange);
  }, [editor]);

  return null;
}

Preview Excerpt

function ContentExcerpt({ maxLength = 200 }) {
  const editor = useYooptaEditor();
  const [excerpt, setExcerpt] = useState('');

  useEffect(() => {
    const handleChange = () => {
      const text = editor.getPlainText(editor.children);
      const truncated = text.length > maxLength
        ? text.substring(0, maxLength) + '...'
        : text;
      setExcerpt(truncated);
    };

    handleChange();
    editor.on('change', handleChange);
    return () => editor.off('change', handleChange);
  }, [editor, maxLength]);

  return (
    <div className="excerpt">
      <h4>Preview</h4>
      <p>{excerpt}</p>
    </div>
  );
}

Use Cases

  • Character/Word Counting: Display document statistics
  • Reading Time: Estimate how long content takes to read
  • Search Indexing: Index content for search functionality
  • Validation: Check content length limits
  • Analytics: Track content metrics
  • Plain Text Export: Download as .txt file
  • Clipboard: Copy unformatted text
  • Previews: Generate text excerpts
  • SEO: Extract meta descriptions
  • Accessibility: Provide text alternatives

What Gets Removed

The plain text extraction removes:
  • ✗ All HTML tags
  • ✗ Text formatting (bold, italic, etc.)
  • ✗ Links (keeps link text, removes URLs)
  • ✗ Images (removes entirely)
  • ✗ Block structure
  • ✗ Metadata (alignment, depth, etc.)
  • ✗ Embeds and void elements
What’s preserved:
  • ✓ Text content
  • ✓ Line breaks (from block separation)
  • ✓ Spacing

Performance Considerations

Since getPlainText creates HTML first then extracts text:
  • May be slower for very large documents
  • Creates temporary DOM elements
  • Consider caching for frequently accessed values
  • Use debouncing for live counters
// Debounced counter
function DebouncedCounter() {
  const editor = useYooptaEditor();
  const [count, setCount] = useState(0);

  useEffect(() => {
    const handleChange = debounce(() => {
      const text = editor.getPlainText(editor.children);
      setCount(text.length);
    }, 300);

    editor.on('change', handleChange);
    return () => editor.off('change', handleChange);
  }, [editor]);

  return <div>{count} characters</div>;
}

See Also

Build docs developers (and LLMs) love