From 853c1acf7e53821c887d388895fdc61144fa6663 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 20 May 2025 13:10:02 +0100 Subject: [PATCH 01/82] Bumped version to 15.4.2 --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 3780adcd49..921bad63ef 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "15.4.1", + "version": "15.4.2", "assemblyVersion": { "precision": "build" }, From d920e93d1ee29dc3301697e444f53e8cd5db3cf9 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 3 Jun 2025 05:21:11 +0200 Subject: [PATCH 02/82] Merge commit from fork --- .../ContentSettingsExtensions.cs | 17 +++-- .../ContentSettingsExtensionsTests.cs | 76 +++++++++++++++++++ 2 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/ContentSettingsExtensionsTests.cs diff --git a/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs b/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs index 23a67f3267..35ce4a2933 100644 --- a/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs +++ b/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs @@ -5,14 +5,17 @@ namespace Umbraco.Extensions; public static class ContentSettingsExtensions { /// - /// Determines if file extension is allowed for upload based on (optional) white list and black list - /// held in settings. - /// Allow upload if extension is whitelisted OR if there is no whitelist and extension is NOT blacklisted. + /// Determines if file extension is allowed for upload based on (optional) allow list and deny list held in settings. + /// Disallowed file extensions are only considered if there are no allowed file extensions. /// - public static bool IsFileAllowedForUpload(this ContentSettings contentSettings, string extension) => - contentSettings.AllowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension)) || - (contentSettings.AllowedUploadedFileExtensions.Any() == false && - contentSettings.DisallowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension)) == false); + /// The content settings. + /// The file extension. + /// + /// true if the file extension is allowed for upload; otherwise, false. + /// + public static bool IsFileAllowedForUpload(this ContentSettings contentSettings, string extension) + => contentSettings.AllowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension.Trim())) || + (contentSettings.AllowedUploadedFileExtensions.Any() == false && contentSettings.DisallowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension.Trim())) == false); /// /// Gets the auto-fill configuration for a specified property alias. diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/ContentSettingsExtensionsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/ContentSettingsExtensionsTests.cs new file mode 100644 index 0000000000..97d2202ef2 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/ContentSettingsExtensionsTests.cs @@ -0,0 +1,76 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration; + +[TestFixture] +public class ContentSettingsExtensionsTests +{ + [TestCase("jpg")] + [TestCase("JPG")] + [TestCase("jpg ")] + public void IsFileAllowedForUpload_Allows_File_In_Allow_List(string extension) + { + var contentSettings = new ContentSettings + { + AllowedUploadedFileExtensions = ["jpg", "png"], + }; + + Assert.IsTrue(contentSettings.IsFileAllowedForUpload(extension)); + } + + [TestCase("gif")] + [TestCase("GIF")] + [TestCase("gif ")] + public void IsFileAllowedForUpload_Rejects_File_Not_In_Allow_List(string extension) + { + var contentSettings = new ContentSettings + { + AllowedUploadedFileExtensions = ["jpg", "png"], + }; + + Assert.IsFalse(contentSettings.IsFileAllowedForUpload(extension)); + } + + [TestCase("jpg")] + [TestCase("JPG")] + [TestCase("jpg ")] + public void IsFileAllowedForUpload_Allows_File_Not_In_Disallow_List(string extension) + { + var contentSettings = new ContentSettings + { + DisallowedUploadedFileExtensions = ["gif", "png"], + }; + + Assert.IsTrue(contentSettings.IsFileAllowedForUpload(extension)); + } + + [TestCase("gif")] + [TestCase("GIF")] + [TestCase("gif ")] + public void IsFileAllowedForUpload_Rejects_File_In_Disallow_List(string extension) + { + var contentSettings = new ContentSettings + { + DisallowedUploadedFileExtensions = ["gif", "png"], + }; + + Assert.IsFalse(contentSettings.IsFileAllowedForUpload(extension)); + } + + [Test] + public void IsFileAllowedForUpload_Allows_File_In_Allow_List_Even_If_Also_In_Disallow_List() + { + var contentSettings = new ContentSettings + { + AllowedUploadedFileExtensions = ["jpg", "png"], + DisallowedUploadedFileExtensions = ["jpg"], + }; + + Assert.IsTrue(contentSettings.IsFileAllowedForUpload("jpg")); + } +} From 3468177c0c5e6fb90bcde97f49986d4664221c29 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 10 Jun 2025 08:22:02 +0200 Subject: [PATCH 03/82] Fix issues with removal of user logins on change to external login provider configuration (16) (#19512) * Ensure to delete related tokens when removing logins for removed external login providers. Ensure to avoid removing logins for members. * Removed unnecessary <= check. --- .../Implement/ExternalLoginRepository.cs | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs index 766d654c08..1ba91ab3e1 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs @@ -57,10 +57,17 @@ internal class ExternalLoginRepository : EntityRepositoryBase("WHERE userOrMemberKey=@userOrMemberKey", new { userOrMemberKey }); /// - public void DeleteUserLoginsForRemovedProviders(IEnumerable currentLoginProviders) => - Database.Execute(Sql() - .Delete() - .WhereNotIn(x => x.LoginProvider, currentLoginProviders)); + public void DeleteUserLoginsForRemovedProviders(IEnumerable currentLoginProviders) + { + Sql sql = Sql() + .Select(x => x.Id) + .From() + .Where(x => !x.LoginProvider.StartsWith(Constants.Security.MemberExternalAuthenticationTypePrefix)) // Only remove external logins relating to backoffice users, not members. + .WhereNotIn(x => x.LoginProvider, currentLoginProviders); + + var toDelete = Database.Query(sql).Select(x => x.Id).ToList(); + DeleteExternalLogins(toDelete); + } /// public void Save(Guid userOrMemberKey, IEnumerable logins) @@ -100,13 +107,7 @@ internal class ExternalLoginRepository : EntityRepositoryBase 0) - { - // Before we can remove the external login, we must remove the external login tokens associated with that external login, - // otherwise we'll get foreign key constraint errors - Database.DeleteMany().Where(x => toDelete.Contains(x.ExternalLoginId)).Execute(); - Database.DeleteMany().Where(x => toDelete.Contains(x.Id)).Execute(); - } + DeleteExternalLogins(toDelete); foreach (KeyValuePair u in toUpdate) { @@ -116,6 +117,19 @@ internal class ExternalLoginRepository : EntityRepositoryBase ExternalLoginFactory.BuildDto(userOrMemberKey, i))); } + private void DeleteExternalLogins(List externalLoginIds) + { + if (externalLoginIds.Count == 0) + { + return; + } + + // Before we can remove the external login, we must remove the external login tokens associated with that external login, + // otherwise we'll get foreign key constraint errors + Database.DeleteMany().Where(x => externalLoginIds.Contains(x.ExternalLoginId)).Execute(); + Database.DeleteMany().Where(x => externalLoginIds.Contains(x.Id)).Execute(); + } + /// public void Save(Guid userOrMemberKey, IEnumerable tokens) { From 69224cb53cb09fcc7d76c3c5e14f461d8ad6b19d Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Tue, 10 Jun 2025 08:54:33 +0100 Subject: [PATCH 04/82] Tiptap RTE: Toolbar/statusbar config initial value state (#19514) Tiptap configuration waits until initialized to set value Fixes #19009 --- ...editor-ui-tiptap-statusbar-configuration.element.ts | 10 ++++++++-- ...y-editor-ui-tiptap-toolbar-configuration.element.ts | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-statusbar-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-statusbar-configuration.element.ts index c4c3d85839..7d503645ff 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-statusbar-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-statusbar-configuration.element.ts @@ -20,6 +20,8 @@ export class UmbPropertyEditorUiTiptapStatusbarConfigurationElement this._availableExtensions = this.#context.filterExtensions(query); }, 250); + #initialized = false; + @state() private _availableExtensions: Array = []; @@ -33,6 +35,7 @@ export class UmbPropertyEditorUiTiptapStatusbarConfigurationElement this.#value = value; } get value(): UmbTiptapStatusbarValue | undefined { + if (this.#value === undefined) return undefined; return this.#context.cloneStatusbarValue(this.#value); } #value?: UmbTiptapStatusbarValue; @@ -54,14 +57,17 @@ export class UmbPropertyEditorUiTiptapStatusbarConfigurationElement this.observe(this.#context.statusbar, (statusbar) => { if (!statusbar.length) return; this._statusbar = statusbar; - this.#value = statusbar.map((area) => [...area.data]); - propertyContext?.setValue(this.#value); + if (this.#initialized) { + this.#value = statusbar.map((area) => [...area.data]); + propertyContext?.setValue(this.#value); + } }); }); } protected override firstUpdated() { this.#context.setStatusbar(this.#value); + this.#initialized = true; } #onClick(item: UmbTiptapStatusbarExtension) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts index f548f9598b..e28de7e314 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts @@ -24,6 +24,8 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement this._availableExtensions = this.#context.filterExtensions(query); }, 250); + #initialized = false; + @state() private _availableExtensions: Array = []; @@ -37,6 +39,7 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement this.#value = this.#context.isValidToolbarValue(value) ? value : [[[]]]; } get value(): UmbTiptapToolbarValue | undefined { + if (this.#value === undefined) return undefined; return this.#context.cloneToolbarValue(this.#value); } #value?: UmbTiptapToolbarValue; @@ -58,14 +61,17 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement this.observe(this.#context.toolbar, (toolbar) => { if (!toolbar.length) return; this._toolbar = toolbar; - this.#value = toolbar.map((rows) => rows.data.map((groups) => [...groups.data])); - propertyContext?.setValue(this.#value); + if (this.#initialized) { + this.#value = toolbar.map((rows) => rows.data.map((groups) => [...groups.data])); + propertyContext?.setValue(this.#value); + } }); }); } protected override firstUpdated() { this.#context.setToolbar(this.value); + this.#initialized = true; } #onClick(item: UmbTiptapToolbarExtension) { From 8d2ff6f92a57f16aabec4a22746dc7b77e288712 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 10 Jun 2025 10:28:45 +0200 Subject: [PATCH 05/82] Optimize initialization of document URLs on start-up (#19498) * Optimize initialization of document URLs on startup. * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Services/DocumentUrlService.cs | 70 ++++++---- .../Services/DocumentUrlServiceTests.cs | 127 ++++++++++++++++++ 2 files changed, 172 insertions(+), 25 deletions(-) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DocumentUrlServiceTests.cs diff --git a/src/Umbraco.Core/Services/DocumentUrlService.cs b/src/Umbraco.Core/Services/DocumentUrlService.cs index c73b2c41e6..c9bc338906 100644 --- a/src/Umbraco.Core/Services/DocumentUrlService.cs +++ b/src/Umbraco.Core/Services/DocumentUrlService.cs @@ -44,26 +44,51 @@ public class DocumentUrlService : IDocumentUrlService /// /// Model used to cache a single published document along with all it's URL segments. /// - private class PublishedDocumentUrlSegments + /// Internal for the purpose of unit and benchmark testing. + internal class PublishedDocumentUrlSegments { + /// + /// Gets or sets the document key. + /// public required Guid DocumentKey { get; set; } + /// + /// Gets or sets the language Id. + /// public required int LanguageId { get; set; } + /// + /// Gets or sets the collection of for the document, language and state. + /// public required IList UrlSegments { get; set; } + /// + /// Gets or sets a value indicating whether the document is a draft version or not. + /// public required bool IsDraft { get; set; } + /// + /// Model used to represent a URL segment for a document in the cache. + /// public class UrlSegment { + /// + /// Initializes a new instance of the class. + /// public UrlSegment(string segment, bool isPrimary) { Segment = segment; IsPrimary = isPrimary; } + /// + /// Gets the URL segment string. + /// public string Segment { get; } + /// + /// Gets a value indicating whether this URL segment is the primary one for the document, language and state. + /// public bool IsPrimary { get; } } } @@ -168,45 +193,40 @@ public class DocumentUrlService : IDocumentUrlService scope.Complete(); } - private static IEnumerable ConvertToCacheModel(IEnumerable publishedDocumentUrlSegments) + /// + /// Converts a collection of to a collection of for caching purposes. + /// + /// The collection of retrieved from the database on startup. + /// The collection of cache models. + /// Internal for the purpose of unit and benchmark testing. + internal static IEnumerable ConvertToCacheModel(IEnumerable publishedDocumentUrlSegments) { - var cacheModels = new List(); + var cacheModels = new Dictionary<(Guid DocumentKey, int LanguageId, bool IsDraft), PublishedDocumentUrlSegments>(); + foreach (PublishedDocumentUrlSegment model in publishedDocumentUrlSegments) { - PublishedDocumentUrlSegments? existingCacheModel = GetModelFromCache(cacheModels, model); - if (existingCacheModel is null) + (Guid DocumentKey, int LanguageId, bool IsDraft) key = (model.DocumentKey, model.LanguageId, model.IsDraft); + + if (!cacheModels.TryGetValue(key, out PublishedDocumentUrlSegments? existingCacheModel)) { - cacheModels.Add(new PublishedDocumentUrlSegments + cacheModels[key] = new PublishedDocumentUrlSegments { DocumentKey = model.DocumentKey, LanguageId = model.LanguageId, UrlSegments = [new PublishedDocumentUrlSegments.UrlSegment(model.UrlSegment, model.IsPrimary)], IsDraft = model.IsDraft, - }); + }; } else { - existingCacheModel.UrlSegments = GetUpdatedUrlSegments(existingCacheModel.UrlSegments, model.UrlSegment, model.IsPrimary); + if (existingCacheModel.UrlSegments.Any(x => x.Segment == model.UrlSegment) is false) + { + existingCacheModel.UrlSegments.Add(new PublishedDocumentUrlSegments.UrlSegment(model.UrlSegment, model.IsPrimary)); + } } } - return cacheModels; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static PublishedDocumentUrlSegments? GetModelFromCache(List cacheModels, PublishedDocumentUrlSegment model) - => cacheModels - .SingleOrDefault(x => x.DocumentKey == model.DocumentKey && x.LanguageId == model.LanguageId && x.IsDraft == model.IsDraft); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static IList GetUpdatedUrlSegments(IList urlSegments, string segment, bool isPrimary) - { - if (urlSegments.FirstOrDefault(x => x.Segment == segment) is null) - { - urlSegments.Add(new PublishedDocumentUrlSegments.UrlSegment(segment, isPrimary)); - } - - return urlSegments; + return cacheModels.Values; } private void RemoveFromCache(IScopeContext scopeContext, Guid documentKey, string isoCode, bool isDraft) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DocumentUrlServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DocumentUrlServiceTests.cs new file mode 100644 index 0000000000..da7f433b6c --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DocumentUrlServiceTests.cs @@ -0,0 +1,127 @@ +using NUnit.Framework; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Services; + +[TestFixture] +public class DocumentUrlServiceTests +{ + [Test] + public void ConvertToCacheModel_Converts_Single_Document_With_Single_Segment_To_Expected_Cache_Model() + { + var segments = new List + { + new() + { + DocumentKey = Guid.NewGuid(), + IsDraft = false, + IsPrimary = true, + LanguageId = 1, + UrlSegment = "test-segment", + }, + }; + var cacheModels = DocumentUrlService.ConvertToCacheModel(segments).ToList(); + + Assert.AreEqual(1, cacheModels.Count); + Assert.AreEqual(segments[0].DocumentKey, cacheModels[0].DocumentKey); + Assert.AreEqual(1, cacheModels[0].LanguageId); + Assert.AreEqual(1, cacheModels[0].UrlSegments.Count); + Assert.AreEqual("test-segment", cacheModels[0].UrlSegments[0].Segment); + Assert.IsTrue(cacheModels[0].UrlSegments[0].IsPrimary); + } + + [Test] + public void ConvertToCacheModel_Converts_Multiple_Documents_With_Single_Segment_To_Expected_Cache_Model() + { + var segments = new List + { + new() + { + DocumentKey = Guid.NewGuid(), + IsDraft = false, + IsPrimary = true, + LanguageId = 1, + UrlSegment = "test-segment", + }, + new() + { + DocumentKey = Guid.NewGuid(), + IsDraft = false, + IsPrimary = true, + LanguageId = 1, + UrlSegment = "test-segment-2", + }, + }; + var cacheModels = DocumentUrlService.ConvertToCacheModel(segments).ToList(); + + Assert.AreEqual(2, cacheModels.Count); + Assert.AreEqual(segments[0].DocumentKey, cacheModels[0].DocumentKey); + Assert.AreEqual(segments[1].DocumentKey, cacheModels[1].DocumentKey); + Assert.AreEqual(1, cacheModels[0].LanguageId); + Assert.AreEqual(1, cacheModels[1].LanguageId); + Assert.AreEqual(1, cacheModels[0].UrlSegments.Count); + Assert.AreEqual("test-segment", cacheModels[0].UrlSegments[0].Segment); + Assert.AreEqual(1, cacheModels[1].UrlSegments.Count); + Assert.AreEqual("test-segment-2", cacheModels[1].UrlSegments[0].Segment); + Assert.IsTrue(cacheModels[0].UrlSegments[0].IsPrimary); + Assert.IsTrue(cacheModels[1].UrlSegments[0].IsPrimary); + } + + [Test] + public void ConvertToCacheModel_Converts_Single_Document_With_Multiple_Segments_To_Expected_Cache_Model() + { + var documentKey = Guid.NewGuid(); + var segments = new List + { + new() + { + DocumentKey = documentKey, + IsDraft = false, + IsPrimary = true, + LanguageId = 1, + UrlSegment = "test-segment", + }, + new() + { + DocumentKey = documentKey, + IsDraft = false, + IsPrimary = false, + LanguageId = 1, + UrlSegment = "test-segment-2", + }, + }; + var cacheModels = DocumentUrlService.ConvertToCacheModel(segments).ToList(); + + Assert.AreEqual(1, cacheModels.Count); + Assert.AreEqual(documentKey, cacheModels[0].DocumentKey); + Assert.AreEqual(1, cacheModels[0].LanguageId); + Assert.AreEqual(2, cacheModels[0].UrlSegments.Count); + Assert.AreEqual("test-segment", cacheModels[0].UrlSegments[0].Segment); + Assert.AreEqual("test-segment-2", cacheModels[0].UrlSegments[1].Segment); + Assert.IsTrue(cacheModels[0].UrlSegments[0].IsPrimary); + Assert.IsFalse(cacheModels[0].UrlSegments[1].IsPrimary); + } + + [Test] + public void ConvertToCacheModel_Performance_Test() + { + const int NumberOfSegments = 1; + var segments = Enumerable.Range(0, NumberOfSegments) + .Select((x, i) => new PublishedDocumentUrlSegment + { + DocumentKey = Guid.NewGuid(), + IsDraft = false, + IsPrimary = true, + LanguageId = 1, + UrlSegment = $"test-segment-{x + 1}", + }); + var cacheModels = DocumentUrlService.ConvertToCacheModel(segments).ToList(); + + Assert.AreEqual(NumberOfSegments, cacheModels.Count); + + // Benchmarking (for NumberOfSegments = 50000): + // - Initial implementation (15.4): ~28s + // - Current implementation: ~100ms + } +} From 024697f3bf07edb534e205fe0b35298193b304cc Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 10 Jun 2025 10:28:55 +0200 Subject: [PATCH 06/82] Bumped version to 15.4.3 --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 921bad63ef..f54e9f0879 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "15.4.2", + "version": "15.4.3", "assemblyVersion": { "precision": "build" }, From ab600cb7985a6099f9dca0185091fdf5e7db4452 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 10 Jun 2025 10:28:45 +0200 Subject: [PATCH 07/82] Optimize initialization of document URLs on start-up (#19498) * Optimize initialization of document URLs on startup. * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> (cherry picked from commit 8d2ff6f92a57f16aabec4a22746dc7b77e288712) --- .../Services/DocumentUrlService.cs | 70 ++++++---- .../Services/DocumentUrlServiceTests.cs | 127 ++++++++++++++++++ 2 files changed, 172 insertions(+), 25 deletions(-) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DocumentUrlServiceTests.cs diff --git a/src/Umbraco.Core/Services/DocumentUrlService.cs b/src/Umbraco.Core/Services/DocumentUrlService.cs index c73b2c41e6..c9bc338906 100644 --- a/src/Umbraco.Core/Services/DocumentUrlService.cs +++ b/src/Umbraco.Core/Services/DocumentUrlService.cs @@ -44,26 +44,51 @@ public class DocumentUrlService : IDocumentUrlService /// /// Model used to cache a single published document along with all it's URL segments. /// - private class PublishedDocumentUrlSegments + /// Internal for the purpose of unit and benchmark testing. + internal class PublishedDocumentUrlSegments { + /// + /// Gets or sets the document key. + /// public required Guid DocumentKey { get; set; } + /// + /// Gets or sets the language Id. + /// public required int LanguageId { get; set; } + /// + /// Gets or sets the collection of for the document, language and state. + /// public required IList UrlSegments { get; set; } + /// + /// Gets or sets a value indicating whether the document is a draft version or not. + /// public required bool IsDraft { get; set; } + /// + /// Model used to represent a URL segment for a document in the cache. + /// public class UrlSegment { + /// + /// Initializes a new instance of the class. + /// public UrlSegment(string segment, bool isPrimary) { Segment = segment; IsPrimary = isPrimary; } + /// + /// Gets the URL segment string. + /// public string Segment { get; } + /// + /// Gets a value indicating whether this URL segment is the primary one for the document, language and state. + /// public bool IsPrimary { get; } } } @@ -168,45 +193,40 @@ public class DocumentUrlService : IDocumentUrlService scope.Complete(); } - private static IEnumerable ConvertToCacheModel(IEnumerable publishedDocumentUrlSegments) + /// + /// Converts a collection of to a collection of for caching purposes. + /// + /// The collection of retrieved from the database on startup. + /// The collection of cache models. + /// Internal for the purpose of unit and benchmark testing. + internal static IEnumerable ConvertToCacheModel(IEnumerable publishedDocumentUrlSegments) { - var cacheModels = new List(); + var cacheModels = new Dictionary<(Guid DocumentKey, int LanguageId, bool IsDraft), PublishedDocumentUrlSegments>(); + foreach (PublishedDocumentUrlSegment model in publishedDocumentUrlSegments) { - PublishedDocumentUrlSegments? existingCacheModel = GetModelFromCache(cacheModels, model); - if (existingCacheModel is null) + (Guid DocumentKey, int LanguageId, bool IsDraft) key = (model.DocumentKey, model.LanguageId, model.IsDraft); + + if (!cacheModels.TryGetValue(key, out PublishedDocumentUrlSegments? existingCacheModel)) { - cacheModels.Add(new PublishedDocumentUrlSegments + cacheModels[key] = new PublishedDocumentUrlSegments { DocumentKey = model.DocumentKey, LanguageId = model.LanguageId, UrlSegments = [new PublishedDocumentUrlSegments.UrlSegment(model.UrlSegment, model.IsPrimary)], IsDraft = model.IsDraft, - }); + }; } else { - existingCacheModel.UrlSegments = GetUpdatedUrlSegments(existingCacheModel.UrlSegments, model.UrlSegment, model.IsPrimary); + if (existingCacheModel.UrlSegments.Any(x => x.Segment == model.UrlSegment) is false) + { + existingCacheModel.UrlSegments.Add(new PublishedDocumentUrlSegments.UrlSegment(model.UrlSegment, model.IsPrimary)); + } } } - return cacheModels; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static PublishedDocumentUrlSegments? GetModelFromCache(List cacheModels, PublishedDocumentUrlSegment model) - => cacheModels - .SingleOrDefault(x => x.DocumentKey == model.DocumentKey && x.LanguageId == model.LanguageId && x.IsDraft == model.IsDraft); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static IList GetUpdatedUrlSegments(IList urlSegments, string segment, bool isPrimary) - { - if (urlSegments.FirstOrDefault(x => x.Segment == segment) is null) - { - urlSegments.Add(new PublishedDocumentUrlSegments.UrlSegment(segment, isPrimary)); - } - - return urlSegments; + return cacheModels.Values; } private void RemoveFromCache(IScopeContext scopeContext, Guid documentKey, string isoCode, bool isDraft) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DocumentUrlServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DocumentUrlServiceTests.cs new file mode 100644 index 0000000000..da7f433b6c --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DocumentUrlServiceTests.cs @@ -0,0 +1,127 @@ +using NUnit.Framework; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Services; + +[TestFixture] +public class DocumentUrlServiceTests +{ + [Test] + public void ConvertToCacheModel_Converts_Single_Document_With_Single_Segment_To_Expected_Cache_Model() + { + var segments = new List + { + new() + { + DocumentKey = Guid.NewGuid(), + IsDraft = false, + IsPrimary = true, + LanguageId = 1, + UrlSegment = "test-segment", + }, + }; + var cacheModels = DocumentUrlService.ConvertToCacheModel(segments).ToList(); + + Assert.AreEqual(1, cacheModels.Count); + Assert.AreEqual(segments[0].DocumentKey, cacheModels[0].DocumentKey); + Assert.AreEqual(1, cacheModels[0].LanguageId); + Assert.AreEqual(1, cacheModels[0].UrlSegments.Count); + Assert.AreEqual("test-segment", cacheModels[0].UrlSegments[0].Segment); + Assert.IsTrue(cacheModels[0].UrlSegments[0].IsPrimary); + } + + [Test] + public void ConvertToCacheModel_Converts_Multiple_Documents_With_Single_Segment_To_Expected_Cache_Model() + { + var segments = new List + { + new() + { + DocumentKey = Guid.NewGuid(), + IsDraft = false, + IsPrimary = true, + LanguageId = 1, + UrlSegment = "test-segment", + }, + new() + { + DocumentKey = Guid.NewGuid(), + IsDraft = false, + IsPrimary = true, + LanguageId = 1, + UrlSegment = "test-segment-2", + }, + }; + var cacheModels = DocumentUrlService.ConvertToCacheModel(segments).ToList(); + + Assert.AreEqual(2, cacheModels.Count); + Assert.AreEqual(segments[0].DocumentKey, cacheModels[0].DocumentKey); + Assert.AreEqual(segments[1].DocumentKey, cacheModels[1].DocumentKey); + Assert.AreEqual(1, cacheModels[0].LanguageId); + Assert.AreEqual(1, cacheModels[1].LanguageId); + Assert.AreEqual(1, cacheModels[0].UrlSegments.Count); + Assert.AreEqual("test-segment", cacheModels[0].UrlSegments[0].Segment); + Assert.AreEqual(1, cacheModels[1].UrlSegments.Count); + Assert.AreEqual("test-segment-2", cacheModels[1].UrlSegments[0].Segment); + Assert.IsTrue(cacheModels[0].UrlSegments[0].IsPrimary); + Assert.IsTrue(cacheModels[1].UrlSegments[0].IsPrimary); + } + + [Test] + public void ConvertToCacheModel_Converts_Single_Document_With_Multiple_Segments_To_Expected_Cache_Model() + { + var documentKey = Guid.NewGuid(); + var segments = new List + { + new() + { + DocumentKey = documentKey, + IsDraft = false, + IsPrimary = true, + LanguageId = 1, + UrlSegment = "test-segment", + }, + new() + { + DocumentKey = documentKey, + IsDraft = false, + IsPrimary = false, + LanguageId = 1, + UrlSegment = "test-segment-2", + }, + }; + var cacheModels = DocumentUrlService.ConvertToCacheModel(segments).ToList(); + + Assert.AreEqual(1, cacheModels.Count); + Assert.AreEqual(documentKey, cacheModels[0].DocumentKey); + Assert.AreEqual(1, cacheModels[0].LanguageId); + Assert.AreEqual(2, cacheModels[0].UrlSegments.Count); + Assert.AreEqual("test-segment", cacheModels[0].UrlSegments[0].Segment); + Assert.AreEqual("test-segment-2", cacheModels[0].UrlSegments[1].Segment); + Assert.IsTrue(cacheModels[0].UrlSegments[0].IsPrimary); + Assert.IsFalse(cacheModels[0].UrlSegments[1].IsPrimary); + } + + [Test] + public void ConvertToCacheModel_Performance_Test() + { + const int NumberOfSegments = 1; + var segments = Enumerable.Range(0, NumberOfSegments) + .Select((x, i) => new PublishedDocumentUrlSegment + { + DocumentKey = Guid.NewGuid(), + IsDraft = false, + IsPrimary = true, + LanguageId = 1, + UrlSegment = $"test-segment-{x + 1}", + }); + var cacheModels = DocumentUrlService.ConvertToCacheModel(segments).ToList(); + + Assert.AreEqual(NumberOfSegments, cacheModels.Count); + + // Benchmarking (for NumberOfSegments = 50000): + // - Initial implementation (15.4): ~28s + // - Current implementation: ~100ms + } +} From fe7f0558c139b38177b864ae6552261bac9afd3a Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 10 Jun 2025 10:31:10 +0200 Subject: [PATCH 08/82] Update template to reference latest LTS version (#19517) Update template to reference latest LTS version. --- templates/UmbracoProject/.template.config/template.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/UmbracoProject/.template.config/template.json b/templates/UmbracoProject/.template.config/template.json index d9ae3a078e..fefc0f7b26 100644 --- a/templates/UmbracoProject/.template.config/template.json +++ b/templates/UmbracoProject/.template.config/template.json @@ -98,7 +98,7 @@ }, { "condition": "(UmbracoRelease == 'LTS')", - "value": "13.5.0" + "value": "13.9.1" } ] } From 9b2fd1253b1c2d1dda59635bdbd8312120090b86 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 10 Jun 2025 10:28:45 +0200 Subject: [PATCH 09/82] Optimize initialization of document URLs on start-up (#19498) * Optimize initialization of document URLs on startup. * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Services/DocumentUrlService.cs | 70 ++++++---- .../Services/DocumentUrlServiceTests.cs | 127 ++++++++++++++++++ 2 files changed, 172 insertions(+), 25 deletions(-) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DocumentUrlServiceTests.cs diff --git a/src/Umbraco.Core/Services/DocumentUrlService.cs b/src/Umbraco.Core/Services/DocumentUrlService.cs index c73b2c41e6..c9bc338906 100644 --- a/src/Umbraco.Core/Services/DocumentUrlService.cs +++ b/src/Umbraco.Core/Services/DocumentUrlService.cs @@ -44,26 +44,51 @@ public class DocumentUrlService : IDocumentUrlService /// /// Model used to cache a single published document along with all it's URL segments. /// - private class PublishedDocumentUrlSegments + /// Internal for the purpose of unit and benchmark testing. + internal class PublishedDocumentUrlSegments { + /// + /// Gets or sets the document key. + /// public required Guid DocumentKey { get; set; } + /// + /// Gets or sets the language Id. + /// public required int LanguageId { get; set; } + /// + /// Gets or sets the collection of for the document, language and state. + /// public required IList UrlSegments { get; set; } + /// + /// Gets or sets a value indicating whether the document is a draft version or not. + /// public required bool IsDraft { get; set; } + /// + /// Model used to represent a URL segment for a document in the cache. + /// public class UrlSegment { + /// + /// Initializes a new instance of the class. + /// public UrlSegment(string segment, bool isPrimary) { Segment = segment; IsPrimary = isPrimary; } + /// + /// Gets the URL segment string. + /// public string Segment { get; } + /// + /// Gets a value indicating whether this URL segment is the primary one for the document, language and state. + /// public bool IsPrimary { get; } } } @@ -168,45 +193,40 @@ public class DocumentUrlService : IDocumentUrlService scope.Complete(); } - private static IEnumerable ConvertToCacheModel(IEnumerable publishedDocumentUrlSegments) + /// + /// Converts a collection of to a collection of for caching purposes. + /// + /// The collection of retrieved from the database on startup. + /// The collection of cache models. + /// Internal for the purpose of unit and benchmark testing. + internal static IEnumerable ConvertToCacheModel(IEnumerable publishedDocumentUrlSegments) { - var cacheModels = new List(); + var cacheModels = new Dictionary<(Guid DocumentKey, int LanguageId, bool IsDraft), PublishedDocumentUrlSegments>(); + foreach (PublishedDocumentUrlSegment model in publishedDocumentUrlSegments) { - PublishedDocumentUrlSegments? existingCacheModel = GetModelFromCache(cacheModels, model); - if (existingCacheModel is null) + (Guid DocumentKey, int LanguageId, bool IsDraft) key = (model.DocumentKey, model.LanguageId, model.IsDraft); + + if (!cacheModels.TryGetValue(key, out PublishedDocumentUrlSegments? existingCacheModel)) { - cacheModels.Add(new PublishedDocumentUrlSegments + cacheModels[key] = new PublishedDocumentUrlSegments { DocumentKey = model.DocumentKey, LanguageId = model.LanguageId, UrlSegments = [new PublishedDocumentUrlSegments.UrlSegment(model.UrlSegment, model.IsPrimary)], IsDraft = model.IsDraft, - }); + }; } else { - existingCacheModel.UrlSegments = GetUpdatedUrlSegments(existingCacheModel.UrlSegments, model.UrlSegment, model.IsPrimary); + if (existingCacheModel.UrlSegments.Any(x => x.Segment == model.UrlSegment) is false) + { + existingCacheModel.UrlSegments.Add(new PublishedDocumentUrlSegments.UrlSegment(model.UrlSegment, model.IsPrimary)); + } } } - return cacheModels; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static PublishedDocumentUrlSegments? GetModelFromCache(List cacheModels, PublishedDocumentUrlSegment model) - => cacheModels - .SingleOrDefault(x => x.DocumentKey == model.DocumentKey && x.LanguageId == model.LanguageId && x.IsDraft == model.IsDraft); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static IList GetUpdatedUrlSegments(IList urlSegments, string segment, bool isPrimary) - { - if (urlSegments.FirstOrDefault(x => x.Segment == segment) is null) - { - urlSegments.Add(new PublishedDocumentUrlSegments.UrlSegment(segment, isPrimary)); - } - - return urlSegments; + return cacheModels.Values; } private void RemoveFromCache(IScopeContext scopeContext, Guid documentKey, string isoCode, bool isDraft) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DocumentUrlServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DocumentUrlServiceTests.cs new file mode 100644 index 0000000000..da7f433b6c --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DocumentUrlServiceTests.cs @@ -0,0 +1,127 @@ +using NUnit.Framework; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Services; + +[TestFixture] +public class DocumentUrlServiceTests +{ + [Test] + public void ConvertToCacheModel_Converts_Single_Document_With_Single_Segment_To_Expected_Cache_Model() + { + var segments = new List + { + new() + { + DocumentKey = Guid.NewGuid(), + IsDraft = false, + IsPrimary = true, + LanguageId = 1, + UrlSegment = "test-segment", + }, + }; + var cacheModels = DocumentUrlService.ConvertToCacheModel(segments).ToList(); + + Assert.AreEqual(1, cacheModels.Count); + Assert.AreEqual(segments[0].DocumentKey, cacheModels[0].DocumentKey); + Assert.AreEqual(1, cacheModels[0].LanguageId); + Assert.AreEqual(1, cacheModels[0].UrlSegments.Count); + Assert.AreEqual("test-segment", cacheModels[0].UrlSegments[0].Segment); + Assert.IsTrue(cacheModels[0].UrlSegments[0].IsPrimary); + } + + [Test] + public void ConvertToCacheModel_Converts_Multiple_Documents_With_Single_Segment_To_Expected_Cache_Model() + { + var segments = new List + { + new() + { + DocumentKey = Guid.NewGuid(), + IsDraft = false, + IsPrimary = true, + LanguageId = 1, + UrlSegment = "test-segment", + }, + new() + { + DocumentKey = Guid.NewGuid(), + IsDraft = false, + IsPrimary = true, + LanguageId = 1, + UrlSegment = "test-segment-2", + }, + }; + var cacheModels = DocumentUrlService.ConvertToCacheModel(segments).ToList(); + + Assert.AreEqual(2, cacheModels.Count); + Assert.AreEqual(segments[0].DocumentKey, cacheModels[0].DocumentKey); + Assert.AreEqual(segments[1].DocumentKey, cacheModels[1].DocumentKey); + Assert.AreEqual(1, cacheModels[0].LanguageId); + Assert.AreEqual(1, cacheModels[1].LanguageId); + Assert.AreEqual(1, cacheModels[0].UrlSegments.Count); + Assert.AreEqual("test-segment", cacheModels[0].UrlSegments[0].Segment); + Assert.AreEqual(1, cacheModels[1].UrlSegments.Count); + Assert.AreEqual("test-segment-2", cacheModels[1].UrlSegments[0].Segment); + Assert.IsTrue(cacheModels[0].UrlSegments[0].IsPrimary); + Assert.IsTrue(cacheModels[1].UrlSegments[0].IsPrimary); + } + + [Test] + public void ConvertToCacheModel_Converts_Single_Document_With_Multiple_Segments_To_Expected_Cache_Model() + { + var documentKey = Guid.NewGuid(); + var segments = new List + { + new() + { + DocumentKey = documentKey, + IsDraft = false, + IsPrimary = true, + LanguageId = 1, + UrlSegment = "test-segment", + }, + new() + { + DocumentKey = documentKey, + IsDraft = false, + IsPrimary = false, + LanguageId = 1, + UrlSegment = "test-segment-2", + }, + }; + var cacheModels = DocumentUrlService.ConvertToCacheModel(segments).ToList(); + + Assert.AreEqual(1, cacheModels.Count); + Assert.AreEqual(documentKey, cacheModels[0].DocumentKey); + Assert.AreEqual(1, cacheModels[0].LanguageId); + Assert.AreEqual(2, cacheModels[0].UrlSegments.Count); + Assert.AreEqual("test-segment", cacheModels[0].UrlSegments[0].Segment); + Assert.AreEqual("test-segment-2", cacheModels[0].UrlSegments[1].Segment); + Assert.IsTrue(cacheModels[0].UrlSegments[0].IsPrimary); + Assert.IsFalse(cacheModels[0].UrlSegments[1].IsPrimary); + } + + [Test] + public void ConvertToCacheModel_Performance_Test() + { + const int NumberOfSegments = 1; + var segments = Enumerable.Range(0, NumberOfSegments) + .Select((x, i) => new PublishedDocumentUrlSegment + { + DocumentKey = Guid.NewGuid(), + IsDraft = false, + IsPrimary = true, + LanguageId = 1, + UrlSegment = $"test-segment-{x + 1}", + }); + var cacheModels = DocumentUrlService.ConvertToCacheModel(segments).ToList(); + + Assert.AreEqual(NumberOfSegments, cacheModels.Count); + + // Benchmarking (for NumberOfSegments = 50000): + // - Initial implementation (15.4): ~28s + // - Current implementation: ~100ms + } +} From 0c33a23c50ff51e39fdfaf777ad9d7f0e6ac7c2f Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 10 Jun 2025 11:47:16 +0200 Subject: [PATCH 10/82] Bump version to 16.0.0-rc6. --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 44c7e516dd..68b4b19991 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "16.0.0-rc5", // TODO: When bumping this to 16.0.0, also remove the -rc suffix in the starterkits.template.json file. The starter kit final version for 16 will be released at the same time as the CMS. + "version": "16.0.0-rc6", // TODO: When bumping this to 16.0.0, also remove the -rc suffix in the starterkits.template.json file. The starter kit final version for 16 will be released at the same time as the CMS. "assemblyVersion": { "precision": "build" }, From 919b65ea19894751082138a26067dff915519c2d Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Wed, 11 Jun 2025 08:21:21 +0100 Subject: [PATCH 11/82] Tiptap RTE: Style Menu action toggles (#19520) * Tiptap style menu toggles (for classes and IDs) Fixes #19244 * Tiptap style menu toggles (for font/color) Fixes #19508 * Tiptap "Clear Formatting" remove classes and styles * Tiptap font sizes, removes trailing semicolon as the API handles the delimiter * Tiptap global attrs: adds set/unset styles commands --- ...tiptap-html-global-attributes.extension.ts | 42 +++++++++ .../extensions/tiptap-span.extension.ts | 89 +++++++++++++++---- .../toolbar/style-menu.tiptap-toolbar-api.ts | 10 +-- .../packages/tiptap/extensions/manifests.ts | 18 ++-- .../clear-formatting.tiptap-toolbar-api.ts | 2 +- .../toolbar/font-family.tiptap-toolbar-api.ts | 2 +- .../toolbar/font-size.tiptap-toolbar-api.ts | 2 +- ...ext-color-background.tiptap-toolbar-api.ts | 2 +- ...ext-color-foreground.tiptap-toolbar-api.ts | 2 +- 9 files changed, 135 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-html-global-attributes.extension.ts b/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-html-global-attributes.extension.ts index 011a8209c6..69c0cbdd1f 100644 --- a/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-html-global-attributes.extension.ts +++ b/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-html-global-attributes.extension.ts @@ -64,6 +64,14 @@ export const HtmlGlobalAttributes = Extension.create commands.updateAttributes(type, { class: className })) .every((response) => response); }, + toggleClassName: + (className, type) => + ({ commands, editor }) => { + if (!className) return false; + const types = type ? [type] : this.options.types; + const existing = types.map((type) => editor.getAttributes(type)?.class as string).filter((x) => x); + return existing.length ? commands.unsetClassName(type) : commands.setClassName(className, type); + }, unsetClassName: (type) => ({ commands }) => { @@ -77,12 +85,41 @@ export const HtmlGlobalAttributes = Extension.create commands.updateAttributes(type, { id })).every((response) => response); }, + toggleId: + (id, type) => + ({ commands, editor }) => { + if (!id) return false; + const types = type ? [type] : this.options.types; + const existing = types.map((type) => editor.getAttributes(type)?.id as string).filter((x) => x); + return existing.length ? commands.unsetId(type) : commands.setId(id, type); + }, unsetId: (type) => ({ commands }) => { const types = type ? [type] : this.options.types; return types.map((type) => commands.resetAttributes(type, 'id')).every((response) => response); }, + setStyles: + (style, type) => + ({ commands }) => { + if (!style) return false; + const types = type ? [type] : this.options.types; + return types.map((type) => commands.updateAttributes(type, { style })).every((response) => response); + }, + toggleStyles: + (style, type) => + ({ commands, editor }) => { + if (!style) return false; + const types = type ? [type] : this.options.types; + const existing = types.map((type) => editor.getAttributes(type)?.style as string).filter((x) => x); + return existing.length ? commands.unsetStyles(type) : commands.setStyles(style, type); + }, + unsetStyles: + (type) => + ({ commands }) => { + const types = type ? [type] : this.options.types; + return types.map((type) => commands.resetAttributes(type, 'style')).every((response) => response); + }, }; }, }); @@ -91,9 +128,14 @@ declare module '@tiptap/core' { interface Commands { htmlGlobalAttributes: { setClassName: (className?: string, type?: string) => ReturnType; + toggleClassName: (className?: string, type?: string) => ReturnType; unsetClassName: (type?: string) => ReturnType; setId: (id?: string, type?: string) => ReturnType; + toggleId: (id?: string, type?: string) => ReturnType; unsetId: (type?: string) => ReturnType; + setStyles: (style?: string, type?: string) => ReturnType; + toggleStyles: (style?: string, type?: string) => ReturnType; + unsetStyles: (type?: string) => ReturnType; }; } } diff --git a/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-span.extension.ts b/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-span.extension.ts index 6d49c46461..4f6d241f4b 100644 --- a/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-span.extension.ts +++ b/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-span.extension.ts @@ -9,6 +9,31 @@ export interface SpanOptions { HTMLAttributes: Record; } +function parseStyles(style: string | undefined): Record { + const items: Record = {}; + + (style ?? '') + .split(';') + .map((x) => x.trim()) + .filter((x) => x) + .forEach((rule) => { + const [key, value] = rule.split(':'); + if (key && value) { + items[key.trim()] = value.trim(); + } + }); + + return items; +} + +function serializeStyles(items: Record): string { + return ( + Object.entries(items) + .map(([key, value]) => `${key}: ${value}`) + .join(';') + ';' + ); +} + export const Span = Mark.create({ name: 'span', @@ -32,29 +57,61 @@ export const Span = Mark.create({ if (!styles) return false; const existing = editor.getAttributes(this.name)?.style as string; - if (!existing && !editor.isActive(this.name)) { return commands.setMark(this.name, { style: styles }); } - const rules = ((existing ?? '') + ';' + styles).split(';'); - const items: Record = {}; + const items = { + ...parseStyles(existing), + ...parseStyles(styles), + }; - rules - .filter((x) => x) - .forEach((rule) => { - if (rule.trim() !== '') { - const [key, value] = rule.split(':'); - items[key.trim()] = value.trim(); - } - }); - - const style = Object.entries(items) - .map(([key, value]) => `${key}: ${value}`) - .join(';'); + const style = serializeStyles(items); + if (style === ';') return false; return commands.updateAttributes(this.name, { style }); }, + toggleSpanStyle: + (styles) => + ({ commands, editor }) => { + if (!styles) return false; + const existing = editor.getAttributes(this.name)?.style as string; + return existing?.includes(styles) === true ? commands.unsetSpanStyle(styles) : commands.setSpanStyle(styles); + }, + unsetSpanStyle: + (styles) => + ({ commands, editor }) => { + if (!styles) return false; + + parseStyles(styles); + + const toBeRemoved = new Set(); + + styles + .split(';') + .map((x) => x.trim()) + .filter((x) => x) + .forEach((rule) => { + const [key] = rule.split(':'); + if (key) toBeRemoved.add(key.trim()); + }); + + if (toBeRemoved.size === 0) return false; + + const existing = editor.getAttributes(this.name)?.style as string; + const items = parseStyles(existing); + + // Remove keys + for (const key of toBeRemoved) { + delete items[key]; + } + + const style = serializeStyles(items); + + return style === ';' + ? commands.resetAttributes(this.name, 'style') + : commands.updateAttributes(this.name, { style }); + }, }; }, }); @@ -63,6 +120,8 @@ declare module '@tiptap/core' { interface Commands { span: { setSpanStyle: (styles?: string) => ReturnType; + toggleSpanStyle: (styles?: string) => ReturnType; + unsetSpanStyle: (styles?: string) => ReturnType; }; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/style-menu.tiptap-toolbar-api.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/style-menu.tiptap-toolbar-api.ts index 797c382aec..dda2e85f26 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/style-menu.tiptap-toolbar-api.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/style-menu.tiptap-toolbar-api.ts @@ -15,12 +15,12 @@ export default class UmbTiptapToolbarStyleMenuApi extends UmbTiptapToolbarElemen code: { type: 'code', command: (chain) => chain.toggleCode() }, codeBlock: { type: 'codeBlock', command: (chain) => chain.toggleCodeBlock() }, div: { type: 'div', command: (chain) => chain.toggleNode('div', 'paragraph') }, - em: { type: 'italic', command: (chain) => chain.setItalic() }, + em: { type: 'italic', command: (chain) => chain.toggleItalic() }, ol: { type: 'orderedList', command: (chain) => chain.toggleOrderedList() }, - strong: { type: 'bold', command: (chain) => chain.setBold() }, - s: { type: 'strike', command: (chain) => chain.setStrike() }, + strong: { type: 'bold', command: (chain) => chain.toggleBold() }, + s: { type: 'strike', command: (chain) => chain.toggleStrike() }, span: { type: 'span', command: (chain) => chain.toggleMark('span') }, - u: { type: 'underline', command: (chain) => chain.setUnderline() }, + u: { type: 'underline', command: (chain) => chain.toggleUnderline() }, ul: { type: 'bulletList', command: (chain) => chain.toggleBulletList() }, }; @@ -29,6 +29,6 @@ export default class UmbTiptapToolbarStyleMenuApi extends UmbTiptapToolbarElemen const { tag, id, class: className } = item.data; const focus = editor.chain().focus(); const ext = tag ? this.#commands[tag] : null; - (ext?.command?.(focus) ?? focus).setId(id, ext?.type).setClassName(className, ext?.type).run(); + (ext?.command?.(focus) ?? focus).toggleId(id, ext?.type).toggleClassName(className, ext?.type).run(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/manifests.ts index 06305ea6bd..1e96b77519 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/manifests.ts @@ -611,15 +611,15 @@ const toolbarExtensions: Array = [ name: 'Font Size Tiptap Extension', api: () => import('./toolbar/font-size.tiptap-toolbar-api.js'), items: [ - { label: '8pt', data: '8pt;' }, - { label: '10pt', data: '10pt;' }, - { label: '12pt', data: '12pt;' }, - { label: '14pt', data: '14pt;' }, - { label: '16pt', data: '16pt;' }, - { label: '18pt', data: '18pt;' }, - { label: '24pt', data: '24pt;' }, - { label: '26pt', data: '26pt;' }, - { label: '48pt', data: '48pt;' }, + { label: '8pt', data: '8pt' }, + { label: '10pt', data: '10pt' }, + { label: '12pt', data: '12pt' }, + { label: '14pt', data: '14pt' }, + { label: '16pt', data: '16pt' }, + { label: '18pt', data: '18pt' }, + { label: '24pt', data: '24pt' }, + { label: '26pt', data: '26pt' }, + { label: '48pt', data: '48pt' }, ], meta: { alias: 'umbFontSize', diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/clear-formatting.tiptap-toolbar-api.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/clear-formatting.tiptap-toolbar-api.ts index 2541a24615..9da1b318f4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/clear-formatting.tiptap-toolbar-api.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/clear-formatting.tiptap-toolbar-api.ts @@ -3,6 +3,6 @@ import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; export default class UmbTiptapToolbarClearFormattingExtensionApi extends UmbTiptapToolbarElementApiBase { override execute(editor?: Editor) { - editor?.chain().focus().unsetAllMarks().run(); + editor?.chain().focus().unsetAllMarks().unsetClassName().unsetStyles().run(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/font-family.tiptap-toolbar-api.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/font-family.tiptap-toolbar-api.ts index 02b6b168a3..41f698d3f9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/font-family.tiptap-toolbar-api.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/font-family.tiptap-toolbar-api.ts @@ -5,6 +5,6 @@ import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; export default class UmbTiptapToolbarFontFamilyExtensionApi extends UmbTiptapToolbarElementApiBase { override execute(editor?: Editor, item?: MetaTiptapToolbarMenuItem) { if (!item?.data) return; - editor?.chain().focus().setSpanStyle(`font-family: ${item.data};`).run(); + editor?.chain().focus().toggleSpanStyle(`font-family: ${item.data};`).run(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/font-size.tiptap-toolbar-api.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/font-size.tiptap-toolbar-api.ts index d62b9c82a1..3b7f4c5333 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/font-size.tiptap-toolbar-api.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/font-size.tiptap-toolbar-api.ts @@ -5,6 +5,6 @@ import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; export default class UmbTiptapToolbarFontFamilyExtensionApi extends UmbTiptapToolbarElementApiBase { override execute(editor?: Editor, item?: MetaTiptapToolbarMenuItem) { if (!item?.data) return; - editor?.chain().focus().setSpanStyle(`font-size: ${item.data};`).run(); + editor?.chain().focus().toggleSpanStyle(`font-size: ${item.data};`).run(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/text-color-background.tiptap-toolbar-api.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/text-color-background.tiptap-toolbar-api.ts index f29d160c77..75b53fba9a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/text-color-background.tiptap-toolbar-api.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/text-color-background.tiptap-toolbar-api.ts @@ -3,6 +3,6 @@ import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; export default class UmbTiptapToolbarTextColorBackgroundExtensionApi extends UmbTiptapToolbarElementApiBase { override execute(editor?: Editor, selectedColor?: string) { - editor?.chain().focus().setSpanStyle(`background-color: ${selectedColor};`).run(); + editor?.chain().focus().toggleSpanStyle(`background-color: ${selectedColor};`).run(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/text-color-foreground.tiptap-toolbar-api.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/text-color-foreground.tiptap-toolbar-api.ts index e53f7d054a..41ac8e82f5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/text-color-foreground.tiptap-toolbar-api.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/text-color-foreground.tiptap-toolbar-api.ts @@ -3,6 +3,6 @@ import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; export default class UmbTiptapToolbarTextColorForegroundExtensionApi extends UmbTiptapToolbarElementApiBase { override execute(editor?: Editor, selectedColor?: string) { - editor?.chain().focus().setSpanStyle(`color: ${selectedColor};`).run(); + editor?.chain().focus().toggleSpanStyle(`color: ${selectedColor};`).run(); } } From e231feb5220e24e63a0c3c3866f1f0a6d08fcf66 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Wed, 11 Jun 2025 11:53:05 +0200 Subject: [PATCH 12/82] Bump version to 16.0.0, update starter kit reference and enable package validation. --- Directory.Build.props | 4 ++-- .../UmbracoProject/.template.config/starterkits.template.json | 2 +- version.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 4dda16251e..7a38d8f922 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -40,8 +40,8 @@ false - false - 15.0.0 + true + 16.0.0 true true diff --git a/templates/UmbracoProject/.template.config/starterkits.template.json b/templates/UmbracoProject/.template.config/starterkits.template.json index ba71f42e2a..745eef20ae 100644 --- a/templates/UmbracoProject/.template.config/starterkits.template.json +++ b/templates/UmbracoProject/.template.config/starterkits.template.json @@ -34,7 +34,7 @@ "cases": [ { "condition": "(StarterKit == 'Umbraco.TheStarterKit' && (UmbracoRelease == 'Latest' || UmbracoRelease == 'Custom'))", - "value": "16.0.0-rc" + "value": "16.0.0" }, { "condition": "(StarterKit == 'Umbraco.TheStarterKit' && UmbracoRelease == 'LTS')", diff --git a/version.json b/version.json index 68b4b19991..52f2d6cc8e 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "16.0.0-rc6", // TODO: When bumping this to 16.0.0, also remove the -rc suffix in the starterkits.template.json file. The starter kit final version for 16 will be released at the same time as the CMS. + "version": "16.0.0", "assemblyVersion": { "precision": "build" }, From 8396249a38f95102d492e754b7f0dfab223082d1 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Wed, 11 Jun 2025 11:56:33 +0200 Subject: [PATCH 13/82] Update version number in package.json. --- src/Umbraco.Web.UI.Client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index d6da19b510..6b7e4745a1 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -1,7 +1,7 @@ { "name": "@umbraco-cms/backoffice", "license": "MIT", - "version": "16.0.0-rc", + "version": "16.0.0", "type": "module", "exports": { ".": null, From 1cf95be97de7175106abe0e08e6abcb42e719523 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Wed, 11 Jun 2025 12:07:10 +0200 Subject: [PATCH 14/82] Re-disabled package validation (can't enable this yet). --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 7a38d8f922..6ad381a3b5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -40,7 +40,7 @@ false - true + false 16.0.0 true true From 47bc2c26473a8d2099f0d3b71ec7a82e65d180d0 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 11 Jun 2025 13:57:41 +0200 Subject: [PATCH 15/82] build(deps): bump @umbraco-ui/uui to 1.14.0 (#19524) --- src/Umbraco.Web.UI.Client/package-lock.json | 964 ++++++++++---------- src/Umbraco.Web.UI.Client/package.json | 6 +- 2 files changed, 485 insertions(+), 485 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 1dc6518a3e..f8cf041e50 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -27,8 +27,8 @@ "@tiptap/extension-underline": "2.11.7", "@tiptap/pm": "2.11.7", "@tiptap/starter-kit": "2.11.7", - "@umbraco-ui/uui": "1.14.0-rc.4", - "@umbraco-ui/uui-css": "1.14.0-rc.4", + "@umbraco-ui/uui": "1.14.0", + "@umbraco-ui/uui-css": "1.14.0", "dompurify": "^3.2.5", "element-internals-polyfill": "^3.0.2", "lit": "^3.3.0", @@ -4520,908 +4520,908 @@ "link": true }, "node_modules/@umbraco-ui/uui": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.14.0-rc.4.tgz", - "integrity": "sha512-oC0tDbzcfCsoc1Hb5yHI2wui5/FA7yLNNIxPBP2yAtek1GIWDSIj3cEY08SNKLOv49y052tl3bH/QZr8hrji2w==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.14.0.tgz", + "integrity": "sha512-et9xGGEcFyIBaMzSbPFt81SDyPdGyV8qyZzLePbs4vDTJiqjtefl0ICZib3Cwm8X4TjCXOcbVMU84wV2RCcIsQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-action-bar": "1.14.0-rc.4", - "@umbraco-ui/uui-avatar": "1.14.0-rc.4", - "@umbraco-ui/uui-avatar-group": "1.14.0-rc.4", - "@umbraco-ui/uui-badge": "1.14.0-rc.4", - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-boolean-input": "1.14.0-rc.4", - "@umbraco-ui/uui-box": "1.14.0-rc.4", - "@umbraco-ui/uui-breadcrumbs": "1.14.0-rc.4", - "@umbraco-ui/uui-button": "1.14.0-rc.4", - "@umbraco-ui/uui-button-copy-text": "1.14.0-rc.4", - "@umbraco-ui/uui-button-group": "1.14.0-rc.4", - "@umbraco-ui/uui-button-inline-create": "1.14.0-rc.4", - "@umbraco-ui/uui-card": "1.14.0-rc.4", - "@umbraco-ui/uui-card-block-type": "1.14.0-rc.4", - "@umbraco-ui/uui-card-content-node": "1.14.0-rc.4", - "@umbraco-ui/uui-card-media": "1.14.0-rc.4", - "@umbraco-ui/uui-card-user": "1.14.0-rc.4", - "@umbraco-ui/uui-caret": "1.14.0-rc.4", - "@umbraco-ui/uui-checkbox": "1.14.0-rc.4", - "@umbraco-ui/uui-color-area": "1.14.0-rc.4", - "@umbraco-ui/uui-color-picker": "1.14.0-rc.4", - "@umbraco-ui/uui-color-slider": "1.14.0-rc.4", - "@umbraco-ui/uui-color-swatch": "1.14.0-rc.4", - "@umbraco-ui/uui-color-swatches": "1.14.0-rc.4", - "@umbraco-ui/uui-combobox": "1.14.0-rc.4", - "@umbraco-ui/uui-combobox-list": "1.14.0-rc.4", - "@umbraco-ui/uui-css": "1.14.0-rc.4", - "@umbraco-ui/uui-dialog": "1.14.0-rc.4", - "@umbraco-ui/uui-dialog-layout": "1.14.0-rc.4", - "@umbraco-ui/uui-file-dropzone": "1.14.0-rc.4", - "@umbraco-ui/uui-file-preview": "1.14.0-rc.4", - "@umbraco-ui/uui-form": "1.14.0-rc.4", - "@umbraco-ui/uui-form-layout-item": "1.14.0-rc.4", - "@umbraco-ui/uui-form-validation-message": "1.14.0-rc.4", - "@umbraco-ui/uui-icon": "1.14.0-rc.4", - "@umbraco-ui/uui-icon-registry": "1.14.0-rc.4", - "@umbraco-ui/uui-icon-registry-essential": "1.14.0-rc.4", - "@umbraco-ui/uui-input": "1.14.0-rc.4", - "@umbraco-ui/uui-input-file": "1.14.0-rc.4", - "@umbraco-ui/uui-input-lock": "1.14.0-rc.4", - "@umbraco-ui/uui-input-password": "1.14.0-rc.4", - "@umbraco-ui/uui-keyboard-shortcut": "1.14.0-rc.4", - "@umbraco-ui/uui-label": "1.14.0-rc.4", - "@umbraco-ui/uui-loader": "1.14.0-rc.4", - "@umbraco-ui/uui-loader-bar": "1.14.0-rc.4", - "@umbraco-ui/uui-loader-circle": "1.14.0-rc.4", - "@umbraco-ui/uui-menu-item": "1.14.0-rc.4", - "@umbraco-ui/uui-modal": "1.14.0-rc.4", - "@umbraco-ui/uui-pagination": "1.14.0-rc.4", - "@umbraco-ui/uui-popover": "1.14.0-rc.4", - "@umbraco-ui/uui-popover-container": "1.14.0-rc.4", - "@umbraco-ui/uui-progress-bar": "1.14.0-rc.4", - "@umbraco-ui/uui-radio": "1.14.0-rc.4", - "@umbraco-ui/uui-range-slider": "1.14.0-rc.4", - "@umbraco-ui/uui-ref": "1.14.0-rc.4", - "@umbraco-ui/uui-ref-list": "1.14.0-rc.4", - "@umbraco-ui/uui-ref-node": "1.14.0-rc.4", - "@umbraco-ui/uui-ref-node-data-type": "1.14.0-rc.4", - "@umbraco-ui/uui-ref-node-document-type": "1.14.0-rc.4", - "@umbraco-ui/uui-ref-node-form": "1.14.0-rc.4", - "@umbraco-ui/uui-ref-node-member": "1.14.0-rc.4", - "@umbraco-ui/uui-ref-node-package": "1.14.0-rc.4", - "@umbraco-ui/uui-ref-node-user": "1.14.0-rc.4", - "@umbraco-ui/uui-scroll-container": "1.14.0-rc.4", - "@umbraco-ui/uui-select": "1.14.0-rc.4", - "@umbraco-ui/uui-slider": "1.14.0-rc.4", - "@umbraco-ui/uui-symbol-expand": "1.14.0-rc.4", - "@umbraco-ui/uui-symbol-file": "1.14.0-rc.4", - "@umbraco-ui/uui-symbol-file-dropzone": "1.14.0-rc.4", - "@umbraco-ui/uui-symbol-file-thumbnail": "1.14.0-rc.4", - "@umbraco-ui/uui-symbol-folder": "1.14.0-rc.4", - "@umbraco-ui/uui-symbol-lock": "1.14.0-rc.4", - "@umbraco-ui/uui-symbol-more": "1.14.0-rc.4", - "@umbraco-ui/uui-symbol-sort": "1.14.0-rc.4", - "@umbraco-ui/uui-table": "1.14.0-rc.4", - "@umbraco-ui/uui-tabs": "1.14.0-rc.4", - "@umbraco-ui/uui-tag": "1.14.0-rc.4", - "@umbraco-ui/uui-textarea": "1.14.0-rc.4", - "@umbraco-ui/uui-toast-notification": "1.14.0-rc.4", - "@umbraco-ui/uui-toast-notification-container": "1.14.0-rc.4", - "@umbraco-ui/uui-toast-notification-layout": "1.14.0-rc.4", - "@umbraco-ui/uui-toggle": "1.14.0-rc.4", - "@umbraco-ui/uui-visually-hidden": "1.14.0-rc.4" + "@umbraco-ui/uui-action-bar": "1.14.0", + "@umbraco-ui/uui-avatar": "1.14.0", + "@umbraco-ui/uui-avatar-group": "1.14.0", + "@umbraco-ui/uui-badge": "1.14.0", + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-boolean-input": "1.14.0", + "@umbraco-ui/uui-box": "1.14.0", + "@umbraco-ui/uui-breadcrumbs": "1.14.0", + "@umbraco-ui/uui-button": "1.14.0", + "@umbraco-ui/uui-button-copy-text": "1.14.0", + "@umbraco-ui/uui-button-group": "1.14.0", + "@umbraco-ui/uui-button-inline-create": "1.14.0", + "@umbraco-ui/uui-card": "1.14.0", + "@umbraco-ui/uui-card-block-type": "1.14.0", + "@umbraco-ui/uui-card-content-node": "1.14.0", + "@umbraco-ui/uui-card-media": "1.14.0", + "@umbraco-ui/uui-card-user": "1.14.0", + "@umbraco-ui/uui-caret": "1.14.0", + "@umbraco-ui/uui-checkbox": "1.14.0", + "@umbraco-ui/uui-color-area": "1.14.0", + "@umbraco-ui/uui-color-picker": "1.14.0", + "@umbraco-ui/uui-color-slider": "1.14.0", + "@umbraco-ui/uui-color-swatch": "1.14.0", + "@umbraco-ui/uui-color-swatches": "1.14.0", + "@umbraco-ui/uui-combobox": "1.14.0", + "@umbraco-ui/uui-combobox-list": "1.14.0", + "@umbraco-ui/uui-css": "1.14.0", + "@umbraco-ui/uui-dialog": "1.14.0", + "@umbraco-ui/uui-dialog-layout": "1.14.0", + "@umbraco-ui/uui-file-dropzone": "1.14.0", + "@umbraco-ui/uui-file-preview": "1.14.0", + "@umbraco-ui/uui-form": "1.14.0", + "@umbraco-ui/uui-form-layout-item": "1.14.0", + "@umbraco-ui/uui-form-validation-message": "1.14.0", + "@umbraco-ui/uui-icon": "1.14.0", + "@umbraco-ui/uui-icon-registry": "1.14.0", + "@umbraco-ui/uui-icon-registry-essential": "1.14.0", + "@umbraco-ui/uui-input": "1.14.0", + "@umbraco-ui/uui-input-file": "1.14.0", + "@umbraco-ui/uui-input-lock": "1.14.0", + "@umbraco-ui/uui-input-password": "1.14.0", + "@umbraco-ui/uui-keyboard-shortcut": "1.14.0", + "@umbraco-ui/uui-label": "1.14.0", + "@umbraco-ui/uui-loader": "1.14.0", + "@umbraco-ui/uui-loader-bar": "1.14.0", + "@umbraco-ui/uui-loader-circle": "1.14.0", + "@umbraco-ui/uui-menu-item": "1.14.0", + "@umbraco-ui/uui-modal": "1.14.0", + "@umbraco-ui/uui-pagination": "1.14.0", + "@umbraco-ui/uui-popover": "1.14.0", + "@umbraco-ui/uui-popover-container": "1.14.0", + "@umbraco-ui/uui-progress-bar": "1.14.0", + "@umbraco-ui/uui-radio": "1.14.0", + "@umbraco-ui/uui-range-slider": "1.14.0", + "@umbraco-ui/uui-ref": "1.14.0", + "@umbraco-ui/uui-ref-list": "1.14.0", + "@umbraco-ui/uui-ref-node": "1.14.0", + "@umbraco-ui/uui-ref-node-data-type": "1.14.0", + "@umbraco-ui/uui-ref-node-document-type": "1.14.0", + "@umbraco-ui/uui-ref-node-form": "1.14.0", + "@umbraco-ui/uui-ref-node-member": "1.14.0", + "@umbraco-ui/uui-ref-node-package": "1.14.0", + "@umbraco-ui/uui-ref-node-user": "1.14.0", + "@umbraco-ui/uui-scroll-container": "1.14.0", + "@umbraco-ui/uui-select": "1.14.0", + "@umbraco-ui/uui-slider": "1.14.0", + "@umbraco-ui/uui-symbol-expand": "1.14.0", + "@umbraco-ui/uui-symbol-file": "1.14.0", + "@umbraco-ui/uui-symbol-file-dropzone": "1.14.0", + "@umbraco-ui/uui-symbol-file-thumbnail": "1.14.0", + "@umbraco-ui/uui-symbol-folder": "1.14.0", + "@umbraco-ui/uui-symbol-lock": "1.14.0", + "@umbraco-ui/uui-symbol-more": "1.14.0", + "@umbraco-ui/uui-symbol-sort": "1.14.0", + "@umbraco-ui/uui-table": "1.14.0", + "@umbraco-ui/uui-tabs": "1.14.0", + "@umbraco-ui/uui-tag": "1.14.0", + "@umbraco-ui/uui-textarea": "1.14.0", + "@umbraco-ui/uui-toast-notification": "1.14.0", + "@umbraco-ui/uui-toast-notification-container": "1.14.0", + "@umbraco-ui/uui-toast-notification-layout": "1.14.0", + "@umbraco-ui/uui-toggle": "1.14.0", + "@umbraco-ui/uui-visually-hidden": "1.14.0" } }, "node_modules/@umbraco-ui/uui-action-bar": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.14.0-rc.4.tgz", - "integrity": "sha512-zESDURH6TbAPOs3yV7KtLJ1XbDq7EWUwFgZ7jn3thu7Ue0LepcV+BoqyaYZgisUCig/wkp3xc0HPddyZHn5uZA==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.14.0.tgz", + "integrity": "sha512-cTX0TvVxNC7EFMtEqMGMBFC8E5O8bedmJ1Hkddvp4lAzrbLGrFTPcwOG/kISaSXzFrnMzyQNdi3s23orcL5VRA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-button-group": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-button-group": "1.14.0" } }, "node_modules/@umbraco-ui/uui-avatar": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.14.0-rc.4.tgz", - "integrity": "sha512-q7FYC/njV+w0al01Nsaw42KeLVsOKjjqtCnuB7GUGgCEbbXFXIUjrbeg62feyC6KRM1x+1qqqO+JIDWeGUFspA==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.14.0.tgz", + "integrity": "sha512-ykYlbHV4K+zW7viv+oqfsGcL0ZII4vQy3YnPusFiz6bS3ceDDpY9MpRtuDTv4z+PXW4Wo1FjB2iMHrza55/RUw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-avatar-group": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.14.0-rc.4.tgz", - "integrity": "sha512-HcuGGYvssq24qipPH1mn46E7QD1WyWL+GBNvlI2GmTrgxxcQKIDpqaiFOrLSM90mTLVBnZkhkoQ4Rf/Fb+OKEA==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.14.0.tgz", + "integrity": "sha512-8pLxQvtW1yuaReuSy0wq6kYZXPSiZjKv8ecmciLgWr9aKGR++CwYrwWKA3c+jZTarb8dz4MGMnQpqHCTqlQbpQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-avatar": "1.14.0-rc.4", - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-avatar": "1.14.0", + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-badge": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.14.0-rc.4.tgz", - "integrity": "sha512-nVq4qvsZVIieq2qpNTgzrytkKNF4/3VM2qxgzOsyRC/u0bc+hpGtIenzQDUuwgcUC1kkLPii40dXTu6fVVCGHg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.14.0.tgz", + "integrity": "sha512-iUosWuf7XngJBdwmvx8BZkzsollH4146Gt2CQBGltFZRCZ7uUkB2zCYb2E1ys4BEWuKHK4ZLiOcYtpPtoNeZJQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-base": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.14.0-rc.4.tgz", - "integrity": "sha512-UXBJ1o3fdn/aGKY/Nn597EzTHxrPVsEg/gjcnRQi5u+NKEf38jXAHQ8HZsjDpfIgxnkFg2PztLQaZcapgEEOjA==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.14.0.tgz", + "integrity": "sha512-m/BQYeKL9XmHPfHfCfSwTjcmUmJxynI0m4yqLTmQqQ3x1hiRqqfYyLSrpi3uW1H/HCxttUkxEwkhAdYogMDIpQ==", "license": "MIT", "peerDependencies": { "lit": ">=2.8.0" } }, "node_modules/@umbraco-ui/uui-boolean-input": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.14.0-rc.4.tgz", - "integrity": "sha512-UBeRk80W77iojie9NQIu1TTT5p4oghU4Vf497y1vglsuXFkKTawH/0kYOzDuapJMyMraAtUqOA0hwugrFY1T/g==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.14.0.tgz", + "integrity": "sha512-O+/GzpF2mNLdhXXNAfxI0k5VaR7CUnUxDDxYPhMgmuLOBwdjiq9iScJM4MUl+l7hihF5ue7os6I8DY2CnG7LJQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-box": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.14.0-rc.4.tgz", - "integrity": "sha512-u1XQYgG/UIrNPEhIjR7e8e8lLkp5M/Ao7zIgm3BleNkYHAQU2NQqb2bLowyjpT5GrufWrW2jmVO8pVJ66vyrxw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.14.0.tgz", + "integrity": "sha512-VjD6MtEnJuHOYarFtLvn/Dyz2MRJ0sPXSDTZ3HWsF0G5fdAUB487ErOGb8CL1JtmUYgZOy6N3CqPlFyWHD+DIA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-css": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-css": "1.14.0" } }, "node_modules/@umbraco-ui/uui-breadcrumbs": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.14.0-rc.4.tgz", - "integrity": "sha512-pt1ZA7XrpmXJctitb+UPhjdAqEQa5E/tDtaEQbW/wTK/iPC6dv6BnHdThlC69FvhddIng2PPgbXeBojjx9Pk6w==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.14.0.tgz", + "integrity": "sha512-IxHPUnIaGyvo54oDdcJf4AfzkYF1Nf727SCLHD28WqMh4QCKQQsyBGa5xhFjcQ4RSediNwvAnY7dNVVYu9OrzQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-button": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.14.0-rc.4.tgz", - "integrity": "sha512-lmYxADppNZW+4Rg5XIqWFrMd8EQ65h+peCedx/tjJ4gERq4uTl5iw/INWLGWFqPhNgye8ZH3oSsHa4VUPSCJJg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.14.0.tgz", + "integrity": "sha512-TVCPLVcXR4wGjtLtrTYOjoAdvwQPiCep1GiHAbozD0QKNgOqjZ2fE3CapwEwoaSNGcgw/37t0KMhUqfmZgYo2g==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-icon-registry-essential": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-icon-registry-essential": "1.14.0" } }, "node_modules/@umbraco-ui/uui-button-copy-text": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-copy-text/-/uui-button-copy-text-1.14.0-rc.4.tgz", - "integrity": "sha512-+9GHDLSPpb8rRVDLZMwCg18I16m3eos2mg+f0dRbHTZOrYgeAkk+QsFdy7sTQVj2cgML0JXrDg8y5N1eihce1Q==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-copy-text/-/uui-button-copy-text-1.14.0.tgz", + "integrity": "sha512-cE3ZjSaWzzdgYdNtGv1SzIClMoCxqL86+QPz9zMYvN//yA8YQmkv7y2eUyT+lKFNJXXHMgzVKMhoSn8aUzvQrA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-button": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-button": "1.14.0" } }, "node_modules/@umbraco-ui/uui-button-group": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.14.0-rc.4.tgz", - "integrity": "sha512-Cb5faAo2ln3KFgQ1xA+l4KvdZJ2dQ6ThjUWUe9uYdhS/9w2IfUOzkIJsUCXCpzhUU0kSuJpo1AfX8z7XkrVUUw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.14.0.tgz", + "integrity": "sha512-W4Jf671PqtnBnYKWNyyB6rgq88qyT0IWhqUR3ilJS45znIiht/ec5xDhTFoyhLWP9+zQn/3e8EqZbmnJUj2HAA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-button-inline-create": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.14.0-rc.4.tgz", - "integrity": "sha512-D5nXcswss/4IOzXbQF1dRsLH7hHwaEkVmiqZd21421ZrxGJ7XejeM3s4afe3AkVDi+wAyS4kl2e2K7/8lyiNHA==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.14.0.tgz", + "integrity": "sha512-vDOZJEfjQDqIKymdpxD3h/uvBacXu/yD/xnHMrxADeMQYinvNn0AFjTFBakgfusymRLjXQubrJ63MWqidTRsQQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-card": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.14.0-rc.4.tgz", - "integrity": "sha512-57lXGDrFUc0uuS2G9csLpRhFJyJEt2SNvc38/RTlE8liDL1KrtZk5tbsx8MNlq48e01q80zn64hy4Wu4Rxox3w==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.14.0.tgz", + "integrity": "sha512-9A44pCbx9nyBtbvFE26FiP+rLE2rUg177vgoMTuURuszYoiEgfU8ixVhWCbDD14LpxET0/Yg9RNiMYF8K1bDvw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-card-block-type": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-block-type/-/uui-card-block-type-1.14.0-rc.4.tgz", - "integrity": "sha512-5GDFxbUiJrGaxvCjGAfGojtCc+t8wQFRxLPZnEKDuI5fFr87WvRBd84HOdXCZs9/6jR+N+ZvIWSkEysVwVlHYg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-block-type/-/uui-card-block-type-1.14.0.tgz", + "integrity": "sha512-FQAInMb4AKj11Jy3TQTc6iz80h0ulmlraw3CtFbnOpwHIRP/aqUVGCW0Zb+Yykz1DGmmGvFE1u1epK/jH//6aQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-card": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-card": "1.14.0" } }, "node_modules/@umbraco-ui/uui-card-content-node": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.14.0-rc.4.tgz", - "integrity": "sha512-v7ujHGkDkk9/RwxFWtO0FGoVdR6kHGfvtZ05udl5hRVjcpR6u8Jij7vylSizOBF7kW4j7IboN+vgAbwlsJ0BYw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.14.0.tgz", + "integrity": "sha512-KcXiUfG0ulgvXWuqOGu3LTcRVoGru+Q4sj4q0bV9H/d3ZfY1idPqhkbM7v6TO56gzCng0DJ/kTL0/H5IWd8IcA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-card": "1.14.0-rc.4", - "@umbraco-ui/uui-icon": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-card": "1.14.0", + "@umbraco-ui/uui-icon": "1.14.0" } }, "node_modules/@umbraco-ui/uui-card-media": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.14.0-rc.4.tgz", - "integrity": "sha512-3aeWUeJgDDD+xahHiZdsdBzQTvuXyBDIlFPFa2vNDEPJ7VRU31E1FlZ3zr3B3MNZtB30HcuUKTilUsTkmX3D5g==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.14.0.tgz", + "integrity": "sha512-Lnr8Y1bxj6QoleSMCj8GDsyJu1N5Rm105/nHYdnPO3+JcNNv3ThodKdHXYo/slSLrcVOoPJHNAQodZG66mIqsg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-card": "1.14.0-rc.4", - "@umbraco-ui/uui-symbol-file": "1.14.0-rc.4", - "@umbraco-ui/uui-symbol-folder": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-card": "1.14.0", + "@umbraco-ui/uui-symbol-file": "1.14.0", + "@umbraco-ui/uui-symbol-folder": "1.14.0" } }, "node_modules/@umbraco-ui/uui-card-user": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.14.0-rc.4.tgz", - "integrity": "sha512-jf/gisfoE17A1aSyUUCUkAJ72RlkZTorKFyHbw4uhQBOY9su3twHK7FfpdVfvhPTT1WiLrIj6hJfT4LvsTRIYg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.14.0.tgz", + "integrity": "sha512-ZBFWO2109+A9SkkznqNHUiul6G6zab/D318yz0wMTW6m2R0E8QE9mljIw8Entd720HeZlvOKpvK3ElSTNlxnJg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-avatar": "1.14.0-rc.4", - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-card": "1.14.0-rc.4" + "@umbraco-ui/uui-avatar": "1.14.0", + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-card": "1.14.0" } }, "node_modules/@umbraco-ui/uui-caret": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.14.0-rc.4.tgz", - "integrity": "sha512-3leJJlN4vBomZr4Y382nQ44/meeQI7mD+pjz/GRqmDagRAezq8olql0Z+3FEXJjzm7ycz2TtP2Fjn7L2nUulBQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.14.0.tgz", + "integrity": "sha512-c+71esCgWn7V6Z8gr9fZkfw9BQgewZi5pbJ8R1G6HLEzz0NN11zAn5BAVebdxF5OUi/ajFqvxnAYOSSiWel5tg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-checkbox": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.14.0-rc.4.tgz", - "integrity": "sha512-p69yFqM8UEGmDWC1XVcKCY7htsF1vxPBO8L6YXGKNExLxwv/DLAvmLtIa8tnqX5M/51DpZ41SoDDf7Kl0kd5HA==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.14.0.tgz", + "integrity": "sha512-qD/O8H7pcPnJkaf5iWjDKg89LgQKZeuBiRmrXqVePDk0HHjdZ+8TJlDaANRyBq5JePezrj6UpHPVabYDqXIJYQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-boolean-input": "1.14.0-rc.4", - "@umbraco-ui/uui-icon-registry-essential": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-boolean-input": "1.14.0", + "@umbraco-ui/uui-icon-registry-essential": "1.14.0" } }, "node_modules/@umbraco-ui/uui-color-area": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.14.0-rc.4.tgz", - "integrity": "sha512-4XbHjgAEnefVyw9xBeKIuy6EEKRORFIhekRwhmvGsr3kqbhG4TMdZzhT4BSBi4mfVVa+VMiZ+lMhkT6eeDoKDw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.14.0.tgz", + "integrity": "sha512-ijja8REx/1OhG2ZA1yK98Q8IhSeDf+GIjfCvkR1ptzzFkz1Wiv1mvxkh9eExByidp90SgsTF3kdUxR8x6V570A==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", + "@umbraco-ui/uui-base": "1.14.0", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-picker": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.14.0-rc.4.tgz", - "integrity": "sha512-vD+k43g/iyj5MDbs7v1e4AWTih8Q3bMoJZnyEoS1IrhZ3l1QnzxmvFRAs3kZQLBzCBUct9vWG88uvavygU5j4g==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.14.0.tgz", + "integrity": "sha512-WG7I2mYDjW3W27V3LDRpUrZfkjnnuHPo8+X4ZBnY6xRXnQ83hxbdqXkaKYI6VY1dMhhqGa82wrbb4NBHGkKBiQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-popover-container": "1.14.0-rc.4", + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-popover-container": "1.14.0", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-slider": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.14.0-rc.4.tgz", - "integrity": "sha512-H7N8ep0L8GxUEChyFa6eFFzuR8yqppqeIuBNujLPhtl6aV1hV7V4wBnp2Wh5ttWtt5Ns/VMW4ZKzfrlG7f6JGA==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.14.0.tgz", + "integrity": "sha512-8eNA+7GJNVl68amAJIbCWMe/8usWanZ1fKXPf3ZJ54K65K2cDYd2hS7DEVEwSXo+AV9iMeBYgbHIRBqVPZ89jw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-color-swatch": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.14.0-rc.4.tgz", - "integrity": "sha512-dUFJk1/xD2W+ztOb+4QRGU6rgnXP4HvGZhg6V6B1qd15MU+9flSMri9HbxuclVmR9cqt3kuO6bwajMbp6Q2aTQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.14.0.tgz", + "integrity": "sha512-1c2bNmEqL5J1ZW24adzSsGDwnYFQOyjsI29M+UQdlTZW16s3zh9O97414KIN9ivE+SkgbE7c9lZhNEKyi2IJpw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-icon-registry-essential": "1.14.0-rc.4", + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-icon-registry-essential": "1.14.0", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-swatches": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.14.0-rc.4.tgz", - "integrity": "sha512-aGvlKkW2DebrS51itO7JzGTRPyUrDmOYS6QZQxs03BDLMfBcmBBRBDnKN4Prx7fEbioC4bXOBPTwH73R06j2Xg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.14.0.tgz", + "integrity": "sha512-UIQysF89CZH0CKwhzbd+1BZAXxUlnCmHoWDGot+Mb4sGZL5esrEB0QQmhJOVO/ehMP+GoFUnh4fWLXUCzRPdvw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-color-swatch": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-color-swatch": "1.14.0" } }, "node_modules/@umbraco-ui/uui-combobox": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.14.0-rc.4.tgz", - "integrity": "sha512-GI+fi+jnX2Ihxwtsmk0QtsDZKQICtP7hrSv4dyMSvbXmACT8X31tKLvkzEE+/UyJnVbWKe3M5dkFaoM0dFsIPg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.14.0.tgz", + "integrity": "sha512-ZKa0KF0ADSX//hm116QdEDjQgyZK1ahY+hzOtdU7EDlJBQdTq3cHtwn6B8JdhPoVlS0Yd3XB+oQ7UXjYn7rGQQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-button": "1.14.0-rc.4", - "@umbraco-ui/uui-combobox-list": "1.14.0-rc.4", - "@umbraco-ui/uui-icon": "1.14.0-rc.4", - "@umbraco-ui/uui-popover-container": "1.14.0-rc.4", - "@umbraco-ui/uui-scroll-container": "1.14.0-rc.4", - "@umbraco-ui/uui-symbol-expand": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-button": "1.14.0", + "@umbraco-ui/uui-combobox-list": "1.14.0", + "@umbraco-ui/uui-icon": "1.14.0", + "@umbraco-ui/uui-popover-container": "1.14.0", + "@umbraco-ui/uui-scroll-container": "1.14.0", + "@umbraco-ui/uui-symbol-expand": "1.14.0" } }, "node_modules/@umbraco-ui/uui-combobox-list": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.14.0-rc.4.tgz", - "integrity": "sha512-c996vWj/fCzaxn/P7JlmewG11UiJt0HuB7X+dq6mIrxOiGPYOLoM/SVg3z3g8+jiObkeJHn2IaXTd4wIL/OQyQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.14.0.tgz", + "integrity": "sha512-CRsRycwyb9CeyNINQ1KztGAHTRhQcphVEl/bLVr3jTtuqSWWxKsGQVDe69iKNAfHuiU3o7MlsUH0+ea296x/8w==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-css": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.14.0-rc.4.tgz", - "integrity": "sha512-nZZ9HCPh9SpS1ibxvJHhEcWW5hB4xmCev9p4vCvVBwsvnGAs2pyDaJJUwbMzs1LOPUP/AbohM3g1R6y1gGgFrg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.14.0.tgz", + "integrity": "sha512-M0zmrjBpDzrb3r+l1qMNGEhJbJSHNeR7PDtpHoMaO96ozaZSL/87XzpwsBklwTR9xyfm+VgDFNTqQXqYnS2e/A==", "license": "MIT", "peerDependencies": { "lit": ">=2.8.0" } }, "node_modules/@umbraco-ui/uui-dialog": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.14.0-rc.4.tgz", - "integrity": "sha512-9O65Hxgj6BTVfhB04KxpXvJsVCJa9ScfzIMN71nbPf+IhZ/iM/2k3dO29BCtW4oSyw64Fu43idq7y5sktSSZcQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.14.0.tgz", + "integrity": "sha512-eZdmNLkSW5OAETTZlvUKByQbXv/4/tYznNHCHyWxxGrYuHVHh5sNj+3ZUbZp+VjIy1zd42slKh/KDmYV6pBarQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-css": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-css": "1.14.0" } }, "node_modules/@umbraco-ui/uui-dialog-layout": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.14.0-rc.4.tgz", - "integrity": "sha512-4DGWIoKWwI0pDz7+E2s0RDojYXngxYFGvcPnt4p+GfBQUkjOdoinIE+rsQkoQcZKs5mw8604exXkp1DmvaZaLg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.14.0.tgz", + "integrity": "sha512-rYlwHk5zsX+eBZLBxI/68W6Q1vb7G/NuZoasquQXZ7jgxRhaRw199YQojtUCWtIowWn2uqqbD2a0RYPs9n3FIg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-file-dropzone": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.14.0-rc.4.tgz", - "integrity": "sha512-gmDgk8Tzmnmf+YUiEvDKskbgdFKvyfcCRfsW6TEdmFMmWAKEn8A52mg/AprSZB6ReoWLsIHFxhmggeIPbt0QHQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.14.0.tgz", + "integrity": "sha512-GSy0mlR5KsyC9oF3CMB2qwuGiT5P3moVFxanRAO7u8qimRAO2jLS0/8u1QCh120AGRQZzDhw/TJ9XF7NXTWJtA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-symbol-file-dropzone": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-symbol-file-dropzone": "1.14.0" } }, "node_modules/@umbraco-ui/uui-file-preview": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.14.0-rc.4.tgz", - "integrity": "sha512-BeZ2AlEwqHiycgaspWqHdjeDcA3Uv84ZwxxPrlXXO5gshND3S5B6q7xkWe92KCGLvAAB/0sEe3kLfFS82AciiQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.14.0.tgz", + "integrity": "sha512-UGxlpKoCVjFYbkNfXcMi0kCSjcocnHlTHH1fyk/Mg5jZ1OZCmV8dnQQKCB139X9FdHZhL0QeZA3KZUYA28iqaQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-symbol-file": "1.14.0-rc.4", - "@umbraco-ui/uui-symbol-file-thumbnail": "1.14.0-rc.4", - "@umbraco-ui/uui-symbol-folder": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-symbol-file": "1.14.0", + "@umbraco-ui/uui-symbol-file-thumbnail": "1.14.0", + "@umbraco-ui/uui-symbol-folder": "1.14.0" } }, "node_modules/@umbraco-ui/uui-form": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.14.0-rc.4.tgz", - "integrity": "sha512-D/yHES83/gCUoUbpW7CvokDjCEm8Delo1AM718SoCPOJNt1DyUaQtMJ+MPlfnJCJGelcnOSGCKOsPpCdTBQZlw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.14.0.tgz", + "integrity": "sha512-UoEP62nCNTa4ILDNFX2ASNN95XfUugPhGmtUdKmvTUH6F3NSai2iiLIp/dM+GBC4PJXmt8rzq6NdLqYonkMK+w==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-form-layout-item": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.14.0-rc.4.tgz", - "integrity": "sha512-M8UczkVX9c2U/sc+cPuZ0YUBTyKpHwN3wlv/R2XaBE3mA6gKV/N3cRMDvNz9g7KBeaoAvREbF2LM8XxfC0v/tw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.14.0.tgz", + "integrity": "sha512-1ahnmF9Ciw0RC/pRAS3FJ2vVmnpQ6O20bwqJrCTYvJQeqJXV3bzSxYmMY/s6Z5tsoNDzkfYcTHfnti/MmyuFJw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-form-validation-message": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-form-validation-message": "1.14.0" } }, "node_modules/@umbraco-ui/uui-form-validation-message": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.14.0-rc.4.tgz", - "integrity": "sha512-tZ48nDLldzkxQgEK7bmWOzqKRtSCMZbY68wnr4jNhPgRj48NMHkOwA3bzdyVpEXkQCO7kRvsNcxIecbS3TUhyA==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.14.0.tgz", + "integrity": "sha512-rv+mId8htw/8V3rle5bOjgWK8X+3IX7B+PAvFAfy+lc89OUV+OT04RGy0sg3hhncoPsIT8EhQ2MYunIyh3MwnA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-icon": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.14.0-rc.4.tgz", - "integrity": "sha512-U+2Gh1N6kAtCFVFkSnh6GmYf0ZQhX1KFlBKt7KIZ/uw4LMop2qLnOz3V0GLKsHwWGMAUuDpdveAHFS3MCVJ4Xw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.14.0.tgz", + "integrity": "sha512-IdBRPC8xc9leIBRaHmTVoGhxRkz8CNeYjgJLNBauFox5uSkWuE7OE9BUYBJKdZz4k8yHLHHrWHVkcaHvgF+QUw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-icon-registry": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.14.0-rc.4.tgz", - "integrity": "sha512-rKX5YquEU8Hg9MpPJTKvLjl6OH/S/EojGWnSfXpWnTDT9zWdeNzoJwubae0MILmlcMDnMoI1pmcASgfdHpFZWw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.14.0.tgz", + "integrity": "sha512-N9cXDF6B3R+h2TCaCHkOJUTSsD10Wei8NrldvYL2fhBqG8FgaquqBI/715NGoRtwp9KKz74N/Z6EIn2MBiMaMQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-icon": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-icon": "1.14.0" } }, "node_modules/@umbraco-ui/uui-icon-registry-essential": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.14.0-rc.4.tgz", - "integrity": "sha512-M9jV2buP2+C5GLzixXA87rCuZy+32GAi6/Q0W6SGzBHIzHQhNU7R6xbIeIc/Ki/i1lVBghXJ1JrvBL6Qjom31A==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.14.0.tgz", + "integrity": "sha512-NjkNmQpMHLcyGakqGlASyPOr8Vnr8+KCdExfkbDdg07iDFlzyGyNmCkTdzY2tNXsIq5bD1c4nzHYmE76FszorQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-icon-registry": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-icon-registry": "1.14.0" } }, "node_modules/@umbraco-ui/uui-input": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.14.0-rc.4.tgz", - "integrity": "sha512-zxWvaaAwZ7Rihe+Ca/ezGZmoNRl0jzp6Tls20vQI/CRHQGR+anJiY4JmsQZbErMOtUib7o04REGZdfEzs1vJ7Q==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.14.0.tgz", + "integrity": "sha512-FeYiTUzCcZdNtury6B8ZMl66mW/fGfgXMB5HvIVDFp0ik+WpC8vLcQqHgJ/qFxWGF32H0qIsVqLnzcwkAwvRxw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-input-file": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.14.0-rc.4.tgz", - "integrity": "sha512-mawTTpQG/hU3+Ug5cDpCmJjsiFzv1KreQ+TRQTcWx4lkGjgAoOECXuZMXTq3Clg34aCf63aGlafSlDI3z59J5Q==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.14.0.tgz", + "integrity": "sha512-l4RcQWf+0OLM9i9NWvnMkQtzzNcALBRmtiTBLdz6ROFm2Z+S3MuT8vzl0QiduJNWK5gzANu/FFuTL70fIh/BDw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-action-bar": "1.14.0-rc.4", - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-button": "1.14.0-rc.4", - "@umbraco-ui/uui-file-dropzone": "1.14.0-rc.4", - "@umbraco-ui/uui-icon": "1.14.0-rc.4", - "@umbraco-ui/uui-icon-registry-essential": "1.14.0-rc.4" + "@umbraco-ui/uui-action-bar": "1.14.0", + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-button": "1.14.0", + "@umbraco-ui/uui-file-dropzone": "1.14.0", + "@umbraco-ui/uui-icon": "1.14.0", + "@umbraco-ui/uui-icon-registry-essential": "1.14.0" } }, "node_modules/@umbraco-ui/uui-input-lock": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.14.0-rc.4.tgz", - "integrity": "sha512-TUCh15WKDqbz10KdLzIOo2POcYYreQvwcicGx6H3jyLrZZwttE4VP8xZPBln7C7sewLCJNtH/gw2YeLLJDzTmg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.14.0.tgz", + "integrity": "sha512-wt/VL43EpHJcvf9GEnXSuHG/iW7yI7vD3wEWI+wgCKv9SdTzE/M4aPon/pxnQsVCvGvWhWvdFeGdlfwhXSurLQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-button": "1.14.0-rc.4", - "@umbraco-ui/uui-icon": "1.14.0-rc.4", - "@umbraco-ui/uui-input": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-button": "1.14.0", + "@umbraco-ui/uui-icon": "1.14.0", + "@umbraco-ui/uui-input": "1.14.0" } }, "node_modules/@umbraco-ui/uui-input-password": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.14.0-rc.4.tgz", - "integrity": "sha512-rcRLKVPyUkZUUk2r5yeURfP57tw/KMZeMg3rYNXZszRTE+i/WDpoRBodzt9xvfKKnbYjsoZ6VZ83L3c0CUeMrw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.14.0.tgz", + "integrity": "sha512-XCc/0QJH2w9PZJPouhbJbMR+w0QKUusut1MWW9NsfzRheHkcDuzc3Vf69OLFGGww/FjYjkxwT9as/2aLXxotjw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-icon-registry-essential": "1.14.0-rc.4", - "@umbraco-ui/uui-input": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-icon-registry-essential": "1.14.0", + "@umbraco-ui/uui-input": "1.14.0" } }, "node_modules/@umbraco-ui/uui-keyboard-shortcut": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.14.0-rc.4.tgz", - "integrity": "sha512-Tcy1EUQTob8Ds/5hAfG2iMHsrVwrS12fjzRy+p29K8BUhnp87JgJ1OJt6W3kQHQskLQlYb4/ZSIaBBD5pEEPEQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.14.0.tgz", + "integrity": "sha512-G3LCdfP5uPe00bg8kKBMZhLan8gH7QbSRMX7aMsT+Fc6nAyWWTwJ/Qt4qJjk/fbeHts1OWD+sbHdRtXK+DotRA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-label": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.14.0-rc.4.tgz", - "integrity": "sha512-YfRGdCqFaJk6Cguh36mxrHfScbtrIKpxfQlCzwjfZ8DcxXh6cYYSZkQkWHrbjuiDWG/8853FrKUA36lV2oGItA==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.14.0.tgz", + "integrity": "sha512-a22p01O0CqnNTxQxmjPwCFBFXi5KKzhpno4DXjSDVTmeJc85IxiR5ODAELKHJf6XwZMkOv+QG+AZuIJFVEZ13Q==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-loader": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.14.0-rc.4.tgz", - "integrity": "sha512-DovO8MVZV29ZivLDWgHYPrHLxJjJLS261RnFDJrIxflVfE0E4S60T9/3IE3pQT/ztUf241uq+ddNICNU/7vbTw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.14.0.tgz", + "integrity": "sha512-2/HNDk0AZQ992hHYQk+VP5GetofSKxCsLf77/wiswyz48kM9eJ9wkieovxzLK1IuOQs0A+cCe2NnU/z5fZnvvw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-loader-bar": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.14.0-rc.4.tgz", - "integrity": "sha512-TPWAoZ8WJiQXYo2dpuOCltq5npTOt+h+ahaQwkQpk9wNAwTr9y299HxiOK4c26efHGQ+9O797paCMi7knNgW+w==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.14.0.tgz", + "integrity": "sha512-hAviuSx29RPWpYIqmWiGmW31r3nj8A1VGobmdVwR0BJHfdxee57ZrNGsEZhK6pzuHSvshGTITNwLk03E1UA/Nw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-loader-circle": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.14.0-rc.4.tgz", - "integrity": "sha512-MYVvbs2az+hA7gAfjLR+gu1VuILzC/zsAZtTuNCpMoFozvp5BirWzme9DGYlMD6hlW8pTSLdE9SOV19+ptK7yw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.14.0.tgz", + "integrity": "sha512-I+rcgwbxwKGxLzVCGZ3qT4e/sK8CofTPzdCmh1BpNlKrWpuJ9NGgysrGs7V1IleJJxIXuzD+BBlIoGxuCwBJQg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-menu-item": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.14.0-rc.4.tgz", - "integrity": "sha512-FXjNrrpBUUkF7t7Q9ikT+Mw3DV9QvEsMF2XPl01XI6zhsCEZDvp3OxNLlWkkhv99Scf2teZqdulLQ8A+LmkwqA==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.14.0.tgz", + "integrity": "sha512-8Pc68dJLwl7GrbGIRD7MpyMSBkuz8/CtzuLhygrFHK608crg5bBPC1+Zdt3VdkqDk7QZRd5rtL+pYgEJm87Q4A==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-loader-bar": "1.14.0-rc.4", - "@umbraco-ui/uui-symbol-expand": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-loader-bar": "1.14.0", + "@umbraco-ui/uui-symbol-expand": "1.14.0" } }, "node_modules/@umbraco-ui/uui-modal": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.14.0-rc.4.tgz", - "integrity": "sha512-Qq/WQbHahMIyZ2wnmrR98SV2fc3iE3AXySFXbL7uEB5M3pmU8SC1JpLGA90/B5lDSyttA64mbqdces1vzxPvaQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.14.0.tgz", + "integrity": "sha512-3Ux1guj029PIcUn4nmPUU29Oqxq1HoRUib3lWoRRIgJ3F8WyGms+GEgCMj4v/LzIdezczqVtxKdOMcLIm2gvcQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-pagination": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.14.0-rc.4.tgz", - "integrity": "sha512-JaPFK/IIr4bygmTftEUv8iV0GVfWLez+/YvNDRii9pViE0nn4MsF20PDUpu1CN3XvYOCqqu5ptFnmwIWhXf/Mg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.14.0.tgz", + "integrity": "sha512-jP906bsiXOBpAdF/ZVi0hlRyR/+HX52ocjItlvMJWc2Xt4Fpzms7W90buYcG4hvz7g0snKy84JgTMup5vxf2iQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-button": "1.14.0-rc.4", - "@umbraco-ui/uui-button-group": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-button": "1.14.0", + "@umbraco-ui/uui-button-group": "1.14.0" } }, "node_modules/@umbraco-ui/uui-popover": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.14.0-rc.4.tgz", - "integrity": "sha512-UGBplMad24pvornzgk3xBACl/DszLlwIl+I4+fRWknLpIgwjFnQHhD/ary7RbjJncfC78GyeqogVapRITc4oRQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.14.0.tgz", + "integrity": "sha512-blMgYLRlEUon7vAQ6s1KE0hNBgyuMeI7ugxHCMDAFwgtHIh9VO2YfPAqlKBkofM72R9QZDbkCg1tOUuuF0yX1Q==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-popover-container": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.14.0-rc.4.tgz", - "integrity": "sha512-nZCyIQOMmBwgOPFWedsoUhxiKr5+i7P/9x+WYRPjDouu1KwW85y3D50j2ELQRZ5jSpt16KrF29hucxTKMmYrHg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.14.0.tgz", + "integrity": "sha512-1wG99PbKDdkzvV3W2avF5/zU7XLoxmui125EfKwCdDYuE5fsR1alBZHsdk6PvFXXpcbGaNJ/dWyWg+Ip687HeA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-progress-bar": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.14.0-rc.4.tgz", - "integrity": "sha512-CZSogzxLbbcfdg9ik3vKrpnGsE2IB0nRZ3xr485QOcFPY7MCVbdVF+z/jicokvjz0MT24k80sAw1/RqD6LZS3g==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.14.0.tgz", + "integrity": "sha512-ImFS/QWWSZ9oExINb8thaQ6mexFpq62AbvZoVDzdBrje1pf9FErSs4u1XReS9iRtkE1kyGiyY302a4fAoKyMtQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-radio": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.14.0-rc.4.tgz", - "integrity": "sha512-DtfexpuS2tkrU3xM203nfrJg6CUqFXWJHry4/veuSlO7TBIaMEzhDrfAboyOUvXJF5q5130CmFhN3i69/bgFLw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.14.0.tgz", + "integrity": "sha512-PbQ0SloYLJE6YUldwPU5MoBj+/zIQifNhaEYb2Ss2Ka7LFXFAZ9TvXr/INreh4zxI9DSeXirj41k3O+7fbB/Cg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-range-slider": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.14.0-rc.4.tgz", - "integrity": "sha512-QqFcYCeKwYm6ahwe+60oZs0uzdELMk1zcCcQRHdspze7vx4fqDwYtBL64IjGoKQF/S1T+s3AEq7PG8eqR086Dw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.14.0.tgz", + "integrity": "sha512-ha798qXr/J3Kjd++eHBYdfqFSVKvSg9TWd+aAhAVj9rVb0Q8mbuinqUcWN9ZHukTNl7lG0/4HbTfM80Lm5V6TA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-ref": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.14.0-rc.4.tgz", - "integrity": "sha512-DcGm5JYTMFZgWfBPzRp/RgUVtfJ+s1idK5tkwKRgQyXECEbBHXzpXwz/rSiMeiEuWVrS/49vHtFY8YeolzVEXw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.14.0.tgz", + "integrity": "sha512-bjKcCLRxcu6HR+0kRrLpdit449FHhc16x1LZPncTtjAXN+kZYVmBiQ1QL2/W1l734vRm68nmHVuE5LB1Y2RuIw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-ref-list": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.14.0-rc.4.tgz", - "integrity": "sha512-OjQlNzCBhJVseV2o99uxSd003tGQjSOpYIlTZT+Dh8Gqfe+6mJSnFwfHMUuLstgFP204i6CEcixtIM0x2Gl9/A==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.14.0.tgz", + "integrity": "sha512-rVUldYm4FMAM3SJ8cCbvwdTm4LL9iz3LoFeTxXpfuo6STP+Y26kqR5z5hex6rUcX51se5yEp7PpQDO5bHHz5OA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-ref-node": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.14.0-rc.4.tgz", - "integrity": "sha512-/JUk5L9k6rOQIlIk/wykeTyQZ+pUhSAl6zlzkPjgU2DoLkVTaetfdggmA6NVDsMesZQrxtg+e0UD36FAJtx9Qw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.14.0.tgz", + "integrity": "sha512-d10iNjb5x3klPZzzt4AZqeGJ3xbqbaLc4NJb4lQ6C6+djLL+tsJf1MN1vC17dC/wPJ5B894iSBaha0fa8fVfMQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-icon": "1.14.0-rc.4", - "@umbraco-ui/uui-ref": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-icon": "1.14.0", + "@umbraco-ui/uui-ref": "1.14.0" } }, "node_modules/@umbraco-ui/uui-ref-node-data-type": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.14.0-rc.4.tgz", - "integrity": "sha512-V/OGX8mRNP/93vZmPdCL8djdicRAwSxPSVTgHJ5QsnCkwo15FfZmjqnIkU162aI4Levgs9B9JFGIgkaELmi7hg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.14.0.tgz", + "integrity": "sha512-DcwR0qltykP1NHT8aRqbgQ4/PF2h64ehvBUpEeYg7U9/1xgpWlelkHlZ6CREzZUENaOFrpJzmhzbQWxYa7XKWA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-ref-node": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-ref-node": "1.14.0" } }, "node_modules/@umbraco-ui/uui-ref-node-document-type": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.14.0-rc.4.tgz", - "integrity": "sha512-CVQFGFdoJroLlSkDXajQG1t6gDkxoB2IuldTzqZv3M6rN1/UwjIrChbfFIpMwgRtLwMPKsX8v9PyjYlvjbnJkA==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.14.0.tgz", + "integrity": "sha512-71A3vJa5SAZd6rTRaa5r/0fV+fr/Am4T5rZu8gdSfEw52ppkVNbg5iHqIwFKN2QDBzKI9GFSrjSVPmRJIsTNTQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-ref-node": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-ref-node": "1.14.0" } }, "node_modules/@umbraco-ui/uui-ref-node-form": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.14.0-rc.4.tgz", - "integrity": "sha512-JKSSi9XIrNbTjFE9NIYWMfQPjv3zXYwMyCTMwDGc3/qNCPI/1Vp+Q8PVWPX6tHBSPHmMnWb85Tolik7+k+Qtew==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.14.0.tgz", + "integrity": "sha512-hVF6NtGqAZ0GRr28H2q2jOD7T4fTD837sJw7kJTLdzV5Oatu0rqWs4nmV6KpUCJjoUGYFWg+fKc5vvrF+gXXFA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-ref-node": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-ref-node": "1.14.0" } }, "node_modules/@umbraco-ui/uui-ref-node-member": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.14.0-rc.4.tgz", - "integrity": "sha512-FCdk4TDYITLWU32uQMIxWIPrj3gH7GIj34dOfuKtIeHroUvH1t9blrSRUvbxWo4IS0lxl11g/q+w2Ffy8nBy4g==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.14.0.tgz", + "integrity": "sha512-Xy1mCgaPDLWnpXyfU1KgaEX+u04JXKnkbrj92d43k4HB30tbI/8BjwyYEaT3Phvs4fmUC0h4ege41Zu8aYfqDg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-ref-node": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-ref-node": "1.14.0" } }, "node_modules/@umbraco-ui/uui-ref-node-package": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.14.0-rc.4.tgz", - "integrity": "sha512-unE0mQkxk0UOPFevU3XWftj4Zg5rtnuvx2T4jvKU63DfwAPvtXjPVyG1Np5z4LLCI3kbTdqigOdoINfRo7L3+Q==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.14.0.tgz", + "integrity": "sha512-MNF0n9nlC6W7Ove9fm7+YwhWwEL5+nUmhYZySEb3YAwjOXHDgL9hHS0gmT1YXxu+66RtBXdqUkZbfI2AVKv7qw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-ref-node": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-ref-node": "1.14.0" } }, "node_modules/@umbraco-ui/uui-ref-node-user": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.14.0-rc.4.tgz", - "integrity": "sha512-TFN+tV2AxPKKlDtnSQYvDkM12D/VISW03SPR+AZwoxBmEycPHT8yEwOY5s3V2wwOEUTOnJ5dOw9SWCgzTXvmRg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.14.0.tgz", + "integrity": "sha512-AFycox1NtGnhVtGgJ3Sg0fCAUlOf38V7S2KPrFubAFmjbxcddWqlMVWzxTcUbUDE2TL5KHnU/JCUxf4BQO1pUw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-ref-node": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-ref-node": "1.14.0" } }, "node_modules/@umbraco-ui/uui-scroll-container": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.14.0-rc.4.tgz", - "integrity": "sha512-8o1mRxWjpsfOoZ7itGaTJzvrCWhh1AVZ/cWne6Sh/wfHqQX2iXm9W7TVJ1o5t3fQEbMERFTXEB3tzkb9x+WKyQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.14.0.tgz", + "integrity": "sha512-N+jYDLTCmo5vC1Mutv/d/mWMivkbWXI1AWM20i7wDQ3U8R6VsbA4Rr7Ne8V9jSnOrgQY9PHrTE2OI99S0bFrnw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-select": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.14.0-rc.4.tgz", - "integrity": "sha512-G7JMfmMdOEGE2BRGfFuYcvDp3hiJdmxR85xvrbi0gz1deB/TvY0pHhHXlXk+kApkQJQveJIT89CjQ/u2AyvbLQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.14.0.tgz", + "integrity": "sha512-/hTUiJ38/gpEf4pk7AWauy/i4o+DYkJR9CpdkL8oyjjwjkmJAVL817v4sXUcTvuaYYVrVqBY1M7U3FgEumKHVw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-slider": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.14.0-rc.4.tgz", - "integrity": "sha512-sB+JUieP/oRGOfHonfnSG4mtSPB4SCfOmeFq8shqzR9ucvtaFH75ei/cjYheFabhdxOPIzzC40PMyIBL/nsdBA==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.14.0.tgz", + "integrity": "sha512-biiJ7+aJnkfaPcNF4fuIIGfEmvmTXoOmI56BZN4ICRo1+wntVkfY64hjGTQ2gPV/d26eK1FNyUFpRl8leIxjVA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-symbol-expand": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.14.0-rc.4.tgz", - "integrity": "sha512-5+B8L2qeYKIAqjPoAAEqi6qAwxt17iNUwVYiqyidMBh/zis4cDym+5y5ccC/hbuGzCeBHwFWSRBfFpEOPTYrzg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.14.0.tgz", + "integrity": "sha512-8cXPlHmIoajexztcHKGdTrmbp+NR4O0Le+EtQrRMqf6S8apbw7SNy98h3CeSb6Gq2ZTXdXxzZnCtyo+znxpFHA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-symbol-file": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.14.0-rc.4.tgz", - "integrity": "sha512-sC7IeABeoMjxgtMy/HqLCL1Zzm3A5sn7cxHaOztNbI4PO+DRc9rHKq6IIZAMbFgz53PDQvKs/ok2bnlhloFVjQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.14.0.tgz", + "integrity": "sha512-vWx6C/0xT+SUk3LTeqrzbS4P6YXPzN0kqqnUH7riHACYNZxmpAgB8EVU0MzlMdW/TpiMcapw0/KHYuMtBZ8Nkw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-symbol-file-dropzone": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.14.0-rc.4.tgz", - "integrity": "sha512-bYanzwC8H028GoVtHL/lQngPnK4WY1QvOz4oCK9EVzaWsLCAr6ktQm8bEO3LO/vWM/HQJ4nkS7yaNCOc+so7Rw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.14.0.tgz", + "integrity": "sha512-AAb/Cv/INzjonxc4fDR1n0YMs2hO+O+fYtsW9VyAUxqLHnhxNFufwPU80v1Y0nNnKuaNlSAdGwM/4QJujwhj3w==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-symbol-file-thumbnail": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.14.0-rc.4.tgz", - "integrity": "sha512-pqut/KGBor3csD+Zvj6CRGRerhXcS7/UTAcEQSTWjhpohz5iTPd7sLRuuUAdWcAEAkNQXykbJuhuQ9GhoQKjyQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.14.0.tgz", + "integrity": "sha512-BBQKo03UVTPq6MO6GVDPv40w3Nizy8LRKQ6quNuhB0UcrWkqOAoJEMX/afX17oGtCoONN/Zq54mmXWgHD8yo1Q==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-symbol-folder": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.14.0-rc.4.tgz", - "integrity": "sha512-9b/XEd6I2WsjaHJ6s2hZfl9GDfDA5ZlD4YGYRhnoVyWatsA9W4f4OdBcgzHOKA7I4m2IbdRc3F6Jc0SA/osjmA==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.14.0.tgz", + "integrity": "sha512-Z+Kcdk2QyuLf+hKNTacdM6jeNo+wexZ0ktUPbVHJUjYaHuyzqNVV0Du8NJyFBMwyiomV9xLKxQi0YeI/aDg+Cg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-symbol-lock": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.14.0-rc.4.tgz", - "integrity": "sha512-r+zi5yU5WiKVAkLrzLQgi7wvEPJka9aYYw8SeTGko2OiQRXgBCqrssW+FGaRvElWwX3+klv34uRNlr6Hr7Q0Xw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.14.0.tgz", + "integrity": "sha512-dLcc1TkD541ikC+iOEguJmXsJYphqBwEmt2fqVJEDYddmGUf1ZlUNJSjkamU8vaER6NgNIhmqByU0Lv2SdDrjQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-symbol-more": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.14.0-rc.4.tgz", - "integrity": "sha512-IirqSEyLg7edOJlqpyECTVrZPUkkBj2+7l0bBLWnvNZ3xZ8Zd2hXMVXVTOsIcnUYZlyL3SpYSaHS7XkR4Uxy6Q==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.14.0.tgz", + "integrity": "sha512-HgelD3iF2YMRrCumw8YqeVW/OsByGMWF1ILk8aylyS+4faIEKhnKIpLlw0WovFBYJQpWilcm/JtMqBqa6DfMvg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-symbol-sort": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.14.0-rc.4.tgz", - "integrity": "sha512-+HyeiY9TEuenbKlxS+T46t5qwvf+20vT71XcXjHufjPgo0C05HqqaLWZ5dVZMQs/TyEY1OrR7Vh9K7EJ/71vQg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.14.0.tgz", + "integrity": "sha512-cXahfWqCZuu1AOQyrycTmKgZXzBq8v+cqLsEeE929ZokVD35AsTXkLbo6kLN+zVRXAz5kNyaERrfS8yLoZFtWA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-table": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.14.0-rc.4.tgz", - "integrity": "sha512-4KDA6pDUfRoXA2PhM6pS3V4CMdQ3GGP9SbtBSs6rWj71rPt2J4PpzNsRdyNQXpR2iWsoL5h2MVSXf1X1zJWk3A==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.14.0.tgz", + "integrity": "sha512-4ko7jaoH24qLnlwo6jWAuphmkcNL/7RXcDOSgW8aBc0x3nXG2Ufk4PQi0z+k614eDW6+seMZASAsnMx94XhLEQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-tabs": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.14.0-rc.4.tgz", - "integrity": "sha512-cNfwUN1Swj0MUUGTXta8vJ3bicciiLb1Ep2K6DVDVAurgJTsZMwreYTJ7FUhiYNsunB6KYICmBA7tykuYS5fsw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.14.0.tgz", + "integrity": "sha512-m7OEIFK9YD2z7PgD78+U0uFacob/9DqN4nlZXxOkaj/tIxcBbWDXCqRnVBkhkxJKocs6NBYaGi2XHBq9F7/S/w==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-button": "1.14.0-rc.4", - "@umbraco-ui/uui-popover-container": "1.14.0-rc.4", - "@umbraco-ui/uui-symbol-more": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-button": "1.14.0", + "@umbraco-ui/uui-popover-container": "1.14.0", + "@umbraco-ui/uui-symbol-more": "1.14.0" } }, "node_modules/@umbraco-ui/uui-tag": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.14.0-rc.4.tgz", - "integrity": "sha512-N2DyheYdOseh7ep4RUzwSlASVc4p9MsFlBGcsbPjlfT+Iz2rX8t1LF6ZzqrQXewZsW5fflIuO78+xADkjqL5ww==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.14.0.tgz", + "integrity": "sha512-CphycjqzlUnMA+eEgJCCLKtmsCn5ileGPDn5ei427dc5P5wOEU6aGKqeAGMivP6db4UhUMjh3g0xXfQCKbqEaA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-textarea": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.14.0-rc.4.tgz", - "integrity": "sha512-AP1tGEvzmrstw8kxi3kuSTVBTtGC3rqepZ29V1Lw6I7LNr10Oeo8rWpMYjCQYTkgAe/dMqR0IHefYyrdTAvuYg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.14.0.tgz", + "integrity": "sha512-l/hyV78IQn+Akb4UA0AtOTsdYJgCun7eC+i0vaOeNANXrO/B0Dhr2yembO0/mf/u2RxIFeOSsW8GUYixrIxSPw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@umbraco-ui/uui-toast-notification": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.14.0-rc.4.tgz", - "integrity": "sha512-Uq61neNnYzgGqKMnOd5X6aGMIi+5PfwM7E/DcGdAYNWR5t5NgU1uc1/GuxEHPXrWatiC15UT5G2ETiaIPUGX+A==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.14.0.tgz", + "integrity": "sha512-5pb4miAkdgoURoTQGvXQZoUHWIR4tgdUe78hPr2et3xSNw+N0Y/LHlDX1Bo9FBOKEvtFT6YHM0nqOIjW9/RpKw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-button": "1.14.0-rc.4", - "@umbraco-ui/uui-css": "1.14.0-rc.4", - "@umbraco-ui/uui-icon": "1.14.0-rc.4", - "@umbraco-ui/uui-icon-registry-essential": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-button": "1.14.0", + "@umbraco-ui/uui-css": "1.14.0", + "@umbraco-ui/uui-icon": "1.14.0", + "@umbraco-ui/uui-icon-registry-essential": "1.14.0" } }, "node_modules/@umbraco-ui/uui-toast-notification-container": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.14.0-rc.4.tgz", - "integrity": "sha512-H0rQnkt6OlbBCZQ5rAb31WByydZBMqXA8UBKCRzQL4JOqOjO8XSxMag1sZLorul3QPXfL5A40IDbwyVw0/FO1g==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.14.0.tgz", + "integrity": "sha512-5ai853OExMOFrKTrAgvx4OkRNJY8gfIA3UmLBQSVE4E065I0xW4F+L9A3foEU4so2z01OIwvJ53RRk7JriohTg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-toast-notification": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-toast-notification": "1.14.0" } }, "node_modules/@umbraco-ui/uui-toast-notification-layout": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.14.0-rc.4.tgz", - "integrity": "sha512-CCugRovI3Kglzjr4ejkMfWS7KjVWOhewY2kc9yixc8d+UTW5QZsGlSFwovuADi6gRMYMaOkO6hiB0Ejgd0HL6g==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.14.0.tgz", + "integrity": "sha512-8WaiSNLB8NoKJMRQCqFh+KkhjOStXcJ+yLJJR/AM6HF6Pc0tYl+R3zM4LY9WJjQQEOXENcTUPMURJSwpJ2fsGA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-css": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-css": "1.14.0" } }, "node_modules/@umbraco-ui/uui-toggle": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.14.0-rc.4.tgz", - "integrity": "sha512-W7dvUAgHh1gRxtSfMh6BwmIUSLlH9ZajesFkfHVqy0ZtMfs+d3Glnw+MIETRN/QuqBy/wl0bOxPqcujyc+8iQw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.14.0.tgz", + "integrity": "sha512-s8//Y2LAqDQ3h4C3PA9yJcVXF2H6gnv2NzMZ22KotJQT9+yhhR3UrOlndOZKkWqKtDxwSLEp9EmyITgDdEoT3A==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4", - "@umbraco-ui/uui-boolean-input": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-boolean-input": "1.14.0" } }, "node_modules/@umbraco-ui/uui-visually-hidden": { - "version": "1.14.0-rc.4", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-visually-hidden/-/uui-visually-hidden-1.14.0-rc.4.tgz", - "integrity": "sha512-0Zhi67ZRUMCgPpiS44+mYlXey1apzib8B0YMK8Dgy2Gu3HpVqsFl+yPUWTOEfUcRFBzQnZqewEudo2OYhewtJw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-visually-hidden/-/uui-visually-hidden-1.14.0.tgz", + "integrity": "sha512-wGbMiw+UuMYayMDBau5dD2B3HX2tFPlnOftvD9Z+FNKnGnU5e/V+QInCYy7FlywBQ5fDpfKcXseud/kONGRmsA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0-rc.4" + "@umbraco-ui/uui-base": "1.14.0" } }, "node_modules/@vitest/expect": { diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index d6da19b510..436fe46859 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -215,8 +215,8 @@ "@tiptap/extension-underline": "2.11.7", "@tiptap/pm": "2.11.7", "@tiptap/starter-kit": "2.11.7", - "@umbraco-ui/uui": "1.14.0-rc.4", - "@umbraco-ui/uui-css": "1.14.0-rc.4", + "@umbraco-ui/uui": "1.14.0", + "@umbraco-ui/uui-css": "1.14.0", "dompurify": "^3.2.5", "element-internals-polyfill": "^3.0.2", "lit": "^3.3.0", @@ -288,4 +288,4 @@ "access": "public", "registry": "https://registry.npmjs.org/" } -} +} \ No newline at end of file From 9a96ebf81215d184d6c4a435150de6bd0db61ca6 Mon Sep 17 00:00:00 2001 From: Laura Neto <12862535+lauraneto@users.noreply.github.com> Date: Thu, 12 Jun 2025 07:03:49 +0200 Subject: [PATCH 16/82] Replace keys in Rich Text Editor blocks on clone operations (#19526) * Regenerate keys in RTE blocks on clone operations This was already present for BlockList and BlockGrid, but not Blocks in RTE. * Small adjustment from code review --- .../UmbracoBuilder.CoreServices.cs | 3 +++ .../RichTextPropertyNotificationHandler.cs | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyNotificationHandler.cs diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 0e4e6fdb42..56fdface93 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -352,6 +352,9 @@ public static partial class UmbracoBuilderExtensions .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler() diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyNotificationHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyNotificationHandler.cs new file mode 100644 index 0000000000..5f07c8342d --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyNotificationHandler.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Models.Blocks; + +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// A handler for Rich Text editors used to bind to notifications. +/// +public class RichTextPropertyNotificationHandler : BlockEditorPropertyNotificationHandlerBase +{ + public RichTextPropertyNotificationHandler(ILogger logger) + : base(logger) + { + } + + protected override string EditorAlias => Constants.PropertyEditors.Aliases.RichText; +} From e89e18f5bae5903b55faafc6389e650c0c0e81db Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 12 Jun 2025 12:59:11 +0200 Subject: [PATCH 17/82] V16: Item and Detail Base Repository should use correct typings for return types (#19447) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: add a catcher to most `asPromise` for stores to prevent cascading errors * fix: remove conditional instances - they should be able to be undefined * fix: check for missing store and extract UmbProblemDetails * fix: only append data if no error * fix: adds error handling to missing stores and to extract the ProblemDetails object * revert commit * fix: ignore errors completely instead of unsetting stores * revert commit * chore: cleanup imports * fix: do not unset store * stop observation in a proper way * stop observation of for document-user-permissions * check for manager twice * save action * save action optional * fix: ensure the right types are used for base stores --------- Co-authored-by: Niels Lyngsø --- .../content-type-structure-manager.class.ts | 2 +- .../detail/detail-repository-base.ts | 4 +-- .../read/read-detail-repository.interface.ts | 2 +- .../repository/item/item-repository-base.ts | 14 +++----- .../item/item-repository.interface.ts | 8 ++--- .../repository/repository-items.manager.ts | 3 +- .../src/packages/core/repository/types.ts | 9 +++-- .../core/tree/data/tree-repository-base.ts | 33 +++++++------------ .../tree/data/tree-repository.interface.ts | 32 ++++++++---------- 9 files changed, 45 insertions(+), 62 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/structure/content-type-structure-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/structure/content-type-structure-manager.class.ts index 1a71834059..eed0caeedb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/structure/content-type-structure-manager.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/structure/content-type-structure-manager.class.ts @@ -175,7 +175,7 @@ export class UmbContentTypeStructureManager< * @param {string} unique - The unique of the ContentType to load. * @returns {Promise} - Promise resolved */ - public async loadType(unique: string): Promise> { + public async loadType(unique: string): Promise> { if (this.#ownerContentTypeUnique === unique) { // Its the same, but we do not know if its done loading jet, so we will wait for the load promise to finish. [NL] await this.#init; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/detail-repository-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/detail-repository-base.ts index 418d95f0f4..af37a4fa1b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/detail-repository-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/detail-repository-base.ts @@ -60,7 +60,7 @@ export abstract class UmbDetailRepositoryBase< * @returns {*} * @memberof UmbDetailRepositoryBase */ - async requestByUnique(unique: string): Promise> { + async requestByUnique(unique: string): Promise> { if (!unique) throw new Error('Unique is missing'); await this.#init; @@ -73,7 +73,7 @@ export abstract class UmbDetailRepositoryBase< return { data, error, - asObservable: () => this.#detailStore!.byUnique(unique), + asObservable: () => this.#detailStore?.byUnique(unique), }; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/read/read-detail-repository.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/read/read-detail-repository.interface.ts index 702dfb9a1f..a19f530b19 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/read/read-detail-repository.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/read/read-detail-repository.interface.ts @@ -3,6 +3,6 @@ import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; export interface UmbReadDetailRepository extends UmbApi { - requestByUnique(unique: string): Promise>; + requestByUnique(unique: string): Promise>; byUnique(unique: string): Promise>; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository-base.ts index 1f502778cd..15134e562e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository-base.ts @@ -41,22 +41,18 @@ export class UmbItemRepositoryBase try { await this._init; } catch { - return {}; + return { + asObservable: () => undefined, + }; } const { data, error } = await this.#itemSource.getItems(uniques); - if (!this._itemStore) { - // If store is gone, then we are most likely in a disassembled state. - return {}; - } - if (data) { - this._itemStore.appendItems(data); + this._itemStore?.appendItems(data); } - // TODO: Fix the type of error, it should be UmbApiError, but currently it is any. - return { data, error: error as any, asObservable: () => this._itemStore!.items(uniques) }; + return { data, error, asObservable: () => this._itemStore?.items(uniques) }; } /** diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository.interface.ts index 1c4bcfe070..71e25909a2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository.interface.ts @@ -1,12 +1,8 @@ +import type { UmbRepositoryResponseWithAsObservable } from '../types.js'; import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; -import type { UmbProblemDetails } from '@umbraco-cms/backoffice/resources'; export interface UmbItemRepository extends UmbApi { - requestItems: (uniques: string[]) => Promise<{ - data?: Array | undefined; - error?: UmbProblemDetails | undefined; - asObservable?: () => Observable>; - }>; + requestItems: (uniques: string[]) => Promise>; items: (uniques: string[]) => Promise> | undefined>; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-items.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-items.manager.ts index d5fac8fabb..bc3536a3e9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-items.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-items.manager.ts @@ -250,7 +250,8 @@ export class UmbRepositoryItemsManager exte } } - #sortByUniques(data: Array): Array { + #sortByUniques(data?: Array): Array { + if (!data) return []; const uniques = this.getUniques(); return [...data].sort((a, b) => { const aIndex = uniques.indexOf(this.#getUnique(a) ?? ''); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/types.ts index c75d422538..32d7a55020 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/types.ts @@ -11,8 +11,13 @@ export interface UmbRepositoryResponse extends UmbDataSourceResponse {} // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface UmbRepositoryErrorResponse extends UmbDataSourceErrorResponse {} -export interface UmbRepositoryResponseWithAsObservable extends UmbRepositoryResponse { - asObservable: () => Observable; +/** + * Interface for a repository that can return a paged model. + * @template T - The type of items in the paged model. + * @template T$ - The type of items returned by the asObservable method, defaults to T. You should only use this if you want to return a different type from the asObservable method. + */ +export interface UmbRepositoryResponseWithAsObservable extends UmbRepositoryResponse { + asObservable: () => Observable | undefined; } export type * from './data-mapper/mapping/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository-base.ts index e9b4b8ba58..84d8fac131 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository-base.ts @@ -7,11 +7,10 @@ import type { UmbTreeChildrenOfRequestArgs, UmbTreeRootItemsRequestArgs, } from './types.js'; -import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository'; +import { UmbRepositoryBase, type UmbRepositoryResponse } from '@umbraco-cms/backoffice/repository'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; import type { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; -import type { UmbProblemDetails } from '@umbraco-cms/backoffice/resources'; import { of } from '@umbraco-cms/backoffice/external/rxjs'; /** @@ -74,7 +73,7 @@ export abstract class UmbTreeRepositoryBase< * @returns {*} * @memberof UmbTreeRepositoryBase */ - abstract requestTreeRoot(): Promise<{ data?: TreeRootType; error?: UmbProblemDetails }>; + abstract requestTreeRoot(): Promise>; /** * Requests root items of a tree @@ -89,15 +88,16 @@ export abstract class UmbTreeRepositoryBase< if (!this._treeStore) { // If the tree store is not available, then we most likely are in a destructed setting. - return {}; + return { + asObservable: () => undefined, + }; } if (data) { this._treeStore.appendItems(data.items); } - // TODO: Fix the type of error, it should be UmbApiError, but currently it is any. - return { data, error: error as any, asObservable: () => this._treeStore!.rootItems }; + return { data, error, asObservable: () => this._treeStore?.rootItems }; } /** @@ -117,15 +117,16 @@ export abstract class UmbTreeRepositoryBase< if (!this._treeStore) { // If the tree store is not available, then we most likely are in a destructed setting. - return {}; + return { + asObservable: () => undefined, + }; } if (data) { this._treeStore.appendItems(data.items); } - // TODO: Fix the type of error, it should be UmbApiError, but currently it is any. - return { data, error: error as any, asObservable: () => this._treeStore!.childrenOf(args.parent.unique) }; + return { data, error, asObservable: () => this._treeStore?.childrenOf(args.parent.unique) }; } /** @@ -153,12 +154,7 @@ export abstract class UmbTreeRepositoryBase< async rootTreeItems() { await this._init; - if (!this._treeStore) { - // If the tree store is not available, then we most likely are in a destructed setting. - return of([]); - } - - return this._treeStore.rootItems; + return this._treeStore?.rootItems ?? of([]); } /** @@ -171,11 +167,6 @@ export abstract class UmbTreeRepositoryBase< if (parentUnique === undefined) throw new Error('Parent unique is missing'); await this._init; - if (!this._treeStore) { - // If the tree store is not available, then we most likely are in a destructed setting. - return of([]); - } - - return this._treeStore.childrenOf(parentUnique); + return this._treeStore?.childrenOf(parentUnique) ?? of([]); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository.interface.ts index afdb7226b2..221db148ba 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository.interface.ts @@ -4,10 +4,13 @@ import type { UmbTreeAncestorsOfRequestArgs, UmbTreeRootItemsRequestArgs, } from './types.js'; -import type { UmbPagedModel } from '@umbraco-cms/backoffice/repository'; +import type { + UmbPagedModel, + UmbRepositoryResponse, + UmbRepositoryResponseWithAsObservable, +} from '@umbraco-cms/backoffice/repository'; import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; -import type { UmbProblemDetails } from '@umbraco-cms/backoffice/resources'; /** * Interface for a tree repository. @@ -27,41 +30,32 @@ export interface UmbTreeRepository< * Requests the root of the tree. * @memberof UmbTreeRepository */ - requestTreeRoot: () => Promise<{ - data?: TreeRootType; - error?: UmbProblemDetails; - }>; + requestTreeRoot: () => Promise>; /** * Requests the root items of the tree. * @param {UmbTreeRootItemsRequestArgs} args * @memberof UmbTreeRepository */ - requestTreeRootItems: (args: TreeRootItemsRequestArgsType) => Promise<{ - data?: UmbPagedModel; - error?: UmbProblemDetails; - asObservable?: () => Observable; - }>; + requestTreeRootItems: ( + args: TreeRootItemsRequestArgsType, + ) => Promise, TreeItemType[]>>; /** * Requests the children of the given parent item. * @param {UmbTreeChildrenOfRequestArgs} args * @memberof UmbTreeRepository */ - requestTreeItemsOf: (args: TreeChildrenOfRequestArgsType) => Promise<{ - data?: UmbPagedModel; - error?: UmbProblemDetails; - asObservable?: () => Observable; - }>; + requestTreeItemsOf: ( + args: TreeChildrenOfRequestArgsType, + ) => Promise, TreeItemType[]>>; /** * Requests the ancestors of the given item. * @param {UmbTreeAncestorsOfRequestArgs} args * @memberof UmbTreeRepository */ - requestTreeItemAncestors: ( - args: TreeAncestorsOfRequestArgsType, - ) => Promise<{ data?: TreeItemType[]; error?: UmbProblemDetails; asObservable?: () => Observable }>; + requestTreeItemAncestors: (args: TreeAncestorsOfRequestArgsType) => Promise>; /** * Returns an observable of the root items of the tree. From cd72dbe4ea95bb9d200bc193c34e276c507fa50a Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 12 Jun 2025 13:24:11 +0200 Subject: [PATCH 18/82] V16: Retry requests after timeout (#19495) * feat: fix a small-ish nitpick where extensions would reload after login this could potentially try to re-register all private extensions after each auth signal, which is being prevented anyway because of duplicate aliases, but still nice to remove and not have to listen to * feat: align login UI extension load with backoffice, i.e. wait for external load before registering core extensions * build(deps): bump @hey-api to newest and re-generate client * chore: adds extra error logging * feat: adds retry logic to the api interceptor * feat: warn about incomplete actions * fix: the body was already plain text, but we need to ensure the headers say so as well * feat: warns the user when actions could not be completed * build(deps): update @hey-api/client-fetch * chore: generate new api * feat: simplify error handling to just UmbApiError and UmbCancelError * feat: moves error notifications from interceptors into tryExecute, so you more easily can opt out of it and everything is gathered in one place * feat: recreate responses with correct 'status' and 'statusText' * build: stop dotnet processes after debug session * feat: extrapolate common logic into helper method to create responses * feat: returns a UmbProblemDetails like object on interceptors to be handled by tryExecute * chore: deprecates duplicate, outdated UmbProblemDetails interface and type guard * feat: uses the 'title' of the problem details object to convey the main message * chore: 401 and 403 uses their own interceptors * feat: show no notification if 401 * feat: uses the real request method and url (instead of the template placeholders) to tell the user what did not succeed * feat: retry requests with no timeout/race * feat: throttle and delay signals and disallow them from being updated from the outside * chore: adds more logging to timeouts * chore: optimise imports * test: ignores any test files left in node_modules folder * feat: uses auditTime to wait a bit before showing the timeout screen * feat: adds 404 handling to error interceptor * chore: cleans up after response modification * feat: preserve only a few headers this mimicks the v15 behavior * feat: lets the UI handle 404 errors instead of notifying directly * test: uses create action menu option instead to find the correct locator, and skips a seemingly unnecessary timeout --- .vscode/launch.json | 2 + .vscode/tasks.json | 159 +-- src/Umbraco.Web.UI.Client/package-lock.json | 64 +- .../src/apps/app/app-auth.controller.ts | 1 + .../src/apps/backoffice/backoffice.element.ts | 23 +- .../src/external/rxjs/index.ts | 3 + .../src/packages/core/auth/auth.context.ts | 32 +- .../src/packages/core/backend-api/sdk.gen.ts | 976 +++++++++--------- .../packages/core/backend-api/types.gen.ts | 8 +- .../src/packages/core/http-client/index.ts | 1 + .../src/packages/core/openapi-ts.config.js | 4 +- .../src/packages/core/package.json | 6 +- .../resources/api-interceptor.controller.ts | 293 ++++-- .../core/resources/resource.controller.ts | 19 +- .../try-execute/try-execute.controller.ts | 48 +- .../src/packages/core/resources/types.ts | 4 +- .../web-test-runner.config.mjs | 2 +- src/Umbraco.Web.UI.Login/src/auth.element.ts | 425 ++++---- .../slim-backoffice-initializer.ts | 25 +- src/Umbraco.Web.UI.Login/src/types.ts | 3 + .../src/utils/is-problem-details.function.ts | 3 + .../tests/DefaultConfig/Media/Media.spec.ts | 4 +- 22 files changed, 1158 insertions(+), 947 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 5f4eb90345..ef4677989e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -62,6 +62,7 @@ "cwd": "${workspaceFolder}/src/Umbraco.Web.UI", "stopAtEntry": false, "requireExactSource": false, + "postDebugTask": "kill-umbraco-web-ui", // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser "serverReadyAction": { "action": "openExternally", @@ -96,6 +97,7 @@ "stopAtEntry": false, "requireExactSource": false, "checkForDevCert": true, + "postDebugTask": "kill-umbraco-web-ui", "env": { "ASPNETCORE_ENVIRONMENT": "Development", "ASPNETCORE_URLS": "https://localhost:44339", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f143ad431c..ed79d86c9a 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,76 +1,87 @@ { - "version": "2.0.0", - "tasks": [ - { - "label": "Build", - "detail": "Builds the client and SLN", - "promptOnClose": true, - "group": "build", - "dependsOn": [ - "Client Build", - "Dotnet build" - ], - "problemMatcher": [] - }, - { - "label": "Client Install", - "detail": "install npm for Umbraco.Web.UI.Client", - "promptOnClose": true, - "type": "npm", - "script": "install", - "path": "src/Umbraco.Web.UI.Client/", - "problemMatcher": [] - }, - { - "label": "Client Build", - "detail": "runs npm run build for Umbraco.Web.UI.Client", - "promptOnClose": true, - "group": "build", - "type": "npm", - "script": "build:for:cms", - "path": "src/Umbraco.Web.UI.Client/", - "problemMatcher": [] - }, - { - "label": "Client Watch", - "detail": "runs npm run dev for Umbraco.Web.UI.Client", - "promptOnClose": true, - "group": "build", - "type": "npm", - "script": "dev", - "path": "src/Umbraco.Web.UI.Client/", - "problemMatcher": [] - }, - { - "label": "Dotnet build", - "detail": "Dotnet build of SLN", - "promptOnClose": true, - "group": "build", - "command": "dotnet", - "type": "process", - "args": [ - "build", - "${workspaceFolder}/umbraco.sln", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "Dotnet watch", - "detail": "Dotnet run and watch of Web.UI", - "promptOnClose": true, - "command": "dotnet", - "type": "process", - "args": [ - "watch", - "run", - "--project", - "${workspaceFolder}/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - } - ] + "version": "2.0.0", + "tasks": [ + { + "label": "Build", + "detail": "Builds the client and SLN", + "promptOnClose": true, + "group": "build", + "dependsOn": ["Client Build", "Dotnet build"], + "problemMatcher": [] + }, + { + "label": "Client Install", + "detail": "install npm for Umbraco.Web.UI.Client", + "promptOnClose": true, + "type": "npm", + "script": "install", + "path": "src/Umbraco.Web.UI.Client/", + "problemMatcher": [] + }, + { + "label": "Client Build", + "detail": "runs npm run build for Umbraco.Web.UI.Client", + "promptOnClose": true, + "group": "build", + "type": "npm", + "script": "build:for:cms", + "path": "src/Umbraco.Web.UI.Client/", + "problemMatcher": [] + }, + { + "label": "Client Watch", + "detail": "runs npm run dev for Umbraco.Web.UI.Client", + "promptOnClose": true, + "group": "build", + "type": "npm", + "script": "dev", + "path": "src/Umbraco.Web.UI.Client/", + "problemMatcher": [] + }, + { + "label": "Dotnet build", + "detail": "Dotnet build of SLN", + "promptOnClose": true, + "group": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/umbraco.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "Dotnet watch", + "detail": "Dotnet run and watch of Web.UI", + "promptOnClose": true, + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "kill-umbraco-web-ui", + "type": "shell", + "problemMatcher": [], + "osx": { + "command": "pkill -f Umbraco.Web.UI" + }, + "linux": { + "command": "pkill -f Umbraco.Web.UI" + }, + "windows": { + "command": "taskkill /IM Umbraco.Web.UI.exe /F" + } + } + ] } diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index f9eef1b0bf..0daaaf60ba 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -1039,22 +1039,10 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@hey-api/client-fetch": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.10.0.tgz", - "integrity": "sha512-C7vzj4t52qPiHCqjn1l8cRTI2p4pZCd7ViLtJDTHr5ZwI4sWOYC1tmv6bd529qqY6HFFbhGCz4TAZSwKAMJncg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/hey-api" - }, - "peerDependencies": { - "@hey-api/openapi-ts": "< 2" - } - }, "node_modules/@hey-api/json-schema-ref-parser": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@hey-api/json-schema-ref-parser/-/json-schema-ref-parser-1.0.5.tgz", - "integrity": "sha512-bWUV9ICwvU5I3YKVZqWIUXFC2SIXznUi/u+LqurJx6ILiyImfZD5+g/lj3w4EiyXxmjqyaxptzUz/1IgK3vVtw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@hey-api/json-schema-ref-parser/-/json-schema-ref-parser-1.0.6.tgz", + "integrity": "sha512-yktiFZoWPtEW8QKS65eqKwA5MTKp88CyiL8q72WynrBs/73SAaxlSWlA2zW/DZlywZ5hX1OYzrCC0wFdvO9c2w==", "dev": true, "license": "MIT", "dependencies": { @@ -1071,14 +1059,16 @@ } }, "node_modules/@hey-api/openapi-ts": { - "version": "0.66.6", - "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.66.6.tgz", - "integrity": "sha512-EmZHVqfHuGNoyBDPcL+3vGHLb/qEbjIix3dnQ/CzfZQ+xm4vnOecpR7JaaaR9u2W8Ldeyqnk7NwmEqOBgkgG4Q==", + "version": "0.71.0", + "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.71.0.tgz", + "integrity": "sha512-4uE0TwEyoi0QgqJssfj7W3CMeomEYnxb6U5k8FegAIcuHD46kU7EYZbcJz5DV5vOGZSecmxc6h03YB31+Xm3gw==", "dev": true, "license": "MIT", "dependencies": { - "@hey-api/json-schema-ref-parser": "1.0.5", + "@hey-api/json-schema-ref-parser": "1.0.6", + "ansi-colors": "4.1.3", "c12": "2.0.1", + "color-support": "1.1.3", "commander": "13.0.0", "handlebars": "4.7.8" }, @@ -6078,6 +6068,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -7210,6 +7210,16 @@ "dev": true, "license": "MIT" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", @@ -17576,13 +17586,25 @@ "src/packages/core": { "name": "@umbraco-backoffice/core", "dependencies": { - "@hey-api/client-fetch": "^0.10.0", + "@hey-api/client-fetch": "^0.12.0", "@types/diff": "^7.0.2", "diff": "^7.0.0", "uuid": "^11.1.0" }, "devDependencies": { - "@hey-api/openapi-ts": "^0.66.6" + "@hey-api/openapi-ts": "^0.71.0" + } + }, + "src/packages/core/node_modules/@hey-api/client-fetch": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.12.0.tgz", + "integrity": "sha512-/iZ6dhs39N0kjzCa9tlNeLNufVUd8zzv/wI1ewQt5AEHaVuDnAxvuQT+fRIPYkfIWKR3gVZKRp5mcCo29voYzQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/hey-api" + }, + "peerDependencies": { + "@hey-api/openapi-ts": "< 2" } }, "src/packages/data-type": { diff --git a/src/Umbraco.Web.UI.Client/src/apps/app/app-auth.controller.ts b/src/Umbraco.Web.UI.Client/src/apps/app/app-auth.controller.ts index 69cfb190f9..a67aa2a069 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/app/app-auth.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/app/app-auth.controller.ts @@ -21,6 +21,7 @@ export class UmbAppAuthController extends UmbControllerBase { this.observe( context?.timeoutSignal, () => { + console.log('[UmbAppAuthController] Authorization timed out, starting authorization flow'); this.makeAuthorizationRequest('timedOut'); }, '_authState', diff --git a/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts b/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts index 1a6523e7b0..2c0b5fd876 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts @@ -66,22 +66,25 @@ export class UmbBackofficeElement extends UmbLitElement { new UmbBackofficeEntryPointExtensionInitializer(this, umbExtensionsRegistry); new UmbEntryPointExtensionInitializer(this, umbExtensionsRegistry); + } - // So far local packages are this simple to registerer, so no need for a manager to do that: + override async firstUpdated() { + await this.#extensionsAfterAuth(); + + // So far local packages are this simple to register, so no need for a manager to do that: CORE_PACKAGES.forEach(async (packageImport) => { const packageModule = await packageImport; umbExtensionsRegistry.registerMany(packageModule.extensions); }); + } - const serverExtensions = new UmbServerExtensionRegistrator(this, umbExtensionsRegistry); - - // TODO: We need to ensure this request is called every time the user logs in, but this should be done somewhere across the app and not here [JOV] - this.consumeContext(UMB_AUTH_CONTEXT, (authContext) => { - this.observe(authContext?.isAuthorized, (isAuthorized) => { - if (!isAuthorized) return; - serverExtensions.registerPrivateExtensions(); - }); - }); + async #extensionsAfterAuth() { + const authContext = await this.getContext(UMB_AUTH_CONTEXT, { preventTimeout: true }); + if (!authContext) { + throw new Error('UmbBackofficeElement requires the UMB_AUTH_CONTEXT to be set.'); + } + await this.observe(authContext.isAuthorized).asPromise(); + await new UmbServerExtensionRegistrator(this, umbExtensionsRegistry).registerPrivateExtensions(); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/external/rxjs/index.ts b/src/Umbraco.Web.UI.Client/src/external/rxjs/index.ts index 36d1337320..1e71570cdb 100644 --- a/src/Umbraco.Web.UI.Client/src/external/rxjs/index.ts +++ b/src/Umbraco.Web.UI.Client/src/external/rxjs/index.ts @@ -21,4 +21,7 @@ export { switchMap, takeUntil, tap, + delay, + throttleTime, + auditTime, } from 'rxjs'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth.context.ts index 58438b221e..05646acc2c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth.context.ts @@ -6,7 +6,16 @@ import { UMB_STORAGE_TOKEN_RESPONSE_NAME } from './constants.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api'; -import { ReplaySubject, Subject, firstValueFrom, switchMap } from '@umbraco-cms/backoffice/external/rxjs'; +import { + ReplaySubject, + Subject, + firstValueFrom, + switchMap, + distinctUntilChanged, + throttleTime, + auditTime, +} from '@umbraco-cms/backoffice/external/rxjs'; +import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; import type { UmbBackofficeExtensionRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { umbHttpClient } from '@umbraco-cms/backoffice/http-client'; @@ -33,20 +42,29 @@ export class UmbAuthContext extends UmbContextBase { * Observable that emits true if the user is authorized, otherwise false. * @remark It will only emit when the authorization state changes. */ - readonly isAuthorized = this.#isAuthorized.asObservable(); + readonly isAuthorized = this.#isAuthorized.asObservable().pipe(distinctUntilChanged()); /** * Observable that acts as a signal and emits when the user has timed out, i.e. the token has expired. * This can be used to show a timeout message to the user. - * @remark It can emit multiple times if more than one request is made after the token has expired. + * @remark It will emit once per second, so it can be used to trigger UI updates or other actions when the user has timed out. */ - readonly timeoutSignal = this.#isTimeout.asObservable(); + readonly timeoutSignal = this.#isTimeout.asObservable().pipe( + // Audit the timeout signal to ensure that it waits for 1s before allowing another emission, which prevents rapid firing of the signal. + // This is useful to prevent the UI from being flooded with timeout events. + auditTime(1000), + ); /** * Observable that acts as a signal for when the authorization state changes. + * @remark It will emit once per second, so it can be used to trigger UI updates or other actions when the authorization state changes. + * @returns {Subject} An observable that emits when the authorization state changes. */ - get authorizationSignal() { - return this.#authFlow.authorizationSignal; + get authorizationSignal(): Observable { + return this.#authFlow.authorizationSignal.asObservable().pipe( + // Throttle the signal to ensure that it emits once, then waits for 1s before allowing another emission. + throttleTime(1000), + ); } constructor(host: UmbControllerHost, serverUrl: string, backofficePath: string, isBypassed: boolean) { @@ -89,7 +107,7 @@ export class UmbAuthContext extends UmbContextBase { // Refresh the local storage state into memory await this.setInitialState(); // Let any auth listeners (such as the auth modal) know that the auth state has changed - this.authorizationSignal.next(); + this.#authFlow.authorizationSignal.next(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/sdk.gen.ts b/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/sdk.gen.ts index bc37142f89..a954f24ad2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/sdk.gen.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import { type Options as ClientOptions, type TDataShape, type Client, formDataBodySerializer } from '@hey-api/client-fetch'; -import type { GetCultureData, GetCultureResponse, PostDataTypeData, PostDataTypeError, DeleteDataTypeByIdData, DeleteDataTypeByIdError, GetDataTypeByIdData, GetDataTypeByIdResponse, GetDataTypeByIdError, PutDataTypeByIdData, PutDataTypeByIdError, PostDataTypeByIdCopyData, PostDataTypeByIdCopyError, GetDataTypeByIdIsUsedData, GetDataTypeByIdIsUsedResponse, GetDataTypeByIdIsUsedError, PutDataTypeByIdMoveData, PutDataTypeByIdMoveError, GetDataTypeByIdReferencedByData, GetDataTypeByIdReferencedByResponse, GetDataTypeByIdReferencesData, GetDataTypeByIdReferencesResponse, GetDataTypeByIdReferencesError, GetDataTypeConfigurationData, GetDataTypeConfigurationResponse, PostDataTypeFolderData, PostDataTypeFolderError, DeleteDataTypeFolderByIdData, DeleteDataTypeFolderByIdError, GetDataTypeFolderByIdData, GetDataTypeFolderByIdResponse, GetDataTypeFolderByIdError, PutDataTypeFolderByIdData, PutDataTypeFolderByIdError, GetFilterDataTypeData, GetFilterDataTypeResponse, GetItemDataTypeData, GetItemDataTypeResponse, GetItemDataTypeSearchData, GetItemDataTypeSearchResponse, GetTreeDataTypeAncestorsData, GetTreeDataTypeAncestorsResponse, GetTreeDataTypeChildrenData, GetTreeDataTypeChildrenResponse, GetTreeDataTypeRootData, GetTreeDataTypeRootResponse, GetDictionaryData, GetDictionaryResponse, PostDictionaryData, PostDictionaryError, DeleteDictionaryByIdData, DeleteDictionaryByIdError, GetDictionaryByIdData, GetDictionaryByIdResponse, GetDictionaryByIdError, PutDictionaryByIdData, PutDictionaryByIdError, GetDictionaryByIdExportData, GetDictionaryByIdExportResponse, GetDictionaryByIdExportError, PutDictionaryByIdMoveData, PutDictionaryByIdMoveError, PostDictionaryImportData, PostDictionaryImportError, GetItemDictionaryData, GetItemDictionaryResponse, GetTreeDictionaryAncestorsData, GetTreeDictionaryAncestorsResponse, GetTreeDictionaryChildrenData, GetTreeDictionaryChildrenResponse, GetTreeDictionaryRootData, GetTreeDictionaryRootResponse, PostDocumentBlueprintData, PostDocumentBlueprintError, DeleteDocumentBlueprintByIdData, DeleteDocumentBlueprintByIdError, GetDocumentBlueprintByIdData, GetDocumentBlueprintByIdResponse, GetDocumentBlueprintByIdError, PutDocumentBlueprintByIdData, PutDocumentBlueprintByIdError, PutDocumentBlueprintByIdMoveData, PutDocumentBlueprintByIdMoveError, GetDocumentBlueprintByIdScaffoldData, GetDocumentBlueprintByIdScaffoldResponse, GetDocumentBlueprintByIdScaffoldError, PostDocumentBlueprintFolderData, PostDocumentBlueprintFolderError, DeleteDocumentBlueprintFolderByIdData, DeleteDocumentBlueprintFolderByIdError, GetDocumentBlueprintFolderByIdData, GetDocumentBlueprintFolderByIdResponse, GetDocumentBlueprintFolderByIdError, PutDocumentBlueprintFolderByIdData, PutDocumentBlueprintFolderByIdError, PostDocumentBlueprintFromDocumentData, PostDocumentBlueprintFromDocumentError, GetItemDocumentBlueprintData, GetItemDocumentBlueprintResponse, GetTreeDocumentBlueprintAncestorsData, GetTreeDocumentBlueprintAncestorsResponse, GetTreeDocumentBlueprintChildrenData, GetTreeDocumentBlueprintChildrenResponse, GetTreeDocumentBlueprintRootData, GetTreeDocumentBlueprintRootResponse, PostDocumentTypeData, PostDocumentTypeError, DeleteDocumentTypeByIdData, DeleteDocumentTypeByIdError, GetDocumentTypeByIdData, GetDocumentTypeByIdResponse, GetDocumentTypeByIdError, PutDocumentTypeByIdData, PutDocumentTypeByIdError, GetDocumentTypeByIdAllowedChildrenData, GetDocumentTypeByIdAllowedChildrenResponse, GetDocumentTypeByIdAllowedChildrenError, GetDocumentTypeByIdBlueprintData, GetDocumentTypeByIdBlueprintResponse, GetDocumentTypeByIdBlueprintError, GetDocumentTypeByIdCompositionReferencesData, GetDocumentTypeByIdCompositionReferencesResponse, GetDocumentTypeByIdCompositionReferencesError, PostDocumentTypeByIdCopyData, PostDocumentTypeByIdCopyError, GetDocumentTypeByIdExportData, GetDocumentTypeByIdExportResponse, GetDocumentTypeByIdExportError, PutDocumentTypeByIdImportData, PutDocumentTypeByIdImportError, PutDocumentTypeByIdMoveData, PutDocumentTypeByIdMoveError, GetDocumentTypeAllowedAtRootData, GetDocumentTypeAllowedAtRootResponse, PostDocumentTypeAvailableCompositionsData, PostDocumentTypeAvailableCompositionsResponse, GetDocumentTypeConfigurationData, GetDocumentTypeConfigurationResponse, PostDocumentTypeFolderData, PostDocumentTypeFolderError, DeleteDocumentTypeFolderByIdData, DeleteDocumentTypeFolderByIdError, GetDocumentTypeFolderByIdData, GetDocumentTypeFolderByIdResponse, GetDocumentTypeFolderByIdError, PutDocumentTypeFolderByIdData, PutDocumentTypeFolderByIdError, PostDocumentTypeImportData, PostDocumentTypeImportError, GetItemDocumentTypeData, GetItemDocumentTypeResponse, GetItemDocumentTypeSearchData, GetItemDocumentTypeSearchResponse, GetTreeDocumentTypeAncestorsData, GetTreeDocumentTypeAncestorsResponse, GetTreeDocumentTypeChildrenData, GetTreeDocumentTypeChildrenResponse, GetTreeDocumentTypeRootData, GetTreeDocumentTypeRootResponse, GetDocumentVersionData, GetDocumentVersionResponse, GetDocumentVersionError, GetDocumentVersionByIdData, GetDocumentVersionByIdResponse, GetDocumentVersionByIdError, PutDocumentVersionByIdPreventCleanupData, PutDocumentVersionByIdPreventCleanupError, PostDocumentVersionByIdRollbackData, PostDocumentVersionByIdRollbackError, GetCollectionDocumentByIdData, GetCollectionDocumentByIdResponse, GetCollectionDocumentByIdError, PostDocumentData, PostDocumentError, DeleteDocumentByIdData, DeleteDocumentByIdError, GetDocumentByIdData, GetDocumentByIdResponse, GetDocumentByIdError, PutDocumentByIdData, PutDocumentByIdError, GetDocumentByIdAuditLogData, GetDocumentByIdAuditLogResponse, PostDocumentByIdCopyData, PostDocumentByIdCopyError, GetDocumentByIdDomainsData, GetDocumentByIdDomainsResponse, GetDocumentByIdDomainsError, PutDocumentByIdDomainsData, PutDocumentByIdDomainsError, PutDocumentByIdMoveData, PutDocumentByIdMoveError, PutDocumentByIdMoveToRecycleBinData, PutDocumentByIdMoveToRecycleBinError, GetDocumentByIdNotificationsData, GetDocumentByIdNotificationsResponse, GetDocumentByIdNotificationsError, PutDocumentByIdNotificationsData, PutDocumentByIdNotificationsError, DeleteDocumentByIdPublicAccessData, DeleteDocumentByIdPublicAccessError, GetDocumentByIdPublicAccessData, GetDocumentByIdPublicAccessResponse, GetDocumentByIdPublicAccessError, PostDocumentByIdPublicAccessData, PostDocumentByIdPublicAccessError, PutDocumentByIdPublicAccessData, PutDocumentByIdPublicAccessError, PutDocumentByIdPublishData, PutDocumentByIdPublishError, PutDocumentByIdPublishWithDescendantsData, PutDocumentByIdPublishWithDescendantsResponse, PutDocumentByIdPublishWithDescendantsError, GetDocumentByIdPublishWithDescendantsResultByTaskIdData, GetDocumentByIdPublishWithDescendantsResultByTaskIdResponse, GetDocumentByIdPublishWithDescendantsResultByTaskIdError, GetDocumentByIdPublishedData, GetDocumentByIdPublishedResponse, GetDocumentByIdPublishedError, GetDocumentByIdReferencedByData, GetDocumentByIdReferencedByResponse, GetDocumentByIdReferencedDescendantsData, GetDocumentByIdReferencedDescendantsResponse, PutDocumentByIdUnpublishData, PutDocumentByIdUnpublishError, PutUmbracoManagementApiV11DocumentByIdValidate11Data, PutUmbracoManagementApiV11DocumentByIdValidate11Error, GetDocumentAreReferencedData, GetDocumentAreReferencedResponse, GetDocumentConfigurationData, GetDocumentConfigurationResponse, PutDocumentSortData, PutDocumentSortError, GetDocumentUrlsData, GetDocumentUrlsResponse, PostDocumentValidateData, PostDocumentValidateError, GetItemDocumentData, GetItemDocumentResponse, GetItemDocumentSearchData, GetItemDocumentSearchResponse, DeleteRecycleBinDocumentData, DeleteRecycleBinDocumentError, DeleteRecycleBinDocumentByIdData, DeleteRecycleBinDocumentByIdError, GetRecycleBinDocumentByIdOriginalParentData, GetRecycleBinDocumentByIdOriginalParentResponse, GetRecycleBinDocumentByIdOriginalParentError, PutRecycleBinDocumentByIdRestoreData, PutRecycleBinDocumentByIdRestoreError, GetRecycleBinDocumentChildrenData, GetRecycleBinDocumentChildrenResponse, GetRecycleBinDocumentReferencedByData, GetRecycleBinDocumentReferencedByResponse, GetRecycleBinDocumentRootData, GetRecycleBinDocumentRootResponse, GetTreeDocumentAncestorsData, GetTreeDocumentAncestorsResponse, GetTreeDocumentChildrenData, GetTreeDocumentChildrenResponse, GetTreeDocumentRootData, GetTreeDocumentRootResponse, PostDynamicRootQueryData, PostDynamicRootQueryResponse, GetDynamicRootStepsData, GetDynamicRootStepsResponse, GetHealthCheckGroupData, GetHealthCheckGroupResponse, GetHealthCheckGroupByNameData, GetHealthCheckGroupByNameResponse, GetHealthCheckGroupByNameError, PostHealthCheckGroupByNameCheckData, PostHealthCheckGroupByNameCheckResponse, PostHealthCheckGroupByNameCheckError, PostHealthCheckExecuteActionData, PostHealthCheckExecuteActionResponse, PostHealthCheckExecuteActionError, GetHelpData, GetHelpResponse, GetHelpError, GetImagingResizeUrlsData, GetImagingResizeUrlsResponse, GetImportAnalyzeData, GetImportAnalyzeResponse, GetImportAnalyzeError, GetIndexerData, GetIndexerResponse, GetIndexerByIndexNameData, GetIndexerByIndexNameResponse, GetIndexerByIndexNameError, PostIndexerByIndexNameRebuildData, PostIndexerByIndexNameRebuildError, GetInstallSettingsData, GetInstallSettingsResponse, GetInstallSettingsError, PostInstallSetupData, PostInstallSetupError, PostInstallValidateDatabaseData, PostInstallValidateDatabaseError, GetItemLanguageData, GetItemLanguageResponse, GetItemLanguageDefaultData, GetItemLanguageDefaultResponse, GetLanguageData, GetLanguageResponse, PostLanguageData, PostLanguageError, DeleteLanguageByIsoCodeData, DeleteLanguageByIsoCodeError, GetLanguageByIsoCodeData, GetLanguageByIsoCodeResponse, GetLanguageByIsoCodeError, PutLanguageByIsoCodeData, PutLanguageByIsoCodeError, GetLogViewerLevelData, GetLogViewerLevelResponse, GetLogViewerLevelCountData, GetLogViewerLevelCountResponse, GetLogViewerLevelCountError, GetLogViewerLogData, GetLogViewerLogResponse, GetLogViewerMessageTemplateData, GetLogViewerMessageTemplateResponse, GetLogViewerMessageTemplateError, GetLogViewerSavedSearchData, GetLogViewerSavedSearchResponse, PostLogViewerSavedSearchData, PostLogViewerSavedSearchError, DeleteLogViewerSavedSearchByNameData, DeleteLogViewerSavedSearchByNameError, GetLogViewerSavedSearchByNameData, GetLogViewerSavedSearchByNameResponse, GetLogViewerSavedSearchByNameError, GetLogViewerValidateLogsSizeData, GetLogViewerValidateLogsSizeError, GetManifestManifestData, GetManifestManifestResponse, GetManifestManifestPrivateData, GetManifestManifestPrivateResponse, GetManifestManifestPublicData, GetManifestManifestPublicResponse, GetItemMediaTypeData, GetItemMediaTypeResponse, GetItemMediaTypeAllowedData, GetItemMediaTypeAllowedResponse, GetItemMediaTypeFoldersData, GetItemMediaTypeFoldersResponse, GetItemMediaTypeSearchData, GetItemMediaTypeSearchResponse, PostMediaTypeData, PostMediaTypeError, DeleteMediaTypeByIdData, DeleteMediaTypeByIdError, GetMediaTypeByIdData, GetMediaTypeByIdResponse, GetMediaTypeByIdError, PutMediaTypeByIdData, PutMediaTypeByIdError, GetMediaTypeByIdAllowedChildrenData, GetMediaTypeByIdAllowedChildrenResponse, GetMediaTypeByIdAllowedChildrenError, GetMediaTypeByIdCompositionReferencesData, GetMediaTypeByIdCompositionReferencesResponse, GetMediaTypeByIdCompositionReferencesError, PostMediaTypeByIdCopyData, PostMediaTypeByIdCopyError, GetMediaTypeByIdExportData, GetMediaTypeByIdExportResponse, GetMediaTypeByIdExportError, PutMediaTypeByIdImportData, PutMediaTypeByIdImportError, PutMediaTypeByIdMoveData, PutMediaTypeByIdMoveError, GetMediaTypeAllowedAtRootData, GetMediaTypeAllowedAtRootResponse, PostMediaTypeAvailableCompositionsData, PostMediaTypeAvailableCompositionsResponse, GetMediaTypeConfigurationData, GetMediaTypeConfigurationResponse, PostMediaTypeFolderData, PostMediaTypeFolderError, DeleteMediaTypeFolderByIdData, DeleteMediaTypeFolderByIdError, GetMediaTypeFolderByIdData, GetMediaTypeFolderByIdResponse, GetMediaTypeFolderByIdError, PutMediaTypeFolderByIdData, PutMediaTypeFolderByIdError, PostMediaTypeImportData, PostMediaTypeImportError, GetTreeMediaTypeAncestorsData, GetTreeMediaTypeAncestorsResponse, GetTreeMediaTypeChildrenData, GetTreeMediaTypeChildrenResponse, GetTreeMediaTypeRootData, GetTreeMediaTypeRootResponse, GetCollectionMediaData, GetCollectionMediaResponse, GetCollectionMediaError, GetItemMediaData, GetItemMediaResponse, GetItemMediaSearchData, GetItemMediaSearchResponse, PostMediaData, PostMediaError, DeleteMediaByIdData, DeleteMediaByIdError, GetMediaByIdData, GetMediaByIdResponse, GetMediaByIdError, PutMediaByIdData, PutMediaByIdError, GetMediaByIdAuditLogData, GetMediaByIdAuditLogResponse, PutMediaByIdMoveData, PutMediaByIdMoveError, PutMediaByIdMoveToRecycleBinData, PutMediaByIdMoveToRecycleBinError, GetMediaByIdReferencedByData, GetMediaByIdReferencedByResponse, GetMediaByIdReferencedDescendantsData, GetMediaByIdReferencedDescendantsResponse, PutMediaByIdValidateData, PutMediaByIdValidateError, GetMediaAreReferencedData, GetMediaAreReferencedResponse, GetMediaConfigurationData, GetMediaConfigurationResponse, PutMediaSortData, PutMediaSortError, GetMediaUrlsData, GetMediaUrlsResponse, PostMediaValidateData, PostMediaValidateError, DeleteRecycleBinMediaData, DeleteRecycleBinMediaError, DeleteRecycleBinMediaByIdData, DeleteRecycleBinMediaByIdError, GetRecycleBinMediaByIdOriginalParentData, GetRecycleBinMediaByIdOriginalParentResponse, GetRecycleBinMediaByIdOriginalParentError, PutRecycleBinMediaByIdRestoreData, PutRecycleBinMediaByIdRestoreError, GetRecycleBinMediaChildrenData, GetRecycleBinMediaChildrenResponse, GetRecycleBinMediaReferencedByData, GetRecycleBinMediaReferencedByResponse, GetRecycleBinMediaRootData, GetRecycleBinMediaRootResponse, GetTreeMediaAncestorsData, GetTreeMediaAncestorsResponse, GetTreeMediaChildrenData, GetTreeMediaChildrenResponse, GetTreeMediaRootData, GetTreeMediaRootResponse, GetItemMemberGroupData, GetItemMemberGroupResponse, GetMemberGroupData, GetMemberGroupResponse, PostMemberGroupData, PostMemberGroupError, DeleteMemberGroupByIdData, DeleteMemberGroupByIdError, GetMemberGroupByIdData, GetMemberGroupByIdResponse, PutMemberGroupByIdData, PutMemberGroupByIdError, GetTreeMemberGroupRootData, GetTreeMemberGroupRootResponse, GetItemMemberTypeData, GetItemMemberTypeResponse, GetItemMemberTypeSearchData, GetItemMemberTypeSearchResponse, PostMemberTypeData, PostMemberTypeError, DeleteMemberTypeByIdData, DeleteMemberTypeByIdError, GetMemberTypeByIdData, GetMemberTypeByIdResponse, GetMemberTypeByIdError, PutMemberTypeByIdData, PutMemberTypeByIdError, GetMemberTypeByIdCompositionReferencesData, GetMemberTypeByIdCompositionReferencesResponse, GetMemberTypeByIdCompositionReferencesError, PostMemberTypeByIdCopyData, PostMemberTypeByIdCopyError, PostMemberTypeAvailableCompositionsData, PostMemberTypeAvailableCompositionsResponse, GetMemberTypeConfigurationData, GetMemberTypeConfigurationResponse, GetTreeMemberTypeRootData, GetTreeMemberTypeRootResponse, GetFilterMemberData, GetFilterMemberResponse, GetFilterMemberError, GetItemMemberData, GetItemMemberResponse, GetItemMemberSearchData, GetItemMemberSearchResponse, PostMemberData, PostMemberError, DeleteMemberByIdData, DeleteMemberByIdError, GetMemberByIdData, GetMemberByIdResponse, GetMemberByIdError, PutMemberByIdData, PutMemberByIdError, GetMemberByIdReferencedByData, GetMemberByIdReferencedByResponse, GetMemberByIdReferencedDescendantsData, GetMemberByIdReferencedDescendantsResponse, PutMemberByIdValidateData, PutMemberByIdValidateError, GetMemberAreReferencedData, GetMemberAreReferencedResponse, GetMemberConfigurationData, GetMemberConfigurationResponse, PostMemberValidateData, PostMemberValidateError, PostModelsBuilderBuildData, PostModelsBuilderBuildError, GetModelsBuilderDashboardData, GetModelsBuilderDashboardResponse, GetModelsBuilderStatusData, GetModelsBuilderStatusResponse, GetObjectTypesData, GetObjectTypesResponse, GetOembedQueryData, GetOembedQueryResponse, PostPackageByNameRunMigrationData, PostPackageByNameRunMigrationError, GetPackageConfigurationData, GetPackageConfigurationResponse, GetPackageCreatedData, GetPackageCreatedResponse, PostPackageCreatedData, PostPackageCreatedError, DeletePackageCreatedByIdData, DeletePackageCreatedByIdError, GetPackageCreatedByIdData, GetPackageCreatedByIdResponse, GetPackageCreatedByIdError, PutPackageCreatedByIdData, PutPackageCreatedByIdError, GetPackageCreatedByIdDownloadData, GetPackageCreatedByIdDownloadResponse, GetPackageCreatedByIdDownloadError, GetPackageMigrationStatusData, GetPackageMigrationStatusResponse, GetItemPartialViewData, GetItemPartialViewResponse, PostPartialViewData, PostPartialViewError, DeletePartialViewByPathData, DeletePartialViewByPathError, GetPartialViewByPathData, GetPartialViewByPathResponse, GetPartialViewByPathError, PutPartialViewByPathData, PutPartialViewByPathError, PutPartialViewByPathRenameData, PutPartialViewByPathRenameError, PostPartialViewFolderData, PostPartialViewFolderError, DeletePartialViewFolderByPathData, DeletePartialViewFolderByPathError, GetPartialViewFolderByPathData, GetPartialViewFolderByPathResponse, GetPartialViewFolderByPathError, GetPartialViewSnippetData, GetPartialViewSnippetResponse, GetPartialViewSnippetByIdData, GetPartialViewSnippetByIdResponse, GetPartialViewSnippetByIdError, GetTreePartialViewAncestorsData, GetTreePartialViewAncestorsResponse, GetTreePartialViewChildrenData, GetTreePartialViewChildrenResponse, GetTreePartialViewRootData, GetTreePartialViewRootResponse, DeletePreviewData, PostPreviewData, GetProfilingStatusData, GetProfilingStatusResponse, PutProfilingStatusData, GetPropertyTypeIsUsedData, GetPropertyTypeIsUsedResponse, GetPropertyTypeIsUsedError, PostPublishedCacheRebuildData, GetPublishedCacheRebuildStatusData, GetPublishedCacheRebuildStatusResponse, PostPublishedCacheReloadData, GetRedirectManagementData, GetRedirectManagementResponse, GetRedirectManagementError, DeleteRedirectManagementByIdData, GetRedirectManagementByIdData, GetRedirectManagementByIdResponse, GetRedirectManagementStatusData, GetRedirectManagementStatusResponse, PostRedirectManagementStatusData, GetItemRelationTypeData, GetItemRelationTypeResponse, GetRelationTypeData, GetRelationTypeResponse, GetRelationTypeByIdData, GetRelationTypeByIdResponse, GetRelationTypeByIdError, GetRelationByRelationTypeIdData, GetRelationByRelationTypeIdResponse, GetRelationByRelationTypeIdError, GetItemScriptData, GetItemScriptResponse, PostScriptData, PostScriptError, DeleteScriptByPathData, DeleteScriptByPathError, GetScriptByPathData, GetScriptByPathResponse, GetScriptByPathError, PutScriptByPathData, PutScriptByPathError, PutScriptByPathRenameData, PutScriptByPathRenameError, PostScriptFolderData, PostScriptFolderError, DeleteScriptFolderByPathData, DeleteScriptFolderByPathError, GetScriptFolderByPathData, GetScriptFolderByPathResponse, GetScriptFolderByPathError, GetTreeScriptAncestorsData, GetTreeScriptAncestorsResponse, GetTreeScriptChildrenData, GetTreeScriptChildrenResponse, GetTreeScriptRootData, GetTreeScriptRootResponse, GetSearcherData, GetSearcherResponse, GetSearcherBySearcherNameQueryData, GetSearcherBySearcherNameQueryResponse, GetSearcherBySearcherNameQueryError, GetSecurityConfigurationData, GetSecurityConfigurationResponse, PostSecurityForgotPasswordData, PostSecurityForgotPasswordError, PostSecurityForgotPasswordResetData, PostSecurityForgotPasswordResetResponse, PostSecurityForgotPasswordResetError, PostSecurityForgotPasswordVerifyData, PostSecurityForgotPasswordVerifyResponse, PostSecurityForgotPasswordVerifyError, GetSegmentData, GetSegmentResponse, GetSegmentError, GetServerConfigurationData, GetServerConfigurationResponse, GetServerInformationData, GetServerInformationResponse, GetServerStatusData, GetServerStatusResponse, GetServerStatusError, GetServerTroubleshootingData, GetServerTroubleshootingResponse, GetServerUpgradeCheckData, GetServerUpgradeCheckResponse, GetItemStaticFileData, GetItemStaticFileResponse, GetTreeStaticFileAncestorsData, GetTreeStaticFileAncestorsResponse, GetTreeStaticFileChildrenData, GetTreeStaticFileChildrenResponse, GetTreeStaticFileRootData, GetTreeStaticFileRootResponse, GetItemStylesheetData, GetItemStylesheetResponse, PostStylesheetData, PostStylesheetError, DeleteStylesheetByPathData, DeleteStylesheetByPathError, GetStylesheetByPathData, GetStylesheetByPathResponse, GetStylesheetByPathError, PutStylesheetByPathData, PutStylesheetByPathError, PutStylesheetByPathRenameData, PutStylesheetByPathRenameError, PostStylesheetFolderData, PostStylesheetFolderError, DeleteStylesheetFolderByPathData, DeleteStylesheetFolderByPathError, GetStylesheetFolderByPathData, GetStylesheetFolderByPathResponse, GetStylesheetFolderByPathError, GetTreeStylesheetAncestorsData, GetTreeStylesheetAncestorsResponse, GetTreeStylesheetChildrenData, GetTreeStylesheetChildrenResponse, GetTreeStylesheetRootData, GetTreeStylesheetRootResponse, GetTagData, GetTagResponse, GetTelemetryData, GetTelemetryResponse, GetTelemetryLevelData, GetTelemetryLevelResponse, PostTelemetryLevelData, PostTelemetryLevelError, GetItemTemplateData, GetItemTemplateResponse, GetItemTemplateSearchData, GetItemTemplateSearchResponse, PostTemplateData, PostTemplateError, DeleteTemplateByIdData, DeleteTemplateByIdError, GetTemplateByIdData, GetTemplateByIdResponse, GetTemplateByIdError, PutTemplateByIdData, PutTemplateByIdError, GetTemplateConfigurationData, GetTemplateConfigurationResponse, PostTemplateQueryExecuteData, PostTemplateQueryExecuteResponse, GetTemplateQuerySettingsData, GetTemplateQuerySettingsResponse, GetTreeTemplateAncestorsData, GetTreeTemplateAncestorsResponse, GetTreeTemplateChildrenData, GetTreeTemplateChildrenResponse, GetTreeTemplateRootData, GetTreeTemplateRootResponse, PostTemporaryFileData, PostTemporaryFileError, DeleteTemporaryFileByIdData, DeleteTemporaryFileByIdError, GetTemporaryFileByIdData, GetTemporaryFileByIdResponse, GetTemporaryFileByIdError, GetTemporaryFileConfigurationData, GetTemporaryFileConfigurationResponse, PostUpgradeAuthorizeData, PostUpgradeAuthorizeError, GetUpgradeSettingsData, GetUpgradeSettingsResponse, GetUpgradeSettingsError, GetUserDataData, GetUserDataResponse, PostUserDataData, PostUserDataError, PutUserDataData, PutUserDataError, GetUserDataByIdData, GetUserDataByIdResponse, GetFilterUserGroupData, GetFilterUserGroupResponse, GetFilterUserGroupError, GetItemUserGroupData, GetItemUserGroupResponse, DeleteUserGroupData, DeleteUserGroupError, GetUserGroupData, GetUserGroupResponse, PostUserGroupData, PostUserGroupError, DeleteUserGroupByIdData, DeleteUserGroupByIdError, GetUserGroupByIdData, GetUserGroupByIdResponse, GetUserGroupByIdError, PutUserGroupByIdData, PutUserGroupByIdError, DeleteUserGroupByIdUsersData, DeleteUserGroupByIdUsersError, PostUserGroupByIdUsersData, PostUserGroupByIdUsersError, GetFilterUserData, GetFilterUserResponse, GetFilterUserError, GetItemUserData, GetItemUserResponse, DeleteUserData, DeleteUserError, GetUserData, GetUserResponse, GetUserError, PostUserData, PostUserError, DeleteUserByIdData, DeleteUserByIdError, GetUserByIdData, GetUserByIdResponse, GetUserByIdError, PutUserByIdData, PutUserByIdError, GetUserById2FaData, GetUserById2FaResponse, GetUserById2FaError, DeleteUserById2FaByProviderNameData, DeleteUserById2FaByProviderNameError, GetUserByIdCalculateStartNodesData, GetUserByIdCalculateStartNodesResponse, GetUserByIdCalculateStartNodesError, PostUserByIdChangePasswordData, PostUserByIdChangePasswordError, GetUserByIdClientCredentialsData, GetUserByIdClientCredentialsResponse, PostUserByIdClientCredentialsData, PostUserByIdClientCredentialsError, DeleteUserByIdClientCredentialsByClientIdData, DeleteUserByIdClientCredentialsByClientIdError, PostUserByIdResetPasswordData, PostUserByIdResetPasswordResponse, PostUserByIdResetPasswordError, DeleteUserAvatarByIdData, DeleteUserAvatarByIdError, PostUserAvatarByIdData, PostUserAvatarByIdError, GetUserConfigurationData, GetUserConfigurationResponse, GetUserCurrentData, GetUserCurrentResponse, GetUserCurrent2FaData, GetUserCurrent2FaResponse, DeleteUserCurrent2FaByProviderNameData, DeleteUserCurrent2FaByProviderNameError, GetUserCurrent2FaByProviderNameData, GetUserCurrent2FaByProviderNameResponse, GetUserCurrent2FaByProviderNameError, PostUserCurrent2FaByProviderNameData, PostUserCurrent2FaByProviderNameResponse, PostUserCurrent2FaByProviderNameError, PostUserCurrentAvatarData, PostUserCurrentAvatarError, PostUserCurrentChangePasswordData, PostUserCurrentChangePasswordError, GetUserCurrentConfigurationData, GetUserCurrentConfigurationResponse, GetUserCurrentLoginProvidersData, GetUserCurrentLoginProvidersResponse, GetUserCurrentPermissionsData, GetUserCurrentPermissionsResponse, GetUserCurrentPermissionsError, GetUserCurrentPermissionsDocumentData, GetUserCurrentPermissionsDocumentResponse, GetUserCurrentPermissionsDocumentError, GetUserCurrentPermissionsMediaData, GetUserCurrentPermissionsMediaResponse, GetUserCurrentPermissionsMediaError, PostUserDisableData, PostUserDisableError, PostUserEnableData, PostUserEnableError, PostUserInviteData, PostUserInviteError, PostUserInviteCreatePasswordData, PostUserInviteCreatePasswordError, PostUserInviteResendData, PostUserInviteResendError, PostUserInviteVerifyData, PostUserInviteVerifyResponse, PostUserInviteVerifyError, PostUserSetUserGroupsData, PostUserUnlockData, PostUserUnlockError, GetItemWebhookData, GetItemWebhookResponse, GetWebhookData, GetWebhookResponse, PostWebhookData, PostWebhookError, DeleteWebhookByIdData, DeleteWebhookByIdError, GetWebhookByIdData, GetWebhookByIdResponse, GetWebhookByIdError, PutWebhookByIdData, PutWebhookByIdError, GetWebhookByIdLogsData, GetWebhookByIdLogsResponse, GetWebhookEventsData, GetWebhookEventsResponse, GetWebhookLogsData, GetWebhookLogsResponse } from './types.gen'; +import type { GetCultureData, GetCultureResponses, GetCultureErrors, PostDataTypeData, PostDataTypeResponses, PostDataTypeErrors, DeleteDataTypeByIdData, DeleteDataTypeByIdResponses, DeleteDataTypeByIdErrors, GetDataTypeByIdData, GetDataTypeByIdResponses, GetDataTypeByIdErrors, PutDataTypeByIdData, PutDataTypeByIdResponses, PutDataTypeByIdErrors, PostDataTypeByIdCopyData, PostDataTypeByIdCopyResponses, PostDataTypeByIdCopyErrors, GetDataTypeByIdIsUsedData, GetDataTypeByIdIsUsedResponses, GetDataTypeByIdIsUsedErrors, PutDataTypeByIdMoveData, PutDataTypeByIdMoveResponses, PutDataTypeByIdMoveErrors, GetDataTypeByIdReferencedByData, GetDataTypeByIdReferencedByResponses, GetDataTypeByIdReferencedByErrors, GetDataTypeByIdReferencesData, GetDataTypeByIdReferencesResponses, GetDataTypeByIdReferencesErrors, GetDataTypeConfigurationData, GetDataTypeConfigurationResponses, GetDataTypeConfigurationErrors, PostDataTypeFolderData, PostDataTypeFolderResponses, PostDataTypeFolderErrors, DeleteDataTypeFolderByIdData, DeleteDataTypeFolderByIdResponses, DeleteDataTypeFolderByIdErrors, GetDataTypeFolderByIdData, GetDataTypeFolderByIdResponses, GetDataTypeFolderByIdErrors, PutDataTypeFolderByIdData, PutDataTypeFolderByIdResponses, PutDataTypeFolderByIdErrors, GetFilterDataTypeData, GetFilterDataTypeResponses, GetFilterDataTypeErrors, GetItemDataTypeData, GetItemDataTypeResponses, GetItemDataTypeErrors, GetItemDataTypeSearchData, GetItemDataTypeSearchResponses, GetItemDataTypeSearchErrors, GetTreeDataTypeAncestorsData, GetTreeDataTypeAncestorsResponses, GetTreeDataTypeAncestorsErrors, GetTreeDataTypeChildrenData, GetTreeDataTypeChildrenResponses, GetTreeDataTypeChildrenErrors, GetTreeDataTypeRootData, GetTreeDataTypeRootResponses, GetTreeDataTypeRootErrors, GetDictionaryData, GetDictionaryResponses, GetDictionaryErrors, PostDictionaryData, PostDictionaryResponses, PostDictionaryErrors, DeleteDictionaryByIdData, DeleteDictionaryByIdResponses, DeleteDictionaryByIdErrors, GetDictionaryByIdData, GetDictionaryByIdResponses, GetDictionaryByIdErrors, PutDictionaryByIdData, PutDictionaryByIdResponses, PutDictionaryByIdErrors, GetDictionaryByIdExportData, GetDictionaryByIdExportResponses, GetDictionaryByIdExportErrors, PutDictionaryByIdMoveData, PutDictionaryByIdMoveResponses, PutDictionaryByIdMoveErrors, PostDictionaryImportData, PostDictionaryImportResponses, PostDictionaryImportErrors, GetItemDictionaryData, GetItemDictionaryResponses, GetItemDictionaryErrors, GetTreeDictionaryAncestorsData, GetTreeDictionaryAncestorsResponses, GetTreeDictionaryAncestorsErrors, GetTreeDictionaryChildrenData, GetTreeDictionaryChildrenResponses, GetTreeDictionaryChildrenErrors, GetTreeDictionaryRootData, GetTreeDictionaryRootResponses, GetTreeDictionaryRootErrors, PostDocumentBlueprintData, PostDocumentBlueprintResponses, PostDocumentBlueprintErrors, DeleteDocumentBlueprintByIdData, DeleteDocumentBlueprintByIdResponses, DeleteDocumentBlueprintByIdErrors, GetDocumentBlueprintByIdData, GetDocumentBlueprintByIdResponses, GetDocumentBlueprintByIdErrors, PutDocumentBlueprintByIdData, PutDocumentBlueprintByIdResponses, PutDocumentBlueprintByIdErrors, PutDocumentBlueprintByIdMoveData, PutDocumentBlueprintByIdMoveResponses, PutDocumentBlueprintByIdMoveErrors, GetDocumentBlueprintByIdScaffoldData, GetDocumentBlueprintByIdScaffoldResponses, GetDocumentBlueprintByIdScaffoldErrors, PostDocumentBlueprintFolderData, PostDocumentBlueprintFolderResponses, PostDocumentBlueprintFolderErrors, DeleteDocumentBlueprintFolderByIdData, DeleteDocumentBlueprintFolderByIdResponses, DeleteDocumentBlueprintFolderByIdErrors, GetDocumentBlueprintFolderByIdData, GetDocumentBlueprintFolderByIdResponses, GetDocumentBlueprintFolderByIdErrors, PutDocumentBlueprintFolderByIdData, PutDocumentBlueprintFolderByIdResponses, PutDocumentBlueprintFolderByIdErrors, PostDocumentBlueprintFromDocumentData, PostDocumentBlueprintFromDocumentResponses, PostDocumentBlueprintFromDocumentErrors, GetItemDocumentBlueprintData, GetItemDocumentBlueprintResponses, GetItemDocumentBlueprintErrors, GetTreeDocumentBlueprintAncestorsData, GetTreeDocumentBlueprintAncestorsResponses, GetTreeDocumentBlueprintAncestorsErrors, GetTreeDocumentBlueprintChildrenData, GetTreeDocumentBlueprintChildrenResponses, GetTreeDocumentBlueprintChildrenErrors, GetTreeDocumentBlueprintRootData, GetTreeDocumentBlueprintRootResponses, GetTreeDocumentBlueprintRootErrors, PostDocumentTypeData, PostDocumentTypeResponses, PostDocumentTypeErrors, DeleteDocumentTypeByIdData, DeleteDocumentTypeByIdResponses, DeleteDocumentTypeByIdErrors, GetDocumentTypeByIdData, GetDocumentTypeByIdResponses, GetDocumentTypeByIdErrors, PutDocumentTypeByIdData, PutDocumentTypeByIdResponses, PutDocumentTypeByIdErrors, GetDocumentTypeByIdAllowedChildrenData, GetDocumentTypeByIdAllowedChildrenResponses, GetDocumentTypeByIdAllowedChildrenErrors, GetDocumentTypeByIdBlueprintData, GetDocumentTypeByIdBlueprintResponses, GetDocumentTypeByIdBlueprintErrors, GetDocumentTypeByIdCompositionReferencesData, GetDocumentTypeByIdCompositionReferencesResponses, GetDocumentTypeByIdCompositionReferencesErrors, PostDocumentTypeByIdCopyData, PostDocumentTypeByIdCopyResponses, PostDocumentTypeByIdCopyErrors, GetDocumentTypeByIdExportData, GetDocumentTypeByIdExportResponses, GetDocumentTypeByIdExportErrors, PutDocumentTypeByIdImportData, PutDocumentTypeByIdImportResponses, PutDocumentTypeByIdImportErrors, PutDocumentTypeByIdMoveData, PutDocumentTypeByIdMoveResponses, PutDocumentTypeByIdMoveErrors, GetDocumentTypeAllowedAtRootData, GetDocumentTypeAllowedAtRootResponses, GetDocumentTypeAllowedAtRootErrors, PostDocumentTypeAvailableCompositionsData, PostDocumentTypeAvailableCompositionsResponses, PostDocumentTypeAvailableCompositionsErrors, GetDocumentTypeConfigurationData, GetDocumentTypeConfigurationResponses, GetDocumentTypeConfigurationErrors, PostDocumentTypeFolderData, PostDocumentTypeFolderResponses, PostDocumentTypeFolderErrors, DeleteDocumentTypeFolderByIdData, DeleteDocumentTypeFolderByIdResponses, DeleteDocumentTypeFolderByIdErrors, GetDocumentTypeFolderByIdData, GetDocumentTypeFolderByIdResponses, GetDocumentTypeFolderByIdErrors, PutDocumentTypeFolderByIdData, PutDocumentTypeFolderByIdResponses, PutDocumentTypeFolderByIdErrors, PostDocumentTypeImportData, PostDocumentTypeImportResponses, PostDocumentTypeImportErrors, GetItemDocumentTypeData, GetItemDocumentTypeResponses, GetItemDocumentTypeErrors, GetItemDocumentTypeSearchData, GetItemDocumentTypeSearchResponses, GetItemDocumentTypeSearchErrors, GetTreeDocumentTypeAncestorsData, GetTreeDocumentTypeAncestorsResponses, GetTreeDocumentTypeAncestorsErrors, GetTreeDocumentTypeChildrenData, GetTreeDocumentTypeChildrenResponses, GetTreeDocumentTypeChildrenErrors, GetTreeDocumentTypeRootData, GetTreeDocumentTypeRootResponses, GetTreeDocumentTypeRootErrors, GetDocumentVersionData, GetDocumentVersionResponses, GetDocumentVersionErrors, GetDocumentVersionByIdData, GetDocumentVersionByIdResponses, GetDocumentVersionByIdErrors, PutDocumentVersionByIdPreventCleanupData, PutDocumentVersionByIdPreventCleanupResponses, PutDocumentVersionByIdPreventCleanupErrors, PostDocumentVersionByIdRollbackData, PostDocumentVersionByIdRollbackResponses, PostDocumentVersionByIdRollbackErrors, GetCollectionDocumentByIdData, GetCollectionDocumentByIdResponses, GetCollectionDocumentByIdErrors, PostDocumentData, PostDocumentResponses, PostDocumentErrors, DeleteDocumentByIdData, DeleteDocumentByIdResponses, DeleteDocumentByIdErrors, GetDocumentByIdData, GetDocumentByIdResponses, GetDocumentByIdErrors, PutDocumentByIdData, PutDocumentByIdResponses, PutDocumentByIdErrors, GetDocumentByIdAuditLogData, GetDocumentByIdAuditLogResponses, GetDocumentByIdAuditLogErrors, PostDocumentByIdCopyData, PostDocumentByIdCopyResponses, PostDocumentByIdCopyErrors, GetDocumentByIdDomainsData, GetDocumentByIdDomainsResponses, GetDocumentByIdDomainsErrors, PutDocumentByIdDomainsData, PutDocumentByIdDomainsResponses, PutDocumentByIdDomainsErrors, PutDocumentByIdMoveData, PutDocumentByIdMoveResponses, PutDocumentByIdMoveErrors, PutDocumentByIdMoveToRecycleBinData, PutDocumentByIdMoveToRecycleBinResponses, PutDocumentByIdMoveToRecycleBinErrors, GetDocumentByIdNotificationsData, GetDocumentByIdNotificationsResponses, GetDocumentByIdNotificationsErrors, PutDocumentByIdNotificationsData, PutDocumentByIdNotificationsResponses, PutDocumentByIdNotificationsErrors, DeleteDocumentByIdPublicAccessData, DeleteDocumentByIdPublicAccessResponses, DeleteDocumentByIdPublicAccessErrors, GetDocumentByIdPublicAccessData, GetDocumentByIdPublicAccessResponses, GetDocumentByIdPublicAccessErrors, PostDocumentByIdPublicAccessData, PostDocumentByIdPublicAccessResponses, PostDocumentByIdPublicAccessErrors, PutDocumentByIdPublicAccessData, PutDocumentByIdPublicAccessResponses, PutDocumentByIdPublicAccessErrors, PutDocumentByIdPublishData, PutDocumentByIdPublishResponses, PutDocumentByIdPublishErrors, PutDocumentByIdPublishWithDescendantsData, PutDocumentByIdPublishWithDescendantsResponses, PutDocumentByIdPublishWithDescendantsErrors, GetDocumentByIdPublishWithDescendantsResultByTaskIdData, GetDocumentByIdPublishWithDescendantsResultByTaskIdResponses, GetDocumentByIdPublishWithDescendantsResultByTaskIdErrors, GetDocumentByIdPublishedData, GetDocumentByIdPublishedResponses, GetDocumentByIdPublishedErrors, GetDocumentByIdReferencedByData, GetDocumentByIdReferencedByResponses, GetDocumentByIdReferencedByErrors, GetDocumentByIdReferencedDescendantsData, GetDocumentByIdReferencedDescendantsResponses, GetDocumentByIdReferencedDescendantsErrors, PutDocumentByIdUnpublishData, PutDocumentByIdUnpublishResponses, PutDocumentByIdUnpublishErrors, PutUmbracoManagementApiV11DocumentByIdValidate11Data, PutUmbracoManagementApiV11DocumentByIdValidate11Responses, PutUmbracoManagementApiV11DocumentByIdValidate11Errors, GetDocumentAreReferencedData, GetDocumentAreReferencedResponses, GetDocumentAreReferencedErrors, GetDocumentConfigurationData, GetDocumentConfigurationResponses, GetDocumentConfigurationErrors, PutDocumentSortData, PutDocumentSortResponses, PutDocumentSortErrors, GetDocumentUrlsData, GetDocumentUrlsResponses, GetDocumentUrlsErrors, PostDocumentValidateData, PostDocumentValidateResponses, PostDocumentValidateErrors, GetItemDocumentData, GetItemDocumentResponses, GetItemDocumentErrors, GetItemDocumentSearchData, GetItemDocumentSearchResponses, GetItemDocumentSearchErrors, DeleteRecycleBinDocumentData, DeleteRecycleBinDocumentResponses, DeleteRecycleBinDocumentErrors, DeleteRecycleBinDocumentByIdData, DeleteRecycleBinDocumentByIdResponses, DeleteRecycleBinDocumentByIdErrors, GetRecycleBinDocumentByIdOriginalParentData, GetRecycleBinDocumentByIdOriginalParentResponses, GetRecycleBinDocumentByIdOriginalParentErrors, PutRecycleBinDocumentByIdRestoreData, PutRecycleBinDocumentByIdRestoreResponses, PutRecycleBinDocumentByIdRestoreErrors, GetRecycleBinDocumentChildrenData, GetRecycleBinDocumentChildrenResponses, GetRecycleBinDocumentChildrenErrors, GetRecycleBinDocumentReferencedByData, GetRecycleBinDocumentReferencedByResponses, GetRecycleBinDocumentReferencedByErrors, GetRecycleBinDocumentRootData, GetRecycleBinDocumentRootResponses, GetRecycleBinDocumentRootErrors, GetTreeDocumentAncestorsData, GetTreeDocumentAncestorsResponses, GetTreeDocumentAncestorsErrors, GetTreeDocumentChildrenData, GetTreeDocumentChildrenResponses, GetTreeDocumentChildrenErrors, GetTreeDocumentRootData, GetTreeDocumentRootResponses, GetTreeDocumentRootErrors, PostDynamicRootQueryData, PostDynamicRootQueryResponses, PostDynamicRootQueryErrors, GetDynamicRootStepsData, GetDynamicRootStepsResponses, GetDynamicRootStepsErrors, GetHealthCheckGroupData, GetHealthCheckGroupResponses, GetHealthCheckGroupErrors, GetHealthCheckGroupByNameData, GetHealthCheckGroupByNameResponses, GetHealthCheckGroupByNameErrors, PostHealthCheckGroupByNameCheckData, PostHealthCheckGroupByNameCheckResponses, PostHealthCheckGroupByNameCheckErrors, PostHealthCheckExecuteActionData, PostHealthCheckExecuteActionResponses, PostHealthCheckExecuteActionErrors, GetHelpData, GetHelpResponses, GetHelpErrors, GetImagingResizeUrlsData, GetImagingResizeUrlsResponses, GetImagingResizeUrlsErrors, GetImportAnalyzeData, GetImportAnalyzeResponses, GetImportAnalyzeErrors, GetIndexerData, GetIndexerResponses, GetIndexerErrors, GetIndexerByIndexNameData, GetIndexerByIndexNameResponses, GetIndexerByIndexNameErrors, PostIndexerByIndexNameRebuildData, PostIndexerByIndexNameRebuildResponses, PostIndexerByIndexNameRebuildErrors, GetInstallSettingsData, GetInstallSettingsResponses, GetInstallSettingsErrors, PostInstallSetupData, PostInstallSetupResponses, PostInstallSetupErrors, PostInstallValidateDatabaseData, PostInstallValidateDatabaseResponses, PostInstallValidateDatabaseErrors, GetItemLanguageData, GetItemLanguageResponses, GetItemLanguageErrors, GetItemLanguageDefaultData, GetItemLanguageDefaultResponses, GetItemLanguageDefaultErrors, GetLanguageData, GetLanguageResponses, GetLanguageErrors, PostLanguageData, PostLanguageResponses, PostLanguageErrors, DeleteLanguageByIsoCodeData, DeleteLanguageByIsoCodeResponses, DeleteLanguageByIsoCodeErrors, GetLanguageByIsoCodeData, GetLanguageByIsoCodeResponses, GetLanguageByIsoCodeErrors, PutLanguageByIsoCodeData, PutLanguageByIsoCodeResponses, PutLanguageByIsoCodeErrors, GetLogViewerLevelData, GetLogViewerLevelResponses, GetLogViewerLevelErrors, GetLogViewerLevelCountData, GetLogViewerLevelCountResponses, GetLogViewerLevelCountErrors, GetLogViewerLogData, GetLogViewerLogResponses, GetLogViewerLogErrors, GetLogViewerMessageTemplateData, GetLogViewerMessageTemplateResponses, GetLogViewerMessageTemplateErrors, GetLogViewerSavedSearchData, GetLogViewerSavedSearchResponses, GetLogViewerSavedSearchErrors, PostLogViewerSavedSearchData, PostLogViewerSavedSearchResponses, PostLogViewerSavedSearchErrors, DeleteLogViewerSavedSearchByNameData, DeleteLogViewerSavedSearchByNameResponses, DeleteLogViewerSavedSearchByNameErrors, GetLogViewerSavedSearchByNameData, GetLogViewerSavedSearchByNameResponses, GetLogViewerSavedSearchByNameErrors, GetLogViewerValidateLogsSizeData, GetLogViewerValidateLogsSizeResponses, GetLogViewerValidateLogsSizeErrors, GetManifestManifestData, GetManifestManifestResponses, GetManifestManifestErrors, GetManifestManifestPrivateData, GetManifestManifestPrivateResponses, GetManifestManifestPrivateErrors, GetManifestManifestPublicData, GetManifestManifestPublicResponses, GetItemMediaTypeData, GetItemMediaTypeResponses, GetItemMediaTypeErrors, GetItemMediaTypeAllowedData, GetItemMediaTypeAllowedResponses, GetItemMediaTypeAllowedErrors, GetItemMediaTypeFoldersData, GetItemMediaTypeFoldersResponses, GetItemMediaTypeFoldersErrors, GetItemMediaTypeSearchData, GetItemMediaTypeSearchResponses, GetItemMediaTypeSearchErrors, PostMediaTypeData, PostMediaTypeResponses, PostMediaTypeErrors, DeleteMediaTypeByIdData, DeleteMediaTypeByIdResponses, DeleteMediaTypeByIdErrors, GetMediaTypeByIdData, GetMediaTypeByIdResponses, GetMediaTypeByIdErrors, PutMediaTypeByIdData, PutMediaTypeByIdResponses, PutMediaTypeByIdErrors, GetMediaTypeByIdAllowedChildrenData, GetMediaTypeByIdAllowedChildrenResponses, GetMediaTypeByIdAllowedChildrenErrors, GetMediaTypeByIdCompositionReferencesData, GetMediaTypeByIdCompositionReferencesResponses, GetMediaTypeByIdCompositionReferencesErrors, PostMediaTypeByIdCopyData, PostMediaTypeByIdCopyResponses, PostMediaTypeByIdCopyErrors, GetMediaTypeByIdExportData, GetMediaTypeByIdExportResponses, GetMediaTypeByIdExportErrors, PutMediaTypeByIdImportData, PutMediaTypeByIdImportResponses, PutMediaTypeByIdImportErrors, PutMediaTypeByIdMoveData, PutMediaTypeByIdMoveResponses, PutMediaTypeByIdMoveErrors, GetMediaTypeAllowedAtRootData, GetMediaTypeAllowedAtRootResponses, GetMediaTypeAllowedAtRootErrors, PostMediaTypeAvailableCompositionsData, PostMediaTypeAvailableCompositionsResponses, PostMediaTypeAvailableCompositionsErrors, GetMediaTypeConfigurationData, GetMediaTypeConfigurationResponses, GetMediaTypeConfigurationErrors, PostMediaTypeFolderData, PostMediaTypeFolderResponses, PostMediaTypeFolderErrors, DeleteMediaTypeFolderByIdData, DeleteMediaTypeFolderByIdResponses, DeleteMediaTypeFolderByIdErrors, GetMediaTypeFolderByIdData, GetMediaTypeFolderByIdResponses, GetMediaTypeFolderByIdErrors, PutMediaTypeFolderByIdData, PutMediaTypeFolderByIdResponses, PutMediaTypeFolderByIdErrors, PostMediaTypeImportData, PostMediaTypeImportResponses, PostMediaTypeImportErrors, GetTreeMediaTypeAncestorsData, GetTreeMediaTypeAncestorsResponses, GetTreeMediaTypeAncestorsErrors, GetTreeMediaTypeChildrenData, GetTreeMediaTypeChildrenResponses, GetTreeMediaTypeChildrenErrors, GetTreeMediaTypeRootData, GetTreeMediaTypeRootResponses, GetTreeMediaTypeRootErrors, GetCollectionMediaData, GetCollectionMediaResponses, GetCollectionMediaErrors, GetItemMediaData, GetItemMediaResponses, GetItemMediaErrors, GetItemMediaSearchData, GetItemMediaSearchResponses, GetItemMediaSearchErrors, PostMediaData, PostMediaResponses, PostMediaErrors, DeleteMediaByIdData, DeleteMediaByIdResponses, DeleteMediaByIdErrors, GetMediaByIdData, GetMediaByIdResponses, GetMediaByIdErrors, PutMediaByIdData, PutMediaByIdResponses, PutMediaByIdErrors, GetMediaByIdAuditLogData, GetMediaByIdAuditLogResponses, GetMediaByIdAuditLogErrors, PutMediaByIdMoveData, PutMediaByIdMoveResponses, PutMediaByIdMoveErrors, PutMediaByIdMoveToRecycleBinData, PutMediaByIdMoveToRecycleBinResponses, PutMediaByIdMoveToRecycleBinErrors, GetMediaByIdReferencedByData, GetMediaByIdReferencedByResponses, GetMediaByIdReferencedByErrors, GetMediaByIdReferencedDescendantsData, GetMediaByIdReferencedDescendantsResponses, GetMediaByIdReferencedDescendantsErrors, PutMediaByIdValidateData, PutMediaByIdValidateResponses, PutMediaByIdValidateErrors, GetMediaAreReferencedData, GetMediaAreReferencedResponses, GetMediaAreReferencedErrors, GetMediaConfigurationData, GetMediaConfigurationResponses, GetMediaConfigurationErrors, PutMediaSortData, PutMediaSortResponses, PutMediaSortErrors, GetMediaUrlsData, GetMediaUrlsResponses, GetMediaUrlsErrors, PostMediaValidateData, PostMediaValidateResponses, PostMediaValidateErrors, DeleteRecycleBinMediaData, DeleteRecycleBinMediaResponses, DeleteRecycleBinMediaErrors, DeleteRecycleBinMediaByIdData, DeleteRecycleBinMediaByIdResponses, DeleteRecycleBinMediaByIdErrors, GetRecycleBinMediaByIdOriginalParentData, GetRecycleBinMediaByIdOriginalParentResponses, GetRecycleBinMediaByIdOriginalParentErrors, PutRecycleBinMediaByIdRestoreData, PutRecycleBinMediaByIdRestoreResponses, PutRecycleBinMediaByIdRestoreErrors, GetRecycleBinMediaChildrenData, GetRecycleBinMediaChildrenResponses, GetRecycleBinMediaChildrenErrors, GetRecycleBinMediaReferencedByData, GetRecycleBinMediaReferencedByResponses, GetRecycleBinMediaReferencedByErrors, GetRecycleBinMediaRootData, GetRecycleBinMediaRootResponses, GetRecycleBinMediaRootErrors, GetTreeMediaAncestorsData, GetTreeMediaAncestorsResponses, GetTreeMediaAncestorsErrors, GetTreeMediaChildrenData, GetTreeMediaChildrenResponses, GetTreeMediaChildrenErrors, GetTreeMediaRootData, GetTreeMediaRootResponses, GetTreeMediaRootErrors, GetItemMemberGroupData, GetItemMemberGroupResponses, GetItemMemberGroupErrors, GetMemberGroupData, GetMemberGroupResponses, GetMemberGroupErrors, PostMemberGroupData, PostMemberGroupResponses, PostMemberGroupErrors, DeleteMemberGroupByIdData, DeleteMemberGroupByIdResponses, DeleteMemberGroupByIdErrors, GetMemberGroupByIdData, GetMemberGroupByIdResponses, GetMemberGroupByIdErrors, PutMemberGroupByIdData, PutMemberGroupByIdResponses, PutMemberGroupByIdErrors, GetTreeMemberGroupRootData, GetTreeMemberGroupRootResponses, GetTreeMemberGroupRootErrors, GetItemMemberTypeData, GetItemMemberTypeResponses, GetItemMemberTypeErrors, GetItemMemberTypeSearchData, GetItemMemberTypeSearchResponses, GetItemMemberTypeSearchErrors, PostMemberTypeData, PostMemberTypeResponses, PostMemberTypeErrors, DeleteMemberTypeByIdData, DeleteMemberTypeByIdResponses, DeleteMemberTypeByIdErrors, GetMemberTypeByIdData, GetMemberTypeByIdResponses, GetMemberTypeByIdErrors, PutMemberTypeByIdData, PutMemberTypeByIdResponses, PutMemberTypeByIdErrors, GetMemberTypeByIdCompositionReferencesData, GetMemberTypeByIdCompositionReferencesResponses, GetMemberTypeByIdCompositionReferencesErrors, PostMemberTypeByIdCopyData, PostMemberTypeByIdCopyResponses, PostMemberTypeByIdCopyErrors, PostMemberTypeAvailableCompositionsData, PostMemberTypeAvailableCompositionsResponses, PostMemberTypeAvailableCompositionsErrors, GetMemberTypeConfigurationData, GetMemberTypeConfigurationResponses, GetMemberTypeConfigurationErrors, GetTreeMemberTypeRootData, GetTreeMemberTypeRootResponses, GetTreeMemberTypeRootErrors, GetFilterMemberData, GetFilterMemberResponses, GetFilterMemberErrors, GetItemMemberData, GetItemMemberResponses, GetItemMemberErrors, GetItemMemberSearchData, GetItemMemberSearchResponses, GetItemMemberSearchErrors, PostMemberData, PostMemberResponses, PostMemberErrors, DeleteMemberByIdData, DeleteMemberByIdResponses, DeleteMemberByIdErrors, GetMemberByIdData, GetMemberByIdResponses, GetMemberByIdErrors, PutMemberByIdData, PutMemberByIdResponses, PutMemberByIdErrors, GetMemberByIdReferencedByData, GetMemberByIdReferencedByResponses, GetMemberByIdReferencedByErrors, GetMemberByIdReferencedDescendantsData, GetMemberByIdReferencedDescendantsResponses, GetMemberByIdReferencedDescendantsErrors, PutMemberByIdValidateData, PutMemberByIdValidateResponses, PutMemberByIdValidateErrors, GetMemberAreReferencedData, GetMemberAreReferencedResponses, GetMemberAreReferencedErrors, GetMemberConfigurationData, GetMemberConfigurationResponses, GetMemberConfigurationErrors, PostMemberValidateData, PostMemberValidateResponses, PostMemberValidateErrors, PostModelsBuilderBuildData, PostModelsBuilderBuildResponses, PostModelsBuilderBuildErrors, GetModelsBuilderDashboardData, GetModelsBuilderDashboardResponses, GetModelsBuilderDashboardErrors, GetModelsBuilderStatusData, GetModelsBuilderStatusResponses, GetModelsBuilderStatusErrors, GetObjectTypesData, GetObjectTypesResponses, GetObjectTypesErrors, GetOembedQueryData, GetOembedQueryResponses, GetOembedQueryErrors, PostPackageByNameRunMigrationData, PostPackageByNameRunMigrationResponses, PostPackageByNameRunMigrationErrors, GetPackageConfigurationData, GetPackageConfigurationResponses, GetPackageConfigurationErrors, GetPackageCreatedData, GetPackageCreatedResponses, GetPackageCreatedErrors, PostPackageCreatedData, PostPackageCreatedResponses, PostPackageCreatedErrors, DeletePackageCreatedByIdData, DeletePackageCreatedByIdResponses, DeletePackageCreatedByIdErrors, GetPackageCreatedByIdData, GetPackageCreatedByIdResponses, GetPackageCreatedByIdErrors, PutPackageCreatedByIdData, PutPackageCreatedByIdResponses, PutPackageCreatedByIdErrors, GetPackageCreatedByIdDownloadData, GetPackageCreatedByIdDownloadResponses, GetPackageCreatedByIdDownloadErrors, GetPackageMigrationStatusData, GetPackageMigrationStatusResponses, GetPackageMigrationStatusErrors, GetItemPartialViewData, GetItemPartialViewResponses, GetItemPartialViewErrors, PostPartialViewData, PostPartialViewResponses, PostPartialViewErrors, DeletePartialViewByPathData, DeletePartialViewByPathResponses, DeletePartialViewByPathErrors, GetPartialViewByPathData, GetPartialViewByPathResponses, GetPartialViewByPathErrors, PutPartialViewByPathData, PutPartialViewByPathResponses, PutPartialViewByPathErrors, PutPartialViewByPathRenameData, PutPartialViewByPathRenameResponses, PutPartialViewByPathRenameErrors, PostPartialViewFolderData, PostPartialViewFolderResponses, PostPartialViewFolderErrors, DeletePartialViewFolderByPathData, DeletePartialViewFolderByPathResponses, DeletePartialViewFolderByPathErrors, GetPartialViewFolderByPathData, GetPartialViewFolderByPathResponses, GetPartialViewFolderByPathErrors, GetPartialViewSnippetData, GetPartialViewSnippetResponses, GetPartialViewSnippetErrors, GetPartialViewSnippetByIdData, GetPartialViewSnippetByIdResponses, GetPartialViewSnippetByIdErrors, GetTreePartialViewAncestorsData, GetTreePartialViewAncestorsResponses, GetTreePartialViewAncestorsErrors, GetTreePartialViewChildrenData, GetTreePartialViewChildrenResponses, GetTreePartialViewChildrenErrors, GetTreePartialViewRootData, GetTreePartialViewRootResponses, GetTreePartialViewRootErrors, DeletePreviewData, DeletePreviewResponses, PostPreviewData, PostPreviewResponses, PostPreviewErrors, GetProfilingStatusData, GetProfilingStatusResponses, GetProfilingStatusErrors, PutProfilingStatusData, PutProfilingStatusResponses, PutProfilingStatusErrors, GetPropertyTypeIsUsedData, GetPropertyTypeIsUsedResponses, GetPropertyTypeIsUsedErrors, PostPublishedCacheRebuildData, PostPublishedCacheRebuildResponses, PostPublishedCacheRebuildErrors, GetPublishedCacheRebuildStatusData, GetPublishedCacheRebuildStatusResponses, GetPublishedCacheRebuildStatusErrors, PostPublishedCacheReloadData, PostPublishedCacheReloadResponses, PostPublishedCacheReloadErrors, GetRedirectManagementData, GetRedirectManagementResponses, GetRedirectManagementErrors, DeleteRedirectManagementByIdData, DeleteRedirectManagementByIdResponses, DeleteRedirectManagementByIdErrors, GetRedirectManagementByIdData, GetRedirectManagementByIdResponses, GetRedirectManagementByIdErrors, GetRedirectManagementStatusData, GetRedirectManagementStatusResponses, GetRedirectManagementStatusErrors, PostRedirectManagementStatusData, PostRedirectManagementStatusResponses, PostRedirectManagementStatusErrors, GetItemRelationTypeData, GetItemRelationTypeResponses, GetItemRelationTypeErrors, GetRelationTypeData, GetRelationTypeResponses, GetRelationTypeErrors, GetRelationTypeByIdData, GetRelationTypeByIdResponses, GetRelationTypeByIdErrors, GetRelationByRelationTypeIdData, GetRelationByRelationTypeIdResponses, GetRelationByRelationTypeIdErrors, GetItemScriptData, GetItemScriptResponses, GetItemScriptErrors, PostScriptData, PostScriptResponses, PostScriptErrors, DeleteScriptByPathData, DeleteScriptByPathResponses, DeleteScriptByPathErrors, GetScriptByPathData, GetScriptByPathResponses, GetScriptByPathErrors, PutScriptByPathData, PutScriptByPathResponses, PutScriptByPathErrors, PutScriptByPathRenameData, PutScriptByPathRenameResponses, PutScriptByPathRenameErrors, PostScriptFolderData, PostScriptFolderResponses, PostScriptFolderErrors, DeleteScriptFolderByPathData, DeleteScriptFolderByPathResponses, DeleteScriptFolderByPathErrors, GetScriptFolderByPathData, GetScriptFolderByPathResponses, GetScriptFolderByPathErrors, GetTreeScriptAncestorsData, GetTreeScriptAncestorsResponses, GetTreeScriptAncestorsErrors, GetTreeScriptChildrenData, GetTreeScriptChildrenResponses, GetTreeScriptChildrenErrors, GetTreeScriptRootData, GetTreeScriptRootResponses, GetTreeScriptRootErrors, GetSearcherData, GetSearcherResponses, GetSearcherErrors, GetSearcherBySearcherNameQueryData, GetSearcherBySearcherNameQueryResponses, GetSearcherBySearcherNameQueryErrors, GetSecurityConfigurationData, GetSecurityConfigurationResponses, GetSecurityConfigurationErrors, PostSecurityForgotPasswordData, PostSecurityForgotPasswordResponses, PostSecurityForgotPasswordErrors, PostSecurityForgotPasswordResetData, PostSecurityForgotPasswordResetResponses, PostSecurityForgotPasswordResetErrors, PostSecurityForgotPasswordVerifyData, PostSecurityForgotPasswordVerifyResponses, PostSecurityForgotPasswordVerifyErrors, GetSegmentData, GetSegmentResponses, GetSegmentErrors, GetServerConfigurationData, GetServerConfigurationResponses, GetServerInformationData, GetServerInformationResponses, GetServerInformationErrors, GetServerStatusData, GetServerStatusResponses, GetServerStatusErrors, GetServerTroubleshootingData, GetServerTroubleshootingResponses, GetServerTroubleshootingErrors, GetServerUpgradeCheckData, GetServerUpgradeCheckResponses, GetServerUpgradeCheckErrors, GetItemStaticFileData, GetItemStaticFileResponses, GetItemStaticFileErrors, GetTreeStaticFileAncestorsData, GetTreeStaticFileAncestorsResponses, GetTreeStaticFileAncestorsErrors, GetTreeStaticFileChildrenData, GetTreeStaticFileChildrenResponses, GetTreeStaticFileChildrenErrors, GetTreeStaticFileRootData, GetTreeStaticFileRootResponses, GetTreeStaticFileRootErrors, GetItemStylesheetData, GetItemStylesheetResponses, GetItemStylesheetErrors, PostStylesheetData, PostStylesheetResponses, PostStylesheetErrors, DeleteStylesheetByPathData, DeleteStylesheetByPathResponses, DeleteStylesheetByPathErrors, GetStylesheetByPathData, GetStylesheetByPathResponses, GetStylesheetByPathErrors, PutStylesheetByPathData, PutStylesheetByPathResponses, PutStylesheetByPathErrors, PutStylesheetByPathRenameData, PutStylesheetByPathRenameResponses, PutStylesheetByPathRenameErrors, PostStylesheetFolderData, PostStylesheetFolderResponses, PostStylesheetFolderErrors, DeleteStylesheetFolderByPathData, DeleteStylesheetFolderByPathResponses, DeleteStylesheetFolderByPathErrors, GetStylesheetFolderByPathData, GetStylesheetFolderByPathResponses, GetStylesheetFolderByPathErrors, GetTreeStylesheetAncestorsData, GetTreeStylesheetAncestorsResponses, GetTreeStylesheetAncestorsErrors, GetTreeStylesheetChildrenData, GetTreeStylesheetChildrenResponses, GetTreeStylesheetChildrenErrors, GetTreeStylesheetRootData, GetTreeStylesheetRootResponses, GetTreeStylesheetRootErrors, GetTagData, GetTagResponses, GetTagErrors, GetTelemetryData, GetTelemetryResponses, GetTelemetryErrors, GetTelemetryLevelData, GetTelemetryLevelResponses, GetTelemetryLevelErrors, PostTelemetryLevelData, PostTelemetryLevelResponses, PostTelemetryLevelErrors, GetItemTemplateData, GetItemTemplateResponses, GetItemTemplateErrors, GetItemTemplateSearchData, GetItemTemplateSearchResponses, GetItemTemplateSearchErrors, PostTemplateData, PostTemplateResponses, PostTemplateErrors, DeleteTemplateByIdData, DeleteTemplateByIdResponses, DeleteTemplateByIdErrors, GetTemplateByIdData, GetTemplateByIdResponses, GetTemplateByIdErrors, PutTemplateByIdData, PutTemplateByIdResponses, PutTemplateByIdErrors, GetTemplateConfigurationData, GetTemplateConfigurationResponses, GetTemplateConfigurationErrors, PostTemplateQueryExecuteData, PostTemplateQueryExecuteResponses, PostTemplateQueryExecuteErrors, GetTemplateQuerySettingsData, GetTemplateQuerySettingsResponses, GetTemplateQuerySettingsErrors, GetTreeTemplateAncestorsData, GetTreeTemplateAncestorsResponses, GetTreeTemplateAncestorsErrors, GetTreeTemplateChildrenData, GetTreeTemplateChildrenResponses, GetTreeTemplateChildrenErrors, GetTreeTemplateRootData, GetTreeTemplateRootResponses, GetTreeTemplateRootErrors, PostTemporaryFileData, PostTemporaryFileResponses, PostTemporaryFileErrors, DeleteTemporaryFileByIdData, DeleteTemporaryFileByIdResponses, DeleteTemporaryFileByIdErrors, GetTemporaryFileByIdData, GetTemporaryFileByIdResponses, GetTemporaryFileByIdErrors, GetTemporaryFileConfigurationData, GetTemporaryFileConfigurationResponses, GetTemporaryFileConfigurationErrors, PostUpgradeAuthorizeData, PostUpgradeAuthorizeResponses, PostUpgradeAuthorizeErrors, GetUpgradeSettingsData, GetUpgradeSettingsResponses, GetUpgradeSettingsErrors, GetUserDataData, GetUserDataResponses, GetUserDataErrors, PostUserDataData, PostUserDataResponses, PostUserDataErrors, PutUserDataData, PutUserDataResponses, PutUserDataErrors, GetUserDataByIdData, GetUserDataByIdResponses, GetUserDataByIdErrors, GetFilterUserGroupData, GetFilterUserGroupResponses, GetFilterUserGroupErrors, GetItemUserGroupData, GetItemUserGroupResponses, GetItemUserGroupErrors, DeleteUserGroupData, DeleteUserGroupResponses, DeleteUserGroupErrors, GetUserGroupData, GetUserGroupResponses, GetUserGroupErrors, PostUserGroupData, PostUserGroupResponses, PostUserGroupErrors, DeleteUserGroupByIdData, DeleteUserGroupByIdResponses, DeleteUserGroupByIdErrors, GetUserGroupByIdData, GetUserGroupByIdResponses, GetUserGroupByIdErrors, PutUserGroupByIdData, PutUserGroupByIdResponses, PutUserGroupByIdErrors, DeleteUserGroupByIdUsersData, DeleteUserGroupByIdUsersResponses, DeleteUserGroupByIdUsersErrors, PostUserGroupByIdUsersData, PostUserGroupByIdUsersResponses, PostUserGroupByIdUsersErrors, GetFilterUserData, GetFilterUserResponses, GetFilterUserErrors, GetItemUserData, GetItemUserResponses, GetItemUserErrors, DeleteUserData, DeleteUserResponses, DeleteUserErrors, GetUserData, GetUserResponses, GetUserErrors, PostUserData, PostUserResponses, PostUserErrors, DeleteUserByIdData, DeleteUserByIdResponses, DeleteUserByIdErrors, GetUserByIdData, GetUserByIdResponses, GetUserByIdErrors, PutUserByIdData, PutUserByIdResponses, PutUserByIdErrors, GetUserById2FaData, GetUserById2FaResponses, GetUserById2FaErrors, DeleteUserById2FaByProviderNameData, DeleteUserById2FaByProviderNameResponses, DeleteUserById2FaByProviderNameErrors, GetUserByIdCalculateStartNodesData, GetUserByIdCalculateStartNodesResponses, GetUserByIdCalculateStartNodesErrors, PostUserByIdChangePasswordData, PostUserByIdChangePasswordResponses, PostUserByIdChangePasswordErrors, GetUserByIdClientCredentialsData, GetUserByIdClientCredentialsResponses, GetUserByIdClientCredentialsErrors, PostUserByIdClientCredentialsData, PostUserByIdClientCredentialsResponses, PostUserByIdClientCredentialsErrors, DeleteUserByIdClientCredentialsByClientIdData, DeleteUserByIdClientCredentialsByClientIdResponses, DeleteUserByIdClientCredentialsByClientIdErrors, PostUserByIdResetPasswordData, PostUserByIdResetPasswordResponses, PostUserByIdResetPasswordErrors, DeleteUserAvatarByIdData, DeleteUserAvatarByIdResponses, DeleteUserAvatarByIdErrors, PostUserAvatarByIdData, PostUserAvatarByIdResponses, PostUserAvatarByIdErrors, GetUserConfigurationData, GetUserConfigurationResponses, GetUserConfigurationErrors, GetUserCurrentData, GetUserCurrentResponses, GetUserCurrentErrors, GetUserCurrent2FaData, GetUserCurrent2FaResponses, GetUserCurrent2FaErrors, DeleteUserCurrent2FaByProviderNameData, DeleteUserCurrent2FaByProviderNameResponses, DeleteUserCurrent2FaByProviderNameErrors, GetUserCurrent2FaByProviderNameData, GetUserCurrent2FaByProviderNameResponses, GetUserCurrent2FaByProviderNameErrors, PostUserCurrent2FaByProviderNameData, PostUserCurrent2FaByProviderNameResponses, PostUserCurrent2FaByProviderNameErrors, PostUserCurrentAvatarData, PostUserCurrentAvatarResponses, PostUserCurrentAvatarErrors, PostUserCurrentChangePasswordData, PostUserCurrentChangePasswordResponses, PostUserCurrentChangePasswordErrors, GetUserCurrentConfigurationData, GetUserCurrentConfigurationResponses, GetUserCurrentConfigurationErrors, GetUserCurrentLoginProvidersData, GetUserCurrentLoginProvidersResponses, GetUserCurrentLoginProvidersErrors, GetUserCurrentPermissionsData, GetUserCurrentPermissionsResponses, GetUserCurrentPermissionsErrors, GetUserCurrentPermissionsDocumentData, GetUserCurrentPermissionsDocumentResponses, GetUserCurrentPermissionsDocumentErrors, GetUserCurrentPermissionsMediaData, GetUserCurrentPermissionsMediaResponses, GetUserCurrentPermissionsMediaErrors, PostUserDisableData, PostUserDisableResponses, PostUserDisableErrors, PostUserEnableData, PostUserEnableResponses, PostUserEnableErrors, PostUserInviteData, PostUserInviteResponses, PostUserInviteErrors, PostUserInviteCreatePasswordData, PostUserInviteCreatePasswordResponses, PostUserInviteCreatePasswordErrors, PostUserInviteResendData, PostUserInviteResendResponses, PostUserInviteResendErrors, PostUserInviteVerifyData, PostUserInviteVerifyResponses, PostUserInviteVerifyErrors, PostUserSetUserGroupsData, PostUserSetUserGroupsResponses, PostUserSetUserGroupsErrors, PostUserUnlockData, PostUserUnlockResponses, PostUserUnlockErrors, GetItemWebhookData, GetItemWebhookResponses, GetItemWebhookErrors, GetWebhookData, GetWebhookResponses, GetWebhookErrors, PostWebhookData, PostWebhookResponses, PostWebhookErrors, DeleteWebhookByIdData, DeleteWebhookByIdResponses, DeleteWebhookByIdErrors, GetWebhookByIdData, GetWebhookByIdResponses, GetWebhookByIdErrors, PutWebhookByIdData, PutWebhookByIdResponses, PutWebhookByIdErrors, GetWebhookByIdLogsData, GetWebhookByIdLogsResponses, GetWebhookByIdLogsErrors, GetWebhookEventsData, GetWebhookEventsResponses, GetWebhookEventsErrors, GetWebhookLogsData, GetWebhookLogsResponses, GetWebhookLogsErrors } from './types.gen'; import { client as _heyApiClient } from './client.gen'; export type Options = ClientOptions & { @@ -20,7 +20,7 @@ export type Options(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -31,12 +31,11 @@ export class CultureService { ...options }); } - } export class DataTypeService { public static postDataType(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -53,7 +52,7 @@ export class DataTypeService { } public static deleteDataTypeById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -66,7 +65,7 @@ export class DataTypeService { } public static getDataTypeById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -79,7 +78,7 @@ export class DataTypeService { } public static putDataTypeById(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -90,13 +89,13 @@ export class DataTypeService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static postDataTypeByIdCopy(options: Options) { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -107,13 +106,13 @@ export class DataTypeService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getDataTypeByIdIsUsed(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -126,7 +125,7 @@ export class DataTypeService { } public static putDataTypeByIdMove(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -137,13 +136,13 @@ export class DataTypeService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getDataTypeByIdReferencedBy(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -159,7 +158,7 @@ export class DataTypeService { * @deprecated */ public static getDataTypeByIdReferences(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -172,7 +171,7 @@ export class DataTypeService { } public static getDataTypeConfiguration(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -185,7 +184,7 @@ export class DataTypeService { } public static postDataTypeFolder(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -202,7 +201,7 @@ export class DataTypeService { } public static deleteDataTypeFolderById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -215,7 +214,7 @@ export class DataTypeService { } public static getDataTypeFolderById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -228,7 +227,7 @@ export class DataTypeService { } public static putDataTypeFolderById(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -239,13 +238,13 @@ export class DataTypeService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getFilterDataType(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -258,7 +257,7 @@ export class DataTypeService { } public static getItemDataType(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -271,7 +270,7 @@ export class DataTypeService { } public static getItemDataTypeSearch(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -284,7 +283,7 @@ export class DataTypeService { } public static getTreeDataTypeAncestors(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -297,7 +296,7 @@ export class DataTypeService { } public static getTreeDataTypeChildren(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -310,7 +309,7 @@ export class DataTypeService { } public static getTreeDataTypeRoot(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -321,12 +320,11 @@ export class DataTypeService { ...options }); } - } export class DictionaryService { public static getDictionary(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -339,7 +337,7 @@ export class DictionaryService { } public static postDictionary(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -356,7 +354,7 @@ export class DictionaryService { } public static deleteDictionaryById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -369,7 +367,7 @@ export class DictionaryService { } public static getDictionaryById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -382,7 +380,7 @@ export class DictionaryService { } public static putDictionaryById(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -393,13 +391,13 @@ export class DictionaryService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getDictionaryByIdExport(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -412,7 +410,7 @@ export class DictionaryService { } public static putDictionaryByIdMove(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -423,13 +421,13 @@ export class DictionaryService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static postDictionaryImport(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -446,7 +444,7 @@ export class DictionaryService { } public static getItemDictionary(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -459,7 +457,7 @@ export class DictionaryService { } public static getTreeDictionaryAncestors(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -472,7 +470,7 @@ export class DictionaryService { } public static getTreeDictionaryChildren(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -485,7 +483,7 @@ export class DictionaryService { } public static getTreeDictionaryRoot(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -496,12 +494,11 @@ export class DictionaryService { ...options }); } - } export class DocumentBlueprintService { public static postDocumentBlueprint(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -518,7 +515,7 @@ export class DocumentBlueprintService { } public static deleteDocumentBlueprintById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -531,7 +528,7 @@ export class DocumentBlueprintService { } public static getDocumentBlueprintById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -544,7 +541,7 @@ export class DocumentBlueprintService { } public static putDocumentBlueprintById(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -555,13 +552,13 @@ export class DocumentBlueprintService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static putDocumentBlueprintByIdMove(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -572,13 +569,13 @@ export class DocumentBlueprintService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getDocumentBlueprintByIdScaffold(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -591,7 +588,7 @@ export class DocumentBlueprintService { } public static postDocumentBlueprintFolder(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -608,7 +605,7 @@ export class DocumentBlueprintService { } public static deleteDocumentBlueprintFolderById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -621,7 +618,7 @@ export class DocumentBlueprintService { } public static getDocumentBlueprintFolderById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -634,7 +631,7 @@ export class DocumentBlueprintService { } public static putDocumentBlueprintFolderById(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -645,13 +642,13 @@ export class DocumentBlueprintService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static postDocumentBlueprintFromDocument(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -668,7 +665,7 @@ export class DocumentBlueprintService { } public static getItemDocumentBlueprint(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -681,7 +678,7 @@ export class DocumentBlueprintService { } public static getTreeDocumentBlueprintAncestors(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -694,7 +691,7 @@ export class DocumentBlueprintService { } public static getTreeDocumentBlueprintChildren(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -707,7 +704,7 @@ export class DocumentBlueprintService { } public static getTreeDocumentBlueprintRoot(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -718,12 +715,11 @@ export class DocumentBlueprintService { ...options }); } - } export class DocumentTypeService { public static postDocumentType(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -740,7 +736,7 @@ export class DocumentTypeService { } public static deleteDocumentTypeById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -753,7 +749,7 @@ export class DocumentTypeService { } public static getDocumentTypeById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -766,7 +762,7 @@ export class DocumentTypeService { } public static putDocumentTypeById(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -777,13 +773,13 @@ export class DocumentTypeService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getDocumentTypeByIdAllowedChildren(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -796,7 +792,7 @@ export class DocumentTypeService { } public static getDocumentTypeByIdBlueprint(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -809,7 +805,7 @@ export class DocumentTypeService { } public static getDocumentTypeByIdCompositionReferences(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -822,7 +818,7 @@ export class DocumentTypeService { } public static postDocumentTypeByIdCopy(options: Options) { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -833,13 +829,13 @@ export class DocumentTypeService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getDocumentTypeByIdExport(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -852,7 +848,7 @@ export class DocumentTypeService { } public static putDocumentTypeByIdImport(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -863,13 +859,13 @@ export class DocumentTypeService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static putDocumentTypeByIdMove(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -880,13 +876,13 @@ export class DocumentTypeService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getDocumentTypeAllowedAtRoot(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -899,7 +895,7 @@ export class DocumentTypeService { } public static postDocumentTypeAvailableCompositions(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -916,7 +912,7 @@ export class DocumentTypeService { } public static getDocumentTypeConfiguration(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -929,7 +925,7 @@ export class DocumentTypeService { } public static postDocumentTypeFolder(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -946,7 +942,7 @@ export class DocumentTypeService { } public static deleteDocumentTypeFolderById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -959,7 +955,7 @@ export class DocumentTypeService { } public static getDocumentTypeFolderById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -972,7 +968,7 @@ export class DocumentTypeService { } public static putDocumentTypeFolderById(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -983,13 +979,13 @@ export class DocumentTypeService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static postDocumentTypeImport(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -1006,7 +1002,7 @@ export class DocumentTypeService { } public static getItemDocumentType(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1019,7 +1015,7 @@ export class DocumentTypeService { } public static getItemDocumentTypeSearch(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1032,7 +1028,7 @@ export class DocumentTypeService { } public static getTreeDocumentTypeAncestors(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1045,7 +1041,7 @@ export class DocumentTypeService { } public static getTreeDocumentTypeChildren(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1058,7 +1054,7 @@ export class DocumentTypeService { } public static getTreeDocumentTypeRoot(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1069,12 +1065,11 @@ export class DocumentTypeService { ...options }); } - } export class DocumentVersionService { public static getDocumentVersion(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1087,7 +1082,7 @@ export class DocumentVersionService { } public static getDocumentVersionById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1100,7 +1095,7 @@ export class DocumentVersionService { } public static putDocumentVersionByIdPreventCleanup(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -1113,7 +1108,7 @@ export class DocumentVersionService { } public static postDocumentVersionByIdRollback(options: Options) { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -1124,12 +1119,11 @@ export class DocumentVersionService { ...options }); } - } export class DocumentService { public static getCollectionDocumentById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1142,7 +1136,7 @@ export class DocumentService { } public static postDocument(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -1159,7 +1153,7 @@ export class DocumentService { } public static deleteDocumentById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -1172,7 +1166,7 @@ export class DocumentService { } public static getDocumentById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1185,7 +1179,7 @@ export class DocumentService { } public static putDocumentById(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -1196,13 +1190,13 @@ export class DocumentService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getDocumentByIdAuditLog(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1215,7 +1209,7 @@ export class DocumentService { } public static postDocumentByIdCopy(options: Options) { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -1226,13 +1220,13 @@ export class DocumentService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getDocumentByIdDomains(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1245,7 +1239,7 @@ export class DocumentService { } public static putDocumentByIdDomains(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -1256,13 +1250,13 @@ export class DocumentService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static putDocumentByIdMove(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -1273,13 +1267,13 @@ export class DocumentService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static putDocumentByIdMoveToRecycleBin(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -1292,7 +1286,7 @@ export class DocumentService { } public static getDocumentByIdNotifications(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1305,7 +1299,7 @@ export class DocumentService { } public static putDocumentByIdNotifications(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -1316,13 +1310,13 @@ export class DocumentService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static deleteDocumentByIdPublicAccess(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -1335,7 +1329,7 @@ export class DocumentService { } public static getDocumentByIdPublicAccess(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1348,7 +1342,7 @@ export class DocumentService { } public static postDocumentByIdPublicAccess(options: Options) { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -1359,13 +1353,13 @@ export class DocumentService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static putDocumentByIdPublicAccess(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -1376,13 +1370,13 @@ export class DocumentService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static putDocumentByIdPublish(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -1393,13 +1387,13 @@ export class DocumentService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static putDocumentByIdPublishWithDescendants(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -1410,13 +1404,13 @@ export class DocumentService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getDocumentByIdPublishWithDescendantsResultByTaskId(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1429,7 +1423,7 @@ export class DocumentService { } public static getDocumentByIdPublished(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1442,7 +1436,7 @@ export class DocumentService { } public static getDocumentByIdReferencedBy(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1455,7 +1449,7 @@ export class DocumentService { } public static getDocumentByIdReferencedDescendants(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1468,7 +1462,7 @@ export class DocumentService { } public static putDocumentByIdUnpublish(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -1479,13 +1473,13 @@ export class DocumentService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static putUmbracoManagementApiV11DocumentByIdValidate11(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -1496,13 +1490,13 @@ export class DocumentService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getDocumentAreReferenced(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1515,7 +1509,7 @@ export class DocumentService { } public static getDocumentConfiguration(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1528,7 +1522,7 @@ export class DocumentService { } public static putDocumentSort(options?: Options) { - return (options?.client ?? _heyApiClient).put({ + return (options?.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -1545,7 +1539,7 @@ export class DocumentService { } public static getDocumentUrls(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1558,7 +1552,7 @@ export class DocumentService { } public static postDocumentValidate(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -1575,7 +1569,7 @@ export class DocumentService { } public static getItemDocument(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1588,7 +1582,7 @@ export class DocumentService { } public static getItemDocumentSearch(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1601,7 +1595,7 @@ export class DocumentService { } public static deleteRecycleBinDocument(options?: Options) { - return (options?.client ?? _heyApiClient).delete({ + return (options?.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -1614,7 +1608,7 @@ export class DocumentService { } public static deleteRecycleBinDocumentById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -1627,7 +1621,7 @@ export class DocumentService { } public static getRecycleBinDocumentByIdOriginalParent(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1640,7 +1634,7 @@ export class DocumentService { } public static putRecycleBinDocumentByIdRestore(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -1651,13 +1645,13 @@ export class DocumentService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getRecycleBinDocumentChildren(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1670,7 +1664,7 @@ export class DocumentService { } public static getRecycleBinDocumentReferencedBy(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1683,7 +1677,7 @@ export class DocumentService { } public static getRecycleBinDocumentRoot(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1696,7 +1690,7 @@ export class DocumentService { } public static getTreeDocumentAncestors(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1709,7 +1703,7 @@ export class DocumentService { } public static getTreeDocumentChildren(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1722,7 +1716,7 @@ export class DocumentService { } public static getTreeDocumentRoot(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1733,12 +1727,11 @@ export class DocumentService { ...options }); } - } export class DynamicRootService { public static postDynamicRootQuery(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -1755,7 +1748,7 @@ export class DynamicRootService { } public static getDynamicRootSteps(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1766,12 +1759,11 @@ export class DynamicRootService { ...options }); } - } export class HealthCheckService { public static getHealthCheckGroup(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1784,7 +1776,7 @@ export class HealthCheckService { } public static getHealthCheckGroupByName(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1797,7 +1789,7 @@ export class HealthCheckService { } public static postHealthCheckGroupByNameCheck(options: Options) { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -1810,7 +1802,7 @@ export class HealthCheckService { } public static postHealthCheckExecuteAction(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -1825,12 +1817,11 @@ export class HealthCheckService { } }); } - } export class HelpService { public static getHelp(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1841,12 +1832,11 @@ export class HelpService { ...options }); } - } export class ImagingService { public static getImagingResizeUrls(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1857,12 +1847,11 @@ export class ImagingService { ...options }); } - } export class ImportService { public static getImportAnalyze(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1873,12 +1862,11 @@ export class ImportService { ...options }); } - } export class IndexerService { public static getIndexer(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1891,7 +1879,7 @@ export class IndexerService { } public static getIndexerByIndexName(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1904,7 +1892,7 @@ export class IndexerService { } public static postIndexerByIndexNameRebuild(options: Options) { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -1915,19 +1903,18 @@ export class IndexerService { ...options }); } - } export class InstallService { public static getInstallSettings(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ url: '/umbraco/management/api/v1/install/settings', ...options }); } public static postInstallSetup(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ url: '/umbraco/management/api/v1/install/setup', ...options, headers: { @@ -1938,7 +1925,7 @@ export class InstallService { } public static postInstallValidateDatabase(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ url: '/umbraco/management/api/v1/install/validate-database', ...options, headers: { @@ -1947,12 +1934,11 @@ export class InstallService { } }); } - } export class LanguageService { public static getItemLanguage(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1965,7 +1951,7 @@ export class LanguageService { } public static getItemLanguageDefault(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1978,7 +1964,7 @@ export class LanguageService { } public static getLanguage(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -1991,7 +1977,7 @@ export class LanguageService { } public static postLanguage(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -2008,7 +1994,7 @@ export class LanguageService { } public static deleteLanguageByIsoCode(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -2021,7 +2007,7 @@ export class LanguageService { } public static getLanguageByIsoCode(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2034,7 +2020,7 @@ export class LanguageService { } public static putLanguageByIsoCode(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -2045,16 +2031,15 @@ export class LanguageService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } - } export class LogViewerService { public static getLogViewerLevel(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2067,7 +2052,7 @@ export class LogViewerService { } public static getLogViewerLevelCount(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2080,7 +2065,7 @@ export class LogViewerService { } public static getLogViewerLog(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2093,7 +2078,7 @@ export class LogViewerService { } public static getLogViewerMessageTemplate(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2106,7 +2091,7 @@ export class LogViewerService { } public static getLogViewerSavedSearch(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2119,7 +2104,7 @@ export class LogViewerService { } public static postLogViewerSavedSearch(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -2136,7 +2121,7 @@ export class LogViewerService { } public static deleteLogViewerSavedSearchByName(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -2149,7 +2134,7 @@ export class LogViewerService { } public static getLogViewerSavedSearchByName(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2162,7 +2147,7 @@ export class LogViewerService { } public static getLogViewerValidateLogsSize(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2173,12 +2158,11 @@ export class LogViewerService { ...options }); } - } export class ManifestService { public static getManifestManifest(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2191,7 +2175,7 @@ export class ManifestService { } public static getManifestManifestPrivate(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2204,17 +2188,16 @@ export class ManifestService { } public static getManifestManifestPublic(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ url: '/umbraco/management/api/v1/manifest/manifest/public', ...options }); } - } export class MediaTypeService { public static getItemMediaType(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2227,7 +2210,7 @@ export class MediaTypeService { } public static getItemMediaTypeAllowed(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2240,7 +2223,7 @@ export class MediaTypeService { } public static getItemMediaTypeFolders(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2253,7 +2236,7 @@ export class MediaTypeService { } public static getItemMediaTypeSearch(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2266,7 +2249,7 @@ export class MediaTypeService { } public static postMediaType(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -2283,7 +2266,7 @@ export class MediaTypeService { } public static deleteMediaTypeById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -2296,7 +2279,7 @@ export class MediaTypeService { } public static getMediaTypeById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2309,7 +2292,7 @@ export class MediaTypeService { } public static putMediaTypeById(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -2320,13 +2303,13 @@ export class MediaTypeService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getMediaTypeByIdAllowedChildren(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2339,7 +2322,7 @@ export class MediaTypeService { } public static getMediaTypeByIdCompositionReferences(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2352,7 +2335,7 @@ export class MediaTypeService { } public static postMediaTypeByIdCopy(options: Options) { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -2363,13 +2346,13 @@ export class MediaTypeService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getMediaTypeByIdExport(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2382,7 +2365,7 @@ export class MediaTypeService { } public static putMediaTypeByIdImport(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -2393,13 +2376,13 @@ export class MediaTypeService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static putMediaTypeByIdMove(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -2410,13 +2393,13 @@ export class MediaTypeService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getMediaTypeAllowedAtRoot(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2429,7 +2412,7 @@ export class MediaTypeService { } public static postMediaTypeAvailableCompositions(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -2446,7 +2429,7 @@ export class MediaTypeService { } public static getMediaTypeConfiguration(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2459,7 +2442,7 @@ export class MediaTypeService { } public static postMediaTypeFolder(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -2476,7 +2459,7 @@ export class MediaTypeService { } public static deleteMediaTypeFolderById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -2489,7 +2472,7 @@ export class MediaTypeService { } public static getMediaTypeFolderById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2502,7 +2485,7 @@ export class MediaTypeService { } public static putMediaTypeFolderById(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -2513,13 +2496,13 @@ export class MediaTypeService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static postMediaTypeImport(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -2536,7 +2519,7 @@ export class MediaTypeService { } public static getTreeMediaTypeAncestors(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2549,7 +2532,7 @@ export class MediaTypeService { } public static getTreeMediaTypeChildren(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2562,7 +2545,7 @@ export class MediaTypeService { } public static getTreeMediaTypeRoot(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2573,12 +2556,11 @@ export class MediaTypeService { ...options }); } - } export class MediaService { public static getCollectionMedia(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2591,7 +2573,7 @@ export class MediaService { } public static getItemMedia(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2604,7 +2586,7 @@ export class MediaService { } public static getItemMediaSearch(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2617,7 +2599,7 @@ export class MediaService { } public static postMedia(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -2634,7 +2616,7 @@ export class MediaService { } public static deleteMediaById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -2647,7 +2629,7 @@ export class MediaService { } public static getMediaById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2660,7 +2642,7 @@ export class MediaService { } public static putMediaById(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -2671,13 +2653,13 @@ export class MediaService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getMediaByIdAuditLog(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2690,7 +2672,7 @@ export class MediaService { } public static putMediaByIdMove(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -2701,13 +2683,13 @@ export class MediaService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static putMediaByIdMoveToRecycleBin(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -2720,7 +2702,7 @@ export class MediaService { } public static getMediaByIdReferencedBy(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2733,7 +2715,7 @@ export class MediaService { } public static getMediaByIdReferencedDescendants(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2746,7 +2728,7 @@ export class MediaService { } public static putMediaByIdValidate(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -2757,13 +2739,13 @@ export class MediaService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getMediaAreReferenced(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2779,7 +2761,7 @@ export class MediaService { * @deprecated */ public static getMediaConfiguration(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2792,7 +2774,7 @@ export class MediaService { } public static putMediaSort(options?: Options) { - return (options?.client ?? _heyApiClient).put({ + return (options?.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -2809,7 +2791,7 @@ export class MediaService { } public static getMediaUrls(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2822,7 +2804,7 @@ export class MediaService { } public static postMediaValidate(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -2839,7 +2821,7 @@ export class MediaService { } public static deleteRecycleBinMedia(options?: Options) { - return (options?.client ?? _heyApiClient).delete({ + return (options?.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -2852,7 +2834,7 @@ export class MediaService { } public static deleteRecycleBinMediaById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -2865,7 +2847,7 @@ export class MediaService { } public static getRecycleBinMediaByIdOriginalParent(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2878,7 +2860,7 @@ export class MediaService { } public static putRecycleBinMediaByIdRestore(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -2889,13 +2871,13 @@ export class MediaService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getRecycleBinMediaChildren(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2908,7 +2890,7 @@ export class MediaService { } public static getRecycleBinMediaReferencedBy(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2921,7 +2903,7 @@ export class MediaService { } public static getRecycleBinMediaRoot(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2934,7 +2916,7 @@ export class MediaService { } public static getTreeMediaAncestors(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2947,7 +2929,7 @@ export class MediaService { } public static getTreeMediaChildren(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2960,7 +2942,7 @@ export class MediaService { } public static getTreeMediaRoot(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2971,12 +2953,11 @@ export class MediaService { ...options }); } - } export class MemberGroupService { public static getItemMemberGroup(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -2989,7 +2970,7 @@ export class MemberGroupService { } public static getMemberGroup(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3002,7 +2983,7 @@ export class MemberGroupService { } public static postMemberGroup(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -3019,7 +3000,7 @@ export class MemberGroupService { } public static deleteMemberGroupById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -3032,7 +3013,7 @@ export class MemberGroupService { } public static getMemberGroupById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3045,7 +3026,7 @@ export class MemberGroupService { } public static putMemberGroupById(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -3056,13 +3037,13 @@ export class MemberGroupService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getTreeMemberGroupRoot(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3073,12 +3054,11 @@ export class MemberGroupService { ...options }); } - } export class MemberTypeService { public static getItemMemberType(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3091,7 +3071,7 @@ export class MemberTypeService { } public static getItemMemberTypeSearch(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3104,7 +3084,7 @@ export class MemberTypeService { } public static postMemberType(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -3121,7 +3101,7 @@ export class MemberTypeService { } public static deleteMemberTypeById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -3134,7 +3114,7 @@ export class MemberTypeService { } public static getMemberTypeById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3147,7 +3127,7 @@ export class MemberTypeService { } public static putMemberTypeById(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -3158,13 +3138,13 @@ export class MemberTypeService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getMemberTypeByIdCompositionReferences(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3177,7 +3157,7 @@ export class MemberTypeService { } public static postMemberTypeByIdCopy(options: Options) { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -3190,7 +3170,7 @@ export class MemberTypeService { } public static postMemberTypeAvailableCompositions(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -3207,7 +3187,7 @@ export class MemberTypeService { } public static getMemberTypeConfiguration(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3220,7 +3200,7 @@ export class MemberTypeService { } public static getTreeMemberTypeRoot(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3231,12 +3211,11 @@ export class MemberTypeService { ...options }); } - } export class MemberService { public static getFilterMember(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3249,7 +3228,7 @@ export class MemberService { } public static getItemMember(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3262,7 +3241,7 @@ export class MemberService { } public static getItemMemberSearch(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3275,7 +3254,7 @@ export class MemberService { } public static postMember(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -3292,7 +3271,7 @@ export class MemberService { } public static deleteMemberById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -3305,7 +3284,7 @@ export class MemberService { } public static getMemberById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3318,7 +3297,7 @@ export class MemberService { } public static putMemberById(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -3329,13 +3308,13 @@ export class MemberService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getMemberByIdReferencedBy(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3348,7 +3327,7 @@ export class MemberService { } public static getMemberByIdReferencedDescendants(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3361,7 +3340,7 @@ export class MemberService { } public static putMemberByIdValidate(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -3372,13 +3351,13 @@ export class MemberService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getMemberAreReferenced(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3391,7 +3370,7 @@ export class MemberService { } public static getMemberConfiguration(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3404,7 +3383,7 @@ export class MemberService { } public static postMemberValidate(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -3419,12 +3398,11 @@ export class MemberService { } }); } - } export class ModelsBuilderService { public static postModelsBuilderBuild(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -3437,7 +3415,7 @@ export class ModelsBuilderService { } public static getModelsBuilderDashboard(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3450,7 +3428,7 @@ export class ModelsBuilderService { } public static getModelsBuilderStatus(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3461,12 +3439,11 @@ export class ModelsBuilderService { ...options }); } - } export class ObjectTypesService { public static getObjectTypes(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3477,12 +3454,11 @@ export class ObjectTypesService { ...options }); } - } export class OEmbedService { public static getOembedQuery(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3493,12 +3469,11 @@ export class OEmbedService { ...options }); } - } export class PackageService { public static postPackageByNameRunMigration(options: Options) { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -3511,7 +3486,7 @@ export class PackageService { } public static getPackageConfiguration(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3524,7 +3499,7 @@ export class PackageService { } public static getPackageCreated(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3537,7 +3512,7 @@ export class PackageService { } public static postPackageCreated(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -3554,7 +3529,7 @@ export class PackageService { } public static deletePackageCreatedById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -3567,7 +3542,7 @@ export class PackageService { } public static getPackageCreatedById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3580,7 +3555,7 @@ export class PackageService { } public static putPackageCreatedById(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -3591,13 +3566,13 @@ export class PackageService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getPackageCreatedByIdDownload(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3610,7 +3585,7 @@ export class PackageService { } public static getPackageMigrationStatus(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3621,12 +3596,11 @@ export class PackageService { ...options }); } - } export class PartialViewService { public static getItemPartialView(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3639,7 +3613,7 @@ export class PartialViewService { } public static postPartialView(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -3656,7 +3630,7 @@ export class PartialViewService { } public static deletePartialViewByPath(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -3669,7 +3643,7 @@ export class PartialViewService { } public static getPartialViewByPath(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3682,7 +3656,7 @@ export class PartialViewService { } public static putPartialViewByPath(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -3693,13 +3667,13 @@ export class PartialViewService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static putPartialViewByPathRename(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -3710,13 +3684,13 @@ export class PartialViewService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static postPartialViewFolder(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -3733,7 +3707,7 @@ export class PartialViewService { } public static deletePartialViewFolderByPath(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -3746,7 +3720,7 @@ export class PartialViewService { } public static getPartialViewFolderByPath(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3759,7 +3733,7 @@ export class PartialViewService { } public static getPartialViewSnippet(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3772,7 +3746,7 @@ export class PartialViewService { } public static getPartialViewSnippetById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3785,7 +3759,7 @@ export class PartialViewService { } public static getTreePartialViewAncestors(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3798,7 +3772,7 @@ export class PartialViewService { } public static getTreePartialViewChildren(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3811,7 +3785,7 @@ export class PartialViewService { } public static getTreePartialViewRoot(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3822,19 +3796,18 @@ export class PartialViewService { ...options }); } - } export class PreviewService { public static deletePreview(options?: Options) { - return (options?.client ?? _heyApiClient).delete({ + return (options?.client ?? _heyApiClient).delete({ url: '/umbraco/management/api/v1/preview', ...options }); } public static postPreview(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -3845,12 +3818,11 @@ export class PreviewService { ...options }); } - } export class ProfilingService { public static getProfilingStatus(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3863,7 +3835,7 @@ export class ProfilingService { } public static putProfilingStatus(options?: Options) { - return (options?.client ?? _heyApiClient).put({ + return (options?.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -3878,12 +3850,11 @@ export class ProfilingService { } }); } - } export class PropertyTypeService { public static getPropertyTypeIsUsed(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3894,12 +3865,11 @@ export class PropertyTypeService { ...options }); } - } export class PublishedCacheService { public static postPublishedCacheRebuild(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -3912,7 +3882,7 @@ export class PublishedCacheService { } public static getPublishedCacheRebuildStatus(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3925,7 +3895,7 @@ export class PublishedCacheService { } public static postPublishedCacheReload(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -3936,12 +3906,11 @@ export class PublishedCacheService { ...options }); } - } export class RedirectManagementService { public static getRedirectManagement(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3954,7 +3923,7 @@ export class RedirectManagementService { } public static deleteRedirectManagementById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -3967,7 +3936,7 @@ export class RedirectManagementService { } public static getRedirectManagementById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3980,7 +3949,7 @@ export class RedirectManagementService { } public static getRedirectManagementStatus(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -3993,7 +3962,7 @@ export class RedirectManagementService { } public static postRedirectManagementStatus(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -4004,12 +3973,11 @@ export class RedirectManagementService { ...options }); } - } export class RelationTypeService { public static getItemRelationType(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4022,7 +3990,7 @@ export class RelationTypeService { } public static getRelationType(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4035,7 +4003,7 @@ export class RelationTypeService { } public static getRelationTypeById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4046,12 +4014,11 @@ export class RelationTypeService { ...options }); } - } export class RelationService { public static getRelationByRelationTypeId(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4062,12 +4029,11 @@ export class RelationService { ...options }); } - } export class ScriptService { public static getItemScript(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4080,7 +4046,7 @@ export class ScriptService { } public static postScript(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -4097,7 +4063,7 @@ export class ScriptService { } public static deleteScriptByPath(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -4110,7 +4076,7 @@ export class ScriptService { } public static getScriptByPath(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4123,7 +4089,7 @@ export class ScriptService { } public static putScriptByPath(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -4134,13 +4100,13 @@ export class ScriptService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static putScriptByPathRename(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -4151,13 +4117,13 @@ export class ScriptService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static postScriptFolder(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -4174,7 +4140,7 @@ export class ScriptService { } public static deleteScriptFolderByPath(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -4187,7 +4153,7 @@ export class ScriptService { } public static getScriptFolderByPath(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4200,7 +4166,7 @@ export class ScriptService { } public static getTreeScriptAncestors(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4213,7 +4179,7 @@ export class ScriptService { } public static getTreeScriptChildren(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4226,7 +4192,7 @@ export class ScriptService { } public static getTreeScriptRoot(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4237,12 +4203,11 @@ export class ScriptService { ...options }); } - } export class SearcherService { public static getSearcher(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4255,7 +4220,7 @@ export class SearcherService { } public static getSearcherBySearcherNameQuery(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4266,12 +4231,11 @@ export class SearcherService { ...options }); } - } export class SecurityService { public static getSecurityConfiguration(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4284,7 +4248,7 @@ export class SecurityService { } public static postSecurityForgotPassword(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -4301,7 +4265,7 @@ export class SecurityService { } public static postSecurityForgotPasswordReset(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -4318,7 +4282,7 @@ export class SecurityService { } public static postSecurityForgotPasswordVerify(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ url: '/umbraco/management/api/v1/security/forgot-password/verify', ...options, headers: { @@ -4327,12 +4291,11 @@ export class SecurityService { } }); } - } export class SegmentService { public static getSegment(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4343,19 +4306,18 @@ export class SegmentService { ...options }); } - } export class ServerService { public static getServerConfiguration(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ url: '/umbraco/management/api/v1/server/configuration', ...options }); } public static getServerInformation(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4368,14 +4330,14 @@ export class ServerService { } public static getServerStatus(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ url: '/umbraco/management/api/v1/server/status', ...options }); } public static getServerTroubleshooting(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4388,7 +4350,7 @@ export class ServerService { } public static getServerUpgradeCheck(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4399,12 +4361,11 @@ export class ServerService { ...options }); } - } export class StaticFileService { public static getItemStaticFile(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4417,7 +4378,7 @@ export class StaticFileService { } public static getTreeStaticFileAncestors(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4430,7 +4391,7 @@ export class StaticFileService { } public static getTreeStaticFileChildren(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4443,7 +4404,7 @@ export class StaticFileService { } public static getTreeStaticFileRoot(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4454,12 +4415,11 @@ export class StaticFileService { ...options }); } - } export class StylesheetService { public static getItemStylesheet(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4472,7 +4432,7 @@ export class StylesheetService { } public static postStylesheet(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -4489,7 +4449,7 @@ export class StylesheetService { } public static deleteStylesheetByPath(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -4502,7 +4462,7 @@ export class StylesheetService { } public static getStylesheetByPath(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4515,7 +4475,7 @@ export class StylesheetService { } public static putStylesheetByPath(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -4526,13 +4486,13 @@ export class StylesheetService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static putStylesheetByPathRename(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -4543,13 +4503,13 @@ export class StylesheetService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static postStylesheetFolder(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -4566,7 +4526,7 @@ export class StylesheetService { } public static deleteStylesheetFolderByPath(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -4579,7 +4539,7 @@ export class StylesheetService { } public static getStylesheetFolderByPath(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4592,7 +4552,7 @@ export class StylesheetService { } public static getTreeStylesheetAncestors(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4605,7 +4565,7 @@ export class StylesheetService { } public static getTreeStylesheetChildren(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4618,7 +4578,7 @@ export class StylesheetService { } public static getTreeStylesheetRoot(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4629,12 +4589,11 @@ export class StylesheetService { ...options }); } - } export class TagService { public static getTag(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4645,12 +4604,11 @@ export class TagService { ...options }); } - } export class TelemetryService { public static getTelemetry(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4663,7 +4621,7 @@ export class TelemetryService { } public static getTelemetryLevel(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4676,7 +4634,7 @@ export class TelemetryService { } public static postTelemetryLevel(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -4691,12 +4649,11 @@ export class TelemetryService { } }); } - } export class TemplateService { public static getItemTemplate(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4709,7 +4666,7 @@ export class TemplateService { } public static getItemTemplateSearch(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4722,7 +4679,7 @@ export class TemplateService { } public static postTemplate(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -4739,7 +4696,7 @@ export class TemplateService { } public static deleteTemplateById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -4752,7 +4709,7 @@ export class TemplateService { } public static getTemplateById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4765,7 +4722,7 @@ export class TemplateService { } public static putTemplateById(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -4776,13 +4733,13 @@ export class TemplateService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getTemplateConfiguration(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4795,7 +4752,7 @@ export class TemplateService { } public static postTemplateQueryExecute(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -4812,7 +4769,7 @@ export class TemplateService { } public static getTemplateQuerySettings(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4825,7 +4782,7 @@ export class TemplateService { } public static getTreeTemplateAncestors(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4838,7 +4795,7 @@ export class TemplateService { } public static getTreeTemplateChildren(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4851,7 +4808,7 @@ export class TemplateService { } public static getTreeTemplateRoot(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4862,12 +4819,11 @@ export class TemplateService { ...options }); } - } export class TemporaryFileService { public static postTemporaryFile(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ ...formDataBodySerializer, security: [ { @@ -4885,7 +4841,7 @@ export class TemporaryFileService { } public static deleteTemporaryFileById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -4898,7 +4854,7 @@ export class TemporaryFileService { } public static getTemporaryFileById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4911,7 +4867,7 @@ export class TemporaryFileService { } public static getTemporaryFileConfiguration(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4922,12 +4878,11 @@ export class TemporaryFileService { ...options }); } - } export class UpgradeService { public static postUpgradeAuthorize(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -4940,7 +4895,7 @@ export class UpgradeService { } public static getUpgradeSettings(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4951,12 +4906,11 @@ export class UpgradeService { ...options }); } - } export class UserDataService { public static getUserData(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -4969,7 +4923,7 @@ export class UserDataService { } public static postUserData(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -4986,7 +4940,7 @@ export class UserDataService { } public static putUserData(options?: Options) { - return (options?.client ?? _heyApiClient).put({ + return (options?.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -5003,7 +4957,7 @@ export class UserDataService { } public static getUserDataById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5014,12 +4968,11 @@ export class UserDataService { ...options }); } - } export class UserGroupService { public static getFilterUserGroup(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5032,7 +4985,7 @@ export class UserGroupService { } public static getItemUserGroup(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5045,7 +4998,7 @@ export class UserGroupService { } public static deleteUserGroup(options?: Options) { - return (options?.client ?? _heyApiClient).delete({ + return (options?.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -5062,7 +5015,7 @@ export class UserGroupService { } public static getUserGroup(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5075,7 +5028,7 @@ export class UserGroupService { } public static postUserGroup(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -5092,7 +5045,7 @@ export class UserGroupService { } public static deleteUserGroupById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -5105,7 +5058,7 @@ export class UserGroupService { } public static getUserGroupById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5118,7 +5071,7 @@ export class UserGroupService { } public static putUserGroupById(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -5129,13 +5082,13 @@ export class UserGroupService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static deleteUserGroupByIdUsers(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -5146,13 +5099,13 @@ export class UserGroupService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static postUserGroupByIdUsers(options: Options) { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -5163,16 +5116,15 @@ export class UserGroupService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } - } export class UserService { public static getFilterUser(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5185,7 +5137,7 @@ export class UserService { } public static getItemUser(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5198,7 +5150,7 @@ export class UserService { } public static deleteUser(options?: Options) { - return (options?.client ?? _heyApiClient).delete({ + return (options?.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -5215,7 +5167,7 @@ export class UserService { } public static getUser(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5228,7 +5180,7 @@ export class UserService { } public static postUser(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -5245,7 +5197,7 @@ export class UserService { } public static deleteUserById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -5258,7 +5210,7 @@ export class UserService { } public static getUserById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5271,7 +5223,7 @@ export class UserService { } public static putUserById(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -5282,13 +5234,13 @@ export class UserService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getUserById2Fa(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5301,7 +5253,7 @@ export class UserService { } public static deleteUserById2FaByProviderName(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -5314,7 +5266,7 @@ export class UserService { } public static getUserByIdCalculateStartNodes(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5327,7 +5279,7 @@ export class UserService { } public static postUserByIdChangePassword(options: Options) { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -5338,13 +5290,13 @@ export class UserService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getUserByIdClientCredentials(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5357,7 +5309,7 @@ export class UserService { } public static postUserByIdClientCredentials(options: Options) { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -5368,13 +5320,13 @@ export class UserService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static deleteUserByIdClientCredentialsByClientId(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -5387,7 +5339,7 @@ export class UserService { } public static postUserByIdResetPassword(options: Options) { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -5400,7 +5352,7 @@ export class UserService { } public static deleteUserAvatarById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -5413,7 +5365,7 @@ export class UserService { } public static postUserAvatarById(options: Options) { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -5424,13 +5376,13 @@ export class UserService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getUserConfiguration(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5443,7 +5395,7 @@ export class UserService { } public static getUserCurrent(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5456,7 +5408,7 @@ export class UserService { } public static getUserCurrent2Fa(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5469,7 +5421,7 @@ export class UserService { } public static deleteUserCurrent2FaByProviderName(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -5482,7 +5434,7 @@ export class UserService { } public static getUserCurrent2FaByProviderName(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5495,7 +5447,7 @@ export class UserService { } public static postUserCurrent2FaByProviderName(options: Options) { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -5506,13 +5458,13 @@ export class UserService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static postUserCurrentAvatar(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -5529,7 +5481,7 @@ export class UserService { } public static postUserCurrentChangePassword(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -5546,7 +5498,7 @@ export class UserService { } public static getUserCurrentConfiguration(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5559,7 +5511,7 @@ export class UserService { } public static getUserCurrentLoginProviders(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5572,7 +5524,7 @@ export class UserService { } public static getUserCurrentPermissions(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5585,7 +5537,7 @@ export class UserService { } public static getUserCurrentPermissionsDocument(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5598,7 +5550,7 @@ export class UserService { } public static getUserCurrentPermissionsMedia(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5611,7 +5563,7 @@ export class UserService { } public static postUserDisable(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -5628,7 +5580,7 @@ export class UserService { } public static postUserEnable(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -5645,7 +5597,7 @@ export class UserService { } public static postUserInvite(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -5662,7 +5614,7 @@ export class UserService { } public static postUserInviteCreatePassword(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ url: '/umbraco/management/api/v1/user/invite/create-password', ...options, headers: { @@ -5673,7 +5625,7 @@ export class UserService { } public static postUserInviteResend(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -5690,7 +5642,7 @@ export class UserService { } public static postUserInviteVerify(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ url: '/umbraco/management/api/v1/user/invite/verify', ...options, headers: { @@ -5701,7 +5653,7 @@ export class UserService { } public static postUserSetUserGroups(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -5718,7 +5670,7 @@ export class UserService { } public static postUserUnlock(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -5733,12 +5685,11 @@ export class UserService { } }); } - } export class WebhookService { public static getItemWebhook(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5751,7 +5702,7 @@ export class WebhookService { } public static getWebhook(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5764,7 +5715,7 @@ export class WebhookService { } public static postWebhook(options?: Options) { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -5781,7 +5732,7 @@ export class WebhookService { } public static deleteWebhookById(options: Options) { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? _heyApiClient).delete({ security: [ { scheme: 'bearer', @@ -5794,7 +5745,7 @@ export class WebhookService { } public static getWebhookById(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5807,7 +5758,7 @@ export class WebhookService { } public static putWebhookById(options: Options) { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? _heyApiClient).put({ security: [ { scheme: 'bearer', @@ -5818,13 +5769,13 @@ export class WebhookService { ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); } public static getWebhookByIdLogs(options: Options) { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5837,7 +5788,7 @@ export class WebhookService { } public static getWebhookEvents(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5850,7 +5801,7 @@ export class WebhookService { } public static getWebhookLogs(options?: Options) { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? _heyApiClient).get({ security: [ { scheme: 'bearer', @@ -5861,5 +5812,4 @@ export class WebhookService { ...options }); } - } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts b/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts index d442a7c153..cf958d9ab7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts @@ -1014,7 +1014,9 @@ export type HealthCheckActionRequestModel = { providedValue?: string | null; providedValueValidation?: string | null; providedValueValidationRegex?: string | null; - actionParameters?: {} | null; + actionParameters?: { + [key: string]: unknown; + } | null; }; export type HealthCheckGroupPresentationModel = { @@ -1096,7 +1098,9 @@ export type IndexResponseModel = { searcherName: string; documentCount: number; fieldCount: number; - providerProperties?: {} | null; + providerProperties?: { + [key: string]: unknown; + } | null; }; export type InstallRequestModelReadable = { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/http-client/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/http-client/index.ts index 450649ecb2..8b39688895 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/http-client/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/http-client/index.ts @@ -1 +1,2 @@ export { client as umbHttpClient } from '@umbraco-cms/backoffice/external/backend-api'; +export type { RequestOptions } from '@hey-api/client-fetch'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/openapi-ts.config.js b/src/Umbraco.Web.UI.Client/src/packages/core/openapi-ts.config.js index 25ad5655d9..41ee9c4301 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/openapi-ts.config.js +++ b/src/Umbraco.Web.UI.Client/src/packages/core/openapi-ts.config.js @@ -19,7 +19,9 @@ export default defineConfig({ }, { name: '@hey-api/sdk', - asClass: true + asClass: true, + classNameBuilder: (name) => `${name}Service`, + responseStyle: 'fields', } ] }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/package.json b/src/Umbraco.Web.UI.Client/src/packages/core/package.json index fb8160f45b..d4907d423e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/package.json +++ b/src/Umbraco.Web.UI.Client/src/packages/core/package.json @@ -10,9 +10,9 @@ "@types/diff": "^7.0.2", "diff": "^7.0.0", "uuid": "^11.1.0", - "@hey-api/client-fetch": "^0.10.0" + "@hey-api/client-fetch": "^0.12.0" }, "devDependencies": { - "@hey-api/openapi-ts": "^0.66.6" + "@hey-api/openapi-ts": "^0.71.0" } -} +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/api-interceptor.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/api-interceptor.controller.ts index 00c5a524c4..467d3bbb2f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/api-interceptor.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/api-interceptor.controller.ts @@ -1,22 +1,49 @@ import { extractUmbNotificationColor } from './extractUmbNotificationColor.function.js'; import { isUmbNotifications, UMB_NOTIFICATION_HEADER } from './isUmbNotifications.function.js'; import { isProblemDetailsLike } from './apiTypeValidators.function.js'; +import type { UmbProblemDetails } from './types.js'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth'; import type { UmbNotificationColor } from '@umbraco-cms/backoffice/notification'; -import type { umbHttpClient } from '@umbraco-cms/backoffice/http-client'; +import type { RequestOptions, umbHttpClient } from '@umbraco-cms/backoffice/http-client'; + +const MAX_RETRIES = 3; export class UmbApiInterceptorController extends UmbControllerBase { + /** + * Store pending requests that received a 401 response and are waiting for re-authentication. + * This is used to retry the requests after re-authentication. + */ + #pending401Requests: Array<{ + request: Request; + requestConfig: RequestOptions; + retry: () => Promise; + resolve: (value: Response) => void; + reject: (reason?: unknown) => void; + retries: number; + }> = []; + + /** + * Store non-GET requests that received a 401 response. + * This is used to notify the user about actions that could not be completed due to session expiration. + * These requests will not be retried, as they are not idempotent. + * Instead, we will notify the user about these requests after re-authentication. + */ + #nonGet401Requests: Array<{ request: Request; requestConfig: RequestOptions }> = []; + /** * Binds the default interceptors to the client. * This includes the auth response interceptor, the error interceptor and the umb-notifications interceptor. * @param {umbHttpClient} client The OpenAPI client to add the interceptor to. It can be any client supporting Response and Request interceptors. */ public bindDefaultInterceptors(client: typeof umbHttpClient) { + // Add the default observables to the instance + this.handleUnauthorizedAuthRetry(); + // Add the default interceptors to the client this.addAuthResponseInterceptor(client); + this.addForbiddenResponseInterceptor(client); this.addUmbGeneratedResourceInterceptor(client); this.addUmbNotificationsInterceptor(client); - this.addForbiddenResponseInterceptor(client); this.addErrorInterceptor(client); } @@ -26,16 +53,72 @@ export class UmbApiInterceptorController extends UmbControllerBase { * @internal */ addAuthResponseInterceptor(client: typeof umbHttpClient) { - client.interceptors.response.use(async (response: Response) => { - if (response.status === 401) { - // See if we can get the UmbAuthContext and let it know the user is timed out - const authContext = await this.getContext(UMB_AUTH_CONTEXT, { preventTimeout: true }); - if (!authContext) { - throw new Error('Could not get the auth context'); - } + client.interceptors.response.use(async (response, request, requestConfig): Promise => { + if (response.status !== 401) return response; + + // Build a plain ProblemDetails object for the response body + const problemDetails: UmbProblemDetails = { + status: response.status, + title: response.statusText || 'Unauthorized request, waiting for re-authentication.', + detail: undefined, + errors: undefined, + type: 'Unauthorized', + stack: undefined, + }; + + const newResponse = this.#createResponse(problemDetails, response); + + const authContext = await this.getContext(UMB_AUTH_CONTEXT, { preventTimeout: true }); + if (!authContext) throw new Error('Could not get the auth context'); + + // Only retry for GET requests + if (request.method !== 'GET') { + // Collect info for later notification + this.#nonGet401Requests.push({ request, requestConfig }); + + // Show login overlay (only once per burst, as before) authContext.timeOut(); + return newResponse; } - return response; + + // Find if this request is already in the queue and increment retries + let retries = 1; + const existing = this.#pending401Requests.find( + (req) => req.request === request && req.requestConfig === requestConfig, + ); + if (existing) { + retries = existing.retries + 1; + if (retries > MAX_RETRIES) { + return newResponse; + } + existing.retries = retries; + } + + // Return a promise that will resolve when re-auth completes + return new Promise((resolve, reject) => { + this.#pending401Requests.push({ + request, + requestConfig, + retry: async () => { + const { data, response: retryResponse } = await client.request(requestConfig as never); + + return this.#createResponse(data, retryResponse); + }, + resolve, + reject, + retries, + }); + + // Show login overlay + authContext.timeOut(); + + console.log( + '[Interceptor] 401 Unauthorized - queuing request for re-authentication and have tried', + retries - 1, + 'times before', + requestConfig, + ); + }); }); } @@ -45,17 +128,24 @@ export class UmbApiInterceptorController extends UmbControllerBase { * @internal */ addForbiddenResponseInterceptor(client: typeof umbHttpClient) { - client.interceptors.response.use(async (response: Response) => { - if (response.status === 403) { - const headline = 'Permission Denied'; - const message = 'You do not have the necessary permissions to complete the requested action. If you believe this is in error, please reach out to your administrator.'; + client.interceptors.response.use((response): Response => { + if (response.status !== 403) return response; - this.#peekError(headline, message, null); - } + // Build a plain ProblemDetails object for the response body + const problemDetails: UmbProblemDetails = { + status: response.status, + title: + response.statusText || + 'You do not have the necessary permissions to complete the requested action. If you believe this is in error, please reach out to your administrator.', + detail: undefined, + errors: undefined, + type: 'Unauthorized', + stack: undefined, + }; - return response; - }); - } + return this.#createResponse(problemDetails, response); + }); + } /** * Interceptor which checks responses for the Umb-Generated-Resource header and replaces the value into the response body. @@ -63,22 +153,16 @@ export class UmbApiInterceptorController extends UmbControllerBase { * @internal */ addUmbGeneratedResourceInterceptor(client: typeof umbHttpClient) { - client.interceptors.response.use(async (response: Response) => { - if (!response.headers.has('Umb-Generated-Resource')) { - return response; - } + client.interceptors.response.use((response): Response => { + if (!response.headers.has('Umb-Generated-Resource')) return response; const generatedResource = response.headers.get('Umb-Generated-Resource'); if (generatedResource === null) { return response; } - // Generate new response body with the generated resource, which is a guid - const newResponse = new Response(generatedResource, { - ...response, - }); - - return newResponse; + // Return a new response with the generated resource in the body (plain text) + return this.#createResponse(generatedResource, response); }); } @@ -88,47 +172,54 @@ export class UmbApiInterceptorController extends UmbControllerBase { * @internal */ addErrorInterceptor(client: typeof umbHttpClient) { - client.interceptors.response.use(async (response) => { + client.interceptors.response.use(async (response): Promise => { + // If the response is ok, we just return the response if (response.ok) return response; - // Handle 500 errors - we need to show a notification - if (response.status === 500) { - try { - // Clones the response to read the body - const origResponse = response.clone(); - const error = await origResponse.json(); + // We will check if it is not a 401 or 403 error, as that is handled by other interceptors + if (response.status === 401 || response.status === 403) return response; - // If there is no JSON in the error, we just return the response - if (!error) return response; - - // Check if the error is a problem details object - if (!isProblemDetailsLike(error)) { - // If not, we just return the response - return response; - } - - let headline = error.title ?? 'Server Error'; - let message = 'A fatal server error occurred. If this continues, please reach out to your administrator.'; - - // Special handling for ObjectCacheAppCache corruption errors, which we are investigating - if ( - error.detail?.includes('ObjectCacheAppCache') || - error.detail?.includes('Umbraco.Cms.Infrastructure.Scoping.Scope.DisposeLastScope()') - ) { - headline = 'Please restart the server'; - message = - 'The Umbraco object cache is corrupt, but your action may still have been executed. Please restart the server to reset the cache. This is a work in progress.'; - } - - this.#peekError(headline, message, error.errors); - } catch (e) { - // Ignore JSON parse error - console.error('[Interceptor] Caught a 500 Error, but failed parsing error body (expected JSON)', e); - } + // Special handling for 404 Not Found + if (response.status === 404) { + const notFoundProblemDetails: UmbProblemDetails = { + status: response.status, + title: response.statusText || 'The requested resource was not found.', + detail: undefined, + errors: undefined, + type: 'NotFound', + stack: undefined, + }; + return this.#createResponse(notFoundProblemDetails, response); } - // Return original response - return response; + // For all other errors, we will build a ProblemDetails object + let problemDetails: UmbProblemDetails = { + status: response.status, + title: + response.statusText || + 'A fatal server error occurred. If this continues, please reach out to your administrator.', + detail: undefined, + errors: undefined, + type: 'ServerError', + stack: undefined, + }; + + try { + // Clones the response to read the body + const origResponse = response.clone(); + const errorBody = await origResponse.json(); + + // If there is JSON in the error, we will try to parse it as a ProblemDetails object + if (errorBody && isProblemDetailsLike(errorBody)) { + // Merge the parsed problem details into our default + problemDetails = errorBody; + } + } catch (e) { + // Ignore JSON parse error + console.error('[Interceptor] Caught a server error, but failed parsing error body (expected JSON)', e); + } + + return this.#createResponse(problemDetails, response); }); } @@ -166,6 +257,78 @@ export class UmbApiInterceptorController extends UmbControllerBase { }); } + /** + * Listen for authorization signal to retry GET-requests that received a 401 Unauthorized response. + * This will retry all pending requests that received a 401 Unauthorized response after re-authentication. + * It will also notify the user about non-GET requests that received a 401 Unauthorized response. + * @internal + */ + handleUnauthorizedAuthRetry() { + this.consumeContext(UMB_AUTH_CONTEXT, (context) => { + this.observe( + context?.authorizationSignal, + () => { + console.log('[Interceptor] 401 Unauthorized - re-authentication completed'); + + // On auth, retry all pending requests + const requests = this.#pending401Requests.splice(0, this.#pending401Requests.length); + requests.forEach((req) => { + console.log('[Interceptor] 401 Unauthorized - retrying request after re-authentication', req.requestConfig); + req.retry().then(req.resolve).catch(req.reject); + }); + + // Notify about non-GET 401s after successful re-auth + if (this.#nonGet401Requests.length > 0) { + const errors: Record = {}; + this.#nonGet401Requests.forEach((req) => { + errors[`${req.request.method} ${req.request.url}`] = `Request failed with 401 Unauthorized.`; + }); + this.#peekError( + 'Some actions were not completed', + 'Some actions could not be completed because your session expired. Please try again.', + errors, + 'warning', + ); + this.#nonGet401Requests.length = 0; // Clear after notifying + } + }, + '_authClearNonGet401Requests', + ); + }); + } + + /** + * Helper to create a new Response with correct Content-Type. + * @param {unknown} body The body of the response, can be a string or an object. + * @param {Response} originalResponse The original response to copy status and headers from. + * @returns {Response} The new Response object with the correct Content-Type and body. + */ + #createResponse(body: unknown, originalResponse: Response): Response { + const isString = typeof body === 'string'; + const contentType = isString ? 'text/plain' : 'application/json'; + const responseBody = isString ? body : JSON.stringify(body); + + // Construct new headers but preserve "X-" headers from the original response + const headersOverride: Record = {}; + originalResponse.headers.forEach((value, key) => { + if (key.toLowerCase().startsWith('x-')) { + headersOverride[key] = value; + } + }); + + return new Response(responseBody, { + status: originalResponse.status, + statusText: originalResponse.statusText, + headers: { + ...headersOverride, + 'Content-Type': contentType, + }, + }); + } + + /** + * Helper to show a notification error. + */ async #peekError(headline: string, message: string, details: unknown, color?: UmbNotificationColor) { // Store the host for usage in the following async context const host = this._host; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/resource.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/resource.controller.ts index 172176ecc7..f66114e5f5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/resource.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/resource.controller.ts @@ -1,5 +1,5 @@ import { isApiError, isCancelablePromise, isCancelError, isProblemDetailsLike } from './apiTypeValidators.function.js'; -import { UmbApiError, UmbCancelError, UmbError } from './umb-error.js'; +import { UmbApiError, UmbCancelError } from './umb-error.js'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @@ -22,7 +22,7 @@ export class UmbResourceController extends UmbControllerBase { * @param {*} error The error to map * @returns {*} The mapped error */ - mapToUmbError(error: unknown): UmbApiError | UmbCancelError | UmbError { + mapToUmbError(error: unknown): UmbApiError | UmbCancelError { if (isProblemDetailsLike(error)) { return new UmbApiError(error.detail ?? error.title, error.status, null, error); } else if (isApiError(error)) { @@ -33,11 +33,18 @@ export class UmbResourceController extends UmbControllerBase { return error; } else if (UmbApiError.isUmbApiError(error)) { return error; - } else if (UmbError.isUmbError(error)) { - return error; } - // If the error is not an UmbError, we will just return it as is - return new UmbError(error instanceof Error ? error.message : 'Unknown error'); + + // If the error is not recognizable, for example if it has no ProblemDetails body, we will return a generic UmbApiError. + // This is to ensure that we always return an UmbApiError, so we can handle it in a consistent way. + return new UmbApiError(error instanceof Error ? error.message : 'Unknown error', 0, null, { + status: 0, + title: 'Unknown error', + detail: error instanceof Error ? error.message : 'Unknown error', + errors: undefined, + type: 'error', + stack: error instanceof Error ? error.stack : undefined, + }); } /** diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/try-execute.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/try-execute.controller.ts index afca34ad55..2c5a356fe3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/try-execute.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/try-execute.controller.ts @@ -1,7 +1,7 @@ -import { isProblemDetailsLike } from '../apiTypeValidators.function.js'; import { UmbResourceController } from '../resource.controller.js'; import type { UmbApiResponse, UmbTryExecuteOptions } from '../types.js'; -import { UmbApiError, UmbCancelError } from '../umb-error.js'; +import { UmbCancelError } from '../umb-error.js'; +import type { UmbApiError } from '../umb-error.js'; export class UmbTryExecuteController extends UmbResourceController { #abortSignal?: AbortSignal; @@ -35,33 +35,51 @@ export class UmbTryExecuteController extends UmbResourceController { super.destroy(); } - #notifyOnError(error: unknown) { + #notifyOnError(error: UmbApiError | UmbCancelError): void { if (UmbCancelError.isUmbCancelError(error)) { // Cancel error, do not show notification return; } let headline = 'An error occurred'; - let message = 'An error occurred while trying to execute the request.'; + let message = 'A fatal server error occurred. If this continues, please reach out to your administrator.'; let details: Record | undefined = undefined; - // Check if we can extract problem details from the error - const problemDetails = UmbApiError.isUmbApiError(error) - ? error.problemDetails - : isProblemDetailsLike(error) - ? error - : undefined; + const apiError = error as UmbApiError; + + // Check if we can extract problem details from the error + if (apiError.problemDetails) { + if (apiError.problemDetails.status === 401) { + // Unauthorized error, show no notification + // the user will see a login screen instead + return; + } + + if (apiError.problemDetails.status === 404) { + // Not found error, show no notification + // the user will see a 404 page instead, or otherwise the UI will handle it + return; + } - if (problemDetails) { // UmbProblemDetails, show notification - message = problemDetails.title; - details = problemDetails.errors ?? undefined; + message = apiError.problemDetails.title; + details = apiError.problemDetails.errors ?? undefined; + + // Special handling for ObjectCacheAppCache corruption errors, which we are investigating + if ( + apiError.problemDetails.detail?.includes('ObjectCacheAppCache') || + apiError.problemDetails.detail?.includes('Umbraco.Cms.Infrastructure.Scoping.Scope.DisposeLastScope()') + ) { + headline = 'Please restart the server'; + message = + 'The Umbraco object cache is corrupt, but your action may still have been executed. Please restart the server to reset the cache. This is a work in progress.'; + } } else { // Unknown error, show notification - headline = ''; - message = error instanceof Error ? error.message : 'An unknown error occurred.'; + message = apiError instanceof Error ? apiError.message : 'An unknown error occurred.'; } this._peekError(headline, message, details); + console.error('[UmbTryExecuteController] Error in request:', error); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/types.ts index 6a438f0bab..bd685f0089 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/types.ts @@ -1,4 +1,4 @@ -import type { UmbApiError, UmbCancelError, UmbError } from './umb-error.js'; +import type { UmbApiError, UmbCancelError } from './umb-error.js'; export type * from './data-api/types.js'; export interface XhrRequestOptions extends UmbTryExecuteOptions { @@ -38,7 +38,7 @@ export interface UmbTryExecuteOptions { } export type UmbApiWithErrorResponse = { - error?: UmbError | UmbApiError | UmbCancelError | Error; + error?: UmbApiError | UmbCancelError; }; /** diff --git a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs index ba4fb5a0a1..806dd72ffb 100644 --- a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs +++ b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs @@ -22,7 +22,7 @@ const silencedLogs = [ /** @type {import('@web/dev-server').DevServerConfig} */ export default { rootDir: '.', - files: ['./src/**/*.test.ts'], + files: ['./src/**/*.test.ts', '!**/node_modules/**'], nodeResolve: { exportConditions: mode === 'dev' ? ['development'] : [], preferBuiltins: false, browser: false }, browsers: [playwrightLauncher({ product: 'chromium' })], /* TODO: fix coverage report diff --git a/src/Umbraco.Web.UI.Login/src/auth.element.ts b/src/Umbraco.Web.UI.Login/src/auth.element.ts index 2414bfc0a6..b8942fbcf4 100644 --- a/src/Umbraco.Web.UI.Login/src/auth.element.ts +++ b/src/Umbraco.Web.UI.Login/src/auth.element.ts @@ -1,10 +1,10 @@ import { html, customElement, property, ifDefined } from '@umbraco-cms/backoffice/external/lit'; -import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; -import type { InputType, UUIFormLayoutItemElement } from "@umbraco-cms/backoffice/external/uui"; -import { umbExtensionsRegistry } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import type { InputType, UUIFormLayoutItemElement } from '@umbraco-cms/backoffice/external/uui'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; -import { UMB_AUTH_CONTEXT, UmbAuthContext } from "./contexts"; -import { UmbSlimBackofficeController } from "./controllers"; +import { UMB_AUTH_CONTEXT, UmbAuthContext } from './contexts'; +import { UmbSlimBackofficeController } from './controllers'; // We import the authStyles here so that we can inline it in the shadow DOM that is created outside of the UmbAuthElement. import authStyles from './auth-styles.css?inline'; @@ -13,264 +13,259 @@ import authStyles from './auth-styles.css?inline'; import { extensions } from './umbraco-package.js'; const createInput = (opts: { - id: string; - type: InputType; - name: string; - autocomplete: AutoFill; - label: string; - inputmode: string; - autofocus?: boolean; + id: string; + type: InputType; + name: string; + autocomplete: AutoFill; + label: string; + inputmode: string; + autofocus?: boolean; }) => { - const input = document.createElement('input'); - input.type = opts.type; - input.name = opts.name; - input.autocomplete = opts.autocomplete; - input.id = opts.id; - input.required = true; - input.inputMode = opts.inputmode; - input.ariaLabel = opts.label; - input.autofocus = opts.autofocus || false; + const input = document.createElement('input'); + input.type = opts.type; + input.name = opts.name; + input.autocomplete = opts.autocomplete; + input.id = opts.id; + input.required = true; + input.inputMode = opts.inputmode; + input.ariaLabel = opts.label; + input.autofocus = opts.autofocus || false; - return input; + return input; }; -const createLabel = (opts: { forId: string; localizeAlias: string; localizeFallback: string; }) => { - const label = document.createElement('label'); - const umbLocalize: any = document.createElement('umb-localize'); - umbLocalize.key = opts.localizeAlias; - umbLocalize.innerHTML = opts.localizeFallback; - label.htmlFor = opts.forId; - label.appendChild(umbLocalize); +const createLabel = (opts: { forId: string; localizeAlias: string; localizeFallback: string }) => { + const label = document.createElement('label'); + const umbLocalize: any = document.createElement('umb-localize'); + umbLocalize.key = opts.localizeAlias; + umbLocalize.innerHTML = opts.localizeFallback; + label.htmlFor = opts.forId; + label.appendChild(umbLocalize); - return label; + return label; }; const createFormLayoutItem = (label: HTMLLabelElement, input: HTMLInputElement) => { - const formLayoutItem = document.createElement('uui-form-layout-item') as UUIFormLayoutItemElement; - formLayoutItem.appendChild(label); - formLayoutItem.appendChild(input); + const formLayoutItem = document.createElement('uui-form-layout-item') as UUIFormLayoutItemElement; + formLayoutItem.appendChild(label); + formLayoutItem.appendChild(input); - return formLayoutItem; + return formLayoutItem; }; const createForm = (elements: HTMLElement[]) => { - const styles = document.createElement('style'); - styles.innerHTML = authStyles; - const form = document.createElement('form'); - form.id = 'umb-login-form'; - form.name = 'login-form'; - form.spellcheck = false; + const styles = document.createElement('style'); + styles.innerHTML = authStyles; + const form = document.createElement('form'); + form.id = 'umb-login-form'; + form.name = 'login-form'; + form.spellcheck = false; - elements.push(styles); - elements.forEach((element) => form.appendChild(element)); + elements.push(styles); + elements.forEach((element) => form.appendChild(element)); - return form; + return form; }; @customElement('umb-auth') export default class UmbAuthElement extends UmbLitElement { - /** - * Disables the local login form and only allows external login providers. - * - * @attr disable-local-login - */ - @property({type: Boolean, attribute: 'disable-local-login'}) - disableLocalLogin = false; + /** + * Disables the local login form and only allows external login providers. + * + * @attr disable-local-login + */ + @property({ type: Boolean, attribute: 'disable-local-login' }) + disableLocalLogin = false; - @property({attribute: 'background-image'}) - backgroundImage?: string; + @property({ attribute: 'background-image' }) + backgroundImage?: string; - @property({attribute: 'logo-image'}) - logoImage?: string; + @property({ attribute: 'logo-image' }) + logoImage?: string; - @property({attribute: 'logo-image-alternative'}) - logoImageAlternative?: string; + @property({ attribute: 'logo-image-alternative' }) + logoImageAlternative?: string; - @property({type: Boolean, attribute: 'username-is-email'}) - usernameIsEmail = false; + @property({ type: Boolean, attribute: 'username-is-email' }) + usernameIsEmail = false; - @property({type: Boolean, attribute: 'allow-password-reset'}) - allowPasswordReset = false; + @property({ type: Boolean, attribute: 'allow-password-reset' }) + allowPasswordReset = false; - @property({type: Boolean, attribute: 'allow-user-invite'}) - allowUserInvite = false; + @property({ type: Boolean, attribute: 'allow-user-invite' }) + allowUserInvite = false; - @property({attribute: 'return-url'}) - set returnPath(value: string) { - this.#authContext.returnPath = value; - } - get returnPath() { - return this.#authContext.returnPath; - } + @property({ attribute: 'return-url' }) + set returnPath(value: string) { + this.#authContext.returnPath = value; + } + get returnPath() { + return this.#authContext.returnPath; + } - /** - * Override the default flow. - */ - protected flow?: 'mfa' | 'reset-password' | 'invite-user'; + /** + * Override the default flow. + */ + protected flow?: 'mfa' | 'reset-password' | 'invite-user'; - _form?: HTMLFormElement; - _usernameLayoutItem?: UUIFormLayoutItemElement; - _passwordLayoutItem?: UUIFormLayoutItemElement; - _usernameInput?: HTMLInputElement; - _passwordInput?: HTMLInputElement; - _usernameLabel?: HTMLLabelElement; - _passwordLabel?: HTMLLabelElement; + _form?: HTMLFormElement; + _usernameLayoutItem?: UUIFormLayoutItemElement; + _passwordLayoutItem?: UUIFormLayoutItemElement; + _usernameInput?: HTMLInputElement; + _passwordInput?: HTMLInputElement; + _usernameLabel?: HTMLLabelElement; + _passwordLabel?: HTMLLabelElement; - #authContext = new UmbAuthContext(this, UMB_AUTH_CONTEXT); + #authContext = new UmbAuthContext(this, UMB_AUTH_CONTEXT); - constructor() { - super(); + constructor() { + super(); - (this as unknown as EventTarget).addEventListener('umb-login-flow', (e) => { - if (e instanceof CustomEvent) { - this.flow = e.detail.flow || undefined; - } - this.requestUpdate(); - }); + (this as unknown as EventTarget).addEventListener('umb-login-flow', (e) => { + if (e instanceof CustomEvent) { + this.flow = e.detail.flow || undefined; + } + this.requestUpdate(); + }); + } - // Bind the (slim) Backoffice controller to this element so that we can use utilities from the Backoffice app. - new UmbSlimBackofficeController(this); + async firstUpdated() { + // Bind the (slim) Backoffice controller to this element so that we can use utilities from the Backoffice app. + await new UmbSlimBackofficeController(this).register(this); - // Register the main package for Umbraco.Auth - umbExtensionsRegistry.registerMany(extensions); - } + // Register the main package for Umbraco.Auth + umbExtensionsRegistry.registerMany(extensions); - firstUpdated() { - setTimeout(() => { - requestAnimationFrame(() => { - this.#initializeForm(); - }); - }, 100); - } + setTimeout(() => { + requestAnimationFrame(() => { + this.#initializeForm(); + }); + }, 100); + } - disconnectedCallback() { - super.disconnectedCallback(); - this._usernameLayoutItem?.remove(); - this._passwordLayoutItem?.remove(); - this._usernameLabel?.remove(); - this._usernameInput?.remove(); - this._passwordLabel?.remove(); - this._passwordInput?.remove(); - } + disconnectedCallback() { + super.disconnectedCallback(); + this._usernameLayoutItem?.remove(); + this._passwordLayoutItem?.remove(); + this._usernameLabel?.remove(); + this._usernameInput?.remove(); + this._passwordLabel?.remove(); + this._passwordInput?.remove(); + } - /** - * Creates the login form and adds it to the DOM in the default slot. - * This is done to avoid having to deal with the shadow DOM, which is not supported in Google Chrome for autocomplete/autofill. - * - * @see Track this intent-to-ship for Chrome https://groups.google.com/a/chromium.org/g/blink-dev/c/RY9leYMu5hI?pli=1 - * @private - */ - #initializeForm() { - const labelUsername = this.usernameIsEmail - ? this.localize.term('auth_email') - : this.localize.term('auth_username'); - const labelPassword = this.localize.term('auth_password'); + /** + * Creates the login form and adds it to the DOM in the default slot. + * This is done to avoid having to deal with the shadow DOM, which is not supported in Google Chrome for autocomplete/autofill. + * + * @see Track this intent-to-ship for Chrome https://groups.google.com/a/chromium.org/g/blink-dev/c/RY9leYMu5hI?pli=1 + * @private + */ + #initializeForm() { + const labelUsername = this.usernameIsEmail ? this.localize.term('auth_email') : this.localize.term('auth_username'); + const labelPassword = this.localize.term('auth_password'); - this._usernameInput = createInput({ - id: 'username-input', - type: 'text', - name: 'username', - autocomplete: 'username', - label: labelUsername, - inputmode: this.usernameIsEmail ? 'email' : '', - autofocus: true, - }); - this._passwordInput = createInput({ - id: 'password-input', - type: 'password', - name: 'password', - autocomplete: 'current-password', - label: labelPassword, - inputmode: '', - }); - this._usernameLabel = createLabel({ - forId: 'username-input', - localizeAlias: this.usernameIsEmail ? 'auth_email' : 'auth_username', - localizeFallback: this.usernameIsEmail ? 'Email' : 'Username', - }); - this._passwordLabel = createLabel({forId: 'password-input', localizeAlias: 'auth_password', localizeFallback: 'Password'}); + this._usernameInput = createInput({ + id: 'username-input', + type: 'text', + name: 'username', + autocomplete: 'username', + label: labelUsername, + inputmode: this.usernameIsEmail ? 'email' : '', + autofocus: true, + }); + this._passwordInput = createInput({ + id: 'password-input', + type: 'password', + name: 'password', + autocomplete: 'current-password', + label: labelPassword, + inputmode: '', + }); + this._usernameLabel = createLabel({ + forId: 'username-input', + localizeAlias: this.usernameIsEmail ? 'auth_email' : 'auth_username', + localizeFallback: this.usernameIsEmail ? 'Email' : 'Username', + }); + this._passwordLabel = createLabel({ + forId: 'password-input', + localizeAlias: 'auth_password', + localizeFallback: 'Password', + }); - this._usernameLayoutItem = createFormLayoutItem(this._usernameLabel, this._usernameInput); - this._passwordLayoutItem = createFormLayoutItem(this._passwordLabel, this._passwordInput); + this._usernameLayoutItem = createFormLayoutItem(this._usernameLabel, this._usernameInput); + this._passwordLayoutItem = createFormLayoutItem(this._passwordLabel, this._passwordInput); - this._form = createForm([this._usernameLayoutItem, this._passwordLayoutItem]); + this._form = createForm([this._usernameLayoutItem, this._passwordLayoutItem]); - this.insertAdjacentElement('beforeend', this._form); - } + this.insertAdjacentElement('beforeend', this._form); + } - render() { - return html` - - ${this._renderFlowAndStatus()} - - `; - } + render() { + return html` + + ${this._renderFlowAndStatus()} + + `; + } - private _renderFlowAndStatus() { - if (this.disableLocalLogin) { - return html` - - Unfortunately, it is not possible to log in directly. It has been disabled by a login provider. - - `; - } + private _renderFlowAndStatus() { + if (this.disableLocalLogin) { + return html` + + Unfortunately, it is not possible to log in directly. It has been disabled by a login + provider. + + `; + } - const searchParams = new URLSearchParams(window.location.search); - let flow = this.flow || searchParams.get('flow')?.toLowerCase(); - const status = searchParams.get('status'); + const searchParams = new URLSearchParams(window.location.search); + let flow = this.flow || searchParams.get('flow')?.toLowerCase(); + const status = searchParams.get('status'); - if (status === 'resetCodeExpired') { - return html` - - `; - } + if (status === 'resetCodeExpired') { + return html` `; + } - if (flow === 'invite-user' && status === 'false') { - return html` - - `; - } + if (flow === 'invite-user' && status === 'false') { + return html` + `; + } - // validate - if (flow) { - if (flow === 'mfa' && !this.#authContext.isMfaEnabled) { - flow = undefined; - } - } + // validate + if (flow) { + if (flow === 'mfa' && !this.#authContext.isMfaEnabled) { + flow = undefined; + } + } - switch (flow) { - case 'mfa': - return html` - `; - case 'reset': - return html` - `; - case 'reset-password': - return html` - `; - case 'invite-user': - return html` - `; + switch (flow) { + case 'mfa': + return html` `; + case 'reset': + return html` `; + case 'reset-password': + return html` `; + case 'invite-user': + return html` `; - default: - return html` - - - - `; - } - } + default: + return html` + + + `; + } + } } declare global { - interface HTMLElementTagNameMap { - 'umb-auth': UmbAuthElement; - } + interface HTMLElementTagNameMap { + 'umb-auth': UmbAuthElement; + } } diff --git a/src/Umbraco.Web.UI.Login/src/controllers/slim-backoffice-initializer.ts b/src/Umbraco.Web.UI.Login/src/controllers/slim-backoffice-initializer.ts index 153a927303..3c7c3335ee 100644 --- a/src/Umbraco.Web.UI.Login/src/controllers/slim-backoffice-initializer.ts +++ b/src/Umbraco.Web.UI.Login/src/controllers/slim-backoffice-initializer.ts @@ -7,6 +7,7 @@ import type { UmbElement } from '@umbraco-cms/backoffice/element-api'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import { UUIIconRegistryEssential } from '@umbraco-cms/backoffice/external/uui'; import { UmbServerConnection, UmbServerContext } from '@umbraco-cms/backoffice/server'; +import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; // We import what we need from the Backoffice app. // In the future the login screen app will be a part of the Backoffice app, and we will not need to import these. @@ -20,6 +21,17 @@ export class UmbSlimBackofficeController extends UmbControllerBase { constructor(host: UmbElement) { super(host); + new UmbBundleExtensionInitializer(host, umbExtensionsRegistry); + + // Attach the UUIIconRegistry to the host so that it can be used in the login screen. + new UUIIconRegistryEssential().attach(host); + + // Add the essential styles to the host so that it can be used in the login screen. + host.classList.add('uui-text'); + host.classList.add('uui-font'); + } + + async register(host: UmbElement) { // Get the server URL and backoffice path from the host. const serverUrl = window.location.origin; const serverConnection = new UmbServerConnection(host, serverUrl); @@ -32,19 +44,12 @@ export class UmbSlimBackofficeController extends UmbControllerBase { serverConnection: serverConnection, }); - new UmbBundleExtensionInitializer(host, umbExtensionsRegistry); - new UmbAppEntryPointExtensionInitializer(host, umbExtensionsRegistry); - // Register the public extensions for the slim backoffice. - new UmbServerExtensionRegistrator(this, umbExtensionsRegistry).registerPublicExtensions().catch((error) => { + await new UmbServerExtensionRegistrator(this, umbExtensionsRegistry).registerPublicExtensions().catch((error) => { console.error(`Failed to register public extensions for the slim backoffice.`, error); }); - // Attach the UUIIconRegistry to the host so that it can be used in the login screen. - new UUIIconRegistryEssential().attach(host); - - // Add the essential styles to the host so that it can be used in the login screen. - host.classList.add('uui-text'); - host.classList.add('uui-font'); + const initializer = new UmbAppEntryPointExtensionInitializer(host, umbExtensionsRegistry); + await firstValueFrom(initializer.loaded); } } diff --git a/src/Umbraco.Web.UI.Login/src/types.ts b/src/Umbraco.Web.UI.Login/src/types.ts index 803336edf0..c44ec8d53b 100644 --- a/src/Umbraco.Web.UI.Login/src/types.ts +++ b/src/Umbraco.Web.UI.Login/src/types.ts @@ -40,6 +40,9 @@ export type ValidateInviteCodeResponse = { export type PasswordConfigurationModel = PasswordConfigurationResponseModel; +/** + * @deprecated Use `UmbProblemDetails` from `@umbraco-cms/backoffice/resources` instead. + */ export type UmbProblemDetails = { type?: string | null; title?: string | null; diff --git a/src/Umbraco.Web.UI.Login/src/utils/is-problem-details.function.ts b/src/Umbraco.Web.UI.Login/src/utils/is-problem-details.function.ts index 8465450e27..e118535fe5 100644 --- a/src/Umbraco.Web.UI.Login/src/utils/is-problem-details.function.ts +++ b/src/Umbraco.Web.UI.Login/src/utils/is-problem-details.function.ts @@ -1,5 +1,8 @@ import type { UmbProblemDetails } from '../types.js'; +/** + * @deprecated Use `isProblemDetailsLike` from `@umbraco-cms/backoffice/resources` instead. + */ export function isProblemDetails(obj: unknown): obj is UmbProblemDetails { return ( typeof obj === 'object' && obj !== null && 'type' in obj && 'title' in obj && 'status' in obj && 'detail' in obj diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts index 9292d61540..b1c4d9b1e6 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts @@ -131,13 +131,13 @@ test('can create a folder in a folder', async ({umbracoApi, umbracoUi}) => { // Act await umbracoUi.media.clickActionsMenuForName(parentFolderName); - await umbracoUi.media.clickCreateModalButton(); + await umbracoUi.media.clickCreateActionMenuOption(); await umbracoUi.media.clickMediaTypeName('Folder'); await umbracoUi.media.enterMediaItemName(folderName); await umbracoUi.media.clickSaveButton(); // Assert - await umbracoUi.media.waitForMediaItemToBeCreated(); + //await umbracoUi.media.waitForMediaItemToBeCreated(); // This is flaky, and Playwright seems to succeed even with its default timeout await umbracoUi.media.isMediaTreeItemVisible(parentFolderName); await umbracoUi.media.isMediaTreeItemVisible(folderName, false); await umbracoUi.media.clickMediaCaretButtonForName(parentFolderName); From da9c6e24ce223565990e5857c41b7f6b6a447635 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 12 Jun 2025 13:50:29 +0200 Subject: [PATCH 19/82] Removed unnecessary compatibility suppressions file from integration tests. --- .../CompatibilitySuppressions.xml | 144 ------------------ 1 file changed, 144 deletions(-) delete mode 100644 tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml diff --git a/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml b/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml deleted file mode 100644 index c3bf60b3cb..0000000000 --- a/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml +++ /dev/null @@ -1,144 +0,0 @@ - - - - - CP0001 - T:Umbraco.Cms.Tests.Integration.Umbraco.Core.Services.DocumentUrlServiceTest - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - - CP0001 - T:Umbraco.Cms.Tests.Integration.Umbraco.Core.Services.DocumentUrlServiceTest_HideTopLevel_False - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - - CP0001 - T:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors.BlockEditorBackwardsCompatibilityTests - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - - CP0001 - T:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors.BlockEditorElementVariationTestBase - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - - CP0001 - T:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors.BlockGridElementLevelVariationTests - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - - CP0001 - T:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors.BlockListElementLevelVariationTests - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - - CP0001 - T:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors.BlockListPropertyEditorTests - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - - CP0001 - T:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors.PropertyIndexValueFactoryTests - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - - CP0001 - T:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors.RichTextEditorPastedImagesTests - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - - CP0001 - T:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors.RichTextElementLevelVariationTests - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - - CP0001 - T:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors.RichTextPropertyEditorTests - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - - CP0002 - M:Umbraco.Cms.Tests.Integration.Umbraco.Core.Services.DocumentNavigationServiceTests.Bin_Structure_Can_Rebuild - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - - CP0002 - M:Umbraco.Cms.Tests.Integration.Umbraco.Core.Services.DocumentNavigationServiceTests.Structure_Can_Rebuild - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - - CP0002 - M:Umbraco.Cms.Tests.Integration.Umbraco.Core.Services.UserServiceCrudTests.Cannot_Request_Disabled_If_Hidden(Umbraco.Cms.Core.Models.Membership.UserState) - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - - CP0002 - M:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services.ContentPublishingServiceTests.Publish_Branch_Does_Not_Publish_Unpublished_Children_Unless_Explicitly_Instructed_To(System.Boolean) - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - - CP0002 - M:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services.EntityServiceTests.CreateTestData - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - - CP0002 - M:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services.MemberEditingServiceTests.Cannot_Change_IsApproved_Without_Access - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - - CP0002 - M:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services.MemberEditingServiceTests.Cannot_Change_IsLockedOut_Without_Access - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - - CP0002 - M:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services.TemplateServiceTests.Deleting_Master_Template_Also_Deletes_Children - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - - CP0002 - M:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services.TrackedReferencesServiceTests.Does_not_return_references_if_item_is_not_referenced - lib/net9.0/Umbraco.Tests.Integration.dll - lib/net9.0/Umbraco.Tests.Integration.dll - true - - \ No newline at end of file From 9d11c76ea12b8aa706e189d425caede28dd0b72d Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 12 Jun 2025 22:20:44 +0200 Subject: [PATCH 20/82] Added start-up logging of document URL caching (#19538) Added startup logging of document URL caching. --- src/Umbraco.Core/Services/DocumentUrlService.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/DocumentUrlService.cs b/src/Umbraco.Core/Services/DocumentUrlService.cs index c9bc338906..2b60d736cc 100644 --- a/src/Umbraco.Core/Services/DocumentUrlService.cs +++ b/src/Umbraco.Core/Services/DocumentUrlService.cs @@ -141,14 +141,18 @@ public class DocumentUrlService : IDocumentUrlService using ICoreScope scope = _coreScopeProvider.CreateCoreScope(); if (ShouldRebuildUrls()) { - _logger.LogInformation("Rebuilding all URLs."); + _logger.LogInformation("Rebuilding all document URLs."); await RebuildAllUrlsAsync(); } + _logger.LogInformation("Caching document URLs."); + IEnumerable publishedDocumentUrlSegments = _documentUrlRepository.GetAll(); IEnumerable languages = await _languageService.GetAllAsync(); var languageIdToIsoCode = languages.ToDictionary(x => x.Id, x => x.IsoCode); + + int numberOfCachedUrls = 0; foreach (PublishedDocumentUrlSegments publishedDocumentUrlSegment in ConvertToCacheModel(publishedDocumentUrlSegments)) { if (cancellationToken.IsCancellationRequested) @@ -159,9 +163,12 @@ public class DocumentUrlService : IDocumentUrlService if (languageIdToIsoCode.TryGetValue(publishedDocumentUrlSegment.LanguageId, out var isoCode)) { UpdateCache(_coreScopeProvider.Context!, publishedDocumentUrlSegment, isoCode); + numberOfCachedUrls++; } } + _logger.LogInformation("Cached {NumberOfUrls} document URLs.", numberOfCachedUrls); + _isInitialized = true; scope.Complete(); } From 9964e0b8ecd2bdd595694a5bbc844a7b9e2d1d6e Mon Sep 17 00:00:00 2001 From: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com> Date: Fri, 13 Jun 2025 11:13:28 +0700 Subject: [PATCH 21/82] V16 QA Added acceptance tests for granular property value permission (#19458) * Updated due to test helper changes * Updated user group tests due to api helper changes * Updated tests for user group default configuration due to UI changes * Added tests for document property value permission * Added tests for document property value permission in content with block * Bumped version * Make specific tests run in the pipeline * Added skip tag and issue link for the failing tests * Added tests for granular property value permission * Fixed comment * Bumped version * Bumped version * Fixed comments * Bumped version and reverted npm command * Make all tests for user group permission run in the pipeline * Updated smokeTest command * Fixed comments * Reverted npm command --- ...entPropertyValueGranularPermission.spec.ts | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DocumentPropertyValueGranularPermission.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DocumentPropertyValueGranularPermission.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DocumentPropertyValueGranularPermission.spec.ts new file mode 100644 index 0000000000..8541d8d9d1 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DocumentPropertyValueGranularPermission.spec.ts @@ -0,0 +1,143 @@ +import { expect } from '@playwright/test'; +import {AliasHelper, ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; + +const testUser = ConstantHelper.testUserCredentials; +let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; + +const userGroupName = 'TestPropertyValuePermission'; +let userGroupId = null; + +const firstDocumentName = 'FirstTestDocument'; +const secondDocumentName = 'SecondTestDocument'; +const documentTypeName = 'TestDocumentType'; +const firstPropertyName = ['Textstring', 'text-box']; +const secondPropertyName = ['True/false', 'toggle']; +let documentTypeId = null; +let firstDocumentId = null; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.document.ensureNameNotExists(firstDocumentName); + await umbracoApi.document.ensureNameNotExists(secondDocumentName); + const firstPropertyData = await umbracoApi.dataType.getByName(firstPropertyName[0]); + const secondPropertyData = await umbracoApi.dataType.getByName(secondPropertyName[0]); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithTwoPropertyEditors(documentTypeName, firstPropertyName[0], firstPropertyData.id, secondPropertyName[0], secondPropertyData.id); + firstDocumentId = await umbracoApi.document.createDefaultDocument(firstDocumentName, documentTypeId); + await umbracoApi.document.createDefaultDocument(secondDocumentName, documentTypeId); +}); + +test.afterEach(async ({umbracoApi}) => { + // Ensure we are logged in to admin + await umbracoApi.loginToAdminUser(testUserCookieAndToken.cookie, testUserCookieAndToken.accessToken, testUserCookieAndToken.refreshToken); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.document.ensureNameNotExists(firstDocumentName); + await umbracoApi.document.ensureNameNotExists(secondDocumentName); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); +}); + +test('can only see property values for specific document with read UI enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithPermissionsForSpecificDocumentAndTwoPropertyValues(userGroupName, firstDocumentId, documentTypeId, firstPropertyName[0], true, false, secondPropertyName[0], true, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.goToContentWithName(firstDocumentName); + await umbracoUi.content.isPropertyEditorUiWithNameReadOnly(firstPropertyName[1]); + await umbracoUi.content.isPropertyEditorUiWithNameReadOnly(secondPropertyName[1]); + await umbracoUi.content.goToContentWithName(secondDocumentName); + await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.permissionDenied); +}); + +test('cannot see specific property value without UI read permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithPermissionsForSpecificDocumentAndTwoPropertyValues(userGroupName, firstDocumentId, documentTypeId, firstPropertyName[0], false, false, secondPropertyName[0], false, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + await umbracoUi.content.goToContentWithName(firstDocumentName); + + // Assert + await umbracoUi.content.isPropertyEditorUiWithNameVisible(firstPropertyName[1], false); + await umbracoUi.content.isPropertyEditorUiWithNameVisible(secondPropertyName[1], false); +}); + +test('can see specific property values with UI read permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithPermissionsForSpecificDocumentAndTwoPropertyValues(userGroupName, firstDocumentId, documentTypeId, firstPropertyName[0], true, false, secondPropertyName[0], true, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + await umbracoUi.content.goToContentWithName(firstDocumentName); + + // Assert + await umbracoUi.content.isPropertyEditorUiWithNameReadOnly(firstPropertyName[1]); + await umbracoUi.content.isPropertyEditorUiWithNameReadOnly(secondPropertyName[1]); +}); + +test('can see property with UI read enabled but not another property with UI read disabled in the same document', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithPermissionsForSpecificDocumentAndTwoPropertyValues(userGroupName, firstDocumentId, documentTypeId, firstPropertyName[0], true, false, secondPropertyName[0], false, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + await umbracoUi.content.goToContentWithName(firstDocumentName); + + // Assert + await umbracoUi.content.isPropertyEditorUiWithNameReadOnly(firstPropertyName[1]); + await umbracoUi.content.isPropertyEditorUiWithNameVisible(secondPropertyName[1], false); +}); + +// Remove .skip when the front-end is ready. +// Issue link: https://github.com/umbraco/Umbraco-CMS/issues/19395 +test.skip('can edit specific property values with UI read and write permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + const inputText = 'This is test text'; + userGroupId = await umbracoApi.userGroup.createUserGroupWithPermissionsForSpecificDocumentAndTwoPropertyValues(userGroupName, firstDocumentId, documentTypeId, firstPropertyName[0], true, true, secondPropertyName[0], true, true); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + await umbracoUi.content.goToContentWithName(firstDocumentName); + await umbracoUi.content.enterTextstring(inputText); + await umbracoUi.content.clickToggleButton(); + await umbracoUi.content.clickSaveButton(); + + // Assert + const firstDocumentData = await umbracoApi.document.getByName(firstDocumentName); + expect(firstDocumentData.values[0].alias).toEqual(AliasHelper.toAlias(firstPropertyName[0])); + expect(firstDocumentData.values[0].value).toEqual(inputText); + expect(firstDocumentData.values[1].alias).toEqual(AliasHelper.toAlias(secondPropertyName[0])); + expect(firstDocumentData.values[1].value).toEqual(true); +}); + +test('cannot see specific property values with UI write permission enabled and UI read permission disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithPermissionsForSpecificDocumentAndTwoPropertyValues(userGroupName, firstDocumentId, documentTypeId, firstPropertyName[0], false, true, secondPropertyName[0], false, true); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + await umbracoUi.content.goToContentWithName(firstDocumentName); + + // Assert + await umbracoUi.content.isPropertyEditorUiWithNameVisible(firstPropertyName[1], false); + await umbracoUi.content.isPropertyEditorUiWithNameVisible(secondPropertyName[1], false); +}); From 50272f7401b0ec09fd70735de7c4cd7bed214d76 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 13 Jun 2025 14:41:08 +0200 Subject: [PATCH 22/82] Removed duplicate/obsolete code fixing issue with document move permission checks (#19552) Removed duplicate/obsolete code fixing issue with document move permission checks. --- .../Controllers/Document/MoveDocumentController.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/MoveDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/MoveDocumentController.cs index 1141b34b5c..cdaafa904a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/MoveDocumentController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/MoveDocumentController.cs @@ -53,16 +53,6 @@ public class MoveDocumentController : DocumentControllerBase return Forbidden(); } - AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync( - User, - ContentPermissionResource.WithKeys(ActionMove.ActionLetter, new[] { moveDocumentRequestModel.Target?.Id, id }), - AuthorizationPolicies.ContentPermissionByResource); - - if (!authorizationResult.Succeeded) - { - return Forbidden(); - } - Attempt result = await _contentEditingService.MoveAsync( id, moveDocumentRequestModel.Target?.Id, From e6791246e49132c6af9d8acd9ff225ee9d8c0fab Mon Sep 17 00:00:00 2001 From: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com> Date: Tue, 17 Jun 2025 10:55:30 +0200 Subject: [PATCH 23/82] V16 QA Added acceptance tests for Webhook (#19545) * Added tests for webhook * Added tests for webhook trigger * Bumped version * Make all Webhook tests run in the pipeline * Fixed comment * Reverted npm command --- .../package-lock.json | 19 +- .../Umbraco.Tests.AcceptanceTest/package.json | 4 +- .../DefaultConfig/Webhook/Webhook.spec.ts | 188 ++++++++++++++ .../Webhook/WebhookTrigger.spec.ts | 242 ++++++++++++++++++ 4 files changed, 441 insertions(+), 12 deletions(-) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Webhook/Webhook.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Webhook/WebhookTrigger.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index bc9cbdfc34..f2cabf43c3 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -7,8 +7,8 @@ "name": "acceptancetest", "hasInstallScript": true, "dependencies": { - "@umbraco/json-models-builders": "^2.0.35", - "@umbraco/playwright-testhelpers": "^16.0.23", + "@umbraco/json-models-builders": "^2.0.36", + "@umbraco/playwright-testhelpers": "^16.0.25", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" @@ -58,20 +58,19 @@ } }, "node_modules/@umbraco/json-models-builders": { - "version": "2.0.35", - "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.35.tgz", - "integrity": "sha512-9qHvp7j8Lhj2x4xxWYz+ovRyoSnJIl4eLbSAst4nODCNqXSTWOy3+mzho3SeYWeil00/r0zXExHoXklVYi4iKA==", + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.36.tgz", + "integrity": "sha512-vQLL/y2ZIqrhCBe61W6YuA/C3KjGkva90WA09YfQuPL9HujOkJpVaP7KjJ0F8bBxwURy9tzMBFBLUz5fOdbkxQ==", "dependencies": { "camelize": "^1.0.1" } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "16.0.23", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-16.0.23.tgz", - "integrity": "sha512-9By0jqdscFh5pRFCpBEvoOCOpEOUG9eoSGvyLPnWjiJtfTCSm2OHARX8QcP5vCU65TO59w7JwUcXBTExaRIFXg==", - "license": "MIT", + "version": "16.0.25", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-16.0.25.tgz", + "integrity": "sha512-IvRkkrTIxlXbg2dw0RhAUgkb7KSBJCyktK6zJynOORgZ5RXRae19hqKk7yEu2EwJpTstl6m9AzoVf1x4b94x5w==", "dependencies": { - "@umbraco/json-models-builders": "2.0.35", + "@umbraco/json-models-builders": "2.0.36", "node-fetch": "^2.6.7" } }, diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index 5998bf61af..78c09e9719 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -20,8 +20,8 @@ "typescript": "^4.8.3" }, "dependencies": { - "@umbraco/json-models-builders": "^2.0.35", - "@umbraco/playwright-testhelpers": "^16.0.23", + "@umbraco/json-models-builders": "^2.0.36", + "@umbraco/playwright-testhelpers": "^16.0.25", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Webhook/Webhook.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Webhook/Webhook.spec.ts new file mode 100644 index 0000000000..7a42842d4b --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Webhook/Webhook.spec.ts @@ -0,0 +1,188 @@ +import {expect} from "@playwright/test"; +import {test} from '@umbraco/playwright-testhelpers'; + +const webhookName = 'Test Webhook'; +let webhookSiteToken = ''; + +test.beforeEach(async ({umbracoUi, umbracoApi}) => { + await umbracoApi.webhook.ensureNameNotExists(webhookName); + await umbracoUi.goToBackOffice(); + webhookSiteToken = await umbracoApi.webhook.generateWebhookSiteToken(); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.webhook.ensureNameNotExists(webhookName); +}); + +test('can create a webhook', async ({umbracoApi, umbracoUi}) => { + // Arrange + const event = 'Content Deleted'; + const webhookSiteUrl = umbracoApi.webhook.webhookSiteUrl + webhookSiteToken; + await umbracoUi.webhook.goToWebhooks(); + + // Act + await umbracoUi.webhook.clickWebhookCreateButton(); + await umbracoUi.webhook.enterWebhookName(webhookName); + await umbracoUi.webhook.enterUrl(webhookSiteUrl); + await umbracoUi.webhook.clickChooseEventButton(); + await umbracoUi.webhook.clickTextButtonWithName(event); + await umbracoUi.webhook.clickSubmitButton(); + await umbracoUi.webhook.clickSaveButton(); + + // Assert + await umbracoUi.webhook.isSuccessStateVisibleForSaveButton(); + expect(await umbracoApi.webhook.doesNameExist(webhookName)).toBeTruthy(); + expect(await umbracoApi.webhook.doesWebhookHaveUrl(webhookName, webhookSiteUrl)).toBeTruthy(); + expect(await umbracoApi.webhook.doesWebhookHaveEvent(webhookName, event)).toBeTruthy(); + await umbracoApi.webhook.isWebhookEnabled(webhookName); +}); + +test('can update webhook name', async ({umbracoApi, umbracoUi}) => { + // Arrange + const updatedName = 'Updated Webhook'; + await umbracoApi.webhook.createDefaultWebhook(webhookName, webhookSiteToken); + await umbracoUi.webhook.goToWebhookWithName(webhookName); + + // Act + await umbracoUi.webhook.enterWebhookName(updatedName); + await umbracoUi.webhook.clickSaveButton(); + + // Assert + await umbracoUi.webhook.isSuccessStateVisibleForSaveButton(); + expect(await umbracoApi.webhook.doesNameExist(updatedName)).toBeTruthy(); + expect(await umbracoApi.webhook.doesNameExist(webhookName)).toBeFalsy(); +}); + +test('can delete a webhook', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.webhook.createDefaultWebhook(webhookName, webhookSiteToken); + await umbracoUi.webhook.goToWebhooks(); + + // Act + await umbracoUi.webhook.clickDeleteWebhookWithName(webhookName); + await umbracoUi.webhook.clickConfirmToDeleteButton(); + + // Assert + expect(await umbracoApi.webhook.doesNameExist(webhookName)).toBeFalsy(); +}); + +test('can add content type for a webhook', async ({umbracoApi, umbracoUi}) => { + // Arrange + const contentTypeName = 'Test Document Type For Webhook'; + const documentTypeId = await umbracoApi.documentType.createDefaultDocumentType(contentTypeName); + await umbracoApi.webhook.createDefaultWebhook(webhookName, webhookSiteToken); + await umbracoUi.webhook.goToWebhookWithName(webhookName); + + // Act + await umbracoUi.webhook.clickChooseContentTypeButton(); + await umbracoUi.webhook.clickModalMenuItemWithName(contentTypeName); + await umbracoUi.webhook.clickChooseModalButton(); + await umbracoUi.webhook.clickSaveButton(); + + // Assert + await umbracoUi.webhook.isSuccessStateVisibleForSaveButton(); + expect(await umbracoApi.webhook.doesNameExist(webhookName)).toBeTruthy(); + expect(await umbracoApi.webhook.doesWebhookHaveContentTypeId(webhookName, documentTypeId)).toBeTruthy(); + + // Clean + await umbracoApi.documentType.ensureNameNotExists(contentTypeName); +}); + +test('can add header for a webhook', async ({umbracoApi, umbracoUi}) => { + // Arrange + const headerName = 'test-header-name'; + const headerValue = 'test-header-value'; + await umbracoApi.webhook.createDefaultWebhook(webhookName, webhookSiteToken); + await umbracoUi.webhook.goToWebhookWithName(webhookName); + + // Act + await umbracoUi.webhook.clickAddHeadersButton(); + await umbracoUi.webhook.enterHeaderName(headerName); + await umbracoUi.webhook.enterHeaderValue(headerValue); + await umbracoUi.webhook.clickSaveButton(); + + // Assert + await umbracoUi.webhook.isSuccessStateVisibleForSaveButton(); + expect(await umbracoApi.webhook.doesNameExist(webhookName)).toBeTruthy(); + expect(await umbracoApi.webhook.doesWebhookHaveHeader(webhookName, headerName, headerValue)).toBeTruthy(); +}); + +test('can disable a webhook', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.webhook.createDefaultWebhook(webhookName, webhookSiteToken); + await umbracoUi.webhook.goToWebhookWithName(webhookName); + + // Act + await umbracoUi.webhook.clickEnabledToggleButton(); + await umbracoUi.webhook.clickSaveButton(); + + // Assert + await umbracoUi.webhook.isSuccessStateVisibleForSaveButton(); + expect(await umbracoApi.webhook.doesNameExist(webhookName)).toBeTruthy(); + await umbracoApi.webhook.isWebhookEnabled(webhookName, false); +}); + +test('cannot remove all events from a webhook', async ({umbracoApi, umbracoUi}) => { + // Arrange + const event = 'Content Deleted'; + await umbracoApi.webhook.createDefaultWebhook(webhookName, webhookSiteToken, event); + await umbracoUi.webhook.goToWebhookWithName(webhookName); + + // Act + await umbracoUi.webhook.clickRemoveButtonForName(event); + await umbracoUi.webhook.clickSaveButton(); + + // Assert + await umbracoUi.content.isErrorNotificationVisible(); +}); + +test('can remove a content type from a webhook', async ({umbracoApi, umbracoUi}) => { + // Arrange + const event = 'Media Saved'; + const mediaTypeName = 'Audio'; + const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName); + await umbracoApi.webhook.createWebhookForSpecificContentType(webhookName, webhookSiteToken, event, mediaTypeName); + expect(await umbracoApi.webhook.doesWebhookHaveContentTypeId(webhookName, mediaTypeData.id)).toBeTruthy(); + await umbracoUi.webhook.goToWebhookWithName(webhookName); + + // Act + await umbracoUi.webhook.clickRemoveButtonForName(mediaTypeName); + await umbracoUi.webhook.clickConfirmRemoveButton(); + await umbracoUi.webhook.clickSaveButton(); + + // Assert + await umbracoUi.webhook.isSuccessStateVisibleForSaveButton(); + expect(await umbracoApi.webhook.doesWebhookHaveContentTypeId(webhookName, mediaTypeData.id)).toBeFalsy(); +}); + +test('can remove a header from a webhook', async ({umbracoApi, umbracoUi}) => { + // Arrange + const event = 'Content Published'; + const headerName = 'test-header-name'; + const headerValue = 'automation-test'; + await umbracoApi.webhook.createWebhookWithHeader(webhookName, webhookSiteToken, event, headerName, headerValue); + expect(await umbracoApi.webhook.doesWebhookHaveHeader(webhookName, headerName, headerValue)).toBeTruthy(); + await umbracoUi.webhook.goToWebhookWithName(webhookName); + + // Act + await umbracoUi.webhook.clickHeaderRemoveButton(); + await umbracoUi.webhook.clickSaveButton(); + + // Assert + await umbracoUi.webhook.isSuccessStateVisibleForSaveButton(); + expect(await umbracoApi.webhook.doesWebhookHaveHeader(webhookName, headerName, headerValue)).toBeFalsy(); +}); + +test('cannot add both content event and media event for a webhook', async ({umbracoApi, umbracoUi}) => { + // Arrange + const event = 'Content Published'; + await umbracoApi.webhook.createDefaultWebhook(webhookName, webhookSiteToken, event); + await umbracoUi.webhook.goToWebhookWithName(webhookName); + + // Act + await umbracoUi.webhook.clickChooseEventButton(); + + // Assert + await umbracoUi.webhook.isModalMenuItemWithNameDisabled('Media Saved'); + await umbracoUi.webhook.isModalMenuItemWithNameDisabled('Media Deleted'); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Webhook/WebhookTrigger.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Webhook/WebhookTrigger.spec.ts new file mode 100644 index 0000000000..5b9f22d45a --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Webhook/WebhookTrigger.spec.ts @@ -0,0 +1,242 @@ +import {expect} from "@playwright/test"; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; + +// Webhook +const webhookName = 'Test Webhook'; +let webhookSiteToken = ''; + +// Content +const documentName = 'Test Webhook Content'; +const documentTypeName = 'TestDocumentTypeForWebhook'; +let documentTypeId = ''; + +// Media +const mediaName = 'Test Webhook Media'; +const mediaTypeName = 'Image'; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.webhook.ensureNameNotExists(webhookName); + webhookSiteToken = await umbracoApi.webhook.generateWebhookSiteToken(); + documentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeName); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.webhook.ensureNameNotExists(webhookName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.document.ensureNameNotExists(documentName); + await umbracoApi.media.ensureNameNotExists(mediaName); +}); + +test('can trigger when content is published', async ({umbracoApi, umbracoUi}) => { + test.slow(); + + // Arrange + const event = 'Content Published'; + await umbracoApi.webhook.createDefaultWebhook(webhookName, webhookSiteToken, event); + await umbracoApi.document.createDefaultDocument(documentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.webhook.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(documentName); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.isSuccessStateVisibleForSaveAndPublishButton(); + const webhookSiteData = await umbracoApi.webhook.getWebhookSiteRequestResponse(webhookSiteToken); + expect(webhookSiteData[0].content).toContain(documentName); +}); + +test('can trigger when content is deleted', async ({umbracoApi, umbracoUi}) => { + test.slow(); + + // Arrange + const event = 'Content Deleted'; + await umbracoApi.webhook.createDefaultWebhook(webhookName, webhookSiteToken, event); + const contentId = await umbracoApi.document.createDefaultDocument(documentName, documentTypeId); + await umbracoApi.document.moveToRecycleBin(contentId); + await umbracoUi.goToBackOffice(); + await umbracoUi.webhook.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickEmptyRecycleBinButton(); + await umbracoUi.content.clickConfirmEmptyRecycleBinButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.emptiedRecycleBin); + const webhookSiteData = await umbracoApi.webhook.getWebhookSiteRequestResponse(webhookSiteToken); + expect(webhookSiteData[0].content).toContain(contentId); +}); + +test('can trigger when content is unpublished', async ({umbracoApi, umbracoUi}) => { + test.slow(); + + // Arrange + const event = 'Content Unpublished'; + await umbracoApi.webhook.createDefaultWebhook(webhookName, webhookSiteToken, event); + const contentId = await umbracoApi.document.createDefaultDocument(documentName, documentTypeId); + await umbracoApi.document.publish(contentId); + await umbracoUi.goToBackOffice(); + await umbracoUi.webhook.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuForContent(documentName); + await umbracoUi.content.clickUnpublishActionMenuOption(); + await umbracoUi.content.clickConfirmToUnpublishButton(); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.unpublished); + + // Assert + const webhookSiteData = await umbracoApi.webhook.getWebhookSiteRequestResponse(webhookSiteToken); + expect(webhookSiteData[0].content).toContain(contentId); +}); + +test('can trigger when media is saved', async ({umbracoApi, umbracoUi}) => { + test.slow(); + + // Arrange + const event = 'Media Saved'; + await umbracoApi.webhook.createDefaultWebhook(webhookName, webhookSiteToken, event); + const mediaId = await umbracoApi.media.createDefaultMediaWithImage(mediaName); + await umbracoUi.goToBackOffice(); + await umbracoUi.webhook.goToSection(ConstantHelper.sections.media); + + // Act + await umbracoUi.media.goToMediaWithName(mediaName); + await umbracoUi.media.clickSaveButton(); + await umbracoUi.content.isSuccessStateVisibleForSaveButton(); + + // Assert + const webhookSiteData = await umbracoApi.webhook.getWebhookSiteRequestResponse(webhookSiteToken); + expect(webhookSiteData[0].content).toContain(mediaName); + expect(webhookSiteData[0].content).toContain(mediaId); +}); + +test('can trigger when media is deleted', async ({umbracoApi, umbracoUi}) => { + test.slow(); + + // Arrange + const event = 'Media Deleted'; + await umbracoApi.webhook.createDefaultWebhook(webhookName, webhookSiteToken, event); + const mediaId = await umbracoApi.media.createDefaultMediaWithImage(mediaName); + await umbracoApi.media.trashMediaItem(mediaName); + await umbracoUi.goToBackOffice(); + await umbracoUi.webhook.goToSection(ConstantHelper.sections.media); + + // Act + await umbracoUi.media.isItemVisibleInRecycleBin(mediaName); + await umbracoUi.media.deleteMediaItem(mediaName); + await umbracoUi.media.waitForMediaToBeTrashed(); + + // Assert + const webhookSiteData = await umbracoApi.webhook.getWebhookSiteRequestResponse(webhookSiteToken); + expect(webhookSiteData[0].content).toContain(mediaId); +}); + +test('can trigger the webhook for a specific media type', async ({umbracoApi, umbracoUi}) => { + test.slow(); + + // Arrange + const event = 'Media Deleted'; + const secondMediaName = 'Test Second Media'; + await umbracoApi.webhook.createWebhookForSpecificContentType(webhookName, webhookSiteToken, event, mediaTypeName); + const mediaId = await umbracoApi.media.createDefaultMediaWithImage(mediaName); + const secondMediaId = await umbracoApi.media.createDefaultMediaWithArticle(secondMediaName); + await umbracoApi.media.trashMediaItem(mediaName); + await umbracoApi.media.trashMediaItem(secondMediaName); + await umbracoUi.goToBackOffice(); + await umbracoUi.webhook.goToSection(ConstantHelper.sections.media); + + // Act + await umbracoUi.media.isItemVisibleInRecycleBin(mediaName); + await umbracoUi.media.deleteMediaItem(mediaName); + await umbracoUi.media.deleteMediaItem(secondMediaName); + + // Assert + const webhookSiteData = await umbracoApi.webhook.getWebhookSiteRequestResponse(webhookSiteToken); + expect(webhookSiteData[0].content).toContain(mediaId); + expect(webhookSiteData[0].content).not.toContain(secondMediaId); + + // Clean + await umbracoApi.media.ensureNameNotExists(secondMediaName); +}); + +test('can trigger the webhook for a specific content type', async ({umbracoApi, umbracoUi}) => { + test.slow(); + + // Arrange + const event = 'Content Published'; + const secondDocumentName = 'Second Test Webhook Content'; + const secondDocumentTypeName = 'SecondTestDocumentTypeForWebhook'; + const secondDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(secondDocumentTypeName); + await umbracoApi.document.createDefaultDocument(secondDocumentName, secondDocumentTypeId); + await umbracoApi.webhook.createWebhookForSpecificContentType(webhookName, webhookSiteToken, event, documentTypeName); + await umbracoApi.document.createDefaultDocument(documentName, documentTypeId); + + await umbracoUi.goToBackOffice(); + await umbracoUi.webhook.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuForContent(documentName); + await umbracoUi.content.clickPublishActionMenuOption(); + await umbracoUi.content.clickConfirmToPublishButton(); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + await umbracoUi.content.clickActionsMenuForContent(secondDocumentName); + await umbracoUi.content.clickPublishActionMenuOption(); + await umbracoUi.content.clickConfirmToPublishButton(); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + // Assert + const webhookSiteData = await umbracoApi.webhook.getWebhookSiteRequestResponse(webhookSiteToken); + expect(webhookSiteData[0].content).toContain(documentName); + expect(webhookSiteData[0].content).not.toContain(secondDocumentName); + + // Clean + await umbracoApi.documentType.ensureNameNotExists(secondDocumentTypeName); + await umbracoApi.document.ensureNameNotExists(secondDocumentName); +}); + +test('cannot trigger when the webhook is disabled', async ({umbracoApi, umbracoUi}) => { + test.slow(); + + // Arrange + const event = 'Content Published'; + await umbracoApi.webhook.createDefaultWebhook(webhookName, webhookSiteToken, event, false); + await umbracoApi.document.createDefaultDocument(documentName, documentTypeId); + + await umbracoUi.goToBackOffice(); + await umbracoUi.webhook.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuForContent(documentName); + await umbracoUi.content.clickPublishActionMenuOption(); + await umbracoUi.content.clickConfirmToPublishButton(); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + // Assert + const webhookSiteData = await umbracoApi.webhook.getWebhookSiteRequestResponse(webhookSiteToken, 7000); + expect(webhookSiteData).toBeFalsy(); +}); + +test('can custom header for the webhook request', async ({umbracoApi, umbracoUi}) => { + test.slow(); + + // Arrange + const event = 'Content Published'; + const headerName = 'test-header-name'; + const headerValue = 'automation-test'; + await umbracoApi.webhook.createWebhookWithHeader(webhookName, webhookSiteToken, event, headerName, headerValue); + await umbracoApi.document.createDefaultDocument(documentName, documentTypeId); + + await umbracoUi.goToBackOffice(); + await umbracoUi.webhook.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuForContent(documentName); + await umbracoUi.content.clickPublishActionMenuOption(); + await umbracoUi.content.clickConfirmToPublishButton(); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + // Assert + const webhookSiteData = await umbracoApi.webhook.getWebhookSiteRequestResponse(webhookSiteToken); + expect(webhookSiteData[0].headers[headerName]).toEqual([headerValue]); +}); \ No newline at end of file From ef453ad62eebdd12d058f17f36760eaa14df07f9 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 24 Jun 2025 09:31:26 +0200 Subject: [PATCH 24/82] Docs: Collection example (#19593) * add basic collection example * add card view example * update example readme * Add workspace view example with collection --- .../examples/collection/README.md | 20 +++++ .../card-view/collection-view.element.ts | 82 +++++++++++++++++ .../collection/collection/card-view/index.ts | 1 + .../collection/card-view/manifests.ts | 23 +++++ .../collection/collection/constants.ts | 1 + .../collection/collection/manifests.ts | 20 +++++ .../repository/collection.repository.ts | 64 +++++++++++++ .../collection/repository/constants.ts | 1 + .../collection/repository/manifests.ts | 10 +++ .../collection/collection/repository/types.ts | 9 ++ .../table-view/collection-view.element.ts | 89 +++++++++++++++++++ .../collection/collection/table-view/index.ts | 1 + .../collection/table-view/manifests.ts | 23 +++++ .../dashboard-with-collection.element.ts | 23 +++++ .../dashboard-with-collection/manifests.ts | 14 +++ .../examples/collection/index.ts | 9 ++ .../manifests.ts | 25 ++++++ ...server-extension-registrator.controller.ts | 2 +- 18 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web.UI.Client/examples/collection/README.md create mode 100644 src/Umbraco.Web.UI.Client/examples/collection/collection/card-view/collection-view.element.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/collection/collection/card-view/index.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/collection/collection/card-view/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/collection/collection/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/collection/collection/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/collection/collection/repository/collection.repository.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/collection/collection/repository/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/collection/collection/repository/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/collection/collection/repository/types.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/collection/collection/table-view/collection-view.element.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/collection/collection/table-view/index.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/collection/collection/table-view/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/collection/dashboard-with-collection/dashboard-with-collection.element.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/collection/dashboard-with-collection/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/collection/index.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/collection/workspace-view-with-collection/manifests.ts diff --git a/src/Umbraco.Web.UI.Client/examples/collection/README.md b/src/Umbraco.Web.UI.Client/examples/collection/README.md new file mode 100644 index 0000000000..d845cb46aa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/collection/README.md @@ -0,0 +1,20 @@ +# Collection Example + +This example demonstrates how to register a collection with collection views. + +The example includes: + +- Collection Registration +- Collection Repository +- Collection Pagination +- Table Collection View +- Card Collection View +- Collection as a Dashboard +- Collection as a Workspace View + +TODO: This example is not complete, it is missing the following features: + +- Collection Action +- Collection Filtering +- Entity Actions +- Selection + Bulk Actions diff --git a/src/Umbraco.Web.UI.Client/examples/collection/collection/card-view/collection-view.element.ts b/src/Umbraco.Web.UI.Client/examples/collection/collection/card-view/collection-view.element.ts new file mode 100644 index 0000000000..64a49f2073 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/collection/collection/card-view/collection-view.element.ts @@ -0,0 +1,82 @@ +import type { ExampleCollectionItemModel } from '../repository/types.js'; +import type { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection'; +import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection'; +import { css, html, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; + +@customElement('example-card-collection-view') +export class ExampleCardCollectionViewElement extends UmbLitElement { + @state() + private _items: Array = []; + + #collectionContext?: UmbDefaultCollectionContext; + + constructor() { + super(); + + this.consumeContext(UMB_COLLECTION_CONTEXT, (instance) => { + this.#collectionContext = instance; + this.#observeCollectionItems(); + }); + } + + #observeCollectionItems() { + this.observe(this.#collectionContext?.items, (items) => (this._items = items || []), 'umbCollectionItemsObserver'); + } + + override render() { + return html` +
+ ${repeat( + this._items, + (item) => item.unique, + (item) => + html` + +
${item.name}
+
`, + )} +
+ `; + } + + static override styles = [ + UmbTextStyles, + css` + :host { + display: flex; + flex-direction: column; + } + + #card-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + grid-auto-rows: 200px; + gap: var(--uui-size-space-5); + } + + uui-card { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + height: 100%; + + uui-icon { + font-size: 2em; + margin-bottom: var(--uui-size-space-4); + } + } + `, + ]; +} + +export { ExampleCardCollectionViewElement as element }; + +declare global { + interface HTMLElementTagNameMap { + 'example-card-collection-view': ExampleCardCollectionViewElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/examples/collection/collection/card-view/index.ts b/src/Umbraco.Web.UI.Client/examples/collection/collection/card-view/index.ts new file mode 100644 index 0000000000..e9d6c50fc5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/collection/collection/card-view/index.ts @@ -0,0 +1 @@ +export { UMB_LANGUAGE_TABLE_COLLECTION_VIEW_ALIAS } from './manifests.js'; diff --git a/src/Umbraco.Web.UI.Client/examples/collection/collection/card-view/manifests.ts b/src/Umbraco.Web.UI.Client/examples/collection/collection/card-view/manifests.ts new file mode 100644 index 0000000000..bf7299c530 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/collection/collection/card-view/manifests.ts @@ -0,0 +1,23 @@ +import { EXAMPLE_COLLECTION_ALIAS } from '../constants.js'; +import { UMB_COLLECTION_ALIAS_CONDITION } from '@umbraco-cms/backoffice/collection'; + +export const manifests: Array = [ + { + type: 'collectionView', + alias: 'Example.CollectionView.Card', + name: 'Example Card Collection View', + js: () => import('./collection-view.element.js'), + weight: 50, + meta: { + label: 'Card', + icon: 'icon-grid', + pathName: 'card', + }, + conditions: [ + { + alias: UMB_COLLECTION_ALIAS_CONDITION, + match: EXAMPLE_COLLECTION_ALIAS, + }, + ], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/collection/collection/constants.ts b/src/Umbraco.Web.UI.Client/examples/collection/collection/constants.ts new file mode 100644 index 0000000000..7afa1073f7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/collection/collection/constants.ts @@ -0,0 +1 @@ +export const EXAMPLE_COLLECTION_ALIAS = 'Example.Collection'; diff --git a/src/Umbraco.Web.UI.Client/examples/collection/collection/manifests.ts b/src/Umbraco.Web.UI.Client/examples/collection/collection/manifests.ts new file mode 100644 index 0000000000..eb8b44d061 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/collection/collection/manifests.ts @@ -0,0 +1,20 @@ +import { EXAMPLE_COLLECTION_ALIAS } from './constants.js'; +import { EXAMPLE_COLLECTION_REPOSITORY_ALIAS } from './repository/constants.js'; +import { manifests as cardViewManifests } from './card-view/manifests.js'; +import { manifests as repositoryManifests } from './repository/manifests.js'; +import { manifests as tableViewManifests } from './table-view/manifests.js'; + +export const manifests: Array = [ + { + type: 'collection', + kind: 'default', + alias: EXAMPLE_COLLECTION_ALIAS, + name: 'Example Collection', + meta: { + repositoryAlias: EXAMPLE_COLLECTION_REPOSITORY_ALIAS, + }, + }, + ...cardViewManifests, + ...repositoryManifests, + ...tableViewManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/collection/collection/repository/collection.repository.ts b/src/Umbraco.Web.UI.Client/examples/collection/collection/repository/collection.repository.ts new file mode 100644 index 0000000000..ecd8a1495c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/collection/collection/repository/collection.repository.ts @@ -0,0 +1,64 @@ +import type { ExampleCollectionFilterModel, ExampleCollectionItemModel } from './types.js'; +import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository'; +import type { UmbCollectionRepository } from '@umbraco-cms/backoffice/collection'; + +export class ExampleCollectionRepository + extends UmbRepositoryBase + implements UmbCollectionRepository +{ + async requestCollection(args: ExampleCollectionFilterModel) { + const skip = args.skip || 0; + const take = args.take || 10; + + // Simulating a data fetch. This would in most cases be replaced with an API call. + let items = [ + { + unique: '3e31e9c5-7d66-4c99-a9e5-d9f2b1e2b22f', + entityType: 'example', + name: 'Example Item 1', + }, + { + unique: 'bc9b6e24-4b11-4dd6-8d4e-7c4f70e59f3c', + entityType: 'example', + name: 'Example Item 2', + }, + { + unique: '5a2f4e3a-ef7e-470e-8c3c-3d859c02ae0d', + entityType: 'example', + name: 'Example Item 3', + }, + { + unique: 'f4c3d8b8-6d79-4c87-9aa9-56b1d8fda702', + entityType: 'example', + name: 'Example Item 4', + }, + { + unique: 'c9f0a8a3-1b49-4724-bde3-70e31592eb6e', + entityType: 'example', + name: 'Example Item 5', + }, + ]; + + // Simulating filtering based on the args + if (args.filter) { + items = items.filter((item) => item.name.toLowerCase().includes(args.filter!.toLowerCase())); + } + + // Get the total number of items before pagination + const totalItems = items.length; + + // Simulating pagination + const start = skip; + const end = start + take; + items = items.slice(start, end); + + const data = { + items, + total: totalItems, + }; + + return { data }; + } +} + +export { ExampleCollectionRepository as api }; diff --git a/src/Umbraco.Web.UI.Client/examples/collection/collection/repository/constants.ts b/src/Umbraco.Web.UI.Client/examples/collection/collection/repository/constants.ts new file mode 100644 index 0000000000..edfe07d179 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/collection/collection/repository/constants.ts @@ -0,0 +1 @@ +export const EXAMPLE_COLLECTION_REPOSITORY_ALIAS = 'Example.Repository.Collection'; diff --git a/src/Umbraco.Web.UI.Client/examples/collection/collection/repository/manifests.ts b/src/Umbraco.Web.UI.Client/examples/collection/collection/repository/manifests.ts new file mode 100644 index 0000000000..c699409c4e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/collection/collection/repository/manifests.ts @@ -0,0 +1,10 @@ +import { EXAMPLE_COLLECTION_REPOSITORY_ALIAS } from './constants.js'; + +export const manifests: Array = [ + { + type: 'repository', + alias: EXAMPLE_COLLECTION_REPOSITORY_ALIAS, + name: 'Example Collection Repository', + api: () => import('./collection.repository.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/collection/collection/repository/types.ts b/src/Umbraco.Web.UI.Client/examples/collection/collection/repository/types.ts new file mode 100644 index 0000000000..4430638c16 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/collection/collection/repository/types.ts @@ -0,0 +1,9 @@ +import type { UmbCollectionFilterModel } from '@umbraco-cms/backoffice/collection'; + +export interface ExampleCollectionItemModel { + unique: string; + entityType: string; + name: string; +} + +export interface ExampleCollectionFilterModel extends UmbCollectionFilterModel {} diff --git a/src/Umbraco.Web.UI.Client/examples/collection/collection/table-view/collection-view.element.ts b/src/Umbraco.Web.UI.Client/examples/collection/collection/table-view/collection-view.element.ts new file mode 100644 index 0000000000..530ee846dd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/collection/collection/table-view/collection-view.element.ts @@ -0,0 +1,89 @@ +import type { ExampleCollectionItemModel } from '../repository/types.js'; +import type { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection'; +import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection'; +import type { UmbTableColumn, UmbTableConfig, UmbTableItem } from '@umbraco-cms/backoffice/components'; +import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; + +@customElement('example-table-collection-view') +export class ExampleTableCollectionViewElement extends UmbLitElement { + @state() + private _tableConfig: UmbTableConfig = { + allowSelection: false, + }; + + @state() + private _tableColumns: Array = [ + { + name: 'Name', + alias: 'name', + }, + ]; + + @state() + private _tableItems: Array = []; + + #collectionContext?: UmbDefaultCollectionContext; + + constructor() { + super(); + + this.consumeContext(UMB_COLLECTION_CONTEXT, (instance) => { + this.#collectionContext = instance; + this.#observeCollectionItems(); + }); + } + + #observeCollectionItems() { + this.observe( + this.#collectionContext?.items, + (items) => this.#createTableItems(items), + 'umbCollectionItemsObserver', + ); + } + + #createTableItems(items: Array | undefined) { + if (!items) { + this._tableItems = []; + return; + } + + this._tableItems = items.map((item) => { + return { + id: item.unique, + icon: 'icon-newspaper', + data: [ + { + columnAlias: 'name', + value: item.name, + }, + ], + }; + }); + } + + override render() { + return html` + + `; + } + + static override styles = [ + UmbTextStyles, + css` + :host { + display: flex; + flex-direction: column; + } + `, + ]; +} + +export { ExampleTableCollectionViewElement as element }; + +declare global { + interface HTMLElementTagNameMap { + 'example-table-collection-view': ExampleTableCollectionViewElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/examples/collection/collection/table-view/index.ts b/src/Umbraco.Web.UI.Client/examples/collection/collection/table-view/index.ts new file mode 100644 index 0000000000..e9d6c50fc5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/collection/collection/table-view/index.ts @@ -0,0 +1 @@ +export { UMB_LANGUAGE_TABLE_COLLECTION_VIEW_ALIAS } from './manifests.js'; diff --git a/src/Umbraco.Web.UI.Client/examples/collection/collection/table-view/manifests.ts b/src/Umbraco.Web.UI.Client/examples/collection/collection/table-view/manifests.ts new file mode 100644 index 0000000000..495a2bcbe9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/collection/collection/table-view/manifests.ts @@ -0,0 +1,23 @@ +import { EXAMPLE_COLLECTION_ALIAS } from '../constants.js'; +import { UMB_COLLECTION_ALIAS_CONDITION } from '@umbraco-cms/backoffice/collection'; + +export const manifests: Array = [ + { + type: 'collectionView', + alias: 'Example.CollectionView.Table', + name: 'Example Table Collection View', + js: () => import('./collection-view.element.js'), + weight: 100, + meta: { + label: 'Table', + icon: 'icon-list', + pathName: 'table', + }, + conditions: [ + { + alias: UMB_COLLECTION_ALIAS_CONDITION, + match: EXAMPLE_COLLECTION_ALIAS, + }, + ], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/collection/dashboard-with-collection/dashboard-with-collection.element.ts b/src/Umbraco.Web.UI.Client/examples/collection/dashboard-with-collection/dashboard-with-collection.element.ts new file mode 100644 index 0000000000..c11c88ef71 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/collection/dashboard-with-collection/dashboard-with-collection.element.ts @@ -0,0 +1,23 @@ +import { EXAMPLE_COLLECTION_ALIAS } from '../collection/constants.js'; +import { html, customElement, LitElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; +import type { UmbCollectionConfiguration } from '@umbraco-cms/backoffice/collection'; + +@customElement('example-dashboard-with-collection') +export class ExampleDashboardWithCollection extends UmbElementMixin(LitElement) { + #config: UmbCollectionConfiguration = { + pageSize: 3, + }; + + override render() { + return html``; + } +} + +export { ExampleDashboardWithCollection as element }; + +declare global { + interface HTMLElementTagNameMap { + 'example-dashboard-with-collection': ExampleDashboardWithCollection; + } +} diff --git a/src/Umbraco.Web.UI.Client/examples/collection/dashboard-with-collection/manifests.ts b/src/Umbraco.Web.UI.Client/examples/collection/dashboard-with-collection/manifests.ts new file mode 100644 index 0000000000..3f8c7a6715 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/collection/dashboard-with-collection/manifests.ts @@ -0,0 +1,14 @@ +export const manifests: Array = [ + { + type: 'dashboard', + kind: 'default', + name: 'Example Dashboard With Collection', + alias: 'Example.Dashboard.WithCollection', + element: () => import('./dashboard-with-collection.element.js'), + weight: 3000, + meta: { + label: 'Collection Example', + pathname: 'collection-example', + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/collection/index.ts b/src/Umbraco.Web.UI.Client/examples/collection/index.ts new file mode 100644 index 0000000000..4d7691f330 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/collection/index.ts @@ -0,0 +1,9 @@ +import { manifests as collectionManifests } from './collection/manifests.js'; +import { manifests as dashboardManifests } from './dashboard-with-collection/manifests.js'; +import { manifests as workspaceViewManifests } from './workspace-view-with-collection/manifests.js'; + +export const manifests: Array = [ + ...collectionManifests, + ...dashboardManifests, + ...workspaceViewManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/collection/workspace-view-with-collection/manifests.ts b/src/Umbraco.Web.UI.Client/examples/collection/workspace-view-with-collection/manifests.ts new file mode 100644 index 0000000000..d4650f9f65 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/collection/workspace-view-with-collection/manifests.ts @@ -0,0 +1,25 @@ +import { EXAMPLE_COLLECTION_ALIAS } from '../collection/constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; +import { UMB_DOCUMENT_WORKSPACE_ALIAS } from '@umbraco-cms/backoffice/document'; + +export const manifests: Array = [ + { + type: 'workspaceView', + kind: 'collection', + name: 'Example Workspace View With Collection', + alias: 'Example.WorkspaceView.WithCollection', + weight: 3000, + meta: { + label: 'Collection Example', + pathname: 'collection-example', + icon: 'icon-layers', + collectionAlias: EXAMPLE_COLLECTION_ALIAS, + }, + conditions: [ + { + alias: UMB_WORKSPACE_CONDITION_ALIAS, + match: UMB_DOCUMENT_WORKSPACE_ALIAS, + }, + ], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/server-extension-registrator.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/server-extension-registrator.controller.ts index bd366ddb06..786e9bcf8b 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/server-extension-registrator.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/server-extension-registrator.controller.ts @@ -67,7 +67,7 @@ export class UmbServerExtensionRegistrator extends UmbControllerBase { const apiBaseUrl = serverContext?.getServerUrl(); - packages.forEach((p) => { + packages?.forEach((p) => { p.extensions?.forEach((e) => { // Crudely validate that the extension at least follows a basic manifest structure // Idea: Use `Zod` to validate the manifest From 6237ddca8937d50ab70fe711404e4963f3f77807 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 24 Jun 2025 10:08:09 +0200 Subject: [PATCH 25/82] Improves the layout on the create blueprint dialog (#19556) * Improves the layout on the create blueprint dialog. * localize texts --------- Co-authored-by: Mads Rasmussen --- .../modal/create-blueprint-modal.element.ts | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create-blueprint/modal/create-blueprint-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create-blueprint/modal/create-blueprint-modal.element.ts index 9fef11870e..988e2221a9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create-blueprint/modal/create-blueprint-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create-blueprint/modal/create-blueprint-modal.element.ts @@ -38,21 +38,21 @@ export class UmbCreateBlueprintModalElement extends UmbModalBaseElement< this.modalContext?.submit(); } - #renderBlueprintName() { - return html`Create a new Content Template from ${this._documentName} - A Content Template is predefined content that an editor can select to use as the basis for creating new content . - Name - (this._blueprintName = e.target.value as string)}>`; - } - override render() { return html` - - ${this.#renderBlueprintName()} + + + + +
+ (this._blueprintName = e.target.value as string)}> +
+
+
Date: Tue, 24 Jun 2025 09:10:02 +0100 Subject: [PATCH 26/82] Variant workspace breadcrumb item parentheses (#19599) * Refactored breadcrumb variant name The parentheses will be added to unnamed variant ancestor items. * Adds `last-item` attribute (for semantics) * Imports tidy-up * Refactored `#getHref` to early exit for `.isFolder` Saves on the string allocation. --- .../workspace-menu-breadcrumb.element.ts | 13 +++--- ...rkspace-variant-menu-breadcrumb.element.ts | 40 +++++++++++-------- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-menu-breadcrumb/workspace-menu-breadcrumb.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-menu-breadcrumb/workspace-menu-breadcrumb.element.ts index 21b55ca72e..4160be3e0b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-menu-breadcrumb/workspace-menu-breadcrumb.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-menu-breadcrumb/workspace-menu-breadcrumb.element.ts @@ -2,12 +2,9 @@ import { UMB_WORKSPACE_CONTEXT } from '../../../workspace.context-token.js'; import { css, customElement, html, ifDefined, map, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UMB_MENU_STRUCTURE_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/menu'; import { UMB_SECTION_CONTEXT } from '@umbraco-cms/backoffice/section'; -import { - UMB_MENU_STRUCTURE_WORKSPACE_CONTEXT, - type UmbMenuStructureWorkspaceContext, - type UmbStructureItemModel, -} from '@umbraco-cms/backoffice/menu'; +import type { UmbMenuStructureWorkspaceContext, UmbStructureItemModel } from '@umbraco-cms/backoffice/menu'; @customElement('umb-workspace-breadcrumb') export class UmbWorkspaceBreadcrumbElement extends UmbLitElement { @@ -65,8 +62,8 @@ export class UmbWorkspaceBreadcrumbElement extends UmbLitElement { } #getHref(structureItem: UmbStructureItemModel) { - const workspaceBasePath = `section/${this.#sectionContext?.getPathname()}/workspace/${structureItem.entityType}/edit`; - return structureItem.isFolder ? undefined : `${workspaceBasePath}/${structureItem.unique}`; + if (structureItem.isFolder) return undefined; + return `section/${this.#sectionContext?.getPathname()}/workspace/${structureItem.entityType}/edit/${structureItem.unique}`; } override render() { @@ -79,7 +76,7 @@ export class UmbWorkspaceBreadcrumbElement extends UmbLitElement { >${this.localize.string(structureItem.name)}`, )} - ${this._name} + ${this._name} `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-variant-menu-breadcrumb/workspace-variant-menu-breadcrumb.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-variant-menu-breadcrumb/workspace-variant-menu-breadcrumb.element.ts index 98973543b4..05a7d9c449 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-variant-menu-breadcrumb/workspace-variant-menu-breadcrumb.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-variant-menu-breadcrumb/workspace-variant-menu-breadcrumb.element.ts @@ -1,15 +1,14 @@ -import { UMB_VARIANT_WORKSPACE_CONTEXT, type UmbVariantDatasetWorkspaceContext } from '../../../contexts/index.js'; +import { UMB_VARIANT_WORKSPACE_CONTEXT } from '../../../contexts/index.js'; +import type { UmbVariantDatasetWorkspaceContext } from '../../../contexts/index.js'; import { css, customElement, html, ifDefined, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import { UMB_APP_LANGUAGE_CONTEXT } from '@umbraco-cms/backoffice/language'; +import { UMB_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/menu'; import { UMB_SECTION_CONTEXT } from '@umbraco-cms/backoffice/section'; import type { UmbAppLanguageContext } from '@umbraco-cms/backoffice/language'; -import { - UMB_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT, - type UmbVariantStructureItemModel, -} from '@umbraco-cms/backoffice/menu'; +import type { UmbVariantStructureItemModel } from '@umbraco-cms/backoffice/menu'; @customElement('umb-workspace-variant-menu-breadcrumb') export class UmbWorkspaceVariantMenuBreadcrumbElement extends UmbLitElement { @@ -96,19 +95,28 @@ export class UmbWorkspaceVariantMenuBreadcrumbElement extends UmbLitElement { // TODO: we should move the fallback name logic to a helper class. It will be used in multiple places #getItemVariantName(structureItem: UmbVariantStructureItemModel) { - const fallbackName = - structureItem.variants.find((variant) => variant.culture === this._appDefaultCulture)?.name ?? - structureItem.variants[0].name ?? - 'Unknown'; - const name = structureItem.variants.find((variant) => this._workspaceActiveVariantId?.compare(variant))?.name; - return name ?? `(${fallbackName})`; + // If the active workspace is a variant, we will try to find the matching variant name. + if (!this._workspaceActiveVariantId?.isInvariant()) { + const variant = structureItem.variants.find((variantId) => this._workspaceActiveVariantId?.compare(variantId)); + if (variant) { + return variant.name; + } + } + + // If the active workspace is invariant, we will try to find the variant that matches the app default culture. + const variant = structureItem.variants.find((variant) => variant.culture === this._appDefaultCulture); + if (variant) { + return `(${variant.name})`; + } + + // If an active variant can not be found, then we fallback to the first variant name or a generic "unknown" label. + return structureItem.variants?.[0]?.name ?? '(#general_unknown)'; } #getHref(structureItem: any) { + if (structureItem.isFolder) return undefined; const workspaceBasePath = `section/${this.#sectionContext?.getPathname()}/workspace/${structureItem.entityType}/edit`; - return structureItem.isFolder - ? undefined - : `${workspaceBasePath}/${structureItem.unique}/${this._workspaceActiveVariantId?.culture}`; + return `${workspaceBasePath}/${structureItem.unique}/${this._workspaceActiveVariantId?.culture}`; } override render() { @@ -116,11 +124,11 @@ export class UmbWorkspaceVariantMenuBreadcrumbElement extends UmbLitElement { ${this._structure.map( (structureItem) => - html`${this.localize.string(this.#getItemVariantName(structureItem))}`, )} - ${this._name} + ${this._name} `; } From b41eecf58c0f98c56fc701f14c16ce551a82e4a3 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 24 Jun 2025 10:45:31 +0200 Subject: [PATCH 27/82] V16: Document shows blank page when clicking an active link (#19553) * fix: if no workspace views are found at all, show a not found page * fix: rather than redirecting to the first available tab, which may not always be available on secondary routing, let the router display the first tab on an empty url this mirrors how workspace views are displayed in umb-workspace-editor --- .../views/edit/content-editor.element.ts | 24 ++++++++++--------- .../workspace-editor.element.ts | 12 ++++++---- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor.element.ts index 693808923e..8432f1c8cf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor.element.ts @@ -85,7 +85,7 @@ export class UmbContentWorkspaceViewEditElement extends UmbLitElement implements if (this._hasRootGroups) { routes.push({ - path: `root`, + path: 'root', component: () => import('./content-editor-tab.element.js'), setup: (component) => { (component as UmbContentWorkspaceViewEditTabElement).containerId = null; @@ -108,17 +108,17 @@ export class UmbContentWorkspaceViewEditElement extends UmbLitElement implements if (routes.length !== 0) { routes.push({ + ...routes[0], + unique: routes[0].path, path: '', - pathMatch: 'full', - redirectTo: routes[0].path, - }); - - routes.push({ - path: `**`, - component: async () => (await import('@umbraco-cms/backoffice/router')).UmbRouteNotFoundElement, }); } + routes.push({ + path: `**`, + component: async () => (await import('@umbraco-cms/backoffice/router')).UmbRouteNotFoundElement, + }); + this._routes = routes; } @@ -132,18 +132,20 @@ export class UmbContentWorkspaceViewEditElement extends UmbLitElement implements ? html` ` : ''} ${repeat( this._tabs, (tab) => tab.name, - (tab) => { + (tab, index) => { const path = this._routerPath + '/tab/' + encodeFolderName(tab.name || ''); return html``; }, )} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-editor/workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-editor/workspace-editor.element.ts index 6c16b3aac4..f55bcd8c89 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-editor/workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-editor/workspace-editor.element.ts @@ -75,13 +75,15 @@ export class UmbWorkspaceEditorElement extends UmbLitElement { // Duplicate first workspace and use it for the empty path scenario. [NL] newRoutes.push({ ...newRoutes[0], unique: newRoutes[0].path, path: '' }); - - newRoutes.push({ - path: `**`, - component: async () => (await import('@umbraco-cms/backoffice/router')).UmbRouteNotFoundElement, - }); } + // Add a catch-all route for not found + // This will be the last route, so it will only match if no other routes match or if no workspace views are defined. + newRoutes.push({ + path: `**`, + component: async () => (await import('@umbraco-cms/backoffice/router')).UmbRouteNotFoundElement, + }); + this._routes = newRoutes; } From 55506bac3ac143140374e2071bef3f4d43246fb2 Mon Sep 17 00:00:00 2001 From: Laura Neto <12862535+lauraneto@users.noreply.github.com> Date: Tue, 24 Jun 2025 13:43:34 +0200 Subject: [PATCH 28/82] Simplify creating content from a blueprint programmatically (#19528) * Rename `IContentService.CreateContentFromBlueprint` to `CreateBlueprintFromContent` In reality, this method is used by the core to create a blueprint from content, and not the other way around, which doesn't need new ids. This was causing confusion, so the old name has been marked as deprecated in favor of the new name. If developers want to create content from blueprints they should use `IContentBlueprintEditingService.GetScaffoldedAsync()` instead, which is what is used by the management api. * Added integration tests to verify that new block ids are generated when creating content from a blueprint * Return copy of the blueprint in `ContentBlueprintEditingService.GetScaffoldedAsync` instead of the blueprint itself * Update CreateContentFromBlueprint xml docs to mention both replacement methods * Fix tests for rich text blocks * Small re-organization * Adjusted tests that were still referencing `ContentService.CreateContentFromBlueprint` * Add default implementation to new CreateBlueprintFromContent method * Update tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentBlueprintEditingServiceTests.GetScaffold.cs Co-authored-by: Andy Butland --------- Co-authored-by: Andy Butland --- .../ContentBlueprintEditingService.cs | 8 +- src/Umbraco.Core/Services/ContentService.cs | 27 ++- src/Umbraco.Core/Services/IContentService.cs | 12 +- .../Builders/DataTypeBuilder.cs | 101 +++++++- .../UmbracoIntegrationTestWithContent.cs | 2 + .../Services/ContentServiceTests.cs | 29 ++- .../Services/ElementSwitchValidatorTests.cs | 100 +------- .../Services/TelemetryProviderTests.cs | 8 +- ...lueprintEditingServiceTests.GetScaffold.cs | 223 +++++++++++++++++- .../ContentBlueprintEditingServiceTests.cs | 12 +- 10 files changed, 386 insertions(+), 136 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs b/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs index 7af5171bc9..5e8357e453 100644 --- a/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs +++ b/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs @@ -45,11 +45,13 @@ internal sealed class ContentBlueprintEditingService return Task.FromResult(null); } + IContent scaffold = blueprint.DeepCloneWithResetIdentities(); + using ICoreScope scope = CoreScopeProvider.CreateCoreScope(); - scope.Notifications.Publish(new ContentScaffoldedNotification(blueprint, blueprint, Constants.System.Root, new EventMessages())); + scope.Notifications.Publish(new ContentScaffoldedNotification(blueprint, scaffold, Constants.System.Root, new EventMessages())); scope.Complete(); - return Task.FromResult(blueprint); + return Task.FromResult(scaffold); } public async Task?, ContentEditingOperationStatus>> GetPagedByContentTypeAsync(Guid contentTypeKey, int skip, int take) @@ -112,7 +114,7 @@ internal sealed class ContentBlueprintEditingService // Create Blueprint var currentUserId = await GetUserIdAsync(userKey); - IContent blueprint = ContentService.CreateContentFromBlueprint(content, name, currentUserId); + IContent blueprint = ContentService.CreateBlueprintFromContent(content, name, currentUserId); if (key.HasValue) { diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index f246b9c3f0..34ac8db8ff 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -3654,12 +3654,12 @@ public class ContentService : RepositoryService, IContentService private static readonly string?[] ArrayOfOneNullString = { null }; - public IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = Constants.Security.SuperUserId) + public IContent CreateBlueprintFromContent( + IContent blueprint, + string name, + int userId = Constants.Security.SuperUserId) { - if (blueprint == null) - { - throw new ArgumentNullException(nameof(blueprint)); - } + ArgumentNullException.ThrowIfNull(blueprint); IContentType contentType = GetContentType(blueprint.ContentType.Alias); var content = new Content(name, -1, contentType); @@ -3672,15 +3672,13 @@ public class ContentService : RepositoryService, IContentService if (blueprint.CultureInfos?.Count > 0) { cultures = blueprint.CultureInfos.Values.Select(x => x.Culture); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + using ICoreScope scope = ScopeProvider.CreateCoreScope(); + if (blueprint.CultureInfos.TryGetValue(_languageRepository.GetDefaultIsoCode(), out ContentCultureInfos defaultCulture)) { - if (blueprint.CultureInfos.TryGetValue(_languageRepository.GetDefaultIsoCode(), out ContentCultureInfos defaultCulture)) - { - defaultCulture.Name = name; - } - - scope.Complete(); + defaultCulture.Name = name; } + + scope.Complete(); } DateTime now = DateTime.Now; @@ -3701,6 +3699,11 @@ public class ContentService : RepositoryService, IContentService return content; } + /// + [Obsolete("Use IContentBlueprintEditingService.GetScaffoldedAsync() instead. Scheduled for removal in V18.")] + public IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = Constants.Security.SuperUserId) + => CreateBlueprintFromContent(blueprint, name, userId); + public IEnumerable GetBlueprintsForContentTypes(params int[] contentTypeId) { using (ScopeProvider.CreateCoreScope(autoComplete: true)) diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index ea14e6771a..423f157874 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -55,8 +55,18 @@ public interface IContentService : IContentServiceBase void DeleteBlueprint(IContent content, int userId = Constants.Security.SuperUserId); /// - /// Creates a new content item from a blueprint. + /// Creates a blueprint from a content item. /// + // TODO: Remove the default implementation when CreateContentFromBlueprint is removed. + IContent CreateBlueprintFromContent(IContent blueprint, string name, int userId = Constants.Security.SuperUserId) + => throw new NotImplementedException(); + + /// + /// (Deprecated) Creates a new content item from a blueprint. + /// + /// If creating content from a blueprint, use + /// instead. If creating a blueprint from content use instead. + [Obsolete("Use IContentBlueprintEditingService.GetScaffoldedAsync() instead. Scheduled for removal in V18.")] IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = Constants.Security.SuperUserId); /// diff --git a/tests/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs b/tests/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs index 9c26114e3d..9226d42474 100644 --- a/tests/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs +++ b/tests/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs @@ -1,9 +1,12 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Infrastructure.Serialization; +using Umbraco.Cms.Tests.Common.Builders.Extensions; using Umbraco.Cms.Tests.Common.Builders.Interfaces; namespace Umbraco.Cms.Tests.Common.Builders; @@ -155,4 +158,100 @@ public class DataTypeBuilder return dataType; } + + public static DataType CreateSimpleElementDataType( + IIOHelper ioHelper, + string editorAlias, + Guid elementKey, + Guid? elementSettingKey) + { + Dictionary configuration = editorAlias switch + { + Constants.PropertyEditors.Aliases.BlockGrid => GetBlockGridBaseConfiguration(), + Constants.PropertyEditors.Aliases.RichText => GetRteBaseConfiguration(), + _ => [], + }; + + SetBlockConfiguration( + configuration, + elementKey, + elementSettingKey, + editorAlias == Constants.PropertyEditors.Aliases.BlockGrid ? true : null); + + + var dataTypeBuilder = new DataTypeBuilder() + .WithId(0) + .WithDatabaseType(ValueStorageType.Nvarchar) + .AddEditor() + .WithAlias(editorAlias); + + switch (editorAlias) + { + case Constants.PropertyEditors.Aliases.BlockGrid: + dataTypeBuilder.WithConfigurationEditor( + new BlockGridConfigurationEditor(ioHelper) { DefaultConfiguration = configuration }); + break; + case Constants.PropertyEditors.Aliases.BlockList: + dataTypeBuilder.WithConfigurationEditor( + new BlockListConfigurationEditor(ioHelper) { DefaultConfiguration = configuration }); + break; + case Constants.PropertyEditors.Aliases.RichText: + dataTypeBuilder.WithConfigurationEditor( + new RichTextConfigurationEditor(ioHelper) { DefaultConfiguration = configuration }); + break; + } + + return dataTypeBuilder.Done().Build(); + } + + private static void SetBlockConfiguration( + Dictionary dictionary, + Guid? elementKey, + Guid? elementSettingKey, + bool? allowAtRoot) + { + if (elementKey is null) + { + return; + } + + dictionary["blocks"] = new[] { BuildBlockConfiguration(elementKey.Value, elementSettingKey, allowAtRoot) }; + } + + private static Dictionary GetBlockGridBaseConfiguration() => new() { ["gridColumns"] = 12 }; + + private static Dictionary GetRteBaseConfiguration() + { + var dictionary = new Dictionary + { + ["maxImageSize"] = 500, + ["mode"] = "Classic", + ["toolbar"] = new[] + { + "styles", "bold", "italic", "alignleft", "aligncenter", "alignright", "bullist", "numlist", "outdent", + "indent", "sourcecode", "link", "umbmediapicker", "umbembeddialog" + }, + }; + return dictionary; + } + + private static Dictionary BuildBlockConfiguration( + Guid? elementKey, + Guid? elementSettingKey, + bool? allowAtRoot) + { + var dictionary = new Dictionary(); + if (allowAtRoot is not null) + { + dictionary.Add("allowAtRoot", allowAtRoot.Value); + } + + dictionary.Add("contentElementTypeKey", elementKey.ToString()); + if (elementSettingKey is not null) + { + dictionary.Add("settingsElementTypeKey", elementSettingKey.ToString()); + } + + return dictionary; + } } diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestWithContent.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestWithContent.cs index 70957f4c83..42aba90eb2 100644 --- a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestWithContent.cs +++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestWithContent.cs @@ -19,6 +19,8 @@ public abstract class UmbracoIntegrationTestWithContent : UmbracoIntegrationTest protected IContentTypeService ContentTypeService => GetRequiredService(); + protected IDataTypeService DataTypeService => GetRequiredService(); + protected IFileService FileService => GetRequiredService(); protected ContentService ContentService => (ContentService)GetRequiredService(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs index 242be71557..2c1ea1e278 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs @@ -130,7 +130,7 @@ internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent } [Test] - public void Create_Content_From_Blueprint() + public void Create_Blueprint_From_Content() { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { @@ -140,22 +140,21 @@ internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent var contentType = ContentTypeBuilder.CreateTextPageContentType(defaultTemplateId: template.Id); ContentTypeService.Save(contentType); - var blueprint = ContentBuilder.CreateTextpageContent(contentType, "hello", Constants.System.Root); - blueprint.SetValue("title", "blueprint 1"); - blueprint.SetValue("bodyText", "blueprint 2"); - blueprint.SetValue("keywords", "blueprint 3"); - blueprint.SetValue("description", "blueprint 4"); + var originalPage = ContentBuilder.CreateTextpageContent(contentType, "hello", Constants.System.Root); + originalPage.SetValue("title", "blueprint 1"); + originalPage.SetValue("bodyText", "blueprint 2"); + originalPage.SetValue("keywords", "blueprint 3"); + originalPage.SetValue("description", "blueprint 4"); + ContentService.Save(originalPage); - ContentService.SaveBlueprint(blueprint); + var fromContent = ContentService.CreateBlueprintFromContent(originalPage, "hello world"); + ContentService.SaveBlueprint(fromContent); - var fromBlueprint = ContentService.CreateContentFromBlueprint(blueprint, "hello world"); - ContentService.Save(fromBlueprint); - - Assert.IsTrue(fromBlueprint.HasIdentity); - Assert.AreEqual("blueprint 1", fromBlueprint.Properties["title"].GetValue()); - Assert.AreEqual("blueprint 2", fromBlueprint.Properties["bodyText"].GetValue()); - Assert.AreEqual("blueprint 3", fromBlueprint.Properties["keywords"].GetValue()); - Assert.AreEqual("blueprint 4", fromBlueprint.Properties["description"].GetValue()); + Assert.IsTrue(fromContent.HasIdentity); + Assert.AreEqual("blueprint 1", fromContent.Properties["title"]?.GetValue()); + Assert.AreEqual("blueprint 2", fromContent.Properties["bodyText"]?.GetValue()); + Assert.AreEqual("blueprint 3", fromContent.Properties["keywords"]?.GetValue()); + Assert.AreEqual("blueprint 4", fromContent.Properties["description"]?.GetValue()); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ElementSwitchValidatorTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ElementSwitchValidatorTests.cs index 916ae02cea..794dc81efc 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ElementSwitchValidatorTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ElementSwitchValidatorTests.cs @@ -286,104 +286,12 @@ internal sealed class ElementSwitchValidatorTests : UmbracoIntegrationTest Guid elementKey, Guid? elementSettingKey) { - Dictionary configuration; - switch (editorAlias) - { - case Constants.PropertyEditors.Aliases.BlockGrid: - configuration = GetBlockGridBaseConfiguration(); - break; - case Constants.PropertyEditors.Aliases.RichText: - configuration = GetRteBaseConfiguration(); - break; - default: - configuration = new Dictionary(); - break; - } - - SetBlockConfiguration( - configuration, + var dataType = DataTypeBuilder.CreateSimpleElementDataType( + IOHelper, + editorAlias, elementKey, - elementSettingKey, - editorAlias == Constants.PropertyEditors.Aliases.BlockGrid ? true : null); - - - var dataTypeBuilder = new DataTypeBuilder() - .WithId(0) - .WithDatabaseType(ValueStorageType.Nvarchar) - .AddEditor() - .WithAlias(editorAlias); - - switch (editorAlias) - { - case Constants.PropertyEditors.Aliases.BlockGrid: - dataTypeBuilder.WithConfigurationEditor( - new BlockGridConfigurationEditor(IOHelper) { DefaultConfiguration = configuration }); - break; - case Constants.PropertyEditors.Aliases.BlockList: - dataTypeBuilder.WithConfigurationEditor( - new BlockListConfigurationEditor(IOHelper) { DefaultConfiguration = configuration }); - break; - case Constants.PropertyEditors.Aliases.RichText: - dataTypeBuilder.WithConfigurationEditor( - new RichTextConfigurationEditor(IOHelper) { DefaultConfiguration = configuration }); - break; - } - - var dataType = dataTypeBuilder.Done() - .Build(); + elementSettingKey); await DataTypeService.CreateAsync(dataType, Constants.Security.SuperUserKey); } - - private void SetBlockConfiguration( - Dictionary dictionary, - Guid? elementKey, - Guid? elementSettingKey, - bool? allowAtRoot) - { - if (elementKey is null) - { - return; - } - - dictionary["blocks"] = new[] { BuildBlockConfiguration(elementKey.Value, elementSettingKey, allowAtRoot) }; - } - - private Dictionary GetBlockGridBaseConfiguration() - => new Dictionary { ["gridColumns"] = 12 }; - - private Dictionary GetRteBaseConfiguration() - { - var dictionary = new Dictionary - { - ["maxImageSize"] = 500, - ["mode"] = "Classic", - ["toolbar"] = new[] - { - "styles", "bold", "italic", "alignleft", "aligncenter", "alignright", "bullist", "numlist", - "outdent", "indent", "sourcecode", "link", "umbmediapicker", "umbembeddialog" - }, - }; - return dictionary; - } - - private Dictionary BuildBlockConfiguration( - Guid? elementKey, - Guid? elementSettingKey, - bool? allowAtRoot) - { - var dictionary = new Dictionary(); - if (allowAtRoot is not null) - { - dictionary.Add("allowAtRoot", allowAtRoot.Value); - } - - dictionary.Add("contentElementTypeKey", elementKey.ToString()); - if (elementSettingKey is not null) - { - dictionary.Add("settingsElementTypeKey", elementSettingKey.ToString()); - } - - return dictionary; - } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs index 210d66f28a..c5d6548677 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs @@ -54,6 +54,8 @@ internal sealed class TelemetryProviderTests : UmbracoIntegrationTest private IMediaTypeService MediaTypeService => GetRequiredService(); + private IContentBlueprintEditingService ContentBlueprintEditingService => GetRequiredService(); + private readonly LanguageBuilder _languageBuilder = new(); private readonly UserBuilder _userBuilder = new(); @@ -99,7 +101,7 @@ internal sealed class TelemetryProviderTests : UmbracoIntegrationTest } [Test] - public void SectionService_Can_Get_Allowed_Sections_For_User() + public async Task SectionService_Can_Get_Allowed_Sections_For_User() { // Arrange var template = TemplateBuilder.CreateTextPageTemplate(); @@ -116,7 +118,9 @@ internal sealed class TelemetryProviderTests : UmbracoIntegrationTest ContentService.SaveBlueprint(blueprint); - var fromBlueprint = ContentService.CreateContentFromBlueprint(blueprint, "My test content"); + var fromBlueprint = await ContentBlueprintEditingService.GetScaffoldedAsync(blueprint.Key); + Assert.IsNotNull(fromBlueprint); + fromBlueprint.Name = "My test content"; ContentService.Save(fromBlueprint); IEnumerable result = null; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentBlueprintEditingServiceTests.GetScaffold.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentBlueprintEditingServiceTests.GetScaffold.cs index 56ac93ba60..32b92a20b2 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentBlueprintEditingServiceTests.GetScaffold.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentBlueprintEditingServiceTests.GetScaffold.cs @@ -1,14 +1,26 @@ using NUnit.Framework; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Blocks; +using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; using Umbraco.Cms.Tests.Integration.Attributes; +using IContent = Umbraco.Cms.Core.Models.IContent; namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; public partial class ContentBlueprintEditingServiceTests { public static void AddScaffoldedNotificationHandler(IUmbracoBuilder builder) - => builder.AddNotificationHandler(); + => builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); [TestCase(true)] [TestCase(false)] @@ -28,7 +40,15 @@ public partial class ContentBlueprintEditingServiceTests }; var result = await ContentBlueprintEditingService.GetScaffoldedAsync(blueprint.Key); Assert.IsNotNull(result); - Assert.AreEqual(blueprint.Key, result.Key); + Assert.AreNotEqual(blueprint.Key, result.Key); + Assert.AreEqual( + blueprint.ContentType.Key, + result.ContentType.Key, + "The content type of the scaffolded content should match the original blueprint content type."); + Assert.AreEqual( + blueprint.Properties.Select(p => (p.Alias, p.PropertyType.Key)), + result.Properties.Select(p => (p.Alias, p.PropertyType.Key)), + "The properties of the scaffolded content should match the original blueprint properties."); var propertyValues = result.Properties.SelectMany(property => property.Values).ToArray(); Assert.IsNotEmpty(propertyValues); @@ -51,10 +71,209 @@ public partial class ContentBlueprintEditingServiceTests Assert.IsNull(result); } + [TestCase(false, Constants.PropertyEditors.Aliases.BlockList)] + [TestCase(false, Constants.PropertyEditors.Aliases.BlockGrid)] + [TestCase(false, Constants.PropertyEditors.Aliases.RichText)] + [TestCase(true, Constants.PropertyEditors.Aliases.BlockList)] + [TestCase(true, Constants.PropertyEditors.Aliases.BlockGrid)] + [TestCase(true, Constants.PropertyEditors.Aliases.RichText)] + [ConfigureBuilder(ActionName = nameof(AddScaffoldedNotificationHandler))] + public async Task Get_Scaffold_With_Blocks_Generates_New_Block_Ids(bool variant, string editorAlias) + { + var blueprint = await CreateBlueprintWithBlocksEditor(variant, editorAlias); + var result = await ContentBlueprintEditingService.GetScaffoldedAsync(blueprint.Content.Key); + Assert.IsNotNull(result); + Assert.AreNotEqual(blueprint.Content.Key, result.Key); + + List newKeys = []; + var newInvariantBlocklist = GetBlockValue("invariantBlocks"); + newKeys.AddRange( + newInvariantBlocklist.Layout + .SelectMany(x => x.Value) + .SelectMany(v => new List { v.ContentKey, v.SettingsKey!.Value })); + + if (variant) + { + foreach (var culture in result.AvailableCultures) + { + var newVariantBlocklist = GetBlockValue("blocks", culture); + newKeys.AddRange( + newVariantBlocklist.Layout + .SelectMany(x => x.Value) + .SelectMany(v => new List { v.ContentKey, v.SettingsKey!.Value })); + } + } + + foreach (var newKey in newKeys) + { + Assert.IsFalse(blueprint.BlockKeys.Contains(newKey), "The blocks in a content item generated from a template should have new keys."); + } + + return; + + BlockValue GetBlockValue(string propertyAlias, string? culture = null) + { + return editorAlias switch + { + Constants.PropertyEditors.Aliases.BlockList => JsonSerializer.Deserialize(result.GetValue(propertyAlias, culture)), + Constants.PropertyEditors.Aliases.BlockGrid => JsonSerializer.Deserialize(result.GetValue(propertyAlias, culture)), + Constants.PropertyEditors.Aliases.RichText => JsonSerializer.Deserialize(result.GetValue(propertyAlias, culture)).Blocks!, + _ => throw new NotSupportedException($"Editor alias '{editorAlias}' is not supported for block blueprints."), + }; + } + } + public class ContentScaffoldedNotificationHandler : INotificationHandler { public static Action? ContentScaffolded { get; set; } public void Handle(ContentScaffoldedNotification notification) => ContentScaffolded?.Invoke(notification); } + + private async Task<(IContent Content, List BlockKeys)> CreateBlueprintWithBlocksEditor(bool variant, string editorAlias) + { + var contentType = variant ? await CreateVariantContentType() : CreateInvariantContentType(); + + // Create element type + var elementContentType = new ContentTypeBuilder() + .WithAlias("elementType") + .WithName("Element") + .WithIsElement(true) + .Build(); + await ContentTypeService.CreateAsync(elementContentType, Constants.Security.SuperUserKey); + + // Create settings element type + var settingsContentType = new ContentTypeBuilder() + .WithAlias("settingsType") + .WithName("Settings") + .WithIsElement(true) + .Build(); + await ContentTypeService.CreateAsync(settingsContentType, Constants.Security.SuperUserKey); + + // Create blocks datatype using the created elements + var dataType = DataTypeBuilder.CreateSimpleElementDataType(IOHelper, editorAlias, elementContentType.Key, settingsContentType.Key); + var dataTypeAttempt = await DataTypeService.CreateAsync(dataType, Constants.Security.SuperUserKey); + Assert.True(dataTypeAttempt.Success, $"Failed to create data type: {dataTypeAttempt.Exception?.Message}"); + + // Create new blocks property types + var invariantPropertyType = new PropertyTypeBuilder(new ContentTypeBuilder()) + .WithPropertyEditorAlias(editorAlias) + .WithValueStorageType(ValueStorageType.Ntext) + .WithAlias("invariantBlocks") + .WithName("Invariant Blocks") + .WithDataTypeId(dataType.Id) + .WithVariations(ContentVariation.Nothing) + .Build(); + contentType.AddPropertyType(invariantPropertyType); + + if (contentType.VariesByCulture()) + { + var propertyType = new PropertyTypeBuilder(new ContentTypeBuilder()) + .WithPropertyEditorAlias(editorAlias) + .WithValueStorageType(ValueStorageType.Ntext) + .WithAlias("blocks") + .WithName("Blocks") + .WithDataTypeId(dataType.Id) + .WithVariations(contentType.Variations) + .Build(); + contentType.AddPropertyType(propertyType); + } + + // Update the content type with the new blocks property type + await ContentTypeService.UpdateAsync(contentType, Constants.Security.SuperUserKey); + + string?[] cultures = contentType.VariesByCulture() + ? [null, "en-US", "da-DK"] + : [null]; + + var createModel = new ContentBlueprintCreateModel + { + ContentTypeKey = contentType.Key, + ParentKey = Constants.System.RootKey, + Variants = cultures.Where(c => variant ? c != null : c == null).Select(c => new VariantModel { Culture = c, Name = $"Initial Blueprint {c}" }), + }; + + List allBlockKeys = []; + foreach (var culture in cultures) + { + var (blockValue, blockKeys) = CreateBlockValue(editorAlias, elementContentType, settingsContentType); + createModel.Properties = createModel.Properties.Append( + new PropertyValueModel + { + Alias = culture == null ? "invariantBlocks" : "blocks", + Value = JsonSerializer.Serialize(blockValue), + Culture = culture, + }); + allBlockKeys.AddRange(blockKeys); + } + + var result = await ContentBlueprintEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey); + Assert.IsTrue(result.Success); + return (result.Result.Content, allBlockKeys); + } + + private static (object BlockValue, IEnumerable BlockKeys) CreateBlockValue( + string editorAlias, + IContentType elementContentType, + IContentType settingsContentType) + { + switch (editorAlias) + { + case Constants.PropertyEditors.Aliases.BlockList: + return CreateBlockValueOfType(editorAlias, elementContentType, settingsContentType); + case Constants.PropertyEditors.Aliases.BlockGrid: + return CreateBlockValueOfType(editorAlias, elementContentType, settingsContentType); + case Constants.PropertyEditors.Aliases.RichText: + var res = CreateBlockValueOfType(editorAlias, elementContentType, settingsContentType); + return (new RichTextEditorValue + { + Markup = string.Join(string.Empty, res.BlockKeys.Chunk(2).Select(c => $"")), + Blocks = res.BlockValue, + }, res.BlockKeys); + default: + throw new NotSupportedException($"Editor alias '{editorAlias}' is not supported for block blueprints."); + } + } + + private static (T BlockValue, IEnumerable BlockKeys) CreateBlockValueOfType( + string editorAlias, + IContentType elementContentType, + IContentType settingsContentType) + where T : BlockValue, new() + where TLayout : IBlockLayoutItem, new() + { + // Generate two pairs of Guids as a list of tuples + const int numberOfBlocks = 2; + var blockKeys = Enumerable.Range(0, numberOfBlocks) + .Select(_ => Enumerable.Range(0, 2).Select(_ => Guid.NewGuid()).ToList()) + .ToList(); + return (new T + { + Layout = new Dictionary> + { + [editorAlias] = blockKeys.Select(blockKeyGroup => + new TLayout + { + ContentKey = blockKeyGroup[0], + SettingsKey = blockKeyGroup[1], + }).OfType(), + }, + ContentData = blockKeys.Select(blockKeyGroup => new BlockItemData + { + Key = blockKeyGroup[0], + ContentTypeAlias = elementContentType.Alias, + ContentTypeKey = elementContentType.Key, + Values = [], + }) + .ToList(), + SettingsData = blockKeys.Select(blockKeyGroup => new BlockItemData + { + Key = blockKeyGroup[1], + ContentTypeAlias = settingsContentType.Alias, + ContentTypeKey = settingsContentType.Key, + Values = [], + }) + .ToList(), + }, blockKeys.SelectMany(l => l)); + } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentBlueprintEditingServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentBlueprintEditingServiceTests.cs index c15e619409..57b59da68a 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentBlueprintEditingServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentBlueprintEditingServiceTests.cs @@ -3,6 +3,7 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; @@ -17,6 +18,8 @@ public partial class ContentBlueprintEditingServiceTests : ContentEditingService private IEntityService EntityService => GetRequiredService(); + private IJsonSerializer JsonSerializer => GetRequiredService(); + private async Task CreateInvariantContentBlueprint() { var contentType = CreateInvariantContentType(); @@ -75,8 +78,8 @@ public partial class ContentBlueprintEditingServiceTests : ContentEditingService Properties = [ new PropertyValueModel { Alias = "title", Value = "The title value" }, - new PropertyValueModel { Alias = "author", Value = "The author value" } - ] + new PropertyValueModel { Alias = "author", Value = "The author value" }, + ], }; return createModel; } @@ -90,11 +93,12 @@ public partial class ContentBlueprintEditingServiceTests : ContentEditingService [ new PropertyValueModel { Alias = "title", Value = "The title value updated" }, new PropertyValueModel { Alias = "author", Value = "The author value updated" } - ] + ], }; return createModel; } private IEntitySlim[] GetBlueprintChildren(Guid? containerKey) - => EntityService.GetPagedChildren(containerKey, new[] { UmbracoObjectTypes.DocumentBlueprintContainer }, UmbracoObjectTypes.DocumentBlueprint, 0, 100, out _).ToArray(); + => EntityService.GetPagedChildren(containerKey, [UmbracoObjectTypes.DocumentBlueprintContainer], UmbracoObjectTypes.DocumentBlueprint, 0, 100, out _).ToArray(); } + From 67106f0813d569888c1f969c060c7afcf22f7d41 Mon Sep 17 00:00:00 2001 From: WoutVanBoxem <91462836+WoutVanBoxem@users.noreply.github.com> Date: Tue, 24 Jun 2025 14:17:40 +0200 Subject: [PATCH 29/82] Add missing media exports (#19585) * Add missing media exports Fixes #19560 and #19561 * Update src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts index 3b40d852fc..ba5dbe387f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts @@ -1,11 +1,14 @@ +export * from './audit-log/index.js'; export * from './components/index.js'; export * from './constants.js'; export * from './dropzone/index.js'; +export {UMB_IMAGE_CROPPER_EDITOR_MODAL, UMB_MEDIA_PICKER_MODAL} from './modals/index.js'; +export * from './recycle-bin/index.js'; export * from './reference/index.js'; export * from './repository/index.js'; export * from './search/index.js'; +export * from './tree/index.js'; export * from './url/index.js'; -export { UmbMediaAuditLogRepository } from './audit-log/index.js'; export type * from './types.js'; From c61fc7419ca440900a966d71f35f88d49d56d784 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 26 Jun 2025 09:16:49 +0200 Subject: [PATCH 30/82] Cherry picked #19540 to V16 (and fixed changed signatures) (#19592) --- .../RichTextPropertyIndexValueFactory.cs | 26 ++++++- .../RichTextPropertyIndexValueFactoryTests.cs | 78 +++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/RichTextPropertyIndexValueFactoryTests.cs diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyIndexValueFactory.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyIndexValueFactory.cs index 0eb1ee257a..6013e6c4c2 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyIndexValueFactory.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyIndexValueFactory.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using System.Text.RegularExpressions; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Serialization; @@ -50,7 +51,7 @@ internal class RichTextPropertyIndexValueFactory : BlockValuePropertyIndexValueF }; // the actual content (RTE content without markup, i.e. the actual words) must be indexed under the property alias - var richTextWithoutMarkup = richTextEditorValue.Markup.StripHtml(); + var richTextWithoutMarkup = StripHtmlForIndexing(richTextEditorValue.Markup); if (richTextEditorValue.Blocks?.ContentData.Any() is not true) { // no blocks; index the content for the culture and be done with it @@ -132,4 +133,27 @@ internal class RichTextPropertyIndexValueFactory : BlockValuePropertyIndexValueF protected override IEnumerable GetDataItems(RichTextEditorValue input, bool published) => GetDataItems(input.Blocks?.ContentData ?? [], input.Blocks?.Expose ?? [], published); + + /// + /// Strips HTML tags from content while preserving whitespace from line breaks. + /// This addresses the issue where <br> tags don't create word boundaries when HTML is stripped. + /// + /// The HTML content to strip + /// Plain text with proper word boundaries + private static string StripHtmlForIndexing(string html) + { + if (string.IsNullOrWhiteSpace(html)) + { + return string.Empty; + } + + // Replace
and
tags (with any amount of whitespace and attributes) with spaces + // This regex matches: + // -
(with / without spaces or attributes) + // -
(with / without spaces or attributes) + html = Regex.Replace(html, @"]*/?>\s*", " ", RegexOptions.IgnoreCase); + + // Use the existing Microsoft StripHtml function for everything else + return html.StripHtml(); + } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/RichTextPropertyIndexValueFactoryTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/RichTextPropertyIndexValueFactoryTests.cs new file mode 100644 index 0000000000..826395a64a --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/RichTextPropertyIndexValueFactoryTests.cs @@ -0,0 +1,78 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors; + +/// +/// Tests for to ensure it correctly creates index values from rich text properties. +/// +public class RichTextPropertyIndexValueFactoryTests +{ + /// + /// Tests that the factory can create index values from a rich text property with valid content + /// + /// + /// + [TestCase("

Sample text

", "Sample text")] + [TestCase("

John Smith
Company ABC
London

", "John Smith Company ABC London")] + [TestCase("

John SmithCompany ABCLondon

", "John SmithCompany ABCLondon")] + [TestCase("

John Smith
Company ABCLondon

", "John Smith Company ABCLondon")] + [TestCase("

Another sample text with bold content

", "Another sample text with bold content")] + [TestCase("

Text with link

", "Text with link")] + [TestCase("

Text with \"image\"

", "Text with")] + [TestCase("

Text with styled text

", "Text with styled text")] + [TestCase("

Text with emphasized content

", "Text with emphasized content")] + [TestCase("

Text with underlined content

", "Text with underlined content")] + [TestCase("

Text with inline code

", "Text with inline code")] + [TestCase("

Text with

code block

", "Text with code block")] + [TestCase("

Text with

quoted text

", "Text with quoted text")] + [TestCase("

Text with

  • list item 1
  • list item 2

", + "Text with list item 1list item 2")] + [TestCase("

Text with

  1. ordered item 1
  2. ordered item 2

", + "Text with ordered item 1ordered item 2")] + [TestCase("

Text with

div content

", "Text with div content")] + [TestCase("

Text with span content

", "Text with span content")] + [TestCase("

Text with bold and italic content

", + "Text with bold and italic content")] + [TestCase("

Text with external link

", + "Text with external link")] + [TestCase("

John Smith
Company ABC
London

", "John Smith Company ABC London")] + [TestCase("

John Smith
Company ABC
London

", "John Smith Company ABC London")] + public void Can_Create_Index_Values_From_RichText_Property(string testContent, string expected) + { + var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(() => null)); + var jsonSerializer = Mock.Of(); + var indexingSettings = Mock.Of>(); + Mock.Get(indexingSettings).Setup(x => x.CurrentValue).Returns(new IndexingSettings { }); + var logger = Mock.Of>(); + string alias = "richText"; + + var factory = new RichTextPropertyIndexValueFactory( + propertyEditorCollection, + jsonSerializer, + indexingSettings, + logger); + + // create a mock property with the rich text value + var property = Mock.Of(p => p.Alias == alias + && (string)p.GetValue(It.IsAny(), It.IsAny(), + It.IsAny()) == testContent); + + // get the index value for the property + var indexValue = factory + .GetIndexValues(property, null, null, true, [], new Dictionary()) + .FirstOrDefault(kvp => kvp.FieldName == alias); + Assert.IsNotNull(indexValue); + + // assert that index the value is created correctly (it might contain a trailing whitespace, but that's OK) + var expectedIndexValue = indexValue.Values.SingleOrDefault() as string; + Assert.IsNotNull(expectedIndexValue); + Assert.AreEqual(expected, expectedIndexValue.TrimEnd()); + } +} From e3c52afbbbe03690e976746dbf47ff871106ea8c Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Thu, 26 Jun 2025 13:10:58 +0200 Subject: [PATCH 31/82] v16 QA Remove smoke tag from flaky test (#19611) * Remove from smoke tag * Update tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithImageCropper.spec.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../DefaultConfig/Content/ContentWithImageCropper.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithImageCropper.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithImageCropper.spec.ts index 3a869b08a3..8872538319 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithImageCropper.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithImageCropper.spec.ts @@ -22,7 +22,8 @@ test.afterEach(async ({umbracoApi}) => { await umbracoApi.documentType.ensureNameNotExists(documentTypeName); }); -test('can create content with the image cropper data type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { +// TODO: Investigate pipeline flakiness for this test. Reapply '@smoke' tag once the issue is resolved. +test('can create content with the image cropper data type', async ({umbracoApi, umbracoUi}) => { // Arrange const expectedState = 'Draft'; const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); From 5cafa599fab8401fe88c9724b29bb9c5ed16a49c Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 27 Jun 2025 13:31:08 +0200 Subject: [PATCH 32/82] Remove trash success notifications (#19534) * remove trash notifications * Updated tests so we no longer use the notification for moving to recycle bin --------- Co-authored-by: Andreas Zerbst --- .../recycle-bin-repository-base.ts | 19 ++----------------- .../Content/TrashContent/TrashContent.spec.ts | 19 ++++++++++--------- .../tests/DefaultConfig/Media/Media.spec.ts | 2 -- 3 files changed, 12 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/recycle-bin-repository-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/recycle-bin-repository-base.ts index e246dac49d..f91742f0f3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/recycle-bin-repository-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/recycle-bin-repository-base.ts @@ -23,7 +23,6 @@ import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository'; export abstract class UmbRecycleBinRepositoryBase extends UmbRepositoryBase implements UmbRecycleBinRepository { #recycleBinSource: UmbRecycleBinDataSource; #notificationContext?: typeof UMB_NOTIFICATION_CONTEXT.TYPE; - #requestTrashSuccessNotification?: UmbNotificationHandler; #requestRestoreSuccessNotification?: UmbNotificationHandler; /** @@ -48,15 +47,7 @@ export abstract class UmbRecycleBinRepositoryBase extends UmbRepositoryBase impl * @memberof UmbRecycleBinRepositoryBase */ async requestTrash(args: UmbRecycleBinTrashRequestArgs) { - const { error } = await this.#recycleBinSource.trash(args); - - if (!error) { - this.#requestTrashSuccessNotification?.close(); - const notification = { data: { message: `Trashed` } }; - this.#requestTrashSuccessNotification = this.#notificationContext?.peek('positive', notification); - } - - return { error }; + return this.#recycleBinSource.trash(args); } /** @@ -69,6 +60,7 @@ export abstract class UmbRecycleBinRepositoryBase extends UmbRepositoryBase impl const { error } = await this.#recycleBinSource.restore(args); if (!error) { + // TODO: keep this notification until we refresh the tree/structure correctly after the action this.#requestRestoreSuccessNotification?.close(); const notification = { data: { message: `Restored` } }; this.#requestRestoreSuccessNotification = this.#notificationContext?.peek('positive', notification); @@ -83,13 +75,6 @@ export abstract class UmbRecycleBinRepositoryBase extends UmbRepositoryBase impl * @memberof UmbRecycleBinRepositoryBase */ async requestEmpty() { - const { error } = await this.#recycleBinSource.empty(); - - if (!error) { - const notification = { data: { message: `Recycle Bin Emptied` } }; - this.#notificationContext?.peek('positive', notification); - } - return this.#recycleBinSource.empty(); } diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/TrashContent/TrashContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/TrashContent/TrashContent.spec.ts index 0d3c854141..f65a686143 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/TrashContent/TrashContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/TrashContent/TrashContent.spec.ts @@ -37,7 +37,7 @@ test('can trash an invariant content node', {tag: '@smoke'}, async ({umbracoApi, await umbracoUi.content.clickConfirmTrashButton(); // Assert - await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); + await umbracoUi.content.waitForContentToBeTrashed(); expect(await umbracoApi.document.doesNameExist(contentName)).toBeFalsy(); await umbracoUi.content.isItemVisibleInRecycleBin(contentName); expect(await umbracoApi.document.doesItemExistInRecycleBin(contentName)).toBeTruthy(); @@ -58,7 +58,7 @@ test('can trash a variant content node', async ({umbracoApi, umbracoUi}) => { await umbracoUi.content.clickConfirmTrashButton(); // Assert - await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); + await umbracoUi.content.waitForContentToBeTrashed(); expect(await umbracoApi.document.doesNameExist(contentName)).toBeFalsy(); await umbracoUi.content.isItemVisibleInRecycleBin(contentName); expect(await umbracoApi.document.doesItemExistInRecycleBin(contentName)).toBeTruthy(); @@ -80,7 +80,7 @@ test('can trash a published content node', async ({umbracoApi, umbracoUi}) => { await umbracoUi.content.clickConfirmTrashButton(); // Assert - await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); + await umbracoUi.content.waitForContentToBeTrashed(); expect(await umbracoApi.document.doesNameExist(contentName)).toBeFalsy(); await umbracoUi.content.isItemVisibleInRecycleBin(contentName); expect(await umbracoApi.document.doesItemExistInRecycleBin(contentName)).toBeTruthy(); @@ -107,7 +107,7 @@ test('can trash an invariant content node that references one item', async ({umb await umbracoUi.content.clickConfirmTrashButton(); // Assert - await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); + await umbracoUi.content.waitForContentToBeTrashed(); expect(await umbracoApi.document.doesNameExist(contentName)).toBeFalsy(); await umbracoUi.content.isItemVisibleInRecycleBin(contentName); expect(await umbracoApi.document.doesItemExistInRecycleBin(contentName)).toBeTruthy(); @@ -134,7 +134,7 @@ test('can trash a variant content node that references one item', async ({umbrac await umbracoUi.content.clickConfirmTrashButton(); // Assert - await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); + await umbracoUi.content.waitForContentToBeTrashed(); expect(await umbracoApi.document.doesNameExist(contentName)).toBeFalsy(); await umbracoUi.content.isItemVisibleInRecycleBin(contentName); expect(await umbracoApi.document.doesItemExistInRecycleBin(contentName)).toBeTruthy(); @@ -170,7 +170,7 @@ test('can trash an invariant content node that references more than 3 items', as await umbracoUi.content.clickConfirmTrashButton(); // Assert - await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); + await umbracoUi.content.waitForContentToBeTrashed(); expect(await umbracoApi.document.doesNameExist(contentName)).toBeFalsy(); await umbracoUi.content.isItemVisibleInRecycleBin(contentName); expect(await umbracoApi.document.doesItemExistInRecycleBin(contentName)).toBeTruthy(); @@ -211,7 +211,7 @@ test('can trash a variant content node that references more than 3 items', async await umbracoUi.content.clickConfirmTrashButton(); // Assert - await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); + await umbracoUi.content.waitForContentToBeTrashed(); expect(await umbracoApi.document.doesNameExist(contentName)).toBeFalsy(); await umbracoUi.content.isItemVisibleInRecycleBin(contentName); expect(await umbracoApi.document.doesItemExistInRecycleBin(contentName)).toBeTruthy(); @@ -226,6 +226,7 @@ test('can trash a content node with multiple cultures that references one item', // Arrange const firstCulture = 'en-US'; const secondCulture = 'da'; + await umbracoApi.language.ensureIsoCodeNotExists(secondCulture); await umbracoApi.language.createDanishLanguage(); // Create a content node with multiple cultures const documentTypeId = await umbracoApi.documentType.createVariantDocumentTypeWithInvariantPropertyEditor(documentTypeName, dataTypeName, dataTypeId); @@ -247,11 +248,11 @@ test('can trash a content node with multiple cultures that references one item', await umbracoUi.content.clickConfirmTrashButton(); // Assert - await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); + await umbracoUi.content.waitForContentToBeTrashed(); expect(await umbracoApi.document.doesNameExist(contentName)).toBeFalsy(); await umbracoUi.content.isItemVisibleInRecycleBin(contentName); expect(await umbracoApi.document.doesItemExistInRecycleBin(contentName)).toBeTruthy(); // Clean await umbracoApi.language.ensureIsoCodeNotExists(secondCulture); -}); \ No newline at end of file +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts index b1c4d9b1e6..ada2e6f149 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts @@ -116,7 +116,6 @@ test('can trash a folder', async ({umbracoApi, umbracoUi}) => { // Assert await umbracoUi.media.waitForMediaToBeTrashed(); - await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); await umbracoUi.media.isTreeItemVisible(folderName, false); await umbracoUi.media.isItemVisibleInRecycleBin(folderName); expect(await umbracoApi.media.doesNameExist(folderName)).toBeFalsy(); @@ -180,7 +179,6 @@ test('can trash a media item', async ({umbracoApi, umbracoUi}) => { // Assert await umbracoUi.media.waitForMediaToBeTrashed(); - await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); await umbracoUi.media.isMediaTreeItemVisible(mediaFileName, false); await umbracoUi.media.isItemVisibleInRecycleBin(mediaFileName); expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeFalsy(); From ede906e152825ae9298901ca7c76aa7a0364fd31 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 27 Jun 2025 13:32:22 +0200 Subject: [PATCH 33/82] DocumentUrlDataResolver: Use UMB_VARIANT_CONTEXT instead of UMB_PROPERTY_DATASET_CONTEXT (#19537) * change to variant context * remove unused --- .../documents/url/document-urls-data-resolver.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/url/document-urls-data-resolver.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/url/document-urls-data-resolver.ts index 83ab17f7b3..74475de8ec 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/url/document-urls-data-resolver.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/url/document-urls-data-resolver.ts @@ -2,8 +2,7 @@ import type { UmbDocumentUrlModel } from './repository/types.js'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; -import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; -import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import { UMB_VARIANT_CONTEXT, type UmbVariantId } from '@umbraco-cms/backoffice/variant'; /** * A controller for resolving data for document urls @@ -13,7 +12,7 @@ import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; */ export class UmbDocumentUrlsDataResolver extends UmbControllerBase { #appCulture?: string; - #propertyDataSetCulture?: UmbVariantId; + #variantId?: UmbVariantId; #data?: Array | undefined; #init: Promise; @@ -29,11 +28,9 @@ export class UmbDocumentUrlsDataResolver extends UmbControllerBase { constructor(host: UmbControllerHost) { super(host); - // TODO: listen for UMB_VARIANT_CONTEXT when available this.#init = Promise.all([ - this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, (context) => { - this.#propertyDataSetCulture = context?.getVariantId(); - this.#setCultureAwareValues(); + this.consumeContext(UMB_VARIANT_CONTEXT, async (context) => { + this.#variantId = await context?.getVariantId(); }).asPromise(), ]); } @@ -83,7 +80,7 @@ export class UmbDocumentUrlsDataResolver extends UmbControllerBase { } #getCurrentCulture(): string | undefined { - return this.#propertyDataSetCulture?.culture || this.#appCulture; + return this.#variantId?.culture || this.#appCulture; } #getDataForCurrentCulture(): Array | undefined { From cdf7b3dbefc67df8c80c28f7face4c5088a53c02 Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Fri, 27 Jun 2025 12:33:17 +0100 Subject: [PATCH 34/82] Tiptap RTE: Toolbar menu active highlighting (#19532) * Adds optional parameter to Tiptap toolbar item's `isActive` * Adds `isActive` support to toolbar menus and cascading menus * Adds `isActive` support to the font menus * Adds `isActive` support to the table menu + UI/CSS tweak * Adds `isActive` support to the style menu API + refactored the commands * Improves cascading menu popover closing it previously didn't close the menu when an action was clicked. --- .../cascading-menu-popover.element.ts | 43 +++++++++++++------ .../toolbar/style-menu.tiptap-toolbar-api.ts | 42 +++++++++++++++--- .../toolbar/tiptap-toolbar-menu.element.ts | 15 +++++-- .../components/table-toolbar-menu.element.ts | 2 +- .../table/table.tiptap-toolbar-api.ts | 5 +++ .../toolbar/font-family.tiptap-toolbar-api.ts | 5 +++ .../toolbar/font-size.tiptap-toolbar-api.ts | 5 +++ .../src/packages/tiptap/extensions/types.ts | 2 +- 8 files changed, 95 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/cascading-menu-popover/cascading-menu-popover.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/cascading-menu-popover/cascading-menu-popover.element.ts index 047b6c0589..8709139f64 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/cascading-menu-popover/cascading-menu-popover.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/cascading-menu-popover/cascading-menu-popover.element.ts @@ -9,6 +9,7 @@ export type UmbCascadingMenuItem = { element?: HTMLElement; separatorAfter?: boolean; style?: string; + isActive?: () => boolean | undefined; execute?: () => void; }; @@ -21,8 +22,12 @@ export class UmbCascadingMenuPopoverElement extends UmbElementMixin(UUIPopoverCo return this.shadowRoot?.querySelector(`#${popoverId}`) as UUIPopoverContainerElement; } - #onMouseEnter(item: UmbCascadingMenuItem, popoverId: string) { - if (!item.items?.length) return; + #isMenuActive(items?: UmbCascadingMenuItem[]): boolean { + return !!items?.some((item) => item.isActive?.() || this.#isMenuActive(item.items)); + } + + #onMouseEnter(item: UmbCascadingMenuItem, popoverId?: string) { + if (!item.items?.length || !popoverId) return; const popover = this.#getPopoverById(popoverId); if (!popover) return; @@ -33,7 +38,9 @@ export class UmbCascadingMenuPopoverElement extends UmbElementMixin(UUIPopoverCo popover.showPopover(); } - #onMouseLeave(item: UmbCascadingMenuItem, popoverId: string) { + #onMouseLeave(item: UmbCascadingMenuItem, popoverId?: string) { + if (!popoverId) return; + const popover = this.#getPopoverById(popoverId); if (!popover) return; @@ -43,12 +50,16 @@ export class UmbCascadingMenuPopoverElement extends UmbElementMixin(UUIPopoverCo popover.hidePopover(); } - #onClick(item: UmbCascadingMenuItem, popoverId: string) { + #onClick(item: UmbCascadingMenuItem, popoverId?: string) { item.execute?.(); - setTimeout(() => { - this.#onMouseLeave(item, popoverId); - }, 100); + if (!popoverId) { + setTimeout(() => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this.hidePopover(); + }, 100); + } } override render() { @@ -64,14 +75,15 @@ export class UmbCascadingMenuPopoverElement extends UmbElementMixin(UUIPopoverCo } #renderItem(item: UmbCascadingMenuItem, index: number) { - const popoverId = `item-${index}`; + const popoverId = item.items ? `menu-${index}` : undefined; const element = item.element; - if (element) { + if (element && popoverId) { element.setAttribute('popovertarget', popoverId); } const label = this.localize.string(item.label); + const isActive = item.isActive?.() || this.#isMenuActive(item.items) || false; return html`
this.#onClick(item, popoverId)}> ${when(item.icon, (icon) => html``)} `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/style-menu.tiptap-toolbar-api.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/style-menu.tiptap-toolbar-api.ts index dda2e85f26..381607675a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/style-menu.tiptap-toolbar-api.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/style-menu.tiptap-toolbar-api.ts @@ -2,14 +2,28 @@ import { UmbTiptapToolbarElementApiBase } from '../../extensions/base.js'; import type { MetaTiptapToolbarStyleMenuItem } from '../../extensions/types.js'; import type { ChainedCommands, Editor } from '@umbraco-cms/backoffice/external/tiptap'; +type UmbTiptapToolbarStyleMenuCommandType = { + type: string; + command: (chain: ChainedCommands) => ChainedCommands; + isActive?: (editor?: Editor) => boolean | undefined; +}; + export default class UmbTiptapToolbarStyleMenuApi extends UmbTiptapToolbarElementApiBase { - #commands: Record ChainedCommands }> = { - h1: { type: 'heading', command: (chain) => chain.toggleHeading({ level: 1 }) }, - h2: { type: 'heading', command: (chain) => chain.toggleHeading({ level: 2 }) }, - h3: { type: 'heading', command: (chain) => chain.toggleHeading({ level: 3 }) }, - h4: { type: 'heading', command: (chain) => chain.toggleHeading({ level: 4 }) }, - h5: { type: 'heading', command: (chain) => chain.toggleHeading({ level: 5 }) }, - h6: { type: 'heading', command: (chain) => chain.toggleHeading({ level: 6 }) }, + #headingCommand(level: 1 | 2 | 3 | 4 | 5 | 6): UmbTiptapToolbarStyleMenuCommandType { + return { + type: 'heading', + command: (chain) => chain.toggleHeading({ level }), + isActive: (editor) => editor?.isActive('heading', { level }), + }; + } + + #commands: Record = { + h1: this.#headingCommand(1), + h2: this.#headingCommand(2), + h3: this.#headingCommand(3), + h4: this.#headingCommand(4), + h5: this.#headingCommand(5), + h6: this.#headingCommand(6), p: { type: 'paragraph', command: (chain) => chain.setParagraph() }, blockquote: { type: 'blockquote', command: (chain) => chain.toggleBlockquote() }, code: { type: 'code', command: (chain) => chain.toggleCode() }, @@ -24,6 +38,20 @@ export default class UmbTiptapToolbarStyleMenuApi extends UmbTiptapToolbarElemen ul: { type: 'bulletList', command: (chain) => chain.toggleBulletList() }, }; + override isActive(editor?: Editor, item?: MetaTiptapToolbarStyleMenuItem) { + if (!editor || !item?.data) return false; + + const { tag, id, class: className } = item.data; + const ext = tag ? this.#commands[tag] : null; + const attrs = editor?.getAttributes(ext?.type ?? 'paragraph'); + + const tagMatch = !tag ? true : ext ? (ext.isActive?.(editor) ?? editor?.isActive(ext.type) ?? false) : false; + const idMatch = !id ? true : attrs.id === id; + const classMatch = !className ? true : attrs.class?.includes(className) === true; + + return tagMatch && idMatch && classMatch; + } + override execute(editor?: Editor, item?: MetaTiptapToolbarStyleMenuItem) { if (!editor || !item?.data) return; const { tag, id, class: className } = item.data; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/tiptap-toolbar-menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/tiptap-toolbar-menu.element.ts index 7f7726eb10..4ffa6538d4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/tiptap-toolbar-menu.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/tiptap-toolbar-menu.element.ts @@ -99,13 +99,18 @@ export class UmbTiptapToolbarMenuElement extends UmbLitElement { style: item.appearance?.style ?? item.style, separatorAfter: item.separatorAfter, element, + isActive: () => this.api?.isActive(this.editor, item), execute: () => this.api?.execute(this.editor, item), }; } + #isMenuActive(items?: UmbCascadingMenuItem[]): boolean { + return !!items?.some((item) => item.isActive?.() || this.#isMenuActive(item.items)); + } + readonly #onEditorUpdate = () => { if (this.api && this.editor && this.manifest) { - this.isActive = this.api.isActive(this.editor); + this.isActive = this.api.isActive(this.editor) || this.#isMenuActive(this.#menu) || false; } }; @@ -117,8 +122,8 @@ export class UmbTiptapToolbarMenuElement extends UmbLitElement { () => html` ${when( @@ -130,7 +135,11 @@ export class UmbTiptapToolbarMenuElement extends UmbLitElement { `, () => html` - + ${label} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/table/components/table-toolbar-menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/table/components/table-toolbar-menu.element.ts index d57dc13401..c59e032265 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/table/components/table-toolbar-menu.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/table/components/table-toolbar-menu.element.ts @@ -32,7 +32,7 @@ export class UmbTiptapTableToolbarMenuElement extends UmbTiptapToolbarMenuElemen `, )} ${this.renderMenu()} - + `; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/table/table.tiptap-toolbar-api.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/table/table.tiptap-toolbar-api.ts index 6fd0e5bba4..569b913fe6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/table/table.tiptap-toolbar-api.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/table/table.tiptap-toolbar-api.ts @@ -29,6 +29,11 @@ export class UmbTiptapToolbarTableExtensionApi extends UmbTiptapToolbarElementAp tableProperties: (editor) => this.#tableProperties(editor), }; + override isActive(editor?: Editor, item?: unknown) { + if (!item) return super.isActive(editor); + return false; + } + async #tableProperties(editor?: Editor) { if (!editor || !editor.isActive('table')) return; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/font-family.tiptap-toolbar-api.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/font-family.tiptap-toolbar-api.ts index 41f698d3f9..4b4c92e03f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/font-family.tiptap-toolbar-api.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/font-family.tiptap-toolbar-api.ts @@ -3,6 +3,11 @@ import type { MetaTiptapToolbarMenuItem } from '../types.js'; import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; export default class UmbTiptapToolbarFontFamilyExtensionApi extends UmbTiptapToolbarElementApiBase { + override isActive(editor?: Editor, item?: MetaTiptapToolbarMenuItem) { + const styles = editor?.getAttributes('span')?.style; + return styles?.includes(`font-family: ${item?.data};`) === true; + } + override execute(editor?: Editor, item?: MetaTiptapToolbarMenuItem) { if (!item?.data) return; editor?.chain().focus().toggleSpanStyle(`font-family: ${item.data};`).run(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/font-size.tiptap-toolbar-api.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/font-size.tiptap-toolbar-api.ts index 3b7f4c5333..6088077159 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/font-size.tiptap-toolbar-api.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/toolbar/font-size.tiptap-toolbar-api.ts @@ -3,6 +3,11 @@ import type { MetaTiptapToolbarMenuItem } from '../types.js'; import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; export default class UmbTiptapToolbarFontFamilyExtensionApi extends UmbTiptapToolbarElementApiBase { + override isActive(editor?: Editor, item?: MetaTiptapToolbarMenuItem) { + const styles = editor?.getAttributes('span')?.style; + return styles?.includes(`font-size: ${item?.data};`) === true; + } + override execute(editor?: Editor, item?: MetaTiptapToolbarMenuItem) { if (!item?.data) return; editor?.chain().focus().toggleSpanStyle(`font-size: ${item.data};`).run(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/types.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/types.ts index 7b2f40033c..68d5711693 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/types.ts @@ -54,7 +54,7 @@ export interface UmbTiptapToolbarElementApi extends UmbApi, UmbTiptapExtensionAr /** * Checks if the toolbar element is active. */ - isActive(editor?: Editor): boolean; + isActive(editor?: Editor, ...args: Array): boolean; /** * Checks if the toolbar element is disabled. From 3e891972d0eff0462839e2ad10a084c06b2aeee2 Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Fri, 27 Jun 2025 12:35:36 +0100 Subject: [PATCH 35/82] Tiptap RTE: Adds token for statusbar context (#19530) Tiptap: adds token for Statusbar context --- .../packages/tiptap/property-editors/tiptap/constants.ts | 1 + .../tiptap-statusbar-configuration.context-token.ts | 6 ++++++ .../contexts/tiptap-statusbar-configuration.context.ts | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-statusbar-configuration.context-token.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/constants.ts index a89988e5c2..be2b430511 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/constants.ts @@ -1 +1,2 @@ +export { UMB_TIPTAP_STATUSBAR_CONFIGURATION_CONTEXT } from './contexts/tiptap-statusbar-configuration.context-token.js'; export { UMB_TIPTAP_TOOLBAR_CONFIGURATION_CONTEXT } from './contexts/tiptap-toolbar-configuration.context-token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-statusbar-configuration.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-statusbar-configuration.context-token.ts new file mode 100644 index 0000000000..49f06add54 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-statusbar-configuration.context-token.ts @@ -0,0 +1,6 @@ +import type { UmbTiptapStatusbarConfigurationContext } from './tiptap-statusbar-configuration.context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_TIPTAP_STATUSBAR_CONFIGURATION_CONTEXT = new UmbContextToken( + 'UmbTiptapStatusbarConfigurationContext', +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-statusbar-configuration.context.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-statusbar-configuration.context.ts index 107ef5cda1..bc0a680fb8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-statusbar-configuration.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-statusbar-configuration.context.ts @@ -1,5 +1,6 @@ import type { UmbTiptapStatusbarExtension, UmbTiptapStatusbarViewModel } from '../types.js'; import type { UmbTiptapStatusbarValue } from '../../../components/types.js'; +import { UMB_TIPTAP_STATUSBAR_CONFIGURATION_CONTEXT } from './tiptap-statusbar-configuration.context-token.js'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbArrayState, UmbBooleanState } from '@umbraco-cms/backoffice/observable-api'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; @@ -24,7 +25,7 @@ export class UmbTiptapStatusbarConfigurationContext extends UmbContextBase { public readonly statusbar = this.#statusbar.asObservable(); constructor(host: UmbControllerHost) { - super(host, 'UmbTiptapStatusbarConfigurationContext'); + super(host, UMB_TIPTAP_STATUSBAR_CONFIGURATION_CONTEXT); this.observe(umbExtensionsRegistry.byType('tiptapStatusbarExtension'), (extensions) => { const _extensions = extensions From 376a2c8ff66c8706f9c81c4990560da4d0d817b0 Mon Sep 17 00:00:00 2001 From: Laura Neto <12862535+lauraneto@users.noreply.github.com> Date: Fri, 27 Jun 2025 13:36:14 +0200 Subject: [PATCH 36/82] V16.1: Fix broken content creation when using blueprints (#19518) Fixes broken content creation based on blueprints Fixes preset not overriding values in the various createScaffold methods. --- .../repository/detail/document-detail.server.data-source.ts | 2 +- .../repository/detail/media-detail.server.data-source.ts | 2 +- .../repository/detail/member-detail.server.data-source.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts index cfe9e123f4..9adeb01d11 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts @@ -56,7 +56,7 @@ export class UmbDocumentServerDataSource variants: [], }; - const scaffold = umbDeepMerge(defaultData, preset) as UmbDocumentDetailModel; + const scaffold = umbDeepMerge(preset, defaultData); return { data: scaffold }; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/detail/media-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/detail/media-detail.server.data-source.ts index fbee9cb0e8..614e79d94e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/detail/media-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/detail/media-detail.server.data-source.ts @@ -57,7 +57,7 @@ export class UmbMediaServerDataSource extends UmbControllerBase implements UmbDe ], }; - const scaffold = umbDeepMerge(defaultData, preset) as UmbMediaDetailModel; + const scaffold = umbDeepMerge(preset, defaultData); return { data: scaffold }; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/detail/member-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/detail/member-detail.server.data-source.ts index 881c450f58..adda4eb2aa 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/detail/member-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/detail/member-detail.server.data-source.ts @@ -6,7 +6,7 @@ import type { UmbDetailDataSource } from '@umbraco-cms/backoffice/repository'; import type { CreateMemberRequestModel, UpdateMemberRequestModel } from '@umbraco-cms/backoffice/external/backend-api'; import { MemberService } from '@umbraco-cms/backoffice/external/backend-api'; import { tryExecute } from '@umbraco-cms/backoffice/resources'; -import { umbDeepMerge } from '@umbraco-cms/backoffice/utils'; +import {umbDeepMerge, type UmbDeepPartialObject} from '@umbraco-cms/backoffice/utils'; import { UmbMemberTypeDetailServerDataSource } from '@umbraco-cms/backoffice/member-type'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; @@ -22,7 +22,7 @@ export class UmbMemberServerDataSource extends UmbControllerBase implements UmbD * @returns { CreateMemberRequestModel } * @memberof UmbMemberServerDataSource */ - async createScaffold(preset: Partial = {}) { + async createScaffold(preset: UmbDeepPartialObject = {}) { let memberTypeIcon = ''; const memberTypeUnique = preset.memberType?.unique; @@ -64,7 +64,7 @@ export class UmbMemberServerDataSource extends UmbControllerBase implements UmbD ], }; - const scaffold = umbDeepMerge(defaultData, preset) as UmbMemberDetailModel; + const scaffold = umbDeepMerge(preset, defaultData); return { data: scaffold }; } From b7be95b2395bf49eed08f90b4342e3a2d3538c82 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 27 Jun 2025 13:38:01 +0200 Subject: [PATCH 37/82] Add folder workspace icon (#19366) * add slot for icon * expose icon data * render icon * load type for scaffold * rename * render icon for media * add observable for content type icon * request data in data source * wire up document scaffolding * remove unused * export server data source * render icon for member * rename data source to align with other detail sources * rename data source * remove unused styling * remove console log Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * remove console log Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * remove console log * render workspace icon for document type folders * make folder workspace editor * use element * remove const * use folder-workspace-editor for templating folders * introduce name write guard manager * prevent name change of file system folders * Update script-folder-workspace-editor.element.ts * make guard optional --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/packages/core/tree/folder/index.ts | 3 +- .../folder-workspace-editor.element.ts | 30 +++++++++++++++ .../core/tree/folder/workspace/index.ts | 2 + .../workspace-header-name-editable.element.ts | 16 +++++++- .../entity-named-detail-workspace-base.ts | 13 ++++++- .../packages/core/workspace/namable/index.ts | 1 + .../namable-workspace-context.interface.ts | 3 ++ .../namable/name-write-guard.manager.ts | 20 ++++++++++ .../data-type-folder-editor.element.ts | 12 ++---- ...ocument-blueprint-folder-editor.element.ts | 12 ++---- .../document-type-folder-editor.element.ts | 12 ++---- .../media-type-folder-editor.element.ts | 12 ++---- ...al-view-folder-workspace-editor.element.ts | 37 +++++-------------- .../script-folder-workspace-editor.element.ts | 37 +++++-------------- ...lesheet-folder-workspace-editor.element.ts | 37 +++++-------------- 15 files changed, 123 insertions(+), 124 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/workspace/folder-workspace-editor.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/workspace/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/workspace/namable/name-write-guard.manager.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/index.ts index 7386cf5ee7..16f022ca5c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/index.ts @@ -1,2 +1,3 @@ -export * from './modal/index.js'; export * from './entity-action/index.js'; +export * from './modal/index.js'; +export * from './workspace/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/workspace/folder-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/workspace/folder-workspace-editor.element.ts new file mode 100644 index 0000000000..96b456dece --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/workspace/folder-workspace-editor.element.ts @@ -0,0 +1,30 @@ +import { html, customElement, css } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; + +@customElement('umb-folder-workspace-editor') +export class UmbFolderWorkspaceEditorElement extends UmbLitElement { + override render() { + return html` + + + `; + } + + static override styles = [ + UmbTextStyles, + css` + #icon { + display: inline-block; + font-size: var(--uui-size-6); + margin-right: var(--uui-size-space-4); + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + ['umb-folder-workspace-editor']: UmbFolderWorkspaceEditorElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/workspace/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/workspace/index.ts new file mode 100644 index 0000000000..0d50e73e9c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/folder/workspace/index.ts @@ -0,0 +1,2 @@ +import './folder-workspace-editor.element.js'; +export * from './folder-workspace-editor.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-header-name-editable/workspace-header-name-editable.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-header-name-editable/workspace-header-name-editable.element.ts index b4a8ba4a90..5456a3045a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-header-name-editable/workspace-header-name-editable.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-header-name-editable/workspace-header-name-editable.element.ts @@ -31,6 +31,9 @@ export class UmbWorkspaceHeaderNameEditableElement extends UmbLitElement { @state() private _name = ''; + @state() + private _isWritableName = true; + #workspaceContext?: typeof UMB_NAMABLE_WORKSPACE_CONTEXT.TYPE; constructor() { @@ -39,9 +42,20 @@ export class UmbWorkspaceHeaderNameEditableElement extends UmbLitElement { this.consumeContext(UMB_NAMABLE_WORKSPACE_CONTEXT, (workspaceContext) => { this.#workspaceContext = workspaceContext; this.#observeName(); + this.#observeNameWriteGuardRules(); }); } + #observeNameWriteGuardRules() { + this.observe( + this.#workspaceContext?.nameWriteGuard?.isPermittedForName(), + (isPermitted) => { + this._isWritableName = isPermitted ?? true; + }, + 'umbObserveWorkspaceNameWriteGuardRules', + ); + } + #observeName() { if (!this.#workspaceContext) return; this.observe( @@ -73,7 +87,7 @@ export class UmbWorkspaceHeaderNameEditableElement extends UmbLitElement { @input="${this.#onNameInput}" label=${this.label ?? this.localize.term('placeholders_entername')} placeholder=${this.placeholder ?? this.localize.term('placeholders_entername')} - ?readonly=${this.readonly} + ?readonly=${this.readonly || !this._isWritableName} required ${umbBindToValidation(this, `$.name`, this._name)} ${umbFocus()}>`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-named-detail-workspace-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-named-detail-workspace-base.ts index 25a7250eb8..0cf1573409 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-named-detail-workspace-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-named-detail-workspace-base.ts @@ -1,8 +1,10 @@ import type { UmbNamableWorkspaceContext } from '../types.js'; +import { UmbNameWriteGuardManager } from '../namable/index.js'; import { UmbEntityDetailWorkspaceContextBase } from './entity-detail-workspace-base.js'; -import type { UmbEntityDetailWorkspaceContextCreateArgs } from './types.js'; +import type { UmbEntityDetailWorkspaceContextArgs, UmbEntityDetailWorkspaceContextCreateArgs } from './types.js'; import type { UmbNamedEntityModel } from '@umbraco-cms/backoffice/entity'; import type { UmbDetailRepository } from '@umbraco-cms/backoffice/repository'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; export abstract class UmbEntityNamedDetailWorkspaceContextBase< NamedDetailModelType extends UmbNamedEntityModel = UmbNamedEntityModel, @@ -17,7 +19,14 @@ export abstract class UmbEntityNamedDetailWorkspaceContextBase< // Just for context token safety: public readonly IS_ENTITY_NAMED_DETAIL_WORKSPACE_CONTEXT = true; - readonly name = this._data.createObservablePartOfCurrent((data) => data?.name); + public readonly name = this._data.createObservablePartOfCurrent((data) => data?.name); + + public readonly nameWriteGuard = new UmbNameWriteGuardManager(this); + + constructor(host: UmbControllerHost, args: UmbEntityDetailWorkspaceContextArgs) { + super(host, args); + this.nameWriteGuard.fallbackToPermitted(); + } getName() { return this._data.getCurrent()?.name; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/namable/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/namable/index.ts index 2c3c5cd178..93463bcfb3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/namable/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/namable/index.ts @@ -1 +1,2 @@ export * from './namable-workspace.context-token.js'; +export * from './name-write-guard.manager.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/namable/namable-workspace-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/namable/namable-workspace-context.interface.ts index 18863e7619..9a5c787569 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/namable/namable-workspace-context.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/namable/namable-workspace-context.interface.ts @@ -1,8 +1,11 @@ import type { UmbWorkspaceContext } from '../workspace-context.interface.js'; +import type { UmbNameWriteGuardManager } from './name-write-guard.manager.js'; import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; export interface UmbNamableWorkspaceContext extends UmbWorkspaceContext { name: Observable; getName(): string | undefined; setName(name: string): void; + // TODO: implement across all namable workspaces and make it mandatory + nameWriteGuard?: UmbNameWriteGuardManager; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/namable/name-write-guard.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/namable/name-write-guard.manager.ts new file mode 100644 index 0000000000..18da553e95 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/namable/name-write-guard.manager.ts @@ -0,0 +1,20 @@ +import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; +import { UmbGuardManagerBase, type UmbGuardRule } from '@umbraco-cms/backoffice/utils'; + +export class UmbNameWriteGuardManager extends UmbGuardManagerBase { + public isPermittedForName(): Observable { + return this._rules.asObservablePart((rules) => this.#resolvePermission(rules)); + } + + #resolvePermission(rules: Array): boolean { + if (rules.some((rule) => rule.permitted === false)) { + return false; + } + + if (rules.some((rule) => rule.permitted === true)) { + return true; + } + + return this._fallback; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/folder/workspace/data-type-folder-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/folder/workspace/data-type-folder-editor.element.ts index 65046d2744..f4c30a5103 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/folder/workspace/data-type-folder-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/folder/workspace/data-type-folder-editor.element.ts @@ -1,23 +1,17 @@ import { html, customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -const elementName = 'umb-data-type-folder-workspace-editor'; -@customElement(elementName) +@customElement('umb-data-type-folder-workspace-editor') export class UmbDataTypeFolderWorkspaceEditorElement extends UmbLitElement { override render() { - return html` - - `; + return html``; } - - static override styles = [UmbTextStyles]; } export { UmbDataTypeFolderWorkspaceEditorElement as element }; declare global { interface HTMLElementTagNameMap { - [elementName]: UmbDataTypeFolderWorkspaceEditorElement; + ['umb-data-type-folder-workspace-editor']: UmbDataTypeFolderWorkspaceEditorElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/tree/folder/workspace/document-blueprint-folder-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/tree/folder/workspace/document-blueprint-folder-editor.element.ts index bbbbd8dd17..1d944ab75b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/tree/folder/workspace/document-blueprint-folder-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/tree/folder/workspace/document-blueprint-folder-editor.element.ts @@ -1,23 +1,17 @@ import { html, customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -const elementName = 'umb-document-blueprint-folder-workspace-editor'; -@customElement(elementName) +@customElement('umb-document-blueprint-folder-workspace-editor') export class UmbDocumentBlueprintFolderWorkspaceEditorElement extends UmbLitElement { override render() { - return html` - - `; + return html` `; } - - static override styles = [UmbTextStyles]; } export { UmbDocumentBlueprintFolderWorkspaceEditorElement as element }; declare global { interface HTMLElementTagNameMap { - [elementName]: UmbDocumentBlueprintFolderWorkspaceEditorElement; + ['umb-document-blueprint-folder-workspace-editor']: UmbDocumentBlueprintFolderWorkspaceEditorElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/tree/folder/workspace/document-type-folder-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/tree/folder/workspace/document-type-folder-editor.element.ts index 19803c6715..ff20daffd9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/tree/folder/workspace/document-type-folder-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/tree/folder/workspace/document-type-folder-editor.element.ts @@ -1,23 +1,17 @@ import { html, customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -const elementName = 'umb-document-type-folder-workspace-editor'; -@customElement(elementName) +@customElement('umb-document-type-folder-workspace-editor') export class UmbDocumentTypeFolderWorkspaceEditorElement extends UmbLitElement { override render() { - return html` - - `; + return html``; } - - static override styles = [UmbTextStyles]; } export { UmbDocumentTypeFolderWorkspaceEditorElement as element }; declare global { interface HTMLElementTagNameMap { - [elementName]: UmbDocumentTypeFolderWorkspaceEditorElement; + ['umb-document-type-folder-workspace-editor']: UmbDocumentTypeFolderWorkspaceEditorElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/folder/workspace/media-type-folder-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/folder/workspace/media-type-folder-editor.element.ts index 1fd8798b49..f26b6e1bc9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/folder/workspace/media-type-folder-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/folder/workspace/media-type-folder-editor.element.ts @@ -1,23 +1,17 @@ import { html, customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -const elementName = 'umb-media-type-folder-workspace-editor'; -@customElement(elementName) +@customElement('umb-media-type-folder-workspace-editor') export class UmbMediaTypeFolderWorkspaceEditorElement extends UmbLitElement { override render() { - return html` - - `; + return html``; } - - static override styles = [UmbTextStyles]; } export { UmbMediaTypeFolderWorkspaceEditorElement as element }; declare global { interface HTMLElementTagNameMap { - [elementName]: UmbMediaTypeFolderWorkspaceEditorElement; + ['umb-media-type-folder-workspace-editor']: UmbMediaTypeFolderWorkspaceEditorElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/tree/folder/workspace/partial-view-folder-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/tree/folder/workspace/partial-view-folder-workspace-editor.element.ts index a086ae2bda..e6eb239ca7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/tree/folder/workspace/partial-view-folder-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/tree/folder/workspace/partial-view-folder-workspace-editor.element.ts @@ -1,49 +1,30 @@ import { UMB_PARTIAL_VIEW_FOLDER_WORKSPACE_CONTEXT } from './partial-view-folder-workspace.context-token.js'; -import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -const elementName = 'umb-partial-view-folder-workspace-editor'; -@customElement(elementName) +@customElement('umb-partial-view-folder-workspace-editor') export class UmbPartialViewFolderWorkspaceEditorElement extends UmbLitElement { - @state() - private _name = ''; - - #workspaceContext?: typeof UMB_PARTIAL_VIEW_FOLDER_WORKSPACE_CONTEXT.TYPE; - constructor() { super(); this.consumeContext(UMB_PARTIAL_VIEW_FOLDER_WORKSPACE_CONTEXT, (workspaceContext) => { - this.#workspaceContext = workspaceContext; - this.#observeName(); + workspaceContext?.nameWriteGuard.addRule({ + unique: 'UMB_SERVER_PREVENT_FILE_SYSTEM_FOLDER_RENAME', + permitted: false, + message: 'It is not possible to change the name of a Partial View folder.', + }); }); } - #observeName() { - if (!this.#workspaceContext) return; - this.observe( - this.#workspaceContext.name, - (name) => { - if (name !== this._name) { - this._name = name ?? ''; - } - }, - 'observeName', - ); - } - override render() { - return html` `; + return html``; } - - static override styles = [UmbTextStyles, css``]; } export { UmbPartialViewFolderWorkspaceEditorElement as element }; declare global { interface HTMLElementTagNameMap { - [elementName]: UmbPartialViewFolderWorkspaceEditorElement; + ['umb-partial-view-folder-workspace-editor']: UmbPartialViewFolderWorkspaceEditorElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/tree/folder/workspace/script-folder-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/tree/folder/workspace/script-folder-workspace-editor.element.ts index aa4d1dd764..fd29d23a39 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/tree/folder/workspace/script-folder-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/tree/folder/workspace/script-folder-workspace-editor.element.ts @@ -1,49 +1,30 @@ import { UMB_SCRIPT_FOLDER_WORKSPACE_CONTEXT } from './script-folder-workspace.context-token.js'; -import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -const elementName = 'umb-script-folder-workspace-editor'; -@customElement(elementName) +@customElement('umb-script-folder-workspace-editor') export class UmbScriptFolderWorkspaceEditorElement extends UmbLitElement { - @state() - private _name = ''; - - #workspaceContext?: typeof UMB_SCRIPT_FOLDER_WORKSPACE_CONTEXT.TYPE; - constructor() { super(); this.consumeContext(UMB_SCRIPT_FOLDER_WORKSPACE_CONTEXT, (workspaceContext) => { - this.#workspaceContext = workspaceContext; - this.#observeName(); + workspaceContext?.nameWriteGuard.addRule({ + unique: 'UMB_SERVER_PREVENT_FILE_SYSTEM_FOLDER_RENAME', + permitted: false, + message: 'It is not possible to change the name of a script folder.', + }); }); } - #observeName() { - if (!this.#workspaceContext) return; - this.observe( - this.#workspaceContext.name, - (name) => { - if (name !== this._name) { - this._name = name ?? ''; - } - }, - 'observeName', - ); - } - override render() { - return html` `; + return html``; } - - static override styles = [UmbTextStyles, css``]; } export { UmbScriptFolderWorkspaceEditorElement as element }; declare global { interface HTMLElementTagNameMap { - [elementName]: UmbScriptFolderWorkspaceEditorElement; + ['umb-script-folder-workspace-editor']: UmbScriptFolderWorkspaceEditorElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/tree/folder/workspace/stylesheet-folder-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/tree/folder/workspace/stylesheet-folder-workspace-editor.element.ts index e9fa8bc2e2..5f86cbf362 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/tree/folder/workspace/stylesheet-folder-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/tree/folder/workspace/stylesheet-folder-workspace-editor.element.ts @@ -1,49 +1,30 @@ import { UMB_STYLESHEET_FOLDER_WORKSPACE_CONTEXT } from './stylesheet-folder-workspace.context-token.js'; -import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -const elementName = 'umb-stylesheet-folder-workspace-editor'; -@customElement(elementName) +@customElement('umb-stylesheet-folder-workspace-editor') export class UmbStylesheetFolderWorkspaceEditorElement extends UmbLitElement { - @state() - private _name = ''; - - #workspaceContext?: typeof UMB_STYLESHEET_FOLDER_WORKSPACE_CONTEXT.TYPE; - constructor() { super(); this.consumeContext(UMB_STYLESHEET_FOLDER_WORKSPACE_CONTEXT, (workspaceContext) => { - this.#workspaceContext = workspaceContext; - this.#observeName(); + workspaceContext?.nameWriteGuard.addRule({ + unique: 'UMB_SERVER_PREVENT_FILE_SYSTEM_FOLDER_RENAME', + permitted: false, + message: 'It is not possible to change the name of a Stylesheet folder.', + }); }); } - #observeName() { - if (!this.#workspaceContext) return; - this.observe( - this.#workspaceContext.name, - (name) => { - if (name !== this._name) { - this._name = name ?? ''; - } - }, - 'observeName', - ); - } - override render() { - return html` `; + return html``; } - - static override styles = [UmbTextStyles, css``]; } export { UmbStylesheetFolderWorkspaceEditorElement as element }; declare global { interface HTMLElementTagNameMap { - [elementName]: UmbStylesheetFolderWorkspaceEditorElement; + ['umb-stylesheet-folder-workspace-editor']: UmbStylesheetFolderWorkspaceEditorElement; } } From 75ee1e65d174ba9f80d18fb631fc3baddfe028b4 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 27 Jun 2025 13:38:21 +0200 Subject: [PATCH 38/82] Show success/failed state for workspace buttons with additional options (#19535) * also show success/failed state when button have additional options * rename method * clear timeout --- .../workspace-action-default-kind.element.ts | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action-default-kind.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action-default-kind.element.ts index 5568fec2f1..019657e5d7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action-default-kind.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action-default-kind.element.ts @@ -78,6 +78,8 @@ export class UmbWorkspaceActionElement< @state() private _items: Array> = []; + #buttonStateResetTimeoutId: number | null = null; + /** * Create a list of original and overwritten aliases of workspace actions for the action. */ @@ -115,16 +117,14 @@ export class UmbWorkspaceActionElement< try { if (!this.#api) throw new Error('No api defined'); await this.#api.execute(); - if (!this._additionalOptions) { - this._buttonState = 'success'; - } + this._buttonState = 'success'; + this.#initButtonStateReset(); } catch (reason) { if (reason) { console.warn(reason); } - if (!this._additionalOptions) { - this._buttonState = 'failed'; - } + this._buttonState = 'failed'; + this.#initButtonStateReset(); } } this.dispatchEvent(new UmbActionExecutedEvent()); @@ -140,6 +140,23 @@ export class UmbWorkspaceActionElement< ); } + #initButtonStateReset() { + /* When the button has additional options, we do not show the waiting state. + Therefore, we need to ensure the button state is reset, so we are able to show the success state again. */ + this.#clearButtonStateResetTimeout(); + + this.#buttonStateResetTimeoutId = window.setTimeout(() => { + this._buttonState = undefined; + }, 2000); + } + + #clearButtonStateResetTimeout() { + if (this.#buttonStateResetTimeoutId !== null) { + clearTimeout(this.#buttonStateResetTimeoutId); + this.#buttonStateResetTimeoutId = null; + } + } + #observeExtensions(aliases: string[]): void { this.#extensionsController?.destroy(); this.#extensionsController = new UmbExtensionsElementAndApiInitializer< @@ -192,6 +209,11 @@ export class UmbWorkspaceActionElement< () => this.#renderButton(), ); } + + override disconnectedCallback(): void { + super.disconnectedCallback(); + this.#clearButtonStateResetTimeout(); + } } export default UmbWorkspaceActionElement; From f7aab5e901c428827f34f40dc619e373e2dbbb40 Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Fri, 27 Jun 2025 12:58:00 +0100 Subject: [PATCH 39/82] Table checkbox/icon alignment (#19615) * Table checkboxes slotted whitespace Fixes #19563 * Table markup tidy-up Resolved the `style` attribute error * localize texts --------- Co-authored-by: Mads Rasmussen --- .../core/components/table/table.element.ts | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.element.ts index b7dae8ab0d..ffc3bf770f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.element.ts @@ -235,13 +235,10 @@ export class UmbTableElement extends UmbLitElement { } override render() { + const style = !(this.config.allowSelection === false && this.config.hideIcon === true) ? 'width: 60px' : undefined; return html` - 'width: 60px', - )}> + ${this._renderHeaderCheckboxCell()} ${this.columns.map((column) => this._renderHeaderCell(column))} @@ -274,18 +271,17 @@ export class UmbTableElement extends UmbLitElement { private _renderHeaderCheckboxCell() { if (this.config.hideIcon && !this.config.allowSelection) return; - return html` ${when( this.config.allowSelection, - () => - html` html` + - `, + ?checked=${this.selection.length === this.items.length}> + `, )} `; @@ -307,9 +303,11 @@ export class UmbTableElement extends UmbLitElement { private _renderRowCheckboxCell(item: UmbTableItem) { if (this.sortable === true) { - return html` - - `; + return html` + + + + `; } if (this.config.hideIcon && !this.config.allowSelection) return; @@ -321,11 +319,10 @@ export class UmbTableElement extends UmbLitElement { this.config.allowSelection, () => html` e.stopPropagation()} @change=${(event: Event) => this._handleRowCheckboxChange(event, item)} - ?checked="${this._isSelected(item.id)}"> - + ?checked=${this._isSelected(item.id)}> `, )} From 9cb0557120bcf0e810ad61e735f6c83e5c44c543 Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Fri, 27 Jun 2025 13:10:12 +0100 Subject: [PATCH 40/82] Tiptap RTE: A11Y label improvements (#19531) * a11y: Adds labels to Tiptap RTE toolbar buttons/menus * a11y: Adds labels to Tiptap toolbar designer actions * a11y: Adds labels to Tiptap statusbar designer actions * a11y: Adds label to Overlay Size dropdown --------- Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> --- ...property-editor-ui-overlay-size.element.ts | 8 ++++++- .../toolbar/tiptap-toolbar-button.element.ts | 4 ++-- .../toolbar/tiptap-toolbar-menu.element.ts | 6 ++--- ...-tiptap-statusbar-configuration.element.ts | 17 +++++++------- ...ui-tiptap-toolbar-configuration.element.ts | 22 ++++++++++--------- 5 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/overlay-size/property-editor-ui-overlay-size.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/overlay-size/property-editor-ui-overlay-size.element.ts index 555b1767c7..d67c4dae0c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/overlay-size/property-editor-ui-overlay-size.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/overlay-size/property-editor-ui-overlay-size.element.ts @@ -42,7 +42,13 @@ export class UmbPropertyEditorUIOverlaySizeElement extends UmbLitElement impleme } override render() { - return html``; + return html` + + + `; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/tiptap-toolbar-button.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/tiptap-toolbar-button.element.ts index c96cb42d1c..62cab87d77 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/tiptap-toolbar-button.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/tiptap-toolbar-button.element.ts @@ -1,7 +1,7 @@ import type { ManifestTiptapToolbarExtensionButtonKind } from '../../extensions/index.js'; import type { UmbTiptapToolbarElementApi } from '../../extensions/types.js'; import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; -import { customElement, html, ifDefined, state, when } from '@umbraco-cms/backoffice/external/lit'; +import { customElement, html, state, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @customElement('umb-tiptap-toolbar-button') @@ -43,7 +43,7 @@ export class UmbTiptapToolbarButtonElement extends UmbLitElement { this.api?.execute(this.editor)}> diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/tiptap-toolbar-menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/tiptap-toolbar-menu.element.ts index 4ffa6538d4..6408fe91a5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/tiptap-toolbar-menu.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/tiptap-toolbar-menu.element.ts @@ -4,7 +4,7 @@ import type { UmbTiptapToolbarElementApi, } from '../../extensions/index.js'; import type { UmbCascadingMenuItem } from '../../components/cascading-menu-popover/cascading-menu-popover.element.js'; -import { css, customElement, html, ifDefined, state, when } from '@umbraco-cms/backoffice/external/lit'; +import { css, customElement, html, state, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; @@ -122,7 +122,7 @@ export class UmbTiptapToolbarMenuElement extends UmbLitElement { () => html` @@ -137,7 +137,7 @@ export class UmbTiptapToolbarMenuElement extends UmbLitElement { () => html` ${label} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-statusbar-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-statusbar-configuration.element.ts index 7d503645ff..d7a9be22ef 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-statusbar-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-statusbar-configuration.element.ts @@ -128,14 +128,11 @@ export class UmbPropertyEditorUiTiptapStatusbarConfigurationElement } #renderAvailableItems() { + const label = this.localize.term('placeholders_filter'); return html`
- +
@@ -161,12 +158,14 @@ export class UmbPropertyEditorUiTiptapStatusbarConfigurationElement const forbidden = !this.#context.isExtensionEnabled(item.alias); const inUse = this.#context.isExtensionInUse(item.alias); if (inUse || forbidden) return nothing; + const label = this.localize.string(item.label); return html` this.#onClick(item)} @@ -174,7 +173,7 @@ export class UmbPropertyEditorUiTiptapStatusbarConfigurationElement @dragend=${this.#onDragEnd}>
${when(item.icon, () => html``)} - ${this.localize.string(item.label)} + ${label}
`; @@ -218,6 +217,7 @@ export class UmbPropertyEditorUiTiptapStatusbarConfigurationElement if (!item) return nothing; const forbidden = !this.#context?.isExtensionEnabled(item.alias); + const label = this.localize.string(item.label); return html` this.#context.removeStatusbarItem([areaIndex, itemIndex])} @dragend=${this.#onDragEnd} @dragstart=${(e: DragEvent) => this.#onDragStart(e, alias, [areaIndex, itemIndex])}>
${when(item.icon, (icon) => html``)} - ${this.localize.string(item.label)} + ${label}
`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts index e28de7e314..651f386b2f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts @@ -133,14 +133,11 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement } #renderAvailableItems() { + const label = this.localize.term('placeholders_filter'); return html`
- +
@@ -166,12 +163,14 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement const forbidden = !this.#context.isExtensionEnabled(item.alias); const inUse = this.#context.isExtensionInUse(item.alias); if (inUse || forbidden) return nothing; + const label = this.localize.string(item.label); return html` this.#onClick(item)} @@ -179,7 +178,7 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement @dragend=${this.#onDragEnd}>
${when(item.icon, () => html``)} - ${this.localize.string(item.label)} + ${label}
`; @@ -282,6 +281,7 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement if (!item) return nothing; const forbidden = !this.#context?.isExtensionEnabled(item.alias); + const label = this.localize.string(item.label); switch (item.kind) { case 'styleMenu': @@ -291,14 +291,15 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement compact class=${forbidden ? 'forbidden' : ''} draggable="true" + label=${label} look=${forbidden ? 'placeholder' : 'outline'} - title=${this.localize.string(item.label)} + title=${label} ?disabled=${forbidden} @click=${() => this.#context.removeToolbarItem([rowIndex, groupIndex, itemIndex])} @dragend=${this.#onDragEnd} @dragstart=${(e: DragEvent) => this.#onDragStart(e, alias, [rowIndex, groupIndex, itemIndex])}>
- ${this.localize.string(item.label)} + ${label}
@@ -313,7 +314,8 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement data-mark="tiptap-toolbar-item:${item.alias}" draggable="true" look=${forbidden ? 'placeholder' : 'outline'} - title=${this.localize.string(item.label)} + label=${label} + title=${label} ?disabled=${forbidden} @click=${() => this.#context.removeToolbarItem([rowIndex, groupIndex, itemIndex])} @dragend=${this.#onDragEnd} @@ -322,7 +324,7 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement ${when( item.icon, () => html``, - () => html`${this.localize.string(item.label)}`, + () => html`${label}`, )}
From c432f5a66c8ffb55167bf079b2bd85a11c3ebe76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Sun, 29 Jun 2025 18:39:07 +0200 Subject: [PATCH 41/82] load more button component and styling (#19622) * localization * tree-load-more-button component * implement * ability to hide block actions * Revert "ability to hide block actions" This reverts commit bf8222f49b92ecbf6008d8e716a578d18ca43b19. --- .../src/assets/lang/da.ts | 1 + .../src/assets/lang/en.ts | 1 + .../packages/core/tree/components/index.ts | 1 + .../tree-load-more-button.element.ts | 35 +++++++++++++++++++ .../core/tree/default/default-tree.element.ts | 2 +- .../src/packages/core/tree/index.ts | 1 + .../tree-item-base/tree-item-element-base.ts | 2 +- 7 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/tree/components/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/tree/components/tree-load-more-button.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts index 07abe03401..481377c202 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts @@ -65,6 +65,7 @@ export default { editContent: 'Rediger indhold', chooseWhereToImport: 'Vælg hvor du vil importere', viewActionsFor: (name) => (name ? `Se handlinger for '${name}'` : 'Se handlinger'), + loadMore: 'Indlæs flere', }, actionCategories: { content: 'Indhold', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts index f7a62dc346..11f000d5e8 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts @@ -73,6 +73,7 @@ export default { wasDeleted: 'was deleted', wasMovedTo: 'was moved to', viewActionsFor: (name) => (name ? `View actions for '${name}'` : 'View actions'), + loadMore: 'Load more', }, actionCategories: { content: 'Content', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/index.ts new file mode 100644 index 0000000000..dda6b6dd69 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/index.ts @@ -0,0 +1 @@ +export * from './tree-load-more-button.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/tree-load-more-button.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/tree-load-more-button.element.ts new file mode 100644 index 0000000000..5c7749f8a5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/tree-load-more-button.element.ts @@ -0,0 +1,35 @@ +import { css, customElement, html } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; + +@customElement('umb-tree-load-more-button') +export class UmbTreeLoadMoreButtonElement extends UmbLitElement { + override render() { + return html``; + } + + static override readonly styles = css` + :host { + position: relative; + display: block; + padding-left: var(--uui-size-space-3); + margin-right: var(--uui-size-space-2); + margin-bottom: var(--uui-size-layout-2); + margin-left: calc(var(--uui-menu-item-indent, 0) * var(--uui-size-4)); + } + uui-button { + width: 100%; + height: var(--uui-size---uui-size-layout-3); + --uui-box-border-radius: calc(var(--uui-border-radius) * 2); + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-tree-load-more-button': UmbTreeLoadMoreButtonElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/default/default-tree.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/default/default-tree.element.ts index c8d9e15a33..a2fcdef5b4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/default/default-tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/default/default-tree.element.ts @@ -170,7 +170,7 @@ export class UmbDefaultTreeElement extends UmbLitElement { return nothing; } - return html` `; + return html` `; } static override styles = css` diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/index.ts index e80702822e..c947f5199f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/index.ts @@ -1,3 +1,4 @@ +export * from './components/index.js'; export * from './constants.js'; export * from './data/index.js'; export * from './default/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts index a5e6a1b01d..4879f2865c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts @@ -220,6 +220,6 @@ export abstract class UmbTreeItemElementBase< return nothing; } - return html` `; + return html` `; } } From c223d93a53391accb6b7ae516285ababbd645c2d Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 30 Jun 2025 07:53:09 +0200 Subject: [PATCH 42/82] Adds XML header docs indicating usage of options on NuCacheSerializerType (#19555) Adds XML header docs indicating usage of options on NuCacheSerializerType. --- .../Models/NuCacheSerializerType.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/NuCacheSerializerType.cs b/src/Umbraco.Core/Configuration/Models/NuCacheSerializerType.cs index 0506ddb98b..a012f47aa3 100644 --- a/src/Umbraco.Core/Configuration/Models/NuCacheSerializerType.cs +++ b/src/Umbraco.Core/Configuration/Models/NuCacheSerializerType.cs @@ -4,10 +4,23 @@ namespace Umbraco.Cms.Core.Configuration.Models; /// -/// The serializer type that nucache uses to persist documents in the database. +/// The serializer type that the published content cache uses to persist documents in the database. /// public enum NuCacheSerializerType { - MessagePack = 1, // Default + /// + /// The default serializer type, which uses MessagePack for serialization. + /// + MessagePack = 1, + + /// + /// The legacy JSON serializer type, which uses JSON for serialization. + /// + /// + /// This option was provided for backward compatibility for the Umbraco cache implementation used from Umbraco 8 to 14 (NuCache). + /// It is no longer supported with the cache implementation from Umbraco 15 based on .NET's Hybrid cache. + /// Use the faster and more compact instead. + /// The option is kept available only for a more readable format suitable for testing purposes. + /// JSON = 2, } From 29b6c2d49e2fba069cbeacf2f8001a17edb6f5fe Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 30 Jun 2025 07:59:22 +0200 Subject: [PATCH 43/82] Assert dates in content editing integration tests to millisecond only (#19513) * Assert dates in content editing integration tests to millisecond only. * Add date time extension unit tests and refactor to switch statement. * Removed whitespace. --- .../Extensions/DateTimeExtensions.cs | 41 +++++---------- .../ContentEditingServiceTests.Update.cs | 10 ++-- .../Extensions/DateTimeExtensionsTests.cs | 51 +++++++++++++++++++ 3 files changed, 70 insertions(+), 32 deletions(-) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/DateTimeExtensionsTests.cs diff --git a/src/Umbraco.Core/Extensions/DateTimeExtensions.cs b/src/Umbraco.Core/Extensions/DateTimeExtensions.cs index 00191e5a76..9fadb6217a 100644 --- a/src/Umbraco.Core/Extensions/DateTimeExtensions.cs +++ b/src/Umbraco.Core/Extensions/DateTimeExtensions.cs @@ -5,6 +5,9 @@ using System.Globalization; namespace Umbraco.Extensions; +/// +/// Provides Extensions for . +/// public static class DateTimeExtensions { /// @@ -18,6 +21,7 @@ public static class DateTimeExtensions Hour, Minute, Second, + Millisecond, } /// @@ -35,32 +39,15 @@ public static class DateTimeExtensions /// The level to truncate the date to. /// The truncated date. public static DateTime TruncateTo(this DateTime dt, DateTruncate truncateTo) - { - if (truncateTo == DateTruncate.Year) + => truncateTo switch { - return new DateTime(dt.Year, 1, 1, 0, 0, 0, dt.Kind); - } - - if (truncateTo == DateTruncate.Month) - { - return new DateTime(dt.Year, dt.Month, 1, 0, 0, 0, dt.Kind); - } - - if (truncateTo == DateTruncate.Day) - { - return new DateTime(dt.Year, dt.Month, dt.Day, 0, 0, 0, dt.Kind); - } - - if (truncateTo == DateTruncate.Hour) - { - return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, 0, 0, dt.Kind); - } - - if (truncateTo == DateTruncate.Minute) - { - return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0, dt.Kind); - } - - return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, dt.Kind); - } + DateTruncate.Year => new DateTime(dt.Year, 1, 1, 0, 0, 0, dt.Kind), + DateTruncate.Month => new DateTime(dt.Year, dt.Month, 1, 0, 0, 0, dt.Kind), + DateTruncate.Day => new DateTime(dt.Year, dt.Month, dt.Day, 0, 0, 0, dt.Kind), + DateTruncate.Hour => new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, 0, 0, dt.Kind), + DateTruncate.Minute => new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0, dt.Kind), + DateTruncate.Second => new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, dt.Kind), + DateTruncate.Millisecond => new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, dt.Millisecond, dt.Kind), + _ => throw new ArgumentOutOfRangeException(nameof(truncateTo), truncateTo, "Invalid truncation level"), + }; } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Update.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Update.cs index b760d94c29..589c4a3516 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Update.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Update.cs @@ -569,7 +569,7 @@ public partial class ContentEditingServiceTests } [Test] - public async Task Updating_Single_Variant_Name_Does_Not_Change_Update_Dates_Of_Other_Vaiants() + public async Task Updating_Single_Variant_Name_Does_Not_Change_Update_Dates_Of_Other_Variants() { var contentType = await CreateVariantContentType(variantTitleAsMandatory: false); @@ -614,11 +614,11 @@ public partial class ContentEditingServiceTests void VerifyUpdate(IContent? updatedContent) { Assert.IsNotNull(updatedContent); - Assert.AreEqual(firstUpdateDateDa, updatedContent.GetUpdateDate("da-DK")); + Assert.AreEqual(firstUpdateDateDa?.TruncateTo(DateTimeExtensions.DateTruncate.Millisecond), updatedContent.GetUpdateDate("da-DK")?.TruncateTo(DateTimeExtensions.DateTruncate.Millisecond)); var lastUpdateDateEn = updatedContent.GetUpdateDate("en-US") ?? throw new InvalidOperationException("Expected a publish date for EN"); - Assert.Greater(lastUpdateDateEn, firstUpdateDateEn); + Assert.Greater(lastUpdateDateEn.TruncateTo(DateTimeExtensions.DateTruncate.Millisecond), firstUpdateDateEn?.TruncateTo(DateTimeExtensions.DateTruncate.Millisecond)); } } @@ -671,11 +671,11 @@ public partial class ContentEditingServiceTests void VerifyUpdate(IContent? updatedContent) { Assert.IsNotNull(updatedContent); - Assert.AreEqual(firstUpdateDateEn, updatedContent.GetUpdateDate("en-US")); + Assert.AreEqual(firstUpdateDateEn.TruncateTo(DateTimeExtensions.DateTruncate.Millisecond), updatedContent.GetUpdateDate("en-US")?.TruncateTo(DateTimeExtensions.DateTruncate.Millisecond)); var lastUpdateDateDa = updatedContent.GetUpdateDate("da-DK") ?? throw new InvalidOperationException("Expected an update date for DA"); - Assert.Greater(lastUpdateDateDa, firstUpdateDateDa); + Assert.Greater(lastUpdateDateDa.TruncateTo(DateTimeExtensions.DateTruncate.Millisecond), firstUpdateDateDa.TruncateTo(DateTimeExtensions.DateTruncate.Millisecond)); } } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/DateTimeExtensionsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/DateTimeExtensionsTests.cs new file mode 100644 index 0000000000..deffcfa7ca --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/DateTimeExtensionsTests.cs @@ -0,0 +1,51 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Extensions; + +[TestFixture] +public class DateTimeExtensionsTests +{ + [Test] + public void ToIsoString_ReturnsCorrectFormat() + { + var date = new DateTime(2025, 6, 9, 14, 30, 45, DateTimeKind.Utc); + var result = date.ToIsoString(); + Assert.AreEqual("2025-06-09 14:30:45", result); + } + + [TestCase(2023, 5, 15, 14, 30, 45, 123, DateTimeExtensions.DateTruncate.Year, 2023, 1, 1, 0, 0, 0, 0)] + [TestCase(2023, 5, 15, 14, 30, 45, 123, DateTimeExtensions.DateTruncate.Month, 2023, 5, 1, 0, 0, 0, 0)] + [TestCase(2023, 5, 15, 14, 30, 45, 123, DateTimeExtensions.DateTruncate.Day, 2023, 5, 15, 0, 0, 0, 0)] + [TestCase(2023, 5, 15, 14, 30, 45, 123, DateTimeExtensions.DateTruncate.Hour, 2023, 5, 15, 14, 0, 0, 0)] + [TestCase(2023, 5, 15, 14, 30, 45, 123, DateTimeExtensions.DateTruncate.Minute, 2023, 5, 15, 14, 30, 0, 0)] + [TestCase(2023, 5, 15, 14, 30, 45, 123, DateTimeExtensions.DateTruncate.Second, 2023, 5, 15, 14, 30, 45, 0)] + [TestCase(2023, 5, 15, 14, 30, 45, 123, DateTimeExtensions.DateTruncate.Millisecond, 2023, 5, 15, 14, 30, 45, 123)] + public void TruncateTo_TruncatesCorrectly( + int year, + int month, + int day, + int hour, + int minute, + int second, + int millisecond, + DateTimeExtensions.DateTruncate truncateTo, + int expectedYear, + int expectedMonth, + int expectedDay, + int expectedHour, + int expectedMinute, + int expectedSecond, + int expectedMillisecond) + { + var date = new DateTime(year, month, day, hour, minute, second, millisecond, DateTimeKind.Utc); + var expected = new DateTime(expectedYear, expectedMonth, expectedDay, expectedHour, expectedMinute, expectedSecond, expectedMillisecond, DateTimeKind.Utc); + + var result = date.TruncateTo(truncateTo); + + Assert.AreEqual(expected, result); + } +} From 861afde3a0f544dc93f651fe84f57853b9ad56aa Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 30 Jun 2025 10:04:50 +0200 Subject: [PATCH 44/82] Fix for code scanning alert no. 1719: Client-side cross-site scripting (#19607) * Fix for code scanning alert no. 1719: Client-side cross-site scripting Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * feat: uses built-in sanitizeHtml in backoffice --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .../url/info-app/media-links-workspace-info-app.element.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/url/info-app/media-links-workspace-info-app.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/url/info-app/media-links-workspace-info-app.element.ts index a98e3d9df1..41ab4a4d2e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/url/info-app/media-links-workspace-info-app.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/url/info-app/media-links-workspace-info-app.element.ts @@ -6,7 +6,7 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action'; import type { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; -import { debounce } from '@umbraco-cms/backoffice/utils'; +import { debounce, sanitizeHTML } from '@umbraco-cms/backoffice/utils'; interface UmbMediaInfoViewLink { url: string | undefined; @@ -111,7 +111,7 @@ export class UmbMediaLinksWorkspaceInfoAppElement extends UmbLitElement { const html = ` - + `; popup.document.open(); From fb2aad0b1d7b7d5e9dd612360708f1c219b4bba2 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 30 Jun 2025 11:00:11 +0200 Subject: [PATCH 45/82] V16: Adds a state for "forbidden" for entities that return 403 (Access denied) (#19557) * feat: adds new localization keys for forbidden routes * feat: ignore all 400, 401, 403, and 404 errors as they are handled by the UI * feat: adds new elements to show forbidden routes and entities * feat: adds generic forbidden state to base entities * feat: injects a forbidden route component to documents * feat: adds 'forbidden' state to media workspace * chore: aligns document and media workspaces * test(mock): adds user configuration endpoint * test(mock): adds calculate-start-nodes endpoint to users * test(mock): adds missing endpoint for 'client-credentials' * feat: clean up old observers on entity errors * feat: aligns UI for better DX if there is no user * fix: returns early if there is no user, instead of trying to append properties to the object * feat: adds 'forbidden' state to members * feat: adds support for forbidden document blueprints * feat: allows parent to be undefined as well as null * feat: forbidden route for members as a state * chore: simplify language workspace * test: adds forbidden mock data * test: adds missing endpoints and a check for forbidden ids --- .../src/assets/lang/da.ts | 21 +++++++ .../src/assets/lang/de.ts | 26 ++++++++ .../src/assets/lang/en.ts | 11 ++++ .../src/mocks/browser-handlers.ts | 2 + .../mocks/data/data-type/data-type.data.ts | 12 ++++ .../mocks/data/dictionary/dictionary.data.ts | 17 ++++++ .../document-blueprint.data.ts | 32 ++++++++++ .../data/document-type/document-type.data.ts | 54 +++++++++++++++++ .../src/mocks/data/document/document.data.ts | 47 ++++++++++++++- .../src/mocks/data/language/language.data.ts | 6 ++ .../mocks/data/media-type/media-type.data.ts | 50 ++++++++++++++++ .../src/mocks/data/media/media.data.ts | 32 ++++++++++ .../data/member-group/member-group.data.ts | 4 ++ .../data/member-type/member-type.data.ts | 48 +++++++++++++++ .../src/mocks/data/member/member.data.ts | 25 ++++++++ .../src/mocks/data/member/member.db.ts | 45 ++++++++++++++ .../data/partial-view/partial-view.data.ts | 56 ++++++++++-------- .../src/mocks/data/script/script.data.ts | 8 +++ .../mocks/data/stylesheet/stylesheet.data.ts | 8 +++ .../src/mocks/data/template/template.data.ts | 8 +++ .../mocks/data/user-group/user-group.data.ts | 16 +++++ .../src/mocks/data/user/user.data.ts | 22 +++++++ .../src/mocks/data/user/user.db.ts | 43 ++++++++++++++ .../data/utils/entity/entity-tree.manager.ts | 2 +- .../handlers/data-type/detail.handlers.ts | 12 ++++ .../handlers/dictionary/detail.handlers.ts | 12 ++++ .../handlers/dictionary/tree.handlers.ts | 7 +++ .../document-blueprint/detail.handlers.ts | 12 ++++ .../handlers/document-type/detail.handlers.ts | 27 ++++++++- .../handlers/document-type/tree.handlers.ts | 7 +++ .../handlers/document/detail.handlers.ts | 24 ++++++++ .../handlers/language/detail.handlers.ts | 12 ++++ .../handlers/media-type/detail.handlers.ts | 12 ++++ .../mocks/handlers/media/detail.handlers.ts | 20 +++++++ .../handlers/member-group/detail.handlers.ts | 23 ++++++++ .../handlers/member-type/detail.handlers.ts | 12 ++++ .../handlers/member-type/tree.handlers.ts | 8 +-- .../mocks/handlers/member/detail.handlers.ts | 12 ++++ .../mocks/handlers/member/filter.handlers.ts | 29 +++++++++ .../src/mocks/handlers/member/index.ts | 3 +- .../handlers/partial-view/detail.handlers.ts | 12 ++++ .../handlers/relation-type/detail.handlers.ts | 4 ++ .../mocks/handlers/script/detail.handlers.ts | 12 ++++ .../src/mocks/handlers/segment.handlers.ts | 17 ++++++ .../handlers/stylesheet/detail.handlers.ts | 12 ++++ .../handlers/template/detail.handlers.ts | 12 ++++ .../handlers/user-group/detail.handlers.ts | 12 ++++ .../mocks/handlers/user/detail.handlers.ts | 36 +++++++++++ .../try-execute/try-execute.controller.ts | 12 +--- .../forbidden/route-forbidden.element.ts | 59 +++++++++++++++++++ .../src/packages/core/router/route/index.ts | 1 + .../entity-detail-workspace-base.ts | 23 +++++--- .../entity-detail-forbidden.element.ts | 50 ++++++++++++++++ .../entity-detail-workspace-editor.element.ts | 24 +++++++- .../entity-detail/global-components/index.ts | 4 +- ...ment-blueprint-workspace-editor.element.ts | 49 +++++++++++---- .../document-workspace-editor.element.ts | 23 ++++++-- .../language-workspace-editor.element.ts | 5 +- .../media-workspace-editor.element.ts | 48 +++++++++++---- .../member/member-workspace-editor.element.ts | 38 ++++++++---- .../user/user-workspace-editor.element.ts | 20 +++---- .../workspace/user/user-workspace.context.ts | 6 ++ 62 files changed, 1192 insertions(+), 114 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/mocks/handlers/member/filter.handlers.ts create mode 100644 src/Umbraco.Web.UI.Client/src/mocks/handlers/segment.handlers.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/router/route/forbidden/route-forbidden.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/global-components/entity-detail-forbidden.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts index 481377c202..6cd1667361 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts @@ -343,6 +343,24 @@ export default { blueprintDescription: 'En indholdskabelon er foruddefineret indhold, som en redaktør kan vælge at bruge\n som grundlag for at oprette nyt indhold\n ', }, + entityDetail: { + notFoundTitle: (entityType: string) => { + const entityName = entityType ?? 'Elementet'; + return `${entityName} blev ikke fundet`; + }, + notFoundDescription: (entityType: string) => { + const entityName = entityType ?? 'element'; + return `Den/det ønskede ${entityName} kunne ikke findes. Dette kan skyldes, at den/det er blevet slettet, eller at du ikke har adgang. Kontakt din administrator for hjælp.`; + }, + forbiddenTitle: (entityType: string) => { + const entityName = entityType ?? 'Elementet'; + return `${entityName} er ikke tilgængelig`; + }, + forbiddenDescription: (entityType: string) => { + const entityName = entityType ?? 'element'; + return `Du har ikke adgang til den/det ønskede ${entityName}. Kontakt din administrator for hjælp.`; + }, + }, media: { clickToUpload: 'Klik for at uploade', orClickHereToUpload: 'eller klik her for at vælge filer', @@ -2606,6 +2624,9 @@ export default { routing: { routeNotFoundTitle: 'Ikke fundet', routeNotFoundDescription: 'Den side du leder efter kunne ikke findes. Kontroller adressen og prøv igen.', + routeForbiddenTitle: 'Adgang nægtet', + routeForbiddenDescription: + 'Du har ikke tilladelse til at få adgang til denne ressource. Kontakt venligst din administrator for hjælp.', }, codeEditor: { label: 'Code editor', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/de.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/de.ts index b33b1c09a6..799967daea 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/de.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/de.ts @@ -344,6 +344,24 @@ export default { blueprintDescription: 'Eine Inhaltsvorlage ist vordefinierter Inhalt,\n den ein Redakteur als Basis für neuen Inhalt verwenden kann\n ', }, + entityDetail: { + notFoundTitle: (entityType: string) => { + const entityName = entityType ?? 'Element'; + return `${entityName} nicht gefunden`; + }, + notFoundDescription: (entityType: string) => { + const entityName = entityType ?? 'element'; + return `Der angeforderte ${entityName} konnte nicht gefunden werden. Bitte überprüfen Sie die URL und versuchen Sie es erneut.`; + }, + forbiddenTitle: (entityType: string) => { + const entityName = entityType ?? 'Element'; + return `${entityName} nicht verfügbar`; + }, + forbiddenDescription: (entityType: string) => { + const entityName = entityType ?? 'dieses Element'; + return `Sie haben keine Berechtigung, auf ${entityName} zuzugreifen. Bitte wenden Sie sich an Ihren Administrator, um Unterstützung zu erhalten.`; + }, + }, media: { clickToUpload: 'Für Upload klicken', orClickHereToUpload: 'oder klicken Sie hier um eine Datei zu wählen', @@ -2007,4 +2025,12 @@ export default { searchResult: 'Element zurückgegeben', searchResults: 'Elemente zurückgegeben', }, + routing: { + routeNotFoundTitle: 'Seite wurde nicht gefunden', + routeNotFoundDescription: + 'Die angeforderte Seite konnte nicht gefunden werden. Bitte überprüfen Sie die URL und versuchen Sie es erneut.', + routeForbiddenTitle: 'Zugriff verweigert', + routeForbiddenDescription: + 'Sie haben keine Berechtigung, auf diese Seite zuzugreifen. Bitte wenden Sie sich an Ihren Administrator, um Unterstützung zu erhalten.', + }, } as UmbLocalizationDictionary; diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts index 11f000d5e8..a9b25282a5 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts @@ -374,6 +374,14 @@ export default { const entityName = entityType ?? 'item'; return `The requested ${entityName} could not be found. Please check the URL and try again.`; }, + forbiddenTitle: (entityType: string) => { + const entityName = entityType ?? 'item'; + return `Access denied to this ${entityName}`; + }, + forbiddenDescription: (entityType: string) => { + const entityName = entityType ?? 'item'; + return `You do not have permission to access this ${entityName}. Please contact your administrator for assistance.`; + }, }, media: { clickToUpload: 'Click to upload', @@ -2736,6 +2744,9 @@ export default { routing: { routeNotFoundTitle: 'Not found', routeNotFoundDescription: 'The requested route could not be found. Please check the URL and try again.', + routeForbiddenTitle: 'Access denied', + routeForbiddenDescription: + 'You do not have permission to access this resource. Please contact your administrator for assistance.', }, codeEditor: { label: 'Code editor', diff --git a/src/Umbraco.Web.UI.Client/src/mocks/browser-handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/browser-handlers.ts index 7ce13e1c64..6003f11ff5 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/browser-handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/browser-handlers.ts @@ -39,6 +39,7 @@ import * as manifestsHandlers from './handlers/manifests.handlers.js'; import * as serverHandlers from './handlers/server.handlers.js'; import { handlers as documentBlueprintHandlers } from './handlers/document-blueprint/index.js'; import { handlers as temporaryFileHandlers } from './handlers/temporary-file/index.js'; +import { handlers as segmentHandlers } from './handlers/segment.handlers.js'; const handlers = [ ...backofficeHandlers, @@ -80,6 +81,7 @@ const handlers = [ ...userHandlers, ...documentBlueprintHandlers, ...temporaryFileHandlers, + ...segmentHandlers, ...serverHandlers.serverInformationHandlers, serverHandlers.serverRunningHandler, ...manifestsHandlers.manifestEmptyHandlers, diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts index c317402c4c..571074884e 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts @@ -31,6 +31,18 @@ export const data: Array = [ isDeletable: true, canIgnoreStartNodes: false, }, + { + id: 'forbidden', + parent: null, + name: 'Forbidden Data Type', + editorAlias: 'Umbraco.TextBox', + editorUiAlias: 'Umb.PropertyEditorUi.TextBox', + values: [], + hasChildren: false, + isFolder: false, + isDeletable: true, + canIgnoreStartNodes: false, + }, { id: '0cc0eba1-9960-42c9-bf9b-60e150b429ae', parent: null, diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/dictionary/dictionary.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/dictionary/dictionary.data.ts index 3f68fa97f5..ba7e0e18bf 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/dictionary/dictionary.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/dictionary/dictionary.data.ts @@ -11,6 +11,23 @@ export type UmbMockDictionaryModel = DictionaryItemResponseModel & DictionaryOverviewResponseModel; export const data: Array = [ + { + name: 'Forbidden', + id: 'forbidden', + parent: null, + hasChildren: false, + translatedIsoCodes: ['en-us'], + translations: [ + { + isoCode: 'en-us', + translation: 'This is a forbidden dictionary item', + }, + { + isoCode: 'da', + translation: 'Dette er et forbudt ordbogsobjekt', + }, + ], + }, { name: 'Hello', id: 'aae7d0ab-53ba-485d-b8bd-12537f9925cb', diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document-blueprint/document-blueprint.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document-blueprint/document-blueprint.data.ts index 8588c17bb2..6be07dc369 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document-blueprint/document-blueprint.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document-blueprint/document-blueprint.data.ts @@ -18,6 +18,7 @@ export const data: Array = [ }, hasChildren: false, isFolder: false, + parent: null, name: 'The Simplest Document Blueprint', variants: [ { @@ -40,4 +41,35 @@ export const data: Array = [ }, ], }, + { + id: 'forbidden', + documentType: { + id: 'the-simplest-document-type-id', + icon: 'icon-document', + }, + hasChildren: false, + isFolder: false, + parent: null, + name: 'A Forbidden Document Blueprint', + variants: [ + { + state: DocumentVariantStateModel.DRAFT, + publishDate: '2023-02-06T15:32:24.957009', + culture: 'en-US', + segment: null, + name: 'A Forbidden Document Blueprint', + createDate: '2023-02-06T15:32:05.350038', + updateDate: '2023-02-06T15:32:24.957009', + }, + ], + values: [ + { + editorAlias: 'Umbraco.TextBox', + alias: 'prop1', + culture: null, + segment: null, + value: 'my blueprint value', + }, + ], + }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts index 0c1815eaf7..7314cc77d3 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts @@ -1870,4 +1870,58 @@ export const data: Array = [ keepLatestVersionPerDayForDays: null, }, }, + { + allowedTemplates: [], + defaultTemplate: { id: 'the-simplest-document-type-id' }, + id: 'forbidden', + alias: 'forbidden', + name: 'A forbidden document type', + description: null, + icon: 'icon-document', + allowedAsRoot: true, + variesByCulture: false, + variesBySegment: false, + isElement: false, + hasChildren: false, + parent: null, + isFolder: false, + properties: [ + { + id: '1680d4d2-cda8-4ac2-affd-a69fc10382b1', + container: { id: 'the-simplest-document-type-id-container' }, + alias: 'prop1', + name: 'Prop 1', + description: null, + dataType: { id: '0cc0eba1-9960-42c9-bf9b-60e150b429ae' }, + variesByCulture: false, + variesBySegment: false, + sortOrder: 0, + validation: { + mandatory: false, + mandatoryMessage: null, + regEx: null, + regExMessage: null, + }, + appearance: { + labelOnTop: false, + }, + }, + ], + containers: [ + { + id: 'the-simplest-document-type-id-container', + parent: null, + name: 'Content', + type: 'Group', + sortOrder: 0, + }, + ], + allowedDocumentTypes: [], + compositions: [], + cleanup: { + preventCleanup: false, + keepAllVersionsNewerThanDays: null, + keepLatestVersionPerDayForDays: null, + }, + }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts index ec53e44a1c..ca181f29ab 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts @@ -28,7 +28,7 @@ export const data: Array = [ { state: DocumentVariantStateModel.DRAFT, publishDate: '2023-02-06T15:32:24.957009', - culture: 'en-us', + culture: 'en-US', segment: null, name: 'The Simplest Document', createDate: '2023-02-06T15:32:05.350038', @@ -1227,5 +1227,50 @@ export const data: Array = [ }, ], }, + { + ancestors: [], + urls: [], + template: null, + id: 'forbidden', + createDate: '2023-02-06T15:32:05.350038', + parent: null, + documentType: { + id: 'the-simplest-document-type-id', + icon: 'icon-document', + }, + hasChildren: false, + noAccess: false, + isProtected: false, + isTrashed: false, + variants: [ + { + state: DocumentVariantStateModel.PUBLISHED, + publishDate: '2023-02-06T15:32:24.957009', + culture: 'en-US', + segment: null, + name: 'A forbidden document', + createDate: '2023-02-06T15:32:05.350038', + updateDate: '2023-02-06T15:32:24.957009', + }, + { + state: DocumentVariantStateModel.PUBLISHED, + publishDate: '2023-02-06T15:32:24.957009', + culture: 'da-dk', + segment: null, + name: 'Et utilgængeligt dokument', + createDate: '2023-02-06T15:32:05.350038', + updateDate: '2023-02-06T15:32:24.957009', + }, + ], + values: [ + { + editorAlias: 'Umbraco.TextBox', + alias: 'prop1', + culture: null, + segment: null, + value: 'default value here', + }, + ], + }, ...permissionsTestData, ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/language/language.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/language/language.data.ts index 10c0771e8b..1f3136ab67 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/language/language.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/language/language.data.ts @@ -16,4 +16,10 @@ export const data: Array = [ isMandatory: false, fallbackIsoCode: 'en-US', }, + { + name: 'Forbidden', + isoCode: 'forbidden', + isDefault: false, + isMandatory: false, + }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.data.ts index 1614c94fc1..4e01037aeb 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.data.ts @@ -304,4 +304,54 @@ export const data: Array = [ isDeletable: false, aliasCanBeChanged: false, }, + { + name: 'A Forbidden Media Type', + id: 'forbidden', + parent: null, + description: 'Clicking on this results in a 403 Forbidden error', + alias: 'forbidden', + icon: 'icon-document', + properties: [ + { + id: '19', + container: { id: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75' }, + alias: 'umbracoFile', + name: 'File', + description: '', + dataType: { id: 'dt-uploadFieldFiles' }, + variesByCulture: false, + variesBySegment: false, + sortOrder: 0, + validation: { + mandatory: true, + mandatoryMessage: null, + regEx: null, + regExMessage: null, + }, + appearance: { + labelOnTop: false, + }, + }, + ], + containers: [ + { + id: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + parent: null, + name: 'Content', + type: 'Group', + sortOrder: 0, + }, + ], + allowedAsRoot: true, + variesByCulture: false, + variesBySegment: false, + isElement: false, + allowedMediaTypes: [{ mediaType: { id: 'forbidden' }, sortOrder: 0 }], + compositions: [], + isFolder: false, + hasChildren: false, + collection: { id: 'dt-collectionView' }, + isDeletable: true, + aliasCanBeChanged: false, + }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts index dc4e3507dd..33cc297800 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts @@ -244,4 +244,36 @@ export const data: Array = [ ], urls: [], }, + { + hasChildren: false, + id: 'forbidden', + createDate: '2023-02-06T15:32:05.350038', + parent: null, + noAccess: false, + isTrashed: false, + mediaType: { + id: 'media-type-1-id', + icon: 'icon-picture', + }, + values: [ + { + editorAlias: 'Umbraco.UploadField', + alias: 'mediaPicker', + value: { + src: '/umbraco/backoffice/assets/installer-illustration.svg', + }, + }, + ], + variants: [ + { + publishDate: '2023-02-06T15:31:51.354764', + culture: null, + segment: null, + name: 'Forbidden Media', + createDate: '2023-02-06T15:31:46.876902', + updateDate: '2023-02-06T15:31:51.354764', + }, + ], + urls: [], + }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/member-group/member-group.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/member-group/member-group.data.ts index 7ac00b079a..d0ffd87cf4 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/member-group/member-group.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/member-group/member-group.data.ts @@ -11,4 +11,8 @@ export const data: Array = [ name: 'Member Group 2', id: 'member-group-2-id', }, + { + name: 'Forbidden Member Group', + id: 'forbidden', + }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/member-type/member-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/member-type/member-type.data.ts index a2466459f4..918ba7f61d 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/member-type/member-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/member-type/member-type.data.ts @@ -59,4 +59,52 @@ export const data: Array = [ hasChildren: false, hasListView: false, }, + { + name: 'A Forbidden Member Type', + id: 'forbidden', + description: 'Clicking on this results in a 403 Forbidden error.', + alias: 'forbidden', + icon: 'icon-bug', + properties: [ + { + id: '1680d4d2-cda8-4ac2-affd-a69fc10382b1', + container: { id: 'the-simplest-document-type-id-container' }, + alias: 'prop1', + name: 'Prop 1', + description: null, + isSensitive: false, + visibility: { memberCanEdit: true, memberCanView: true }, + dataType: { id: '0cc0eba1-9960-42c9-bf9b-60e150b429ae' }, + variesByCulture: false, + variesBySegment: false, + sortOrder: 0, + validation: { + mandatory: false, + mandatoryMessage: null, + regEx: null, + regExMessage: null, + }, + appearance: { + labelOnTop: false, + }, + }, + ], + containers: [ + { + id: 'the-simplest-document-type-id-container', + parent: null, + name: 'Content', + type: 'Group', + sortOrder: 0, + }, + ], + allowedAsRoot: false, + variesByCulture: false, + variesBySegment: false, + isElement: false, + compositions: [], + parent: null, + hasChildren: false, + hasListView: false, + }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/member/member.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/member/member.data.ts index ee2d5c0e1d..55597474c2 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/member/member.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/member/member.data.ts @@ -82,4 +82,29 @@ export const data: Array = [ ], kind: MemberKindModel.DEFAULT, }, + { + email: 'forbidden@example.com', + failedPasswordAttempts: 0, + groups: [], + id: 'forbidden', + isApproved: false, + isLockedOut: false, + isTwoFactorEnabled: false, + lastLockoutDate: null, + lastLoginDate: null, + lastPasswordChangeDate: null, + memberType: { id: 'member-type-1-id', icon: '' }, + username: 'forbidden', + values: [], + variants: [ + { + name: 'A Forbidden Member', + culture: 'en-us', + segment: null, + createDate: '2023-02-06T15:31:46.876902', + updateDate: '2023-02-06T15:31:51.354764', + }, + ], + kind: MemberKindModel.DEFAULT, + }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/member/member.db.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/member/member.db.ts index 77f41e2cde..076b7234c8 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/member/member.db.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/member/member.db.ts @@ -3,6 +3,7 @@ import { UmbMockEntityItemManager } from '../utils/entity/entity-item.manager.js import { UmbMockEntityDetailManager } from '../utils/entity/entity-detail.manager.js'; import { umbMemberTypeMockDb } from '../member-type/member-type.db.js'; import { UmbMockContentCollectionManager } from '../utils/content/content-collection.manager.js'; +import { objectArrayFilter, queryFilter } from '../utils.js'; import type { UmbMockMemberModel } from './member.data.js'; import { data } from './member.data.js'; import { UmbId } from '@umbraco-cms/backoffice/id'; @@ -12,8 +13,26 @@ import { type MemberItemResponseModel, type MemberResponseModel, type MemberValueResponseModel, + type PagedMemberResponseModel, } from '@umbraco-cms/backoffice/external/backend-api'; +interface MemberFilterOptions { + skip: number; + take: number; + orderBy: string; + orderDirection: string; + memberGroupIds: Array<{ id: string }>; + memberTypeId: string; + filter: string; +} + +const memberGroupFilter = (filterOptions: MemberFilterOptions, item: UmbMockMemberModel) => + objectArrayFilter(filterOptions.memberGroupIds, item.groups, 'id'); +const memberTypeFilter = (filterOptions: MemberFilterOptions, item: UmbMockMemberModel) => + queryFilter(filterOptions.memberTypeId, item.memberType.id); +const memberQueryFilter = (filterOptions: MemberFilterOptions, item: UmbMockMemberModel) => + queryFilter(filterOptions.filter, item.username); + class UmbMemberMockDB extends UmbEntityMockDbBase { item = new UmbMockEntityItemManager(this, itemResponseMapper); detail = new UmbMockEntityDetailManager(this, createDetailMockMapper, detailResponseMapper); @@ -22,6 +41,32 @@ class UmbMemberMockDB extends UmbEntityMockDbBase { constructor(data: Array) { super(data); } + + filter(options: MemberFilterOptions): PagedMemberResponseModel { + const allItems = this.getAll(); + + const filterOptions: MemberFilterOptions = { + skip: options.skip || 0, + take: options.take || 25, + orderBy: options.orderBy || 'name', + orderDirection: options.orderDirection || 'asc', + memberGroupIds: options.memberGroupIds, + memberTypeId: options.memberTypeId || '', + filter: options.filter, + }; + + const filteredItems = allItems.filter( + (item) => + memberGroupFilter(filterOptions, item) && + memberTypeFilter(filterOptions, item) && + memberQueryFilter(filterOptions, item), + ); + const totalItems = filteredItems.length; + + const paginatedItems = filteredItems.slice(filterOptions.skip, filterOptions.skip + filterOptions.take); + + return { total: totalItems, items: paginatedItems }; + } } const createDetailMockMapper = (request: CreateMemberRequestModel): UmbMockMemberModel => { diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/partial-view/partial-view.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/partial-view/partial-view.data.ts index 0fa1bf78c7..0bc254e703 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/partial-view/partial-view.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/partial-view/partial-view.data.ts @@ -10,6 +10,14 @@ export type UmbMockPartialViewModel = PartialViewResponseModel & PartialViewItemResponseModel; export const data: Array = [ + { + name: 'Forbidden', + path: '/forbidden', + parent: null, + isFolder: false, + hasChildren: false, + content: '', + }, { name: 'blockgrid', path: '/blockgrid', @@ -44,7 +52,7 @@ export const data: Array = [ hasChildren: false, content: `@using Umbraco.Extensions @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage - +
= [ @{ if (Model?.Any() != true) { return; } } - +
@@ -99,7 +107,7 @@ export const data: Array = [ { string embedValue = Convert.ToString(Model.value); embedValue = embedValue.DetectIsJson() ? Model.value.preview : Model.value; - +
@Html.Raw(embedValue)
@@ -130,18 +138,18 @@ export const snippets: Array = [ content: `@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage @using Umbraco.Cms.Core.Routing @using Umbraco.Extensions - + @inject IPublishedUrlProvider PublishedUrlProvider @* This snippet makes a breadcrumb of parents using an unordered HTML list. - + How it works: - It uses the Ancestors() method to get all parents and then generates links so the visitor can go back - Finally it outputs the name of the current page (without a link) *@ - + @{ var selection = Model.Ancestors().ToArray(); } - + @if (selection?.Length > 0) { @@ -170,7 +178,7 @@ export const snippets: Array = [ @inject IMemberExternalLoginProviders memberExternalLoginProviders @inject IExternalLoginWithKeyService externalLoginWithKeyService @{ - + // Build a profile model to edit var profileModel = await memberModelBuilderFactory .CreateProfileModel() @@ -179,21 +187,21 @@ export const snippets: Array = [ // Include editable custom properties on the form .WithCustomProperties(true) .BuildForCurrentMemberAsync(); - + var success = TempData["FormSuccess"] != null; - + var loginProviders = await memberExternalLoginProviders.GetMemberProvidersAsync(); var externalSignInError = ViewData.GetExternalSignInProviderErrors(); - + var currentExternalLogin = profileModel is null ? new Dictionary() : externalLoginWithKeyService.GetExternalLogins(profileModel.Key).ToDictionary(x=>x.LoginProvider, x=>x.ProviderKey); } - + - + @if (profileModel != null) { if (success) @@ -201,7 +209,7 @@ export const snippets: Array = [ @* This message will show if profileModel.RedirectUrl is not defined (default) *@

Profile updated

} - + using (Html.BeginUmbracoForm("HandleUpdateProfile", new { RedirectUrl = profileModel.RedirectUrl })) {

Update your account.

@@ -217,7 +225,7 @@ export const snippets: Array = [
- + @if (!string.IsNullOrWhiteSpace(profileModel.UserName)) {
@@ -226,7 +234,7 @@ export const snippets: Array = [
} - + @if (profileModel.MemberProperties != null) { for (var i = 0; i < profileModel.MemberProperties.Count; i++) @@ -239,19 +247,19 @@ export const snippets: Array = [
} } - + - + if (loginProviders.Any()) {

Link external accounts

- + if (externalSignInError?.AuthenticationType is null && externalSignInError?.Errors.Any() == true) { @Html.DisplayFor(x => externalSignInError.Errors); } - + @foreach (var login in loginProviders) { if (currentExternalLogin.TryGetValue(login.ExternalLoginProvider.AuthenticationType, out var providerKey)) @@ -262,7 +270,7 @@ export const snippets: Array = [ - + if (externalSignInError?.AuthenticationType == login.ExternalLoginProvider.AuthenticationType) { @Html.DisplayFor(x => externalSignInError.Errors); @@ -276,14 +284,14 @@ export const snippets: Array = [ - + if (externalSignInError?.AuthenticationType == login.ExternalLoginProvider.AuthenticationType) { @Html.DisplayFor(x => externalSignInError.Errors); } } } - + } } } diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/script/script.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/script/script.data.ts index 24b7fb53e0..6d3dd66628 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/script/script.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/script/script.data.ts @@ -129,4 +129,12 @@ export const data: Array = [ hasChildren: false, content: `alert('hello file with dash');`, }, + { + name: 'Forbidden', + path: '/forbidden', + parent: null, + isFolder: false, + hasChildren: false, + content: `console.log('You are not allowed to see this script!');`, + }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/stylesheet/stylesheet.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/stylesheet/stylesheet.data.ts index e2ce26ce29..f8ccec9577 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/stylesheet/stylesheet.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/stylesheet/stylesheet.data.ts @@ -89,4 +89,12 @@ body { } `, }, + { + name: 'Forbidden', + path: '/forbidden', + parent: null, + isFolder: false, + hasChildren: false, + content: `console.log('You are not allowed to see this stylesheet!');`, + }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/template/template.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/template/template.data.ts index 4aadfd758e..36c308dfd3 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/template/template.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/template/template.data.ts @@ -59,6 +59,14 @@ export const data: Array = [ content: '@using Umbraco.Cms.Web.Common.PublishedModels;\n@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage\r\n@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;\r\n@{\r\n\tLayout = "Test.cshtml";\r\n}', }, + { + id: 'forbidden', + parent: null, + name: 'Forbidden', + hasChildren: false, + alias: 'Forbidden', + content: `console.log('You are not allowed to see this template!');`, + }, ]; export const createTemplateScaffold = (masterTemplateAlias: string) => { diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.data.ts index ab62454c9e..11f7db6c3a 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.data.ts @@ -169,4 +169,20 @@ export const data: Array = [ aliasCanBeChanged: true, isDeletable: true, }, + { + id: 'forbidden', + name: 'Forbidden', + alias: 'forbidden', + icon: 'icon-lock', + documentStartNode: { id: 'forbidden-document-id' }, + fallbackPermissions: [], + permissions: [], + sections: [], + languages: [], + hasAccessToAllLanguages: true, + documentRootAccess: true, + mediaRootAccess: true, + aliasCanBeChanged: false, + isDeletable: false, + }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.data.ts index 2043e9d216..97f60264fe 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.data.ts @@ -118,6 +118,28 @@ export const data: Array = [ userGroupIds: [{ id: 'user-group-editors-id' }, { id: 'user-group-sensitive-data-id' }], userName: '', }, + { + avatarUrls: [], + createDate: '2023-10-12T18:30:32.879Z', + documentStartNodeIds: [], + email: 'forbidden@example.com', + failedLoginAttempts: 0, + hasDocumentRootAccess: true, + hasMediaRootAccess: true, + id: 'forbidden', + isAdmin: false, + kind: UserKindModel.DEFAULT, + languageIsoCode: 'en-us', + lastLockoutDate: null, + lastLoginDate: '2023-10-12T18:30:32.879Z', + lastPasswordChangeDate: null, + mediaStartNodeIds: [], + name: 'Forbidden', + state: UserStateModel.ACTIVE, + updateDate: '2023-10-12T18:30:32.879Z', + userGroupIds: [{ id: 'user-group-editors-id' }, { id: 'user-group-sensitive-data-id' }], + userName: '', + }, ]; /** diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.db.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.db.ts index 7a4934357e..8e1d46acba 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.db.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.db.ts @@ -7,11 +7,13 @@ import type { UmbMockUserModel } from './user.data.js'; import { data, mfaLoginProviders } from './user.data.js'; import { UmbId } from '@umbraco-cms/backoffice/id'; import type { + CalculatedUserStartNodesResponseModel, CreateUserRequestModel, CurrentUserResponseModel, InviteUserRequestModel, PagedUserResponseModel, UpdateUserGroupsOnUserRequestModel, + UserConfigurationResponseModel, UserItemResponseModel, UserResponseModel, } from '@umbraco-cms/backoffice/external/backend-api'; @@ -43,6 +45,47 @@ class UmbUserMockDB extends UmbEntityMockDbBase { super(data); } + calculateStartNodes(id: string): CalculatedUserStartNodesResponseModel { + const user = this.data.find((user) => user.id === id); + if (!user) { + throw new Error(`User with id ${id} not found`); + } + + return { + id: user.id, + documentStartNodeIds: user.documentStartNodeIds, + mediaStartNodeIds: user.mediaStartNodeIds, + hasDocumentRootAccess: user.hasDocumentRootAccess, + hasMediaRootAccess: user.hasMediaRootAccess, + }; + } + + clientCredentials(id: string): Array { + const user = this.data.find((user) => user.id === id); + if (!user) { + throw new Error(`User with id ${id} not found`); + } + + // TODO: Implement logic to return client credentials for the user + return []; + } + + getConfiguration(): UserConfigurationResponseModel { + return { + allowChangePassword: true, + allowTwoFactor: true, + canInviteUsers: true, + passwordConfiguration: { + minimumPasswordLength: 8, + requireDigit: true, + requireLowercase: true, + requireUppercase: true, + requireNonLetterOrDigit: true, + }, + usernameIsEmail: true, + }; + } + /** * Set user groups * @param {UpdateUserGroupsOnUserRequestModel} data diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/utils/entity/entity-tree.manager.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/utils/entity/entity-tree.manager.ts index 61ebfea7a5..4d1fd70e7c 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/utils/entity/entity-tree.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/utils/entity/entity-tree.manager.ts @@ -12,7 +12,7 @@ export class UmbMockEntityTreeManager item.parent === null); + const items = this.#db.getAll().filter((item) => item.parent === null || item.parent === undefined); return this.#pagedTreeResult({ items, skip, take }); } diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/data-type/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/data-type/detail.handlers.ts index 025e33248c..75804751b8 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/data-type/detail.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/data-type/detail.handlers.ts @@ -26,6 +26,10 @@ export const detailHandlers = [ rest.get(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const response = umbDataTypeMockDb.detail.read(id); return res(ctx.status(200), ctx.json(response)); }), @@ -33,6 +37,10 @@ export const detailHandlers = [ rest.put(umbracoPath(`${UMB_SLUG}/:id`), async (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const requestBody = (await req.json()) as UpdateDataTypeRequestModel; if (!requestBody) return res(ctx.status(400, 'no body found')); umbDataTypeMockDb.detail.update(id, requestBody); @@ -42,6 +50,10 @@ export const detailHandlers = [ rest.delete(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } umbDataTypeMockDb.detail.delete(id); return res(ctx.status(200)); }), diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/dictionary/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/dictionary/detail.handlers.ts index fcff5fefd7..55e44dd1fc 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/dictionary/detail.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/dictionary/detail.handlers.ts @@ -32,6 +32,10 @@ export const detailHandlers = [ rest.get(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const response = umbDictionaryMockDb.detail.read(id); return res(ctx.status(200), ctx.json(response)); }), @@ -39,6 +43,10 @@ export const detailHandlers = [ rest.put(umbracoPath(`${UMB_SLUG}/:id`), async (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const requestBody = (await req.json()) as UpdateDictionaryItemRequestModel; if (!requestBody) return res(ctx.status(400, 'no body found')); umbDictionaryMockDb.detail.update(id, requestBody); @@ -48,6 +56,10 @@ export const detailHandlers = [ rest.delete(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } umbDictionaryMockDb.detail.delete(id); return res(ctx.status(200)); }), diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/dictionary/tree.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/dictionary/tree.handlers.ts index 6d79c28104..620e0f2723 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/dictionary/tree.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/dictionary/tree.handlers.ts @@ -19,4 +19,11 @@ export const treeHandlers = [ const response = umbDictionaryMockDb.tree.getChildrenOf({ parentId, skip, take }); return res(ctx.status(200), ctx.json(response)); }), + + rest.get(umbracoPath(`/tree${UMB_SLUG}/ancestors`), (req, res, ctx) => { + const descendantId = req.url.searchParams.get('descendantId'); + if (!descendantId) return; + const response = umbDictionaryMockDb.tree.getAncestorsOf({ descendantId }); + return res(ctx.status(200), ctx.json(response)); + }), ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/document-blueprint/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/document-blueprint/detail.handlers.ts index 80722ba2a8..f9ed905395 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/document-blueprint/detail.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/document-blueprint/detail.handlers.ts @@ -26,6 +26,10 @@ export const detailHandlers = [ rest.get(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const response = umbDocumentBlueprintMockDb.detail.read(id); return res(ctx.status(200), ctx.json(response)); }), @@ -33,6 +37,10 @@ export const detailHandlers = [ rest.put(umbracoPath(`${UMB_SLUG}/:id`), async (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const requestBody = (await req.json()) as UpdateDocumentRequestModel; if (!requestBody) return res(ctx.status(400, 'no body found')); umbDocumentBlueprintMockDb.detail.update(id, requestBody); @@ -42,6 +50,10 @@ export const detailHandlers = [ rest.delete(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } umbDocumentBlueprintMockDb.detail.delete(id); return res(ctx.status(200)); }), diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/document-type/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/document-type/detail.handlers.ts index 2c9237cebd..6ecfb5ffac 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/document-type/detail.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/document-type/detail.handlers.ts @@ -1,8 +1,10 @@ const { rest } = window.MockServiceWorker; +import { umbDocumentBlueprintMockDb } from '../../data/document-blueprint/document-blueprint.db.js'; import { umbDocumentTypeMockDb } from '../../data/document-type/document-type.db.js'; import { UMB_SLUG } from './slug.js'; import type { CreateMediaTypeRequestModel, + PagedDocumentTypeBlueprintItemResponseModel, UpdateMediaTypeRequestModel, } from '@umbraco-cms/backoffice/external/backend-api'; import { umbracoPath } from '@umbraco-cms/backoffice/utils'; @@ -26,13 +28,28 @@ export const detailHandlers = [ rest.get(umbracoPath(`${UMB_SLUG}/:id/blueprint`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } - return res(ctx.status(404)); + const relevantBlueprints = umbDocumentBlueprintMockDb + .getAll() + .filter((blueprint) => blueprint.documentType.id === id); + const response: PagedDocumentTypeBlueprintItemResponseModel = { + total: relevantBlueprints.length, + items: relevantBlueprints, + }; + return res(ctx.status(200), ctx.json(response)); }), rest.get(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const response = umbDocumentTypeMockDb.detail.read(id); return res(ctx.status(200), ctx.json(response)); }), @@ -40,6 +57,10 @@ export const detailHandlers = [ rest.put(umbracoPath(`${UMB_SLUG}/:id`), async (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const requestBody = (await req.json()) as UpdateMediaTypeRequestModel; if (!requestBody) return res(ctx.status(400, 'no body found')); umbDocumentTypeMockDb.detail.update(id, requestBody); @@ -49,6 +70,10 @@ export const detailHandlers = [ rest.delete(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } umbDocumentTypeMockDb.detail.delete(id); return res(ctx.status(200)); }), diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/document-type/tree.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/document-type/tree.handlers.ts index 9f8a2297ce..80a905469d 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/document-type/tree.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/document-type/tree.handlers.ts @@ -19,4 +19,11 @@ export const treeHandlers = [ const response = umbDocumentTypeMockDb.tree.getChildrenOf({ parentId, skip, take }); return res(ctx.status(200), ctx.json(response)); }), + + rest.get(umbracoPath(`/tree${UMB_SLUG}/ancestors`), (req, res, ctx) => { + const descendantId = req.url.searchParams.get('descendantId'); + if (!descendantId) return; + const response = umbDocumentTypeMockDb.tree.getAncestorsOf({ descendantId }); + return res(ctx.status(200), ctx.json(response)); + }), ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/document/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/document/detail.handlers.ts index 2831e3c2e0..c1a3563664 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/document/detail.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/document/detail.handlers.ts @@ -34,6 +34,10 @@ export const detailHandlers = [ rest.get(umbracoPath(`${UMB_SLUG}/:id/referenced-by`), (_req, res, ctx) => { const id = _req.params.id as string; if (!id) return; + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const query = _req.url.searchParams; const skip = query.get('skip') ? parseInt(query.get('skip') as string, 10) : 0; @@ -56,6 +60,10 @@ export const detailHandlers = [ rest.get(umbracoPath(`${UMB_SLUG}/:id/referenced-descendants`), (_req, res, ctx) => { const id = _req.params.id as string; if (!id) return; + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const ReferencedDescendantsResponse: GetDocumentByIdReferencedDescendantsResponse = { total: 0, @@ -68,6 +76,10 @@ export const detailHandlers = [ rest.put(umbracoPath(`${UMB_SLUG}/:id/validate`, 'v1.1'), (_req, res, ctx) => { const id = _req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } return res(ctx.status(200)); }), @@ -75,6 +87,10 @@ export const detailHandlers = [ rest.get(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const response = umbDocumentMockDb.detail.read(id); return res(ctx.status(200), ctx.json(response)); }), @@ -82,6 +98,10 @@ export const detailHandlers = [ rest.put(umbracoPath(`${UMB_SLUG}/:id`), async (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const requestBody = (await req.json()) as UpdateDocumentRequestModel; if (!requestBody) return res(ctx.status(400, 'no body found')); umbDocumentMockDb.detail.update(id, requestBody); @@ -91,6 +111,10 @@ export const detailHandlers = [ rest.delete(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } umbDocumentMockDb.detail.delete(id); return res(ctx.status(200)); }), diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/language/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/language/detail.handlers.ts index 667081e36c..bd5d474db4 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/language/detail.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/language/detail.handlers.ts @@ -37,6 +37,10 @@ export const detailHandlers = [ rest.get(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const response = umbLanguageMockDb.detail.read(id); return res(ctx.status(200), ctx.json(response)); }), @@ -44,6 +48,10 @@ export const detailHandlers = [ rest.put(umbracoPath(`${UMB_SLUG}/:id`), async (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const requestBody = (await req.json()) as UpdateLanguageRequestModel; if (!requestBody) return res(ctx.status(400, 'no body found')); umbLanguageMockDb.detail.update(id, requestBody); @@ -53,6 +61,10 @@ export const detailHandlers = [ rest.delete(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } umbLanguageMockDb.detail.delete(id); return res(ctx.status(200)); }), diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/media-type/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/media-type/detail.handlers.ts index ab46534549..2e2de688e3 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/media-type/detail.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/media-type/detail.handlers.ts @@ -26,6 +26,10 @@ export const detailHandlers = [ rest.get(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const response = umbMediaTypeMockDb.detail.read(id); return res(ctx.status(200), ctx.json(response)); }), @@ -33,6 +37,10 @@ export const detailHandlers = [ rest.put(umbracoPath(`${UMB_SLUG}/:id`), async (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const requestBody = (await req.json()) as UpdateMediaTypeRequestModel; if (!requestBody) return res(ctx.status(400, 'no body found')); umbMediaTypeMockDb.detail.update(id, requestBody); @@ -42,6 +50,10 @@ export const detailHandlers = [ rest.delete(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } umbMediaTypeMockDb.detail.delete(id); return res(ctx.status(200)); }), diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/media/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/media/detail.handlers.ts index fe52ac9a25..20a53376fe 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/media/detail.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/media/detail.handlers.ts @@ -29,6 +29,10 @@ export const detailHandlers = [ rest.get(umbracoPath(`${UMB_SLUG}/:id/referenced-by`), (_req, res, ctx) => { const id = _req.params.id as string; if (!id) return; + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const PagedTrackedReference = { total: referenceData.length, @@ -41,6 +45,10 @@ export const detailHandlers = [ rest.get(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const response = umbMediaMockDb.detail.read(id); return res(ctx.status(200), ctx.json(response)); }), @@ -48,6 +56,10 @@ export const detailHandlers = [ rest.put(umbracoPath(`${UMB_SLUG}/:id/validate`), async (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const model = await req.json(); if (!model) return res(ctx.status(400)); @@ -65,6 +77,10 @@ export const detailHandlers = [ rest.put(umbracoPath(`${UMB_SLUG}/:id`), async (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const requestBody = (await req.json()) as UpdateMediaRequestModel; if (!requestBody) return res(ctx.status(400, 'no body found')); umbMediaMockDb.detail.update(id, requestBody); @@ -74,6 +90,10 @@ export const detailHandlers = [ rest.delete(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } umbMediaMockDb.detail.delete(id); return res(ctx.status(200)); }), diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/member-group/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/member-group/detail.handlers.ts index f59ed732e3..8bf3d1e4f2 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/member-group/detail.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/member-group/detail.handlers.ts @@ -19,9 +19,24 @@ export const detailHandlers = [ ); }), + rest.get(umbracoPath(`${UMB_SLUG}`), (req, res, ctx) => { + const skipParam = req.url.searchParams.get('skip'); + const skip = skipParam ? Number.parseInt(skipParam) : undefined; + const takeParam = req.url.searchParams.get('take'); + const take = takeParam ? Number.parseInt(takeParam) : undefined; + + const response = umbMemberGroupMockDb.get({ skip, take }); + + return res(ctx.status(200), ctx.json(response)); + }), + rest.get(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const response = umbMemberGroupMockDb.detail.read(id); return res(ctx.status(200), ctx.json(response)); }), @@ -29,6 +44,10 @@ export const detailHandlers = [ rest.put(umbracoPath(`${UMB_SLUG}/:id`), async (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const requestBody = (await req.json()) as any; if (!requestBody) return res(ctx.status(400, 'no body found')); umbMemberGroupMockDb.detail.update(id, requestBody); @@ -38,6 +57,10 @@ export const detailHandlers = [ rest.delete(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } umbMemberGroupMockDb.detail.delete(id); return res(ctx.status(200)); }), diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/member-type/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/member-type/detail.handlers.ts index 01a23c5519..4e1978f99a 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/member-type/detail.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/member-type/detail.handlers.ts @@ -26,6 +26,10 @@ export const detailHandlers = [ rest.get(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const response = umbMemberTypeMockDb.detail.read(id); return res(ctx.status(200), ctx.json(response)); }), @@ -33,6 +37,10 @@ export const detailHandlers = [ rest.put(umbracoPath(`${UMB_SLUG}/:id`), async (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const requestBody = (await req.json()) as UpdateMemberTypeRequestModel; if (!requestBody) return res(ctx.status(400, 'no body found')); umbMemberTypeMockDb.detail.update(id, requestBody); @@ -42,6 +50,10 @@ export const detailHandlers = [ rest.delete(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } umbMemberTypeMockDb.detail.delete(id); return res(ctx.status(200)); }), diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/member-type/tree.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/member-type/tree.handlers.ts index 0aef1a0996..ab08a7bde2 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/member-type/tree.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/member-type/tree.handlers.ts @@ -5,8 +5,8 @@ import { umbracoPath } from '@umbraco-cms/backoffice/utils'; export const treeHandlers = [ rest.get(umbracoPath(`/tree${UMB_SLUG}/root`), (req, res, ctx) => { - const skip = Number(req.url.searchParams.get('skip')); - const take = Number(req.url.searchParams.get('take')); + const skip = Number(req.url.searchParams.get('skip') ?? '0'); + const take = Number(req.url.searchParams.get('take') ?? '100'); const response = umbMemberTypeMockDb.tree.getRoot({ skip, take }); return res(ctx.status(200), ctx.json(response)); }), @@ -14,8 +14,8 @@ export const treeHandlers = [ rest.get(umbracoPath(`/tree${UMB_SLUG}/children`), (req, res, ctx) => { const parentId = req.url.searchParams.get('parentId'); if (!parentId) return; - const skip = Number(req.url.searchParams.get('skip')); - const take = Number(req.url.searchParams.get('take')); + const skip = Number(req.url.searchParams.get('skip') ?? '0'); + const take = Number(req.url.searchParams.get('take') ?? '100'); const response = umbMemberTypeMockDb.tree.getChildrenOf({ parentId, skip, take }); return res(ctx.status(200), ctx.json(response)); }), diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/member/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/member/detail.handlers.ts index 1321d13d38..c25a194ad1 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/member/detail.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/member/detail.handlers.ts @@ -23,6 +23,10 @@ export const detailHandlers = [ rest.get(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const response = umbMemberMockDb.detail.read(id); return res(ctx.status(200), ctx.json(response)); }), @@ -30,6 +34,10 @@ export const detailHandlers = [ rest.put(umbracoPath(`${UMB_SLUG}/:id`), async (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const requestBody = (await req.json()) as UpdateMemberRequestModel; if (!requestBody) return res(ctx.status(400, 'no body found')); umbMemberMockDb.detail.update(id, requestBody); @@ -39,6 +47,10 @@ export const detailHandlers = [ rest.delete(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } umbMemberMockDb.detail.delete(id); return res(ctx.status(200)); }), diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/member/filter.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/member/filter.handlers.ts new file mode 100644 index 0000000000..3047ca203f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/member/filter.handlers.ts @@ -0,0 +1,29 @@ +const { rest } = window.MockServiceWorker; +import { umbMemberMockDb } from '../../data/member/member.db.js'; +import { UMB_SLUG } from './slug.js'; +import { umbracoPath } from '@umbraco-cms/backoffice/utils'; + +export const handlers = [ + rest.get(umbracoPath(`/filter${UMB_SLUG}`), (req, res, ctx) => { + const skip = Number(req.url.searchParams.get('skip')); + const take = Number(req.url.searchParams.get('take')); + const orderBy = req.url.searchParams.get('orderBy'); + const orderDirection = req.url.searchParams.get('orderDirection'); + const memberGroupIds = req.url.searchParams.getAll('memberGroupIds'); + const memberTypeId = req.url.searchParams.get('memberTypeId'); + const filter = req.url.searchParams.get('filter'); + + const options: any = { + skip: skip || undefined, + take: take || undefined, + orderBy: orderBy || undefined, + orderDirection: orderDirection || undefined, + memberGroupIds: memberGroupIds.length > 0 ? memberGroupIds : undefined, + memberTypeId: memberTypeId || undefined, + filter: filter || undefined, + }; + + const response = umbMemberMockDb.filter(options); + return res(ctx.status(200), ctx.json(response)); + }), +]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/member/index.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/member/index.ts index 1326eb078e..032773439b 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/member/index.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/member/index.ts @@ -1,5 +1,6 @@ import { detailHandlers } from './detail.handlers.js'; import { itemHandlers } from './item.handlers.js'; import { collectionHandlers } from './collection.handlers.js'; +import { handlers as filterHandlers } from './filter.handlers.js'; -export const handlers = [...itemHandlers, ...collectionHandlers, ...detailHandlers]; +export const handlers = [...itemHandlers, ...collectionHandlers, ...detailHandlers, ...filterHandlers]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/partial-view/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/partial-view/detail.handlers.ts index 52fe83cd43..4d06d535b5 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/partial-view/detail.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/partial-view/detail.handlers.ts @@ -35,6 +35,10 @@ export const detailHandlers = [ rest.get(umbracoPath(`${UMB_SLUG}/:path`), (req, res, ctx) => { const path = req.params.path as string; if (!path) return res(ctx.status(400)); + if (path.endsWith('forbidden')) { + // Simulate a forbidden response + return res(ctx.status(403)); + } const response = umbPartialViewMockDB.file.read(decodeURIComponent(path)); return res(ctx.status(200), ctx.json(response)); }), @@ -42,6 +46,10 @@ export const detailHandlers = [ rest.delete(umbracoPath(`${UMB_SLUG}/:path`), (req, res, ctx) => { const path = req.params.path as string; if (!path) return res(ctx.status(400)); + if (path.endsWith('forbidden')) { + // Simulate a forbidden response + return res(ctx.status(403)); + } umbPartialViewMockDB.file.delete(decodeURIComponent(path)); return res(ctx.status(200)); }), @@ -49,6 +57,10 @@ export const detailHandlers = [ rest.put(umbracoPath(`${UMB_SLUG}/:path`), async (req, res, ctx) => { const path = req.params.path as string; if (!path) return res(ctx.status(400)); + if (path.endsWith('forbidden')) { + // Simulate a forbidden response + return res(ctx.status(403)); + } const requestBody = (await req.json()) as UpdatePartialViewRequestModel; if (!requestBody) return res(ctx.status(400, 'no body found')); umbPartialViewMockDB.file.update(decodeURIComponent(path), requestBody); diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/relation-type/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/relation-type/detail.handlers.ts index 118d391ed0..279b973f58 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/relation-type/detail.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/relation-type/detail.handlers.ts @@ -22,6 +22,10 @@ export const detailHandlers = [ rest.get(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const response = umbRelationTypeMockDb.detail.read(id); return res(ctx.status(200), ctx.json(response)); }), diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/script/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/script/detail.handlers.ts index 03e6ab6c0e..f85e6c0800 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/script/detail.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/script/detail.handlers.ts @@ -32,6 +32,10 @@ export const detailHandlers = [ rest.get(umbracoPath(`${UMB_SLUG}/:path`), (req, res, ctx) => { const path = req.params.path as string; if (!path) return res(ctx.status(400)); + if (path.endsWith('forbidden')) { + // Simulate a forbidden response + return res(ctx.status(403)); + } const response = umbScriptMockDb.file.read(decodeURIComponent(path)); return res(ctx.status(200), ctx.json(response)); }), @@ -39,6 +43,10 @@ export const detailHandlers = [ rest.delete(umbracoPath(`${UMB_SLUG}/:path`), (req, res, ctx) => { const path = req.params.path as string; if (!path) return res(ctx.status(400)); + if (path.endsWith('forbidden')) { + // Simulate a forbidden response + return res(ctx.status(403)); + } umbScriptMockDb.file.delete(decodeURIComponent(path)); return res(ctx.status(200)); }), @@ -46,6 +54,10 @@ export const detailHandlers = [ rest.put(umbracoPath(`${UMB_SLUG}/:path`), async (req, res, ctx) => { const path = req.params.path as string; if (!path) return res(ctx.status(400)); + if (path.endsWith('forbidden')) { + // Simulate a forbidden response + return res(ctx.status(403)); + } const requestBody = (await req.json()) as UpdateScriptRequestModel; if (!requestBody) return res(ctx.status(400, 'no body found')); umbScriptMockDb.file.update(decodeURIComponent(path), requestBody); diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/segment.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/segment.handlers.ts new file mode 100644 index 0000000000..2eeca2a961 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/segment.handlers.ts @@ -0,0 +1,17 @@ +const { rest } = window.MockServiceWorker; + +import { umbracoPath } from '@umbraco-cms/backoffice/utils'; +import type { PagedSegmentResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; + +export const handlers = [ + rest.get(umbracoPath('/segment'), (_req, res, ctx) => { + return res( + // Respond with a 200 status code + ctx.status(200), + ctx.json({ + total: 0, + items: [], + }), + ); + }), +]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/stylesheet/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/stylesheet/detail.handlers.ts index 4d59dffd1b..efcb3d5a28 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/stylesheet/detail.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/stylesheet/detail.handlers.ts @@ -35,6 +35,10 @@ export const detailHandlers = [ rest.get(umbracoPath(`${UMB_SLUG}/:path`), (req, res, ctx) => { const path = req.params.path as string; if (!path) return res(ctx.status(400)); + if (path.endsWith('forbidden')) { + // Simulate a forbidden response + return res(ctx.status(403)); + } const response = umbStylesheetMockDb.file.read(decodeURIComponent(path)); return res(ctx.status(200), ctx.json(response)); }), @@ -42,6 +46,10 @@ export const detailHandlers = [ rest.delete(umbracoPath(`${UMB_SLUG}/:path`), (req, res, ctx) => { const path = req.params.path as string; if (!path) return res(ctx.status(400)); + if (path.endsWith('forbidden')) { + // Simulate a forbidden response + return res(ctx.status(403)); + } umbStylesheetMockDb.file.delete(decodeURIComponent(path)); return res(ctx.status(200)); }), @@ -49,6 +57,10 @@ export const detailHandlers = [ rest.put(umbracoPath(`${UMB_SLUG}/:path`), async (req, res, ctx) => { const path = req.params.path as string; if (!path) return res(ctx.status(400)); + if (path.endsWith('forbidden')) { + // Simulate a forbidden response + return res(ctx.status(403)); + } const requestBody = (await req.json()) as UpdateStylesheetRequestModel; if (!requestBody) return res(ctx.status(400, 'no body found')); umbStylesheetMockDb.file.update(decodeURIComponent(path), requestBody); diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/template/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/template/detail.handlers.ts index 732271025f..134b4fbf99 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/template/detail.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/template/detail.handlers.ts @@ -40,6 +40,10 @@ export const detailHandlers = [ rest.get(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const response = umbTemplateMockDb.detail.read(id); return res(ctx.status(200), ctx.json(response)); }), @@ -47,6 +51,10 @@ export const detailHandlers = [ rest.put(umbracoPath(`${UMB_SLUG}/:id`), async (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const requestBody = (await req.json()) as UpdateTemplateRequestModel; if (!requestBody) return res(ctx.status(400, 'no body found')); @@ -65,6 +73,10 @@ export const detailHandlers = [ rest.delete(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } umbTemplateMockDb.detail.delete(id); return res(ctx.status(200)); }), diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/user-group/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/user-group/detail.handlers.ts index 181fd2cbfc..64e2db87a5 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/user-group/detail.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/user-group/detail.handlers.ts @@ -36,6 +36,10 @@ export const detailHandlers = [ rest.get(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const response = umbUserGroupMockDb.detail.read(id); return res(ctx.status(200), ctx.json(response)); }), @@ -43,6 +47,10 @@ export const detailHandlers = [ rest.put(umbracoPath(`${UMB_SLUG}/:id`), async (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const requestBody = (await req.json()) as UpdateUserGroupRequestModel; if (!requestBody) return res(ctx.status(400, 'no body found')); umbUserGroupMockDb.detail.update(id, requestBody); @@ -52,6 +60,10 @@ export const detailHandlers = [ rest.delete(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } umbUserGroupMockDb.detail.delete(id); return res(ctx.status(200)); }), diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/user/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/user/detail.handlers.ts index 675f37a0b3..c9f4bfa4a6 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/user/detail.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/user/detail.handlers.ts @@ -20,9 +20,37 @@ export const detailHandlers = [ ); }), + rest.get(umbracoPath(`${UMB_SLUG}/configuration`), (_req, res, ctx) => { + return res(ctx.status(200), ctx.json(umbUserMockDb.getConfiguration())); + }), + + rest.get(umbracoPath(`${UMB_SLUG}/:id/calculate-start-nodes`), (req, res, ctx) => { + const id = req.params.id as string; + if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } + return res(ctx.status(200), ctx.json(umbUserMockDb.calculateStartNodes(id))); + }), + + rest.get(umbracoPath(`${UMB_SLUG}/:id/client-credentials`), (req, res, ctx) => { + const id = req.params.id as string; + if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } + return res(ctx.status(200), ctx.json(umbUserMockDb.clientCredentials(id))); + }), + rest.get(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const response = umbUserMockDb.detail.read(id); return res(ctx.status(200), ctx.json(response)); }), @@ -30,6 +58,10 @@ export const detailHandlers = [ rest.put(umbracoPath(`${UMB_SLUG}/:id`), async (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } const requestBody = (await req.json()) as UpdateUserRequestModel; if (!requestBody) return res(ctx.status(400, 'no body found')); umbUserMockDb.detail.update(id, requestBody); @@ -39,6 +71,10 @@ export const detailHandlers = [ rest.delete(umbracoPath(`${UMB_SLUG}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); + if (id === 'forbidden') { + // Simulate a forbidden response + return res(ctx.status(403)); + } umbUserMockDb.detail.delete(id); return res(ctx.status(200)); }), diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/try-execute.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/try-execute.controller.ts index 2c5a356fe3..0ccf210fdc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/try-execute.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/try-execute.controller.ts @@ -49,15 +49,9 @@ export class UmbTryExecuteController extends UmbResourceController { // Check if we can extract problem details from the error if (apiError.problemDetails) { - if (apiError.problemDetails.status === 401) { - // Unauthorized error, show no notification - // the user will see a login screen instead - return; - } - - if (apiError.problemDetails.status === 404) { - // Not found error, show no notification - // the user will see a 404 page instead, or otherwise the UI will handle it + if ([400, 401, 403, 404].includes(apiError.problemDetails.status)) { + // Non-fatal errors that the UI can handle gracefully + // so we avoid showing a notification return; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/route/forbidden/route-forbidden.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/route/forbidden/route-forbidden.element.ts new file mode 100644 index 0000000000..bd1c461f25 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/route/forbidden/route-forbidden.element.ts @@ -0,0 +1,59 @@ +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; + +/** + * A component that displays a "Forbidden" message when a user tries to access a route they do not have permission for. + * This is typically used in routing scenarios where access control is enforced. + * It informs the user that they do not have the necessary permissions to view the requested resource. + * @element umb-route-forbidden + */ +@customElement('umb-route-forbidden') +export class UmbRouteForbiddenElement extends UmbLitElement { + override render() { + return html` +
+

Access denied

+ + You do not have permission to access this resource. Please contact your administrator for assistance. + +
+ `; + } + + static override styles = [ + UmbTextStyles, + css` + :host { + display: block; + width: 100%; + height: 100%; + min-width: 0; + } + + :host > div { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100%; + opacity: 0; + animation: fadeIn 2s 2s forwards; + } + + @keyframes fadeIn { + 100% { + opacity: 100%; + } + } + `, + ]; +} + +export default UmbRouteForbiddenElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-route-forbidden': UmbRouteForbiddenElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/route/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/route/index.ts index 1c30607927..4dd43b9748 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/router/route/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/route/index.ts @@ -1,3 +1,4 @@ +export * from './forbidden/route-forbidden.element.js'; export * from './not-found/route-not-found.element.js'; export * from './route.context.js'; export * from './router-slot-change.event.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts index a6a78e8f4a..30fbf2019e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts @@ -22,8 +22,10 @@ import type { import { UmbDeprecation, UmbStateManager } from '@umbraco-cms/backoffice/utils'; import { UmbValidationContext } from '@umbraco-cms/backoffice/validation'; import { UmbId } from '@umbraco-cms/backoffice/id'; +import { UmbApiError } from '@umbraco-cms/backoffice/resources'; const LOADING_STATE_UNIQUE = 'umbLoadingEntityDetail'; +const FORBIDDEN_STATE_UNIQUE = 'umbForbiddenEntityDetail'; export abstract class UmbEntityDetailWorkspaceContextBase< DetailModelType extends UmbEntityModel = UmbEntityModel, @@ -51,6 +53,7 @@ export abstract class UmbEntityDetailWorkspaceContextBase< public readonly data = this._data.current; public readonly persistedData = this._data.persisted; public readonly loading = new UmbStateManager(this); + public readonly forbidden = new UmbStateManager(this); protected _getDataPromise?: Promise< UmbRepositoryResponse | UmbRepositoryResponseWithAsObservable @@ -234,18 +237,21 @@ export abstract class UmbEntityDetailWorkspaceContextBase< this.loading.addState({ unique: LOADING_STATE_UNIQUE, message: `Loading ${this.getEntityType()} Details` }); await this.#init; this._getDataPromise = this._detailRepository!.requestByUnique(unique); - const response = await this._getDataPromise; - const data = response.data; + const response = (await this._getDataPromise) as UmbRepositoryResponseWithAsObservable; + const { data, error, asObservable } = response; - if (data) { + if (error) { + this.removeUmbControllerByAlias('umbEntityDetailTypeStoreObserver'); + if (UmbApiError.isUmbApiError(error)) { + if (error.status === 401 || error.status === 403) { + this.forbidden.addState({ unique: FORBIDDEN_STATE_UNIQUE, message: error.message }); + } + } + } else if (data) { this._data.setPersisted(data); this._data.setCurrent(data); - this.observe( - (response as UmbRepositoryResponseWithAsObservable).asObservable?.(), - (entity) => this.#onDetailStoreChange(entity), - 'umbEntityDetailTypeStoreObserver', - ); + this.observe(asObservable?.(), (entity) => this.#onDetailStoreChange(entity), 'umbEntityDetailTypeStoreObserver'); } this.loading.removeState(LOADING_STATE_UNIQUE); @@ -461,6 +467,7 @@ export abstract class UmbEntityDetailWorkspaceContextBase< override resetState() { super.resetState(); this.loading.clear(); + this.forbidden.clear(); this._data.clear(); this.#allowNavigateAway = false; this._getDataPromise = undefined; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/global-components/entity-detail-forbidden.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/global-components/entity-detail-forbidden.element.ts new file mode 100644 index 0000000000..841fd6b22d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/global-components/entity-detail-forbidden.element.ts @@ -0,0 +1,50 @@ +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; + +@customElement('umb-entity-detail-forbidden') +export class UmbEntityDetailForbiddenElement extends UmbLitElement { + @property({ type: String, attribute: 'entity-type' }) + entityType = ''; + + override render() { + return html` +
+

${this.localize.term('entityDetail_forbiddenTitle', this.entityType)}

+ ${this.localize.term('entityDetail_forbiddenDescription', this.entityType)} +
+ `; + } + + static override styles = [ + UmbTextStyles, + css` + :host { + display: block; + width: 100%; + height: 100%; + min-width: 0; + } + + :host > div { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100%; + } + + @keyframes fadeIn { + 100% { + opacity: 100%; + } + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-entity-detail-forbidden': UmbEntityDetailForbiddenElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/global-components/entity-detail-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/global-components/entity-detail-workspace-editor.element.ts index e98b9f46b4..482bfd8e58 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/global-components/entity-detail-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/global-components/entity-detail-workspace-editor.element.ts @@ -13,6 +13,9 @@ export class UmbEntityDetailWorkspaceEditorElement extends UmbLitElement { @state() private _isLoading = false; + @state() + private _isForbidden = false; + @state() private _exists = false; @@ -28,15 +31,30 @@ export class UmbEntityDetailWorkspaceEditorElement extends UmbLitElement { this.#context = context; this.observe(this.#context?.entityType, (entityType) => (this._entityType = entityType)); this.observe(this.#context?.loading.isOn, (isLoading) => (this._isLoading = isLoading ?? false)); + this.observe(this.#context?.forbidden.isOn, (isForbidden) => (this._isForbidden = isForbidden ?? false)); this.observe(this.#context?.data, (data) => (this._exists = !!data)); this.observe(this.#context?.isNew, (isNew) => (this._isNew = isNew)); }); } + #renderForbidden() { + if (!this._isLoading && this._isForbidden) { + return html``; + } + return nothing; + } + + #renderNotFound() { + if (!this._isLoading && !this._exists) { + return html``; + } + return nothing; + } + protected override render() { - return html` ${!this._exists && !this._isLoading - ? html`` - : nothing} + return html` ${this.#renderForbidden()} ${this.#renderNotFound()} + + + CP0002 + M:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services.ContentEditingServiceTests.Updating_Single_Variant_Name_Does_Not_Change_Update_Dates_Of_Other_Vaiants + lib/net9.0/Umbraco.Tests.Integration.dll + lib/net9.0/Umbraco.Tests.Integration.dll + true + + \ No newline at end of file From 14063a0b89cc05edc4042dfb19184a11bbdf4932 Mon Sep 17 00:00:00 2001 From: Peter <45105665+PeterKvayt@users.noreply.github.com> Date: Mon, 30 Jun 2025 14:21:10 +0300 Subject: [PATCH 47/82] Add support for file upload property editor within the block list and grid (#18976) * Fix for https://github.com/umbraco/Umbraco-CMS/issues/18872 * Parsing added for current value * Build fix. * Cyclomatic complexity fix * Resolved breaking change. * Pass content key. * Simplified collections. * Added unit tests to verify behaviour. * Allow file upload on block list. * Added unit test verifying added property. * Added unit test verifying removed property. * Restored null return for null value fixing failing integration tests. * Logic has been updated according edge cases * Logic to copy files from block list items has been added. * Logic to delete files from block list items on content deletion has been added * Test fix. * Refactoring. * WIP: Resolved breaking changes, minor refactoring. * Consistently return null over empty, resolving failure in integration test. * Removed unnecessary code nesting. * Handle distinct paths. * Handles clean up of files added via file upload in rich text blocks on delete of the content. * Update src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs Co-authored-by: Sven Geusens * Fixed build of integration tests project. * Handled delete of file uploads when deleting a block from an RTE using a file upload property. * Refactored ensure of property type property populated on rich text values to a common helper extension method. * Fixed integration tests build. * Handle create of new file from file upload block in an RTE when the document is copied. * Fixed failing integration tests. * Refactored notification handlers relating to file uploads into separate classes. * Handle nested rich text editor block with file upload when copying content. * Handle nested rich text editor block with file upload when deleting content. * Minor refactor. * Integration test compatibility supressions. --------- Co-authored-by: Andy Butland Co-authored-by: Sven Geusens --- .../UmbracoBuilder.CoreServices.cs | 11 +- .../RichTextEditorValueExtensions.cs | 38 +++ .../BlockEditorPropertyValueEditor.cs | 70 ++-- .../BlockEditorValidatorBase.cs | 10 +- .../BlockValuePropertyValueEditorBase.cs | 145 +++++--- .../FileUploadPropertyEditor.cs | 170 +++------- .../FileUploadPropertyValueEditor.cs | 42 +-- .../PropertyEditors/FileUploadValueParser.cs | 45 +++ ...eUploadContentCopiedNotificationHandler.cs | 312 ++++++++++++++++++ ...UploadContentDeletedNotificationHandler.cs | 29 ++ ...oadEntityDeletedNotificationHandlerBase.cs | 218 ++++++++++++ ...leUploadMediaDeletedNotificationHandler.cs | 29 ++ ...ileUploadMediaSavingNotificationHandler.cs | 77 +++++ ...eUploadMemberDeletedNotificationHandler.cs | 29 ++ .../FileUploadNotificationHandlerBase.cs | 140 ++++++++ .../PropertyEditors/RichTextPropertyEditor.cs | 53 ++- .../components/unsupported-property/utils.ts | 2 +- .../Services/EntityXmlSerializerTests.cs | 2 + ...BlockListEditorPropertyValueEditorTests.cs | 245 ++++++++++++-- 19 files changed, 1397 insertions(+), 270 deletions(-) create mode 100644 src/Umbraco.Infrastructure/Extensions/RichTextEditorValueExtensions.cs create mode 100644 src/Umbraco.Infrastructure/PropertyEditors/FileUploadValueParser.cs create mode 100644 src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedNotificationHandler.cs create mode 100644 src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentDeletedNotificationHandler.cs create mode 100644 src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadEntityDeletedNotificationHandlerBase.cs create mode 100644 src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMediaDeletedNotificationHandler.cs create mode 100644 src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMediaSavingNotificationHandler.cs create mode 100644 src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMemberDeletedNotificationHandler.cs create mode 100644 src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadNotificationHandlerBase.cs diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 56fdface93..d0c68d5aa5 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -52,6 +52,7 @@ using Umbraco.Cms.Infrastructure.Migrations; using Umbraco.Cms.Infrastructure.Migrations.Install; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Mappers; +using Umbraco.Cms.Infrastructure.PropertyEditors.NotificationHandlers; using Umbraco.Cms.Infrastructure.Routing; using Umbraco.Cms.Infrastructure.Runtime; using Umbraco.Cms.Infrastructure.Runtime.RuntimeModeValidators; @@ -355,11 +356,11 @@ public static partial class UmbracoBuilderExtensions .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() - .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler() diff --git a/src/Umbraco.Infrastructure/Extensions/RichTextEditorValueExtensions.cs b/src/Umbraco.Infrastructure/Extensions/RichTextEditorValueExtensions.cs new file mode 100644 index 0000000000..3cc16b6c21 --- /dev/null +++ b/src/Umbraco.Infrastructure/Extensions/RichTextEditorValueExtensions.cs @@ -0,0 +1,38 @@ +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache.PropertyEditors; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Blocks; + +namespace Umbraco.Cms.Infrastructure.Extensions; + +/// +/// Defines extensions on . +/// +internal static class RichTextEditorValueExtensions +{ + /// + /// Ensures that the property type property is populated on all blocks. + /// + /// The providing the blocks. + /// Cache for element types. + public static void EnsurePropertyTypePopulatedOnBlocks(this RichTextEditorValue richTextEditorValue, IBlockEditorElementTypeCache elementTypeCache) + { + Guid[] elementTypeKeys = (richTextEditorValue.Blocks?.ContentData ?? []) + .Select(x => x.ContentTypeKey) + .Union((richTextEditorValue.Blocks?.SettingsData ?? []) + .Select(x => x.ContentTypeKey)) + .Distinct() + .ToArray(); + + IEnumerable elementTypes = elementTypeCache.GetMany(elementTypeKeys); + + foreach (BlockItemData dataItem in (richTextEditorValue.Blocks?.ContentData ?? []) + .Union(richTextEditorValue.Blocks?.SettingsData ?? [])) + { + foreach (BlockPropertyValue item in dataItem.Values) + { + item.PropertyType = elementTypes.FirstOrDefault(x => x.Key == dataItem.ContentTypeKey)?.PropertyTypes.FirstOrDefault(pt => pt.Alias == item.Alias); + } + } + } +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs index a129dce83f..21359ec1c0 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs @@ -1,10 +1,8 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; +using System.Diagnostics.CodeAnalysis; using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; @@ -16,10 +14,16 @@ using Umbraco.Cms.Core.Strings; namespace Umbraco.Cms.Core.PropertyEditors; +/// +/// Provides an abstract base class for property value editors based on block editors. +/// public abstract class BlockEditorPropertyValueEditor : BlockValuePropertyValueEditorBase where TValue : BlockValue, new() where TLayout : class, IBlockLayoutItem, new() { + /// + /// Initializes a new instance of the class. + /// protected BlockEditorPropertyValueEditor( PropertyEditorCollection propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories, @@ -62,13 +66,7 @@ public abstract class BlockEditorPropertyValueEditor : BlockVal return BlockEditorValues.DeserializeAndClean(rawJson)?.BlockValue; } - /// - /// Ensure that sub-editor values are translated through their ToEditor methods - /// - /// - /// - /// - /// + /// public override object ToEditor(IProperty property, string? culture = null, string? segment = null) { var val = property.GetValue(culture, segment); @@ -95,38 +93,48 @@ public abstract class BlockEditorPropertyValueEditor : BlockVal return blockEditorData.BlockValue; } - /// - /// Ensure that sub-editor values are translated through their FromEditor methods - /// - /// - /// - /// + /// public override object? FromEditor(ContentPropertyData editorValue, object? currentValue) { - if (editorValue.Value == null || string.IsNullOrWhiteSpace(editorValue.Value.ToString())) + // Note: we can't early return here if editorValue is null or empty, because these is the following case: + // - current value not null (which means doc has at least one element in block list) + // - editor value (new value) is null (which means doc has no elements in block list) + // If we check editor value for null value and return before MapBlockValueFromEditor, then we will not trigger updates for properties. + // For most of the properties this is fine, but for properties which contain other state it might be critical (e.g. file upload field). + // So, we must run MapBlockValueFromEditor even if editorValue is null or string.IsNullOrWhiteSpace(editorValue.Value.ToString()) is true. + + BlockEditorData? currentBlockEditorData = GetBlockEditorData(currentValue); + BlockEditorData? blockEditorData = GetBlockEditorData(editorValue.Value); + + // We can skip MapBlockValueFromEditor if both editorValue and currentValue values are empty. + if (IsBlockEditorDataEmpty(currentBlockEditorData) && IsBlockEditorDataEmpty(blockEditorData)) { return null; } - BlockEditorData? blockEditorData; + MapBlockValueFromEditor(blockEditorData?.BlockValue, currentBlockEditorData?.BlockValue, editorValue.ContentKey); + + if (IsBlockEditorDataEmpty(blockEditorData)) + { + return null; + } + + return JsonSerializer.Serialize(blockEditorData.BlockValue); + } + + private BlockEditorData? GetBlockEditorData(object? value) + { try { - blockEditorData = BlockEditorValues.DeserializeAndClean(editorValue.Value); + return BlockEditorValues.DeserializeAndClean(value); } catch { - // if this occurs it means the data is invalid, shouldn't happen but has happened if we change the data format. - return string.Empty; + // If this occurs it means the data is invalid. It shouldn't happen could if we change the data format. + return null; } - - if (blockEditorData == null || blockEditorData.BlockValue.ContentData.Count == 0) - { - return string.Empty; - } - - MapBlockValueFromEditor(blockEditorData.BlockValue); - - // return json - return JsonSerializer.Serialize(blockEditorData.BlockValue); } + + private static bool IsBlockEditorDataEmpty([NotNullWhen(false)] BlockEditorData? editorData) + => editorData is null || editorData.BlockValue.ContentData.Count == 0; } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidatorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidatorBase.cs index 419b84845a..252834209f 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidatorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidatorBase.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Cache.PropertyEditors; +using Umbraco.Cms.Core.Cache.PropertyEditors; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Models.Validation; @@ -88,7 +88,13 @@ public abstract class BlockEditorValidatorBase : ComplexEditorV foreach (var group in itemDataGroups) { - var allElementTypes = _elementTypeCache.GetMany(group.Items.Select(x => x.ContentTypeKey).ToArray()).ToDictionary(x => x.Key); + Guid[] elementTypeKeys = group.Items.Select(x => x.ContentTypeKey).ToArray(); + if (elementTypeKeys.Length == 0) + { + continue; + } + + var allElementTypes = _elementTypeCache.GetMany(elementTypeKeys).ToDictionary(x => x.Key); for (var i = 0; i < group.Items.Length; i++) { diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs index 3777d84eb4..52c02c46bc 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs @@ -129,10 +129,115 @@ public abstract class BlockValuePropertyValueEditorBase : DataV return result; } - protected void MapBlockValueFromEditor(TValue blockValue) + [Obsolete("This method is no longer used within Umbraco. Please use the overload taking all parameters. Scheduled for removal in Umbraco 17.")] + protected void MapBlockValueFromEditor(TValue blockValue) => MapBlockValueFromEditor(blockValue, null, Guid.Empty); + + protected void MapBlockValueFromEditor(TValue? editedBlockValue, TValue? currentBlockValue, Guid contentKey) { - MapBlockItemDataFromEditor(blockValue.ContentData); - MapBlockItemDataFromEditor(blockValue.SettingsData); + MapBlockItemDataFromEditor( + editedBlockValue?.ContentData ?? [], + currentBlockValue?.ContentData ?? [], + contentKey); + + MapBlockItemDataFromEditor( + editedBlockValue?.SettingsData ?? [], + currentBlockValue?.SettingsData ?? [], + contentKey); + } + + private void MapBlockItemDataFromEditor(List editedItems, List currentItems, Guid contentKey) + { + // Create mapping between edited and current block items. + IEnumerable> itemsMapping = GetBlockStatesMapping(editedItems, currentItems, (mapping, current) => mapping.Edited?.Key == current.Key); + + foreach (BlockStateMapping itemMapping in itemsMapping) + { + // Create mapping between edited and current block item values. + IEnumerable> valuesMapping = GetBlockStatesMapping(itemMapping.Edited?.Values, itemMapping.Current?.Values, (mapping, current) => mapping.Edited?.Alias == current.Alias); + + foreach (BlockStateMapping valueMapping in valuesMapping) + { + BlockPropertyValue? editedValue = valueMapping.Edited; + BlockPropertyValue? currentValue = valueMapping.Current; + + IPropertyType propertyType = editedValue?.PropertyType + ?? currentValue?.PropertyType + ?? throw new ArgumentException("One or more block properties did not have a resolved property type. Block editor values must be resolved before attempting to map them from editor.", nameof(editedItems)); + + // Lookup the property editor. + IDataEditor? propertyEditor = _propertyEditors[propertyType.PropertyEditorAlias]; + if (propertyEditor is null) + { + continue; + } + + // Fetch the property types prevalue. + var configuration = _dataTypeConfigurationCache.GetConfiguration(propertyType.DataTypeKey); + + // Create a real content property data object. + var propertyData = new ContentPropertyData(editedValue?.Value, configuration) + { + ContentKey = contentKey, + PropertyTypeKey = propertyType.Key, + }; + + // Get the property editor to do it's conversion. + IDataValueEditor valueEditor = propertyEditor.GetValueEditor(); + var newValue = valueEditor.FromEditor(propertyData, currentValue?.Value); + + // Update the raw value since this is what will get serialized out. + if (editedValue != null) + { + editedValue.Value = newValue; + } + } + } + } + + private sealed class BlockStateMapping + { + public T? Edited { get; set; } + + public T? Current { get; set; } + } + + private static IEnumerable> GetBlockStatesMapping(IList? editedItems, IList? currentItems, Func, T, bool> condition) + { + // filling with edited items first + List> mapping = editedItems? + .Select(editedItem => new BlockStateMapping + { + Current = default, + Edited = editedItem, + }) + .ToList() + ?? []; + + if (currentItems is null) + { + return mapping; + } + + // then adding current items + foreach (T currentItem in currentItems) + { + BlockStateMapping? mappingItem = mapping.FirstOrDefault(x => condition(x, currentItem)); + + if (mappingItem == null) // if there is no edited item, then adding just current + { + mapping.Add(new BlockStateMapping + { + Current = currentItem, + Edited = default, + }); + } + else + { + mappingItem.Current = currentItem; + } + } + + return mapping; } protected void MapBlockValueToEditor(IProperty property, TValue blockValue, string? culture, string? segment) @@ -197,40 +302,6 @@ public abstract class BlockValuePropertyValueEditorBase : DataV } } - private void MapBlockItemDataFromEditor(List items) - { - foreach (BlockItemData item in items) - { - foreach (BlockPropertyValue blockPropertyValue in item.Values) - { - IPropertyType? propertyType = blockPropertyValue.PropertyType; - if (propertyType is null) - { - throw new ArgumentException("One or more block properties did not have a resolved property type. Block editor values must be resolved before attempting to map them from editor.", nameof(items)); - } - - // Lookup the property editor - IDataEditor? propertyEditor = _propertyEditors[propertyType.PropertyEditorAlias]; - if (propertyEditor is null) - { - continue; - } - - // Fetch the property types prevalue - var configuration = _dataTypeConfigurationCache.GetConfiguration(propertyType.DataTypeKey); - - // Create a fake content property data object - var propertyData = new ContentPropertyData(blockPropertyValue.Value, configuration); - - // Get the property editor to do it's conversion - var newValue = propertyEditor.GetValueEditor().FromEditor(propertyData, blockPropertyValue.Value); - - // update the raw value since this is what will get serialized out - blockPropertyValue.Value = newValue; - } - } - } - /// /// Updates the invariant data in the source with the invariant data in the value if allowed /// diff --git a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs index 1d1b5a633f..32df946ed5 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs @@ -10,10 +10,16 @@ using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; -using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors; +// TODO (V17): +// - Remove the implementation of INotificationHandler as these have all been refactored out into sepate notification handler classes. +// - Remove the unused parameters from the constructor. + +/// +/// Defines the file upload property editor. +/// [DataEditor( Constants.PropertyEditors.Aliases.UploadField, ValueEditorIsReusable = true)] @@ -22,12 +28,11 @@ public class FileUploadPropertyEditor : DataEditor, IMediaUrlGenerator, INotificationHandler, INotificationHandler, INotificationHandler { - private readonly IContentService _contentService; - private readonly IOptionsMonitor _contentSettings; private readonly IIOHelper _ioHelper; - private readonly MediaFileManager _mediaFileManager; - private readonly UploadAutoFillProperties _uploadAutoFillProperties; + /// + /// Initializes a new instance of the class. + /// public FileUploadPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, MediaFileManager mediaFileManager, @@ -37,14 +42,11 @@ public class FileUploadPropertyEditor : DataEditor, IMediaUrlGenerator, IIOHelper ioHelper) : base(dataValueEditorFactory) { - _mediaFileManager = mediaFileManager ?? throw new ArgumentNullException(nameof(mediaFileManager)); - _contentSettings = contentSettings; - _uploadAutoFillProperties = uploadAutoFillProperties; - _contentService = contentService; _ioHelper = ioHelper; SupportsReadOnly = true; } + /// public bool TryGetMediaPath(string? propertyEditorAlias, object? value, [MaybeNullWhen(false)] out string mediaPath) { if (propertyEditorAlias == Alias && @@ -59,53 +61,6 @@ public class FileUploadPropertyEditor : DataEditor, IMediaUrlGenerator, return false; } - public void Handle(ContentCopiedNotification notification) - { - // get the upload field properties with a value - IEnumerable properties = notification.Original.Properties.Where(IsUploadField); - - // copy files - var isUpdated = false; - foreach (IProperty property in properties) - { - // copy each of the property values (variants, segments) to the destination - foreach (IPropertyValue propertyValue in property.Values) - { - var propVal = property.GetValue(propertyValue.Culture, propertyValue.Segment); - if (propVal == null || !(propVal is string str) || str.IsNullOrWhiteSpace()) - { - continue; - } - - var sourcePath = _mediaFileManager.FileSystem.GetRelativePath(str); - var copyPath = _mediaFileManager.CopyFile(notification.Copy, property.PropertyType, sourcePath); - notification.Copy.SetValue(property.Alias, _mediaFileManager.FileSystem.GetUrl(copyPath), - propertyValue.Culture, propertyValue.Segment); - isUpdated = true; - } - } - - // if updated, re-save the copy with the updated value - if (isUpdated) - { - _contentService.Save(notification.Copy); - } - } - - public void Handle(ContentDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); - - public void Handle(MediaDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); - - public void Handle(MediaSavingNotification notification) - { - foreach (IMedia entity in notification.SavedEntities) - { - AutoFillProperties(entity); - } - } - - public void Handle(MemberDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); - /// protected override IConfigurationEditor CreateConfigurationEditor() => new FileUploadConfigurationEditor(_ioHelper); @@ -117,86 +72,43 @@ public class FileUploadPropertyEditor : DataEditor, IMediaUrlGenerator, protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create(Attribute!); - /// - /// Gets a value indicating whether a property is an upload field. - /// - /// The property. - /// - /// true if the specified property is an upload field; otherwise, false. - /// - private static bool IsUploadField(IProperty property) => property.PropertyType.PropertyEditorAlias == - Constants.PropertyEditors.Aliases.UploadField; + #region Obsolete notification handler notifications - /// - /// The paths to all file upload property files contained within a collection of content entities - /// - /// - private IEnumerable ContainedFilePaths(IEnumerable entities) => entities - .SelectMany(x => x.Properties) - .Where(IsUploadField) - .SelectMany(GetFilePathsFromPropertyValues) - .Distinct(); - - /// - /// Look through all property values stored against the property and resolve any file paths stored - /// - /// - /// - private IEnumerable GetFilePathsFromPropertyValues(IProperty prop) + /// + [Obsolete("This handler is no longer registered. Logic has been migrated to FileUploadContentCopiedNotificationHandler. Scheduled for removal in Umbraco 17.")] + public void Handle(ContentCopiedNotification notification) { - IReadOnlyCollection propVals = prop.Values; - foreach (IPropertyValue propertyValue in propVals) - { - // check if the published value contains data and return it - var propVal = propertyValue.PublishedValue; - if (propVal != null && propVal is string str1 && !str1.IsNullOrWhiteSpace()) - { - yield return _mediaFileManager.FileSystem.GetRelativePath(str1); - } - - // check if the edited value contains data and return it - propVal = propertyValue.EditedValue; - if (propVal != null && propVal is string str2 && !str2.IsNullOrWhiteSpace()) - { - yield return _mediaFileManager.FileSystem.GetRelativePath(str2); - } - } + // This handler is no longer registered. Logic has been migrated to FileUploadContentCopiedNotificationHandler. } - private void DeleteContainedFiles(IEnumerable deletedEntities) + /// + [Obsolete("This handler is no longer registered. Logic has been migrated to FileUploadMediaSavingNotificationHandler. Scheduled for removal in Umbraco 17.")] + public void Handle(MediaSavingNotification notification) { - IEnumerable filePathsToDelete = ContainedFilePaths(deletedEntities); - _mediaFileManager.DeleteMediaFiles(filePathsToDelete); + // This handler is no longer registered. Logic has been migrated to FileUploadMediaSavingNotificationHandler. } - /// - /// Auto-fill properties (or clear). - /// - private void AutoFillProperties(IContentBase model) + /// + [Obsolete("This handler is no longer registered. Logic has been migrated to FileUploadContentDeletedNotificationHandler. Scheduled for removal in Umbraco 17.")] + public void Handle(ContentDeletedNotification notification) { - IEnumerable properties = model.Properties.Where(IsUploadField); - - foreach (IProperty property in properties) - { - ImagingAutoFillUploadField? autoFillConfig = _contentSettings.CurrentValue.GetConfig(property.Alias); - if (autoFillConfig == null) - { - continue; - } - - foreach (IPropertyValue pvalue in property.Values) - { - var svalue = property.GetValue(pvalue.Culture, pvalue.Segment) as string; - if (string.IsNullOrWhiteSpace(svalue)) - { - _uploadAutoFillProperties.Reset(model, autoFillConfig, pvalue.Culture, pvalue.Segment); - } - else - { - _uploadAutoFillProperties.Populate(model, autoFillConfig, - _mediaFileManager.FileSystem.GetRelativePath(svalue), pvalue.Culture, pvalue.Segment); - } - } - } + // This handler is no longer registered. Logic has been migrated to FileUploadContentDeletedNotificationHandler. } + + + /// + [Obsolete("This handler is no longer registered. Logic has been migrated to FileUploadMediaDeletedNotificationHandler. Scheduled for removal in Umbraco 17.")] + public void Handle(MediaDeletedNotification notification) + { + // This handler is no longer registered. Logic has been migrated to FileUploadMediaDeletedNotificationHandler. + } + + /// + [Obsolete("This handler is no longer registered. Logic has been migrated to FileUploadMemberDeletedNotificationHandler. Scheduled for removal in Umbraco 17.")] + public void Handle(MemberDeletedNotification notification) + { + // This handler is no longer registered. Logic has been migrated to FileUploadMemberDeletedNotificationHandler. + } + + #endregion } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs index 1150fa09a2..1d0a0a0702 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs @@ -12,6 +12,7 @@ using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.PropertyEditors; using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; @@ -23,12 +24,15 @@ namespace Umbraco.Cms.Core.PropertyEditors; internal class FileUploadPropertyValueEditor : DataValueEditor { private readonly MediaFileManager _mediaFileManager; - private readonly IJsonSerializer _jsonSerializer; private readonly ITemporaryFileService _temporaryFileService; private readonly IScopeProvider _scopeProvider; private readonly IFileStreamSecurityValidator _fileStreamSecurityValidator; + private readonly FileUploadValueParser _valueParser; private ContentSettings _contentSettings; + /// + /// Initializes a new instance of the class. + /// public FileUploadPropertyValueEditor( DataEditorAttribute attribute, MediaFileManager mediaFileManager, @@ -42,10 +46,11 @@ internal class FileUploadPropertyValueEditor : DataValueEditor : base(shortStringHelper, jsonSerializer, ioHelper, attribute) { _mediaFileManager = mediaFileManager ?? throw new ArgumentNullException(nameof(mediaFileManager)); - _jsonSerializer = jsonSerializer; _temporaryFileService = temporaryFileService; _scopeProvider = scopeProvider; _fileStreamSecurityValidator = fileStreamSecurityValidator; + _valueParser = new FileUploadValueParser(jsonSerializer); + _contentSettings = contentSettings.CurrentValue ?? throw new ArgumentNullException(nameof(contentSettings)); contentSettings.OnChange(x => _contentSettings = x); @@ -56,6 +61,7 @@ internal class FileUploadPropertyValueEditor : DataValueEditor IsAllowedInDataTypeConfiguration)); } + /// public override object? ToEditor(IProperty property, string? culture = null, string? segment = null) { // the stored property value (if any) is the path to the file; convert it to the client model (FileUploadValue) @@ -63,11 +69,12 @@ internal class FileUploadPropertyValueEditor : DataValueEditor return propertyValue is string stringValue ? new FileUploadValue { - Src = stringValue + Src = stringValue, } : null; } + /// /// /// Converts the client model (FileUploadValue) into the value can be stored in the database (the file path). /// @@ -83,12 +90,15 @@ internal class FileUploadPropertyValueEditor : DataValueEditor /// public override object? FromEditor(ContentPropertyData editorValue, object? currentValue) { - FileUploadValue? editorModelValue = ParseFileUploadValue(editorValue.Value); + FileUploadValue? editorModelValue = _valueParser.Parse(editorValue.Value); // no change? if (editorModelValue?.TemporaryFileId.HasValue is not true && string.IsNullOrEmpty(editorModelValue?.Src) is false) { - return currentValue; + // since current value can be json string, we have to parse value + FileUploadValue? currentModelValue = _valueParser.Parse(currentValue); + + return currentModelValue?.Src; } // the current editor value (if any) is the path to the file @@ -146,28 +156,8 @@ internal class FileUploadPropertyValueEditor : DataValueEditor return filepath is null ? null : _mediaFileManager.FileSystem.GetUrl(filepath); } - private FileUploadValue? ParseFileUploadValue(object? editorValue) - { - if (editorValue is null) - { - return null; - } - - if (editorValue is string sourceString && sourceString.DetectIsJson() is false) - { - return new FileUploadValue() - { - Src = sourceString - }; - } - - return _jsonSerializer.TryDeserialize(editorValue, out FileUploadValue? modelValue) - ? modelValue - : throw new ArgumentException($"Could not parse editor value to a {nameof(FileUploadValue)} object."); - } - private Guid? TryParseTemporaryFileKey(object? editorValue) - => ParseFileUploadValue(editorValue)?.TemporaryFileId; + => _valueParser.Parse(editorValue)?.TemporaryFileId; private TemporaryFileModel? TryGetTemporaryFile(Guid temporaryFileKey) => _temporaryFileService.GetAsync(temporaryFileKey).GetAwaiter().GetResult(); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadValueParser.cs b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadValueParser.cs new file mode 100644 index 0000000000..3fee3d6744 --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadValueParser.cs @@ -0,0 +1,45 @@ +using Umbraco.Cms.Core.PropertyEditors.ValueConverters; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.PropertyEditors; + +/// +/// Handles the parsing of raw values to objects. +/// +internal sealed class FileUploadValueParser +{ + private readonly IJsonSerializer _jsonSerializer; + + /// + /// Initializes a new instance of the class. + /// + /// + public FileUploadValueParser(IJsonSerializer jsonSerializer) => _jsonSerializer = jsonSerializer; + + /// + /// Parses raw value to a . + /// + /// The editor value. + /// value + /// + public FileUploadValue? Parse(object? editorValue) + { + if (editorValue is null) + { + return null; + } + + if (editorValue is string sourceString && sourceString.DetectIsJson() is false) + { + return new FileUploadValue() + { + Src = sourceString, + }; + } + + return _jsonSerializer.TryDeserialize(editorValue, out FileUploadValue? modelValue) + ? modelValue + : throw new ArgumentException($"Could not parse editor value to a {nameof(FileUploadValue)} object."); + } +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedNotificationHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedNotificationHandler.cs new file mode 100644 index 0000000000..b056d31c79 --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedNotificationHandler.cs @@ -0,0 +1,312 @@ +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache.PropertyEditors; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Blocks; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors.ValueConverters; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Infrastructure.PropertyEditors.NotificationHandlers; + +/// +/// Implements a notification handler that processes file uploads when content is copied, making sure the copied contetnt relates to a new instance +/// of the file. +/// +internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNotificationHandlerBase, INotificationHandler +{ + private readonly IContentService _contentService; + + private readonly BlockEditorValues _blockListEditorValues; + private readonly BlockEditorValues _blockGridEditorValues; + + /// + /// Initializes a new instance of the class. + /// + public FileUploadContentCopiedNotificationHandler( + IJsonSerializer jsonSerializer, + MediaFileManager mediaFileManager, + IBlockEditorElementTypeCache elementTypeCache, + ILogger logger, + IContentService contentService) + : base(jsonSerializer, mediaFileManager, elementTypeCache) + { + _blockListEditorValues = new(new BlockListEditorDataConverter(jsonSerializer), elementTypeCache, logger); + _blockGridEditorValues = new(new BlockGridEditorDataConverter(jsonSerializer), elementTypeCache, logger); + _contentService = contentService; + } + + /// + public void Handle(ContentCopiedNotification notification) + { + ArgumentNullException.ThrowIfNull(notification); + + var isUpdated = false; + + foreach (IProperty property in notification.Original.Properties) + { + if (IsUploadFieldPropertyType(property.PropertyType)) + { + isUpdated |= UpdateUploadFieldProperty(notification, property); + + continue; + } + + if (IsBlockListPropertyType(property.PropertyType)) + { + isUpdated |= UpdateBlockProperty(notification, property, _blockListEditorValues); + + continue; + } + + if (IsBlockGridPropertyType(property.PropertyType)) + { + isUpdated |= UpdateBlockProperty(notification, property, _blockGridEditorValues); + + continue; + } + + if (IsRichTextPropertyType(property.PropertyType)) + { + isUpdated |= UpdateRichTextProperty(notification, property); + + continue; + } + } + + // if updated, re-save the copy with the updated value + if (isUpdated) + { + _contentService.Save(notification.Copy); + } + } + + private bool UpdateUploadFieldProperty(ContentCopiedNotification notification, IProperty property) + { + var isUpdated = false; + + // Copy each of the property values (variants, segments) to the destination. + foreach (IPropertyValue propertyValue in property.Values) + { + var propVal = property.GetValue(propertyValue.Culture, propertyValue.Segment); + if (propVal == null || propVal is not string sourceUrl || string.IsNullOrWhiteSpace(sourceUrl)) + { + continue; + } + + var copyUrl = CopyFile(sourceUrl, notification.Copy, property.PropertyType); + + notification.Copy.SetValue(property.Alias, copyUrl, propertyValue.Culture, propertyValue.Segment); + + isUpdated = true; + } + + return isUpdated; + } + + private bool UpdateBlockProperty(ContentCopiedNotification notification, IProperty property, BlockEditorValues blockEditorValues) + where TValue : BlockValue, new() + where TLayout : class, IBlockLayoutItem, new() + { + var isUpdated = false; + + foreach (IPropertyValue blockPropertyValue in property.Values) + { + var rawBlockPropertyValue = property.GetValue(blockPropertyValue.Culture, blockPropertyValue.Segment); + + BlockEditorData? blockEditorData = GetBlockEditorData(rawBlockPropertyValue, blockEditorValues); + + (bool hasUpdates, string? updatedValue) = UpdateBlockEditorData(notification, blockEditorData); + + if (hasUpdates) + { + notification.Copy.SetValue(property.Alias, updatedValue, blockPropertyValue.Culture, blockPropertyValue.Segment); + } + + isUpdated |= hasUpdates; + } + + return isUpdated; + } + + private (bool, string?) UpdateBlockEditorData(ContentCopiedNotification notification, BlockEditorData? blockEditorData) + where TValue : BlockValue, new() + where TLayout : class, IBlockLayoutItem, new() + { + var isUpdated = false; + + if (blockEditorData is null) + { + return (isUpdated, null); + } + + IEnumerable blockPropertyValues = blockEditorData.BlockValue.ContentData + .Concat(blockEditorData.BlockValue.SettingsData) + .SelectMany(x => x.Values); + + isUpdated = UpdateBlockPropertyValues(notification, isUpdated, blockPropertyValues); + + var updatedValue = JsonSerializer.Serialize(blockEditorData.BlockValue); + + return (isUpdated, updatedValue); + } + + private bool UpdateRichTextProperty(ContentCopiedNotification notification, IProperty property) + { + var isUpdated = false; + + foreach (IPropertyValue blockPropertyValue in property.Values) + { + var rawBlockPropertyValue = property.GetValue(blockPropertyValue.Culture, blockPropertyValue.Segment); + + RichTextBlockValue? richTextBlockValue = GetRichTextBlockValue(rawBlockPropertyValue); + + (bool hasUpdates, string? updatedValue) = UpdateBlockEditorData(notification, richTextBlockValue); + + if (hasUpdates && string.IsNullOrEmpty(updatedValue) is false) + { + RichTextEditorValue? richTextEditorValue = GetRichTextEditorValue(rawBlockPropertyValue); + if (richTextEditorValue is not null) + { + richTextEditorValue.Blocks = JsonSerializer.Deserialize(updatedValue); + notification.Copy.SetValue(property.Alias, JsonSerializer.Serialize(richTextEditorValue), blockPropertyValue.Culture, blockPropertyValue.Segment); + } + } + + isUpdated |= hasUpdates; + } + + return isUpdated; + } + + private (bool, string?) UpdateBlockEditorData(ContentCopiedNotification notification, RichTextBlockValue? richTextBlockValue) + { + var isUpdated = false; + + if (richTextBlockValue is null) + { + return (isUpdated, null); + } + + IEnumerable blockPropertyValues = richTextBlockValue.ContentData + .Concat(richTextBlockValue.SettingsData) + .SelectMany(x => x.Values); + + isUpdated = UpdateBlockPropertyValues(notification, isUpdated, blockPropertyValues); + + var updatedValue = JsonSerializer.Serialize(richTextBlockValue); + + return (isUpdated, updatedValue); + } + + private bool UpdateBlockPropertyValues(ContentCopiedNotification notification, bool isUpdated, IEnumerable blockPropertyValues) + { + foreach (BlockPropertyValue blockPropertyValue in blockPropertyValues) + { + if (blockPropertyValue.Value is null) + { + continue; + } + + IPropertyType? propertyType = blockPropertyValue.PropertyType; + + if (propertyType is null) + { + continue; + } + + if (IsUploadFieldPropertyType(propertyType)) + { + isUpdated |= UpdateUploadFieldBlockPropertyValue(blockPropertyValue, notification, propertyType); + + continue; + } + + if (IsBlockListPropertyType(propertyType)) + { + (bool hasUpdates, string? newValue) = UpdateBlockPropertyValue(blockPropertyValue, notification, _blockListEditorValues); + + isUpdated |= hasUpdates; + + blockPropertyValue.Value = newValue; + + continue; + } + + if (IsBlockGridPropertyType(propertyType)) + { + (bool hasUpdates, string? newValue) = UpdateBlockPropertyValue(blockPropertyValue, notification, _blockGridEditorValues); + + isUpdated |= hasUpdates; + + blockPropertyValue.Value = newValue; + + continue; + } + + if (IsRichTextPropertyType(propertyType)) + { + (bool hasUpdates, string? newValue) = UpdateRichTextPropertyValue(blockPropertyValue, notification); + + if (hasUpdates && string.IsNullOrEmpty(newValue) is false) + { + RichTextEditorValue? richTextEditorValue = GetRichTextEditorValue(blockPropertyValue.Value); + if (richTextEditorValue is not null) + { + isUpdated |= hasUpdates; + + richTextEditorValue.Blocks = JsonSerializer.Deserialize(newValue); + blockPropertyValue.Value = richTextEditorValue; + } + } + + continue; + } + } + + return isUpdated; + } + + private bool UpdateUploadFieldBlockPropertyValue(BlockPropertyValue blockItemDataValue, ContentCopiedNotification notification, IPropertyType propertyType) + { + FileUploadValue? fileUploadValue = FileUploadValueParser.Parse(blockItemDataValue.Value); + + // if original value is empty, we do not need to copy file + if (string.IsNullOrWhiteSpace(fileUploadValue?.Src)) + { + return false; + } + + var copyFileUrl = CopyFile(fileUploadValue.Src, notification.Copy, propertyType); + + blockItemDataValue.Value = copyFileUrl; + + return true; + } + + private (bool, string?) UpdateBlockPropertyValue(BlockPropertyValue blockItemDataValue, ContentCopiedNotification notification, BlockEditorValues blockEditorValues) + where TValue : BlockValue, new() + where TLayout : class, IBlockLayoutItem, new() + { + BlockEditorData? blockItemEditorDataValue = GetBlockEditorData(blockItemDataValue.Value, blockEditorValues); + + return UpdateBlockEditorData(notification, blockItemEditorDataValue); + } + + private (bool, string?) UpdateRichTextPropertyValue(BlockPropertyValue blockItemDataValue, ContentCopiedNotification notification) + { + RichTextBlockValue? richTextBlockValue = GetRichTextBlockValue(blockItemDataValue.Value); + return UpdateBlockEditorData(notification, richTextBlockValue); + } + + private string CopyFile(string sourceUrl, IContent destinationContent, IPropertyType propertyType) + { + var sourcePath = MediaFileManager.FileSystem.GetRelativePath(sourceUrl); + var copyPath = MediaFileManager.CopyFile(destinationContent, propertyType, sourcePath); + return MediaFileManager.FileSystem.GetUrl(copyPath); + } +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentDeletedNotificationHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentDeletedNotificationHandler.cs new file mode 100644 index 0000000000..681c31cc58 --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentDeletedNotificationHandler.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Cache.PropertyEditors; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Serialization; + +namespace Umbraco.Cms.Infrastructure.PropertyEditors.NotificationHandlers; + +/// +/// Implements a notification handler that processes file uploads when content is deleted, removing associated files. +/// +internal sealed class FileUploadContentDeletedNotificationHandler : FileUploadEntityDeletedNotificationHandlerBase, INotificationHandler +{ + /// + /// Initializes a new instance of the class. + /// + public FileUploadContentDeletedNotificationHandler( + IJsonSerializer jsonSerializer, + MediaFileManager mediaFileManager, + IBlockEditorElementTypeCache elementTypeCache, + ILogger logger) + : base(jsonSerializer, mediaFileManager, elementTypeCache, logger) + { + } + + /// + public void Handle(ContentDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadEntityDeletedNotificationHandlerBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadEntityDeletedNotificationHandlerBase.cs new file mode 100644 index 0000000000..40877d2fef --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadEntityDeletedNotificationHandlerBase.cs @@ -0,0 +1,218 @@ +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Blocks; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors.ValueConverters; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Infrastructure.Extensions; + +namespace Umbraco.Cms.Infrastructure.PropertyEditors.NotificationHandlers; + +/// +/// Provides base class for notification handler that processes file uploads when a content entity is deleted, removing associated files. +/// +internal abstract class FileUploadEntityDeletedNotificationHandlerBase : FileUploadNotificationHandlerBase +{ + private readonly BlockEditorValues _blockListEditorValues; + private readonly BlockEditorValues _blockGridEditorValues; + + /// + /// Initializes a new instance of the class. + /// + protected FileUploadEntityDeletedNotificationHandlerBase( + IJsonSerializer jsonSerializer, + MediaFileManager mediaFileManager, + IBlockEditorElementTypeCache elementTypeCache, + ILogger logger) + : base(jsonSerializer, mediaFileManager, elementTypeCache) + { + _blockListEditorValues = new(new BlockListEditorDataConverter(jsonSerializer), elementTypeCache, logger); + _blockGridEditorValues = new(new BlockGridEditorDataConverter(jsonSerializer), elementTypeCache, logger); + } + + /// + /// Deletes all file upload property files contained within a collection of content entities. + /// + /// + protected void DeleteContainedFiles(IEnumerable deletedEntities) + { + IReadOnlyList filePathsToDelete = ContainedFilePaths(deletedEntities); + MediaFileManager.DeleteMediaFiles(filePathsToDelete); + } + + /// + /// Gets the paths to all file upload property files contained within a collection of content entities. + /// + private IReadOnlyList ContainedFilePaths(IEnumerable entities) + { + var paths = new List(); + + foreach (IProperty? property in entities.SelectMany(x => x.Properties)) + { + if (IsUploadFieldPropertyType(property.PropertyType)) + { + paths.AddRange(GetPathsFromUploadFieldProperty(property)); + + continue; + } + + if (IsBlockListPropertyType(property.PropertyType)) + { + paths.AddRange(GetPathsFromBlockProperty(property, _blockListEditorValues)); + + continue; + } + + if (IsBlockGridPropertyType(property.PropertyType)) + { + paths.AddRange(GetPathsFromBlockProperty(property, _blockGridEditorValues)); + + continue; + } + + if (IsRichTextPropertyType(property.PropertyType)) + { + paths.AddRange(GetPathsFromRichTextProperty(property)); + + continue; + } + } + + return paths.Distinct().ToList().AsReadOnly(); + } + + private IEnumerable GetPathsFromUploadFieldProperty(IProperty property) + { + foreach (IPropertyValue propertyValue in property.Values) + { + if (propertyValue.PublishedValue != null && propertyValue.PublishedValue is string publishedUrl && !string.IsNullOrWhiteSpace(publishedUrl)) + { + yield return MediaFileManager.FileSystem.GetRelativePath(publishedUrl); + } + + if (propertyValue.EditedValue != null && propertyValue.EditedValue is string editedUrl && !string.IsNullOrWhiteSpace(editedUrl)) + { + yield return MediaFileManager.FileSystem.GetRelativePath(editedUrl); + } + } + } + + private IReadOnlyCollection GetPathsFromBlockProperty(IProperty property, BlockEditorValues blockEditorValues) + where TValue : BlockValue, new() + where TLayout : class, IBlockLayoutItem, new() + { + var paths = new List(); + + foreach (IPropertyValue blockPropertyValue in property.Values) + { + paths.AddRange(GetPathsFromBlockValue(GetBlockEditorData(blockPropertyValue.PublishedValue, blockEditorValues)?.BlockValue)); + paths.AddRange(GetPathsFromBlockValue(GetBlockEditorData(blockPropertyValue.EditedValue, blockEditorValues)?.BlockValue)); + } + + return paths; + } + + private IReadOnlyCollection GetPathsFromBlockValue(BlockValue? blockValue) + { + var paths = new List(); + + if (blockValue is null) + { + return paths; + } + + IEnumerable blockPropertyValues = blockValue.ContentData + .Concat(blockValue.SettingsData) + .SelectMany(x => x.Values); + + foreach (BlockPropertyValue blockPropertyValue in blockPropertyValues) + { + if (blockPropertyValue.Value == null) + { + continue; + } + + IPropertyType? propertyType = blockPropertyValue.PropertyType; + + if (propertyType == null) + { + continue; + } + + if (IsUploadFieldPropertyType(propertyType)) + { + FileUploadValue? originalValue = FileUploadValueParser.Parse(blockPropertyValue.Value); + + if (string.IsNullOrWhiteSpace(originalValue?.Src)) + { + continue; + } + + paths.Add(MediaFileManager.FileSystem.GetRelativePath(originalValue.Src)); + + continue; + } + + if (IsBlockListPropertyType(propertyType)) + { + paths.AddRange(GetPathsFromBlockPropertyValue(blockPropertyValue, _blockListEditorValues)); + + continue; + } + + if (IsBlockGridPropertyType(propertyType)) + { + paths.AddRange(GetPathsFromBlockPropertyValue(blockPropertyValue, _blockGridEditorValues)); + + continue; + } + + if (IsRichTextPropertyType(propertyType)) + { + paths.AddRange(GetPathsFromRichTextPropertyValue(blockPropertyValue)); + + continue; + } + } + + return paths; + } + + private IReadOnlyCollection GetPathsFromBlockPropertyValue(BlockPropertyValue blockItemDataValue, BlockEditorValues blockEditorValues) + where TValue : BlockValue, new() + where TLayout : class, IBlockLayoutItem, new() + { + BlockEditorData? blockItemEditorDataValue = GetBlockEditorData(blockItemDataValue.Value, blockEditorValues); + + return GetPathsFromBlockValue(blockItemEditorDataValue?.BlockValue); + } + + private IReadOnlyCollection GetPathsFromRichTextProperty(IProperty property) + { + var paths = new List(); + + IPropertyValue? propertyValue = property.Values.FirstOrDefault(); + if (propertyValue is null) + { + return paths; + } + + paths.AddRange(GetPathsFromBlockValue(GetRichTextBlockValue(propertyValue.PublishedValue))); + paths.AddRange(GetPathsFromBlockValue(GetRichTextBlockValue(propertyValue.EditedValue))); + + return paths; + } + + private IReadOnlyCollection GetPathsFromRichTextPropertyValue(BlockPropertyValue blockItemDataValue) + { + RichTextEditorValue? richTextEditorValue = GetRichTextEditorValue(blockItemDataValue.Value); + + // Ensure the property type is populated on all blocks. + richTextEditorValue?.EnsurePropertyTypePopulatedOnBlocks(ElementTypeCache); + + return GetPathsFromBlockValue(richTextEditorValue?.Blocks); + } +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMediaDeletedNotificationHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMediaDeletedNotificationHandler.cs new file mode 100644 index 0000000000..3a07193ec8 --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMediaDeletedNotificationHandler.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Cache.PropertyEditors; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Serialization; + +namespace Umbraco.Cms.Infrastructure.PropertyEditors.NotificationHandlers; + +/// +/// Implements a notification handler that processes file uploads when media is deleted, removing associated files. +/// +internal sealed class FileUploadMediaDeletedNotificationHandler : FileUploadEntityDeletedNotificationHandlerBase, INotificationHandler +{ + /// + /// Initializes a new instance of the class. + /// + public FileUploadMediaDeletedNotificationHandler( + IJsonSerializer jsonSerializer, + MediaFileManager mediaFileManager, + IBlockEditorElementTypeCache elementTypeCache, + ILogger logger) + : base(jsonSerializer, mediaFileManager, elementTypeCache, logger) + { + } + + /// + public void Handle(MediaDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMediaSavingNotificationHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMediaSavingNotificationHandler.cs new file mode 100644 index 0000000000..ffc67391dd --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMediaSavingNotificationHandler.cs @@ -0,0 +1,77 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Cache.PropertyEditors; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Media; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.PropertyEditors.NotificationHandlers; + +/// +/// Implements a notification handler that processes file uploads media is saved, completing properties on the media item. +/// +internal sealed class FileUploadMediaSavingNotificationHandler : FileUploadNotificationHandlerBase, INotificationHandler +{ + private readonly IOptionsMonitor _contentSettings; + private readonly UploadAutoFillProperties _uploadAutoFillProperties; + + /// + /// Initializes a new instance of the class. + /// + public FileUploadMediaSavingNotificationHandler( + IJsonSerializer jsonSerializer, + MediaFileManager mediaFileManager, + IBlockEditorElementTypeCache elementTypeCache, + IOptionsMonitor contentSettings, + UploadAutoFillProperties uploadAutoFillProperties) + : base(jsonSerializer, mediaFileManager, elementTypeCache) + { + _contentSettings = contentSettings; + _uploadAutoFillProperties = uploadAutoFillProperties; + } + + /// + public void Handle(MediaSavingNotification notification) + { + foreach (IMedia entity in notification.SavedEntities) + { + AutoFillProperties(entity); + } + } + + private void AutoFillProperties(IContentBase model) + { + IEnumerable properties = model.Properties.Where(x => IsUploadFieldPropertyType(x.PropertyType)); + + foreach (IProperty property in properties) + { + ImagingAutoFillUploadField? autoFillConfig = _contentSettings.CurrentValue.GetConfig(property.Alias); + if (autoFillConfig == null) + { + continue; + } + + foreach (IPropertyValue pvalue in property.Values) + { + var svalue = property.GetValue(pvalue.Culture, pvalue.Segment) as string; + if (string.IsNullOrWhiteSpace(svalue)) + { + _uploadAutoFillProperties.Reset(model, autoFillConfig, pvalue.Culture, pvalue.Segment); + } + else + { + _uploadAutoFillProperties.Populate( + model, + autoFillConfig, + MediaFileManager.FileSystem.GetRelativePath(svalue), + pvalue.Culture, + pvalue.Segment); + } + } + } + } +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMemberDeletedNotificationHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMemberDeletedNotificationHandler.cs new file mode 100644 index 0000000000..91433b88b9 --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMemberDeletedNotificationHandler.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Cache.PropertyEditors; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Serialization; + +namespace Umbraco.Cms.Infrastructure.PropertyEditors.NotificationHandlers; + +/// +/// Implements a notification handler that processes file uploads when a member is deleted, removing associated files. +/// +internal sealed class FileUploadMemberDeletedNotificationHandler : FileUploadEntityDeletedNotificationHandlerBase, INotificationHandler +{ + /// + /// Initializes a new instance of the class. + /// + public FileUploadMemberDeletedNotificationHandler( + IJsonSerializer jsonSerializer, + MediaFileManager mediaFileManager, + IBlockEditorElementTypeCache elementTypeCache, + ILogger logger) + : base(jsonSerializer, mediaFileManager, elementTypeCache, logger) + { + } + + /// + public void Handle(MemberDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadNotificationHandlerBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadNotificationHandlerBase.cs new file mode 100644 index 0000000000..4e0f6afcc2 --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadNotificationHandlerBase.cs @@ -0,0 +1,140 @@ +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache.PropertyEditors; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Blocks; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Infrastructure.Extensions; + +namespace Umbraco.Cms.Infrastructure.PropertyEditors.NotificationHandlers; + +/// +/// Provides a base class for all notification handlers relating to file uploads in property editors. +/// +internal abstract class FileUploadNotificationHandlerBase +{ + /// + /// Initializes a new instance of the class. + /// + protected FileUploadNotificationHandlerBase( + IJsonSerializer jsonSerializer, + MediaFileManager mediaFileManager, + IBlockEditorElementTypeCache elementTypeCache) + { + JsonSerializer = jsonSerializer; + MediaFileManager = mediaFileManager; + ElementTypeCache = elementTypeCache; + FileUploadValueParser = new FileUploadValueParser(jsonSerializer); + } + + /// + /// Gets the used for serializing and deserializing values. + /// + protected IJsonSerializer JsonSerializer { get; } + + /// + /// Gets the used for managing media files. + /// + protected MediaFileManager MediaFileManager { get; } + + /// + /// Gets the used for caching block editor element types. + /// + protected IBlockEditorElementTypeCache ElementTypeCache { get; } + + /// + /// Gets the used for parsing file upload values. + /// + protected FileUploadValueParser FileUploadValueParser { get; } + + /// + /// Gets a value indicating whether a property is an upload field. + /// + /// The property type. + /// + /// true if the specified property is an upload field; otherwise, false. + /// + protected static bool IsUploadFieldPropertyType(IPropertyType propertyType) + => propertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField; + + /// + /// Gets a value indicating whether a property is an block list field. + /// + /// The property type. + /// + /// true if the specified property is an block list field; otherwise, false. + /// + protected static bool IsBlockListPropertyType(IPropertyType propertyType) + => propertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.BlockList; + + /// + /// Gets a value indicating whether a property is an block grid field. + /// + /// The property type. + /// + /// true if the specified property is an block grid field; otherwise, false. + /// + protected static bool IsBlockGridPropertyType(IPropertyType propertyType) + => propertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.BlockGrid; + + /// + /// Gets a value indicating whether a property is an rich text field (supporting blocks). + /// + /// The property type. + /// + /// true if the specified property is an rich text field; otherwise, false. + /// + protected static bool IsRichTextPropertyType(IPropertyType propertyType) + => propertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.RichText || + propertyType.PropertyEditorAlias == "Umbraco.TinyMCE"; + + /// + /// Deserializes the block editor data value. + /// + protected static BlockEditorData? GetBlockEditorData(object? value, BlockEditorValues blockListEditorValues) + where TValue : BlockValue, new() + where TLayout : class, IBlockLayoutItem, new() + { + try + { + return blockListEditorValues.DeserializeAndClean(value); + } + catch + { + // If this occurs it means the data is invalid. Shouldn't happen but could if we change the data format. + return null; + } + } + + /// + /// Deserializes the rich text editor value. + /// + protected RichTextEditorValue? GetRichTextEditorValue(object? value) + { + if (value is null) + { + return null; + } + + JsonSerializer.TryDeserialize(value, out RichTextEditorValue? richTextEditorValue); + return richTextEditorValue; + } + + /// + /// Deserializes the rich text block value. + /// + protected RichTextBlockValue? GetRichTextBlockValue(object? value) + { + RichTextEditorValue? richTextEditorValue = GetRichTextEditorValue(value); + if (richTextEditorValue?.Blocks is null) + { + return null; + } + + // Ensure the property type is populated on all blocks. + richTextEditorValue.EnsurePropertyTypePopulatedOnBlocks(ElementTypeCache); + + return richTextEditorValue.Blocks; + } +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs index 7d75725ef6..7c9b26f343 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs @@ -16,6 +16,7 @@ using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Core.Templates; +using Umbraco.Cms.Infrastructure.Extensions; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors; @@ -33,8 +34,11 @@ public class RichTextPropertyEditor : DataEditor private readonly IRichTextPropertyIndexValueFactory _richTextPropertyIndexValueFactory; /// - /// The constructor will setup the property editor based on the attribute if one is found. + /// Initializes a new instance of the class. /// + /// + /// The constructor will setup the property editor based on the attribute if one is found. + /// public RichTextPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, IIOHelper ioHelper, @@ -43,6 +47,7 @@ public class RichTextPropertyEditor : DataEditor { _ioHelper = ioHelper; _richTextPropertyIndexValueFactory = richTextPropertyIndexValueFactory; + SupportsReadOnly = true; } @@ -95,6 +100,7 @@ public class RichTextPropertyEditor : DataEditor private readonly IRichTextRequiredValidator _richTextRequiredValidator; private readonly IRichTextRegexValidator _richTextRegexValidator; private readonly ILogger _logger; + private readonly IBlockEditorElementTypeCache _elementTypeCache; public RichTextPropertyValueEditor( DataEditorAttribute attribute, @@ -123,6 +129,7 @@ public class RichTextPropertyEditor : DataEditor _localLinkParser = localLinkParser; _pastedImages = pastedImages; _htmlSanitizer = htmlSanitizer; + _elementTypeCache = elementTypeCache; _richTextRequiredValidator = richTextRequiredValidator; _richTextRegexValidator = richTextRegexValidator; _jsonSerializer = jsonSerializer; @@ -242,7 +249,23 @@ public class RichTextPropertyEditor : DataEditor /// public override object? FromEditor(ContentPropertyData editorValue, object? currentValue) { - if (TryParseEditorValue(editorValue.Value, out RichTextEditorValue? richTextEditorValue) is false) + // See note on BlockEditorPropertyValueEditor.FromEditor for why we can't return early with only a null or empty editorValue. + TryParseEditorValue(editorValue.Value, out RichTextEditorValue? richTextEditorValue); + TryParseEditorValue(currentValue, out RichTextEditorValue? currentRichTextEditorValue); + + // We can early return if we have a null value and the current value doesn't have any blocks. + if (richTextEditorValue is null && currentRichTextEditorValue?.Blocks is null) + { + return null; + } + + // Ensure the property type is populated on all blocks. + richTextEditorValue?.EnsurePropertyTypePopulatedOnBlocks(_elementTypeCache); + currentRichTextEditorValue?.EnsurePropertyTypePopulatedOnBlocks(_elementTypeCache); + + RichTextEditorValue cleanedUpRichTextEditorValue = CleanAndMapBlocks(richTextEditorValue, blockValue => MapBlockValueFromEditor(blockValue, currentRichTextEditorValue?.Blocks, editorValue.ContentKey)); + + if (string.IsNullOrWhiteSpace(richTextEditorValue?.Markup)) { return null; } @@ -253,11 +276,6 @@ public class RichTextPropertyEditor : DataEditor var config = editorValue.DataTypeConfiguration as RichTextConfiguration; Guid mediaParentId = config?.MediaParentId ?? Guid.Empty; - if (string.IsNullOrWhiteSpace(richTextEditorValue.Markup)) - { - return null; - } - var parseAndSavedTempImages = _pastedImages .FindAndPersistPastedTempImagesAsync(richTextEditorValue.Markup, mediaParentId, userKey) .GetAwaiter() @@ -267,8 +285,6 @@ public class RichTextPropertyEditor : DataEditor richTextEditorValue.Markup = sanitized.NullOrWhiteSpaceAsNull() ?? string.Empty; - RichTextEditorValue cleanedUpRichTextEditorValue = CleanAndMapBlocks(richTextEditorValue, MapBlockValueFromEditor); - // return json return RichTextPropertyEditorHelper.SerializeRichTextEditorValue(cleanedUpRichTextEditorValue, _jsonSerializer); } @@ -377,19 +393,26 @@ public class RichTextPropertyEditor : DataEditor private bool TryParseEditorValue(object? value, [NotNullWhen(true)] out RichTextEditorValue? richTextEditorValue) => RichTextPropertyEditorHelper.TryParseRichTextEditorValue(value, _jsonSerializer, _logger, out richTextEditorValue); - private RichTextEditorValue CleanAndMapBlocks(RichTextEditorValue richTextEditorValue, Action handleMapping) + private RichTextEditorValue CleanAndMapBlocks(RichTextEditorValue? richTextEditorValue, Action handleMapping) { - if (richTextEditorValue.Blocks is null) + // We handle mapping of blocks even if the edited value is empty, so property editors can clean up any resources + // relating to removed blocks, e.g. files uploaded to the media library from the file upload property editor. + BlockEditorData? blockEditorData = null; + if (richTextEditorValue?.Blocks is not null) + { + blockEditorData = ConvertAndClean(richTextEditorValue.Blocks); + } + + handleMapping(blockEditorData?.BlockValue ?? new RichTextBlockValue()); + + if (richTextEditorValue?.Blocks is null) { // no blocks defined, store empty block value return MarkupWithEmptyBlocks(); } - BlockEditorData? blockEditorData = ConvertAndClean(richTextEditorValue.Blocks); - if (blockEditorData is not null) { - handleMapping(blockEditorData.BlockValue); return new RichTextEditorValue { Markup = richTextEditorValue.Markup, @@ -402,7 +425,7 @@ public class RichTextPropertyEditor : DataEditor RichTextEditorValue MarkupWithEmptyBlocks() => new() { - Markup = richTextEditorValue.Markup, + Markup = richTextEditorValue?.Markup ?? string.Empty, Blocks = new RichTextBlockValue(), }; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/components/unsupported-property/utils.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/components/unsupported-property/utils.ts index 15bf237e9c..f313aa61d4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/components/unsupported-property/utils.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/components/unsupported-property/utils.ts @@ -1,5 +1,5 @@ import type { UmbUnsupportedEditorSchemaAliases } from '../../types/index.js'; export const UMB_UNSUPPORTED_EDITOR_SCHEMA_ALIASES: UmbUnsupportedEditorSchemaAliases = { - block: ['Umbraco.ImageCropper', 'Umbraco.UploadField'], + block: ['Umbraco.ImageCropper'], }; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityXmlSerializerTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityXmlSerializerTests.cs index d404268c3b..3661f8abaa 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityXmlSerializerTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityXmlSerializerTests.cs @@ -11,6 +11,7 @@ using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache.PropertyEditors; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; @@ -18,6 +19,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Tests.Common.Builders; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListEditorPropertyValueEditorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListEditorPropertyValueEditorTests.cs index fc56535200..32655e88b2 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListEditorPropertyValueEditorTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListEditorPropertyValueEditorTests.cs @@ -7,7 +7,9 @@ using NUnit.Framework; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Cache.PropertyEditors; using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; +using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.Models.Validation; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.PropertyEditors.ValueConverters; @@ -15,6 +17,9 @@ using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Serialization; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Extensions; using static Umbraco.Cms.Core.PropertyEditors.BlockListPropertyEditorBase; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors; @@ -22,6 +27,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors; [TestFixture] public class BlockListEditorPropertyValueEditorTests { + private static readonly Guid _contentTypeKey = Guid.NewGuid(); + private static readonly Guid _contentKey = Guid.NewGuid(); + [Test] public void Validates_Null_As_Below_Configured_Min() { @@ -84,14 +92,80 @@ public class BlockListEditorPropertyValueEditorTests Assert.IsNull(result); } - private static JsonObject CreateBlocksJson(int numberOfBlocks) + [Test] + public void FromEditor_With_Null_Current_Value_Returns_Expected_Json_Value() + { + var editedValue = CreateBlocksJson(1); + var editor = CreateValueEditor(); + + var contentPropertyData = new ContentPropertyData(editedValue, null); + + var result = editor.FromEditor(contentPropertyData, null); + AssertResultValue(result, 0, "A"); + } + + [Test] + public void FromEditor_With_Current_Value_Returns_Expected_Json_Value() + { + var editedValue = CreateBlocksJson(1, "B"); + var currentValue = CreateBlocksJson(1); + var editor = CreateValueEditor(); + + var contentPropertyData = new ContentPropertyData(editedValue, null); + + var result = editor.FromEditor(contentPropertyData, currentValue); + AssertResultValue(result, 0, "B"); + } + + [Test] + public void FromEditor_With_Block_Item_Editor_That_Uses_Current_Value_With_Edited_Property_Returns_Expected_Json_Value() + { + var editedValue = CreateBlocksJson(1, "B"); + var currentValue = CreateBlocksJson(1); + var editor = CreateValueEditor(ValueEditorSetup.ConcatenatingTextValueEditor); + + var contentPropertyData = new ContentPropertyData(editedValue, null); + + var result = editor.FromEditor(contentPropertyData, currentValue); + AssertResultValue(result, 0, "A, B"); + } + + [Test] + public void FromEditor_With_Block_Item_Editor_That_Uses_Current_Value_With_Edited_And_Added_Property_Returns_Expected_Json_Value() + { + var editedValue = CreateBlocksJson(1, "B", "C"); + var currentValue = CreateBlocksJson(1); + var editor = CreateValueEditor(ValueEditorSetup.ConcatenatingTextValueEditor); + + var contentPropertyData = new ContentPropertyData(editedValue, null); + + var result = editor.FromEditor(contentPropertyData, currentValue); + AssertResultValue(result, 0, "A, B"); + AssertResultValue(result, 1, "C"); + } + + [Test] + public void FromEditor_With_Block_Item_Editor_That_Uses_Current_Value_With_Edited_And_Removed_Property_Returns_Expected_Json_Value() + { + var editedValue = CreateBlocksJson(1, "B", "C"); + var currentValue = CreateBlocksJson(1, null); + var editor = CreateValueEditor(ValueEditorSetup.ConcatenatingTextValueEditor); + + var contentPropertyData = new ContentPropertyData(editedValue, null); + + var result = editor.FromEditor(contentPropertyData, currentValue); + AssertResultValue(result, 0, "B"); + AssertResultValue(result, 1, "C"); + } + + private static JsonObject CreateBlocksJson(int numberOfBlocks, string? blockMessagePropertyValue = "A", string? blockMessage2PropertyValue = null) { var layoutItems = new JsonArray(); var contentData = new JsonArray(); for (int i = 0; i < numberOfBlocks; i++) { layoutItems.Add(CreateLayoutBlockJson()); - contentData.Add(CreateContentDataBlockJson()); + contentData.Add(CreateContentDataBlockJson(blockMessagePropertyValue, blockMessage2PropertyValue)); } return new JsonObject @@ -110,48 +184,113 @@ public class BlockListEditorPropertyValueEditorTests new() { { "$type", "BlockListLayoutItem" }, - { "contentKey", Guid.NewGuid() }, + { "contentKey", _contentKey }, }; - private static JsonObject CreateContentDataBlockJson() => - new() + private static JsonObject CreateContentDataBlockJson(string? blockMessagePropertyValue, string? blockMessage2PropertyValue) + { + var values = new JsonArray(); + if (!string.IsNullOrEmpty(blockMessagePropertyValue)) { - { "contentTypeKey", Guid.Parse("01935a73-c86b-4521-9dcb-ad7cea402215") }, - { "key", Guid.NewGuid() }, + values.Add(new JsonObject { - "values", - new JsonArray - { - new JsonObject - { - { "editorAlias", "Umbraco.TextBox" }, - { "alias", "message" }, - { "value", "Hello" }, - }, - } - } - }; + { "editorAlias", "Umbraco.TextBox" }, + { "alias", "message" }, + { "value", blockMessagePropertyValue }, + }); + } - private static BlockListEditorPropertyValueEditor CreateValueEditor() + if (!string.IsNullOrEmpty(blockMessage2PropertyValue)) + { + values.Add(new JsonObject + { + { "editorAlias", "Umbraco.TextBox" }, + { "alias", "message2" }, + { "value", blockMessage2PropertyValue }, + }); + } + + return new() + { + { "contentTypeKey", _contentTypeKey }, + { "key", _contentKey }, + { "values", values } + }; + } + + private enum ValueEditorSetup + { + TextOnlyValueEditor, + ConcatenatingTextValueEditor, + } + + private static BlockListEditorPropertyValueEditor CreateValueEditor(ValueEditorSetup valueEditorSetup = ValueEditorSetup.TextOnlyValueEditor) { var localizedTextServiceMock = new Mock(); - localizedTextServiceMock.Setup(x => x.Localize( + localizedTextServiceMock + .Setup(x => x.Localize( It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny>())) + It.IsAny>())) .Returns((string key, string alias, CultureInfo culture, IDictionary args) => $"{key}_{alias}"); var jsonSerializer = new SystemTextJsonSerializer(); var languageService = Mock.Of(); + var dataValueEditorFactoryMock = new Mock(); + + DataEditor textBoxEditor; + switch (valueEditorSetup) + { + case ValueEditorSetup.ConcatenatingTextValueEditor: + dataValueEditorFactoryMock + .Setup(x => x.Create(It.IsAny())) + .Returns(new ConcatenatingTextValueEditor( + Mock.Of(), + new SystemTextJsonSerializer())); + textBoxEditor = new ConcatenatingTextboxPropertyEditor( + dataValueEditorFactoryMock.Object); + + break; + default: + dataValueEditorFactoryMock + .Setup(x => x.Create(It.IsAny())) + .Returns(new TextOnlyValueEditor( + new DataEditorAttribute("a"), + Mock.Of(), + Mock.Of(), + new SystemTextJsonSerializer(), + Mock.Of())); + textBoxEditor = new TextboxPropertyEditor( + dataValueEditorFactoryMock.Object, + Mock.Of()); + break; + } + + var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(() => textBoxEditor.Yield())); + + var elementType = new ContentTypeBuilder() + .WithKey(_contentTypeKey) + .AddPropertyType() + .WithAlias("message") + .Done() + .AddPropertyType() + .WithAlias("message2") + .Done() + .Build(); + var elementTypeCacheMock = new Mock(); + elementTypeCacheMock + .Setup(x => x.GetMany(It.Is>(y => y.First() == _contentTypeKey))) + .Returns([elementType]); + return new BlockListEditorPropertyValueEditor( new DataEditorAttribute("alias"), new BlockListEditorDataConverter(jsonSerializer), - new(new DataEditorCollection(() => [])), + propertyEditors, new DataValueReferenceFactoryCollection(Enumerable.Empty, Mock.Of>()), Mock.Of(), - Mock.Of(), + elementTypeCacheMock.Object, localizedTextServiceMock.Object, new NullLogger(), Mock.Of(), @@ -171,4 +310,62 @@ public class BlockListEditorPropertyValueEditorTests }, }; } + + /// + /// An illustrative property editor that uses the edited and current value when returning a result from the FromEditor calls. + /// + /// + /// This is used to simulate a real-world editor that needs to use this value from within the block editor and verify + /// that it receives and processes the value. + /// + [DataEditor( + global::Umbraco.Cms.Core.Constants.PropertyEditors.Aliases.TextBox)] + private class ConcatenatingTextboxPropertyEditor : DataEditor + { + public ConcatenatingTextboxPropertyEditor(IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) + { + } + + protected override IDataValueEditor CreateValueEditor() => + DataValueEditorFactory.Create(Attribute!); + } + + /// + /// An illustrative value editor that uses the edited and current value when returning a result from the FromEditor calls. + /// + /// + /// See notes on . + /// + private class ConcatenatingTextValueEditor : DataValueEditor + { + public ConcatenatingTextValueEditor(IShortStringHelper shortStringHelper, IJsonSerializer? jsonSerializer) + : base(shortStringHelper, jsonSerializer) + { + } + + public override object FromEditor(ContentPropertyData propertyData, object? currentValue) + { + var values = new List(); + if (currentValue is not null) + { + values.Add(currentValue.ToString()); + } + + var editedValue = propertyData.Value; + if (editedValue is not null) + { + values.Add(editedValue.ToString()); + } + + return string.Join(", ", values); + } + } + + private static void AssertResultValue(object? result, int valueIndex, string expectedValue) + { + Assert.IsNotNull(result); + var resultAsJson = (JsonObject)JsonNode.Parse(result.ToString()); + Assert.AreEqual(expectedValue, resultAsJson["contentData"][0]["values"][valueIndex]["value"].ToString()); + } } From 49b95c1225701feb87f85b63b84e8ea14afe7aa8 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 30 Jun 2025 13:24:00 +0200 Subject: [PATCH 48/82] Introduce Section Alias condition const (#19633) * Refactor section conditions into subfolders Split section condition logic into 'section-alias' and 'section-user-permission' subfolders, each with their own constants, manifests, and types. Updated imports and manifest aggregation to use the new structure for improved modularity and maintainability. * use const * fix build * Refactor section alias condition to use constant Replaces hardcoded 'Umb.Condition.SectionAlias' strings with the UMB_SECTION_ALIAS_CONDITION_ALIAS constant across all manifests and related files. This improves maintainability and consistency by centralizing the section alias condition reference. --- .github/contributing-backoffice.md | 2 +- .../collection/collection/card-view/index.ts | 1 - .../collection/collection/table-view/index.ts | 1 - .../examples/validation-context/index.ts | 3 +- .../core/section/conditions/constants.ts | 3 +- .../core/section/conditions/manifests.ts | 20 ++-------- .../conditions/section-alias/constants.ts | 1 + .../conditions/section-alias/manifests.ts | 11 ++++++ .../section-alias.condition.ts | 6 +-- .../section/conditions/section-alias/types.ts | 22 +++++++++++ .../section-user-permission/constants.ts | 1 + .../section-user-permission/manifests.ts | 11 ++++++ .../section-user-permission.condition.test.ts | 4 +- .../section-user-permission.condition.ts | 2 +- .../section-user-permission/types.ts | 20 ++++++++++ .../packages/core/section/conditions/types.ts | 37 +------------------ .../dictionary/dashboard/manifests.ts | 3 +- .../document-redirect-management/manifests.ts | 3 +- .../packages/documents/section/manifests.ts | 7 +++- .../src/packages/health-check/manifests.ts | 3 +- .../language/app-language-select/manifests.ts | 3 +- .../packages/media/media-section/manifests.ts | 7 +++- .../media/media/dashboard/manifests.ts | 4 +- .../members/section/sidebar-app/manifests.ts | 3 +- .../src/packages/models-builder/manifests.ts | 3 +- .../packages/package-section/manifests.ts | 11 ++++-- .../performance-profiling/manifests.ts | 3 +- .../src/packages/publish-cache/manifests.ts | 3 +- .../src/packages/search/manifests.ts | 3 +- .../packages/settings/advanced/manifests.ts | 3 +- .../packages/settings/structure/manifests.ts | 3 +- .../packages/settings/welcome/manifests.ts | 3 +- .../src/packages/telemetry/manifests.ts | 3 +- .../src/packages/templating/menu/manifests.ts | 3 +- .../packages/translation/menu/manifests.ts | 3 +- .../src/packages/umbraco-news/manifests.ts | 3 +- .../user/section/sidebar-app/manifests.ts | 3 +- 37 files changed, 136 insertions(+), 89 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/examples/collection/collection/card-view/index.ts delete mode 100644 src/Umbraco.Web.UI.Client/examples/collection/collection/table-view/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/manifests.ts rename src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/{ => section-alias}/section-alias.condition.ts (82%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission/manifests.ts rename src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/{ => section-user-permission}/section-user-permission.condition.test.ts (95%) rename src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/{ => section-user-permission}/section-user-permission.condition.ts (92%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission/types.ts diff --git a/.github/contributing-backoffice.md b/.github/contributing-backoffice.md index 21993dd92f..76b8246fff 100644 --- a/.github/contributing-backoffice.md +++ b/.github/contributing-backoffice.md @@ -114,7 +114,7 @@ To declare the Published Cache Status Dashboard as a new manifest, we need to ad }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: 'Umb.Section.Settings', }, ], diff --git a/src/Umbraco.Web.UI.Client/examples/collection/collection/card-view/index.ts b/src/Umbraco.Web.UI.Client/examples/collection/collection/card-view/index.ts deleted file mode 100644 index e9d6c50fc5..0000000000 --- a/src/Umbraco.Web.UI.Client/examples/collection/collection/card-view/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { UMB_LANGUAGE_TABLE_COLLECTION_VIEW_ALIAS } from './manifests.js'; diff --git a/src/Umbraco.Web.UI.Client/examples/collection/collection/table-view/index.ts b/src/Umbraco.Web.UI.Client/examples/collection/collection/table-view/index.ts deleted file mode 100644 index e9d6c50fc5..0000000000 --- a/src/Umbraco.Web.UI.Client/examples/collection/collection/table-view/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { UMB_LANGUAGE_TABLE_COLLECTION_VIEW_ALIAS } from './manifests.js'; diff --git a/src/Umbraco.Web.UI.Client/examples/validation-context/index.ts b/src/Umbraco.Web.UI.Client/examples/validation-context/index.ts index d21809b882..31668d7237 100644 --- a/src/Umbraco.Web.UI.Client/examples/validation-context/index.ts +++ b/src/Umbraco.Web.UI.Client/examples/validation-context/index.ts @@ -1,4 +1,5 @@ import type { ManifestDashboard } from '@umbraco-cms/backoffice/dashboard'; +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; const dashboard: ManifestDashboard = { type: 'dashboard', @@ -12,7 +13,7 @@ const dashboard: ManifestDashboard = { }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: 'Umb.Section.Content', }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/constants.ts index b09ab84caf..36d4b2350f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/constants.ts @@ -1 +1,2 @@ -export const UMB_SECTION_USER_PERMISSION_CONDITION_ALIAS = 'Umb.Condition.SectionUserPermission'; +export * from './section-alias/constants.js'; +export * from './section-user-permission/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/manifests.ts index 8e11e8eeb0..68e9e244a8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/manifests.ts @@ -1,18 +1,4 @@ -import { UMB_SECTION_USER_PERMISSION_CONDITION_ALIAS } from './constants.js'; -import { UmbSectionAliasCondition } from './section-alias.condition.js'; -import { UmbSectionUserPermissionCondition } from './section-user-permission.condition.js'; +import { manifests as sectionAliasManifests } from './section-alias/manifests.js'; +import { manifests as sectionUserPermissionManifests } from './section-user-permission/manifests.js'; -export const manifests: Array = [ - { - type: 'condition', - name: 'Section User Permission Condition', - alias: UMB_SECTION_USER_PERMISSION_CONDITION_ALIAS, - api: UmbSectionUserPermissionCondition, - }, - { - type: 'condition', - name: 'Section Alias Condition', - alias: 'Umb.Condition.SectionAlias', - api: UmbSectionAliasCondition, - }, -]; +export const manifests: Array = [...sectionAliasManifests, ...sectionUserPermissionManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/constants.ts new file mode 100644 index 0000000000..e097c49be4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/constants.ts @@ -0,0 +1 @@ +export const UMB_SECTION_ALIAS_CONDITION_ALIAS = 'Umb.Condition.SectionAlias'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/manifests.ts new file mode 100644 index 0000000000..bb24bca85a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/manifests.ts @@ -0,0 +1,11 @@ +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from './constants.js'; +import { UmbSectionAliasCondition } from './section-alias.condition.js'; + +export const manifests: Array = [ + { + type: 'condition', + name: 'Section Alias Condition', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, + api: UmbSectionAliasCondition, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/section-alias.condition.ts similarity index 82% rename from src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias.condition.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/section-alias.condition.ts index 9a18d0ce15..f18f3e75f6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias.condition.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/section-alias.condition.ts @@ -1,6 +1,6 @@ -import { UmbConditionBase } from '../../extension-registry/conditions/condition-base.controller.js'; -import { UMB_SECTION_CONTEXT } from '../section.context.js'; -import type { SectionAliasConditionConfig } from './types.js'; +import { UmbConditionBase } from '../../../extension-registry/conditions/condition-base.controller.js'; +import { UMB_SECTION_CONTEXT } from '../../section.context.js'; +import type { SectionAliasConditionConfig } from '../types.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/types.ts new file mode 100644 index 0000000000..5ef40e419d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/types.ts @@ -0,0 +1,22 @@ +import type { UMB_SECTION_ALIAS_CONDITION_ALIAS } from './constants.js'; +import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; + +export type SectionAliasConditionConfig = UmbConditionConfigBase & { + /** + * Define the section that this extension should be available in + * @example "Umb.Section.Content" + */ + match: string; + /** + * Define one or more workspaces that this extension should be available in + * @example + * ["Umb.Section.Content", "Umb.Section.Media"] + */ + oneOf?: Array; +}; + +declare global { + interface UmbExtensionConditionConfigMap { + UmbSectionAliasConditionConfig: SectionAliasConditionConfig; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission/constants.ts new file mode 100644 index 0000000000..b09ab84caf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission/constants.ts @@ -0,0 +1 @@ +export const UMB_SECTION_USER_PERMISSION_CONDITION_ALIAS = 'Umb.Condition.SectionUserPermission'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission/manifests.ts new file mode 100644 index 0000000000..ba819fa346 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission/manifests.ts @@ -0,0 +1,11 @@ +import { UmbSectionUserPermissionCondition } from './section-user-permission.condition.js'; +import { UMB_SECTION_USER_PERMISSION_CONDITION_ALIAS } from './constants.js'; + +export const manifests: Array = [ + { + type: 'condition', + name: 'Section User Permission Condition', + alias: UMB_SECTION_USER_PERMISSION_CONDITION_ALIAS, + api: UmbSectionUserPermissionCondition, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission.condition.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission/section-user-permission.condition.test.ts similarity index 95% rename from src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission.condition.test.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission/section-user-permission.condition.test.ts index 59f3f92c86..c4c87f6624 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission.condition.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission/section-user-permission.condition.test.ts @@ -3,8 +3,8 @@ import { UmbNotificationContext } from '@umbraco-cms/backoffice/notification'; import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; import { customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbCurrentUserContext, UmbCurrentUserStore } from '@umbraco-cms/backoffice/current-user'; -import { UmbSectionUserPermissionCondition } from './section-user-permission.condition'; -import { UMB_SECTION_USER_PERMISSION_CONDITION_ALIAS } from './constants.js'; +import { UmbSectionUserPermissionCondition } from './section-user-permission.condition.js'; +import { UMB_SECTION_USER_PERMISSION_CONDITION_ALIAS } from '../constants.js'; @customElement('test-controller-host') class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission/section-user-permission.condition.ts similarity index 92% rename from src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission.condition.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission/section-user-permission.condition.ts index dc753e563d..3fc4518c56 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission.condition.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission/section-user-permission.condition.ts @@ -1,4 +1,4 @@ -import type { UmbSectionUserPermissionConditionConfig } from './types.js'; +import type { UmbSectionUserPermissionConditionConfig } from '../types.js'; import { UMB_CURRENT_USER_CONTEXT } from '@umbraco-cms/backoffice/current-user'; import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission/types.ts new file mode 100644 index 0000000000..44b032d217 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-user-permission/types.ts @@ -0,0 +1,20 @@ +import type { UMB_SECTION_USER_PERMISSION_CONDITION_ALIAS } from './constants.js'; +import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; + +export type UmbSectionUserPermissionConditionConfig = UmbConditionConfigBase< + typeof UMB_SECTION_USER_PERMISSION_CONDITION_ALIAS +> & { + /** + * + * + * @example + * "Umb.Section.Content" + */ + match: string; +}; + +declare global { + interface UmbExtensionConditionConfigMap { + UmbSectionUserPermissionConditionConfig: UmbSectionUserPermissionConditionConfig; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/types.ts index f1f8f6ab52..02345913ab 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/types.ts @@ -1,35 +1,2 @@ -import type { UMB_SECTION_USER_PERMISSION_CONDITION_ALIAS } from './constants.js'; -import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; - -export type UmbSectionUserPermissionConditionConfig = UmbConditionConfigBase< - typeof UMB_SECTION_USER_PERMISSION_CONDITION_ALIAS -> & { - /** - * - * - * @example - * "Umb.Section.Content" - */ - match: string; -}; - -export type SectionAliasConditionConfig = UmbConditionConfigBase<'Umb.Condition.SectionAlias'> & { - /** - * Define the section that this extension should be available in - * @example "Umb.Section.Content" - */ - match: string; - /** - * Define one or more workspaces that this extension should be available in - * @example - * ["Umb.Section.Content", "Umb.Section.Media"] - */ - oneOf?: Array; -}; - -declare global { - interface UmbExtensionConditionConfigMap { - UmbSectionUserPermissionConditionConfig: UmbSectionUserPermissionConditionConfig; - UmbSectionAliasConditionConfig: SectionAliasConditionConfig; - } -} +export type * from './section-alias/types.js'; +export type * from './section-user-permission/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dashboard/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dashboard/manifests.ts index 3d3f01c245..f5fbb3d1e6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dashboard/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dashboard/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; import { UMB_TRANSLATION_SECTION_ALIAS } from '@umbraco-cms/backoffice/translation'; export const manifests: Array = [ @@ -12,7 +13,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: UMB_TRANSLATION_SECTION_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-redirect-management/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-redirect-management/manifests.ts index 57d82f47f6..e8ba15e16a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-redirect-management/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-redirect-management/manifests.ts @@ -1,4 +1,5 @@ import { UMB_CONTENT_SECTION_ALIAS } from '@umbraco-cms/backoffice/content'; +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; export const manifests: Array = [ { @@ -13,7 +14,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: UMB_CONTENT_SECTION_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/section/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/section/manifests.ts index 46d3001e26..25a18c1917 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/section/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/section/manifests.ts @@ -1,6 +1,9 @@ import { UMB_CONTENT_SECTION_ALIAS } from '@umbraco-cms/backoffice/content'; import { UMB_DOCUMENT_ROOT_ENTITY_TYPE, UMB_CONTENT_MENU_ALIAS } from '@umbraco-cms/backoffice/document'; -import { UMB_SECTION_USER_PERMISSION_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; +import { + UMB_SECTION_ALIAS_CONDITION_ALIAS, + UMB_SECTION_USER_PERMISSION_CONDITION_ALIAS, +} from '@umbraco-cms/backoffice/section'; export const manifests: Array = [ { @@ -32,7 +35,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: UMB_CONTENT_SECTION_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/health-check/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/health-check/manifests.ts index f383dded95..cf14adac5e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/health-check/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/health-check/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; import { UMB_SETTINGS_SECTION_ALIAS } from '@umbraco-cms/backoffice/settings'; export const manifests: Array = [ @@ -14,7 +15,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: UMB_SETTINGS_SECTION_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/language/app-language-select/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/language/app-language-select/manifests.ts index 5bb2dc5fd6..b1fbead97f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/language/app-language-select/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/language/app-language-select/manifests.ts @@ -1,4 +1,5 @@ import { UMB_CONTENT_SECTION_ALIAS } from '@umbraco-cms/backoffice/content'; +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; export const manifests: Array = [ { type: 'sectionSidebarApp', @@ -8,7 +9,7 @@ export const manifests: Array = [ weight: 900, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: UMB_CONTENT_SECTION_ALIAS, }, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-section/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-section/manifests.ts index d1fdb8bf24..23791a4c75 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-section/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-section/manifests.ts @@ -1,5 +1,8 @@ import { UMB_MEDIA_ROOT_ENTITY_TYPE, UMB_MEDIA_MENU_ALIAS } from '../media/index.js'; -import { UMB_SECTION_USER_PERMISSION_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; +import { + UMB_SECTION_ALIAS_CONDITION_ALIAS, + UMB_SECTION_USER_PERMISSION_CONDITION_ALIAS, +} from '@umbraco-cms/backoffice/section'; const sectionAlias = 'Umb.Section.Media'; @@ -33,7 +36,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: sectionAlias, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dashboard/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dashboard/manifests.ts index 0bfa3c6797..f08955e9fd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/dashboard/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dashboard/manifests.ts @@ -1,3 +1,5 @@ +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; + export const manifests: Array = [ { type: 'dashboard', @@ -11,7 +13,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: 'Umb.Section.Media', }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/section/sidebar-app/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/section/sidebar-app/manifests.ts index ed9177516e..e814353f00 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/section/sidebar-app/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/section/sidebar-app/manifests.ts @@ -1,5 +1,6 @@ import { UMB_MEMBER_MANAGEMENT_SECTION_ALIAS } from '../constants.js'; import { UMB_MEMBER_MANAGEMENT_MENU_ALIAS } from '../menu/index.js'; +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; export const manifests: Array = [ { @@ -14,7 +15,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: UMB_MEMBER_MANAGEMENT_SECTION_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/models-builder/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/models-builder/manifests.ts index 862ab234b4..c74ca1cd2c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/models-builder/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/models-builder/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; import { UMB_SETTINGS_SECTION_ALIAS } from '@umbraco-cms/backoffice/settings'; export const manifests: Array = [ @@ -13,7 +14,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: UMB_SETTINGS_SECTION_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/packages/package-section/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/packages/package-section/manifests.ts index 0dd57300c3..1118433040 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/packages/package-section/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/packages/package-section/manifests.ts @@ -1,4 +1,7 @@ -import { UMB_SECTION_USER_PERMISSION_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; +import { + UMB_SECTION_ALIAS_CONDITION_ALIAS, + UMB_SECTION_USER_PERMISSION_CONDITION_ALIAS, +} from '@umbraco-cms/backoffice/section'; const sectionAlias = 'Umb.Section.Packages'; @@ -32,7 +35,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: sectionAlias, }, ], @@ -50,7 +53,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: sectionAlias, }, ], @@ -68,7 +71,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: sectionAlias, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/performance-profiling/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/performance-profiling/manifests.ts index 67f5747cae..51b65e2308 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/performance-profiling/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/performance-profiling/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; import { UMB_SETTINGS_SECTION_ALIAS } from '@umbraco-cms/backoffice/settings'; export const manifests = [ @@ -13,7 +14,7 @@ export const manifests = [ }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: UMB_SETTINGS_SECTION_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/publish-cache/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/publish-cache/manifests.ts index 203dfd16fb..37edcd745d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/publish-cache/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/publish-cache/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; import { UMB_SETTINGS_SECTION_ALIAS } from '@umbraco-cms/backoffice/settings'; export const manifests = [ @@ -13,7 +14,7 @@ export const manifests = [ }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: UMB_SETTINGS_SECTION_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/search/manifests.ts index 92e972641c..bcdee40ae3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/search/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/search/manifests.ts @@ -1,4 +1,5 @@ import { manifests as examineManifests } from './examine-management-dashboard/manifests.js'; +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; import { UMB_SETTINGS_SECTION_ALIAS } from '@umbraco-cms/backoffice/settings'; export const manifests: Array = [ @@ -32,7 +33,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: UMB_SETTINGS_SECTION_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/advanced/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/advanced/manifests.ts index c94fa669de..e36e2846e0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/advanced/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/advanced/manifests.ts @@ -1,5 +1,6 @@ import { UMB_SETTINGS_SECTION_ALIAS } from '../constants.js'; import { UMB_ADVANCED_SETTINGS_MENU_ALIAS } from './constants.js'; +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; export const manifests = [ { @@ -19,7 +20,7 @@ export const manifests = [ }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: UMB_SETTINGS_SECTION_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/structure/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/structure/manifests.ts index f66f848986..e9cbb29f10 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/structure/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/structure/manifests.ts @@ -1,5 +1,6 @@ import { UMB_SETTINGS_SECTION_ALIAS } from '../constants.js'; import { UMB_STRUCTURE_SETTINGS_MENU_ALIAS } from './constants.js'; +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; export const manifests: Array = [ @@ -20,7 +21,7 @@ export const manifests: Array = }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: UMB_SETTINGS_SECTION_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/welcome/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/welcome/manifests.ts index ae001f27cd..c7f1d141f4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/welcome/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/welcome/manifests.ts @@ -1,4 +1,5 @@ import { UMB_SETTINGS_SECTION_ALIAS } from '../constants.js'; +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; export const manifests: Array = [ @@ -14,7 +15,7 @@ export const manifests: Array = }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: UMB_SETTINGS_SECTION_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/telemetry/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/telemetry/manifests.ts index 1d179068fc..05bba7100c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/telemetry/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/telemetry/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; import { UMB_SETTINGS_SECTION_ALIAS } from '@umbraco-cms/backoffice/settings'; export const manifests = [ @@ -13,7 +14,7 @@ export const manifests = [ }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: UMB_SETTINGS_SECTION_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/menu/manifests.ts index 7ff3fe0b79..b204c22dac 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/menu/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; import { UMB_SETTINGS_SECTION_ALIAS } from '@umbraco-cms/backoffice/settings'; export const manifests: Array = [ @@ -18,7 +19,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: UMB_SETTINGS_SECTION_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/translation/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/translation/menu/manifests.ts index 67b9c47ac6..9022a60266 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/translation/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/translation/menu/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; import { UMB_TRANSLATION_SECTION_ALIAS } from '../section/index.js'; import { UMB_TRANSLATION_MENU_ALIAS } from './constants.js'; @@ -20,7 +21,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: UMB_TRANSLATION_SECTION_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/manifests.ts index 202efc257c..4621106db5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/manifests.ts @@ -1,5 +1,6 @@ import type { ManifestDashboard } from '@umbraco-cms/backoffice/dashboard'; import { UMB_CONTENT_SECTION_ALIAS } from '@umbraco-cms/backoffice/content'; +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; export const dashboard: ManifestDashboard = { type: 'dashboard', @@ -12,7 +13,7 @@ export const dashboard: ManifestDashboard = { }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: UMB_CONTENT_SECTION_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/section/sidebar-app/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/section/sidebar-app/manifests.ts index 4b5fcd9bb2..cba6d25c4f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/section/sidebar-app/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/section/sidebar-app/manifests.ts @@ -1,5 +1,6 @@ import { UMB_USER_MANAGEMENT_SECTION_ALIAS } from '../constants.js'; import { UMB_USER_MANAGEMENT_MENU_ALIAS } from '../menu/index.js'; +import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; export const manifests: Array = [ { @@ -14,7 +15,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', + alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, match: UMB_USER_MANAGEMENT_SECTION_ALIAS, }, ], From ba2072c9795fd4451b53f9709d39eeea9dcf068c Mon Sep 17 00:00:00 2001 From: Davor Zlotrg Date: Mon, 30 Jun 2025 14:07:00 +0200 Subject: [PATCH 49/82] fix: json serialization and deserialization for NuCacheSerializerType (#19617) fix: json serialization and deserialization --- .../JsonContentNestedDataSerializer.cs | 8 +++++-- .../Serialization/JsonObjectConverter.cs | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.PublishedCache.HybridCache/Serialization/JsonObjectConverter.cs diff --git a/src/Umbraco.PublishedCache.HybridCache/Serialization/JsonContentNestedDataSerializer.cs b/src/Umbraco.PublishedCache.HybridCache/Serialization/JsonContentNestedDataSerializer.cs index a580b07b37..cfe1fb2425 100644 --- a/src/Umbraco.PublishedCache.HybridCache/Serialization/JsonContentNestedDataSerializer.cs +++ b/src/Umbraco.PublishedCache.HybridCache/Serialization/JsonContentNestedDataSerializer.cs @@ -1,4 +1,4 @@ -using System.Text.Json; +using System.Text.Json; using System.Text.Json.Serialization; using Umbraco.Cms.Core.Models; @@ -8,7 +8,11 @@ internal class JsonContentNestedDataSerializer : IContentCacheDataSerializer { private static readonly JsonSerializerOptions _jsonSerializerOptions = new() { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + Converters = + { + new JsonObjectConverter(), + }, }; /// diff --git a/src/Umbraco.PublishedCache.HybridCache/Serialization/JsonObjectConverter.cs b/src/Umbraco.PublishedCache.HybridCache/Serialization/JsonObjectConverter.cs new file mode 100644 index 0000000000..a28ccf0562 --- /dev/null +++ b/src/Umbraco.PublishedCache.HybridCache/Serialization/JsonObjectConverter.cs @@ -0,0 +1,22 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.HybridCache.Serialization; + +internal class JsonObjectConverter : JsonConverter +{ + public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => reader.TokenType switch + { + JsonTokenType.String => reader.GetString(), + JsonTokenType.Number => reader.TryGetInt64(out var l) ? l : reader.GetDouble(), + JsonTokenType.True => true, + JsonTokenType.False => false, + JsonTokenType.Null => null, + _ => JsonDocument.ParseValue(ref reader).RootElement.Clone(), // fallback for complex types + }; + + public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) + => JsonSerializer.Serialize(writer, value, value.GetType(), options); +} From e78eee50edce537f81534ff713ff3bd820409a6f Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 30 Jun 2025 13:56:51 +0200 Subject: [PATCH 50/82] Minor refactor following merge of PR #19617. --- .../IContentCacheDataSerializer.cs | 8 +++--- .../JsonContentNestedDataSerializer.cs | 26 +++++++++++++++++++ .../Serialization/JsonObjectConverter.cs | 22 ---------------- .../MsgPackContentNestedDataSerializer.cs | 20 ++++++++------ 4 files changed, 42 insertions(+), 34 deletions(-) delete mode 100644 src/Umbraco.PublishedCache.HybridCache/Serialization/JsonObjectConverter.cs diff --git a/src/Umbraco.PublishedCache.HybridCache/Serialization/IContentCacheDataSerializer.cs b/src/Umbraco.PublishedCache.HybridCache/Serialization/IContentCacheDataSerializer.cs index a46c667a4d..91c32d6161 100644 --- a/src/Umbraco.PublishedCache.HybridCache/Serialization/IContentCacheDataSerializer.cs +++ b/src/Umbraco.PublishedCache.HybridCache/Serialization/IContentCacheDataSerializer.cs @@ -1,9 +1,9 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Infrastructure.HybridCache.Serialization; /// -/// Serializes/Deserializes document to the SQL Database as a string +/// Serializes/Deserializes document to the SQL Database as a string. /// /// /// Resolved from the . This cannot be resolved from DI. @@ -11,12 +11,12 @@ namespace Umbraco.Cms.Infrastructure.HybridCache.Serialization; internal interface IContentCacheDataSerializer { /// - /// Deserialize the data into a + /// Deserialize the data into a . /// ContentCacheDataModel? Deserialize(IReadOnlyContentBase content, string? stringData, byte[]? byteData, bool published); /// - /// Serializes the + /// Serializes the . /// ContentCacheDataSerializationResult Serialize(IReadOnlyContentBase content, ContentCacheDataModel model, bool published); } diff --git a/src/Umbraco.PublishedCache.HybridCache/Serialization/JsonContentNestedDataSerializer.cs b/src/Umbraco.PublishedCache.HybridCache/Serialization/JsonContentNestedDataSerializer.cs index cfe1fb2425..1bb4822ced 100644 --- a/src/Umbraco.PublishedCache.HybridCache/Serialization/JsonContentNestedDataSerializer.cs +++ b/src/Umbraco.PublishedCache.HybridCache/Serialization/JsonContentNestedDataSerializer.cs @@ -4,6 +4,9 @@ using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Infrastructure.HybridCache.Serialization; +/// +/// Serializes/deserializes documents to the SQL Database as JSON. +/// internal class JsonContentNestedDataSerializer : IContentCacheDataSerializer { private static readonly JsonSerializerOptions _jsonSerializerOptions = new() @@ -40,4 +43,27 @@ internal class JsonContentNestedDataSerializer : IContentCacheDataSerializer var json = JsonSerializer.Serialize(model, _jsonSerializerOptions); return new ContentCacheDataSerializationResult(json, null); } + + /// + /// Provides a converter for handling JSON objects that can be of various types (string, number, boolean, null, or complex types). + /// + internal class JsonObjectConverter : JsonConverter + { + /// + public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => reader.TokenType switch + { + JsonTokenType.String => reader.GetString(), + JsonTokenType.Number => reader.TryGetInt64(out var value) ? value : reader.GetDouble(), + JsonTokenType.True => true, + JsonTokenType.False => false, + JsonTokenType.Null => null, + _ => JsonDocument.ParseValue(ref reader).RootElement.Clone(), // fallback for complex types + }; + + /// + public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) + => JsonSerializer.Serialize(writer, value, value.GetType(), options); + } + } diff --git a/src/Umbraco.PublishedCache.HybridCache/Serialization/JsonObjectConverter.cs b/src/Umbraco.PublishedCache.HybridCache/Serialization/JsonObjectConverter.cs deleted file mode 100644 index a28ccf0562..0000000000 --- a/src/Umbraco.PublishedCache.HybridCache/Serialization/JsonObjectConverter.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Infrastructure.HybridCache.Serialization; - -internal class JsonObjectConverter : JsonConverter -{ - public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - => reader.TokenType switch - { - JsonTokenType.String => reader.GetString(), - JsonTokenType.Number => reader.TryGetInt64(out var l) ? l : reader.GetDouble(), - JsonTokenType.True => true, - JsonTokenType.False => false, - JsonTokenType.Null => null, - _ => JsonDocument.ParseValue(ref reader).RootElement.Clone(), // fallback for complex types - }; - - public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) - => JsonSerializer.Serialize(writer, value, value.GetType(), options); -} diff --git a/src/Umbraco.PublishedCache.HybridCache/Serialization/MsgPackContentNestedDataSerializer.cs b/src/Umbraco.PublishedCache.HybridCache/Serialization/MsgPackContentNestedDataSerializer.cs index ec4e047d29..9962b07da9 100644 --- a/src/Umbraco.PublishedCache.HybridCache/Serialization/MsgPackContentNestedDataSerializer.cs +++ b/src/Umbraco.PublishedCache.HybridCache/Serialization/MsgPackContentNestedDataSerializer.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; using K4os.Compression.LZ4; using MessagePack; using MessagePack.Resolvers; @@ -8,14 +8,17 @@ using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Cms.Infrastructure.HybridCache.Serialization; /// -/// Serializes/Deserializes document to the SQL Database as bytes using -/// MessagePack +/// Serializes/deserializes documents to the SQL Database as bytes using +/// MessagePack. /// internal sealed class MsgPackContentNestedDataSerializer : IContentCacheDataSerializer { private readonly MessagePackSerializerOptions _options; private readonly IPropertyCacheCompression _propertyOptions; + /// + /// Initializes a new instance of the class. + /// public MsgPackContentNestedDataSerializer(IPropertyCacheCompression propertyOptions) { _propertyOptions = propertyOptions ?? throw new ArgumentNullException(nameof(propertyOptions)); @@ -40,6 +43,7 @@ internal sealed class MsgPackContentNestedDataSerializer : IContentCacheDataSeri .WithSecurity(MessagePackSecurity.UntrustedData); } + /// public ContentCacheDataModel? Deserialize(IReadOnlyContentBase content, string? stringData, byte[]? byteData, bool published) { if (byteData != null) @@ -62,6 +66,7 @@ internal sealed class MsgPackContentNestedDataSerializer : IContentCacheDataSeri return null; } + /// public ContentCacheDataSerializationResult Serialize(IReadOnlyContentBase content, ContentCacheDataModel model, bool published) { Compress(content, model, published); @@ -69,11 +74,10 @@ internal sealed class MsgPackContentNestedDataSerializer : IContentCacheDataSeri return new ContentCacheDataSerializationResult(null, bytes); } - public string ToJson(byte[] bin) - { - var json = MessagePackSerializer.ConvertToJson(bin, _options); - return json; - } + /// + /// Converts the binary MessagePack data to a JSON string representation. + /// + public string ToJson(byte[] bin) => MessagePackSerializer.ConvertToJson(bin, _options); /// /// Used during serialization to compress properties From 1f28f1043a5d8e2fc105c3d309d210e950b93013 Mon Sep 17 00:00:00 2001 From: Henrik Date: Mon, 30 Jun 2025 14:15:22 +0200 Subject: [PATCH 51/82] Avoid async await Task.FromResult, plus some other minor tweaks (#19597) --- .../RebuildPublishedCacheController.cs | 6 ++--- .../ContentTypeEditingServiceBase.cs | 26 +++++++++---------- .../ElementSwitchValidator.cs | 14 +++++----- src/Umbraco.Core/Services/DataTypeService.cs | 18 ++++++------- .../Services/EntityTypeContainerService.cs | 8 +++--- .../Services/Querying/ContentQueryService.cs | 12 ++++----- src/Umbraco.Core/Services/RelationService.cs | 14 +++++----- src/Umbraco.Core/Strings/Diff.cs | 2 +- 8 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/RebuildPublishedCacheController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/RebuildPublishedCacheController.cs index 392c9338db..8f9cd490eb 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/RebuildPublishedCacheController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/RebuildPublishedCacheController.cs @@ -15,7 +15,7 @@ public class RebuildPublishedCacheController : PublishedCacheControllerBase [HttpPost("rebuild")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task Rebuild(CancellationToken cancellationToken) + public Task Rebuild(CancellationToken cancellationToken) { if (_databaseCacheRebuilder.IsRebuilding()) { @@ -27,10 +27,10 @@ public class RebuildPublishedCacheController : PublishedCacheControllerBase Type = "Error", }; - return await Task.FromResult(Conflict(problemDetails)); + return Task.FromResult(Conflict(problemDetails)); } _databaseCacheRebuilder.Rebuild(true); - return await Task.FromResult(Ok()); + return Task.FromResult(Ok()); } } diff --git a/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingServiceBase.cs b/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingServiceBase.cs index fdf160ef4f..680d78d5ea 100644 --- a/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingServiceBase.cs +++ b/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingServiceBase.cs @@ -50,7 +50,7 @@ internal abstract class ContentTypeEditingServiceBase currentCompositeKeys.Contains(ct.Key)).Select(ct => ct.Alias).ToArray() - : Array.Empty(); + : []; ContentTypeAvailableCompositionsResults availableCompositions = _contentTypeService.GetAvailableCompositeContentTypes( contentType, @@ -142,9 +142,9 @@ internal abstract class ContentTypeEditingServiceBase(ContentTypeOperationStatus.Success, contentType); } - protected virtual async Task AdditionalCreateValidationAsync( + protected virtual Task AdditionalCreateValidationAsync( ContentTypeEditingModelBase model) - => await Task.FromResult(ContentTypeOperationStatus.Success); + => Task.FromResult(ContentTypeOperationStatus.Success); #region Sanitization @@ -346,7 +346,7 @@ internal abstract class ContentTypeEditingServiceBase model, IContentTypeComposition[] allContentTypeCompositions) + private static ContentTypeOperationStatus ValidateProperties(ContentTypeEditingModelBase model, IContentTypeComposition[] allContentTypeCompositions) { // grab all content types used for composition and/or inheritance Guid[] allCompositionKeys = KeysForCompositionTypes(model, CompositionType.Composition, CompositionType.Inheritance); @@ -365,7 +365,7 @@ internal abstract class ContentTypeEditingServiceBase model, IContentTypeComposition[] allContentTypeCompositions) + private static ContentTypeOperationStatus ValidateContainers(ContentTypeEditingModelBase model, IContentTypeComposition[] allContentTypeCompositions) { if (model.Containers.Any(container => Enum.TryParse(container.Type, out _) is false)) { @@ -420,7 +420,7 @@ internal abstract class ContentTypeEditingServiceBase model, IContentTypeComposition[] allContentTypeCompositions) @@ -573,7 +573,7 @@ internal abstract class ContentTypeEditingServiceBase model, IContentTypeComposition[] allContentTypeCompositions) @@ -658,7 +658,7 @@ internal abstract class ContentTypeEditingServiceBase model, IContentTypeComposition[] allContentTypeCompositions) @@ -677,7 +677,7 @@ internal abstract class ContentTypeEditingServiceBase model) + private static Guid[] GetDataTypeKeys(ContentTypeEditingModelBase model) => model.Properties.Select(property => property.DataTypeKey).Distinct().ToArray(); private async Task GetDataTypesAsync(ContentTypeEditingModelBase model) @@ -685,7 +685,7 @@ internal abstract class ContentTypeEditingServiceBase(); + : []; } private int? GetParentId(ContentTypeEditingModelBase model, Guid? containerKey) @@ -711,7 +711,7 @@ internal abstract class ContentTypeEditingServiceBase model, params CompositionType[] compositionTypes) + private static Guid[] KeysForCompositionTypes(ContentTypeEditingModelBase model, params CompositionType[] compositionTypes) => model.Compositions .Where(c => compositionTypes.Contains(c.CompositionType)) .Select(c => c.Key) diff --git a/src/Umbraco.Core/Services/ContentTypeEditing/ElementSwitchValidator.cs b/src/Umbraco.Core/Services/ContentTypeEditing/ElementSwitchValidator.cs index bf02fa6759..4d41e78b1e 100644 --- a/src/Umbraco.Core/Services/ContentTypeEditing/ElementSwitchValidator.cs +++ b/src/Umbraco.Core/Services/ContentTypeEditing/ElementSwitchValidator.cs @@ -20,27 +20,27 @@ public class ElementSwitchValidator : IElementSwitchValidator _dataTypeService = dataTypeService; } - public async Task AncestorsAreAlignedAsync(IContentType contentType) + public Task AncestorsAreAlignedAsync(IContentType contentType) { // this call does not return the system roots var ancestorIds = contentType.AncestorIds(); if (ancestorIds.Length == 0) { // if there are no ancestors, validation passes - return true; + return Task.FromResult(true); } // if there are any ancestors where IsElement is different from the contentType, the validation fails - return await Task.FromResult(_contentTypeService.GetMany(ancestorIds) + return Task.FromResult(_contentTypeService.GetMany(ancestorIds) .Any(ancestor => ancestor.IsElement != contentType.IsElement) is false); } - public async Task DescendantsAreAlignedAsync(IContentType contentType) + public Task DescendantsAreAlignedAsync(IContentType contentType) { IEnumerable descendants = _contentTypeService.GetDescendants(contentType.Id, false); // if there are any descendants where IsElement is different from the contentType, the validation fails - return await Task.FromResult(descendants.Any(descendant => descendant.IsElement != contentType.IsElement) is false); + return Task.FromResult(descendants.Any(descendant => descendant.IsElement != contentType.IsElement) is false); } public async Task ElementToDocumentNotUsedInBlockStructuresAsync(IContentTypeBase contentType) @@ -59,8 +59,8 @@ public class ElementSwitchValidator : IElementSwitchValidator .ConfiguredElementTypeKeys().Contains(contentType.Key)) is false; } - public async Task DocumentToElementHasNoContentAsync(IContentTypeBase contentType) => + public Task DocumentToElementHasNoContentAsync(IContentTypeBase contentType) => // if any content for the content type exists, the validation fails. - await Task.FromResult(_contentTypeService.HasContentNodes(contentType.Id) is false); + Task.FromResult(_contentTypeService.HasContentNodes(contentType.Id) is false); } diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 83effe7400..8b1f7f953c 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -355,13 +355,13 @@ namespace Umbraco.Cms.Core.Services.Implement } /// - public async Task> GetByEditorAliasAsync(string[] propertyEditorAlias) + public Task> GetByEditorAliasAsync(string[] propertyEditorAlias) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); IQuery query = Query().Where(x => propertyEditorAlias.Contains(x.EditorAlias)); IEnumerable dataTypes = _dataTypeRepository.Get(query).ToArray(); ConvertMissingEditorsOfDataTypesToLabels(dataTypes); - return await Task.FromResult(dataTypes); + return Task.FromResult(dataTypes); } /// @@ -396,7 +396,7 @@ namespace Umbraco.Cms.Core.Services.Implement return; } - ConvertMissingEditorsOfDataTypesToLabels(new[] { dataType }); + ConvertMissingEditorsOfDataTypesToLabels([dataType]); } private void ConvertMissingEditorsOfDataTypesToLabels(IEnumerable dataTypes) @@ -763,7 +763,7 @@ namespace Umbraco.Cms.Core.Services.Implement var totalItems = combinedUsages.Count; // Create the page of items. - IList<(string PropertyAlias, Udi Udi)> pagedUsages = combinedUsages + List<(string PropertyAlias, Udi Udi)> pagedUsages = combinedUsages .OrderBy(x => x.Udi.EntityType) // Document types first, then media types, then member types. .ThenBy(x => x.PropertyAlias) .Skip(skip) @@ -772,7 +772,7 @@ namespace Umbraco.Cms.Core.Services.Implement // Get the content types for the UDIs referenced in the page of items to construct the response from. // They could be document, media or member types. - IList contentTypes = GetReferencedContentTypes(pagedUsages); + List contentTypes = GetReferencedContentTypes(pagedUsages); IEnumerable relations = pagedUsages .Select(x => @@ -807,7 +807,7 @@ namespace Umbraco.Cms.Core.Services.Implement return Task.FromResult(pagedModel); } - private IList GetReferencedContentTypes(IList<(string PropertyAlias, Udi Udi)> pagedUsages) + private List GetReferencedContentTypes(List<(string PropertyAlias, Udi Udi)> pagedUsages) { IEnumerable documentTypes = GetContentTypes( pagedUsages, @@ -845,10 +845,10 @@ namespace Umbraco.Cms.Core.Services.Implement { IConfigurationEditor? configurationEditor = dataType.Editor?.GetConfigurationEditor(); return configurationEditor == null - ? new[] - { + ? + [ new ValidationResult($"Data type with editor alias {dataType.EditorAlias} does not have a configuration editor") - } + ] : configurationEditor.Validate(dataType.ConfigurationData); } diff --git a/src/Umbraco.Core/Services/EntityTypeContainerService.cs b/src/Umbraco.Core/Services/EntityTypeContainerService.cs index 4a403e0c1f..575ff11f2e 100644 --- a/src/Umbraco.Core/Services/EntityTypeContainerService.cs +++ b/src/Umbraco.Core/Services/EntityTypeContainerService.cs @@ -52,18 +52,18 @@ internal abstract class EntityTypeContainerService - public async Task> GetAsync(string name, int level) + public Task> GetAsync(string name, int level) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); ReadLock(scope); - return await Task.FromResult(_entityContainerRepository.Get(name, level)); + return Task.FromResult(_entityContainerRepository.Get(name, level)); } /// - public async Task> GetAllAsync() + public Task> GetAllAsync() { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); ReadLock(scope); - return await Task.FromResult(_entityContainerRepository.GetMany()); + return Task.FromResult(_entityContainerRepository.GetMany()); } /// diff --git a/src/Umbraco.Core/Services/Querying/ContentQueryService.cs b/src/Umbraco.Core/Services/Querying/ContentQueryService.cs index 241236f550..22c4eb40df 100644 --- a/src/Umbraco.Core/Services/Querying/ContentQueryService.cs +++ b/src/Umbraco.Core/Services/Querying/ContentQueryService.cs @@ -23,23 +23,23 @@ public class ContentQueryService : IContentQueryService _coreScopeProvider = coreScopeProvider; } - public async Task> GetWithSchedulesAsync(Guid id) + public Task> GetWithSchedulesAsync(Guid id) { using ICoreScope scope = _coreScopeProvider.CreateCoreScope(autoComplete: true); - IContent? content = await Task.FromResult(_contentService.GetById(id)); + IContent? content = _contentService.GetById(id); if (content == null) { - return Attempt.Fail(ContentQueryOperationStatus - .ContentNotFound); + return Task.FromResult(Attempt.Fail(ContentQueryOperationStatus + .ContentNotFound)); } ContentScheduleCollection schedules = _contentService.GetContentScheduleByContentId(id); - return Attempt + return Task.FromResult(Attempt .Succeed( ContentQueryOperationStatus.Success, - new ContentScheduleQueryResult(content, schedules)); + new ContentScheduleQueryResult(content, schedules))); } } diff --git a/src/Umbraco.Core/Services/RelationService.cs b/src/Umbraco.Core/Services/RelationService.cs index 7dfcb4ae2f..456a3bb8dc 100644 --- a/src/Umbraco.Core/Services/RelationService.cs +++ b/src/Umbraco.Core/Services/RelationService.cs @@ -220,7 +220,7 @@ public class RelationService : RepositoryService, IRelationService } return relationTypeIds.Count == 0 - ? Enumerable.Empty() + ? [] : GetRelationsByListOfTypeIds(relationTypeIds); } @@ -230,8 +230,8 @@ public class RelationService : RepositoryService, IRelationService IRelationType? relationType = GetRelationType(relationTypeAlias); return relationType == null - ? Enumerable.Empty() - : GetRelationsByListOfTypeIds(new[] { relationType.Id }); + ? [] + : GetRelationsByListOfTypeIds([relationType.Id]); } /// @@ -594,7 +594,7 @@ public class RelationService : RepositoryService, IRelationService EventMessages eventMessages = EventMessagesFactory.Get(); var savingNotification = new RelationTypeSavingNotification(relationType, eventMessages); - if (scope.Notifications.PublishCancelable(savingNotification)) + if (await scope.Notifications.PublishCancelableAsync(savingNotification)) { scope.Complete(); return Attempt.FailWithStatus(RelationTypeOperationStatus.CancelledByNotification, relationType); @@ -659,7 +659,7 @@ public class RelationService : RepositoryService, IRelationService EventMessages eventMessages = EventMessagesFactory.Get(); var deletingNotification = new RelationTypeDeletingNotification(relationType, eventMessages); - if (scope.Notifications.PublishCancelable(deletingNotification)) + if (await scope.Notifications.PublishCancelableAsync(deletingNotification)) { scope.Complete(); return Attempt.FailWithStatus(RelationTypeOperationStatus.CancelledByNotification, null); @@ -670,7 +670,7 @@ public class RelationService : RepositoryService, IRelationService Audit(AuditType.Delete, currentUser, relationType.Id, "Deleted relation type"); scope.Notifications.Publish(new RelationTypeDeletedNotification(relationType, eventMessages).WithStateFrom(deletingNotification)); scope.Complete(); - return await Task.FromResult(Attempt.SucceedWithStatus(RelationTypeOperationStatus.Success, relationType)); + return Attempt.SucceedWithStatus(RelationTypeOperationStatus.Success, relationType); } /// @@ -726,7 +726,7 @@ public class RelationService : RepositoryService, IRelationService return _relationTypeRepository.Get(query).FirstOrDefault(); } - private IEnumerable GetRelationsByListOfTypeIds(IEnumerable relationTypeIds) + private List GetRelationsByListOfTypeIds(IEnumerable relationTypeIds) { var relations = new List(); using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) diff --git a/src/Umbraco.Core/Strings/Diff.cs b/src/Umbraco.Core/Strings/Diff.cs index e8a3fdf84c..e8a5cc1324 100644 --- a/src/Umbraco.Core/Strings/Diff.cs +++ b/src/Umbraco.Core/Strings/Diff.cs @@ -13,7 +13,7 @@ namespace Umbraco.Cms.Core.Strings; /// Copyright (c) by Matthias Hertel, http://www.mathertel.de /// This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx /// -internal class Diff +internal sealed class Diff { /// /// Find the difference in 2 texts, comparing by text lines. From f9c2b9594f2bb94c1ce5f2368774d3f07b56c1ad Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Mon, 30 Jun 2025 13:18:02 +0100 Subject: [PATCH 52/82] Label property-editor, adds UFM template support (#19610) * Adds `labelTemplate` config to Label editor * Code tidyup for Label editor element * Adds "format-bytes" UFM filter * Renamed "format-bytes" to "bytes" * Label element tidy-up + copilot amends * Added `formatBytes` options * Simplified condition, thanks CodeScene * Reverted element export --- .../property-editors/label/manifests.ts | 10 ++++++++ .../label/property-editor-ui-label.element.ts | 25 +++++++++++-------- .../src/packages/ufm/filters/bytes.filter.ts | 11 ++++++++ .../src/packages/ufm/filters/manifests.ts | 7 ++++++ 4 files changed, 42 insertions(+), 11 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/ufm/filters/bytes.filter.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/label/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/label/manifests.ts index aa672fc776..7b87f756d1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/label/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/label/manifests.ts @@ -12,6 +12,16 @@ export const manifests: Array = [ group: 'common', propertyEditorSchemaAlias: 'Umbraco.Label', supportsReadOnly: true, + settings: { + properties: [ + { + alias: 'labelTemplate', + label: 'Label template', + description: 'Enter a template for the label.', + propertyEditorUiAlias: 'Umb.PropertyEditorUi.TextBox', + }, + ], + }, }, }, labelSchemaManifest, diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/label/property-editor-ui-label.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/label/property-editor-ui-label.element.ts index 7d75fb8a76..90f59bf98a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/label/property-editor-ui-label.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/label/property-editor-ui-label.element.ts @@ -1,30 +1,33 @@ -import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { customElement, html, property, state, when } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UmbPropertyEditorUiElement, UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; /** * @element umb-property-editor-ui-label */ @customElement('umb-property-editor-ui-label') export class UmbPropertyEditorUILabelElement extends UmbLitElement implements UmbPropertyEditorUiElement { + @state() + private _labelTemplate?: string; + @property() value = ''; - @property() - description = ''; - @property({ attribute: false }) - public config?: UmbPropertyEditorConfigCollection; - - override render() { - return html`${this.value ?? ''}`; + public set config(config: UmbPropertyEditorConfigCollection | undefined) { + this._labelTemplate = config?.getValueByAlias('labelTemplate'); } - static override styles = [UmbTextStyles]; + override render() { + return when( + this._labelTemplate?.length, + () => html``, + () => html`${this.value ?? ''}`, + ); + } } export default UmbPropertyEditorUILabelElement; diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/filters/bytes.filter.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/filters/bytes.filter.ts new file mode 100644 index 0000000000..2aacfb3e1f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/filters/bytes.filter.ts @@ -0,0 +1,11 @@ +import { UmbUfmFilterBase } from './base.filter.js'; +import { formatBytes } from '@umbraco-cms/backoffice/utils'; + +class UmbUfmBytesFilterApi extends UmbUfmFilterBase { + filter(str?: string, decimals?: number, kilo?: number, culture?: string): string { + if (!str?.length) return ''; + return formatBytes(Number(str), { decimals, kilo, culture }); + } +} + +export { UmbUfmBytesFilterApi as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/filters/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/filters/manifests.ts index 1e08fa9f26..eb12e0643c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/ufm/filters/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/filters/manifests.ts @@ -8,6 +8,13 @@ export const manifests: Array = [ api: () => import('./fallback.filter.js'), meta: { alias: 'fallback' }, }, + { + type: 'ufmFilter', + alias: 'Umb.Filter.Bytes', + name: 'Bytes UFM Filter', + api: () => import('./bytes.filter.js'), + meta: { alias: 'bytes' }, + }, { type: 'ufmFilter', alias: 'Umb.Filter.Lowercase', From 880d84b030f97babae067904652fddaf5a0cf6a8 Mon Sep 17 00:00:00 2001 From: Henrik Date: Mon, 30 Jun 2025 14:24:04 +0200 Subject: [PATCH 53/82] Reduce lookups needed in ConcurrentDictionaries in ContentNavigationServiceBase (#19603) Reduce lookups needed in ConcurrentDictionaries and sort using List.Sort, make key removal O(1) by using hashsets and avoid duplicates, remove unneeded .ToList() and other minor tweaks --- .../Composing/TypeFinderConfig.cs | 4 +- .../Extensions/StringExtensions.cs | 3 +- .../Factories/UserSettingsFactory.cs | 1 - .../Logging/LoggingConfiguration.cs | 3 +- .../Models/Blocks/BlockEditorDataConverter.cs | 6 +- src/Umbraco.Core/Routing/DomainUtilities.cs | 19 ++-- .../ContentNavigationServiceBase.cs | 88 +++++++++++-------- .../Strings/Css/StylesheetHelper.cs | 3 +- .../Strings/Css/StylesheetRule.cs | 6 +- .../Models/RegisterModelBuilder.cs | 2 +- 10 files changed, 77 insertions(+), 58 deletions(-) diff --git a/src/Umbraco.Core/Composing/TypeFinderConfig.cs b/src/Umbraco.Core/Composing/TypeFinderConfig.cs index 2fd9283500..efba61ccb4 100644 --- a/src/Umbraco.Core/Composing/TypeFinderConfig.cs +++ b/src/Umbraco.Core/Composing/TypeFinderConfig.cs @@ -25,8 +25,8 @@ public class TypeFinderConfig : ITypeFinderConfig var s = _settings.AssembliesAcceptingLoadExceptions; return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s) - ? Array.Empty() - : s.Split(',').Select(x => x.Trim()).ToArray(); + ? [] + : s.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); } } } diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index 7a3d90b205..7f3cbbd66b 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -509,8 +509,7 @@ public static class StringExtensions var convertToHex = input.ConvertToHex(); var hexLength = convertToHex.Length < 32 ? convertToHex.Length : 32; var hex = convertToHex[..hexLength].PadLeft(32, '0'); - Guid output = Guid.Empty; - return Guid.TryParse(hex, out output) ? output : Guid.Empty; + return Guid.TryParse(hex, out Guid output) ? output : Guid.Empty; } /// diff --git a/src/Umbraco.Core/Factories/UserSettingsFactory.cs b/src/Umbraco.Core/Factories/UserSettingsFactory.cs index c006fe0043..99da578eaa 100644 --- a/src/Umbraco.Core/Factories/UserSettingsFactory.cs +++ b/src/Umbraco.Core/Factories/UserSettingsFactory.cs @@ -36,7 +36,6 @@ public class UserSettingsFactory : IUserSettingsFactory private IEnumerable CreateConsentLevelModels() => Enum.GetValues() - .ToList() .Select(level => new ConsentLevelModel { Level = level, diff --git a/src/Umbraco.Core/Logging/LoggingConfiguration.cs b/src/Umbraco.Core/Logging/LoggingConfiguration.cs index b6d6893482..d21d56dfea 100644 --- a/src/Umbraco.Core/Logging/LoggingConfiguration.cs +++ b/src/Umbraco.Core/Logging/LoggingConfiguration.cs @@ -58,8 +58,7 @@ public class LoggingConfiguration : ILoggingConfiguration public string LogFileNameFormat { get; } /// - public string[] GetLogFileNameFormatArguments() => _logFileNameFormatArguments.Split(',', StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.Trim()) + public string[] GetLogFileNameFormatArguments() => _logFileNameFormatArguments.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .Select(GetValue) .ToArray(); diff --git a/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs b/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs index b3bc74e54c..c408d40100 100644 --- a/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs +++ b/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs @@ -71,12 +71,12 @@ public abstract class BlockEditorDataConverter // this method is only meant to have any effect when migrating block editor values // from the original format to the new, variant enabled format - private void AmendExpose(TValue value) - => value.Expose = value.ContentData.Select(cd => new BlockItemVariation(cd.Key, null, null)).ToList(); + private static void AmendExpose(TValue value) + => value.Expose = value.ContentData.ConvertAll(cd => new BlockItemVariation(cd.Key, null, null)); // this method is only meant to have any effect when migrating block editor values // from the original format to the new, variant enabled format - private bool ConvertOriginalBlockFormat(List blockItemDatas) + private static bool ConvertOriginalBlockFormat(List blockItemDatas) { var converted = false; foreach (BlockItemData blockItemData in blockItemDatas) diff --git a/src/Umbraco.Core/Routing/DomainUtilities.cs b/src/Umbraco.Core/Routing/DomainUtilities.cs index 2d9be885a3..03ddd438d3 100644 --- a/src/Umbraco.Core/Routing/DomainUtilities.cs +++ b/src/Umbraco.Core/Routing/DomainUtilities.cs @@ -256,14 +256,14 @@ namespace Umbraco.Cms.Core.Routing // if a culture is specified, then try to get domains for that culture // (else cultureDomains will be null) // do NOT specify a default culture, else it would pick those domains - IReadOnlyCollection? cultureDomains = SelectByCulture(domainsAndUris, culture, defaultCulture: null); + IReadOnlyList? cultureDomains = SelectByCulture(domainsAndUris, culture, defaultCulture: null); IReadOnlyCollection considerForBaseDomains = domainsAndUris; if (cultureDomains != null) { if (cultureDomains.Count == 1) { // only 1, return - return cultureDomains.First(); + return cultureDomains[0]; } // else restrict to those domains, for base lookup @@ -272,11 +272,11 @@ namespace Umbraco.Cms.Core.Routing // look for domains that would be the base of the uri // we need to order so example.com/foo matches before example.com/ - IReadOnlyCollection baseDomains = SelectByBase(considerForBaseDomains.OrderByDescending(d => d.Uri.ToString()).ToList(), uri, culture); + List baseDomains = SelectByBase(considerForBaseDomains.OrderByDescending(d => d.Uri.ToString()).ToArray(), uri, culture); if (baseDomains.Count > 0) { // found, return - return baseDomains.First(); + return baseDomains[0]; } // if nothing works, then try to run the filter to select a domain @@ -296,7 +296,7 @@ namespace Umbraco.Cms.Core.Routing private static bool MatchesCulture(DomainAndUri domain, string? culture) => culture == null || domain.Culture.InvariantEquals(culture); - private static IReadOnlyCollection SelectByBase(IReadOnlyCollection domainsAndUris, Uri uri, string? culture) + private static List SelectByBase(DomainAndUri[] domainsAndUris, Uri uri, string? culture) { // look for domains that would be the base of the uri // ie current is www.example.com/foo/bar, look for domain www.example.com @@ -314,7 +314,7 @@ namespace Umbraco.Cms.Core.Routing return baseDomains; } - private static IReadOnlyCollection? SelectByCulture(IReadOnlyCollection domainsAndUris, string? culture, string? defaultCulture) + private static List? SelectByCulture(DomainAndUri[] domainsAndUris, string? culture, string? defaultCulture) { // we try our best to match cultures, but may end with a bogus domain if (culture is not null) @@ -434,13 +434,18 @@ namespace Umbraco.Cms.Core.Routing private static Domain? FindDomainInPath(IEnumerable? domains, string path, int? rootNodeId, bool isWildcard) { + if (domains is null) + { + return null; + } + var stopNodeId = rootNodeId ?? -1; return path.Split(Constants.CharArrays.Comma) .Reverse() .Select(s => int.Parse(s, CultureInfo.InvariantCulture)) .TakeWhile(id => id != stopNodeId) - .Select(id => domains?.FirstOrDefault(d => d.ContentId == id && d.IsWildcard == isWildcard)) + .Select(id => domains.FirstOrDefault(d => d.ContentId == id && d.IsWildcard == isWildcard)) .FirstOrDefault(domain => domain is not null); } diff --git a/src/Umbraco.Core/Services/Navigation/ContentNavigationServiceBase.cs b/src/Umbraco.Core/Services/Navigation/ContentNavigationServiceBase.cs index d7562a76b9..bb849c61e7 100644 --- a/src/Umbraco.Core/Services/Navigation/ContentNavigationServiceBase.cs +++ b/src/Umbraco.Core/Services/Navigation/ContentNavigationServiceBase.cs @@ -17,8 +17,8 @@ internal abstract class ContentNavigationServiceBase> _contentTypeAliasToKeyMap; private ConcurrentDictionary _navigationStructure = new(); private ConcurrentDictionary _recycleBinNavigationStructure = new(); - private IList _roots = new List(); - private IList _recycleBinRoots = new List(); + private HashSet _roots = []; + private HashSet _recycleBinRoots = []; protected ContentNavigationServiceBase(ICoreScopeProvider coreScopeProvider, INavigationRepository navigationRepository, TContentTypeService typeService) { @@ -321,7 +321,7 @@ internal abstract class ContentNavigationServiceBase structure, Guid childKey, out Guid? parentKey) + private static bool TryGetParentKeyFromStructure(ConcurrentDictionary structure, Guid childKey, out Guid? parentKey) { if (structure.TryGetValue(childKey, out NavigationNode? childNode)) { @@ -335,25 +335,32 @@ internal abstract class ContentNavigationServiceBase input, + HashSet input, out IEnumerable rootKeys, Guid? contentTypeKey = null) { - // Apply contentTypeKey filter - IEnumerable filteredKeys = contentTypeKey.HasValue - ? input.Where(key => _navigationStructure[key].ContentTypeKey == contentTypeKey.Value) - : input; + var keysWithSortOrder = new List<(Guid Key, int SortOrder)>(input.Count); + foreach (Guid key in input) + { + NavigationNode navigationNode = _navigationStructure[key]; + + // Apply contentTypeKey filter + if (contentTypeKey.HasValue && navigationNode.ContentTypeKey != contentTypeKey.Value) + { + continue; + } + + keysWithSortOrder.Add((key, navigationNode.SortOrder)); + } - // TODO can we make this more efficient? // Sort by SortOrder - rootKeys = filteredKeys - .OrderBy(key => _navigationStructure[key].SortOrder) - .ToList(); + keysWithSortOrder.Sort((a, b) => a.SortOrder.CompareTo(b.SortOrder)); + rootKeys = keysWithSortOrder.ConvertAll(keyWithSortOrder => keyWithSortOrder.Key); return true; } - private bool TryGetChildrenKeysFromStructure( + private static bool TryGetChildrenKeysFromStructure( ConcurrentDictionary structure, Guid parentKey, out IEnumerable childrenKeys, @@ -367,12 +374,12 @@ internal abstract class ContentNavigationServiceBase structure, Guid parentKey, out IEnumerable descendantsKeys, @@ -393,7 +400,7 @@ internal abstract class ContentNavigationServiceBase structure, Guid childKey, out IEnumerable ancestorsKeys, @@ -421,7 +428,7 @@ internal abstract class ContentNavigationServiceBase structure, Guid key, out IEnumerable siblingsKeys, @@ -463,14 +470,14 @@ internal abstract class ContentNavigationServiceBase structure, NavigationNode node, List descendants, Guid? contentTypeKey = null) { // Get all children regardless of contentType - var childrenKeys = GetOrderedChildren(node, structure).ToList(); + IReadOnlyList childrenKeys = GetOrderedChildren(node, structure); foreach (Guid childKey in childrenKeys) { // Apply contentTypeKey filter @@ -487,7 +494,7 @@ internal abstract class ContentNavigationServiceBase structure, Guid key, out NavigationNode? nodeToRemove) + private static bool TryRemoveNodeFromParentInStructure(ConcurrentDictionary structure, Guid key, out NavigationNode? nodeToRemove) { if (structure.TryGetValue(key, out nodeToRemove) is false) { @@ -507,7 +514,7 @@ internal abstract class ContentNavigationServiceBase childrenKeys = GetOrderedChildren(node, _navigationStructure); foreach (Guid childKey in childrenKeys) { @@ -530,7 +537,7 @@ internal abstract class ContentNavigationServiceBase childrenKeys = GetOrderedChildren(node, _recycleBinNavigationStructure); foreach (Guid childKey in childrenKeys) { if (_recycleBinNavigationStructure.TryGetValue(childKey, out NavigationNode? childNode) is false) @@ -551,7 +558,7 @@ internal abstract class ContentNavigationServiceBase childrenKeys = GetOrderedChildren(node, _recycleBinNavigationStructure); foreach (Guid childKey in childrenKeys) { @@ -570,24 +577,35 @@ internal abstract class ContentNavigationServiceBase GetOrderedChildren( + private static IReadOnlyList GetOrderedChildren( NavigationNode node, ConcurrentDictionary structure, Guid? contentTypeKey = null) { - IEnumerable children = node - .Children - .Where(structure.ContainsKey); - - // Apply contentTypeKey filter - if (contentTypeKey.HasValue) + if (node.Children.Count < 1) { - children = children.Where(childKey => structure[childKey].ContentTypeKey == contentTypeKey.Value); + return []; } - return children - .OrderBy(childKey => structure[childKey].SortOrder) - .ToList(); + var childrenWithSortOrder = new List<(Guid ChildNodeKey, int SortOrder)>(node.Children.Count); + foreach (Guid childNodeKey in node.Children) + { + if (!structure.TryGetValue(childNodeKey, out NavigationNode? childNode)) + { + continue; + } + + // Apply contentTypeKey filter + if (contentTypeKey.HasValue && childNode.ContentTypeKey != contentTypeKey.Value) + { + continue; + } + + childrenWithSortOrder.Add((childNodeKey, childNode.SortOrder)); + } + + childrenWithSortOrder.Sort((a, b) => a.SortOrder.CompareTo(b.SortOrder)); + return childrenWithSortOrder.ConvertAll(childWithSortOrder => childWithSortOrder.ChildNodeKey); } private bool TryGetContentTypeKey(string contentTypeAlias, out Guid? contentTypeKey) @@ -613,7 +631,7 @@ internal abstract class ContentNavigationServiceBase nodesStructure, IList roots, IEnumerable entities) + private static void BuildNavigationDictionary(ConcurrentDictionary nodesStructure, HashSet roots, IEnumerable entities) { var entityList = entities.ToList(); var idToKeyMap = entityList.ToDictionary(x => x.Id, x => x.Key); diff --git a/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs b/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs index f3d14a6fe2..85ce813907 100644 --- a/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs +++ b/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs @@ -39,8 +39,7 @@ public class StylesheetHelper // Only match first selector when chained together Styles = string.Join( Environment.NewLine, - match.Groups["Styles"].Value.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None) - .Select(x => x.Trim()).ToArray()), + match.Groups["Styles"].Value.Split(new[] { "\r\n", "\n" }, StringSplitOptions.TrimEntries)), }); } } diff --git a/src/Umbraco.Core/Strings/Css/StylesheetRule.cs b/src/Umbraco.Core/Strings/Css/StylesheetRule.cs index 4b726f34ef..032901c13e 100644 --- a/src/Umbraco.Core/Strings/Css/StylesheetRule.cs +++ b/src/Umbraco.Core/Strings/Css/StylesheetRule.cs @@ -30,13 +30,13 @@ public class StylesheetRule // instead of using string interpolation (for increased performance) foreach (var style in Styles?.Split(Constants.CharArrays.Semicolon, StringSplitOptions.RemoveEmptyEntries) ?? - Array.Empty()) + []) { - sb.Append("\t").Append(style.StripNewLines().Trim()).Append(";").Append(Environment.NewLine); + sb.Append('\t').Append(style.StripNewLines().Trim()).Append(';').Append(Environment.NewLine); } } - sb.Append("}"); + sb.Append('}'); return sb.ToString(); } diff --git a/src/Umbraco.Web.Website/Models/RegisterModelBuilder.cs b/src/Umbraco.Web.Website/Models/RegisterModelBuilder.cs index 0b3e73fe73..660af70f05 100644 --- a/src/Umbraco.Web.Website/Models/RegisterModelBuilder.cs +++ b/src/Umbraco.Web.Website/Models/RegisterModelBuilder.cs @@ -68,7 +68,7 @@ public class RegisterModelBuilder : MemberModelBuilderBase UsernameIsEmail = _usernameIsEmail, MemberProperties = _lookupProperties ? GetMemberPropertiesViewModel(memberType) - : Enumerable.Empty().ToList(), + : [], AutomaticLogIn = _automaticLogIn }; return model; From df39026d0dcf184e4ec2293160053587ca934ef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 30 Jun 2025 14:32:45 +0200 Subject: [PATCH 54/82] set height to match workspace top bar (#19384) Co-authored-by: Mads Rasmussen --- .../section-sidebar-menu/section-sidebar-menu.element.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-sidebar-menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-sidebar-menu.element.ts index 2c7c75e351..51b8b8c34d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-sidebar-menu.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-sidebar-menu.element.ts @@ -48,8 +48,12 @@ export class UmbSectionSidebarMenuElement< UmbTextStyles, css` h3 { - margin: var(--uui-size-5) 0; + display: flex; + align-items: center; + height: var(--umb-header-layout-height); + margin: 0; padding: var(--uui-size-4) var(--uui-size-8); + box-sizing: border-box; font-size: 14px; } `, From b81d45901b58df50a0af7a6705cf03c9c570b46e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 30 Jun 2025 14:35:18 +0200 Subject: [PATCH 55/82] ability to hide block actions (#19626) Co-authored-by: Mads Rasmussen --- .../block-grid-entry.element.ts | 24 ++++++++++++++----- .../block-list-entry.element.ts | 24 +++++++++++++++---- .../block-rte-entry.element.ts | 19 ++++++++++++++- .../block/context/block-entry.context.ts | 10 ++++++++ 4 files changed, 66 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts index f0f5a9de01..d4b38c13e8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts @@ -92,6 +92,9 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper @state() _unsupported?: boolean; + @state() + _showActions?: boolean; + @state() _workspaceEditContentPath?: string; @@ -214,6 +217,13 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper }, null, ); + this.observe( + this.#context.actionsVisibility, + (showActions) => { + this._showActions = showActions; + }, + null, + ); this.observe( this.#context.inlineEditingMode, @@ -546,12 +556,14 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper } #renderActionBar() { - return html` - - ${this.#renderEditAction()} ${this.#renderEditSettingsAction()} ${this.#renderCopyToClipboardAction()} - ${this.#renderDeleteAction()} - `; + return this._showActions + ? html` + + ${this.#renderEditAction()} ${this.#renderEditSettingsAction()} ${this.#renderCopyToClipboardAction()} + ${this.#renderDeleteAction()} + ` + : nothing; } #renderEditAction() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts index 6242f413a3..b20e72711c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts @@ -84,6 +84,9 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper @state() _unsupported?: boolean; + @state() + _showActions?: boolean; + @state() _workspaceEditContentPath?: string; @@ -178,6 +181,13 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper }, null, ); + this.observe( + this.#context.actionsVisibility, + (showActions) => { + this._showActions = showActions; + }, + null, + ); this.observe( this.#context.inlineEditingMode, (mode) => { @@ -412,10 +422,7 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper single >${this.#renderBuiltinBlockView()} - - ${this.#renderEditContentAction()} ${this.#renderEditSettingsAction()} - ${this.#renderCopyToClipboardAction()} ${this.#renderDeleteAction()} - + ${this.#renderActionBar()} ${!this._showContentEdit && this._contentInvalid ? html`!` : nothing} @@ -424,6 +431,15 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper : nothing; } + #renderActionBar() { + return this._showActions + ? html` + ${this.#renderEditContentAction()} ${this.#renderEditSettingsAction()} ${this.#renderCopyToClipboardAction()} + ${this.#renderDeleteAction()} + ` + : nothing; + } + #renderEditContentAction() { return this._showContentEdit && this._workspaceEditContentPath ? html` { + this._showActions = showActions; + }, + null, + ); // Data props: this.observe( this.#context.layout, @@ -269,7 +280,7 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert single> ${this.#renderRefBlock()} - ${this.#renderEditAction()} ${this.#renderEditSettingsAction()} + ${this.#renderActionBar()} ${!this._showContentEdit && this._contentInvalid ? html`!` : nothing} @@ -278,6 +289,12 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert : nothing; } + #renderActionBar() { + return this._showActions + ? html` ${this.#renderEditAction()} ${this.#renderEditSettingsAction()} ` + : nothing; + } + #renderRefBlock() { return html` Date: Mon, 30 Jun 2025 14:37:04 +0200 Subject: [PATCH 56/82] Further fix to configuration classes using ISet, resolving regression with custom 404 pages (#19573) Further fix to configuration classes using ISet, resolving regression with custom 404 pages. --- src/Umbraco.Core/CompatibilitySuppressions.xml | 11 +++++++++++ .../Configuration/Models/ContentSettings.cs | 2 +- .../Validation/ContentSettingsValidatorTests.cs | 4 ++-- 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 src/Umbraco.Core/CompatibilitySuppressions.xml diff --git a/src/Umbraco.Core/CompatibilitySuppressions.xml b/src/Umbraco.Core/CompatibilitySuppressions.xml new file mode 100644 index 0000000000..3cd59acc38 --- /dev/null +++ b/src/Umbraco.Core/CompatibilitySuppressions.xml @@ -0,0 +1,11 @@ + + + + + CP0002 + M:Umbraco.Cms.Core.Configuration.Models.ContentSettings.get_Error404Collection + lib/net9.0/Umbraco.Core.dll + lib/net9.0/Umbraco.Core.dll + true + + \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs index 3e89369f00..6df9b429e9 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs @@ -50,7 +50,7 @@ public class ContentSettings /// /// Gets or sets a value for the collection of error pages. /// - public ISet Error404Collection { get; set; } = new HashSet(); + public IEnumerable Error404Collection { get; set; } = []; /// /// Gets or sets a value for the preview badge mark-up. diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidatorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidatorTests.cs index bea57e6786..1d4afe2c3b 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidatorTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidatorTests.cs @@ -42,9 +42,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validati new ContentSettings { Error404Collection = - { + [ new() { Culture = culture, ContentId = 1 }, - }, + ], Imaging = { AutoFillImageProperties = From 283e6b2ff0d5a19bc9e6f7352a97a3b8256aacc7 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 30 Jun 2025 14:51:21 +0200 Subject: [PATCH 57/82] Entity type + Entity Unique conditions (#19614) * Add entity-type and entity-unique condition support Introduces new condition types for entity-type and entity-unique, including their constants, types, condition implementations, and manifests. Updates exports in core entity modules to include these new features, enabling more granular extension conditions based on entity type and uniqueness. * register conditions * add support for oneOf * fix self imports * Update manifests.ts * remove unused --- .../src/packages/core/entity/constants.ts | 2 ++ .../core/entity/entity-type/constants.ts | 1 + .../entity-type/entity-type.condition.ts | 35 ++++++++++++++++++ .../core/entity/entity-type/manifests.ts | 10 ++++++ .../packages/core/entity/entity-type/types.ts | 13 +++++++ .../core/entity/entity-unique/constants.ts | 1 + .../entity-unique/entity-unique.condition.ts | 36 +++++++++++++++++++ .../core/entity/entity-unique/manifests.ts | 10 ++++++ .../core/entity/entity-unique/types.ts | 15 ++++++++ .../src/packages/core/entity/manifests.ts | 4 +++ .../src/packages/core/entity/types.ts | 5 ++- .../src/packages/core/manifests.ts | 2 ++ 12 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-type/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-type/entity-type.condition.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-type/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-type/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-unique/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-unique/entity-unique.condition.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-unique/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-unique/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity/manifests.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/constants.ts index f804358649..5f9ddc3310 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/constants.ts @@ -1,2 +1,4 @@ export * from './contexts/ancestors/constants.js'; export * from './contexts/parent/constants.js'; +export * from './entity-type/constants.js'; +export * from './entity-unique/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-type/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-type/constants.ts new file mode 100644 index 0000000000..eb2c39863d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-type/constants.ts @@ -0,0 +1 @@ +export const UMB_ENTITY_TYPE_CONDITION_ALIAS = 'Umb.Condition.Entity.Type'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-type/entity-type.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-type/entity-type.condition.ts new file mode 100644 index 0000000000..051607765d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-type/entity-type.condition.ts @@ -0,0 +1,35 @@ +import { UMB_ENTITY_CONTEXT } from '../entity.context-token.js'; +import type { UmbEntityTypeConditionConfig } from './types.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api'; +import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry'; + +export class UmbEntityTypeCondition + extends UmbConditionBase + implements UmbExtensionCondition +{ + constructor(host: UmbControllerHost, args: UmbConditionControllerArguments) { + super(host, args); + + this.consumeContext(UMB_ENTITY_CONTEXT, (context) => { + this.observe(context?.entityType, (entityType) => this.#check(entityType), 'umbEntityTypeObserver'); + }); + } + + #check(value: string | undefined) { + if (!value) { + this.permitted = false; + return; + } + + // if the config has a match, we only check that + if (this.config.match) { + this.permitted = value === this.config.match; + return; + } + + this.permitted = this.config.oneOf?.some((configValue) => configValue === value) ?? false; + } +} + +export { UmbEntityTypeCondition as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-type/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-type/manifests.ts new file mode 100644 index 0000000000..fdab02c688 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-type/manifests.ts @@ -0,0 +1,10 @@ +import { UMB_ENTITY_TYPE_CONDITION_ALIAS } from './constants.js'; + +export const manifests: Array = [ + { + type: 'condition', + name: 'Umbraco Entity Type Condition', + alias: UMB_ENTITY_TYPE_CONDITION_ALIAS, + api: () => import('./entity-type.condition.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-type/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-type/types.ts new file mode 100644 index 0000000000..d613b7e642 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-type/types.ts @@ -0,0 +1,13 @@ +import type { UMB_ENTITY_TYPE_CONDITION_ALIAS } from './constants.js'; +import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; + +export type UmbEntityTypeConditionConfig = UmbConditionConfigBase & { + match: string; + oneOf?: Array; +}; + +declare global { + interface UmbExtensionConditionConfigMap { + UmbEntityTypeConditionConfig: UmbEntityTypeConditionConfig; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-unique/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-unique/constants.ts new file mode 100644 index 0000000000..29a480d672 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-unique/constants.ts @@ -0,0 +1 @@ +export const UMB_ENTITY_UNIQUE_CONDITION_ALIAS = 'Umb.Condition.Entity.Unique'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-unique/entity-unique.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-unique/entity-unique.condition.ts new file mode 100644 index 0000000000..6b21f64c1e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-unique/entity-unique.condition.ts @@ -0,0 +1,36 @@ +import { UMB_ENTITY_CONTEXT } from '../entity.context-token.js'; +import type { UmbEntityUnique } from '../types.js'; +import type { UmbEntityUniqueConditionConfig } from './types.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api'; +import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry'; + +export class UmbEntityUniqueCondition + extends UmbConditionBase + implements UmbExtensionCondition +{ + constructor(host: UmbControllerHost, args: UmbConditionControllerArguments) { + super(host, args); + + this.consumeContext(UMB_ENTITY_CONTEXT, (context) => { + this.observe(context?.unique, (unique) => this.#check(unique), 'umbEntityUniqueObserver'); + }); + } + + #check(value: UmbEntityUnique | undefined) { + if (value === undefined) { + this.permitted = false; + return; + } + + // if the config has a match, we only check that + if (this.config.match !== undefined) { + this.permitted = value === this.config.match; + return; + } + + this.permitted = this.config.oneOf?.some((configValue) => configValue === value) ?? false; + } +} + +export { UmbEntityUniqueCondition as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-unique/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-unique/manifests.ts new file mode 100644 index 0000000000..accb4199eb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-unique/manifests.ts @@ -0,0 +1,10 @@ +import { UMB_ENTITY_UNIQUE_CONDITION_ALIAS } from './constants.js'; + +export const manifests: Array = [ + { + type: 'condition', + name: 'Umbraco Entity Unique Condition', + alias: UMB_ENTITY_UNIQUE_CONDITION_ALIAS, + api: () => import('./entity-unique.condition.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-unique/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-unique/types.ts new file mode 100644 index 0000000000..22e442f84f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/entity-unique/types.ts @@ -0,0 +1,15 @@ +import type { UMB_ENTITY_UNIQUE_CONDITION_ALIAS } from './constants.js'; +import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; + +export type UmbEntityUnique = string | null; + +export type UmbEntityUniqueConditionConfig = UmbConditionConfigBase & { + match?: UmbEntityUnique; + oneOf?: Array; +}; + +declare global { + interface UmbExtensionConditionConfigMap { + UmbEntityUniqueConditionConfig: UmbEntityUniqueConditionConfig; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/manifests.ts new file mode 100644 index 0000000000..d0f7437bd9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/manifests.ts @@ -0,0 +1,4 @@ +import { manifests as entityTypeManifests } from './entity-type/manifests.js'; +import { manifests as entityUniqueManifests } from './entity-unique/manifests.js'; + +export const manifests: Array = [...entityTypeManifests, ...entityUniqueManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/types.ts index f2fcb7dde9..51ac14114a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/types.ts @@ -1,4 +1,4 @@ -export type UmbEntityUnique = string | null; +import type { UmbEntityUnique } from './entity-unique/types.js'; export interface UmbEntityModel { unique: UmbEntityUnique; @@ -8,3 +8,6 @@ export interface UmbEntityModel { export interface UmbNamedEntityModel extends UmbEntityModel { name: string; } + +export type * from './entity-type/types.js'; +export type * from './entity-unique/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts index 2d382b768a..1e046ec818 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts @@ -4,6 +4,7 @@ import { manifests as cultureManifests } from './culture/manifests.js'; import { manifests as debugManifests } from './debug/manifests.js'; import { manifests as entityActionManifests } from './entity-action/manifests.js'; import { manifests as entityBulkActionManifests } from './entity-bulk-action/manifests.js'; +import { manifests as entityManifests } from './entity/manifests.js'; import { manifests as extensionManifests } from './extension-registry/manifests.js'; import { manifests as iconRegistryManifests } from './icon-registry/manifests.js'; import { manifests as localizationManifests } from './localization/manifests.js'; @@ -30,6 +31,7 @@ export const manifests: Array = ...debugManifests, ...entityActionManifests, ...entityBulkActionManifests, + ...entityManifests, ...extensionManifests, ...iconRegistryManifests, ...localizationManifests, From 32a4fbebd5a0086b05a32fdf6ed853b15d838b75 Mon Sep 17 00:00:00 2001 From: Lili <82643045+Lili-Rossiter@users.noreply.github.com> Date: Mon, 30 Jun 2025 13:52:35 +0100 Subject: [PATCH 58/82] V15 Bugfix: Horizontal scrollbar in Media section #19479 (#19541) fix to stop the scroll bar appearing in the media section. --- .../src/packages/media/media/dropzone/dropzone-media.element.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-media.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-media.element.ts index 75adf08d70..1d5b9c1f67 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-media.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-media.element.ts @@ -126,6 +126,7 @@ export class UmbDropzoneMediaElement extends UmbInputDropzoneElement { z-index: 100; border-radius: var(--uui-border-radius); border: 1px solid var(--uui-color-focus); + box-sizing:border-box; } `, ]; From d6a66bc4d8e16a25286a2a8b2946f7e6beb630fb Mon Sep 17 00:00:00 2001 From: Lili <82643045+Lili-Rossiter@users.noreply.github.com> Date: Mon, 30 Jun 2025 13:52:35 +0100 Subject: [PATCH 59/82] V15 Bugfix: Horizontal scrollbar in Media section #19479 (#19541) fix to stop the scroll bar appearing in the media section. --- .../src/packages/media/media/dropzone/dropzone-media.element.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-media.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-media.element.ts index ac998a0d7d..f15848e80a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-media.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-media.element.ts @@ -118,6 +118,7 @@ export class UmbDropzoneMediaElement extends UmbInputDropzoneElement { z-index: 100; border-radius: var(--uui-border-radius); border: 1px solid var(--uui-color-focus); + box-sizing:border-box; } `, ]; From e135599c96dd23c534b84405fb07325899055732 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 30 Jun 2025 15:19:46 +0200 Subject: [PATCH 60/82] Fix readonly UI for create document user permission (#19554) * Update document-workspace.context.ts * Update src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * add check for undefined --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../workspace/document-workspace.context.ts | 82 ++++++++----------- 1 file changed, 33 insertions(+), 49 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 009d2698a8..d66f888b42 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -73,8 +73,6 @@ export class UmbDocumentWorkspaceContext #isTrashedContext = new UmbIsTrashedEntityContext(this); #publishingContext?: typeof UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT.TYPE; - #userCanCreate = false; - #userCanUpdate = false; constructor(host: UmbControllerHost) { super(host, { @@ -126,52 +124,19 @@ export class UmbDocumentWorkspaceContext this.#publishingContext = context; }); - createExtensionApiByAlias(this, UMB_DOCUMENT_USER_PERMISSION_CONDITION_ALIAS, [ - { - config: { - allOf: [UMB_USER_PERMISSION_DOCUMENT_CREATE], - }, - onChange: (permitted: boolean) => { - if (permitted === this.#userCanCreate) return; - this.#userCanCreate = permitted; - this.#setReadOnlyStateForUserPermission( - UMB_USER_PERMISSION_DOCUMENT_CREATE, - this.#userCanCreate, - 'You do not have permission to create documents.', - ); - }, - }, - ]); - - createExtensionApiByAlias(this, UMB_DOCUMENT_USER_PERMISSION_CONDITION_ALIAS, [ - { - config: { - allOf: [UMB_USER_PERMISSION_DOCUMENT_UPDATE], - }, - onChange: (permitted: boolean) => { - if (permitted === this.#userCanUpdate) return; - this.#userCanUpdate = permitted; - this.#setReadOnlyStateForUserPermission( - UMB_USER_PERMISSION_DOCUMENT_UPDATE, - this.#userCanUpdate, - 'You do not have permission to update documents.', - ); - }, - }, - ]); - - this.observe(this.variants, () => { - this.#setReadOnlyStateForUserPermission( - UMB_USER_PERMISSION_DOCUMENT_CREATE, - this.#userCanCreate, - 'You do not have permission to create documents.', - ); - - this.#setReadOnlyStateForUserPermission( - UMB_USER_PERMISSION_DOCUMENT_UPDATE, - this.#userCanUpdate, - 'You do not have permission to update documents.', - ); + this.observe(this.isNew, (isNew) => { + if (isNew === undefined) return; + if (isNew) { + this.#enforceUserPermission( + UMB_USER_PERMISSION_DOCUMENT_CREATE, + 'You do not have permission to create documents.', + ); + } else { + this.#enforceUserPermission( + UMB_USER_PERMISSION_DOCUMENT_UPDATE, + 'You do not have permission to update documents.', + ); + } }); this.routes.setRoutes([ @@ -225,6 +190,22 @@ export class UmbDocumentWorkspaceContext ]); } + #enforceUserPermission(verb: string, message: string) { + // We set the initial permission state to false because the condition is false by default and only execute the callback if it changes. + this.#handleUserPermissionChange(verb, false, message); + + createExtensionApiByAlias(this, UMB_DOCUMENT_USER_PERMISSION_CONDITION_ALIAS, [ + { + config: { + allOf: [verb], + }, + onChange: (permitted: boolean) => { + this.#handleUserPermissionChange(verb, permitted, message); + }, + }, + ]); + } + override resetState(): void { super.resetState(); this.#isTrashedContext.setIsTrashed(false); @@ -425,7 +406,7 @@ export class UmbDocumentWorkspaceContext return new UmbDocumentPropertyDatasetContext(host, this, variantId); } - async #setReadOnlyStateForUserPermission(identifier: string, permitted: boolean, message: string) { + async #handleUserPermissionChange(identifier: string, permitted: boolean, message: string) { if (permitted) { this.readOnlyGuard?.removeRule(identifier); return; @@ -434,6 +415,9 @@ export class UmbDocumentWorkspaceContext this.readOnlyGuard?.addRule({ unique: identifier, message, + /* This guard is a bit backwards. The rule is permitted to be read-only. + If the user does not have permission, we set it to true = permitted to be read-only. */ + permitted: true, }); } From 50327be6e73be4eb0c2eca349aa7a3ca8a83eada Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 30 Jun 2025 15:22:44 +0200 Subject: [PATCH 61/82] React on late read only guard rules (#19621) * Observe read-only guard rules in variant selector Added observation of read-only guard rules in the workspace split view variant selector to ensure read-only cultures are updated when rules change. * Improve save action to react to read-only rule changes * remove unused --- ...ace-split-view-variant-selector.element.ts | 9 +++++ .../workspace/actions/save.action.ts | 35 ++++++++++++++----- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts index 194319a1e9..3ee0a82c33 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts @@ -76,6 +76,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement< this.#observeVariants(workspaceContext); this.#observeActiveVariants(workspaceContext); this.#observeCurrentVariant(); + this.#observeReadOnlyGuardRules(workspaceContext); this.observe( workspaceContext?.variesBySegment, @@ -152,6 +153,14 @@ export class UmbWorkspaceSplitViewVariantSelectorElement< ); } + #observeReadOnlyGuardRules(workspaceContext?: UmbVariantDatasetWorkspaceContext) { + this.observe( + workspaceContext?.readOnlyGuard.rules, + () => this.#setReadOnlyCultures(workspaceContext), + 'umbObserveReadOnlyGuardRules', + ); + } + #handleInput(event: UUIInputEvent) { if (event instanceof UUIInputEvent) { const target = event.composedPath()[0] as UUIInputElement; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/save.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/save.action.ts index eb8e00e2ea..c17c8b7e66 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/save.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/save.action.ts @@ -1,5 +1,6 @@ import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '../document-workspace.context-token.js'; import type UmbDocumentWorkspaceContext from '../document-workspace.context.js'; +import type { UmbDocumentVariantModel } from '../../types.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import { @@ -13,6 +14,8 @@ export class UmbDocumentSaveWorkspaceAction extends UmbSaveWorkspaceAction implements UmbWorkspaceActionDefaultKind { + #variants: Array | undefined; + constructor( host: UmbControllerHost, args: UmbSaveWorkspaceActionArgs, @@ -32,25 +35,39 @@ export class UmbDocumentSaveWorkspaceAction override _gotWorkspaceContext() { super._gotWorkspaceContext(); this.#observeVariants(); + this.#observeReadOnlyGuardRules(); } #observeVariants() { this.observe( this._workspaceContext?.variants, (variants) => { - const allVariantsAreReadOnly = - variants?.filter((variant) => - this._workspaceContext!.readOnlyGuard.getIsPermittedForVariant(UmbVariantId.Create(variant)), - ).length === variants?.length; - if (allVariantsAreReadOnly) { - this.disable(); - } else { - this.enable(); - } + this.#variants = variants; + this.#checkReadOnlyGuardRules(); }, 'saveWorkspaceActionVariantsObserver', ); } + + #observeReadOnlyGuardRules() { + this.observe( + this._workspaceContext?.readOnlyGuard.rules, + () => this.#checkReadOnlyGuardRules(), + 'umbObserveReadOnlyGuardRules', + ); + } + + #checkReadOnlyGuardRules() { + const allVariantsAreReadOnly = + this.#variants?.filter((variant) => + this._workspaceContext!.readOnlyGuard.getIsPermittedForVariant(UmbVariantId.Create(variant)), + ).length === this.#variants?.length; + if (allVariantsAreReadOnly) { + this.disable(); + } else { + this.enable(); + } + } } export { UmbDocumentSaveWorkspaceAction as api }; From b3d86d53953e4623481956b0c4dcd940a2dbea0f Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 30 Jun 2025 14:37:52 +0200 Subject: [PATCH 62/82] build(deps): bump @umbraco-ui/uui from 1.14.0 to 1.14.1 --- src/Umbraco.Web.UI.Client/package-lock.json | 964 ++++++++++---------- src/Umbraco.Web.UI.Client/package.json | 6 +- 2 files changed, 485 insertions(+), 485 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 0daaaf60ba..37ff56412f 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -27,8 +27,8 @@ "@tiptap/extension-underline": "2.11.7", "@tiptap/pm": "2.11.7", "@tiptap/starter-kit": "2.11.7", - "@umbraco-ui/uui": "1.14.0", - "@umbraco-ui/uui-css": "1.14.0", + "@umbraco-ui/uui": "1.14.1", + "@umbraco-ui/uui-css": "1.14.1", "dompurify": "^3.2.5", "element-internals-polyfill": "^3.0.2", "lit": "^3.3.0", @@ -4510,908 +4510,908 @@ "link": true }, "node_modules/@umbraco-ui/uui": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.14.0.tgz", - "integrity": "sha512-et9xGGEcFyIBaMzSbPFt81SDyPdGyV8qyZzLePbs4vDTJiqjtefl0ICZib3Cwm8X4TjCXOcbVMU84wV2RCcIsQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.14.1.tgz", + "integrity": "sha512-j00nw54rmabF7k+04NZpZKWalfGsVtO8puiOb6dfZF9xabPc5z6JOjpFnPCkRbKoytD9/qSyqF1Nb61C6jpWoA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-action-bar": "1.14.0", - "@umbraco-ui/uui-avatar": "1.14.0", - "@umbraco-ui/uui-avatar-group": "1.14.0", - "@umbraco-ui/uui-badge": "1.14.0", - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-boolean-input": "1.14.0", - "@umbraco-ui/uui-box": "1.14.0", - "@umbraco-ui/uui-breadcrumbs": "1.14.0", - "@umbraco-ui/uui-button": "1.14.0", - "@umbraco-ui/uui-button-copy-text": "1.14.0", - "@umbraco-ui/uui-button-group": "1.14.0", - "@umbraco-ui/uui-button-inline-create": "1.14.0", - "@umbraco-ui/uui-card": "1.14.0", - "@umbraco-ui/uui-card-block-type": "1.14.0", - "@umbraco-ui/uui-card-content-node": "1.14.0", - "@umbraco-ui/uui-card-media": "1.14.0", - "@umbraco-ui/uui-card-user": "1.14.0", - "@umbraco-ui/uui-caret": "1.14.0", - "@umbraco-ui/uui-checkbox": "1.14.0", - "@umbraco-ui/uui-color-area": "1.14.0", - "@umbraco-ui/uui-color-picker": "1.14.0", - "@umbraco-ui/uui-color-slider": "1.14.0", - "@umbraco-ui/uui-color-swatch": "1.14.0", - "@umbraco-ui/uui-color-swatches": "1.14.0", - "@umbraco-ui/uui-combobox": "1.14.0", - "@umbraco-ui/uui-combobox-list": "1.14.0", - "@umbraco-ui/uui-css": "1.14.0", - "@umbraco-ui/uui-dialog": "1.14.0", - "@umbraco-ui/uui-dialog-layout": "1.14.0", - "@umbraco-ui/uui-file-dropzone": "1.14.0", - "@umbraco-ui/uui-file-preview": "1.14.0", - "@umbraco-ui/uui-form": "1.14.0", - "@umbraco-ui/uui-form-layout-item": "1.14.0", - "@umbraco-ui/uui-form-validation-message": "1.14.0", - "@umbraco-ui/uui-icon": "1.14.0", - "@umbraco-ui/uui-icon-registry": "1.14.0", - "@umbraco-ui/uui-icon-registry-essential": "1.14.0", - "@umbraco-ui/uui-input": "1.14.0", - "@umbraco-ui/uui-input-file": "1.14.0", - "@umbraco-ui/uui-input-lock": "1.14.0", - "@umbraco-ui/uui-input-password": "1.14.0", - "@umbraco-ui/uui-keyboard-shortcut": "1.14.0", - "@umbraco-ui/uui-label": "1.14.0", - "@umbraco-ui/uui-loader": "1.14.0", - "@umbraco-ui/uui-loader-bar": "1.14.0", - "@umbraco-ui/uui-loader-circle": "1.14.0", - "@umbraco-ui/uui-menu-item": "1.14.0", - "@umbraco-ui/uui-modal": "1.14.0", - "@umbraco-ui/uui-pagination": "1.14.0", - "@umbraco-ui/uui-popover": "1.14.0", - "@umbraco-ui/uui-popover-container": "1.14.0", - "@umbraco-ui/uui-progress-bar": "1.14.0", - "@umbraco-ui/uui-radio": "1.14.0", - "@umbraco-ui/uui-range-slider": "1.14.0", - "@umbraco-ui/uui-ref": "1.14.0", - "@umbraco-ui/uui-ref-list": "1.14.0", - "@umbraco-ui/uui-ref-node": "1.14.0", - "@umbraco-ui/uui-ref-node-data-type": "1.14.0", - "@umbraco-ui/uui-ref-node-document-type": "1.14.0", - "@umbraco-ui/uui-ref-node-form": "1.14.0", - "@umbraco-ui/uui-ref-node-member": "1.14.0", - "@umbraco-ui/uui-ref-node-package": "1.14.0", - "@umbraco-ui/uui-ref-node-user": "1.14.0", - "@umbraco-ui/uui-scroll-container": "1.14.0", - "@umbraco-ui/uui-select": "1.14.0", - "@umbraco-ui/uui-slider": "1.14.0", - "@umbraco-ui/uui-symbol-expand": "1.14.0", - "@umbraco-ui/uui-symbol-file": "1.14.0", - "@umbraco-ui/uui-symbol-file-dropzone": "1.14.0", - "@umbraco-ui/uui-symbol-file-thumbnail": "1.14.0", - "@umbraco-ui/uui-symbol-folder": "1.14.0", - "@umbraco-ui/uui-symbol-lock": "1.14.0", - "@umbraco-ui/uui-symbol-more": "1.14.0", - "@umbraco-ui/uui-symbol-sort": "1.14.0", - "@umbraco-ui/uui-table": "1.14.0", - "@umbraco-ui/uui-tabs": "1.14.0", - "@umbraco-ui/uui-tag": "1.14.0", - "@umbraco-ui/uui-textarea": "1.14.0", - "@umbraco-ui/uui-toast-notification": "1.14.0", - "@umbraco-ui/uui-toast-notification-container": "1.14.0", - "@umbraco-ui/uui-toast-notification-layout": "1.14.0", - "@umbraco-ui/uui-toggle": "1.14.0", - "@umbraco-ui/uui-visually-hidden": "1.14.0" + "@umbraco-ui/uui-action-bar": "1.14.1", + "@umbraco-ui/uui-avatar": "1.14.1", + "@umbraco-ui/uui-avatar-group": "1.14.1", + "@umbraco-ui/uui-badge": "1.14.1", + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-boolean-input": "1.14.1", + "@umbraco-ui/uui-box": "1.14.1", + "@umbraco-ui/uui-breadcrumbs": "1.14.1", + "@umbraco-ui/uui-button": "1.14.1", + "@umbraco-ui/uui-button-copy-text": "1.14.1", + "@umbraco-ui/uui-button-group": "1.14.1", + "@umbraco-ui/uui-button-inline-create": "1.14.1", + "@umbraco-ui/uui-card": "1.14.1", + "@umbraco-ui/uui-card-block-type": "1.14.1", + "@umbraco-ui/uui-card-content-node": "1.14.1", + "@umbraco-ui/uui-card-media": "1.14.1", + "@umbraco-ui/uui-card-user": "1.14.1", + "@umbraco-ui/uui-caret": "1.14.1", + "@umbraco-ui/uui-checkbox": "1.14.1", + "@umbraco-ui/uui-color-area": "1.14.1", + "@umbraco-ui/uui-color-picker": "1.14.1", + "@umbraco-ui/uui-color-slider": "1.14.1", + "@umbraco-ui/uui-color-swatch": "1.14.1", + "@umbraco-ui/uui-color-swatches": "1.14.1", + "@umbraco-ui/uui-combobox": "1.14.1", + "@umbraco-ui/uui-combobox-list": "1.14.1", + "@umbraco-ui/uui-css": "1.14.1", + "@umbraco-ui/uui-dialog": "1.14.1", + "@umbraco-ui/uui-dialog-layout": "1.14.1", + "@umbraco-ui/uui-file-dropzone": "1.14.1", + "@umbraco-ui/uui-file-preview": "1.14.1", + "@umbraco-ui/uui-form": "1.14.1", + "@umbraco-ui/uui-form-layout-item": "1.14.1", + "@umbraco-ui/uui-form-validation-message": "1.14.1", + "@umbraco-ui/uui-icon": "1.14.1", + "@umbraco-ui/uui-icon-registry": "1.14.1", + "@umbraco-ui/uui-icon-registry-essential": "1.14.1", + "@umbraco-ui/uui-input": "1.14.1", + "@umbraco-ui/uui-input-file": "1.14.1", + "@umbraco-ui/uui-input-lock": "1.14.1", + "@umbraco-ui/uui-input-password": "1.14.1", + "@umbraco-ui/uui-keyboard-shortcut": "1.14.1", + "@umbraco-ui/uui-label": "1.14.1", + "@umbraco-ui/uui-loader": "1.14.1", + "@umbraco-ui/uui-loader-bar": "1.14.1", + "@umbraco-ui/uui-loader-circle": "1.14.1", + "@umbraco-ui/uui-menu-item": "1.14.1", + "@umbraco-ui/uui-modal": "1.14.1", + "@umbraco-ui/uui-pagination": "1.14.1", + "@umbraco-ui/uui-popover": "1.14.1", + "@umbraco-ui/uui-popover-container": "1.14.1", + "@umbraco-ui/uui-progress-bar": "1.14.1", + "@umbraco-ui/uui-radio": "1.14.1", + "@umbraco-ui/uui-range-slider": "1.14.1", + "@umbraco-ui/uui-ref": "1.14.1", + "@umbraco-ui/uui-ref-list": "1.14.1", + "@umbraco-ui/uui-ref-node": "1.14.1", + "@umbraco-ui/uui-ref-node-data-type": "1.14.1", + "@umbraco-ui/uui-ref-node-document-type": "1.14.1", + "@umbraco-ui/uui-ref-node-form": "1.14.1", + "@umbraco-ui/uui-ref-node-member": "1.14.1", + "@umbraco-ui/uui-ref-node-package": "1.14.1", + "@umbraco-ui/uui-ref-node-user": "1.14.1", + "@umbraco-ui/uui-scroll-container": "1.14.1", + "@umbraco-ui/uui-select": "1.14.1", + "@umbraco-ui/uui-slider": "1.14.1", + "@umbraco-ui/uui-symbol-expand": "1.14.1", + "@umbraco-ui/uui-symbol-file": "1.14.1", + "@umbraco-ui/uui-symbol-file-dropzone": "1.14.1", + "@umbraco-ui/uui-symbol-file-thumbnail": "1.14.1", + "@umbraco-ui/uui-symbol-folder": "1.14.1", + "@umbraco-ui/uui-symbol-lock": "1.14.1", + "@umbraco-ui/uui-symbol-more": "1.14.1", + "@umbraco-ui/uui-symbol-sort": "1.14.1", + "@umbraco-ui/uui-table": "1.14.1", + "@umbraco-ui/uui-tabs": "1.14.1", + "@umbraco-ui/uui-tag": "1.14.1", + "@umbraco-ui/uui-textarea": "1.14.1", + "@umbraco-ui/uui-toast-notification": "1.14.1", + "@umbraco-ui/uui-toast-notification-container": "1.14.1", + "@umbraco-ui/uui-toast-notification-layout": "1.14.1", + "@umbraco-ui/uui-toggle": "1.14.1", + "@umbraco-ui/uui-visually-hidden": "1.14.1" } }, "node_modules/@umbraco-ui/uui-action-bar": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.14.0.tgz", - "integrity": "sha512-cTX0TvVxNC7EFMtEqMGMBFC8E5O8bedmJ1Hkddvp4lAzrbLGrFTPcwOG/kISaSXzFrnMzyQNdi3s23orcL5VRA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.14.1.tgz", + "integrity": "sha512-EEYN9iF3kFZa4glujIZ2aMvQavis7/Y2WHpVlazZpD47Q1XA1mK6g31R0azt/MSCwulOp0WYYVB7wSHR9i8v/Q==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-button-group": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-button-group": "1.14.1" } }, "node_modules/@umbraco-ui/uui-avatar": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.14.0.tgz", - "integrity": "sha512-ykYlbHV4K+zW7viv+oqfsGcL0ZII4vQy3YnPusFiz6bS3ceDDpY9MpRtuDTv4z+PXW4Wo1FjB2iMHrza55/RUw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.14.1.tgz", + "integrity": "sha512-FmHWKw6XPdL5T1Lb1JaEiueIic1ONTsTZx9DWqTuUtwDz85P10E0HJW0zQA7jcvAoNhVBvj2/mJboB6MK8EPJw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-avatar-group": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.14.0.tgz", - "integrity": "sha512-8pLxQvtW1yuaReuSy0wq6kYZXPSiZjKv8ecmciLgWr9aKGR++CwYrwWKA3c+jZTarb8dz4MGMnQpqHCTqlQbpQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.14.1.tgz", + "integrity": "sha512-0HLJ0oU6v6Pp470wclTIB6zk84jLJJuNEOxcSJYkReXEAenNyydgjdq2gG5rnImJeiwtn9cqCHcv0XgZnMiLPA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-avatar": "1.14.0", - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-avatar": "1.14.1", + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-badge": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.14.0.tgz", - "integrity": "sha512-iUosWuf7XngJBdwmvx8BZkzsollH4146Gt2CQBGltFZRCZ7uUkB2zCYb2E1ys4BEWuKHK4ZLiOcYtpPtoNeZJQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.14.1.tgz", + "integrity": "sha512-PtjLNwjAYZfX07ZYbI5SQOLzD6Z2l6eTEEOmMBEjd3T+1oFVoq2m1TtvMjGmcIjABQ89H9GIYkiQgOGk31CG1g==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-base": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.14.0.tgz", - "integrity": "sha512-m/BQYeKL9XmHPfHfCfSwTjcmUmJxynI0m4yqLTmQqQ3x1hiRqqfYyLSrpi3uW1H/HCxttUkxEwkhAdYogMDIpQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.14.1.tgz", + "integrity": "sha512-N+Gkcuu2raF79EACVpEhgWXxfohnuKqmVytTXyAh8N2QmLGaUPiO7/NDknOLgbY3lf9be2VyEaQ5N931zDBcVQ==", "license": "MIT", "peerDependencies": { "lit": ">=2.8.0" } }, "node_modules/@umbraco-ui/uui-boolean-input": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.14.0.tgz", - "integrity": "sha512-O+/GzpF2mNLdhXXNAfxI0k5VaR7CUnUxDDxYPhMgmuLOBwdjiq9iScJM4MUl+l7hihF5ue7os6I8DY2CnG7LJQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.14.1.tgz", + "integrity": "sha512-GwQePrMnHV7r6JFUKgYZzLfETEyAOK5SimSEZ4N+VMdpEqtmjqMXUJ34Te5Y5ca20ZK1ACL4cs/Dgz76af9Yaw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-box": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.14.0.tgz", - "integrity": "sha512-VjD6MtEnJuHOYarFtLvn/Dyz2MRJ0sPXSDTZ3HWsF0G5fdAUB487ErOGb8CL1JtmUYgZOy6N3CqPlFyWHD+DIA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.14.1.tgz", + "integrity": "sha512-8YULja9KMOhpBigqOgZi0NunobqJyesKmYWGmrHFFdifbh9kTrGyP5eES70etNAfiqC1gLJAcynPd9fmcAzYAQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-css": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-css": "1.14.1" } }, "node_modules/@umbraco-ui/uui-breadcrumbs": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.14.0.tgz", - "integrity": "sha512-IxHPUnIaGyvo54oDdcJf4AfzkYF1Nf727SCLHD28WqMh4QCKQQsyBGa5xhFjcQ4RSediNwvAnY7dNVVYu9OrzQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.14.1.tgz", + "integrity": "sha512-0Llsq8J/SUH+EbxcY5kmkAzCtsxVCq9H0HBDJ/YsOF6wl8imAouvuwKBCUPFNGsGXncsVEpQfpMkzNw/5VR+Qg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-button": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.14.0.tgz", - "integrity": "sha512-TVCPLVcXR4wGjtLtrTYOjoAdvwQPiCep1GiHAbozD0QKNgOqjZ2fE3CapwEwoaSNGcgw/37t0KMhUqfmZgYo2g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.14.1.tgz", + "integrity": "sha512-2nll4AVHqbp0UFkbc7oevMqG2ZnfM3mjV/YpSsGzJBqsNefP3t0vbcTQ/Fp11j4MWOgaFiLQR7Z0YVFMZ5qn4g==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-icon-registry-essential": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-icon-registry-essential": "1.14.1" } }, "node_modules/@umbraco-ui/uui-button-copy-text": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-copy-text/-/uui-button-copy-text-1.14.0.tgz", - "integrity": "sha512-cE3ZjSaWzzdgYdNtGv1SzIClMoCxqL86+QPz9zMYvN//yA8YQmkv7y2eUyT+lKFNJXXHMgzVKMhoSn8aUzvQrA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-copy-text/-/uui-button-copy-text-1.14.1.tgz", + "integrity": "sha512-kfJFXdNr0nsXNFkNrwDbx0k9cA8E2YV22Vm9vz3fmulcBJyJW+fs6MW+k8JJMiSvXfMPZOPxmQKG4mTFl9x6pA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-button": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-button": "1.14.1" } }, "node_modules/@umbraco-ui/uui-button-group": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.14.0.tgz", - "integrity": "sha512-W4Jf671PqtnBnYKWNyyB6rgq88qyT0IWhqUR3ilJS45znIiht/ec5xDhTFoyhLWP9+zQn/3e8EqZbmnJUj2HAA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.14.1.tgz", + "integrity": "sha512-oJpp3U7Ec5sl304PbhDB/pCXLKkHMJ4bYSLr9kngi3eU1n66RCVpRFNSowBuo/ciKgWWNoTcYPJCcimamd6OQQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-button-inline-create": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.14.0.tgz", - "integrity": "sha512-vDOZJEfjQDqIKymdpxD3h/uvBacXu/yD/xnHMrxADeMQYinvNn0AFjTFBakgfusymRLjXQubrJ63MWqidTRsQQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.14.1.tgz", + "integrity": "sha512-q92E/jfPRSQN7+pgWpq65mTuIGvdJ/M+Kp23ru4ziTLRTkLiGp2ULKwICP+G2RsrfLfGzjmrSzjbTlhC5C13lA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-card": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.14.0.tgz", - "integrity": "sha512-9A44pCbx9nyBtbvFE26FiP+rLE2rUg177vgoMTuURuszYoiEgfU8ixVhWCbDD14LpxET0/Yg9RNiMYF8K1bDvw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.14.1.tgz", + "integrity": "sha512-cfvoUvqgG4lYwNgq0X0iVIj1OJ03cEmLqrWnChUDULFu4JKwO+CDUeKK1R75t/QNgPoR569bDuXaWJ8m56LoBg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-card-block-type": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-block-type/-/uui-card-block-type-1.14.0.tgz", - "integrity": "sha512-FQAInMb4AKj11Jy3TQTc6iz80h0ulmlraw3CtFbnOpwHIRP/aqUVGCW0Zb+Yykz1DGmmGvFE1u1epK/jH//6aQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-block-type/-/uui-card-block-type-1.14.1.tgz", + "integrity": "sha512-dl9Hxa56Z+te3/oAiRFZLTsSjr8YTGKW/u1Gz8lpuU7qQzGXwZkwNQt7S0GHd4BnUwulS5sfa2V2c/IXFH45oQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-card": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-card": "1.14.1" } }, "node_modules/@umbraco-ui/uui-card-content-node": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.14.0.tgz", - "integrity": "sha512-KcXiUfG0ulgvXWuqOGu3LTcRVoGru+Q4sj4q0bV9H/d3ZfY1idPqhkbM7v6TO56gzCng0DJ/kTL0/H5IWd8IcA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.14.1.tgz", + "integrity": "sha512-56zGxxZ8hGrMJePgBh11TTLKxUbDYqtiX4DhTaImACVp8H/jK6bWGvrmIK3dlq0IbK82mnanHYd3IxCiuZejxg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-card": "1.14.0", - "@umbraco-ui/uui-icon": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-card": "1.14.1", + "@umbraco-ui/uui-icon": "1.14.1" } }, "node_modules/@umbraco-ui/uui-card-media": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.14.0.tgz", - "integrity": "sha512-Lnr8Y1bxj6QoleSMCj8GDsyJu1N5Rm105/nHYdnPO3+JcNNv3ThodKdHXYo/slSLrcVOoPJHNAQodZG66mIqsg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.14.1.tgz", + "integrity": "sha512-6/XJSaJT3Px4FrkArVQeRvkBUyoWhIDJCZAxbrvWsFdpEhRxqkLZdzqCeTdcWfZQf228ijKW7z9Fqvdaf2ZTqw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-card": "1.14.0", - "@umbraco-ui/uui-symbol-file": "1.14.0", - "@umbraco-ui/uui-symbol-folder": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-card": "1.14.1", + "@umbraco-ui/uui-symbol-file": "1.14.1", + "@umbraco-ui/uui-symbol-folder": "1.14.1" } }, "node_modules/@umbraco-ui/uui-card-user": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.14.0.tgz", - "integrity": "sha512-ZBFWO2109+A9SkkznqNHUiul6G6zab/D318yz0wMTW6m2R0E8QE9mljIw8Entd720HeZlvOKpvK3ElSTNlxnJg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.14.1.tgz", + "integrity": "sha512-yoyOuKpzWghuVY7j5AZJLTmmzM1+4EHTqSxbDG04Bi0RM35+o9Rr8m0rEyyov/pRPSn47dpzUFHWxbrYJojSxQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-avatar": "1.14.0", - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-card": "1.14.0" + "@umbraco-ui/uui-avatar": "1.14.1", + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-card": "1.14.1" } }, "node_modules/@umbraco-ui/uui-caret": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.14.0.tgz", - "integrity": "sha512-c+71esCgWn7V6Z8gr9fZkfw9BQgewZi5pbJ8R1G6HLEzz0NN11zAn5BAVebdxF5OUi/ajFqvxnAYOSSiWel5tg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.14.1.tgz", + "integrity": "sha512-6z+ZGNQtpGs4wwg5jO91QUGqywJuDkg5QL4oXC5gaXlsf3SGH7Qc4hwsNiNjkiFjUmfIR2cafGtyZeziWjAIvg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-checkbox": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.14.0.tgz", - "integrity": "sha512-qD/O8H7pcPnJkaf5iWjDKg89LgQKZeuBiRmrXqVePDk0HHjdZ+8TJlDaANRyBq5JePezrj6UpHPVabYDqXIJYQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.14.1.tgz", + "integrity": "sha512-xabQds7W7BXkFtIomPq+HSzOitZiYRPB1v/GN/y/EtQ/f6/zUAiLvE3IJZx8hIgF7Z75RkTANRF5GhPQPN+Cwg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-boolean-input": "1.14.0", - "@umbraco-ui/uui-icon-registry-essential": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-boolean-input": "1.14.1", + "@umbraco-ui/uui-icon-registry-essential": "1.14.1" } }, "node_modules/@umbraco-ui/uui-color-area": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.14.0.tgz", - "integrity": "sha512-ijja8REx/1OhG2ZA1yK98Q8IhSeDf+GIjfCvkR1ptzzFkz1Wiv1mvxkh9eExByidp90SgsTF3kdUxR8x6V570A==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.14.1.tgz", + "integrity": "sha512-I7O7XH/VWHROywwudJG1Z5mgarlabpZXUoRdeqCfBwtbgIstO+Nh812eGwCEyISBeNOuPUKwM1aamAG4XBRQ9A==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", + "@umbraco-ui/uui-base": "1.14.1", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-picker": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.14.0.tgz", - "integrity": "sha512-WG7I2mYDjW3W27V3LDRpUrZfkjnnuHPo8+X4ZBnY6xRXnQ83hxbdqXkaKYI6VY1dMhhqGa82wrbb4NBHGkKBiQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.14.1.tgz", + "integrity": "sha512-OqTXrVNs0WiiPiaeXbPskd16zdiy+yn1D16x4pxG9eI2d6Y92JYmHjd9kyzNL4eBtwxEBZc7o89vlhJYco7vIg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-popover-container": "1.14.0", + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-popover-container": "1.14.1", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-slider": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.14.0.tgz", - "integrity": "sha512-8eNA+7GJNVl68amAJIbCWMe/8usWanZ1fKXPf3ZJ54K65K2cDYd2hS7DEVEwSXo+AV9iMeBYgbHIRBqVPZ89jw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.14.1.tgz", + "integrity": "sha512-DmzE/D20HtvVkYgw5imbjyuJfIooZMxM2ViqemzwlnWUiuEkGLOdzEybuwHLa2leMlNmhWHotUN6JRK/0L7hSA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-color-swatch": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.14.0.tgz", - "integrity": "sha512-1c2bNmEqL5J1ZW24adzSsGDwnYFQOyjsI29M+UQdlTZW16s3zh9O97414KIN9ivE+SkgbE7c9lZhNEKyi2IJpw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.14.1.tgz", + "integrity": "sha512-PMlNKgCuVgfhUgVjtLVbaLQYcPGQMIs8d33EI0l1422MJtkp2ecn9xqJ840r1t+8Ly5OrANc6PPPr1vTvdV+wQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-icon-registry-essential": "1.14.0", + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-icon-registry-essential": "1.14.1", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-swatches": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.14.0.tgz", - "integrity": "sha512-UIQysF89CZH0CKwhzbd+1BZAXxUlnCmHoWDGot+Mb4sGZL5esrEB0QQmhJOVO/ehMP+GoFUnh4fWLXUCzRPdvw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.14.1.tgz", + "integrity": "sha512-8R2iKw62ZY8op+ONOQhpblXO44jKF4tulfFniQznGVBOcIa4PZHn/Q5TYNFTzXp+EUNaZsR7pcuna313yxYTow==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-color-swatch": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-color-swatch": "1.14.1" } }, "node_modules/@umbraco-ui/uui-combobox": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.14.0.tgz", - "integrity": "sha512-ZKa0KF0ADSX//hm116QdEDjQgyZK1ahY+hzOtdU7EDlJBQdTq3cHtwn6B8JdhPoVlS0Yd3XB+oQ7UXjYn7rGQQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.14.1.tgz", + "integrity": "sha512-BKRmcSdUWjs1cBkbWBeVWKtexJpRjAH+eTGwXwJDgi73ygT9PiPEMjHZobC+lUyoa2hWF/yHnubJS4SzPrTirg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-button": "1.14.0", - "@umbraco-ui/uui-combobox-list": "1.14.0", - "@umbraco-ui/uui-icon": "1.14.0", - "@umbraco-ui/uui-popover-container": "1.14.0", - "@umbraco-ui/uui-scroll-container": "1.14.0", - "@umbraco-ui/uui-symbol-expand": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-button": "1.14.1", + "@umbraco-ui/uui-combobox-list": "1.14.1", + "@umbraco-ui/uui-icon": "1.14.1", + "@umbraco-ui/uui-popover-container": "1.14.1", + "@umbraco-ui/uui-scroll-container": "1.14.1", + "@umbraco-ui/uui-symbol-expand": "1.14.1" } }, "node_modules/@umbraco-ui/uui-combobox-list": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.14.0.tgz", - "integrity": "sha512-CRsRycwyb9CeyNINQ1KztGAHTRhQcphVEl/bLVr3jTtuqSWWxKsGQVDe69iKNAfHuiU3o7MlsUH0+ea296x/8w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.14.1.tgz", + "integrity": "sha512-mc3I1Rwydmu+emSPuLBr6LeX6tGsEip28oHp3RSveUrEo+BaCIOhTH4yZRD+kXC3KqYMd9LaNldmSbndAALI1Q==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-css": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.14.0.tgz", - "integrity": "sha512-M0zmrjBpDzrb3r+l1qMNGEhJbJSHNeR7PDtpHoMaO96ozaZSL/87XzpwsBklwTR9xyfm+VgDFNTqQXqYnS2e/A==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.14.1.tgz", + "integrity": "sha512-J5wtUKx0gCc4K2R6wkXk6pQJEJzy8M5a/npOignp0EcHxoOOLHQyoTQoS6TffK1FBFD0QwrsiS7fHyJFRbWaDA==", "license": "MIT", "peerDependencies": { "lit": ">=2.8.0" } }, "node_modules/@umbraco-ui/uui-dialog": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.14.0.tgz", - "integrity": "sha512-eZdmNLkSW5OAETTZlvUKByQbXv/4/tYznNHCHyWxxGrYuHVHh5sNj+3ZUbZp+VjIy1zd42slKh/KDmYV6pBarQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.14.1.tgz", + "integrity": "sha512-hUSZLCxS12SXJnx4NSO7MSZOOPYLF61lZk5SFz3z1JMqsAjn06Rj9rWaM+Yol4LFlJdio1jmQn6KzwlQblW/Sw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-css": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-css": "1.14.1" } }, "node_modules/@umbraco-ui/uui-dialog-layout": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.14.0.tgz", - "integrity": "sha512-rYlwHk5zsX+eBZLBxI/68W6Q1vb7G/NuZoasquQXZ7jgxRhaRw199YQojtUCWtIowWn2uqqbD2a0RYPs9n3FIg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.14.1.tgz", + "integrity": "sha512-7G+8U8iYYTkiQruljL5focZgSwVaw6AeWFfIETaSqCsOjqtM8QnrcHXMDuncbPUDGe01B7EI9fMC3PpB+jTLzg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-file-dropzone": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.14.0.tgz", - "integrity": "sha512-GSy0mlR5KsyC9oF3CMB2qwuGiT5P3moVFxanRAO7u8qimRAO2jLS0/8u1QCh120AGRQZzDhw/TJ9XF7NXTWJtA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.14.1.tgz", + "integrity": "sha512-KSYuwmvzUNA3fUp/zRmhwE7jL9Q/FEIvoI1Wx01mRtodOkDaEnntYUwwAko0cGw8boVQ9IkS00771TXxKX0dbg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-symbol-file-dropzone": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-symbol-file-dropzone": "1.14.1" } }, "node_modules/@umbraco-ui/uui-file-preview": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.14.0.tgz", - "integrity": "sha512-UGxlpKoCVjFYbkNfXcMi0kCSjcocnHlTHH1fyk/Mg5jZ1OZCmV8dnQQKCB139X9FdHZhL0QeZA3KZUYA28iqaQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.14.1.tgz", + "integrity": "sha512-kJy5HPLakwDKfRz9Z2kX/ZrpFNws/6jq4NXCC9OhBqZ87NHM/OZQ5wY0AwUFbwTHu3+HlJXhKPUQ8kcfnxzh0w==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-symbol-file": "1.14.0", - "@umbraco-ui/uui-symbol-file-thumbnail": "1.14.0", - "@umbraco-ui/uui-symbol-folder": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-symbol-file": "1.14.1", + "@umbraco-ui/uui-symbol-file-thumbnail": "1.14.1", + "@umbraco-ui/uui-symbol-folder": "1.14.1" } }, "node_modules/@umbraco-ui/uui-form": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.14.0.tgz", - "integrity": "sha512-UoEP62nCNTa4ILDNFX2ASNN95XfUugPhGmtUdKmvTUH6F3NSai2iiLIp/dM+GBC4PJXmt8rzq6NdLqYonkMK+w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.14.1.tgz", + "integrity": "sha512-hgdJygXYJmTzgkdao9XTnFmBGg3vUSmCsJGyKbx94yScvb0C/kf1tHQj1qTRfoRH+dn35G05DIvYWTTGPtfKjg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-form-layout-item": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.14.0.tgz", - "integrity": "sha512-1ahnmF9Ciw0RC/pRAS3FJ2vVmnpQ6O20bwqJrCTYvJQeqJXV3bzSxYmMY/s6Z5tsoNDzkfYcTHfnti/MmyuFJw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.14.1.tgz", + "integrity": "sha512-1jZCT8A8SMLJgXu06gTCIP8olT9ycplRMNwJnBw5BO6mH4IM1z1A2gS353W1nEmxghxHHLOkFj0YpRAVZ7SYFw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-form-validation-message": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-form-validation-message": "1.14.1" } }, "node_modules/@umbraco-ui/uui-form-validation-message": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.14.0.tgz", - "integrity": "sha512-rv+mId8htw/8V3rle5bOjgWK8X+3IX7B+PAvFAfy+lc89OUV+OT04RGy0sg3hhncoPsIT8EhQ2MYunIyh3MwnA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.14.1.tgz", + "integrity": "sha512-IWs4bIysbgl9V614BGIAPIDVmbNc1XRVWgrmtcDbIqXICWEZJVGoG24p1vatq2MUQzvgjyuqhWYbKNfeyvxYWQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-icon": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.14.0.tgz", - "integrity": "sha512-IdBRPC8xc9leIBRaHmTVoGhxRkz8CNeYjgJLNBauFox5uSkWuE7OE9BUYBJKdZz4k8yHLHHrWHVkcaHvgF+QUw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.14.1.tgz", + "integrity": "sha512-8AeS20Wi46vHiDJ5piv7LeFHrGK+Thuvairhv32SR0EuVDS12treR2Bq2HdFLBrKH6JUVLN2C8XeN7xOoR89oQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-icon-registry": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.14.0.tgz", - "integrity": "sha512-N9cXDF6B3R+h2TCaCHkOJUTSsD10Wei8NrldvYL2fhBqG8FgaquqBI/715NGoRtwp9KKz74N/Z6EIn2MBiMaMQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.14.1.tgz", + "integrity": "sha512-VwQy66wjEHSd1ZeGuUikcRLnPGUaVOC1ZZ+3xeVF9yyt0HlHM5oC8wWFyXHA38rfeJqZZJu43vi/SigUnfD62Q==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-icon": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-icon": "1.14.1" } }, "node_modules/@umbraco-ui/uui-icon-registry-essential": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.14.0.tgz", - "integrity": "sha512-NjkNmQpMHLcyGakqGlASyPOr8Vnr8+KCdExfkbDdg07iDFlzyGyNmCkTdzY2tNXsIq5bD1c4nzHYmE76FszorQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.14.1.tgz", + "integrity": "sha512-AxQ8HhZ5q3oXBHneEqW6krLeodCHEPbNgKxukF/xt4PPTOm1twrL/Li9k4RmTnv9IbM/tjcnEXRsBT2PDxJYAw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-icon-registry": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-icon-registry": "1.14.1" } }, "node_modules/@umbraco-ui/uui-input": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.14.0.tgz", - "integrity": "sha512-FeYiTUzCcZdNtury6B8ZMl66mW/fGfgXMB5HvIVDFp0ik+WpC8vLcQqHgJ/qFxWGF32H0qIsVqLnzcwkAwvRxw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.14.1.tgz", + "integrity": "sha512-17JqrblBduXemJZkV5hvYk11TmMgfyCVPBeZo50qotgfC9Pj1m3LRUiTZ0ANMP8VG6Pnur8LxXUJbXSdLqeNVw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-input-file": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.14.0.tgz", - "integrity": "sha512-l4RcQWf+0OLM9i9NWvnMkQtzzNcALBRmtiTBLdz6ROFm2Z+S3MuT8vzl0QiduJNWK5gzANu/FFuTL70fIh/BDw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.14.1.tgz", + "integrity": "sha512-1PNmly2DqvmjqUUPR7zIfdL8SvAQNK5BQnXuVobWEYL7b2om4yin2ZAtvSRrNWq5GfvY9WaQ8yHMutPIGjIq3Q==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-action-bar": "1.14.0", - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-button": "1.14.0", - "@umbraco-ui/uui-file-dropzone": "1.14.0", - "@umbraco-ui/uui-icon": "1.14.0", - "@umbraco-ui/uui-icon-registry-essential": "1.14.0" + "@umbraco-ui/uui-action-bar": "1.14.1", + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-button": "1.14.1", + "@umbraco-ui/uui-file-dropzone": "1.14.1", + "@umbraco-ui/uui-icon": "1.14.1", + "@umbraco-ui/uui-icon-registry-essential": "1.14.1" } }, "node_modules/@umbraco-ui/uui-input-lock": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.14.0.tgz", - "integrity": "sha512-wt/VL43EpHJcvf9GEnXSuHG/iW7yI7vD3wEWI+wgCKv9SdTzE/M4aPon/pxnQsVCvGvWhWvdFeGdlfwhXSurLQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.14.1.tgz", + "integrity": "sha512-z3VjqUB8aaXqmu4QVzRXLtUPcr+9OokEbswK8mXDgYNoBFuIqr7LNM8+Bwtt3Yr1E90+8RI3ATby9nqiIQLeDg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-button": "1.14.0", - "@umbraco-ui/uui-icon": "1.14.0", - "@umbraco-ui/uui-input": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-button": "1.14.1", + "@umbraco-ui/uui-icon": "1.14.1", + "@umbraco-ui/uui-input": "1.14.1" } }, "node_modules/@umbraco-ui/uui-input-password": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.14.0.tgz", - "integrity": "sha512-XCc/0QJH2w9PZJPouhbJbMR+w0QKUusut1MWW9NsfzRheHkcDuzc3Vf69OLFGGww/FjYjkxwT9as/2aLXxotjw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.14.1.tgz", + "integrity": "sha512-7S5iZkSFlWdYt7SsOGnXNrrTv1bXHpG6Cg/8xIxUD4cqbz2ckr8NrVCtkyELlJPuJqUMg08/WwY2Pr8RumBScw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-icon-registry-essential": "1.14.0", - "@umbraco-ui/uui-input": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-icon-registry-essential": "1.14.1", + "@umbraco-ui/uui-input": "1.14.1" } }, "node_modules/@umbraco-ui/uui-keyboard-shortcut": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.14.0.tgz", - "integrity": "sha512-G3LCdfP5uPe00bg8kKBMZhLan8gH7QbSRMX7aMsT+Fc6nAyWWTwJ/Qt4qJjk/fbeHts1OWD+sbHdRtXK+DotRA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.14.1.tgz", + "integrity": "sha512-XVeiahiUazGssS0/Wfhjnlg5nshWXwMjVY1IvfpUm3g3576NA4mMdeN7F7/8e6mVAzxLDx/xNG5VJA5nsIkpqQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-label": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.14.0.tgz", - "integrity": "sha512-a22p01O0CqnNTxQxmjPwCFBFXi5KKzhpno4DXjSDVTmeJc85IxiR5ODAELKHJf6XwZMkOv+QG+AZuIJFVEZ13Q==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.14.1.tgz", + "integrity": "sha512-or860nfJ0QTZAuKp8l8HIcmuFlJhGCk1mxvLxQVSQav6NSIuQ8WBvI5rDoFi2gAFfhYYFvudpvkgVmqCCRZ7bA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-loader": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.14.0.tgz", - "integrity": "sha512-2/HNDk0AZQ992hHYQk+VP5GetofSKxCsLf77/wiswyz48kM9eJ9wkieovxzLK1IuOQs0A+cCe2NnU/z5fZnvvw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.14.1.tgz", + "integrity": "sha512-U3z7mAei2BZqFqYImegzQeG3Qg8tCvlG7GcCR4vj8eQ8L3Y8EqRTI3cF3Izx4GranuJjTJ45HEw/lhMvZU5xlQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-loader-bar": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.14.0.tgz", - "integrity": "sha512-hAviuSx29RPWpYIqmWiGmW31r3nj8A1VGobmdVwR0BJHfdxee57ZrNGsEZhK6pzuHSvshGTITNwLk03E1UA/Nw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.14.1.tgz", + "integrity": "sha512-crzU1MXPN3gVwCGJgRt8Y7qY99jrHSMZlrM9lybqmqmjD3dOW+lEcZjP5zxoCMC2D1JY0gk0JB088FIzsB9hRA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-loader-circle": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.14.0.tgz", - "integrity": "sha512-I+rcgwbxwKGxLzVCGZ3qT4e/sK8CofTPzdCmh1BpNlKrWpuJ9NGgysrGs7V1IleJJxIXuzD+BBlIoGxuCwBJQg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.14.1.tgz", + "integrity": "sha512-aCPtJmA5mfo9cf5hV34cSY3J3c4ttffnV/I4FVZ9Lm2kja1IzUSx197a88nZoA6TngbBq5ZPeNomwqSjpxM5hA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-menu-item": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.14.0.tgz", - "integrity": "sha512-8Pc68dJLwl7GrbGIRD7MpyMSBkuz8/CtzuLhygrFHK608crg5bBPC1+Zdt3VdkqDk7QZRd5rtL+pYgEJm87Q4A==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.14.1.tgz", + "integrity": "sha512-AURyV7i0ydKeYNiz6yI9m3bFXFfbKIKNkicyznobxscr36HmQGEN+reVXnRL4cbW/NNgSvIC8BM///a5cQoyfQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-loader-bar": "1.14.0", - "@umbraco-ui/uui-symbol-expand": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-loader-bar": "1.14.1", + "@umbraco-ui/uui-symbol-expand": "1.14.1" } }, "node_modules/@umbraco-ui/uui-modal": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.14.0.tgz", - "integrity": "sha512-3Ux1guj029PIcUn4nmPUU29Oqxq1HoRUib3lWoRRIgJ3F8WyGms+GEgCMj4v/LzIdezczqVtxKdOMcLIm2gvcQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.14.1.tgz", + "integrity": "sha512-b0vxoawsZkj701fwLeVdUOd95TlXqZOwwWecTFxPj2ttQw54ReCziABbVGlYuJso6Ze/dR3rflR2i73EhYhiyw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-pagination": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.14.0.tgz", - "integrity": "sha512-jP906bsiXOBpAdF/ZVi0hlRyR/+HX52ocjItlvMJWc2Xt4Fpzms7W90buYcG4hvz7g0snKy84JgTMup5vxf2iQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.14.1.tgz", + "integrity": "sha512-DDKiypckWLQw5/qpheyxkxho0wlo9AtMHvxovUzjjuhTY9tZ6R+2SF9rJdzJuMWINOx/h9YZ+damKubLSpquYQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-button": "1.14.0", - "@umbraco-ui/uui-button-group": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-button": "1.14.1", + "@umbraco-ui/uui-button-group": "1.14.1" } }, "node_modules/@umbraco-ui/uui-popover": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.14.0.tgz", - "integrity": "sha512-blMgYLRlEUon7vAQ6s1KE0hNBgyuMeI7ugxHCMDAFwgtHIh9VO2YfPAqlKBkofM72R9QZDbkCg1tOUuuF0yX1Q==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.14.1.tgz", + "integrity": "sha512-fVEsj8UZREw/JAjAIRJWm3P8ncpuhx2FNSKR/3FaQxOcXNRJx0EQWkEpSV7AMxUADcpQH5xqEFL74MqaREuTGw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-popover-container": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.14.0.tgz", - "integrity": "sha512-1wG99PbKDdkzvV3W2avF5/zU7XLoxmui125EfKwCdDYuE5fsR1alBZHsdk6PvFXXpcbGaNJ/dWyWg+Ip687HeA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.14.1.tgz", + "integrity": "sha512-FrKtJN73f9yQHkx2/GBDr7V6pEyZ0AwYH/dA5xzHAoLkLNiMGf3faXzb94JjXhxr/zzS2d1+XpGIlE8MbMLafQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-progress-bar": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.14.0.tgz", - "integrity": "sha512-ImFS/QWWSZ9oExINb8thaQ6mexFpq62AbvZoVDzdBrje1pf9FErSs4u1XReS9iRtkE1kyGiyY302a4fAoKyMtQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.14.1.tgz", + "integrity": "sha512-3YFs5XrxuNOuOVj9pga/QWonq7YVW0kOsUqY79DYyISZysYiZKF+CvoXfx+VcomLW6I8LUa23/nRwtpd3qt0qA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-radio": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.14.0.tgz", - "integrity": "sha512-PbQ0SloYLJE6YUldwPU5MoBj+/zIQifNhaEYb2Ss2Ka7LFXFAZ9TvXr/INreh4zxI9DSeXirj41k3O+7fbB/Cg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.14.1.tgz", + "integrity": "sha512-2ULCr5YZFqXBombaaIrQ1uM8ADIZW+h0y1lEYydkdyICQiRL3qKSqunr8Wkgxr0gtGMZSNH+qALuky+1mIjBZQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-range-slider": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.14.0.tgz", - "integrity": "sha512-ha798qXr/J3Kjd++eHBYdfqFSVKvSg9TWd+aAhAVj9rVb0Q8mbuinqUcWN9ZHukTNl7lG0/4HbTfM80Lm5V6TA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.14.1.tgz", + "integrity": "sha512-0aQObWAAzWD1K1Zmeh0OBm251dc0Lg82LygVhNvhMqy7CzUygS/F5bMAFanAQzORfdjeWSoX4rJyzX8bJLOX/A==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-ref": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.14.0.tgz", - "integrity": "sha512-bjKcCLRxcu6HR+0kRrLpdit449FHhc16x1LZPncTtjAXN+kZYVmBiQ1QL2/W1l734vRm68nmHVuE5LB1Y2RuIw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.14.1.tgz", + "integrity": "sha512-F6OzXuXRbo/gQLXb1c3QiHlaJevq2oFDaOI0lh4DRD4s4cWVpUcJ/4G2AX92GLcPwU+ddPpuDuTCx7nh6egnqg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-ref-list": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.14.0.tgz", - "integrity": "sha512-rVUldYm4FMAM3SJ8cCbvwdTm4LL9iz3LoFeTxXpfuo6STP+Y26kqR5z5hex6rUcX51se5yEp7PpQDO5bHHz5OA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.14.1.tgz", + "integrity": "sha512-g6QgLZ/89BN8eejBeorKo4hHBe2MfAJWEX0fpM4RkpPlrxwltFpvDWCMv7Ge8rzrZocJSzgugm+ZzOOzIX/8Mw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-ref-node": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.14.0.tgz", - "integrity": "sha512-d10iNjb5x3klPZzzt4AZqeGJ3xbqbaLc4NJb4lQ6C6+djLL+tsJf1MN1vC17dC/wPJ5B894iSBaha0fa8fVfMQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.14.1.tgz", + "integrity": "sha512-PpF1Sp4VcxHPFF9I7S9WgIjIEt7ogYpGvDxXAuVxs0932LQkWby3Jgzx4xEzwOVVOXxTXKyvzq4t5UD8K3SziQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-icon": "1.14.0", - "@umbraco-ui/uui-ref": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-icon": "1.14.1", + "@umbraco-ui/uui-ref": "1.14.1" } }, "node_modules/@umbraco-ui/uui-ref-node-data-type": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.14.0.tgz", - "integrity": "sha512-DcwR0qltykP1NHT8aRqbgQ4/PF2h64ehvBUpEeYg7U9/1xgpWlelkHlZ6CREzZUENaOFrpJzmhzbQWxYa7XKWA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.14.1.tgz", + "integrity": "sha512-KY39RAgo76iL/NIQe8ApP1Rt408sfJ5R7UMNyIkKvfr+qRImPIj3VWD5Z5bBooj5MsxLIz8mUbxH77G2BVhc6A==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-ref-node": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-ref-node": "1.14.1" } }, "node_modules/@umbraco-ui/uui-ref-node-document-type": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.14.0.tgz", - "integrity": "sha512-71A3vJa5SAZd6rTRaa5r/0fV+fr/Am4T5rZu8gdSfEw52ppkVNbg5iHqIwFKN2QDBzKI9GFSrjSVPmRJIsTNTQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.14.1.tgz", + "integrity": "sha512-eIewTOCp5C4h231nMWAFxd5BxOWE3B/1U1xH7sz6pierRGrfY805xsvhhj0XsNZF0dW+C0E1tucRsB6rXNiAcA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-ref-node": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-ref-node": "1.14.1" } }, "node_modules/@umbraco-ui/uui-ref-node-form": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.14.0.tgz", - "integrity": "sha512-hVF6NtGqAZ0GRr28H2q2jOD7T4fTD837sJw7kJTLdzV5Oatu0rqWs4nmV6KpUCJjoUGYFWg+fKc5vvrF+gXXFA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.14.1.tgz", + "integrity": "sha512-5wRqUnJS/g63uCB8LCJ7+OKE98meEQDDXELMXVxRyQ6PbKG9Zfg9RlYaoX84HsOG91r6zYR2PcU6CEkVHPcRqw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-ref-node": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-ref-node": "1.14.1" } }, "node_modules/@umbraco-ui/uui-ref-node-member": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.14.0.tgz", - "integrity": "sha512-Xy1mCgaPDLWnpXyfU1KgaEX+u04JXKnkbrj92d43k4HB30tbI/8BjwyYEaT3Phvs4fmUC0h4ege41Zu8aYfqDg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.14.1.tgz", + "integrity": "sha512-KwMkeOQJ+4tG5pPGsWIdMKNCmJWqqGpY8nTL/5Z6dY/i8OaZWmk5506hvOLXo6Z/7sdZitk243ZNc5eVNFv8wA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-ref-node": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-ref-node": "1.14.1" } }, "node_modules/@umbraco-ui/uui-ref-node-package": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.14.0.tgz", - "integrity": "sha512-MNF0n9nlC6W7Ove9fm7+YwhWwEL5+nUmhYZySEb3YAwjOXHDgL9hHS0gmT1YXxu+66RtBXdqUkZbfI2AVKv7qw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.14.1.tgz", + "integrity": "sha512-T7H624F2l/76KXp0F9NhDBV2U/jS2Xt9D3/l5nw5apqPu8LH/r9jweLC33XnFyAtU/eUyjnQyBKO4y3dfRmB2A==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-ref-node": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-ref-node": "1.14.1" } }, "node_modules/@umbraco-ui/uui-ref-node-user": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.14.0.tgz", - "integrity": "sha512-AFycox1NtGnhVtGgJ3Sg0fCAUlOf38V7S2KPrFubAFmjbxcddWqlMVWzxTcUbUDE2TL5KHnU/JCUxf4BQO1pUw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.14.1.tgz", + "integrity": "sha512-OsC74T5HrbrGYzgqphVoT+QRsjiv+6mcVjIZAtvfqU7sDEBOglM72GGhgtsqjQ/oMyc2kAIcZWtlbQbkwIaaFg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-ref-node": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-ref-node": "1.14.1" } }, "node_modules/@umbraco-ui/uui-scroll-container": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.14.0.tgz", - "integrity": "sha512-N+jYDLTCmo5vC1Mutv/d/mWMivkbWXI1AWM20i7wDQ3U8R6VsbA4Rr7Ne8V9jSnOrgQY9PHrTE2OI99S0bFrnw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.14.1.tgz", + "integrity": "sha512-ForbIiZtzhP/xWij1kvdduY/CEm3dg3CjsaWT4llJTWnxOwUMc5vT9HXigur/CQs+JbbraajG9mfEEv0O1FNPQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-select": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.14.0.tgz", - "integrity": "sha512-/hTUiJ38/gpEf4pk7AWauy/i4o+DYkJR9CpdkL8oyjjwjkmJAVL817v4sXUcTvuaYYVrVqBY1M7U3FgEumKHVw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.14.1.tgz", + "integrity": "sha512-q4mqmu+TMNbiRY0mI8f+Jcye9Af/P1SCcQLmk1RPf41KGh+/HzOz0RjB6+dugkqbDUk019j2zHTxRD4Ppm/o0A==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-slider": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.14.0.tgz", - "integrity": "sha512-biiJ7+aJnkfaPcNF4fuIIGfEmvmTXoOmI56BZN4ICRo1+wntVkfY64hjGTQ2gPV/d26eK1FNyUFpRl8leIxjVA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.14.1.tgz", + "integrity": "sha512-arvr/mRfJxrrcNa9ngsLFrp/NXneQlAztPntTYSkrcBbvvT37fqxLDcx/hAiYaJyMpJz1VmlFPp8VXC5X52ISQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-symbol-expand": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.14.0.tgz", - "integrity": "sha512-8cXPlHmIoajexztcHKGdTrmbp+NR4O0Le+EtQrRMqf6S8apbw7SNy98h3CeSb6Gq2ZTXdXxzZnCtyo+znxpFHA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.14.1.tgz", + "integrity": "sha512-lhbtxs+XDm32Lb2iOWiv3KwRjxpqCHfCntrRUHbYMDgldFXrfynX5hPSsIxPWSvnRy6ojD+y81jHuXw84ZnfUw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-symbol-file": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.14.0.tgz", - "integrity": "sha512-vWx6C/0xT+SUk3LTeqrzbS4P6YXPzN0kqqnUH7riHACYNZxmpAgB8EVU0MzlMdW/TpiMcapw0/KHYuMtBZ8Nkw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.14.1.tgz", + "integrity": "sha512-s4bvuJ9pwc4TZFBi1QEwijZpFf9x95VKMQCNl8MJ7PqbKJPUUNnpMbWuSJmA5ChrPCju3++wtC25H5XUwD89hQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-symbol-file-dropzone": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.14.0.tgz", - "integrity": "sha512-AAb/Cv/INzjonxc4fDR1n0YMs2hO+O+fYtsW9VyAUxqLHnhxNFufwPU80v1Y0nNnKuaNlSAdGwM/4QJujwhj3w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.14.1.tgz", + "integrity": "sha512-9zVztMKfPxGX2bC1clXSbq57+UtoAMvXHzROJjsCdkL6ft8mbkTiRv3ta/0cW2q62rBT77H293YD6GS5CBfuTg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-symbol-file-thumbnail": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.14.0.tgz", - "integrity": "sha512-BBQKo03UVTPq6MO6GVDPv40w3Nizy8LRKQ6quNuhB0UcrWkqOAoJEMX/afX17oGtCoONN/Zq54mmXWgHD8yo1Q==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.14.1.tgz", + "integrity": "sha512-BaYy8EwQ83au4Z33wiA1hpLBSB3L4wEQfhs3qCEdSQ45vqCEwVFO4W5ira2EjM4zxsCxXsH2Nlo2jVbWdAqzoQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-symbol-folder": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.14.0.tgz", - "integrity": "sha512-Z+Kcdk2QyuLf+hKNTacdM6jeNo+wexZ0ktUPbVHJUjYaHuyzqNVV0Du8NJyFBMwyiomV9xLKxQi0YeI/aDg+Cg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.14.1.tgz", + "integrity": "sha512-5y+Wc3SDeOTMYQSGAO/IDxB/MMCUfi6gfJ76qiNNVPaMcqL2uHxifzbwpM8X5bQCgDQ2YU/PHfAWvQGdZWx2Pw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-symbol-lock": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.14.0.tgz", - "integrity": "sha512-dLcc1TkD541ikC+iOEguJmXsJYphqBwEmt2fqVJEDYddmGUf1ZlUNJSjkamU8vaER6NgNIhmqByU0Lv2SdDrjQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.14.1.tgz", + "integrity": "sha512-VOkehn1kth6dLq8nlOQ3xWRK7fJSscbC5MEQ3pbJEwJZjgms1LKzNxWGcRv0F/Rju87ZfsNZlW3mLH43zEm1dw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-symbol-more": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.14.0.tgz", - "integrity": "sha512-HgelD3iF2YMRrCumw8YqeVW/OsByGMWF1ILk8aylyS+4faIEKhnKIpLlw0WovFBYJQpWilcm/JtMqBqa6DfMvg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.14.1.tgz", + "integrity": "sha512-AXEPgnE0GOErAR/eMrqw2M0sedZdGxPNJqpApSUUjaDd+MXeVDzRQ08K+sbPJVDXPkirc/70gO1kkasQ80S6hA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-symbol-sort": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.14.0.tgz", - "integrity": "sha512-cXahfWqCZuu1AOQyrycTmKgZXzBq8v+cqLsEeE929ZokVD35AsTXkLbo6kLN+zVRXAz5kNyaERrfS8yLoZFtWA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.14.1.tgz", + "integrity": "sha512-IuiWSRuQmCa88kSIudfq0tcxwiqSCfow4xsBEEoFAjYtNpGtW9HJ2yeA9exT76XH2m2zgY1FZu9Cf6FxB/B86A==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-table": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.14.0.tgz", - "integrity": "sha512-4ko7jaoH24qLnlwo6jWAuphmkcNL/7RXcDOSgW8aBc0x3nXG2Ufk4PQi0z+k614eDW6+seMZASAsnMx94XhLEQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.14.1.tgz", + "integrity": "sha512-Bt62bKQSmVM8yPv9y0HZ0ywho5T1yXUJsP6q4Bx1g7x3lYotXB8ekLffMSlXmupNOEoHXVQn5ZInTRqeQF8H+Q==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-tabs": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.14.0.tgz", - "integrity": "sha512-m7OEIFK9YD2z7PgD78+U0uFacob/9DqN4nlZXxOkaj/tIxcBbWDXCqRnVBkhkxJKocs6NBYaGi2XHBq9F7/S/w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.14.1.tgz", + "integrity": "sha512-ysYSa3IHOmmMKEwFf8pRhxw+fuxM7IcTbLrayelXWu3fDLUv/CzTW6ew69u3YYgFlljaGGQ50bqMILCrtHqoKQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-button": "1.14.0", - "@umbraco-ui/uui-popover-container": "1.14.0", - "@umbraco-ui/uui-symbol-more": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-button": "1.14.1", + "@umbraco-ui/uui-popover-container": "1.14.1", + "@umbraco-ui/uui-symbol-more": "1.14.1" } }, "node_modules/@umbraco-ui/uui-tag": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.14.0.tgz", - "integrity": "sha512-CphycjqzlUnMA+eEgJCCLKtmsCn5ileGPDn5ei427dc5P5wOEU6aGKqeAGMivP6db4UhUMjh3g0xXfQCKbqEaA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.14.1.tgz", + "integrity": "sha512-8RufQ5snD+sHjr4VqWWd2OPWIXyX+6iImsm630XAv4UA/5PO6XXbifHwlsBpTsLrdMrcn+na1xp8ZtJfe9y5oA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-textarea": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.14.0.tgz", - "integrity": "sha512-l/hyV78IQn+Akb4UA0AtOTsdYJgCun7eC+i0vaOeNANXrO/B0Dhr2yembO0/mf/u2RxIFeOSsW8GUYixrIxSPw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.14.1.tgz", + "integrity": "sha512-YptYir6xNAwX1Y3n1k34yem3sbTlO18CgSvvNZw3bhKjIo3hYM3HdKS07H5pvgRmJRYQ3zbNQbbJreTnVel/Zg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@umbraco-ui/uui-toast-notification": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.14.0.tgz", - "integrity": "sha512-5pb4miAkdgoURoTQGvXQZoUHWIR4tgdUe78hPr2et3xSNw+N0Y/LHlDX1Bo9FBOKEvtFT6YHM0nqOIjW9/RpKw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.14.1.tgz", + "integrity": "sha512-6vpIAEedHg/wr/bWmkD/JPwSOxnmg8eZMiF1FBmZWJA9raiiUcIGjTt/OgNTAejTj6lRldFW/hJUTBwfWOABnQ==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-button": "1.14.0", - "@umbraco-ui/uui-css": "1.14.0", - "@umbraco-ui/uui-icon": "1.14.0", - "@umbraco-ui/uui-icon-registry-essential": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-button": "1.14.1", + "@umbraco-ui/uui-css": "1.14.1", + "@umbraco-ui/uui-icon": "1.14.1", + "@umbraco-ui/uui-icon-registry-essential": "1.14.1" } }, "node_modules/@umbraco-ui/uui-toast-notification-container": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.14.0.tgz", - "integrity": "sha512-5ai853OExMOFrKTrAgvx4OkRNJY8gfIA3UmLBQSVE4E065I0xW4F+L9A3foEU4so2z01OIwvJ53RRk7JriohTg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.14.1.tgz", + "integrity": "sha512-NbNxLFOqApyUuOYCCsAxQvBzQJ4gWOxNn+9PLRqkVtTVHOLZLmsDYMjhQKqHDJ7fuwi8hEAO5rz7q2Vluhg4lg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-toast-notification": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-toast-notification": "1.14.1" } }, "node_modules/@umbraco-ui/uui-toast-notification-layout": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.14.0.tgz", - "integrity": "sha512-8WaiSNLB8NoKJMRQCqFh+KkhjOStXcJ+yLJJR/AM6HF6Pc0tYl+R3zM4LY9WJjQQEOXENcTUPMURJSwpJ2fsGA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.14.1.tgz", + "integrity": "sha512-uJDEzB2hJy7icd7yUESWvQUjGhgHuWRwy0tdFTeJr4Ghu/7R7N6fx0Q4qnCgYQfZWhQdKby+Skisxcnqb8J7hA==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-css": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-css": "1.14.1" } }, "node_modules/@umbraco-ui/uui-toggle": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.14.0.tgz", - "integrity": "sha512-s8//Y2LAqDQ3h4C3PA9yJcVXF2H6gnv2NzMZ22KotJQT9+yhhR3UrOlndOZKkWqKtDxwSLEp9EmyITgDdEoT3A==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.14.1.tgz", + "integrity": "sha512-Py0mWbkN8no0CJAwpGaKotwyUSkQ4EWST1N2iTeBD5s9rYTQwc74ovH2SAyJdHNDfzF894jLB5M7jlGFzJlbgg==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0", - "@umbraco-ui/uui-boolean-input": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1", + "@umbraco-ui/uui-boolean-input": "1.14.1" } }, "node_modules/@umbraco-ui/uui-visually-hidden": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-visually-hidden/-/uui-visually-hidden-1.14.0.tgz", - "integrity": "sha512-wGbMiw+UuMYayMDBau5dD2B3HX2tFPlnOftvD9Z+FNKnGnU5e/V+QInCYy7FlywBQ5fDpfKcXseud/kONGRmsA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-visually-hidden/-/uui-visually-hidden-1.14.1.tgz", + "integrity": "sha512-be1AJdwpbe1W9KMW7BtuVkheYmMroPsBdrs4TrSihw/NfDp3VsP0S9mkmeFZILGW8CMQ7qGcb6Eg1LdJoSYHjw==", "license": "MIT", "dependencies": { - "@umbraco-ui/uui-base": "1.14.0" + "@umbraco-ui/uui-base": "1.14.1" } }, "node_modules/@vitest/expect": { diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 1e346d69c4..88a5536570 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -215,8 +215,8 @@ "@tiptap/extension-underline": "2.11.7", "@tiptap/pm": "2.11.7", "@tiptap/starter-kit": "2.11.7", - "@umbraco-ui/uui": "1.14.0", - "@umbraco-ui/uui-css": "1.14.0", + "@umbraco-ui/uui": "1.14.1", + "@umbraco-ui/uui-css": "1.14.1", "dompurify": "^3.2.5", "element-internals-polyfill": "^3.0.2", "lit": "^3.3.0", @@ -288,4 +288,4 @@ "access": "public", "registry": "https://registry.npmjs.org/" } -} \ No newline at end of file +} From 3e99c2f69d514ca4b425dba71006fc4f1e7922d4 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 30 Jun 2025 16:35:34 +0200 Subject: [PATCH 63/82] Removes full-width from toggle property editor (#19641) * Removes full-width from toggle property editor. * Fixed linting error. --- .../components/input-toggle/input-toggle.element.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-toggle/input-toggle.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-toggle/input-toggle.element.ts index e261311f84..22960a90f2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-toggle/input-toggle.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-toggle/input-toggle.element.ts @@ -1,4 +1,4 @@ -import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UUIBooleanInputEvent } from '@umbraco-cms/backoffice/external/uui'; @@ -71,14 +71,6 @@ export class UmbInputToggleElement extends UmbFormControlMixin(UmbLitElement, '' >${label} `; } - - static override styles = [ - css` - uui-toggle { - width: 100%; - } - `, - ]; } export default UmbInputToggleElement; From cdc62d3020010732ba0b68cac55b847e64362de3 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 30 Jun 2025 18:52:25 +0200 Subject: [PATCH 64/82] Aligned jsdoc and inferred function type on UmbCurrentUserContext (#19642) Aligned jsdoc and inferred function type on UmbCurrentUserContext. --- .../src/packages/user/current-user/current-user.context.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user.context.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user.context.ts index 42f5ed16b6..d160525d8d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/current-user.context.ts @@ -196,9 +196,9 @@ export class UmbCurrentUserContext extends UmbContextBase { /** * Get the permissions for the current user - * @returns {Array | undefined} The permissions for the current user + * @returns {unknown[] | undefined} The permissions for the current user */ - getPermissions() { + getPermissions(): unknown[] | undefined { return this.#currentUser.getValue()?.permissions; } From ad5a18f1eea8daa6929b630e6041eefb4041e507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brynjar=20=C3=9Eorsteinsson?= <85184333+Brynjarth@users.noreply.github.com> Date: Tue, 1 Jul 2025 06:11:00 +0000 Subject: [PATCH 65/82] Fix pagination in Content Delivery API Index Helper (#19606) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor descendant enumeration in DeliveryApiContentIndexHelper Improved loop condition to allow for processing of more than 10.000 descendants for indexing. * Add failing test for original issue. * Renamed variable for clarity. --------- Co-authored-by: Brynjar Þorsteinsson Co-authored-by: Andy Butland --- .../Examine/DeliveryApiContentIndexHelper.cs | 21 ++- .../DeliveryApiContentIndexHelperTests.cs | 120 ++++++++++++++++++ 2 files changed, 134 insertions(+), 7 deletions(-) create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexHelperTests.cs diff --git a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexHelper.cs b/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexHelper.cs index 744502b3de..d7f2fc0c69 100644 --- a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexHelper.cs +++ b/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexHelper.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; @@ -28,21 +28,28 @@ internal sealed class DeliveryApiContentIndexHelper : IDeliveryApiContentIndexHe public void EnumerateApplicableDescendantsForContentIndex(int rootContentId, Action actionToPerform) { const int pageSize = 10000; - var pageIndex = 0; + EnumerateApplicableDescendantsForContentIndex(rootContentId, actionToPerform, pageSize); + } + + internal void EnumerateApplicableDescendantsForContentIndex(int rootContentId, Action actionToPerform, int pageSize) + { + var itemIndex = 0; + long total; + + IQuery query = _umbracoDatabaseFactory.SqlContext.Query().Where(content => content.Trashed == false); IContent[] descendants; - IQuery query = _umbracoDatabaseFactory.SqlContext.Query().Where(content => content.Trashed == false); do { descendants = _contentService - .GetPagedDescendants(rootContentId, pageIndex, pageSize, out _, query, Ordering.By("Path")) + .GetPagedDescendants(rootContentId, itemIndex / pageSize, pageSize, out total, query, Ordering.By("Path")) .Where(descendant => _deliveryApiSettings.IsAllowedContentType(descendant.ContentType.Alias)) .ToArray(); - actionToPerform(descendants.ToArray()); + actionToPerform(descendants); - pageIndex++; + itemIndex += pageSize; } - while (descendants.Length == pageSize); + while (descendants.Length > 0 && itemIndex < total); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexHelperTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexHelperTests.cs new file mode 100644 index 0000000000..dc16d1ae16 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexHelperTests.cs @@ -0,0 +1,120 @@ +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Examine; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Examine; + +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] +[TestFixture] +public class DeliveryApiContentIndexHelperTests : UmbracoIntegrationTestWithContent +{ + public override void CreateTestData() + { + base.CreateTestData(); + + // Save an extra, published content item of a different type to those created via the base class, + // that we'll use to test filtering out disallowed content types. + var template = TemplateBuilder.CreateTextPageTemplate("textPage2"); + FileService.SaveTemplate(template); + + var contentType = ContentTypeBuilder.CreateSimpleContentType("umbTextpage2", "Textpage2", defaultTemplateId: template.Id); + contentType.Key = Guid.NewGuid(); + ContentTypeService.Save(contentType); + + ContentType.AllowedContentTypes = + [ + new ContentTypeSort(ContentType.Key, 0, "umbTextpage"), + new ContentTypeSort(contentType.Key, 1, "umbTextpage2"), + ]; + ContentTypeService.Save(ContentType); + + var subpage = ContentBuilder.CreateSimpleContent(contentType, "Alternate Text Page 4", Textpage.Id); + ContentService.Save(subpage); + + // And then add some more of the first type, so the one we'll filter out in tests isn't in the last page. + for (int i = 0; i < 5; i++) + { + subpage = ContentBuilder.CreateSimpleContent(ContentType, $"Text Page {5 + i}", Textpage.Id); + ContentService.Save(subpage); + } + } + + [Test] + public void Can_Enumerate_Descendants_For_Content_Index() + { + var sut = CreateDeliveryApiContentIndexHelper(); + + var expectedNumberOfContentItems = GetExpectedNumberOfContentItems(); + + var contentEnumerated = 0; + Action actionToPerform = content => + { + contentEnumerated += content.Length; + }; + + const int pageSize = 3; + sut.EnumerateApplicableDescendantsForContentIndex( + Cms.Core.Constants.System.Root, + actionToPerform, + pageSize); + + Assert.AreEqual(expectedNumberOfContentItems, contentEnumerated); + } + + [Test] + public void Can_Enumerate_Descendants_For_Content_Index_With_Disallowed_Content_Type() + { + var sut = CreateDeliveryApiContentIndexHelper(["umbTextPage2"]); + + var expectedNumberOfContentItems = GetExpectedNumberOfContentItems(); + + var contentEnumerated = 0; + Action actionToPerform = content => + { + contentEnumerated += content.Length; + }; + + const int pageSize = 3; + sut.EnumerateApplicableDescendantsForContentIndex( + Cms.Core.Constants.System.Root, + actionToPerform, + pageSize); + + Assert.AreEqual(expectedNumberOfContentItems - 1, contentEnumerated); + } + + private DeliveryApiContentIndexHelper CreateDeliveryApiContentIndexHelper(HashSet? disallowedContentTypeAliases = null) + { + return new DeliveryApiContentIndexHelper( + ContentService, + GetRequiredService(), + GetDeliveryApiSettings(disallowedContentTypeAliases ?? [])); + } + + private IOptionsMonitor GetDeliveryApiSettings(HashSet disallowedContentTypeAliases) + { + var deliveryApiSettings = new DeliveryApiSettings + { + DisallowedContentTypeAliases = disallowedContentTypeAliases, + }; + + var optionsMonitorMock = new Mock>(); + optionsMonitorMock.Setup(o => o.CurrentValue).Returns(deliveryApiSettings); + return optionsMonitorMock.Object; + } + + private int GetExpectedNumberOfContentItems() + { + var result = ContentService.GetAllPublished().Count(); + Assert.AreEqual(10, result); + return result; + } +} From 629635e0dde28c1387930600bca7655cbab5e478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 1 Jul 2025 09:30:55 +0200 Subject: [PATCH 66/82] observable for detecting if split view is active (#19394) * observable for detecting if split view is active * declare splitView manager on content detail workspace * remove declaration as it was a double --- .../controllers/workspace-split-view-manager.controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/controllers/workspace-split-view-manager.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/controllers/workspace-split-view-manager.controller.ts index e1d2f5dce2..e365c08b3e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/controllers/workspace-split-view-manager.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/controllers/workspace-split-view-manager.controller.ts @@ -16,6 +16,7 @@ export class UmbWorkspaceSplitViewManager { (a, b) => (a.index || 0) - (b.index || 0), ); public readonly activeVariantsInfo = this.#activeVariantsInfo.asObservable(); + public readonly splitViewActive = this.#activeVariantsInfo.asObservablePart((x) => x.length > 1); private _routeBase?: string; public getWorkspaceRoute(): string | undefined { From d5159ae4f75fc18ec76db18d255f90201b8bec29 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 1 Jul 2025 09:50:27 +0200 Subject: [PATCH 67/82] V16: Upgrade Storybook from V8 to V9 (#19640) * build(deps-dev): bump storybook from v8 to v9 * chore: run storybook v9 migrations * chore: updates import paths for storybook-webcomponents-vite (migration) * chore: migrates eslint for storybook config * fix: updates old link to composed storybook so we reference the latest production uui * chore: formats eslint config file * chore: changes import path to build mdx stories * chore: updates language list to reflect v16 --- src/Umbraco.Web.UI.Client/.storybook/main.ts | 15 +- .../.storybook/manager.ts | 2 +- .../.storybook/preview.js | 2 +- src/Umbraco.Web.UI.Client/eslint.config.js | 8 +- src/Umbraco.Web.UI.Client/package-lock.json | 1581 +++++------------ src/Umbraco.Web.UI.Client/package.json | 14 +- .../src/apps/backoffice/backoffice.stories.ts | 2 +- .../consent/installer-consent.stories.ts | 2 +- .../database/installer-database.stories.ts | 2 +- .../error/installer-error.stories.ts | 2 +- .../src/apps/installer/installer.stories.ts | 2 +- .../installer-installing.stories.ts | 2 +- .../shared/layout/installer-layout.stories.ts | 2 +- .../installer/user/installer-user.stories.ts | 2 +- .../src/apps/preview/preview.stories.ts | 2 +- .../src/apps/upgrader/upgrader.stories.ts | 2 +- ...ditor-ui-block-grid-column-span.stories.ts | 2 +- .../property-editor-ui-block-grid.stories.ts | 2 +- ...-block-grid-group-configuration.stories.ts | 2 +- ...ui-block-grid-layout-stylesheet.stories.ts | 2 +- ...i-block-grid-type-configuration.stories.ts | 2 +- .../property-editor-ui-block-list.stories.ts | 2 +- ...i-block-list-type-configuration.stories.ts | 2 +- .../input-block-type.stories.ts | 2 +- .../code-editor-modal.stories.ts | 2 +- .../components/code-editor.stories.ts | 2 +- .../property-editor-ui-code-editor.stories.ts | 2 +- .../body-layout/body-layout.stories.ts | 2 +- .../code-block/code-block.stories.ts | 2 +- .../footer-layout/footer-layout.stories.ts | 2 +- .../header-app/header-app.stories.ts | 2 +- .../history/history-list.stories.ts | 2 +- .../core/components/icon/icon.stories.ts | 2 +- .../components/input-color/input-color.mdx | 2 +- .../input-color/input-color.stories.ts | 2 +- .../input-date/input-date.stories.ts | 2 +- .../input-dropdown-list.stories.ts | 2 +- .../input-eye-dropper.stories.ts | 2 +- .../input-number-range.stories.ts | 2 +- .../input-radio-button-list.stories.ts | 2 +- .../input-slider/input-slider.stories.ts | 2 +- .../input-toggle/input-toggle.stories.ts | 2 +- .../split-panel/split-panel.stories.ts | 2 +- .../core/components/stack/stack.stories.ts | 2 +- .../core/components/table/table.stories.ts | 2 +- .../input-culture-select.stories.ts | 2 +- .../src/packages/core/debug/stories/debug.mdx | 2 +- .../icon-picker-modal.stories.ts | 2 +- .../core/icon-registry/icon.stories.ts | 2 +- .../localization/stories/localization.mdx | 9 +- .../stories/localize.element.stories.ts | 2 +- ...idebar-menu-with-entity-actions.stories.ts | 2 +- .../section-sidebar-menu.stories.ts | 2 +- .../common/confirm/confirm-modal.stories.ts | 2 +- .../notification-layout-default.stories.ts | 2 +- .../notification/stories/notification.mdx | 2 +- .../stories/notification.stories.ts | 2 +- .../ref-property-editor-ui.stories.ts | 2 +- ...property-editor-ui-picker-modal.stories.ts | 2 +- .../property-layout.stories.ts | 2 +- .../components/property/property.stories.ts | 2 +- .../section-main/section-main.stories.ts | 2 +- .../section-sidebar.stories.ts | 2 +- .../src/packages/core/tree/tree.stories.ts | 2 +- .../workspace-editor.stories.ts | 2 +- .../workspace-footer.stories.ts | 2 +- .../property-editor-config.stories.ts | 2 +- .../ref-data-type/ref-data-type.stories.ts | 2 +- ...ata-type-details-workspace-view.stories.ts | 2 +- .../workspace-view-data-type-info.stories.ts | 2 +- ...orkspace-view-dictionary-editor.stories.ts | 2 +- .../dashboard-redirect-management.stories.ts | 2 +- ...-editor-ui-document-type-picker.stories.ts | 2 +- .../input-document/input-document.stories.ts | 2 +- .../save-modal/document-save-modal.stories.ts | 2 +- ...perty-editor-ui-document-picker.stories.ts | 2 +- ...-publish-with-descendants-modal.stories.ts | 2 +- .../modal/document-publish-modal.stories.ts | 2 +- .../modal/document-schedule-modal.stories.ts | 2 +- .../modal/document-unpublish-modal.stories.ts | 2 +- .../document-reference-table.stories.ts | 2 +- .../document-workspace-view-info.stories.ts | 2 +- .../modal/embedded-media-modal.stories.ts | 2 +- .../dashboard-health-check.stories.ts | 2 +- .../input-language/input-language.stories.ts | 2 +- .../donut-chart/donut-chart.stories.ts | 2 +- .../input-markdown.stories.ts | 2 +- ...perty-editor-ui-markdown-editor.stories.ts | 2 +- .../input-dropzone/input-dropzone.stories.ts | 2 +- ...rty-editor-ui-media-type-picker.stories.ts | 2 +- .../input-media/input-media.stories.ts | 2 +- .../input-upload-field.stories.ts | 2 +- ...roperty-editor-ui-image-cropper.stories.ts | 2 +- .../property-editor-ui-image-crops.stories.ts | 2 +- ...property-editor-ui-media-picker.stories.ts | 2 +- ...property-editor-ui-upload-field.stories.ts | 2 +- ...y-editor-ui-member-group-picker.stories.ts | 2 +- .../input-member/input-member.stories.ts | 2 +- ...roperty-editor-ui-member-picker.stories.ts | 2 +- .../models-builder-dashboard.stories.ts | 2 +- .../input-multi-url.stories.ts | 2 +- .../link-picker-modal.stories.ts | 2 +- ...erty-editor-ui-multi-url-picker.stories.ts | 2 +- ...dashboard-performance-profiling.stories.ts | 2 +- ...editor-ui-accepted-upload-types.stories.ts | 2 +- .../input-checkbox-list.stories.ts | 2 +- ...roperty-editor-ui-checkbox-list.stories.ts | 2 +- .../permissions.stories.ts | 2 +- .../column/column-configuration.stories.ts | 2 +- .../layout/layout-configuration.stories.ts | 2 +- .../config/order-by/order-by.stories.ts | 2 +- ...perty-editor-ui-collection-view.stories.ts | 2 +- ...property-editor-ui-color-picker.stories.ts | 2 +- .../input-content/input-content.stories.ts | 2 +- ...editor-ui-content-picker-source.stories.ts | 2 +- ...operty-editor-ui-content-picker.stories.ts | 2 +- .../property-editor-ui-date-picker.stories.ts | 2 +- .../property-editor-ui-dimensions.stories.ts | 2 +- .../property-editor-ui-dropdown.stories.ts | 2 +- .../property-editor-ui-eye-dropper.stories.ts | 2 +- .../property-editor-ui-icon-picker.stories.ts | 2 +- .../label/property-editor-ui-label.stories.ts | 2 +- ...-editor-ui-multiple-text-string.stories.ts | 2 +- ...property-editor-ui-number-range.stories.ts | 2 +- .../property-editor-ui-number.stories.ts | 2 +- ...perty-editor-ui-order-direction.stories.ts | 2 +- ...property-editor-ui-overlay-size.stories.ts | 2 +- ...rty-editor-ui-radio-button-list.stories.ts | 2 +- .../property-editor-ui-select.stories.ts | 2 +- .../property-editor-ui-slider.stories.ts | 2 +- .../property-editor-ui-text-box.stories.ts | 2 +- .../property-editor-ui-textarea.stories.ts | 2 +- .../property-editor-ui-toggle.stories.ts | 2 +- .../property-editor-ui-value-type.stories.ts | 2 +- .../dashboard-published-status.stories.ts | 2 +- .../dashboard-examine-management.stories.ts | 2 +- .../settings-welcome-dashboard.stories.ts | 2 +- .../input-static-file.stories.ts | 2 +- ...ty-editor-ui-static-file-picker.stories.ts | 2 +- .../tags-input/tags-input.stories.ts | 2 +- .../tags/property-editor-ui-tags.stories.ts | 2 +- .../telemetry/dashboard-telemetry.stories.ts | 2 +- .../template-field-dropdown-list.stories.ts | 2 +- ...rty-editor-ui-stylesheet-picker.stories.ts | 2 +- .../template-card/template-card.stories.ts | 2 +- .../property-editor-ui-tiptap.stories.ts | 2 +- .../umbraco-news-dashboard.stories.ts | 2 +- .../modals/external-login-modal.stories.ts | 2 +- .../current-user-mfa-disable-modal.stories.ts | 2 +- .../current-user-mfa-enable-modal.stories.ts | 2 +- .../current-user-mfa-modal.stories.ts | 2 +- .../user-group-input.stories.ts | 2 +- .../user-input/user-input.stories.ts | 2 +- .../modals/user-mfa/user-mfa-modal.stories.ts | 2 +- .../property-editor-ui-user-picker.stories.ts | 2 +- 155 files changed, 580 insertions(+), 1347 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/.storybook/main.ts b/src/Umbraco.Web.UI.Client/.storybook/main.ts index 9c16ad5b5a..5b44d4555b 100644 --- a/src/Umbraco.Web.UI.Client/.storybook/main.ts +++ b/src/Umbraco.Web.UI.Client/.storybook/main.ts @@ -4,12 +4,12 @@ import remarkGfm from 'remark-gfm'; const config: StorybookConfig = { stories: ['../@(src|libs|apps|storybook)/**/*.mdx', '../@(src|libs|apps|storybook)/**/*.stories.@(js|jsx|ts|tsx)'], + addons: [ getAbsolutePath('@storybook/addon-links'), - getAbsolutePath('@storybook/addon-essentials'), getAbsolutePath('@storybook/addon-a11y'), { - name: '@storybook/addon-docs', + name: getAbsolutePath('@storybook/addon-docs'), options: { mdxPluginOptions: { mdxCompileOptions: { @@ -19,10 +19,12 @@ const config: StorybookConfig = { }, }, ], + framework: { name: getAbsolutePath('@storybook/web-components-vite'), options: {}, }, + staticDirs: [ '../public-assets', '../public', @@ -32,12 +34,11 @@ const config: StorybookConfig = { to: 'assets/icons', }, ], + typescript: { check: true, }, - docs: { - autodocs: true - }, + managerHead(head, { configType }) { const base = process.env.VITE_BASE_PATH || '/'; const injections = [ @@ -45,10 +46,12 @@ const config: StorybookConfig = { ]; return configType === 'PRODUCTION' ? `${injections.join('')}${head}` : head; }, + refs: { uui: { title: 'Umbraco UI Library', - url: 'https://62189360eeb21b003ab2f4ad-vfnpsanjps.chromatic.com/', + url: 'https://uui.umbraco.com/', + expanded: false, }, }, }; diff --git a/src/Umbraco.Web.UI.Client/.storybook/manager.ts b/src/Umbraco.Web.UI.Client/.storybook/manager.ts index 74d2e3170f..877616d1ca 100644 --- a/src/Umbraco.Web.UI.Client/.storybook/manager.ts +++ b/src/Umbraco.Web.UI.Client/.storybook/manager.ts @@ -1,4 +1,4 @@ -import { addons } from '@storybook/manager-api'; +import { addons } from 'storybook/manager-api'; addons.setConfig({ enableShortcuts: false, diff --git a/src/Umbraco.Web.UI.Client/.storybook/preview.js b/src/Umbraco.Web.UI.Client/.storybook/preview.js index 262cfe4e44..4dbb21c98a 100644 --- a/src/Umbraco.Web.UI.Client/.storybook/preview.js +++ b/src/Umbraco.Web.UI.Client/.storybook/preview.js @@ -5,7 +5,7 @@ import 'element-internals-polyfill'; import '@umbraco-ui/uui'; import { html } from 'lit'; -import { setCustomElements } from '@storybook/web-components'; +import { setCustomElements } from '@storybook/web-components-vite'; import { startMockServiceWorker } from '../src/mocks'; diff --git a/src/Umbraco.Web.UI.Client/eslint.config.js b/src/Umbraco.Web.UI.Client/eslint.config.js index ccdebe2b10..56b90b5f3b 100644 --- a/src/Umbraco.Web.UI.Client/eslint.config.js +++ b/src/Umbraco.Web.UI.Client/eslint.config.js @@ -1,7 +1,10 @@ +// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format + import js from '@eslint/js'; import globals from 'globals'; import importPlugin from 'eslint-plugin-import'; import localRules from 'eslint-plugin-local-rules'; +import storybook from 'eslint-plugin-storybook'; import wcPlugin from 'eslint-plugin-wc'; import litPlugin from 'eslint-plugin-lit'; import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; @@ -13,8 +16,8 @@ export default [ js.configs.recommended, ...tseslint.configs.recommended, wcPlugin.configs['flat/recommended'], - litPlugin.configs['flat/recommended'], - jsdoc.configs['flat/recommended'], // We use the non typescript version to allow types to be defined in the jsdoc comments. This will allow js docs as an alternative to typescript types. + litPlugin.configs['flat/recommended'], // We use the non typescript version to allow types to be defined in the jsdoc comments. This will allow js docs as an alternative to typescript types. + jsdoc.configs['flat/recommended'], localRules.configs.all, eslintPluginPrettierRecommended, @@ -101,4 +104,5 @@ export default [ }, }, }, + ...storybook.configs['flat/recommended'], ]; diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 37ff56412f..54b0ee3124 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -44,13 +44,10 @@ "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-replace": "^6.0.2", - "@storybook/addon-a11y": "^8.6.12", - "@storybook/addon-actions": "^8.6.12", - "@storybook/addon-essentials": "^8.6.12", - "@storybook/addon-links": "^8.6.12", - "@storybook/manager-api": "^8.6.12", - "@storybook/web-components": "^8.6.12", - "@storybook/web-components-vite": "^8.6.12", + "@storybook/addon-a11y": "^9.0.14", + "@storybook/addon-docs": "^9.0.14", + "@storybook/addon-links": "^9.0.14", + "@storybook/web-components-vite": "^9.0.14", "@types/chai": "^5.2.1", "@types/eslint__js": "^8.42.3", "@types/mocha": "^10.0.10", @@ -67,6 +64,7 @@ "eslint-plugin-lit": "^2.1.1", "eslint-plugin-local-rules": "^3.0.2", "eslint-plugin-prettier": "^5.2.6", + "eslint-plugin-storybook": "9.0.14", "eslint-plugin-wc": "^2.2.0", "glob": "^11.0.1", "globals": "^16.0.0", @@ -80,7 +78,7 @@ "rollup-plugin-esbuild": "^6.2.1", "rollup-plugin-import-css": "^3.5.8", "simple-icons": "^14.12.3", - "storybook": "^8.6.12", + "storybook": "^9.0.14", "svgo": "^3.3.2", "tiny-glob": "^0.2.9", "tsc-alias": "^1.8.15", @@ -99,9 +97,9 @@ } }, "node_modules/@adobe/css-tools": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.2.tgz", - "integrity": "sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.3.tgz", + "integrity": "sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==", "dev": true, "license": "MIT" }, @@ -301,19 +299,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/runtime": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", - "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", @@ -1148,9 +1133,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", - "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1168,6 +1153,29 @@ "dev": true, "license": "MIT" }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1186,6 +1194,91 @@ "node": ">=12" } }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -1229,9 +1322,9 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.27", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.27.tgz", + "integrity": "sha512-VO95AxtSFMelbg3ouljAYnfvTEwSWVt/2YLf+U5Ejd8iT5mXE2Sa/1LGyvySMne2CGsepGLI7KpF3EzE3Aq9Mg==", "dev": true, "license": "MIT", "dependencies": { @@ -1490,9 +1583,9 @@ } }, "node_modules/@puppeteer/browsers/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { @@ -1936,15 +2029,13 @@ "license": "MIT" }, "node_modules/@storybook/addon-a11y": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-8.6.12.tgz", - "integrity": "sha512-H28zHiL8uuv29XsVNf9VjNWsCeht/l66GPYHT7aom1jh+f3fS9+sutrCGEBC/T7cnRpy8ZyuHCtihUqS+RI4pg==", + "version": "9.0.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-9.0.14.tgz", + "integrity": "sha512-xDtzD89lyyq706yynJ8iAUjBfNebb7F5OoJXSAPYPnUiHoNHAcRT9ia2HrC6Yjp3f3JX2PRIEMjD5Myz3sL04A==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/addon-highlight": "8.6.12", "@storybook/global": "^5.0.0", - "@storybook/test": "8.6.12", "axe-core": "^4.2.0" }, "funding": { @@ -1952,93 +2043,20 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.6.12" - } - }, - "node_modules/@storybook/addon-actions": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.6.12.tgz", - "integrity": "sha512-B5kfiRvi35oJ0NIo53CGH66H471A3XTzrfaa6SxXEJsgxxSeKScG5YeXcCvLiZfvANRQ7QDsmzPUgg0o3hdMXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/global": "^5.0.0", - "@types/uuid": "^9.0.1", - "dequal": "^2.0.2", - "polished": "^4.2.2", - "uuid": "^9.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^8.6.12" - } - }, - "node_modules/@storybook/addon-actions/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@storybook/addon-backgrounds": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-8.6.12.tgz", - "integrity": "sha512-lmIAma9BiiCTbJ8YfdZkXjpnAIrOUcgboLkt1f6XJ78vNEMnLNzD9gnh7Tssz1qrqvm34v9daDjIb+ggdiKp3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/global": "^5.0.0", - "memoizerific": "^1.11.3", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^8.6.12" - } - }, - "node_modules/@storybook/addon-controls": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-8.6.12.tgz", - "integrity": "sha512-9VSRPJWQVb9wLp21uvpxDGNctYptyUX0gbvxIWOHMH3R2DslSoq41lsC/oQ4l4zSHVdL+nq8sCTkhBxIsjKqdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/global": "^5.0.0", - "dequal": "^2.0.2", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^8.6.12" + "storybook": "^9.0.14" } }, "node_modules/@storybook/addon-docs": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-8.6.12.tgz", - "integrity": "sha512-kEezQjAf/p3SpDzLABgg4fbT48B6dkT2LiZCKTRmCrJVtuReaAr4R9MMM6Jsph6XjbIj/SvOWf3CMeOPXOs9sg==", + "version": "9.0.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-9.0.14.tgz", + "integrity": "sha512-vjWH2FamLzoPZXitecbhRSUvQDj27q/dDaCKXSwCIwEVziIQrqHBGDmuJPCWoroCkKxLo8s8gwMi6wk5Minaqg==", "dev": true, "license": "MIT", "dependencies": { "@mdx-js/react": "^3.0.0", - "@storybook/blocks": "8.6.12", - "@storybook/csf-plugin": "8.6.12", - "@storybook/react-dom-shim": "8.6.12", + "@storybook/csf-plugin": "9.0.14", + "@storybook/icons": "^1.2.12", + "@storybook/react-dom-shim": "9.0.14", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" @@ -2048,39 +2066,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.6.12" + "storybook": "^9.0.14" } }, - "node_modules/@storybook/addon-essentials": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-8.6.12.tgz", - "integrity": "sha512-Y/7e8KFlttaNfv7q2zoHMPdX6hPXHdsuQMAjYl5NG9HOAJREu4XBy4KZpbcozRe4ApZ78rYsN/MO1EuA+bNMIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/addon-actions": "8.6.12", - "@storybook/addon-backgrounds": "8.6.12", - "@storybook/addon-controls": "8.6.12", - "@storybook/addon-docs": "8.6.12", - "@storybook/addon-highlight": "8.6.12", - "@storybook/addon-measure": "8.6.12", - "@storybook/addon-outline": "8.6.12", - "@storybook/addon-toolbars": "8.6.12", - "@storybook/addon-viewport": "8.6.12", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^8.6.12" - } - }, - "node_modules/@storybook/addon-highlight": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-8.6.12.tgz", - "integrity": "sha512-9FITVxdoycZ+eXuAZL9ElWyML/0fPPn9UgnnAkrU7zkMi+Segq/Tx7y+WWanC5zfWZrXAuG6WTOYEXeWQdm//w==", + "node_modules/@storybook/addon-links": { + "version": "9.0.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-9.0.14.tgz", + "integrity": "sha512-qzlRT+GRInP3H0bfvTVgdxWqbbSNLyBln9RNm1t5H3DsHc4NFyYdpPXXUapyFrWW7O0ByWMWO94RG0T+qG5R3g==", "dev": true, "license": "MIT", "dependencies": { @@ -2090,27 +2082,9 @@ "type": "opencollective", "url": "https://opencollective.com/storybook" }, - "peerDependencies": { - "storybook": "^8.6.12" - } - }, - "node_modules/@storybook/addon-links": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-8.6.12.tgz", - "integrity": "sha512-AfKujFHoAxhxq4yu+6NwylltS9lf5MPs1eLLXvOlwo3l7Y/c68OdxJ7j68vLQhs9H173WVYjKyjbjFxJWf/YYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/global": "^5.0.0", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.6.12" + "storybook": "^9.0.14" }, "peerDependenciesMeta": { "react": { @@ -2118,110 +2092,14 @@ } } }, - "node_modules/@storybook/addon-measure": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-8.6.12.tgz", - "integrity": "sha512-tACmwqqOvutaQSduw8SMb62wICaT1rWaHtMN3vtWXuxgDPSdJQxLP+wdVyRYMAgpxhLyIO7YRf++Hfha9RHgFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/global": "^5.0.0", - "tiny-invariant": "^1.3.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^8.6.12" - } - }, - "node_modules/@storybook/addon-outline": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-8.6.12.tgz", - "integrity": "sha512-1ylwm+n1s40S91No0v9T4tCjZORu3GbnjINlyjYTDLLhQHyBQd3nWR1Y1eewU4xH4cW9SnSLcMQFS/82xHqU6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/global": "^5.0.0", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^8.6.12" - } - }, - "node_modules/@storybook/addon-toolbars": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-8.6.12.tgz", - "integrity": "sha512-HEcSzo1DyFtIu5/ikVOmh5h85C1IvK9iFKSzBR6ice33zBOaehVJK+Z5f487MOXxPsZ63uvWUytwPyViGInj+g==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^8.6.12" - } - }, - "node_modules/@storybook/addon-viewport": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-8.6.12.tgz", - "integrity": "sha512-EXK2LArAnABsPP0leJKy78L/lbMWow+EIJfytEP5fHaW4EhMR6h7Hzaqzre6U0IMMr/jVFa1ci+m0PJ0eQc2bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "memoizerific": "^1.11.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^8.6.12" - } - }, - "node_modules/@storybook/blocks": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-8.6.12.tgz", - "integrity": "sha512-DohlTq6HM1jDbHYiXL4ZvZ00VkhpUp5uftzj/CZDLY1fYHRjqtaTwWm2/OpceivMA8zDitLcq5atEZN+f+siTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/icons": "^1.2.12", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^8.6.12" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, "node_modules/@storybook/builder-vite": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-8.6.12.tgz", - "integrity": "sha512-Gju21ud/3Qw4v2vLNaa5SuJECsI9ICNRr2G0UyCCzRvCHg8jpA9lDReu2NqhLDyFIuDG+ZYT38gcaHEUoNQ8KQ==", + "version": "9.0.14", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-9.0.14.tgz", + "integrity": "sha512-pMe/RmiC98SMRNVDvfvISW/rEVbKwKLuLm3KilHSKkW1187S/BkxBQx/o61avAEnZR2AC+JgwWZC18PJGRH/pw==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/csf-plugin": "8.6.12", - "browser-assert": "^1.2.1", + "@storybook/csf-plugin": "9.0.14", "ts-dedent": "^2.0.0" }, "funding": { @@ -2229,95 +2107,14 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.6.12", - "vite": "^4.0.0 || ^5.0.0 || ^6.0.0" - } - }, - "node_modules/@storybook/components": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.6.12.tgz", - "integrity": "sha512-FiaE8xvCdvKC2arYusgtlDNZ77b8ysr8njAYQZwwaIHjy27TbR2tEpLDCmUwSbANNmivtc/xGEiDDwcNppMWlQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" - } - }, - "node_modules/@storybook/core": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/core/-/core-8.6.12.tgz", - "integrity": "sha512-t+ZuDzAlsXKa6tLxNZT81gEAt4GNwsKP/Id2wluhmUWD/lwYW0uum1JiPUuanw8xD6TdakCW/7ULZc7aQUBLCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/theming": "8.6.12", - "better-opn": "^3.0.2", - "browser-assert": "^1.2.1", - "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", - "esbuild-register": "^3.5.0", - "jsdoc-type-pratt-parser": "^4.0.0", - "process": "^0.11.10", - "recast": "^0.23.5", - "semver": "^7.6.2", - "util": "^0.12.5", - "ws": "^8.2.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "prettier": "^2 || ^3" - }, - "peerDependenciesMeta": { - "prettier": { - "optional": true - } - } - }, - "node_modules/@storybook/core/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@storybook/core/node_modules/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "storybook": "^9.0.14", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "node_modules/@storybook/csf-plugin": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-8.6.12.tgz", - "integrity": "sha512-6s8CnP1aoKPb3XtC0jRLUp8M5vTA8RhGAwQDKUsFpCC7g89JR9CaKs9FY2ZSzsNbjR15uASi7b3K8BzeYumYQg==", + "version": "9.0.14", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-9.0.14.tgz", + "integrity": "sha512-PKUmF5y/SfPOifC2bRo79YwfGv6TYISM5JK6r6FHVKMwV1nWLmj7Xx2t5aHa/5JggdBz/iGganTP7oo7QOn+0A==", "dev": true, "license": "MIT", "dependencies": { @@ -2328,7 +2125,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.6.12" + "storybook": "^9.0.14" } }, "node_modules/@storybook/global": { @@ -2352,56 +2149,10 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta" } }, - "node_modules/@storybook/instrumenter": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-8.6.12.tgz", - "integrity": "sha512-VK5fYAF8jMwWP/u3YsmSwKGh+FeSY8WZn78flzRUwirp2Eg1WWjsqPRubAk7yTpcqcC/km9YMF3KbqfzRv2s/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/global": "^5.0.0", - "@vitest/utils": "^2.1.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^8.6.12" - } - }, - "node_modules/@storybook/manager-api": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.6.12.tgz", - "integrity": "sha512-O0SpISeJLNTQvhSBOsWzzkCgs8vCjOq1578rwqHlC6jWWm4QmtfdyXqnv7rR1Hk08kQ+Dzqh0uhwHx0nfwy4nQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" - } - }, - "node_modules/@storybook/preview-api": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.6.12.tgz", - "integrity": "sha512-84FE3Hrs0AYKHqpDZOwx1S/ffOfxBdL65lhCoeI8GoWwCkzwa9zEP3kvXBo/BnEDO7nAfxvMhjASTZXbKRJh5Q==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" - } - }, "node_modules/@storybook/react-dom-shim": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.6.12.tgz", - "integrity": "sha512-51QvoimkBzYs8s3rCYnY5h0cFqLz/Mh0vRcughwYaXckWzDBV8l67WBO5Xf5nBsukCbWyqBVPpEQLww8s7mrLA==", + "version": "9.0.14", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-9.0.14.tgz", + "integrity": "sha512-fXMzhgFMnGZUhWm9zWiR8qOB90OykPhkB/qiebFbD/wUedPyp3H1+NAzX1/UWV2SYqr+aFK9vH1PokAYbpTRsw==", "dev": true, "license": "MIT", "funding": { @@ -2411,63 +2162,22 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.6.12" - } - }, - "node_modules/@storybook/test": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/test/-/test-8.6.12.tgz", - "integrity": "sha512-0BK1Eg+VD0lNMB1BtxqHE3tP9FdkUmohtvWG7cq6lWvMrbCmAmh3VWai3RMCCDOukPFpjabOr8BBRLVvhNpv2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/global": "^5.0.0", - "@storybook/instrumenter": "8.6.12", - "@testing-library/dom": "10.4.0", - "@testing-library/jest-dom": "6.5.0", - "@testing-library/user-event": "14.5.2", - "@vitest/expect": "2.0.5", - "@vitest/spy": "2.0.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^8.6.12" - } - }, - "node_modules/@storybook/theming": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.6.12.tgz", - "integrity": "sha512-6VjZg8HJ2Op7+KV7ihJpYrDnFtd9D1jrQnUS8LckcpuBXrIEbaut5+34ObY8ssQnSqkk2GwIZBBBQYQBCVvkOw==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" + "storybook": "^9.0.14" } }, "node_modules/@storybook/web-components": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/web-components/-/web-components-8.6.12.tgz", - "integrity": "sha512-j+609VT8abBlpV+tB/vqSRO/fKA1QpnKWlbE0JpolzmEbgla//pAZomPysoOnvTLL3lSX3conjiAAaTpwbjyLg==", + "version": "9.0.14", + "resolved": "https://registry.npmjs.org/@storybook/web-components/-/web-components-9.0.14.tgz", + "integrity": "sha512-P9vqYW+M1/NbgaVSsHmLTIgGcUxOOVQcpkBhyuCG8swLC9CSXlSkMyPGXfotuLuv7AykXV/WdpnUHiz/h9weZg==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/components": "8.6.12", "@storybook/global": "^5.0.0", - "@storybook/manager-api": "8.6.12", - "@storybook/preview-api": "8.6.12", - "@storybook/theming": "8.6.12", "tiny-invariant": "^1.3.1", "ts-dedent": "^2.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "funding": { "type": "opencollective", @@ -2475,55 +2185,34 @@ }, "peerDependencies": { "lit": "^2.0.0 || ^3.0.0", - "storybook": "^8.6.12" + "storybook": "^9.0.14" } }, "node_modules/@storybook/web-components-vite": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/@storybook/web-components-vite/-/web-components-vite-8.6.12.tgz", - "integrity": "sha512-1YVnkk7O9zPF5xTeWD5AxpmfCx9M7C9Du8pIwcypjHQLnb34PwiyjMRztYm/JKh5kCl4BZg187r2j3Q258ytHQ==", + "version": "9.0.14", + "resolved": "https://registry.npmjs.org/@storybook/web-components-vite/-/web-components-vite-9.0.14.tgz", + "integrity": "sha512-4jLUgjqLeEFT3NCwnxq2PVGuz9U83fQSZEBqg3QJ+lSCNhYgSzPwhhz4bpHGMZZqvxfKWRzUYKEWQ6rjObU9yg==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/builder-vite": "8.6.12", - "@storybook/web-components": "8.6.12", - "magic-string": "^0.30.0" + "@storybook/builder-vite": "9.0.14", + "@storybook/web-components": "9.0.14" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.6.12" - } - }, - "node_modules/@testing-library/dom": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", - "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=18" + "storybook": "^9.0.14" } }, "node_modules/@testing-library/jest-dom": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.5.0.tgz", - "integrity": "sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==", + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", + "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==", "dev": true, "license": "MIT", "dependencies": { @@ -2555,17 +2244,10 @@ "node": ">=8" } }, - "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", - "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", - "dev": true, - "license": "MIT" - }, "node_modules/@testing-library/user-event": { - "version": "14.5.2", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz", - "integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==", + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", "dev": true, "license": "MIT", "engines": { @@ -3212,13 +2894,6 @@ "@types/node": "*" } }, - "node_modules/@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/babel__code-frame": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/@types/babel__code-frame/-/babel__code-frame-7.0.6.tgz", @@ -3238,9 +2913,9 @@ } }, "node_modules/@types/chai": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.1.tgz", - "integrity": "sha512-iu1JLYmGmITRzUgNiLMZD3WCoFzpYtueuyAgHTXqgwSRAMIlFTnZqG6/xenkpUGRJEzSfklUTI4GNSzks/dc0w==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", "dev": true, "license": "MIT", "dependencies": { @@ -3570,13 +3245,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", - "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "version": "18.19.113", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.113.tgz", + "integrity": "sha512-TmSTE9vyebJ9vSEiU+P+0Sp4F5tMgjiEOZaQUW6wA3ODvi6uBgkHQ+EsIu0pbiKvf9QHEvyRCiaz03rV0b+IaA==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~5.26.4" } }, "node_modules/@types/parse5": { @@ -3681,13 +3356,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/uuid": { - "version": "9.0.8", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", - "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/ws": { "version": "7.4.7", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", @@ -3869,9 +3537,9 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3895,9 +3563,9 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { @@ -4054,9 +3722,9 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4080,9 +3748,9 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { @@ -4149,9 +3817,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4175,9 +3843,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { @@ -4271,9 +3939,9 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4297,9 +3965,9 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { @@ -5415,96 +5083,58 @@ } }, "node_modules/@vitest/expect": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz", - "integrity": "sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.0.5", - "@vitest/utils": "2.0.5", - "chai": "^5.1.1", - "tinyrainbow": "^1.2.0" + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/expect/node_modules/@vitest/pretty-format": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", - "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/expect/node_modules/@vitest/utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz", - "integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "2.0.5", - "estree-walker": "^3.0.3", - "loupe": "^3.1.1", - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/expect/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/@vitest/pretty-format": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", - "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.5.tgz", - "integrity": "sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", "dev": true, "license": "MIT", "dependencies": { - "tinyspy": "^3.0.0" + "tinyspy": "^4.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", - "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.9", - "loupe": "^3.1.2", - "tinyrainbow": "^1.2.0" + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -6108,16 +5738,13 @@ } }, "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">=8" } }, "node_modules/ansi-styles": { @@ -6201,13 +5828,13 @@ "license": "Python-2.0" }, "node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "dev": true, "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" + "engines": { + "node": ">= 0.4" } }, "node_modules/array-back": { @@ -6625,9 +6252,9 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -6648,12 +6275,6 @@ "node": ">=8" } }, - "node_modules/browser-assert": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/browser-assert/-/browser-assert-1.2.1.tgz", - "integrity": "sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==", - "dev": true - }, "node_modules/browserslist": { "version": "4.24.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", @@ -6970,9 +6591,9 @@ } }, "node_modules/chokidar": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", - "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", "dependencies": { @@ -7089,69 +6710,6 @@ "node": ">=12" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -7589,9 +7147,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8040,9 +7598,9 @@ } }, "node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", "dev": true, "license": "MIT" }, @@ -8170,9 +7728,9 @@ "license": "MIT" }, "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, @@ -8683,9 +8241,9 @@ } }, "node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { @@ -8750,6 +8308,23 @@ } } }, + "node_modules/eslint-plugin-storybook": { + "version": "9.0.14", + "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-9.0.14.tgz", + "integrity": "sha512-YZsDhyFgVfeFPdvd7Xcl9ZusY7Jniq7AOAWN/cdg0a2Y+ywKKNYrQ+EfyuhXsiMjh58plYKMpJYxKVxeUwW9jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.8.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "eslint": ">=8", + "storybook": "^9.0.14" + } + }, "node_modules/eslint-plugin-wc": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-2.2.1.tgz", @@ -8782,9 +8357,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -9244,13 +8819,13 @@ } }, "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -9539,15 +9114,15 @@ } }, "node_modules/glob": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", - "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", "dev": true, "license": "ISC", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" @@ -9575,24 +9150,14 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/glob/node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { "node": "20 || >=22" @@ -9602,9 +9167,9 @@ } }, "node_modules/globals": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz", - "integrity": "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz", + "integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==", "dev": true, "license": "MIT", "engines": { @@ -10079,51 +9644,6 @@ "node": ">=12.0.0" } }, - "node_modules/inquirer/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/inquirer/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/inquirer/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -10848,9 +10368,9 @@ } }, "node_modules/jackspeak": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", - "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -11276,51 +10796,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/log-update/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-update/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-update/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/log-update/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -11348,9 +10823,9 @@ } }, "node_modules/loupe": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", - "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", + "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==", "dev": true, "license": "MIT" }, @@ -11378,16 +10853,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lz-string": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, - "license": "MIT", - "bin": { - "lz-string": "bin/bin.js" - } - }, "node_modules/madge": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/madge/-/madge-8.0.0.tgz", @@ -11464,9 +10929,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { @@ -11483,13 +10948,6 @@ "dev": true, "license": "ISC" }, - "node_modules/map-or-similar": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/map-or-similar/-/map-or-similar-1.5.0.tgz", - "integrity": "sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==", - "dev": true, - "license": "MIT" - }, "node_modules/markdown-it": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", @@ -11783,16 +11241,6 @@ "node": ">= 0.6" } }, - "node_modules/memoizerific": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz", - "integrity": "sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==", - "dev": true, - "license": "MIT", - "dependencies": { - "map-or-similar": "^1.5.0" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -13308,29 +12756,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/orderedmap": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", @@ -13608,9 +13033,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", - "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", "dev": true, "license": "ISC", "engines": { @@ -13642,9 +13067,9 @@ "license": "MIT" }, "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, "license": "MIT", "engines": { @@ -13780,19 +13205,6 @@ "node": ">=4" } }, - "node_modules/polished": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", - "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.17.8" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/portfinder": { "version": "1.0.32", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", @@ -13957,44 +13369,6 @@ "node": ">=6.0.0" } }, - "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/pretty-ms": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", @@ -14011,16 +13385,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -14312,9 +13676,9 @@ } }, "node_modules/puppeteer-core/node_modules/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, "license": "MIT", "engines": { @@ -14490,13 +13854,6 @@ "react": "^19.1.0" } }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "license": "MIT" - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -14513,13 +13870,13 @@ } }, "node_modules/readdirp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", - "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 14.16.0" + "node": ">= 14.18.0" }, "funding": { "type": "individual", @@ -14602,13 +13959,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true, - "license": "MIT" - }, "node_modules/regexp.prototype.flags": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", @@ -15334,17 +14684,25 @@ } }, "node_modules/storybook": { - "version": "8.6.12", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.6.12.tgz", - "integrity": "sha512-Z/nWYEHBTLK1ZBtAWdhxC0l5zf7ioJ7G4+zYqtTdYeb67gTnxNj80gehf8o8QY9L2zA2+eyMRGLC2V5fI7Z3Tw==", + "version": "9.0.14", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-9.0.14.tgz", + "integrity": "sha512-PfVo9kSa4XsDTD2gXFvMRGix032+clBDcUMI4MhUzYxONLiZifnhwch4p/1lG+c3IVN4qkOEgGNc9PEgVMgApw==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/core": "8.6.12" + "@storybook/global": "^5.0.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/user-event": "^14.6.1", + "@vitest/expect": "3.2.4", + "@vitest/spy": "3.2.4", + "better-opn": "^3.0.2", + "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", + "esbuild-register": "^3.5.0", + "recast": "^0.23.5", + "semver": "^7.6.2", + "ws": "^8.18.0" }, "bin": { - "getstorybook": "bin/index.cjs", - "sb": "bin/index.cjs", "storybook": "bin/index.cjs" }, "funding": { @@ -15360,6 +14718,41 @@ } } }, + "node_modules/storybook/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/storybook/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/stream-to-array": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-2.3.0.tgz", @@ -15402,25 +14795,6 @@ } }, "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", @@ -15435,31 +14809,17 @@ "node": ">=8" } }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" @@ -15533,23 +14893,6 @@ } }, "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -15562,12 +14905,16 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { "node": ">=8" } @@ -15862,9 +15209,9 @@ } }, "node_modules/tinyrainbow": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", - "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, "license": "MIT", "engines": { @@ -15872,9 +15219,9 @@ } }, "node_modules/tinyspy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", - "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", + "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", "dev": true, "license": "MIT", "engines": { @@ -15918,9 +15265,9 @@ } }, "node_modules/tr46": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", - "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "dev": true, "license": "MIT", "dependencies": { @@ -16360,9 +15707,9 @@ } }, "node_modules/typedoc/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -16442,16 +15789,6 @@ "typescript-json-schema": "bin/typescript-json-schema" } }, - "node_modules/typescript-json-schema/node_modules/@types/node": { - "version": "18.19.67", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.67.tgz", - "integrity": "sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, "node_modules/typescript-json-schema/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -16488,13 +15825,6 @@ "node": ">=14.17" } }, - "node_modules/typescript-json-schema/node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, - "license": "MIT" - }, "node_modules/typical": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", @@ -16576,9 +15906,9 @@ } }, "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true, "license": "MIT" }, @@ -17138,13 +16468,13 @@ "license": "MIT" }, "node_modules/whatwg-url": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", - "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "dev": true, "license": "MIT", "dependencies": { - "tr46": "^5.0.0", + "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" }, "engines": { @@ -17279,18 +16609,18 @@ } }, "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -17315,64 +16645,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -17461,51 +16733,6 @@ "node": ">=12" } }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 88a5536570..4676204c8c 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -232,13 +232,10 @@ "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-replace": "^6.0.2", - "@storybook/addon-a11y": "^8.6.12", - "@storybook/addon-actions": "^8.6.12", - "@storybook/addon-essentials": "^8.6.12", - "@storybook/addon-links": "^8.6.12", - "@storybook/manager-api": "^8.6.12", - "@storybook/web-components": "^8.6.12", - "@storybook/web-components-vite": "^8.6.12", + "@storybook/addon-a11y": "^9.0.14", + "@storybook/addon-docs": "^9.0.14", + "@storybook/addon-links": "^9.0.14", + "@storybook/web-components-vite": "^9.0.14", "@types/chai": "^5.2.1", "@types/eslint__js": "^8.42.3", "@types/mocha": "^10.0.10", @@ -255,6 +252,7 @@ "eslint-plugin-lit": "^2.1.1", "eslint-plugin-local-rules": "^3.0.2", "eslint-plugin-prettier": "^5.2.6", + "eslint-plugin-storybook": "9.0.14", "eslint-plugin-wc": "^2.2.0", "glob": "^11.0.1", "globals": "^16.0.0", @@ -268,7 +266,7 @@ "rollup-plugin-esbuild": "^6.2.1", "rollup-plugin-import-css": "^3.5.8", "simple-icons": "^14.12.3", - "storybook": "^8.6.12", + "storybook": "^9.0.14", "svgo": "^3.3.2", "tiny-glob": "^0.2.9", "tsc-alias": "^1.8.15", diff --git a/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.stories.ts b/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.stories.ts index f0c8fb8802..a3efa3acd7 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.stories.ts @@ -1,5 +1,5 @@ import type { UmbBackofficeElement } from './backoffice.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './backoffice.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/apps/installer/consent/installer-consent.stories.ts b/src/Umbraco.Web.UI.Client/src/apps/installer/consent/installer-consent.stories.ts index 640f3306d3..55fd5d23fd 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/installer/consent/installer-consent.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/installer/consent/installer-consent.stories.ts @@ -1,6 +1,6 @@ import { installerContextProvider } from '../shared/utils.story-helpers.js'; import type { UmbInstallerConsentElement } from './installer-consent.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './installer-consent.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/apps/installer/database/installer-database.stories.ts b/src/Umbraco.Web.UI.Client/src/apps/installer/database/installer-database.stories.ts index e58b561b54..83ee8b9a50 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/installer/database/installer-database.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/installer/database/installer-database.stories.ts @@ -2,7 +2,7 @@ import './installer-database.element.js'; import { installerContextProvider } from '../shared/utils.story-helpers.js'; import type { UmbInstallerDatabaseElement } from './installer-database.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; export default { diff --git a/src/Umbraco.Web.UI.Client/src/apps/installer/error/installer-error.stories.ts b/src/Umbraco.Web.UI.Client/src/apps/installer/error/installer-error.stories.ts index d60a58df9f..1a85324266 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/installer/error/installer-error.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/installer/error/installer-error.stories.ts @@ -1,7 +1,7 @@ import { installerContextProvider } from '../shared/utils.story-helpers.js'; import { UmbInstallerContext } from '../installer.context.js'; import type { UmbInstallerErrorElement } from './installer-error.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './installer-error.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/apps/installer/installer.stories.ts b/src/Umbraco.Web.UI.Client/src/apps/installer/installer.stories.ts index 97d1bed50a..86e848a543 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/installer/installer.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/installer/installer.stories.ts @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/web-components'; +import type { Meta } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/apps/installer/installing/installer-installing.stories.ts b/src/Umbraco.Web.UI.Client/src/apps/installer/installing/installer-installing.stories.ts index acff43403a..ec03c78e6e 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/installer/installing/installer-installing.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/installer/installing/installer-installing.stories.ts @@ -1,6 +1,6 @@ import { installerContextProvider } from '../shared/utils.story-helpers.js'; import type { UmbInstallerInstallingElement } from './installer-installing.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './installer-installing.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/apps/installer/shared/layout/installer-layout.stories.ts b/src/Umbraco.Web.UI.Client/src/apps/installer/shared/layout/installer-layout.stories.ts index 1429b7acdf..3fc5837c27 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/installer/shared/layout/installer-layout.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/installer/shared/layout/installer-layout.stories.ts @@ -1,5 +1,5 @@ import type { UmbInstallerLayoutElement } from './installer-layout.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './installer-layout.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/apps/installer/user/installer-user.stories.ts b/src/Umbraco.Web.UI.Client/src/apps/installer/user/installer-user.stories.ts index 4c103ddb73..556bedc89d 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/installer/user/installer-user.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/installer/user/installer-user.stories.ts @@ -1,6 +1,6 @@ import { installerContextProvider } from '../shared/utils.story-helpers.js'; import type { UmbInstallerUserElement } from './installer-user.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './installer-user.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/apps/preview/preview.stories.ts b/src/Umbraco.Web.UI.Client/src/apps/preview/preview.stories.ts index fb6732e13d..766b8f8940 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/preview/preview.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/preview/preview.stories.ts @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/web-components'; +import type { Meta } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; export default { diff --git a/src/Umbraco.Web.UI.Client/src/apps/upgrader/upgrader.stories.ts b/src/Umbraco.Web.UI.Client/src/apps/upgrader/upgrader.stories.ts index 5ffe48b00c..f340206f69 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/upgrader/upgrader.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/upgrader/upgrader.stories.ts @@ -1,7 +1,7 @@ import './upgrader-view.element.js'; import type { UmbUpgraderViewElement } from './upgrader-view.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; export default { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-column-span/property-editor-ui-block-grid-column-span.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-column-span/property-editor-ui-block-grid-column-span.stories.ts index f31dcc60c3..36b7dc7963 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-column-span/property-editor-ui-block-grid-column-span.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-column-span/property-editor-ui-block-grid-column-span.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIBlockGridColumnSpanElement } from './property-editor-ui-block-grid-column-span.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-block-grid-column-span.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.stories.ts index e696e6cedb..77fa2b666a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIBlockGridElement } from './property-editor-ui-block-grid.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-block-grid.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/property-editor-ui-block-grid-group-configuration.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/property-editor-ui-block-grid-group-configuration.stories.ts index 7e4cdcf322..c266d421b2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/property-editor-ui-block-grid-group-configuration.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/property-editor-ui-block-grid-group-configuration.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIBlockGridGroupConfigurationElement } from './property-editor-ui-block-grid-group-configuration.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-block-grid-group-configuration.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-layout-stylesheet/property-editor-ui-block-grid-layout-stylesheet.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-layout-stylesheet/property-editor-ui-block-grid-layout-stylesheet.stories.ts index 745f256638..3c847ffe8c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-layout-stylesheet/property-editor-ui-block-grid-layout-stylesheet.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-layout-stylesheet/property-editor-ui-block-grid-layout-stylesheet.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIBlockGridLayoutStylesheetElement } from './property-editor-ui-block-grid-layout-stylesheet.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-block-grid-layout-stylesheet.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-type-configuration/property-editor-ui-block-grid-type-configuration.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-type-configuration/property-editor-ui-block-grid-type-configuration.stories.ts index 52523b8d61..26a90366c9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-type-configuration/property-editor-ui-block-grid-type-configuration.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-type-configuration/property-editor-ui-block-grid-type-configuration.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIBlockGridTypeConfigurationElement } from './property-editor-ui-block-grid-type-configuration.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-block-grid-type-configuration.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.stories.ts index 05d864f9c9..f99e324053 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIBlockListElement } from './property-editor-ui-block-list.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-block-list.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.stories.ts index 3528410251..5a42b6e113 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIBlockListBlockConfigurationElement } from './property-editor-ui-block-list-type-configuration.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-block-list-type-configuration.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.stories.ts index c3c3021e02..1baa932257 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-block-type.element.js'; import type { UmbInputBlockTypeElement } from './input-block-type.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/code-editor/code-editor-modal/code-editor-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/code-editor/code-editor-modal/code-editor-modal.stories.ts index e114a8e9b6..4098bfd19d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/code-editor/code-editor-modal/code-editor-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/code-editor/code-editor-modal/code-editor-modal.stories.ts @@ -1,5 +1,5 @@ import type { UmbCodeEditorModalData } from './code-editor-modal.token.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; export default { diff --git a/src/Umbraco.Web.UI.Client/src/packages/code-editor/components/code-editor.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/code-editor/components/code-editor.stories.ts index f9f096bf96..a31e07ce1f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/code-editor/components/code-editor.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/code-editor/components/code-editor.stories.ts @@ -1,7 +1,7 @@ import type { CodeEditorLanguage } from '../models/code-editor.model.js'; import { CodeEditorTheme } from '../models/code-editor.model.js'; import type { UmbCodeEditorElement } from './code-editor.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './code-editor.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/code-editor/property-editor/property-editor-ui-code-editor.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/code-editor/property-editor/property-editor-ui-code-editor.stories.ts index 6abed142e6..4eee51cd8d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/code-editor/property-editor/property-editor-ui-code-editor.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/code-editor/property-editor/property-editor-ui-code-editor.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUICodeEditorElement } from './property-editor-ui-code-editor.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-code-editor.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/body-layout/body-layout.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/body-layout/body-layout.stories.ts index 263c20954d..b94eb5b60f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/body-layout/body-layout.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/body-layout/body-layout.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './body-layout.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/code-block/code-block.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/code-block/code-block.stories.ts index faefbc446e..f705537393 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/code-block/code-block.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/code-block/code-block.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './code-block.element.js'; import type { UmbCodeBlockElement } from './code-block.element.js'; import { html } from '@umbraco-cms/backoffice/external/lit'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/footer-layout/footer-layout.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/footer-layout/footer-layout.stories.ts index 2a42b77c73..d6820725f5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/footer-layout/footer-layout.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/footer-layout/footer-layout.stories.ts @@ -1,7 +1,7 @@ import './footer-layout.element.js'; import type { UmbFooterLayoutElement } from './footer-layout.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; export default { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/header-app/header-app.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/header-app/header-app.stories.ts index a092461abd..8218f1ab7d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/header-app/header-app.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/header-app/header-app.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './header-app-button.element.js'; import type { UmbHeaderAppButtonElement } from './header-app-button.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/history/history-list.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/history/history-list.stories.ts index be67afd590..87fbc0899c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/history/history-list.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/history/history-list.stories.ts @@ -3,7 +3,7 @@ import './history-item.element.js'; import type { UmbHistoryListElement } from './history-list.element.js'; import type { UmbHistoryItemElement } from './history-item.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; export default { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/icon/icon.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/icon/icon.stories.ts index e8a5cfae35..c34120296f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/icon/icon.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/icon/icon.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './icon.element.js'; import type { UmbIconElement } from './icon.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-color/input-color.mdx b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-color/input-color.mdx index f24e138b0c..691b49c6be 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-color/input-color.mdx +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-color/input-color.mdx @@ -1,4 +1,4 @@ -import { Meta, Title, Primary, Controls, Story, Source } from '@storybook/blocks'; +import { Meta, Title, Primary, Controls, Story, Source } from '@storybook/addon-docs/blocks'; import * as ColorStories from './input-color.stories'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-color/input-color.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-color/input-color.stories.ts index 4a1c306eb6..47033e76ae 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-color/input-color.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-color/input-color.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-color.element.js'; import type { UmbInputColorElement } from './input-color.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-date/input-date.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-date/input-date.stories.ts index ceec7f9b1c..f99e300e19 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-date/input-date.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-date/input-date.stories.ts @@ -1,5 +1,5 @@ import type { UmbInputDateElement } from './input-date.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-date.element.js'; const meta: Meta = { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-dropdown/input-dropdown-list.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-dropdown/input-dropdown-list.stories.ts index c79fea4cc3..6e29445802 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-dropdown/input-dropdown-list.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-dropdown/input-dropdown-list.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-dropdown-list.element.js'; import type { UmbInputDropdownListElement } from './input-dropdown-list.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-eye-dropper/input-eye-dropper.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-eye-dropper/input-eye-dropper.stories.ts index b9d3b5aafd..97de0206e4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-eye-dropper/input-eye-dropper.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-eye-dropper/input-eye-dropper.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-eye-dropper.element.js'; import type { UmbInputEyeDropperElement } from './input-eye-dropper.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-number-range/input-number-range.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-number-range/input-number-range.stories.ts index 2058c85c27..d053fa1094 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-number-range/input-number-range.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-number-range/input-number-range.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-number-range.element.js'; import type { UmbInputNumberRangeElement } from './input-number-range.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-radio-button-list/input-radio-button-list.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-radio-button-list/input-radio-button-list.stories.ts index 435af7254d..d6870a3892 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-radio-button-list/input-radio-button-list.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-radio-button-list/input-radio-button-list.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-radio-button-list.element.js'; import type { UmbInputRadioButtonListElement } from './input-radio-button-list.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-slider/input-slider.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-slider/input-slider.stories.ts index 71e1b5c4e0..8567ed5f4b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-slider/input-slider.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-slider/input-slider.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-slider.element.js'; import type { UmbInputSliderElement } from './input-slider.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-toggle/input-toggle.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-toggle/input-toggle.stories.ts index 570d926332..0b08836656 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-toggle/input-toggle.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-toggle/input-toggle.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-toggle.element.js'; import type { UmbInputToggleElement } from './input-toggle.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/split-panel/split-panel.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/split-panel/split-panel.stories.ts index 3cb9752a4e..d93df4574a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/split-panel/split-panel.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/split-panel/split-panel.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './split-panel.element.js'; import type { UmbSplitPanelElement } from './split-panel.element.js'; import { html } from '@umbraco-cms/backoffice/external/lit'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/stack/stack.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/stack/stack.stories.ts index 23477f0ea2..248f5674b5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/stack/stack.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/stack/stack.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './stack.element.js'; import type { UmbStackElement } from './stack.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.stories.ts index fbadfef55f..890bf588cd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.stories.ts @@ -1,5 +1,5 @@ import type { UmbTableElement, UmbTableColumn, UmbTableItem } from './table.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { UmbId } from '@umbraco-cms/backoffice/id'; import './table.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/culture/components/input-culture-select/input-culture-select.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/culture/components/input-culture-select/input-culture-select.stories.ts index 2f2fb45be4..c1bfc7789a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/culture/components/input-culture-select/input-culture-select.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/culture/components/input-culture-select/input-culture-select.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-culture-select.element.js'; import type { UmbInputCultureSelectElement } from './input-culture-select.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/debug/stories/debug.mdx b/src/Umbraco.Web.UI.Client/src/packages/core/debug/stories/debug.mdx index 188a7a3c28..9b2a5fe6d1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/debug/stories/debug.mdx +++ b/src/Umbraco.Web.UI.Client/src/packages/core/debug/stories/debug.mdx @@ -1,4 +1,4 @@ -import { Meta } from '@storybook/blocks'; +import { Meta } from '@storybook/addon-docs/blocks'; import DebugDialogImage from './umb-debug-dialog.jpg'; import DebugImage from './umb-debug.jpg'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-picker-modal/icon-picker-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-picker-modal/icon-picker-modal.stories.ts index 5f00d71447..1103f6cec1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-picker-modal/icon-picker-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-picker-modal/icon-picker-modal.stories.ts @@ -2,7 +2,7 @@ import './icon-picker-modal.element.js'; import type { UmbIconPickerModalElement } from './icon-picker-modal.element.js'; import type { UmbIconPickerModalValue } from './icon-picker-modal.token.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; export default { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon.stories.ts index 5ee8b5106a..899475e9d6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon.stories.ts @@ -1,5 +1,5 @@ import icons from './icons.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html, repeat } from '@umbraco-cms/backoffice/external/lit'; export default { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/localization/stories/localization.mdx b/src/Umbraco.Web.UI.Client/src/packages/core/localization/stories/localization.mdx index 0cbcb08654..97483ad8b4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/localization/stories/localization.mdx +++ b/src/Umbraco.Web.UI.Client/src/packages/core/localization/stories/localization.mdx @@ -1,4 +1,4 @@ -import { Canvas, Meta } from '@storybook/addon-docs'; +import { Canvas, Meta } from '@storybook/addon-docs/blocks'; import * as LocalizeStories from './localize.element.stories'; @@ -135,8 +135,8 @@ Out of the box, Umbraco ships with the following languages denoted by their ISO - `cy-GB` - Welsh (United Kingdom) - `da-DK` - Danish (Denmark) - `de-DE` - German (Germany) -- `en-GB` - English (United Kingdom) -- `en-US` - **English (United States)** (fallback language) +- `en` - **English (United Kingdom)** (fallback language) +- `en-US` - English (United States) - `es-ES` - Spanish (Spain) - `fr-FR` - French (France) - `he-IL` - Hebrew (Israel) @@ -147,11 +147,12 @@ Out of the box, Umbraco ships with the following languages denoted by their ISO - `nb-NO` - Norwegian Bokmål (Norway) - `nl-NL` - Dutch (Netherlands) - `pl-PL` - Polish (Poland) +- `pt` - Portuguese (Portugal) - `pt-BR` - Portuguese (Brazil) - `ro-RO` - Romanian (Romania) - `ru-RU` - Russian (Russia) - `sv-SE` - Swedish (Sweden) - `tr-TR` - Turkish (Turkey) - `ua-UA` - Ukrainian (Ukraine) -- `zh-CN` - Chinese (China) +- `zh` - Chinese (China) - `zh-TW` - Chinese (Taiwan) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/localization/stories/localize.element.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/localization/stories/localize.element.stories.ts index cef1a3f7ee..ee7942b457 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/localization/stories/localize.element.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/localization/stories/localize.element.stories.ts @@ -1,5 +1,5 @@ import type { UmbLocalizeElement } from '../localize.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import '../localize.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/section-sidebar-menu-with-entity-actions.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/section-sidebar-menu-with-entity-actions.stories.ts index d43290dd91..d67234efaa 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/section-sidebar-menu-with-entity-actions.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/section-sidebar-menu-with-entity-actions.stories.ts @@ -1,5 +1,5 @@ import type { UmbSectionSidebarMenuWithEntityActionsElement } from './section-sidebar-menu-with-entity-actions.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './section-sidebar-menu-with-entity-actions.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-sidebar-menu.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-sidebar-menu.stories.ts index a20a0041e7..132ea947d1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-sidebar-menu.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-sidebar-menu.stories.ts @@ -1,5 +1,5 @@ import type { UmbSectionSidebarMenuElement } from './section-sidebar-menu.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './section-sidebar-menu.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/confirm/confirm-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/confirm/confirm-modal.stories.ts index cb28810edb..22cb6bb4c6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/confirm/confirm-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/confirm/confirm-modal.stories.ts @@ -1,7 +1,7 @@ import './confirm-modal.element.js'; import type { UmbConfirmModalElement } from './confirm-modal.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import type { UmbConfirmModalData } from '@umbraco-cms/backoffice/modal'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/notification/layouts/default/notification-layout-default.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/notification/layouts/default/notification-layout-default.stories.ts index 673b4c2f28..118af5eb2a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/notification/layouts/default/notification-layout-default.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/notification/layouts/default/notification-layout-default.stories.ts @@ -1,5 +1,5 @@ import type { UmbNotificationLayoutDefaultElement } from './notification-layout-default.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './notification-layout-default.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/notification/stories/notification.mdx b/src/Umbraco.Web.UI.Client/src/packages/core/notification/stories/notification.mdx index 356767cfed..048cc1f45f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/notification/stories/notification.mdx +++ b/src/Umbraco.Web.UI.Client/src/packages/core/notification/stories/notification.mdx @@ -1,4 +1,4 @@ -import { Meta } from '@storybook/blocks'; +import { Meta } from '@storybook/addon-docs/blocks'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/notification/stories/notification.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/notification/stories/notification.stories.ts index 3b503f40fa..f7fc8d5d14 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/notification/stories/notification.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/notification/stories/notification.stories.ts @@ -1,7 +1,7 @@ import './story-notification-default-example.element.js'; import { UmbNotificationContext } from '../notification.context.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; export default { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/components/ref-property-editor-ui/ref-property-editor-ui.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/components/ref-property-editor-ui/ref-property-editor-ui.stories.ts index 72d6bc552f..2db21beb4a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/components/ref-property-editor-ui/ref-property-editor-ui.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/components/ref-property-editor-ui/ref-property-editor-ui.stories.ts @@ -1,6 +1,6 @@ import { UMB_PROPERTY_EDITOR_SCHEMA_ALIAS_DEFAULT } from '../../constants.js'; import type { UmbRefPropertyEditorUIElement } from './ref-property-editor-ui.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './ref-property-editor-ui.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/ui-picker-modal/property-editor-ui-picker-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/ui-picker-modal/property-editor-ui-picker-modal.stories.ts index 1e6ed0ab92..c2f0f2dffd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/ui-picker-modal/property-editor-ui-picker-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/ui-picker-modal/property-editor-ui-picker-modal.stories.ts @@ -1,6 +1,6 @@ import type { UmbPropertyEditorUIPickerModalElement } from './property-editor-ui-picker-modal.element.js'; import type { UmbPropertyEditorUIPickerModalValue } from './property-editor-ui-picker-modal.token.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-picker-modal.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property-layout/property-layout.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property-layout/property-layout.stories.ts index bdc56d6082..6b778c68d2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property-layout/property-layout.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property-layout/property-layout.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyLayoutElement } from './property-layout.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-layout.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property/property.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property/property.stories.ts index 4ec084432b..20caf29aae 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property/property.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property/property.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyElement } from './property.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main/section-main.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main/section-main.stories.ts index df592591e0..17c801ee73 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main/section-main.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main/section-main.stories.ts @@ -1,5 +1,5 @@ import type { UmbSectionMainElement } from './section-main.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './section-main.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-sidebar/section-sidebar.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-sidebar/section-sidebar.stories.ts index 46ae3703f7..e17d977583 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-sidebar/section-sidebar.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-sidebar/section-sidebar.stories.ts @@ -1,5 +1,5 @@ import type { UmbSectionSidebarElement } from './section-sidebar.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './section-sidebar.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.stories.ts index 2e81b4daeb..296deb93d6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './default/default-tree.element.js'; import type { UmbTreeElement } from './tree.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-editor/workspace-editor.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-editor/workspace-editor.stories.ts index 47b70b6eb8..1d8b65145d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-editor/workspace-editor.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-editor/workspace-editor.stories.ts @@ -1,7 +1,7 @@ import './workspace-editor.element.js'; import type { UmbWorkspaceEditorElement } from './workspace-editor.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; export default { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-footer/workspace-footer.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-footer/workspace-footer.stories.ts index 002b020a3c..5d18189ff9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-footer/workspace-footer.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-footer/workspace-footer.stories.ts @@ -2,7 +2,7 @@ import '../workspace-editor/workspace-editor.element.js'; import './workspace-footer.element.js'; import type { UmbWorkspaceFooterLayoutElement } from './workspace-footer.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; export default { diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/components/property-editor-config/property-editor-config.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/components/property-editor-config/property-editor-config.stories.ts index cf1bd41093..c57f88387e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/components/property-editor-config/property-editor-config.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/components/property-editor-config/property-editor-config.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorConfigElement } from './property-editor-config.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-config.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/components/ref-data-type/ref-data-type.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/components/ref-data-type/ref-data-type.stories.ts index f85219e9a6..cb1907be3e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/components/ref-data-type/ref-data-type.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/components/ref-data-type/ref-data-type.stories.ts @@ -1,5 +1,5 @@ import type { UmbRefDataTypeElement } from './ref-data-type.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import { UMB_PROPERTY_EDITOR_SCHEMA_ALIAS_DEFAULT } from '@umbraco-cms/backoffice/property-editor'; import './ref-data-type.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/details/data-type-details-workspace-view.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/details/data-type-details-workspace-view.stories.ts index f0882c5c73..93c689499d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/details/data-type-details-workspace-view.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/details/data-type-details-workspace-view.stories.ts @@ -1,5 +1,5 @@ import type { UmbDataTypeDetailsWorkspaceViewEditElement } from './data-type-details-workspace-view.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; //import { data } from '../../../../../core/mocks/data/data-type.data.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/info/workspace-view-data-type-info.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/info/workspace-view-data-type-info.stories.ts index 7797287378..ae71461e76 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/info/workspace-view-data-type-info.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/info/workspace-view-data-type-info.stories.ts @@ -1,7 +1,7 @@ import './workspace-view-data-type-info.element.js'; import type { UmbWorkspaceViewDataTypeInfoElement } from './workspace-view-data-type-info.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; //import { data } from '../../../../../core/mocks/data/data-type.data.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/workspace/views/workspace-view-dictionary-editor.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/workspace/views/workspace-view-dictionary-editor.stories.ts index 6010038545..e80718d56c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/workspace/views/workspace-view-dictionary-editor.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/workspace/views/workspace-view-dictionary-editor.stories.ts @@ -1,5 +1,5 @@ import type { UmbWorkspaceViewDictionaryEditorElement } from './workspace-view-dictionary-editor.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; //import { data } from '../../../../../core/mocks/data/dictionary.data.js'; import './workspace-view-dictionary-editor.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-redirect-management/dashboard-redirect-management.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-redirect-management/dashboard-redirect-management.stories.ts index e981b83639..240f8042ef 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-redirect-management/dashboard-redirect-management.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-redirect-management/dashboard-redirect-management.stories.ts @@ -1,7 +1,7 @@ import './dashboard-redirect-management.element.js'; import type { UmbDashboardRedirectManagementElement } from './dashboard-redirect-management.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; export default { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/property-editors/document-type-picker/property-editor-ui-document-type-picker.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/property-editors/document-type-picker/property-editor-ui-document-type-picker.stories.ts index 88b7bd1b2d..4c640ec24e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/property-editors/document-type-picker/property-editor-ui-document-type-picker.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/property-editors/document-type-picker/property-editor-ui-document-type-picker.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIDocumentTypePickerElement } from './property-editor-ui-document-type-picker.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-document-type-picker.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.stories.ts index 9828747dce..6c93ac3b72 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-document.element.js'; import type { UmbInputDocumentElement } from './input-document.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/save-modal/document-save-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/save-modal/document-save-modal.stories.ts index e9cff54cdd..70b574c40f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/save-modal/document-save-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/save-modal/document-save-modal.stories.ts @@ -3,7 +3,7 @@ import './document-save-modal.element.js'; import { UmbDocumentVariantState } from '../../types.js'; import type { UmbDocumentSaveModalData, UmbDocumentSaveModalValue } from './document-save-modal.token.js'; import type { UmbDocumentSaveModalElement } from './document-save-modal.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; const modalData: UmbDocumentSaveModalData = { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.stories.ts index e504667619..2065fe8af9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIDocumentPickerElement } from './property-editor-ui-document-picker.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-document-picker.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.stories.ts index 7f075c86e3..145871d796 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.stories.ts @@ -4,7 +4,7 @@ import type { UmbDocumentPublishWithDescendantsModalValue, } from './document-publish-with-descendants-modal.token.js'; import type { UmbDocumentPublishWithDescendantsModalElement } from './document-publish-with-descendants-modal.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './document-publish-with-descendants-modal.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.stories.ts index 91fbc68533..054968431c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.stories.ts @@ -3,7 +3,7 @@ import './document-publish-modal.element.js'; import { UmbDocumentVariantState } from '../../../types.js'; import type { UmbDocumentPublishModalData, UmbDocumentPublishModalValue } from './document-publish-modal.token.js'; import type { UmbDocumentPublishModalElement } from './document-publish-modal.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; const modalData: UmbDocumentPublishModalData = { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.stories.ts index cec4521ce7..eb41d10976 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.stories.ts @@ -3,7 +3,7 @@ import './document-schedule-modal.element.js'; import { UmbDocumentVariantState } from '../../../types.js'; import type { UmbDocumentScheduleModalData, UmbDocumentScheduleModalValue } from './document-schedule-modal.token.js'; import type { UmbDocumentScheduleModalElement } from './document-schedule-modal.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; const modalData: UmbDocumentScheduleModalData = { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.stories.ts index dd9ed46339..a65c48ea92 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.stories.ts @@ -6,7 +6,7 @@ import type { UmbDocumentUnpublishModalValue, } from './document-unpublish-modal.token.js'; import type { UmbDocumentUnpublishModalElement } from './document-unpublish-modal.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; const modalData: UmbDocumentUnpublishModalData = { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/reference/components/document-reference-table.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/reference/components/document-reference-table.stories.ts index c91b9f2075..cc995da4db 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/reference/components/document-reference-table.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/reference/components/document-reference-table.stories.ts @@ -1,5 +1,5 @@ import type { UmbDocumentReferenceTableElement } from './document-reference-table.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './document-reference-table.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info.stories.ts index 9ff3b06342..3e279b6874 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info.stories.ts @@ -1,7 +1,7 @@ import './document-workspace-view-info.element.js'; import type { UmbDocumentWorkspaceViewInfoElement } from './document-workspace-view-info.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; // import { data } from '../../../../../../core/mocks/data/document.data.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/embedded-media/modal/embedded-media-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/embedded-media/modal/embedded-media-modal.stories.ts index 35e21f4aea..3a786e9516 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/embedded-media/modal/embedded-media-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/embedded-media/modal/embedded-media-modal.stories.ts @@ -1,7 +1,7 @@ import './embedded-media-modal.element.js'; import type { UmbEmbeddedMediaModalData } from './embedded-media-modal.token.js'; -import type { Meta } from '@storybook/web-components'; +import type { Meta } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; export default { diff --git a/src/Umbraco.Web.UI.Client/src/packages/health-check/dashboard-health-check.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/health-check/dashboard-health-check.stories.ts index a95b27213a..3061d5877c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/health-check/dashboard-health-check.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/health-check/dashboard-health-check.stories.ts @@ -1,5 +1,5 @@ import type { UmbDashboardHealthCheckOverviewElement } from './views/health-check-overview.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './views/health-check-overview.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/language/components/input-language/input-language.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/language/components/input-language/input-language.stories.ts index faa99db8d0..7e7a01282d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/language/components/input-language/input-language.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/language/components/input-language/input-language.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-language.element.js'; import type { UmbInputLanguageElement } from './input-language.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.stories.ts index 546e0a51b2..34368b9a1f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.stories.ts @@ -1,7 +1,7 @@ import './donut-slice.element.js'; import './donut-chart.element.js'; -import type { Meta } from '@storybook/web-components'; +import type { Meta } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; export default { diff --git a/src/Umbraco.Web.UI.Client/src/packages/markdown-editor/components/input-markdown-editor/input-markdown.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/markdown-editor/components/input-markdown-editor/input-markdown.stories.ts index b29e0d2338..5dc6dfeec6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/markdown-editor/components/input-markdown-editor/input-markdown.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/markdown-editor/components/input-markdown-editor/input-markdown.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-markdown.element.js'; import type { UmbInputMarkdownElement } from './input-markdown.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/markdown-editor/property-editors/markdown-editor/property-editor-ui-markdown-editor.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/markdown-editor/property-editors/markdown-editor/property-editor-ui-markdown-editor.stories.ts index 948c6ddd6a..f259b4725a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/markdown-editor/property-editors/markdown-editor/property-editor-ui-markdown-editor.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/markdown-editor/property-editors/markdown-editor/property-editor-ui-markdown-editor.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIMarkdownEditorElement } from './property-editor-ui-markdown-editor.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-markdown-editor.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/components/input-dropzone/input-dropzone.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/components/input-dropzone/input-dropzone.stories.ts index d34604ef72..0c3ac171f0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/components/input-dropzone/input-dropzone.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/components/input-dropzone/input-dropzone.stories.ts @@ -1,5 +1,5 @@ import type { UmbInputDropzoneElement } from './input-dropzone.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './input-dropzone.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/property-editors/media-type-picker/property-editor-ui-media-type-picker.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/property-editors/media-type-picker/property-editor-ui-media-type-picker.stories.ts index 686ba394f8..09b77e116e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/property-editors/media-type-picker/property-editor-ui-media-type-picker.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/property-editors/media-type-picker/property-editor-ui-media-type-picker.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIMediaTypePickerElement } from './property-editor-ui-media-type-picker.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-media-type-picker.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.stories.ts index 0667b54a28..2c84f64cab 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-media.element.js'; import type { UmbInputMediaElement } from './input-media.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.stories.ts index 07bc0c8492..039de9488f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-upload-field.element.js'; import type { UmbInputUploadFieldElement } from './input-upload-field.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.stories.ts index 7ae36d34da..135b89fd6c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIImageCropperElement } from './property-editor-ui-image-cropper.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-image-cropper.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-crops/property-editor-ui-image-crops.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-crops/property-editor-ui-image-crops.stories.ts index 9e4fcc22ab..cb17439cd3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-crops/property-editor-ui-image-crops.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-crops/property-editor-ui-image-crops.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIImageCropsElement } from './property-editor-ui-image-crops.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './property-editor-ui-image-crops.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/media-picker/property-editor-ui-media-picker.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/media-picker/property-editor-ui-media-picker.stories.ts index aadb69510d..2221016dbf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/media-picker/property-editor-ui-media-picker.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/media-picker/property-editor-ui-media-picker.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIMediaPickerElement } from './property-editor-ui-media-picker.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-media-picker.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/upload-field/property-editor-ui-upload-field.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/upload-field/property-editor-ui-upload-field.stories.ts index ed5431b4c0..1f0fbcd696 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/upload-field/property-editor-ui-upload-field.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/upload-field/property-editor-ui-upload-field.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIUploadFieldElement } from './property-editor-ui-upload-field.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-upload-field.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-group/property-editor/member-group-picker/property-editor-ui-member-group-picker.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-group/property-editor/member-group-picker/property-editor-ui-member-group-picker.stories.ts index c360d75550..148dfe8a34 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-group/property-editor/member-group-picker/property-editor-ui-member-group-picker.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-group/property-editor/member-group-picker/property-editor-ui-member-group-picker.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIMemberGroupPickerElement } from './property-editor-ui-member-group-picker.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-member-group-picker.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/input-member/input-member.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/input-member/input-member.stories.ts index ccdc261bf7..20a632ed3d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/input-member/input-member.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/input-member/input-member.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-member.element.js'; import type { UmbInputMemberElement } from './input-member.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/property-editor/member-picker/property-editor-ui-member-picker.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/property-editor/member-picker/property-editor-ui-member-picker.stories.ts index 35c583daf0..f8bee20bda 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/property-editor/member-picker/property-editor-ui-member-picker.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/property-editor/member-picker/property-editor-ui-member-picker.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIMemberPickerElement } from './property-editor-ui-member-picker.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-member-picker.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/models-builder/models-builder-dashboard.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/models-builder/models-builder-dashboard.stories.ts index a4cc870e62..92df018324 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/models-builder/models-builder-dashboard.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/models-builder/models-builder-dashboard.stories.ts @@ -1,5 +1,5 @@ import type { UmbModelsBuilderDashboardElement } from './models-builder-dashboard.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './models-builder-dashboard.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/components/input-multi-url/input-multi-url.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/components/input-multi-url/input-multi-url.stories.ts index 1017d7d56d..ee359054a5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/components/input-multi-url/input-multi-url.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/components/input-multi-url/input-multi-url.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-multi-url.element.js'; import type { UmbInputMultiUrlElement } from './input-multi-url.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.stories.ts index 52fdd45979..48afaddc19 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.stories.ts @@ -2,7 +2,7 @@ import '../../core/components/body-layout/body-layout.element.js'; import './link-picker-modal.element.js'; import type { UmbLinkPickerModalElement } from './link-picker-modal.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; export default { diff --git a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/property-editor/property-editor-ui-multi-url-picker.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/property-editor/property-editor-ui-multi-url-picker.stories.ts index 4a904c9fcc..c14f693351 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/property-editor/property-editor-ui-multi-url-picker.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/property-editor/property-editor-ui-multi-url-picker.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIMultiUrlPickerElement } from './property-editor-ui-multi-url-picker.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-multi-url-picker.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/performance-profiling/dashboard-performance-profiling.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/performance-profiling/dashboard-performance-profiling.stories.ts index ed91396653..c16a893d39 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/performance-profiling/dashboard-performance-profiling.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/performance-profiling/dashboard-performance-profiling.stories.ts @@ -1,5 +1,5 @@ import type { UmbDashboardPerformanceProfilingElement } from './dashboard-performance-profiling.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './dashboard-performance-profiling.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/accepted-types/property-editor-ui-accepted-upload-types.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/accepted-types/property-editor-ui-accepted-upload-types.stories.ts index 61d37fc06f..49873abde8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/accepted-types/property-editor-ui-accepted-upload-types.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/accepted-types/property-editor-ui-accepted-upload-types.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIAcceptedUploadTypesElement } from './property-editor-ui-accepted-upload-types.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-accepted-upload-types.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/components/input-checkbox-list/input-checkbox-list.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/components/input-checkbox-list/input-checkbox-list.stories.ts index 61b9442e25..c8051d5022 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/components/input-checkbox-list/input-checkbox-list.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/components/input-checkbox-list/input-checkbox-list.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-checkbox-list.element.js'; import type { UmbInputCheckboxListElement } from './input-checkbox-list.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/property-editor-ui-checkbox-list.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/property-editor-ui-checkbox-list.stories.ts index 42cc10f3a5..828182ef1c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/property-editor-ui-checkbox-list.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/property-editor-ui-checkbox-list.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUICheckboxListElement } from './property-editor-ui-checkbox-list.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-checkbox-list.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/bulk-action-permissions/permissions.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/bulk-action-permissions/permissions.stories.ts index 6c10eb7a06..f21d4971b3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/bulk-action-permissions/permissions.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/bulk-action-permissions/permissions.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUICollectionPermissionsElement } from './permissions.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './permissions.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/column/column-configuration.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/column/column-configuration.stories.ts index e7b25ee756..c0b1367102 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/column/column-configuration.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/column/column-configuration.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUICollectionColumnConfigurationElement } from './column-configuration.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './column-configuration.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/layout/layout-configuration.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/layout/layout-configuration.stories.ts index ca0bf260c3..4c81463f31 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/layout/layout-configuration.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/layout/layout-configuration.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUICollectionLayoutConfigurationElement } from './layout-configuration.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './layout-configuration.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/order-by/order-by.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/order-by/order-by.stories.ts index 8b6fdaf4f6..9fc2d5b0d2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/order-by/order-by.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/order-by/order-by.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUICollectionOrderByElement } from './order-by.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './order-by.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/property-editor-ui-collection-view.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/property-editor-ui-collection-view.stories.ts index 4a4c6c1f3f..c581e73d56 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/property-editor-ui-collection-view.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/property-editor-ui-collection-view.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUICollectionElement } from './property-editor-ui-collection.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-collection.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/property-editor-ui-color-picker.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/property-editor-ui-color-picker.stories.ts index 05ae67871b..5dca0e5e1d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/property-editor-ui-color-picker.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/property-editor-ui-color-picker.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIColorPickerElement } from './property-editor-ui-color-picker.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-color-picker.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/input-content.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/input-content.stories.ts index 4a940912b2..7510aac743 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/input-content.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/input-content.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-content.element.js'; import type { UmbInputContentElement } from './input-content.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/property-editor-ui-content-picker-source.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/property-editor-ui-content-picker-source.stories.ts index 698f8f5898..ca5f6a086e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/property-editor-ui-content-picker-source.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/property-editor-ui-content-picker-source.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIContentPickerSourceElement } from './property-editor-ui-content-picker-source.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-content-picker-source.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/property-editor-ui-content-picker.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/property-editor-ui-content-picker.stories.ts index 173ad3ae3f..ed1c7a3648 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/property-editor-ui-content-picker.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/property-editor-ui-content-picker.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIContentPickerElement } from './property-editor-ui-content-picker.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-content-picker.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/date-picker/property-editor-ui-date-picker.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/date-picker/property-editor-ui-date-picker.stories.ts index 01b5c32884..2f4f6ddf8f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/date-picker/property-editor-ui-date-picker.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/date-picker/property-editor-ui-date-picker.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIDatePickerElement } from './property-editor-ui-date-picker.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-date-picker.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/dimensions/property-editor-ui-dimensions.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/dimensions/property-editor-ui-dimensions.stories.ts index b48b92542f..aa1ddb7aeb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/dimensions/property-editor-ui-dimensions.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/dimensions/property-editor-ui-dimensions.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIDimensionsElement } from './property-editor-ui-dimensions.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-dimensions.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.stories.ts index 12e1c8e892..9d0be36c2b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIDropdownElement } from './property-editor-ui-dropdown.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-dropdown.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/eye-dropper/property-editor-ui-eye-dropper.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/eye-dropper/property-editor-ui-eye-dropper.stories.ts index b7ee633b2f..5dda76c7b3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/eye-dropper/property-editor-ui-eye-dropper.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/eye-dropper/property-editor-ui-eye-dropper.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIEyeDropperElement } from './property-editor-ui-eye-dropper.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-eye-dropper.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/icon-picker/property-editor-ui-icon-picker.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/icon-picker/property-editor-ui-icon-picker.stories.ts index 0297880c16..e1dd406dba 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/icon-picker/property-editor-ui-icon-picker.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/icon-picker/property-editor-ui-icon-picker.stories.ts @@ -1,5 +1,5 @@ import type { UmbIconPickerModalElement } from '@umbraco-cms/backoffice/icon'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-icon-picker.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/label/property-editor-ui-label.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/label/property-editor-ui-label.stories.ts index 1d6b0c7237..263deb2453 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/label/property-editor-ui-label.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/label/property-editor-ui-label.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUILabelElement } from './property-editor-ui-label.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-label.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/multiple-text-string/property-editor-ui-multiple-text-string.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/multiple-text-string/property-editor-ui-multiple-text-string.stories.ts index cb305ceadc..9bdf4c5abd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/multiple-text-string/property-editor-ui-multiple-text-string.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/multiple-text-string/property-editor-ui-multiple-text-string.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIMultipleTextStringElement } from './property-editor-ui-multiple-text-string.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-multiple-text-string.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/number-range/property-editor-ui-number-range.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/number-range/property-editor-ui-number-range.stories.ts index 7976e81350..984e0b5fcc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/number-range/property-editor-ui-number-range.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/number-range/property-editor-ui-number-range.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUINumberRangeElement } from './property-editor-ui-number-range.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-number-range.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/number/property-editor-ui-number.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/number/property-editor-ui-number.stories.ts index 272a64e594..693433985d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/number/property-editor-ui-number.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/number/property-editor-ui-number.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUINumberElement } from './property-editor-ui-number.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-number.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/order-direction/property-editor-ui-order-direction.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/order-direction/property-editor-ui-order-direction.stories.ts index df596f05bf..ffc39c4656 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/order-direction/property-editor-ui-order-direction.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/order-direction/property-editor-ui-order-direction.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIOrderDirectionElement } from './property-editor-ui-order-direction.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-order-direction.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/overlay-size/property-editor-ui-overlay-size.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/overlay-size/property-editor-ui-overlay-size.stories.ts index 15c542f241..945025c35e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/overlay-size/property-editor-ui-overlay-size.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/overlay-size/property-editor-ui-overlay-size.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIOverlaySizeElement } from './property-editor-ui-overlay-size.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-overlay-size.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/radio-button-list/property-editor-ui-radio-button-list.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/radio-button-list/property-editor-ui-radio-button-list.stories.ts index 36ba000b34..91709a5c13 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/radio-button-list/property-editor-ui-radio-button-list.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/radio-button-list/property-editor-ui-radio-button-list.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIRadioButtonListElement } from './property-editor-ui-radio-button-list.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-radio-button-list.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/select/property-editor-ui-select.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/select/property-editor-ui-select.stories.ts index 1e4cd7fdbf..7bc2ad6766 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/select/property-editor-ui-select.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/select/property-editor-ui-select.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUISelectElement } from './property-editor-ui-select.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-select.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/slider/property-editor-ui-slider.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/slider/property-editor-ui-slider.stories.ts index c48c86b9a3..ee5c7d3675 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/slider/property-editor-ui-slider.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/slider/property-editor-ui-slider.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUISliderElement } from './property-editor-ui-slider.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-slider.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/text-box/property-editor-ui-text-box.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/text-box/property-editor-ui-text-box.stories.ts index b814dcccf5..73d5dbc8bf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/text-box/property-editor-ui-text-box.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/text-box/property-editor-ui-text-box.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUITextBoxElement } from './property-editor-ui-text-box.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-text-box.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/textarea/property-editor-ui-textarea.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/textarea/property-editor-ui-textarea.stories.ts index 603c2c6e7f..69d8257571 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/textarea/property-editor-ui-textarea.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/textarea/property-editor-ui-textarea.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUITextareaElement } from './property-editor-ui-textarea.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-textarea.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/toggle/property-editor-ui-toggle.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/toggle/property-editor-ui-toggle.stories.ts index da573e6d7b..1f1023edd3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/toggle/property-editor-ui-toggle.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/toggle/property-editor-ui-toggle.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIToggleElement } from './property-editor-ui-toggle.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-toggle.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/value-type/property-editor-ui-value-type.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/value-type/property-editor-ui-value-type.stories.ts index 14ecf6dc92..92a17606b6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/value-type/property-editor-ui-value-type.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/value-type/property-editor-ui-value-type.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIValueTypeElement } from './property-editor-ui-value-type.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-value-type.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/publish-cache/dashboard-published-status.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/publish-cache/dashboard-published-status.stories.ts index 9d88faf916..67a06efe35 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/publish-cache/dashboard-published-status.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/publish-cache/dashboard-published-status.stories.ts @@ -1,5 +1,5 @@ import type { UmbDashboardPublishedStatusElement } from './dashboard-published-status.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './dashboard-published-status.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/dashboard-examine-management.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/dashboard-examine-management.stories.ts index b645bc807d..227750e665 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/dashboard-examine-management.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/dashboard-examine-management.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; //import type { UmbDashboardExamineManagementElement } from './dashboard-examine-management.element.js'; import './dashboard-examine-management.element.js'; import type { UmbDashboardExamineOverviewElement } from './views/section-view-examine-overview.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/welcome/settings-welcome-dashboard.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/welcome/settings-welcome-dashboard.stories.ts index b3ab11f854..b75966f460 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/welcome/settings-welcome-dashboard.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/welcome/settings-welcome-dashboard.stories.ts @@ -1,5 +1,5 @@ import type { UmbSettingsWelcomeDashboardElement } from './settings-welcome-dashboard.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './settings-welcome-dashboard.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/static-file/components/input-static-file/input-static-file.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/static-file/components/input-static-file/input-static-file.stories.ts index 17156646ea..95610d69a5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/static-file/components/input-static-file/input-static-file.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/static-file/components/input-static-file/input-static-file.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './input-static-file.element.js'; import type { UmbInputStaticFileElement } from './input-static-file.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/property-editor-ui-static-file-picker.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/property-editor-ui-static-file-picker.stories.ts index 1c2fb9b333..35e99889ee 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/property-editor-ui-static-file-picker.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/property-editor-ui-static-file-picker.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIStaticFilePickerElement } from './property-editor-ui-static-file-picker.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-static-file-picker.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tags/components/tags-input/tags-input.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/tags/components/tags-input/tags-input.stories.ts index d18ff289be..80980e7f8f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tags/components/tags-input/tags-input.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tags/components/tags-input/tags-input.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './tags-input.element.js'; import type { UmbTagsInputElement } from './tags-input.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/property-editor-ui-tags.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/property-editor-ui-tags.stories.ts index 0bf2824385..b9e757f36f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/property-editor-ui-tags.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/property-editor-ui-tags.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUITagsElement } from './property-editor-ui-tags.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-tags.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/telemetry/dashboard-telemetry.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/telemetry/dashboard-telemetry.stories.ts index 606f301228..691de20778 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/telemetry/dashboard-telemetry.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/telemetry/dashboard-telemetry.stories.ts @@ -1,5 +1,5 @@ import type { UmbDashboardTelemetryElement } from './dashboard-telemetry.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './dashboard-telemetry.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/modals/templating-page-field-builder/components/template-field-dropdown-list/template-field-dropdown-list.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/modals/templating-page-field-builder/components/template-field-dropdown-list/template-field-dropdown-list.stories.ts index 613e60064c..b237e95438 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/modals/templating-page-field-builder/components/template-field-dropdown-list/template-field-dropdown-list.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/modals/templating-page-field-builder/components/template-field-dropdown-list/template-field-dropdown-list.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './template-field-dropdown-list.element.js'; import type { UmbTemplateFieldDropdownListElement } from './template-field-dropdown-list.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/property-editors/stylesheet-picker/property-editor-ui-stylesheet-picker.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/property-editors/stylesheet-picker/property-editor-ui-stylesheet-picker.stories.ts index fe1803a1ec..fb83b5a4f8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/property-editors/stylesheet-picker/property-editor-ui-stylesheet-picker.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/property-editors/stylesheet-picker/property-editor-ui-stylesheet-picker.stories.ts @@ -1,6 +1,6 @@ import { umbDataTypeMockDb } from '../../../../../mocks/data/data-type/data-type.db.js'; import { html } from '@umbraco-cms/backoffice/external/lit'; -import type { Meta } from '@storybook/web-components'; +import type { Meta } from '@storybook/web-components-vite'; import type { UmbDataTypeDetailModel } from '@umbraco-cms/backoffice/data-type'; import './property-editor-ui-stylesheet-picker.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/global-components/template-card/template-card.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/global-components/template-card/template-card.stories.ts index c04fc23c64..110294e35d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/global-components/template-card/template-card.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/global-components/template-card/template-card.stories.ts @@ -1,5 +1,5 @@ import type { UmbTemplateCardElement } from './template-card.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './template-card.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/property-editor-ui-tiptap.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/property-editor-ui-tiptap.stories.ts index feb8a9c7c8..0967de6a70 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/property-editor-ui-tiptap.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/property-editor-ui-tiptap.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUiTiptapElement } from './property-editor-ui-tiptap.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; import './property-editor-ui-tiptap.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/umbraco-news-dashboard.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/umbraco-news-dashboard.stories.ts index 795c21735b..16071a328f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/umbraco-news-dashboard.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/umbraco-news-dashboard.stories.ts @@ -1,7 +1,7 @@ import './umbraco-news-dashboard.element.js'; import type { UmbUmbracoNewsDashboardElement } from './umbraco-news-dashboard.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; export default { diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/external-login/modals/external-login-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/external-login/modals/external-login-modal.stories.ts index 9800af6069..3ff60a1b21 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/external-login/modals/external-login-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/external-login/modals/external-login-modal.stories.ts @@ -1,5 +1,5 @@ import type { UmbCurrentUserExternalLoginModalElement } from './external-login-modal.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import { UmbServerExtensionRegistrator } from '@umbraco-cms/backoffice/extension-api'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/modals/current-user-mfa-disable/current-user-mfa-disable-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/modals/current-user-mfa-disable/current-user-mfa-disable-modal.stories.ts index 6bf592f1ea..08ad165a29 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/modals/current-user-mfa-disable/current-user-mfa-disable-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/modals/current-user-mfa-disable/current-user-mfa-disable-modal.stories.ts @@ -1,5 +1,5 @@ import type { UmbCurrentUserMfaDisableModalElement } from './current-user-mfa-disable-modal.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './current-user-mfa-disable-modal.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/modals/current-user-mfa-enable/current-user-mfa-enable-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/modals/current-user-mfa-enable/current-user-mfa-enable-modal.stories.ts index bbe1cd4e94..db233eb55e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/modals/current-user-mfa-enable/current-user-mfa-enable-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/modals/current-user-mfa-enable/current-user-mfa-enable-modal.stories.ts @@ -1,5 +1,5 @@ import type { UmbCurrentUserMfaEnableModalElement } from './current-user-mfa-enable-modal.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import { UmbServerExtensionRegistrator } from '@umbraco-cms/backoffice/extension-api'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/modals/current-user-mfa/current-user-mfa-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/modals/current-user-mfa/current-user-mfa-modal.stories.ts index 447eed7e31..f65e55abc9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/modals/current-user-mfa/current-user-mfa-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/modals/current-user-mfa/current-user-mfa-modal.stories.ts @@ -1,5 +1,5 @@ import type { UmbCurrentUserMfaModalElement } from './current-user-mfa-modal.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import { UmbServerExtensionRegistrator } from '@umbraco-cms/backoffice/extension-api'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/input-user-group/user-group-input.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/input-user-group/user-group-input.stories.ts index 98cf770847..a69082f511 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/input-user-group/user-group-input.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/input-user-group/user-group-input.stories.ts @@ -1,5 +1,5 @@ import type { UmbUserGroupInputElement } from './user-group-input.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './user-group-input.element.js'; const meta: Meta = { diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/components/user-input/user-input.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/components/user-input/user-input.stories.ts index fdbf7283c9..42629ff171 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/components/user-input/user-input.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/components/user-input/user-input.stories.ts @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import './user-input.element.js'; import type { UmbUserInputElement } from './user-input.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/modals/user-mfa/user-mfa-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/modals/user-mfa/user-mfa-modal.stories.ts index d5f40befae..9950faac72 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/modals/user-mfa/user-mfa-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/modals/user-mfa/user-mfa-modal.stories.ts @@ -1,5 +1,5 @@ import type { UmbUserMfaModalElement } from './user-mfa-modal.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import { UmbServerExtensionRegistrator } from '@umbraco-cms/backoffice/extension-api'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/property-editor/user-picker/property-editor-ui-user-picker.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/property-editor/user-picker/property-editor-ui-user-picker.stories.ts index a880e25e3b..12039f72fb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/property-editor/user-picker/property-editor-ui-user-picker.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/property-editor/user-picker/property-editor-ui-user-picker.stories.ts @@ -1,5 +1,5 @@ import type { UmbPropertyEditorUIUserPickerElement } from './property-editor-ui-user-picker.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; +import type { Meta, StoryFn } from '@storybook/web-components-vite'; import { html } from '@umbraco-cms/backoffice/external/lit'; import './property-editor-ui-user-picker.element.js'; From 3b4639de08c02ba50833db347862bd26e1d3e35d Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 1 Jul 2025 10:37:57 +0200 Subject: [PATCH 68/82] Collection rendering performance improvements Part 1: Improve Entity actions render performance (#19605) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add null checks for editPath and name in render method The render method now checks for the presence of both editPath and _name before rendering the button, preventing potential errors when these values are missing. * Refactor dropdown open state handling Replaces the public 'open' property with a private field and getter/setter to better control dropdown state. Moves popover open/close logic into the setter, removes the 'updated' lifecycle method, and conditionally renders dropdown content based on the open state. * add opened and closed events * dispatch opened and closed events * Render dropdown content only when open Introduces an _isOpen state to control rendering of the dropdown content in UmbEntityActionsBundleElement. Dropdown content is now only rendered when the dropdown is open, improving performance and preventing unnecessary DOM updates. * Update dropdown.element.ts * create a cache elements * Optimize entity actions observation with IntersectionObserver Adds an IntersectionObserver to only observe entity actions when the element is in the viewport, improving performance. Refactors element creation to use constructors, updates event handling, and ensures cleanup in disconnectedCallback. * only observe once * Update entity-actions-bundle.element.ts * Update dropdown.element.ts * Update entity-actions-bundle.element.ts * split dropdown component * pass compact prop * fix label * Update entity-actions-dropdown.element.ts * Update entity-actions-dropdown.element.ts --------- Co-authored-by: Niels Lyngsø --- .../components/dropdown/dropdown.element.ts | 53 ++++++----- .../entity-actions-bundle.element.ts | 67 ++++++------- .../entity-actions-dropdown.element.ts | 93 +++++++++++++++++++ .../entity-action/global-components/index.ts | 1 + .../src/packages/core/event/closed.event.ts | 8 ++ .../src/packages/core/event/index.ts | 2 + .../src/packages/core/event/opened.event.ts | 8 ++ .../workspace-entity-action-menu.element.ts | 65 +++---------- .../document-table-column-name.element.ts | 4 +- .../package-lock.json | 9 +- .../Umbraco.Tests.AcceptanceTest/package.json | 2 +- .../DocumentTypeDesignTab.spec.ts | 1 + 12 files changed, 202 insertions(+), 111 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/entity-actions-dropdown/entity-actions-dropdown.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/event/closed.event.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/event/opened.event.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/dropdown/dropdown.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/dropdown/dropdown.element.ts index cc8a844b48..fb5004f572 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/dropdown/dropdown.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/dropdown/dropdown.element.ts @@ -5,15 +5,28 @@ import type { UUIPopoverContainerElement, } from '@umbraco-cms/backoffice/external/uui'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; import { css, html, customElement, property, query, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbClosedEvent, UmbOpenedEvent } from '@umbraco-cms/backoffice/event'; // TODO: maybe move this to UI Library. @customElement('umb-dropdown') export class UmbDropdownElement extends UmbLitElement { + #open = false; + @property({ type: Boolean, reflect: true }) - open = false; + public get open() { + return this.#open; + } + public set open(value) { + this.#open = value; + + if (value === true && this.popoverContainerElement) { + this.openDropdown(); + } else { + this.closeDropdown(); + } + } @property() label?: string; @@ -36,29 +49,12 @@ export class UmbDropdownElement extends UmbLitElement { @query('#dropdown-popover') popoverContainerElement?: UUIPopoverContainerElement; - protected override updated(_changedProperties: PropertyValueMap | Map): void { - super.updated(_changedProperties); - if (_changedProperties.has('open') && this.popoverContainerElement) { - if (this.open) { - this.openDropdown(); - } else { - this.closeDropdown(); - } - } - } - - #onToggle(event: ToggleEvent) { - // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet. - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - this.open = event.newState === 'open'; - } - openDropdown() { // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet. // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this.popoverContainerElement?.showPopover(); + this.#open = true; } closeDropdown() { @@ -66,6 +62,20 @@ export class UmbDropdownElement extends UmbLitElement { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this.popoverContainerElement?.hidePopover(); + this.#open = false; + } + + #onToggle(event: ToggleEvent) { + // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet. + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this.open = event.newState === 'open'; + + if (this.open) { + this.dispatchEvent(new UmbOpenedEvent()); + } else { + this.dispatchEvent(new UmbClosedEvent()); + } } override render() { @@ -81,7 +91,7 @@ export class UmbDropdownElement extends UmbLitElement { ${when( !this.hideExpand, - () => html``, + () => html``, )} @@ -97,6 +107,7 @@ export class UmbDropdownElement extends UmbLitElement { css` #dropdown-button { min-width: max-content; + height: 100%; } :host(:not([hide-expand]):not([compact])) #dropdown-button { --uui-button-padding-right-factor: 2; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/entity-actions-bundle/entity-actions-bundle.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/entity-actions-bundle/entity-actions-bundle.element.ts index a8d70cbac6..682ac057f9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/entity-actions-bundle/entity-actions-bundle.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/entity-actions-bundle/entity-actions-bundle.element.ts @@ -1,17 +1,7 @@ import { UmbEntityContext } from '../../entity/entity.context.js'; -import type { UmbDropdownElement } from '../dropdown/index.js'; import type { UmbEntityAction, ManifestEntityActionDefaultKind } from '@umbraco-cms/backoffice/entity-action'; import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; -import { - html, - nothing, - customElement, - property, - state, - ifDefined, - css, - query, -} from '@umbraco-cms/backoffice/external/lit'; +import { html, nothing, customElement, property, state, ifDefined, css } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbExtensionsManifestInitializer, createExtensionApi } from '@umbraco-cms/backoffice/extension-api'; @@ -39,11 +29,32 @@ export class UmbEntityActionsBundleElement extends UmbLitElement { @state() private _firstActionHref?: string; - @query('#action-modal') - private _dropdownElement?: UmbDropdownElement; - // TODO: provide the entity context on a higher level, like the root element of this entity, tree-item/workspace/... [NL] #entityContext = new UmbEntityContext(this); + #inViewport = false; + #observingEntityActions = false; + + constructor() { + super(); + + // Only observe entity actions when the element is in the viewport + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + this.#inViewport = true; + this.#observeEntityActions(); + } + }); + }, + { + root: null, // Use the viewport as the root + threshold: 0.1, // Trigger when at least 10% of the element is visible + }, + ); + + observer.observe(this); + } protected override updated(_changedProperties: PropertyValueMap | Map): void { if (_changedProperties.has('entityType') && _changedProperties.has('unique')) { @@ -54,6 +65,11 @@ export class UmbEntityActionsBundleElement extends UmbLitElement { } #observeEntityActions() { + if (!this.entityType) return; + if (this.unique === undefined) return; + if (!this.#inViewport) return; // Only observe if the element is in the viewport + if (this.#observingEntityActions) return; + new UmbExtensionsManifestInitializer( this, umbExtensionsRegistry, @@ -67,6 +83,8 @@ export class UmbEntityActionsBundleElement extends UmbLitElement { }, 'umbEntityActionsObserver', ); + + this.#observingEntityActions = true; } async #createFirstActionApi() { @@ -90,14 +108,6 @@ export class UmbEntityActionsBundleElement extends UmbLitElement { await this._firstActionApi?.execute().catch(() => {}); } - #onActionExecuted() { - this._dropdownElement?.closeDropdown(); - } - - #onDropdownClick(event: Event) { - event.stopPropagation(); - } - override render() { if (this._numberOfActions === 0) return nothing; return html`${this.#renderMore()} ${this.#renderFirstAction()} `; @@ -107,16 +117,9 @@ export class UmbEntityActionsBundleElement extends UmbLitElement { if (this._numberOfActions === 1) return nothing; return html` - - - - - - + + + `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/entity-actions-dropdown/entity-actions-dropdown.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/entity-actions-dropdown/entity-actions-dropdown.element.ts new file mode 100644 index 0000000000..df3074634e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/entity-actions-dropdown/entity-actions-dropdown.element.ts @@ -0,0 +1,93 @@ +import type { UmbDropdownElement } from '../../../components/dropdown/index.js'; +import { UmbEntityActionListElement } from '../../entity-action-list.element.js'; +import { html, customElement, property, css, query } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UUIScrollContainerElement } from '@umbraco-cms/backoffice/external/uui'; +import { UMB_ENTITY_CONTEXT, type UmbEntityModel } from '@umbraco-cms/backoffice/entity'; +import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; + +@customElement('umb-entity-actions-dropdown') +export class UmbEntityActionsDropdownElement extends UmbLitElement { + @property({ type: Boolean }) + compact = false; + + @property({ type: String }) + public label?: string; + + @query('#action-modal') + private _dropdownElement?: UmbDropdownElement; + + #scrollContainerElement?: UUIScrollContainerElement; + #entityActionListElement?: UmbEntityActionListElement; + #entityType?: UmbEntityModel['entityType']; + #unique?: UmbEntityModel['unique']; + + constructor() { + super(); + this.consumeContext(UMB_ENTITY_CONTEXT, (context) => { + if (!context) return; + + this.observe(observeMultiple([context.entityType, context.unique]), ([entityType, unique]) => { + this.#entityType = entityType; + this.#unique = unique; + + if (this.#entityActionListElement) { + this.#entityActionListElement.entityType = entityType; + this.#entityActionListElement.unique = unique; + } + }); + }); + } + + #onActionExecuted() { + this._dropdownElement?.closeDropdown(); + } + + #onDropdownClick(event: Event) { + event.stopPropagation(); + } + + #onDropdownOpened() { + if (this.#scrollContainerElement) { + return; // Already created + } + + // First create dropdown content when the dropdown is opened. + // Programmatically create the elements so they are cached if the dropdown is opened again + this.#scrollContainerElement = new UUIScrollContainerElement(); + this.#entityActionListElement = new UmbEntityActionListElement(); + this.#entityActionListElement.addEventListener('action-executed', this.#onActionExecuted); + this.#entityActionListElement.entityType = this.#entityType; + this.#entityActionListElement.unique = this.#unique; + this.#entityActionListElement.setAttribute('label', this.label ?? ''); + this.#scrollContainerElement.appendChild(this.#entityActionListElement); + this._dropdownElement?.appendChild(this.#scrollContainerElement); + } + + override render() { + return html` + + + `; + } + + static override styles = [ + css` + uui-scroll-container { + max-height: 700px; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-entity-actions-dropdown': UmbEntityActionsDropdownElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/index.ts index 841ca85af1..493801d33f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/index.ts @@ -1 +1,2 @@ +import './entity-actions-dropdown/entity-actions-dropdown.element.js'; import './entity-actions-table-column-view/entity-actions-table-column-view.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/event/closed.event.ts b/src/Umbraco.Web.UI.Client/src/packages/core/event/closed.event.ts new file mode 100644 index 0000000000..4b6fe30e33 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/event/closed.event.ts @@ -0,0 +1,8 @@ +export class UmbClosedEvent extends Event { + public static readonly TYPE = 'closed'; + + public constructor() { + // mimics the native toggle event + super(UmbClosedEvent.TYPE, { bubbles: false, composed: false, cancelable: false }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/event/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/event/index.ts index 7e986dc488..6d9a131fba 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/event/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/event/index.ts @@ -1,8 +1,10 @@ export * from './action-executed.event.js'; export * from './change.event.js'; +export * from './closed.event.js'; export * from './delete.event.js'; export * from './deselected.event.js'; export * from './input.event.js'; +export * from './opened.event.js'; export * from './progress.event.js'; export * from './selected.event.js'; export * from './selection-change.event.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/event/opened.event.ts b/src/Umbraco.Web.UI.Client/src/packages/core/event/opened.event.ts new file mode 100644 index 0000000000..88e6935bc5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/event/opened.event.ts @@ -0,0 +1,8 @@ +export class UmbOpenedEvent extends Event { + public static readonly TYPE = 'opened'; + + public constructor() { + // mimics the native toggle event + super(UmbOpenedEvent.TYPE, { bubbles: false, composed: false, cancelable: false }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-entity-action-menu/workspace-entity-action-menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-entity-action-menu/workspace-entity-action-menu.element.ts index eb38a8fa53..06252f0472 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-entity-action-menu/workspace-entity-action-menu.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-entity-action-menu/workspace-entity-action-menu.element.ts @@ -1,10 +1,8 @@ import { UMB_ENTITY_WORKSPACE_CONTEXT } from '../../contexts/index.js'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { css, html, customElement, state, nothing, query } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbActionExecutedEvent } from '@umbraco-cms/backoffice/event'; +import { html, customElement, state, nothing, css } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import type { UUIPopoverContainerElement } from '@umbraco-cms/backoffice/external/uui'; import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; @customElement('umb-workspace-entity-action-menu') export class UmbWorkspaceEntityActionMenuElement extends UmbLitElement { private _workspaceContext?: typeof UMB_ENTITY_WORKSPACE_CONTEXT.TYPE; @@ -15,12 +13,6 @@ export class UmbWorkspaceEntityActionMenuElement extends UmbLitElement { @state() private _entityType?: string; - @state() - private _popoverOpen = false; - - @query('#workspace-entity-action-menu-popover') - private _popover?: UUIPopoverContainerElement; - constructor() { super(); @@ -35,48 +27,16 @@ export class UmbWorkspaceEntityActionMenuElement extends UmbLitElement { }); } - #onActionExecuted(event: UmbActionExecutedEvent) { - event.stopPropagation(); - - // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet. - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - this._popover?.hidePopover(); - } - - #onPopoverToggle(event: ToggleEvent) { - // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet. - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - this._popoverOpen = event.newState === 'open'; - } - override render() { - return this._unique !== undefined && this._entityType - ? html` - - - - - - - - - - - - ` - : nothing; + if (!this._entityType) return nothing; + if (this._unique === undefined) return nothing; + + return html` + + `; } static override styles = [ @@ -86,7 +46,8 @@ export class UmbWorkspaceEntityActionMenuElement extends UmbLitElement { height: 100%; margin-left: calc(var(--uui-size-layout-1) * -1); } - :host > uui-button { + + umb-entity-actions-dropdown { height: 100%; } `, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-name.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-name.element.ts index 660c27db90..f8cc6fc297 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-name.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-name.element.ts @@ -34,7 +34,9 @@ export class UmbDocumentTableColumnNameElement extends UmbLitElement implements override render() { if (!this.value) return nothing; - return html` `; + if (!this.value.editPath) return nothing; + if (!this._name) return nothing; + return html``; } static override styles = [ diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index f2cabf43c3..9c0cd687ff 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -8,7 +8,7 @@ "hasInstallScript": true, "dependencies": { "@umbraco/json-models-builders": "^2.0.36", - "@umbraco/playwright-testhelpers": "^16.0.25", + "@umbraco/playwright-testhelpers": "^16.0.27", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" @@ -66,9 +66,10 @@ } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "16.0.25", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-16.0.25.tgz", - "integrity": "sha512-IvRkkrTIxlXbg2dw0RhAUgkb7KSBJCyktK6zJynOORgZ5RXRae19hqKk7yEu2EwJpTstl6m9AzoVf1x4b94x5w==", + "version": "16.0.27", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-16.0.27.tgz", + "integrity": "sha512-KxjIpfFsiK5b1Au8QrlWceK88eo53VxogLs0LMrxsRS3rt4rdmD1YRP6U+yIucdPKnhVgfIsh40J/taGAZyPFQ==", + "license": "MIT", "dependencies": { "@umbraco/json-models-builders": "2.0.36", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index 78c09e9719..b2e9ac7ada 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@umbraco/json-models-builders": "^2.0.36", - "@umbraco/playwright-testhelpers": "^16.0.25", + "@umbraco/playwright-testhelpers": "^16.0.27", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts index 72a5936b0c..c4c317d3e2 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts @@ -213,6 +213,7 @@ test('can create a document type with a composition', {tag: '@smoke'}, async ({u // Act await umbracoUi.documentType.goToDocumentType(documentTypeName); + await umbracoUi.waitForTimeout(1000); await umbracoUi.documentType.clickCompositionsButton(); await umbracoUi.documentType.clickModalMenuItemWithName(compositionDocumentTypeName); await umbracoUi.documentType.clickSubmitButton(); From dcd8b4252270012ebca5952c7fcb9d0f84eb6e26 Mon Sep 17 00:00:00 2001 From: Laura Neto <12862535+lauraneto@users.noreply.github.com> Date: Tue, 1 Jul 2025 11:17:59 +0200 Subject: [PATCH 69/82] Move cache instructions pruning to background job (#19598) * Remove pruning logic from `CacheInstructionService.ProcessInstructions()` * Add and register `CacheInstructionsPruningJob` background job * Add unit tests * Remove breaking change in ICacheInstructionService * Adjust some obsoletion messages to mention v17 * Added missing scope * Update tests * Fix obsoletion messages version * Update ProcessInstructions methods summary --- .../Services/ICacheInstructionService.cs | 27 +++++- .../Services/ProcessInstructionsResult.cs | 2 + .../Jobs/CacheInstructionsPruningJob.cs | 61 +++++++++++++ .../Services/CacheInstructionService.cs | 52 +++--------- .../Sync/BatchedDatabaseServerMessenger.cs | 46 ++++++++-- .../Sync/DatabaseServerMessenger.cs | 43 +++++++--- .../UmbracoBuilderExtensions.cs | 1 + .../Services/CacheInstructionServiceTests.cs | 60 +++---------- .../Jobs/CacheInstructionsPruningJobTests.cs | 85 +++++++++++++++++++ 9 files changed, 269 insertions(+), 108 deletions(-) create mode 100644 src/Umbraco.Infrastructure/BackgroundJobs/Jobs/CacheInstructionsPruningJob.cs create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/CacheInstructionsPruningJobTests.cs diff --git a/src/Umbraco.Core/Services/ICacheInstructionService.cs b/src/Umbraco.Core/Services/ICacheInstructionService.cs index 25b52c09e3..94ccea42b4 100644 --- a/src/Umbraco.Core/Services/ICacheInstructionService.cs +++ b/src/Umbraco.Core/Services/ICacheInstructionService.cs @@ -34,14 +34,37 @@ public interface ICacheInstructionService void DeliverInstructionsInBatches(IEnumerable instructions, string localIdentity); /// - /// Processes and then prunes pending database cache instructions. + /// Processes pending database cache instructions. + /// + /// Cache refreshers. + /// Cancellation token. + /// Local identity of the executing AppDomain. + /// Id of the latest processed instruction. + /// The processing result. + ProcessInstructionsResult ProcessInstructions( + CacheRefresherCollection cacheRefreshers, + CancellationToken cancellationToken, + string localIdentity, + int lastId) => + ProcessInstructions( + cacheRefreshers, + ServerRole.Unknown, + cancellationToken, + localIdentity, + lastPruned: DateTime.UtcNow, + lastId); + + /// + /// Processes pending database cache instructions. /// /// Cache refreshers. /// Server role. /// Cancellation token. /// Local identity of the executing AppDomain. /// Date of last prune operation. - /// Id of the latest processed instruction + /// Id of the latest processed instruction. + /// The processing result. + [Obsolete("Use the non-obsolete overload. Scheduled for removal in V17.")] ProcessInstructionsResult ProcessInstructions( CacheRefresherCollection cacheRefreshers, ServerRole serverRole, diff --git a/src/Umbraco.Core/Services/ProcessInstructionsResult.cs b/src/Umbraco.Core/Services/ProcessInstructionsResult.cs index 39751dad61..e109d1ed10 100644 --- a/src/Umbraco.Core/Services/ProcessInstructionsResult.cs +++ b/src/Umbraco.Core/Services/ProcessInstructionsResult.cs @@ -14,11 +14,13 @@ public class ProcessInstructionsResult public int LastId { get; private set; } + [Obsolete("Instruction pruning has been moved to a separate background job. Scheduled for removal in V18.")] public bool InstructionsWerePruned { get; private set; } public static ProcessInstructionsResult AsCompleted(int numberOfInstructionsProcessed, int lastId) => new() { NumberOfInstructionsProcessed = numberOfInstructionsProcessed, LastId = lastId }; + [Obsolete("Instruction pruning has been moved to a separate background job. Scheduled for removal in V18.")] public static ProcessInstructionsResult AsCompletedAndPruned(int numberOfInstructionsProcessed, int lastId) => new() { diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/CacheInstructionsPruningJob.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/CacheInstructionsPruningJob.cs new file mode 100644 index 0000000000..fca52f1581 --- /dev/null +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/CacheInstructionsPruningJob.cs @@ -0,0 +1,61 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Scoping; + +namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs; + +/// +/// A background job that prunes cache instructions from the database. +/// +public class CacheInstructionsPruningJob : IRecurringBackgroundJob +{ + private readonly IOptions _globalSettings; + private readonly ICacheInstructionRepository _cacheInstructionRepository; + private readonly ICoreScopeProvider _scopeProvider; + private readonly TimeProvider _timeProvider; + + /// + /// Initializes a new instance of the class. + /// + /// Provides scopes for database operations. + /// The global settings configuration. + /// The repository for cache instructions. + /// The time provider. + public CacheInstructionsPruningJob( + IOptions globalSettings, + ICacheInstructionRepository cacheInstructionRepository, + ICoreScopeProvider scopeProvider, + TimeProvider timeProvider) + { + _globalSettings = globalSettings; + _cacheInstructionRepository = cacheInstructionRepository; + _scopeProvider = scopeProvider; + _timeProvider = timeProvider; + Period = globalSettings.Value.DatabaseServerMessenger.TimeBetweenPruneOperations; + } + + /// + public event EventHandler PeriodChanged + { + add { } + remove { } + } + + /// + public TimeSpan Period { get; } + + /// + public Task RunJobAsync() + { + DateTimeOffset pruneDate = _timeProvider.GetUtcNow() - _globalSettings.Value.DatabaseServerMessenger.TimeToRetainInstructions; + using (ICoreScope scope = _scopeProvider.CreateCoreScope()) + { + _cacheInstructionRepository.DeleteInstructionsOlderThan(pruneDate.DateTime); + scope.Complete(); + } + + return Task.CompletedTask; + } +} diff --git a/src/Umbraco.Infrastructure/Services/CacheInstructionService.cs b/src/Umbraco.Infrastructure/Services/CacheInstructionService.cs index f8e9f1ee88..dc697c9a69 100644 --- a/src/Umbraco.Infrastructure/Services/CacheInstructionService.cs +++ b/src/Umbraco.Infrastructure/Services/CacheInstructionService.cs @@ -122,43 +122,30 @@ namespace Umbraco.Cms /// public ProcessInstructionsResult ProcessInstructions( CacheRefresherCollection cacheRefreshers, - ServerRole serverRole, CancellationToken cancellationToken, string localIdentity, - DateTime lastPruned, int lastId) { using (!_profilingLogger.IsEnabled(Core.Logging.LogLevel.Debug) ? null : _profilingLogger.DebugDuration("Syncing from database...")) using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { var numberOfInstructionsProcessed = ProcessDatabaseInstructions(cacheRefreshers, cancellationToken, localIdentity, ref lastId); - - // Check for pruning throttling. - if (cancellationToken.IsCancellationRequested || DateTime.UtcNow - lastPruned <= - _globalSettings.DatabaseServerMessenger.TimeBetweenPruneOperations) - { - scope.Complete(); - return ProcessInstructionsResult.AsCompleted(numberOfInstructionsProcessed, lastId); - } - - var instructionsWerePruned = false; - switch (serverRole) - { - case ServerRole.Single: - case ServerRole.SchedulingPublisher: - PruneOldInstructions(); - instructionsWerePruned = true; - break; - } - scope.Complete(); - - return instructionsWerePruned - ? ProcessInstructionsResult.AsCompletedAndPruned(numberOfInstructionsProcessed, lastId) - : ProcessInstructionsResult.AsCompleted(numberOfInstructionsProcessed, lastId); + return ProcessInstructionsResult.AsCompleted(numberOfInstructionsProcessed, lastId); } } + /// + [Obsolete("Use the non-obsolete overload. Scheduled for removal in V17.")] + public ProcessInstructionsResult ProcessInstructions( + CacheRefresherCollection cacheRefreshers, + ServerRole serverRole, + CancellationToken cancellationToken, + string localIdentity, + DateTime lastPruned, + int lastId) => + ProcessInstructions(cacheRefreshers, cancellationToken, localIdentity, lastId); + private CacheInstruction CreateCacheInstruction(IEnumerable instructions, string localIdentity) => new( 0, @@ -486,21 +473,6 @@ namespace Umbraco.Cms return jsonRefresher; } - - /// - /// Remove old instructions from the database - /// - /// - /// Always leave the last (most recent) record in the db table, this is so that not all instructions are removed which - /// would cause - /// the site to cold boot if there's been no instruction activity for more than TimeToRetainInstructions. - /// See: http://issues.umbraco.org/issue/U4-7643#comment=67-25085 - /// - private void PruneOldInstructions() - { - DateTime pruneDate = DateTime.UtcNow - _globalSettings.DatabaseServerMessenger.TimeToRetainInstructions; - _cacheInstructionRepository.DeleteInstructionsOlderThan(pruneDate); - } } } } diff --git a/src/Umbraco.Infrastructure/Sync/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Infrastructure/Sync/BatchedDatabaseServerMessenger.cs index 89fb1b6ad1..a6380df4b4 100644 --- a/src/Umbraco.Infrastructure/Sync/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Infrastructure/Sync/BatchedDatabaseServerMessenger.cs @@ -16,12 +16,41 @@ namespace Umbraco.Cms.Infrastructure.Sync; /// public class BatchedDatabaseServerMessenger : DatabaseServerMessenger { - private readonly IRequestAccessor _requestAccessor; private readonly IRequestCache _requestCache; /// /// Initializes a new instance of the class. /// + public BatchedDatabaseServerMessenger( + IMainDom mainDom, + CacheRefresherCollection cacheRefreshers, + ILogger logger, + ISyncBootStateAccessor syncBootStateAccessor, + IHostingEnvironment hostingEnvironment, + ICacheInstructionService cacheInstructionService, + IJsonSerializer jsonSerializer, + IRequestCache requestCache, + LastSyncedFileManager lastSyncedFileManager, + IOptionsMonitor globalSettings) + : base( + mainDom, + cacheRefreshers, + logger, + true, + syncBootStateAccessor, + hostingEnvironment, + cacheInstructionService, + jsonSerializer, + lastSyncedFileManager, + globalSettings) + { + _requestCache = requestCache; + } + + /// + /// Initializes a new instance of the class. + /// + [Obsolete("Use the non-obsolete constructor instead. Scheduled for removal in V18.")] public BatchedDatabaseServerMessenger( IMainDom mainDom, CacheRefresherCollection cacheRefreshers, @@ -35,11 +64,18 @@ public class BatchedDatabaseServerMessenger : DatabaseServerMessenger IRequestAccessor requestAccessor, LastSyncedFileManager lastSyncedFileManager, IOptionsMonitor globalSettings) - : base(mainDom, cacheRefreshers, serverRoleAccessor, logger, true, syncBootStateAccessor, hostingEnvironment, - cacheInstructionService, jsonSerializer, lastSyncedFileManager, globalSettings) + : this( + mainDom, + cacheRefreshers, + logger, + syncBootStateAccessor, + hostingEnvironment, + cacheInstructionService, + jsonSerializer, + requestCache, + lastSyncedFileManager, + globalSettings) { - _requestCache = requestCache; - _requestAccessor = requestAccessor; } /// diff --git a/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs index ee85d6c3a8..096318d349 100644 --- a/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs @@ -31,11 +31,9 @@ public abstract class DatabaseServerMessenger : ServerMessengerBase, IDisposable */ private readonly IMainDom _mainDom; - private readonly IServerRoleAccessor _serverRoleAccessor; private readonly ISyncBootStateAccessor _syncBootStateAccessor; private readonly ManualResetEvent _syncIdle; private bool _disposedValue; - private DateTime _lastPruned; private DateTime _lastSync; private bool _syncing; @@ -45,7 +43,6 @@ public abstract class DatabaseServerMessenger : ServerMessengerBase, IDisposable protected DatabaseServerMessenger( IMainDom mainDom, CacheRefresherCollection cacheRefreshers, - IServerRoleAccessor serverRoleAccessor, ILogger logger, bool distributedEnabled, ISyncBootStateAccessor syncBootStateAccessor, @@ -59,7 +56,6 @@ public abstract class DatabaseServerMessenger : ServerMessengerBase, IDisposable _cancellationToken = _cancellationTokenSource.Token; _mainDom = mainDom; _cacheRefreshers = cacheRefreshers; - _serverRoleAccessor = serverRoleAccessor; _hostingEnvironment = hostingEnvironment; Logger = logger; _syncBootStateAccessor = syncBootStateAccessor; @@ -67,7 +63,7 @@ public abstract class DatabaseServerMessenger : ServerMessengerBase, IDisposable JsonSerializer = jsonSerializer; _lastSyncedFileManager = lastSyncedFileManager; GlobalSettings = globalSettings.CurrentValue; - _lastPruned = _lastSync = DateTime.UtcNow; + _lastSync = DateTime.UtcNow; _syncIdle = new ManualResetEvent(true); globalSettings.OnChange(x => GlobalSettings = x); @@ -84,6 +80,36 @@ public abstract class DatabaseServerMessenger : ServerMessengerBase, IDisposable _initialized = new Lazy(InitializeWithMainDom); } + /// + /// Initializes a new instance of the class. + /// + [Obsolete("Use the non-obsolete constructor. Scheduled for removal in V18.")] + protected DatabaseServerMessenger( + IMainDom mainDom, + CacheRefresherCollection cacheRefreshers, + IServerRoleAccessor serverRoleAccessor, + ILogger logger, + bool distributedEnabled, + ISyncBootStateAccessor syncBootStateAccessor, + IHostingEnvironment hostingEnvironment, + ICacheInstructionService cacheInstructionService, + IJsonSerializer jsonSerializer, + LastSyncedFileManager lastSyncedFileManager, + IOptionsMonitor globalSettings) + : this( + mainDom, + cacheRefreshers, + logger, + distributedEnabled, + syncBootStateAccessor, + hostingEnvironment, + cacheInstructionService, + jsonSerializer, + lastSyncedFileManager, + globalSettings) + { + } + public GlobalSettings GlobalSettings { get; private set; } protected ILogger Logger { get; } @@ -146,17 +172,10 @@ public abstract class DatabaseServerMessenger : ServerMessengerBase, IDisposable { ProcessInstructionsResult result = CacheInstructionService.ProcessInstructions( _cacheRefreshers, - _serverRoleAccessor.CurrentServerRole, _cancellationToken, LocalIdentity, - _lastPruned, _lastSyncedFileManager.LastSyncedId); - if (result.InstructionsWerePruned) - { - _lastPruned = _lastSync; - } - if (result.LastId > 0) { _lastSyncedFileManager.SaveLastSyncedId(result.LastId); diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 936609db54..2fd5348f4a 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -189,6 +189,7 @@ public static partial class UmbracoBuilderExtensions builder.Services.AddRecurringBackgroundJob(); builder.Services.AddRecurringBackgroundJob(); builder.Services.AddRecurringBackgroundJob(); + builder.Services.AddRecurringBackgroundJob(); builder.Services.AddSingleton(RecurringBackgroundJobHostedService.CreateHostedServiceFactory); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/CacheInstructionServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/CacheInstructionServiceTests.cs index 7759874b8a..7633297bfd 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/CacheInstructionServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/CacheInstructionServiceTests.cs @@ -20,8 +20,10 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest private const string LocalIdentity = "localIdentity"; private const string AlternateIdentity = "alternateIdentity"; - private CancellationToken CancellationToken => new(); + private CancellationToken CancellationToken => CancellationToken.None; + private CacheRefresherCollection CacheRefreshers => GetRequiredService(); + private IServerRoleAccessor ServerRoleAccessor => GetRequiredService(); [Test] @@ -150,33 +152,16 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest // Create three instruction records, each with two instructions. First two records are for a different identity. CreateAndDeliveryMultipleInstructions(sut); - var result = sut.ProcessInstructions(CacheRefreshers, ServerRoleAccessor.CurrentServerRole, CancellationToken, - LocalIdentity, DateTime.UtcNow.AddSeconds(-1), -1); + var result = sut.ProcessInstructions(CacheRefreshers, CancellationToken, LocalIdentity, -1); Assert.Multiple(() => { Assert.AreEqual(3, result.LastId); // 3 records found. Assert.AreEqual(2, result.NumberOfInstructionsProcessed); // 2 records processed (as one is for the same identity). - Assert.IsFalse(result.InstructionsWerePruned); }); } - [Test] - public void Can_Process_And_Purge_Instructions() - { - // Purging of instructions only occurs on single or master servers, so we need to ensure this is set before running the test. - EnsureServerRegistered(); - var sut = (CacheInstructionService)GetRequiredService(); - - CreateAndDeliveryMultipleInstructions(sut); - - var result = sut.ProcessInstructions(CacheRefreshers, ServerRoleAccessor.CurrentServerRole, CancellationToken, - LocalIdentity, DateTime.UtcNow.AddHours(-1), -1); - - Assert.IsTrue(result.InstructionsWerePruned); - } - [Test] public void Processes_No_Instructions_When_CancellationToken_is_Cancelled() { @@ -187,14 +172,12 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest var cancellationTokenSource = new CancellationTokenSource(); cancellationTokenSource.Cancel(); - var result = sut.ProcessInstructions(CacheRefreshers, ServerRoleAccessor.CurrentServerRole, - cancellationTokenSource.Token, LocalIdentity, DateTime.UtcNow.AddSeconds(-1), -1); + var result = sut.ProcessInstructions(CacheRefreshers, cancellationTokenSource.Token, LocalIdentity, -1); Assert.Multiple(() => { Assert.AreEqual(0, result.LastId); Assert.AreEqual(0, result.NumberOfInstructionsProcessed); - Assert.IsFalse(result.InstructionsWerePruned); }); } @@ -209,14 +192,12 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest var lastId = -1; // Run once - var result = sut.ProcessInstructions(CacheRefreshers, ServerRoleAccessor.CurrentServerRole, CancellationToken, - LocalIdentity, DateTime.UtcNow.AddSeconds(-1), lastId); + var result = sut.ProcessInstructions(CacheRefreshers, CancellationToken, LocalIdentity, lastId); Assert.Multiple(() => { Assert.AreEqual(3, result.LastId); // 3 records found. Assert.AreEqual(2, result.NumberOfInstructionsProcessed); // 2 records processed (as one is for the same identity). - Assert.IsFalse(result.InstructionsWerePruned); }); // DatabaseServerMessenger stores the LastID after ProcessInstructions has been run. @@ -224,13 +205,7 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest // The instructions has now been processed and shouldn't be processed on the next call... // Run again. - var secondResult = sut.ProcessInstructions( - CacheRefreshers, - ServerRoleAccessor.CurrentServerRole, - CancellationToken, - LocalIdentity, - DateTime.UtcNow.AddSeconds(-1), - lastId); + var secondResult = sut.ProcessInstructions(CacheRefreshers, CancellationToken, LocalIdentity, lastId); Assert.Multiple(() => { Assert.AreEqual( @@ -238,7 +213,6 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest secondResult .LastId); // No instructions was processed so LastId is 0, this is consistent with behavior from V8 Assert.AreEqual(0, secondResult.NumberOfInstructionsProcessed); // Nothing was processed. - Assert.IsFalse(secondResult.InstructionsWerePruned); }); } @@ -249,8 +223,7 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest CreateAndDeliveryMultipleInstructions(sut); var lastId = -1; - var result = sut.ProcessInstructions(CacheRefreshers, ServerRoleAccessor.CurrentServerRole, CancellationToken, - LocalIdentity, DateTime.UtcNow.AddSeconds(-1), lastId); + var result = sut.ProcessInstructions(CacheRefreshers, CancellationToken, LocalIdentity, lastId); Assert.AreEqual(3, result.LastId); // Make sure LastId is 3, the rest is tested in other test. lastId = result.LastId; @@ -259,14 +232,12 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest var instructions = CreateInstructions(); sut.DeliverInstructions(instructions, AlternateIdentity); - var secondResult = sut.ProcessInstructions(CacheRefreshers, ServerRoleAccessor.CurrentServerRole, - CancellationToken, LocalIdentity, DateTime.UtcNow.AddSeconds(-1), lastId); + var secondResult = sut.ProcessInstructions(CacheRefreshers, CancellationToken, LocalIdentity, lastId); Assert.Multiple(() => { Assert.AreEqual(4, secondResult.LastId); Assert.AreEqual(1, secondResult.NumberOfInstructionsProcessed); - Assert.IsFalse(secondResult.InstructionsWerePruned); }); } @@ -277,8 +248,7 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest CreateAndDeliveryMultipleInstructions(sut); var lastId = -1; - var result = sut.ProcessInstructions(CacheRefreshers, ServerRoleAccessor.CurrentServerRole, CancellationToken, - LocalIdentity, DateTime.UtcNow.AddSeconds(-1), lastId); + var result = sut.ProcessInstructions(CacheRefreshers, CancellationToken, LocalIdentity, lastId); Assert.AreEqual(3, result.LastId); // Make sure LastId is 3, the rest is tested in other test. lastId = result.LastId; @@ -287,14 +257,12 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest var instructions = CreateInstructions(); sut.DeliverInstructions(instructions, LocalIdentity); - var secondResult = sut.ProcessInstructions(CacheRefreshers, ServerRoleAccessor.CurrentServerRole, - CancellationToken, LocalIdentity, DateTime.UtcNow.AddSeconds(-1), lastId); + var secondResult = sut.ProcessInstructions(CacheRefreshers, CancellationToken, LocalIdentity, lastId); Assert.Multiple(() => { Assert.AreEqual(4, secondResult.LastId); Assert.AreEqual(0, secondResult.NumberOfInstructionsProcessed); - Assert.IsFalse(secondResult.InstructionsWerePruned); }); } @@ -306,10 +274,4 @@ internal sealed class CacheInstructionServiceTests : UmbracoIntegrationTest sut.DeliverInstructions(instructions, i == 2 ? LocalIdentity : AlternateIdentity); } } - - private void EnsureServerRegistered() - { - var serverRegistrationService = GetRequiredService(); - serverRegistrationService.TouchServer("http://localhost", TimeSpan.FromMinutes(10)); - } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/CacheInstructionsPruningJobTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/CacheInstructionsPruningJobTests.cs new file mode 100644 index 0000000000..5ac59498fb --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/CacheInstructionsPruningJobTests.cs @@ -0,0 +1,85 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Data; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.BackgroundJobs.Jobs; + +public class CacheInstructionsPruningJobTests +{ + private readonly Mock> _globalSettingsMock = new(MockBehavior.Strict); + private readonly Mock _cacheInstructionRepositoryMock = new(MockBehavior.Strict); + private readonly Mock _scopeProviderMock = new(MockBehavior.Strict); + private readonly Mock _timeProviderMock = new(MockBehavior.Strict); + + [Test] + public void Run_Period_Is_Retrieved_From_GlobalSettings() + { + var timeBetweenPruneOperations = TimeSpan.FromMinutes(2); + var job = CreateCacheInstructionsPruningJob(timeBetweenPruneOperations); + Assert.AreEqual(timeBetweenPruneOperations, job.Period, "The run period should be the same as 'TimeBetweenPruneOperations'."); + } + + [Test] + public async Task RunJobAsync_Calls_DeleteInstructionsOlderThan_With_Expected_Date() + { + SetupScopeProviderMock(); + + var timeToRetainInstructions = TimeSpan.FromMinutes(30); + var now = DateTime.UtcNow; + var expectedPruneDate = now - timeToRetainInstructions; + + _timeProviderMock.Setup(tp => tp.GetUtcNow()).Returns(now); + _cacheInstructionRepositoryMock.Setup(repo => repo + .DeleteInstructionsOlderThan(expectedPruneDate)); + + var job = CreateCacheInstructionsPruningJob(timeToRetainInstructions: timeToRetainInstructions); + + await job.RunJobAsync(); + + _cacheInstructionRepositoryMock.Verify(repo => repo.DeleteInstructionsOlderThan(expectedPruneDate), Times.Once); + } + + private CacheInstructionsPruningJob CreateCacheInstructionsPruningJob( + TimeSpan? timeBetweenPruneOperations = null, + TimeSpan? timeToRetainInstructions = null) + { + timeBetweenPruneOperations ??= TimeSpan.FromMinutes(5); + timeToRetainInstructions ??= TimeSpan.FromMinutes(20); + + var globalSettings = new GlobalSettings + { + DatabaseServerMessenger = new DatabaseServerMessengerSettings + { + TimeBetweenPruneOperations = timeBetweenPruneOperations.Value, + TimeToRetainInstructions = timeToRetainInstructions.Value, + }, + }; + + _globalSettingsMock + .Setup(g => g.Value) + .Returns(globalSettings); + + return new CacheInstructionsPruningJob(_globalSettingsMock.Object, _cacheInstructionRepositoryMock.Object, _scopeProviderMock.Object, _timeProviderMock.Object); + } + + private void SetupScopeProviderMock() => + _scopeProviderMock + .Setup(x => x.CreateCoreScope( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(Mock.Of()); +} From be3a1759de05e719ee1f5a034762ee6aa41b8ac0 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 2 Jul 2025 09:47:53 +0200 Subject: [PATCH 70/82] GitHub: Only deploy to Azure on direct pull requests (#19647) * build(github): check that the "close" job only runs when the appropriate label is applied it follows that the "build" job would only have built an environment when the label was applied * build(github): check that the action is run directly on the repository and not from a fork this alleviates the problem that the deploymentToken for Azure only exists within the repository --- .github/workflows/azure-backoffice.yml | 4 ++-- .github/workflows/azure-storybook.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/azure-backoffice.yml b/.github/workflows/azure-backoffice.yml index b22b1d36ee..4c40540065 100644 --- a/.github/workflows/azure-backoffice.yml +++ b/.github/workflows/azure-backoffice.yml @@ -20,7 +20,7 @@ on: jobs: build_and_deploy_job: - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed' && (contains(github.event.pull_request.labels.*.name, 'preview/backoffice'))) + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed' && contains(github.event.pull_request.labels.*.name, 'preview/backoffice') && github.repository == github.event.pull_request.head.repo.full_name) runs-on: ubuntu-latest name: Build and Deploy Job steps: @@ -44,7 +44,7 @@ jobs: ###### End of Repository/Build Configurations ###### close_pull_request_job: - if: github.event_name == 'pull_request' && github.event.action == 'closed' + if: github.event_name == 'pull_request' && github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'preview/backoffice') && github.repository == github.event.pull_request.head.repo.full_name runs-on: ubuntu-latest name: Close Pull Request Job steps: diff --git a/.github/workflows/azure-storybook.yml b/.github/workflows/azure-storybook.yml index 0da6fddfbe..586cd10d22 100644 --- a/.github/workflows/azure-storybook.yml +++ b/.github/workflows/azure-storybook.yml @@ -23,7 +23,7 @@ env: jobs: build_and_deploy_job: - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed' && (contains(github.event.pull_request.labels.*.name, 'preview/storybook'))) + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed' && contains(github.event.pull_request.labels.*.name, 'preview/storybook') && github.repository == github.event.pull_request.head.repo.full_name) runs-on: ubuntu-latest name: Build and Deploy Job steps: @@ -45,7 +45,7 @@ jobs: ###### End of Repository/Build Configurations ###### close_pull_request_job: - if: github.event_name == 'pull_request' && github.event.action == 'closed' + if: github.event_name == 'pull_request' && github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'preview/storybook') && github.repository == github.event.pull_request.head.repo.full_name runs-on: ubuntu-latest name: Close Pull Request Job steps: From d10ba420f4e99535b9f21b272d31315c8451ff33 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 2 Jul 2025 10:16:03 +0200 Subject: [PATCH 71/82] V16.1: 400 errors are being wrongfully ignored (#19648) fix: allows 400 errors to have a default notification unless silenced manually by `disableNotifications` --- .../resources/try-execute/try-execute.controller.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/try-execute.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/try-execute.controller.ts index 0ccf210fdc..ce68a29be2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/try-execute.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/try-execute.controller.ts @@ -3,6 +3,14 @@ import type { UmbApiResponse, UmbTryExecuteOptions } from '../types.js'; import { UmbCancelError } from '../umb-error.js'; import type { UmbApiError } from '../umb-error.js'; +/** + * Codes that are ignored for notifications. + * These are typically non-fatal errors that the UI can handle gracefully, + * such as 401 (Unauthorized), 403 (Forbidden), and 404 (Not Found). + * The UI should handle these cases without showing a notification. + */ +const IGNORED_ERROR_CODES = [401, 403, 404]; + export class UmbTryExecuteController extends UmbResourceController { #abortSignal?: AbortSignal; @@ -49,7 +57,7 @@ export class UmbTryExecuteController extends UmbResourceController { // Check if we can extract problem details from the error if (apiError.problemDetails) { - if ([400, 401, 403, 404].includes(apiError.problemDetails.status)) { + if (IGNORED_ERROR_CODES.includes(apiError.problemDetails.status)) { // Non-fatal errors that the UI can handle gracefully // so we avoid showing a notification return; From c9bef96c32d0212da59723a117e5aaed6bcc496c Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 2 Jul 2025 17:10:30 +0200 Subject: [PATCH 72/82] V16.1: Never reject a token response (#19651) * fix: never reject a token response If a token response is rejected, then the pipeline will also fail because it does not understand that error. Let the API interceptors do their job instead and simply return the old, now-invalid token which will prompt the API interceptors to store the request states and retry them afterwards. * chore: removes unused timeoutsignal * chore: captures the stale token before potentially clearing it --- .../src/packages/core/auth/auth-flow.ts | 15 ++++++--------- .../src/packages/core/auth/auth.context.ts | 7 +------ 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth-flow.ts b/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth-flow.ts index f22396f43f..0022bfac74 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth-flow.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth-flow.ts @@ -93,7 +93,6 @@ export class UmbAuthFlow { readonly #postLogoutRedirectUri: string; readonly #clientId: string; readonly #scope: string; - readonly #timeoutSignal; // tokens #tokenResponse?: TokenResponse; @@ -113,13 +112,11 @@ export class UmbAuthFlow { openIdConnectUrl: string, redirectUri: string, postLogoutRedirectUri: string, - timeoutSignal: Subject, clientId = 'umbraco-back-office', scope = 'offline_access', ) { this.#redirectUri = redirectUri; this.#postLogoutRedirectUri = postLogoutRedirectUri; - this.#timeoutSignal = timeoutSignal; this.#clientId = clientId; this.#scope = scope; @@ -305,7 +302,7 @@ export class UmbAuthFlow { /** * This method will check if the token needs to be refreshed and if so, it will refresh it and return the new access token. * If the token does not need to be refreshed, it will return the current access token. - * @returns The access token for the user. + * @returns {Promise} The access token for the user. */ async performWithFreshTokens(): Promise { // if the access token is valid, return it @@ -313,17 +310,17 @@ export class UmbAuthFlow { return Promise.resolve(this.#tokenResponse.accessToken); } + // if the access token is not valid, try to refresh it const success = await this.makeRefreshTokenRequest(); + const newToken = this.#tokenResponse?.accessToken ?? ''; if (!success) { + // if the refresh token request failed, we need to clear the token state this.clearTokenStorage(); - this.#timeoutSignal.next(); - return Promise.reject('Missing tokenResponse.'); } - return this.#tokenResponse - ? Promise.resolve(this.#tokenResponse.accessToken) - : Promise.reject('Missing tokenResponse.'); + // if the refresh token request was successful, return the new access token + return Promise.resolve(newToken); } /** diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth.context.ts index 05646acc2c..2b5ad2811b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth.context.ts @@ -73,12 +73,7 @@ export class UmbAuthContext extends UmbContextBase { this.#serverUrl = serverUrl; this.#backofficePath = backofficePath; - this.#authFlow = new UmbAuthFlow( - serverUrl, - this.getRedirectUrl(), - this.getPostLogoutRedirectUrl(), - this.#isTimeout, - ); + this.#authFlow = new UmbAuthFlow(serverUrl, this.getRedirectUrl(), this.getPostLogoutRedirectUrl()); // Observe the authorization signal and close the auth window this.observe( From aa90253d719d82fee7f870ddd426665eb22f8994 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 3 Jul 2025 11:27:37 +0200 Subject: [PATCH 73/82] build(deps): bump @umbraco-ui from 1.14.1 to 1.14.2 (#19656) --- src/Umbraco.Web.UI.Client/package-lock.json | 46 ++++++++++----------- src/Umbraco.Web.UI.Client/package.json | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 54b0ee3124..2e995a8922 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -27,7 +27,7 @@ "@tiptap/extension-underline": "2.11.7", "@tiptap/pm": "2.11.7", "@tiptap/starter-kit": "2.11.7", - "@umbraco-ui/uui": "1.14.1", + "@umbraco-ui/uui": "1.14.2", "@umbraco-ui/uui-css": "1.14.1", "dompurify": "^3.2.5", "element-internals-polyfill": "^3.0.2", @@ -4178,9 +4178,9 @@ "link": true }, "node_modules/@umbraco-ui/uui": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.14.1.tgz", - "integrity": "sha512-j00nw54rmabF7k+04NZpZKWalfGsVtO8puiOb6dfZF9xabPc5z6JOjpFnPCkRbKoytD9/qSyqF1Nb61C6jpWoA==", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.14.2.tgz", + "integrity": "sha512-aAiQN/pau/uaPoMYm6/FaojzljZvLYNu2Nm4Mm3f+2P1ZTtmTqiVYv56C4NPvSnF3CIRh5roiyY7cHZCNoYqTw==", "license": "MIT", "dependencies": { "@umbraco-ui/uui-action-bar": "1.14.1", @@ -4203,11 +4203,11 @@ "@umbraco-ui/uui-caret": "1.14.1", "@umbraco-ui/uui-checkbox": "1.14.1", "@umbraco-ui/uui-color-area": "1.14.1", - "@umbraco-ui/uui-color-picker": "1.14.1", + "@umbraco-ui/uui-color-picker": "1.14.2", "@umbraco-ui/uui-color-slider": "1.14.1", "@umbraco-ui/uui-color-swatch": "1.14.1", "@umbraco-ui/uui-color-swatches": "1.14.1", - "@umbraco-ui/uui-combobox": "1.14.1", + "@umbraco-ui/uui-combobox": "1.14.2", "@umbraco-ui/uui-combobox-list": "1.14.1", "@umbraco-ui/uui-css": "1.14.1", "@umbraco-ui/uui-dialog": "1.14.1", @@ -4233,7 +4233,7 @@ "@umbraco-ui/uui-modal": "1.14.1", "@umbraco-ui/uui-pagination": "1.14.1", "@umbraco-ui/uui-popover": "1.14.1", - "@umbraco-ui/uui-popover-container": "1.14.1", + "@umbraco-ui/uui-popover-container": "1.14.2", "@umbraco-ui/uui-progress-bar": "1.14.1", "@umbraco-ui/uui-radio": "1.14.1", "@umbraco-ui/uui-range-slider": "1.14.1", @@ -4258,7 +4258,7 @@ "@umbraco-ui/uui-symbol-more": "1.14.1", "@umbraco-ui/uui-symbol-sort": "1.14.1", "@umbraco-ui/uui-table": "1.14.1", - "@umbraco-ui/uui-tabs": "1.14.1", + "@umbraco-ui/uui-tabs": "1.14.2", "@umbraco-ui/uui-tag": "1.14.1", "@umbraco-ui/uui-textarea": "1.14.1", "@umbraco-ui/uui-toast-notification": "1.14.1", @@ -4465,13 +4465,13 @@ } }, "node_modules/@umbraco-ui/uui-color-picker": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.14.1.tgz", - "integrity": "sha512-OqTXrVNs0WiiPiaeXbPskd16zdiy+yn1D16x4pxG9eI2d6Y92JYmHjd9kyzNL4eBtwxEBZc7o89vlhJYco7vIg==", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.14.2.tgz", + "integrity": "sha512-0YWyrhALzg3WRT+YoMpgT0OjuT+5K3wo65fA97EHno+KKzYIbpytxqGnkRwn2A0qVwHsuytC2yzXMLO7t+6eTw==", "license": "MIT", "dependencies": { "@umbraco-ui/uui-base": "1.14.1", - "@umbraco-ui/uui-popover-container": "1.14.1", + "@umbraco-ui/uui-popover-container": "1.14.2", "colord": "^2.9.3" } }, @@ -4506,16 +4506,16 @@ } }, "node_modules/@umbraco-ui/uui-combobox": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.14.1.tgz", - "integrity": "sha512-BKRmcSdUWjs1cBkbWBeVWKtexJpRjAH+eTGwXwJDgi73ygT9PiPEMjHZobC+lUyoa2hWF/yHnubJS4SzPrTirg==", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.14.2.tgz", + "integrity": "sha512-y+tBBR+nZrt3A8n3iMQLkRJM+GN62/E34YfR+UoY9HdziT9wKTeNxXWSM2VmJa5GkYqvKaFf2R2PQiN4v3HRBA==", "license": "MIT", "dependencies": { "@umbraco-ui/uui-base": "1.14.1", "@umbraco-ui/uui-button": "1.14.1", "@umbraco-ui/uui-combobox-list": "1.14.1", "@umbraco-ui/uui-icon": "1.14.1", - "@umbraco-ui/uui-popover-container": "1.14.1", + "@umbraco-ui/uui-popover-container": "1.14.2", "@umbraco-ui/uui-scroll-container": "1.14.1", "@umbraco-ui/uui-symbol-expand": "1.14.1" } @@ -4768,9 +4768,9 @@ } }, "node_modules/@umbraco-ui/uui-popover-container": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.14.1.tgz", - "integrity": "sha512-FrKtJN73f9yQHkx2/GBDr7V6pEyZ0AwYH/dA5xzHAoLkLNiMGf3faXzb94JjXhxr/zzS2d1+XpGIlE8MbMLafQ==", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.14.2.tgz", + "integrity": "sha512-GDG953hPRTmjeI7rmjNQd0d/LcbwkRneXvCe7FYsT5d8ouUJ+P0GnQiuVd8r0aLN+JSit0mBLF+tfBhKpsSf+Q==", "license": "MIT", "dependencies": { "@umbraco-ui/uui-base": "1.14.1" @@ -5001,14 +5001,14 @@ } }, "node_modules/@umbraco-ui/uui-tabs": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.14.1.tgz", - "integrity": "sha512-ysYSa3IHOmmMKEwFf8pRhxw+fuxM7IcTbLrayelXWu3fDLUv/CzTW6ew69u3YYgFlljaGGQ50bqMILCrtHqoKQ==", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.14.2.tgz", + "integrity": "sha512-Qb5ZHVGljjkOZPc49jZPMdHhD11FGS6F8DbNjpWx1ghBvQGM4uIWGfnkZk7YSVZbTMACequyanyx/jZVHLKVRg==", "license": "MIT", "dependencies": { "@umbraco-ui/uui-base": "1.14.1", "@umbraco-ui/uui-button": "1.14.1", - "@umbraco-ui/uui-popover-container": "1.14.1", + "@umbraco-ui/uui-popover-container": "1.14.2", "@umbraco-ui/uui-symbol-more": "1.14.1" } }, diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 4676204c8c..3de46d0ca5 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -215,7 +215,7 @@ "@tiptap/extension-underline": "2.11.7", "@tiptap/pm": "2.11.7", "@tiptap/starter-kit": "2.11.7", - "@umbraco-ui/uui": "1.14.1", + "@umbraco-ui/uui": "1.14.2", "@umbraco-ui/uui-css": "1.14.1", "dompurify": "^3.2.5", "element-internals-polyfill": "^3.0.2", From e0a00bfd04c861060430eedcee53ee73cdab8996 Mon Sep 17 00:00:00 2001 From: Peter <45105665+PeterKvayt@users.noreply.github.com> Date: Fri, 4 Jul 2025 15:22:50 +0300 Subject: [PATCH 74/82] Amend accessibility modifiers on file upload property editor components to support extension (#19643) * Logic of getting media path moved to separate method. * TemporaryFileUploadValidator marked as public * Typo fix. --- .../FileUploadPropertyValueEditor.cs | 15 ++++++++++++--- .../TemporaryFileUploadValidator.cs | 12 ++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs index 1d0a0a0702..22c7402b7b 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs @@ -189,8 +189,7 @@ internal class FileUploadPropertyValueEditor : DataValueEditor } // get the filepath - // in case we are using the old path scheme, try to re-use numbers (bah...) - var filepath = _mediaFileManager.GetMediaPath(file.FileName, contentKey, propertyTypeKey); // fs-relative path + string filepath = GetMediaPath(file, dataTypeConfiguration, contentKey, propertyTypeKey); using (Stream filestream = file.OpenReadStream()) { @@ -201,9 +200,19 @@ internal class FileUploadPropertyValueEditor : DataValueEditor // TODO: Here it would make sense to do the auto-fill properties stuff but the API doesn't allow us to do that right // since we'd need to be able to return values for other properties from these methods - _mediaFileManager.FileSystem.AddFile(filepath, filestream, true); // must overwrite! + _mediaFileManager.FileSystem.AddFile(filepath, filestream, overrideIfExists: true); // must overwrite! } return filepath; } + + /// + /// Provides media path. + /// + /// File system relative path + protected virtual string GetMediaPath(TemporaryFileModel file, object? dataTypeConfiguration, Guid contentKey, Guid propertyTypeKey) + { + // in case we are using the old path scheme, try to re-use numbers (bah...) + return _mediaFileManager.GetMediaPath(file.FileName, contentKey, propertyTypeKey); + } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/TemporaryFileUploadValidator.cs b/src/Umbraco.Infrastructure/PropertyEditors/TemporaryFileUploadValidator.cs index 71aba644cd..e927c2fcb4 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/TemporaryFileUploadValidator.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/TemporaryFileUploadValidator.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models.TemporaryFile; using Umbraco.Cms.Core.Models.Validation; @@ -6,20 +6,20 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors; -internal class TemporaryFileUploadValidator : IValueValidator +public class TemporaryFileUploadValidator : IValueValidator { private readonly GetContentSettings _getContentSettings; private readonly ParseTemporaryFileKey _parseTemporaryFileKey; private readonly GetTemporaryFileModel _getTemporaryFileModel; private readonly ValidateFileType? _validateFileType; - internal delegate ContentSettings GetContentSettings(); + public delegate ContentSettings GetContentSettings(); - internal delegate Guid? ParseTemporaryFileKey(object? editorValue); + public delegate Guid? ParseTemporaryFileKey(object? editorValue); - internal delegate TemporaryFileModel? GetTemporaryFileModel(Guid temporaryFileKey); + public delegate TemporaryFileModel? GetTemporaryFileModel(Guid temporaryFileKey); - internal delegate bool ValidateFileType(string extension, object? dataTypeConfiguration); + public delegate bool ValidateFileType(string extension, object? dataTypeConfiguration); public TemporaryFileUploadValidator( GetContentSettings getContentSettings, From 8489d1a63ede60f51dda23ddc8f5bf3666820954 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 7 Jul 2025 08:13:16 +0200 Subject: [PATCH 75/82] Docs: Tree example (#19624) * add basic collection example * add card view example * update example readme * Add workspace view example with collection * wip tree example * clean up * Update README.md * Update README.md --- .../examples/tree/README.md | 10 ++ .../dashboard-with-tree.element.ts | 27 +++++ .../tree/dashboard-with-tree/manifests.ts | 14 +++ .../examples/tree/entity.ts | 5 + .../examples/tree/index.ts | 5 + .../tree/menu-item-with-tree/manifests.ts | 17 +++ .../examples/tree/tree/constants.ts | 1 + .../examples/tree/tree/data/constants.ts | 2 + .../tree/tree/data/local-data-source/index.ts | 1 + .../tree.local.data-source.ts | 108 ++++++++++++++++++ .../examples/tree/tree/data/manifests.ts | 4 + .../tree/tree/data/repository/constants.ts | 1 + .../tree/tree/data/repository/manifests.ts | 10 ++ .../tree/data/repository/tree.repository.ts | 30 +++++ .../tree/tree/data/store/constants.ts | 1 + .../examples/tree/tree/data/store/index.ts | 1 + .../tree/tree/data/store/manifests.ts | 10 ++ .../data/store/tree.store.context-token.ts | 4 + .../tree/tree/data/store/tree.store.ts | 11 ++ .../examples/tree/tree/manifests.ts | 24 ++++ .../examples/tree/tree/types.ts | 10 ++ 21 files changed, 296 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/README.md create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/dashboard-with-tree/dashboard-with-tree.element.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/dashboard-with-tree/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/entity.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/index.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/menu-item-with-tree/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/tree/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/tree/data/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/tree/data/local-data-source/index.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/tree/data/local-data-source/tree.local.data-source.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/tree/data/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/tree/data/repository/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/tree/data/repository/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/tree/data/repository/tree.repository.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/index.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/tree.store.context-token.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/tree.store.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/tree/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/tree/tree/types.ts diff --git a/src/Umbraco.Web.UI.Client/examples/tree/README.md b/src/Umbraco.Web.UI.Client/examples/tree/README.md new file mode 100644 index 0000000000..dfbefc98fe --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/README.md @@ -0,0 +1,10 @@ +# Tree Example + +This example demonstrates how to register a tree + +The example includes: + +- Tree + Tree Item Registration +- Tree Repository + Store Registration +- A Dashboard to show how to render a tree anywhere in the backoffice +- How to use the tree in the sidebar menu diff --git a/src/Umbraco.Web.UI.Client/examples/tree/dashboard-with-tree/dashboard-with-tree.element.ts b/src/Umbraco.Web.UI.Client/examples/tree/dashboard-with-tree/dashboard-with-tree.element.ts new file mode 100644 index 0000000000..903921fc93 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/dashboard-with-tree/dashboard-with-tree.element.ts @@ -0,0 +1,27 @@ +import { EXAMPLE_TREE_ALIAS } from '../tree/constants.js'; +import { html, customElement, LitElement, css } from '@umbraco-cms/backoffice/external/lit'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; + +@customElement('example-dashboard-with-tree') +export class ExampleDashboardWithTree extends UmbElementMixin(LitElement) { + override render() { + return html``; + } + + static override styles = [ + css` + :host { + display: block; + padding: var(--uui-size-layout-1); + } + `, + ]; +} + +export { ExampleDashboardWithTree as element }; + +declare global { + interface HTMLElementTagNameMap { + 'example-dashboard-with-tree': ExampleDashboardWithTree; + } +} diff --git a/src/Umbraco.Web.UI.Client/examples/tree/dashboard-with-tree/manifests.ts b/src/Umbraco.Web.UI.Client/examples/tree/dashboard-with-tree/manifests.ts new file mode 100644 index 0000000000..f26b94a73c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/dashboard-with-tree/manifests.ts @@ -0,0 +1,14 @@ +export const manifests: Array = [ + { + type: 'dashboard', + kind: 'default', + name: 'Example Dashboard With Tree', + alias: 'Example.Dashboard.WithTree', + element: () => import('./dashboard-with-tree.element.js'), + weight: 3000, + meta: { + label: 'Tree Example', + pathname: 'tree-example', + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/tree/entity.ts b/src/Umbraco.Web.UI.Client/examples/tree/entity.ts new file mode 100644 index 0000000000..f731d0f13f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/entity.ts @@ -0,0 +1,5 @@ +export const EXAMPLE_ENTITY_TYPE = 'example'; +export const EXAMPLE_ROOT_ENTITY_TYPE = 'example-root'; + +export type ExampleEntityType = typeof EXAMPLE_ENTITY_TYPE; +export type ExampleRootEntityType = typeof EXAMPLE_ROOT_ENTITY_TYPE; diff --git a/src/Umbraco.Web.UI.Client/examples/tree/index.ts b/src/Umbraco.Web.UI.Client/examples/tree/index.ts new file mode 100644 index 0000000000..5e0accf018 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/index.ts @@ -0,0 +1,5 @@ +import { manifests as dashboardManifests } from './dashboard-with-tree/manifests.js'; +import { manifests as menuItemManifests } from './menu-item-with-tree/manifests.js'; +import { manifests as treeManifests } from './tree/manifests.js'; + +export const manifests: Array = [...dashboardManifests, ...menuItemManifests, ...treeManifests]; diff --git a/src/Umbraco.Web.UI.Client/examples/tree/menu-item-with-tree/manifests.ts b/src/Umbraco.Web.UI.Client/examples/tree/menu-item-with-tree/manifests.ts new file mode 100644 index 0000000000..75676b4d98 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/menu-item-with-tree/manifests.ts @@ -0,0 +1,17 @@ +import { EXAMPLE_TREE_ALIAS } from '../tree/constants.js'; +import { UMB_CONTENT_MENU_ALIAS } from '@umbraco-cms/backoffice/document'; + +export const manifests: Array = [ + { + type: 'menuItem', + kind: 'tree', + alias: 'Example.MenuItem.Tree', + name: 'Example Tree Menu Item', + weight: 1000, + meta: { + label: 'Example Tree', + menus: [UMB_CONTENT_MENU_ALIAS], + treeAlias: EXAMPLE_TREE_ALIAS, + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/tree/tree/constants.ts b/src/Umbraco.Web.UI.Client/examples/tree/tree/constants.ts new file mode 100644 index 0000000000..8ae0861f99 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/tree/constants.ts @@ -0,0 +1 @@ +export const EXAMPLE_TREE_ALIAS = 'Example.Tree'; diff --git a/src/Umbraco.Web.UI.Client/examples/tree/tree/data/constants.ts b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/constants.ts new file mode 100644 index 0000000000..ffeb21ce2a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/constants.ts @@ -0,0 +1,2 @@ +export * from './repository/constants.js'; +export * from './store/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/examples/tree/tree/data/local-data-source/index.ts b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/local-data-source/index.ts new file mode 100644 index 0000000000..8e41b22b70 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/local-data-source/index.ts @@ -0,0 +1 @@ +export * from './tree.local.data-source.js'; diff --git a/src/Umbraco.Web.UI.Client/examples/tree/tree/data/local-data-source/tree.local.data-source.ts b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/local-data-source/tree.local.data-source.ts new file mode 100644 index 0000000000..aa6bd88ac9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/local-data-source/tree.local.data-source.ts @@ -0,0 +1,108 @@ +import type { ExampleTreeItemModel } from '../../types.js'; +import { EXAMPLE_ENTITY_TYPE, EXAMPLE_ROOT_ENTITY_TYPE } from '../../../entity.js'; +import type { + UmbTreeAncestorsOfRequestArgs, + UmbTreeChildrenOfRequestArgs, + UmbTreeDataSource, + UmbTreeRootItemsRequestArgs, +} from '@umbraco-cms/backoffice/tree'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; + +const EXAMPLE_TREE_DATA: Array = [ + { + entityType: EXAMPLE_ENTITY_TYPE, + hasChildren: false, + isFolder: false, + name: 'Item 1', + parent: { unique: null, entityType: EXAMPLE_ROOT_ENTITY_TYPE }, + unique: 'ab7b6e03-5f4d-4a6b-9f4c-21292d462e08', + icon: 'icon-newspaper', + }, + { + entityType: EXAMPLE_ENTITY_TYPE, + hasChildren: true, + isFolder: false, + name: 'Item 2', + parent: { unique: null, entityType: EXAMPLE_ROOT_ENTITY_TYPE }, + unique: '74a5b2d9-3564-45b8-a3ee-98fc7ec0c1fb', + icon: 'icon-newspaper', + }, + { + entityType: EXAMPLE_ENTITY_TYPE, + hasChildren: false, + isFolder: false, + name: 'Item 3', + parent: { unique: null, entityType: EXAMPLE_ROOT_ENTITY_TYPE }, + unique: '1b8ed2ac-b4bb-4384-972e-2cf18f40586a', + icon: 'icon-newspaper', + }, + { + entityType: EXAMPLE_ENTITY_TYPE, + hasChildren: false, + isFolder: false, + name: 'Item 2.1', + parent: { unique: '74a5b2d9-3564-45b8-a3ee-98fc7ec0c1fb', entityType: EXAMPLE_ENTITY_TYPE }, + unique: '62dbd672-b198-4fc8-8b42-5d21dfbd3788', + icon: 'icon-newspaper', + }, + { + entityType: EXAMPLE_ENTITY_TYPE, + hasChildren: true, + isFolder: false, + name: 'Item 2.2', + parent: { unique: '74a5b2d9-3564-45b8-a3ee-98fc7ec0c1fb', entityType: EXAMPLE_ENTITY_TYPE }, + unique: 'deaa3f8c-e40b-4eb7-8268-34014504152e', + icon: 'icon-newspaper', + }, + { + entityType: EXAMPLE_ENTITY_TYPE, + hasChildren: false, + isFolder: false, + name: 'Item 2.2.1', + parent: { unique: 'deaa3f8c-e40b-4eb7-8268-34014504152e', entityType: EXAMPLE_ENTITY_TYPE }, + unique: 'd4cf5fd2-1f84-4f3b-b63b-5f29a38e72d1', + icon: 'icon-newspaper', + }, +]; + +export class ExampleTreeLocalDataSource extends UmbControllerBase implements UmbTreeDataSource { + async getRootItems(args: UmbTreeRootItemsRequestArgs) { + // TODO: handle skip, take, foldersOnly. + console.log(args); + const rootItems: Array = EXAMPLE_TREE_DATA.filter((item) => item.parent.unique === null); + return { data: { items: rootItems, total: rootItems.length } }; + } + + async getChildrenOf(args: UmbTreeChildrenOfRequestArgs) { + // TODO: handle skip, take, foldersOnly. + const children = EXAMPLE_TREE_DATA.filter( + (item) => item.parent.unique === args.parent.unique && item.parent.entityType === args.parent.entityType, + ); + + return { data: { items: children, total: children.length } }; + } + + async getAncestorsOf(args: UmbTreeAncestorsOfRequestArgs) { + const ancestors = findAncestors(args.treeItem.unique, args.treeItem.entityType); + return { data: ancestors }; + } +} + +// Helper function to find ancestors recursively +const findAncestors = (unique: string, entityType: string): Array => { + const item = EXAMPLE_TREE_DATA.find((i) => i.unique === unique && i.entityType === entityType); + + if (!item || !item.parent || item.parent.unique === null) { + return []; + } + + const parent = EXAMPLE_TREE_DATA.find( + (i) => i.unique === item.parent.unique && i.entityType === item.parent.entityType, + ); + + if (!parent) { + return []; + } + + return [parent, ...findAncestors(parent.unique, parent.entityType)]; +}; diff --git a/src/Umbraco.Web.UI.Client/examples/tree/tree/data/manifests.ts b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/manifests.ts new file mode 100644 index 0000000000..c0d9d27483 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/manifests.ts @@ -0,0 +1,4 @@ +import { manifests as repositoryManifests } from './repository/manifests.js'; +import { manifests as storeManifests } from './store/manifests.js'; + +export const manifests: Array = [...repositoryManifests, ...storeManifests]; diff --git a/src/Umbraco.Web.UI.Client/examples/tree/tree/data/repository/constants.ts b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/repository/constants.ts new file mode 100644 index 0000000000..43c1ad78a0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/repository/constants.ts @@ -0,0 +1 @@ +export const EXAMPLE_TREE_REPOSITORY_ALIAS = 'Example.Repository.Tree'; diff --git a/src/Umbraco.Web.UI.Client/examples/tree/tree/data/repository/manifests.ts b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/repository/manifests.ts new file mode 100644 index 0000000000..db0ccde3d8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/repository/manifests.ts @@ -0,0 +1,10 @@ +import { EXAMPLE_TREE_REPOSITORY_ALIAS } from './constants.js'; + +export const manifests: Array = [ + { + type: 'repository', + alias: EXAMPLE_TREE_REPOSITORY_ALIAS, + name: 'Example Tree Repository', + api: () => import('./tree.repository.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/tree/tree/data/repository/tree.repository.ts b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/repository/tree.repository.ts new file mode 100644 index 0000000000..fc5fce3224 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/repository/tree.repository.ts @@ -0,0 +1,30 @@ +import type { ExampleTreeItemModel, ExampleTreeRootModel } from '../../types.js'; +import { EXAMPLE_ROOT_ENTITY_TYPE } from '../../../entity.js'; +import { EXAMPLE_TREE_STORE_CONTEXT } from '../store/index.js'; +import { ExampleTreeLocalDataSource } from '../local-data-source/index.js'; +import { UmbTreeRepositoryBase, type UmbTreeRepository } from '@umbraco-cms/backoffice/tree'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; + +export class ExampleTreeRepository + extends UmbTreeRepositoryBase + implements UmbTreeRepository, UmbApi +{ + constructor(host: UmbControllerHost) { + super(host, ExampleTreeLocalDataSource, EXAMPLE_TREE_STORE_CONTEXT); + } + + async requestTreeRoot() { + const root: ExampleTreeRootModel = { + unique: null, + entityType: EXAMPLE_ROOT_ENTITY_TYPE, + name: 'Example Tree', + hasChildren: true, + isFolder: true, + }; + + return { data: root }; + } +} + +export { ExampleTreeRepository as api }; diff --git a/src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/constants.ts b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/constants.ts new file mode 100644 index 0000000000..32eb767b83 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/constants.ts @@ -0,0 +1 @@ +export const EXAMPLE_TREE_STORE_ALIAS = 'Example.Store.Tree'; diff --git a/src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/index.ts b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/index.ts new file mode 100644 index 0000000000..115105d23b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/index.ts @@ -0,0 +1 @@ +export * from './tree.store.context-token.js'; diff --git a/src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/manifests.ts b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/manifests.ts new file mode 100644 index 0000000000..8b09af6d1e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/manifests.ts @@ -0,0 +1,10 @@ +import { EXAMPLE_TREE_STORE_ALIAS } from './constants.js'; + +export const manifests: Array = [ + { + type: 'treeStore', + alias: EXAMPLE_TREE_STORE_ALIAS, + name: 'Example Tree Store', + api: () => import('./tree.store.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/tree.store.context-token.ts b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/tree.store.context-token.ts new file mode 100644 index 0000000000..17bea18a59 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/tree.store.context-token.ts @@ -0,0 +1,4 @@ +import type { ExampleTreeStore } from './tree.store.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const EXAMPLE_TREE_STORE_CONTEXT = new UmbContextToken('ExampleTreeStore'); diff --git a/src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/tree.store.ts b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/tree.store.ts new file mode 100644 index 0000000000..aa18bce0a9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/tree/data/store/tree.store.ts @@ -0,0 +1,11 @@ +import { EXAMPLE_TREE_STORE_CONTEXT } from './tree.store.context-token.js'; +import { UmbUniqueTreeStore } from '@umbraco-cms/backoffice/tree'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export class ExampleTreeStore extends UmbUniqueTreeStore { + constructor(host: UmbControllerHost) { + super(host, EXAMPLE_TREE_STORE_CONTEXT.toString()); + } +} + +export { ExampleTreeStore as api }; diff --git a/src/Umbraco.Web.UI.Client/examples/tree/tree/manifests.ts b/src/Umbraco.Web.UI.Client/examples/tree/tree/manifests.ts new file mode 100644 index 0000000000..62213b9525 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/tree/manifests.ts @@ -0,0 +1,24 @@ +import { EXAMPLE_ENTITY_TYPE, EXAMPLE_ROOT_ENTITY_TYPE } from '../entity.js'; +import { EXAMPLE_TREE_ALIAS } from './constants.js'; +import { EXAMPLE_TREE_REPOSITORY_ALIAS } from './data/constants.js'; +import { manifests as dataManifests } from './data/manifests.js'; + +export const manifests: Array = [ + { + type: 'tree', + kind: 'default', + alias: EXAMPLE_TREE_ALIAS, + name: 'Example Tree', + meta: { + repositoryAlias: EXAMPLE_TREE_REPOSITORY_ALIAS, + }, + }, + { + type: 'treeItem', + kind: 'default', + alias: 'Example.TreeItem', + name: 'Example Tree Item', + forEntityTypes: [EXAMPLE_ROOT_ENTITY_TYPE, EXAMPLE_ENTITY_TYPE], + }, + ...dataManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/tree/tree/types.ts b/src/Umbraco.Web.UI.Client/examples/tree/tree/types.ts new file mode 100644 index 0000000000..9b30a92b11 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/tree/tree/types.ts @@ -0,0 +1,10 @@ +import type { ExampleEntityType, ExampleRootEntityType } from '../entity.js'; +import type { UmbTreeItemModel, UmbTreeRootModel } from '@umbraco-cms/backoffice/tree'; + +export interface ExampleTreeItemModel extends UmbTreeItemModel { + entityType: ExampleEntityType; +} + +export interface ExampleTreeRootModel extends UmbTreeRootModel { + entityType: ExampleRootEntityType; +} From 05aef6a91fd00f4f602c0bd4ae2a2b1935f11fea Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 7 Jul 2025 08:31:27 +0200 Subject: [PATCH 76/82] Hide Document/Media Collection Workspace View until the item is created (#19644) * only show collection items workspace view when document is created * do not pass null for collection * only show media collection view when media is created --- .../documents/documents/workspace/manifests.ts | 9 ++++++++- .../src/packages/media/media/workspace/manifests.ts | 10 +++++++++- .../media/media/workspace/media-workspace.context.ts | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/manifests.ts index 2cce72664a..ccae31b036 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/manifests.ts @@ -5,7 +5,10 @@ import { UMB_CONTENT_HAS_PROPERTIES_WORKSPACE_CONDITION, UMB_WORKSPACE_HAS_CONTENT_COLLECTION_CONDITION_ALIAS, } from '@umbraco-cms/backoffice/content'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; +import { + UMB_WORKSPACE_CONDITION_ALIAS, + UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS, +} from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { @@ -36,6 +39,10 @@ export const manifests: Array = [ { alias: UMB_WORKSPACE_HAS_CONTENT_COLLECTION_CONDITION_ALIAS, }, + { + alias: UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS, + match: false, + }, ], }, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/manifests.ts index 4cb6f75235..d43932d72c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/manifests.ts @@ -1,5 +1,9 @@ import { UMB_MEDIA_WORKSPACE_ALIAS } from './constants.js'; -import { UmbSubmitWorkspaceAction, UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; +import { + UmbSubmitWorkspaceAction, + UMB_WORKSPACE_CONDITION_ALIAS, + UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS, +} from '@umbraco-cms/backoffice/workspace'; import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS } from '@umbraco-cms/backoffice/recycle-bin'; import { UMB_CONTENT_HAS_PROPERTIES_WORKSPACE_CONDITION, @@ -35,6 +39,10 @@ export const manifests: Array = [ { alias: UMB_WORKSPACE_HAS_CONTENT_COLLECTION_CONDITION_ALIAS, }, + { + alias: UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS, + match: false, + }, ], }, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts index 423d1cde14..c8a61f1a46 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts @@ -76,7 +76,7 @@ export class UmbMediaWorkspaceContext const mediaTypeUnique = info.match.params.mediaTypeUnique; await this.createScaffold({ parent: { entityType: parentEntityType, unique: parentUnique }, - preset: { mediaType: { unique: mediaTypeUnique, collection: null } }, + preset: { mediaType: { unique: mediaTypeUnique } }, }); new UmbWorkspaceIsNewRedirectController( From af52e68b6ebbeaa6e889ef516ac1cfbca3e4bf4a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 7 Jul 2025 08:34:01 +0200 Subject: [PATCH 77/82] Clean up workspace conditions folder structure (#19634) * Refactor section conditions into subfolders Split section condition logic into 'section-alias' and 'section-user-permission' subfolders, each with their own constants, manifests, and types. Updated imports and manifest aggregation to use the new structure for improved modularity and maintainability. * use const * fix build * Refactor section alias condition to use constant Replaces hardcoded 'Umb.Condition.SectionAlias' strings with the UMB_SECTION_ALIAS_CONDITION_ALIAS constant across all manifests and related files. This improves maintainability and consistency by centralizing the section alias condition reference. * clean up workspace conditions --- .../core/workspace/conditions/constants.ts | 3 ++ .../core/workspace/conditions/manifests.ts | 12 ++--- .../core/workspace/conditions/types.ts | 52 ++----------------- .../conditions/workspace-alias/constants.ts | 4 ++ .../conditions/workspace-alias/manifests.ts | 11 ++++ .../conditions/workspace-alias/types.ts | 23 ++++++++ .../workspace-alias.condition.ts | 15 ++---- .../constants.ts} | 5 -- .../workspace-entity-is-new/manifests.ts | 11 ++++ .../workspace-entity-is-new/types.ts | 19 +++++++ .../workspace-entity-is-new.condition.ts | 12 +---- .../workspace-entity-type/constants.ts | 1 + .../workspace-entity-type/manifests.ts | 11 ++++ .../conditions/workspace-entity-type/types.ts | 23 ++++++++ .../workspace-entity-type.condition.ts | 11 +--- .../src/packages/core/workspace/constants.ts | 3 +- .../src/packages/core/workspace/index.ts | 1 - .../data-type/tree/folder/manifests.ts | 3 +- .../document-types/tree/manifests.ts | 3 +- .../tree/tree-item-children/manifests.ts | 3 +- .../members/member-type/tree/manifests.ts | 3 +- 21 files changed, 133 insertions(+), 96 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias/types.ts rename src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/{ => workspace-alias}/workspace-alias.condition.ts (74%) rename src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/{const.ts => workspace-entity-is-new/constants.ts} (55%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new/types.ts rename src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/{ => workspace-entity-is-new}/workspace-entity-is-new.condition.ts (69%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-type/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-type/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-type/types.ts rename src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/{ => workspace-entity-type}/workspace-entity-type.condition.ts (68%) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/constants.ts new file mode 100644 index 0000000000..49cbe51426 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/constants.ts @@ -0,0 +1,3 @@ +export * from './workspace-alias/constants.js'; +export * from './workspace-entity-is-new/constants.js'; +export * from './workspace-entity-type/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/manifests.ts index 3250ae8565..d486939572 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/manifests.ts @@ -1,9 +1,9 @@ -import { manifest as workspaceAliasCondition } from './workspace-alias.condition.js'; -import { manifest as workspaceEntityTypeCondition } from './workspace-entity-type.condition.js'; -import { manifest as workspaceEntityIsNewCondition } from './workspace-entity-is-new.condition.js'; +import { manifests as workspaceAliasCondition } from './workspace-alias/manifests.js'; +import { manifests as workspaceEntityIsNewCondition } from './workspace-entity-is-new/manifests.js'; +import { manifests as workspaceEntityTypeCondition } from './workspace-entity-type/manifests.js'; export const manifests: Array = [ - workspaceEntityIsNewCondition, - workspaceAliasCondition, - workspaceEntityTypeCondition, + ...workspaceAliasCondition, + ...workspaceEntityIsNewCondition, + ...workspaceEntityTypeCondition, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/types.ts index a8a150d218..a203b8052e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/types.ts @@ -1,49 +1,3 @@ -import type { UMB_WORKSPACE_CONDITION_ALIAS, UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS } from './const.js'; -import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; - -export interface WorkspaceAliasConditionConfig extends UmbConditionConfigBase { - /** - * Define the workspace that this extension should be available in - * @example - * "Umb.Workspace.Document" - */ - match?: string; - /** - * Define one or more workspaces that this extension should be available in - * @example - * ["Umb.Workspace.Document", "Umb.Workspace.Media"] - */ - oneOf?: Array; -} - -export type UmbWorkspaceEntityTypeConditionConfig = UmbConditionConfigBase<'Umb.Condition.WorkspaceEntityType'> & { - /** - * Define the workspace that this extension should be available in - * @example - * "Document" - */ - match: string; -}; -/** - * @deprecated Use `UmbWorkspaceEntityTypeConditionConfig` instead. This will be removed in Umbraco 17. - */ -export type WorkspaceEntityTypeConditionConfig = UmbWorkspaceEntityTypeConditionConfig; - -export interface UmbWorkspaceEntityIsNewConditionConfig - extends UmbConditionConfigBase { - match: boolean; -} - -/** - * @deprecated Use `UmbWorkspaceEntityIsNewConditionConfig` instead. This will be removed in Umbraco 17. - */ -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface WorkspaceEntityIsNewConditionConfig extends UmbWorkspaceEntityIsNewConditionConfig {} - -declare global { - interface UmbExtensionConditionConfigMap { - umbWorkspaceAlias: WorkspaceAliasConditionConfig; - umbWorkspaceEntityIsNewConditionConfig: UmbWorkspaceEntityIsNewConditionConfig; - umbWorkspaceEntityType: UmbWorkspaceEntityTypeConditionConfig; - } -} +export type * from './workspace-alias/types.js'; +export type * from './workspace-entity-is-new/types.js'; +export type * from './workspace-entity-type/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias/constants.ts new file mode 100644 index 0000000000..2ac8ff8dfa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias/constants.ts @@ -0,0 +1,4 @@ +/** + * Workspace alias condition alias + */ +export const UMB_WORKSPACE_CONDITION_ALIAS = 'Umb.Condition.WorkspaceAlias'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias/manifests.ts new file mode 100644 index 0000000000..afb8f9488c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias/manifests.ts @@ -0,0 +1,11 @@ +import { UMB_WORKSPACE_CONDITION_ALIAS } from './constants.js'; +import { UmbWorkspaceAliasCondition } from './workspace-alias.condition.js'; + +export const manifests: Array = [ + { + type: 'condition', + name: 'Workspace Alias Condition', + alias: UMB_WORKSPACE_CONDITION_ALIAS, + api: UmbWorkspaceAliasCondition, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias/types.ts new file mode 100644 index 0000000000..af930a9cdd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias/types.ts @@ -0,0 +1,23 @@ +import type { UMB_WORKSPACE_CONDITION_ALIAS } from './constants.js'; +import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; + +export interface WorkspaceAliasConditionConfig extends UmbConditionConfigBase { + /** + * Define the workspace that this extension should be available in + * @example + * "Umb.Workspace.Document" + */ + match?: string; + /** + * Define one or more workspaces that this extension should be available in + * @example + * ["Umb.Workspace.Document", "Umb.Workspace.Media"] + */ + oneOf?: Array; +} + +declare global { + interface UmbExtensionConditionConfigMap { + umbWorkspaceAlias: WorkspaceAliasConditionConfig; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias/workspace-alias.condition.ts similarity index 74% rename from src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias.condition.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias/workspace-alias.condition.ts index bc7c58f797..1652e7c999 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias.condition.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias/workspace-alias.condition.ts @@ -1,7 +1,7 @@ -import { UMB_WORKSPACE_CONTEXT } from '../workspace.context-token.js'; -import type { UmbWorkspaceContext } from '../workspace-context.interface.js'; -import type { WorkspaceAliasConditionConfig } from './types.js'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from './const.js'; +import type { WorkspaceAliasConditionConfig } from '../types.js'; +import type { UmbWorkspaceContext } from '../../workspace-context.interface.js'; +import { UMB_WORKSPACE_CONTEXT } from '../../workspace.context-token.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from './constants.js'; import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @@ -35,10 +35,3 @@ export class UmbWorkspaceAliasCondition } } } - -export const manifest: UmbExtensionManifest = { - type: 'condition', - name: 'Workspace Alias Condition', - alias: UMB_WORKSPACE_CONDITION_ALIAS, - api: UmbWorkspaceAliasCondition, -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/const.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new/constants.ts similarity index 55% rename from src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/const.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new/constants.ts index c9faa944c2..677ae609b7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/const.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new/constants.ts @@ -2,8 +2,3 @@ * Workspace entity is new condition alias */ export const UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS = 'Umb.Condition.WorkspaceEntityIsNew'; - -/** - * Workspace alias condition alias - */ -export const UMB_WORKSPACE_CONDITION_ALIAS = 'Umb.Condition.WorkspaceAlias'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new/manifests.ts new file mode 100644 index 0000000000..dad14ca661 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new/manifests.ts @@ -0,0 +1,11 @@ +import { UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS } from './constants.js'; +import { UmbWorkspaceEntityIsNewCondition } from './workspace-entity-is-new.condition.js'; + +export const manifests: Array = [ + { + type: 'condition', + name: 'Workspace Entity Is New Condition', + alias: UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS, + api: UmbWorkspaceEntityIsNewCondition, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new/types.ts new file mode 100644 index 0000000000..8294ef15fb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new/types.ts @@ -0,0 +1,19 @@ +import type { UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS } from './constants.js'; +import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; + +export interface UmbWorkspaceEntityIsNewConditionConfig + extends UmbConditionConfigBase { + match: boolean; +} + +/** + * @deprecated Use `UmbWorkspaceEntityIsNewConditionConfig` instead. This will be removed in Umbraco 17. + */ +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface WorkspaceEntityIsNewConditionConfig extends UmbWorkspaceEntityIsNewConditionConfig {} + +declare global { + interface UmbExtensionConditionConfigMap { + umbWorkspaceEntityIsNewConditionConfig: UmbWorkspaceEntityIsNewConditionConfig; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new/workspace-entity-is-new.condition.ts similarity index 69% rename from src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new.condition.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new/workspace-entity-is-new.condition.ts index fa1e059ae0..cc1754f8d5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new.condition.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new/workspace-entity-is-new.condition.ts @@ -1,6 +1,5 @@ -import { UMB_SUBMITTABLE_WORKSPACE_CONTEXT } from '../index.js'; -import type { UmbWorkspaceEntityIsNewConditionConfig } from './types.js'; -import { UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS } from './const.js'; +import type { UmbWorkspaceEntityIsNewConditionConfig } from '../types.js'; +import { UMB_SUBMITTABLE_WORKSPACE_CONTEXT } from '../../contexts/index.js'; import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @@ -28,10 +27,3 @@ export class UmbWorkspaceEntityIsNewCondition }); } } - -export const manifest: UmbExtensionManifest = { - type: 'condition', - name: 'Workspace Entity Is New Condition', - alias: UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS, - api: UmbWorkspaceEntityIsNewCondition, -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-type/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-type/constants.ts new file mode 100644 index 0000000000..18d5dcd19c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-type/constants.ts @@ -0,0 +1 @@ +export const UMB_WORKSPACE_ENTITY_TYPE_CONDITION_ALIAS = 'Umb.Condition.WorkspaceEntityType'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-type/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-type/manifests.ts new file mode 100644 index 0000000000..c35cbc849a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-type/manifests.ts @@ -0,0 +1,11 @@ +import { UMB_WORKSPACE_ENTITY_TYPE_CONDITION_ALIAS } from './constants.js'; +import { UmbWorkspaceEntityTypeCondition } from './workspace-entity-type.condition.js'; + +export const manifests: Array = [ + { + type: 'condition', + name: 'Workspace Entity Type Condition', + alias: UMB_WORKSPACE_ENTITY_TYPE_CONDITION_ALIAS, + api: UmbWorkspaceEntityTypeCondition, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-type/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-type/types.ts new file mode 100644 index 0000000000..89a5df4e5c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-type/types.ts @@ -0,0 +1,23 @@ +import type { UMB_WORKSPACE_ENTITY_TYPE_CONDITION_ALIAS } from './constants.js'; +import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; + +export type UmbWorkspaceEntityTypeConditionConfig = UmbConditionConfigBase< + typeof UMB_WORKSPACE_ENTITY_TYPE_CONDITION_ALIAS +> & { + /** + * Define the workspace that this extension should be available in + * @example + * "Document" + */ + match: string; +}; +/** + * @deprecated Use `UmbWorkspaceEntityTypeConditionConfig` instead. This will be removed in Umbraco 17. + */ +export type WorkspaceEntityTypeConditionConfig = UmbWorkspaceEntityTypeConditionConfig; + +declare global { + interface UmbExtensionConditionConfigMap { + umbWorkspaceEntityType: UmbWorkspaceEntityTypeConditionConfig; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-type.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-type/workspace-entity-type.condition.ts similarity index 68% rename from src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-type.condition.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-type/workspace-entity-type.condition.ts index 73f0dc0ecf..c9b04b0ea7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-type.condition.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-type/workspace-entity-type.condition.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONTEXT } from '../workspace.context-token.js'; -import type { UmbWorkspaceEntityTypeConditionConfig } from './types.js'; +import { UMB_WORKSPACE_CONTEXT } from '../../workspace.context-token.js'; +import type { UmbWorkspaceEntityTypeConditionConfig } from '../types.js'; import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @@ -15,10 +15,3 @@ export class UmbWorkspaceEntityTypeCondition }); } } - -export const manifest: UmbExtensionManifest = { - type: 'condition', - name: 'Workspace Entity Type Condition', - alias: 'Umb.Condition.WorkspaceEntityType', - api: UmbWorkspaceEntityTypeCondition, -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/constants.ts index 960d194914..3590fe5246 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/constants.ts @@ -1,2 +1,3 @@ -export { UMB_NAMABLE_WORKSPACE_CONTEXT } from './namable/index.js'; export { UMB_ENTITY_DETAIL_WORKSPACE_CONTEXT } from './entity-detail/index.js'; +export { UMB_NAMABLE_WORKSPACE_CONTEXT } from './namable/index.js'; +export * from './conditions/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/index.ts index 055747196e..3ee9f93702 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/index.ts @@ -1,5 +1,4 @@ export * from './components/index.js'; -export * from './conditions/const.js'; export * from './constants.js'; export * from './contexts/index.js'; export * from './controllers/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/folder/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/folder/manifests.ts index 767366a374..4c52b60990 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/folder/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/folder/manifests.ts @@ -4,6 +4,7 @@ import { UMB_DATA_TYPE_FOLDER_REPOSITORY_ALIAS } from './repository/index.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; import { UMB_DATA_TYPE_FOLDER_WORKSPACE_ALIAS } from './workspace/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { @@ -39,7 +40,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, oneOf: [UMB_DATA_TYPE_ROOT_WORKSPACE_ALIAS, UMB_DATA_TYPE_FOLDER_WORKSPACE_ALIAS], }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/tree/manifests.ts index a9854ee0c2..683a907247 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/tree/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/tree/manifests.ts @@ -10,6 +10,7 @@ import { } from './constants.js'; import { manifests as folderManifests } from './folder/manifests.js'; import { manifests as treeItemChildrenManifests } from './tree-item-children/manifests.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { @@ -57,7 +58,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, oneOf: [UMB_DOCUMENT_TYPE_ROOT_WORKSPACE_ALIAS, UMB_DOCUMENT_TYPE_FOLDER_WORKSPACE_ALIAS], }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/tree-item-children/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/tree-item-children/manifests.ts index 6636c42e15..94f706608e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/tree-item-children/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/tree-item-children/manifests.ts @@ -6,6 +6,7 @@ import { import { UMB_MEDIA_TYPE_ROOT_WORKSPACE_ALIAS } from '../../media-type-root/constants.js'; import { UMB_MEDIA_TYPE_FOLDER_WORKSPACE_ALIAS } from '../folder/constants.js'; import { manifests as collectionManifests } from './collection/manifests.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { @@ -28,7 +29,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, oneOf: [UMB_MEDIA_TYPE_ROOT_WORKSPACE_ALIAS, UMB_MEDIA_TYPE_FOLDER_WORKSPACE_ALIAS], }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/tree/manifests.ts index 8a2cb18e76..8094f2ad61 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/tree/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/tree/manifests.ts @@ -7,6 +7,7 @@ import { UMB_MEMBER_TYPE_TREE_STORE_ALIAS, } from './constants.js'; import { manifests as treeItemChildrenManifest } from './tree-item-children/manifests.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { @@ -50,7 +51,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, oneOf: [UMB_MEMBER_TYPE_ROOT_WORKSPACE_ALIAS], }, ], From 7b929a7c539be804b6f6138c629516b9074c813e Mon Sep 17 00:00:00 2001 From: Peter <45105665+PeterKvayt@users.noreply.github.com> Date: Mon, 7 Jul 2025 11:04:10 +0300 Subject: [PATCH 78/82] Title returns "Server Error" in non-debug mode (#19665) * Add isDebug check for Title * Error title has been added --- .../DependencyInjection/ApplicationBuilderExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/ApplicationBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/ApplicationBuilderExtensions.cs index 871a98c6d3..44efaf7be8 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/ApplicationBuilderExtensions.cs @@ -43,7 +43,7 @@ internal static class ApplicationBuilderExtensions var response = new ProblemDetails { - Title = exception.Message, + Title = isDebug ? exception.Message : "Server Error", Detail = isDebug ? exception.StackTrace : null, Status = statusCode ?? StatusCodes.Status500InternalServerError, Instance = isDebug ? exception.GetType().Name : null, From 8b4849be0576da7bada224d255333ec7ef2d2744 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 7 Jul 2025 11:33:59 +0200 Subject: [PATCH 79/82] Fix issue with preview in delivery API for MNTP property editor (#19668) * Passes the preview flag to the cache retrieval when resolving the delivery API object for the MNTP property editor. * Added unit test verifying fix and adjusted mocks for tests to acoomodate. * Provided preview flag for Razor rendering. --- .../MultiNodeTreePickerValueConverter.cs | 4 +-- .../MultiNodeTreePickerValueConverterTests.cs | 26 +++++++++++++++++-- .../OutputExpansionStrategyTestBase.cs | 2 +- .../PropertyValueConverterTests.cs | 26 +++++++++++++++++-- 4 files changed, 51 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs index 96e84bfb83..727c28f077 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs @@ -115,7 +115,7 @@ public class MultiNodeTreePickerValueConverter : PropertyValueConverterBase, IDe udi, ref objectType, UmbracoObjectTypes.Document, - id => _contentCache.GetById(guidUdi.Guid)); + id => _contentCache.GetById(preview, guidUdi.Guid)); break; case Constants.UdiEntityType.Media: multiNodeTreePickerItem = GetPublishedContent( @@ -205,7 +205,7 @@ public class MultiNodeTreePickerValueConverter : PropertyValueConverterBase, IDe { Constants.UdiEntityType.Document => entityTypeUdis.Select(udi => { - IPublishedContent? content = _contentCache.GetById(udi.Guid); + IPublishedContent? content = _contentCache.GetById(preview, udi.Guid); return content != null ? _apiContentBuilder.Build(content) : null; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MultiNodeTreePickerValueConverterTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MultiNodeTreePickerValueConverterTests.cs index 59dd0cee87..4c93f44ce5 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MultiNodeTreePickerValueConverterTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MultiNodeTreePickerValueConverterTests.cs @@ -73,7 +73,7 @@ public class MultiNodeTreePickerValueConverterTests : PropertyValueConverterTest var otherContentKey = Guid.NewGuid(); var otherContent = SetupPublishedContent("The other page", otherContentKey, PublishedItemType.Content, PublishedContentType); - RegisterContentWithProviders(otherContent.Object); + RegisterContentWithProviders(otherContent.Object, false); var valueConverter = MultiNodeTreePickerValueConverter(); @@ -94,6 +94,28 @@ public class MultiNodeTreePickerValueConverterTests : PropertyValueConverterTest Assert.AreEqual("TheContentType", result.Last().ContentType); } + [Test] + public void MultiNodeTreePickerValueConverter_InSingleMode_WithPreview_ConvertsValueToListOfContent() + { + var publishedDataType = MultiNodePickerPublishedDataType(false, Constants.UdiEntityType.Document); + var publishedPropertyType = new Mock(); + publishedPropertyType.SetupGet(p => p.DataType).Returns(publishedDataType); + + var valueConverter = MultiNodeTreePickerValueConverter(); + + Assert.AreEqual(typeof(IEnumerable), valueConverter.GetDeliveryApiPropertyValueType(publishedPropertyType.Object)); + + var inter = new Udi[] { new GuidUdi(Constants.UdiEntityType.Document, DraftContent.Key) }; + var result = valueConverter.ConvertIntermediateToDeliveryApiObject(Mock.Of(), publishedPropertyType.Object, PropertyCacheLevel.Element, inter, true, false) as IEnumerable; + Assert.NotNull(result); + Assert.AreEqual(1, result.Count()); + Assert.AreEqual(DraftContent.Name, result.First().Name); + Assert.AreEqual(DraftContent.Key, result.First().Id); + Assert.AreEqual("/the-draft-page-url/", result.First().Route.Path); + Assert.AreEqual("TheContentType", result.First().ContentType); + Assert.IsEmpty(result.First().Properties); + } + [Test] [TestCase(Constants.UdiEntityType.Document)] [TestCase("content")] @@ -113,7 +135,7 @@ public class MultiNodeTreePickerValueConverterTests : PropertyValueConverterTest .Setup(p => p.GetUrl(content.Object, It.IsAny(), It.IsAny(), It.IsAny())) .Returns(content.Object.UrlSegment); PublishedContentCacheMock - .Setup(pcc => pcc.GetById(key)) + .Setup(pcc => pcc.GetById(false, key)) .Returns(content.Object); var publishedDataType = MultiNodePickerPublishedDataType(false, entityType); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/OutputExpansionStrategyTestBase.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/OutputExpansionStrategyTestBase.cs index 3051bced8a..0f6c3eb168 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/OutputExpansionStrategyTestBase.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/OutputExpansionStrategyTestBase.cs @@ -327,7 +327,7 @@ public abstract class OutputExpansionStrategyTestBase : PropertyValueConverterTe var urlSegment = "url-segment"; ConfigurePublishedContentMock(content, key, name, urlSegment, _contentType, properties); - RegisterContentWithProviders(content.Object); + RegisterContentWithProviders(content.Object, false); } protected void SetupMediaMock(Mock media, params IPublishedProperty[] properties) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/PropertyValueConverterTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/PropertyValueConverterTests.cs index 59238902f7..23dbd79d9c 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/PropertyValueConverterTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/PropertyValueConverterTests.cs @@ -23,6 +23,8 @@ public class PropertyValueConverterTests : DeliveryApiTests protected IPublishedContent PublishedMedia { get; private set; } + protected IPublishedContent DraftContent { get; private set; } + protected IPublishedContentType PublishedContentType { get; private set; } protected IPublishedContentType PublishedMediaType { get; private set; } @@ -59,10 +61,21 @@ public class PropertyValueConverterTests : DeliveryApiTests var publishedMedia = SetupPublishedContent("The media", mediaKey, PublishedItemType.Media, publishedMediaType.Object); PublishedMedia = publishedMedia.Object; + var draftContentKey = Guid.NewGuid(); + var draftContent = SetupPublishedContent("The page (draft)", draftContentKey, PublishedItemType.Content, publishedContentType.Object); + DraftContent = draftContent.Object; + PublishedContentCacheMock = new Mock(); PublishedContentCacheMock .Setup(pcc => pcc.GetById(contentKey)) .Returns(publishedContent.Object); + PublishedContentCacheMock + .Setup(pcc => pcc.GetById(false, contentKey)) + .Returns(publishedContent.Object); + PublishedContentCacheMock + .Setup(pcc => pcc.GetById(true, draftContentKey)) + .Returns(draftContent.Object); + PublishedMediaCacheMock = new Mock(); PublishedMediaCacheMock .Setup(pcc => pcc.GetById(mediaKey)) @@ -77,6 +90,9 @@ public class PropertyValueConverterTests : DeliveryApiTests PublishedUrlProviderMock .Setup(p => p.GetUrl(publishedContent.Object, It.IsAny(), It.IsAny(), It.IsAny())) .Returns("the-page-url"); + PublishedUrlProviderMock + .Setup(p => p.GetUrl(draftContent.Object, It.IsAny(), It.IsAny(), It.IsAny())) + .Returns("the-draft-page-url"); PublishedUrlProviderMock .Setup(p => p.GetMediaUrl(publishedMedia.Object, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns("the-media-url"); @@ -97,14 +113,20 @@ public class PropertyValueConverterTests : DeliveryApiTests return content; } - protected void RegisterContentWithProviders(IPublishedContent content) + protected void RegisterContentWithProviders(IPublishedContent content, bool preview) { PublishedUrlProviderMock .Setup(p => p.GetUrl(content, It.IsAny(), It.IsAny(), It.IsAny())) .Returns(content.UrlSegment); PublishedContentCacheMock - .Setup(pcc => pcc.GetById(content.Key)) + .Setup(pcc => pcc.GetById(preview, content.Key)) .Returns(content); + if (preview is false) + { + PublishedContentCacheMock + .Setup(pcc => pcc.GetById(content.Key)) + .Returns(content); + } } protected void RegisterMediaWithProviders(IPublishedContent media) From 4c87cc5fd9aadb964792bcfa0e4f29eb80d7d6c2 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 7 Jul 2025 12:59:55 +0200 Subject: [PATCH 80/82] Adds support for custom granular permissions when aggregating across user groups (#19660) * Added abstraction for aggregation of granular permissions to support custom permissions. * Refactor to move responsibility for aggregating granular permissions to the respective mappers. * Added XML header comments for permission mappers. * Tidied up/removed warnings in UserPresentationFactory interface and implementation. * Optimized retrieval of documents in DocumentPermissionMapper. * Fixed method header comment. * Use entity service rather than content service to retrieve key and path. --- .../Factories/IUserPresentationFactory.cs | 33 +++ .../Factories/UserPresentationFactory.cs | 188 +++++++++++------- .../Permissions/DocumentPermissionMapper.cs | 73 ++++++- .../DocumentPropertyValuePermissionMapper.cs | 34 +++- .../IPermissionPresentationMapper.cs | 21 ++ .../Factories/UserPresentationFactoryTests.cs | 167 +++++++++++++++- 6 files changed, 437 insertions(+), 79 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Factories/IUserPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IUserPresentationFactory.cs index ac33b042eb..9ae171b02e 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/IUserPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/IUserPresentationFactory.cs @@ -6,25 +6,58 @@ using Umbraco.Cms.Core.Models.Membership; namespace Umbraco.Cms.Api.Management.Factories; +/// +/// Defines factory methods for the creation of user presentation models. +/// public interface IUserPresentationFactory { + /// + /// Creates a response model for the provided user. + /// UserResponseModel CreateResponseModel(IUser user); + /// + /// Creates a create model for a user based on the provided request model. + /// Task CreateCreationModelAsync(CreateUserRequestModel requestModel); + /// + /// Creates an invite model for a user based on the provided request model. + /// Task CreateInviteModelAsync(InviteUserRequestModel requestModel); + /// + /// Creates an update model for an existing user based on the provided request model. + /// Task CreateUpdateModelAsync(Guid existingUserKey, UpdateUserRequestModel updateModel); + /// + /// Creates a response model for the current user based on the provided user. + /// Task CreateCurrentUserResponseModelAsync(IUser user); + /// + /// Creates an resend invite model for a user based on the provided request model. + /// Task CreateResendInviteModelAsync(ResendInviteUserRequestModel requestModel); + /// + /// Creates a user configuration model that contains the necessary data for user management operations. + /// Task CreateUserConfigurationModelAsync(); + /// + /// Creates a current user configuration model that contains the necessary data for the current user's management operations. + /// Task CreateCurrentUserConfigurationModelAsync(); + /// + /// Creates a user item response model for the provided user. + /// UserItemResponseModel CreateItemResponseModel(IUser user); + /// + /// Creates a calculated user start nodes response model based on the provided user. + /// Task CreateCalculatedUserStartNodesResponseModelAsync(IUser user); } diff --git a/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs index 4d7ed3c853..80c7b17e78 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Umbraco.Cms.Api.Management.Mapping.Permissions; using Umbraco.Cms.Api.Management.Routing; using Umbraco.Cms.Api.Management.Security; using Umbraco.Cms.Api.Management.ViewModels; @@ -22,6 +23,9 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Api.Management.Factories; +/// +/// Factory for creating user presentation models, implementing . +/// public class UserPresentationFactory : IUserPresentationFactory { private readonly IEntityService _entityService; @@ -34,9 +38,11 @@ public class UserPresentationFactory : IUserPresentationFactory private readonly IPasswordConfigurationPresentationFactory _passwordConfigurationPresentationFactory; private readonly IBackOfficeExternalLoginProviders _externalLoginProviders; private readonly SecuritySettings _securitySettings; - private readonly IUserService _userService; - private readonly IContentService _contentService; + private readonly Dictionary _permissionPresentationMappersByType; + /// + /// Initializes a new instance of the class. + /// [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")] public UserPresentationFactory( IEntityService entityService, @@ -50,7 +56,7 @@ public class UserPresentationFactory : IUserPresentationFactory IOptionsSnapshot securitySettings, IBackOfficeExternalLoginProviders externalLoginProviders) : this( - entityService, + entityService, appCaches, mediaFileManager, imageUrlGenerator, @@ -61,10 +67,15 @@ public class UserPresentationFactory : IUserPresentationFactory securitySettings, externalLoginProviders, StaticServiceProvider.Instance.GetRequiredService(), - StaticServiceProvider.Instance.GetRequiredService()) + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService>()) { } + /// + /// Initializes a new instance of the class. + /// + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")] public UserPresentationFactory( IEntityService entityService, AppCaches appCaches, @@ -78,6 +89,44 @@ public class UserPresentationFactory : IUserPresentationFactory IBackOfficeExternalLoginProviders externalLoginProviders, IUserService userService, IContentService contentService) + : this( + entityService, + appCaches, + mediaFileManager, + imageUrlGenerator, + userGroupPresentationFactory, + absoluteUrlBuilder, + emailSender, + passwordConfigurationPresentationFactory, + securitySettings, + externalLoginProviders, + userService, + contentService, + StaticServiceProvider.Instance.GetRequiredService>()) + { + } + + // TODO (V17): Remove the unused userService and contentService parameters from this constructor. + + /// + /// Initializes a new instance of the class. + /// + public UserPresentationFactory( + IEntityService entityService, + AppCaches appCaches, + MediaFileManager mediaFileManager, + IImageUrlGenerator imageUrlGenerator, + IUserGroupPresentationFactory userGroupPresentationFactory, + IAbsoluteUrlBuilder absoluteUrlBuilder, + IEmailSender emailSender, + IPasswordConfigurationPresentationFactory passwordConfigurationPresentationFactory, + IOptionsSnapshot securitySettings, + IBackOfficeExternalLoginProviders externalLoginProviders, +#pragma warning disable IDE0060 // Remove unused parameter - need to keep these until the next major to avoid breaking changes and/or ambiguous constructor errors + IUserService userService, + IContentService contentService, +#pragma warning restore IDE0060 // Remove unused parameter + IEnumerable permissionPresentationMappers) { _entityService = entityService; _appCaches = appCaches; @@ -89,10 +138,10 @@ public class UserPresentationFactory : IUserPresentationFactory _externalLoginProviders = externalLoginProviders; _securitySettings = securitySettings.Value; _absoluteUrlBuilder = absoluteUrlBuilder; - _userService = userService; - _contentService = contentService; + _permissionPresentationMappersByType = permissionPresentationMappers.ToDictionary(x => x.PresentationModelToHandle); } + /// public UserResponseModel CreateResponseModel(IUser user) { var responseModel = new UserResponseModel @@ -123,6 +172,7 @@ public class UserPresentationFactory : IUserPresentationFactory return responseModel; } + /// public UserItemResponseModel CreateItemResponseModel(IUser user) => new() { @@ -130,9 +180,10 @@ public class UserPresentationFactory : IUserPresentationFactory Name = user.Name ?? user.Username, AvatarUrls = user.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileManager, _imageUrlGenerator) .Select(url => _absoluteUrlBuilder.ToAbsoluteUrl(url).ToString()), - Kind = user.Kind + Kind = user.Kind, }; + /// public Task CreateCreationModelAsync(CreateUserRequestModel requestModel) { var createModel = new UserCreateModel @@ -142,12 +193,13 @@ public class UserPresentationFactory : IUserPresentationFactory Name = requestModel.Name, UserName = requestModel.UserName, UserGroupKeys = requestModel.UserGroupIds.Select(x => x.Id).ToHashSet(), - Kind = requestModel.Kind + Kind = requestModel.Kind, }; return Task.FromResult(createModel); } + /// public Task CreateInviteModelAsync(InviteUserRequestModel requestModel) { var inviteModel = new UserInviteModel @@ -162,6 +214,7 @@ public class UserPresentationFactory : IUserPresentationFactory return Task.FromResult(inviteModel); } + /// public Task CreateResendInviteModelAsync(ResendInviteUserRequestModel requestModel) { var inviteModel = new UserResendInviteModel @@ -173,6 +226,7 @@ public class UserPresentationFactory : IUserPresentationFactory return Task.FromResult(inviteModel); } + /// public Task CreateCurrentUserConfigurationModelAsync() { var model = new CurrentUserConfigurationResponseModel @@ -188,6 +242,7 @@ public class UserPresentationFactory : IUserPresentationFactory return Task.FromResult(model); } + /// public Task CreateUserConfigurationModelAsync() => Task.FromResult(new UserConfigurationResponseModel { @@ -201,6 +256,7 @@ public class UserPresentationFactory : IUserPresentationFactory AllowTwoFactor = _externalLoginProviders.HasDenyLocalLogin() is false, }); + /// public Task CreateUpdateModelAsync(Guid existingUserKey, UpdateUserRequestModel updateModel) { var model = new UserUpdateModel @@ -214,24 +270,24 @@ public class UserPresentationFactory : IUserPresentationFactory HasContentRootAccess = updateModel.HasDocumentRootAccess, MediaStartNodeKeys = updateModel.MediaStartNodeIds.Select(x => x.Id).ToHashSet(), HasMediaRootAccess = updateModel.HasMediaRootAccess, + UserGroupKeys = updateModel.UserGroupIds.Select(x => x.Id).ToHashSet() }; - model.UserGroupKeys = updateModel.UserGroupIds.Select(x => x.Id).ToHashSet(); - return Task.FromResult(model); } + /// public async Task CreateCurrentUserResponseModelAsync(IUser user) { - var presentationUser = CreateResponseModel(user); - var presentationGroups = await _userGroupPresentationFactory.CreateMultipleAsync(user.Groups); + UserResponseModel presentationUser = CreateResponseModel(user); + IEnumerable presentationGroups = await _userGroupPresentationFactory.CreateMultipleAsync(user.Groups); var languages = presentationGroups.SelectMany(x => x.Languages).Distinct().ToArray(); var mediaStartNodeIds = user.CalculateMediaStartNodeIds(_entityService, _appCaches); - var mediaStartNodeKeys = GetKeysFromIds(mediaStartNodeIds, UmbracoObjectTypes.Media); + ISet mediaStartNodeKeys = GetKeysFromIds(mediaStartNodeIds, UmbracoObjectTypes.Media); var contentStartNodeIds = user.CalculateContentStartNodeIds(_entityService, _appCaches); - var documentStartNodeKeys = GetKeysFromIds(contentStartNodeIds, UmbracoObjectTypes.Document); + ISet documentStartNodeKeys = GetKeysFromIds(contentStartNodeIds, UmbracoObjectTypes.Document); - var permissions = GetAggregatedGranularPermissions(user, presentationGroups); + HashSet permissions = GetAggregatedGranularPermissions(user, presentationGroups); var fallbackPermissions = presentationGroups.SelectMany(x => x.FallbackPermissions).ToHashSet(); var hasAccessToAllLanguages = presentationGroups.Any(x => x.HasAccessToAllLanguages); @@ -263,70 +319,56 @@ public class UserPresentationFactory : IUserPresentationFactory private HashSet GetAggregatedGranularPermissions(IUser user, IEnumerable presentationGroups) { - var aggregatedPermissions = new HashSet(); - var permissions = presentationGroups.SelectMany(x => x.Permissions).ToHashSet(); + return GetAggregatedGranularPermissions(user, permissions); + } - AggregateAndAddDocumentPermissions(user, aggregatedPermissions, permissions); + private HashSet GetAggregatedGranularPermissions(IUser user, HashSet permissions) + { + // The raw permission data consists of several permissions for each entity (e.g. document), as permissions are assigned to user groups + // and a user may be part of multiple groups. We want to aggregate this server-side so we return one set of aggregate permissions per + // entity that the client will use. + // We need to handle here not just permissions known to core (e.g. document and document property value permissions), but also custom + // permissions defined by packages or implemetors. + IEnumerable<(Type, IEnumerable)> permissionModelsByType = permissions + .GroupBy(x => x.GetType()) + .Select(x => (x.Key, x.Select(y => y))); - AggregateAndAddDocumentPropertyValuePermissions(aggregatedPermissions, permissions); + var aggregatedPermissions = new HashSet(); + foreach ((Type Type, IEnumerable Models) permissionModelByType in permissionModelsByType) + { + if (_permissionPresentationMappersByType.TryGetValue(permissionModelByType.Type, out IPermissionPresentationMapper? mapper)) + { + + IEnumerable aggregatedModels = mapper.AggregatePresentationModels(user, permissionModelByType.Models); + foreach (IPermissionPresentationModel aggregatedModel in aggregatedModels) + { + aggregatedPermissions.Add(aggregatedModel); + } + } + else + { + IEnumerable<(string Context, ISet Verbs)> groupedModels = permissionModelByType.Models + .Where(x => x is UnknownTypePermissionPresentationModel) + .Cast() + .GroupBy(x => x.Context) + .Select(x => (x.Key, (ISet)x.SelectMany(y => y.Verbs).Distinct().ToHashSet())); + + foreach ((string context, ISet verbs) in groupedModels) + { + aggregatedPermissions.Add(new UnknownTypePermissionPresentationModel + { + Context = context, + Verbs = verbs + }); + } + } + } return aggregatedPermissions; } - private void AggregateAndAddDocumentPermissions(IUser user, HashSet aggregatedPermissions, HashSet permissions) - { - // The raw permission data consists of several permissions for each document. We want to aggregate this server-side so - // we return one set of aggregate permissions per document that the client will use. - - // Get the unique document keys that have granular permissions. - IEnumerable documentKeysWithGranularPermissions = permissions - .Where(x => x is DocumentPermissionPresentationModel) - .Cast() - .Select(x => x.Document.Id) - .Distinct(); - - foreach (Guid documentKey in documentKeysWithGranularPermissions) - { - // Retrieve the path of the document. - var path = _contentService.GetById(documentKey)?.Path; - if (string.IsNullOrEmpty(path)) - { - continue; - } - - // With the path we can call the same logic as used server-side for authorizing access to resources. - EntityPermissionSet permissionsForPath = _userService.GetPermissionsForPath(user, path); - aggregatedPermissions.Add(new DocumentPermissionPresentationModel - { - Document = new ReferenceByIdModel(documentKey), - Verbs = permissionsForPath.GetAllPermissions() - }); - } - } - - private static void AggregateAndAddDocumentPropertyValuePermissions(HashSet aggregatedPermissions, HashSet permissions) - { - // We also have permissions for document type/property type combinations. - // These don't have an ancestor relationship that we need to take into account, but should be aggregated - // and included in the set. - IEnumerable<((Guid DocumentTypeId, Guid PropertyTypeId) Key, ISet Verbs)> documentTypePropertyTypeKeysWithGranularPermissions = permissions - .Where(x => x is DocumentPropertyValuePermissionPresentationModel) - .Cast() - .GroupBy(x => (x.DocumentType.Id, x.PropertyType.Id)) - .Select(x => (x.Key, (ISet)x.SelectMany(y => y.Verbs).Distinct().ToHashSet())); - - foreach (((Guid DocumentTypeId, Guid PropertyTypeId) Key, ISet Verbs) documentTypePropertyTypeKey in documentTypePropertyTypeKeysWithGranularPermissions) - { - aggregatedPermissions.Add(new DocumentPropertyValuePermissionPresentationModel - { - DocumentType = new ReferenceByIdModel(documentTypePropertyTypeKey.Key.DocumentTypeId), - PropertyType = new ReferenceByIdModel(documentTypePropertyTypeKey.Key.PropertyTypeId), - Verbs = documentTypePropertyTypeKey.Verbs - }); - } - } - + /// public Task CreateCalculatedUserStartNodesResponseModelAsync(IUser user) { var mediaStartNodeIds = user.CalculateMediaStartNodeIds(_entityService, _appCaches); @@ -357,6 +399,6 @@ public class UserPresentationFactory : IUserPresentationFactory : new HashSet(models); } - private bool HasRootAccess(IEnumerable? startNodeIds) + private static bool HasRootAccess(IEnumerable? startNodeIds) => startNodeIds?.Contains(Constants.System.Root) is true; } diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Permissions/DocumentPermissionMapper.cs b/src/Umbraco.Cms.Api.Management/Mapping/Permissions/DocumentPermissionMapper.cs index 55c66221fc..abab7a7fab 100644 --- a/src/Umbraco.Cms.Api.Management/Mapping/Permissions/DocumentPermissionMapper.cs +++ b/src/Umbraco.Cms.Api.Management/Mapping/Permissions/DocumentPermissionMapper.cs @@ -1,19 +1,50 @@ +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.ViewModels; using Umbraco.Cms.Api.Management.ViewModels.UserGroup.Permissions; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Models.Membership.Permissions; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Mappers; namespace Umbraco.Cms.Api.Management.Mapping.Permissions; + /// -/// Mapping required for mapping all the way from viewmodel to database and back. +/// Implements for document permissions. /// /// /// This mapping maps all the way from management api to database in one file intentionally, so it is very clear what it takes, if we wanna add permissions to media or other types in the future. /// public class DocumentPermissionMapper : IPermissionPresentationMapper, IPermissionMapper { + private readonly Lazy _entityService; + private readonly Lazy _userService; + + /// + /// Initializes a new instance of the class. + /// + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")] + public DocumentPermissionMapper() + : this( + StaticServiceProvider.Instance.GetRequiredService>(), + StaticServiceProvider.Instance.GetRequiredService>()) + { + } + + /// + /// Initializes a new instance of the class. + /// + public DocumentPermissionMapper(Lazy entityService, Lazy userService) + { + _entityService = entityService; + _userService = userService; + } + + /// public string Context => DocumentGranularPermission.ContextType; + public IGranularPermission MapFromDto(UserGroup2GranularPermissionDto dto) => new DocumentGranularPermission() { @@ -21,8 +52,10 @@ public class DocumentPermissionMapper : IPermissionPresentationMapper, IPermissi Permission = dto.Permission, }; + /// public Type PresentationModelToHandle => typeof(DocumentPermissionPresentationModel); + /// public IEnumerable MapManyAsync(IEnumerable granularPermissions) { IEnumerable> keyGroups = granularPermissions.GroupBy(x => x.Key); @@ -40,6 +73,7 @@ public class DocumentPermissionMapper : IPermissionPresentationMapper, IPermissi } } + /// public IEnumerable MapToGranularPermissions(IPermissionPresentationModel permissionViewModel) { if (permissionViewModel is not DocumentPermissionPresentationModel documentPermissionPresentationModel) @@ -47,7 +81,7 @@ public class DocumentPermissionMapper : IPermissionPresentationMapper, IPermissi yield break; } - if(documentPermissionPresentationModel.Verbs.Any() is false || (documentPermissionPresentationModel.Verbs.Count == 1 && documentPermissionPresentationModel.Verbs.Contains(string.Empty))) + if (documentPermissionPresentationModel.Verbs.Any() is false || (documentPermissionPresentationModel.Verbs.Count == 1 && documentPermissionPresentationModel.Verbs.Contains(string.Empty))) { yield return new DocumentGranularPermission { @@ -56,12 +90,14 @@ public class DocumentPermissionMapper : IPermissionPresentationMapper, IPermissi }; yield break; } + foreach (var verb in documentPermissionPresentationModel.Verbs) { if (string.IsNullOrEmpty(verb)) { continue; } + yield return new DocumentGranularPermission { Key = documentPermissionPresentationModel.Document.Id, @@ -69,4 +105,37 @@ public class DocumentPermissionMapper : IPermissionPresentationMapper, IPermissi }; } } + + /// + public IEnumerable AggregatePresentationModels(IUser user, IEnumerable models) + { + // Get the unique document keys that have granular permissions. + Guid[] documentKeysWithGranularPermissions = models + .Cast() + .Select(x => x.Document.Id) + .Distinct() + .ToArray(); + + // Batch retrieve all documents by their keys. + var documents = _entityService.Value.GetAll(documentKeysWithGranularPermissions) + .ToDictionary(doc => doc.Key, doc => doc.Path); + + // Iterate through each document key that has granular permissions. + foreach (Guid documentKey in documentKeysWithGranularPermissions) + { + // Retrieve the path from the pre-fetched documents. + if (!documents.TryGetValue(documentKey, out var path) || string.IsNullOrEmpty(path)) + { + continue; + } + + // With the path we can call the same logic as used server-side for authorizing access to resources. + EntityPermissionSet permissionsForPath = _userService.Value.GetPermissionsForPath(user, path); + yield return new DocumentPermissionPresentationModel + { + Document = new ReferenceByIdModel(documentKey), + Verbs = permissionsForPath.GetAllPermissions(), + }; + } + } } diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Permissions/DocumentPropertyValuePermissionMapper.cs b/src/Umbraco.Cms.Api.Management/Mapping/Permissions/DocumentPropertyValuePermissionMapper.cs index ed0964abda..2de518f0ba 100644 --- a/src/Umbraco.Cms.Api.Management/Mapping/Permissions/DocumentPropertyValuePermissionMapper.cs +++ b/src/Umbraco.Cms.Api.Management/Mapping/Permissions/DocumentPropertyValuePermissionMapper.cs @@ -1,5 +1,6 @@ -using Umbraco.Cms.Api.Management.ViewModels; +using Umbraco.Cms.Api.Management.ViewModels; using Umbraco.Cms.Api.Management.ViewModels.UserGroup.Permissions; +using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Models.Membership.Permissions; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Mappers; @@ -7,10 +8,18 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Api.Management.Mapping.Permissions; +/// +/// Implements for document property value permissions. +/// public class DocumentPropertyValuePermissionMapper : IPermissionPresentationMapper, IPermissionMapper { + /// public string Context => DocumentPropertyValueGranularPermission.ContextType; + /// + public Type PresentationModelToHandle => typeof(DocumentPropertyValuePermissionPresentationModel); + + /// public IGranularPermission MapFromDto(UserGroup2GranularPermissionDto dto) => new DocumentPropertyValueGranularPermission() { @@ -18,8 +27,7 @@ public class DocumentPropertyValuePermissionMapper : IPermissionPresentationMapp Permission = dto.Permission, }; - public Type PresentationModelToHandle => typeof(DocumentPropertyValuePermissionPresentationModel); - + /// public IEnumerable MapManyAsync(IEnumerable granularPermissions) { var intermediate = granularPermissions.Where(p => p.Key.HasValue).Select(p => @@ -50,6 +58,7 @@ public class DocumentPropertyValuePermissionMapper : IPermissionPresentationMapp } } + /// public IEnumerable MapToGranularPermissions(IPermissionPresentationModel permissionViewModel) { if (permissionViewModel is not DocumentPropertyValuePermissionPresentationModel documentTypePermissionPresentationModel) @@ -66,4 +75,23 @@ public class DocumentPropertyValuePermissionMapper : IPermissionPresentationMapp }; } } + + /// + public IEnumerable AggregatePresentationModels(IUser user, IEnumerable models) + { + IEnumerable<((Guid DocumentTypeId, Guid PropertyTypeId) Key, ISet Verbs)> groupedModels = models + .Cast() + .GroupBy(x => (x.DocumentType.Id, x.PropertyType.Id)) + .Select(x => (x.Key, (ISet)x.SelectMany(y => y.Verbs).Distinct().ToHashSet())); + + foreach (((Guid DocumentTypeId, Guid PropertyTypeId) key, ISet verbs) in groupedModels) + { + yield return new DocumentPropertyValuePermissionPresentationModel + { + DocumentType = new ReferenceByIdModel(key.DocumentTypeId), + PropertyType = new ReferenceByIdModel(key.PropertyTypeId), + Verbs = verbs + }; + } + } } diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Permissions/IPermissionPresentationMapper.cs b/src/Umbraco.Cms.Api.Management/Mapping/Permissions/IPermissionPresentationMapper.cs index 8ba261ce6e..8860f187dc 100644 --- a/src/Umbraco.Cms.Api.Management/Mapping/Permissions/IPermissionPresentationMapper.cs +++ b/src/Umbraco.Cms.Api.Management/Mapping/Permissions/IPermissionPresentationMapper.cs @@ -1,15 +1,36 @@ using Umbraco.Cms.Api.Management.ViewModels.UserGroup.Permissions; +using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Models.Membership.Permissions; namespace Umbraco.Cms.Api.Management.Mapping.Permissions; +/// +/// Defines methods for mapping and aggregating granular permissions to presentation models. +/// public interface IPermissionPresentationMapper { + /// + /// Gets the context type for the permissions being handled by this mapper. + /// string Context { get; } + /// + /// Gets the type of the presentation model that this mapper handles. + /// Type PresentationModelToHandle { get; } + /// + /// Maps a granular permission entity to a granular permission model. + /// IEnumerable MapManyAsync(IEnumerable granularPermissions); + /// + /// Maps a granular permission to a granular permission model. + /// IEnumerable MapToGranularPermissions(IPermissionPresentationModel permissionViewModel); + + /// + /// Aggregates multiple permission presentation models into a collection containing only one item per entity with aggregated permissions. + /// + IEnumerable AggregatePresentationModels(IUser user, IEnumerable models) => []; } diff --git a/tests/Umbraco.Tests.Integration/ManagementApi/Factories/UserPresentationFactoryTests.cs b/tests/Umbraco.Tests.Integration/ManagementApi/Factories/UserPresentationFactoryTests.cs index 80277ab259..f9d103f1e4 100644 --- a/tests/Umbraco.Tests.Integration/ManagementApi/Factories/UserPresentationFactoryTests.cs +++ b/tests/Umbraco.Tests.Integration/ManagementApi/Factories/UserPresentationFactoryTests.cs @@ -10,6 +10,7 @@ using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Models.Membership.Permissions; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Mappers; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; @@ -46,6 +47,10 @@ public class UserPresentationFactoryTests : UmbracoIntegrationTestWithContent services.AddSingleton(); services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + } [Test] @@ -254,12 +259,172 @@ public class UserPresentationFactoryTests : UmbracoIntegrationTestWithContent Assert.IsTrue(propertyTypePermission2.Verbs.ContainsAll(["D"])); } + [Test] + public async Task Can_Create_Current_User_Response_Model_With_Aggregated_Custom_Permissions() + { + var key1 = Guid.NewGuid(); + var key2 = Guid.NewGuid(); + var groupOne = await CreateUserGroup( + "Group One", + "groupOne", + [], + [], + [ + new CustomGranularPermission + { + Permission = $"{key1}|A", + }, + new CustomGranularPermission + { + Permission = $"{key1}|B", + }, + new CustomGranularPermission + { + Permission = $"{key2}|C", + } + ], + Constants.System.Root); + var groupTwo = await CreateUserGroup( + "Group Two", + "groupTwo", + [], + [], + [ + new CustomGranularPermission + { + Permission = $"{key1}|A", + }, + new CustomGranularPermission + { + Permission = $"{key2}|B", + }, + ], + Constants.System.Root); + var user = await CreateUser([groupOne.Key, groupTwo.Key]); + + var model = await UserPresentationFactory.CreateCurrentUserResponseModelAsync(user); + Assert.AreEqual(2, model.Permissions.Count); + + var customPermissions = model.Permissions + .Where(x => x is CustomPermissionPresentationModel) + .Cast(); + Assert.AreEqual(2, customPermissions.Count()); + + var customPermission1 = customPermissions + .Single(x => x.Key == key1); + Assert.AreEqual(2, customPermission1.Verbs.Count); + Assert.IsTrue(customPermission1.Verbs.ContainsAll(["A", "B"])); + + var customPermission2 = customPermissions + .Single(x => x.Key == key2); + Assert.AreEqual(2, customPermission2.Verbs.Count); + Assert.IsTrue(customPermission2.Verbs.ContainsAll(["B", "C"])); + } + + private class CustomGranularPermission : IGranularPermission + { + public const string ContextType = "Custom"; + + public string Context => ContextType; + + public required string Permission { get; set; } + + protected bool Equals(CustomGranularPermission other) => Permission == other.Permission; + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((CustomGranularPermission)obj); + } + + public override int GetHashCode() => HashCode.Combine(Permission); + } + + private class CustomPermissionPresentationModel : IPermissionPresentationModel + { + public required ISet Verbs { get; set; } + + public required Guid Key { get; set; } + } + + private class CustomPermissionMapper : IPermissionMapper, IPermissionPresentationMapper + { + public string Context => CustomGranularPermission.ContextType; + + public Type PresentationModelToHandle => typeof(CustomPermissionPresentationModel); + + public IGranularPermission MapFromDto(UserGroup2GranularPermissionDto dto) + { + return new CustomGranularPermission + { + Permission = dto.Permission, + }; + } + + public IEnumerable MapManyAsync(IEnumerable granularPermissions) + => granularPermissions + .Where(x => x is CustomGranularPermission) + .Cast() + .Select(x => new CustomPermissionPresentationModel + { + Key = Guid.Parse(x.Permission.Split('|')[0]), + Verbs = new HashSet { x.Permission.Split('|')[1] }, + }); + + public IEnumerable MapToGranularPermissions(IPermissionPresentationModel permissionViewModel) + { + if (permissionViewModel is not CustomPermissionPresentationModel customPermissionPresentationModel) + { + yield break; + } + + foreach (var verb in customPermissionPresentationModel.Verbs.Distinct().DefaultIfEmpty(string.Empty)) + { + yield return new CustomGranularPermission + { + Permission = customPermissionPresentationModel.Key + "|" + verb, + }; + } + } + + public IEnumerable AggregatePresentationModels(IUser user, IEnumerable models) + { + IEnumerable<(Guid Key, ISet Verbs)> groupedModels = models + .Cast() + .GroupBy(x => x.Key) + .Select(x => (x.Key, (ISet)x.SelectMany(y => y.Verbs).Distinct().ToHashSet())); + + foreach ((Guid key, ISet verbs) in groupedModels) + { + yield return new CustomPermissionPresentationModel + { + Key = key, + Verbs = verbs, + }; + } + } + } + private async Task CreateUserGroup( string name, string alias, int[] allowedLanguages, string[] permissions, - INodeGranularPermission[] granularPermissions, + IGranularPermission[] granularPermissions, int startMediaId) { var userGroup = new UserGroupBuilder() From b5195ed8eb40acf9c74ebd5aadcd837d0aa34c86 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 7 Jul 2025 14:15:17 +0200 Subject: [PATCH 81/82] Fixes issues with creation of documents from blueprints that have populated file upload properties (#19655) * Fixes issue where content created from blueprint would not persist file upload property values. * Ensure a copy of a file upload is created when scaffolding content from a blueprint, like we do when copying content. * Clarified comment. * Removed unneeded usings. * Fixed spelling. * Handle create of blueprint from content to create a new uploaded file. Handle delete of blueprint to delete uploaded files. --- .../ContentSavedBlueprintNotification.cs | 14 ++ .../ContentBlueprintEditingService.cs | 6 +- src/Umbraco.Core/Services/ContentService.cs | 5 +- src/Umbraco.Core/Services/IContentService.cs | 9 + .../UmbracoBuilder.CoreServices.cs | 9 +- .../MoveDocumentBlueprintsToFolders.cs | 4 +- .../FileUploadPropertyValueEditor.cs | 7 +- ...tCopiedOrScaffoldedNotificationHandler.cs} | 101 ++++---- ...UploadContentDeletedNotificationHandler.cs | 213 ++++++++++++++++- ...oadEntityDeletedNotificationHandlerBase.cs | 218 ------------------ ...leUploadMediaDeletedNotificationHandler.cs | 29 --- ...eUploadMemberDeletedNotificationHandler.cs | 29 --- .../Services/ContentServiceTests.cs | 8 +- .../Services/TelemetryProviderTests.cs | 2 +- 14 files changed, 314 insertions(+), 340 deletions(-) rename src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/{FileUploadContentCopiedNotificationHandler.cs => FileUploadContentCopiedOrScaffoldedNotificationHandler.cs} (67%) delete mode 100644 src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadEntityDeletedNotificationHandlerBase.cs delete mode 100644 src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMediaDeletedNotificationHandler.cs delete mode 100644 src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMemberDeletedNotificationHandler.cs diff --git a/src/Umbraco.Core/Notifications/ContentSavedBlueprintNotification.cs b/src/Umbraco.Core/Notifications/ContentSavedBlueprintNotification.cs index 9219b89f23..897065a532 100644 --- a/src/Umbraco.Core/Notifications/ContentSavedBlueprintNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentSavedBlueprintNotification.cs @@ -5,6 +5,7 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Notifications; + /// /// A notification that is used to trigger the IContentService when the SavedBlueprint method is called in the API. /// @@ -14,8 +15,21 @@ public sealed class ContentSavedBlueprintNotification : ObjectNotification /// Getting the saved blueprint object. /// public IContent SavedBlueprint => Target; + + /// + /// Getting the saved blueprint object. + /// + public IContent? CreatedFromContent { get; } + } diff --git a/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs b/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs index 5e8357e453..d2e68ad327 100644 --- a/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs +++ b/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs @@ -122,7 +122,7 @@ internal sealed class ContentBlueprintEditingService } // Save blueprint - await SaveAsync(blueprint, userKey); + await SaveAsync(blueprint, userKey, content); return Attempt.SucceedWithStatus(ContentEditingOperationStatus.Success, new ContentCreateResult { Content = blueprint }); } @@ -240,10 +240,10 @@ internal sealed class ContentBlueprintEditingService protected override OperationResult? Delete(IContent content, int userId) => throw new NotImplementedException(); - private async Task SaveAsync(IContent blueprint, Guid userKey) + private async Task SaveAsync(IContent blueprint, Guid userKey, IContent? createdFromContent = null) { var currentUserId = await GetUserIdAsync(userKey); - ContentService.SaveBlueprint(blueprint, currentUserId); + ContentService.SaveBlueprint(blueprint, createdFromContent, currentUserId); } private bool ValidateUniqueName(string name, IContent content) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 34ac8db8ff..0c3b61a1c3 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -3611,6 +3611,9 @@ public class ContentService : RepositoryService, IContentService } public void SaveBlueprint(IContent content, int userId = Constants.Security.SuperUserId) + => SaveBlueprint(content, null, userId); + + public void SaveBlueprint(IContent content, IContent? createdFromContent, int userId = Constants.Security.SuperUserId) { EventMessages evtMsgs = EventMessagesFactory.Get(); @@ -3631,7 +3634,7 @@ public class ContentService : RepositoryService, IContentService Audit(AuditType.Save, userId, content.Id, $"Saved content template: {content.Name}"); - scope.Notifications.Publish(new ContentSavedBlueprintNotification(content, evtMsgs)); + scope.Notifications.Publish(new ContentSavedBlueprintNotification(content, createdFromContent, evtMsgs)); scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshNode, evtMsgs)); scope.Complete(); diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 423f157874..a7bde2dc46 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -47,8 +47,17 @@ public interface IContentService : IContentServiceBase /// /// Saves a blueprint. /// + [Obsolete("Please use the method taking all parameters. Scheduled for removal in Umbraco 18.")] void SaveBlueprint(IContent content, int userId = Constants.Security.SuperUserId); + /// + /// Saves a blueprint. + /// + void SaveBlueprint(IContent content, IContent? createdFromContent, int userId = Constants.Security.SuperUserId) +#pragma warning disable CS0618 // Type or member is obsolete + => SaveBlueprint(content, userId); +#pragma warning restore CS0618 // Type or member is obsolete + /// /// Deletes a blueprint. /// diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index d0c68d5aa5..66b3687cc2 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -356,11 +356,14 @@ public static partial class UmbracoBuilderExtensions .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler() - .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() .AddNotificationHandler() - .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() .AddNotificationHandler() - .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler() diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/MoveDocumentBlueprintsToFolders.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/MoveDocumentBlueprintsToFolders.cs index d09783c061..2c48b1e93b 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/MoveDocumentBlueprintsToFolders.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/MoveDocumentBlueprintsToFolders.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Services; @@ -63,7 +63,7 @@ public class MoveDocumentBlueprintsToFolders : MigrationBase } blueprint.ParentId = container.Id; - _contentService.SaveBlueprint(blueprint); + _contentService.SaveBlueprint(blueprint, null); } } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs index 22c7402b7b..241c817cec 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs @@ -92,13 +92,10 @@ internal class FileUploadPropertyValueEditor : DataValueEditor { FileUploadValue? editorModelValue = _valueParser.Parse(editorValue.Value); - // no change? + // No change or created from blueprint. if (editorModelValue?.TemporaryFileId.HasValue is not true && string.IsNullOrEmpty(editorModelValue?.Src) is false) { - // since current value can be json string, we have to parse value - FileUploadValue? currentModelValue = _valueParser.Parse(currentValue); - - return currentModelValue?.Src; + return editorModelValue.Src; } // the current editor value (if any) is the path to the file diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedNotificationHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedOrScaffoldedNotificationHandler.cs similarity index 67% rename from src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedNotificationHandler.cs rename to src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedOrScaffoldedNotificationHandler.cs index b056d31c79..2222b65b3f 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedNotificationHandler.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedOrScaffoldedNotificationHandler.cs @@ -14,24 +14,26 @@ using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Infrastructure.PropertyEditors.NotificationHandlers; /// -/// Implements a notification handler that processes file uploads when content is copied, making sure the copied contetnt relates to a new instance -/// of the file. +/// Implements a notification handler that processes file uploads when content is copied or scaffolded from a blueprint, making +/// sure the new content references a new instance of the file. /// -internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNotificationHandlerBase, INotificationHandler +internal sealed class FileUploadContentCopiedOrScaffoldedNotificationHandler : FileUploadNotificationHandlerBase, + INotificationHandler, + INotificationHandler, + INotificationHandler { private readonly IContentService _contentService; - private readonly BlockEditorValues _blockListEditorValues; private readonly BlockEditorValues _blockGridEditorValues; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public FileUploadContentCopiedNotificationHandler( + public FileUploadContentCopiedOrScaffoldedNotificationHandler( IJsonSerializer jsonSerializer, MediaFileManager mediaFileManager, IBlockEditorElementTypeCache elementTypeCache, - ILogger logger, + ILogger logger, IContentService contentService) : base(jsonSerializer, mediaFileManager, elementTypeCache) { @@ -41,51 +43,66 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot } /// - public void Handle(ContentCopiedNotification notification) - { - ArgumentNullException.ThrowIfNull(notification); + public void Handle(ContentCopiedNotification notification) => Handle(notification.Original, notification.Copy, (IContent c) => _contentService.Save(c)); + /// + public void Handle(ContentScaffoldedNotification notification) => Handle(notification.Original, notification.Scaffold); + + /// + public void Handle(ContentSavedBlueprintNotification notification) + { + if (notification.CreatedFromContent is null) + { + // If there is no original content, we don't need to copy files. + return; + } + + Handle(notification.CreatedFromContent, notification.SavedBlueprint, (IContent c) => _contentService.SaveBlueprint(c, null)); + } + + private void Handle(IContent source, IContent destination, Action? postUpdateAction = null) + { var isUpdated = false; - foreach (IProperty property in notification.Original.Properties) + foreach (IProperty property in source.Properties) { if (IsUploadFieldPropertyType(property.PropertyType)) { - isUpdated |= UpdateUploadFieldProperty(notification, property); + isUpdated |= UpdateUploadFieldProperty(destination, property); continue; } if (IsBlockListPropertyType(property.PropertyType)) { - isUpdated |= UpdateBlockProperty(notification, property, _blockListEditorValues); + isUpdated |= UpdateBlockProperty(destination, property, _blockListEditorValues); continue; } if (IsBlockGridPropertyType(property.PropertyType)) { - isUpdated |= UpdateBlockProperty(notification, property, _blockGridEditorValues); + isUpdated |= UpdateBlockProperty(destination, property, _blockGridEditorValues); continue; } if (IsRichTextPropertyType(property.PropertyType)) { - isUpdated |= UpdateRichTextProperty(notification, property); + isUpdated |= UpdateRichTextProperty(destination, property); continue; } } - // if updated, re-save the copy with the updated value - if (isUpdated) + // If updated, re-save the destination with the updated value. + if (isUpdated && postUpdateAction is not null) { - _contentService.Save(notification.Copy); + postUpdateAction(destination); } } - private bool UpdateUploadFieldProperty(ContentCopiedNotification notification, IProperty property) + private bool UpdateUploadFieldProperty(IContent content, IProperty property) { var isUpdated = false; @@ -98,9 +115,9 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot continue; } - var copyUrl = CopyFile(sourceUrl, notification.Copy, property.PropertyType); + var copyUrl = CopyFile(sourceUrl, content, property.PropertyType); - notification.Copy.SetValue(property.Alias, copyUrl, propertyValue.Culture, propertyValue.Segment); + content.SetValue(property.Alias, copyUrl, propertyValue.Culture, propertyValue.Segment); isUpdated = true; } @@ -108,7 +125,7 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot return isUpdated; } - private bool UpdateBlockProperty(ContentCopiedNotification notification, IProperty property, BlockEditorValues blockEditorValues) + private bool UpdateBlockProperty(IContent content, IProperty property, BlockEditorValues blockEditorValues) where TValue : BlockValue, new() where TLayout : class, IBlockLayoutItem, new() { @@ -120,11 +137,11 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot BlockEditorData? blockEditorData = GetBlockEditorData(rawBlockPropertyValue, blockEditorValues); - (bool hasUpdates, string? updatedValue) = UpdateBlockEditorData(notification, blockEditorData); + (bool hasUpdates, string? updatedValue) = UpdateBlockEditorData(content, blockEditorData); if (hasUpdates) { - notification.Copy.SetValue(property.Alias, updatedValue, blockPropertyValue.Culture, blockPropertyValue.Segment); + content.SetValue(property.Alias, updatedValue, blockPropertyValue.Culture, blockPropertyValue.Segment); } isUpdated |= hasUpdates; @@ -133,7 +150,7 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot return isUpdated; } - private (bool, string?) UpdateBlockEditorData(ContentCopiedNotification notification, BlockEditorData? blockEditorData) + private (bool, string?) UpdateBlockEditorData(IContent content, BlockEditorData? blockEditorData) where TValue : BlockValue, new() where TLayout : class, IBlockLayoutItem, new() { @@ -148,14 +165,14 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot .Concat(blockEditorData.BlockValue.SettingsData) .SelectMany(x => x.Values); - isUpdated = UpdateBlockPropertyValues(notification, isUpdated, blockPropertyValues); + isUpdated = UpdateBlockPropertyValues(content, isUpdated, blockPropertyValues); var updatedValue = JsonSerializer.Serialize(blockEditorData.BlockValue); return (isUpdated, updatedValue); } - private bool UpdateRichTextProperty(ContentCopiedNotification notification, IProperty property) + private bool UpdateRichTextProperty(IContent content, IProperty property) { var isUpdated = false; @@ -165,7 +182,7 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot RichTextBlockValue? richTextBlockValue = GetRichTextBlockValue(rawBlockPropertyValue); - (bool hasUpdates, string? updatedValue) = UpdateBlockEditorData(notification, richTextBlockValue); + (bool hasUpdates, string? updatedValue) = UpdateBlockEditorData(content, richTextBlockValue); if (hasUpdates && string.IsNullOrEmpty(updatedValue) is false) { @@ -173,7 +190,7 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot if (richTextEditorValue is not null) { richTextEditorValue.Blocks = JsonSerializer.Deserialize(updatedValue); - notification.Copy.SetValue(property.Alias, JsonSerializer.Serialize(richTextEditorValue), blockPropertyValue.Culture, blockPropertyValue.Segment); + content.SetValue(property.Alias, JsonSerializer.Serialize(richTextEditorValue), blockPropertyValue.Culture, blockPropertyValue.Segment); } } @@ -183,7 +200,7 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot return isUpdated; } - private (bool, string?) UpdateBlockEditorData(ContentCopiedNotification notification, RichTextBlockValue? richTextBlockValue) + private (bool, string?) UpdateBlockEditorData(IContent content, RichTextBlockValue? richTextBlockValue) { var isUpdated = false; @@ -196,14 +213,14 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot .Concat(richTextBlockValue.SettingsData) .SelectMany(x => x.Values); - isUpdated = UpdateBlockPropertyValues(notification, isUpdated, blockPropertyValues); + isUpdated = UpdateBlockPropertyValues(content, isUpdated, blockPropertyValues); var updatedValue = JsonSerializer.Serialize(richTextBlockValue); return (isUpdated, updatedValue); } - private bool UpdateBlockPropertyValues(ContentCopiedNotification notification, bool isUpdated, IEnumerable blockPropertyValues) + private bool UpdateBlockPropertyValues(IContent content, bool isUpdated, IEnumerable blockPropertyValues) { foreach (BlockPropertyValue blockPropertyValue in blockPropertyValues) { @@ -221,14 +238,14 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot if (IsUploadFieldPropertyType(propertyType)) { - isUpdated |= UpdateUploadFieldBlockPropertyValue(blockPropertyValue, notification, propertyType); + isUpdated |= UpdateUploadFieldBlockPropertyValue(blockPropertyValue, content, propertyType); continue; } if (IsBlockListPropertyType(propertyType)) { - (bool hasUpdates, string? newValue) = UpdateBlockPropertyValue(blockPropertyValue, notification, _blockListEditorValues); + (bool hasUpdates, string? newValue) = UpdateBlockPropertyValue(blockPropertyValue, content, _blockListEditorValues); isUpdated |= hasUpdates; @@ -239,7 +256,7 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot if (IsBlockGridPropertyType(propertyType)) { - (bool hasUpdates, string? newValue) = UpdateBlockPropertyValue(blockPropertyValue, notification, _blockGridEditorValues); + (bool hasUpdates, string? newValue) = UpdateBlockPropertyValue(blockPropertyValue, content, _blockGridEditorValues); isUpdated |= hasUpdates; @@ -250,7 +267,7 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot if (IsRichTextPropertyType(propertyType)) { - (bool hasUpdates, string? newValue) = UpdateRichTextPropertyValue(blockPropertyValue, notification); + (bool hasUpdates, string? newValue) = UpdateRichTextPropertyValue(blockPropertyValue, content); if (hasUpdates && string.IsNullOrEmpty(newValue) is false) { @@ -271,7 +288,7 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot return isUpdated; } - private bool UpdateUploadFieldBlockPropertyValue(BlockPropertyValue blockItemDataValue, ContentCopiedNotification notification, IPropertyType propertyType) + private bool UpdateUploadFieldBlockPropertyValue(BlockPropertyValue blockItemDataValue, IContent content, IPropertyType propertyType) { FileUploadValue? fileUploadValue = FileUploadValueParser.Parse(blockItemDataValue.Value); @@ -281,26 +298,26 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot return false; } - var copyFileUrl = CopyFile(fileUploadValue.Src, notification.Copy, propertyType); + var copyFileUrl = CopyFile(fileUploadValue.Src, content, propertyType); blockItemDataValue.Value = copyFileUrl; return true; } - private (bool, string?) UpdateBlockPropertyValue(BlockPropertyValue blockItemDataValue, ContentCopiedNotification notification, BlockEditorValues blockEditorValues) + private (bool, string?) UpdateBlockPropertyValue(BlockPropertyValue blockItemDataValue, IContent content, BlockEditorValues blockEditorValues) where TValue : BlockValue, new() where TLayout : class, IBlockLayoutItem, new() { BlockEditorData? blockItemEditorDataValue = GetBlockEditorData(blockItemDataValue.Value, blockEditorValues); - return UpdateBlockEditorData(notification, blockItemEditorDataValue); + return UpdateBlockEditorData(content, blockItemEditorDataValue); } - private (bool, string?) UpdateRichTextPropertyValue(BlockPropertyValue blockItemDataValue, ContentCopiedNotification notification) + private (bool, string?) UpdateRichTextPropertyValue(BlockPropertyValue blockItemDataValue, IContent content) { RichTextBlockValue? richTextBlockValue = GetRichTextBlockValue(blockItemDataValue.Value); - return UpdateBlockEditorData(notification, richTextBlockValue); + return UpdateBlockEditorData(content, richTextBlockValue); } private string CopyFile(string sourceUrl, IContent destinationContent, IPropertyType propertyType) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentDeletedNotificationHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentDeletedNotificationHandler.cs index 681c31cc58..4203209b58 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentDeletedNotificationHandler.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentDeletedNotificationHandler.cs @@ -1,17 +1,30 @@ using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache.PropertyEditors; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors.ValueConverters; using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Infrastructure.Extensions; namespace Umbraco.Cms.Infrastructure.PropertyEditors.NotificationHandlers; /// -/// Implements a notification handler that processes file uploads when content is deleted, removing associated files. +/// Provides base class for notification handler that processes file uploads when a content entity is deleted, removing associated files. /// -internal sealed class FileUploadContentDeletedNotificationHandler : FileUploadEntityDeletedNotificationHandlerBase, INotificationHandler +internal sealed class FileUploadContentDeletedNotificationHandler : FileUploadNotificationHandlerBase, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler { + private readonly BlockEditorValues _blockListEditorValues; + private readonly BlockEditorValues _blockGridEditorValues; + /// /// Initializes a new instance of the class. /// @@ -20,10 +33,204 @@ internal sealed class FileUploadContentDeletedNotificationHandler : FileUploadEn MediaFileManager mediaFileManager, IBlockEditorElementTypeCache elementTypeCache, ILogger logger) - : base(jsonSerializer, mediaFileManager, elementTypeCache, logger) + : base(jsonSerializer, mediaFileManager, elementTypeCache) { + _blockListEditorValues = new(new BlockListEditorDataConverter(jsonSerializer), elementTypeCache, logger); + _blockGridEditorValues = new(new BlockGridEditorDataConverter(jsonSerializer), elementTypeCache, logger); } /// public void Handle(ContentDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); + + /// + public void Handle(ContentDeletedBlueprintNotification notification) => DeleteContainedFiles(notification.DeletedBlueprints); + + /// + public void Handle(MediaDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); + + /// + public void Handle(MemberDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); + + /// + /// Deletes all file upload property files contained within a collection of content entities. + /// + /// + private void DeleteContainedFiles(IEnumerable deletedEntities) + { + IReadOnlyList filePathsToDelete = ContainedFilePaths(deletedEntities); + MediaFileManager.DeleteMediaFiles(filePathsToDelete); + } + + /// + /// Gets the paths to all file upload property files contained within a collection of content entities. + /// + private IReadOnlyList ContainedFilePaths(IEnumerable entities) + { + var paths = new List(); + + foreach (IProperty? property in entities.SelectMany(x => x.Properties)) + { + if (IsUploadFieldPropertyType(property.PropertyType)) + { + paths.AddRange(GetPathsFromUploadFieldProperty(property)); + + continue; + } + + if (IsBlockListPropertyType(property.PropertyType)) + { + paths.AddRange(GetPathsFromBlockProperty(property, _blockListEditorValues)); + + continue; + } + + if (IsBlockGridPropertyType(property.PropertyType)) + { + paths.AddRange(GetPathsFromBlockProperty(property, _blockGridEditorValues)); + + continue; + } + + if (IsRichTextPropertyType(property.PropertyType)) + { + paths.AddRange(GetPathsFromRichTextProperty(property)); + + continue; + } + } + + return paths.Distinct().ToList().AsReadOnly(); + } + + private IEnumerable GetPathsFromUploadFieldProperty(IProperty property) + { + foreach (IPropertyValue propertyValue in property.Values) + { + if (propertyValue.PublishedValue != null && propertyValue.PublishedValue is string publishedUrl && !string.IsNullOrWhiteSpace(publishedUrl)) + { + yield return MediaFileManager.FileSystem.GetRelativePath(publishedUrl); + } + + if (propertyValue.EditedValue != null && propertyValue.EditedValue is string editedUrl && !string.IsNullOrWhiteSpace(editedUrl)) + { + yield return MediaFileManager.FileSystem.GetRelativePath(editedUrl); + } + } + } + + private IReadOnlyCollection GetPathsFromBlockProperty(IProperty property, BlockEditorValues blockEditorValues) + where TValue : BlockValue, new() + where TLayout : class, IBlockLayoutItem, new() + { + var paths = new List(); + + foreach (IPropertyValue blockPropertyValue in property.Values) + { + paths.AddRange(GetPathsFromBlockValue(GetBlockEditorData(blockPropertyValue.PublishedValue, blockEditorValues)?.BlockValue)); + paths.AddRange(GetPathsFromBlockValue(GetBlockEditorData(blockPropertyValue.EditedValue, blockEditorValues)?.BlockValue)); + } + + return paths; + } + + private IReadOnlyCollection GetPathsFromBlockValue(BlockValue? blockValue) + { + var paths = new List(); + + if (blockValue is null) + { + return paths; + } + + IEnumerable blockPropertyValues = blockValue.ContentData + .Concat(blockValue.SettingsData) + .SelectMany(x => x.Values); + + foreach (BlockPropertyValue blockPropertyValue in blockPropertyValues) + { + if (blockPropertyValue.Value == null) + { + continue; + } + + IPropertyType? propertyType = blockPropertyValue.PropertyType; + + if (propertyType == null) + { + continue; + } + + if (IsUploadFieldPropertyType(propertyType)) + { + FileUploadValue? originalValue = FileUploadValueParser.Parse(blockPropertyValue.Value); + + if (string.IsNullOrWhiteSpace(originalValue?.Src)) + { + continue; + } + + paths.Add(MediaFileManager.FileSystem.GetRelativePath(originalValue.Src)); + + continue; + } + + if (IsBlockListPropertyType(propertyType)) + { + paths.AddRange(GetPathsFromBlockPropertyValue(blockPropertyValue, _blockListEditorValues)); + + continue; + } + + if (IsBlockGridPropertyType(propertyType)) + { + paths.AddRange(GetPathsFromBlockPropertyValue(blockPropertyValue, _blockGridEditorValues)); + + continue; + } + + if (IsRichTextPropertyType(propertyType)) + { + paths.AddRange(GetPathsFromRichTextPropertyValue(blockPropertyValue)); + + continue; + } + } + + return paths; + } + + private IReadOnlyCollection GetPathsFromBlockPropertyValue(BlockPropertyValue blockItemDataValue, BlockEditorValues blockEditorValues) + where TValue : BlockValue, new() + where TLayout : class, IBlockLayoutItem, new() + { + BlockEditorData? blockItemEditorDataValue = GetBlockEditorData(blockItemDataValue.Value, blockEditorValues); + + return GetPathsFromBlockValue(blockItemEditorDataValue?.BlockValue); + } + + private IReadOnlyCollection GetPathsFromRichTextProperty(IProperty property) + { + var paths = new List(); + + IPropertyValue? propertyValue = property.Values.FirstOrDefault(); + if (propertyValue is null) + { + return paths; + } + + paths.AddRange(GetPathsFromBlockValue(GetRichTextBlockValue(propertyValue.PublishedValue))); + paths.AddRange(GetPathsFromBlockValue(GetRichTextBlockValue(propertyValue.EditedValue))); + + return paths; + } + + private IReadOnlyCollection GetPathsFromRichTextPropertyValue(BlockPropertyValue blockItemDataValue) + { + RichTextEditorValue? richTextEditorValue = GetRichTextEditorValue(blockItemDataValue.Value); + + // Ensure the property type is populated on all blocks. + richTextEditorValue?.EnsurePropertyTypePopulatedOnBlocks(ElementTypeCache); + + return GetPathsFromBlockValue(richTextEditorValue?.Blocks); + } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadEntityDeletedNotificationHandlerBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadEntityDeletedNotificationHandlerBase.cs deleted file mode 100644 index 40877d2fef..0000000000 --- a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadEntityDeletedNotificationHandlerBase.cs +++ /dev/null @@ -1,218 +0,0 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Cache.PropertyEditors; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Blocks; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.PropertyEditors.ValueConverters; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Infrastructure.Extensions; - -namespace Umbraco.Cms.Infrastructure.PropertyEditors.NotificationHandlers; - -/// -/// Provides base class for notification handler that processes file uploads when a content entity is deleted, removing associated files. -/// -internal abstract class FileUploadEntityDeletedNotificationHandlerBase : FileUploadNotificationHandlerBase -{ - private readonly BlockEditorValues _blockListEditorValues; - private readonly BlockEditorValues _blockGridEditorValues; - - /// - /// Initializes a new instance of the class. - /// - protected FileUploadEntityDeletedNotificationHandlerBase( - IJsonSerializer jsonSerializer, - MediaFileManager mediaFileManager, - IBlockEditorElementTypeCache elementTypeCache, - ILogger logger) - : base(jsonSerializer, mediaFileManager, elementTypeCache) - { - _blockListEditorValues = new(new BlockListEditorDataConverter(jsonSerializer), elementTypeCache, logger); - _blockGridEditorValues = new(new BlockGridEditorDataConverter(jsonSerializer), elementTypeCache, logger); - } - - /// - /// Deletes all file upload property files contained within a collection of content entities. - /// - /// - protected void DeleteContainedFiles(IEnumerable deletedEntities) - { - IReadOnlyList filePathsToDelete = ContainedFilePaths(deletedEntities); - MediaFileManager.DeleteMediaFiles(filePathsToDelete); - } - - /// - /// Gets the paths to all file upload property files contained within a collection of content entities. - /// - private IReadOnlyList ContainedFilePaths(IEnumerable entities) - { - var paths = new List(); - - foreach (IProperty? property in entities.SelectMany(x => x.Properties)) - { - if (IsUploadFieldPropertyType(property.PropertyType)) - { - paths.AddRange(GetPathsFromUploadFieldProperty(property)); - - continue; - } - - if (IsBlockListPropertyType(property.PropertyType)) - { - paths.AddRange(GetPathsFromBlockProperty(property, _blockListEditorValues)); - - continue; - } - - if (IsBlockGridPropertyType(property.PropertyType)) - { - paths.AddRange(GetPathsFromBlockProperty(property, _blockGridEditorValues)); - - continue; - } - - if (IsRichTextPropertyType(property.PropertyType)) - { - paths.AddRange(GetPathsFromRichTextProperty(property)); - - continue; - } - } - - return paths.Distinct().ToList().AsReadOnly(); - } - - private IEnumerable GetPathsFromUploadFieldProperty(IProperty property) - { - foreach (IPropertyValue propertyValue in property.Values) - { - if (propertyValue.PublishedValue != null && propertyValue.PublishedValue is string publishedUrl && !string.IsNullOrWhiteSpace(publishedUrl)) - { - yield return MediaFileManager.FileSystem.GetRelativePath(publishedUrl); - } - - if (propertyValue.EditedValue != null && propertyValue.EditedValue is string editedUrl && !string.IsNullOrWhiteSpace(editedUrl)) - { - yield return MediaFileManager.FileSystem.GetRelativePath(editedUrl); - } - } - } - - private IReadOnlyCollection GetPathsFromBlockProperty(IProperty property, BlockEditorValues blockEditorValues) - where TValue : BlockValue, new() - where TLayout : class, IBlockLayoutItem, new() - { - var paths = new List(); - - foreach (IPropertyValue blockPropertyValue in property.Values) - { - paths.AddRange(GetPathsFromBlockValue(GetBlockEditorData(blockPropertyValue.PublishedValue, blockEditorValues)?.BlockValue)); - paths.AddRange(GetPathsFromBlockValue(GetBlockEditorData(blockPropertyValue.EditedValue, blockEditorValues)?.BlockValue)); - } - - return paths; - } - - private IReadOnlyCollection GetPathsFromBlockValue(BlockValue? blockValue) - { - var paths = new List(); - - if (blockValue is null) - { - return paths; - } - - IEnumerable blockPropertyValues = blockValue.ContentData - .Concat(blockValue.SettingsData) - .SelectMany(x => x.Values); - - foreach (BlockPropertyValue blockPropertyValue in blockPropertyValues) - { - if (blockPropertyValue.Value == null) - { - continue; - } - - IPropertyType? propertyType = blockPropertyValue.PropertyType; - - if (propertyType == null) - { - continue; - } - - if (IsUploadFieldPropertyType(propertyType)) - { - FileUploadValue? originalValue = FileUploadValueParser.Parse(blockPropertyValue.Value); - - if (string.IsNullOrWhiteSpace(originalValue?.Src)) - { - continue; - } - - paths.Add(MediaFileManager.FileSystem.GetRelativePath(originalValue.Src)); - - continue; - } - - if (IsBlockListPropertyType(propertyType)) - { - paths.AddRange(GetPathsFromBlockPropertyValue(blockPropertyValue, _blockListEditorValues)); - - continue; - } - - if (IsBlockGridPropertyType(propertyType)) - { - paths.AddRange(GetPathsFromBlockPropertyValue(blockPropertyValue, _blockGridEditorValues)); - - continue; - } - - if (IsRichTextPropertyType(propertyType)) - { - paths.AddRange(GetPathsFromRichTextPropertyValue(blockPropertyValue)); - - continue; - } - } - - return paths; - } - - private IReadOnlyCollection GetPathsFromBlockPropertyValue(BlockPropertyValue blockItemDataValue, BlockEditorValues blockEditorValues) - where TValue : BlockValue, new() - where TLayout : class, IBlockLayoutItem, new() - { - BlockEditorData? blockItemEditorDataValue = GetBlockEditorData(blockItemDataValue.Value, blockEditorValues); - - return GetPathsFromBlockValue(blockItemEditorDataValue?.BlockValue); - } - - private IReadOnlyCollection GetPathsFromRichTextProperty(IProperty property) - { - var paths = new List(); - - IPropertyValue? propertyValue = property.Values.FirstOrDefault(); - if (propertyValue is null) - { - return paths; - } - - paths.AddRange(GetPathsFromBlockValue(GetRichTextBlockValue(propertyValue.PublishedValue))); - paths.AddRange(GetPathsFromBlockValue(GetRichTextBlockValue(propertyValue.EditedValue))); - - return paths; - } - - private IReadOnlyCollection GetPathsFromRichTextPropertyValue(BlockPropertyValue blockItemDataValue) - { - RichTextEditorValue? richTextEditorValue = GetRichTextEditorValue(blockItemDataValue.Value); - - // Ensure the property type is populated on all blocks. - richTextEditorValue?.EnsurePropertyTypePopulatedOnBlocks(ElementTypeCache); - - return GetPathsFromBlockValue(richTextEditorValue?.Blocks); - } -} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMediaDeletedNotificationHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMediaDeletedNotificationHandler.cs deleted file mode 100644 index 3a07193ec8..0000000000 --- a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMediaDeletedNotificationHandler.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Cache.PropertyEditors; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Notifications; -using Umbraco.Cms.Core.Serialization; - -namespace Umbraco.Cms.Infrastructure.PropertyEditors.NotificationHandlers; - -/// -/// Implements a notification handler that processes file uploads when media is deleted, removing associated files. -/// -internal sealed class FileUploadMediaDeletedNotificationHandler : FileUploadEntityDeletedNotificationHandlerBase, INotificationHandler -{ - /// - /// Initializes a new instance of the class. - /// - public FileUploadMediaDeletedNotificationHandler( - IJsonSerializer jsonSerializer, - MediaFileManager mediaFileManager, - IBlockEditorElementTypeCache elementTypeCache, - ILogger logger) - : base(jsonSerializer, mediaFileManager, elementTypeCache, logger) - { - } - - /// - public void Handle(MediaDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); -} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMemberDeletedNotificationHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMemberDeletedNotificationHandler.cs deleted file mode 100644 index 91433b88b9..0000000000 --- a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMemberDeletedNotificationHandler.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Cache.PropertyEditors; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Notifications; -using Umbraco.Cms.Core.Serialization; - -namespace Umbraco.Cms.Infrastructure.PropertyEditors.NotificationHandlers; - -/// -/// Implements a notification handler that processes file uploads when a member is deleted, removing associated files. -/// -internal sealed class FileUploadMemberDeletedNotificationHandler : FileUploadEntityDeletedNotificationHandlerBase, INotificationHandler -{ - /// - /// Initializes a new instance of the class. - /// - public FileUploadMemberDeletedNotificationHandler( - IJsonSerializer jsonSerializer, - MediaFileManager mediaFileManager, - IBlockEditorElementTypeCache elementTypeCache, - ILogger logger) - : base(jsonSerializer, mediaFileManager, elementTypeCache, logger) - { - } - - /// - public void Handle(MemberDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); -} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs index 2c1ea1e278..3d04a9fa6c 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs @@ -96,7 +96,7 @@ internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent blueprint.SetValue("keywords", "blueprint 3"); blueprint.SetValue("description", "blueprint 4"); - ContentService.SaveBlueprint(blueprint); + ContentService.SaveBlueprint(blueprint, null); var found = ContentService.GetBlueprintsForContentTypes().ToArray(); Assert.AreEqual(1, found.Length); @@ -121,7 +121,7 @@ internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent blueprint.SetValue("keywords", "blueprint 3"); blueprint.SetValue("description", "blueprint 4"); - ContentService.SaveBlueprint(blueprint); + ContentService.SaveBlueprint(blueprint, null); ContentService.DeleteBlueprint(blueprint); @@ -148,7 +148,7 @@ internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent ContentService.Save(originalPage); var fromContent = ContentService.CreateBlueprintFromContent(originalPage, "hello world"); - ContentService.SaveBlueprint(fromContent); + ContentService.SaveBlueprint(fromContent, originalPage); Assert.IsTrue(fromContent.HasIdentity); Assert.AreEqual("blueprint 1", fromContent.Properties["title"]?.GetValue()); @@ -176,7 +176,7 @@ internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent { var blueprint = ContentBuilder.CreateTextpageContent(i % 2 == 0 ? ct1 : ct2, "hello" + i, Constants.System.Root); - ContentService.SaveBlueprint(blueprint); + ContentService.SaveBlueprint(blueprint, null); } var found = ContentService.GetBlueprintsForContentTypes().ToArray(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs index c5d6548677..8cce10b716 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs @@ -116,7 +116,7 @@ internal sealed class TelemetryProviderTests : UmbracoIntegrationTest blueprint.SetValue("keywords", "blueprint 3"); blueprint.SetValue("description", "blueprint 4"); - ContentService.SaveBlueprint(blueprint); + ContentService.SaveBlueprint(blueprint, null); var fromBlueprint = await ContentBlueprintEditingService.GetScaffoldedAsync(blueprint.Key); Assert.IsNotNull(fromBlueprint); From 9baf04026e8896b66e7cb0d350982b08d249d966 Mon Sep 17 00:00:00 2001 From: Mole Date: Mon, 7 Jul 2025 14:53:42 +0200 Subject: [PATCH 82/82] V16: Siblings endpoints (#19657) * PoC implementation * Move to controller base * Implement solution that seems worse, but works better * Don't require parent key in repository method * Fix typos * Add siblings for data type, media type and media * Add endpoint for template * Add DocumentType and DocumentBlueprint controllers * Fix naming * Fix case if siblings are under root * Take item ordering into account not all entities are ordered by sort order * Add default implementation * Fix parentkey * Add tests * Format optimizations for split view * Add test covered requirement to description * Cover positive case and make test case output more readable * reduce allocations * Clarify test --------- Co-authored-by: Migaroez --- .../Tree/SiblingsDataTypeTreeController.cs | 19 ++++ .../Tree/SiblingsDocumentTreeController.cs | 40 ++++++++ ...SiblingsDocumentBlueprintTreeController.cs | 24 +++++ .../SiblingsDocumentTypeTreeController.cs | 23 +++++ .../Media/Tree/SiblingsMediaTreeController.cs | 29 ++++++ .../Tree/SiblingsMediaTypeTreeController.cs | 19 ++++ .../Tree/SiblingsTemplateTreeController.cs | 23 +++++ .../Tree/EntityTreeControllerBase.cs | 19 +++- .../Repositories/IEntityRepository.cs | 11 +++ src/Umbraco.Core/Services/EntityService.cs | 33 +++++++ src/Umbraco.Core/Services/IEntityService.cs | 16 ++++ .../Persistence/NPocoSqlExtensions.cs | 8 ++ .../Implement/EntityRepository.cs | 82 ++++++++++++++++ .../Services/EntityServiceTests.cs | 94 ++++++++++++++++++- .../Services/EntityServiceTests.cs | 38 ++++++++ 15 files changed, 476 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/SiblingsDataTypeTreeController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/SiblingsDocumentTreeController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/SiblingsDocumentBlueprintTreeController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/SiblingsDocumentTypeTreeController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/SiblingsMediaTreeController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/SiblingsMediaTypeTreeController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/SiblingsTemplateTreeController.cs create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/EntityServiceTests.cs diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/SiblingsDataTypeTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/SiblingsDataTypeTreeController.cs new file mode 100644 index 0000000000..253d32e3e8 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/SiblingsDataTypeTreeController.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree; + +public class SiblingsDataTypeTreeController : DataTypeTreeControllerBase +{ + public SiblingsDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService) + : base(entityService, dataTypeService) + { + } + + [HttpGet("siblings")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public Task>> Siblings(CancellationToken cancellationToken, Guid target, int before, int after) + => GetSiblings(target, before, after); +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/SiblingsDocumentTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/SiblingsDocumentTreeController.cs new file mode 100644 index 0000000000..3fec79bf36 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/SiblingsDocumentTreeController.cs @@ -0,0 +1,40 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.Services.Entities; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.Document.Tree; + +[ApiVersion("1.0")] +public class SiblingsDocumentTreeController : DocumentTreeControllerBase +{ + public SiblingsDocumentTreeController( + IEntityService entityService, + IUserStartNodeEntitiesService userStartNodeEntitiesService, + IDataTypeService dataTypeService, + IPublicAccessService publicAccessService, + AppCaches appCaches, + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IDocumentPresentationFactory documentPresentationFactory) + : base( + entityService, + userStartNodeEntitiesService, + dataTypeService, + publicAccessService, + appCaches, + backofficeSecurityAccessor, + documentPresentationFactory) + { + } + + [HttpGet("siblings")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public Task>> Siblings(CancellationToken cancellationToken, Guid target, int before, int after) + => GetSiblings(target, before, after); +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/SiblingsDocumentBlueprintTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/SiblingsDocumentBlueprintTreeController.cs new file mode 100644 index 0000000000..ac5578155f --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/SiblingsDocumentBlueprintTreeController.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.DocumentBlueprint.Tree; + +public class SiblingsDocumentBlueprintTreeController : DocumentBlueprintTreeControllerBase +{ + public SiblingsDocumentBlueprintTreeController(IEntityService entityService, IDocumentPresentationFactory documentPresentationFactory) + : base(entityService, documentPresentationFactory) + { + } + + [HttpGet("siblings")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public Task>> Siblings( + CancellationToken cancellationToken, + Guid target, + int before, + int after) => + GetSiblings(target, before, after); +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/SiblingsDocumentTypeTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/SiblingsDocumentTypeTreeController.cs new file mode 100644 index 0000000000..7bb9c26358 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Tree/SiblingsDocumentTypeTreeController.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Tree; + +public class SiblingsDocumentTypeTreeController : DocumentTypeTreeControllerBase +{ + public SiblingsDocumentTypeTreeController(IEntityService entityService, IContentTypeService contentTypeService) + : base(entityService, contentTypeService) + { + } + + [HttpGet("siblings")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public Task>> Siblings( + CancellationToken cancellationToken, + Guid target, + int before, + int after) => + GetSiblings(target, before, after); +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/SiblingsMediaTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/SiblingsMediaTreeController.cs new file mode 100644 index 0000000000..f5708fa638 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/Tree/SiblingsMediaTreeController.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.Services.Entities; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.Media.Tree; + +public class SiblingsMediaTreeController : MediaTreeControllerBase +{ + public SiblingsMediaTreeController( + IEntityService entityService, + IUserStartNodeEntitiesService userStartNodeEntitiesService, + IDataTypeService dataTypeService, + AppCaches appCaches, + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IMediaPresentationFactory mediaPresentationFactory) + : base(entityService, userStartNodeEntitiesService, dataTypeService, appCaches, backofficeSecurityAccessor, mediaPresentationFactory) + { + } + + [HttpGet("siblings")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public Task>> Siblings(CancellationToken cancellationToken, Guid target, int before, int after) + => GetSiblings(target, before, after); +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/SiblingsMediaTypeTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/SiblingsMediaTypeTreeController.cs new file mode 100644 index 0000000000..1482788b57 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/SiblingsMediaTypeTreeController.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.MediaType.Tree; + +public class SiblingsMediaTypeTreeController : MediaTypeTreeControllerBase +{ + public SiblingsMediaTypeTreeController(IEntityService entityService, IMediaTypeService mediaTypeService) + : base(entityService, mediaTypeService) + { + } + + [HttpGet("siblings")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public Task>> Siblings(CancellationToken cancellationToken, Guid target, int before, int after) + => GetSiblings(target, before, after); +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/SiblingsTemplateTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/SiblingsTemplateTreeController.cs new file mode 100644 index 0000000000..ed27092ecb --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Template/Tree/SiblingsTemplateTreeController.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.Template.Tree; + +public class SiblingsTemplateTreeController : TemplateTreeControllerBase +{ + public SiblingsTemplateTreeController(IEntityService entityService) + : base(entityService) + { + } + + [HttpGet("siblings")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public Task>> Siblings( + CancellationToken cancellationToken, + Guid target, + int before, + int after) => + GetSiblings(target, before, after); +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs index bf86b852ed..13bbe9bc2b 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.ViewModels; using Umbraco.Cms.Api.Management.ViewModels.Tree; @@ -44,6 +44,23 @@ public abstract class EntityTreeControllerBase : ManagementApiControllerB return Task.FromResult>>(Ok(result)); } + protected Task>> GetSiblings(Guid target, int before, int after) + { + IEntitySlim[] siblings = EntityService.GetSiblings(target, ItemObjectType, before, after, ItemOrdering).ToArray(); + if (siblings.Length == 0) + { + return Task.FromResult>>(NotFound()); + } + + IEntitySlim? entity = siblings.FirstOrDefault(); + Guid? parentKey = entity?.ParentId > 0 + ? EntityService.GetKey(entity.ParentId, ItemObjectType).Result + : Constants.System.RootKey; + + TItem[] treeItemsViewModels = MapTreeItemViewModels(parentKey, siblings); + return Task.FromResult>>(Ok(treeItemsViewModels)); + } + protected virtual async Task>> GetAncestors(Guid descendantKey, bool includeSelf = true) { IEntitySlim[] ancestorEntities = await GetAncestorEntitiesAsync(descendantKey, includeSelf); diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs index 477f3e5c50..cdf05ca5aa 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs @@ -19,6 +19,17 @@ public interface IEntityRepository : IRepository IEnumerable GetAll(Guid objectType, params Guid[] keys); + /// + /// Gets sibling entities of a specified target entity, within a given range before and after the target, ordered as specified. + /// + /// The object type key of the entities. + /// The key of the target entity whose siblings are to be retrieved. + /// The number of siblings to retrieve before the target entity. + /// The number of siblings to retrieve after the target entity. + /// The ordering to apply to the siblings. + /// Enumerable of sibling entities. + IEnumerable GetSiblings(Guid objectType, Guid targetKey, int before, int after, Ordering ordering) => []; + /// /// Gets entities for a query /// diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index f2fe2eefd8..0a284bdebf 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -318,6 +318,39 @@ public class EntityService : RepositoryService, IEntityService return children; } + /// + public IEnumerable GetSiblings( + Guid key, + UmbracoObjectTypes objectType, + int before, + int after, + Ordering? ordering = null) + { + if (before < 0) + { + throw new ArgumentOutOfRangeException(nameof(before), "The 'before' parameter must be greater than or equal to 0."); + } + + if (after < 0) + { + throw new ArgumentOutOfRangeException(nameof(after), "The 'after' parameter must be greater than or equal to 0."); + } + + ordering ??= new Ordering("sortOrder"); + + using ICoreScope scope = ScopeProvider.CreateCoreScope(); + + IEnumerable siblings = _entityRepository.GetSiblings( + objectType.GetGuid(), + key, + before, + after, + ordering); + + scope.Complete(); + return siblings; + } + /// public virtual IEnumerable GetDescendants(int id) { diff --git a/src/Umbraco.Core/Services/IEntityService.cs b/src/Umbraco.Core/Services/IEntityService.cs index 964ec9f502..6652062ac0 100644 --- a/src/Umbraco.Core/Services/IEntityService.cs +++ b/src/Umbraco.Core/Services/IEntityService.cs @@ -170,6 +170,22 @@ public interface IEntityService /// The object type of the parent. IEntitySlim? GetParent(int id, UmbracoObjectTypes objectType); + /// + /// Gets sibling entities of a specified target entity, within a given range before and after the target, ordered as specified. + /// + /// The key of the target entity whose siblings are to be retrieved. + /// The object type key of the entities. + /// The number of siblings to retrieve before the target entity. Needs to be greater or equal to 0. + /// The number of siblings to retrieve after the target entity. Needs to be greater or equal to 0. + /// The ordering to apply to the siblings. + /// Enumerable of sibling entities. + IEnumerable GetSiblings( + Guid key, + UmbracoObjectTypes objectType, + int before, + int after, + Ordering? ordering = null) => []; + /// /// Gets the children of an entity. /// diff --git a/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs index 80f2bca778..d2c7aa87ff 100644 --- a/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs +++ b/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs @@ -1298,6 +1298,14 @@ namespace Umbraco.Extensions #region Utilities + public static Sql AppendSubQuery(this Sql sql, Sql subQuery, string alias) + { + // Append the subquery as a derived table with an alias + sql.Append("(").Append(subQuery.SQL, subQuery.Arguments).Append($") AS {alias}"); + + return sql; + } + private static string[] GetColumns(this Sql sql, string? tableAlias = null, string? referenceName = null, Expression>[]? columnExpressions = null, bool withAlias = true, bool forInsert = false) { PocoData? pd = sql.SqlContext.PocoDataFactory.ForType(typeof (TDto)); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs index 8ff1fe381b..5e107bd71d 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs @@ -145,6 +145,59 @@ internal class EntityRepository : RepositoryBase, IEntityRepositoryExtended return entity; } + /// + public IEnumerable GetSiblings(Guid objectType, Guid targetKey, int before, int after, Ordering ordering) + { + // Ideally we don't want to have to do a second query for the parent ID, but the siblings query is already messy enough + // without us also having to do a nested query for the parent ID too. + Sql parentIdQuery = Sql() + .Select(x => x.ParentId) + .From() + .Where(x => x.UniqueId == targetKey); + var parentId = Database.ExecuteScalar(parentIdQuery); + + Sql orderingSql = Sql(); + ApplyOrdering(ref orderingSql, ordering); + + // Get all children of the parent node which is not trashed, ordered by SortOrder, and assign each a row number. + // These row numbers are important, we need them to select the "before" and "after" siblings of the target node. + Sql rowNumberSql = Sql() + .Select($"ROW_NUMBER() OVER ({orderingSql.SQL}) AS rn") + .AndSelect(n => n.UniqueId) + .From() + .Where(x => x.ParentId == parentId && x.Trashed == false); + + // Find the specific row number of the target node. + // We need this to determine the bounds of the row numbers to select. + Sql targetRowSql = Sql() + .Select("rn") + .From().AppendSubQuery(rowNumberSql, "Target") + .Where(x => x.UniqueId == targetKey, "Target"); + + // We have to reuse the target row sql arguments, however, we also need to add the "before" and "after" values to the arguments. + // If we try to do this directly in the params array it'll consider the initial argument array as a single argument. + IEnumerable beforeArguments = targetRowSql.Arguments.Concat([before]); + IEnumerable afterArguments = targetRowSql.Arguments.Concat([after]); + + // Select the UniqueId of nodes which row number is within the specified range of the target node's row number. + Sql? mainSql = Sql() + .Select("UniqueId") + .From().AppendSubQuery(rowNumberSql, "NumberedNodes") + .Where($"rn >= ({targetRowSql.SQL}) - @3", beforeArguments.ToArray()) + .Where($"rn <= ({targetRowSql.SQL}) + @3", afterArguments.ToArray()) + .OrderBy("rn"); + + List? keys = Database.Fetch(mainSql); + + if (keys is null || keys.Count == 0) + { + return []; + } + + return PerformGetAll(objectType, ordering, sql => sql.WhereIn(x => x.UniqueId, keys)); + } + + public IEntitySlim? Get(Guid key, Guid objectTypeId) { var isContent = objectTypeId == Constants.ObjectTypes.Document || @@ -216,6 +269,20 @@ internal class EntityRepository : RepositoryBase, IEntityRepositoryExtended return GetEntities(sql, isContent, isMedia, isMember); } + private IEnumerable PerformGetAll( + Guid objectType, + Ordering ordering, + Action>? filter = null) + { + var isContent = objectType == Constants.ObjectTypes.Document || + objectType == Constants.ObjectTypes.DocumentBlueprint; + var isMedia = objectType == Constants.ObjectTypes.Media; + var isMember = objectType == Constants.ObjectTypes.Member; + + Sql sql = GetFullSqlForEntityType(isContent, isMedia, isMember, objectType, ordering, filter); + return GetEntities(sql, isContent, isMedia, isMember); + } + public IEnumerable GetAllPaths(Guid objectType, params int[]? ids) => ids?.Any() ?? false ? PerformGetAllPaths(objectType, sql => sql.WhereIn(x => x.NodeId, ids.Distinct())) @@ -452,6 +519,21 @@ internal class EntityRepository : RepositoryBase, IEntityRepositoryExtended return AddGroupBy(isContent, isMedia, isMember, sql, true); } + protected Sql GetFullSqlForEntityType( + bool isContent, + bool isMedia, + bool isMember, + Guid objectType, + Ordering ordering, + Action>? filter) + { + Sql sql = GetBaseWhere(isContent, isMedia, isMember, false, filter, new[] { objectType }); + AddGroupBy(isContent, isMedia, isMember, sql, false); + ApplyOrdering(ref sql, ordering); + + return sql; + } + protected Sql GetBase(bool isContent, bool isMedia, bool isMember, Action>? filter, bool isCount = false) => GetBase(isContent, isMedia, isMember, filter, [], isCount); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs index 8d33039141..d1031e3b2c 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs @@ -8,7 +8,7 @@ using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.ContentTypeEditing; using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Tests.Common.Attributes; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; @@ -931,6 +931,98 @@ internal sealed class EntityServiceTests : UmbracoIntegrationTest } + [Test] + public void EntityService_Siblings_ReturnsExpectedSiblings() + { + var children = CreateSiblingsTestData(); + + var taget = children[1]; + + var result = EntityService.GetSiblings(taget.Key, UmbracoObjectTypes.Document, 1, 1).ToArray(); + Assert.AreEqual(3, result.Length); + Assert.IsTrue(result[0].Key == children[0].Key); + Assert.IsTrue(result[1].Key == children[1].Key); + Assert.IsTrue(result[2].Key == children[2].Key); + } + + [Test] + public void EntityService_Siblings_SkipsTrashedEntities() + { + var children = CreateSiblingsTestData(); + + var trash = children[1]; + ContentService.MoveToRecycleBin(trash); + + var taget = children[2]; + var result = EntityService.GetSiblings(taget.Key, UmbracoObjectTypes.Document, 1, 1).ToArray(); + Assert.AreEqual(3, result.Length); + Assert.IsFalse(result.Any(x => x.Key == trash.Key)); + Assert.IsTrue(result[0].Key == children[0].Key); + Assert.IsTrue(result[1].Key == children[2].Key); + Assert.IsTrue(result[2].Key == children[3].Key); + } + + [Test] + public void EntityService_Siblings_RespectsOrdering() + { + var children = CreateSiblingsTestData(); + + // Order the children by name to ensure the ordering works when differing from the default sort order, the name is a GUID. + children = children.OrderBy(x => x.Name).ToList(); + + var taget = children[1]; + var result = EntityService.GetSiblings(taget.Key, UmbracoObjectTypes.Document, 1, 1, Ordering.By(nameof(NodeDto.Text))).ToArray(); + Assert.AreEqual(3, result.Length); + Assert.IsTrue(result[0].Key == children[0].Key); + Assert.IsTrue(result[1].Key == children[1].Key); + Assert.IsTrue(result[2].Key == children[2].Key); + } + + [Test] + public void EntityService_Siblings_IgnoresOutOfBoundsLower() + { + var children = CreateSiblingsTestData(); + + var taget = children[1]; + var result = EntityService.GetSiblings(taget.Key, UmbracoObjectTypes.Document, 100, 1).ToArray(); + Assert.AreEqual(3, result.Length); + Assert.IsTrue(result[0].Key == children[0].Key); + Assert.IsTrue(result[1].Key == children[1].Key); + Assert.IsTrue(result[2].Key == children[2].Key); + } + + [Test] + public void EntityService_Siblings_IgnoresOutOfBoundsUpper() + { + var children = CreateSiblingsTestData(); + + var taget = children[^2]; + var result = EntityService.GetSiblings(taget.Key, UmbracoObjectTypes.Document, 1, 100).ToArray(); + Assert.AreEqual(3, result.Length); + Assert.IsTrue(result[^1].Key == children[^1].Key); + Assert.IsTrue(result[^2].Key == children[^2].Key); + Assert.IsTrue(result[^3].Key == children[^3].Key); + } + + private List CreateSiblingsTestData() + { + var contentType = ContentTypeService.Get("umbTextpage"); + + var root = ContentBuilder.CreateSimpleContent(contentType); + ContentService.Save(root); + + var children = new List(); + + for (int i = 0; i < 10; i++) + { + var child = ContentBuilder.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); + ContentService.Save(child); + children.Add(child); + } + + return children; + } + private static bool _isSetup; private int _folderId; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/EntityServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/EntityServiceTests.cs new file mode 100644 index 0000000000..e333267274 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/EntityServiceTests.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Services; + +[TestFixture] +public class EntityServiceTests +{ + [TestCase(1, 1, false, TestName = "Siblings_Index_Validation_Valid")] + [TestCase(-1, 1, true, TestName = "Siblings_Index_Validation_InvalidBefore")] + [TestCase(1, -1, true, TestName = "Siblings_Index_Validation_InvalidAfter")] + [TestCase(-1, -1, true, TestName = "Siblings_Index_Validation_InvalidBeforeAndAfter")] + public void Siblings_Index_Validation(int before, int after, bool shouldThrow) + { + var sut = CreateEntityService(); + + if (shouldThrow) + { + Assert.Throws(() => sut.GetSiblings(Guid.NewGuid(), UmbracoObjectTypes.Document, before, after)); + } + } + + + private EntityService CreateEntityService() => + new( + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of()); +}