@@ -2,12 +2,11 @@ import { availableLanguages } from './input-tiny-mce.languages.js';
2
2
import { defaultFallbackConfig } from './input-tiny-mce.defaults.js' ;
3
3
import { pastePreProcessHandler } from './input-tiny-mce.handlers.js' ;
4
4
import { uriAttributeSanitizer } from './input-tiny-mce.sanitizer.js' ;
5
- import type { TinyMcePluginArguments , UmbTinyMcePluginBase } from './tiny-mce-plugin.js' ;
6
- import { loadManifestApi } from '@umbraco-cms/backoffice/extension-api' ;
7
- import { css , customElement , html , property , query , state } from '@umbraco-cms/backoffice/external/lit' ;
8
- import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs' ;
9
- import { getProcessedImageUrl } from '@umbraco-cms/backoffice/utils' ;
10
- import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry' ;
5
+ import type { UmbTinyMcePluginBase } from './tiny-mce-plugin.js' ;
6
+ import { type ClassConstructor , loadManifestApi } from '@umbraco-cms/backoffice/extension-api' ;
7
+ import { css , customElement , html , property , query } from '@umbraco-cms/backoffice/external/lit' ;
8
+ import { getProcessedImageUrl , umbDeepMerge } from '@umbraco-cms/backoffice/utils' ;
9
+ import { type ManifestTinyMcePlugin , umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry' ;
11
10
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event' ;
12
11
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element' ;
13
12
import { UmbStylesheetDetailRepository , UmbStylesheetRuleManager } from '@umbraco-cms/backoffice/stylesheet' ;
@@ -53,10 +52,7 @@ export class UmbInputTinyMceElement extends UUIFormControlMixin(UmbLitElement, '
53
52
@property ( { attribute : false } )
54
53
configuration ?: UmbPropertyEditorConfigCollection ;
55
54
56
- @state ( )
57
- private _tinyConfig : RawEditorOptions = { } ;
58
-
59
- #plugins: Array < new ( args : TinyMcePluginArguments ) => UmbTinyMcePluginBase > = [ ] ;
55
+ #plugins: Array < ClassConstructor < UmbTinyMcePluginBase > | undefined > = [ ] ;
60
56
#editorRef?: Editor | null = null ;
61
57
#stylesheetRepository = new UmbStylesheetDetailRepository ( this ) ;
62
58
#umbStylesheetRuleManager = new UmbStylesheetRuleManager ( ) ;
@@ -85,15 +81,31 @@ export class UmbInputTinyMceElement extends UUIFormControlMixin(UmbLitElement, '
85
81
return this . #editorRef;
86
82
}
87
83
88
- protected async firstUpdated ( ) : Promise < void > {
89
- await Promise . all ( [ ...( await this . #loadPlugins( ) ) ] ) ;
90
- await this . #setTinyConfig( ) ;
84
+ constructor ( ) {
85
+ super ( ) ;
86
+
87
+ this . #loadEditor( ) ;
88
+ }
89
+
90
+ async #loadEditor( ) {
91
+ this . observe ( umbExtensionsRegistry . byType ( 'tinyMcePlugin' ) , async ( manifests ) => {
92
+ this . #plugins. length = 0 ;
93
+ this . #plugins = await this . #loadPlugins( manifests ) ;
94
+
95
+ let config : RawEditorOptions = { } ;
96
+ manifests . forEach ( ( manifest ) => {
97
+ if ( manifest . meta ?. config ) {
98
+ config = umbDeepMerge ( manifest . meta . config , config ) ;
99
+ }
100
+ } ) ;
101
+
102
+ this . #setTinyConfig( config ) ;
103
+ } ) ;
91
104
}
92
105
93
106
disconnectedCallback ( ) {
94
107
super . disconnectedCallback ( ) ;
95
108
96
- // TODO: Test if there is any problems with destroying the RTE here, but not initializing on connectedCallback. (firstUpdated is only called first time the element is rendered, not when it is reconnected)
97
109
this . #editorRef?. destroy ( ) ;
98
110
}
99
111
@@ -103,29 +115,14 @@ export class UmbInputTinyMceElement extends UUIFormControlMixin(UmbLitElement, '
103
115
* setup method, the asynchronous nature means the editor is loaded before
104
116
* the plugins are ready and so are not associated with the editor.
105
117
*/
106
- async #loadPlugins( ) {
107
- const observable = umbExtensionsRegistry ?. byType ( 'tinyMcePlugin' ) ;
108
- const manifests = await firstValueFrom ( observable ) ;
109
-
118
+ async #loadPlugins( manifests : Array < ManifestTinyMcePlugin > ) {
110
119
const promises = [ ] ;
111
120
for ( const manifest of manifests ) {
112
121
if ( manifest . js ) {
113
- promises . push (
114
- loadManifestApi ( manifest . js ) . then ( ( plugin ) => {
115
- if ( plugin ) {
116
- this . #plugins. push ( plugin ) ;
117
- }
118
- } ) ,
119
- ) ;
122
+ promises . push ( await loadManifestApi ( manifest . js ) ) ;
120
123
}
121
124
if ( manifest . api ) {
122
- promises . push (
123
- loadManifestApi ( manifest . api ) . then ( ( plugin ) => {
124
- if ( plugin ) {
125
- this . #plugins. push ( plugin ) ;
126
- }
127
- } ) ,
128
- ) ;
125
+ promises . push ( await loadManifestApi ( manifest . api ) ) ;
129
126
}
130
127
}
131
128
return promises ;
@@ -181,7 +178,7 @@ export class UmbInputTinyMceElement extends UUIFormControlMixin(UmbLitElement, '
181
178
return formatStyles ;
182
179
}
183
180
184
- async #setTinyConfig( ) {
181
+ async #setTinyConfig( additionalConfig ?: RawEditorOptions ) {
185
182
const dimensions = this . configuration ?. getValueByAlias < { width ?: number ; height ?: number } > ( 'dimensions' ) ;
186
183
187
184
const stylesheetPaths = this . configuration ?. getValueByAlias < string [ ] > ( 'stylesheets' ) ?? [ ] ;
@@ -230,7 +227,7 @@ export class UmbInputTinyMceElement extends UUIFormControlMixin(UmbLitElement, '
230
227
}
231
228
232
229
// set the default values that will not be modified via configuration
233
- this . _tinyConfig = {
230
+ let config : RawEditorOptions = {
234
231
autoresize_bottom_margin : 10 ,
235
232
body_class : 'umb-rte' ,
236
233
contextMenu : false ,
@@ -244,27 +241,31 @@ export class UmbInputTinyMceElement extends UUIFormControlMixin(UmbLitElement, '
244
241
setup : ( editor ) => this . #editorSetup( editor ) ,
245
242
target : this . _editorElement ,
246
243
paste_data_images : false ,
244
+ language : this . #getLanguage( ) ,
245
+ promotion : false ,
247
246
248
247
// Extend with configuration options
249
248
...configurationOptions ,
250
249
} ;
251
250
252
- this . #setLanguage( ) ;
253
-
254
- if ( this . #editorRef) {
255
- this . #editorRef. destroy ( ) ;
251
+ // Extend with additional configuration options
252
+ if ( additionalConfig ) {
253
+ config = umbDeepMerge ( additionalConfig , config ) ;
256
254
}
257
255
258
- const editors = await renderEditor ( this . _tinyConfig ) . catch ( ( error ) => {
256
+ this . #editorRef?. destroy ( ) ;
257
+
258
+ const editors = await renderEditor ( config ) . catch ( ( error ) => {
259
259
console . error ( 'Failed to render TinyMCE' , error ) ;
260
260
return [ ] ;
261
261
} ) ;
262
262
this . #editorRef = editors . pop ( ) ;
263
263
}
264
264
265
265
/**
266
- * Sets the language to use for TinyMCE */
267
- #setLanguage( ) {
266
+ * Gets the language to use for TinyMCE
267
+ **/
268
+ #getLanguage( ) {
268
269
const localeId = this . localize . lang ( ) ;
269
270
//try matching the language using full locale format
270
271
let languageMatch = availableLanguages . find ( ( x ) => localeId ?. localeCompare ( x ) === 0 ) ;
@@ -277,23 +278,12 @@ export class UmbInputTinyMceElement extends UUIFormControlMixin(UmbLitElement, '
277
278
}
278
279
}
279
280
280
- // only set if language exists, will fall back to tiny default
281
- if ( languageMatch ) {
282
- this . _tinyConfig . language = languageMatch ;
283
- }
281
+ return languageMatch ;
284
282
}
285
283
286
284
#editorSetup( editor : Editor ) {
287
285
editor . suffix = '.min' ;
288
286
289
- // instantiate plugins - these are already loaded in this.#loadPlugins
290
- // to ensure they are available before setting up the editor.
291
- // Plugins require a reference to the current editor as a param, so can not
292
- // be instantiated until we have an editor
293
- for ( const plugin of this . #plugins) {
294
- new plugin ( { host : this , editor } ) ;
295
- }
296
-
297
287
// define keyboard shortcuts
298
288
editor . addShortcut ( 'Ctrl+S' , '' , ( ) =>
299
289
this . dispatchEvent ( new CustomEvent ( 'rte.shortcut.save' , { composed : true , bubbles : true } ) ) ,
@@ -336,13 +326,24 @@ export class UmbInputTinyMceElement extends UUIFormControlMixin(UmbLitElement, '
336
326
}
337
327
} ) ;
338
328
} ) ;
339
- editor . on ( 'init' , ( ) => editor . setContent ( this . value ?. toString ( ) ?? '' ) ) ;
329
+
330
+ // instantiate plugins to ensure they are available before setting up the editor.
331
+ // Plugins require a reference to the current editor as a param, so can not
332
+ // be instantiated until we have an editor
333
+ for ( const plugin of this . #plugins) {
334
+ if ( plugin ) {
335
+ // [v15]: This might be improved by changing to `createExtensionApi` and avoiding the `#loadPlugins` method altogether, but that would require a breaking change
336
+ // because that function sends the UmbControllerHost as the first argument, which is not the case here.
337
+ new plugin ( { host : this , editor } ) ;
338
+ }
339
+ }
340
340
}
341
341
342
342
#onInit( editor : Editor ) {
343
343
//enable browser based spell checking
344
344
editor . getBody ( ) . setAttribute ( 'spellcheck' , 'true' ) ;
345
345
uriAttributeSanitizer ( editor ) ;
346
+ editor . setContent ( this . value ?. toString ( ) ?? '' ) ;
346
347
}
347
348
348
349
#onChange( value : string ) {
0 commit comments