lexical-loro 1.0.6__tar.gz

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.
@@ -0,0 +1,49 @@
1
+ .DS_Store
2
+ .wss-loro
3
+ .wss-yjs
4
+ build
5
+ node_modules
6
+ package-lock.json
7
+ tsconfig.tsbuildinfo
8
+
9
+ # Logs
10
+ logs
11
+ *.log
12
+ npm-debug.log*
13
+ yarn-debug.log*
14
+ yarn-error.log*
15
+ pnpm-debug.log*
16
+ lerna-debug.log*
17
+
18
+ *-lock.json
19
+
20
+ node_modules
21
+ lib
22
+ dist
23
+ dist-ssr
24
+ *.local
25
+
26
+ .coverage
27
+ .pytest_cache
28
+ htmlcov
29
+ dist
30
+
31
+ __pycache__
32
+ lexical_loro/__version__.py
33
+
34
+ # Editor directories and files
35
+ .vscode/*
36
+ !.vscode/extensions.json
37
+ .idea
38
+ .DS_Store
39
+ *.suo
40
+ *.ntvs*
41
+ *.njsproj
42
+ *.sln
43
+ *.sw?
44
+
45
+ .models
46
+
47
+ __tests__/*.json
48
+
49
+ documents
@@ -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.
@@ -0,0 +1,387 @@
1
+ Metadata-Version: 2.5
2
+ Name: lexical-loro
3
+ Version: 1.0.6
4
+ Dynamic: Author
5
+ Dynamic: Author-email
6
+ Dynamic: Summary
7
+ Dynamic: Keywords
8
+ Project-URL: Homepage, https://github.com/datalayer/lexical-loro
9
+ Project-URL: Documentation, https://github.com/datalayer/lexical-loro#readme
10
+ Project-URL: Repository, https://github.com/datalayer/lexical-loro.git
11
+ Project-URL: Issues, https://github.com/datalayer/lexical-loro/issues
12
+ License: MIT
13
+ License-File: LICENSE
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Classifier: Topic :: Text Processing :: Markup
26
+ Requires-Python: >=3.8
27
+ Requires-Dist: click>=8.0.0
28
+ Requires-Dist: loro>=1.6.0
29
+ Requires-Dist: mcp>=2
30
+ Requires-Dist: starlette>=0.27
31
+ Requires-Dist: uvicorn>=0.23.1
32
+ Requires-Dist: websockets>=12.0
33
+ Provides-Extra: dev
34
+ Requires-Dist: black>=23.0.0; extra == 'dev'
35
+ Requires-Dist: mypy>=1.0.0; extra == 'dev'
36
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
37
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
38
+ Requires-Dist: pytest>=7.0; extra == 'dev'
39
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
40
+ Provides-Extra: test
41
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'test'
42
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'test'
43
+ Requires-Dist: pytest>=7.0; extra == 'test'
44
+ Description-Content-Type: text/markdown
45
+
46
+ [![Datalayer](https://assets.datalayer.tech/datalayer-25.svg)](https://datalayer.io)
47
+
48
+ [![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)
49
+
50
+ # ✍️ 🦜 Collaborative Plugin for Lexical with Loro CRDT
51
+
52
+ A collaborative editing plugin for [Lexical](https://github.com/facebook/lexical) Rich Editor built with [Loro](https://github.com/loro-dev) CRDT, providing real-time collaborative editing capabilities with conflict-free synchronization.
53
+
54
+ ## Core Components
55
+
56
+ This package provides three main components for building collaborative text editors:
57
+
58
+ 1. **`LoroCollaborativePlugin.tsx`** - A Lexical plugin that integrates Loro CRDT for real-time collaborative editing
59
+ 2. **`LexicalModel` Python Library** - A standalone document model for Lexical content with CRDT capabilities
60
+ 3. **`lexical-loro` WebSocket Server** - A Python server using [loro-py](https://github.com/loro-dev/loro-py) for real-time collaboration
61
+
62
+ ## Quick Start
63
+
64
+ ### Using the Lexical Plugin
65
+
66
+ ```tsx
67
+ import { LoroCollaborativePlugin } from './src/LoroCollaborativePlugin';
68
+
69
+ function MyEditor() {
70
+ return (
71
+ <LexicalComposer initialConfig={editorConfig}>
72
+ <RichTextPlugin />
73
+ <LoroCollaborativePlugin
74
+ websocketUrl="ws://localhost:8081"
75
+ docId="my-document"
76
+ username="user1"
77
+ />
78
+ </LexicalComposer>
79
+ );
80
+ }
81
+ ```
82
+
83
+ ### Using the LexicalModel Library
84
+
85
+ ```python
86
+ from lexical_loro import LexicalModel
87
+
88
+ # Create a new document
89
+ model = LexicalModel.create_document("my-document")
90
+
91
+ # Add content
92
+ model.add_block({
93
+ "text": "My Document",
94
+ "format": 0,
95
+ "style": ""
96
+ }, "heading1")
97
+
98
+ model.add_block({
99
+ "text": "This is a paragraph.",
100
+ "format": 0,
101
+ "style": ""
102
+ }, "paragraph")
103
+
104
+ # Save to file
105
+ model.save_to_file("document.json")
106
+
107
+ # Load from file
108
+ loaded_model = LexicalModel.load_from_file("document.json")
109
+ ```
110
+
111
+ ### Using the Python Server
112
+
113
+ ```bash
114
+ # Install the Python package
115
+ pip install -e .
116
+
117
+ # Start the server
118
+ lexical-loro-server --port 8081
119
+ ```
120
+
121
+ ## Examples
122
+
123
+ For complete working examples, see the `src/examples/` directory which contains:
124
+ - Full React application with dual editor support
125
+ - Server selection interface
126
+ - Connection status indicators
127
+ - Rich text formatting examples
128
+
129
+ **DISCLAIMER** Collaborative Cursors still need fixes, see [this issue](https://github.com/datalayer/lexical-loro/issues/1).
130
+
131
+ <div align="center" style="text-align: center">
132
+ <img alt="" src="https://assets.datalayer.tech/lexical-loro.gif" />
133
+ </div>
134
+
135
+ ## Core Features
136
+
137
+ - 🔄 **Real-time Collaboration**: Multiple users can edit the same document simultaneously
138
+ - 🚀 **Conflict-free**: Uses Loro CRDT to automatically resolve conflicts
139
+ - 📝 **Lexical Integration**: Seamless integration with Lexical rich text editor
140
+ - 📚 **Standalone Library**: Use LexicalModel independently for document management
141
+ - 🌐 **WebSocket Server**: Python server for maintaining document state
142
+ - 📡 **Connection Management**: Robust WebSocket connection handling
143
+ - ✨ **Rich Text Support**: Preserves formatting during collaborative editing
144
+ - 💾 **Serialization**: JSON export/import and file persistence
145
+ - 🔧 **Extensible**: Plugin-based architecture for easy customization
146
+
147
+ ## Technology Stack
148
+
149
+ **Core Dependencies:**
150
+ - **Lexical**: v0.33.1 (Facebook's extensible text editor framework)
151
+ - **Loro CRDT**: v1.5.10 (Conflict-free replicated data types)
152
+ - **React**: 18/19 (for plugin hooks and components)
153
+ - **Python**: 3.8+ with loro-py and websockets
154
+
155
+ **Development Dependencies:**
156
+ - **TypeScript**: For type safety
157
+ - **Vite**: For building and development (examples only)
158
+ - **pytest**: Python testing
159
+ - **ESLint**: Code linting
160
+
161
+ ## Installation
162
+
163
+ ### Core Plugin
164
+
165
+ The Lexical plugin is a single TypeScript/React component that you can copy into your project:
166
+
167
+ ```bash
168
+ # Copy the plugin file
169
+ cp src/LoroCollaborativePlugin.tsx your-project/src/
170
+ ```
171
+
172
+ **Dependencies required:**
173
+ ```bash
174
+ npm install lexical @lexical/react @lexical/selection loro-crdt react react-dom
175
+ ```
176
+
177
+ ### Python Server
178
+
179
+ Install the Python WebSocket server:
180
+
181
+ ```bash
182
+ # Install from this repository
183
+ pip install -e .
184
+
185
+ # Or install specific dependencies
186
+ pip install websockets click loro
187
+ ```
188
+
189
+ ## Usage
190
+
191
+ ### 1. Lexical Plugin Integration
192
+
193
+ Add the plugin to your Lexical editor:
194
+
195
+ ```tsx
196
+ import { LexicalComposer } from '@lexical/react/LexicalComposer';
197
+ import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
198
+ import { ContentEditable } from '@lexical/react/LexicalContentEditable';
199
+ import { LoroCollaborativePlugin } from './LoroCollaborativePlugin';
200
+
201
+ const editorConfig = {
202
+ namespace: 'MyEditor',
203
+ theme: {},
204
+ onError: console.error,
205
+ };
206
+
207
+ function CollaborativeEditor() {
208
+ return (
209
+ <LexicalComposer initialConfig={editorConfig}>
210
+ <div className="editor-container">
211
+ <RichTextPlugin
212
+ contentEditable={<ContentEditable className="editor-input" />}
213
+ placeholder={<div className="editor-placeholder">Start typing...</div>}
214
+ ErrorBoundary={() => <div>Error occurred</div>}
215
+ />
216
+ <LoroCollaborativePlugin
217
+ websocketUrl="ws://localhost:8081"
218
+ docId="shared-document"
219
+ username="user123"
220
+ />
221
+ </div>
222
+ </LexicalComposer>
223
+ );
224
+ }
225
+ ```
226
+
227
+ ### 2. Standalone LexicalModel Library
228
+
229
+ Use the LexicalModel library independently for document management:
230
+
231
+ ```python
232
+ from lexical_loro import LexicalModel
233
+
234
+ # Create a new document
235
+ model = LexicalModel.create_document("my-document")
236
+
237
+ # Add different types of content
238
+ model.add_block({
239
+ "text": "My Document",
240
+ "format": 0,
241
+ "style": ""
242
+ }, "heading1")
243
+
244
+ model.add_block({
245
+ "text": "This is a paragraph with **bold** text.",
246
+ "format": 0,
247
+ "style": ""
248
+ }, "paragraph")
249
+
250
+ model.add_block({
251
+ "text": "",
252
+ "format": 0,
253
+ "style": ""
254
+ }, "list")
255
+
256
+ # Serialize to JSON
257
+ json_data = model.to_json()
258
+
259
+ # Save to file
260
+ model.save_to_file("document.json")
261
+
262
+ # Load from file
263
+ loaded_model = LexicalModel.load_from_file("document.json")
264
+
265
+ # Access blocks
266
+ for block in loaded_model.get_blocks():
267
+ print(f"{block['type']}: {block.get('text', '')}")
268
+ ```
269
+
270
+ For more examples, see:
271
+ - `examples/memory_only_example.py` - Basic document creation and manipulation
272
+ - `examples/file_sync_example.py` - File persistence and batch operations
273
+ - `examples/collaboration_example.py` - Simulating collaborative editing
274
+ - `docs/LEXICAL_MODEL_GUIDE.md` - Comprehensive documentation
275
+
276
+ ### 3. Python Server Setup
277
+
278
+ Start the WebSocket server:
279
+
280
+ ```bash
281
+ # Default port (8081)
282
+ lexical-loro-server
283
+
284
+ # Custom port
285
+ lexical-loro-server --port 8082
286
+
287
+ # With debug logging
288
+ lexical-loro-server --port 8081 --log-level DEBUG
289
+ ```
290
+
291
+ ### 4. Programmatic Server Usage
292
+
293
+ ```python
294
+ import asyncio
295
+ from lexical_loro import LoroWebSocketServer
296
+
297
+ async def main():
298
+ server = LoroWebSocketServer(port=8081)
299
+ await server.start()
300
+ print("Server running on ws://localhost:8081")
301
+
302
+ if __name__ == "__main__":
303
+ asyncio.run(main())
304
+ ```
305
+
306
+ ## Plugin API
307
+
308
+ For detailed API documentation, see [`docs/API.md`](docs/API.md).
309
+
310
+ ### Quick Reference
311
+
312
+ ```tsx
313
+ interface LoroCollaborativePluginProps {
314
+ websocketUrl: string; // WebSocket server URL
315
+ docId: string; // Unique document identifier
316
+ username: string; // User identifier
317
+ userColor?: string; // User cursor color (optional)
318
+ debug?: boolean; // Enable debug logging (optional)
319
+ }
320
+ ```
321
+
322
+ ## Initialization Best Practices
323
+
324
+ ⚠️ **Important**: Always wait for collaboration initialization before enabling other plugins.
325
+
326
+ See [`docs/INITIALIZATION_GUIDE.md`](docs/INITIALIZATION_GUIDE.md) for comprehensive guidance on:
327
+ - Proper plugin ordering
328
+ - Initialization callbacks
329
+ - Error handling
330
+ - Common anti-patterns to avoid
331
+
332
+ ## Examples
333
+
334
+ For complete working examples and demonstrations, see the `src/examples/` directory:
335
+
336
+ ```bash
337
+ # Run the example application
338
+ npm install
339
+ make example
340
+
341
+ # This starts both Node.js and Python servers plus a React demo app
342
+ # Open http://localhost:3000/split/index.html to see the 2 editors view
343
+ ```
344
+
345
+ The examples include:
346
+ - **Complete React App**: Full collaborative editor with UI
347
+ - **Server Selection**: Switch between Node.js and Python backends
348
+ - **Dual Editors**: Simple text area and rich Lexical editor
349
+ - **Real-time Demo**: Multi-user collaboration testing
350
+
351
+ See `src/examples/README.md` for detailed example documentation.
352
+
353
+ ## Architecture
354
+
355
+ ```
356
+ EDITOR 1 EDITOR 2
357
+
358
+ loro loro
359
+ - node(data: root(1)) - node(data: root(12))
360
+ - node(data: element(2)) - node(data: element(22))
361
+ - node(data: text(3)) - node(data: text(13))
362
+ - node(data: counter(4)) - node(data: counter(4))
363
+
364
+ <---- loro updates via websocket ------>
365
+ <---- loro node ids are the same ------>
366
+ <---- lexical node keys are different ------>
367
+
368
+ lexical lexical
369
+ - root(1) - root(12)
370
+ - element(2) - element(22)
371
+ - text(3) - text(13)
372
+ - counter(4) - counter(49)
373
+ ```
374
+
375
+ ## Examples
376
+
377
+ Loro examples
378
+
379
+ - http://localhost:3000/?isCollab=true
380
+
381
+ - http://localhost:3000/split/?isCollab=true
382
+
383
+ Y.js examples (for reference)
384
+
385
+ - http://localhost:3000/?isCollab=true&useYjs=true
386
+
387
+ - http://localhost:3000/split/?isCollab=true&useYjs=true