rayo-editor 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +563 -0
- package/dist/components/BlogEditor.d.ts +4 -0
- package/dist/components/BlogEditor.d.ts.map +1 -0
- package/dist/components/DiffOverlay.d.ts +4 -0
- package/dist/components/DiffOverlay.d.ts.map +1 -0
- package/dist/components/ImageGenerationLoader.d.ts +5 -0
- package/dist/components/ImageGenerationLoader.d.ts.map +1 -0
- package/dist/components/RayoEditor.d.ts +74 -0
- package/dist/components/RayoEditor.d.ts.map +1 -0
- package/dist/components/ReviewButtons.d.ts +4 -0
- package/dist/components/ReviewButtons.d.ts.map +1 -0
- package/dist/components/TitleTextarea.d.ts +4 -0
- package/dist/components/TitleTextarea.d.ts.map +1 -0
- package/dist/components/index.d.ts +7 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/useContentProcessing.d.ts +83 -0
- package/dist/hooks/useContentProcessing.d.ts.map +1 -0
- package/dist/hooks/useEditorDiff.d.ts +65 -0
- package/dist/hooks/useEditorDiff.d.ts.map +1 -0
- package/dist/index.d.ts +213 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +1011 -0
- package/dist/index.umd.js +32 -0
- package/dist/styles.css +1150 -0
- package/dist/types/diff.types.d.ts +84 -0
- package/dist/types/diff.types.d.ts.map +1 -0
- package/dist/types/editor.types.d.ts +59 -0
- package/dist/types/editor.types.d.ts.map +1 -0
- package/dist/utils/contentProcessing.d.ts +81 -0
- package/dist/utils/contentProcessing.d.ts.map +1 -0
- package/dist/utils/diffDetection.d.ts +85 -0
- package/dist/utils/diffDetection.d.ts.map +1 -0
- package/dist/utils/errorHandling.d.ts +57 -0
- package/dist/utils/errorHandling.d.ts.map +1 -0
- package/dist/utils/imageHandling.d.ts +52 -0
- package/dist/utils/imageHandling.d.ts.map +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/proximityMatching.d.ts +80 -0
- package/dist/utils/proximityMatching.d.ts.map +1 -0
- package/package.json +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
# rayo-editor
|
|
2
|
+
|
|
3
|
+
  
|
|
4
|
+
|
|
5
|
+
A professional-grade rich text editor component for React, built with TipTap and optimized for blog content creation. Drop-in replacement for BlogEditor with advanced diff highlighting, collaborative editing support, and real-time content processing.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Rich Text Editing** - Full-featured editor with support for headings, lists, code blocks, tables, and images
|
|
10
|
+
- **Diff Highlighting** - Visual diff overlays showing changes in green (additions) and red (deletions)
|
|
11
|
+
- **Review UI** - Accept/reject individual changes or bulk approve all modifications
|
|
12
|
+
- **Image Handling** - Integrated image management with generation loader
|
|
13
|
+
- **TypeScript Support** - Fully typed API with comprehensive JSDoc documentation
|
|
14
|
+
- **React 18+** - Modern React patterns with hooks and ref forwarding
|
|
15
|
+
- **Zero Configuration** - Works out of the box, no setup required
|
|
16
|
+
- **Rive Animations** - Smooth loading and generation states
|
|
17
|
+
- **96.3% Test Coverage** - Comprehensive test suite with 271+ tests
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install rayo-editor
|
|
23
|
+
# or
|
|
24
|
+
yarn add rayo-editor
|
|
25
|
+
# or
|
|
26
|
+
pnpm add rayo-editor
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### Basic Usage
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import React, { useRef } from 'react';
|
|
35
|
+
import { RayoEditor, BlogSimpleEditorRef } from 'rayo-editor';
|
|
36
|
+
import 'rayo-editor/styles';
|
|
37
|
+
|
|
38
|
+
export function MyBlogEditor() {
|
|
39
|
+
const editorRef = useRef<BlogSimpleEditorRef>(null);
|
|
40
|
+
const [content, setContent] = React.useState('');
|
|
41
|
+
const [title, setTitle] = React.useState('');
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<RayoEditor
|
|
45
|
+
content={content}
|
|
46
|
+
title={title}
|
|
47
|
+
onChange={setContent}
|
|
48
|
+
onTitleChange={setTitle}
|
|
49
|
+
isLoading={false}
|
|
50
|
+
editorRef={editorRef}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### With Diff Highlighting
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
<RayoEditor
|
|
60
|
+
content={content}
|
|
61
|
+
title={title}
|
|
62
|
+
onChange={setContent}
|
|
63
|
+
onTitleChange={setTitle}
|
|
64
|
+
showDiffs={true}
|
|
65
|
+
pendingChanges={true}
|
|
66
|
+
onAcceptChanges={() => {
|
|
67
|
+
// Handle accepting all changes
|
|
68
|
+
console.log('Changes accepted');
|
|
69
|
+
}}
|
|
70
|
+
onRejectChanges={() => {
|
|
71
|
+
// Handle rejecting all changes
|
|
72
|
+
console.log('Changes rejected');
|
|
73
|
+
}}
|
|
74
|
+
isLoading={false}
|
|
75
|
+
editorRef={editorRef}
|
|
76
|
+
/>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Read-Only Mode
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
<RayoEditor
|
|
83
|
+
content={publishedContent}
|
|
84
|
+
title={publishedTitle}
|
|
85
|
+
onChange={() => {}} // No-op
|
|
86
|
+
readOnly={true}
|
|
87
|
+
isLoading={false}
|
|
88
|
+
editorRef={editorRef}
|
|
89
|
+
/>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### With Callbacks
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<RayoEditor
|
|
96
|
+
content={content}
|
|
97
|
+
title={title}
|
|
98
|
+
onChange={setContent}
|
|
99
|
+
onTitleChange={setTitle}
|
|
100
|
+
onAcceptSingleChange={(greenRange, redRange) => {
|
|
101
|
+
console.log('Single change accepted', greenRange, redRange);
|
|
102
|
+
}}
|
|
103
|
+
onRejectSingleChange={(greenRange, redRange) => {
|
|
104
|
+
console.log('Single change rejected', greenRange, redRange);
|
|
105
|
+
}}
|
|
106
|
+
onDiffPairsChange={(pairs) => {
|
|
107
|
+
console.log('Diff pairs updated', pairs);
|
|
108
|
+
}}
|
|
109
|
+
isLoading={false}
|
|
110
|
+
editorRef={editorRef}
|
|
111
|
+
/>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## API Reference
|
|
115
|
+
|
|
116
|
+
### Components
|
|
117
|
+
|
|
118
|
+
#### RayoEditor
|
|
119
|
+
|
|
120
|
+
Main editor component providing a complete rich text editing experience.
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
<RayoEditor {...props} />
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Props:**
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
interface RayoEditorProps {
|
|
130
|
+
// Content management
|
|
131
|
+
content: string;
|
|
132
|
+
title: string;
|
|
133
|
+
onChange: (content: string) => void;
|
|
134
|
+
onTitleChange?: (title: string) => void;
|
|
135
|
+
|
|
136
|
+
// State management
|
|
137
|
+
isLoading: boolean;
|
|
138
|
+
isStreaming?: boolean;
|
|
139
|
+
isAgentThinking?: boolean;
|
|
140
|
+
readOnly?: boolean;
|
|
141
|
+
focusMode?: boolean;
|
|
142
|
+
|
|
143
|
+
// Diff & Review features
|
|
144
|
+
pendingChanges?: boolean;
|
|
145
|
+
showDiffs?: boolean;
|
|
146
|
+
hideReviewUI?: boolean;
|
|
147
|
+
editedLinesCount?: number;
|
|
148
|
+
onAcceptChanges?: () => void;
|
|
149
|
+
onRejectChanges?: () => void;
|
|
150
|
+
onAcceptSingleChange?: (greenRange: DiffRange, redRange?: DiffRange) => void;
|
|
151
|
+
onRejectSingleChange?: (greenRange: DiffRange, redRange?: DiffRange) => void;
|
|
152
|
+
onDiffPairsChange?: (diffPairs: DiffPair[]) => void;
|
|
153
|
+
|
|
154
|
+
// Image management
|
|
155
|
+
featuredImageUrl?: string;
|
|
156
|
+
onEditFeaturedImage?: () => void;
|
|
157
|
+
isGeneratingImage?: boolean;
|
|
158
|
+
|
|
159
|
+
// Advanced options
|
|
160
|
+
editorRef: RefObject<BlogSimpleEditorRef>;
|
|
161
|
+
onAriScoreChange?: (score: number) => void;
|
|
162
|
+
disableAutoScroll?: boolean;
|
|
163
|
+
onUserScrollChange?: (isScrolledUp: boolean) => void;
|
|
164
|
+
showToolbarAnimation?: boolean;
|
|
165
|
+
streamingPhase?: string;
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### BlogEditor
|
|
170
|
+
|
|
171
|
+
Legacy component wrapper - identical to RayoEditor, maintained for backward compatibility.
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
import { BlogEditor } from 'rayo-editor';
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### TitleTextarea
|
|
178
|
+
|
|
179
|
+
Standalone title input component.
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
<TitleTextarea
|
|
183
|
+
title={title}
|
|
184
|
+
onTitleChange={setTitle}
|
|
185
|
+
readOnly={false}
|
|
186
|
+
/>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### DiffOverlay
|
|
190
|
+
|
|
191
|
+
Visual overlay component for displaying diff highlights.
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
<DiffOverlay
|
|
195
|
+
diffPairs={diffPairs}
|
|
196
|
+
overlayHoleRect={rect}
|
|
197
|
+
onPairHover={(index) => console.log(index)}
|
|
198
|
+
onPairClick={(index) => console.log(index)}
|
|
199
|
+
/>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
#### ReviewButtons
|
|
203
|
+
|
|
204
|
+
Accept/reject buttons for review operations.
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
<ReviewButtons
|
|
208
|
+
onAccept={() => console.log('Accepted')}
|
|
209
|
+
onReject={() => console.log('Rejected')}
|
|
210
|
+
position={{ top: 100, left: 50 }}
|
|
211
|
+
/>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Hooks
|
|
215
|
+
|
|
216
|
+
#### useEditorDiff
|
|
217
|
+
|
|
218
|
+
Hook for managing diff detection and overlay state.
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
const {
|
|
222
|
+
diffPairs,
|
|
223
|
+
setDiffPairs,
|
|
224
|
+
activePairIndex,
|
|
225
|
+
setActivePairIndex,
|
|
226
|
+
hoverPairIndex,
|
|
227
|
+
setHoverPairIndex,
|
|
228
|
+
overlayHoleRect,
|
|
229
|
+
setOverlayHoleRect,
|
|
230
|
+
updateDiffRanges
|
|
231
|
+
} = useEditorDiff(editorRef, { focusMode: false });
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Returns:**
|
|
235
|
+
- `diffPairs: DiffPair[]` - Array of detected diff pairs
|
|
236
|
+
- `activePairIndex: number` - Currently active pair index
|
|
237
|
+
- `hoverPairIndex: number` - Currently hovered pair index
|
|
238
|
+
- `overlayHoleRect: Rect | null` - Rectangle for overlay positioning
|
|
239
|
+
- `updateDiffRanges: () => void` - Function to trigger diff update
|
|
240
|
+
|
|
241
|
+
#### useContentProcessing
|
|
242
|
+
|
|
243
|
+
Hook for content transformation and processing.
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
const {
|
|
247
|
+
content,
|
|
248
|
+
setContent,
|
|
249
|
+
isProcessing,
|
|
250
|
+
error,
|
|
251
|
+
processContent
|
|
252
|
+
} = useContentProcessing(initialContent);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Returns:**
|
|
256
|
+
- `content: string` - Current processed content
|
|
257
|
+
- `setContent: (content: string) => void` - Direct content setter
|
|
258
|
+
- `isProcessing: boolean` - Processing state flag
|
|
259
|
+
- `error: Error | null` - Last processing error
|
|
260
|
+
- `processContent: (content: string) => void` - Process and update content
|
|
261
|
+
|
|
262
|
+
### Utility Functions
|
|
263
|
+
|
|
264
|
+
#### Diff Detection
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
// Detect diff markers in content
|
|
268
|
+
detectDiffMarkers(content: string): DiffMarkerResult;
|
|
269
|
+
|
|
270
|
+
// Normalize diff text for comparison
|
|
271
|
+
normalizeDiffText(text: string): string;
|
|
272
|
+
|
|
273
|
+
// Extract diff ranges from content
|
|
274
|
+
extractDiffRanges(content: string, type?: 'all' | 'green' | 'red'): DiffRange[];
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
#### Content Processing
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
// Merge consecutive or overlapping ranges
|
|
281
|
+
mergeConsecutiveRanges(ranges: DiffRange[]): DiffRange[];
|
|
282
|
+
|
|
283
|
+
// Extract plain text from HTML content
|
|
284
|
+
extractTextContent(html: string): string;
|
|
285
|
+
|
|
286
|
+
// Optimize ranges for rendering
|
|
287
|
+
optimizeRanges(ranges: DiffRange[]): DiffRange[];
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
#### Image Handling
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
// Group consecutive images in content
|
|
294
|
+
groupConsecutiveImages(content: string): ImageOperation[];
|
|
295
|
+
|
|
296
|
+
// Match image replacements in diff pairs
|
|
297
|
+
matchImageReplacements(content: string): Array<{old: string, new: string}>;
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
#### Proximity Matching
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
// Calculate proximity score between text segments
|
|
304
|
+
calculateProximity(text1: string, text2: string): number;
|
|
305
|
+
|
|
306
|
+
// Find the best matching text pair
|
|
307
|
+
findOwnerTextPair(target: string, candidates: string[]): string | null;
|
|
308
|
+
|
|
309
|
+
// Group items by proximity
|
|
310
|
+
groupConsecutiveItems<T>(items: T[], key: string): T[][];
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
#### Error Handling
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// Safe execution wrapper
|
|
317
|
+
safeExecute<T>(fn: () => T, errorHandler?: (error: Error) => void): T | null;
|
|
318
|
+
|
|
319
|
+
// Custom error class
|
|
320
|
+
class DiffProcessingError extends Error {
|
|
321
|
+
constructor(message: string, public originalError?: Error) {}
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Props Documentation
|
|
326
|
+
|
|
327
|
+
### RayoEditorProps
|
|
328
|
+
|
|
329
|
+
All props are documented below with their purposes and default values:
|
|
330
|
+
|
|
331
|
+
| Prop | Type | Required | Default | Description |
|
|
332
|
+
|------|------|----------|---------|-------------|
|
|
333
|
+
| `content` | `string` | Yes | - | Current editor content (HTML) |
|
|
334
|
+
| `title` | `string` | Yes | - | Blog post title |
|
|
335
|
+
| `onChange` | `(content: string) => void` | Yes | - | Callback fired on content changes |
|
|
336
|
+
| `onTitleChange` | `(title: string) => void` | No | - | Callback fired on title changes |
|
|
337
|
+
| `isLoading` | `boolean` | Yes | - | Shows loading state |
|
|
338
|
+
| `editorRef` | `RefObject<BlogSimpleEditorRef>` | Yes | - | Reference to editor instance |
|
|
339
|
+
| `isStreaming` | `boolean` | No | `false` | Indicates streaming in progress |
|
|
340
|
+
| `isAgentThinking` | `boolean` | No | `false` | Shows agent thinking state |
|
|
341
|
+
| `readOnly` | `boolean` | No | `false` | Disables editing |
|
|
342
|
+
| `focusMode` | `boolean` | No | `false` | Minimal UI focus mode |
|
|
343
|
+
| `pendingChanges` | `boolean` | No | `false` | Indicates pending changes exist |
|
|
344
|
+
| `showDiffs` | `boolean` | No | `false` | Show diff highlighting |
|
|
345
|
+
| `hideReviewUI` | `boolean` | No | `false` | Hide review buttons |
|
|
346
|
+
| `editedLinesCount` | `number` | No | - | Number of edited lines |
|
|
347
|
+
| `onAcceptChanges` | `() => void` | No | - | Accept all changes callback |
|
|
348
|
+
| `onRejectChanges` | `() => void` | No | - | Reject all changes callback |
|
|
349
|
+
| `onAcceptSingleChange` | `(green, red?) => void` | No | - | Accept single change |
|
|
350
|
+
| `onRejectSingleChange` | `(green, red?) => void` | No | - | Reject single change |
|
|
351
|
+
| `onDiffPairsChange` | `(pairs) => void` | No | - | Diff pairs updated |
|
|
352
|
+
| `featuredImageUrl` | `string` | No | - | Featured image URL |
|
|
353
|
+
| `onEditFeaturedImage` | `() => void` | No | - | Edit featured image callback |
|
|
354
|
+
| `isGeneratingImage` | `boolean` | No | `false` | Image generation in progress |
|
|
355
|
+
| `onAriScoreChange` | `(score: number) => void` | No | - | ARI score updated |
|
|
356
|
+
| `disableAutoScroll` | `boolean` | No | `false` | Disable auto-scroll |
|
|
357
|
+
| `onUserScrollChange` | `(scrolledUp: boolean) => void` | No | - | User scroll state |
|
|
358
|
+
| `showToolbarAnimation` | `boolean` | No | `true` | Show toolbar animations |
|
|
359
|
+
| `streamingPhase` | `string` | No | - | Current streaming phase |
|
|
360
|
+
|
|
361
|
+
## Examples
|
|
362
|
+
|
|
363
|
+
### Advanced: Collaborative Editing
|
|
364
|
+
|
|
365
|
+
```tsx
|
|
366
|
+
import React, { useRef, useState } from 'react';
|
|
367
|
+
import { RayoEditor, BlogSimpleEditorRef, useEditorDiff } from 'rayo-editor';
|
|
368
|
+
|
|
369
|
+
export function CollaborativeEditor() {
|
|
370
|
+
const editorRef = useRef<BlogSimpleEditorRef>(null);
|
|
371
|
+
const [content, setContent] = useState('');
|
|
372
|
+
const [title, setTitle] = useState('');
|
|
373
|
+
const [pendingChanges, setPendingChanges] = useState(false);
|
|
374
|
+
|
|
375
|
+
const { diffPairs, updateDiffRanges } = useEditorDiff(editorRef);
|
|
376
|
+
|
|
377
|
+
const handleAcceptChanges = async () => {
|
|
378
|
+
// Apply changes and send to server
|
|
379
|
+
await fetch('/api/changes/accept', {
|
|
380
|
+
method: 'POST',
|
|
381
|
+
body: JSON.stringify({ content, diffPairs })
|
|
382
|
+
});
|
|
383
|
+
setPendingChanges(false);
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
return (
|
|
387
|
+
<RayoEditor
|
|
388
|
+
content={content}
|
|
389
|
+
title={title}
|
|
390
|
+
onChange={setContent}
|
|
391
|
+
onTitleChange={setTitle}
|
|
392
|
+
showDiffs={pendingChanges}
|
|
393
|
+
pendingChanges={pendingChanges}
|
|
394
|
+
onAcceptChanges={handleAcceptChanges}
|
|
395
|
+
onRejectChanges={() => setContent(content)}
|
|
396
|
+
isLoading={false}
|
|
397
|
+
editorRef={editorRef}
|
|
398
|
+
/>
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Advanced: With Content Processing
|
|
404
|
+
|
|
405
|
+
```tsx
|
|
406
|
+
import { RayoEditor, useContentProcessing } from 'rayo-editor';
|
|
407
|
+
|
|
408
|
+
export function SmartEditor() {
|
|
409
|
+
const editorRef = useRef<BlogSimpleEditorRef>(null);
|
|
410
|
+
const {
|
|
411
|
+
content,
|
|
412
|
+
setContent,
|
|
413
|
+
isProcessing,
|
|
414
|
+
error,
|
|
415
|
+
processContent
|
|
416
|
+
} = useContentProcessing('');
|
|
417
|
+
|
|
418
|
+
const handleAIEnhance = async () => {
|
|
419
|
+
const enhanced = await fetch('/api/enhance', {
|
|
420
|
+
method: 'POST',
|
|
421
|
+
body: JSON.stringify({ content })
|
|
422
|
+
}).then(r => r.json());
|
|
423
|
+
|
|
424
|
+
processContent(enhanced.content);
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
return (
|
|
428
|
+
<>
|
|
429
|
+
<RayoEditor
|
|
430
|
+
content={content}
|
|
431
|
+
title=""
|
|
432
|
+
onChange={processContent}
|
|
433
|
+
isLoading={isProcessing}
|
|
434
|
+
editorRef={editorRef}
|
|
435
|
+
/>
|
|
436
|
+
{error && <div className="error">{error.message}</div>}
|
|
437
|
+
<button onClick={handleAIEnhance} disabled={isProcessing}>
|
|
438
|
+
{isProcessing ? 'Processing...' : 'Enhance with AI'}
|
|
439
|
+
</button>
|
|
440
|
+
</>
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Advanced: Real-time Diff Highlighting
|
|
446
|
+
|
|
447
|
+
```tsx
|
|
448
|
+
import React, { useRef, useState, useEffect } from 'react';
|
|
449
|
+
import { RayoEditor, useEditorDiff, detectDiffMarkers } from 'rayo-editor';
|
|
450
|
+
|
|
451
|
+
export function RealtimeDiffEditor() {
|
|
452
|
+
const editorRef = useRef<BlogSimpleEditorRef>(null);
|
|
453
|
+
const [content, setContent] = useState('');
|
|
454
|
+
const { diffPairs, setDiffPairs } = useEditorDiff(editorRef);
|
|
455
|
+
|
|
456
|
+
useEffect(() => {
|
|
457
|
+
const { hasDiffs } = detectDiffMarkers(content);
|
|
458
|
+
if (hasDiffs) {
|
|
459
|
+
// Process and set diff pairs
|
|
460
|
+
setDiffPairs([]);
|
|
461
|
+
}
|
|
462
|
+
}, [content, setDiffPairs]);
|
|
463
|
+
|
|
464
|
+
return (
|
|
465
|
+
<RayoEditor
|
|
466
|
+
content={content}
|
|
467
|
+
title=""
|
|
468
|
+
onChange={setContent}
|
|
469
|
+
showDiffs={diffPairs.length > 0}
|
|
470
|
+
isLoading={false}
|
|
471
|
+
editorRef={editorRef}
|
|
472
|
+
/>
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
## Architecture
|
|
478
|
+
|
|
479
|
+
### Component Hierarchy
|
|
480
|
+
|
|
481
|
+
```
|
|
482
|
+
RayoEditor (Main Component)
|
|
483
|
+
├── BlogEditor (Core Editor Implementation)
|
|
484
|
+
├── TitleTextarea (Title Input)
|
|
485
|
+
├── DiffOverlay (Diff Visualization)
|
|
486
|
+
├── ReviewButtons (Accept/Reject Controls)
|
|
487
|
+
└── ImageGenerationLoader (Image Loading State)
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Hook System
|
|
491
|
+
|
|
492
|
+
- **useEditorDiff** - Manages diff state and detection
|
|
493
|
+
- **useContentProcessing** - Handles content transformation
|
|
494
|
+
- **Internal hooks** - TipTap editor instance management
|
|
495
|
+
|
|
496
|
+
### Utility Pipeline
|
|
497
|
+
|
|
498
|
+
```
|
|
499
|
+
Content Input
|
|
500
|
+
↓
|
|
501
|
+
Diff Detection (detectDiffMarkers)
|
|
502
|
+
↓
|
|
503
|
+
Normalization (normalizeDiffText)
|
|
504
|
+
↓
|
|
505
|
+
Range Extraction (extractDiffRanges)
|
|
506
|
+
↓
|
|
507
|
+
Range Optimization (mergeConsecutiveRanges, optimizeRanges)
|
|
508
|
+
↓
|
|
509
|
+
Rendering (DiffOverlay)
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
## Testing
|
|
513
|
+
|
|
514
|
+
The package includes comprehensive tests covering all functionality:
|
|
515
|
+
|
|
516
|
+
```bash
|
|
517
|
+
# Run all tests
|
|
518
|
+
npm run test
|
|
519
|
+
|
|
520
|
+
# Run with UI
|
|
521
|
+
npm run test:ui
|
|
522
|
+
|
|
523
|
+
# Generate coverage report
|
|
524
|
+
npm run test:coverage
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
**Test Statistics:**
|
|
528
|
+
- 271+ test cases
|
|
529
|
+
- 96.3% code coverage
|
|
530
|
+
- All critical paths covered
|
|
531
|
+
- Edge cases and error scenarios tested
|
|
532
|
+
|
|
533
|
+
## Contributing
|
|
534
|
+
|
|
535
|
+
Contributions are welcome! Please follow these guidelines:
|
|
536
|
+
|
|
537
|
+
1. Fork the repository
|
|
538
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
539
|
+
3. Make your changes with tests
|
|
540
|
+
4. Ensure all tests pass (`npm run test`)
|
|
541
|
+
5. Commit with clear messages (`git commit -m 'feat: add amazing feature'`)
|
|
542
|
+
6. Push to the branch (`git push origin feature/amazing-feature`)
|
|
543
|
+
7. Open a Pull Request
|
|
544
|
+
|
|
545
|
+
### Code Standards
|
|
546
|
+
|
|
547
|
+
- Use TypeScript for type safety
|
|
548
|
+
- Add JSDoc comments for public APIs
|
|
549
|
+
- Write tests for new features
|
|
550
|
+
- Maintain >95% code coverage
|
|
551
|
+
- Follow eslint configuration
|
|
552
|
+
|
|
553
|
+
## Changelog
|
|
554
|
+
|
|
555
|
+
See [CHANGELOG.md](./CHANGELOG.md) for version history and release notes.
|
|
556
|
+
|
|
557
|
+
## License
|
|
558
|
+
|
|
559
|
+
MIT License - see LICENSE file for details.
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
563
|
+
**Built with ❤️ for content creators**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlogEditor.d.ts","sourceRoot":"","sources":["../../src/components/BlogEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAMvD,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CA4DhD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DiffOverlay.d.ts","sourceRoot":"","sources":["../../src/components/DiffOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CA6ClD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageGenerationLoader.d.ts","sourceRoot":"","sources":["../../src/components/ImageGenerationLoader.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,CASnE,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { RayoEditorProps } from '../types/editor.types';
|
|
3
|
+
/**
|
|
4
|
+
* Main Rayo Editor component - a professional-grade rich text editor
|
|
5
|
+
*
|
|
6
|
+
* This component provides a complete rich text editing experience with support for:
|
|
7
|
+
* - Full HTML editing with headings, lists, code blocks, tables, images
|
|
8
|
+
* - Diff highlighting with green (additions) and red (deletions) overlays
|
|
9
|
+
* - Review UI for accepting/rejecting changes
|
|
10
|
+
* - Image handling with generation support
|
|
11
|
+
* - Keyboard shortcuts and accessibility
|
|
12
|
+
* - Responsive design for all screen sizes
|
|
13
|
+
*
|
|
14
|
+
* The component is a drop-in replacement for BlogEditor with identical API.
|
|
15
|
+
*
|
|
16
|
+
* @component
|
|
17
|
+
* @param props - RayoEditorProps configuration object
|
|
18
|
+
* @returns React component for rich text editing
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* import { RayoEditor } from 'rayo-editor';
|
|
23
|
+
* import 'rayo-editor/styles';
|
|
24
|
+
*
|
|
25
|
+
* export function MyEditor() {
|
|
26
|
+
* const editorRef = useRef<BlogSimpleEditorRef>(null);
|
|
27
|
+
* const [content, setContent] = useState('');
|
|
28
|
+
* const [title, setTitle] = useState('');
|
|
29
|
+
*
|
|
30
|
+
* return (
|
|
31
|
+
* <RayoEditor
|
|
32
|
+
* content={content}
|
|
33
|
+
* title={title}
|
|
34
|
+
* onChange={setContent}
|
|
35
|
+
* onTitleChange={setTitle}
|
|
36
|
+
* isLoading={false}
|
|
37
|
+
* editorRef={editorRef}
|
|
38
|
+
* />
|
|
39
|
+
* );
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* With diff highlighting:
|
|
45
|
+
* ```tsx
|
|
46
|
+
* <RayoEditor
|
|
47
|
+
* content={content}
|
|
48
|
+
* title={title}
|
|
49
|
+
* onChange={setContent}
|
|
50
|
+
* onTitleChange={setTitle}
|
|
51
|
+
* showDiffs={true}
|
|
52
|
+
* pendingChanges={true}
|
|
53
|
+
* onAcceptChanges={() => console.log('Accepted')}
|
|
54
|
+
* onRejectChanges={() => console.log('Rejected')}
|
|
55
|
+
* isLoading={false}
|
|
56
|
+
* editorRef={editorRef}
|
|
57
|
+
* />
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* Read-only mode:
|
|
62
|
+
* ```tsx
|
|
63
|
+
* <RayoEditor
|
|
64
|
+
* content={publishedContent}
|
|
65
|
+
* title={publishedTitle}
|
|
66
|
+
* onChange={() => {}}
|
|
67
|
+
* readOnly={true}
|
|
68
|
+
* isLoading={false}
|
|
69
|
+
* editorRef={editorRef}
|
|
70
|
+
* />
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare const RayoEditor: React.FC<RayoEditorProps>;
|
|
74
|
+
//# sourceMappingURL=RayoEditor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RayoEditor.d.ts","sourceRoot":"","sources":["../../src/components/RayoEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AACH,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAMhD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReviewButtons.d.ts","sourceRoot":"","sources":["../../src/components/ReviewButtons.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAe1D,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAyBtD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TitleTextarea.d.ts","sourceRoot":"","sources":["../../src/components/TitleTextarea.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAyC,MAAM,OAAO,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA8BtD,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { RayoEditor } from './RayoEditor';
|
|
2
|
+
export { BlogEditor } from './BlogEditor';
|
|
3
|
+
export { TitleTextarea } from './TitleTextarea';
|
|
4
|
+
export { DiffOverlay } from './DiffOverlay';
|
|
5
|
+
export { ReviewButtons } from './ReviewButtons';
|
|
6
|
+
export { ImageGenerationLoader } from './ImageGenerationLoader';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC"}
|