@reeverdev/editor
v1.0.2
Published
Reever Editor - Modern, extensible rich text editor framework
Downloads
149
Maintainers
Readme
Reever Editor
A headless, extensible rich text editor framework built on ProseMirror. Inspired by Tiptap, designed for flexibility.
Features
- Headless Architecture - Full control over UI, bring your own components
- Extensible - Modular extension system for nodes, marks, and plugins
- Framework Agnostic - Core is vanilla JS, with first-class React bindings
- TypeScript First - Written in TypeScript with full type coverage
- ProseMirror Foundation - Built on battle-tested ProseMirror
Installation
# npm
npm install @reeverdev/editor
# pnpm
pnpm add @reeverdev/editor
# yarn
yarn add @reeverdev/editorQuick Start
Vanilla JavaScript
import { Editor } from '@reeverdev/editor';
import { StarterKit } from '@reeverdev/editor/starter-kit';
const editor = new Editor({
element: document.querySelector('#editor'),
extensions: [StarterKit],
content: '<p>Hello World!</p>',
});
// Use commands
editor.chain().focus().toggleBold().run();
// Get content
const html = editor.getHTML();
const json = editor.getJSON();React
import { useEditor, EditorContent } from '@reeverdev/editor/react';
import { StarterKit } from '@reeverdev/editor/starter-kit';
function MyEditor() {
const editor = useEditor({
extensions: [StarterKit],
content: '<p>Hello World!</p>',
});
return <EditorContent editor={editor} />;
}Packages
| Package | Description |
|---------|-------------|
| @reeverdev/core | Core editor, extension system, commands |
| @reeverdev/react | React hooks and components |
| @reeverdev/starter-kit | Pre-configured bundle of essential extensions |
| @reeverdev/pm | ProseMirror re-exports |
Extensions
| Extension | Description |
|-----------|-------------|
| @reeverdev/extension-bold | Bold text formatting |
| @reeverdev/extension-italic | Italic text formatting |
| @reeverdev/extension-underline | Underline text formatting |
| @reeverdev/extension-strike | Strikethrough text |
| @reeverdev/extension-code | Inline code |
| @reeverdev/extension-code-block | Code blocks |
| @reeverdev/extension-heading | Headings (h1-h6) |
| @reeverdev/extension-paragraph | Paragraphs |
| @reeverdev/extension-blockquote | Block quotes |
| @reeverdev/extension-bullet-list | Bullet lists |
| @reeverdev/extension-ordered-list | Numbered lists |
| @reeverdev/extension-task-list | Task/checkbox lists |
| @reeverdev/extension-task-item | Task list items |
| @reeverdev/extension-link | Hyperlinks |
| @reeverdev/extension-image | Images with resize support |
| @reeverdev/extension-highlight | Text highlighting |
| @reeverdev/extension-color | Text color |
| @reeverdev/extension-text-style | Text styling base |
| @reeverdev/extension-history | Undo/redo |
| @reeverdev/extension-placeholder | Placeholder text |
| @reeverdev/extension-bubble-menu | Selection-based floating menu |
| @reeverdev/extension-floating-menu | Empty line floating menu |
| @reeverdev/extension-horizontal-rule | Horizontal dividers |
| @reeverdev/extension-hard-break | Line breaks |
| @reeverdev/extension-subscript | Subscript text |
| @reeverdev/extension-superscript | Superscript text |
Creating Custom Extensions
Basic Extension
import { Extension } from '@reeverdev/core';
const MyExtension = Extension.create({
name: 'myExtension',
addCommands() {
return {
myCommand: () => ({ commands }) => {
// Your command logic
return true;
},
};
},
addKeyboardShortcuts() {
return {
'Mod-Shift-x': () => this.editor.commands.myCommand(),
};
},
});Custom Node
import { Node } from '@reeverdev/core';
const CustomNode = Node.create({
name: 'customNode',
group: 'block',
content: 'inline*',
parseHTML() {
return [{ tag: 'div[data-custom]' }];
},
renderHTML({ HTMLAttributes }) {
return ['div', { 'data-custom': '', ...HTMLAttributes }, 0];
},
});Custom Mark
import { Mark } from '@reeverdev/core';
const CustomMark = Mark.create({
name: 'customMark',
parseHTML() {
return [{ tag: 'span[data-custom-mark]' }];
},
renderHTML({ HTMLAttributes }) {
return ['span', { 'data-custom-mark': '', ...HTMLAttributes }, 0];
},
addCommands() {
return {
toggleCustomMark: () => ({ commands }) => {
return commands.toggleMark(this.name);
},
};
},
});Configuration
StarterKit Options
import { StarterKit } from '@reeverdev/editor/starter-kit';
const editor = new Editor({
extensions: [
StarterKit.configure({
// Disable specific extensions
history: false,
codeBlock: false,
// Configure extensions
heading: {
levels: [1, 2, 3],
},
link: {
validate: (href) => href.startsWith('https://'),
},
image: {
allowResize: true,
},
}),
],
});Editor Events
const editor = new Editor({
extensions: [StarterKit],
onUpdate: ({ editor }) => {
console.log('Content updated:', editor.getHTML());
},
onSelectionUpdate: ({ editor }) => {
console.log('Selection changed');
},
onCreate: ({ editor }) => {
console.log('Editor created');
},
onDestroy: () => {
console.log('Editor destroyed');
},
onFocus: ({ editor }) => {
console.log('Editor focused');
},
onBlur: ({ editor }) => {
console.log('Editor blurred');
},
});React Components
BubbleMenu
A floating menu that appears when text is selected.
import { BubbleMenu } from '@reeverdev/editor/react';
function MyEditor() {
const editor = useEditor({ extensions: [StarterKit] });
return (
<>
<EditorContent editor={editor} />
<BubbleMenu editor={editor}>
<button onClick={() => editor.chain().focus().toggleBold().run()}>
Bold
</button>
<button onClick={() => editor.chain().focus().toggleItalic().run()}>
Italic
</button>
</BubbleMenu>
</>
);
}FloatingMenu
A menu that appears on empty lines.
import { FloatingMenu } from '@reeverdev/editor/react';
function MyEditor() {
const editor = useEditor({ extensions: [StarterKit] });
return (
<>
<EditorContent editor={editor} />
<FloatingMenu editor={editor}>
<button onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}>
H1
</button>
<button onClick={() => editor.chain().focus().toggleBulletList().run()}>
List
</button>
</FloatingMenu>
</>
);
}Commands
Commands are chainable and provide a fluent API:
// Chain multiple commands
editor
.chain()
.focus()
.selectAll()
.toggleBold()
.run();
// Check if command can execute
if (editor.can().toggleBold()) {
editor.commands.toggleBold();
}
// Available commands (varies by extensions)
editor.commands.setBold();
editor.commands.unsetBold();
editor.commands.toggleBold();
editor.commands.setItalic();
editor.commands.toggleHeading({ level: 1 });
editor.commands.setLink({ href: 'https://example.com' });
editor.commands.insertContent('<p>New content</p>');
editor.commands.setContent('<p>Replace all</p>');
editor.commands.clearContent();
editor.commands.undo();
editor.commands.redo();Working with Content
// Get content
const html = editor.getHTML();
const json = editor.getJSON();
const text = editor.getText();
// Set content
editor.commands.setContent('<p>New content</p>');
editor.commands.setContent({ type: 'doc', content: [...] });
// Insert content
editor.commands.insertContent('<p>Inserted</p>');
editor.commands.insertContentAt(10, '<p>At position</p>');
// Clear content
editor.commands.clearContent();Development
# Install dependencies
pnpm install
# Run development server
pnpm dev
# Build all packages
pnpm build
# Run tests
pnpm test
# Lint
pnpm lintProject Structure
packages/
├── core/ # Core editor
├── react/ # React bindings
├── starter-kit/ # Pre-configured extensions
├── pm/ # ProseMirror re-exports
├── extension-bold/ # Bold extension
├── extension-italic/ # Italic extension
├── extension-*/ # Other extensions
examples/
└── react-basic/ # React example appLicense
MIT
Contributing
Contributions are welcome! Please read our contributing guidelines before submitting a PR.
