API Reference

    API Reference

    Complete documentation for every method, option, and event in Scribe Editor. All APIs are fully typed with TypeScript definitions included.

    Initialization

    Creating an editor instance

    // Simple one-line initialization
    import { Scribe } from './scribe.js';
    
    // Pass a CSS selector
    const editor = Scribe.init('#editor');
    
    // Or pass an element
    const element = document.getElementById('editor');
    const editor = Scribe.init(element);
    
    // With options
    const editor = Scribe.init('#editor', {
      placeholder: 'Start writing...',
      autofocus: true,
      onChange: (html) => console.log(html)
    });

    Formatting Methods

    Direct methods for text formatting - no exec() needed

    Inline Formatting
    Toggle

    bold()

    Toggle bold on selection

    italic()

    Toggle italic on selection

    underline()

    Toggle underline on selection

    strike()

    Toggle strikethrough

    code()

    Toggle inline code

    subscript()

    Toggle subscript

    superscript()

    Toggle superscript

    clearFormat()

    Remove all formatting

    Block Formatting

    heading(level: 1 | 2 | 3 | 4 | 5 | 6)

    Set heading level

    paragraph()

    Remove block formatting

    blockquote()

    Toggle blockquote

    codeBlock()

    Insert code block

    orderedList()

    Toggle numbered list

    unorderedList()

    Toggle bullet list

    Alignment

    alignLeft()

    Align text left

    alignCenter()

    Center text

    alignRight()

    Align text right

    alignJustify()

    Justify text

    indent()

    Increase indentation

    outdent()

    Decrease indentation

    Links & Insert

    link(url: string)

    Wrap selection in link

    unlink()

    Remove link from selection

    insertHR()

    Insert horizontal rule

    insertHTML(html: string)

    Insert sanitized HTML at cursor

    insertText(text: string)

    Insert plain text at cursor

    Usage Example

    const editor = Scribe.init('#editor');
    
    // Format selected text
    editor.bold();      // Toggle bold
    editor.italic();    // Toggle italic
    editor.heading(2);  // Make it an H2
    editor.link('https://example.com');
    
    // These methods work on the current selection
    // No need to save/restore selection

    Content Methods

    Get and set editor content

    getHTML(): string

    Returns the current HTML content

    setHTML(html: string)

    Sets content (sanitized automatically)

    getText(): string

    Returns plain text content

    isEmpty(): boolean

    Returns true if editor has no content

    // Get content
    const html = editor.getHTML();  // '<p><strong>Hello</strong> world</p>'
    const text = editor.getText();  // 'Hello world'
    
    // Set content
    editor.setHTML('<p>New content</p>');
    
    // Check if empty
    if (editor.isEmpty()) {
      showPlaceholder();
    }

    Selection Methods

    Manage cursor position and selection

    getSelection(): SelectionState | null

    Get current selection with formats

    saveSelection()

    Save current selection (for dialogs)

    restoreSelection()

    Restore saved selection

    focus()

    Focus the editor and restore selection

    blur()

    Remove focus from editor

    // SelectionState interface
    interface SelectionState {
      range: Range | null;      // Native Range object
      collapsed: boolean;       // True = cursor, False = selection
      text: string;            // Selected text content
      rect: DOMRect | null;    // Position for toolbar
      formats: FormatState;    // Current formatting
    }
    
    interface FormatState {
      bold: boolean;
      italic: boolean;
      underline: boolean;
      strike: boolean;
      code: boolean;
      link: string | null;     // URL if in link
      heading: number | null;  // 1-6 or null
      list: 'ordered' | 'unordered' | null;
      blockquote: boolean;
      align: 'left' | 'center' | 'right' | 'justify';
    }
    
    // Usage: Update toolbar based on selection
    editor.on('selectionChange', (selection) => {
      if (selection) {
        boldButton.classList.toggle('active', selection.formats.bold);
        italicButton.classList.toggle('active', selection.formats.italic);
      }
    });

    Events

    Subscribe to editor events

    on(event: string, handler: (...args) => void)

    Subscribe to an event

    off(event: string, handler: (...args) => void)

    Unsubscribe from an event

    emit(event: string, ...args: unknown[])

    Emit an event (for plugins)

    Available Events

    change → (html: string)
    selectionChange → (selection: SelectionState)
    focus → ()
    blur → ()
    ready → ()
    destroy → ()
    readOnlyChange → (readOnly: boolean)

    Best Practices

    • Use onChange config for simple callbacks

    • Use .on() for multiple handlers

    • Always .off() in cleanup

    • Debounce expensive change handlers

    // Event handling
    const editor = Scribe.init('#editor');
    
    // Subscribe
    const handleChange = (html) => {
      console.log('Content changed:', html);
      autoSave(html);
    };
    editor.on('change', handleChange);
    
    // Subscribe to selection changes for toolbar
    editor.on('selectionChange', (selection) => {
      if (selection && !selection.collapsed) {
        showFloatingToolbar(selection.rect, selection.formats);
      } else {
        hideFloatingToolbar();
      }
    });
    
    // Cleanup (important!)
    function cleanup() {
      editor.off('change', handleChange);
      editor.destroy();
    }

    State Methods

    Control editor state

    isReadOnly(): boolean

    Check if editor is read-only

    setReadOnly(readOnly: boolean)

    Enable/disable editing

    undo()

    Undo last action

    redo()

    Redo last undone action

    destroy()

    Clean up and remove editor

    // Toggle read-only mode
    function togglePreview() {
      const isPreview = editor.isReadOnly();
      editor.setReadOnly(!isPreview);
    }
    
    // History
    document.querySelector('#undo').onclick = () => editor.undo();
    document.querySelector('#redo').onclick = () => editor.redo();
    
    // Cleanup when done
    window.addEventListener('beforeunload', () => {
      editor.destroy();
    });

    Keyboard Shortcuts

    Built-in shortcuts (customizable via plugins)

    Formatting

    BoldCtrl/Cmd + B
    ItalicCtrl/Cmd + I
    UnderlineCtrl/Cmd + U
    LinkCtrl/Cmd + K
    Clear FormatCtrl/Cmd + \

    History

    UndoCtrl/Cmd + Z
    RedoCtrl/Cmd + Y
    Redo (Alt)Ctrl/Cmd + Shift + Z

    Styling Methods

    Apply custom styles to text

    setFontSize(size: string | number)

    Set font size (e.g., '18px' or 18)

    setFontFamily(font: string)

    Set font family

    setColor(color: string)

    Set text color (hex, rgb, etc.)

    setBackgroundColor(color: string)

    Set text background/highlight color

    // Apply styling to selection
    editor.setFontSize(18);
    editor.setFontFamily('Georgia');
    editor.setColor('#ff0000');
    editor.setBackgroundColor('#ffff00');
    
    // Note: These use inline styles
    // For better maintainability, consider using classes via plugins

    Sanitization Config

    Configure HTML sanitization rules

    // SanitizeConfig interface
    interface SanitizeConfig {
      // Tags allowed in output
      allowedTags?: string[];
      
      // Attributes allowed per tag ('*' = all tags)
      allowedAttributes?: Record<string, string[]>;
      
      // URL schemes allowed in href/src
      allowedSchemes?: string[];
      
      // Remove empty tags like <p></p>
      stripEmpty?: boolean;
    }
    
    // Default configuration
    const defaultSanitize: SanitizeConfig = {
      allowedTags: [
        'p', 'br', 'div', 'span',
        'strong', 'b', 'em', 'i', 'u', 's', 'del', 'ins',
        'sub', 'sup', 'mark', 'code', 'pre',
        'a', 'img',
        'ul', 'ol', 'li',
        'blockquote', 'hr',
        'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
        'table', 'thead', 'tbody', 'tr', 'td', 'th'
      ],
      allowedAttributes: {
        'a': ['href', 'target', 'rel', 'title'],
        'img': ['src', 'alt', 'width', 'height', 'title'],
        'td': ['colspan', 'rowspan'],
        'th': ['colspan', 'rowspan'],
        '*': ['class', 'id', 'style']
      },
      allowedSchemes: ['http', 'https', 'mailto', 'tel'],
      stripEmpty: true
    };
    
    // Strict configuration (minimal formatting)
    const strictSanitize: SanitizeConfig = {
      allowedTags: ['p', 'br', 'strong', 'em', 'a'],
      allowedAttributes: {
        'a': ['href']
      },
      allowedSchemes: ['https'],
      stripEmpty: true
    };

    Toolbar State & Command Metadata

    Real-time formatting state introspection for toolbar synchronization

    Key Concept

    Scribe provides three levels of state introspection: FormatState for raw formatting booleans, CommandMetadata for rich command info, andformatChange events for reactive UI updates. Together they enable toolbars that always reflect the true state of the selection.
    // FormatState — raw formatting state of the current selection
    interface FormatState {
      bold: boolean;
      italic: boolean;
      underline: boolean;
      strike: boolean;
      code: boolean;
      subscript: boolean;
      superscript: boolean;
      link: string | null;       // URL if cursor is inside a link
      heading: number | null;    // 1-6 or null
      list: 'ordered' | 'unordered' | null;
      blockquote: boolean;
      align: 'left' | 'center' | 'right' | 'justify';
      fontSize: string | null;
      fontFamily: string | null;
      color: string | null;
      backgroundColor: string | null;
    }
    
    // Get current format state at any time
    const state = editor.getFormatState();
    console.log(state.bold);     // true if selection is bold
    console.log(state.link);     // 'https://...' or null
    console.log(state.fontSize); // '16px' or null
    
    // Works with partial selections and nested formatting
    // e.g., selecting across <strong>bold <em>and italic</em></strong>
    // → { bold: true, italic: true, ... }

    Real-World Examples

    // Vanilla JS toolbar with full state sync
    const editor = Scribe.init('#editor');
    const toolbar = document.getElementById('toolbar');
    
    // Build toolbar buttons from command metadata
    const commands = editor.getAllCommandStates();
    commands
      .filter(cmd => ['bold','italic','underline','strike','link'].includes(cmd.name))
      .forEach(cmd => {
        const btn = document.createElement('button');
        btn.textContent = cmd.label;
        btn.title = cmd.shortcut || cmd.label;
        btn.dataset.command = cmd.name;
        toolbar.appendChild(btn);
      });
    
    // Click handler — use direct methods
    toolbar.addEventListener('click', (e) => {
      const cmd = e.target.dataset.command;
      if (cmd && editor[cmd]) {
        editor[cmd]();
      }
    });
    
    // Sync active state reactively
    editor.on('formatChange', ({ current, changed }) => {
      toolbar.querySelectorAll('button').forEach(btn => {
        const cmd = btn.dataset.command;
        const meta = editor.getCommandState(cmd);
        if (meta) {
          btn.classList.toggle('active', meta.active);
          btn.disabled = !meta.supported;
        }
      });
    });

    Cross-Browser Handling Strategy

    Scribe uses a multi-layered approach to ensure toolbars remain in sync across all major browsers (Chrome, Firefox, Safari, Edge), overcoming known Selection API inconsistencies.

    Reactive Triggers

    Prioritizes formatChange events which fire immediately after any formatting command (bold, link, etc.) modifies the DOM.

    Selection Guard

    Subscribes to selectionchange on the document, plus mouseupand keyup fallbacks for Firefox/Safari.

    Polling Fallback

    Uses 100ms polling while the editor is focused to catch programmatic changes that browsers fail to report via standard events.

    Troubleshooting & Edge Cases

    Toolbar doesn't update

    Ensure you're subscribing to formatChange, not selectionChange, for active state. selectionChange fires too frequently and may miss format updates after commands.

    // ❌ Don't use selectionChange for active state
    editor.on('selectionChange', (sel) => {
      // This misses command-triggered changes
    });
    
    // ✅ Use formatChange for toolbar state
    editor.on('formatChange', ({ current }) => {
      updateToolbar(current);
    });

    Partial selection detection

    When selecting across multiple format nodes (e.g., bold + non-bold text), Scribe reports the format of the common ancestor container.

    // Selection: "bold and normal"
    // in: <strong>bold</strong> and normal
    // → formats.bold = false (common ancestor is parent)
    
    // Selection: "bold text"
    // in: <strong>bold text</strong>
    // → formats.bold = true

    Iframe focus issues

    Clicking toolbar buttons in the parent steals focus from the iframe. Always use preventDefault on mousedown.

    toolbar.addEventListener('mousedown', (e) => {
      e.preventDefault(); // Keep focus in iframe
    });

    Performance with many buttons

    Use formatChange's changed array to only update buttons whose state actually changed.

    editor.on('formatChange', ({ changed, current }) => {
      // Only update what changed
      for (const key of changed) {
        const btn = toolbar.querySelector(`[data-format="${key}"]`);
        if (btn) btn.classList.toggle('active', !!current[key]);
      }
    });