eddyter 1.3.73 → 1.3.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +24 -24
- package/README.md +478 -478
- package/dist/EddyterIcon.svg +24 -24
- package/dist/{ImageResizer-FrE13F54.js → ImageResizer-o0eMm1Mg.js} +56 -50
- package/dist/api/ai/aiAgentService.d.ts +22 -1
- package/dist/api/config/endpoints.d.ts +3 -0
- package/dist/assets/style.css +1 -1
- package/dist/{babel-CCPWkrf4.js → babel-B9hn44Wo.js} +726 -1302
- package/dist/components/VideoView/index.d.ts +3 -1
- package/dist/constants.d.ts +2 -1
- package/dist/{estree-CxUPh9wa.js → estree-CocPn_Md.js} +529 -917
- package/dist/hooks/useBlockFormat.d.ts +1 -0
- package/dist/{html-CmniStvG.js → html-CxCicOef.js} +350 -589
- package/dist/{html2pdf.bundle-CQue4YDW.js → html2pdf.bundle-CVq-OpZt.js} +2778 -3797
- package/dist/{html2pdf.bundle.min-BEj2NT3U.js → html2pdf.bundle.min-BxzIoi3T.js} +3405 -5221
- package/dist/{index-CX3cfSUQ.js → index-CtPRZTab.js} +27 -18
- package/dist/index-Cuv9ugJL.js +381 -0
- package/dist/{index-CfRDm1jv.js → index-DxEP36zG.js} +12228 -12186
- package/dist/{index-BO5ICrpG.js → index-eRyVFO7x.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/{markdown-B0mEGGfQ.js → markdown-BUjgWFLu.js} +578 -1015
- package/dist/nodes/GeneratingImageNode.d.ts +30 -0
- package/dist/nodes/VideoNode.d.ts +10 -3
- package/dist/{postcss-B0bxXf7u.js → postcss-CGIcwj_g.js} +615 -1065
- package/dist/{standalone-DmuJV5rn.js → standalone-C0qguT38.js} +350 -596
- package/dist/{typescript-DZlC_9M8.js → typescript-BM7wk6k-.js} +1114 -1806
- package/dist/ui/Icons.d.ts +2 -1
- package/package.json +149 -152
- package/dist/index-Buj5fA92.js +0 -274
package/README.md
CHANGED
|
@@ -1,478 +1,478 @@
|
|
|
1
|
-
# Eddyter
|
|
2
|
-
|
|
3
|
-
A powerful, configurable rich text editor built on [Lexical](https://lexical.dev/) with AI capabilities, dark mode support, and API key authentication.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install eddyter
|
|
9
|
-
# or
|
|
10
|
-
yarn add eddyter
|
|
11
|
-
# or
|
|
12
|
-
pnpm add eddyter
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
### Compatibility
|
|
16
|
-
|
|
17
|
-
| Requirement | Version |
|
|
18
|
-
|-------------|---------|
|
|
19
|
-
| React | 18.2+ or 19.x |
|
|
20
|
-
| React DOM | 18.2+ or 19.x |
|
|
21
|
-
| Node.js | 16+ |
|
|
22
|
-
|
|
23
|
-
## Quick Start
|
|
24
|
-
|
|
25
|
-
### 1. Import styles
|
|
26
|
-
|
|
27
|
-
```tsx
|
|
28
|
-
import 'eddyter/style.css';
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
> **Important:** The stylesheet is required for tables, toolbars, and all editor components to render correctly.
|
|
32
|
-
|
|
33
|
-
### 2. Get your API key
|
|
34
|
-
|
|
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
|
|
42
|
-
import React from 'react';
|
|
43
|
-
import {
|
|
44
|
-
ConfigurableEditorWithAuth,
|
|
45
|
-
EditorProvider,
|
|
46
|
-
defaultEditorConfig
|
|
47
|
-
} from 'eddyter';
|
|
48
|
-
import 'eddyter/style.css';
|
|
49
|
-
|
|
50
|
-
function App() {
|
|
51
|
-
const apiKey = process.env.NEXT_PUBLIC_EDITOR_API_KEY!;
|
|
52
|
-
|
|
53
|
-
const currentUser = {
|
|
54
|
-
id: 'user-123',
|
|
55
|
-
name: 'John Doe',
|
|
56
|
-
email: 'john@example.com',
|
|
57
|
-
avatar: 'https://example.com/avatar.jpg' // optional
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
<EditorProvider
|
|
62
|
-
defaultFontFamilies={defaultEditorConfig.defaultFontFamilies}
|
|
63
|
-
currentUser={currentUser}
|
|
64
|
-
>
|
|
65
|
-
<ConfigurableEditorWithAuth
|
|
66
|
-
apiKey={apiKey}
|
|
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)}
|
|
72
|
-
/>
|
|
73
|
-
</EditorProvider>
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
```
|
|
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
|
-
|
|
151
|
-
## API Reference
|
|
152
|
-
|
|
153
|
-
### `<EditorProvider>`
|
|
154
|
-
|
|
155
|
-
Provides authentication and configuration context. Must wrap the editor component.
|
|
156
|
-
|
|
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 |
|
|
164
|
-
|
|
165
|
-
#### CurrentUser Type
|
|
166
|
-
|
|
167
|
-
```ts
|
|
168
|
-
interface CurrentUser {
|
|
169
|
-
id: string;
|
|
170
|
-
name: string;
|
|
171
|
-
email: string;
|
|
172
|
-
avatar?: string;
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### `<ConfigurableEditorWithAuth>`
|
|
177
|
-
|
|
178
|
-
The main editor component with authentication.
|
|
179
|
-
|
|
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 |
|
|
199
|
-
|
|
200
|
-
## Examples
|
|
201
|
-
|
|
202
|
-
### Basic Editor
|
|
203
|
-
|
|
204
|
-
```tsx
|
|
205
|
-
import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter';
|
|
206
|
-
import 'eddyter/style.css';
|
|
207
|
-
|
|
208
|
-
export default function BasicEditor() {
|
|
209
|
-
return (
|
|
210
|
-
<EditorProvider>
|
|
211
|
-
<ConfigurableEditorWithAuth
|
|
212
|
-
apiKey="your-api-key"
|
|
213
|
-
onAuthSuccess={() => console.log('Ready!')}
|
|
214
|
-
/>
|
|
215
|
-
</EditorProvider>
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
### Editor with State Management
|
|
221
|
-
|
|
222
|
-
```tsx
|
|
223
|
-
import { useState } from 'react';
|
|
224
|
-
import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter';
|
|
225
|
-
import 'eddyter/style.css';
|
|
226
|
-
|
|
227
|
-
export default function EditorWithState() {
|
|
228
|
-
const [content, setContent] = useState('<p>Start writing...</p>');
|
|
229
|
-
|
|
230
|
-
const handleSave = async () => {
|
|
231
|
-
await fetch('/api/save', {
|
|
232
|
-
method: 'POST',
|
|
233
|
-
headers: { 'Content-Type': 'application/json' },
|
|
234
|
-
body: JSON.stringify({ content })
|
|
235
|
-
});
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
return (
|
|
239
|
-
<div>
|
|
240
|
-
<EditorProvider>
|
|
241
|
-
<ConfigurableEditorWithAuth
|
|
242
|
-
apiKey="your-api-key"
|
|
243
|
-
initialContent={content}
|
|
244
|
-
onChange={setContent}
|
|
245
|
-
/>
|
|
246
|
-
</EditorProvider>
|
|
247
|
-
<button onClick={handleSave}>Save</button>
|
|
248
|
-
</div>
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
### Editor with Comments & Mentions
|
|
254
|
-
|
|
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>
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
### Custom API Key Verification
|
|
279
|
-
|
|
280
|
-
```tsx
|
|
281
|
-
import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter';
|
|
282
|
-
import 'eddyter/style.css';
|
|
283
|
-
|
|
284
|
-
export default function EditorWithCustomAuth() {
|
|
285
|
-
const customVerifyKey = async (apiKey: string) => {
|
|
286
|
-
try {
|
|
287
|
-
const response = await fetch('/api/verify-key', {
|
|
288
|
-
method: 'POST',
|
|
289
|
-
headers: { 'Content-Type': 'application/json' },
|
|
290
|
-
body: JSON.stringify({ apiKey })
|
|
291
|
-
});
|
|
292
|
-
const data = await response.json();
|
|
293
|
-
return { success: data.valid, message: data.message || 'Verified' };
|
|
294
|
-
} catch {
|
|
295
|
-
return { success: false, message: 'Verification failed' };
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
return (
|
|
300
|
-
<EditorProvider>
|
|
301
|
-
<ConfigurableEditorWithAuth
|
|
302
|
-
apiKey="your-api-key"
|
|
303
|
-
customVerifyKey={customVerifyKey}
|
|
304
|
-
/>
|
|
305
|
-
</EditorProvider>
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
## Link Preview
|
|
311
|
-
|
|
312
|
-
The editor includes automatic link preview on hover.
|
|
313
|
-
|
|
314
|
-
- **Inside the editor**: Works automatically after authentication
|
|
315
|
-
- **Read-only content**: Pass `apiKey` to `EditorProvider`
|
|
316
|
-
- **Disable**: Set `enableLinkPreview={false}` on `EditorProvider`
|
|
317
|
-
|
|
318
|
-
```tsx
|
|
319
|
-
// Read-only content with link preview
|
|
320
|
-
<EditorProvider apiKey="your-api-key">
|
|
321
|
-
<div dangerouslySetInnerHTML={{ __html: savedHtml }} />
|
|
322
|
-
</EditorProvider>
|
|
323
|
-
|
|
324
|
-
// Disable link preview
|
|
325
|
-
<EditorProvider enableLinkPreview={false}>
|
|
326
|
-
{/* content */}
|
|
327
|
-
</EditorProvider>
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
## React Native Integration
|
|
331
|
-
|
|
332
|
-
Use Eddyter in React Native via WebView by loading a deployed version of the editor.
|
|
333
|
-
|
|
334
|
-
```bash
|
|
335
|
-
npm install react-native-webview
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
```tsx
|
|
339
|
-
import React, { useRef, useState, useCallback } from 'react';
|
|
340
|
-
import { View, ActivityIndicator, KeyboardAvoidingView, Platform } from 'react-native';
|
|
341
|
-
import { WebView, WebViewMessageEvent } from 'react-native-webview';
|
|
342
|
-
|
|
343
|
-
interface RichTextEditorProps {
|
|
344
|
-
editorBaseUrl: string;
|
|
345
|
-
apiKey: string;
|
|
346
|
-
initialContent?: string;
|
|
347
|
-
theme?: 'light' | 'dark';
|
|
348
|
-
style?: object;
|
|
349
|
-
onChange?: (content: string) => void;
|
|
350
|
-
onReady?: () => void;
|
|
351
|
-
onAuthSuccess?: () => void;
|
|
352
|
-
onAuthError?: (error: string) => void;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
export const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
|
356
|
-
editorBaseUrl,
|
|
357
|
-
apiKey,
|
|
358
|
-
initialContent,
|
|
359
|
-
theme = 'light',
|
|
360
|
-
style,
|
|
361
|
-
onChange,
|
|
362
|
-
onReady,
|
|
363
|
-
onAuthSuccess,
|
|
364
|
-
onAuthError,
|
|
365
|
-
}) => {
|
|
366
|
-
const webViewRef = useRef<WebView>(null);
|
|
367
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
368
|
-
|
|
369
|
-
const buildEditorUrl = () => {
|
|
370
|
-
const baseUrl = editorBaseUrl.replace(/\/$/, '');
|
|
371
|
-
const params = new URLSearchParams();
|
|
372
|
-
if (apiKey) params.append('apiKey', apiKey);
|
|
373
|
-
if (theme) params.append('theme', theme);
|
|
374
|
-
return `${baseUrl}?${params.toString()}`;
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
const handleMessage = useCallback((event: WebViewMessageEvent) => {
|
|
378
|
-
try {
|
|
379
|
-
const message = JSON.parse(event.nativeEvent.data);
|
|
380
|
-
switch (message.type) {
|
|
381
|
-
case 'EDITOR_READY':
|
|
382
|
-
setIsLoading(false);
|
|
383
|
-
onReady?.();
|
|
384
|
-
if (initialContent && webViewRef.current) {
|
|
385
|
-
webViewRef.current.postMessage(
|
|
386
|
-
JSON.stringify({ type: 'SET_CONTENT', payload: { content: initialContent } })
|
|
387
|
-
);
|
|
388
|
-
}
|
|
389
|
-
break;
|
|
390
|
-
case 'CONTENT_CHANGE':
|
|
391
|
-
onChange?.(message.payload?.content || '');
|
|
392
|
-
break;
|
|
393
|
-
case 'AUTH_SUCCESS':
|
|
394
|
-
onAuthSuccess?.();
|
|
395
|
-
break;
|
|
396
|
-
case 'AUTH_ERROR':
|
|
397
|
-
onAuthError?.(message.payload?.error);
|
|
398
|
-
break;
|
|
399
|
-
}
|
|
400
|
-
} catch (e) {
|
|
401
|
-
console.warn('[RichTextEditor] Failed to parse message:', e);
|
|
402
|
-
}
|
|
403
|
-
}, [onChange, onReady, onAuthSuccess, onAuthError, initialContent]);
|
|
404
|
-
|
|
405
|
-
return (
|
|
406
|
-
<View style={[{ flex: 1 }, style]}>
|
|
407
|
-
<WebView
|
|
408
|
-
ref={webViewRef}
|
|
409
|
-
source={{ uri: buildEditorUrl() }}
|
|
410
|
-
style={{ flex: 1 }}
|
|
411
|
-
onMessage={handleMessage}
|
|
412
|
-
javaScriptEnabled={true}
|
|
413
|
-
domStorageEnabled={true}
|
|
414
|
-
keyboardDisplayRequiresUserAction={false}
|
|
415
|
-
/>
|
|
416
|
-
{isLoading && (
|
|
417
|
-
<View style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center' }}>
|
|
418
|
-
<ActivityIndicator size="large" />
|
|
419
|
-
</View>
|
|
420
|
-
)}
|
|
421
|
-
</View>
|
|
422
|
-
);
|
|
423
|
-
};
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
### Message Protocol
|
|
427
|
-
|
|
428
|
-
| Message Type | Direction | Description |
|
|
429
|
-
|---|---|---|
|
|
430
|
-
| `EDITOR_READY` | Editor → RN | Editor has finished loading |
|
|
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 }`) |
|
|
435
|
-
|
|
436
|
-
## Exports
|
|
437
|
-
|
|
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';
|
|
446
|
-
|
|
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';
|
|
455
|
-
|
|
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
|
-
```
|
|
469
|
-
|
|
470
|
-
## License
|
|
471
|
-
|
|
472
|
-
Eddyter is **proprietary software**.
|
|
473
|
-
|
|
474
|
-
- Free for evaluation and non-commercial use
|
|
475
|
-
- **Commercial use requires a paid license**
|
|
476
|
-
- SaaS, redistribution, and competing products are prohibited without permission
|
|
477
|
-
|
|
478
|
-
For commercial licensing, visit [eddyter.com](https://www.eddyter.com/)
|
|
1
|
+
# Eddyter
|
|
2
|
+
|
|
3
|
+
A powerful, configurable rich text editor built on [Lexical](https://lexical.dev/) with AI capabilities, dark mode support, and API key authentication.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install eddyter
|
|
9
|
+
# or
|
|
10
|
+
yarn add eddyter
|
|
11
|
+
# or
|
|
12
|
+
pnpm add eddyter
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Compatibility
|
|
16
|
+
|
|
17
|
+
| Requirement | Version |
|
|
18
|
+
|-------------|---------|
|
|
19
|
+
| React | 18.2+ or 19.x |
|
|
20
|
+
| React DOM | 18.2+ or 19.x |
|
|
21
|
+
| Node.js | 16+ |
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### 1. Import styles
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import 'eddyter/style.css';
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> **Important:** The stylesheet is required for tables, toolbars, and all editor components to render correctly.
|
|
32
|
+
|
|
33
|
+
### 2. Get your API key
|
|
34
|
+
|
|
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
|
|
42
|
+
import React from 'react';
|
|
43
|
+
import {
|
|
44
|
+
ConfigurableEditorWithAuth,
|
|
45
|
+
EditorProvider,
|
|
46
|
+
defaultEditorConfig
|
|
47
|
+
} from 'eddyter';
|
|
48
|
+
import 'eddyter/style.css';
|
|
49
|
+
|
|
50
|
+
function App() {
|
|
51
|
+
const apiKey = process.env.NEXT_PUBLIC_EDITOR_API_KEY!;
|
|
52
|
+
|
|
53
|
+
const currentUser = {
|
|
54
|
+
id: 'user-123',
|
|
55
|
+
name: 'John Doe',
|
|
56
|
+
email: 'john@example.com',
|
|
57
|
+
avatar: 'https://example.com/avatar.jpg' // optional
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<EditorProvider
|
|
62
|
+
defaultFontFamilies={defaultEditorConfig.defaultFontFamilies}
|
|
63
|
+
currentUser={currentUser}
|
|
64
|
+
>
|
|
65
|
+
<ConfigurableEditorWithAuth
|
|
66
|
+
apiKey={apiKey}
|
|
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)}
|
|
72
|
+
/>
|
|
73
|
+
</EditorProvider>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
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
|
+
|
|
151
|
+
## API Reference
|
|
152
|
+
|
|
153
|
+
### `<EditorProvider>`
|
|
154
|
+
|
|
155
|
+
Provides authentication and configuration context. Must wrap the editor component.
|
|
156
|
+
|
|
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 |
|
|
164
|
+
|
|
165
|
+
#### CurrentUser Type
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
interface CurrentUser {
|
|
169
|
+
id: string;
|
|
170
|
+
name: string;
|
|
171
|
+
email: string;
|
|
172
|
+
avatar?: string;
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### `<ConfigurableEditorWithAuth>`
|
|
177
|
+
|
|
178
|
+
The main editor component with authentication.
|
|
179
|
+
|
|
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 |
|
|
199
|
+
|
|
200
|
+
## Examples
|
|
201
|
+
|
|
202
|
+
### Basic Editor
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter';
|
|
206
|
+
import 'eddyter/style.css';
|
|
207
|
+
|
|
208
|
+
export default function BasicEditor() {
|
|
209
|
+
return (
|
|
210
|
+
<EditorProvider>
|
|
211
|
+
<ConfigurableEditorWithAuth
|
|
212
|
+
apiKey="your-api-key"
|
|
213
|
+
onAuthSuccess={() => console.log('Ready!')}
|
|
214
|
+
/>
|
|
215
|
+
</EditorProvider>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Editor with State Management
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
import { useState } from 'react';
|
|
224
|
+
import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter';
|
|
225
|
+
import 'eddyter/style.css';
|
|
226
|
+
|
|
227
|
+
export default function EditorWithState() {
|
|
228
|
+
const [content, setContent] = useState('<p>Start writing...</p>');
|
|
229
|
+
|
|
230
|
+
const handleSave = async () => {
|
|
231
|
+
await fetch('/api/save', {
|
|
232
|
+
method: 'POST',
|
|
233
|
+
headers: { 'Content-Type': 'application/json' },
|
|
234
|
+
body: JSON.stringify({ content })
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
return (
|
|
239
|
+
<div>
|
|
240
|
+
<EditorProvider>
|
|
241
|
+
<ConfigurableEditorWithAuth
|
|
242
|
+
apiKey="your-api-key"
|
|
243
|
+
initialContent={content}
|
|
244
|
+
onChange={setContent}
|
|
245
|
+
/>
|
|
246
|
+
</EditorProvider>
|
|
247
|
+
<button onClick={handleSave}>Save</button>
|
|
248
|
+
</div>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Editor with Comments & Mentions
|
|
254
|
+
|
|
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>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Custom API Key Verification
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter';
|
|
282
|
+
import 'eddyter/style.css';
|
|
283
|
+
|
|
284
|
+
export default function EditorWithCustomAuth() {
|
|
285
|
+
const customVerifyKey = async (apiKey: string) => {
|
|
286
|
+
try {
|
|
287
|
+
const response = await fetch('/api/verify-key', {
|
|
288
|
+
method: 'POST',
|
|
289
|
+
headers: { 'Content-Type': 'application/json' },
|
|
290
|
+
body: JSON.stringify({ apiKey })
|
|
291
|
+
});
|
|
292
|
+
const data = await response.json();
|
|
293
|
+
return { success: data.valid, message: data.message || 'Verified' };
|
|
294
|
+
} catch {
|
|
295
|
+
return { success: false, message: 'Verification failed' };
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
<EditorProvider>
|
|
301
|
+
<ConfigurableEditorWithAuth
|
|
302
|
+
apiKey="your-api-key"
|
|
303
|
+
customVerifyKey={customVerifyKey}
|
|
304
|
+
/>
|
|
305
|
+
</EditorProvider>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Link Preview
|
|
311
|
+
|
|
312
|
+
The editor includes automatic link preview on hover.
|
|
313
|
+
|
|
314
|
+
- **Inside the editor**: Works automatically after authentication
|
|
315
|
+
- **Read-only content**: Pass `apiKey` to `EditorProvider`
|
|
316
|
+
- **Disable**: Set `enableLinkPreview={false}` on `EditorProvider`
|
|
317
|
+
|
|
318
|
+
```tsx
|
|
319
|
+
// Read-only content with link preview
|
|
320
|
+
<EditorProvider apiKey="your-api-key">
|
|
321
|
+
<div dangerouslySetInnerHTML={{ __html: savedHtml }} />
|
|
322
|
+
</EditorProvider>
|
|
323
|
+
|
|
324
|
+
// Disable link preview
|
|
325
|
+
<EditorProvider enableLinkPreview={false}>
|
|
326
|
+
{/* content */}
|
|
327
|
+
</EditorProvider>
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## React Native Integration
|
|
331
|
+
|
|
332
|
+
Use Eddyter in React Native via WebView by loading a deployed version of the editor.
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
npm install react-native-webview
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
```tsx
|
|
339
|
+
import React, { useRef, useState, useCallback } from 'react';
|
|
340
|
+
import { View, ActivityIndicator, KeyboardAvoidingView, Platform } from 'react-native';
|
|
341
|
+
import { WebView, WebViewMessageEvent } from 'react-native-webview';
|
|
342
|
+
|
|
343
|
+
interface RichTextEditorProps {
|
|
344
|
+
editorBaseUrl: string;
|
|
345
|
+
apiKey: string;
|
|
346
|
+
initialContent?: string;
|
|
347
|
+
theme?: 'light' | 'dark';
|
|
348
|
+
style?: object;
|
|
349
|
+
onChange?: (content: string) => void;
|
|
350
|
+
onReady?: () => void;
|
|
351
|
+
onAuthSuccess?: () => void;
|
|
352
|
+
onAuthError?: (error: string) => void;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
|
356
|
+
editorBaseUrl,
|
|
357
|
+
apiKey,
|
|
358
|
+
initialContent,
|
|
359
|
+
theme = 'light',
|
|
360
|
+
style,
|
|
361
|
+
onChange,
|
|
362
|
+
onReady,
|
|
363
|
+
onAuthSuccess,
|
|
364
|
+
onAuthError,
|
|
365
|
+
}) => {
|
|
366
|
+
const webViewRef = useRef<WebView>(null);
|
|
367
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
368
|
+
|
|
369
|
+
const buildEditorUrl = () => {
|
|
370
|
+
const baseUrl = editorBaseUrl.replace(/\/$/, '');
|
|
371
|
+
const params = new URLSearchParams();
|
|
372
|
+
if (apiKey) params.append('apiKey', apiKey);
|
|
373
|
+
if (theme) params.append('theme', theme);
|
|
374
|
+
return `${baseUrl}?${params.toString()}`;
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const handleMessage = useCallback((event: WebViewMessageEvent) => {
|
|
378
|
+
try {
|
|
379
|
+
const message = JSON.parse(event.nativeEvent.data);
|
|
380
|
+
switch (message.type) {
|
|
381
|
+
case 'EDITOR_READY':
|
|
382
|
+
setIsLoading(false);
|
|
383
|
+
onReady?.();
|
|
384
|
+
if (initialContent && webViewRef.current) {
|
|
385
|
+
webViewRef.current.postMessage(
|
|
386
|
+
JSON.stringify({ type: 'SET_CONTENT', payload: { content: initialContent } })
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
break;
|
|
390
|
+
case 'CONTENT_CHANGE':
|
|
391
|
+
onChange?.(message.payload?.content || '');
|
|
392
|
+
break;
|
|
393
|
+
case 'AUTH_SUCCESS':
|
|
394
|
+
onAuthSuccess?.();
|
|
395
|
+
break;
|
|
396
|
+
case 'AUTH_ERROR':
|
|
397
|
+
onAuthError?.(message.payload?.error);
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
} catch (e) {
|
|
401
|
+
console.warn('[RichTextEditor] Failed to parse message:', e);
|
|
402
|
+
}
|
|
403
|
+
}, [onChange, onReady, onAuthSuccess, onAuthError, initialContent]);
|
|
404
|
+
|
|
405
|
+
return (
|
|
406
|
+
<View style={[{ flex: 1 }, style]}>
|
|
407
|
+
<WebView
|
|
408
|
+
ref={webViewRef}
|
|
409
|
+
source={{ uri: buildEditorUrl() }}
|
|
410
|
+
style={{ flex: 1 }}
|
|
411
|
+
onMessage={handleMessage}
|
|
412
|
+
javaScriptEnabled={true}
|
|
413
|
+
domStorageEnabled={true}
|
|
414
|
+
keyboardDisplayRequiresUserAction={false}
|
|
415
|
+
/>
|
|
416
|
+
{isLoading && (
|
|
417
|
+
<View style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center' }}>
|
|
418
|
+
<ActivityIndicator size="large" />
|
|
419
|
+
</View>
|
|
420
|
+
)}
|
|
421
|
+
</View>
|
|
422
|
+
);
|
|
423
|
+
};
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Message Protocol
|
|
427
|
+
|
|
428
|
+
| Message Type | Direction | Description |
|
|
429
|
+
|---|---|---|
|
|
430
|
+
| `EDITOR_READY` | Editor → RN | Editor has finished loading |
|
|
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 }`) |
|
|
435
|
+
|
|
436
|
+
## Exports
|
|
437
|
+
|
|
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';
|
|
446
|
+
|
|
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';
|
|
455
|
+
|
|
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
|
+
```
|
|
469
|
+
|
|
470
|
+
## License
|
|
471
|
+
|
|
472
|
+
Eddyter is **proprietary software**.
|
|
473
|
+
|
|
474
|
+
- Free for evaluation and non-commercial use
|
|
475
|
+
- **Commercial use requires a paid license**
|
|
476
|
+
- SaaS, redistribution, and competing products are prohibited without permission
|
|
477
|
+
|
|
478
|
+
For commercial licensing, visit [eddyter.com](https://www.eddyter.com/)
|