https://github.com/stacksjs/ts-medium-editor
A modern, minimal & performant Medium-like rich text editor.
https://github.com/stacksjs/ts-medium-editor
editor lightweight medium rich-text-editor typescript wysiwyg
Last synced: 11 months ago
JSON representation
A modern, minimal & performant Medium-like rich text editor.
- Host: GitHub
- URL: https://github.com/stacksjs/ts-medium-editor
- Owner: stacksjs
- License: mit
- Created: 2025-06-04T14:46:53.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2025-07-19T15:52:06.000Z (11 months ago)
- Last Synced: 2025-07-19T19:29:51.734Z (11 months ago)
- Topics: editor, lightweight, medium, rich-text-editor, typescript, wysiwyg
- Language: TypeScript
- Homepage: https://ts-medium-editor.netlify.app
- Size: 1.39 MB
- Stars: 3
- Watchers: 0
- Forks: 0
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: .github/CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE.md
- Code of conduct: .github/CODE_OF_CONDUCT.md
- Security: .github/SECURITY.md
Awesome Lists containing this project
README

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:
```bash
# 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
```typescript
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
```html
My Editor
Start typing here...
import { MediumEditor } from './node_modules/ts-medium-editor/dist/index.js'
const editor = new MediumEditor('.editable', {
placeholder: { text: 'Tell your story...' }
})
```
## Live Demos
Explore our comprehensive demo collection to see all features in action:
### Core Features
- **[Basic Editor](demo/index.html)** - Simple setup with essential toolbar
- **[Auto-Link Detection](demo/auto-link.html)** - Automatic URL to link conversion
- **[Clean Paste](demo/clean-paste.html)** - Smart content cleaning from Word/Google Docs
- **[Textarea Support](demo/textarea.html)** - Enhance HTML textareas with rich editing
### Advanced Configurations
- **[Custom Toolbars](demo/custom-toolbar.html)** - 5 different toolbar configurations
- **[Static Toolbar](demo/static-toolbar.html)** - Always-visible toolbars with alignment options
- **[Button Examples](demo/button-example.html)** - Custom button creation with Rangy integration
- **[Extension Examples](demo/extension-example.html)** - 4 powerful extensions with Shiki syntax highlighting
### Multiple Editors
- **[Multi-Editor](demo/multi-editor.html)** - Multiple independent editor instances
- **[Single Instance](demo/multi-one-instance.html)** - Dynamic element addition to existing editors
- **[Nested Editable](demo/nested-editable.html)** - Complex nested contenteditable layouts
### Specialized Use Cases
- **[Multi-Paragraph](demo/multi-paragraph.html)** - Toolbar behavior with paragraph selection
- **[Relative Toolbar](demo/relative-toolbar.html)** - Constrained toolbar positioning
- **[Absolute Container](demo/absolute-container.html)** - Absolute positioned container examples
- **[Custom Extensions](demo/pass-instance.html)** - Instance-aware extension development
- **[Table Extension](demo/table-extension.html)** - Custom table insertion functionality
## TypeScript Configuration
For optimal TypeScript support, configure your `tsconfig.json`:
```json
{
"compilerOptions": {
"lib": ["esnext", "dom", "dom.iterable"],
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true
}
}
```
## API Reference
### Constructor Options
```typescript
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 // 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
```typescript
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
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
```typescript
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
```typescript
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
```typescript
// 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
```typescript
const editor = new MediumEditor('.editable', {
paste: {
forcePlainText: false,
cleanPastedHTML: true,
cleanReplacements: [
[/\s*style\s*=\s*["'][^"']*["']/gi, ''], // Remove inline styles
[/|<\/o:p>/gi, ''], // Remove Word tags
[/[\s\S]*?<\/xml>/gi, ''], // Remove XML
[//g, ''] // Remove comments
],
cleanAttrs: ['class', 'style', 'dir'],
cleanTags: ['meta', 'style', 'script', 'object', 'embed']
}
})
```
### Event Handling
```typescript
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
```typescript
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
```typescript
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
```html
```
## Advanced Configuration
### Toolbar Positioning
```typescript
// 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
```typescript
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:
```bash
bun test
```
## Community
For help, discussion about best practices, or any other conversation:
- 💬 [GitHub Discussions](https://github.com/stacksjs/stacks/discussions)
- 🎮 [Discord Server](https://discord.gg/5gHFD8Uk3K)
- 🐛 [Issue Tracker](https://github.com/stacksjs/ts-medium-editor/issues)
## 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:
- [**JetBrains**](https://www.jetbrains.com/) - Professional development tools
- [**The Solana Foundation**](https://solana.com/) - Blockchain infrastructure
_[Become a sponsor](https://github.com/sponsors/stacksjs) and support open source development._
## Credits
- **[Medium](https://medium.com)** - For the beautiful editor design inspiration
- **[medium-editor](https://github.com/yabwe/medium-editor)** - The original JavaScript implementation that inspired this TypeScript port
- **[Chris Breuer](https://github.com/chrisbbreuer)** - Primary maintainer and TypeScript port author
- **[All Contributors](../../contributors)** - Everyone who has contributed to making this project better
## License
The MIT License (MIT). Please see [LICENSE](LICENSE.md) for more information.
---
Made with 💙 by the Stacks team