@mdaemon/html-editor-react 1.0.0 → 1.0.2
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 +588 -171
- package/package.json +26 -15
package/README.md
CHANGED
|
@@ -1,171 +1,588 @@
|
|
|
1
|
-
# MDHTMLEditor React
|
|
2
|
-
|
|
3
|
-
React wrapper for MDHTMLEditor
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install @mdaemon/html-editor-react
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
```tsx
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<
|
|
34
|
-
ref={editorRef}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
1
|
+
# MDHTMLEditor React
|
|
2
|
+
|
|
3
|
+
A React wrapper for [MDHTMLEditor](https://github.com/mdaemon-technologies/MDHTMLEditor) — a WYSIWYG HTML editor built on TipTap. Provides a drop-in replacement for `@tinymce/tinymce-react` with no license key required.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @mdaemon/html-editor-react @mdaemon/html-editor
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Peer dependencies:** `react` and `react-dom` (v18 or v19).
|
|
12
|
+
|
|
13
|
+
## Styles
|
|
14
|
+
|
|
15
|
+
You must import the editor stylesheet for the toolbar and UI to render correctly:
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import '@mdaemon/html-editor/dist/styles.css';
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { useRef } from 'react';
|
|
25
|
+
import { Editor } from '@mdaemon/html-editor-react';
|
|
26
|
+
import type { EditorRef } from '@mdaemon/html-editor-react';
|
|
27
|
+
import '@mdaemon/html-editor/dist/styles.css';
|
|
28
|
+
|
|
29
|
+
function App() {
|
|
30
|
+
const editorRef = useRef<EditorRef>(null);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Editor
|
|
34
|
+
ref={editorRef}
|
|
35
|
+
config={{ height: 400 }}
|
|
36
|
+
initialValue="<p>Hello World</p>"
|
|
37
|
+
onChange={(html) => console.log('Content:', html)}
|
|
38
|
+
onInit={(editor) => console.log('Ready!', editor)}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## `<Editor>` Component
|
|
45
|
+
|
|
46
|
+
The primary way to use the editor. It accepts a `config` object and event callbacks, and exposes imperative methods via a ref.
|
|
47
|
+
|
|
48
|
+
### Props
|
|
49
|
+
|
|
50
|
+
| Prop | Type | Required | Description |
|
|
51
|
+
|------|------|----------|-------------|
|
|
52
|
+
| `config` | `EditorConfig` | Yes | Configuration object passed to the underlying HTMLEditor. See [Configuration](#configuration). |
|
|
53
|
+
| `body` | `string` | No | Initial HTML content. Takes precedence over `initialValue`. |
|
|
54
|
+
| `initialValue` | `string` | No | Initial HTML content (alias for `body`). |
|
|
55
|
+
| `name` | `string` | No | When set, renders a hidden `<input>` with this name containing the editor content — useful for form submission. |
|
|
56
|
+
| `disabled` | `boolean` | No | Visually disables the editor (pointer-events off, reduced opacity). |
|
|
57
|
+
| `onChange` | `(content: string) => void` | No | Called when content changes (debounced). |
|
|
58
|
+
| `onDirty` | `(dirty: boolean) => void` | No | Called when the dirty state changes. |
|
|
59
|
+
| `onInit` | `(editor: HTMLEditor) => void` | No | Called when the editor finishes initialization. Receives the editor instance. |
|
|
60
|
+
| `onFocus` | `() => void` | No | Called when the editor receives focus. |
|
|
61
|
+
| `onBlur` | `() => void` | No | Called when the editor loses focus. |
|
|
62
|
+
| `translate` | `(key: string) => string` | No | Sets a global translation function for all editors on the page. |
|
|
63
|
+
| `getFileSrc` | `(path: string) => string` | No | Sets a global file path resolver (e.g., for CDN prefixing). |
|
|
64
|
+
|
|
65
|
+
### Ref Methods (`EditorRef`)
|
|
66
|
+
|
|
67
|
+
Attach a ref to access imperative methods:
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
const editorRef = useRef<EditorRef>(null);
|
|
71
|
+
|
|
72
|
+
// Later...
|
|
73
|
+
const html = editorRef.current?.getContent();
|
|
74
|
+
editorRef.current?.setContent('<p>New content</p>');
|
|
75
|
+
editorRef.current?.insertContent('<p>Inserted at cursor</p>');
|
|
76
|
+
editorRef.current?.focus();
|
|
77
|
+
|
|
78
|
+
// Access the underlying HTMLEditor instance directly
|
|
79
|
+
const rawEditor = editorRef.current?.getEditor();
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
| Method | Signature | Description |
|
|
83
|
+
|--------|-----------|-------------|
|
|
84
|
+
| `getEditor` | `() => HTMLEditor \| null` | Access the raw HTMLEditor instance for advanced usage. |
|
|
85
|
+
| `getContent` | `() => string` | Get current HTML content. |
|
|
86
|
+
| `setContent` | `(html: string) => void` | Replace the editor content. |
|
|
87
|
+
| `insertContent` | `(html: string) => void` | Insert HTML at the current cursor position. |
|
|
88
|
+
| `focus` | `() => void` | Focus the editor. |
|
|
89
|
+
|
|
90
|
+
### Full Example
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { useRef, useState } from 'react';
|
|
94
|
+
import { Editor } from '@mdaemon/html-editor-react';
|
|
95
|
+
import type { EditorRef } from '@mdaemon/html-editor-react';
|
|
96
|
+
import '@mdaemon/html-editor/dist/styles.css';
|
|
97
|
+
|
|
98
|
+
function EmailComposer() {
|
|
99
|
+
const editorRef = useRef<EditorRef>(null);
|
|
100
|
+
const [dirty, setDirty] = useState(false);
|
|
101
|
+
|
|
102
|
+
const handleSave = () => {
|
|
103
|
+
const content = editorRef.current?.getContent() ?? '';
|
|
104
|
+
console.log('Saving:', content);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<div>
|
|
109
|
+
<Editor
|
|
110
|
+
ref={editorRef}
|
|
111
|
+
config={{
|
|
112
|
+
height: 500,
|
|
113
|
+
basicEditor: false,
|
|
114
|
+
skin: 'oxide',
|
|
115
|
+
fontName: 'Arial',
|
|
116
|
+
fontSize: '12pt',
|
|
117
|
+
}}
|
|
118
|
+
initialValue="<p>Dear recipient,</p>"
|
|
119
|
+
onChange={(html) => console.log('Changed:', html)}
|
|
120
|
+
onDirty={(d) => setDirty(d)}
|
|
121
|
+
onInit={() => console.log('Editor ready')}
|
|
122
|
+
/>
|
|
123
|
+
<button onClick={handleSave} disabled={!dirty}>
|
|
124
|
+
Save
|
|
125
|
+
</button>
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## `useEditor` Hook
|
|
132
|
+
|
|
133
|
+
For more programmatic control, use the `useEditor` hook. You provide a container element via the returned `containerRef`.
|
|
134
|
+
|
|
135
|
+
### Options
|
|
136
|
+
|
|
137
|
+
| Option | Type | Default | Description |
|
|
138
|
+
|--------|------|---------|-------------|
|
|
139
|
+
| `config` | `EditorConfig` | `{}` | Editor configuration. |
|
|
140
|
+
| `content` | `string` | `''` | Initial HTML content. |
|
|
141
|
+
| `onUpdate` | `(html: string) => void` | — | Called on content change. |
|
|
142
|
+
|
|
143
|
+
### Return Value
|
|
144
|
+
|
|
145
|
+
| Property | Type | Description |
|
|
146
|
+
|----------|------|-------------|
|
|
147
|
+
| `editor` | `HTMLEditor \| null` | The raw editor instance (`null` until initialized). |
|
|
148
|
+
| `containerRef` | `RefObject<HTMLDivElement \| null>` | Attach this to your container div. |
|
|
149
|
+
| `ready` | `boolean` | `true` once the editor has fired its `init` event. |
|
|
150
|
+
| `getContent` | `() => string` | Get current HTML content. |
|
|
151
|
+
| `setContent` | `(html: string) => void` | Replace the editor content. |
|
|
152
|
+
| `insertContent` | `(html: string) => void` | Insert HTML at the cursor. |
|
|
153
|
+
| `focus` | `() => void` | Focus the editor. |
|
|
154
|
+
| `isDirty` | `() => boolean` | Check if the editor has unsaved changes. |
|
|
155
|
+
|
|
156
|
+
### Example
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
import { useState } from 'react';
|
|
160
|
+
import { useEditor } from '@mdaemon/html-editor-react';
|
|
161
|
+
import '@mdaemon/html-editor/dist/styles.css';
|
|
162
|
+
|
|
163
|
+
function NotesEditor() {
|
|
164
|
+
const [lastSaved, setLastSaved] = useState('');
|
|
165
|
+
const { containerRef, ready, getContent, setContent, isDirty } = useEditor({
|
|
166
|
+
config: { height: 300, basicEditor: true },
|
|
167
|
+
content: '<p>Start taking notes...</p>',
|
|
168
|
+
onUpdate: (html) => console.log('Updated:', html),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const handleSave = () => {
|
|
172
|
+
setLastSaved(getContent());
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const handleReset = () => {
|
|
176
|
+
setContent('<p>Start taking notes...</p>');
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<div>
|
|
181
|
+
<div ref={containerRef} />
|
|
182
|
+
{ready && (
|
|
183
|
+
<div>
|
|
184
|
+
<button onClick={handleSave}>Save</button>
|
|
185
|
+
<button onClick={handleReset}>Reset</button>
|
|
186
|
+
<span>{isDirty() ? 'Unsaved changes' : 'Saved'}</span>
|
|
187
|
+
</div>
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Global Convenience Functions
|
|
195
|
+
|
|
196
|
+
For single-editor pages, you can get and set content without a ref:
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
import { getEditorContent, setEditorContent } from '@mdaemon/html-editor-react';
|
|
200
|
+
|
|
201
|
+
// Get content from the active editor
|
|
202
|
+
const html = getEditorContent();
|
|
203
|
+
|
|
204
|
+
// Set content on the active editor
|
|
205
|
+
setEditorContent('<p>New content</p>');
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
> **Note:** These functions operate on the most recently mounted `<Editor>` instance. They are intended for pages with a single editor.
|
|
209
|
+
|
|
210
|
+
## Configuration
|
|
211
|
+
|
|
212
|
+
The `config` prop (or `useEditor`'s `config` option) accepts an `EditorConfig` object. All options from `@mdaemon/html-editor` are supported:
|
|
213
|
+
|
|
214
|
+
| Option | Type | Default | Description |
|
|
215
|
+
|--------|------|---------|-------------|
|
|
216
|
+
| `basicEditor` | `boolean` | `false` | Use a simplified toolbar (no images, tables, code blocks). |
|
|
217
|
+
| `height` | `string \| number` | `300` | Editor height. |
|
|
218
|
+
| `language` | `string` | `'en'` | UI language code. 31 languages built in. |
|
|
219
|
+
| `skin` | `'oxide' \| 'oxide-dark'` | `'oxide'` | Toolbar and dialog theme. |
|
|
220
|
+
| `content_css` | `'default' \| 'dark'` | `'default'` | Content area theme. |
|
|
221
|
+
| `content_style` | `string` | — | Custom CSS injected into the editing surface. |
|
|
222
|
+
| `fontName` | `string` | — | Default font family. |
|
|
223
|
+
| `fontSize` | `string` | — | Default font size. |
|
|
224
|
+
| `font_family_formats` | `string` | *(TinyMCE defaults)* | Semicolon-delimited font list (`Name=family,...`). |
|
|
225
|
+
| `font_size_formats` | `string` | `'8pt 9pt 10pt 12pt 14pt 18pt 24pt 36pt'` | Space-delimited size options. |
|
|
226
|
+
| `directionality` | `'ltr' \| 'rtl'` | `'ltr'` | Text direction. |
|
|
227
|
+
| `toolbar` | `string` | *(preset)* | Custom toolbar layout string. |
|
|
228
|
+
| `toolbar_mode` | `'sliding' \| 'floating' \| 'wrap'` | `'wrap'` | Toolbar overflow behavior. |
|
|
229
|
+
| `toolbar_sticky` | `boolean` | `true` | Pin toolbar at top when scrolling. |
|
|
230
|
+
| `auto_focus` | `string` | — | Auto-focus on init. |
|
|
231
|
+
| `browser_spellcheck` | `boolean` | `true` | Enable browser spell check. |
|
|
232
|
+
| `entity_encoding` | `'raw' \| 'named' \| 'numeric'` | `'raw'` | HTML entity encoding mode. |
|
|
233
|
+
| `includeTemplates` | `boolean` | `false` | Show the template dropdown. |
|
|
234
|
+
| `templates` | `Template[]` | `[]` | Predefined HTML templates. |
|
|
235
|
+
| `dropbox` | `boolean` | `false` | Enable Dropbox integration. |
|
|
236
|
+
| `images_upload_url` | `string` | — | Server endpoint for image uploads. |
|
|
237
|
+
| `images_upload_credentials` | `boolean` | `true` | Include credentials with upload requests. |
|
|
238
|
+
| `images_upload_base_path` | `string` | `'/'` | Prefix for uploaded image URLs. |
|
|
239
|
+
| `images_upload_max_size` | `number` | `10485760` | Max upload size in bytes (10 MB). |
|
|
240
|
+
| `images_upload_headers` | `Record<string, string>` | — | Extra headers for upload requests. |
|
|
241
|
+
| `setup` | `(editor) => void` | — | Pre-init callback for registering custom toolbar buttons. |
|
|
242
|
+
|
|
243
|
+
### Dark Theme
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
<Editor
|
|
247
|
+
config={{
|
|
248
|
+
skin: 'oxide-dark',
|
|
249
|
+
content_css: 'dark',
|
|
250
|
+
}}
|
|
251
|
+
/>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Custom Content Styles
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
<Editor
|
|
258
|
+
config={{
|
|
259
|
+
content_style: 'body { font-family: Georgia, serif; font-size: 16px; line-height: 1.6; }',
|
|
260
|
+
}}
|
|
261
|
+
/>
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Templates
|
|
265
|
+
|
|
266
|
+
Enable the template dropdown and provide a `templates` array:
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
import type { Template } from '@mdaemon/html-editor-react';
|
|
270
|
+
|
|
271
|
+
const templates: Template[] = [
|
|
272
|
+
{
|
|
273
|
+
title: 'Greeting',
|
|
274
|
+
description: 'A friendly greeting',
|
|
275
|
+
content: '<p>Hello! Thank you for reaching out.</p>',
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
title: 'Signature',
|
|
279
|
+
content: '<p>Best regards,<br/>Your Name</p>',
|
|
280
|
+
},
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
<Editor
|
|
284
|
+
config={{
|
|
285
|
+
includeTemplates: true,
|
|
286
|
+
templates,
|
|
287
|
+
}}
|
|
288
|
+
/>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The `Template` interface:
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
interface Template {
|
|
295
|
+
id?: number | string;
|
|
296
|
+
title: string;
|
|
297
|
+
description?: string;
|
|
298
|
+
content: string;
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Custom Toolbar Buttons
|
|
303
|
+
|
|
304
|
+
Register custom buttons via the `setup` callback in `config`:
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
<Editor
|
|
308
|
+
config={{
|
|
309
|
+
setup: (editor) => {
|
|
310
|
+
editor.ui.registry.addButton('myButton', {
|
|
311
|
+
tooltip: 'Insert greeting',
|
|
312
|
+
text: 'Greet',
|
|
313
|
+
onAction: (api) => {
|
|
314
|
+
editor.insertContent('<p>Hello from a custom button!</p>');
|
|
315
|
+
},
|
|
316
|
+
onSetup: (api) => {
|
|
317
|
+
// api.isEnabled(), api.setEnabled(bool)
|
|
318
|
+
// api.isActive(), api.setActive(bool)
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
},
|
|
322
|
+
toolbar: 'bold italic | myButton',
|
|
323
|
+
}}
|
|
324
|
+
/>
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Button Options
|
|
328
|
+
|
|
329
|
+
| Option | Type | Description |
|
|
330
|
+
|--------|------|-------------|
|
|
331
|
+
| `tooltip` | `string` | Hover text. |
|
|
332
|
+
| `text` | `string` | Button label. |
|
|
333
|
+
| `icon` | `string` | Image URL (used instead of text). |
|
|
334
|
+
| `disabled` | `boolean` | Initial disabled state. |
|
|
335
|
+
| `onSetup` | `(api) => void \| (() => void)` | Called on creation; may return a teardown function. |
|
|
336
|
+
| `onAction` | `(api) => void` | Called on click. |
|
|
337
|
+
|
|
338
|
+
### Button API
|
|
339
|
+
|
|
340
|
+
The `api` object passed to `onSetup` and `onAction`:
|
|
341
|
+
|
|
342
|
+
| Method | Description |
|
|
343
|
+
|--------|-------------|
|
|
344
|
+
| `isEnabled()` | Returns current enabled state. |
|
|
345
|
+
| `setEnabled(enabled)` | Enable or disable the button. |
|
|
346
|
+
| `isActive()` | Returns active/pressed state. |
|
|
347
|
+
| `setActive(active)` | Toggle active/pressed visual style. |
|
|
348
|
+
|
|
349
|
+
## Custom Toolbar Layout
|
|
350
|
+
|
|
351
|
+
Provide a `toolbar` string to control which buttons appear and in what order. Use `|` to group buttons and `||` to create a collapsible overflow section:
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
<Editor
|
|
355
|
+
config={{
|
|
356
|
+
toolbar: 'bold italic underline | fontfamily fontsize || forecolor backcolor | undo redo',
|
|
357
|
+
}}
|
|
358
|
+
/>
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Buttons after `||` begin collapsed behind a toggle (`...`) button.
|
|
362
|
+
|
|
363
|
+
### Available Toolbar Buttons
|
|
364
|
+
|
|
365
|
+
| Button | Action |
|
|
366
|
+
|--------|--------|
|
|
367
|
+
| `bold` | Toggle bold |
|
|
368
|
+
| `italic` | Toggle italic |
|
|
369
|
+
| `underline` | Toggle underline |
|
|
370
|
+
| `strikethrough` | Toggle strikethrough |
|
|
371
|
+
| `bullist` | Bullet list |
|
|
372
|
+
| `numlist` | Numbered list |
|
|
373
|
+
| `outdent` | Decrease indent |
|
|
374
|
+
| `indent` | Increase indent |
|
|
375
|
+
| `blockquote` | Toggle block quote |
|
|
376
|
+
| `fontfamily` | Font family dropdown |
|
|
377
|
+
| `fontsize` | Font size dropdown |
|
|
378
|
+
| `lineheight` | Line height dropdown |
|
|
379
|
+
| `template` | Template dropdown (requires `includeTemplates: true`) |
|
|
380
|
+
| `alignleft` | Left align |
|
|
381
|
+
| `aligncenter` | Center align |
|
|
382
|
+
| `alignright` | Right align |
|
|
383
|
+
| `alignjustify` | Justify |
|
|
384
|
+
| `forecolor` | Text color picker |
|
|
385
|
+
| `backcolor` | Highlight color picker |
|
|
386
|
+
| `removeformat` | Strip all formatting |
|
|
387
|
+
| `copy` | Copy selection |
|
|
388
|
+
| `cut` | Cut selection |
|
|
389
|
+
| `paste` | Paste from clipboard |
|
|
390
|
+
| `undo` | Undo |
|
|
391
|
+
| `redo` | Redo |
|
|
392
|
+
| `image` | Insert image (upload or URL) |
|
|
393
|
+
| `charmap` | Special character picker |
|
|
394
|
+
| `emoticons` | Emoji picker with search |
|
|
395
|
+
| `code` | Toggle code block |
|
|
396
|
+
| `link` | Insert/edit hyperlink |
|
|
397
|
+
| `codesample` | Toggle code sample |
|
|
398
|
+
| `fullscreen` | Toggle fullscreen |
|
|
399
|
+
| `preview` | Preview in new window |
|
|
400
|
+
| `searchreplace` | Find & Replace dialog |
|
|
401
|
+
| `ltr` | Left-to-right direction |
|
|
402
|
+
| `rtl` | Right-to-left direction |
|
|
403
|
+
|
|
404
|
+
## Image Upload
|
|
405
|
+
|
|
406
|
+
The image toolbar button opens a dialog supporting drag-and-drop upload or direct URL entry. Supported formats: JPEG, PNG, GIF, WebP, SVG.
|
|
407
|
+
|
|
408
|
+
When `images_upload_url` is configured, files are posted as `multipart/form-data`. The server must return JSON with a `location`, `url`, or `link` field. Without an upload URL, images are embedded as base64 data URIs.
|
|
409
|
+
|
|
410
|
+
SVGs are automatically sanitized (script tags, event handlers, and dangerous elements are stripped).
|
|
411
|
+
|
|
412
|
+
```tsx
|
|
413
|
+
<Editor
|
|
414
|
+
config={{
|
|
415
|
+
images_upload_url: '/api/upload',
|
|
416
|
+
images_upload_credentials: true,
|
|
417
|
+
images_upload_base_path: '/files/',
|
|
418
|
+
images_upload_max_size: 5 * 1024 * 1024,
|
|
419
|
+
images_upload_headers: {
|
|
420
|
+
'X-CSRF-Token': csrfToken,
|
|
421
|
+
},
|
|
422
|
+
}}
|
|
423
|
+
/>
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## Localization
|
|
427
|
+
|
|
428
|
+
### Set Language at Init
|
|
429
|
+
|
|
430
|
+
```tsx
|
|
431
|
+
<Editor config={{ language: 'de' }} />
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Custom Translation Function
|
|
435
|
+
|
|
436
|
+
```tsx
|
|
437
|
+
import { setTranslate } from '@mdaemon/html-editor-react';
|
|
438
|
+
|
|
439
|
+
setTranslate((key) => myTranslations[key] ?? key);
|
|
440
|
+
|
|
441
|
+
// Or via the Editor prop:
|
|
442
|
+
<Editor translate={(key) => myTranslations[key] ?? key} config={{}} />
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Change Language at Runtime
|
|
446
|
+
|
|
447
|
+
```tsx
|
|
448
|
+
const editorRef = useRef<EditorRef>(null);
|
|
449
|
+
|
|
450
|
+
// Switch to French
|
|
451
|
+
editorRef.current?.getEditor()?.setLanguage('fr');
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Supported Languages
|
|
455
|
+
|
|
456
|
+
| Code | Language | Code | Language |
|
|
457
|
+
|------|----------|------|----------|
|
|
458
|
+
| `en` | English | `nl` | Nederlands |
|
|
459
|
+
| `ar` | العربية | `nb` | Norsk bokmal |
|
|
460
|
+
| `ca` | Catala | `pl` | Polski |
|
|
461
|
+
| `zh` | Chinese | `pt` | Portugues |
|
|
462
|
+
| `cs` | Cesky | `ro` | Romana |
|
|
463
|
+
| `da` | Dansk | `ru` | Russian |
|
|
464
|
+
| `en-gb` | English (UK) | `sr` | Srpski |
|
|
465
|
+
| `fi` | Suomi | `sl` | Slovenscina |
|
|
466
|
+
| `fr` | Francais | `es` | Espanol |
|
|
467
|
+
| `fr-ca` | Canadien francais | `sv` | Svenska |
|
|
468
|
+
| `de` | Deutsch | `zh-tw` | Chinese (Taiwan) |
|
|
469
|
+
| `el` | Greek | `th` | Thai |
|
|
470
|
+
| `hu` | Magyar | `tr` | Turkce |
|
|
471
|
+
| `id` | Bahasa Indonesia | `vi` | Tieng Viet |
|
|
472
|
+
| `it` | Italiano | | |
|
|
473
|
+
| `ja` | Japanese | | |
|
|
474
|
+
| `ko` | Korean | | |
|
|
475
|
+
|
|
476
|
+
## File Source Resolver
|
|
477
|
+
|
|
478
|
+
Transform image `src` attributes globally, useful for CDN prefixing or relative path resolution:
|
|
479
|
+
|
|
480
|
+
```tsx
|
|
481
|
+
import { setGetFileSrc } from '@mdaemon/html-editor-react';
|
|
482
|
+
|
|
483
|
+
setGetFileSrc((path) => `https://cdn.example.com${path}`);
|
|
484
|
+
|
|
485
|
+
// Or via the Editor prop:
|
|
486
|
+
<Editor getFileSrc={(path) => `https://cdn.example.com${path}`} config={{}} />
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
## Keyboard Shortcuts
|
|
490
|
+
|
|
491
|
+
| Shortcut | Action |
|
|
492
|
+
|----------|--------|
|
|
493
|
+
| Ctrl/Cmd + B | Bold |
|
|
494
|
+
| Ctrl/Cmd + I | Italic |
|
|
495
|
+
| Ctrl/Cmd + U | Underline |
|
|
496
|
+
| Ctrl/Cmd + Z | Undo |
|
|
497
|
+
| Ctrl/Cmd + Shift + Z | Redo |
|
|
498
|
+
| Ctrl/Cmd + F | Find & Replace |
|
|
499
|
+
|
|
500
|
+
## Form Integration
|
|
501
|
+
|
|
502
|
+
Use the `name` prop to render a hidden `<input>` containing the editor content, useful for traditional form submission:
|
|
503
|
+
|
|
504
|
+
```tsx
|
|
505
|
+
<form onSubmit={handleSubmit}>
|
|
506
|
+
<Editor
|
|
507
|
+
config={{}}
|
|
508
|
+
name="email_body"
|
|
509
|
+
initialValue="<p>Draft content</p>"
|
|
510
|
+
/>
|
|
511
|
+
<button type="submit">Send</button>
|
|
512
|
+
</form>
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
## Exports
|
|
516
|
+
|
|
517
|
+
```tsx
|
|
518
|
+
// Components
|
|
519
|
+
import { Editor, MDEditor } from '@mdaemon/html-editor-react'; // MDEditor is an alias
|
|
520
|
+
|
|
521
|
+
// Hook
|
|
522
|
+
import { useEditor } from '@mdaemon/html-editor-react';
|
|
523
|
+
|
|
524
|
+
// Global functions
|
|
525
|
+
import { getEditorContent, setEditorContent } from '@mdaemon/html-editor-react';
|
|
526
|
+
|
|
527
|
+
// Utilities (re-exported from @mdaemon/html-editor)
|
|
528
|
+
import { fontNames, setTranslate, setGetFileSrc } from '@mdaemon/html-editor-react';
|
|
529
|
+
|
|
530
|
+
// Types
|
|
531
|
+
import type {
|
|
532
|
+
EditorProps,
|
|
533
|
+
EditorRef,
|
|
534
|
+
UseEditorOptions,
|
|
535
|
+
UseEditorReturn,
|
|
536
|
+
EditorConfig,
|
|
537
|
+
EditorEvents,
|
|
538
|
+
Template,
|
|
539
|
+
ToolbarButtonSpec,
|
|
540
|
+
ToolbarButtonAPI,
|
|
541
|
+
} from '@mdaemon/html-editor-react';
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
## Migration from `@tinymce/tinymce-react`
|
|
545
|
+
|
|
546
|
+
```tsx
|
|
547
|
+
// Before (TinyMCE)
|
|
548
|
+
import { Editor } from '@tinymce/tinymce-react';
|
|
549
|
+
|
|
550
|
+
<Editor
|
|
551
|
+
apiKey="your-key"
|
|
552
|
+
init={{ height: 400, plugins: 'link image table' }}
|
|
553
|
+
initialValue="<p>Hello</p>"
|
|
554
|
+
onEditorChange={(content) => save(content)}
|
|
555
|
+
/>
|
|
556
|
+
|
|
557
|
+
// After (MDHTMLEditor React)
|
|
558
|
+
import { Editor } from '@mdaemon/html-editor-react';
|
|
559
|
+
import '@mdaemon/html-editor/dist/styles.css';
|
|
560
|
+
|
|
561
|
+
<Editor
|
|
562
|
+
config={{ height: 400 }}
|
|
563
|
+
initialValue="<p>Hello</p>"
|
|
564
|
+
onChange={(content) => save(content)}
|
|
565
|
+
/>
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
Key differences:
|
|
569
|
+
|
|
570
|
+
- No `apiKey` or `licenseKey` required
|
|
571
|
+
- `init` prop is renamed to `config`
|
|
572
|
+
- `onEditorChange` is renamed to `onChange`
|
|
573
|
+
- `plugins` option is not needed — all features are built in
|
|
574
|
+
- Toolbar customization uses `basicEditor: true/false` or a `toolbar` string
|
|
575
|
+
|
|
576
|
+
## Running the Demo
|
|
577
|
+
|
|
578
|
+
A demo app is included to see the editor in action:
|
|
579
|
+
|
|
580
|
+
```bash
|
|
581
|
+
npm run demo
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
This starts a Vite dev server at `http://localhost:5173` with examples of both the `<Editor>` component and `useEditor` hook.
|
|
585
|
+
|
|
586
|
+
## License
|
|
587
|
+
|
|
588
|
+
LGPL-3.0-or-later — [MDaemon Technologies, Ltd.](https://www.mdaemon.com)
|
package/package.json
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mdaemon/html-editor-react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "React wrapper for MDHTMLEditor",
|
|
5
|
+
"homepage": "https://github.com/mdaemon-technologies/MDHTMLEditor-react",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/mdaemon-technologies/MDHTMLEditor-react.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/mdaemon-technologies/MDHTMLEditor-react/issues"
|
|
12
|
+
},
|
|
5
13
|
"main": "dist/index.js",
|
|
6
14
|
"module": "dist/index.mjs",
|
|
7
15
|
"types": "dist/index.d.ts",
|
|
@@ -19,8 +27,10 @@
|
|
|
19
27
|
"dist"
|
|
20
28
|
],
|
|
21
29
|
"scripts": {
|
|
22
|
-
"build": "vite build",
|
|
23
|
-
"dev": "vite build --
|
|
30
|
+
"build": "vite build --mode production",
|
|
31
|
+
"build:dev": "vite build --mode development",
|
|
32
|
+
"dev": "vite build --watch --mode development",
|
|
33
|
+
"demo": "vite serve --config vite.demo.config.ts",
|
|
24
34
|
"test": "jest",
|
|
25
35
|
"test:coverage": "jest --coverage",
|
|
26
36
|
"lint": "eslint src --ext .ts,.tsx",
|
|
@@ -39,26 +49,27 @@
|
|
|
39
49
|
},
|
|
40
50
|
"license": "LGPL-3.0-or-later",
|
|
41
51
|
"peerDependencies": {
|
|
42
|
-
"react": "^
|
|
43
|
-
"react-dom": "^
|
|
52
|
+
"react": "^19.0.0 || ^18.0.0",
|
|
53
|
+
"react-dom": "^19.0.0 || ^18.0.0"
|
|
44
54
|
},
|
|
45
55
|
"dependencies": {
|
|
46
56
|
"@tiptap/react": "^3.19.0"
|
|
47
57
|
},
|
|
48
58
|
"devDependencies": {
|
|
49
|
-
"@mdaemon/html-editor": "^1.0.
|
|
59
|
+
"@mdaemon/html-editor": "^1.0.8",
|
|
60
|
+
"@swc/core": "^1.11.0",
|
|
61
|
+
"@swc/jest": "^0.2.39",
|
|
50
62
|
"@testing-library/jest-dom": "^6.9.1",
|
|
51
63
|
"@testing-library/react": "^16.3.2",
|
|
52
|
-
"@types/jest": "^
|
|
53
|
-
"@types/react": "^
|
|
54
|
-
"@types/react-dom": "^
|
|
64
|
+
"@types/jest": "^30.0.0",
|
|
65
|
+
"@types/react": "^19.2.0",
|
|
66
|
+
"@types/react-dom": "^19.2.0",
|
|
55
67
|
"@vitejs/plugin-react": "^5.1.4",
|
|
56
|
-
"jest": "^
|
|
57
|
-
"jest-environment-jsdom": "^
|
|
58
|
-
"react": "^
|
|
59
|
-
"react-dom": "^
|
|
60
|
-
"
|
|
61
|
-
"typescript": "^5.9.3",
|
|
68
|
+
"jest": "^30.3.0",
|
|
69
|
+
"jest-environment-jsdom": "^30.3.0",
|
|
70
|
+
"react": "^19.2.0",
|
|
71
|
+
"react-dom": "^19.2.0",
|
|
72
|
+
"typescript": "^6.0.2",
|
|
62
73
|
"vite": "^7.3.1",
|
|
63
74
|
"vite-plugin-dts": "^4.5.4"
|
|
64
75
|
}
|