Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Theme
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Create API Key
Add Docs
MediumEditor
https://github.com/yabwe/medium-editor
Admin
MediumEditor is a vanilla JavaScript WYSIWYG editor that clones the inline editor toolbar from
...
Tokens:
62,240
Snippets:
479
Trust Score:
6.7
Update:
4 months ago
Context
Skills
Chat
Benchmark
90.4
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# MediumEditor ## Introduction MediumEditor is a vanilla JavaScript WYSIWYG editor that clones the inline text editing experience from Medium.com. Built without any framework dependencies, it provides a clean, intuitive interface for rich text editing in web applications. The library transforms HTML elements into contenteditable regions with a floating toolbar that appears on text selection, supporting standard formatting operations like bold, italic, headings, and links. The editor is designed for flexibility and extensibility, offering comprehensive options for customizing toolbar behavior, paste handling, keyboard shortcuts, and visual appearance through themes. It supports multiple simultaneous editable elements, textarea integration with automatic synchronization, and a robust event system for integrating with application logic. With built-in extensions for anchor previews, placeholders, auto-linking, and image handling, MediumEditor provides a complete solution for adding Medium-style editing capabilities to any web application. ## API Reference ### Basic Initialization Initialize the editor on HTML elements with contenteditable support. ```javascript // Initialize on elements with class selector var editor = new MediumEditor('.editable'); // Initialize on specific DOM elements var elements = document.querySelectorAll('.editable'); var editor = new MediumEditor(elements); // Initialize with custom options var editor = new MediumEditor('.editable', { toolbar: { buttons: ['bold', 'italic', 'underline', 'anchor', 'h2', 'h3', 'quote'] }, placeholder: { text: 'Type your text here...', hideOnClick: true } }); // Initialize on textarea (automatically creates contenteditable div) // <textarea class="editable"></textarea> var editor = new MediumEditor('textarea.editable'); // Creates hidden textarea with synced contenteditable div ``` ### Toolbar Configuration Customize the floating toolbar appearance, positioning, and button set. ```javascript var editor = new MediumEditor('.editable', { toolbar: { allowMultiParagraphSelection: true, buttons: ['bold', 'italic', 'underline', 'anchor', 'h2', 'h3', 'quote', 'orderedlist', 'unorderedlist', 'pre', 'strikethrough'], diffLeft: 0, diffTop: -10, firstButtonClass: 'medium-editor-button-first', lastButtonClass: 'medium-editor-button-last', standardizeSelectionStart: false, static: false, align: 'center', sticky: false, updateOnEmptySelection: false } }); // Custom button configuration with overrides var editor = new MediumEditor('.editable', { toolbar: { buttons: [ 'bold', 'italic', { name: 'h1', action: 'append-h2', aria: 'header type 1', tagNames: ['h2'], contentDefault: '<b>H1</b>', classList: ['custom-class-h1'], attrs: { 'data-custom-attr': 'attr-value-h1' } }, { name: 'customButton', contentDefault: '<b>✓</b>', classList: ['custom-button'], action: 'bold', aria: 'custom bold button' } ] } }); // Static toolbar (always visible) var editor = new MediumEditor('.editable', { toolbar: { static: true, sticky: true, align: 'left', updateOnEmptySelection: true } }); // Disable toolbar completely var editor = new MediumEditor('.editable', { toolbar: false }); ``` ### Content Management Retrieve, set, and manage editor content programmatically. ```javascript var editor = new MediumEditor('.editable'); // Get content from first element var content = editor.getContent(); console.log(content); // "<p>Hello <strong>world</strong></p>" // Get content from specific element by index var content = editor.getContent(1); // Set content for first element editor.setContent('<p>New content here</p>'); // Set content for specific element editor.setContent('<h2>Title</h2><p>Paragraph</p>', 1); // Serialize all editor elements to JSON var data = editor.serialize(); console.log(data); // {"element-0": {"value": "<p>Content 1</p>"}, "element-1": {"value": "<p>Content 2</p>"}} // Reset content to initial state editor.resetContent(); // Resets all elements editor.resetContent(document.querySelector('.editable')); // Reset specific element // Check if content has changed editor.checkContentChanged(); // Check active element editor.checkContentChanged(document.querySelector('.editable')); // Check specific element ``` ### Event System Subscribe to custom events for tracking editor state and user interactions. ```javascript var editor = new MediumEditor('.editable'); // Listen to content changes editor.subscribe('editableInput', function (event, editable) { console.log('Content changed in:', editable); console.log('New content:', editable.innerHTML); // Send to server, update preview, etc. }); // Listen to focus events editor.subscribe('focus', function (event, editable) { console.log('Editor focused:', editable); editable.style.border = '2px solid blue'; }); // Listen to blur events editor.subscribe('blur', function (event, editable) { console.log('Editor blurred:', editable); editable.style.border = '1px solid #ccc'; // Save content when user leaves editor saveContent(editable.innerHTML); }); // Listen to toolbar events editor.subscribe('showToolbar', function (event, editable) { console.log('Toolbar shown'); }); editor.subscribe('hideToolbar', function (event, editable) { console.log('Toolbar hidden'); }); // Listen to element addition/removal editor.subscribe('addElement', function (data, editable) { console.log('Element added:', editable); }); editor.subscribe('removeElement', function (data, editable) { console.log('Element removed:', editable); }); // Unsubscribe from events var inputHandler = function(event, editable) { console.log('Input detected'); }; editor.subscribe('editableInput', inputHandler); editor.unsubscribe('editableInput', inputHandler); // Manually trigger custom events editor.trigger('customEvent', { data: 'value' }, document.querySelector('.editable')); ``` ### Selection Management Control and manipulate text selection within the editor. ```javascript var editor = new MediumEditor('.editable'); // Save current selection editor.saveSelection(); // Do some work... someAsyncOperation().then(function() { // Restore previously saved selection editor.restoreSelection(); }); // Export selection state (for persistence) var selectionState = editor.exportSelection(); console.log(selectionState); // {start: 5, end: 10, editableElementIndex: 0} localStorage.setItem('selection', JSON.stringify(selectionState)); // Import selection state var savedState = JSON.parse(localStorage.getItem('selection')); editor.importSelection(savedState); // Get focused element var focusedElement = editor.getFocusedElement(); if (focusedElement) { console.log('Currently editing:', focusedElement); } // Get parent element of selection var parentElement = editor.getSelectedParentElement(); console.log('Selection is within:', parentElement); // Select all content in focused element editor.selectAllContents(); // Select specific element var elementToSelect = document.querySelector('.highlight'); editor.selectElement(elementToSelect); // Stop toolbar updates during bulk operations editor.stopSelectionUpdates(); performBulkEdits(); editor.startSelectionUpdates(); // Manually trigger toolbar update editor.checkSelection(); ``` ### Editor Actions Execute formatting commands and content manipulation. ```javascript var editor = new MediumEditor('.editable'); // Execute built-in commands editor.execAction('bold'); editor.execAction('italic'); editor.execAction('underline'); editor.execAction('strikethrough'); editor.execAction('append-h2'); // Create H2 heading editor.execAction('append-h3'); // Create H3 heading editor.execAction('append-blockquote'); // Create links programmatically editor.createLink({ value: 'https://github.com/yabwe/medium-editor', target: '_blank', buttonClass: 'custom-link-class' }); // Paste HTML at cursor editor.pasteHTML('<p>Inserted <strong>HTML</strong> content</p>'); // Paste HTML with cleaning options editor.pasteHTML('<p class="messy" style="color:red">Clean me</p>', { cleanAttrs: ['class', 'style'], cleanTags: ['script', 'style'], unwrapTags: ['span'] }); // Clean paste (convert to plain text) editor.cleanPaste('Plain text to insert'); // Query command state var isBold = editor.queryCommandState('bold'); console.log('Is text bold?', isBold); var isItalic = editor.queryCommandState('italic'); console.log('Is text italic?', isItalic); ``` ### Dynamic Element Management Add and remove editable elements dynamically after initialization. ```javascript var editor = new MediumEditor('.editable'); // Subscribe to events before adding elements editor.subscribe('editableInput', function(event, editable) { console.log('Content changed'); }); // Add new elements dynamically // HTML: <div class="new-editable">New content area</div> editor.addElements('.new-editable'); // Add specific DOM elements var newDiv = document.createElement('div'); newDiv.className = 'dynamic-editor'; newDiv.innerHTML = 'Dynamic content'; document.body.appendChild(newDiv); editor.addElements(newDiv); // Add multiple elements at once var newElements = document.querySelectorAll('.dynamic'); editor.addElements(newElements); // Remove elements dynamically editor.removeElements('.old-editable'); editor.removeElements(document.querySelector('#remove-me')); // Clean up removed elements (useful in SPAs) var removedElements = []; editor.elements.forEach(function(element) { if (!document.body.contains(element)) { removedElements.push(element); } }); editor.removeElements(removedElements); ``` ### Lifecycle Management Initialize, destroy, and reinitialize the editor. ```javascript // Create editor instance var editor = new MediumEditor('.editable', { toolbar: { buttons: ['bold', 'italic', 'underline'] } }); // Destroy editor (removes event listeners and toolbar) editor.destroy(); // Elements remain contenteditable but MediumEditor is torn down // Reinitialize with same configuration editor.setup(); // Get editor instance from element var element = document.querySelector('.editable'); var editor = MediumEditor.getEditorFromElement(element); if (editor) { console.log('Editor found:', editor); console.log('Version:', editor.constructor.version.toString()); } // Check version console.log('MediumEditor version:', MediumEditor.version.toString()); // "5.23.3" ``` ### Anchor and Link Management Configure link creation and preview behavior. ```javascript var editor = new MediumEditor('.editable', { toolbar: { buttons: ['bold', 'italic', 'anchor'] }, anchor: { customClassOption: 'btn', customClassOptionText: 'Button Style', linkValidation: true, placeholderText: 'Paste or type a link', targetCheckbox: true, targetCheckboxText: 'Open in new window' }, anchorPreview: { hideDelay: 500, previewValueSelector: 'a', showWhenToolbarIsVisible: false, showOnEmptyLinks: true } }); // Disable anchor preview var editor = new MediumEditor('.editable', { anchorPreview: false }); ``` ### Paste Handling Control how pasted content is processed and cleaned. ```javascript var editor = new MediumEditor('.editable', { paste: { // Force plain text pasting forcePlainText: true, // Clean pasted HTML from external sources cleanPastedHTML: false, // Remove specific attributes cleanAttrs: ['class', 'style', 'dir'], // Remove specific tags entirely cleanTags: ['meta', 'script', 'style'], // Unwrap tags (keep content, remove wrapper) unwrapTags: ['span', 'div'], // Custom replacements before built-in cleaning preCleanReplacements: [ [/\u00A0/g, ' '] // Replace non-breaking spaces ], // Custom replacements after built-in cleaning cleanReplacements: [ [/<br>/gi, '\n'] // Convert BR to newlines ] } }); // Advanced paste cleaning var editor = new MediumEditor('.editable', { paste: { forcePlainText: false, cleanPastedHTML: true, cleanAttrs: ['style', 'class', 'id', 'name'], cleanTags: ['meta', 'script', 'style', 'iframe'], unwrapTags: ['font', 'span'] } }); ``` ### Keyboard Commands Customize keyboard shortcuts and commands. ```javascript var editor = new MediumEditor('.editable', { keyboardCommands: { commands: [ { command: 'bold', key: 'B', meta: true, shift: false, alt: false }, { command: 'italic', key: 'I', meta: true, shift: false, alt: false }, { command: 'underline', key: 'U', meta: true, shift: false, alt: false }, { command: 'append-h2', key: '1', meta: true, shift: true, alt: false }, { command: 'append-h3', key: '2', meta: true, shift: true, alt: false } ] } }); // Disable keyboard commands var editor = new MediumEditor('.editable', { keyboardCommands: false }); // Disable specific keyboard command var editor = new MediumEditor('.editable', { keyboardCommands: { commands: [ { command: false, // Disable this shortcut key: 'B', meta: true, shift: false, alt: false } ] } }); ``` ### Auto-Linking and Image Handling Enable automatic URL detection and configure image drag-and-drop. ```javascript // Enable auto-linking (URLs become clickable links automatically) var editor = new MediumEditor('.editable', { autoLink: true }); // Disable image dragging var editor = new MediumEditor('.editable', { imageDragging: false }); // Completely disable file dragging (no drag/drop prevention) var editor = new MediumEditor('.editable', { extensions: { 'imageDragging': {} // Empty extension disables default behavior } }); ``` ### Core Editor Options Configure fundamental editor behavior and constraints. ```javascript var editor = new MediumEditor('.editable', { // Delay before showing toolbar (milliseconds) delay: 1000, // Disable return key disableReturn: false, // Disable double return (prevent multiple blank lines) disableDoubleReturn: false, // Disable extra spaces (trim spaces, prevent multiple consecutive spaces) disableExtraSpaces: false, // Disable editing (useful for read-only with toolbar actions) disableEditing: false, // Enable spellcheck spellcheck: true, // Add target="_blank" to all links targetBlank: true, // CSS class for active toolbar buttons activeButtonClass: 'medium-editor-button-active', // Button label type: false or 'fontawesome' buttonLabels: false, // Container for toolbar elements (default: document.body) elementsContainer: document.body }); // Per-element options via data attributes // <div class="editable" // data-disable-return="true" // data-disable-double-return="true" // data-disable-editing="false" // data-placeholder="Custom placeholder text"> // </div> ``` ### Extensions and Custom Functionality Extend editor functionality with custom extensions. ```javascript // Custom button extension var MyCustomButton = MediumEditor.Extension.extend({ name: 'mybutton', init: function() { this.button = this.document.createElement('button'); this.button.classList.add('medium-editor-action'); this.button.innerHTML = '<b>M</b>'; this.button.title = 'My Custom Action'; this.on(this.button, 'click', this.handleClick.bind(this)); }, getButton: function() { return this.button; }, handleClick: function(event) { event.preventDefault(); event.stopPropagation(); this.base.execAction('bold'); // Custom logic here } }); // Use custom extension var editor = new MediumEditor('.editable', { toolbar: { buttons: ['bold', 'italic', 'mybutton'] }, extensions: { 'mybutton': new MyCustomButton() } }); // Access extensions by name var toolbar = editor.getExtensionByName('toolbar'); var anchorPreview = editor.getExtensionByName('anchor-preview'); console.log('Toolbar extension:', toolbar); ``` ### Placeholder Configuration Customize empty editor placeholder text and behavior. ```javascript var editor = new MediumEditor('.editable', { placeholder: { text: 'Start typing your article...', hideOnClick: true // Hide immediately on focus } }); // Show placeholder while empty (hide only when typing) var editor = new MediumEditor('.editable', { placeholder: { text: 'Enter content here', hideOnClick: false // Only hide when content is added } }); // Disable placeholder var editor = new MediumEditor('.editable', { placeholder: false }); // Per-element placeholder via data attribute // <div class="editable" data-placeholder="Custom placeholder for this element"></div> ``` ### Helper Methods Utility functions for working with the editor. ```javascript var editor = new MediumEditor('.editable'); // Delay function execution editor.delay(function() { console.log('Executed after delay option timeout'); editor.checkSelection(); }); // Direct DOM event handling (auto-cleanup on destroy) editor.on(document.querySelector('.custom-button'), 'click', function(event) { console.log('Button clicked'); editor.execAction('bold'); }, false); // Remove event listener var handler = function(event) { console.log('Clicked'); }; editor.on(element, 'click', handler, false); editor.off(element, 'click', handler, false); ``` ## Use Cases and Integration MediumEditor is ideal for implementing rich text editing in content management systems, blogging platforms, commenting systems, and collaborative writing tools. Its lightweight footprint and framework-agnostic design make it suitable for integration into any web application without introducing heavy dependencies. Common use cases include article editors, note-taking applications, email composition interfaces, and inline content editing for CMS platforms. The editor integrates seamlessly with modern JavaScript frameworks through community-maintained wrappers for React, Angular, Vue, and other popular frameworks. Its event-driven architecture allows for easy integration with autosave functionality, version control systems, and real-time collaboration features. The extensive configuration options enable developers to tailor the editing experience to match their application's requirements, from minimal formatting toolbars for simple comments to full-featured editors for long-form content. With built-in support for custom extensions, keyboard shortcuts, and paste handling, MediumEditor provides a solid foundation for building sophisticated content editing experiences while maintaining the clean, distraction-free aesthetic that made Medium.com's editor popular.