@arsedizioni/ars-utils 22.5.9 → 22.5.12

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.
@@ -1,63 +1,275 @@
1
+ import { RawEditorOptions, TinyMCE, Editor } from 'tinymce';
1
2
  import * as _angular_core from '@angular/core';
3
+ import { OnInit, OnDestroy } from '@angular/core';
4
+ import { ControlValueAccessor } from '@angular/forms';
2
5
 
6
+ /**
7
+ * The editor profiles the applications ask for, ready to be handed to
8
+ * `[tinymceConfig]` on {@link TinymceEditorDirective}.
9
+ *
10
+ * They describe **what the editor offers** — plugins, toolbar, formats, link and image
11
+ * behaviour — and nothing else. Everything about *loading* TinyMCE (`base_url`, `suffix`,
12
+ * `language`, `language_url`, `license_key`) and about the *theme* (`skin`, `content_css`)
13
+ * belongs to `TinymceLoaderService` and to the directive, which resolve them against the
14
+ * document base href and against `ThemeService`: a preset repeating them could only get them
15
+ * wrong, and used to — the old ones pinned a relative `assets/tinymce` that broke under a
16
+ * sub-path, and read the skin from `prefers-color-scheme` instead of the theme the user chose.
17
+ *
18
+ * Uploads are the same story: `automatic_uploads`, `paste_data_images` and `file_picker_types`
19
+ * follow the `tinymceImageUploader` input, so an editor without a handler cannot end up
20
+ * offering an upload tab that leads nowhere.
21
+ *
22
+ * Each accessor returns a **fresh deep copy**: a caller that tweaks a returned object — as the
23
+ * editor dialog does with its toolbar — cannot corrupt the preset for everyone else.
24
+ */
3
25
  declare class TinymceUtils {
4
26
  /**
5
- * Returns `true` when the user agent currently prefers a dark color scheme.
6
- * Evaluated on demand (not frozen at module load) and SSR-safe.
27
+ * Everything TinyMCE 8 open source has to offer, for a full-page editor.
28
+ *
29
+ * Adapted from the TinyMCE 5 profile the library used to ship: `paste`, `print`, `hr`,
30
+ * `textpattern` and the `fullpage_*` options no longer exist (paste and text patterns moved
31
+ * into the core, `fullpage` and `print` were dropped in v6), and `checklist`,
32
+ * `openCodeMirrorButton` and `insertMediaButton` are premium or application-specific buttons
33
+ * that a self-hosted GPL build has no way to render.
7
34
  */
8
- private static prefersDark;
9
- /** URL of the TinyMCE script hosted on the Tiny Cloud CDN. */
10
- static readonly CDN_URL = "https://cdn.tiny.cloud/1/5lnoc6ohmpjau6zyzgqyhyf52cueoennkcs8v1yfoak57ku9/tinymce/7/tinymce.min.js";
11
- /** URL of the locally bundled TinyMCE script. */
12
- static readonly LOCAL_URL = "/assets/tinymce/tinymce.min.js";
35
+ private static readonly FULL;
13
36
  /**
14
- * Full-featured TinyMCE base configuration with all standard plugins and toolbar.
37
+ * The one for a field inside a form: it grows with the text instead of owning the page, the
38
+ * toolbar sits under it and collapses into groups, and there is no status bar or context menu
39
+ * to compete with the form around it.
15
40
  */
16
- static TinymceConfig: Record<string, any>;
41
+ private static readonly COMPACT;
42
+ /** The compact one plus full screen, the insert group and the source view. */
43
+ private static readonly COMPACT_EXTENDED;
17
44
  /**
18
- * Compact TinyMCE configuration with autoresize, bottom toolbar, and grouped toolbar buttons.
19
- * Extends `TinymceConfig` with reduced height and a simplified toolbar.
45
+ * The full-page profile.
46
+ * @returns A fresh copy of the configuration, safe to modify.
20
47
  */
21
- static TinymceCompactConfig: Record<string, any>;
48
+ static get TinymceConfig(): RawEditorOptions;
22
49
  /**
23
- * Extended compact TinyMCE configuration that adds fullscreen support and an insert group.
24
- * Extends `TinymceCompactConfig`.
50
+ * The in-form profile.
51
+ * @returns A fresh copy of the configuration, safe to modify.
25
52
  */
26
- static TinymceCompactExtendedConfig: Record<string, any>;
53
+ static get TinymceCompactConfig(): RawEditorOptions;
27
54
  /**
28
- * Inject the TinyMCE script tag into the document head if it is not already present.
29
- * @param useCDN - When `true`, loads from the Tiny Cloud CDN; otherwise uses the local asset.
55
+ * The in-form profile with full screen and source view.
56
+ * @returns A fresh copy of the configuration, safe to modify.
30
57
  */
31
- static loadTinyMCEScript(useCDN?: boolean): void;
58
+ static get TinymceCompactExtendedConfig(): RawEditorOptions;
32
59
  }
33
60
 
34
- interface FullScreenEditorDialogData {
61
+ declare global {
62
+ interface Window {
63
+ tinymce?: TinyMCE;
64
+ }
65
+ }
66
+ /**
67
+ * Loads the self-hosted TinyMCE bundle on demand.
68
+ *
69
+ * TinyMCE is deliberately NOT imported by any module: a static import would pull ~500 kB into
70
+ * whichever chunk referenced it, and the editor is needed by exactly one dialog. The library is
71
+ * copied to `assets/tinymce` by the build (see `angular.json`) and injected here as a plain
72
+ * `<script>` the first time an editor is created, so it costs nothing until then and is served
73
+ * from the browser cache afterwards. Loading it this way also lets TinyMCE resolve its own theme,
74
+ * model, plugins and skin relative to `base_url`, which is how the self-hosted distribution
75
+ * expects to work.
76
+ */
77
+ declare class TinymceLoaderService {
78
+ /** The in-flight (or completed) load, so concurrent editors share a single script tag. */
79
+ private pending?;
80
+ /**
81
+ * The absolute URL of the TinyMCE asset folder, resolved against the document base href so the
82
+ * app keeps working when it is served from a sub-path.
83
+ * @returns The base URL, without a trailing slash.
84
+ */
85
+ get baseUrl(): string;
86
+ /**
87
+ * Loads TinyMCE, reusing the script already injected by a previous call.
88
+ * @returns A promise resolving with the global TinyMCE instance.
89
+ */
90
+ load(): Promise<TinyMCE>;
91
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<TinymceLoaderService, never>;
92
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<TinymceLoaderService>;
93
+ }
94
+
95
+ /** Uploads one image and resolves with the URL to reference it by. */
96
+ type TinymceImageUploader = (file: File) => Promise<string>;
97
+ /**
98
+ * How the editor presents itself.
99
+ *
100
+ * `fixed` is the classic editor: the content lives in an iframe and the toolbar sits above it,
101
+ * pinned so it stays reachable while the surrounding container scrolls.
102
+ *
103
+ * `inline` edits the host element in place, with no iframe and no chrome until the user focuses
104
+ * it, at which point the toolbar floats over the content. It needs an ordinary element to edit —
105
+ * a `<div>` — because there is nothing to render inside a `<textarea>`.
106
+ */
107
+ type TinymceToolbarMode = 'fixed' | 'inline';
108
+ /**
109
+ * Turns a `<textarea>` into a rich text editor backed by the self-hosted TinyMCE bundle, and wires
110
+ * it to Angular forms as a `ControlValueAccessor`, so `[(ngModel)]`, `required` and the
111
+ * pristine/dirty state keep working exactly as they do on a plain control.
112
+ *
113
+ * The library itself is fetched on demand by <see cref="TinymceLoaderService"/>: nothing about
114
+ * TinyMCE reaches the bundle until an editor is actually created.
115
+ *
116
+ * Usage: `<textarea tinymceEditor [(ngModel)]="item.text" name="text" required></textarea>`
117
+ *
118
+ */
119
+ declare class TinymceEditorDirective implements ControlValueAccessor, OnInit, OnDestroy {
120
+ /** Extra TinyMCE options, merged over (and able to override) the defaults below. */
121
+ readonly tinymceConfig: _angular_core.InputSignal<RawEditorOptions>;
122
+ /** Whether the toolbar is pinned above the content (`fixed`) or floats on focus (`inline`). */
123
+ readonly tinymceToolbarMode: _angular_core.InputSignal<TinymceToolbarMode>;
124
+ /**
125
+ * Handler invoked for every image dropped, pasted or picked in the editor. When not provided
126
+ * the upload tab is hidden and only images referenced by URL can be inserted.
127
+ */
128
+ readonly tinymceImageUploader: _angular_core.InputSignal<TinymceImageUploader>;
129
+ /** Emitted once the editor is up, for callers that want to enable UI only when it is usable. */
130
+ readonly ready: _angular_core.OutputEmitterRef<Editor>;
131
+ /** Emitted when the editor could not be loaded or initialised. */
132
+ readonly failed: _angular_core.OutputEmitterRef<Error>;
133
+ private readonly host;
134
+ private readonly loader;
135
+ private readonly themeService;
136
+ private readonly changeDetector;
137
+ private editor?;
138
+ /** True while the editor is showing itself full screen. */
139
+ private fullscreen;
140
+ /** True once the directive has been destroyed, so a late init resolves into a no-op. */
141
+ private destroyed;
142
+ /**
143
+ * True while the directive itself is pushing a value into the editor. TinyMCE raises the same
144
+ * content events for a programmatic `setContent` as for a keystroke, so without this flag
145
+ * `writeValue` would immediately echo back through `onChange` and mark a freshly loaded form
146
+ * as dirty.
147
+ */
148
+ private writingValue;
149
+ /** The value received before the editor existed, applied as soon as it does. */
150
+ private value;
151
+ private disabled;
152
+ private onChange;
153
+ private onTouched;
154
+ /**
155
+ * Leaves full screen on Escape, for the keystrokes that happen outside the content: with the
156
+ * classic editor the content is an iframe, and a keydown in there never reaches this document.
157
+ * Bound on the capture phase and stopped, so a dialog hosting the editor does not take the
158
+ * Escape and close, throwing the edit away when the user only meant to leave full screen.
159
+ */
160
+ private readonly onDocumentKeydown;
161
+ /**
162
+ * Loads TinyMCE and initialises the editor over the host textarea.
163
+ * @returns A promise that completes once the editor is ready (or has failed).
164
+ */
165
+ ngOnInit(): Promise<void>;
166
+ /**
167
+ * Tears the editor down. TinyMCE keeps global state per instance, so leaving it behind when a
168
+ * dialog closes leaks both DOM and editor registrations.
169
+ * @returns void
170
+ */
171
+ ngOnDestroy(): void;
172
+ /**
173
+ * Decides whether an Escape press means "leave full screen": it does whenever the editor is
174
+ * full screen, full stop. While full screen the editor owns Escape, and the press is stopped
175
+ * here so it can never reach the dialog hosting it — leaving full screen must not also throw
176
+ * away what is being written.
177
+ * @param event The keyboard event, from the document or from the editor's content.
178
+ * @returns True when full screen should be left.
179
+ */
180
+ private shouldEscapeLeaveFullscreen;
181
+ /**
182
+ * Asks the editor to leave full screen. Safe to call when it is not in full screen.
183
+ * @returns void
184
+ */
185
+ private exitFullscreen;
186
+ /**
187
+ * Attaches or detaches everything that only makes sense while the editor is full screen.
188
+ * @param state True when the editor has just entered full screen.
189
+ * @returns void
190
+ */
191
+ private handleFullscreenChanged;
192
+ /**
193
+ * Detaches the listener that only makes sense while the editor is full screen.
194
+ * @returns void
195
+ */
196
+ private releaseFullscreen;
197
+ /**
198
+ * Pushes a value coming from the form into the editor.
199
+ * @param value The new content, or null/undefined for an empty editor.
200
+ * @returns void
201
+ */
202
+ writeValue(value: string | null | undefined): void;
203
+ /**
204
+ * Registers the callback used to report content changes to the form.
205
+ * @param fn The callback supplied by Angular forms.
206
+ * @returns void
207
+ */
208
+ registerOnChange(fn: (value: string) => void): void;
209
+ /**
210
+ * Registers the callback used to report the first blur to the form.
211
+ * @param fn The callback supplied by Angular forms.
212
+ * @returns void
213
+ */
214
+ registerOnTouched(fn: () => void): void;
215
+ /**
216
+ * Enables or disables editing.
217
+ * @param isDisabled True to switch the editor to read-only.
218
+ * @returns void
219
+ */
220
+ setDisabledState(isDisabled: boolean): void;
221
+ /**
222
+ * Writes content into the editor when it exists, guarding against the echo described on
223
+ * <see cref="writingValue"/>.
224
+ * @param value The content to write.
225
+ * @returns void
226
+ */
227
+ private applyValue;
228
+ /**
229
+ * Applies the disabled state to the editor when it exists.
230
+ * @param isDisabled True to switch the editor to read-only.
231
+ * @returns void
232
+ */
233
+ private applyDisabled;
234
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<TinymceEditorDirective, never>;
235
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<TinymceEditorDirective, "textarea[tinymceEditor], div[tinymceEditor]", never, { "tinymceConfig": { "alias": "tinymceConfig"; "required": false; "isSignal": true; }; "tinymceToolbarMode": { "alias": "tinymceToolbarMode"; "required": false; "isSignal": true; }; "tinymceImageUploader": { "alias": "tinymceImageUploader"; "required": false; "isSignal": true; }; }, { "ready": "ready"; "failed": "failed"; }, never, never, true, never>;
236
+ }
237
+
238
+ interface TinyMceEditorDialogData {
35
239
  text: string;
36
- configuration: Record<string, any>;
240
+ configuration: RawEditorOptions;
37
241
  infoButtonLabel?: string;
38
242
  onShowInfo?: Function;
39
243
  disabled?: boolean;
40
244
  }
41
- declare class FullScreenEditorComponent {
245
+ /**
246
+ * A dialog that is nothing but an editor: the caller hands it a text and gets the edited one
247
+ * back through {@link saving}.
248
+ *
249
+ * The editor is {@link TinymceEditorDirective} on a plain `<textarea>`, so this dialog costs
250
+ * exactly what any other editor in the application costs — the TinyMCE bundle is fetched on
251
+ * first use and nothing about it reaches the initial chunk.
252
+ */
253
+ declare class TinyMceEditorComponent {
42
254
  /** Emitted with the edited text when the user saves. */
43
255
  readonly saving: _angular_core.OutputEmitterRef<string>;
44
256
  private readonly dialogRef;
45
257
  /** Dialog configuration, injected and exposed as a signal. */
46
- protected readonly dialogData: _angular_core.WritableSignal<FullScreenEditorDialogData>;
258
+ protected readonly dialogData: _angular_core.WritableSignal<TinyMceEditorDialogData>;
47
259
  /** Whether the editor is in read-only mode. */
48
260
  protected readonly disabled: _angular_core.WritableSignal<boolean>;
49
261
  /** Current editor content, kept as a plain field for [(ngModel)] two-way binding. */
50
262
  protected text: string;
51
- /** TinyMCE editor configuration. */
52
- protected tinymceConfig: Record<string, any>;
263
+ /** The profile handed to the editor directive, merged over its defaults. */
264
+ protected tinymceConfig: RawEditorOptions;
53
265
  constructor();
54
266
  /**
55
267
  * Save the current editor content and close the dialog.
56
268
  */
57
269
  protected ok(): void;
58
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<FullScreenEditorComponent, never>;
59
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<FullScreenEditorComponent, "ng-component", never, {}, { "saving": "saving"; }, never, never, true, never>;
270
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<TinyMceEditorComponent, never>;
271
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<TinyMceEditorComponent, "ng-component", never, {}, { "saving": "saving"; }, never, never, true, never>;
60
272
  }
61
273
 
62
- export { FullScreenEditorComponent, TinymceUtils };
63
- export type { FullScreenEditorDialogData };
274
+ export { TinyMceEditorComponent, TinymceEditorDirective, TinymceLoaderService, TinymceUtils };
275
+ export type { TinyMceEditorDialogData, TinymceImageUploader, TinymceToolbarMode };
@@ -1,12 +1,82 @@
1
- in application:
2
- 1) install tinymce (npm install tinymce)
3
- 1) install @tinymce/tinymce-angular (npm install @tinymce/tinymce-angular)
4
- 2) setup assets in angular.json
5
- {
6
- "glob": "**/*",
7
- "input": "node_modules/tinymce",
8
- "output": "assets/tinymce"
9
- }
10
- 3) download language from
11
- https://github.com/tinymce/tinymce/blob/develop/modules/tinymce/src/langs/it.js and put it in
12
- "assets/tinymce/langs/it.js"
1
+ # ui.tinymce
2
+
3
+ L'editor ricco della libreria: la direttiva `[tinymceEditor]`, il caricatore a runtime e i
4
+ profili di configurazione.
5
+
6
+ Non c'e' nessun wrapper Angular di mezzo. `@tinymce/tinymce-angular` era una dipendenza in piu'
7
+ da tenere allineata ad Angular ad ogni major, e in cambio dava un componente `<editor>` che non
8
+ sa fare il `ControlValueAccessor` come lo vogliamo noi: la direttiva sta su una `<textarea>`
9
+ normale, quindi `[(ngModel)]`, `required` e lo stato pristine/dirty funzionano come su qualunque
10
+ altro controllo.
11
+
12
+ ## Nell'applicazione
13
+
14
+ 1) `npm install tinymce` (peer dependency opzionale, versione 8)
15
+
16
+ 2) pubblicare il pacchetto fra gli asset, in `angular.json`:
17
+
18
+ ```json
19
+ {
20
+ "glob": "**/*",
21
+ "input": "node_modules/tinymce",
22
+ "output": "assets/tinymce"
23
+ }
24
+ ```
25
+
26
+ 3) la lingua italiana non sta dentro il pacchetto npm: `langs/it.js` e' committato in questo
27
+ entry point, va copiato in `src/assets/tinymce/langs/it.js`.
28
+
29
+ Nient'altro: **non** serve nessun `<script>` in `index.html`. `TinymceLoaderService` inietta
30
+ `assets/tinymce/tinymce.min.js` la prima volta che un editor viene creato, risolvendo l'URL
31
+ contro il base href del documento (quindi funziona anche se l'applicazione e' servita da un
32
+ sotto-percorso), e le chiamate successive riusano lo stesso script.
33
+
34
+ ## Uso
35
+
36
+ ```html
37
+ <textarea tinymceEditor [(ngModel)]="item.text" name="text" required></textarea>
38
+ ```
39
+
40
+ Con un profilo e un uploader di immagini:
41
+
42
+ ```html
43
+ <textarea tinymceEditor
44
+ [tinymceConfig]="config"
45
+ [tinymceImageUploader]="upload"
46
+ (ready)="onReady($event)"
47
+ (failed)="onFailed($event)"
48
+ [(ngModel)]="item.text" name="text"></textarea>
49
+ ```
50
+
51
+ ```typescript
52
+ protected readonly config = TinymceUtils.TinymceCompactConfig;
53
+ protected readonly upload = (file: File) => this.api.upload(file); // torna l'URL dell'immagine
54
+ ```
55
+
56
+ `tinymceToolbarMode="inline"` edita l'elemento sul posto invece di sostituirlo: richiede un
57
+ `<div>`, non una `<textarea>`, e la direttiva lo dice esplicitamente se ci si sbaglia.
58
+
59
+ ## Profili
60
+
61
+ `TinymceUtils` espone tre configurazioni, ognuna restituita come copia fresca ad ogni lettura:
62
+
63
+ | Profilo | Per cosa |
64
+ |---|---|
65
+ | `TinymceConfig` | editor a tutta pagina, tutti i plugin open source di TinyMCE 8 |
66
+ | `TinymceCompactConfig` | campo dentro una form: cresce col testo, toolbar in basso raggruppata |
67
+ | `TinymceCompactExtendedConfig` | come sopra, piu' schermo intero, gruppo inserisci e vista sorgente |
68
+
69
+ Descrivono **cosa offre l'editor** e nient'altro: caricamento (`base_url`, `language`,
70
+ `license_key`) e tema (`skin`, `content_css`) li decidono il loader e la direttiva, che li
71
+ risolvono contro il base href e contro `ThemeService`. Anche le opzioni di upload seguono
72
+ `tinymceImageUploader`, cosi' un editor senza handler non puo' mostrare una scheda di caricamento
73
+ che non porta da nessuna parte.
74
+
75
+ I profili si passano a `[tinymceConfig]` e vincono sui default della direttiva; restano suoi
76
+ `target`, `base_url`, `inline` e `setup`, che e' quello che tiene l'editor agganciato alla form.
77
+
78
+ ## Il dialog
79
+
80
+ `TinyMceEditorComponent` e' un dialog che contiene solo un editor: riceve un testo, ne
81
+ restituisce uno modificato tramite l'output `saving`. Usa il profilo compatto esteso, con la
82
+ toolbar senza schermo intero — il dialog e' gia' tutto lo spazio disponibile.