diff --git a/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs b/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs index fce3b36373..e48e90c0e6 100644 --- a/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs @@ -146,6 +146,11 @@ public class RichTextEditorSettings [DefaultValue(StaticInvalidElements)] public string InvalidElements { get; set; } = StaticInvalidElements; + /// + /// Cloud API Key for TinyMCE. This is required to use TinyMCE premium plugins. + /// + public string? CloudApiKey { get; set; } + public class RichTextEditorCommand { [Required] diff --git a/src/Umbraco.Core/Models/ContentEditing/RichTextEditorConfiguration.cs b/src/Umbraco.Core/Models/ContentEditing/RichTextEditorConfiguration.cs index c621aa8c59..b0e858136b 100644 --- a/src/Umbraco.Core/Models/ContentEditing/RichTextEditorConfiguration.cs +++ b/src/Umbraco.Core/Models/ContentEditing/RichTextEditorConfiguration.cs @@ -19,4 +19,7 @@ public class RichTextEditorConfiguration [DataMember(Name = "customConfig")] public IDictionary? CustomConfig { get; set; } + + [DataMember(Name = "cloudApiKey")] + public string? CloudApiKey { get; set; } } diff --git a/src/Umbraco.Web.BackOffice/PropertyEditors/RichTextPreValueController.cs b/src/Umbraco.Web.BackOffice/PropertyEditors/RichTextPreValueController.cs index 602914fe43..f075199882 100644 --- a/src/Umbraco.Web.BackOffice/PropertyEditors/RichTextPreValueController.cs +++ b/src/Umbraco.Web.BackOffice/PropertyEditors/RichTextPreValueController.cs @@ -30,7 +30,8 @@ public class RichTextPreValueController : UmbracoAuthorizedJsonController new RichTextEditorCommand { Alias = x.Alias, Mode = x.Mode, Name = x.Name }), ValidElements = settings.ValidElements, InvalidElements = settings.InvalidElements, - CustomConfig = settings.CustomConfig + CustomConfig = settings.CustomConfig, + CloudApiKey = settings.CloudApiKey, }; return config; diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index ad30a030a1..16fbf7611f 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -39,7 +39,7 @@ "ng-file-upload": "12.2.13", "nouislider": "15.7.1", "spectrum-colorpicker2": "2.0.10", - "tinymce": "6.7.3", + "tinymce": "6.8.1", "typeahead.js": "0.11.1", "underscore": "1.13.6", "wicg-inert": "3.1.2" @@ -16606,9 +16606,9 @@ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" }, "node_modules/tinymce": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.7.3.tgz", - "integrity": "sha512-J7WmYIi/gt1RvZ6Ap2oQiUjzAoiS9pfV+d4GnKuZuPu8agmlAEAInNmMvMjfCNBzHv4JnZXY7qlHUAI0IuYQVA==" + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.8.1.tgz", + "integrity": "sha512-WYPvMXIjBrXM/oBiqGCbT2a8ptiO3TWXm/xxPWDCl8SxRKMW7Rfp0Lk190E9fXmX6uh9lJMRCnmKHzvryz0ftA==" }, "node_modules/to-absolute-glob": { "version": "2.0.2", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 3d3dc9da15..f23a0ab750 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -51,7 +51,7 @@ "ng-file-upload": "12.2.13", "nouislider": "15.7.1", "spectrum-colorpicker2": "2.0.10", - "tinymce": "6.7.3", + "tinymce": "6.8.1", "typeahead.js": "0.11.1", "underscore": "1.13.6", "wicg-inert": "3.1.2" diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 7767e3c17b..e7c86c0303 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -387,8 +387,10 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s //create a baseline Config to extend upon var config = { + cloudApiKey: tinyMceConfig.cloudApiKey, + promotion: false, inline: modeInline, - plugins: plugins, + plugins: [...new Set(plugins)], custom_elements: 'umb-rte-block,~umb-rte-block-inline', valid_elements: tinyMceConfig.validElements, invalid_elements: tinyMceConfig.inValidElements, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.component.js index 4fe1beeb85..2bb5b6c3a0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.component.js @@ -177,24 +177,40 @@ vm.containerHeight = "auto"; vm.containerOverflow = "inherit"; - var promises = [blockModelObjectLoading]; + const assetPromises = [blockModelObjectLoading]; //queue file loading tinyMceAssets.forEach(function (tinyJsAsset) { - promises.push(assetsService.loadJs(tinyJsAsset, $scope)); + assetPromises.push(assetsService.loadJs(tinyJsAsset, $scope)); }); - promises.push(tinyMceService.getTinyMceEditorConfig({ + const tinyMceConfigDeferred = $q.defer(); + + //wait for assets to load before proceeding + $q.all(assetPromises).then(function () { + + tinyMceService.getTinyMceEditorConfig({ htmlId: vm.textAreaHtmlId, stylesheets: editorConfig.stylesheets, toolbar: editorConfig.toolbar, mode: editorConfig.mode - })); + }) + .then(function (tinyMceConfig) { + // Load the plugins.min.js file from the TinyMCE Cloud if a Cloud Api Key is specified + if (tinyMceConfig.cloudApiKey) { + return assetsService.loadJs(`https://cdn.tiny.cloud/1/${tinyMceConfig.cloudApiKey}/tinymce/${tinymce.majorVersion}.${tinymce.minorVersion}/plugins.min.js`) + .then(() => tinyMceConfig); + } - //wait for queue to end - $q.all(promises).then(function (result) { + return tinyMceConfig; + }) + .then(function (tinyMceConfig) { + tinyMceConfigDeferred.resolve(tinyMceConfig); + }); + }); - var standardConfig = result[promises.length - 1]; + //wait for config to be ready after assets have loaded + tinyMceConfigDeferred.promise.then(function (standardConfig) { if (height !== null) { standardConfig.plugins.splice(standardConfig.plugins.indexOf("autoresize"), 1);