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
- Learn about Event Handling for editor communication
- Explore Extensions for shared functionality
- Check out Real-World Use Cases for complete applications