tiptap/demos/src/Examples/CollaborativeEditing/React/index.jsx
2021-10-31 23:13:57 +01:00

137 lines
3.8 KiB
JavaScript

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.10', 'rooms.11', 'rooms.12']
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://connect.tiptap.dev', room, ydoc)
const getInitialUser = () => {
return JSON.parse(localStorage.getItem('currentUser')) || {
name: getRandomName(),
color: getRandomColor(),
}
}
export default () => {
const [status, setStatus] = useState('connecting')
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,
}),
],
})
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().updateUser(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'
? `${editor.storage.collaborationCursor.users.length} user${editor.storage.collaborationCursor.users.length === 1 ? '' : 's'} online in ${room}`
: 'offline'}
</div>
<div className="editor__name">
<button onClick={setName}>{currentUser.name}</button>
</div>
</div>
</div>
)
}