@datalayer/lexical-loro 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +551 -0
- package/lib/LoroCollaborativePlugin.d.ts +14 -0
- package/lib/LoroCollaborativePlugin.js +2421 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Copyright (c) 2021-2023 Datalayer, Inc.
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
[](https://datalayer.io)
|
|
2
|
+
|
|
3
|
+
[](https://github.com/sponsors/datalayer)
|
|
4
|
+
|
|
5
|
+
# Collaborative Plugin for Lexical based on Loro CRDT
|
|
6
|
+
|
|
7
|
+
A real-time collaborative editing application for [Lexical](https://github.com/facebook/lexical) built with [Loro CRDT](https://github.com/loro-dev), React, TypeScript, Vite with a Python WebSocket server using [loro-py](https://github.com/loro-dev/loro-py) to maintain the Lexical JSON model sever-side
|
|
8
|
+
|
|
9
|
+
Features both simple text editing and rich text editing with Lexical. Multiple users can edit the same documents simultaneously with conflict-free collaborative editing powered by Conflict-free Replicated Data Types (CRDTs).
|
|
10
|
+
|
|
11
|
+
**DISCLAIMER** Collaborative Cursors still need fixes, see [this issue](https://github.com/datalayer/lexical-loro/issues/1).
|
|
12
|
+
|
|
13
|
+
**NEW** Now supports both Node.js and Python WebSocket servers!
|
|
14
|
+
|
|
15
|
+
<div align="center" style="text-align: center">
|
|
16
|
+
<img alt="" src="https://assets.datalayer.tech/lexical-loro.gif" />
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- 🔄 **Real-time Collaboration**: Multiple users can edit the same document simultaneously
|
|
22
|
+
- 🚀 **Conflict-free**: Uses Loro CRDT to automatically resolve conflicts
|
|
23
|
+
- 📝 **Dual Editor Support**: Choose between simple text area or rich text Lexical editor
|
|
24
|
+
- 🌐 **Multi-server Support**: Choose between Node.js and Python WebSocket servers
|
|
25
|
+
- ⚡ **Fast Development**: Built with Vite for lightning-fast development
|
|
26
|
+
- 🎨 **Responsive Design**: Works on desktop and mobile devices
|
|
27
|
+
- 📡 **Connection Status**: Visual indicators for connection state
|
|
28
|
+
- ✨ **Rich Text Features**: Bold, italic, underline with real-time formatting sync
|
|
29
|
+
- 🔧 **Server Selection**: Switch between Node.js and Python backends
|
|
30
|
+
|
|
31
|
+
## Technology Stack
|
|
32
|
+
|
|
33
|
+
- **Frontend**: React 19 + TypeScript + Vite
|
|
34
|
+
- **CRDT Library**: Loro CRDT
|
|
35
|
+
- **Rich Text Editor**: Lexical (Facebook's extensible text editor)
|
|
36
|
+
- **Backend Options**:
|
|
37
|
+
- Node.js + TypeScript + ws library
|
|
38
|
+
- Python + loro-py + websockets library
|
|
39
|
+
- **Real-time Communication**: WebSockets (ws)
|
|
40
|
+
- **Styling**: CSS3 with responsive design
|
|
41
|
+
- **Development Tools**: ESLint, tsx, concurrently
|
|
42
|
+
|
|
43
|
+
## Getting Started
|
|
44
|
+
|
|
45
|
+
### Prerequisites
|
|
46
|
+
|
|
47
|
+
- Node.js (v16 or higher)
|
|
48
|
+
- npm or yarn
|
|
49
|
+
- Python 3.8+ (for Python server option)
|
|
50
|
+
- pip3 (for Python dependencies)
|
|
51
|
+
|
|
52
|
+
### Installation
|
|
53
|
+
|
|
54
|
+
1. Install Node.js dependencies:
|
|
55
|
+
```bash
|
|
56
|
+
npm install
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
2. Install Python dependencies (optional - for Python server):
|
|
60
|
+
```bash
|
|
61
|
+
pip3 install -r requirements.txt
|
|
62
|
+
# or run the setup script
|
|
63
|
+
./setup-python.sh
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Running the Application
|
|
67
|
+
|
|
68
|
+
#### Option 1: All Servers (Recommended)
|
|
69
|
+
```bash
|
|
70
|
+
npm run dev:all
|
|
71
|
+
```
|
|
72
|
+
This starts **both** WebSocket servers (Node.js on port 8080 and Python on port 8081) plus the React development server (port 5173). You can then switch between servers using the UI.
|
|
73
|
+
|
|
74
|
+
#### Option 2: Python Server Only
|
|
75
|
+
```bash
|
|
76
|
+
npm run dev:all:py
|
|
77
|
+
```
|
|
78
|
+
This starts only the Python WebSocket server (port 8081) and React development server.
|
|
79
|
+
|
|
80
|
+
#### Option 3: Node.js Server Only
|
|
81
|
+
```bash
|
|
82
|
+
npm run dev:all:js
|
|
83
|
+
```
|
|
84
|
+
This starts only the Node.js WebSocket server (port 8080) and React development server.
|
|
85
|
+
|
|
86
|
+
#### Option 4: Run servers separately
|
|
87
|
+
|
|
88
|
+
**All servers manually:**
|
|
89
|
+
```bash
|
|
90
|
+
# Terminal 1: Start Node.js WebSocket server
|
|
91
|
+
npm run server
|
|
92
|
+
|
|
93
|
+
# Terminal 2: Start Python WebSocket server
|
|
94
|
+
npm run server:py
|
|
95
|
+
|
|
96
|
+
# Terminal 3: Start React development server
|
|
97
|
+
npm run dev
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Node.js Server only:**
|
|
101
|
+
```bash
|
|
102
|
+
# Terminal 1: Start Node.js WebSocket server
|
|
103
|
+
npm run server
|
|
104
|
+
|
|
105
|
+
# Terminal 2: Start React development server
|
|
106
|
+
npm run dev
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Python Server only:**
|
|
110
|
+
```bash
|
|
111
|
+
# Terminal 1: Start Python WebSocket server
|
|
112
|
+
npm run server:py
|
|
113
|
+
# or directly: python3 server.py
|
|
114
|
+
|
|
115
|
+
# Terminal 2: Start React development server
|
|
116
|
+
npm run dev
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
2. In another terminal, start the React development server:
|
|
120
|
+
```bash
|
|
121
|
+
npm run dev
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Usage
|
|
125
|
+
|
|
126
|
+
1. Open your browser and navigate to the development server URL (typically `http://localhost:5173`)
|
|
127
|
+
2. **Select Server Type**: Use the server selection radio buttons to choose:
|
|
128
|
+
- **Node.js Server**: `ws://localhost:8080` (TypeScript implementation)
|
|
129
|
+
- **Python Server**: `ws://localhost:8081` (Python + loro-py implementation)
|
|
130
|
+
|
|
131
|
+
💡 **Tip**: When using `npm run dev:all`, both servers are running simultaneously, so you can switch between them in real-time!
|
|
132
|
+
|
|
133
|
+
3. **Choose Editor Type**: Click the tabs to select:
|
|
134
|
+
- **Simple Text Editor**: A basic textarea for plain text collaboration
|
|
135
|
+
- **Rich Text Editor (Lexical)**: A full-featured rich text editor with Bold/Italic/Underline formatting
|
|
136
|
+
4. Start typing in either editor
|
|
137
|
+
5. Open another browser window/tab or share the URL with others
|
|
138
|
+
6. All users will see real-time updates as they type in the same editor type
|
|
139
|
+
7. Each editor maintains its own document state (they are separate collaborative spaces)
|
|
140
|
+
|
|
141
|
+
**Note**: You must disconnect from the current server before switching to a different server type.
|
|
142
|
+
|
|
143
|
+
### Testing Collaboration
|
|
144
|
+
|
|
145
|
+
To test the real-time collaboration:
|
|
146
|
+
|
|
147
|
+
1. Open multiple browser tabs/windows to the development server URL
|
|
148
|
+
2. **Select the same server** in all tabs (Node.js or Python)
|
|
149
|
+
3. **Test Simple Text Editor**:
|
|
150
|
+
- Keep all tabs on the "Simple Text Editor" tab
|
|
151
|
+
- Start typing in one window - you'll see the changes appear in other windows instantly
|
|
152
|
+
4. **Test Lexical Rich Text Editor**:
|
|
153
|
+
- Switch all tabs to the "Rich Text Editor (Lexical)" tab
|
|
154
|
+
- Try formatting text with the toolbar buttons (Bold, Italic, Underline)
|
|
155
|
+
- Changes and formatting will sync in real-time across all tabs
|
|
156
|
+
5. **Test Cross-Server Compatibility**:
|
|
157
|
+
- Verify that documents are properly synchronized between Node.js and Python servers
|
|
158
|
+
- Each server maintains its own document state
|
|
159
|
+
6. **Test Independent Documents**:
|
|
160
|
+
- Have some tabs on "Simple Text Editor" and others on "Lexical Editor"
|
|
161
|
+
- Notice that each editor type maintains its own separate document
|
|
162
|
+
5. **New collaborators will automatically receive the current document content** when they join
|
|
163
|
+
|
|
164
|
+
**Note**: The application now properly synchronizes initial content:
|
|
165
|
+
- When a new collaborator joins, they automatically receive the current document state for both editors
|
|
166
|
+
- If no snapshot is available on the server, existing clients will provide their current state
|
|
167
|
+
- The first client to join with content will automatically share their document state
|
|
168
|
+
- Each editor type (simple text vs Lexical) maintains separate collaborative documents
|
|
169
|
+
|
|
170
|
+
## Project Structure
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
src/
|
|
174
|
+
├── App.tsx # Main application component with tabbed interface
|
|
175
|
+
├── App.css # Application styles
|
|
176
|
+
├── CollaborativeEditor.tsx # Simple text editor component with Loro CRDT integration
|
|
177
|
+
├── CollaborativeEditor.css # Simple editor styles
|
|
178
|
+
├── LexicalCollaborativeEditor.tsx # Lexical rich text editor component
|
|
179
|
+
├── LexicalCollaborativeEditor.css # Lexical editor styles
|
|
180
|
+
├── LoroCollaborativePlugin.tsx # Lexical plugin for Loro CRDT integration
|
|
181
|
+
├── main.tsx # React application entry point
|
|
182
|
+
└── vite-env.d.ts # Vite type definitions
|
|
183
|
+
|
|
184
|
+
server.ts # WebSocket server for real-time communication
|
|
185
|
+
package.json # Dependencies and scripts
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## How It Works
|
|
189
|
+
|
|
190
|
+
### Loro CRDT Integration
|
|
191
|
+
|
|
192
|
+
The application uses Loro CRDT to manage collaborative editing across two different editor types:
|
|
193
|
+
|
|
194
|
+
1. **Document Creation**: Each editor type creates its own Loro document with a unique identifier:
|
|
195
|
+
- Simple Text Editor: `shared-text`
|
|
196
|
+
- Lexical Editor: `lexical-shared-doc`
|
|
197
|
+
2. **Local Changes**: When a user types, changes are applied to the corresponding local Loro document
|
|
198
|
+
3. **Change Detection**: The application detects insertions, deletions, and replacements
|
|
199
|
+
4. **Synchronization**: Changes are serialized and sent to other clients via WebSocket with document ID
|
|
200
|
+
5. **Conflict Resolution**: Loro CRDT automatically merges changes without conflicts
|
|
201
|
+
|
|
202
|
+
The Complete Flow Diagram
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
Remote User Types
|
|
206
|
+
↓
|
|
207
|
+
WebSocket Message
|
|
208
|
+
↓
|
|
209
|
+
loro-update received
|
|
210
|
+
↓
|
|
211
|
+
loroDocRef.current.import(update)
|
|
212
|
+
↓
|
|
213
|
+
doc.subscribe() callback fires
|
|
214
|
+
↓
|
|
215
|
+
updateLexicalFromLoro(editor, newText)
|
|
216
|
+
↓
|
|
217
|
+
editor.update() with new content
|
|
218
|
+
↓
|
|
219
|
+
Lexical State Updated
|
|
220
|
+
↓
|
|
221
|
+
UI Re-renders with New Content
|
|
222
|
+
|
|
223
|
+
Protection Against Infinite Loops
|
|
224
|
+
|
|
225
|
+
The system uses several mechanisms to prevent loops:
|
|
226
|
+
|
|
227
|
+
isLocalChange.current flag - Prevents local changes from triggering remote updates
|
|
228
|
+
{ tag: 'collaboration' } on editor.update() - Allows the update listener to ignore these changes
|
|
229
|
+
JSON comparison in updateLexicalFromLoro to avoid redundant updates
|
|
230
|
+
|
|
231
|
+
When a Loro update is received, the Lexical state is updated through:
|
|
232
|
+
|
|
233
|
+
WebSocket receives loro-update message
|
|
234
|
+
|
|
235
|
+
loroDocRef.current.import(update) applies the change to Loro
|
|
236
|
+
doc.subscribe() callback automatically fires
|
|
237
|
+
updateLexicalFromLoro() converts Loro text to Lexical state
|
|
238
|
+
editor.setEditorState() or DOM manipulation updates the editor
|
|
239
|
+
|
|
240
|
+
The bridge is the doc.subscribe() callback on line 1901 - this is what makes Lexical automatically reflect any Loro document changes!
|
|
241
|
+
|
|
242
|
+
### Lexical Integration
|
|
243
|
+
|
|
244
|
+
The Lexical editor integration includes:
|
|
245
|
+
|
|
246
|
+
1. **LoroCollaborativePlugin**: A custom Lexical plugin that bridges Lexical and Loro CRDT
|
|
247
|
+
2. **Bidirectional Sync**: Changes flow from Lexical → Loro → WebSocket and vice versa
|
|
248
|
+
3. **Rich Text Preservation**: The plugin maintains rich text formatting during collaborative editing
|
|
249
|
+
4. **Independent State**: Lexical editor maintains separate document state from simple text editor
|
|
250
|
+
|
|
251
|
+
### WebSocket Communication
|
|
252
|
+
|
|
253
|
+
The WebSocket server:
|
|
254
|
+
- Maintains connections to all clients
|
|
255
|
+
- Broadcasts Loro document updates to all connected clients with document ID filtering
|
|
256
|
+
- Handles client connections and disconnections
|
|
257
|
+
- Provides connection status feedback
|
|
258
|
+
- Stores separate snapshots for each document type
|
|
259
|
+
|
|
260
|
+
### Real-time Updates
|
|
261
|
+
|
|
262
|
+
1. User types in the text area
|
|
263
|
+
2. Change is applied to local Loro document
|
|
264
|
+
3. Document update is serialized and sent via WebSocket
|
|
265
|
+
4. Other clients receive the update and apply it to their documents
|
|
266
|
+
5. UI is updated to reflect the changes
|
|
267
|
+
|
|
268
|
+
### Initial Content Synchronization
|
|
269
|
+
|
|
270
|
+
When a new collaborator joins:
|
|
271
|
+
|
|
272
|
+
1. **Connection**: New client connects to WebSocket server
|
|
273
|
+
2. **Welcome**: Server sends welcome message to new client
|
|
274
|
+
3. **Snapshot Request**: New client requests current document state
|
|
275
|
+
4. **Snapshot Delivery**: Server sends stored snapshot or requests one from existing clients
|
|
276
|
+
5. **Content Sync**: New client applies snapshot and sees current document content
|
|
277
|
+
6. **Ready to Collaborate**: New client can now participate in real-time editing
|
|
278
|
+
|
|
279
|
+
The server maintains the latest document snapshot to ensure new collaborators always see existing content.
|
|
280
|
+
|
|
281
|
+
## Configuration
|
|
282
|
+
|
|
283
|
+
### WebSocket Server URL
|
|
284
|
+
|
|
285
|
+
You can configure the WebSocket server URL in the UI or by modifying the default in `CollaborativeEditor.tsx`:
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
const [websocketUrl, setWebsocketUrl] = useState('ws://localhost:8080')
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Server Port
|
|
292
|
+
|
|
293
|
+
To change the server port, modify `server.ts`:
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
const server = new LoroWebSocketServer(8080); // Change port here
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Development Scripts
|
|
300
|
+
|
|
301
|
+
- `npm run dev` - Start React development server
|
|
302
|
+
- `npm run server` - Start WebSocket server
|
|
303
|
+
- `npm run dev:all` - Start both server and client
|
|
304
|
+
- `npm run build` - Build for production
|
|
305
|
+
- `npm run lint` - Run ESLint
|
|
306
|
+
- `npm run preview` - Preview production build
|
|
307
|
+
|
|
308
|
+
## Production Deployment
|
|
309
|
+
|
|
310
|
+
1. Build the application:
|
|
311
|
+
```bash
|
|
312
|
+
npm run build
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
2. Deploy the `dist` folder to your web server
|
|
316
|
+
|
|
317
|
+
3. Deploy the WebSocket server to your backend infrastructure
|
|
318
|
+
|
|
319
|
+
4. Update the WebSocket URL in the application to point to your production server
|
|
320
|
+
|
|
321
|
+
## Contributing
|
|
322
|
+
|
|
323
|
+
1. Fork the repository
|
|
324
|
+
2. Create a feature branch
|
|
325
|
+
3. Make your changes
|
|
326
|
+
4. Add tests if applicable
|
|
327
|
+
5. Submit a pull request
|
|
328
|
+
|
|
329
|
+
## License
|
|
330
|
+
|
|
331
|
+
This project is open source and available under the [MIT License](LICENSE).
|
|
332
|
+
|
|
333
|
+
## Acknowledgments
|
|
334
|
+
|
|
335
|
+
- [Loro CRDT](https://loro.dev/) - The CRDT library powering collaborative editing
|
|
336
|
+
- [Vite](https://vitejs.dev/) - Fast development build tool
|
|
337
|
+
- [React](https://reactjs.org/) - UI library
|
|
338
|
+
- [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)
|
|
339
|
+
|
|
340
|
+
# Lexical Loro Python Package
|
|
341
|
+
|
|
342
|
+
A Python package for Lexical + Loro CRDT integration, providing a WebSocket server for real-time collaborative text editing.
|
|
343
|
+
|
|
344
|
+
## Features
|
|
345
|
+
|
|
346
|
+
- **Real-time collaboration**: WebSocket server for live document collaboration
|
|
347
|
+
- **Loro CRDT integration**: Uses Loro CRDT for conflict-free replicated data types
|
|
348
|
+
- **Lexical compatibility**: Designed to work with Lexical rich text editor
|
|
349
|
+
- **Ephemeral data support**: Handles cursor positions and selections
|
|
350
|
+
- **Multiple document support**: Manages multiple collaborative documents
|
|
351
|
+
|
|
352
|
+
## Installation
|
|
353
|
+
|
|
354
|
+
### From PyPI (when published)
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
pip install lexical-loro
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Local Development
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
# Install in development mode
|
|
364
|
+
pip install -e "python_src/[dev]"
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## Usage
|
|
368
|
+
|
|
369
|
+
### Command Line
|
|
370
|
+
|
|
371
|
+
Start the server using the command line interface:
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
# Start server on default port (8081)
|
|
375
|
+
lexical-loro-server
|
|
376
|
+
|
|
377
|
+
# Start server on custom port
|
|
378
|
+
lexical-loro-server --port 8082
|
|
379
|
+
|
|
380
|
+
# Start with debug logging
|
|
381
|
+
lexical-loro-server --log-level DEBUG
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Programmatic Usage
|
|
385
|
+
|
|
386
|
+
```python
|
|
387
|
+
import asyncio
|
|
388
|
+
from lexical_loro import LoroWebSocketServer
|
|
389
|
+
|
|
390
|
+
async def main():
|
|
391
|
+
server = LoroWebSocketServer(port=8081)
|
|
392
|
+
await server.start()
|
|
393
|
+
|
|
394
|
+
if __name__ == "__main__":
|
|
395
|
+
asyncio.run(main())
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Integration with Node.js/TypeScript Projects
|
|
399
|
+
|
|
400
|
+
Update your `package.json` scripts:
|
|
401
|
+
|
|
402
|
+
```json
|
|
403
|
+
{
|
|
404
|
+
"scripts": {
|
|
405
|
+
"server:py": "lexical-loro-server",
|
|
406
|
+
"dev:py": "concurrently \"lexical-loro-server\" \"npm run dev\""
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## API Reference
|
|
412
|
+
|
|
413
|
+
### LoroWebSocketServer
|
|
414
|
+
|
|
415
|
+
Main server class for handling WebSocket connections and Loro document management.
|
|
416
|
+
|
|
417
|
+
#### Constructor
|
|
418
|
+
|
|
419
|
+
```python
|
|
420
|
+
server = LoroWebSocketServer(port=8081)
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
#### Methods
|
|
424
|
+
|
|
425
|
+
- `start()`: Start the WebSocket server
|
|
426
|
+
- `shutdown()`: Gracefully shutdown the server
|
|
427
|
+
- `handle_client(websocket)`: Handle new client connections
|
|
428
|
+
- `handle_message(client_id, message)`: Process messages from clients
|
|
429
|
+
|
|
430
|
+
### Client
|
|
431
|
+
|
|
432
|
+
Represents a connected client with metadata.
|
|
433
|
+
|
|
434
|
+
```python
|
|
435
|
+
class Client:
|
|
436
|
+
def __init__(self, websocket, client_id):
|
|
437
|
+
self.websocket = websocket
|
|
438
|
+
self.id = client_id
|
|
439
|
+
self.color = self._generate_color()
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
## Development
|
|
443
|
+
|
|
444
|
+
### Setup Development Environment
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
# Install development dependencies
|
|
448
|
+
pip install -e "python_src/[dev]"
|
|
449
|
+
|
|
450
|
+
# Run tests
|
|
451
|
+
pytest
|
|
452
|
+
|
|
453
|
+
# Run tests with coverage
|
|
454
|
+
pytest --cov=lexical_loro --cov-report=html
|
|
455
|
+
|
|
456
|
+
# Format code
|
|
457
|
+
black python_src/
|
|
458
|
+
|
|
459
|
+
# Lint code
|
|
460
|
+
ruff python_src/
|
|
461
|
+
|
|
462
|
+
# Type checking
|
|
463
|
+
mypy python_src/
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Testing
|
|
467
|
+
|
|
468
|
+
The package includes comprehensive tests for:
|
|
469
|
+
|
|
470
|
+
- WebSocket connection handling
|
|
471
|
+
- Loro document operations
|
|
472
|
+
- Message processing
|
|
473
|
+
- Client management
|
|
474
|
+
- Error handling
|
|
475
|
+
|
|
476
|
+
Run tests:
|
|
477
|
+
|
|
478
|
+
```bash
|
|
479
|
+
pytest tests/ -v
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Building
|
|
483
|
+
|
|
484
|
+
Build the package:
|
|
485
|
+
|
|
486
|
+
```bash
|
|
487
|
+
pip install build
|
|
488
|
+
python -m build
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
## Protocol
|
|
492
|
+
|
|
493
|
+
The server communicates with clients using a JSON-based WebSocket protocol:
|
|
494
|
+
|
|
495
|
+
### Message Types
|
|
496
|
+
|
|
497
|
+
- `loro-update`: Apply Loro CRDT updates
|
|
498
|
+
- `snapshot`: Full document snapshots
|
|
499
|
+
- `request-snapshot`: Request current document state
|
|
500
|
+
- `ephemeral-update`: Cursor and selection updates
|
|
501
|
+
- `awareness-update`: User presence information
|
|
502
|
+
|
|
503
|
+
### Example Messages
|
|
504
|
+
|
|
505
|
+
```json
|
|
506
|
+
{
|
|
507
|
+
"type": "loro-update",
|
|
508
|
+
"docId": "lexical-shared-doc",
|
|
509
|
+
"updateHex": "deadbeef..."
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
## Configuration
|
|
514
|
+
|
|
515
|
+
### Environment Variables
|
|
516
|
+
|
|
517
|
+
- `LEXICAL_LORO_PORT`: Default server port (default: 8081)
|
|
518
|
+
- `LEXICAL_LORO_HOST`: Host to bind to (default: localhost)
|
|
519
|
+
- `LEXICAL_LORO_LOG_LEVEL`: Logging level (default: INFO)
|
|
520
|
+
|
|
521
|
+
### Supported Documents
|
|
522
|
+
|
|
523
|
+
The server pre-initializes several document types:
|
|
524
|
+
|
|
525
|
+
- `shared-text`: Basic text document
|
|
526
|
+
- `lexical-shared-doc-v0`: Minimal plugin document
|
|
527
|
+
- `lexical-shared-doc-v1`: Full-featured plugin document
|
|
528
|
+
- `lexical-shared-doc-v2`: Clean JSON plugin document
|
|
529
|
+
- `lexical-shared-doc-v3`: Text-only plugin document
|
|
530
|
+
- `lexical-shared-doc-v4`: Smart hybrid plugin document
|
|
531
|
+
|
|
532
|
+
## License
|
|
533
|
+
|
|
534
|
+
MIT License - see LICENSE file for details.
|
|
535
|
+
|
|
536
|
+
## Contributing
|
|
537
|
+
|
|
538
|
+
1. Fork the repository
|
|
539
|
+
2. Create a feature branch
|
|
540
|
+
3. Make your changes
|
|
541
|
+
4. Add tests
|
|
542
|
+
5. Run the test suite
|
|
543
|
+
6. Submit a pull request
|
|
544
|
+
|
|
545
|
+
## Support
|
|
546
|
+
|
|
547
|
+
For issues and questions:
|
|
548
|
+
|
|
549
|
+
- GitHub Issues: https://github.com/datalayer/lexical-loro/issues
|
|
550
|
+
- Documentation: https://github.com/datalayer/lexical-loro#readme
|
|
551
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface LoroCollaborativePluginProps {
|
|
2
|
+
websocketUrl: string;
|
|
3
|
+
docId: string;
|
|
4
|
+
onConnectionChange?: (connected: boolean) => void;
|
|
5
|
+
onPeerIdChange?: (peerId: string) => void;
|
|
6
|
+
onDisconnectReady?: (disconnectFn: () => void) => void;
|
|
7
|
+
onAwarenessChange?: (awareness: Array<{
|
|
8
|
+
peerId: string;
|
|
9
|
+
userName: string;
|
|
10
|
+
isCurrentUser?: boolean;
|
|
11
|
+
}>) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function LoroCollaborativePlugin({ websocketUrl, docId, onConnectionChange, onDisconnectReady, onPeerIdChange, onAwarenessChange }: LoroCollaborativePluginProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export default LoroCollaborativePlugin;
|