@eddyter/core 1.0.0
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 +19 -0
- package/README.md +627 -0
- package/dist/editor.cjs +1344 -0
- package/dist/editor.iife.js +1344 -0
- package/dist/editor.mjs +240680 -0
- package/dist/index.d.ts +2 -0
- package/dist/main.d.ts +3 -0
- package/dist/style.css +1 -0
- package/dist/types.d.ts +56 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2026 Craxinno Technologies Pvt Ltd
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the “Software”) are proprietary to Craxinno Technologies Pvt Ltd.
|
|
6
|
+
|
|
7
|
+
Permission is granted to use the Software for evaluation and non-commercial purposes only.
|
|
8
|
+
|
|
9
|
+
Commercial use of the Software, including embedding in websites, applications, SaaS platforms, or any product or service offered for monetary or strategic gain, requires a valid commercial license.
|
|
10
|
+
|
|
11
|
+
You may not:
|
|
12
|
+
- copy, modify, or create derivative works for commercial purposes
|
|
13
|
+
- redistribute, sublicense, sell, or resell access to the Software
|
|
14
|
+
- use the Software to build competing products or services
|
|
15
|
+
- host or re-distribute the Software via CDN or other delivery mechanisms without permission
|
|
16
|
+
|
|
17
|
+
Unauthorized use terminates this license immediately.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
package/README.md
ADDED
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
# richtext-core-sdk
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/richtext-core-sdk)
|
|
4
|
+
[](https://www.npmjs.com/package/richtext-core-sdk)
|
|
5
|
+
[](https://bundlephobia.com/package/richtext-core-sdk)
|
|
6
|
+
|
|
7
|
+
Plug and Play AI Rich Text Editor for **any** website, blog, CRM, ERP, or web app — built on [Lexical](https://lexical.dev/) with dark mode support and API key authentication. No React knowledge required.
|
|
8
|
+
|
|
9
|
+
This is the **framework-agnostic SDK** for the Eddyter editor. It wraps the official React package [`eddyter`](https://www.npmjs.com/package/eddyter) and exposes a single `init()` function so you can mount the editor into any HTML element from plain JavaScript, Vue, Svelte, Angular, Laravel/Blade, WordPress, or any other stack.
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
## Resources
|
|
14
|
+
|
|
15
|
+
- [Docs](https://eddyter.com/docs) — Full API reference and integration guides
|
|
16
|
+
- [What is Eddyter? Why Developers Are Switching to This AI Editor (2026)](https://youtu.be/oNHBa-DImZc) — YouTube
|
|
17
|
+
- [Integrate Eddyter in 30 Minutes Using AI Tools Cursor, Claude, Lovable](https://youtu.be/5lTjRFjUWgs) — YouTube
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install richtext-core-sdk
|
|
23
|
+
# or
|
|
24
|
+
yarn add richtext-core-sdk
|
|
25
|
+
# or
|
|
26
|
+
pnpm add richtext-core-sdk
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or include it directly via a CDN as a classic script:
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<script src="https://unpkg.com/richtext-core-sdk/dist/editor.iife.js"></script>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Compatibility
|
|
36
|
+
|
|
37
|
+
| Requirement | Version |
|
|
38
|
+
|-------------|---------|
|
|
39
|
+
| Node.js | 16+ |
|
|
40
|
+
| Browsers | Evergreen (Chrome, Edge, Firefox, Safari) |
|
|
41
|
+
| Frameworks | Any — React, Vue, Svelte, Angular, vanilla JS, server-rendered HTML |
|
|
42
|
+
|
|
43
|
+
> Internally the SDK ships React + ReactDOM bundled with the editor, so your host app does **not** need to install or configure React.
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
### 1. Import styles
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
import 'richtext-core-sdk/style.css';
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
> **Important:** The stylesheet is required for tables, toolbars, and all editor components to render correctly. When the SDK is loaded as a classic script (`editor.iife.js`), the stylesheet is injected automatically by the IIFE bootstrap.
|
|
54
|
+
|
|
55
|
+
### 2. Get your API key
|
|
56
|
+
|
|
57
|
+
1. Create an account at [eddyter.com](https://www.eddyter.com/)
|
|
58
|
+
2. Navigate to [License Keys](https://www.eddyter.com/user/license-key) in your dashboard
|
|
59
|
+
3. Copy your API key
|
|
60
|
+
|
|
61
|
+
### 3. Add the editor
|
|
62
|
+
|
|
63
|
+
```html
|
|
64
|
+
<div id="editor"></div>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
import { init } from 'richtext-core-sdk';
|
|
69
|
+
import 'richtext-core-sdk/style.css';
|
|
70
|
+
|
|
71
|
+
const apiKey = 'YOUR_API_KEY';
|
|
72
|
+
|
|
73
|
+
const currentUser = {
|
|
74
|
+
id: 'user-123',
|
|
75
|
+
name: 'John Doe',
|
|
76
|
+
email: 'john@example.com',
|
|
77
|
+
avatar: 'https://example.com/avatar.jpg', // optional
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const instance = init({
|
|
81
|
+
container: '#editor',
|
|
82
|
+
apiKey,
|
|
83
|
+
user: currentUser,
|
|
84
|
+
initialContent: '<p>Start writing...</p>',
|
|
85
|
+
mentionUserList: ['Alice', 'Bob', 'Charlie'],
|
|
86
|
+
onChange: (html) => console.log('Content:', html),
|
|
87
|
+
onReady: () => console.log('Editor ready!'),
|
|
88
|
+
onAuthSuccess: () => console.log('Auth succeeded'),
|
|
89
|
+
onAuthError: (error) => console.error('Auth failed:', error),
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
When loaded via `<script>`, the same API is available on `window.Eddyter`:
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<div id="editor"></div>
|
|
97
|
+
|
|
98
|
+
<script src="https://unpkg.com/richtext-core-sdk/dist/editor.iife.js"></script>
|
|
99
|
+
<script>
|
|
100
|
+
const instance = Eddyter.init({
|
|
101
|
+
container: '#editor',
|
|
102
|
+
apiKey: 'YOUR_API_KEY',
|
|
103
|
+
onChange: (html) => console.log(html),
|
|
104
|
+
});
|
|
105
|
+
</script>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Features
|
|
109
|
+
|
|
110
|
+
### Text & Formatting
|
|
111
|
+
- Bold, italic, underline, strikethrough, subscript, superscript
|
|
112
|
+
- Text color and background highlight with color picker
|
|
113
|
+
- 20+ font families with adjustable font sizes
|
|
114
|
+
- Text alignment (left, center, right, justify)
|
|
115
|
+
- Line height and letter spacing controls
|
|
116
|
+
|
|
117
|
+
### Lists & Structure
|
|
118
|
+
- Bullet lists, numbered lists (decimal, alpha, roman)
|
|
119
|
+
- Interactive checklists with strikethrough
|
|
120
|
+
- Headings (H1-H6), blockquotes
|
|
121
|
+
- Horizontal rules
|
|
122
|
+
|
|
123
|
+
### Tables
|
|
124
|
+
- Insert/delete rows and columns, merge cells
|
|
125
|
+
- Drag-to-resize columns and rows
|
|
126
|
+
- Header row styling
|
|
127
|
+
- Row striping with custom colors
|
|
128
|
+
- Right-click context menu for table actions
|
|
129
|
+
|
|
130
|
+
### Media
|
|
131
|
+
- Image upload with drag-drop and 8-point resize handles
|
|
132
|
+
- Video embed with drag-drop and paste support
|
|
133
|
+
- File attachments (downloadable files)
|
|
134
|
+
- Link insertion with floating editor
|
|
135
|
+
- Automatic link preview on hover
|
|
136
|
+
- Rich embeds for external content (YouTube, etc.)
|
|
137
|
+
|
|
138
|
+
### AI Features (Premium)
|
|
139
|
+
- AI Chat assistant for content help
|
|
140
|
+
- Smart autocomplete (AI-powered text suggestions)
|
|
141
|
+
- Real-time grammar check and corrections
|
|
142
|
+
- Text enhancement (improve, shorten, expand)
|
|
143
|
+
- Tone adjustment (formal, casual, professional)
|
|
144
|
+
- AI image generation from text prompts
|
|
145
|
+
|
|
146
|
+
### Advanced
|
|
147
|
+
- Slash commands (`/` for quick formatting)
|
|
148
|
+
- @Mentions with customizable user list
|
|
149
|
+
- Inline comments with bubble UI and sidebar
|
|
150
|
+
- Note panels (info, warning, error, success)
|
|
151
|
+
- Code blocks with syntax highlighting
|
|
152
|
+
- Interactive charts
|
|
153
|
+
- Digital signature capture
|
|
154
|
+
- Voice input / transcription
|
|
155
|
+
- Export to PDF
|
|
156
|
+
- HTML view toggle
|
|
157
|
+
- Drag-and-drop block reordering
|
|
158
|
+
- Markdown shortcuts
|
|
159
|
+
|
|
160
|
+
### Dark Mode
|
|
161
|
+
|
|
162
|
+
The editor automatically detects your app's theme:
|
|
163
|
+
- Checks for `dark` class on `<html>` or `<body>`
|
|
164
|
+
- Falls back to `prefers-color-scheme: dark` system preference
|
|
165
|
+
- Or set explicitly via the `darkMode` option, and toggle it at runtime without remounting:
|
|
166
|
+
|
|
167
|
+
```js
|
|
168
|
+
instance.update({ darkMode: true });
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Preview Mode
|
|
172
|
+
|
|
173
|
+
Display saved editor content in read-only mode with interactive features:
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
init({
|
|
177
|
+
container: '#preview',
|
|
178
|
+
apiKey: 'YOUR_API_KEY',
|
|
179
|
+
mode: 'preview',
|
|
180
|
+
initialContent: savedHtml,
|
|
181
|
+
onPreviewClick: () => switchToEditMode(),
|
|
182
|
+
containerClass: 'my-preview-styles',
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## API Reference
|
|
187
|
+
|
|
188
|
+
### `init(options)` → `EddyterInstance`
|
|
189
|
+
|
|
190
|
+
Mounts the editor into a DOM container and returns a small instance handle. Calling `init()` twice on the same container returns the existing instance instead of remounting.
|
|
191
|
+
|
|
192
|
+
#### `EddyterInitOptions`
|
|
193
|
+
|
|
194
|
+
| Option | Type | Required | Description |
|
|
195
|
+
|--------|------|----------|-------------|
|
|
196
|
+
| `container` | `string \| HTMLElement` | Yes | CSS selector or DOM element to mount into |
|
|
197
|
+
| `apiKey` | `string` | Yes | Your Eddyter license key |
|
|
198
|
+
| `user` | `EddyterCurrentUser` | No | Current user for comments / mentions. Defaults to `{ id: 'anonymous', name: 'Anonymous' }` |
|
|
199
|
+
| `initialContent` | `string` | No | Initial HTML rendered in the editor |
|
|
200
|
+
| `mode` | `'edit' \| 'preview'` | No | Editor mode (default: `'edit'`) |
|
|
201
|
+
| `darkMode` | `boolean` | No | `true`/`false`. Omit to auto-detect host's `.dark` class |
|
|
202
|
+
| `customVerifyKey` | `(key: string) => Promise<EddyterApiResponse>` | No | Use your own backend to validate the API key |
|
|
203
|
+
| `mentionUserList` | `string[]` | No | Names that appear in `@mention` suggestions |
|
|
204
|
+
| `defaultFontFamilies` | `string[]` | No | Font family names for the font selector |
|
|
205
|
+
| `class` | `string` | No | CSS class applied to the outermost editor wrapper |
|
|
206
|
+
| `containerClass` | `string` | No | CSS class applied to the **preview** container (only used when `mode: 'preview'`) |
|
|
207
|
+
| `contentClass` | `string` | No | CSS class applied to the editable **content area** |
|
|
208
|
+
| `floatingToolbarClass` | `string` | No | CSS class applied to the floating toolbar and its portal container |
|
|
209
|
+
| `style` | `React.CSSProperties` | No | Inline style object applied to the wrapper |
|
|
210
|
+
| `toolbar` | `EddyterToolbarConfig` | No | Toolbar behavior (default: `{ mode: 'sticky', offset: 20, zIndex: 1000 }`) |
|
|
211
|
+
| `editor` | `EddyterEditorOptions` | No | Editor container options (`maxHeight`) |
|
|
212
|
+
| `enableReactNativeBridge` | `boolean` | No | Force-enable RN WebView bridge messaging (auto-detected in a WebView context) |
|
|
213
|
+
| `onChange` | `(html: string) => void` | No | Editor content changes (debounced) |
|
|
214
|
+
| `onReady` | `() => void` | No | Editor finished mounting and authenticated |
|
|
215
|
+
| `onAuthSuccess` | `() => void` | No | API key validated successfully |
|
|
216
|
+
| `onAuthError` | `(error: string) => void` | No | API key validation failed |
|
|
217
|
+
| `onFocus` | `() => void` | No | Editor gained focus (React Native bridge) |
|
|
218
|
+
| `onBlur` | `() => void` | No | Editor lost focus (React Native bridge) |
|
|
219
|
+
| `onHeightChange` | `(height: number) => void` | No | Editor content height changes (React Native bridge) |
|
|
220
|
+
| `onPreviewClick` | `() => void` | No | User clicks anywhere inside the preview (e.g. to open edit mode) |
|
|
221
|
+
|
|
222
|
+
#### Supporting types
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
interface EddyterCurrentUser {
|
|
226
|
+
id: string;
|
|
227
|
+
name: string;
|
|
228
|
+
email?: string;
|
|
229
|
+
avatar?: string;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
interface EddyterApiResponse {
|
|
233
|
+
success: boolean;
|
|
234
|
+
message: string;
|
|
235
|
+
data?: unknown;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
interface EddyterToolbarConfig {
|
|
239
|
+
mode?: 'sticky' | 'static';
|
|
240
|
+
offset?: number;
|
|
241
|
+
zIndex?: number;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
interface EddyterEditorOptions {
|
|
245
|
+
maxHeight?: string | number;
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### `EddyterInstance`
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
interface EddyterInstance {
|
|
253
|
+
destroy(): void;
|
|
254
|
+
update(partial: EddyterUpdatableOptions): void;
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
#### `instance.update(partial)`
|
|
259
|
+
|
|
260
|
+
Updates the editor **without remounting**. Useful for theme toggles, toolbar mode changes, and class swaps.
|
|
261
|
+
|
|
262
|
+
Supported fields:
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
mode, darkMode, class, containerClass, contentClass,
|
|
266
|
+
floatingToolbarClass, style, toolbar, editor,
|
|
267
|
+
defaultFontFamilies, mentionUserList, enableReactNativeBridge
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
```js
|
|
271
|
+
instance.update({ darkMode: true });
|
|
272
|
+
instance.update({ toolbar: { mode: 'static' }, editor: { maxHeight: 480 } });
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
> `apiKey`, `container`, `user`, `initialContent`, and `customVerifyKey` require a fresh `init()` — they drive the auth/provider lifecycle and should not be hot-swapped.
|
|
276
|
+
|
|
277
|
+
#### `instance.destroy()`
|
|
278
|
+
|
|
279
|
+
Unmounts the editor and clears the singleton stored on the container element. Call this when removing the editor from the DOM (modal close, SPA route change, etc.).
|
|
280
|
+
|
|
281
|
+
```js
|
|
282
|
+
instance.destroy();
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Toolbar Configuration
|
|
286
|
+
|
|
287
|
+
Use the `toolbar` option to control sticky/static toolbar behavior:
|
|
288
|
+
|
|
289
|
+
```js
|
|
290
|
+
init({
|
|
291
|
+
container: '#editor',
|
|
292
|
+
apiKey: 'your-api-key',
|
|
293
|
+
toolbar: { mode: 'sticky', offset: 64, zIndex: 1200 },
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Modes:
|
|
298
|
+
|
|
299
|
+
- `mode: 'sticky'` -> toolbar detaches/sticks while scrolling and applies `offset` + `zIndex`
|
|
300
|
+
- `mode: 'static'` -> toolbar stays attached and ignores `offset` + `zIndex` even if provided
|
|
301
|
+
|
|
302
|
+
Defaults:
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
const defaultToolbar = {
|
|
306
|
+
mode: 'sticky',
|
|
307
|
+
offset: 20,
|
|
308
|
+
zIndex: 1000,
|
|
309
|
+
};
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
In static mode, if you want only the editor content area to scroll, pass a `maxHeight` using `editor`:
|
|
313
|
+
|
|
314
|
+
```js
|
|
315
|
+
init({
|
|
316
|
+
container: '#editor',
|
|
317
|
+
apiKey: 'your-api-key',
|
|
318
|
+
toolbar: { mode: 'static' },
|
|
319
|
+
editor: { maxHeight: 600 },
|
|
320
|
+
});
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
If `maxHeight` is not provided, the full page/container scrolls normally with the toolbar.
|
|
324
|
+
|
|
325
|
+
## Examples
|
|
326
|
+
|
|
327
|
+
### Basic Editor
|
|
328
|
+
|
|
329
|
+
```js
|
|
330
|
+
import { init } from 'richtext-core-sdk';
|
|
331
|
+
import 'richtext-core-sdk/style.css';
|
|
332
|
+
|
|
333
|
+
init({
|
|
334
|
+
container: '#editor',
|
|
335
|
+
apiKey: 'your-api-key',
|
|
336
|
+
onReady: () => console.log('Ready!'),
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Editor with State Management
|
|
341
|
+
|
|
342
|
+
Since the SDK is framework-agnostic, you wire state via the `onChange` callback:
|
|
343
|
+
|
|
344
|
+
```js
|
|
345
|
+
import { init } from 'richtext-core-sdk';
|
|
346
|
+
import 'richtext-core-sdk/style.css';
|
|
347
|
+
|
|
348
|
+
let content = '<p>Start writing...</p>';
|
|
349
|
+
|
|
350
|
+
const instance = init({
|
|
351
|
+
container: '#editor',
|
|
352
|
+
apiKey: 'your-api-key',
|
|
353
|
+
initialContent: content,
|
|
354
|
+
onChange: (html) => {
|
|
355
|
+
content = html;
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
document.getElementById('save').addEventListener('click', async () => {
|
|
360
|
+
await fetch('/api/save', {
|
|
361
|
+
method: 'POST',
|
|
362
|
+
headers: { 'Content-Type': 'application/json' },
|
|
363
|
+
body: JSON.stringify({ content }),
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Editor with Comments & Mentions
|
|
369
|
+
|
|
370
|
+
```js
|
|
371
|
+
init({
|
|
372
|
+
container: '#editor',
|
|
373
|
+
apiKey: 'your-api-key',
|
|
374
|
+
user: {
|
|
375
|
+
id: currentUser.id,
|
|
376
|
+
name: currentUser.name,
|
|
377
|
+
email: currentUser.email,
|
|
378
|
+
avatar: currentUser.avatarUrl,
|
|
379
|
+
},
|
|
380
|
+
mentionUserList: ['Alice', 'Bob', 'Charlie'],
|
|
381
|
+
});
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Custom API Key Verification
|
|
385
|
+
|
|
386
|
+
```js
|
|
387
|
+
init({
|
|
388
|
+
container: '#editor',
|
|
389
|
+
apiKey: 'your-api-key',
|
|
390
|
+
customVerifyKey: async (apiKey) => {
|
|
391
|
+
try {
|
|
392
|
+
const response = await fetch('/api/verify-key', {
|
|
393
|
+
method: 'POST',
|
|
394
|
+
headers: { 'Content-Type': 'application/json' },
|
|
395
|
+
body: JSON.stringify({ apiKey }),
|
|
396
|
+
});
|
|
397
|
+
const data = await response.json();
|
|
398
|
+
return { success: data.valid, message: data.message || 'Verified' };
|
|
399
|
+
} catch {
|
|
400
|
+
return { success: false, message: 'Verification failed' };
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Theme Toggle Without Remount
|
|
407
|
+
|
|
408
|
+
```js
|
|
409
|
+
const instance = init({
|
|
410
|
+
container: '#editor',
|
|
411
|
+
apiKey: 'your-api-key',
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
let isDark = false;
|
|
415
|
+
document.getElementById('theme-toggle')?.addEventListener('click', () => {
|
|
416
|
+
isDark = !isDark;
|
|
417
|
+
instance.update({ darkMode: isDark });
|
|
418
|
+
});
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Static Toolbar with Scrollable Content
|
|
422
|
+
|
|
423
|
+
```js
|
|
424
|
+
init({
|
|
425
|
+
container: '#editor',
|
|
426
|
+
apiKey: 'your-api-key',
|
|
427
|
+
toolbar: { mode: 'static' },
|
|
428
|
+
editor: { maxHeight: '420px' },
|
|
429
|
+
});
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Multiple Editors on One Page
|
|
433
|
+
|
|
434
|
+
Each container gets its own instance. Call `init()` once per container and `destroy()` when removing it.
|
|
435
|
+
|
|
436
|
+
```js
|
|
437
|
+
const a = init({ container: '#editor-a', apiKey: 'your-api-key' });
|
|
438
|
+
const b = init({ container: '#editor-b', apiKey: 'your-api-key' });
|
|
439
|
+
|
|
440
|
+
a.destroy();
|
|
441
|
+
b.destroy();
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
## Link Preview
|
|
445
|
+
|
|
446
|
+
The editor includes automatic link preview on hover. It works automatically inside the editor after authentication and inside the preview when `mode: 'preview'` is set with a valid `apiKey`.
|
|
447
|
+
|
|
448
|
+
## Framework Integration
|
|
449
|
+
|
|
450
|
+
The SDK ships official thin wrappers for popular frameworks. If you prefer a more idiomatic API, use the framework package — internally each one calls `init()` from this SDK.
|
|
451
|
+
|
|
452
|
+
- **Vue** — [`richtext-core-vue`](https://www.npmjs.com/package/richtext-core-vue)
|
|
453
|
+
- **Svelte** — [`richtext-core-svelte`](https://www.npmjs.com/package/richtext-core-svelte)
|
|
454
|
+
- **Angular** — [`richtext-core-angular`](https://www.npmjs.com/package/richtext-core-angular)
|
|
455
|
+
- **React** — use the original [`eddyter`](https://www.npmjs.com/package/eddyter) package directly
|
|
456
|
+
|
|
457
|
+
For server-rendered apps (Laravel/Blade, Django, Rails, WordPress, etc.) load the IIFE bundle via `<script>` and call `Eddyter.init(...)` from a small inline script.
|
|
458
|
+
|
|
459
|
+
## React Native Integration
|
|
460
|
+
|
|
461
|
+
Use Eddyter in React Native via WebView by loading a deployed version of the editor. The bridge messaging is automatically enabled when running inside a WebView, or you can force it on via the `enableReactNativeBridge` option on the host page.
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
npm install react-native-webview
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
```tsx
|
|
468
|
+
import React, { useRef, useState, useCallback } from 'react';
|
|
469
|
+
import { View, ActivityIndicator } from 'react-native';
|
|
470
|
+
import { WebView, WebViewMessageEvent } from 'react-native-webview';
|
|
471
|
+
|
|
472
|
+
interface RichTextEditorProps {
|
|
473
|
+
editorBaseUrl: string;
|
|
474
|
+
apiKey: string;
|
|
475
|
+
initialContent?: string;
|
|
476
|
+
theme?: 'light' | 'dark';
|
|
477
|
+
style?: object;
|
|
478
|
+
onChange?: (content: string) => void;
|
|
479
|
+
onReady?: () => void;
|
|
480
|
+
onAuthSuccess?: () => void;
|
|
481
|
+
onAuthError?: (error: string) => void;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
|
485
|
+
editorBaseUrl,
|
|
486
|
+
apiKey,
|
|
487
|
+
initialContent,
|
|
488
|
+
theme = 'light',
|
|
489
|
+
style,
|
|
490
|
+
onChange,
|
|
491
|
+
onReady,
|
|
492
|
+
onAuthSuccess,
|
|
493
|
+
onAuthError,
|
|
494
|
+
}) => {
|
|
495
|
+
const webViewRef = useRef<WebView>(null);
|
|
496
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
497
|
+
|
|
498
|
+
const buildEditorUrl = () => {
|
|
499
|
+
const baseUrl = editorBaseUrl.replace(/\/$/, '');
|
|
500
|
+
const params = new URLSearchParams();
|
|
501
|
+
if (apiKey) params.append('apiKey', apiKey);
|
|
502
|
+
if (theme) params.append('theme', theme);
|
|
503
|
+
return `${baseUrl}?${params.toString()}`;
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
const handleMessage = useCallback((event: WebViewMessageEvent) => {
|
|
507
|
+
try {
|
|
508
|
+
const message = JSON.parse(event.nativeEvent.data);
|
|
509
|
+
switch (message.type) {
|
|
510
|
+
case 'EDITOR_READY':
|
|
511
|
+
setIsLoading(false);
|
|
512
|
+
onReady?.();
|
|
513
|
+
if (initialContent && webViewRef.current) {
|
|
514
|
+
webViewRef.current.postMessage(
|
|
515
|
+
JSON.stringify({ type: 'SET_CONTENT', payload: { content: initialContent } })
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
break;
|
|
519
|
+
case 'CONTENT_CHANGE':
|
|
520
|
+
onChange?.(message.payload?.content || '');
|
|
521
|
+
break;
|
|
522
|
+
case 'AUTH_SUCCESS':
|
|
523
|
+
onAuthSuccess?.();
|
|
524
|
+
break;
|
|
525
|
+
case 'AUTH_ERROR':
|
|
526
|
+
onAuthError?.(message.payload?.error);
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
} catch (e) {
|
|
530
|
+
console.warn('[RichTextEditor] Failed to parse message:', e);
|
|
531
|
+
}
|
|
532
|
+
}, [onChange, onReady, onAuthSuccess, onAuthError, initialContent]);
|
|
533
|
+
|
|
534
|
+
return (
|
|
535
|
+
<View style={[{ flex: 1 }, style]}>
|
|
536
|
+
<WebView
|
|
537
|
+
ref={webViewRef}
|
|
538
|
+
source={{ uri: buildEditorUrl() }}
|
|
539
|
+
style={{ flex: 1 }}
|
|
540
|
+
onMessage={handleMessage}
|
|
541
|
+
javaScriptEnabled={true}
|
|
542
|
+
domStorageEnabled={true}
|
|
543
|
+
keyboardDisplayRequiresUserAction={false}
|
|
544
|
+
/>
|
|
545
|
+
{isLoading && (
|
|
546
|
+
<View style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center' }}>
|
|
547
|
+
<ActivityIndicator size="large" />
|
|
548
|
+
</View>
|
|
549
|
+
)}
|
|
550
|
+
</View>
|
|
551
|
+
);
|
|
552
|
+
};
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### Message Protocol
|
|
556
|
+
|
|
557
|
+
| Message Type | Direction | Description |
|
|
558
|
+
|---|---|---|
|
|
559
|
+
| `EDITOR_READY` | Editor → RN | Editor has finished loading |
|
|
560
|
+
| `CONTENT_CHANGE` | Editor → RN | Content was modified (`{ content: string }`) |
|
|
561
|
+
| `AUTH_SUCCESS` | Editor → RN | Authentication succeeded |
|
|
562
|
+
| `AUTH_ERROR` | Editor → RN | Authentication failed (`{ error: string }`) |
|
|
563
|
+
| `SET_CONTENT` | RN → Editor | Set editor content (`{ content: string }`) |
|
|
564
|
+
|
|
565
|
+
## Exports
|
|
566
|
+
|
|
567
|
+
```ts
|
|
568
|
+
// Entrypoint
|
|
569
|
+
import { init } from 'richtext-core-sdk';
|
|
570
|
+
|
|
571
|
+
// Types
|
|
572
|
+
import type {
|
|
573
|
+
EddyterInitOptions,
|
|
574
|
+
EddyterInstance,
|
|
575
|
+
EddyterUpdatableOptions,
|
|
576
|
+
EddyterCurrentUser,
|
|
577
|
+
EddyterToolbarConfig,
|
|
578
|
+
EddyterEditorOptions,
|
|
579
|
+
EddyterApiResponse,
|
|
580
|
+
} from 'richtext-core-sdk';
|
|
581
|
+
|
|
582
|
+
// Styles (one-time import at app entry)
|
|
583
|
+
import 'richtext-core-sdk/style.css';
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
When loaded as a classic script the SDK is available on `window.Eddyter`:
|
|
587
|
+
|
|
588
|
+
```js
|
|
589
|
+
window.Eddyter.init({ /* options */ });
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
## Troubleshooting
|
|
593
|
+
|
|
594
|
+
| Symptom | Fix |
|
|
595
|
+
|---------|-----|
|
|
596
|
+
| Editor renders but toolbars look broken | The stylesheet is missing. Import `richtext-core-sdk/style.css` in the entry that builds production. |
|
|
597
|
+
| `Invalid container` thrown | The selector did not resolve, or you passed something that isn't an `HTMLElement`. |
|
|
598
|
+
| Theme does not switch | Use `instance.update({ darkMode })`; do not remount. |
|
|
599
|
+
| API key errors | Confirm the key in your env vars and check the network tab for the verification request. |
|
|
600
|
+
| Editor mounts twice in dev (StrictMode-like behavior) | `init()` is idempotent per container — it returns the existing instance if one exists. |
|
|
601
|
+
|
|
602
|
+
## Migration
|
|
603
|
+
|
|
604
|
+
If you are upgrading from an earlier release, some legacy options were removed because they targeted React props that never existed:
|
|
605
|
+
|
|
606
|
+
| Old option | New option | Status |
|
|
607
|
+
|------------|------------|--------|
|
|
608
|
+
| `previewClass` | `containerClass` | Removed — old value was a no-op |
|
|
609
|
+
| `editorClass` | `contentClass` | Removed — old value was a no-op |
|
|
610
|
+
| `previewClassName` | `containerClass` | Removed (never wired up) |
|
|
611
|
+
| `editorClassName` | `contentClass` | Removed (never wired up) |
|
|
612
|
+
| — | `floatingToolbarClass` | **New** — style the floating toolbar / portal |
|
|
613
|
+
|
|
614
|
+
```diff
|
|
615
|
+
- init({ container: '#editor', apiKey, previewClass: 'p', editorClass: 'c' });
|
|
616
|
+
+ init({ container: '#editor', apiKey, containerClass: 'p', contentClass: 'c' });
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
## License
|
|
620
|
+
|
|
621
|
+
Eddyter is **proprietary software**.
|
|
622
|
+
|
|
623
|
+
- Free for evaluation and non-commercial use
|
|
624
|
+
- **Commercial use requires a paid license**
|
|
625
|
+
- SaaS, redistribution, and competing products are prohibited without permission
|
|
626
|
+
|
|
627
|
+
For commercial licensing, visit [eddyter.com](https://www.eddyter.com/)
|