eddyter 1.3.70 → 1.3.71
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 +247 -262
- package/dist/EddyterIcon.svg +24 -0
- package/dist/ImageResizer-FrE13F54.js +237 -0
- package/dist/assets/style.css +1 -1
- package/dist/components/EddyterLogo/EddyterLogo.d.ts +13 -0
- package/dist/{html2pdf.bundle-ZbgrRtz0.js → html2pdf.bundle-Bptnpoce.js} +1 -1
- package/dist/{html2pdf.bundle.min-D6BlTaug.js → html2pdf.bundle.min-CL0HFz_I.js} +1 -1
- package/dist/{index-DJEsPHep.js → index-BgjRNNca.js} +6719 -6616
- package/dist/{index-7sHS7FZi.js → index-CTVNcCfu.js} +2 -2
- package/dist/{index-BJBbBVqE.js → index-Y3Jbqb5R.js} +351 -331
- package/dist/{index-aG2ca7vV.js → index-aoU-Zn1p.js} +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/ImageResizer-CbtUYHip.js +0 -199
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Eddyter
|
|
2
2
|
|
|
3
|
-
A configurable rich text editor
|
|
3
|
+
A powerful, configurable rich text editor built on [Lexical](https://lexical.dev/) with AI capabilities, dark mode support, and API key authentication.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,44 +8,48 @@ A configurable rich text editor component with AI-powered features and API key a
|
|
|
8
8
|
npm install eddyter
|
|
9
9
|
# or
|
|
10
10
|
yarn add eddyter
|
|
11
|
+
# or
|
|
12
|
+
pnpm add eddyter
|
|
11
13
|
```
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
### Compatibility
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- AI chat integration (for premium plans)
|
|
21
|
-
- Environment-based API configuration
|
|
17
|
+
| Requirement | Version |
|
|
18
|
+
|-------------|---------|
|
|
19
|
+
| React | 18.2+ or 19.x |
|
|
20
|
+
| React DOM | 18.2+ or 19.x |
|
|
21
|
+
| Node.js | 16+ |
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## Quick Start
|
|
24
24
|
|
|
25
|
-
###
|
|
26
|
-
To ensure proper styling of the editor components including tables, you must import the package's CSS:
|
|
25
|
+
### 1. Import styles
|
|
27
26
|
|
|
28
|
-
```
|
|
29
|
-
// Import the styles in your application
|
|
27
|
+
```tsx
|
|
30
28
|
import 'eddyter/style.css';
|
|
31
29
|
```
|
|
32
30
|
|
|
33
|
-
|
|
31
|
+
> **Important:** The stylesheet is required for tables, toolbars, and all editor components to render correctly.
|
|
32
|
+
|
|
33
|
+
### 2. Get your API key
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
1. Create an account at [eddyter.com](https://www.eddyter.com/)
|
|
36
|
+
2. Navigate to [License Keys](https://www.eddyter.com/user/license-key) in your dashboard
|
|
37
|
+
3. Copy your API key
|
|
38
|
+
|
|
39
|
+
### 3. Add the editor
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
36
42
|
import React from 'react';
|
|
37
43
|
import {
|
|
38
44
|
ConfigurableEditorWithAuth,
|
|
39
45
|
EditorProvider,
|
|
40
46
|
defaultEditorConfig
|
|
41
47
|
} from 'eddyter';
|
|
42
|
-
// Import required styles
|
|
43
48
|
import 'eddyter/style.css';
|
|
44
49
|
|
|
45
50
|
function App() {
|
|
46
|
-
const apiKey =
|
|
51
|
+
const apiKey = process.env.NEXT_PUBLIC_EDITOR_API_KEY!;
|
|
47
52
|
|
|
48
|
-
// Current logged-in user for comments
|
|
49
53
|
const currentUser = {
|
|
50
54
|
id: 'user-123',
|
|
51
55
|
name: 'John Doe',
|
|
@@ -53,11 +57,6 @@ function App() {
|
|
|
53
57
|
avatar: 'https://example.com/avatar.jpg' // optional
|
|
54
58
|
};
|
|
55
59
|
|
|
56
|
-
const handleContentChange = (html) => {
|
|
57
|
-
console.log('Editor HTML content:', html);
|
|
58
|
-
// Handle the HTML content (save to state, send to server, etc.)
|
|
59
|
-
};
|
|
60
|
-
|
|
61
60
|
return (
|
|
62
61
|
<EditorProvider
|
|
63
62
|
defaultFontFamilies={defaultEditorConfig.defaultFontFamilies}
|
|
@@ -65,156 +64,235 @@ function App() {
|
|
|
65
64
|
>
|
|
66
65
|
<ConfigurableEditorWithAuth
|
|
67
66
|
apiKey={apiKey}
|
|
68
|
-
onChange={
|
|
69
|
-
initialContent="<p>
|
|
70
|
-
mentionUserList={[
|
|
71
|
-
onAuthSuccess={() => console.log('
|
|
72
|
-
onAuthError={(error) => console.error('
|
|
67
|
+
onChange={(html) => console.log('Content:', html)}
|
|
68
|
+
initialContent="<p>Start writing...</p>"
|
|
69
|
+
mentionUserList={['Alice', 'Bob', 'Charlie']}
|
|
70
|
+
onAuthSuccess={() => console.log('Editor ready!')}
|
|
71
|
+
onAuthError={(error) => console.error('Auth failed:', error)}
|
|
73
72
|
/>
|
|
74
73
|
</EditorProvider>
|
|
75
74
|
);
|
|
76
75
|
}
|
|
77
76
|
```
|
|
78
77
|
|
|
78
|
+
## Features
|
|
79
|
+
|
|
80
|
+
### Text & Formatting
|
|
81
|
+
- Bold, italic, underline, strikethrough, subscript, superscript
|
|
82
|
+
- Text color and background highlight with color picker
|
|
83
|
+
- 20+ font families with adjustable font sizes
|
|
84
|
+
- Text alignment (left, center, right, justify)
|
|
85
|
+
- Line height and letter spacing controls
|
|
86
|
+
|
|
87
|
+
### Lists & Structure
|
|
88
|
+
- Bullet lists, numbered lists (decimal, alpha, roman)
|
|
89
|
+
- Interactive checklists with strikethrough
|
|
90
|
+
- Headings (H1-H6), blockquotes
|
|
91
|
+
- Horizontal rules
|
|
92
|
+
|
|
93
|
+
### Tables
|
|
94
|
+
- Insert/delete rows and columns, merge cells
|
|
95
|
+
- Drag-to-resize columns and rows
|
|
96
|
+
- Header row styling
|
|
97
|
+
- Row striping with custom colors
|
|
98
|
+
- Right-click context menu for table actions
|
|
99
|
+
|
|
100
|
+
### Media
|
|
101
|
+
- Image upload with drag-drop and 8-point resize handles
|
|
102
|
+
- Video embed with drag-drop and paste support
|
|
103
|
+
- File attachments (downloadable files)
|
|
104
|
+
- Link insertion with floating editor
|
|
105
|
+
- Automatic link preview on hover
|
|
106
|
+
- Rich embeds for external content (YouTube, etc.)
|
|
107
|
+
|
|
108
|
+
### AI Features (Premium)
|
|
109
|
+
- AI Chat assistant for content help
|
|
110
|
+
- Smart autocomplete (AI-powered text suggestions)
|
|
111
|
+
- Real-time grammar check and corrections
|
|
112
|
+
- Text enhancement (improve, shorten, expand)
|
|
113
|
+
- Tone adjustment (formal, casual, professional)
|
|
114
|
+
- AI image generation from text prompts
|
|
115
|
+
|
|
116
|
+
### Advanced
|
|
117
|
+
- Slash commands (`/` for quick formatting)
|
|
118
|
+
- @Mentions with customizable user list
|
|
119
|
+
- Inline comments with bubble UI and sidebar
|
|
120
|
+
- Note panels (info, warning, error, success)
|
|
121
|
+
- Code blocks with syntax highlighting
|
|
122
|
+
- Interactive charts
|
|
123
|
+
- Digital signature capture
|
|
124
|
+
- Voice input / transcription
|
|
125
|
+
- Export to PDF
|
|
126
|
+
- HTML view toggle
|
|
127
|
+
- Drag-and-drop block reordering
|
|
128
|
+
- Markdown shortcuts
|
|
129
|
+
|
|
130
|
+
### Dark Mode
|
|
131
|
+
|
|
132
|
+
The editor automatically detects your app's theme:
|
|
133
|
+
- Checks for `dark` class on `<html>` or `<body>`
|
|
134
|
+
- Falls back to `prefers-color-scheme: dark` system preference
|
|
135
|
+
- Or set explicitly via the `darkMode` prop on `EditorProvider`
|
|
136
|
+
|
|
137
|
+
### Preview Mode
|
|
138
|
+
|
|
139
|
+
Display saved editor content in read-only mode with interactive features:
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
<ConfigurableEditorWithAuth
|
|
143
|
+
apiKey={apiKey}
|
|
144
|
+
mode="preview"
|
|
145
|
+
initialContent={savedHtml}
|
|
146
|
+
onPreviewClick={() => setMode('edit')}
|
|
147
|
+
previewClassName="my-preview-styles"
|
|
148
|
+
/>
|
|
149
|
+
```
|
|
150
|
+
|
|
79
151
|
## API Reference
|
|
80
152
|
|
|
81
|
-
### EditorProvider
|
|
153
|
+
### `<EditorProvider>`
|
|
82
154
|
|
|
83
|
-
Provides authentication and configuration context
|
|
155
|
+
Provides authentication and configuration context. Must wrap the editor component.
|
|
84
156
|
|
|
85
|
-
|
|
157
|
+
| Prop | Type | Required | Description |
|
|
158
|
+
|------|------|----------|-------------|
|
|
159
|
+
| `children` | `ReactNode` | Yes | Editor component to render |
|
|
160
|
+
| `defaultFontFamilies` | `string[]` | No | Font family names for the font selector |
|
|
161
|
+
| `currentUser` | `CurrentUser` | No | Current user for comments feature |
|
|
162
|
+
| `enableLinkPreview` | `boolean` | No | Enable link preview on hover (default: `true`) |
|
|
163
|
+
| `apiKey` | `string` | No | API key for link preview in read-only mode |
|
|
86
164
|
|
|
87
|
-
|
|
88
|
-
- `defaultFontFamilies`: Array of font names (optional)
|
|
89
|
-
- `currentUser`: Current logged-in user for comments (optional) - Object with `id`, `name`, `email`, and optional `avatar`
|
|
90
|
-
- `enableLinkPreview`: Enable automatic link preview on hover (optional, default: `true`)
|
|
91
|
-
- `apiKey`: API key for authentication (optional) - Required only if you need link preview to work immediately without opening the editor first
|
|
165
|
+
#### CurrentUser Type
|
|
92
166
|
|
|
93
|
-
|
|
167
|
+
```ts
|
|
168
|
+
interface CurrentUser {
|
|
169
|
+
id: string;
|
|
170
|
+
name: string;
|
|
171
|
+
email: string;
|
|
172
|
+
avatar?: string;
|
|
173
|
+
}
|
|
174
|
+
```
|
|
94
175
|
|
|
95
|
-
|
|
176
|
+
### `<ConfigurableEditorWithAuth>`
|
|
96
177
|
|
|
97
|
-
|
|
178
|
+
The main editor component with authentication.
|
|
98
179
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
180
|
+
| Prop | Type | Required | Description |
|
|
181
|
+
|------|------|----------|-------------|
|
|
182
|
+
| `apiKey` | `string` | Yes | Your API key for authentication |
|
|
183
|
+
| `initialContent` | `string` | No | Initial HTML content |
|
|
184
|
+
| `onChange` | `(html: string) => void` | No | Content change callback |
|
|
185
|
+
| `defaultFontFamilies` | `string[]` | No | Font names for the font selector |
|
|
186
|
+
| `mentionUserList` | `string[]` | No | Usernames for @mention feature |
|
|
187
|
+
| `onAuthSuccess` | `() => void` | No | Called when authentication succeeds |
|
|
188
|
+
| `onAuthError` | `(error: string) => void` | No | Called when authentication fails |
|
|
189
|
+
| `customVerifyKey` | `(key: string) => Promise<ApiResponse>` | No | Custom key verification function |
|
|
190
|
+
| `mode` | `"edit" \| "preview"` | No | Editor mode (default: `"edit"`) |
|
|
191
|
+
| `previewClassName` | `string` | No | CSS class for preview container |
|
|
192
|
+
| `previewStyle` | `React.CSSProperties` | No | Inline styles for preview container |
|
|
193
|
+
| `onPreviewClick` | `() => void` | No | Click handler for preview mode |
|
|
194
|
+
| `enableReactNativeBridge` | `boolean` | No | Enable React Native WebView bridge |
|
|
195
|
+
| `onEditorReady` | `() => void` | No | Called when editor is fully loaded |
|
|
196
|
+
| `onFocus` | `() => void` | No | Called on editor focus |
|
|
197
|
+
| `onBlur` | `() => void` | No | Called on editor blur |
|
|
198
|
+
| `onHeightChange` | `(height: number) => void` | No | Called when editor height changes |
|
|
107
199
|
|
|
108
200
|
## Examples
|
|
109
201
|
|
|
110
|
-
### Basic Editor
|
|
202
|
+
### Basic Editor
|
|
111
203
|
|
|
112
|
-
```
|
|
113
|
-
import React from 'react';
|
|
204
|
+
```tsx
|
|
114
205
|
import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter';
|
|
115
206
|
import 'eddyter/style.css';
|
|
116
207
|
|
|
117
|
-
function
|
|
208
|
+
export default function BasicEditor() {
|
|
118
209
|
return (
|
|
119
210
|
<EditorProvider>
|
|
120
211
|
<ConfigurableEditorWithAuth
|
|
121
212
|
apiKey="your-api-key"
|
|
122
|
-
onAuthSuccess={() => console.log('
|
|
123
|
-
onAuthError={(error) => console.error(error)}
|
|
213
|
+
onAuthSuccess={() => console.log('Ready!')}
|
|
124
214
|
/>
|
|
125
215
|
</EditorProvider>
|
|
126
216
|
);
|
|
127
217
|
}
|
|
128
218
|
```
|
|
129
219
|
|
|
130
|
-
### Editor with
|
|
220
|
+
### Editor with State Management
|
|
131
221
|
|
|
132
|
-
```
|
|
133
|
-
import
|
|
222
|
+
```tsx
|
|
223
|
+
import { useState } from 'react';
|
|
134
224
|
import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter';
|
|
135
225
|
import 'eddyter/style.css';
|
|
136
226
|
|
|
137
|
-
function
|
|
138
|
-
const [
|
|
139
|
-
|
|
140
|
-
// Current user (typically from your auth system)
|
|
141
|
-
const currentUser = {
|
|
142
|
-
id: 'user-456',
|
|
143
|
-
name: 'Jane Smith',
|
|
144
|
-
email: 'jane@example.com'
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const handleContentChange = (html) => {
|
|
148
|
-
setEditorContent(html);
|
|
149
|
-
console.log('Current content:', html);
|
|
150
|
-
// You can also save to localStorage, send to API, etc.
|
|
151
|
-
};
|
|
227
|
+
export default function EditorWithState() {
|
|
228
|
+
const [content, setContent] = useState('<p>Start writing...</p>');
|
|
152
229
|
|
|
153
|
-
const handleSave = () => {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const loadSavedContent = () => {
|
|
160
|
-
const saved = '<p>Start writing your content here...</p>';
|
|
161
|
-
return saved;
|
|
230
|
+
const handleSave = async () => {
|
|
231
|
+
await fetch('/api/save', {
|
|
232
|
+
method: 'POST',
|
|
233
|
+
headers: { 'Content-Type': 'application/json' },
|
|
234
|
+
body: JSON.stringify({ content })
|
|
235
|
+
});
|
|
162
236
|
};
|
|
163
237
|
|
|
164
238
|
return (
|
|
165
239
|
<div>
|
|
166
|
-
<EditorProvider
|
|
240
|
+
<EditorProvider>
|
|
167
241
|
<ConfigurableEditorWithAuth
|
|
168
242
|
apiKey="your-api-key"
|
|
169
|
-
initialContent={
|
|
170
|
-
onChange={
|
|
171
|
-
defaultFontFamilies={['Arial', 'Helvetica', 'Times New Roman']}
|
|
172
|
-
mentionUserList={['Alice', 'Bob', 'Charlie']}
|
|
173
|
-
onAuthSuccess={() => console.log('Ready to edit!')}
|
|
174
|
-
onAuthError={(error) => console.error('Auth failed:', error)}
|
|
243
|
+
initialContent={content}
|
|
244
|
+
onChange={setContent}
|
|
175
245
|
/>
|
|
176
246
|
</EditorProvider>
|
|
247
|
+
<button onClick={handleSave}>Save</button>
|
|
248
|
+
</div>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
```
|
|
177
252
|
|
|
178
|
-
|
|
179
|
-
Save Content
|
|
180
|
-
</button>
|
|
253
|
+
### Editor with Comments & Mentions
|
|
181
254
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
255
|
+
```tsx
|
|
256
|
+
import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter';
|
|
257
|
+
import 'eddyter/style.css';
|
|
258
|
+
|
|
259
|
+
export default function EditorWithComments({ user }) {
|
|
260
|
+
const currentUser = {
|
|
261
|
+
id: user.id,
|
|
262
|
+
name: user.name,
|
|
263
|
+
email: user.email,
|
|
264
|
+
avatar: user.avatarUrl
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<EditorProvider currentUser={currentUser}>
|
|
269
|
+
<ConfigurableEditorWithAuth
|
|
270
|
+
apiKey="your-api-key"
|
|
271
|
+
mentionUserList={['Alice', 'Bob', 'Charlie']}
|
|
272
|
+
/>
|
|
273
|
+
</EditorProvider>
|
|
187
274
|
);
|
|
188
275
|
}
|
|
189
276
|
```
|
|
190
277
|
|
|
191
278
|
### Custom API Key Verification
|
|
192
279
|
|
|
193
|
-
```
|
|
194
|
-
import React from 'react';
|
|
280
|
+
```tsx
|
|
195
281
|
import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter';
|
|
196
282
|
import 'eddyter/style.css';
|
|
197
283
|
|
|
198
|
-
function
|
|
199
|
-
const customVerifyKey = async (apiKey) => {
|
|
284
|
+
export default function EditorWithCustomAuth() {
|
|
285
|
+
const customVerifyKey = async (apiKey: string) => {
|
|
200
286
|
try {
|
|
201
287
|
const response = await fetch('/api/verify-key', {
|
|
202
288
|
method: 'POST',
|
|
203
289
|
headers: { 'Content-Type': 'application/json' },
|
|
204
290
|
body: JSON.stringify({ apiKey })
|
|
205
291
|
});
|
|
206
|
-
|
|
207
292
|
const data = await response.json();
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
message: data.message || 'API key verified'
|
|
212
|
-
};
|
|
213
|
-
} catch (error) {
|
|
214
|
-
return {
|
|
215
|
-
success: false,
|
|
216
|
-
message: 'Failed to verify API key'
|
|
217
|
-
};
|
|
293
|
+
return { success: data.valid, message: data.message || 'Verified' };
|
|
294
|
+
} catch {
|
|
295
|
+
return { success: false, message: 'Verification failed' };
|
|
218
296
|
}
|
|
219
297
|
};
|
|
220
298
|
|
|
@@ -223,71 +301,43 @@ function App() {
|
|
|
223
301
|
<ConfigurableEditorWithAuth
|
|
224
302
|
apiKey="your-api-key"
|
|
225
303
|
customVerifyKey={customVerifyKey}
|
|
226
|
-
onChange={(html) => console.log('Content changed:', html)}
|
|
227
304
|
/>
|
|
228
305
|
</EditorProvider>
|
|
229
306
|
);
|
|
230
307
|
}
|
|
231
308
|
```
|
|
232
309
|
|
|
233
|
-
## Link Preview
|
|
234
|
-
|
|
235
|
-
Eddyter includes automatic link preview on hover. When users hover over links in content wrapped by `EditorProvider`, a preview popup shows the link's title, description, and image.
|
|
236
|
-
|
|
237
|
-
### How It Works
|
|
238
|
-
|
|
239
|
-
- **Inside the editor**: Link preview works automatically after authentication
|
|
240
|
-
- **Outside the editor** (preview mode, saved content): Link preview works if the user has opened the editor at least once in the session (authentication stores the API key)
|
|
241
|
-
|
|
242
|
-
### Preview-Only Scenarios
|
|
243
|
-
|
|
244
|
-
If your application displays saved content without ever opening the editor (e.g., a read-only view), you need to pass `apiKey` to `EditorProvider`:
|
|
245
|
-
|
|
246
|
-
```jsx
|
|
247
|
-
import React from 'react';
|
|
248
|
-
import { EditorProvider } from 'eddyter';
|
|
249
|
-
import 'eddyter/style.css';
|
|
310
|
+
## Link Preview
|
|
250
311
|
|
|
251
|
-
|
|
252
|
-
return (
|
|
253
|
-
<EditorProvider apiKey="your-api-key">
|
|
254
|
-
<div dangerouslySetInnerHTML={{ __html: savedHtml }} />
|
|
255
|
-
</EditorProvider>
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
```
|
|
312
|
+
The editor includes automatic link preview on hover.
|
|
259
313
|
|
|
260
|
-
|
|
314
|
+
- **Inside the editor**: Works automatically after authentication
|
|
315
|
+
- **Read-only content**: Pass `apiKey` to `EditorProvider`
|
|
316
|
+
- **Disable**: Set `enableLinkPreview={false}` on `EditorProvider`
|
|
261
317
|
|
|
262
|
-
|
|
318
|
+
```tsx
|
|
319
|
+
// Read-only content with link preview
|
|
320
|
+
<EditorProvider apiKey="your-api-key">
|
|
321
|
+
<div dangerouslySetInnerHTML={{ __html: savedHtml }} />
|
|
322
|
+
</EditorProvider>
|
|
263
323
|
|
|
264
|
-
|
|
324
|
+
// Disable link preview
|
|
265
325
|
<EditorProvider enableLinkPreview={false}>
|
|
266
|
-
{/*
|
|
326
|
+
{/* content */}
|
|
267
327
|
</EditorProvider>
|
|
268
328
|
```
|
|
269
329
|
|
|
270
|
-
---
|
|
271
|
-
|
|
272
330
|
## React Native Integration
|
|
273
331
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
### Prerequisites
|
|
332
|
+
Use Eddyter in React Native via WebView by loading a deployed version of the editor.
|
|
277
333
|
|
|
278
334
|
```bash
|
|
279
335
|
npm install react-native-webview
|
|
280
|
-
# or
|
|
281
|
-
yarn add react-native-webview
|
|
282
336
|
```
|
|
283
337
|
|
|
284
|
-
### RichTextEditor Component
|
|
285
|
-
|
|
286
|
-
Create a reusable `RichTextEditor` component that wraps the WebView:
|
|
287
|
-
|
|
288
338
|
```tsx
|
|
289
339
|
import React, { useRef, useState, useCallback } from 'react';
|
|
290
|
-
import { View, ActivityIndicator,
|
|
340
|
+
import { View, ActivityIndicator, KeyboardAvoidingView, Platform } from 'react-native';
|
|
291
341
|
import { WebView, WebViewMessageEvent } from 'react-native-webview';
|
|
292
342
|
|
|
293
343
|
interface RichTextEditorProps {
|
|
@@ -302,11 +352,6 @@ interface RichTextEditorProps {
|
|
|
302
352
|
onAuthError?: (error: string) => void;
|
|
303
353
|
}
|
|
304
354
|
|
|
305
|
-
interface WebViewMessage {
|
|
306
|
-
type: string;
|
|
307
|
-
payload?: Record<string, unknown>;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
355
|
export const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
|
311
356
|
editorBaseUrl,
|
|
312
357
|
apiKey,
|
|
@@ -320,7 +365,6 @@ export const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
|
|
320
365
|
}) => {
|
|
321
366
|
const webViewRef = useRef<WebView>(null);
|
|
322
367
|
const [isLoading, setIsLoading] = useState(true);
|
|
323
|
-
const [error, setError] = useState<string | null>(null);
|
|
324
368
|
|
|
325
369
|
const buildEditorUrl = () => {
|
|
326
370
|
const baseUrl = editorBaseUrl.replace(/\/$/, '');
|
|
@@ -332,33 +376,25 @@ export const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
|
|
332
376
|
|
|
333
377
|
const handleMessage = useCallback((event: WebViewMessageEvent) => {
|
|
334
378
|
try {
|
|
335
|
-
const message
|
|
336
|
-
|
|
379
|
+
const message = JSON.parse(event.nativeEvent.data);
|
|
337
380
|
switch (message.type) {
|
|
338
381
|
case 'EDITOR_READY':
|
|
339
382
|
setIsLoading(false);
|
|
340
383
|
onReady?.();
|
|
341
|
-
// Send initial content after editor is ready
|
|
342
384
|
if (initialContent && webViewRef.current) {
|
|
343
385
|
webViewRef.current.postMessage(
|
|
344
|
-
JSON.stringify({
|
|
345
|
-
type: 'SET_CONTENT',
|
|
346
|
-
payload: { content: initialContent },
|
|
347
|
-
})
|
|
386
|
+
JSON.stringify({ type: 'SET_CONTENT', payload: { content: initialContent } })
|
|
348
387
|
);
|
|
349
388
|
}
|
|
350
389
|
break;
|
|
351
|
-
|
|
352
390
|
case 'CONTENT_CHANGE':
|
|
353
|
-
onChange?.(message.payload?.content
|
|
391
|
+
onChange?.(message.payload?.content || '');
|
|
354
392
|
break;
|
|
355
|
-
|
|
356
393
|
case 'AUTH_SUCCESS':
|
|
357
394
|
onAuthSuccess?.();
|
|
358
395
|
break;
|
|
359
|
-
|
|
360
396
|
case 'AUTH_ERROR':
|
|
361
|
-
onAuthError?.(message.payload?.error
|
|
397
|
+
onAuthError?.(message.payload?.error);
|
|
362
398
|
break;
|
|
363
399
|
}
|
|
364
400
|
} catch (e) {
|
|
@@ -366,121 +402,70 @@ export const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
|
|
366
402
|
}
|
|
367
403
|
}, [onChange, onReady, onAuthSuccess, onAuthError, initialContent]);
|
|
368
404
|
|
|
369
|
-
const handleError = useCallback((syntheticEvent: any) => {
|
|
370
|
-
const { nativeEvent } = syntheticEvent;
|
|
371
|
-
setError(nativeEvent.description || 'Failed to load editor');
|
|
372
|
-
setIsLoading(false);
|
|
373
|
-
}, []);
|
|
374
|
-
|
|
375
|
-
if (error) {
|
|
376
|
-
return (
|
|
377
|
-
<View style={styles.errorContainer}>
|
|
378
|
-
<Text style={styles.errorText}>Failed to load editor</Text>
|
|
379
|
-
<Text style={styles.errorDetail}>{error}</Text>
|
|
380
|
-
</View>
|
|
381
|
-
);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
405
|
return (
|
|
385
|
-
<View style={[
|
|
406
|
+
<View style={[{ flex: 1 }, style]}>
|
|
386
407
|
<WebView
|
|
387
408
|
ref={webViewRef}
|
|
388
409
|
source={{ uri: buildEditorUrl() }}
|
|
389
|
-
style={
|
|
410
|
+
style={{ flex: 1 }}
|
|
390
411
|
onMessage={handleMessage}
|
|
391
|
-
onError={handleError}
|
|
392
412
|
javaScriptEnabled={true}
|
|
393
413
|
domStorageEnabled={true}
|
|
394
|
-
startInLoadingState={false}
|
|
395
|
-
scalesPageToFit={true}
|
|
396
|
-
allowsInlineMediaPlayback={true}
|
|
397
414
|
keyboardDisplayRequiresUserAction={false}
|
|
398
415
|
/>
|
|
399
416
|
{isLoading && (
|
|
400
|
-
<View style={
|
|
401
|
-
<ActivityIndicator size="large"
|
|
417
|
+
<View style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center' }}>
|
|
418
|
+
<ActivityIndicator size="large" />
|
|
402
419
|
</View>
|
|
403
420
|
)}
|
|
404
421
|
</View>
|
|
405
422
|
);
|
|
406
423
|
};
|
|
407
|
-
|
|
408
|
-
const styles = StyleSheet.create({
|
|
409
|
-
container: { flex: 1 },
|
|
410
|
-
webview: { flex: 1, backgroundColor: 'transparent' },
|
|
411
|
-
loadingOverlay: {
|
|
412
|
-
...StyleSheet.absoluteFillObject,
|
|
413
|
-
justifyContent: 'center',
|
|
414
|
-
alignItems: 'center',
|
|
415
|
-
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
|
416
|
-
},
|
|
417
|
-
errorContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 },
|
|
418
|
-
errorText: { fontSize: 18, fontWeight: 'bold', color: '#FF3B30' },
|
|
419
|
-
errorDetail: { fontSize: 14, color: '#666', marginTop: 8, textAlign: 'center' },
|
|
420
|
-
});
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
### Usage Example
|
|
424
|
-
|
|
425
|
-
```tsx
|
|
426
|
-
import React, { useState } from 'react';
|
|
427
|
-
import { View, KeyboardAvoidingView, Platform } from 'react-native';
|
|
428
|
-
import { RichTextEditor } from './components/RichTextEditor';
|
|
429
|
-
|
|
430
|
-
const EDITOR_CONFIG = {
|
|
431
|
-
editorBaseUrl: 'https://your-deployed-editor-url.com',
|
|
432
|
-
apiKey: 'your-api-key',
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
function NoteEditorScreen() {
|
|
436
|
-
const [content, setContent] = useState('');
|
|
437
|
-
|
|
438
|
-
return (
|
|
439
|
-
<KeyboardAvoidingView
|
|
440
|
-
style={{ flex: 1 }}
|
|
441
|
-
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
442
|
-
>
|
|
443
|
-
<RichTextEditor
|
|
444
|
-
editorBaseUrl={EDITOR_CONFIG.editorBaseUrl}
|
|
445
|
-
apiKey={EDITOR_CONFIG.apiKey}
|
|
446
|
-
theme="light"
|
|
447
|
-
initialContent="<p>Start writing...</p>"
|
|
448
|
-
onChange={setContent}
|
|
449
|
-
onReady={() => console.log('Editor ready')}
|
|
450
|
-
onAuthSuccess={() => console.log('Authenticated')}
|
|
451
|
-
onAuthError={(error) => console.error('Auth failed:', error)}
|
|
452
|
-
/>
|
|
453
|
-
</KeyboardAvoidingView>
|
|
454
|
-
);
|
|
455
|
-
}
|
|
456
424
|
```
|
|
457
425
|
|
|
458
426
|
### Message Protocol
|
|
459
427
|
|
|
460
|
-
The editor and React Native communicate via `postMessage`. Here are the supported message types:
|
|
461
|
-
|
|
462
428
|
| Message Type | Direction | Description |
|
|
463
|
-
|
|
429
|
+
|---|---|---|
|
|
464
430
|
| `EDITOR_READY` | Editor → RN | Editor has finished loading |
|
|
465
|
-
| `CONTENT_CHANGE` | Editor → RN | Content was modified (
|
|
466
|
-
| `AUTH_SUCCESS` | Editor → RN |
|
|
467
|
-
| `AUTH_ERROR` | Editor → RN | Authentication failed (
|
|
468
|
-
| `SET_CONTENT` | RN → Editor | Set editor content (
|
|
431
|
+
| `CONTENT_CHANGE` | Editor → RN | Content was modified (`{ content: string }`) |
|
|
432
|
+
| `AUTH_SUCCESS` | Editor → RN | Authentication succeeded |
|
|
433
|
+
| `AUTH_ERROR` | Editor → RN | Authentication failed (`{ error: string }`) |
|
|
434
|
+
| `SET_CONTENT` | RN → Editor | Set editor content (`{ content: string }`) |
|
|
469
435
|
|
|
470
|
-
|
|
436
|
+
## Exports
|
|
471
437
|
|
|
472
|
-
|
|
438
|
+
```ts
|
|
439
|
+
// Components
|
|
440
|
+
import {
|
|
441
|
+
ConfigurableEditorWithAuth, // Main editor with auth
|
|
442
|
+
ConfigurableEditor, // Editor without auth wrapper
|
|
443
|
+
EditorProvider, // Context provider
|
|
444
|
+
LinkPreviewHover, // Standalone link preview component
|
|
445
|
+
} from 'eddyter';
|
|
473
446
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
447
|
+
// Hooks & utilities
|
|
448
|
+
import {
|
|
449
|
+
useEditor, // Access editor context
|
|
450
|
+
useHtmlView, // Access HTML view state
|
|
451
|
+
verifyApiKey, // Verify API key programmatically
|
|
452
|
+
useReactNativeBridge, // React Native bridge hook
|
|
453
|
+
isReactNativeWebView, // Check if running in RN WebView
|
|
454
|
+
} from 'eddyter';
|
|
482
455
|
|
|
483
|
-
|
|
456
|
+
// Config
|
|
457
|
+
import { defaultEditorConfig } from 'eddyter';
|
|
458
|
+
|
|
459
|
+
// Types
|
|
460
|
+
import type {
|
|
461
|
+
CurrentUser,
|
|
462
|
+
EditorConfigTypes,
|
|
463
|
+
LinkPreviewHoverProps,
|
|
464
|
+
ReactNativeBridgeConfig,
|
|
465
|
+
ReactNativeMessage,
|
|
466
|
+
ReactNativeMessageType,
|
|
467
|
+
} from 'eddyter';
|
|
468
|
+
```
|
|
484
469
|
|
|
485
470
|
## License
|
|
486
471
|
|
|
@@ -490,4 +475,4 @@ Eddyter is **proprietary software**.
|
|
|
490
475
|
- **Commercial use requires a paid license**
|
|
491
476
|
- SaaS, redistribution, and competing products are prohibited without permission
|
|
492
477
|
|
|
493
|
-
For commercial licensing,
|
|
478
|
+
For commercial licensing, visit [eddyter.com](https://www.eddyter.com/)
|