Introduction
Noteverse’s editor is built on top of Novel.sh , which provides a powerful React wrapper around TipTap. The editor supports real-time collaboration, rich text formatting, media embeds, and custom extensions.
Architecture
The editor is composed of several key components:
EditorRoot : Container that manages editor state
EditorContent : The main editing surface
EditorBubble : Floating toolbar for text formatting
EditorCommand : Slash command menu for quick actions
Custom Extensions : Collaborative features and enhanced functionality
Component Props
Initial editor content in JSON format from TipTap
onChange
(value: JSONContent) => void
required
Callback fired when editor content changes
Additional CSS classes for the editor container
Placeholder text shown when editor is empty
Array of connected users for collaborative editing
Current user data for collaboration features
Unique identifier for the note being edited
Whether the user has permission to edit
Authentication token for API requests
Basic Usage
Basic Editor
With Connected Users
import Editor from '@/components/editor/editor'
import { useState } from 'react'
import { JSONContent } from 'novel'
export default function MyEditor () {
const [ content , setContent ] = useState < JSONContent >()
return (
< Editor
content = { content }
onChange = { setContent }
userData = { { id: 1 , name: 'User' } }
notesId = { 123 }
canEdit = { true }
authToken = "your-token"
placeholder = "Start writing..."
/>
)
}
Real-Time Collaboration
The editor integrates with Socket.IO for real-time collaboration features:
Cursor Position Tracking
When content updates, the editor broadcasts cursor position:
socket . emit (
'updateUser' ,
{
userId: userData . id ,
notesId ,
updates: { position: cursorPosition },
},
( response ) => {
if ( response . success ) {
setLiveUsers ( response . users )
}
},
)
Multiple Carets
The editor displays colored carets for all connected users using the MultipleCarets extension:
MultipleCarets . configure ({
carets: connectedUsers . map (( u ) => ({
position: u . position ,
name: u . userName ,
color: u . color ,
isActive: u . isActive || false ,
})),
})
Editor Configuration
Editor Props
The editor is configured with several TipTap properties:
DOM Events
Image Handling
Styling
editorProps = {{
handleDOMEvents : {
keydown : ( _view , event ) => handleCommandNavigation ( event ),
},
}}
Handles keyboard navigation for command menu. editorProps = {{
handlePaste : ( view , event ) =>
handleImagePaste ( view , event , uploadFn ),
handleDrop : ( view , event , _slice , moved ) =>
handleImageDrop ( view , event , moved , uploadFn ),
}}
Supports drag-and-drop and paste for images. editorProps = {{
attributes : {
class : `prose prose-sm dark:prose-invert
prose-headings:font-title font-default
focus:outline-none max-w-full p-0` ,
},
}}
Uses Tailwind Typography plugin for content styling.
The floating toolbar appears when text is selected:
< EditorBubble
tippyOptions = { {
placement: 'top' ,
zIndex: 10 ,
} }
className = "flex w-fit max-w-[90vw] overflow-hidden rounded-md border"
>
< NodeSelector open = { openNode } onOpenChange = { setOpenNode } />
< LinkSelector open = { openLink } onOpenChange = { setOpenLink } />
< TextButtons />
< ColorSelector open = { openColor } onOpenChange = { setOpenColor } />
</ EditorBubble >
See the Selectors documentation for detailed information.
Command Menu
Press / to open the command menu for quick formatting:
< EditorCommand className = "z-20 h-auto max-h-[330px] overflow-y-auto" >
< EditorCommandEmpty > No results </ EditorCommandEmpty >
< EditorCommandList >
{ suggestionItems . map (( item ) => (
< EditorCommandItem
value = { item . title }
onCommand = { ( val ) => item . command ?.( val ) }
key = { item . title }
>
< div className = "flex h-10 w-10 items-center justify-center" >
{ item . icon }
</ div >
< div >
< p className = "font-medium" > { item . title } </ p >
< p className = "text-xs text-muted-foreground" >
{ item . description }
</ p >
</ div >
</ EditorCommandItem >
)) }
</ EditorCommandList >
</ EditorCommand >
See the Slash Commands documentation for available commands.
Image Upload
The editor includes image upload functionality with the ImageResizer component:
< EditorContent
// ... other props
slotAfter = { < ImageResizer /> }
/>
Images can be:
Pasted from clipboard
Dragged and dropped
Uploaded via slash command
You need to implement the uploadFn function in image-upload.ts to handle your image storage solution.
Extensions
The editor uses a combination of built-in and custom extensions:
extensions = { [
... defaultExtensions,
slashCommand,
TextSearch,
MultipleCarets. configure ({
carets: connectedUsers. map (( u ) => ({
position: u.position,
name: u.userName,
color: u.color,
isActive: u.isActive || false ,
})),
}),
]}
See the Extensions documentation for the complete list.
Accessing the Editor Instance
The editor instance is managed through context:
import { useEditorContext } from '@/context/editorContext'
function MyComponent () {
const { editor , setEditor } = useEditorContext ()
// Use editor instance
const getContent = () => {
return editor . getJSON ()
}
const clearContent = () => {
editor . commands . clearContent ()
}
}
TypeScript Interface
interface EditorProp {
content ?: JSONContent
onChange : ( value : JSONContent ) => void
className ?: string
placeholder ?: string
connectedUsers ?: any
userData : any
notesId : number
canEdit : boolean
authToken : string
}
Styling
The editor includes custom ProseMirror styles:
import './style/prosemirror.css'
The editor container can be styled with Tailwind classes:
< EditorContent
className = { cn ( 'border rounded-xl w-full pb-10' , className ) }
/>
Next Steps
Extensions Learn about TipTap extensions and configuration
Slash Commands Explore available slash commands
Selectors Understand the bubble menu selectors