Skip to content

Multiple Editors

Manage multiple editor instances on a single page with different configurations.

Basic Multiple Editors

Create multiple editors with different purposes:

Interactive Demo

Try the Demo

Select text in any editor below to see different toolbar configurations for each editor type.

Multiple editors with different configurations:
🔄 Loading interactive demo...

Title Editor

Your Article Title

Content Editor

This is the main content area with full formatting options.

Summary Editor

A brief summary with limited formatting.

HTML

html
<div class="editors-container">
  <!-- Title Editor -->
  <div class="editor-section">
    <h3>Title</h3>
    <div class="title-editor" data-placeholder="Enter your title...">
      <h1>Your Article Title</h1>
    </div>
  </div>

  <!-- Content Editor -->
  <div class="editor-section">
    <h3>Content</h3>
    <div class="content-editor" data-placeholder="Write your content...">
      <p>This is the main content area with full formatting options.</p>
    </div>
  </div>

  <!-- Summary Editor -->
  <div class="editor-section">
    <h3>Summary</h3>
    <div class="summary-editor" data-placeholder="Brief summary...">
      <p>A brief summary with limited formatting.</p>
    </div>
  </div>
</div>

TypeScript

typescript
import { MediumEditor } from 'ts-medium-editor'
import 'ts-medium-editor/css/medium-editor.css'

// Title editor - minimal formatting
const titleEditor = new MediumEditor('.title-editor', {
  toolbar: {
    buttons: ['bold', 'italic']
  },
  placeholder: {
    text: 'Enter your title...',
    hideOnClick: true
  },
  disableReturn: true, // Prevent line breaks in titles
  disableDoubleReturn: true
})

// Content editor - full formatting
const contentEditor = new MediumEditor('.content-editor', {
  toolbar: {
    buttons: ['bold', 'italic', 'underline', 'anchor', 'h2', 'h3', 'quote', 'unorderedlist', 'orderedlist']
  },
  placeholder: {
    text: 'Write your content...',
    hideOnClick: true
  }
})

// Summary editor - limited formatting
const summaryEditor = new MediumEditor('.summary-editor', {
  toolbar: {
    buttons: ['bold', 'italic', 'anchor']
  },
  placeholder: {
    text: 'Brief summary...',
    hideOnClick: true
  }
})

Blog Post Editor System

A complete blog post editing interface:

HTML

html
<form class="blog-post-form">
  <div class="form-group">
    <label for="post-title">Title</label>
    <div id="post-title" class="post-title-editor" data-placeholder="Enter post title...">
      <h1>How to Use Multiple Editors</h1>
    </div>
  </div>

  <div class="form-group">
    <label for="post-excerpt">Excerpt</label>
    <div id="post-excerpt" class="post-excerpt-editor" data-placeholder="Write a compelling excerpt...">
      <p>Learn how to implement multiple Medium Editor instances with different configurations for various content types.</p>
    </div>
  </div>

  <div class="form-group">
    <label for="post-content">Content</label>
    <div id="post-content" class="post-content-editor" data-placeholder="Write your blog post...">
      <p>Welcome to this comprehensive guide on using multiple editors...</p>
      <h2>Getting Started</h2>
      <p>First, let's understand the basic concepts...</p>
    </div>
  </div>

  <div class="form-group">
    <label for="post-tags">Tags</label>
    <div id="post-tags" class="post-tags-editor" data-placeholder="Add tags (comma separated)...">
      <p>typescript, medium-editor, tutorial, web-development</p>
    </div>
  </div>

  <button type="submit" class="submit-btn">Publish Post</button>
</form>

TypeScript

typescript
// Initialize all editors
const editors = {
  title: new MediumEditor('#post-title', {
    toolbar: {
      buttons: ['bold', 'italic']
    },
    disableReturn: true,
    disableDoubleReturn: true
  }),

  excerpt: new MediumEditor('#post-excerpt', {
    toolbar: {
      buttons: ['bold', 'italic', 'anchor']
    }
  }),

  content: new MediumEditor('#post-content', {
    toolbar: {
      buttons: ['bold', 'italic', 'underline', 'anchor', 'h2', 'h3', 'quote', 'unorderedlist', 'orderedlist']
    },
    anchor: {
      linkValidation: true,
      placeholderText: 'Paste or type a link'
    }
  }),

  tags: new MediumEditor('#post-tags', {
    toolbar: false, // No toolbar for tags
    disableReturn: true,
    disableDoubleReturn: true
  })
}

// Handle form submission
document.querySelector('.blog-post-form').addEventListener('submit', (e) => {
  e.preventDefault()

  const postData = {
    title: document.getElementById('post-title').textContent.trim(),
    excerpt: document.getElementById('post-excerpt').innerHTML,
    content: document.getElementById('post-content').innerHTML,
    tags: document.getElementById('post-tags').textContent.split(',').map(tag => tag.trim())
  }

  console.log('Post data:', postData)
  // Submit to your backend
})

Comment System

Multiple editors for a comment system:

HTML

html
<div class="comment-system">
  <div class="new-comment">
    <h4>Add a Comment</h4>
    <div class="comment-editor" data-placeholder="Write your comment...">
      <p>Share your thoughts...</p>
    </div>
    <button class="post-comment-btn">Post Comment</button>
  </div>

  <div class="comments-list">
    <div class="comment">
      <div class="comment-header">
        <strong>John Doe</strong>
        <span class="comment-date">2 hours ago</span>
      </div>
      <div class="comment-content">
        <p>Great article! I especially liked the part about <strong>multiple configurations</strong>.</p>
      </div>
      <div class="comment-actions">
        <button class="reply-btn" data-comment-id="1">Reply</button>
      </div>
      <div class="reply-editor" id="reply-editor-1" style="display: none;" data-placeholder="Write a reply..."></div>
    </div>

    <div class="comment">
      <div class="comment-header">
        <strong>Jane Smith</strong>
        <span class="comment-date">1 hour ago</span>
      </div>
      <div class="comment-content">
        <p>Thanks for sharing! The <em>blog post example</em> is very helpful.</p>
      </div>
      <div class="comment-actions">
        <button class="reply-btn" data-comment-id="2">Reply</button>
      </div>
      <div class="reply-editor" id="reply-editor-2" style="display: none;" data-placeholder="Write a reply..."></div>
    </div>
  </div>
</div>

TypeScript

typescript
// Main comment editor
const commentEditor = new MediumEditor('.comment-editor', {
  toolbar: {
    buttons: ['bold', 'italic', 'anchor']
  },
  placeholder: {
    text: 'Write your comment...',
    hideOnClick: true
  }
})

// Reply editors (created dynamically)
const replyEditors = new Map()

// Handle reply button clicks
document.addEventListener('click', (e) => {
  if (e.target.classList.contains('reply-btn')) {
    const commentId = e.target.getAttribute('data-comment-id')
    const replyEditor = document.getElementById(`reply-editor-${commentId}`)

    if (replyEditor.style.display === 'none') {
      // Show reply editor
      replyEditor.style.display = 'block'

      // Initialize editor if not already done
      if (!replyEditors.has(commentId)) {
        const editor = new MediumEditor(replyEditor, {
          toolbar: {
            buttons: ['bold', 'italic']
          },
          placeholder: {
            text: 'Write a reply...',
            hideOnClick: true
          }
        })
        replyEditors.set(commentId, editor)
      }

      // Focus the editor
      replyEditor.focus()
      e.target.textContent = 'Cancel'
    }
    else {
      // Hide reply editor
      replyEditor.style.display = 'none'
      e.target.textContent = 'Reply'
    }
  }
})

// Handle comment posting
document.querySelector('.post-comment-btn').addEventListener('click', () => {
  const content = document.querySelector('.comment-editor').innerHTML
  console.log('New comment:', content)

  // Clear editor after posting
  document.querySelector('.comment-editor').innerHTML = ''
})

Editor Synchronization

Sync content between multiple editors:

HTML

html
<div class="sync-demo">
  <div class="sync-editors">
    <div class="editor-panel">
      <h4>Editor A</h4>
      <div class="sync-editor-a" data-placeholder="Type here...">
        <p>This content will sync to Editor B</p>
      </div>
    </div>

    <div class="editor-panel">
      <h4>Editor B (Read-only sync)</h4>
      <div class="sync-editor-b" data-placeholder="Synced content appears here...">
        <p>This content will sync to Editor B</p>
      </div>
    </div>
  </div>

  <div class="sync-controls">
    <button id="toggle-sync">Toggle Sync</button>
    <button id="clear-all">Clear All</button>
  </div>
</div>

TypeScript

typescript
let syncEnabled = true

const editorA = new MediumEditor('.sync-editor-a', {
  toolbar: {
    buttons: ['bold', 'italic', 'underline']
  }
})

const editorB = new MediumEditor('.sync-editor-b', {
  toolbar: false, // Read-only display
  disableEditing: true
})

// Sync content from A to B
editorA.subscribe('editableInput', (event, editable) => {
  if (syncEnabled) {
    const syncTarget = document.querySelector('.sync-editor-b')
    syncTarget.innerHTML = editable.innerHTML
  }
})

// Sync controls
document.getElementById('toggle-sync').addEventListener('click', () => {
  syncEnabled = !syncEnabled
  const button = document.getElementById('toggle-sync')
  button.textContent = syncEnabled ? 'Disable Sync' : 'Enable Sync'
  button.style.background = syncEnabled ? '#dc3545' : '#28a745'
})

document.getElementById('clear-all').addEventListener('click', () => {
  document.querySelector('.sync-editor-a').innerHTML = ''
  document.querySelector('.sync-editor-b').innerHTML = ''
})

Performance Optimization

Optimize multiple editors for better performance:

typescript
class MultipleEditorsManager {
  private editors: Map<string, MediumEditor> = new Map()
  private lazyEditors: Set<string> = new Set()

  // Initialize only visible editors
  initializeVisibleEditors() {
    const visibleEditors = document.querySelectorAll('.editor[data-lazy="false"]')

    visibleEditors.forEach((element, index) => {
      const id = element.id || `editor-${index}`
      this.createEditor(id, element as HTMLElement)
    })
  }

  // Lazy load editors when they come into view
  setupLazyLoading() {
    const lazyEditors = document.querySelectorAll('.editor[data-lazy="true"]')

    const observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          const element = entry.target as HTMLElement
          const id = element.id

          if (!this.editors.has(id)) {
            this.createEditor(id, element)
            observer.unobserve(element)
          }
        }
      })
    })

    lazyEditors.forEach(editor => observer.observe(editor))
  }

  private createEditor(id: string, element: HTMLElement) {
    const config = this.getEditorConfig(element)
    const editor = new MediumEditor(element, config)
    this.editors.set(id, editor)

    console.log(`Editor ${id} initialized`)
  }

  private getEditorConfig(element: HTMLElement): any {
    const type = element.getAttribute('data-editor-type')

    switch (type) {
      case 'title':
        return {
          toolbar: { buttons: ['bold', 'italic'] },
          disableReturn: true
        }
      case 'content':
        return {
          toolbar: { buttons: ['bold', 'italic', 'anchor', 'h2', 'h3'] }
        }
      case 'comment':
        return {
          toolbar: { buttons: ['bold', 'italic'] }
        }
      default:
        return {}
    }
  }

  // Destroy editors to free memory
  destroyEditor(id: string) {
    const editor = this.editors.get(id)
    if (editor) {
      editor.destroy()
      this.editors.delete(id)
    }
  }

  // Get all editor contents
  getAllContent(): Record<string, string> {
    const content: Record<string, string> = {}

    this.editors.forEach((editor, id) => {
      const element = editor.elements[0]
      content[id] = element.innerHTML
    })

    return content
  }
}

// Usage
const manager = new MultipleEditorsManager()
manager.initializeVisibleEditors()
manager.setupLazyLoading()

CSS Styling

Style multiple editors consistently:

css
.editors-container {
  max-width: 800px;
  margin: 0 auto;
  padding: 2rem;
}

.editor-section {
  margin-bottom: 2rem;
  border: 1px solid #e9ecef;
  border-radius: 8px;
  padding: 1.5rem;
  background: #fff;
}

.editor-section h3 {
  margin: 0 0 1rem 0;
  color: #495057;
  font-size: 1.1rem;
  border-bottom: 2px solid #007bff;
  padding-bottom: 0.5rem;
}

/* Title Editor */
.title-editor {
  font-size: 1.5rem;
  font-weight: bold;
  border: 2px dashed #dee2e6;
  border-radius: 6px;
  padding: 1rem;
  min-height: 60px;
}

.title-editor:focus {
  border-color: #007bff;
  outline: none;
}

/* Content Editor */
.content-editor {
  border: 2px dashed #dee2e6;
  border-radius: 6px;
  padding: 1rem;
  min-height: 200px;
  line-height: 1.6;
}

.content-editor:focus {
  border-color: #28a745;
  outline: none;
}

/* Summary Editor */
.summary-editor {
  border: 2px dashed #dee2e6;
  border-radius: 6px;
  padding: 1rem;
  min-height: 100px;
  background: #f8f9fa;
}

.summary-editor:focus {
  border-color: #ffc107;
  outline: none;
}

/* Comment System */
.comment-system {
  max-width: 600px;
  margin: 2rem auto;
}

.comment {
  border: 1px solid #e9ecef;
  border-radius: 8px;
  padding: 1rem;
  margin-bottom: 1rem;
  background: #fff;
}

.comment-header {
  display: flex;
  justify-content: space-between;
  margin-bottom: 0.5rem;
  font-size: 0.9rem;
}

.comment-date {
  color: #6c757d;
}

.reply-editor {
  margin-top: 1rem;
  border: 1px solid #dee2e6;
  border-radius: 4px;
  padding: 0.75rem;
  background: #f8f9fa;
}

/* Sync Demo */
.sync-editors {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  margin-bottom: 1rem;
}

.editor-panel {
  border: 1px solid #dee2e6;
  border-radius: 6px;
  padding: 1rem;
}

.sync-controls {
  text-align: center;
}

.sync-controls button {
  margin: 0 0.5rem;
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

@media (max-width: 768px) {
  .sync-editors {
    grid-template-columns: 1fr;
  }
}

Next Steps

Released under the MIT License.