demos: add React collaboration demo (#1991)
* Add react collab demo * Update editor styles for react collaboration demo
This commit is contained in:
140
demos/src/Examples/CollaborativeEditing/React/index.jsx
Normal file
140
demos/src/Examples/CollaborativeEditing/React/index.jsx
Normal file
@@ -0,0 +1,140 @@
|
||||
import React, {
|
||||
useState, useCallback, useEffect,
|
||||
} from 'react'
|
||||
import * as Y from 'yjs'
|
||||
import { WebsocketProvider } from 'y-websocket'
|
||||
import { IndexeddbPersistence } from 'y-indexeddb'
|
||||
import { useEditor, EditorContent } from '@tiptap/react'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import TaskList from '@tiptap/extension-task-list'
|
||||
import TaskItem from '@tiptap/extension-task-item'
|
||||
import Highlight from '@tiptap/extension-highlight'
|
||||
import CharacterCount from '@tiptap/extension-character-count'
|
||||
import Collaboration from '@tiptap/extension-collaboration'
|
||||
import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
|
||||
import MenuBar from './MenuBar'
|
||||
import './styles.scss'
|
||||
|
||||
const colors = ['#958DF1', '#F98181', '#FBBC88', '#FAF594', '#70CFF8', '#94FADB', '#B9F18D']
|
||||
const rooms = ['rooms.7', 'rooms.8', 'rooms.9']
|
||||
const names = [
|
||||
'Lea Thompson',
|
||||
'Cyndi Lauper',
|
||||
'Tom Cruise',
|
||||
'Madonna',
|
||||
'Jerry Hall',
|
||||
'Joan Collins',
|
||||
'Winona Ryder',
|
||||
'Christina Applegate',
|
||||
'Alyssa Milano',
|
||||
'Molly Ringwald',
|
||||
'Ally Sheedy',
|
||||
'Debbie Harry',
|
||||
'Olivia Newton-John',
|
||||
'Elton John',
|
||||
'Michael J. Fox',
|
||||
'Axl Rose',
|
||||
'Emilio Estevez',
|
||||
'Ralph Macchio',
|
||||
'Rob Lowe',
|
||||
'Jennifer Grey',
|
||||
'Mickey Rourke',
|
||||
'John Cusack',
|
||||
'Matthew Broderick',
|
||||
'Justine Bateman',
|
||||
'Lisa Bonet',
|
||||
]
|
||||
|
||||
const getRandomElement = list => list[Math.floor(Math.random() * list.length)]
|
||||
|
||||
const getRandomRoom = () => getRandomElement(rooms)
|
||||
const getRandomColor = () => getRandomElement(colors)
|
||||
const getRandomName = () => getRandomElement(names)
|
||||
|
||||
const room = getRandomRoom()
|
||||
|
||||
const ydoc = new Y.Doc()
|
||||
const websocketProvider = new WebsocketProvider('wss://websocket.tiptap.dev', room, ydoc)
|
||||
|
||||
const getInitialUser = () => {
|
||||
return JSON.parse(localStorage.getItem('currentUser')) || {
|
||||
name: getRandomName(),
|
||||
color: getRandomColor(),
|
||||
}
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const [status, setStatus] = useState('connecting')
|
||||
const [users, setUsers] = useState([])
|
||||
const [currentUser, setCurrentUser] = useState(getInitialUser)
|
||||
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit.configure({
|
||||
history: false,
|
||||
}),
|
||||
Highlight,
|
||||
TaskList,
|
||||
TaskItem,
|
||||
CharacterCount.configure({
|
||||
limit: 10000,
|
||||
}),
|
||||
Collaboration.configure({
|
||||
document: ydoc,
|
||||
}),
|
||||
CollaborationCursor.configure({
|
||||
provider: websocketProvider,
|
||||
onUpdate: updatedUsers => {
|
||||
setUsers(updatedUsers)
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
// Store shared data persistently in browser to make offline editing possible
|
||||
const indexeddbProvider = new IndexeddbPersistence(room, ydoc)
|
||||
|
||||
indexeddbProvider.on('synced', () => {
|
||||
console.log('Loaded content from database …')
|
||||
})
|
||||
|
||||
// Update status changes
|
||||
websocketProvider.on('status', event => {
|
||||
setStatus(event.status)
|
||||
})
|
||||
}, [])
|
||||
|
||||
// Save current user to localStorage and emit to editor
|
||||
useEffect(() => {
|
||||
if (editor && currentUser) {
|
||||
localStorage.setItem('currentUser', JSON.stringify(currentUser))
|
||||
editor.chain().focus().user(currentUser).run()
|
||||
}
|
||||
}, [editor, currentUser])
|
||||
|
||||
const setName = useCallback(() => {
|
||||
const name = (window.prompt('Name') || '').trim().substring(0, 32)
|
||||
|
||||
if (name) {
|
||||
return setCurrentUser({ ...currentUser, name })
|
||||
}
|
||||
}, [currentUser])
|
||||
|
||||
return (
|
||||
<div className="editor">
|
||||
{editor && <MenuBar editor={editor} />}
|
||||
<EditorContent className="editor__content" editor={editor} />
|
||||
<div className="editor__footer">
|
||||
<div className={`editor__status editor__status--${status}`}>
|
||||
{status === 'connected'
|
||||
? `${users.length} user${users.length === 1 ? '' : 's'} online in ${room}`
|
||||
: 'offline'}
|
||||
</div>
|
||||
<div className="editor__name">
|
||||
<button onClick={setName}>{currentUser.name}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user