@cannyminds/rich-text-editor
v0.3.0
Published
A React Rich Text Editor with multi-user track changes, complete JSON history export, and collaborative editing support. Built on Tiptap.
Maintainers
Readme
@cannyminds/rich-text-editor
A powerful React Rich Text Editor component built with Tiptap, featuring multi-user track changes with complete JSON history export.
✨ Features
- 🎨 Multi-User Track Changes - Real-time collaborative editing with user-specific highlights
- 📋 Complete History in JSON - Export all changes with user attribution and timestamps
- 🛠️ Headless Architecture - Full styling control, works with any design system
- ⚛️ React 16.8+ - Modern hooks-based API with TypeScript support
- 🔌 Extensible - Built on Tiptap, use 100+ community extensions
- 📝 Rich Formatting - Tables, images, links, code blocks, and more
📦 Installation
npm
npm install @cannyminds/rich-text-editorpnpm
pnpm add @cannyminds/rich-text-editoryarn
yarn add @cannyminds/rich-text-editorbun
bun add @cannyminds/rich-text-editorLocal Development (Monorepo / Workspaces)
If you're developing locally within a monorepo or want to link the package during development:
pnpm workspaces:
// In your consuming package's package.json
{
"dependencies": {
"@cannyminds/rich-text-editor": "workspace:*"
}
}npm/yarn link:
# In the rich-text-editor package directory
npm link
# In your consuming project
npm link @cannyminds/rich-text-editorFile reference:
// In your consuming package's package.json
{
"dependencies": {
"@cannyminds/rich-text-editor": "file:../packages/rich-text-editor"
}
}🚀 Quick Start
Basic Usage
import { RichTextEditor } from '@cannyminds/rich-text-editor';
import '@cannyminds/rich-text-editor/styles.css';
function App() {
const [content, setContent] = useState('<p>Hello World!</p>');
return (
<RichTextEditor
content={content}
onUpdate={(html) => setContent(html)}
placeholder="Start writing..."
showToolbar={true}
showCharacterCount={true}
/>
);
}With Track Changes
import {
RichTextEditor,
TrackChanges,
type TrackChangesUser
} from '@cannyminds/rich-text-editor';
import '@cannyminds/rich-text-editor/styles.css';
function CollaborativeEditor() {
const [changes, setChanges] = useState<TrackChange[]>([]);
const currentUser: TrackChangesUser = {
id: 'user-1',
name: 'John Doe',
color: '#3B82F6' // Blue highlights
};
const handleTrackChange = (change: TrackChange) => {
// Each change is delivered as JSON
setChanges(prev => [...prev, {
id: crypto.randomUUID(),
...change,
}]);
};
return (
<RichTextEditor
content=""
extensions={[
TrackChanges.configure({
enabled: true,
currentUser,
onTrackChange: handleTrackChange,
})
]}
/>
);
}📊 Track Changes JSON Format
Every change is captured in a structured JSON format, perfect for auditing, version control, and collaborative workflows.
TrackChange Interface
interface TrackChange {
userId: string; // User identifier
userName: string; // Display name
userColor: string; // Hex color for highlighting
text: string; // Changed text content
from: number; // Start position in document
to: number; // End position in document
type: 'insert' | 'delete'; // Type of change
}TextChange (Extended Format)
For complete history tracking with persistence:
interface TextChange {
id: string; // Unique change identifier
userId: string; // User ID
userName: string; // User display name
userColor: string; // Hex color (#3B82F6)
timestamp: number; // Unix timestamp (ms)
startOffset: number; // Document start position
endOffset: number; // Document end position
oldText: string; // Text before change
newText: string; // Text after change
type: 'insert' | 'delete' | 'replace';
}Example JSON Output
[
{
"id": "change-1705678901234",
"userId": "user-1",
"userName": "John Doe",
"userColor": "#3B82F6",
"timestamp": 1705678901234,
"startOffset": 0,
"endOffset": 12,
"oldText": "",
"newText": "Hello World!",
"type": "insert"
},
{
"id": "change-1705678905678",
"userId": "user-2",
"userName": "Jane Smith",
"userColor": "#10B981",
"timestamp": 1705678905678,
"startOffset": 6,
"endOffset": 11,
"oldText": "World",
"newText": "",
"type": "delete"
}
]🎨 Multi-User Highlighting
Each user gets a unique color for their changes:
const users: TrackChangesUser[] = [
{ id: 'user-1', name: 'John Doe', color: '#3B82F6' }, // Blue
{ id: 'user-2', name: 'Jane Smith', color: '#10B981' }, // Green
{ id: 'user-3', name: 'Bob Wilson', color: '#F59E0B' }, // Orange
{ id: 'user-4', name: 'Alice Brown', color: '#8B5CF6' }, // Purple
];Visual Indicators:
- Insertions: Background highlight + colored underline
- Deletions: Red strikethrough with light red background
- Tooltips: Hover to see "Added by [User] • [Date/Time]"
🔧 API Reference
RichTextEditor Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| content | string | '' | Initial HTML content |
| onUpdate | (html: string) => void | - | Callback on content change |
| onBlur | (html: string) => void | - | Callback on blur |
| editable | boolean | true | Enable/disable editing |
| placeholder | string | 'Start writing...' | Placeholder text |
| extensions | Extension[] | [] | Additional Tiptap extensions |
| showToolbar | boolean | true | Show/hide toolbar |
| showCharacterCount | boolean | false | Show character count |
| minHeight | number \| string | 200 | Minimum editor height |
| maxHeight | number \| string | - | Maximum editor height |
| autoFocus | boolean | false | Auto-focus on mount |
| className | string | '' | Custom container class |
TrackChanges Extension Options
TrackChanges.configure({
enabled: boolean; // Enable/disable tracking
currentUser: TrackChangesUser; // Current user info
onTrackChange: (change) => {}; // Change callback
})Editor Commands
// Enable/disable tracking
editor.commands.enableTrackChanges();
editor.commands.disableTrackChanges();
// Set current user
editor.commands.setTrackChangesUser({
id: 'user-1',
name: 'John Doe',
color: '#3B82F6'
});🎯 Advanced Usage
Headless Mode (Custom UI)
import { useEditor, EditorContent, StarterKit } from '@cannyminds/rich-text-editor';
import '@cannyminds/rich-text-editor/styles.css';
function CustomEditor() {
const editor = useEditor({
extensions: [StarterKit],
content: '<p>Custom UI</p>',
});
return (
<div>
<button onClick={() => editor?.chain().focus().toggleBold().run()}>
Bold
</button>
{/*
IMPORTANT: Wrap EditorContent in 'rte-content' to apply library styles
(tables, lists, blockquotes, etc.)
*/}
<div className="rte-content">
<EditorContent editor={editor} />
</div>
</div>
);
}Export History as JSON
// Get all changes from your state management
const exportHistory = () => {
const historyJson = JSON.stringify(changes, null, 2);
// Download as file
const blob = new Blob([historyJson], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'document-history.json';
a.click();
};Toggle Highlight Visibility
Use CSS to show/hide track change highlights:
/* Hide all highlights */
.hide-highlights .track-change-highlight {
background: transparent !important;
border-bottom: none !important;
}
/* Hide deletions */
.hide-highlights .track-change-delete {
display: none;
}📚 Exports
// Components
export { RichTextEditor } from '@cannyminds/rich-text-editor';
// Extensions
export { TrackChanges, trackChangesPluginKey } from '@cannyminds/rich-text-editor';
export { ResizableImage } from '@cannyminds/rich-text-editor';
// Tiptap Re-exports
export { useEditor, EditorContent } from '@cannyminds/rich-text-editor';
export { StarterKit, Table, Link, Image, Highlight } from '@cannyminds/rich-text-editor';
// Types
export type {
TrackChangesUser,
TrackChangesOptions,
RichTextEditorProps,
Editor
} from '@cannyminds/rich-text-editor';🎨 Styling
Import the default styles:
import '@cannyminds/rich-text-editor/styles.css';Or use the dist path:
import '@cannyminds/rich-text-editor/dist/styles.css';CSS Variables (Customization)
:root {
--rte-border-color: #e2e8f0;
--rte-toolbar-bg: #f8fafc;
--rte-content-bg: #ffffff;
--rte-focus-ring: #3b82f6;
}📄 License
MIT © CannyMinds Dev Team
🔗 Links
- GitHub Repository
- Demo Application (Run
pnpm devin the project) - Tiptap Documentation
