Skip to content

stacksjs/ts-medium-editor

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

49 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

TypeScript Medium Editor - A modern WYSIWYG editor

npm version GitHub Actions Commitizen friendly License Discord

A modern TypeScript port of the popular Medium.com-style WYSIWYG editor

Features โ€ข Installation โ€ข Quick Start โ€ข Live Demos โ€ข API โ€ข Examples โ€ข Contributing


Features

  • ๐Ÿ“ Medium-like Editor - A modern TypeScript port of the popular Medium.com-style WYSIWYG editor
  • ๐Ÿ”ง Extensible Architecture - Plugin system for custom functionality and toolbar buttons
  • ๐Ÿ“ฑ Mobile Friendly - Touch and mobile device support with responsive design
  • ๐ŸŽจ Customizable Themes - 7 built-in themes plus extensive styling options
  • โšก Lightweight - Zero dependencies, small bundle size
  • ๐Ÿ”’ Type Safe - Full TypeScript support with comprehensive type definitions
  • ๐ŸŽฏ Auto-Link Detection - Automatically converts URLs to clickable links
  • ๐Ÿ“‹ Smart Paste - Cleans up pasted content from Word, Google Docs, etc.
  • ๐Ÿ”„ Event System - Comprehensive event handling for content changes
  • ๐ŸŽ›๏ธ Flexible Toolbars - Static, floating, or custom positioned toolbars

Installation

Choose your preferred package manager:

# npm npm install ts-medium-editor # yarn yarn add ts-medium-editor # pnpm pnpm add ts-medium-editor # bun bun add ts-medium-editor

Quick Start

Basic Setup

import { MediumEditor } from 'ts-medium-editor' import 'ts-medium-editor/css/medium-editor.css' import 'ts-medium-editor/css/themes/default.css' // Initialize editor const editor = new MediumEditor('.editable', { toolbar: { buttons: ['bold', 'italic', 'underline', 'anchor', 'h2', 'h3', 'quote'] }, placeholder: { text: 'Tell your story...' } })

HTML Structure

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>My Editor</title> <link rel="stylesheet" href="node_modules/ts-medium-editor/css/medium-editor.css"> <link rel="stylesheet" href="node_modules/ts-medium-editor/css/themes/default.css"> </head> <body> <div class="editable"> <p>Start typing here...</p> </div> <script type="module"> import { MediumEditor } from './node_modules/ts-medium-editor/dist/index.js' const editor = new MediumEditor('.editable', { placeholder: { text: 'Tell your story...' } }) </script> </body> </html>

Live Demos

Explore our comprehensive demo collection to see all features in action:

Core Features

Advanced Configurations

Multiple Editors

Specialized Use Cases

TypeScript Configuration

For optimal TypeScript support, configure your tsconfig.json:

{ "compilerOptions": { "lib": ["esnext", "dom", "dom.iterable"], "moduleResolution": "bundler", "allowSyntheticDefaultImports": true, "esModuleInterop": true, "strict": true, "skipLibCheck": true } }

API Reference

Constructor Options

interface MediumEditorOptions { // Core Settings activeButtonClass?: string // CSS class for active buttons buttonLabels?: boolean | string | ButtonLabels // Button label configuration delay?: number // Toolbar show delay (ms) disableReturn?: boolean // Disable return key disableDoubleReturn?: boolean // Disable double return disableExtraSpaces?: boolean // Prevent extra spaces disableEditing?: boolean // Make editor read-only spellcheck?: boolean // Enable spellcheck // Auto-features autoLink?: boolean // Auto-convert URLs to links targetBlank?: boolean // Open links in new tab imageDragging?: boolean // Enable image drag-and-drop fileDragging?: boolean // Enable file drag-and-drop // DOM Configuration elementsContainer?: HTMLElement // Container for editor elements contentWindow?: Window // Window context ownerDocument?: Document // Document context // Extensions extensions?: Record<string, Extension> // Custom extensions // Feature Modules toolbar?: ToolbarOptions | false // Toolbar configuration anchorPreview?: AnchorPreviewOptions | false // Link preview placeholder?: PlaceholderOptions | false // Placeholder text anchor?: AnchorOptions | false // Link creation paste?: PasteOptions | false // Paste handling keyboardCommands?: KeyboardOptions | false // Keyboard shortcuts }

Core Methods

class MediumEditor { // Lifecycle constructor(elements: Elements, options?: MediumEditorOptions) setup(): MediumEditor destroy(): void // Content Management getContent(index?: number): string setContent(html: string, index?: number): void serialize(): Record<string, string> resetContent(element?: HTMLElement): void // Element Management addElements(elements: Elements): void removeElements(elements: Elements): void // Selection Management exportSelection(): SelectionState | null importSelection(state: SelectionState, favorLater?: boolean): void saveSelection(): void restoreSelection(): void selectAllContents(): void selectElement(element: HTMLElement): void // Event Handling subscribe(event: string, listener: EventListener): MediumEditor unsubscribe(event: string, listener: EventListener): MediumEditor trigger(event: string, data?: any, editable?: HTMLElement): MediumEditor // Actions execAction(action: string, opts?: any): boolean queryCommandState(action: string): boolean }

Examples

Custom Toolbar with FontAwesome

const editor = new MediumEditor('.editable', { buttonLabels: 'fontawesome', toolbar: { buttons: [ 'bold', 'italic', 'underline', 'strikethrough', 'subscript', 'superscript', 'anchor', 'image', 'quote', 'pre', 'orderedlist', 'unorderedlist', 'indent', 'outdent', 'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ], static: true, sticky: true, align: 'center' } })

Auto-Link Configuration

const editor = new MediumEditor('.editable', { autoLink: true, targetBlank: true, toolbar: { buttons: ['bold', 'italic', 'anchor'] }, anchor: { placeholderText: 'Enter a URL', targetCheckbox: true, targetCheckboxText: 'Open in new tab' } })

Multiple Editors with Different Configs

// Title editor (no line breaks) const titleEditor = new MediumEditor('.title', { disableReturn: true, disableExtraSpaces: true, toolbar: { buttons: ['bold', 'italic'] }, placeholder: { text: 'Enter title...' } }) // Content editor (full features) const contentEditor = new MediumEditor('.content', { autoLink: true, toolbar: { buttons: ['bold', 'italic', 'underline', 'anchor', 'h2', 'h3', 'quote', 'orderedlist', 'unorderedlist'] }, placeholder: { text: 'Tell your story...' } })

Smart Paste Configuration

const editor = new MediumEditor('.editable', { paste: { forcePlainText: false, cleanPastedHTML: true, cleanReplacements: [ [/\s*style\s*=\s*["'][^"']*["']/gi, ''], // Remove inline styles [/<o:p\s*\/?>|<\/o:p>/gi, ''], // Remove Word tags [/<xml>[\s\S]*?<\/xml>/gi, ''], // Remove XML [/<!--[\s\S]*?-->/g, ''] // Remove comments ], cleanAttrs: ['class', 'style', 'dir'], cleanTags: ['meta', 'style', 'script', 'object', 'embed'] } })

Event Handling

const editor = new MediumEditor('.editable') // Content change events editor.subscribe('editableInput', (event, editable) => { console.log('Content changed:', editable.innerHTML) // Auto-save logic here }) // Selection change events editor.subscribe('editableKeyup', (event, editable) => { const selection = editor.exportSelection() console.log('Cursor position:', selection) }) // Focus events editor.subscribe('focus', (event, editable) => { console.log('Editor focused') }) editor.subscribe('blur', (event, editable) => { console.log('Editor blurred') })

Creating Custom Extensions

import { MediumEditorExtension } from 'ts-medium-editor' class EmojiExtension implements MediumEditorExtension { name = 'emoji' private button!: HTMLButtonElement private base: any init(): void { this.button = this.createButton() } getButton(): HTMLButtonElement { return this.button } private createButton(): HTMLButtonElement { const button = document.createElement('button') button.className = 'medium-editor-action' button.innerHTML = '๐Ÿ˜€' button.title = 'Insert Emoji' button.addEventListener('click', this.handleClick.bind(this)) return button } private handleClick(): void { const emoji = '๐ŸŽ‰' const selection = window.getSelection() if (selection && selection.rangeCount > 0) { const range = selection.getRangeAt(0) range.deleteContents() range.insertNode(document.createTextNode(emoji)) range.collapse(false) selection.removeAllRanges() selection.addRange(range) } } destroy(): void { if (this.button) { this.button.removeEventListener('click', this.handleClick) } } } // Use the extension const editor = new MediumEditor('.editable', { toolbar: { buttons: ['bold', 'italic', 'emoji'] }, extensions: { emoji: new EmojiExtension() } })

Theme Switching

const themeSelector = document.getElementById('theme-select') as HTMLSelectElement const themeLink = document.getElementById('theme-css') as HTMLLinkElement const themes = [ 'default', 'beagle', 'bootstrap', 'flat', 'mani', 'roman', 'tim' ] themeSelector.addEventListener('change', (event) => { const theme = (event.target as HTMLSelectElement).value themeLink.href = `./dist/css/themes/${theme}.css` })

Available Themes

The library includes 7 beautiful themes:

  • Default - Clean, modern design
  • Beagle - Friendly, rounded interface
  • Bootstrap - Bootstrap-compatible styling
  • Flat - Minimalist flat design
  • Mani - Elegant, sophisticated look
  • Roman - Classic, serif-inspired
  • Tim - Bold, high-contrast theme
<!-- Include your chosen theme --> <link rel="stylesheet" href="dist/css/themes/default.css">

Advanced Configuration

Toolbar Positioning

// Static toolbar (always visible) const editor = new MediumEditor('.editable', { toolbar: { static: true, sticky: true, align: 'center' } }) // Relative container const editor = new MediumEditor('.editable', { toolbar: { relativeContainer: document.getElementById('toolbar-container') } })

Custom Button Configuration

const editor = new MediumEditor('.editable', { toolbar: { buttons: [ 'bold', 'italic', { name: 'highlight', action: 'highlight', aria: 'Highlight text', contentDefault: 'H', classList: ['custom-highlight-button'], attrs: { 'data-action': 'highlight' } } ] } })

Testing

Run the test suite:

bun test

Community

For help, discussion about best practices, or any other conversation:

Postcardware

โ€œSoftware that is free, but hopes for a postcard.โ€ We love receiving postcards from around the world showing where Stacks is being used! We showcase them on our website too.

Our address: Stacks.js, 12665 Village Ln #2306, Playa Vista, CA 90094, United States ๐ŸŒŽ

Sponsors

We would like to extend our thanks to the following sponsors for funding Stacks development:

Become a sponsor and support open source development.

Credits

  • Medium - For the beautiful editor design inspiration
  • medium-editor - The original JavaScript implementation that inspired this TypeScript port
  • Chris Breuer - Primary maintainer and TypeScript port author
  • All Contributors - Everyone who has contributed to making this project better

License

The MIT License (MIT). Please see LICENSE for more information.


Made with ๐Ÿ’™ by the Stacks team

โญ Star us on GitHub โ€ข ๐Ÿฆ Follow on Bluesky โ€ข ๐Ÿ’ฌ Join Discord

About

A modern, minimal & performant Medium-like rich text editor.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

  •  

Contributors 5