@mdaemon/html-editor 1.3.0 → 1.4.1

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
@@ -75,6 +75,8 @@ const editor = new HTMLEditor(container, {
75
75
  | Option | Type | Default | Description |
76
76
  |--------|------|---------|-------------|
77
77
  | `basicEditor` | boolean | false | Use simplified toolbar for notes/tasks |
78
+ | `readonly` | boolean | false | Start the editor in read-only mode (see `setReadOnly`) |
79
+ | `forced_root_block` | 'p' \| 'div' | 'p' | Block element produced on Enter. `'div'` gives CKEditor `ENTER_DIV` parity |
78
80
  | `includeTemplates` | boolean | false | Show template dropdown |
79
81
  | `templates` | Template[] | [] | Array of templates |
80
82
  | `dropbox` | boolean | false | Enable Dropbox integration |
@@ -83,8 +85,15 @@ const editor = new HTMLEditor(container, {
83
85
  | `images_upload_base_path` | string | '/' | Base path prefix for uploaded image URLs |
84
86
  | `images_upload_max_size` | number | 10485760 | Maximum upload file size in bytes (default 10 MB) |
85
87
  | `images_upload_headers` | Record\<string, string\> | - | Custom HTTP headers for upload requests |
88
+ | `images_file_types` | string | *(permissive)* | Comma/space-separated accepted extensions (e.g. `'jpg,jpeg,png,gif,bmp'`). Restricts uploads when set |
89
+ | `images_upload_validate` | (file: File) => string \| null | - | Pre-upload hook; return a message to reject the file, or null to allow |
90
+ | `images_upload_error` | (message: string) => void | - | Caller-supplied alert for drag-drop/paste upload rejections & failures (no dialog to show errors in) |
86
91
  | `font_family_formats` | string | *(TinyMCE defaults)* | Semicolon-separated font list (`Name=family,...`) |
87
92
  | `font_size_formats` | string | '8pt 9pt 10pt 12pt 14pt 18pt 24pt 36pt' | Space-separated font sizes |
93
+ | `font_names` | string | - | CKEditor alias for `font_family_formats` |
94
+ | `fontSize_sizes` | string | - | CKEditor alias for `font_size_formats` |
95
+ | `block_formats` | string | 'Paragraph=p;Heading 1=h1;…' | Block-format dropdown definitions (`blocks` button) |
96
+ | `style_formats` | StyleFormat[] | *(subset)* | Named styles for the Styles dropdown (`styles` button) |
88
97
  | `fontName` | string | - | Default font family |
89
98
  | `fontSize` | string | - | Default font size |
90
99
  | `directionality` | 'ltr' \| 'rtl' | 'ltr' | Text direction |
@@ -132,6 +141,8 @@ If your custom toolbar string contains no `||`, all buttons render in a single f
132
141
  - `execCommand(cmd: string, ui?: boolean, value?: any): boolean` - Execute editor command
133
142
  - `isDirty(): boolean` - Check if content has changed
134
143
  - `setDirty(state: boolean): void` - Set dirty state
144
+ - `setReadOnly(state: boolean): void` - Toggle read-only mode (disables editing and dims the toolbar)
145
+ - `isReadOnly(): boolean` - Check if the editor is read-only
135
146
  - `focus(): void` - Focus the editor
136
147
  - `hasFocus(): boolean` - Check if editor has focus
137
148
  - `setLanguage(code: string): void` - Change UI language at runtime (rebuilds toolbar)
@@ -230,6 +241,8 @@ All built-in toolbar button names that can be used in the `toolbar` config strin
230
241
  | `italic` | Toggle italic |
231
242
  | `underline` | Toggle underline |
232
243
  | `strikethrough` | Toggle strikethrough |
244
+ | `subscript` | Toggle subscript |
245
+ | `superscript` | Toggle superscript |
233
246
  | `bullist` | Toggle bullet list |
234
247
  | `numlist` | Toggle numbered list |
235
248
  | `outdent` | Decrease indent |
@@ -238,6 +251,10 @@ All built-in toolbar button names that can be used in the `toolbar` config strin
238
251
  | `fontfamily` | Font family dropdown |
239
252
  | `fontsize` | Font size dropdown |
240
253
  | `lineheight` | Line height dropdown (1, 1.2, 1.4, 1.6, 2) |
254
+ | `blocks` | Block format dropdown (Paragraph, Heading 1–6; alias `formatselect`) |
255
+ | `styles` | Named styles dropdown (configurable via `style_formats`) |
256
+ | `table` | Table dropdown (insert table + row/column/cell operations) |
257
+ | `hr` | Insert horizontal rule |
241
258
  | `template` | Template dropdown (requires `includeTemplates: true`) |
242
259
  | `alignleft` | Align left |
243
260
  | `aligncenter` | Align center |
@@ -256,6 +273,8 @@ All built-in toolbar button names that can be used in the `toolbar` config strin
256
273
  | `emoticons` | Emoji picker with search (smileys, gestures, hearts, objects, symbols, arrows) |
257
274
  | `code` | Open HTML source code editor dialog |
258
275
  | `link` | Open Insert/Edit Link dialog |
276
+ | `unlink` | Remove the link at the cursor |
277
+ | `anchor` | Insert a named anchor (`<a id>` target) |
259
278
  | `codesample` | Toggle code sample block |
260
279
  | `fullscreen` | Toggle fullscreen editing mode |
261
280
  | `preview` | Open content preview in a new window |
@@ -270,13 +289,13 @@ All built-in toolbar button names that can be used in the `toolbar` config strin
270
289
  **Full toolbar** (default when `basicEditor: false`):
271
290
 
272
291
  ```
273
- 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
292
+ bold italic underline strikethrough subscript superscript | blocks styles | bullist numlist outdent indent blockquote | fontfamily fontsize | lineheight alignleft aligncenter alignright alignjustify | forecolor backcolor | removeformat copy cut paste | undo redo | image table charmap emoticons hr | fullscreen preview | code link unlink anchor codesample | ltr rtl | searchreplace
274
293
  ```
275
294
 
276
295
  **Basic toolbar** (when `basicEditor: true`):
277
296
 
278
297
  ```
279
- 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
298
+ bold italic underline strikethrough subscript superscript | bullist numlist outdent indent | fontfamily fontsize blockquote | lineheight alignleft aligncenter alignright alignjustify | forecolor backcolor | removeformat copy cut paste | undo redo | charmap emoticons | link unlink | ltr rtl | searchreplace
280
299
  ```
281
300
 
282
301
  ## Image Upload
@@ -302,6 +321,25 @@ const editor = new HTMLEditor(container, {
302
321
  });
303
322
  ```
304
323
 
324
+ ### Restricting file types and rejecting uploads
325
+
326
+ `images_file_types` restricts which extensions are accepted (matched against the
327
+ file name, falling back to MIME for clipboard pastes that have no name). The
328
+ `images_upload_validate` hook runs before every upload — return a message to
329
+ reject the file. For drag-drop and clipboard-paste uploads there is no dialog to
330
+ show the message in, so rejections and failures are routed to `images_upload_error`
331
+ (supply your own alert/notification — the CKEditor `simpleuploads` `newAlert` flow).
332
+
333
+ ```typescript
334
+ const editor = new HTMLEditor(container, {
335
+ images_upload_url: '/api/upload',
336
+ images_file_types: 'jpg,jpeg,png,gif,bmp', // CKEditor simpleuploads parity
337
+ images_upload_validate: (file) =>
338
+ file.size === 0 ? 'Empty files are not allowed' : null,
339
+ images_upload_error: (message) => myApp.showAlert(message),
340
+ });
341
+ ```
342
+
305
343
  ## Source Code Editor
306
344
 
307
345
  The `code` toolbar button opens a modal dialog for viewing and editing the raw HTML source of the editor content. The dialog features a full-size monospace textarea with the current HTML, **Cancel** and **Save** buttons, and supports Escape to close and Tab to indent.
@@ -317,7 +355,69 @@ The `link` toolbar button opens a modal dialog for inserting or editing hyperlin
317
355
  - **Title** — the HTML `title` attribute (shown as a tooltip on hover)
318
356
  - **Open link in…** — dropdown to choose between _Current window_ or _New window_ (`target="_blank"`)
319
357
 
320
- When editing an existing link, all fields are pre-populated from the current link attributes. Clearing the URL and saving removes the link. The dialog inherits the active skin theme.
358
+ When editing an existing link, all fields are pre-populated from the current link attributes. Clearing the URL and saving removes the link. The dialog inherits the active skin theme. The separate `unlink` toolbar button removes the link at the cursor without opening the dialog.
359
+
360
+ ## Named Anchors
361
+
362
+ The `anchor` toolbar button opens a dialog to insert a named anchor — an `<a id="name">` target for in-page linking. The anchor name is required and may not contain spaces. Existing anchors in loaded content (`<a id>` with no `href`) are preserved through the editor's parse/serialize cycle.
363
+
364
+ ```typescript
365
+ // Programmatic insertion via the TipTap command:
366
+ editor.getTipTap()?.commands.setAnchor('section-1');
367
+ ```
368
+
369
+ ## Tables
370
+
371
+ The `table` toolbar button is a dropdown that inserts a table and edits the one under the cursor:
372
+
373
+ - **Insert table** — inserts a 3×3 table with a header row.
374
+ - **Insert/delete row**, **Insert/delete column**, **Merge cells**, **Split cell**, **Toggle header row**, **Delete table**.
375
+
376
+ Tables are resizable by dragging column borders.
377
+
378
+ ## Block & Style Formats
379
+
380
+ - The `blocks` button (alias `formatselect`) is a block-format dropdown — Paragraph and Heading 1–6 by default. Customize it with `block_formats` (TinyMCE syntax):
381
+
382
+ ```typescript
383
+ const editor = new HTMLEditor(container, {
384
+ block_formats: 'Paragraph=p;Title=h1;Subtitle=h2',
385
+ });
386
+ ```
387
+
388
+ - The `styles` button is a named-styles dropdown driven by `style_formats` (CKEditor `stylesSet`-compatible). Each entry applies a block element and/or inline color/background/class to the selection:
389
+
390
+ ```typescript
391
+ const editor = new HTMLEditor(container, {
392
+ style_formats: [
393
+ { title: 'Blue Title', block: 'h3', styles: { color: 'Blue' } },
394
+ { title: 'Marker', inline: 'span', styles: { 'background-color': 'Yellow' } },
395
+ { title: 'Callout', inline: 'span', classes: 'callout' },
396
+ ],
397
+ });
398
+ ```
399
+
400
+ > Note: block elements map to headings/paragraph, `color`/`background-color` map to the editor's text-color/highlight marks, and `classes` apply a CSS class to the selection. Arbitrary element wrapping from CKEditor's stylesSet (e.g. `big`, `tt`, `cite`) is not supported by the underlying TipTap schema.
401
+
402
+ ## Read-Only Mode
403
+
404
+ Start the editor read-only with `readonly: true`, or toggle it at runtime. Read-only mode disables editing and dims/blocks the toolbar.
405
+
406
+ ```typescript
407
+ const editor = new HTMLEditor(container, { readonly: true });
408
+
409
+ editor.setReadOnly(false); // make editable
410
+ editor.setReadOnly(true); // back to read-only
411
+ console.log(editor.isReadOnly());
412
+ ```
413
+
414
+ ## Enter Behavior (`forced_root_block`)
415
+
416
+ By default, pressing Enter creates a new `<p>` block. Set `forced_root_block: 'div'` for CKEditor `ENTER_DIV` parity, so new blocks (and the serialized output) use `<div>` instead. The `<div id="signature">` signature block is still preserved in either mode.
417
+
418
+ ```typescript
419
+ const editor = new HTMLEditor(container, { forced_root_block: 'div' });
420
+ ```
321
421
 
322
422
  ## Search & Replace
323
423
 
package/dist/index.d.ts CHANGED
@@ -2,6 +2,34 @@ import { Editor } from '@tiptap/core';
2
2
  import { Extension } from '@tiptap/core';
3
3
  import { Node as Node_2 } from '@tiptap/core';
4
4
 
5
+ export declare const Anchor: Node_2<AnchorOptions, any>;
6
+
7
+ export declare class AnchorDialog {
8
+ private options;
9
+ private overlay;
10
+ private dialog;
11
+ private nameInput;
12
+ private errorEl;
13
+ constructor(options: AnchorDialogOptions);
14
+ private get tiptap();
15
+ open(): void;
16
+ close(): void;
17
+ private clearError;
18
+ private showError;
19
+ private save;
20
+ private createDialog;
21
+ destroy(): void;
22
+ }
23
+
24
+ export declare interface AnchorDialogOptions {
25
+ editor: HTMLEditor;
26
+ trans: (key: string) => string;
27
+ }
28
+
29
+ export declare interface AnchorOptions {
30
+ HTMLAttributes: Record<string, unknown>;
31
+ }
32
+
5
33
  /** List of all supported locale codes */
6
34
  export declare const availableLocales: string[];
7
35
 
@@ -137,6 +165,13 @@ export declare interface DropboxFile {
137
165
  */
138
166
  export declare interface EditorConfig {
139
167
  basicEditor?: boolean;
168
+ /** Start the editor in read-only mode. */
169
+ readonly?: boolean;
170
+ /**
171
+ * Root block element produced when the user presses Enter.
172
+ * 'p' (default) emits <p>; 'div' emits <div> (CKEditor ENTER_DIV parity).
173
+ */
174
+ forced_root_block?: 'p' | 'div';
140
175
  includeTemplates?: boolean;
141
176
  templates?: Template[];
142
177
  dropbox?: boolean;
@@ -145,10 +180,36 @@ export declare interface EditorConfig {
145
180
  images_upload_base_path?: string;
146
181
  images_upload_max_size?: number;
147
182
  images_upload_headers?: Record<string, string>;
183
+ /**
184
+ * Comma- or space-separated list of accepted image file extensions
185
+ * (e.g. 'jpg,jpeg,png,gif,bmp'). When set, restricts uploads to these
186
+ * types. When omitted, the default permissive set is used.
187
+ */
188
+ images_file_types?: string;
189
+ /**
190
+ * Optional validation hook run before an image upload starts. Return a
191
+ * non-null string to reject the file (the string is shown via
192
+ * images_upload_error / the dialog). Return null to allow.
193
+ */
194
+ images_upload_validate?: (file: File) => string | null;
195
+ /**
196
+ * Caller-supplied alert/notification used to report upload rejections and
197
+ * failures from drag-drop / paste (which have no dialog to show errors in).
198
+ * Mirrors the CKEditor simpleuploads newAlert flow.
199
+ */
200
+ images_upload_error?: (message: string) => void;
148
201
  font_family_formats?: string;
149
202
  font_size_formats?: string;
203
+ /** CKEditor alias for font_size_formats (space-separated sizes). */
204
+ fontSize_sizes?: string;
205
+ /** CKEditor alias for font_family_formats (semicolon list). */
206
+ font_names?: string;
150
207
  fontName?: string;
151
208
  fontSize?: string;
209
+ /** Block format dropdown definitions, TinyMCE-style 'Paragraph=p;Heading 1=h1;...'. */
210
+ block_formats?: string;
211
+ /** Named style definitions for the Styles dropdown (CKEditor stylesSet-compatible). */
212
+ style_formats?: StyleFormat[];
152
213
  directionality?: 'ltr' | 'rtl';
153
214
  language?: string;
154
215
  height?: string | number;
@@ -301,6 +362,12 @@ export declare class HTMLEditor implements MDHTMLEditor {
301
362
  execCommand(command: string, _ui?: boolean, value?: unknown): boolean;
302
363
  isDirty(): boolean;
303
364
  setDirty(state: boolean): void;
365
+ /**
366
+ * Toggle read-only mode. Disables editing and dims the toolbar.
367
+ * Mirrors CKEditor's editor.setReadOnly(bool).
368
+ */
369
+ setReadOnly(state: boolean): void;
370
+ isReadOnly(): boolean;
304
371
  focus(): void;
305
372
  hasFocus(): boolean;
306
373
  on<K extends keyof EditorEventMap>(event: K, callback: EditorEventMap[K]): void;
@@ -348,6 +415,12 @@ export declare interface ImageUploadResult {
348
415
  */
349
416
  declare type InitCallback = (editor: MDHTMLEditor) => void;
350
417
 
418
+ export declare const InlineStyle: Extension<InlineStyleOptions, any>;
419
+
420
+ export declare interface InlineStyleOptions {
421
+ types: string[];
422
+ }
423
+
351
424
  /**
352
425
  * Check if the Web Speech API is available in the current browser
353
426
  */
@@ -399,6 +472,8 @@ export declare interface MDHTMLEditor {
399
472
  execCommand(command: string, ui?: boolean, value?: unknown): boolean;
400
473
  isDirty(): boolean;
401
474
  setDirty(state: boolean): void;
475
+ setReadOnly(state: boolean): void;
476
+ isReadOnly(): boolean;
402
477
  focus(): void;
403
478
  hasFocus(): boolean;
404
479
  on<K extends keyof EditorEventMap>(event: K, callback: EditorEventMap[K]): void;
@@ -542,6 +617,29 @@ export declare interface SpeechToTextOptions {
542
617
  trans: (key: string) => string;
543
618
  }
544
619
 
620
+ /**
621
+ * Named style definition for the Styles dropdown.
622
+ * CKEditor stylesSet-compatible shape. Either `inline` or `block` identifies
623
+ * how the style is applied; `element` is the legacy/CKEditor tag name and is
624
+ * treated as inline unless `block` is set.
625
+ */
626
+ export declare interface StyleFormat {
627
+ /** Display name in the Styles dropdown. */
628
+ title: string;
629
+ /** Inline element to apply (e.g. 'span'). */
630
+ inline?: string;
631
+ /** Block element to apply (e.g. 'h3', 'p'). */
632
+ block?: string;
633
+ /** CKEditor-style element name (mapped to inline unless block is set). */
634
+ element?: string;
635
+ /** Inline CSS styles to apply (e.g. { color: 'Blue' }). */
636
+ styles?: Record<string, string>;
637
+ /** Space-separated CSS class names to apply. */
638
+ classes?: string;
639
+ /** HTML attributes to apply (e.g. { dir: 'rtl' }). */
640
+ attributes?: Record<string, string>;
641
+ }
642
+
545
643
  /**
546
644
  * Template object for email templates
547
645
  */
@@ -574,6 +672,7 @@ export declare class Toolbar {
574
672
  private searchReplace;
575
673
  private sourceEditor;
576
674
  private linkEditor;
675
+ private anchorDialog;
577
676
  private speechToText;
578
677
  private dictation;
579
678
  private updateInterval;
@@ -603,6 +702,17 @@ export declare class Toolbar {
603
702
  private createFontSizeDropdown;
604
703
  private createLineHeightDropdown;
605
704
  private createTemplateDropdown;
705
+ private createFormatDropdown;
706
+ private getStyleFormats;
707
+ private createStylesDropdown;
708
+ /**
709
+ * Apply a StyleFormat to the current selection. Block elements map to
710
+ * heading/paragraph; color and background map to the existing Color /
711
+ * Highlight marks; classes map to the InlineStyle textStyle class.
712
+ * (Arbitrary element wrapping from CKEditor stylesSet is not supported.)
713
+ */
714
+ private applyStyleFormat;
715
+ private createTableDropdown;
606
716
  /**
607
717
  * Position a fixed-position menu below its trigger button.
608
718
  * Uses getBoundingClientRect so the menu escapes any overflow:hidden ancestor.
@@ -617,6 +727,12 @@ export declare class Toolbar {
617
727
  private updateButtonStates;
618
728
  private openImageDialog;
619
729
  private openLinkDialog;
730
+ private openAnchorDialog;
731
+ /**
732
+ * Enable or disable the whole toolbar (used for read-only mode).
733
+ * Blocks pointer interaction and dims the toolbar via CSS.
734
+ */
735
+ setEnabled(enabled: boolean): void;
620
736
  private openCharMap;
621
737
  private openEmojiPicker;
622
738
  private openSearchReplace;
@@ -726,3 +842,25 @@ declare module '@tiptap/core' {
726
842
  };
727
843
  }
728
844
  }
845
+
846
+
847
+ declare module '@tiptap/core' {
848
+ interface Commands<ReturnType> {
849
+ anchor: {
850
+ /** Insert a named anchor at the cursor. */
851
+ setAnchor: (name: string) => ReturnType;
852
+ };
853
+ }
854
+ }
855
+
856
+
857
+ declare module '@tiptap/core' {
858
+ interface Commands<ReturnType> {
859
+ inlineStyle: {
860
+ /** Apply a CSS class to the current selection via the textStyle mark. */
861
+ setInlineClass: (className: string) => ReturnType;
862
+ /** Remove the textStyle class. */
863
+ unsetInlineClass: () => ReturnType;
864
+ };
865
+ }
866
+ }