@brainfish-ai/components 0.18.4 → 0.18.7

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.
Files changed (39) hide show
  1. package/dist/alert-dialog.d.ts +2 -2
  2. package/dist/article-suggestions-banner.d.ts +16 -0
  3. package/dist/button-group.d.ts +3 -2
  4. package/dist/button.d.ts +1 -1
  5. package/dist/combobox.d.ts +24 -3
  6. package/dist/confirm-dialog.d.ts +1 -1
  7. package/dist/div-button.d.ts +1 -1
  8. package/dist/esm/chunks/{ChatSearch.BJtS7ZMs.js → ChatSearch.DSB-T76c.js} +2 -2
  9. package/dist/esm/chunks/{ChatSearch.BJtS7ZMs.js.map → ChatSearch.DSB-T76c.js.map} +1 -1
  10. package/dist/esm/chunks/{combobox.CkN-wAHB.js → combobox.DNYCWyub.js} +40 -21
  11. package/dist/esm/chunks/combobox.DNYCWyub.js.map +1 -0
  12. package/dist/esm/components/article-suggestions-banner.js +55 -0
  13. package/dist/esm/components/article-suggestions-banner.js.map +1 -0
  14. package/dist/esm/components/chat-search.js +1 -1
  15. package/dist/esm/components/combobox.js +1 -1
  16. package/dist/esm/components/convos.js +1 -1
  17. package/dist/esm/components/convos.js.map +1 -1
  18. package/dist/esm/components/header-nav.js +55 -0
  19. package/dist/esm/components/header-nav.js.map +1 -0
  20. package/dist/esm/components/ui/button-group.js +23 -8
  21. package/dist/esm/components/ui/button-group.js.map +1 -1
  22. package/dist/esm/components/ui/button.js +2 -8
  23. package/dist/esm/components/ui/button.js.map +1 -1
  24. package/dist/esm/components/ui/command.js +1 -1
  25. package/dist/esm/components/ui/command.js.map +1 -1
  26. package/dist/esm/components/ui/div-button.js +1 -0
  27. package/dist/esm/components/ui/div-button.js.map +1 -1
  28. package/dist/esm/global.css +1 -1
  29. package/dist/esm/index.js +179 -5
  30. package/dist/esm/index.js.map +1 -1
  31. package/dist/header-nav.d.ts +28 -0
  32. package/dist/index.d.ts +134 -9
  33. package/dist/stats.html +1 -1
  34. package/dist/two-level-combobox.d.ts +2 -0
  35. package/package.json +6 -1
  36. package/components.json +0 -16
  37. package/dist/esm/chunks/combobox.CkN-wAHB.js.map +0 -1
  38. package/markdown-editor.plan.md +0 -2101
  39. package/markdown-editor.spec.md +0 -915
@@ -1,2101 +0,0 @@
1
- # Markdown Editor/Viewer Component Implementation Checklist
2
-
3
- ## Overview
4
-
5
- This checklist tracks the implementation progress for the Markdown Editor/Viewer component based on `markdown-editor.spec.md`. The component will replace FormattedMessage.tsx while maintaining compatibility with the existing multimedia/embed system.
6
-
7
- ### Implementation Strategy
8
- - **6-Phase Approach**: Foundation → Multimedia → Suggestions → Streaming → Collaboration → Integration
9
- - **Monorepo Strategy**:
10
- - All editor-related development, including the main component (`SimpleEditor` evolving into `MarkdownEditorViewer`), UI primitives, hooks, and extensions, will be centralized within the **`@brainfish-ai/tiptap`** package.
11
- - **Incremental Development**: Build on existing FormattedMessage.tsx and embed system patterns
12
- - **TipTap Foundation**: Core editing engine with custom extensions
13
- - **Backward Compatibility**: Seamless integration with existing codebase
14
-
15
- ### Dependencies Setup
16
- - [x] Install TipTap dependencies (@tiptap/react, @tiptap/starter-kit, @tiptap/extension-table, etc.)
17
- - [x] Install collaboration dependencies (yjs, y-webrtc, @tiptap/extension-collaboration)
18
- - [x] Install markdown processing dependencies (remark, unified, rehype)
19
- - [x] Verify existing dependencies (react-markdown, remark-gfm, @radix-ui/*, framer-motion)
20
-
21
- ## Phase 1: Foundation and Basic Editor (Week 1-2)
22
- **Objective**: Create basic TipTap editor with markdown conversion and mode switching
23
-
24
- ### Core Component Structure
25
- - [ ] Create MarkdownEditorViewer component to lazy load ContentViewer and SimpleEditor component depending on mode
26
- - [ ] Adapt existing file structure in `packages/tiptap/` for new components (Viewer, Suggestions, etc.)
27
- - [ ] Implement lazy loading for Editor and Viewer modes within `SimpleEditor.tsx`
28
- - [ ] Add error boundaries
29
-
30
- ### Markdown Translation Layer
31
- - [x] Create `markdownTranslator.ts` utility
32
- - [x] Implement markdown to TipTap JSON conversion using remark-gfm
33
- - [x] Implement TipTap JSON to markdown conversion
34
- - [ ] Handle existing FormattedMessage content patterns (video prefixes, etc.)
35
- - [x] Add proper error handling and fallbacks
36
- - [ ] Preserve multimedia embeds and custom HTML elements
37
-
38
- ### Basic TipTap Editor
39
- - [x] Initialize TipTap editor with StarterKit extensions
40
- - [~] Apply consistent Tailwind classes matching FormattedMessage styling
41
- - [x] Add focus management and event handling
42
- - [~] Handle content updates and markdown conversion
43
- - [x] Implement proper cleanup and memory management
44
-
45
- ### Read-Only Viewer
46
- - [ ] Create `ViewerContent.tsx` component
47
- - [ ] Use react-markdown with same configuration as FormattedMessage
48
- - [ ] Apply identical styling classes from FormattedMessage
49
- - [ ] Support all existing markdown features (tables, lists, headings)
50
- - [ ] Implement proper link handling and UTM parameter addition
51
- - [ ] Handle streaming content display
52
- - [ ] Maintain consistent component rendering patterns
53
-
54
- ### Mode Switching
55
- - [ ] Implement seamless edit/readonly mode switching
56
- - [ ] Preserve content during mode changes
57
- - [ ] Handle focus management during switches
58
- - [ ] Test content integrity across mode changes
59
-
60
- ### **Phase 1 Success Criteria**
61
- - [ ] Editor initializes in under 500ms
62
- - [ ] Mode switching completes in under 100ms
63
- - [ ] Content preservation across all operations
64
- - [ ] Styling consistency with FormattedMessage
65
- - [ ] Unit tests passing for core functionality
66
-
67
- ---
68
-
69
- ## Phase 2: Multimedia System Integration (Week 3)
70
- **Objective**: Integrate existing embed system and add multimedia handling capabilities
71
-
72
- ### Multimedia Extension
73
- - [ ] Create `MultimediaExtension.ts` for TipTap in `@brainfish-ai/tiptap` package
74
- - [ ] Integrate with existing EmbedComponent system from embeds/index.tsx
75
- - [ ] Support all existing embed types (YouTube, Vimeo, Loom, etc.)
76
- - [ ] Implement drag-and-drop file uploads using react-dropzone patterns
77
- - [ ] Handle paste event handling for URLs and files
78
- - [ ] Maintain consistency with FormattedMessage multimedia rendering
79
- - [ ] Add proper error handling for unsupported media types
80
-
81
- ### Media Upload Component
82
- - [ ] Create `MediaUpload.tsx` component
83
- - [ ] Use react-dropzone for file upload UI
84
- - [ ] Implement progress indicators and validation
85
- - [ ] Handle multiple file types (images, videos, documents)
86
- - [ ] Add file size and type validation with error messages
87
- - [ ] Support drag-and-drop and click-to-upload interactions
88
- - [ ] Integrate with existing file upload utilities and styles
89
-
90
- ### Media Renderer
91
- - [ ] Create `MediaRenderer.tsx` component
92
- - [ ] Reuse existing embed components (Youtube, Vimeo, ZoomableImage, etc.)
93
- - [ ] Handle video content type prefixes and attachment redirects
94
- - [ ] Implement proper error states and loading indicators
95
- - [ ] Support all multimedia types defined in specification
96
- - [ ] Provide consistent styling and responsive behavior
97
-
98
- ### Media Toolbar
99
- - [ ] Create `MediaToolbar.tsx` component
100
- - [ ] Add buttons for different media insertion types
101
- - [ ] Use existing UI components (Button, Tooltip, DropdownMenu)
102
- - [ ] Implement keyboard shortcuts for media insertion
103
- - [ ] Show appropriate icons using @phosphor-icons/react
104
- - [ ] Integrate with TipTap editor commands for media insertion
105
- - [ ] Provide contextual media editing options (resize, align, etc.)
106
-
107
- ### **Phase 2 Success Criteria**
108
- - [ ] 100% compatibility with existing embed system
109
- - [ ] All FormattedMessage media types supported
110
- - [ ] Drag-and-drop functionality working
111
- - [ ] File upload with progress indicators
112
- - [ ] Error handling for unsupported media
113
- - [ ] Integration tests passing
114
-
115
- ---
116
-
117
- ## Phase 3: Suggestion System (Week 4)
118
- **Objective**: Implement visual suggestion system with accept/reject functionality
119
-
120
- ### Suggestion Extension
121
- - [ ] Create `SuggestionExtension.ts` for TipTap in `@brainfish-ai/tiptap` package
122
- - [ ] Add suggestion marks to document nodes with visual styling
123
- - [ ] Implement three suggestion types: insert, delete, replace
124
- - [ ] Provide keyboard navigation through suggestions (Tab/Shift-Tab)
125
- - [ ] Handle suggestion acceptance and rejection with proper cleanup
126
- - [ ] Track suggestion metadata (author, timestamp, reason)
127
- - [ ] Implement conflict resolution for overlapping suggestions
128
-
129
- ### Suggestion Visual Styles
130
- - [ ] Create suggestion CSS styles with consistent color schemes
131
- - [ ] Provide clear visual distinction between insert/delete/replace
132
- - [ ] Implement hover and focus states for suggestions
133
- - [ ] Support dark mode with appropriate color adjustments
134
- - [ ] Maintain accessibility standards for color contrast
135
- - [ ] Add smooth transitions for suggestion state changes
136
-
137
- ### Suggestion List Component
138
- - [ ] Create `SuggestionList.tsx` component
139
- - [ ] Display all suggestions in sidebar panel
140
- - [ ] Group suggestions by type with filtering options
141
- - [ ] Show suggestion metadata (author, timestamp, reason)
142
- - [ ] Implement batch accept/reject operations
143
- - [ ] Add search and sorting functionality
144
- - [ ] Use existing UI components (ScrollArea, Button, Badge)
145
- - [ ] Implement keyboard navigation and focus management
146
-
147
- ### Suggestion Navigation
148
- - [ ] Create `useSuggestionNavigation` hook
149
- - [ ] Manage active suggestion focus state
150
- - [ ] Implement keyboard navigation between suggestions
151
- - [ ] Provide methods for programmatic navigation
152
- - [ ] Handle edge cases (no suggestions, deleted suggestions)
153
- - [ ] Integrate with TipTap editor focus management
154
- - [ ] Implement smooth scrolling to active suggestions
155
-
156
- ### **Phase 3 Success Criteria**
157
- - [ ] All suggestion types visually distinct
158
- - [ ] Keyboard navigation working smoothly
159
- - [ ] Accept/reject operations functioning
160
- - [ ] Conflict resolution working
161
- - [ ] Accessibility compliance verified
162
- - [ ] Performance with large suggestion lists
163
-
164
- ---
165
-
166
- ## Phase 4: Streaming Content Support (Week 5)
167
- **Objective**: Implement real-time streaming content functionality
168
-
169
- ### Streaming Extension
170
- - [ ] Create `StreamingExtension.ts` for TipTap in `@brainfish-ai/tiptap` package
171
- - [ ] Handle real-time content insertion at document end
172
- - [ ] Manage streaming state and prevent editing during streams
173
- - [ ] Implement content buffering and batch updates for performance
174
- - [ ] Provide visual indicators for streaming content
175
- - [ ] Handle stream interruption and recovery gracefully
176
- - [ ] Maintain document structure during streaming updates
177
-
178
- ### Streaming Hook
179
- - [ ] Create `useStreaming` hook
180
- - [ ] Manage streaming state and content buffering
181
- - [ ] Implement debounced content updates for performance
182
- - [ ] Handle streaming errors and connection issues
183
- - [ ] Provide streaming progress and status information
184
- - [ ] Integrate with TipTap editor streaming extension
185
- - [ ] Implement proper cleanup and memory management
186
-
187
- ### Streaming Indicator Component
188
- - [ ] Create `StreamingIndicator.tsx` component
189
- - [ ] Show visual feedback during content streaming
190
- - [ ] Display streaming progress and connection status
191
- - [ ] Implement pulsing animation for active streaming
192
- - [ ] Provide error states and retry functionality
193
- - [ ] Use existing UI components (Progress, Badge, Button)
194
- - [ ] Maintain consistent styling with rest of editor
195
-
196
- ### Streaming Buffer Management
197
- - [ ] Create `StreamingBuffer` utility class
198
- - [ ] Manage content buffering and batch processing
199
- - [ ] Implement intelligent content chunking for performance
200
- - [ ] Handle malformed or incomplete streaming content
201
- - [ ] Provide content validation and sanitization
202
- - [ ] Implement backpressure handling for fast streams
203
- - [ ] Maintain content integrity during streaming
204
-
205
- ### **Phase 4 Success Criteria**
206
- - [ ] Streaming content updates at 1000+ chars/second
207
- - [ ] Buffer overflow protection working
208
- - [ ] Error handling and recovery functioning
209
- - [ ] Performance with large streams maintained
210
- - [ ] Integration with editor state working
211
- - [ ] Memory usage under control
212
-
213
- ---
214
-
215
- ## Phase 5: Collaboration Features (Week 6)
216
- **Objective**: Implement real-time collaboration with cursors and presence
217
-
218
- ### Collaboration Extension
219
- - [ ] Create `CollaborationExtension.ts` in `@brainfish-ai/tiptap` package
220
- - [ ] Integrate with Yjs for operational transformation
221
- - [ ] Implement real-time cursor tracking and presence
222
- - [ ] Handle conflict resolution for simultaneous edits
223
- - [ ] Provide user identification and avatar display
224
- - [ ] Implement connection management and reconnection
225
- - [ ] Maintain document consistency across all clients
226
-
227
- ### Collaborator Components
228
- - [ ] Create `CollaboratorList.tsx` component
229
- - [ ] Show all active collaborators with avatars and names
230
- - [ ] Display real-time presence indicators (typing, idle, away)
231
- - [ ] Implement user color coding for cursor identification
232
- - [ ] Show connection status for each collaborator
233
- - [ ] Use existing UI components (Avatar, Badge, Tooltip)
234
- - [ ] Handle user interactions like viewing collaborator location
235
-
236
- ### Cursor Management System
237
- - [ ] Create `CollaboratorCursor.tsx` component
238
- - [ ] Render real-time cursor positions for all collaborators
239
- - [ ] Show user names and colors for cursor identification
240
- - [ ] Implement smooth cursor animations and transitions
241
- - [ ] Handle cursor visibility based on viewport
242
- - [ ] Provide selection range indicators for collaborators
243
- - [ ] Integrate with TipTap collaboration cursor system
244
-
245
- ### Presence Management
246
- - [ ] Create `useCollaborationPresence` hook
247
- - [ ] Manage user presence state (active, idle, away)
248
- - [ ] Implement typing indicators with debounced updates
249
- - [ ] Handle connection status and reconnection logic
250
- - [ ] Provide methods for sending presence updates
251
- - [ ] Manage awareness state for all collaborators
252
- - [ ] Implement proper cleanup and memory management
253
-
254
- ### **Phase 5 Success Criteria**
255
- - [ ] Real-time collaboration working
256
- - [ ] Cursor synchronization accurate
257
- - [ ] Conflict resolution functioning
258
- - [ ] Connection handling robust
259
- - [ ] Presence indicators working
260
- - [ ] Performance with multiple users maintained
261
-
262
- ---
263
-
264
- ## Phase 6: Integration and Polish (Week 7-8)
265
- **Objective**: Complete integration, testing, and production readiness
266
-
267
- ### FormattedMessage Migration
268
- - [ ] Create migration utilities for seamless replacement
269
- - [ ] Maintain backward compatibility with existing props
270
- - [ ] Implement feature flags for gradual rollout
271
- - [ ] Provide fallback mechanisms for unsupported features
272
- - [ ] Handle existing content migration and formatting preservation
273
- - [ ] Create migration wrapper component for compatibility
274
-
275
- ### Comprehensive Error Handling
276
- - [ ] Implement global error boundaries for graceful degradation
277
- - [ ] Provide detailed error reporting and logging
278
- - [ ] Handle network failures and API errors gracefully
279
- - [ ] Implement retry mechanisms for recoverable errors
280
- - [ ] Provide user-friendly error messages and recovery actions
281
- - [ ] Integrate with existing error tracking systems
282
-
283
- ### Performance Optimization
284
- - [ ] Implement React.memo and useMemo for expensive operations
285
- - [ ] Add virtual scrolling for large documents
286
- - [ ] Optimize bundle size with dynamic imports
287
- - [ ] Implement debouncing for frequent operations
288
- - [ ] Use web workers for heavy computations
289
- - [ ] Implement efficient re-rendering strategies
290
-
291
- ### Accessibility Implementation
292
- - [ ] Provide proper ARIA labels and roles for all interactive elements
293
- - [ ] Implement keyboard navigation for all functionality
294
- - [ ] Support screen readers with descriptive text and live regions
295
- - [ ] Maintain focus management during mode switches and updates
296
- - [ ] Provide high contrast mode support
297
- - [ ] Verify color contrast ratios throughout interface
298
-
299
- ### Comprehensive Testing Suite
300
- - [ ] Create unit tests for all major components
301
- - [ ] Implement integration tests for workflows
302
- - [ ] Add visual regression testing for UI components
303
- - [ ] Test accessibility features with automated tools
304
- - [ ] Include performance benchmarks and memory leak detection
305
- - [ ] Test cross-browser compatibility
306
- - [ ] Implement end-to-end testing scenarios
307
-
308
- ### Documentation and Migration Guide
309
- - [ ] Create API documentation for all components
310
- - [ ] Write migration guide from FormattedMessage
311
- - [ ] Document all prop interfaces and callbacks
312
- - [ ] Provide usage examples and best practices
313
- - [ ] Create troubleshooting guide
314
- - [ ] Document performance considerations
315
-
316
- ### **Phase 6 Success Criteria**
317
- - [ ] All functionality working end-to-end
318
- - [ ] Performance benchmarks met (initialization < 500ms, typing lag < 50ms)
319
- - [ ] Accessibility compliance verified (WCAG 2.1 AA)
320
- - [ ] Cross-browser testing complete
321
- - [ ] Migration path validated
322
- - [ ] Production deployment ready
323
-
324
- ---
325
-
326
- ## Final Acceptance Criteria
327
-
328
- ### Performance Requirements
329
- - [ ] Editor initialization: < 500ms
330
- - [ ] Mode switching: < 100ms
331
- - [ ] Large document loading (1MB): < 2 seconds
332
- - [ ] Typing lag: < 50ms
333
- - [ ] Memory usage baseline: < 20MB
334
- - [ ] Memory leak tolerance: < 1MB/hour
335
-
336
- ### Functionality Requirements
337
- - [ ] Complete FormattedMessage.tsx feature parity
338
- - [ ] 100% compatibility with existing embed system
339
- - [ ] Consistent styling with existing components
340
- - [ ] Support for all existing markdown features
341
- - [ ] Real-time collaboration working
342
- - [ ] Streaming content support functional
343
-
344
- ### Quality Assurance
345
- - [ ] Unit test coverage > 90%
346
- - [ ] All integration tests passing
347
- - [ ] End-to-end tests covering complete user scenarios
348
- - [ ] WCAG 2.1 AA accessibility compliance
349
- - [ ] Cross-browser support (Chrome 90+, Firefox 88+, Safari 14+, Edge 90+)
350
- - [ ] Mobile responsive design working
351
- - [ ] Touch interface optimized
352
-
353
- ### Risk Mitigation Completed
354
- - [ ] TipTap integration complexity managed with incremental approach
355
- - [ ] Performance monitoring and optimization implemented
356
- - [ ] Browser compatibility tested across platforms
357
- - [ ] Backward compatibility maintained strictly
358
- - [ ] Embed system integration verified
359
- - [ ] State management patterns proven and tested
360
-
361
- ### Phase 1: Foundation and Basic Editor (Week 1-2)
362
-
363
- **Objective**: Create basic TipTap editor with markdown conversion and mode switching.
364
-
365
- **Implementation Prompts:**
366
-
367
- **3.1.1 Create Root Component Structure**
368
- ```typescript
369
- // Prompt: Create the main MarkdownEditorViewer component that:
370
- // - Accepts all props defined in the specification interface
371
- // - Implements basic mode switching between 'edit' and 'readonly'
372
- // - Uses React.lazy for dynamic imports of Editor and Viewer components
373
- // - Implements error boundaries for graceful error handling
374
- // - Follows the existing FormattedMessage patterns for props and structure
375
-
376
- interface MarkdownEditorViewerProps {
377
- content: string;
378
- mode: 'edit' | 'readonly';
379
- onContentChange?: (content: string) => void;
380
- onModeChange?: (mode: 'edit' | 'readonly') => void;
381
- // ... other props from specification
382
- }
383
-
384
- const MarkdownEditorViewer: React.FC<MarkdownEditorViewerProps> = (props) => {
385
- // Implementation that handles mode switching and component loading
386
- };
387
- ```
388
-
389
- **3.1.2 Build Markdown Translation Layer**
390
- ```typescript
391
- // Prompt: Create markdownTranslator utility that:
392
- // - Uses remark-gfm and rehype-raw to parse markdown (like FormattedMessage)
393
- // - Converts markdown to TipTap JSON format for editing
394
- // - Converts TipTap JSON back to markdown for export
395
- // - Handles existing FormattedMessage patterns like video content prefixes
396
- // - Preserves multimedia embeds and custom HTML elements
397
- // - Implements proper error handling and fallbacks
398
-
399
- const markdownTranslator = {
400
- markdownToTipTap: (markdown: string): TipTapDocument => {
401
- // Convert markdown to TipTap JSON using remark/rehype pipeline
402
- },
403
-
404
- tipTapToMarkdown: (doc: TipTapDocument): string => {
405
- // Convert TipTap JSON back to markdown
406
- },
407
-
408
- parseExistingContent: (content: string, redirectRules?: RedirectRule[]): TipTapDocument => {
409
- // Handle FormattedMessage-specific content patterns
410
- }
411
- };
412
- ```
413
-
414
- **3.1.3 Implement Basic TipTap Editor**
415
- ```typescript
416
- // Prompt: Create EditorContent component that:
417
- // - Initializes TipTap editor with StarterKit extensions
418
- // - Applies consistent Tailwind classes matching FormattedMessage styling
419
- // - Implements proper focus management and event handling
420
- // - Provides basic formatting toolbar with common actions
421
- // - Handles content updates and markdown conversion
422
- // - Implements proper cleanup and memory management
423
-
424
- const EditorContent: React.FC<EditorContentProps> = ({ content, onChange, className }) => {
425
- const editor = useEditor({
426
- extensions: [/* TipTap extensions */],
427
- content: markdownTranslator.markdownToTipTap(content),
428
- onUpdate: ({ editor }) => {
429
- const markdown = markdownTranslator.tipTapToMarkdown(editor.getJSON());
430
- onChange(markdown);
431
- },
432
- });
433
-
434
- // Implementation with proper styling and event handling
435
- };
436
- ```
437
-
438
- **3.1.4 Create Read-Only Viewer**
439
- ```typescript
440
- // Prompt: Create ViewerContent component that:
441
- // - Renders markdown content using react-markdown (like FormattedMessage)
442
- // - Applies identical styling classes from FormattedMessage
443
- // - Supports all existing markdown features (tables, lists, headings)
444
- // - Implements proper link handling and UTM parameter addition
445
- // - Maintains consistent component rendering patterns
446
- // - Handles streaming content display
447
-
448
- const ViewerContent: React.FC<ViewerContentProps> = ({ content, isStreaming, redirectRules }) => {
449
- // Use react-markdown with same configuration as FormattedMessage
450
- return (
451
- <MemoizedReactMarkdown
452
- className="dark:prose-invert prose flex-1"
453
- remarkPlugins={[remarkGfm]}
454
- rehypePlugins={[rehypeRaw]}
455
- isStreaming={isStreaming}
456
- components={{
457
- // Match FormattedMessage component overrides exactly
458
- }}
459
- >
460
- {content}
461
- </MemoizedReactMarkdown>
462
- );
463
- };
464
- ```
465
-
466
- **Phase 1 Testing Strategy:**
467
- - Unit tests for markdown translation utilities
468
- - Integration tests for editor initialization
469
- - Mode switching functionality tests
470
- - Content preservation tests
471
- - Styling consistency verification
472
-
473
- ### Phase 2: Multimedia System Integration (Week 3)
474
-
475
- **Objective**: Integrate existing embed system and add multimedia handling capabilities.
476
-
477
- **Implementation Prompts:**
478
-
479
- **3.2.1 Create Multimedia Extension**
480
- ```typescript
481
- // Prompt: Create MultimediaExtension for TipTap that:
482
- // - Integrates with existing EmbedComponent system from embeds/index.tsx
483
- // - Supports all existing embed types (YouTube, Vimeo, Loom, etc.)
484
- // - Handles drag-and-drop file uploads using react-dropzone patterns
485
- // - Implements paste event handling for URLs and files
486
- // - Maintains consistency with FormattedMessage multimedia rendering
487
- // - Provides proper error handling for unsupported media types
488
-
489
- const MultimediaExtension = Extension.create({
490
- name: 'multimedia',
491
-
492
- addCommands() {
493
- return {
494
- insertMedia: (attrs: MediaAttributes) => ({ tr, state }) => {
495
- // Insert media node using existing EmbedComponent patterns
496
- },
497
-
498
- handlePaste: (view: EditorView, event: ClipboardEvent) => {
499
- // Handle pasted URLs and files
500
- },
501
-
502
- handleDrop: (view: EditorView, event: DragEvent) => {
503
- // Handle dropped files
504
- }
505
- };
506
- },
507
-
508
- addProseMirrorPlugins() {
509
- return [
510
- new Plugin({
511
- key: new PluginKey('multimedia'),
512
- props: {
513
- handleDOMEvents: {
514
- paste: handlePasteEvent,
515
- drop: handleDropEvent,
516
- },
517
- },
518
- }),
519
- ];
520
- },
521
- });
522
- ```
523
-
524
- **3.2.2 Build Media Upload Component**
525
- ```typescript
526
- // Prompt: Create MediaUpload component that:
527
- // - Uses react-dropzone for file upload UI (following existing file-upload patterns)
528
- // - Implements progress indicators and validation
529
- // - Handles multiple file types (images, videos, documents)
530
- // - Provides file size and type validation with clear error messages
531
- // - Integrates with existing file upload utilities and styles
532
- // - Supports drag-and-drop and click-to-upload interactions
533
-
534
- const MediaUpload: React.FC<MediaUploadProps> = ({ onUpload, allowedTypes, maxSize }) => {
535
- const { getRootProps, getInputProps, isDragActive } = useDropzone({
536
- accept: allowedTypes,
537
- maxSize,
538
- onDrop: handleFileDrop,
539
- });
540
-
541
- // Implementation with progress tracking and error handling
542
- };
543
- ```
544
-
545
- **3.2.3 Implement Media Renderer**
546
- ```typescript
547
- // Prompt: Create MediaRenderer component that:
548
- // - Reuses existing embed components (Youtube, Vimeo, ZoomableImage, etc.)
549
- // - Maintains consistent rendering patterns from FormattedMessage
550
- // - Handles video content type prefixes and attachment redirects
551
- // - Implements proper error states and loading indicators
552
- // - Supports all multimedia types defined in the specification
553
- // - Provides consistent styling and responsive behavior
554
-
555
- const MediaRenderer: React.FC<MediaRendererProps> = ({ url, type, isAttachmentVideo, alt }) => {
556
- // Use existing EmbedComponent logic
557
- const embedComponent = EmbedComponent({
558
- attrs: { href: url },
559
- isAttachmentVideo,
560
- });
561
-
562
- if (React.isValidElement(embedComponent)) {
563
- return embedComponent;
564
- }
565
-
566
- // Handle ZoomableImage for regular images
567
- if (type === 'image') {
568
- return <ZoomableImage src={url} alt={alt} />;
569
- }
570
-
571
- // Fallback for unsupported types
572
- return <div>Unsupported media type: {type}</div>;
573
- };
574
- ```
575
-
576
- **3.2.4 Create Media Toolbar**
577
- ```typescript
578
- // Prompt: Create MediaToolbar component that:
579
- // - Provides buttons for different media insertion types
580
- // - Uses existing UI components (Button, Tooltip, DropdownMenu from ui/)
581
- // - Implements keyboard shortcuts for media insertion
582
- // - Shows appropriate icons using @phosphor-icons/react
583
- // - Integrates with TipTap editor commands for media insertion
584
- // - Provides contextual media editing options (resize, align, etc.)
585
-
586
- const MediaToolbar: React.FC<MediaToolbarProps> = ({ editor, onMediaInsert }) => {
587
- const insertButtons = [
588
- {
589
- type: 'image',
590
- icon: <Image size={16} />,
591
- label: 'Insert Image',
592
- shortcut: 'Mod-Shift-I',
593
- },
594
- {
595
- type: 'video',
596
- icon: <VideoCamera size={16} />,
597
- label: 'Insert Video',
598
- shortcut: 'Mod-Shift-V',
599
- },
600
- // ... other media types
601
- ];
602
-
603
- // Implementation with proper button states and handlers
604
- };
605
- ```
606
-
607
- **Phase 2 Testing Strategy:**
608
- - Integration tests with existing embed components
609
- - File upload functionality testing
610
- - Media insertion and rendering tests
611
- - Drag-and-drop behavior verification
612
- - Error handling for unsupported media types
613
-
614
- ### Phase 3: Suggestion System (Week 4)
615
-
616
- **Objective**: Implement visual suggestion system with accept/reject functionality.
617
-
618
- **Implementation Prompts:**
619
-
620
- **3.3.1 Create Suggestion Extension**
621
- ```typescript
622
- // Prompt: Create SuggestionExtension for TipTap that:
623
- // - Adds suggestion marks to document nodes with visual styling
624
- // - Implements three suggestion types: insert, delete, replace
625
- // - Provides keyboard navigation through suggestions (Tab/Shift-Tab)
626
- // - Handles suggestion acceptance and rejection with proper cleanup
627
- // - Tracks suggestion metadata (author, timestamp, reason)
628
- // - Implements proper conflict resolution for overlapping suggestions
629
-
630
- const SuggestionExtension = Extension.create({
631
- name: 'suggestions',
632
-
633
- addGlobalAttributes() {
634
- return [
635
- {
636
- types: ['paragraph', 'heading', 'text'],
637
- attributes: {
638
- suggestionId: { default: null },
639
- suggestionType: { default: null },
640
- suggestionAuthor: { default: null },
641
- suggestionTimestamp: { default: null },
642
- },
643
- },
644
- ];
645
- },
646
-
647
- addCommands() {
648
- return {
649
- addSuggestion: (suggestion: EditorSuggestion) => ({ tr, state }) => {
650
- // Add suggestion marks to the document
651
- },
652
-
653
- acceptSuggestion: (suggestionId: string) => ({ tr, state }) => {
654
- // Apply suggestion changes and remove marks
655
- },
656
-
657
- rejectSuggestion: (suggestionId: string) => ({ tr, state }) => {
658
- // Remove suggestion marks without applying changes
659
- },
660
-
661
- navigateToNextSuggestion: () => ({ tr, state }) => {
662
- // Move focus to next suggestion
663
- },
664
-
665
- acceptAllSuggestions: () => ({ tr, state }) => {
666
- // Accept all pending suggestions
667
- }
668
- };
669
- },
670
-
671
- addKeyboardShortcuts() {
672
- return {
673
- 'Tab': () => this.editor.commands.navigateToNextSuggestion(),
674
- 'Shift-Tab': () => this.editor.commands.navigateToPreviousSuggestion(),
675
- 'Enter': () => this.editor.commands.acceptActiveSuggestion(),
676
- 'Escape': () => this.editor.commands.rejectActiveSuggestion(),
677
- };
678
- },
679
- });
680
- ```
681
-
682
- **3.3.2 Implement Suggestion Visual Styles**
683
- ```css
684
- /* Prompt: Create suggestion CSS styles that:
685
- * - Use consistent color schemes for different suggestion types
686
- * - Provide clear visual distinction between insert/delete/replace
687
- * - Implement hover and focus states for suggestions
688
- * - Support dark mode with appropriate color adjustments
689
- * - Maintain accessibility standards for color contrast
690
- * - Provide smooth transitions for suggestion state changes
691
- */
692
-
693
- .suggestion-insert {
694
- text-decoration: underline;
695
- background-color: rgba(34, 197, 94, 0.1);
696
- color: rgba(34, 197, 94, 0.8);
697
- border-radius: 2px;
698
- padding: 1px 2px;
699
- transition: all 0.2s ease;
700
- }
701
-
702
- .suggestion-delete {
703
- text-decoration: line-through;
704
- background-color: rgba(239, 68, 68, 0.1);
705
- color: rgba(239, 68, 68, 0.7);
706
- border-radius: 2px;
707
- padding: 1px 2px;
708
- transition: all 0.2s ease;
709
- }
710
-
711
- .suggestion-replace {
712
- position: relative;
713
- }
714
-
715
- .suggestion-replace .old-text {
716
- text-decoration: line-through;
717
- background-color: rgba(239, 68, 68, 0.1);
718
- color: rgba(239, 68, 68, 0.7);
719
- }
720
-
721
- .suggestion-replace .new-text {
722
- text-decoration: underline;
723
- background-color: rgba(34, 197, 94, 0.1);
724
- color: rgba(34, 197, 94, 0.8);
725
- }
726
-
727
- .suggestion-active {
728
- outline: 2px solid #3b82f6;
729
- outline-offset: 1px;
730
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
731
- }
732
-
733
- /* Dark mode adjustments */
734
- .dark .suggestion-insert {
735
- background-color: rgba(34, 197, 94, 0.2);
736
- color: rgba(34, 197, 94, 0.9);
737
- }
738
-
739
- .dark .suggestion-delete {
740
- background-color: rgba(239, 68, 68, 0.2);
741
- color: rgba(239, 68, 68, 0.8);
742
- }
743
- ```
744
-
745
- **3.3.3 Build Suggestion List Component**
746
- ```typescript
747
- // Prompt: Create SuggestionList component that:
748
- // - Displays all suggestions in a sidebar panel
749
- // - Groups suggestions by type and provides filtering options
750
- // - Shows suggestion metadata (author, timestamp, reason)
751
- // - Implements batch accept/reject operations
752
- // - Provides search and sorting functionality
753
- // - Uses existing UI components (ScrollArea, Button, Badge)
754
- // - Implements proper keyboard navigation and focus management
755
-
756
- const SuggestionList: React.FC<SuggestionListProps> = ({ suggestions, onAccept, onReject }) => {
757
- const [filter, setFilter] = useState<'all' | 'insert' | 'delete' | 'replace'>('all');
758
- const [sortBy, setSortBy] = useState<'timestamp' | 'author' | 'type'>('timestamp');
759
-
760
- const filteredSuggestions = useMemo(() => {
761
- return suggestions
762
- .filter(s => filter === 'all' || s.type === filter)
763
- .sort((a, b) => sortBySuggestion(a, b, sortBy));
764
- }, [suggestions, filter, sortBy]);
765
-
766
- return (
767
- <ScrollArea className="h-full">
768
- <div className="space-y-2 p-4">
769
- {/* Filter and sort controls */}
770
- <div className="flex gap-2 mb-4">
771
- <Badge variant="outline" onClick={() => setFilter('all')}>
772
- All ({suggestions.length})
773
- </Badge>
774
- {/* Other filter badges */}
775
- </div>
776
-
777
- {/* Suggestion items */}
778
- {filteredSuggestions.map(suggestion => (
779
- <SuggestionItem
780
- key={suggestion.id}
781
- suggestion={suggestion}
782
- onAccept={() => onAccept(suggestion.id)}
783
- onReject={() => onReject(suggestion.id)}
784
- />
785
- ))}
786
- </div>
787
- </ScrollArea>
788
- );
789
- };
790
- ```
791
-
792
- **3.3.4 Implement Suggestion Navigation Hooks**
793
- ```typescript
794
- // Prompt: Create useSuggestionNavigation hook that:
795
- // - Manages active suggestion focus state
796
- // - Implements keyboard navigation between suggestions
797
- // - Provides methods for programmatic suggestion navigation
798
- // - Handles edge cases (no suggestions, deleted suggestions)
799
- // - Integrates with TipTap editor focus management
800
- // - Implements smooth scrolling to active suggestions
801
-
802
- const useSuggestionNavigation = (editor: Editor, suggestions: EditorSuggestion[]) => {
803
- const [activeSuggestionId, setActiveSuggestionId] = useState<string | null>(null);
804
-
805
- const navigateToNext = useCallback(() => {
806
- const currentIndex = suggestions.findIndex(s => s.id === activeSuggestionId);
807
- const nextIndex = currentIndex < suggestions.length - 1 ? currentIndex + 1 : 0;
808
- const nextSuggestion = suggestions[nextIndex];
809
-
810
- if (nextSuggestion) {
811
- setActiveSuggestionId(nextSuggestion.id);
812
- editor.commands.setTextSelection(nextSuggestion.position.from);
813
- scrollToSuggestion(nextSuggestion.id);
814
- }
815
- }, [editor, suggestions, activeSuggestionId]);
816
-
817
- const navigateToPrevious = useCallback(() => {
818
- const currentIndex = suggestions.findIndex(s => s.id === activeSuggestionId);
819
- const prevIndex = currentIndex > 0 ? currentIndex - 1 : suggestions.length - 1;
820
- const prevSuggestion = suggestions[prevIndex];
821
-
822
- if (prevSuggestion) {
823
- setActiveSuggestionId(prevSuggestion.id);
824
- editor.commands.setTextSelection(prevSuggestion.position.from);
825
- scrollToSuggestion(prevSuggestion.id);
826
- }
827
- }, [editor, suggestions, activeSuggestionId]);
828
-
829
- return {
830
- activeSuggestionId,
831
- setActiveSuggestionId,
832
- navigateToNext,
833
- navigateToPrevious,
834
- hasNext: /* logic */,
835
- hasPrevious: /* logic */,
836
- };
837
- };
838
- ```
839
-
840
- **Phase 3 Testing Strategy:**
841
- - Suggestion visual rendering tests
842
- - Keyboard navigation functionality
843
- - Accept/reject operation tests
844
- - Suggestion conflict resolution
845
- - Accessibility compliance testing
846
-
847
- ### Phase 4: Streaming Content Support (Week 5)
848
-
849
- **Objective**: Implement real-time streaming content functionality.
850
-
851
- **Implementation Prompts:**
852
-
853
- **4.4.1 Create Streaming Extension**
854
- ```typescript
855
- // Prompt: Create StreamingExtension for TipTap that:
856
- // - Handles real-time content insertion at document end
857
- // - Manages streaming state and prevents editing during streams
858
- // - Implements content buffering and batch updates for performance
859
- // - Provides visual indicators for streaming content
860
- // - Handles stream interruption and recovery gracefully
861
- // - Maintains document structure during streaming updates
862
-
863
- const StreamingExtension = Extension.create({
864
- name: 'streaming',
865
-
866
- addStorage() {
867
- return {
868
- isStreaming: false,
869
- streamBuffer: '',
870
- streamPosition: 0,
871
- };
872
- },
873
-
874
- addCommands() {
875
- return {
876
- startStream: () => ({ tr, state }) => {
877
- this.storage.isStreaming = true;
878
- this.storage.streamPosition = state.doc.content.size;
879
- return true;
880
- },
881
-
882
- appendStreamContent: (content: string) => ({ tr, state, dispatch }) => {
883
- if (!this.storage.isStreaming) return false;
884
-
885
- this.storage.streamBuffer += content;
886
-
887
- // Batch updates for performance
888
- if (dispatch) {
889
- tr.insertText(content, this.storage.streamPosition);
890
- this.storage.streamPosition += content.length;
891
- }
892
-
893
- return true;
894
- },
895
-
896
- finalizeStream: () => ({ tr, state }) => {
897
- this.storage.isStreaming = false;
898
- this.storage.streamBuffer = '';
899
-
900
- // Process final content formatting
901
- this.editor.commands.focus('end');
902
- return true;
903
- },
904
- };
905
- },
906
-
907
- onUpdate() {
908
- // Update streaming position if document changes
909
- if (this.storage.isStreaming) {
910
- this.storage.streamPosition = this.editor.state.doc.content.size;
911
- }
912
- },
913
- });
914
- ```
915
-
916
- **4.4.2 Implement Streaming Hook**
917
- ```typescript
918
- // Prompt: Create useStreaming hook that:
919
- // - Manages streaming state and content buffering
920
- // - Implements debounced content updates for performance
921
- // - Handles streaming errors and connection issues
922
- // - Provides streaming progress and status information
923
- // - Integrates with TipTap editor streaming extension
924
- // - Implements proper cleanup and memory management
925
-
926
- const useStreaming = (editor: Editor | null, isStreaming?: boolean) => {
927
- const [streamState, setStreamState] = useState<StreamingState>({
928
- isActive: false,
929
- buffer: '',
930
- progress: 0,
931
- error: null,
932
- });
933
-
934
- const debouncedUpdate = useCallback(
935
- debounce((content: string) => {
936
- if (editor && streamState.isActive) {
937
- editor.commands.appendStreamContent(content);
938
- }
939
- }, 50),
940
- [editor, streamState.isActive]
941
- );
942
-
943
- const startStream = useCallback(() => {
944
- if (!editor) return;
945
-
946
- editor.commands.startStream();
947
- setStreamState(prev => ({ ...prev, isActive: true, error: null }));
948
- }, [editor]);
949
-
950
- const appendContent = useCallback((chunk: string) => {
951
- setStreamState(prev => ({
952
- ...prev,
953
- buffer: prev.buffer + chunk,
954
- progress: prev.progress + chunk.length
955
- }));
956
-
957
- debouncedUpdate(chunk);
958
- }, [debouncedUpdate]);
959
-
960
- const finalizeStream = useCallback(() => {
961
- if (!editor) return;
962
-
963
- editor.commands.finalizeStream();
964
- setStreamState(prev => ({
965
- ...prev,
966
- isActive: false,
967
- buffer: '',
968
- progress: 0
969
- }));
970
- }, [editor]);
971
-
972
- // Sync with external streaming prop
973
- useEffect(() => {
974
- if (isStreaming && !streamState.isActive) {
975
- startStream();
976
- } else if (!isStreaming && streamState.isActive) {
977
- finalizeStream();
978
- }
979
- }, [isStreaming, streamState.isActive, startStream, finalizeStream]);
980
-
981
- return {
982
- streamState,
983
- startStream,
984
- appendContent,
985
- finalizeStream,
986
- isStreaming: streamState.isActive,
987
- };
988
- };
989
- ```
990
-
991
- **4.4.3 Build Streaming Indicator Component**
992
- ```typescript
993
- // Prompt: Create StreamingIndicator component that:
994
- // - Shows visual feedback during content streaming
995
- // - Displays streaming progress and connection status
996
- // - Implements pulsing animation for active streaming
997
- // - Provides error states and retry functionality
998
- // - Uses existing UI components (Progress, Badge, Button)
999
- // - Maintains consistent styling with rest of editor
1000
-
1001
- const StreamingIndicator: React.FC<StreamingIndicatorProps> = ({
1002
- isStreaming,
1003
- progress,
1004
- error,
1005
- onRetry
1006
- }) => {
1007
- if (error) {
1008
- return (
1009
- <div className="flex items-center gap-2 px-3 py-2 bg-red-50 border border-red-200 rounded-md">
1010
- <ExclamationTriangle className="h-4 w-4 text-red-500" />
1011
- <span className="text-sm text-red-700">Streaming error: {error.message}</span>
1012
- <Button size="sm" variant="outline" onClick={onRetry}>
1013
- Retry
1014
- </Button>
1015
- </div>
1016
- );
1017
- }
1018
-
1019
- if (!isStreaming) return null;
1020
-
1021
- return (
1022
- <div className="flex items-center gap-2 px-3 py-2 bg-blue-50 border border-blue-200 rounded-md">
1023
- <div className="flex items-center gap-2">
1024
- <div className="h-2 w-2 bg-blue-500 rounded-full animate-pulse" />
1025
- <span className="text-sm text-blue-700">Streaming content...</span>
1026
- </div>
1027
-
1028
- {progress > 0 && (
1029
- <div className="flex items-center gap-2 ml-4">
1030
- <Progress value={progress} className="w-24 h-1" />
1031
- <span className="text-xs text-blue-600">{progress}%</span>
1032
- </div>
1033
- )}
1034
- </div>
1035
- );
1036
- };
1037
- ```
1038
-
1039
- **4.4.4 Implement Streaming Buffer Management**
1040
- ```typescript
1041
- // Prompt: Create StreamingBuffer utility that:
1042
- // - Manages content buffering and batch processing
1043
- // - Implements intelligent content chunking for performance
1044
- // - Handles malformed or incomplete streaming content
1045
- // - Provides content validation and sanitization
1046
- // - Implements backpressure handling for fast streams
1047
- // - Maintains content integrity during streaming
1048
-
1049
- class StreamingBuffer {
1050
- private buffer: string = '';
1051
- private processingQueue: string[] = [];
1052
- private isProcessing: boolean = false;
1053
- private readonly chunkSize: number = 1000;
1054
- private readonly maxBufferSize: number = 50000;
1055
-
1056
- constructor(
1057
- private onChunk: (chunk: string) => void,
1058
- private onError: (error: Error) => void
1059
- ) {}
1060
-
1061
- append(content: string): void {
1062
- if (this.buffer.length + content.length > this.maxBufferSize) {
1063
- this.onError(new Error('Buffer overflow: content too large'));
1064
- return;
1065
- }
1066
-
1067
- this.buffer += content;
1068
- this.processBuffer();
1069
- }
1070
-
1071
- private async processBuffer(): Promise<void> {
1072
- if (this.isProcessing) return;
1073
-
1074
- this.isProcessing = true;
1075
-
1076
- try {
1077
- while (this.buffer.length > 0) {
1078
- const chunk = this.buffer.slice(0, this.chunkSize);
1079
- this.buffer = this.buffer.slice(this.chunkSize);
1080
-
1081
- // Process chunk and handle any formatting
1082
- const processedChunk = this.sanitizeContent(chunk);
1083
- this.onChunk(processedChunk);
1084
-
1085
- // Yield to browser for rendering
1086
- await new Promise(resolve => setTimeout(resolve, 0));
1087
- }
1088
- } catch (error) {
1089
- this.onError(error instanceof Error ? error : new Error('Unknown streaming error'));
1090
- } finally {
1091
- this.isProcessing = false;
1092
- }
1093
- }
1094
-
1095
- private sanitizeContent(content: string): string {
1096
- // Implement content validation and sanitization
1097
- return content
1098
- .replace(/\x00/g, '') // Remove null bytes
1099
- .replace(/[\x01-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ''); // Remove control chars
1100
- }
1101
-
1102
- clear(): void {
1103
- this.buffer = '';
1104
- this.processingQueue = [];
1105
- this.isProcessing = false;
1106
- }
1107
-
1108
- getBufferSize(): number {
1109
- return this.buffer.length;
1110
- }
1111
- }
1112
- ```
1113
-
1114
- **Phase 4 Testing Strategy:**
1115
- - Streaming content insertion tests
1116
- - Performance testing with large streams
1117
- - Error handling and recovery tests
1118
- - Buffer overflow and backpressure tests
1119
- - Integration with editor state management
1120
-
1121
- ### Phase 5: Collaboration Features (Week 6)
1122
-
1123
- **Objective**: Implement real-time collaboration with cursors and presence.
1124
-
1125
- **Implementation Prompts:**
1126
-
1127
- **5.5.1 Create Collaboration Extension**
1128
- ```typescript
1129
- // Prompt: Create CollaborationExtension that:
1130
- // - Integrates with Yjs for operational transformation
1131
- // - Implements real-time cursor tracking and presence
1132
- // - Handles conflict resolution for simultaneous edits
1133
- // - Provides user identification and avatar display
1134
- // - Implements proper connection management and reconnection
1135
- // - Maintains document consistency across all clients
1136
-
1137
- import { Collaboration } from '@tiptap/extension-collaboration';
1138
- import { CollaborationCursor } from '@tiptap/extension-collaboration-cursor';
1139
- import * as Y from 'yjs';
1140
- import { WebrtcProvider } from 'y-webrtc';
1141
-
1142
- const CollaborationExtension = Extension.create({
1143
- name: 'brainfish-collaboration',
1144
-
1145
- addExtensions() {
1146
- const ydoc = new Y.Doc();
1147
- const provider = new WebrtcProvider(this.options.roomId, ydoc, {
1148
- signaling: this.options.signalingServer ? [this.options.signalingServer] : undefined,
1149
- });
1150
-
1151
- return [
1152
- Collaboration.configure({
1153
- document: ydoc,
1154
- }),
1155
- CollaborationCursor.configure({
1156
- provider,
1157
- user: this.options.currentUser,
1158
- render: (user) => this.renderCollaboratorCursor(user),
1159
- }),
1160
- ];
1161
- },
1162
-
1163
- addCommands() {
1164
- return {
1165
- updateCursor: (position: number, selection?: { from: number; to: number }) => () => {
1166
- this.options.onCollaboratorCursor?.(this.options.currentUser.id, position);
1167
- return true;
1168
- },
1169
-
1170
- sendPresence: (data: any) => () => {
1171
- if (this.provider) {
1172
- this.provider.awareness.setLocalStateField('user', {
1173
- ...this.options.currentUser,
1174
- ...data,
1175
- });
1176
- }
1177
- return true;
1178
- },
1179
- };
1180
- },
1181
-
1182
- onCreate() {
1183
- if (this.provider) {
1184
- this.provider.on('status', (event) => {
1185
- this.options.onConnectionStatus?.(event.status);
1186
- });
1187
-
1188
- this.provider.awareness.on('change', () => {
1189
- const users = Array.from(this.provider.awareness.getStates().entries())
1190
- .map(([clientId, state]) => ({ ...state.user, clientId }))
1191
- .filter((user) => user.clientId !== this.provider.doc.clientID);
1192
-
1193
- this.options.onCollaboratorsChange?.(users);
1194
- });
1195
- }
1196
- },
1197
-
1198
- onDestroy() {
1199
- if (this.provider) {
1200
- this.provider.destroy();
1201
- }
1202
- },
1203
-
1204
- private renderCollaboratorCursor(user: CollaboratorUser) {
1205
- const cursor = document.createElement('span');
1206
- cursor.classList.add('collaboration-cursor');
1207
- cursor.style.borderColor = user.color || '#3b82f6';
1208
- cursor.setAttribute('data-user', user.name);
1209
-
1210
- return cursor;
1211
- },
1212
- });
1213
- ```
1214
-
1215
- **5.5.2 Implement Collaborator Components**
1216
- ```typescript
1217
- // Prompt: Create CollaboratorList component that:
1218
- // - Shows all active collaborators with avatars and names
1219
- // - Displays real-time presence indicators (typing, idle, away)
1220
- // - Implements user color coding for cursor identification
1221
- // - Shows connection status for each collaborator
1222
- // - Uses existing UI components (Avatar, Badge, Tooltip)
1223
- // - Handles user interactions like viewing collaborator location
1224
-
1225
- const CollaboratorList: React.FC<CollaboratorListProps> = ({
1226
- collaborators,
1227
- currentUser,
1228
- onUserClick
1229
- }) => {
1230
- const sortedCollaborators = useMemo(() => {
1231
- return collaborators.sort((a, b) => {
1232
- // Sort by activity status, then by name
1233
- if (a.isActive !== b.isActive) {
1234
- return a.isActive ? -1 : 1;
1235
- }
1236
- return a.name.localeCompare(b.name);
1237
- });
1238
- }, [collaborators]);
1239
-
1240
- return (
1241
- <div className="flex items-center gap-2 px-4 py-2 border-b">
1242
- <span className="text-sm font-medium text-gray-700">
1243
- Collaborators ({collaborators.length})
1244
- </span>
1245
-
1246
- <div className="flex items-center gap-1 ml-2">
1247
- {sortedCollaborators.map(collaborator => (
1248
- <Tooltip key={collaborator.id} content={collaborator.name}>
1249
- <div className="relative">
1250
- <Avatar
1251
- src={collaborator.avatar}
1252
- alt={collaborator.name}
1253
- size="sm"
1254
- className="cursor-pointer hover:scale-110 transition-transform"
1255
- style={{ borderColor: collaborator.color }}
1256
- onClick={() => onUserClick?.(collaborator)}
1257
- />
1258
-
1259
- {/* Activity indicator */}
1260
- <div
1261
- className={cn(
1262
- "absolute -bottom-1 -right-1 h-3 w-3 rounded-full border-2 border-white",
1263
- collaborator.isActive ? "bg-green-400" : "bg-gray-400"
1264
- )}
1265
- />
1266
-
1267
- {/* Typing indicator */}
1268
- {collaborator.isTyping && (
1269
- <div className="absolute -top-1 -right-1 h-2 w-2 bg-blue-400 rounded-full animate-pulse" />
1270
- )}
1271
- </div>
1272
- </Tooltip>
1273
- ))}
1274
- </div>
1275
- </div>
1276
- );
1277
- };
1278
- ```
1279
-
1280
- **5.5.3 Build Cursor Management System**
1281
- ```typescript
1282
- // Prompt: Create CollaboratorCursor component that:
1283
- // - Renders real-time cursor positions for all collaborators
1284
- // - Shows user names and colors for cursor identification
1285
- // - Implements smooth cursor animations and transitions
1286
- // - Handles cursor visibility based on viewport
1287
- // - Provides selection range indicators for collaborators
1288
- // - Integrates with TipTap collaboration cursor system
1289
-
1290
- const CollaboratorCursor: React.FC<CollaboratorCursorProps> = ({
1291
- user,
1292
- position,
1293
- selection
1294
- }) => {
1295
- const [isVisible, setIsVisible] = useState(true);
1296
-
1297
- useEffect(() => {
1298
- // Auto-hide cursor after inactivity
1299
- const timer = setTimeout(() => setIsVisible(false), 5000);
1300
- return () => clearTimeout(timer);
1301
- }, [position]);
1302
-
1303
- const cursorStyle = useMemo(() => ({
1304
- borderLeftColor: user.color,
1305
- transform: `translateY(${position.top}px) translateX(${position.left}px)`,
1306
- }), [user.color, position]);
1307
-
1308
- return (
1309
- <motion.div
1310
- className={cn(
1311
- "absolute pointer-events-none z-50",
1312
- "border-l-2 h-5",
1313
- { "opacity-0": !isVisible }
1314
- )}
1315
- style={cursorStyle}
1316
- initial={{ opacity: 0, scale: 0.8 }}
1317
- animate={{ opacity: isVisible ? 1 : 0, scale: 1 }}
1318
- transition={{ duration: 0.2 }}
1319
- >
1320
- {/* User label */}
1321
- <div
1322
- className="absolute -top-6 left-0 px-2 py-1 text-xs text-white rounded whitespace-nowrap"
1323
- style={{ backgroundColor: user.color }}
1324
- >
1325
- {user.name}
1326
- </div>
1327
-
1328
- {/* Selection range */}
1329
- {selection && (
1330
- <div
1331
- className="absolute opacity-20 rounded"
1332
- style={{
1333
- backgroundColor: user.color,
1334
- top: 0,
1335
- left: 0,
1336
- width: selection.width,
1337
- height: selection.height,
1338
- }}
1339
- />
1340
- )}
1341
- </motion.div>
1342
- );
1343
- };
1344
- ```
1345
-
1346
- **5.5.4 Implement Presence Management**
1347
- ```typescript
1348
- // Prompt: Create useCollaborationPresence hook that:
1349
- // - Manages user presence state (active, idle, away)
1350
- // - Implements typing indicators with debounced updates
1351
- // - Handles connection status and reconnection logic
1352
- // - Provides methods for sending presence updates
1353
- // - Manages awareness state for all collaborators
1354
- // - Implements proper cleanup and memory management
1355
-
1356
- const useCollaborationPresence = (
1357
- provider: WebrtcProvider | null,
1358
- currentUser: User,
1359
- editor: Editor | null
1360
- ) => {
1361
- const [collaborators, setCollaborators] = useState<Collaborator[]>([]);
1362
- const [connectionStatus, setConnectionStatus] = useState<'connecting' | 'connected' | 'disconnected'>('disconnected');
1363
- const [isTyping, setIsTyping] = useState(false);
1364
-
1365
- const typingTimeoutRef = useRef<NodeJS.Timeout>();
1366
-
1367
- const updatePresence = useCallback((updates: Partial<Collaborator>) => {
1368
- if (!provider) return;
1369
-
1370
- const currentState = provider.awareness.getLocalState();
1371
- provider.awareness.setLocalStateField('user', {
1372
- ...currentUser,
1373
- ...currentState.user,
1374
- ...updates,
1375
- lastSeen: Date.now(),
1376
- });
1377
- }, [provider, currentUser]);
1378
-
1379
- const startTyping = useCallback(() => {
1380
- if (isTyping) return;
1381
-
1382
- setIsTyping(true);
1383
- updatePresence({ isTyping: true });
1384
-
1385
- // Clear existing timeout
1386
- if (typingTimeoutRef.current) {
1387
- clearTimeout(typingTimeoutRef.current);
1388
- }
1389
-
1390
- // Stop typing after 3 seconds of inactivity
1391
- typingTimeoutRef.current = setTimeout(() => {
1392
- setIsTyping(false);
1393
- updatePresence({ isTyping: false });
1394
- }, 3000);
1395
- }, [isTyping, updatePresence]);
1396
-
1397
- const stopTyping = useCallback(() => {
1398
- if (typingTimeoutRef.current) {
1399
- clearTimeout(typingTimeoutRef.current);
1400
- }
1401
- setIsTyping(false);
1402
- updatePresence({ isTyping: false });
1403
- }, [updatePresence]);
1404
-
1405
- // Listen to editor changes for typing indicators
1406
- useEffect(() => {
1407
- if (!editor) return;
1408
-
1409
- const handleUpdate = () => startTyping();
1410
- editor.on('update', handleUpdate);
1411
-
1412
- return () => editor.off('update', handleUpdate);
1413
- }, [editor, startTyping]);
1414
-
1415
- // Listen to provider awareness changes
1416
- useEffect(() => {
1417
- if (!provider) return;
1418
-
1419
- const handleAwarenessChange = () => {
1420
- const states = Array.from(provider.awareness.getStates().entries());
1421
- const users: Collaborator[] = states
1422
- .filter(([clientId]) => clientId !== provider.doc.clientID)
1423
- .map(([clientId, state]) => ({
1424
- id: state.user?.id || clientId.toString(),
1425
- name: state.user?.name || 'Anonymous',
1426
- avatar: state.user?.avatar,
1427
- color: state.user?.color || generateUserColor(clientId),
1428
- isActive: Date.now() - (state.user?.lastSeen || 0) < 30000,
1429
- isTyping: state.user?.isTyping || false,
1430
- cursor: state.cursor,
1431
- }));
1432
-
1433
- setCollaborators(users);
1434
- };
1435
-
1436
- const handleStatusChange = (event: { status: string }) => {
1437
- setConnectionStatus(event.status as any);
1438
- };
1439
-
1440
- provider.awareness.on('change', handleAwarenessChange);
1441
- provider.on('status', handleStatusChange);
1442
-
1443
- return () => {
1444
- provider.awareness.off('change', handleAwarenessChange);
1445
- provider.off('status', handleStatusChange);
1446
- };
1447
- }, [provider]);
1448
-
1449
- // Cleanup on unmount
1450
- useEffect(() => {
1451
- return () => {
1452
- if (typingTimeoutRef.current) {
1453
- clearTimeout(typingTimeoutRef.current);
1454
- }
1455
- stopTyping();
1456
- };
1457
- }, [stopTyping]);
1458
-
1459
- return {
1460
- collaborators,
1461
- connectionStatus,
1462
- isTyping,
1463
- updatePresence,
1464
- startTyping,
1465
- stopTyping,
1466
- };
1467
- };
1468
- ```
1469
-
1470
- **Phase 5 Testing Strategy:**
1471
- - Real-time collaboration functionality
1472
- - Cursor synchronization across clients
1473
- - Conflict resolution testing
1474
- - Connection handling and recovery
1475
- - Presence and typing indicators
1476
-
1477
- ### Phase 6: Integration and Polish (Week 7-8)
1478
-
1479
- **Objective**: Complete integration, testing, and production readiness.
1480
-
1481
- **Implementation Prompts:**
1482
-
1483
- **6.6.1 FormattedMessage Migration Strategy**
1484
- ```typescript
1485
- // Prompt: Create migration utilities that:
1486
- // - Provide seamless replacement of FormattedMessage with MarkdownEditorViewer
1487
- // - Maintain backward compatibility with existing props and behavior
1488
- // - Implement feature flags for gradual rollout
1489
- // - Provide fallback mechanisms for unsupported features
1490
- // - Handle existing content migration and formatting preservation
1491
- // - Create comprehensive migration guide and documentation
1492
-
1493
- const FormattedMessageMigration = {
1494
- // Convert FormattedMessage props to MarkdownEditorViewer props
1495
- migrateProps: (formattedMessageProps: FormattedMessageProps): MarkdownEditorViewerProps => {
1496
- return {
1497
- content: formattedMessageProps.message.content,
1498
- mode: 'readonly', // FormattedMessage is always readonly
1499
- isStreaming: formattedMessageProps.isStreaming,
1500
- redirectRules: formattedMessageProps.redirectRules,
1501
- contentTypeEndpoint: formattedMessageProps.contentTypeEndpoint,
1502
- // ... map other relevant props
1503
- };
1504
- },
1505
-
1506
- // Wrapper component for gradual migration
1507
- FormattedMessageCompatWrapper: React.FC<FormattedMessageProps> = (props) => {
1508
- const [useNewEditor, setUseNewEditor] = useState(() =>
1509
- // Check feature flag or user preference
1510
- window.BRAINFISH_USE_NEW_EDITOR === true
1511
- );
1512
-
1513
- if (useNewEditor) {
1514
- const migratedProps = FormattedMessageMigration.migrateProps(props);
1515
- return <MarkdownEditorViewer {...migratedProps} />;
1516
- }
1517
-
1518
- // Fallback to original FormattedMessage
1519
- return <FormattedMessage {...props} />;
1520
- },
1521
-
1522
- // Validate content compatibility
1523
- validateContent: (content: string): { isCompatible: boolean; issues: string[] } => {
1524
- const issues: string[] = [];
1525
-
1526
- // Check for unsupported patterns
1527
- if (content.includes('<!-- unsupported -->')) {
1528
- issues.push('Content contains unsupported HTML comments');
1529
- }
1530
-
1531
- // Check for complex nested structures
1532
- const complexTableRegex = /<table[^>]*>.*?<table/s;
1533
- if (complexTableRegex.test(content)) {
1534
- issues.push('Nested tables are not fully supported');
1535
- }
1536
-
1537
- return {
1538
- isCompatible: issues.length === 0,
1539
- issues,
1540
- };
1541
- },
1542
- };
1543
- ```
1544
-
1545
- **6.6.2 Comprehensive Error Handling**
1546
- ```typescript
1547
- // Prompt: Create robust error handling system that:
1548
- // - Implements global error boundaries for graceful degradation
1549
- // - Provides detailed error reporting and logging
1550
- // - Handles network failures and API errors gracefully
1551
- // - Implements retry mechanisms for recoverable errors
1552
- // - Provides user-friendly error messages and recovery actions
1553
- // - Integrates with existing error tracking systems
1554
-
1555
- class EditorErrorBoundary extends React.Component<
1556
- { children: React.ReactNode; fallback?: React.ComponentType<any> },
1557
- { hasError: boolean; error: Error | null }
1558
- > {
1559
- constructor(props: any) {
1560
- super(props);
1561
- this.state = { hasError: false, error: null };
1562
- }
1563
-
1564
- static getDerivedStateFromError(error: Error) {
1565
- return { hasError: true, error };
1566
- }
1567
-
1568
- componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
1569
- console.error('MarkdownEditorViewer Error:', error, errorInfo);
1570
-
1571
- // Report to error tracking service
1572
- if (window.BRAINFISH_ERROR_TRACKING) {
1573
- window.BRAINFISH_ERROR_TRACKING.reportError(error, {
1574
- component: 'MarkdownEditorViewer',
1575
- errorInfo,
1576
- });
1577
- }
1578
- }
1579
-
1580
- render() {
1581
- if (this.state.hasError) {
1582
- const FallbackComponent = this.props.fallback || DefaultErrorFallback;
1583
- return <FallbackComponent error={this.state.error} />;
1584
- }
1585
-
1586
- return this.props.children;
1587
- }
1588
- }
1589
-
1590
- const DefaultErrorFallback: React.FC<{ error: Error | null }> = ({ error }) => {
1591
- return (
1592
- <div className="p-4 border border-red-200 rounded-md bg-red-50">
1593
- <h3 className="text-red-800 font-semibold mb-2">Editor Error</h3>
1594
- <p className="text-red-700 text-sm mb-3">
1595
- The editor encountered an error and couldn't load properly. You can try refreshing the page or switching to the basic editor.
1596
- </p>
1597
- <div className="flex gap-2">
1598
- <Button size="sm" onClick={() => window.location.reload()}>
1599
- Refresh Page
1600
- </Button>
1601
- <Button size="sm" variant="outline" onClick={() => {/* Switch to basic editor */}}>
1602
- Use Basic Editor
1603
- </Button>
1604
- </div>
1605
- {process.env.NODE_ENV === 'development' && error && (
1606
- <details className="mt-3">
1607
- <summary className="text-xs cursor-pointer">Error Details</summary>
1608
- <pre className="text-xs mt-1 overflow-auto">{error.stack}</pre>
1609
- </details>
1610
- )}
1611
- </div>
1612
- );
1613
- };
1614
- ```
1615
-
1616
- **6.6.3 Performance Optimization**
1617
- ```typescript
1618
- // Prompt: Implement performance optimizations that:
1619
- // - Use React.memo and useMemo for expensive operations
1620
- // - Implement virtual scrolling for large documents
1621
- // - Optimize bundle size with dynamic imports
1622
- // - Implement debouncing for frequent operations
1623
- // - Use web workers for heavy computations
1624
- // - Implement efficient re-rendering strategies
1625
-
1626
- // Memoized editor component
1627
- const MemoizedEditorContent = React.memo(
1628
- EditorContent,
1629
- (prevProps, nextProps) => {
1630
- return (
1631
- prevProps.content === nextProps.content &&
1632
- prevProps.mode === nextProps.mode &&
1633
- prevProps.isStreaming === nextProps.isStreaming &&
1634
- shallowEqual(prevProps.suggestions, nextProps.suggestions)
1635
- );
1636
- }
1637
- );
1638
-
1639
- // Virtual scrolling for large suggestion lists
1640
- const VirtualizedSuggestionList: React.FC<SuggestionListProps> = ({ suggestions }) => {
1641
- const [containerRef, setContainerRef] = useState<HTMLDivElement | null>(null);
1642
- const [scrollTop, setScrollTop] = useState(0);
1643
-
1644
- const itemHeight = 60;
1645
- const containerHeight = 400;
1646
- const overscan = 5;
1647
-
1648
- const visibleItems = useMemo(() => {
1649
- if (!containerRef) return suggestions.slice(0, 20);
1650
-
1651
- const startIndex = Math.floor(scrollTop / itemHeight);
1652
- const endIndex = Math.min(
1653
- suggestions.length - 1,
1654
- startIndex + Math.ceil(containerHeight / itemHeight) + overscan
1655
- );
1656
-
1657
- return suggestions.slice(Math.max(0, startIndex - overscan), endIndex + 1);
1658
- }, [suggestions, scrollTop, containerRef]);
1659
-
1660
- return (
1661
- <div
1662
- ref={setContainerRef}
1663
- className="h-96 overflow-auto"
1664
- onScroll={(e) => setScrollTop(e.currentTarget.scrollTop)}
1665
- >
1666
- <div style={{ height: suggestions.length * itemHeight }}>
1667
- <div style={{ transform: `translateY(${Math.floor(scrollTop / itemHeight) * itemHeight}px)` }}>
1668
- {visibleItems.map((suggestion, index) => (
1669
- <SuggestionItem key={suggestion.id} suggestion={suggestion} />
1670
- ))}
1671
- </div>
1672
- </div>
1673
- </div>
1674
- );
1675
- };
1676
-
1677
- // Web worker for markdown processing
1678
- const useMarkdownWorker = () => {
1679
- const workerRef = useRef<Worker>();
1680
-
1681
- useEffect(() => {
1682
- workerRef.current = new Worker('/markdown-processor.worker.js');
1683
- return () => workerRef.current?.terminate();
1684
- }, []);
1685
-
1686
- const processMarkdown = useCallback((markdown: string): Promise<TipTapDocument> => {
1687
- return new Promise((resolve, reject) => {
1688
- if (!workerRef.current) {
1689
- reject(new Error('Worker not available'));
1690
- return;
1691
- }
1692
-
1693
- const id = Math.random().toString(36);
1694
-
1695
- const handleMessage = (e: MessageEvent) => {
1696
- if (e.data.id === id) {
1697
- workerRef.current?.removeEventListener('message', handleMessage);
1698
- if (e.data.error) {
1699
- reject(new Error(e.data.error));
1700
- } else {
1701
- resolve(e.data.result);
1702
- }
1703
- }
1704
- };
1705
-
1706
- workerRef.current.addEventListener('message', handleMessage);
1707
- workerRef.current.postMessage({ id, markdown });
1708
- });
1709
- }, []);
1710
-
1711
- return { processMarkdown };
1712
- };
1713
- ```
1714
-
1715
- **6.6.4 Accessibility Implementation**
1716
- ```typescript
1717
- // Prompt: Implement comprehensive accessibility features that:
1718
- // - Provide proper ARIA labels and roles for all interactive elements
1719
- // - Implement keyboard navigation for all functionality
1720
- // - Support screen readers with descriptive text and live regions
1721
- // - Maintain focus management during mode switches and updates
1722
- // - Provide high contrast mode support
1723
- // - Implement proper color contrast ratios throughout
1724
-
1725
- const AccessibilityProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
1726
- const [announcements, setAnnouncements] = useState<string[]>([]);
1727
- const [focusedElement, setFocusedElement] = useState<string | null>(null);
1728
-
1729
- const announce = useCallback((message: string) => {
1730
- setAnnouncements(prev => [...prev.slice(-2), message]);
1731
- }, []);
1732
-
1733
- const manageFocus = useCallback((elementId: string) => {
1734
- setFocusedElement(elementId);
1735
- setTimeout(() => {
1736
- const element = document.getElementById(elementId);
1737
- element?.focus();
1738
- }, 0);
1739
- }, []);
1740
-
1741
- const contextValue = useMemo(() => ({
1742
- announce,
1743
- manageFocus,
1744
- focusedElement,
1745
- }), [announce, manageFocus, focusedElement]);
1746
-
1747
- return (
1748
- <AccessibilityContext.Provider value={contextValue}>
1749
- {children}
1750
-
1751
- {/* Live region for announcements */}
1752
- <div
1753
- className="sr-only"
1754
- role="status"
1755
- aria-live="polite"
1756
- aria-atomic="true"
1757
- >
1758
- {announcements[announcements.length - 1]}
1759
- </div>
1760
-
1761
- {/* Skip links for keyboard navigation */}
1762
- <div className="sr-only focus:not-sr-only focus:absolute focus:top-0 focus:left-0 z-50">
1763
- <a
1764
- href="#editor-content"
1765
- className="bg-blue-600 text-white p-2 rounded"
1766
- >
1767
- Skip to editor
1768
- </a>
1769
- <a
1770
- href="#suggestions-panel"
1771
- className="bg-blue-600 text-white p-2 rounded ml-2"
1772
- >
1773
- Skip to suggestions
1774
- </a>
1775
- </div>
1776
- </AccessibilityContext.Provider>
1777
- );
1778
- };
1779
-
1780
- // Keyboard shortcuts with accessibility
1781
- const useAccessibleKeyboardShortcuts = (editor: Editor | null) => {
1782
- useEffect(() => {
1783
- const handleKeyDown = (event: KeyboardEvent) => {
1784
- // Handle accessibility-specific shortcuts
1785
- if (event.altKey && event.key === '1') {
1786
- // Focus main editor
1787
- event.preventDefault();
1788
- document.getElementById('editor-content')?.focus();
1789
- announce('Focused main editor');
1790
- }
1791
-
1792
- if (event.altKey && event.key === '2') {
1793
- // Focus suggestions panel
1794
- event.preventDefault();
1795
- document.getElementById('suggestions-panel')?.focus();
1796
- announce('Focused suggestions panel');
1797
- }
1798
-
1799
- if (event.altKey && event.key === '3') {
1800
- // Focus toolbar
1801
- event.preventDefault();
1802
- document.getElementById('editor-toolbar')?.focus();
1803
- announce('Focused editor toolbar');
1804
- }
1805
-
1806
- // Announce current selection or cursor position
1807
- if (event.key === 'F1' && editor) {
1808
- event.preventDefault();
1809
- const { from, to } = editor.state.selection;
1810
- const text = editor.state.doc.textBetween(from, to);
1811
- if (text) {
1812
- announce(`Selected: ${text}`);
1813
- } else {
1814
- announce(`Cursor at position ${from}`);
1815
- }
1816
- }
1817
- };
1818
-
1819
- document.addEventListener('keydown', handleKeyDown);
1820
- return () => document.removeEventListener('keydown', handleKeyDown);
1821
- }, [editor]);
1822
- };
1823
- ```
1824
-
1825
- **6.6.5 Comprehensive Testing Suite**
1826
- ```typescript
1827
- // Prompt: Create comprehensive test suite that:
1828
- // - Tests all major functionality with unit and integration tests
1829
- // - Implements visual regression testing for UI components
1830
- // - Tests accessibility features with automated tools
1831
- // - Includes performance benchmarks and memory leak detection
1832
- // - Tests cross-browser compatibility
1833
- // - Implements end-to-end testing scenarios
1834
-
1835
- // Example test structure
1836
- describe('MarkdownEditorViewer', () => {
1837
- describe('Core Functionality', () => {
1838
- it('should render in edit mode with toolbar', async () => {
1839
- render(
1840
- <MarkdownEditorViewer
1841
- content="# Hello World"
1842
- mode="edit"
1843
- onContentChange={jest.fn()}
1844
- />
1845
- );
1846
-
1847
- expect(screen.getByRole('textbox', { name: /markdown editor/i })).toBeInTheDocument();
1848
- expect(screen.getByRole('toolbar', { name: /formatting toolbar/i })).toBeInTheDocument();
1849
- });
1850
-
1851
- it('should render in readonly mode without toolbar', async () => {
1852
- render(
1853
- <MarkdownEditorViewer
1854
- content="# Hello World"
1855
- mode="readonly"
1856
- />
1857
- );
1858
-
1859
- expect(screen.getByRole('article')).toBeInTheDocument();
1860
- expect(screen.queryByRole('toolbar')).not.toBeInTheDocument();
1861
- });
1862
-
1863
- it('should switch modes while preserving content', async () => {
1864
- const onModeChange = jest.fn();
1865
- const { rerender } = render(
1866
- <MarkdownEditorViewer
1867
- content="# Test Content"
1868
- mode="edit"
1869
- onModeChange={onModeChange}
1870
- />
1871
- );
1872
-
1873
- // Switch to readonly
1874
- rerender(
1875
- <MarkdownEditorViewer
1876
- content="# Test Content"
1877
- mode="readonly"
1878
- onModeChange={onModeChange}
1879
- />
1880
- );
1881
-
1882
- expect(screen.getByText('Test Content')).toBeInTheDocument();
1883
- });
1884
- });
1885
-
1886
- describe('Suggestion System', () => {
1887
- it('should display suggestions with proper styling', async () => {
1888
- const suggestions: EditorSuggestion[] = [
1889
- {
1890
- id: '1',
1891
- type: 'insert',
1892
- position: { from: 0, to: 0 },
1893
- newText: 'New text',
1894
- status: 'pending',
1895
- timestamp: new Date(),
1896
- },
1897
- ];
1898
-
1899
- render(
1900
- <MarkdownEditorViewer
1901
- content="Original text"
1902
- mode="edit"
1903
- suggestions={suggestions}
1904
- showSuggestions={true}
1905
- />
1906
- );
1907
-
1908
- expect(screen.getByText('New text')).toHaveClass('suggestion-insert');
1909
- });
1910
-
1911
- it('should handle suggestion acceptance', async () => {
1912
- const onSuggestionAccept = jest.fn();
1913
- // ... test implementation
1914
- });
1915
- });
1916
-
1917
- describe('Multimedia Integration', () => {
1918
- it('should render images using ZoomableImage', async () => {
1919
- render(
1920
- <MarkdownEditorViewer
1921
- content="![Alt text](https://example.com/image.jpg)"
1922
- mode="readonly"
1923
- />
1924
- );
1925
-
1926
- expect(screen.getByRole('img', { name: /alt text/i })).toBeInTheDocument();
1927
- });
1928
-
1929
- it('should handle video embeds', async () => {
1930
- // ... test implementation
1931
- });
1932
- });
1933
-
1934
- describe('Accessibility', () => {
1935
- it('should have proper ARIA labels', async () => {
1936
- render(
1937
- <MarkdownEditorViewer
1938
- content="# Test"
1939
- mode="edit"
1940
- />
1941
- );
1942
-
1943
- expect(screen.getByRole('textbox')).toHaveAccessibleName(/markdown editor/i);
1944
- expect(screen.getByRole('toolbar')).toHaveAccessibleName(/formatting toolbar/i);
1945
- });
1946
-
1947
- it('should support keyboard navigation', async () => {
1948
- // ... test implementation
1949
- });
1950
-
1951
- it('should announce changes to screen readers', async () => {
1952
- // ... test implementation
1953
- });
1954
- });
1955
-
1956
- describe('Performance', () => {
1957
- it('should handle large documents efficiently', async () => {
1958
- const largeContent = 'a'.repeat(100000);
1959
- const startTime = performance.now();
1960
-
1961
- render(
1962
- <MarkdownEditorViewer
1963
- content={largeContent}
1964
- mode="edit"
1965
- />
1966
- );
1967
-
1968
- const endTime = performance.now();
1969
- expect(endTime - startTime).toBeLessThan(1000); // Should render in under 1 second
1970
- });
1971
-
1972
- it('should not have memory leaks', async () => {
1973
- // ... memory leak testing
1974
- });
1975
- });
1976
-
1977
- describe('Error Handling', () => {
1978
- it('should gracefully handle malformed markdown', async () => {
1979
- const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
1980
-
1981
- render(
1982
- <MarkdownEditorViewer
1983
- content="# Malformed [link"
1984
- mode="edit"
1985
- />
1986
- );
1987
-
1988
- expect(screen.getByText(/malformed/i)).toBeInTheDocument();
1989
- expect(consoleSpy).not.toHaveBeenCalled();
1990
-
1991
- consoleSpy.mockRestore();
1992
- });
1993
- });
1994
- });
1995
- ```
1996
-
1997
- ## 4. Success Metrics and Acceptance Criteria
1998
-
1999
- ### 4.1 Performance Benchmarks
2000
-
2001
- **Initialization Performance:**
2002
- - Editor initialization: < 500ms
2003
- - Mode switching: < 100ms
2004
- - Large document loading (1MB): < 2 seconds
2005
-
2006
- **Runtime Performance:**
2007
- - Typing lag: < 50ms
2008
- - Suggestion processing: < 200ms
2009
- - Streaming content updates: 1000+ chars/second
2010
-
2011
- **Memory Usage:**
2012
- - Baseline memory: < 20MB
2013
- - Large document (1MB): < 50MB
2014
- - Memory leak tolerance: < 1MB/hour
2015
-
2016
- ### 4.2 Functionality Requirements
2017
-
2018
- **Core Features:**
2019
- - ✅ Mode switching (edit/readonly) with content preservation
2020
- - ✅ Markdown translation (markdown ↔ TipTap JSON)
2021
- - ✅ Complete multimedia system integration
2022
- - ✅ Visual suggestion system with keyboard navigation
2023
- - ✅ Streaming content support
2024
- - ✅ Basic collaboration features
2025
-
2026
- **Compatibility:**
2027
- - ✅ 100% compatibility with existing embed system
2028
- - ✅ FormattedMessage.tsx feature parity
2029
- - ✅ Consistent styling with existing components
2030
- - ✅ Support for all existing markdown features
2031
-
2032
- ### 4.3 Quality Assurance
2033
-
2034
- **Testing Coverage:**
2035
- - Unit tests: > 90% coverage
2036
- - Integration tests: All major workflows
2037
- - E2E tests: Complete user scenarios
2038
- - Accessibility: WCAG 2.1 AA compliance
2039
-
2040
- **Browser Support:**
2041
- - Chrome 90+, Firefox 88+, Safari 14+, Edge 90+
2042
- - Mobile responsive design
2043
- - Touch interface optimization
2044
-
2045
- **Error Handling:**
2046
- - Graceful degradation for all error scenarios
2047
- - Clear user feedback and recovery options
2048
- - Comprehensive error logging and reporting
2049
-
2050
- ## 5. Implementation Timeline
2051
-
2052
- **Week 1-2: Foundation** (Phase 1)
2053
- - Core component structure
2054
- - TipTap integration
2055
- - Markdown translation
2056
- - Basic editor/viewer modes
2057
-
2058
- **Week 3: Multimedia** (Phase 2)
2059
- - Embed system integration
2060
- - File upload functionality
2061
- - Media rendering components
2062
-
2063
- **Week 4: Suggestions** (Phase 3)
2064
- - Visual suggestion system
2065
- - Keyboard navigation
2066
- - Accept/reject functionality
2067
-
2068
- **Week 5: Streaming** (Phase 4)
2069
- - Real-time content streaming
2070
- - Performance optimization
2071
- - Error handling
2072
-
2073
- **Week 6: Collaboration** (Phase 5)
2074
- - Real-time collaboration
2075
- - Cursor synchronization
2076
- - Presence indicators
2077
-
2078
- **Week 7-8: Integration & Polish** (Phase 6)
2079
- - Migration utilities
2080
- - Comprehensive testing
2081
- - Performance optimization
2082
- - Documentation
2083
-
2084
- ## 6. Risk Mitigation
2085
-
2086
- **Technical Risks:**
2087
- - **TipTap integration complexity**: Use incremental approach with fallbacks
2088
- - **Performance issues**: Implement monitoring and optimization from start
2089
- - **Browser compatibility**: Test early and often across platforms
2090
-
2091
- **Integration Risks:**
2092
- - **Breaking changes**: Maintain strict backward compatibility
2093
- - **Embed system conflicts**: Leverage existing patterns exactly
2094
- - **State management complexity**: Use proven React patterns
2095
-
2096
- **Timeline Risks:**
2097
- - **Feature creep**: Stick to specification requirements
2098
- - **Testing delays**: Implement testing incrementally
2099
- - **Performance bottlenecks**: Profile and optimize continuously
2100
-
2101
- This comprehensive implementation plan provides detailed guidance for building the Markdown Editor/Viewer component while maintaining consistency with existing codebase patterns and ensuring all specification requirements are met. Each phase builds incrementally on the previous one, with clear success criteria and testing strategies to ensure quality delivery.