@@ -2,12 +2,11 @@ import { availableLanguages } from './input-tiny-mce.languages.js';
22import { defaultFallbackConfig } from './input-tiny-mce.defaults.js' ;
33import { pastePreProcessHandler } from './input-tiny-mce.handlers.js' ;
44import { 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' ;
1110import { UmbChangeEvent } from '@umbraco-cms/backoffice/event' ;
1211import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element' ;
1312import { UmbStylesheetDetailRepository , UmbStylesheetRuleManager } from '@umbraco-cms/backoffice/stylesheet' ;
@@ -53,10 +52,7 @@ export class UmbInputTinyMceElement extends UUIFormControlMixin(UmbLitElement, '
5352 @property ( { attribute : false } )
5453 configuration ?: UmbPropertyEditorConfigCollection ;
5554
56- @state ( )
57- private _tinyConfig : RawEditorOptions = { } ;
58-
59- #plugins: Array < new ( args : TinyMcePluginArguments ) => UmbTinyMcePluginBase > = [ ] ;
55+ #plugins: Array < ClassConstructor < UmbTinyMcePluginBase > | undefined > = [ ] ;
6056 #editorRef?: Editor | null = null ;
6157 #stylesheetRepository = new UmbStylesheetDetailRepository ( this ) ;
6258 #umbStylesheetRuleManager = new UmbStylesheetRuleManager ( ) ;
@@ -85,15 +81,31 @@ export class UmbInputTinyMceElement extends UUIFormControlMixin(UmbLitElement, '
8581 return this . #editorRef;
8682 }
8783
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+ } ) ;
91104 }
92105
93106 disconnectedCallback ( ) {
94107 super . disconnectedCallback ( ) ;
95108
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)
97109 this . #editorRef?. destroy ( ) ;
98110 }
99111
@@ -103,29 +115,14 @@ export class UmbInputTinyMceElement extends UUIFormControlMixin(UmbLitElement, '
103115 * setup method, the asynchronous nature means the editor is loaded before
104116 * the plugins are ready and so are not associated with the editor.
105117 */
106- async #loadPlugins( ) {
107- const observable = umbExtensionsRegistry ?. byType ( 'tinyMcePlugin' ) ;
108- const manifests = await firstValueFrom ( observable ) ;
109-
118+ async #loadPlugins( manifests : Array < ManifestTinyMcePlugin > ) {
110119 const promises = [ ] ;
111120 for ( const manifest of manifests ) {
112121 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 ) ) ;
120123 }
121124 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 ) ) ;
129126 }
130127 }
131128 return promises ;
@@ -181,7 +178,7 @@ export class UmbInputTinyMceElement extends UUIFormControlMixin(UmbLitElement, '
181178 return formatStyles ;
182179 }
183180
184- async #setTinyConfig( ) {
181+ async #setTinyConfig( additionalConfig ?: RawEditorOptions ) {
185182 const dimensions = this . configuration ?. getValueByAlias < { width ?: number ; height ?: number } > ( 'dimensions' ) ;
186183
187184 const stylesheetPaths = this . configuration ?. getValueByAlias < string [ ] > ( 'stylesheets' ) ?? [ ] ;
@@ -230,7 +227,7 @@ export class UmbInputTinyMceElement extends UUIFormControlMixin(UmbLitElement, '
230227 }
231228
232229 // set the default values that will not be modified via configuration
233- this . _tinyConfig = {
230+ let config : RawEditorOptions = {
234231 autoresize_bottom_margin : 10 ,
235232 body_class : 'umb-rte' ,
236233 contextMenu : false ,
@@ -244,27 +241,31 @@ export class UmbInputTinyMceElement extends UUIFormControlMixin(UmbLitElement, '
244241 setup : ( editor ) => this . #editorSetup( editor ) ,
245242 target : this . _editorElement ,
246243 paste_data_images : false ,
244+ language : this . #getLanguage( ) ,
245+ promotion : false ,
247246
248247 // Extend with configuration options
249248 ...configurationOptions ,
250249 } ;
251250
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 ) ;
256254 }
257255
258- const editors = await renderEditor ( this . _tinyConfig ) . catch ( ( error ) => {
256+ this . #editorRef?. destroy ( ) ;
257+
258+ const editors = await renderEditor ( config ) . catch ( ( error ) => {
259259 console . error ( 'Failed to render TinyMCE' , error ) ;
260260 return [ ] ;
261261 } ) ;
262262 this . #editorRef = editors . pop ( ) ;
263263 }
264264
265265 /**
266- * Sets the language to use for TinyMCE */
267- #setLanguage( ) {
266+ * Gets the language to use for TinyMCE
267+ **/
268+ #getLanguage( ) {
268269 const localeId = this . localize . lang ( ) ;
269270 //try matching the language using full locale format
270271 let languageMatch = availableLanguages . find ( ( x ) => localeId ?. localeCompare ( x ) === 0 ) ;
@@ -277,23 +278,12 @@ export class UmbInputTinyMceElement extends UUIFormControlMixin(UmbLitElement, '
277278 }
278279 }
279280
280- // only set if language exists, will fall back to tiny default
281- if ( languageMatch ) {
282- this . _tinyConfig . language = languageMatch ;
283- }
281+ return languageMatch ;
284282 }
285283
286284 #editorSetup( editor : Editor ) {
287285 editor . suffix = '.min' ;
288286
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-
297287 // define keyboard shortcuts
298288 editor . addShortcut ( 'Ctrl+S' , '' , ( ) =>
299289 this . dispatchEvent ( new CustomEvent ( 'rte.shortcut.save' , { composed : true , bubbles : true } ) ) ,
@@ -336,13 +326,24 @@ export class UmbInputTinyMceElement extends UUIFormControlMixin(UmbLitElement, '
336326 }
337327 } ) ;
338328 } ) ;
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+ }
340340 }
341341
342342 #onInit( editor : Editor ) {
343343 //enable browser based spell checking
344344 editor . getBody ( ) . setAttribute ( 'spellcheck' , 'true' ) ;
345345 uriAttributeSanitizer ( editor ) ;
346+ editor . setContent ( this . value ?. toString ( ) ?? '' ) ;
346347 }
347348
348349 #onChange( value : string ) {
0 commit comments