@mdaemon/html-editor 1.0.5 → 1.0.7

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 CHANGED
@@ -13,16 +13,14 @@ npm install @mdaemon/html-editor
13
13
  ### Vanilla JavaScript/TypeScript
14
14
 
15
15
  ```typescript
16
- import { HTMLEditor, setTranslate } from '@mdaemon/html-editor';
16
+ import { HTMLEditor } from '@mdaemon/html-editor';
17
17
  import '@mdaemon/html-editor/styles';
18
18
 
19
- // Optional: Set up translation function
20
- setTranslate((key) => myTranslationFunction(key));
21
-
22
- // Create editor
19
+ // Create editor with built-in language support
23
20
  const container = document.getElementById('editor');
24
21
  const editor = new HTMLEditor(container, {
25
22
  height: 400,
23
+ language: 'de', // German UI — see Localization section for supported codes
26
24
  basicEditor: false, // Full toolbar
27
25
  includeTemplates: true,
28
26
  templates: [
@@ -81,15 +79,46 @@ const editor = new HTMLEditor(container, {
81
79
  | `templates` | Template[] | [] | Array of templates |
82
80
  | `dropbox` | boolean | false | Enable Dropbox integration |
83
81
  | `images_upload_url` | string | - | URL for image uploads |
84
- | `font_family_formats` | string | - | Font family options |
85
- | `font_size_formats` | string | - | Font size options |
82
+ | `images_upload_credentials` | boolean | true | Include credentials in CORS upload requests |
83
+ | `images_upload_base_path` | string | '/' | Base path prefix for uploaded image URLs |
84
+ | `images_upload_max_size` | number | 10485760 | Maximum upload file size in bytes (default 10 MB) |
85
+ | `images_upload_headers` | Record\<string, string\> | - | Custom HTTP headers for upload requests |
86
+ | `font_family_formats` | string | *(TinyMCE defaults)* | Semicolon-separated font list (`Name=family,...`) |
87
+ | `font_size_formats` | string | '8pt 9pt 10pt 12pt 14pt 18pt 24pt 36pt' | Space-separated font sizes |
86
88
  | `fontName` | string | - | Default font family |
87
89
  | `fontSize` | string | - | Default font size |
88
90
  | `directionality` | 'ltr' \| 'rtl' | 'ltr' | Text direction |
89
- | `language` | string | 'en' | UI language |
90
- | `height` | string \| number | 300 | Editor height |
91
- | `skin` | 'oxide' \| 'oxide-dark' | 'oxide' | Theme |
92
- | `toolbar_mode` | 'sliding' \| 'wrap' | 'sliding' | Toolbar overflow behavior |
91
+ | `language` | string | 'en' | UI language code (built-in translations for 31 languages) |
92
+ | `height` | string \| number | 300 | Editor height (fixed) |
93
+ | `min_height` | string \| number | - | Minimum editor height |
94
+ | `max_height` | string \| number | - | Maximum editor height |
95
+ | `auto_focus` | string | - | Auto-focus the editor on init |
96
+ | `skin` | 'oxide' \| 'oxide-dark' \| 'confab' \| 'confab-dark' | 'oxide' | Theme (see Theming section) |
97
+ | `content_css` | 'default' \| 'dark' \| 'confab' \| 'confab-dark' | 'default' | Content area styling |
98
+ | `content_style` | string | - | Custom CSS injected into the content area |
99
+ | `toolbar` | string | *(see Toolbar section)* | Custom toolbar button layout |
100
+ | `toolbar_mode` | 'sliding' \| 'floating' \| 'wrap' | 'wrap' | Toolbar overflow behavior |
101
+ | `toolbar_sticky` | boolean | true | Keep toolbar pinned at the top while scrolling |
102
+ | `browser_spellcheck` | boolean | true | Enable browser spell-check |
103
+ | `entity_encoding` | 'raw' \| 'named' \| 'numeric' | 'raw' | HTML entity encoding mode |
104
+ | `convert_unsafe_embeds` | boolean | true | Sanitize embedded content |
105
+ | `format_empty_lines` | boolean | true | Format empty lines |
106
+ | `setup` | (editor) => void | - | Callback invoked before init — use to register custom buttons |
107
+
108
+ ### Toolbar Toggle
109
+
110
+ The toolbar supports a `||` (double pipe) separator to split buttons into a **primary row** and **collapsible overflow rows**. When `||` is present, a toggle button (`…`) appears at the far right of the primary row. Clicking it shows or hides the overflow buttons.
111
+
112
+ ```typescript
113
+ const editor = new HTMLEditor(container, {
114
+ // Buttons before || are always visible; buttons after || are toggled
115
+ toolbar: 'bold italic underline strikethrough | fontfamily fontsize || alignleft aligncenter alignright | forecolor backcolor | undo redo',
116
+ });
117
+ ```
118
+
119
+ The default `FULL_TOOLBAR` and `BASIC_TOOLBAR` presets include `||` so the toggle button appears out of the box. The overflow rows start collapsed.
120
+
121
+ If your custom toolbar string contains no `||`, all buttons render in a single flat toolbar with no toggle button (backwards compatible).
93
122
 
94
123
  ## API Reference
95
124
 
@@ -103,6 +132,13 @@ const editor = new HTMLEditor(container, {
103
132
  - `setDirty(state: boolean): void` - Set dirty state
104
133
  - `focus(): void` - Focus the editor
105
134
  - `hasFocus(): boolean` - Check if editor has focus
135
+ - `setLanguage(code: string): void` - Change UI language at runtime (rebuilds toolbar)
136
+ - `getTipTap(): Editor | null` - Access the underlying TipTap editor instance
137
+ - `getConfig(): EditorConfig` - Get the resolved editor configuration
138
+ - `isBasicEditor(): boolean` - Check if the editor is in basic mode
139
+ - `on(event, callback): void` - Subscribe to an editor event
140
+ - `off(event, callback): void` - Unsubscribe from an editor event
141
+ - `fire(event, ...args): void` - Fire an editor event manually
106
142
  - `destroy(): void` - Clean up and remove editor
107
143
 
108
144
  ### Events
@@ -112,6 +148,8 @@ const editor = new HTMLEditor(container, {
112
148
  - `dirty` - Dirty state changed
113
149
  - `focus` - Editor focused
114
150
  - `blur` - Editor blurred
151
+ - `languagechange` - UI language changed (receives language code)
152
+ - `templatechange` - A template was applied (receives the `Template` object)
115
153
 
116
154
  ### Supported Commands
117
155
 
@@ -119,12 +157,394 @@ The following TinyMCE-compatible commands are supported:
119
157
 
120
158
  - `bold`, `italic`, `underline`, `strikethrough`
121
159
  - `fontname`, `fontsize`, `lineheight`
122
- - `forecolor`, `backcolor`
160
+ - `forecolor`, `hilitecolor`, `backcolor`
123
161
  - `justifyleft`, `justifycenter`, `justifyright`, `justifyfull`
124
162
  - `insertunorderedlist`, `insertorderedlist`
125
163
  - `indent`, `outdent`
126
164
  - `undo`, `redo`
127
165
  - `removeformat`
166
+ - `mceremoveeditor` - Destroys the editor instance
167
+
168
+ ## Templates
169
+
170
+ The editor supports a template dropdown that lets users insert predefined HTML snippets. Enable it with `includeTemplates: true` and provide a `templates` array.
171
+
172
+ ### Template interface
173
+
174
+ ```typescript
175
+ interface Template {
176
+ id?: number | string; // Optional identifier
177
+ title: string; // Display name in the dropdown
178
+ description?: string; // Tooltip/description shown in the dropdown
179
+ content: string; // HTML content to insert
180
+ }
181
+ ```
182
+
183
+ ### Configuration
184
+
185
+ ```typescript
186
+ const editor = new HTMLEditor(container, {
187
+ includeTemplates: true,
188
+ templates: [
189
+ {
190
+ id: 1,
191
+ title: 'Greeting',
192
+ description: 'A simple greeting message',
193
+ content: '<h2>Hello!</h2><p>Thank you for reaching out.</p>',
194
+ },
195
+ {
196
+ id: 2,
197
+ title: 'Meeting Notes',
198
+ description: 'Template for meeting minutes',
199
+ content: '<h2>Meeting Notes</h2><p><strong>Date:</strong> [date]</p><h3>Agenda</h3><ol><li>Item 1</li></ol>',
200
+ },
201
+ ],
202
+ // Make sure 'template' appears in the toolbar string
203
+ toolbar: 'bold italic | template | undo redo',
204
+ });
205
+ ```
206
+
207
+ ### Listening for template changes
208
+
209
+ When a user selects a template, the `templatechange` event fires with the full `Template` object:
210
+
211
+ ```typescript
212
+ editor.on('templatechange', (template) => {
213
+ console.log('Applied template:', template.title);
214
+ console.log('Template ID:', template.id);
215
+ console.log('Content:', template.content);
216
+ });
217
+ ```
218
+
219
+ This event fires in addition to the standard `change` event, so consumers can distinguish a template insertion from regular edits and react accordingly (e.g. populate form fields, trigger a save, or log analytics).
220
+
221
+ ## Toolbar Buttons
222
+
223
+ All built-in toolbar button names that can be used in the `toolbar` config string:
224
+
225
+ | Button | Description |
226
+ |--------|-------------|
227
+ | `bold` | Toggle bold |
228
+ | `italic` | Toggle italic |
229
+ | `underline` | Toggle underline |
230
+ | `strikethrough` | Toggle strikethrough |
231
+ | `bullist` | Toggle bullet list |
232
+ | `numlist` | Toggle numbered list |
233
+ | `outdent` | Decrease indent |
234
+ | `indent` | Increase indent |
235
+ | `blockquote` | Toggle block quote |
236
+ | `fontfamily` | Font family dropdown |
237
+ | `fontsize` | Font size dropdown |
238
+ | `lineheight` | Line height dropdown (1, 1.2, 1.4, 1.6, 2) |
239
+ | `template` | Template dropdown (requires `includeTemplates: true`) |
240
+ | `alignleft` | Align left |
241
+ | `aligncenter` | Align center |
242
+ | `alignright` | Align right |
243
+ | `alignjustify` | Justify text |
244
+ | `forecolor` | Text color picker (28 preset colors + custom hex input) |
245
+ | `backcolor` | Highlight color picker (28 preset colors + custom hex input) |
246
+ | `removeformat` | Clear all formatting |
247
+ | `copy` | Copy selection |
248
+ | `cut` | Cut selection |
249
+ | `paste` | Paste from clipboard |
250
+ | `undo` | Undo last change |
251
+ | `redo` | Redo last undo |
252
+ | `image` | Insert image dialog (upload file or enter URL) |
253
+ | `charmap` | Special character picker (currency, math, arrows, symbols, Greek, punctuation) |
254
+ | `emoticons` | Emoji picker with search (smileys, gestures, hearts, objects, symbols, arrows) |
255
+ | `code` | Toggle code block |
256
+ | `link` | Insert/edit hyperlink dialog |
257
+ | `codesample` | Toggle code sample block |
258
+ | `fullscreen` | Toggle fullscreen editing mode |
259
+ | `preview` | Open content preview in a new window |
260
+ | `searchreplace` | Open Find & Replace dialog |
261
+ | `ltr` | Set text direction to left-to-right |
262
+ | `rtl` | Set text direction to right-to-left |
263
+
264
+ ### Default toolbars
265
+
266
+ **Full toolbar** (default when `basicEditor: false`):
267
+
268
+ ```
269
+ bold italic underline strikethrough | bullist numlist outdent indent blockquote | fontfamily fontsize || lineheight alignleft aligncenter alignright alignjustify | forecolor backcolor | removeformat copy cut paste | undo redo | image charmap emoticons | fullscreen preview | code link codesample | ltr rtl | searchreplace
270
+ ```
271
+
272
+ **Basic toolbar** (when `basicEditor: true`):
273
+
274
+ ```
275
+ bold italic underline strikethrough | bullist numlist outdent indent | fontfamily fontsize blockquote || lineheight alignleft aligncenter alignright alignjustify | forecolor backcolor | removeformat copy cut paste | undo redo | charmap emoticons | link | ltr rtl | searchreplace
276
+ ```
277
+
278
+ ## Image Upload
279
+
280
+ The image button opens a two-tab dialog:
281
+
282
+ - **Upload** — Drag-and-drop or browse for a local file. Supports JPEG, PNG, GIF, WebP, and SVG.
283
+ - **URL** — Enter an image URL directly.
284
+
285
+ If `images_upload_url` is configured, files are sent as `multipart/form-data` to that endpoint. The server response must be JSON containing a `location`, `url`, or `link` field with the resulting image URL. When no upload URL is set, images are embedded as base64 data URIs.
286
+
287
+ SVG files are automatically sanitized — dangerous elements (`<script>`, `<foreignObject>`, `<animate*>`) and event-handler attributes are stripped before insertion.
288
+
289
+ ```typescript
290
+ const editor = new HTMLEditor(container, {
291
+ images_upload_url: '/api/upload',
292
+ images_upload_credentials: true, // send cookies with CORS requests
293
+ images_upload_base_path: '/files/', // prefix for returned URLs
294
+ images_upload_max_size: 5 * 1024 * 1024, // 5 MB limit
295
+ images_upload_headers: {
296
+ 'X-CSRF-Token': token,
297
+ },
298
+ });
299
+ ```
300
+
301
+ ## Search & Replace
302
+
303
+ The `searchreplace` toolbar button (or **Ctrl/Cmd+F**) opens a Find & Replace dialog with:
304
+
305
+ - Case-sensitive matching toggle
306
+ - Whole-word matching toggle
307
+ - Find next / Find previous navigation
308
+ - Replace current match
309
+ - Replace all matches
310
+
311
+ **Dialog keyboard shortcuts:** Enter = find next, Shift+Enter = find previous, Escape = close.
312
+
313
+ ## Keyboard Shortcuts
314
+
315
+ | Shortcut | Action |
316
+ |----------|--------|
317
+ | Ctrl/Cmd+B | Toggle bold |
318
+ | Ctrl/Cmd+I | Toggle italic |
319
+ | Ctrl/Cmd+U | Toggle underline |
320
+ | Ctrl/Cmd+Z | Undo |
321
+ | Ctrl/Cmd+Shift+Z | Redo |
322
+ | Ctrl/Cmd+F | Open Find & Replace |
323
+
324
+ ## Localization
325
+
326
+ The editor ships with built-in translations for 31 languages. Set the language at construction or switch dynamically at runtime.
327
+
328
+ ### Setting the language
329
+
330
+ ```typescript
331
+ // At construction
332
+ const editor = new HTMLEditor(container, { language: 'fr' });
333
+
334
+ // Change at runtime — toolbar and dialogs update immediately
335
+ editor.setLanguage('ja');
336
+
337
+ // Listen for language changes
338
+ editor.on('languagechange', (code) => {
339
+ console.log('Language changed to:', code);
340
+ });
341
+ ```
342
+
343
+ ### Custom translations
344
+
345
+ If you need translations the built-in locales don't cover, you can provide your own function. A custom `setTranslate()` call takes priority over the built-in locale at construction time.
346
+
347
+ ```typescript
348
+ import { setTranslate, TRANSLATION_KEYS } from '@mdaemon/html-editor';
349
+
350
+ // See all keys that need translating
351
+ console.log(TRANSLATION_KEYS);
352
+
353
+ // Provide a custom translation function
354
+ setTranslate((key) => myCustomLookup(key));
355
+ ```
356
+
357
+ ### Supported languages
358
+
359
+ | Code | Language | Code | Language |
360
+ |------|----------|------|----------|
361
+ | `en` | English | `nl` | Nederlands |
362
+ | `ar` | العربية | `nb` | Norsk bokmål |
363
+ | `ca` | Català | `pl` | Polski |
364
+ | `zh` | 中文 | `pt` | Português |
365
+ | `cs` | Česky | `ro` | Româna |
366
+ | `da` | Dansk | `ru` | Русский |
367
+ | `en-gb` | English (UK) | `sr` | Srpski |
368
+ | `fi` | Suomi | `sl` | Slovenščina |
369
+ | `fr` | Français | `es` | Español |
370
+ | `fr-ca` | Canadien français | `sv` | Svenska |
371
+ | `de` | Deutsch | `zh-tw` | 繁體中文 (Taiwan) |
372
+ | `el` | Ελληνικά | `th` | ไทย |
373
+ | `hu` | Magyar | `tr` | Türkçe |
374
+ | `id` | Bahasa Indonesia | `vi` | Tiếng Việt |
375
+ | `it` | Italiano | | |
376
+ | `ja` | 日本語 | | |
377
+ | `ko` | 한글 | | |
378
+
379
+ ### Discovering available locales
380
+
381
+ ```typescript
382
+ import { availableLocales, getLocale } from '@mdaemon/html-editor';
383
+
384
+ console.log(availableLocales); // ['en', 'ar', 'ca', ...]
385
+ console.log(getLocale('de')); // { Bold: 'Fett', Italic: 'Kursiv', ... }
386
+ ```
387
+
388
+ ## Global Utilities
389
+
390
+ These functions are exported from the package and operate globally (affecting all editor instances).
391
+
392
+ ### Translation
393
+
394
+ ```typescript
395
+ import { setTranslate, getTranslate, resetTranslate, createTranslateFunction } from '@mdaemon/html-editor';
396
+
397
+ // Override translations for all editors
398
+ setTranslate((key) => myLookup(key));
399
+
400
+ // Get the current translate function
401
+ const t = getTranslate();
402
+
403
+ // Reset to built-in locale translations
404
+ resetTranslate();
405
+
406
+ // Create a translate function for a specific locale
407
+ const translateDe = createTranslateFunction('de');
408
+ console.log(translateDe('Bold')); // 'Fett'
409
+ ```
410
+
411
+ ### File source resolver
412
+
413
+ The file source resolver lets you transform image `src` attributes (e.g. to prepend a CDN URL or resolve relative paths).
414
+
415
+ ```typescript
416
+ import { setGetFileSrc, getGetFileSrc } from '@mdaemon/html-editor';
417
+
418
+ setGetFileSrc((path) => `https://cdn.example.com${path}`);
419
+
420
+ const resolver = getGetFileSrc();
421
+ console.log(resolver('/img/photo.jpg')); // 'https://cdn.example.com/img/photo.jpg'
422
+ ```
423
+
424
+ ### Exported constants
425
+
426
+ ```typescript
427
+ import { fontNames, CHAR_MAP, EMOJI_CATEGORIES, TRANSLATION_KEYS } from '@mdaemon/html-editor';
428
+
429
+ // fontNames — default semicolon-separated font family string
430
+ // CHAR_MAP — array of character categories for the character map dialog
431
+ // EMOJI_CATEGORIES — array of emoji categories for the emoji picker
432
+ // TRANSLATION_KEYS — array of all translation key strings
433
+ ```
434
+
435
+ ## Custom Toolbar Button API
436
+
437
+ The `setup` callback receives a TinyMCE-compatible editor API. Use `ui.registry.addButton()` to register custom buttons.
438
+
439
+ ```typescript
440
+ const editor = new HTMLEditor(container, {
441
+ toolbar: 'bold italic | myButton | undo redo',
442
+ setup: (ed) => {
443
+ ed.ui.registry.addButton('myButton', {
444
+ tooltip: 'My Custom Button',
445
+ text: 'Click Me', // text label on the button
446
+ icon: '/icons/custom.svg', // image URL (alternative to text)
447
+ disabled: false,
448
+ onSetup: (api) => {
449
+ // Called when the button is created
450
+ // api.setActive(true) — toggle active state
451
+ // api.setEnabled(false) — disable the button
452
+ // Return a teardown function (optional)
453
+ return () => { /* cleanup */ };
454
+ },
455
+ onAction: (api) => {
456
+ ed.insertContent('<p>Custom content!</p>');
457
+ },
458
+ });
459
+ },
460
+ });
461
+ ```
462
+
463
+ ### ToolbarButtonAPI
464
+
465
+ The `api` object passed to `onSetup` and `onAction`:
466
+
467
+ | Method | Description |
468
+ |--------|-------------|
469
+ | `isEnabled()` | Whether the button is currently enabled |
470
+ | `setEnabled(enabled)` | Enable or disable the button |
471
+ | `isActive()` | Whether the button is in an active/pressed state |
472
+ | `setActive(active)` | Toggle the active/pressed visual state |
473
+
474
+ ## Theming
475
+
476
+ ### Light and dark themes
477
+
478
+ Set `skin` to switch between light and dark chrome:
479
+
480
+ ```typescript
481
+ const editor = new HTMLEditor(container, {
482
+ skin: 'oxide-dark', // dark toolbar and dialogs
483
+ content_css: 'dark', // dark content area
484
+ });
485
+ ```
486
+
487
+ ### Confab skin
488
+
489
+ The `confab` and `confab-dark` skins integrate with the WorldClient theming system. They use CSS custom properties from the host application so the editor automatically adapts when the app theme changes.
490
+
491
+ The confab skins also replace the default text/emoji toolbar icons with clean SVG line icons for a more polished look.
492
+
493
+ ```typescript
494
+ // Light mode
495
+ const editor = new HTMLEditor(container, {
496
+ skin: 'confab',
497
+ content_css: 'confab',
498
+ });
499
+
500
+ // Dark mode
501
+ const editor = new HTMLEditor(container, {
502
+ skin: 'confab-dark',
503
+ content_css: 'confab-dark',
504
+ });
505
+ ```
506
+
507
+ The confab skins expect the following CSS custom properties to be defined by the host application:
508
+
509
+ | Variable | Purpose |
510
+ |----------|---------||
511
+ | `--theme-primary` | Primary brand color (buttons, active states) |
512
+ | `--theme-primary-hover` | Hover state for primary color |
513
+ | `--color-confab-gray-50` to `--color-confab-gray-900` | Gray scale for backgrounds, text, borders |
514
+ | `--color-confab-500` | Focus ring color |
515
+ | `--color-dark-bg-primary` | Dark mode primary background |
516
+ | `--color-dark-bg-secondary` | Dark mode toolbar/panel background |
517
+ | `--color-dark-bg-tertiary` | Dark mode inset/recessed background |
518
+ | `--color-dark-bg-hover` | Dark mode hover state |
519
+ | `--color-dark-text-primary` | Dark mode primary text |
520
+ | `--color-dark-text-secondary` | Dark mode secondary text |
521
+ | `--color-dark-text-muted` | Dark mode muted text |
522
+ | `--color-dark-border` | Dark mode border color |
523
+
524
+ All variables include fallback values so the editor remains usable without the host CSS, though colors may not match the intended design.
525
+
526
+ ### Custom content styles
527
+
528
+ Inject additional CSS into the editor content area:
529
+
530
+ ```typescript
531
+ const editor = new HTMLEditor(container, {
532
+ content_style: 'body { font-family: Georgia, serif; font-size: 16px; }',
533
+ });
534
+ ```
535
+
536
+ ### CSS classes
537
+
538
+ The editor DOM uses BEM-style classes you can target for further customization:
539
+
540
+ - `.md-editor` — outer wrapper
541
+ - `.md-editor-fullscreen` — applied in fullscreen mode
542
+ - `.md-editor-oxide` / `.md-editor-oxide-dark` — oxide theme variant
543
+ - `.md-editor-confab` / `.md-editor-confab-dark` — confab theme variant
544
+ - `.md-toolbar` — toolbar container
545
+ - `.md-toolbar-sticky` — sticky toolbar
546
+ - `.md-toolbar-primary` / `.md-toolbar-overflow` — primary and collapsible toolbar rows
547
+ - `.md-toolbar-btn` / `.md-toolbar-btn-active` — toolbar buttons
128
548
 
129
549
  ## Browser Support
130
550