@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 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
+ [![Datalayer](https://assets.datalayer.tech/datalayer-25.svg)](https://datalayer.io)
2
+
3
+ [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](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;