@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.
Files changed (2) hide show
  1. package/README.md +588 -171
  2. package/package.json +26 -15
package/README.md CHANGED
@@ -1,171 +1,588 @@
1
- # MDHTMLEditor React
2
-
3
- React wrapper for MDHTMLEditor, providing a TinyMCE-compatible React component.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install @mdaemon/html-editor-react
9
- ```
10
-
11
- Note: This package requires `@mdaemon/html-editor` as a peer dependency.
12
-
13
- ## Usage
14
-
15
- ### Basic Usage
16
-
17
- ```tsx
18
- import React, { useRef } from 'react';
19
- import { MDEditor, EditorRef } from '@mdaemon/html-editor-react';
20
-
21
- function MyComponent() {
22
- const editorRef = useRef<EditorRef>(null);
23
-
24
- const handleInit = (editor) => {
25
- console.log('Editor ready:', editor);
26
- };
27
-
28
- const handleChange = (content) => {
29
- console.log('Content changed:', content);
30
- };
31
-
32
- return (
33
- <MDEditor
34
- ref={editorRef}
35
- init={{
36
- height: 400,
37
- basicEditor: false,
38
- }}
39
- initialValue="<p>Hello World</p>"
40
- onInit={handleInit}
41
- onEditorChange={handleChange}
42
- />
43
- );
44
- }
45
- ```
46
-
47
- ### useEditor Hook
48
-
49
- For more control, use the `useEditor` hook:
50
-
51
- ```tsx
52
- import { useEditor } from '@mdaemon/html-editor-react';
53
-
54
- function MyComponent() {
55
- const { containerRef, editor, content, setContent, isDirty } = useEditor({
56
- initialValue: '<p>Hello</p>',
57
- onChange: (html) => console.log('Changed:', html),
58
- });
59
-
60
- const handleSave = () => {
61
- if (isDirty) {
62
- saveContent(content);
63
- editor?.setDirty(false);
64
- }
65
- };
66
-
67
- return (
68
- <div>
69
- <div ref={containerRef} />
70
- <button onClick={handleSave}>Save</button>
71
- </div>
72
- );
73
- }
74
- ```
75
-
76
- ### Global Access
77
-
78
- For legacy compatibility, editors can be accessed globally:
79
-
80
- ```tsx
81
- import { getEditorContent, setEditorContent } from '@mdaemon/html-editor-react';
82
-
83
- // Get content from any editor by ID
84
- const content = getEditorContent('myEditor');
85
-
86
- // Set content
87
- setEditorContent('myEditor', '<p>New content</p>');
88
- ```
89
-
90
- ## Props
91
-
92
- | Prop | Type | Description |
93
- |------|------|-------------|
94
- | `id` | string | Editor identifier for global access |
95
- | `init` | EditorConfig | Configuration object passed to HTMLEditor |
96
- | `initialValue` | string | Initial HTML content |
97
- | `value` | string | Controlled content value |
98
- | `disabled` | boolean | Disable editing |
99
- | `onInit` | (editor) => void | Called when editor initializes |
100
- | `onEditorChange` | (content) => void | Called on content change |
101
- | `onDirtyChange` | (isDirty) => void | Called when dirty state changes |
102
- | `onFocus` | () => void | Called on focus |
103
- | `onBlur` | () => void | Called on blur |
104
-
105
- ## EditorRef Methods
106
-
107
- When using the `ref` prop, you have access to:
108
-
109
- - `getContent(): string` - Get current HTML
110
- - `setContent(html: string): void` - Set HTML content
111
- - `insertContent(html: string): void` - Insert at cursor
112
- - `execCommand(cmd, ui?, value?): boolean` - Run command
113
- - `focus(): void` - Focus editor
114
- - `isDirty(): boolean` - Check dirty state
115
- - `setDirty(state): void` - Set dirty state
116
-
117
- ## Configuration
118
-
119
- All configuration options from `@mdaemon/html-editor` are supported in the `init` prop:
120
-
121
- ```tsx
122
- <MDEditor
123
- init={{
124
- basicEditor: false,
125
- includeTemplates: true,
126
- templates: [
127
- { title: 'Signature', content: '<p>Best regards,<br/>John</p>' }
128
- ],
129
- dropbox: true,
130
- images_upload_url: '/api/upload',
131
- font_family_formats: 'Arial=arial;Times=times new roman',
132
- font_size_formats: '10pt 12pt 14pt 16pt',
133
- fontName: 'Arial',
134
- fontSize: '12pt',
135
- directionality: 'ltr',
136
- skin: 'oxide',
137
- height: 400,
138
- }}
139
- />
140
- ```
141
-
142
- ## Migration from @tinymce/tinymce-react
143
-
144
- This package provides a similar API to `@tinymce/tinymce-react`:
145
-
146
- ```tsx
147
- // Before (TinyMCE)
148
- import { Editor } from '@tinymce/tinymce-react';
149
-
150
- <Editor
151
- init={{ height: 400, plugins: 'link image' }}
152
- onEditorChange={handleChange}
153
- />
154
-
155
- // After (MDHTMLEditor)
156
- import { MDEditor } from '@mdaemon/html-editor-react';
157
-
158
- <MDEditor
159
- init={{ height: 400 }}
160
- onEditorChange={handleChange}
161
- />
162
- ```
163
-
164
- Key differences:
165
- - No `licenseKey` or `apiKey` required
166
- - `plugins` option not needed (features built-in)
167
- - Toolbar customization uses `basicEditor` boolean instead of `toolbar` string
168
-
169
- ## License
170
-
171
- LGPL 3.0 or later - MDaemon Technologies
1
+ # MDHTMLEditor React
2
+
3
+ A React wrapper for [MDHTMLEditor](https://github.com/mdaemon-technologies/MDHTMLEditor) &mdash; 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 &mdash; 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` | &mdash; | 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` | &mdash; | Custom CSS injected into the editing surface. |
222
+ | `fontName` | `string` | &mdash; | Default font family. |
223
+ | `fontSize` | `string` | &mdash; | 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` | &mdash; | 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` | &mdash; | 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>` | &mdash; | Extra headers for upload requests. |
241
+ | `setup` | `(editor) => void` | &mdash; | 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 &mdash; 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 &mdash; [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.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 --watch",
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": "^18.0.0 || ^17.0.0",
43
- "react-dom": "^18.0.0 || ^17.0.0"
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.2",
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": "^29.5.0",
53
- "@types/react": "^18.2.0",
54
- "@types/react-dom": "^18.2.0",
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": "^29.5.0",
57
- "jest-environment-jsdom": "^29.5.0",
58
- "react": "^18.2.0",
59
- "react-dom": "^18.2.0",
60
- "ts-jest": "^29.4.6",
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
  }