From 609e9f43e1b55f0987ecf982ed9dadc0c73ec607 Mon Sep 17 00:00:00 2001
From: Andy Butland
Date: Wed, 26 Mar 2025 07:37:04 +0100
Subject: [PATCH 01/38] Fixed issue where siblings of type at route are omitted
from the result. (#18796)
---
.../Extensions/PublishedContentExtensions.cs | 18 +++++-------------
1 file changed, 5 insertions(+), 13 deletions(-)
diff --git a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs
index 6b9b0359d7..f8ef83c350 100644
--- a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs
+++ b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs
@@ -3850,9 +3850,8 @@ public static class PublishedContentExtensions
if (parentKey.HasValue)
{
- IEnumerable childrenKeys;
var foundChildrenKeys = contentTypeAlias is null
- ? navigationQueryService.TryGetChildrenKeys(parentKey.Value, out childrenKeys)
+ ? navigationQueryService.TryGetChildrenKeys(parentKey.Value, out IEnumerable childrenKeys)
: navigationQueryService.TryGetChildrenKeysOfType(parentKey.Value, contentTypeAlias, out childrenKeys);
return foundChildrenKeys
@@ -3860,19 +3859,12 @@ public static class PublishedContentExtensions
: [];
}
- IEnumerable rootKeys;
var foundRootKeys = contentTypeAlias is null
- ? navigationQueryService.TryGetRootKeys(out rootKeys)
+ ? navigationQueryService.TryGetRootKeys(out IEnumerable rootKeys)
: navigationQueryService.TryGetRootKeysOfType(contentTypeAlias, out rootKeys);
- if (foundRootKeys)
- {
- IEnumerable rootKeysArray = rootKeys as Guid[] ?? rootKeys.ToArray();
- return rootKeysArray.Contains(content.Key)
- ? publishedStatusFilteringService.FilterAvailable(rootKeysArray, culture)
- : [];
- }
-
- return [];
+ return foundRootKeys
+ ? publishedStatusFilteringService.FilterAvailable(rootKeys, culture)
+ : [];
}
}
From 54601d42e25b771993d5d7b0d32d41bda646946f Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 25 Mar 2025 15:26:37 +0000
Subject: [PATCH 02/38] Bump vite from 6.2.2 to 6.2.3 in
/src/Umbraco.Web.UI.Login
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.2 to 6.2.3.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.3/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.3/packages/vite)
---
updated-dependencies:
- dependency-name: vite
dependency-type: direct:development
...
Signed-off-by: dependabot[bot]
---
src/Umbraco.Web.UI.Login/package-lock.json | 8 ++++----
src/Umbraco.Web.UI.Login/package.json | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/Umbraco.Web.UI.Login/package-lock.json b/src/Umbraco.Web.UI.Login/package-lock.json
index d2685028b3..847fd8258f 100644
--- a/src/Umbraco.Web.UI.Login/package-lock.json
+++ b/src/Umbraco.Web.UI.Login/package-lock.json
@@ -9,7 +9,7 @@
"@umbraco-cms/backoffice": "15.2.1",
"msw": "^2.7.0",
"typescript": "^5.7.3",
- "vite": "^6.2.2",
+ "vite": "^6.2.3",
"vite-tsconfig-paths": "^5.1.4"
},
"engines": {
@@ -3772,9 +3772,9 @@
}
},
"node_modules/vite": {
- "version": "6.2.2",
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.2.tgz",
- "integrity": "sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==",
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.3.tgz",
+ "integrity": "sha512-IzwM54g4y9JA/xAeBPNaDXiBF8Jsgl3VBQ2YQ/wOY6fyW3xMdSoltIV3Bo59DErdqdE6RxUfv8W69DvUorE4Eg==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/src/Umbraco.Web.UI.Login/package.json b/src/Umbraco.Web.UI.Login/package.json
index 449fa53bfe..02dc6a9ee9 100644
--- a/src/Umbraco.Web.UI.Login/package.json
+++ b/src/Umbraco.Web.UI.Login/package.json
@@ -16,7 +16,7 @@
"@umbraco-cms/backoffice": "15.2.1",
"msw": "^2.7.0",
"typescript": "^5.7.3",
- "vite": "^6.2.2",
+ "vite": "^6.2.3",
"vite-tsconfig-paths": "^5.1.4"
},
"msw": {
From 37035e6e7fb25177182d13b7c5dcec55a65b22c8 Mon Sep 17 00:00:00 2001
From: Kenn Jacobsen
Date: Wed, 26 Mar 2025 11:27:23 +0100
Subject: [PATCH 03/38] Clean up leftover block item data when changing element
variance (#18804)
---
.../BlockValuePropertyValueEditorBase.cs | 13 +-
.../BlockEditorVarianceHandler.cs | 54 ++++-
...kListElementLevelVariationTests.Editing.cs | 184 +++++++++++++++++-
3 files changed, 232 insertions(+), 19 deletions(-)
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs
index 00bc627d60..91d3ddf2ad 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs
@@ -184,6 +184,12 @@ public abstract class BlockValuePropertyValueEditorBase : DataV
foreach (BlockItemData item in items)
{
+ // if changes were made to the element type variations, we need those changes reflected in the block property values.
+ // for regular content this happens when a content type is saved (copies of property values are created in the DB),
+ // but for local block level properties we don't have that kind of handling, so we to do it manually.
+ // to be friendly we'll map "formerly invariant properties" to the default language ISO code instead of performing a
+ // hard reset of the property values (which would likely be the most correct thing to do from a data point of view).
+ item.Values = _blockEditorVarianceHandler.AlignPropertyVarianceAsync(item.Values, culture).GetAwaiter().GetResult();
foreach (BlockPropertyValue blockPropertyValue in item.Values)
{
IPropertyType? propertyType = blockPropertyValue.PropertyType;
@@ -199,13 +205,6 @@ public abstract class BlockValuePropertyValueEditorBase : DataV
continue;
}
- // if changes were made to the element type variation, we need those changes reflected in the block property values.
- // for regular content this happens when a content type is saved (copies of property values are created in the DB),
- // but for local block level properties we don't have that kind of handling, so we to do it manually.
- // to be friendly we'll map "formerly invariant properties" to the default language ISO code instead of performing a
- // hard reset of the property values (which would likely be the most correct thing to do from a data point of view).
- _blockEditorVarianceHandler.AlignPropertyVarianceAsync(blockPropertyValue, propertyType, culture).GetAwaiter().GetResult();
-
if (!valueEditorsByKey.TryGetValue(propertyType.DataTypeKey, out IDataValueEditor? valueEditor))
{
var configuration = _dataTypeConfigurationCache.GetConfiguration(propertyType.DataTypeKey);
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorVarianceHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorVarianceHandler.cs
index 815bc0f1b0..042d57ff04 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorVarianceHandler.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorVarianceHandler.cs
@@ -25,15 +25,7 @@ public sealed class BlockEditorVarianceHandler
_contentTypeService = contentTypeService;
}
- ///
- /// Aligns a block property value for variance changes.
- ///
- /// The block property value to align.
- /// The underlying property type.
- /// The culture being handled (null if invariant).
- ///
- /// Used for aligning variance changes when editing content.
- ///
+ [Obsolete("Please use the method that allows alignment for a collection of values. Scheduled for removal in V17.")]
public async Task AlignPropertyVarianceAsync(BlockPropertyValue blockPropertyValue, IPropertyType propertyType, string? culture)
{
culture ??= await _languageService.GetDefaultIsoCodeAsync();
@@ -45,6 +37,48 @@ public sealed class BlockEditorVarianceHandler
}
}
+ ///
+ /// Aligns a collection of block property values for variance changes.
+ ///
+ /// The block property values to align.
+ /// The culture being handled (null if invariant).
+ ///
+ /// Used for aligning variance changes when editing content.
+ ///
+ public async Task> AlignPropertyVarianceAsync(IList blockPropertyValues, string? culture)
+ {
+ var defaultIsoCodeAsync = await _languageService.GetDefaultIsoCodeAsync();
+ culture ??= defaultIsoCodeAsync;
+
+ var valuesToRemove = new List();
+ foreach (BlockPropertyValue blockPropertyValue in blockPropertyValues)
+ {
+ 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 to editor.", nameof(blockPropertyValues));
+ }
+
+ if (propertyType.VariesByCulture() == VariesByCulture(blockPropertyValue))
+ {
+ continue;
+ }
+
+ if (propertyType.VariesByCulture() is false && blockPropertyValue.Culture.InvariantEquals(defaultIsoCodeAsync) is false)
+ {
+ valuesToRemove.Add(blockPropertyValue);
+ }
+ else
+ {
+ blockPropertyValue.Culture = propertyType.VariesByCulture()
+ ? culture
+ : null;
+ }
+ }
+
+ return blockPropertyValues.Except(valuesToRemove).ToList();
+ }
+
///
/// Aligns a block property value for variance changes.
///
@@ -199,6 +233,8 @@ public sealed class BlockEditorVarianceHandler
blockValue.Expose.Add(new BlockItemVariation(contentData.Key, value.Culture, value.Segment));
}
}
+
+ blockValue.Expose = blockValue.Expose.DistinctBy(e => $"{e.ContentKey}.{e.Culture}.{e.Segment}").ToList();
}
private static bool VariesByCulture(BlockPropertyValue blockPropertyValue)
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/BlockListElementLevelVariationTests.Editing.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/BlockListElementLevelVariationTests.Editing.cs
index 295b134e22..8af4cf4987 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/BlockListElementLevelVariationTests.Editing.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/BlockListElementLevelVariationTests.Editing.cs
@@ -1,11 +1,10 @@
-using Microsoft.Extensions.DependencyInjection;
-using NUnit.Framework;
+using NUnit.Framework;
using Umbraco.Cms.Core;
-using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Models.Membership;
+using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
@@ -742,6 +741,185 @@ internal partial class BlockListElementLevelVariationTests
}
}
+ [Test]
+ public async Task Can_Align_Culture_Variance_For_Variant_Element_Types()
+ {
+ var elementType = CreateElementType(ContentVariation.Culture);
+ var blockListDataType = await CreateBlockListDataType(elementType);
+ var contentType = CreateContentType(ContentVariation.Nothing, blockListDataType);
+
+ var content = CreateContent(
+ contentType,
+ elementType,
+ new List
+ {
+ new() { Alias = "invariantText", Value = "The invariant content value" },
+ new() { Alias = "variantText", Value = "Another invariant content value" }
+ },
+ new List
+ {
+ new() { Alias = "invariantText", Value = "The invariant settings value" },
+ new() { Alias = "variantText", Value = "Another invariant settings value" }
+ },
+ false);
+
+ contentType.Variations = ContentVariation.Culture;
+ ContentTypeService.Save(contentType);
+
+ // re-fetch content
+ content = ContentService.GetById(content.Key);
+
+ var valueEditor = (BlockListPropertyEditorBase.BlockListEditorPropertyValueEditor)blockListDataType.Editor!.GetValueEditor();
+
+ var blockListValue = valueEditor.ToEditor(content!.Properties["blocks"]!) as BlockListValue;
+ Assert.IsNotNull(blockListValue);
+ Assert.Multiple(() =>
+ {
+ Assert.AreEqual(1, blockListValue.ContentData.Count);
+ Assert.AreEqual(2, blockListValue.ContentData.First().Values.Count);
+ var invariantValue = blockListValue.ContentData.First().Values.First(value => value.Alias == "invariantText");
+ var variantValue = blockListValue.ContentData.First().Values.First(value => value.Alias == "variantText");
+ Assert.IsNull(invariantValue.Culture);
+ Assert.AreEqual("en-US", variantValue.Culture);
+ });
+ Assert.Multiple(() =>
+ {
+ Assert.AreEqual(1, blockListValue.SettingsData.Count);
+ Assert.AreEqual(2, blockListValue.SettingsData.First().Values.Count);
+ var invariantValue = blockListValue.SettingsData.First().Values.First(value => value.Alias == "invariantText");
+ var variantValue = blockListValue.SettingsData.First().Values.First(value => value.Alias == "variantText");
+ Assert.IsNull(invariantValue.Culture);
+ Assert.AreEqual("en-US", variantValue.Culture);
+ });
+ Assert.Multiple(() =>
+ {
+ Assert.AreEqual(1, blockListValue.Expose.Count);
+ Assert.AreEqual("en-US", blockListValue.Expose.First().Culture);
+ });
+ }
+
+ [TestCase(ContentVariation.Culture)]
+ [TestCase(ContentVariation.Nothing)]
+ public async Task Can_Turn_Invariant_Element_Variant(ContentVariation contentTypeVariation)
+ {
+ var elementType = CreateElementType(ContentVariation.Nothing);
+ var blockListDataType = await CreateBlockListDataType(elementType);
+ var contentType = CreateContentType(contentTypeVariation, blockListDataType);
+
+ var content = CreateContent(
+ contentType,
+ elementType,
+ new List
+ {
+ new() { Alias = "invariantText", Value = "The invariant content value" },
+ new() { Alias = "variantText", Value = "Another invariant content value" }
+ },
+ new List
+ {
+ new() { Alias = "invariantText", Value = "The invariant settings value" },
+ new() { Alias = "variantText", Value = "Another invariant settings value" }
+ },
+ false);
+
+ elementType.Variations = ContentVariation.Culture;
+ elementType.PropertyTypes.First(p => p.Alias == "variantText").Variations = ContentVariation.Culture;
+ ContentTypeService.Save(elementType);
+
+ // re-fetch content
+ content = ContentService.GetById(content.Key);
+
+ var valueEditor = (BlockListPropertyEditorBase.BlockListEditorPropertyValueEditor)blockListDataType.Editor!.GetValueEditor();
+
+ var blockListValue = valueEditor.ToEditor(content!.Properties["blocks"]!) as BlockListValue;
+ Assert.IsNotNull(blockListValue);
+ Assert.Multiple(() =>
+ {
+ Assert.AreEqual(1, blockListValue.ContentData.Count);
+ Assert.AreEqual(2, blockListValue.ContentData.First().Values.Count);
+ var invariantValue = blockListValue.ContentData.First().Values.First(value => value.Alias == "invariantText");
+ var variantValue = blockListValue.ContentData.First().Values.First(value => value.Alias == "variantText");
+ Assert.IsNull(invariantValue.Culture);
+ Assert.AreEqual("en-US", variantValue.Culture);
+ });
+ Assert.Multiple(() =>
+ {
+ Assert.AreEqual(1, blockListValue.SettingsData.Count);
+ Assert.AreEqual(2, blockListValue.SettingsData.First().Values.Count);
+ var invariantValue = blockListValue.SettingsData.First().Values.First(value => value.Alias == "invariantText");
+ var variantValue = blockListValue.SettingsData.First().Values.First(value => value.Alias == "variantText");
+ Assert.IsNull(invariantValue.Culture);
+ Assert.AreEqual("en-US", variantValue.Culture);
+ });
+ Assert.Multiple(() =>
+ {
+ Assert.AreEqual(1, blockListValue.Expose.Count);
+ Assert.AreEqual("en-US", blockListValue.Expose.First().Culture);
+ });
+ }
+
+ [TestCase(ContentVariation.Nothing)]
+ [TestCase(ContentVariation.Culture)]
+ public async Task Can_Turn_Variant_Element_Invariant(ContentVariation contentTypeVariation)
+ {
+ var elementType = CreateElementType(ContentVariation.Culture);
+ var blockListDataType = await CreateBlockListDataType(elementType);
+ var contentType = CreateContentType(contentTypeVariation, blockListDataType);
+
+ var content = CreateContent(
+ contentType,
+ elementType,
+ new List
+ {
+ new() { Alias = "invariantText", Value = "The invariant content value" },
+ new() { Alias = "variantText", Value = "Variant content in English", Culture = "en-US" },
+ new() { Alias = "variantText", Value = "Variant content in Danish", Culture = "da-DK" }
+ },
+ new List
+ {
+ new() { Alias = "invariantText", Value = "The invariant settings value" },
+ new() { Alias = "variantText", Value = "Variant settings in English", Culture = "en-US" },
+ new() { Alias = "variantText", Value = "Variant settings in Danish", Culture = "da-DK" }
+ },
+ false);
+
+ elementType.Variations = ContentVariation.Nothing;
+ elementType.PropertyTypes.First(p => p.Alias == "variantText").Variations = ContentVariation.Nothing;
+ ContentTypeService.Save(elementType);
+
+ // re-fetch content
+ content = ContentService.GetById(content.Key);
+
+ var valueEditor = (BlockListPropertyEditorBase.BlockListEditorPropertyValueEditor)blockListDataType.Editor!.GetValueEditor();
+
+ var blockListValue = valueEditor.ToEditor(content!.Properties["blocks"]!) as BlockListValue;
+ Assert.IsNotNull(blockListValue);
+ Assert.Multiple(() =>
+ {
+ Assert.AreEqual(1, blockListValue.ContentData.Count);
+ Assert.AreEqual(2, blockListValue.ContentData.First().Values.Count);
+ var invariantValue = blockListValue.ContentData.First().Values.First(value => value.Alias == "invariantText");
+ var variantValue = blockListValue.ContentData.First().Values.First(value => value.Alias == "variantText");
+ Assert.IsNull(invariantValue.Culture);
+ Assert.IsNull(variantValue.Culture);
+ Assert.AreEqual("Variant content in English", variantValue.Value);
+ });
+ Assert.Multiple(() =>
+ {
+ Assert.AreEqual(1, blockListValue.SettingsData.Count);
+ Assert.AreEqual(2, blockListValue.SettingsData.First().Values.Count);
+ var invariantValue = blockListValue.SettingsData.First().Values.First(value => value.Alias == "invariantText");
+ var variantValue = blockListValue.SettingsData.First().Values.First(value => value.Alias == "variantText");
+ Assert.IsNull(invariantValue.Culture);
+ Assert.IsNull(variantValue.Culture);
+ Assert.AreEqual("Variant settings in English", variantValue.Value);
+ });
+ Assert.Multiple(() =>
+ {
+ Assert.AreEqual(1, blockListValue.Expose.Count);
+ Assert.IsNull(blockListValue.Expose.First().Culture);
+ });
+ }
+
private async Task CreateLimitedUser()
{
var userGroupService = GetRequiredService();
From 028da4545ebe23df44ed03f2cbc7df21d41775c3 Mon Sep 17 00:00:00 2001
From: Henrik
Date: Wed, 26 Mar 2025 16:01:53 +0100
Subject: [PATCH 04/38] Reduce CPU time when initiating RepositoryCacheKeys
(#18267)
* Avoid an unneeded lookups in the Keys dictionary when initiating key cache
* Add further comments and unit tests around updated code.
---------
Co-authored-by: Andy Butland
---
.../Repositories/RepositoryCacheKeys.cs | 25 ++++++++++++++++--
.../Repositories/RepositoryCacheKeysTests.cs | 26 +++++++++++++++++++
2 files changed, 49 insertions(+), 2 deletions(-)
create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeysTests.cs
diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeys.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeys.cs
index a6b6c16aa5..aca68e9762 100644
--- a/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeys.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeys.cs
@@ -1,3 +1,5 @@
+using System.Runtime.InteropServices;
+
namespace Umbraco.Cms.Core.Persistence.Repositories;
///
@@ -5,15 +7,34 @@ namespace Umbraco.Cms.Core.Persistence.Repositories;
///
public static class RepositoryCacheKeys
{
- // used to cache keys so we don't keep allocating strings
+ ///
+ /// A cache for the keys we don't keep allocating strings.
+ ///
private static readonly Dictionary Keys = new();
+ ///
+ /// Gets the repository cache key for the provided type.
+ ///
public static string GetKey()
{
Type type = typeof(T);
- return Keys.TryGetValue(type, out var key) ? key : Keys[type] = "uRepo_" + type.Name + "_";
+
+ // The following code is a micro-optimization to avoid an unnecessary lookup in the Keys dictionary, when writing the newly created key.
+ // Previously, the code was:
+ // return Keys.TryGetValue(type, out var key)
+ // ? key
+ // : Keys[type] = "uRepo_" + type.Name + "_";
+
+ // Look up the existing value or get a reference to the newly created default value.
+ ref string? key = ref CollectionsMarshal.GetValueRefOrAddDefault(Keys, type, out _);
+
+ // As we have the reference, we can just assign it if null, without the expensive write back to the dictionary.
+ return key ??= "uRepo_" + type.Name + "_";
}
+ ///
+ /// Gets the repository cache key for the provided type and Id.
+ ///
public static string GetKey(TId? id)
{
if (EqualityComparer.Default.Equals(id, default))
diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeysTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeysTests.cs
new file mode 100644
index 0000000000..fef83541b9
--- /dev/null
+++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeysTests.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using NUnit.Framework;
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Persistence.Repositories;
+
+namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors;
+
+[TestFixture]
+public class RepositoryCacheKeysTests
+{
+ [Test]
+ public void GetKey_Returns_Expected_Key_For_Type()
+ {
+ var key = RepositoryCacheKeys.GetKey();
+ Assert.AreEqual("uRepo_IContent_", key);
+ }
+
+ [Test]
+ public void GetKey_Returns_Expected_Key_For_Type_And_Id()
+ {
+ var key = RepositoryCacheKeys.GetKey(1000);
+ Assert.AreEqual("uRepo_IContent_1000", key);
+ }
+}
From 95e89f84816dd0ca9738f5cd82930656a58e7f68 Mon Sep 17 00:00:00 2001
From: Henrik
Date: Wed, 26 Mar 2025 16:55:03 +0100
Subject: [PATCH 05/38] Avoid a hash key generation and lookup when inserting
in the LockingMechanism (#18243)
* Avoid a hash key generation and lookup when inserting in the LockingMechanism
* Added comments for CollectionsMarshal.GetValueRefOrAddDefault
* Added further comments and tests.
---------
Co-authored-by: Andy Butland
---
src/Umbraco.Core/Scoping/LockingMechanism.cs | 42 ++++++++++++-----
.../Builders/ContentBuilder.cs | 5 +--
.../Scoping/LockingMechanismTests.cs | 45 +++++++++++++++++++
3 files changed, 77 insertions(+), 15 deletions(-)
create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/LockingMechanismTests.cs
diff --git a/src/Umbraco.Core/Scoping/LockingMechanism.cs b/src/Umbraco.Core/Scoping/LockingMechanism.cs
index 0cee4293f6..e078b047e6 100644
--- a/src/Umbraco.Core/Scoping/LockingMechanism.cs
+++ b/src/Umbraco.Core/Scoping/LockingMechanism.cs
@@ -1,3 +1,4 @@
+using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Collections;
@@ -7,7 +8,7 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Scoping;
///
-/// Mechanism for handling read and write locks
+/// Mechanism for handling read and write locks.
///
public class LockingMechanism : ILockingMechanism
{
@@ -189,24 +190,43 @@ public class LockingMechanism : ILockingMechanism
/// Lock ID to increment.
/// Instance ID of the scope requesting the lock.
/// Reference to the dictionary to increment on
- private void IncrementLock(int lockId, Guid instanceId, ref Dictionary>? locks)
+ /// Internal for tests.
+ internal static void IncrementLock(int lockId, Guid instanceId, ref Dictionary>? locks)
{
// Since we've already checked that we're the parent in the WriteLockInner method, we don't need to check again.
- // If it's the very first time a lock has been requested the WriteLocks dict hasn't been instantiated yet.
- locks ??= new Dictionary>();
+ // If it's the very first time a lock has been requested the WriteLocks dictionary hasn't been instantiated yet.
+ locks ??= [];
- // Try and get the dict associated with the scope id.
- var locksDictFound = locks.TryGetValue(instanceId, out Dictionary? locksDict);
+ // Try and get the dictionary associated with the scope id.
+
+ // The following code is a micro-optimization.
+ // GetValueRefOrAddDefault does lookup or creation with only one hash key generation, internal bucket lookup and value lookup in the bucket.
+ // This compares to doing it twice when initializing, one for the lookup and one for the insertion of the initial value, we had with the
+ // previous code:
+ // var locksDictFound = locks.TryGetValue(instanceId, out Dictionary? locksDict);
+ // if (locksDictFound)
+ // {
+ // locksDict!.TryGetValue(lockId, out var value);
+ // locksDict[lockId] = value + 1;
+ // }
+ // else
+ // {
+ // // The scope hasn't requested a lock yet, so we have to create a dict for it.
+ // locks.Add(instanceId, new Dictionary());
+ // locks[instanceId][lockId] = 1;
+ // }
+
+ ref Dictionary? locksDict = ref CollectionsMarshal.GetValueRefOrAddDefault(locks, instanceId, out bool locksDictFound);
if (locksDictFound)
{
- locksDict!.TryGetValue(lockId, out var value);
- locksDict[lockId] = value + 1;
+ // By getting a reference to any existing or default 0 value, we can increment it without the expensive write back into the dictionary.
+ ref int value = ref CollectionsMarshal.GetValueRefOrAddDefault(locksDict!, lockId, out _);
+ value++;
}
else
{
- // The scope hasn't requested a lock yet, so we have to create a dict for it.
- locks.Add(instanceId, new Dictionary());
- locks[instanceId][lockId] = 1;
+ // The scope hasn't requested a lock yet, so we have to create a dictionary for it.
+ locksDict = new Dictionary { { lockId, 1 } };
}
}
diff --git a/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs b/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs
index 53c2f50f10..1fd66da312 100644
--- a/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs
+++ b/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs
@@ -185,10 +185,7 @@ public class ContentBuilder
{
if (string.IsNullOrWhiteSpace(name))
{
- if (_cultureNames.TryGetValue(culture, out _))
- {
- _cultureNames.Remove(culture);
- }
+ _cultureNames.Remove(culture);
}
else
{
diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/LockingMechanismTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/LockingMechanismTests.cs
new file mode 100644
index 0000000000..eb2d6abdfb
--- /dev/null
+++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/LockingMechanismTests.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using NUnit.Framework;
+using Umbraco.Cms.Core.Scoping;
+
+namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Scoping;
+
+[TestFixture]
+internal class LockingMechanismTests
+{
+ private const int LockId = 1000;
+ private const int LockId2 = 1001;
+ private static readonly Guid _scopeInstanceId = Guid.NewGuid();
+
+ [Test]
+ public void IncrementLock_WithoutLocksDictionary_CreatesLock()
+ {
+ var locks = new Dictionary>();
+ LockingMechanism.IncrementLock(LockId, _scopeInstanceId, ref locks);
+ Assert.AreEqual(1, locks.Count);
+ Assert.AreEqual(1, locks[_scopeInstanceId][LockId]);
+ }
+
+ [Test]
+ public void IncrementLock_WithExistingLocksDictionary_CreatesLock()
+ {
+ var locks = new Dictionary>()
+ {
+ {
+ _scopeInstanceId,
+ new Dictionary()
+ {
+ { LockId, 100 },
+ { LockId2, 200 }
+ }
+ }
+ };
+ LockingMechanism.IncrementLock(LockId, _scopeInstanceId, ref locks);
+ Assert.AreEqual(1, locks.Count);
+ Assert.AreEqual(2, locks[_scopeInstanceId].Count);
+ Assert.AreEqual(101, locks[_scopeInstanceId][LockId]);
+ Assert.AreEqual(200, locks[_scopeInstanceId][LockId2]);
+ }
+}
From 94f0add4d90cdb5a76db8702b64c572179c09e11 Mon Sep 17 00:00:00 2001
From: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>
Date: Thu, 27 Mar 2025 10:42:13 +0700
Subject: [PATCH 06/38] V15 Added acceptance tests for data type default
configuration (#18740)
* Added tests for Approved Color default configuration
* Updated tests for Approved Color configuration
* Added tests for ChecklboxList configuration
* Added tests for Data type default configuration - part 1
* Added tests for data type configuration and updated tests due to test helper changes
* Added more steps to verify the default configuration
* Added tests for the default configuration and refactoring code
* Added steps to verify the TinyMCE default configuration
* Bumped version
* Fixed tests due to test helper changes
* Make all Data Type tests run in the pipeline
* Updated assertion steps
* Fixed format
* Bumped version
* Bumped version
* Comment failing tests
* Reverted npm command
---
.../package-lock.json | 8 +-
.../Umbraco.Tests.AcceptanceTest/package.json | 2 +-
.../Content/ContentWithApprovedColor.spec.ts | 2 +-
.../DataType/ApprovedColor.spec.ts | 98 ++---
.../DataType/CheckboxList.spec.ts | 86 ++--
.../DataType/ContentPicker.spec.ts | 75 ++--
.../DefaultConfig/DataType/DataType.spec.ts | 9 +-
.../DefaultConfig/DataType/DatePicker.spec.ts | 114 ++---
.../DefaultConfig/DataType/Dropdown.spec.ts | 85 ++--
.../DataType/ImageCropper.spec.ts | 120 ++---
.../DefaultConfig/DataType/Label.spec.ts | 75 ++--
.../DefaultConfig/DataType/ListView.spec.ts | 414 +++++++-----------
.../DataType/MediaPicker.spec.ts | 394 ++++++++---------
.../DataType/MemberPicker.spec.ts | 22 +
.../DataType/MultiUrlPicker.spec.ts | 88 ++--
.../DefaultConfig/DataType/Numeric.spec.ts | 77 ++--
.../DefaultConfig/DataType/Radiobox.spec.ts | 67 +--
.../DataType/RichTextEditor.spec.ts | 70 ++-
.../tests/DefaultConfig/DataType/Tags.spec.ts | 50 ++-
.../DefaultConfig/DataType/Textarea.spec.ts | 53 ++-
.../DefaultConfig/DataType/Textstring.spec.ts | 41 +-
.../DefaultConfig/DataType/TinyMCE.spec.ts | 102 ++---
.../DefaultConfig/DataType/Tiptap.spec.ts | 51 +--
.../DefaultConfig/DataType/TrueFalse.spec.ts | 81 ++--
.../DefaultConfig/DataType/Upload.spec.ts | 76 ----
.../DataType/UploadField.spec.ts | 73 +++
.../RenderingContentWithApprovedColor.spec.ts | 2 +-
27 files changed, 1082 insertions(+), 1253 deletions(-)
create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MemberPicker.spec.ts
delete mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Upload.spec.ts
create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/UploadField.spec.ts
diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json
index bf03842d46..3454d8ffe7 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.31",
- "@umbraco/playwright-testhelpers": "^15.0.39",
+ "@umbraco/playwright-testhelpers": "^15.0.40",
"camelize": "^1.0.0",
"dotenv": "^16.3.1",
"node-fetch": "^2.6.7"
@@ -67,9 +67,9 @@
}
},
"node_modules/@umbraco/playwright-testhelpers": {
- "version": "15.0.39",
- "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.39.tgz",
- "integrity": "sha512-dNl+P5LOW4CZrlzt7TnXOKUDeHI3juN/BfG9b0P/selpiFPrseH1HrB0myJVmPfuq4KCa+490VTu46qXHGwGLw==",
+ "version": "15.0.40",
+ "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.40.tgz",
+ "integrity": "sha512-dxXCCYeUH0rlASdHHNu8gQQrhK52gxGcwb/K1BlXFsr7Z7dz1U5eYMPUiVjDVg6LNCbqmQ/tmZqoAZLU5zDzIw==",
"dependencies": {
"@umbraco/json-models-builders": "2.0.31",
"node-fetch": "^2.6.7"
diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json
index 82bbc68831..63aafd3813 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.31",
- "@umbraco/playwright-testhelpers": "^15.0.39",
+ "@umbraco/playwright-testhelpers": "^15.0.40",
"camelize": "^1.0.0",
"dotenv": "^16.3.1",
"node-fetch": "^2.6.7"
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithApprovedColor.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithApprovedColor.spec.ts
index cfe7aaeb15..a50279788f 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithApprovedColor.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithApprovedColor.spec.ts
@@ -63,7 +63,7 @@ test('can create content with the custom approved color data type', async ({umbr
const customDataTypeName = 'CustomApprovedColor';
const colorValue = 'd73737';
const colorLabel = 'Test Label';
- const customDataTypeId = await umbracoApi.dataType.createApprovedColorDataTypeWithOneItem(customDataTypeName, colorLabel, colorValue);
+ const customDataTypeId = await umbracoApi.dataType.createDefaultApprovedColorDataTypeWithOneItem(customDataTypeName, colorLabel, colorValue);
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ApprovedColor.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ApprovedColor.spec.ts
index e853b2afa8..efff81e2f7 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ApprovedColor.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ApprovedColor.spec.ts
@@ -1,102 +1,78 @@
-import {test} from '@umbraco/playwright-testhelpers';
+import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
const dataTypeName = 'Approved Color';
-let dataTypeDefaultData = null;
-let dataTypeData = null;
-const colorValue = 'ffffff';
-const colorLabel = '';
+const customDataTypeName = 'Custom Approved Color';
+const editorAlias = 'Umbraco.ColorPicker';
+const editorUiAlias = 'Umb.PropertyEditorUi.ColorPicker';
+const colorValue = '9c2121';
+const colorLabel = 'red';
test.beforeEach(async ({umbracoUi, umbracoApi}) => {
await umbracoUi.goToBackOffice();
await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
- dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test.afterEach(async ({umbracoApi}) => {
- if (dataTypeDefaultData !== null) {
- await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData);
- }
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test('can include label', async ({umbracoApi, umbracoUi}) => {
// Arrange
- const expectedDataTypeValues = [
- {
- "alias": "useLabel",
- "value": true
- }
- ];
- // Remove all existing values
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- dataTypeData.values = [];
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
- await umbracoUi.dataType.goToDataType(dataTypeName);
+ await umbracoApi.dataType.createDefaultApprovedColorDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.clickIncludeLabelsToggle();
await umbracoUi.dataType.clickSaveButton();
// Assert
- await umbracoUi.dataType.isSuccessNotificationVisible();
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'useLabel', true)).toBeTruthy();
});
test('can add color', async ({umbracoApi, umbracoUi}) => {
// Arrange
- const expectedDataTypeValues = [
- {
- "alias": "items",
- "value": [
- {
- "value": colorValue,
- "label": colorLabel
- }
- ]
- }
- ];
- // Remove all existing values
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- dataTypeData.values = [];
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
- await umbracoUi.dataType.goToDataType(dataTypeName);
+ await umbracoApi.dataType.createDefaultApprovedColorDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.addColor(colorValue);
await umbracoUi.dataType.clickSaveButton();
// Assert
- await umbracoUi.dataType.isSuccessNotificationVisible();
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesApprovedColorHaveColor(customDataTypeName, colorValue)).toBeTruthy();
});
test('can remove color', async ({umbracoApi, umbracoUi}) => {
// Arrange
- const removedDataTypeValues = [
- {
- "alias": "items",
- "value": [
- {
- "value": colorValue,
- "label": colorLabel
- }
- ]
- }
- ];
- // Remove all existing values and add a color to remove
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- dataTypeData.values = removedDataTypeValues;
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
- await umbracoUi.dataType.goToDataType(dataTypeName);
+ await umbracoApi.dataType.createApprovedColorDataTypeWithOneItem(customDataTypeName, colorLabel, colorValue);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.removeColorByValue(colorValue);
await umbracoUi.dataType.clickSaveButton();
// Assert
- await umbracoUi.dataType.isSuccessNotificationVisible();
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toEqual([]);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesApprovedColorHaveColor(customDataTypeName, colorValue)).toBeFalsy();;
});
+
+test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.dataType.goToDataType(dataTypeName);
+
+ // Assert
+ await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.approvedColorSettings);
+ await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.approvedColorSettings);
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias);
+ const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ expect(dataTypeDefaultData.editorAlias).toBe(editorAlias);
+ expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias);
+ expect(dataTypeDefaultData.values).toEqual([]);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'useLabel')).toBeFalsy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'items')).toBeFalsy();
+});
\ No newline at end of file
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/CheckboxList.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/CheckboxList.spec.ts
index 99ad3e1cf2..0e9cd8ed77 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/CheckboxList.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/CheckboxList.spec.ts
@@ -1,36 +1,26 @@
-import {test} from '@umbraco/playwright-testhelpers';
+import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
const dataTypeName = 'Checkbox list';
-let dataTypeDefaultData = null;
-let dataTypeData = null;
+const customDataTypeName = 'Custom Checkbox List';
+const editorAlias = 'Umbraco.CheckBoxList';
+const editorUiAlias = 'Umb.PropertyEditorUi.CheckBoxList';
test.beforeEach(async ({umbracoUi, umbracoApi}) => {
await umbracoUi.goToBackOffice();
await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
- dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test.afterEach(async ({umbracoApi}) => {
- if (dataTypeDefaultData !== null) {
- await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData);
- }
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test('can add option', async ({umbracoApi, umbracoUi}) => {
// Arrange
const optionName = 'Test option';
- const expectedDataTypeValues = [
- {
- "alias": "items",
- "value": [optionName]
- }
- ];
- // Remove all existing options
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- dataTypeData.values = [];
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
- await umbracoUi.dataType.goToDataType(dataTypeName);
+ await umbracoApi.dataType.createCheckboxListDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.clickAddOptionButton();
@@ -38,61 +28,53 @@ test('can add option', async ({umbracoApi, umbracoUi}) => {
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'items', [optionName])).toBeTruthy();
});
test('can remove option', async ({umbracoApi, umbracoUi}) => {
// Arrange
const removedOptionName = 'Removed Option';
- const removedOptionValues = [
- {
- "alias": "items",
- "value": [removedOptionName]
- }
- ];
- // Remove all existing options and add an option to remove
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- dataTypeData.values = removedOptionValues;
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
- await umbracoUi.dataType.goToDataType(dataTypeName);
+ await umbracoApi.dataType.createCheckboxListDataType(customDataTypeName, [removedOptionName]);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.removeOptionByName(removedOptionName);
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toEqual([]);
+ const customDataTypeData = await umbracoApi.dataType.getByName(customDataTypeName);
+ expect(customDataTypeData.values).toEqual([]);
});
test('can update option', async ({umbracoApi, umbracoUi}) => {
// Arrange
const optionName = 'Test option';
const updatedOptionName = 'Updated option';
- const optionValues = [
- {
- "alias": "items",
- "value": [optionName]
- }
- ];
- const expectedOptionValues = [
- {
- "alias": "items",
- "value": [updatedOptionName]
- }
- ];
- // Remove all existing options and add an option to update
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- dataTypeData.values = optionValues;
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
- await umbracoUi.dataType.goToDataType(dataTypeName);
+ await umbracoApi.dataType.createCheckboxListDataType(customDataTypeName, [optionName]);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.enterOptionName(updatedOptionName);
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toEqual(expectedOptionValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'items', [updatedOptionName])).toBeTruthy();
});
+
+test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.dataType.goToDataType(dataTypeName);
+
+ // Assert
+ await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.checkboxListSettings);
+ await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.checkboxListSettings);
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias);
+ const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName)
+ expect(dataTypeDefaultData.editorAlias).toBe(editorAlias);
+ expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias);
+ expect(dataTypeDefaultData.values).toEqual([]);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'items')).toBeFalsy();
+});
\ No newline at end of file
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ContentPicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ContentPicker.spec.ts
index 92819d3c37..4f34c9cf2c 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ContentPicker.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ContentPicker.spec.ts
@@ -1,37 +1,33 @@
-import {test} from '@umbraco/playwright-testhelpers';
+import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
const dataTypeName = 'Content Picker';
-let dataTypeDefaultData = null;
-let dataTypeData = null;
+const customDataTypeName = 'Custom Content Picker';
+const editorAlias = 'Umbraco.ContentPicker';
+const editorUiAlias = 'Umb.PropertyEditorUi.DocumentPicker';
test.beforeEach(async ({umbracoUi, umbracoApi}) => {
await umbracoUi.goToBackOffice();
await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
- dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test.afterEach(async ({umbracoApi}) => {
- if (dataTypeDefaultData !== null) {
- await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData);
- }
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test('can ignore user start nodes', async ({umbracoApi, umbracoUi}) => {
// Arrange
- const expectedDataTypeValues = {
- "alias": "ignoreUserStartNodes",
- "value": true
- };
- await umbracoUi.dataType.goToDataType(dataTypeName);
+ await umbracoApi.dataType.createDefaultContentPickerDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.clickIgnoreUserStartNodesToggle();
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'ignoreUserStartNodes', true)).toBeTruthy();
});
test('can add start node', async ({umbracoApi, umbracoUi}) => {
@@ -39,17 +35,12 @@ test('can add start node', async ({umbracoApi, umbracoUi}) => {
// Create content
const documentTypeName = 'TestDocumentType';
const contentName = 'TestStartNode';
- await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
const documentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeName);
- await umbracoApi.document.ensureNameNotExists(contentName);
const contentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
expect(await umbracoApi.document.doesExist(contentId)).toBeTruthy();
-
- const expectedDataTypeValues = {
- "alias": "startNodeId",
- "value": contentId
- };
- await umbracoUi.dataType.goToDataType(dataTypeName);
+ // Create data type
+ await umbracoApi.dataType.createDefaultContentPickerDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.clickChooseButton();
@@ -57,8 +48,8 @@ test('can add start node', async ({umbracoApi, umbracoUi}) => {
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'startNodeId', contentId)).toBeTruthy();
// Clean
await umbracoApi.document.ensureNameNotExists(contentName);
@@ -70,32 +61,38 @@ test('can remove start node', async ({umbracoApi, umbracoUi}) => {
// Create content
const documentTypeName = 'TestDocumentType';
const contentName = 'TestStartNode';
- await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
const documentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeName);
- await umbracoApi.document.ensureNameNotExists(contentName);
const contentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
expect(await umbracoApi.document.doesExist(contentId)).toBeTruthy();
-
- const removedDataTypeValues = [{
- "alias": "startNodeId",
- "value": contentId
- }];
-
- // Remove all existing values and add a start node to remove
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- dataTypeData.values = removedDataTypeValues;
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
- await umbracoUi.dataType.goToDataType(dataTypeName);
+ // Create data type
+ await umbracoApi.dataType.createContentPickerDataTypeWithStartNode(customDataTypeName, contentId);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.removeContentStartNode(contentName);
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toEqual([]);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ const customDataTypeData = await umbracoApi.dataType.getByName(customDataTypeName);
+ expect(customDataTypeData.values).toEqual([]);
// Clean
await umbracoApi.document.ensureNameNotExists(contentName);
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
});
+
+test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.dataType.goToDataType(dataTypeName);
+
+ // Assert
+ await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.contentPickerSettings);
+ await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.contentPickerSettings);
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias);
+ const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ expect(dataTypeDefaultData.editorAlias).toBe(editorAlias);
+ expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias);
+ expect(dataTypeDefaultData.values).toEqual([]);
+});
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataType.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataType.spec.ts
index f32dd850bf..c4d3d3fda1 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataType.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataType.spec.ts
@@ -103,10 +103,6 @@ test('cannot create a data type without selecting the property editor', {tag: '@
test('can change settings', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
// Arrange
const maxCharsValue = 126;
- const expectedDataTypeValues = {
- "alias": "maxChars",
- "value": maxCharsValue
- };
await umbracoApi.dataType.createTextstringDataType(dataTypeName);
expect(await umbracoApi.dataType.doesNameExist(dataTypeName)).toBeTruthy();
@@ -117,6 +113,5 @@ test('can change settings', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) =>
// Assert
await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
- const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
-});
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'maxChars', maxCharsValue)).toBeTruthy();
+});
\ No newline at end of file
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DatePicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DatePicker.spec.ts
index 3e71aa59c9..06052af6b6 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DatePicker.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DatePicker.spec.ts
@@ -1,75 +1,49 @@
-import { test } from "@umbraco/playwright-testhelpers";
+import { ConstantHelper, NotificationConstantHelper, test } from "@umbraco/playwright-testhelpers";
import { expect } from "@playwright/test";
-const datePickerTypes = ['Date Picker', 'Date Picker with time'];
+const editorAlias = 'Umbraco.DateTime';
+const editorUiAlias = 'Umb.PropertyEditorUi.DatePicker';
+const datePickerTypes = [
+ {type: 'Date Picker', format: 'YYYY-MM-DD'},
+ {type: 'Date Picker with time', format: 'YYYY-MM-DD HH:mm:ss'}
+];
+const customDataTypeName = 'Custom DateTime';
+
+test.beforeEach(async ({umbracoUi, umbracoApi}) => {
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
+});
+
+test.afterEach(async ({umbracoApi}) => {
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
+});
+
+test('can update date format', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dateFormatValue = 'DD-MM-YYYY hh:mm:ss';
+ await umbracoApi.dataType.createDefaultDateTimeDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
+
+ // Act
+ await umbracoUi.dataType.enterDateFormatValue(dateFormatValue);
+ await umbracoUi.dataType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'format', dateFormatValue)).toBeTruthy();
+});
+
for (const datePickerType of datePickerTypes) {
- test.describe(`${datePickerType} tests`, () => {
- let dataTypeDefaultData = null;
- let dataTypeData = null;
+ test(`the default configuration of ${datePickerType.type} is correct`, async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.dataType.goToDataType(datePickerType.type);
- test.beforeEach(async ({ umbracoUi, umbracoApi }) => {
- await umbracoUi.goToBackOffice();
- await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
- dataTypeDefaultData = await umbracoApi.dataType.getByName(datePickerType);
- await umbracoUi.dataType.goToDataType(datePickerType);
- });
-
- test.afterEach(async ({ umbracoApi }) => {
- if (dataTypeDefaultData !== null) {
- await umbracoApi.dataType.update(dataTypeDefaultData.id,dataTypeDefaultData);
- }
- });
-
- // This test is out-of-date since currently it is impossible to update offset time in front-end
- test.skip(`can update offset time`, async ({ umbracoApi, umbracoUi }) => {
- // Arrange
- const expectedDataTypeValues =
- datePickerType === 'Date Picker'
- ? [
- {
- "alias": "format",
- "value": "YYYY-MM-DD",
- },
- {
- "alias": "offsetTime",
- "value": true,
- },
- ]
- : [
- {
- "alias": "format",
- "value": "YYYY-MM-DD HH:mm:ss",
- },
- {
- "alias": "offsetTime",
- "value": true,
- }
- ];
-
- // Act
- await umbracoUi.dataType.clickOffsetTimeToggle();
- await umbracoUi.dataType.clickSaveButton();
-
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(datePickerType);
- expect(dataTypeData.values).toEqual(expectedDataTypeValues);
- });
-
- test('can update date format', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- const dateFormatValue =
- datePickerType === "Date Picker" ? "DD-MM-YYYY" : "DD-MM-YYYY hh:mm:ss";
- const expectedDataTypeValues = {
- "alias": "format",
- "value": dateFormatValue
- };
- // Act
- await umbracoUi.dataType.enterDateFormatValue(dateFormatValue);
- await umbracoUi.dataType.clickSaveButton();
-
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(datePickerType);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
- });
+ // Assert
+ await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.datePickerSettings);
+ await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.datePickerSettings);
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(datePickerType.type, 'format', datePickerType.format)).toBeTruthy();
});
-}
+}
\ No newline at end of file
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Dropdown.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Dropdown.spec.ts
index 5e7f3586f7..e97bb5db1b 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Dropdown.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Dropdown.spec.ts
@@ -1,57 +1,43 @@
-import {test} from '@umbraco/playwright-testhelpers';
+import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
-const dataTypeName = 'Dropdown';
-let dataTypeDefaultData = null;
-let dataTypeData = null;
+const customDataTypeName = 'Custom Dropdown';
+const editorAlias = 'Umbraco.DropDown.Flexible';
+const editorUiAlias = 'Umb.PropertyEditorUi.Dropdown';
+const dropdowns = [
+ {type: 'Dropdown', multipleChoice: false},
+ {type: 'Dropdown multiple', multipleChoice: true}
+];
test.beforeEach(async ({umbracoUi, umbracoApi}) => {
await umbracoUi.goToBackOffice();
await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
- dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test.afterEach(async ({umbracoApi}) => {
- if (dataTypeDefaultData !== null) {
- await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData);
- }
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test('can enable multiple choice', async ({umbracoApi, umbracoUi}) => {
// Arrange
- const expectedDataTypeValues = [{
- "alias": "multiple",
- "value": true
- }];
- // Remove all existing options
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- dataTypeData.values = [];
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
- await umbracoUi.dataType.goToDataType(dataTypeName);
+ await umbracoApi.dataType.createDefaultDropdownDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.clickEnableMultipleChoiceToggle();
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'multiple', true)).toBeTruthy();
});
test('can add option', async ({umbracoApi, umbracoUi}) => {
// Arrange
const optionName = 'Test option';
- const expectedDataTypeValues = [
- {
- "alias": "items",
- "value": [optionName]
- }
- ];
- // Remove all existing options
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- dataTypeData.values = [];
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
- await umbracoUi.dataType.goToDataType(dataTypeName);
+ await umbracoApi.dataType.createDefaultDropdownDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.clickAddOptionButton();
@@ -59,30 +45,39 @@ test('can add option', async ({umbracoApi, umbracoUi}) => {
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'items', [optionName])).toBeTruthy();
});
test('can remove option', async ({umbracoApi, umbracoUi}) => {
// Arrange
const removedOptionName = 'Removed Option';
- const removedOptionValues = [
- {
- "alias": "items",
- "value": [removedOptionName]
- }
- ];
- // Remove all existing options and add an option to remove
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- dataTypeData.values = removedOptionValues;
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
- await umbracoUi.dataType.goToDataType(dataTypeName);
+ await umbracoApi.dataType.createDropdownDataType(customDataTypeName, false, [removedOptionName]);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.removeOptionByName(removedOptionName);
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toEqual([]);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'items', [removedOptionName])).toBeFalsy();
});
+
+for (const dropdown of dropdowns) {
+ test(`the default configuration of ${dropdown.type} is correct`, async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.dataType.goToDataType(dropdown.type);
+
+ // Assert
+ await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.dropdownSettings);
+ await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.dropdownSettings);
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias);
+ const dataTypeDefaultData = await umbracoApi.dataType.getByName(dropdown.type);
+ expect(dataTypeDefaultData.editorAlias).toBe(editorAlias);
+ expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dropdown.type, 'multiple', dropdown.multipleChoice)).toBeTruthy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dropdown.type, 'items')).toBeFalsy();
+ });
+}
\ No newline at end of file
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ImageCropper.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ImageCropper.spec.ts
index 75c7c47946..6721c974f7 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ImageCropper.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ImageCropper.spec.ts
@@ -1,41 +1,26 @@
-import {test} from '@umbraco/playwright-testhelpers';
+import {AliasHelper, ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
const dataTypeName = 'Image Cropper';
-let dataTypeDefaultData = null;
-let dataTypeData = null;
+const editorAlias = 'Umbraco.ImageCropper';
+const editorUiAlias = 'Umb.PropertyEditorUi.ImageCropper';
+const customDataTypeName = 'Custom Image Cropper';
test.beforeEach(async ({umbracoUi, umbracoApi}) => {
await umbracoUi.goToBackOffice();
await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
- dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test.afterEach(async ({umbracoApi}) => {
- if (dataTypeDefaultData !== null) {
- await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData);
- }
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test('can add crop', async ({umbracoApi, umbracoUi}) => {
// Arrange
const cropData = ['Test Label', 'Test Alias', 100, 50];
- const expectedDataTypeValues = [{
- "alias": "crops",
- "value": [
- {
- "label": cropData[0],
- "alias": cropData[1],
- "width": cropData[2],
- "height": cropData[3]
- }
- ]
- }];
- // Remove all existing crops
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- dataTypeData.values = [];
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
- await umbracoUi.dataType.goToDataType(dataTypeName);
+ await umbracoApi.dataType.createDefaultImageCropperDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.enterCropValues(
@@ -48,79 +33,56 @@ test('can add crop', async ({umbracoApi, umbracoUi}) => {
await umbracoUi.dataType.clickSaveButton();
// Assert
- await umbracoUi.dataType.isSuccessNotificationVisible();
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveCrops(customDataTypeName, cropData[0], cropData[1], cropData[2], cropData[3])).toBeTruthy();
});
test('can edit crop', async ({umbracoApi, umbracoUi}) => {
// Arrange
- const wrongCropData = ['Wrong Alias', 50, 100];
- const wrongDataTypeValues = [{
- "alias": "crops",
- "value": [
- {
- "alias": wrongCropData[0],
- "width": wrongCropData[1],
- "height": wrongCropData[2]
- }
- ]
- }];
- const updatedCropData = ['Updated Label', 'Updated Test Alias', 100, 50];
- const expectedDataTypeValues = [{
- "alias": "crops",
- "value": [
- {
- "label": updatedCropData[0],
- "alias": updatedCropData[1],
- "width": updatedCropData[2],
- "height": updatedCropData[3]
- }
- ]
- }];
- // Remove all existing crops and add a crop to edit
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- dataTypeData.values = wrongDataTypeValues;
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
- await umbracoUi.dataType.goToDataType(dataTypeName);
+ const cropData = ['Test Label', AliasHelper.toAlias('Test Label'), 100, 50];
+ const updatedCropData = ['Updated Label', AliasHelper.toAlias('Updated Label'), 80, 30];
+ await umbracoApi.dataType.createImageCropperDataTypeWithOneCrop(customDataTypeName, cropData[0], cropData[2], cropData[3]);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
- await umbracoUi.dataType.editCropByAlias(wrongCropData[0].toString());
- await umbracoUi.dataType.enterCropValues(updatedCropData[0].toString(), updatedCropData[1].toString(), updatedCropData[2].toString(), updatedCropData[3].toString());
+ await umbracoUi.dataType.editCropByAlias(cropData[0]);
+ await umbracoUi.dataType.enterCropValues(updatedCropData[0], updatedCropData[1], updatedCropData[2].toString(), updatedCropData[3].toString());
await umbracoUi.dataType.clickSaveCropButton();
await umbracoUi.dataType.clickSaveButton();
// Assert
- await umbracoUi.dataType.isSuccessNotificationVisible();
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveCrops(customDataTypeName, updatedCropData[0], updatedCropData[1], updatedCropData[2], updatedCropData[3])).toBeTruthy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveCrops(customDataTypeName, cropData[0], cropData[1], cropData[2], cropData[3])).toBeFalsy();
});
test('can delete crop', async ({umbracoApi, umbracoUi}) => {
// Arrange
- const wrongCropData = ['Wrong Alias', 50, 100];
- const wrongDataTypeValues = [{
- "alias": "crops",
- "value": [
- {
- "alias": wrongCropData[0],
- "width": wrongCropData[1],
- "height": wrongCropData[2]
- }
- ]
- }];
- // Remove all existing crops and add a crop to remove
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- dataTypeData.values = wrongDataTypeValues;
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
- await umbracoUi.dataType.goToDataType(dataTypeName);
+ const cropData = ['Deleted Alias', AliasHelper.toAlias('Deleted Alias'), 50, 100];
+ await umbracoApi.dataType.createImageCropperDataTypeWithOneCrop(customDataTypeName, cropData[0], cropData[2], cropData[3]);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
- await umbracoUi.dataType.removeCropByAlias(wrongCropData[0].toString());
+ await umbracoUi.dataType.removeCropByAlias(cropData[0].toString());
await umbracoUi.dataType.clickSaveButton();
// Assert
- await umbracoUi.dataType.isSuccessNotificationVisible();
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toEqual([]);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveCrops(customDataTypeName, cropData[0], cropData[1], cropData[2], cropData[3])).toBeFalsy();
});
+
+test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.dataType.goToDataType(dataTypeName);
+
+ // Assert
+ await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.imageCropperSettings);
+ await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.imageCropperSettings);
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias);
+ const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ expect(dataTypeDefaultData.editorAlias).toBe(editorAlias);
+ expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias);
+ expect(dataTypeDefaultData.values).toEqual([]);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'crops')).toBeFalsy();
+});
\ No newline at end of file
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Label.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Label.spec.ts
index 8d352af482..1a4f431321 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Label.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Label.spec.ts
@@ -1,41 +1,52 @@
-import { test } from "@umbraco/playwright-testhelpers";
+import { ConstantHelper, NotificationConstantHelper, test } from "@umbraco/playwright-testhelpers";
import { expect } from "@playwright/test";
-const labelTypes = ['Label (bigint)', 'Label (datetime)', 'Label (decimal)', 'Label (integer)', 'Label (string)', 'Label (time)'];
-for (const labelType of labelTypes) {
- test.describe(`${labelType} tests`, () => {
- let dataTypeDefaultData = null;
- let dataTypeData = null;
+const labelTypes = [
+ {type: 'Label (bigint)', dataValueType: 'BIGINT'},
+ {type: 'Label (datetime)', dataValueType: 'DATETIME'},
+ {type: 'Label (decimal)', dataValueType: 'DECIMAL'},
+ {type: 'Label (integer)', dataValueType: 'INT'},
+ {type: 'Label (string)', dataValueType: 'STRING'},
+ {type: 'Label (time)', dataValueType: 'TIME'}
+];
+const editorAlias = 'Umbraco.Label';
+const editorUiAlias = 'Umb.PropertyEditorUi.Label';
+const customDataTypeName = 'Custom Label';
- test.beforeEach(async ({ umbracoUi, umbracoApi }) => {
- await umbracoUi.goToBackOffice();
- await umbracoUi.dataType.goToSettingsTreeItem("Data Types");
- dataTypeDefaultData = await umbracoApi.dataType.getByName(labelType);
- await umbracoUi.dataType.goToDataType(labelType);
- });
+test.beforeEach(async ({umbracoUi, umbracoApi}) => {
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
+});
- test.afterEach(async ({ umbracoApi }) => {
- if (dataTypeDefaultData !== null) {
- await umbracoApi.dataType.update(dataTypeDefaultData.id,dataTypeDefaultData);
- }
- });
+test.afterEach(async ({umbracoApi}) => {
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
+});
- test('can change value type', async ({ umbracoApi, umbracoUi }) => {
- // Arrange
- const expectedDataTypeValues = [
- {
- "alias": "umbracoDataValueType",
- "value": "TEXT",
- }
- ];
+test('can change value type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.dataType.createDefaultLabelDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
- // Act
- await umbracoUi.dataType.changeValueType("Long String");
- await umbracoUi.dataType.clickSaveButton();
+ // Act
+ await umbracoUi.dataType.changeValueType("Long String");
+ await umbracoUi.dataType.clickSaveButton();
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(labelType);
- expect(dataTypeData.values).toEqual(expectedDataTypeValues);
- });
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'umbracoDataValueType', 'TEXT')).toBeTruthy();
+});
+
+for (const label of labelTypes) {
+ test(`the default configuration of ${label.type} is correct`, async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.dataType.goToDataType(label.type);
+
+ // Assert
+ await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.labelSettings);
+ await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.labelSettings);
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(label.type, 'umbracoDataValueType', label.dataValueType)).toBeTruthy();
});
}
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ListView.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ListView.spec.ts
index 41d7af774e..d87b674623 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ListView.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ListView.spec.ts
@@ -1,292 +1,198 @@
-import { test } from "@umbraco/playwright-testhelpers";
+import { ConstantHelper, NotificationConstantHelper, test } from "@umbraco/playwright-testhelpers";
import { expect } from "@playwright/test";
-const listViewTypes = ['List View - Content', 'List View - Media'];
-for (const listViewType of listViewTypes) {
- test.describe(`${listViewType} tests`, () => {
- let dataTypeDefaultData = null;
- let dataTypeData = null;
+const listViewTypes = [
+ {type: 'List View - Content', collectionViewGird: 'Umb.CollectionView.Document.Grid', collectionViewList: 'Umb.CollectionView.Document.Table'},
+ {type: 'List View - Media', collectionViewGird: 'Umb.CollectionView.Media.Grid', collectionViewList: 'Umb.CollectionView.Media.Table'}
+];
+const editorAlias = 'Umbraco.ListView';
+const editorUiAlias = 'Umb.PropertyEditorUi.Collection';
+const customDataTypeName = 'Custom List View';
- test.beforeEach(async ({ umbracoUi, umbracoApi }) => {
- await umbracoUi.goToBackOffice();
- await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
- dataTypeDefaultData = await umbracoApi.dataType.getByName(listViewType);
- });
+test.beforeEach(async ({umbracoUi, umbracoApi}) => {
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
+});
- test.afterEach(async ({ umbracoApi }) => {
- if (dataTypeDefaultData !== null) {
- await umbracoApi.dataType.update(dataTypeDefaultData.id,dataTypeDefaultData);
- }
- });
+test.afterEach(async ({umbracoApi}) => {
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
+});
- test('can update page size', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- const pageSizeValue = 5;
- const expectedDataTypeValues = {
- "alias": "pageSize",
- "value": pageSizeValue
- };
+test('can update page size', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const pageSizeValue = 5;
+ await umbracoApi.dataType.createListViewContentDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
- // Act
- await umbracoUi.dataType.goToDataType(listViewType);
- await umbracoUi.dataType.enterPageSizeValue(pageSizeValue.toString());
- await umbracoUi.dataType.clickSaveButton();
+ // Act
+ await umbracoUi.dataType.enterPageSizeValue(pageSizeValue.toString());
+ await umbracoUi.dataType.clickSaveButton();
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(listViewType);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
- });
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'pageSize', pageSizeValue)).toBeTruthy();
+});
- test('can update order direction', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- const isAscending = listViewType == 'List View - Members' ? false : true;
- const orderDirectionValue = isAscending ? 'asc' : 'desc';
- const expectedDataTypeValues = {
- "alias": "orderDirection",
- "value": orderDirectionValue
- };
+test('can update order direction', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const orderDirectionValue = 'asc';
+ await umbracoApi.dataType.createListViewContentDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
- // Act
- await umbracoUi.dataType.goToDataType(listViewType);
- await umbracoUi.dataType.chooseOrderDirection(isAscending);
- await umbracoUi.dataType.clickSaveButton();
+ // Act
+ await umbracoUi.dataType.chooseOrderDirection(true);
+ await umbracoUi.dataType.clickSaveButton();
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(listViewType);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
- });
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'orderDirection', orderDirectionValue)).toBeTruthy();
+});
- test('can add column displayed', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- let columnData: string[];
- if (listViewType === 'List View - Media') {
- columnData = ['Document Type', 'TestDocumentType', 'sortOrder', 'Sort'];
- await umbracoApi.documentType.ensureNameNotExists(columnData[1]);
- await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(columnData[1]);
- } else {
- columnData = ['Media Type', 'Audio', 'sortOrder', 'Sort'];
- }
+test('can add column displayed', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const columnData = ['Document Type', 'TestDocumentType', 'sortOrder', 'Sort'];
+ await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(columnData[1]);
+ await umbracoApi.dataType.createListViewContentDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
- const expectedIncludePropertiesValues = {
- "alias": columnData[2],
- "header": columnData[3],
- "isSystem": 1
- };
+ // Act
+ await umbracoUi.dataType.addColumnDisplayed(columnData[0], columnData[1], columnData[2]);
+ await umbracoUi.dataType.clickSaveButton();
- // Act
- await umbracoUi.dataType.goToDataType(listViewType);
- await umbracoUi.dataType.addColumnDisplayed(columnData[0], columnData[1], columnData[2]);
- await umbracoUi.dataType.clickSaveButton();
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesListViewHaveProperty(customDataTypeName, columnData[3], columnData[2], 1)).toBeTruthy();
+});
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(listViewType);
- const includePropertiesData = dataTypeData.values.find(value => value.alias === "includeProperties");
- expect(includePropertiesData.value).toContainEqual(expectedIncludePropertiesValues);
- });
+test('can remove column displayed', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.dataType.createListViewContentDataType(customDataTypeName);
+ expect(await umbracoApi.dataType.doesListViewHaveProperty(customDataTypeName, 'Last edited', 'updateDate')).toBeTruthy();
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
- test('can remove column displayed', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- let columnData: string[];
- if (listViewType === 'List View - Media') {
- columnData = ['Document Type', 'TestDocumentType', 'owner', 'Created by'];
- await umbracoApi.documentType.ensureNameNotExists(columnData[1]);
- await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(columnData[1]);
- } else {
- columnData = ['Media Type', 'Audio', 'owner', 'Created by'];
- }
+ // Act
+ await umbracoUi.dataType.removeColumnDisplayed('updateDate');
+ await umbracoUi.dataType.clickSaveButton();
- const removedDataTypeValues = [{
- "alias": "includeProperties",
- "value": [{
- "alias": columnData[2],
- "header": columnData[3],
- "isSystem": 1,
- }]
- }];
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesListViewHaveProperty(customDataTypeName, 'Last edited', 'updateDate')).toBeFalsy();
+});
- // Remove all existing values and add a column displayed to remove
- dataTypeData = await umbracoApi.dataType.getByName(listViewType);
- dataTypeData.values = removedDataTypeValues;
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
+test('can add layouts', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const layoutName = 'Extension Table Collection View';
+ const layoutCollectionView = 'Umb.CollectionView.Extension.Table';
+ await umbracoApi.dataType.createListViewContentDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
- // Act
- await umbracoUi.dataType.goToDataType(listViewType);
- await umbracoUi.dataType.removeColumnDisplayed(columnData[2]);
- await umbracoUi.dataType.clickSaveButton();
+ // Act
+ await umbracoUi.dataType.addLayouts(layoutName);
+ await umbracoUi.dataType.clickSaveButton();
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(listViewType);
- expect(dataTypeData.values).toEqual([]);
- });
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesListViewHaveLayout(customDataTypeName, layoutName, 'icon-list', layoutCollectionView)).toBeTruthy();
+});
- test('can add layouts', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- const layoutName = 'Extension Table Collection View';
- const layoutCollectionView = 'Umb.CollectionView.Extension.Table';
- const expectedIncludePropertiesValues = {
- "icon": "icon-list",
- "name": layoutName,
- "collectionView": layoutCollectionView,
- };
+test('can remove layouts', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const layoutName = 'List';
+ const layoutCollectionView = 'Umb.CollectionView.Document.Table';
+ await umbracoApi.dataType.createListViewContentDataType(customDataTypeName);
+ expect(await umbracoApi.dataType.doesListViewHaveLayout(customDataTypeName, layoutName, 'icon-list', layoutCollectionView)).toBeTruthy();
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
- // Act
- await umbracoUi.dataType.goToDataType(listViewType);
- await umbracoUi.dataType.addLayouts(layoutName);
- await umbracoUi.dataType.clickSaveButton();
+ // Act
+ await umbracoUi.dataType.removeLayouts(layoutCollectionView);
+ await umbracoUi.dataType.clickSaveButton();
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(listViewType);
- const includePropertiesData = dataTypeData.values.find(value => value.alias === "layouts");
- expect(includePropertiesData.value).toContainEqual(expectedIncludePropertiesValues);
- });
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesListViewHaveLayout(customDataTypeName, layoutName, 'icon-list', layoutCollectionView)).toBeFalsy();
+});
- test('can remove layouts', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- let layoutsData = 'Document Grid Collection View';
- if (listViewType === 'List View - Media') {
- layoutsData = 'Media Grid Collection View';
- }
+test('can update order by', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const orderByValue = 'Last edited';
+ await umbracoApi.dataType.createListViewContentDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
- const removedDataTypeValues = [{
- "alias": "layouts",
- "value": [{
- "icon": "icon-thumbnails-small",
- "collectionView": layoutsData,
- "isSystem": true,
- "name": "Grid",
- "selected": true
- }]
- }];
+ // Act
+ await umbracoUi.dataType.chooseOrderByValue(orderByValue);
+ await umbracoUi.dataType.clickSaveButton();
- // Remove all existing values and add a layout to remove
- dataTypeData = await umbracoApi.dataType.getByName(listViewType);
- dataTypeData.values = removedDataTypeValues;
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'orderBy', 'updateDate')).toBeTruthy();
+});
- // Act
- await umbracoUi.dataType.goToDataType(listViewType);
- await umbracoUi.dataType.removeLayouts(layoutsData);
- await umbracoUi.dataType.clickSaveButton();
+test('can update workspace view icon', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const iconValue = 'icon-activity';
+ await umbracoApi.dataType.createListViewContentDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(listViewType);
- expect(dataTypeData.values).toEqual([]);
- });
+ // Act
+ await umbracoUi.dataType.clickSelectIconButton();
+ await umbracoUi.dataType.chooseWorkspaceViewIconByValue(iconValue);
+ await umbracoUi.dataType.clickSaveButton();
- test('can update order by', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- const orderByValue = 'Last edited';
- const expectedDataTypeValues = {
- "alias": "orderBy",
- "value": "updateDate"
- };
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'icon', iconValue)).toBeTruthy();
+});
- // Act
- await umbracoUi.dataType.goToDataType(listViewType);
- await umbracoUi.dataType.chooseOrderByValue(orderByValue);
- await umbracoUi.dataType.clickSaveButton();
+test('can update workspace view name', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const workspaceViewName = 'Test Content Name';
+ await umbracoApi.dataType.createListViewContentDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(listViewType);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
- });
+ // Act
+ await umbracoUi.dataType.enterWorkspaceViewName(workspaceViewName);
+ await umbracoUi.dataType.clickSaveButton();
- // Skip this test as currently there is no setting for bulk action permission
- test.skip('can update bulk action permission', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- const bulkActionPermissionValue = 'Allow bulk trash';
- const expectedDataTypeValues = {
- "alias": "bulkActionPermissions",
- "value": {
- "allowBulkCopy": false,
- "allowBulkDelete": true,
- "allowBulkMove": false,
- "allowBulkPublish": false,
- "allowBulkUnpublish": false
- }
- };
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'tabName', workspaceViewName)).toBeTruthy();
+});
- // Act
- await umbracoUi.dataType.goToDataType(listViewType);
- await umbracoUi.dataType.clickBulkActionPermissionsToggleByValue(bulkActionPermissionValue);
- await umbracoUi.dataType.clickSaveButton();
+test('can enable show content workspace view first', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.dataType.createListViewContentDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(listViewType);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
- });
+ // Act
+ await umbracoUi.dataType.clickShowContentWorkspaceViewFirstToggle();
+ await umbracoUi.dataType.clickSaveButton();
- test('can update workspace view icon', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- const iconValue = 'icon-activity';
- const expectedDataTypeValues = {
- "alias": "icon",
- "value": iconValue
- };
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'showContentFirst', true)).toBeTruthy();
+});
- // Act
- await umbracoUi.dataType.goToDataType(listViewType);
- await umbracoUi.dataType.clickSelectIconButton();
- await umbracoUi.dataType.chooseWorkspaceViewIconByValue(iconValue);
- await umbracoUi.dataType.clickSaveButton();
+for (const listView of listViewTypes) {
+ test(`the default configuration of ${listView.type} is correct`, async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.dataType.goToDataType(listView.type);
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(listViewType);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
- });
-
- test('can update workspace view name', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- const WorkspaceViewName = 'Test Content Name';
- const expectedDataTypeValues = {
- "alias": "tabName",
- "value": WorkspaceViewName
- };
-
- // Act
- await umbracoUi.dataType.goToDataType(listViewType);
- await umbracoUi.dataType.enterWorkspaceViewName(WorkspaceViewName);
- await umbracoUi.dataType.clickSaveButton();
-
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(listViewType);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
- });
-
- test('can enable show content workspace view first', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- const expectedDataTypeValues = {
- "alias": "showContentFirst",
- "value": true
- };
-
- // Act
- await umbracoUi.dataType.goToDataType(listViewType);
- await umbracoUi.dataType.clickShowContentWorkspaceViewFirstToggle();
- await umbracoUi.dataType.clickSaveButton();
-
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(listViewType);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
- });
-
- // Skip this test as there are no setting for infinite editor
- test.skip('can enable edit in infinite editor', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- const expectedDataTypeValues = {
- "alias": "useInfiniteEditor",
- "value": true
- };
-
- // Act
- await umbracoUi.dataType.goToDataType(listViewType);
- await umbracoUi.dataType.clickEditInInfiniteEditorToggle();
- await umbracoUi.dataType.clickSaveButton();
-
- // Assert
- await umbracoUi.dataType.isSuccessNotificationVisible();
- dataTypeData = await umbracoApi.dataType.getByName(listViewType);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
- });
+ // Assert
+ await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.listViewSettings);
+ await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.listViewSettings);
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(listView.type, 'pageSize', 100)).toBeTruthy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(listView.type, 'orderBy', 'updateDate')).toBeTruthy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(listView.type, 'orderDirection', 'desc')).toBeTruthy();
+ expect(await umbracoApi.dataType.doesListViewHaveLayout(listView.type, 'Grid', 'icon-thumbnails-small', listView.collectionViewGird)).toBeTruthy();
+ expect(await umbracoApi.dataType.doesListViewHaveLayout(listView.type, 'List', 'icon-list', listView.collectionViewList)).toBeTruthy();
+ expect(await umbracoApi.dataType.doesListViewHaveProperty(listView.type, 'Last edited', 'updateDate')).toBeTruthy();
+ expect(await umbracoApi.dataType.doesListViewHaveProperty(listView.type, 'Updated by', 'creator')).toBeTruthy();
+ // TODO: Uncomment this when the front-end is ready. Currently there is no default icon for workspace view icon
+ // expect(await umbracoApi.dataType.doesDataTypeHaveValue(listView.type, 'icon', 'icon-list')).toBeTruthy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(listView.type, 'tabName')).toBeFalsy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(listView.type, 'showContentFirst')).toBeFalsy();
});
}
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MediaPicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MediaPicker.spec.ts
index d4945b1cb1..cc15b8be68 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MediaPicker.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MediaPicker.spec.ts
@@ -1,237 +1,203 @@
-import {test} from '@umbraco/playwright-testhelpers';
+import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
-const dataTypes = ['Media Picker', 'Multiple Media Picker', 'Image Media Picker', 'Multiple Image Media Picker'];
-for (const dataTypeName of dataTypes) {
- test.describe(`${dataTypeName} tests`, () => {
- let dataTypeDefaultData = null;
- let dataTypeData = null;
+const mediaPickerTypes = [
+ {type: 'Media Picker', isMultiple: false},
+ {type: 'Multiple Media Picker', isMultiple: true},
+ {type: 'Image Media Picker', isMultiple: false},
+ {type: 'Multiple Image Media Picker', isMultiple: true},
+];
+const editorAlias = 'Umbraco.MediaPicker3';
+const editorUiAlias = 'Umb.PropertyEditorUi.MediaPicker';
+const customDataTypeName = 'Custom Media Picker';
- test.beforeEach(async ({umbracoUi, umbracoApi}) => {
- await umbracoUi.goToBackOffice();
- await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
- dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
- });
+test.beforeEach(async ({umbracoUi, umbracoApi}) => {
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
+});
- test.afterEach(async ({umbracoApi}) => {
- if (dataTypeDefaultData !== null) {
- await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData);
- }
- });
+test.afterEach(async ({umbracoApi}) => {
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
+});
- test('can update pick multiple items', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- const expectedDataTypeValues = {
- "alias": "multiple",
- "value": dataTypeName === 'Media Picker' || dataTypeName === 'Image Media Picker' ? true : false,
- };
+test('can update pick multiple items', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.dataType.createDefaultMediaPickerDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
- // Act
- await umbracoUi.dataType.goToDataType(dataTypeName);
- await umbracoUi.dataType.clickPickMultipleItemsToggle();
- await umbracoUi.dataType.clickSaveButton();
+ // Act
+ await umbracoUi.dataType.clickPickMultipleItemsToggle();
+ await umbracoUi.dataType.clickSaveButton();
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
- });
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'multiple', true)).toBeTruthy();
+});
- test('can update amount', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- const lowValue = 5;
- const highValue = 1000;
- const expectedDataTypeValues = {
- "alias": "validationLimit",
- "value": {
- "min": lowValue,
- "max": highValue
- }
- };
+test('can update amount', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const minValue = 5;
+ const maxValue = 1000;
+ await umbracoApi.dataType.createDefaultMediaPickerDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
- // Act
- await umbracoUi.dataType.goToDataType(dataTypeName);
- await umbracoUi.dataType.enterAmountValue(lowValue.toString(), highValue.toString());
- await umbracoUi.dataType.clickSaveButton();
+ // Act
+ await umbracoUi.dataType.enterAmountValue(minValue.toString(), maxValue.toString());
+ await umbracoUi.dataType.clickSaveButton();
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
- });
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesMediaPickerHaveMinAndMaxAmount(customDataTypeName, minValue, maxValue)).toBeTruthy();
+});
- test('can update enable focal point', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- const expectedDataTypeValues = {
- "alias": "enableLocalFocalPoint",
- "value": true
- };
+test('can update enable focal point', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.dataType.createDefaultMediaPickerDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
- // Act
- await umbracoUi.dataType.goToDataType(dataTypeName);
- await umbracoUi.dataType.clickEnableFocalPointToggle();
- await umbracoUi.dataType.clickSaveButton();
+ // Act
+ await umbracoUi.dataType.clickEnableFocalPointToggle();
+ await umbracoUi.dataType.clickSaveButton();
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
- });
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'enableLocalFocalPoint', true)).toBeTruthy();
+});
- test('can add image crop', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- const cropData = ['Test Label', 'Test Alias', 100, 50];
- const expectedDataTypeValues = {
- "alias": "crops",
- "value": [
- {
- "label": cropData[0],
- "alias": cropData[1],
- "width": cropData[2],
- "height": cropData[3]
- }
- ]
- };
+test('can add image crop', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const cropData = ['Test Label', 'testAlias', 100, 50];
+ await umbracoApi.dataType.createDefaultMediaPickerDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
- // Act
- await umbracoUi.dataType.goToDataType(dataTypeName);
- await umbracoUi.waitForTimeout(500);
- await umbracoUi.dataType.enterCropValues(
- cropData[0].toString(),
- cropData[1].toString(),
- cropData[2].toString(),
- cropData[3].toString()
- );
- await umbracoUi.waitForTimeout(500);
- await umbracoUi.dataType.clickAddCropButton();
- await umbracoUi.dataType.clickSaveButton();
- await umbracoUi.waitForTimeout(500);
+ // Act
+ await umbracoUi.dataType.enterCropValues(
+ cropData[0].toString(),
+ cropData[1].toString(),
+ cropData[2].toString(),
+ cropData[3].toString()
+ );
+ await umbracoUi.dataType.clickAddCropButton();
+ await umbracoUi.dataType.clickSaveButton();
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
- });
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveCrops(customDataTypeName, cropData[0], cropData[1], cropData[2], cropData[3])).toBeTruthy();
+});
- test('can update ignore user start nodes', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- const expectedDataTypeValues = {
- "alias": "ignoreUserStartNodes",
- "value": true
- };
+test('can update ignore user start nodes', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.dataType.createDefaultMediaPickerDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
- // Act
- await umbracoUi.dataType.goToDataType(dataTypeName);
- await umbracoUi.dataType.clickIgnoreUserStartNodesToggle();
- await umbracoUi.dataType.clickSaveButton();
+ // Act
+ await umbracoUi.dataType.clickIgnoreUserStartNodesToggle();
+ await umbracoUi.dataType.clickSaveButton();
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
- });
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'ignoreUserStartNodes', true)).toBeTruthy();
+});
- test('can add accepted types', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- const mediaTypeName = 'Audio';
- const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+test('can add accepted types', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const mediaTypeName = 'Audio';
+ const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+ await umbracoApi.dataType.createDefaultMediaPickerDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
+
+ // Act
+ await umbracoUi.dataType.addAcceptedType(mediaTypeName);
+ await umbracoUi.dataType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'filter', mediaTypeData.id)).toBeTruthy();
+});
+
+test('can remove accepted types', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const mediaTypeName = 'Image';
+ const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+ await umbracoApi.dataType.createImageMediaPickerDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
+
+ // Act
+ await umbracoUi.dataType.removeAcceptedType(mediaTypeName);
+ await umbracoUi.dataType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'filter', mediaTypeData.id)).toBeFalsy();
+});
+
+test('can add start node', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ // Create media
+ const mediaName = 'TestStartNode';
+ const mediaId = await umbracoApi.media.createDefaultMediaWithArticle(mediaName);
+ expect(await umbracoApi.media.doesNameExist(mediaName)).toBeTruthy();
+ await umbracoApi.dataType.createDefaultMediaPickerDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
+
+ // Act
+ await umbracoUi.dataType.clickChooseStartNodeButton();
+ await umbracoUi.dataType.addMediaStartNode(mediaName);
+ await umbracoUi.dataType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'startNodeId', mediaId)).toBeTruthy();
+
+ // Clean
+ await umbracoApi.media.ensureNameNotExists(mediaName);
+});
+
+test('can remove start node', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ // Create media
+ const mediaName = 'TestStartNode';
+ await umbracoApi.media.ensureNameNotExists(mediaName);
+ const mediaId = await umbracoApi.media.createDefaultMediaWithArticle(mediaName);
+ expect(await umbracoApi.media.doesNameExist(mediaName)).toBeTruthy();
+ await umbracoApi.dataType.createImageMediaPickerDataTypeWithStartNodeId(customDataTypeName, mediaId);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
+
+ // Act
+ await umbracoUi.dataType.removeMediaStartNode(mediaName);
+ await umbracoUi.dataType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'startNodeId', mediaId)).toBeFalsy();
+
+ // Clean
+ await umbracoApi.media.ensureNameNotExists(mediaName);
+});
+
+for (const mediaPicker of mediaPickerTypes) {
+ test(`the default configuration of ${mediaPicker.type} is correct`, async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.dataType.goToDataType(mediaPicker.type);
+
+ // Assert
+ await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.mediaPickerSettings);
+ await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.mediaPickerSettings);
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(mediaPicker.type, 'multiple', mediaPicker.isMultiple)).toBeTruthy();
+ if (mediaPicker.type.includes('Image')) {
const imageTypeData = await umbracoApi.mediaType.getByName('Image');
- const expectedFilterValue =
- dataTypeName === "Image Media Picker" ||
- dataTypeName === "Multiple Image Media Picker"
- ? imageTypeData.id + "," + mediaTypeData.id
- : mediaTypeData.id;
- const expectedDataTypeValues = {
- "alias": "filter",
- "value": expectedFilterValue
- };
-
- // Act
- await umbracoUi.dataType.goToDataType(dataTypeName);
- await umbracoUi.dataType.addAcceptedType(mediaTypeName);
- await umbracoUi.dataType.clickSaveButton();
-
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
- });
-
- test('can remove accepted types', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- const mediaTypeName = 'Audio';
- const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
- const removedDataTypeValues = [{
- "alias": "filter",
- "value": mediaTypeData.id
- }];
- const expectedDataTypeValues = [];
-
- // Remove all existing options and add an option to remove
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- dataTypeData.values = removedDataTypeValues;
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
-
- // Act
- await umbracoUi.dataType.goToDataType(dataTypeName);
- await umbracoUi.dataType.removeAcceptedType(mediaTypeName);
- await umbracoUi.dataType.clickSaveButton();
-
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toEqual(expectedDataTypeValues);
- });
-
- test('can add start node', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- // Create media
- const mediaName = 'TestStartNode';
- await umbracoApi.media.ensureNameNotExists(mediaName);
- const mediaId = await umbracoApi.media.createDefaultMediaWithArticle(mediaName);
- expect(await umbracoApi.media.doesNameExist(mediaName)).toBeTruthy();
-
- const expectedDataTypeValues = {
- "alias": "startNodeId",
- "value": mediaId
- };
-
- // Act
- await umbracoUi.dataType.goToDataType(dataTypeName);
- await umbracoUi.dataType.clickChooseStartNodeButton();
- await umbracoUi.dataType.addMediaStartNode(mediaName);
- await umbracoUi.dataType.clickSaveButton();
-
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
-
- // Clean
- await umbracoApi.media.ensureNameNotExists(mediaName);
- });
-
- test('can remove start node', async ({umbracoApi, umbracoUi}) => {
- // Arrange
- // Create media
- const mediaName = 'TestStartNode';
- await umbracoApi.media.ensureNameNotExists(mediaName);
- const mediaId = await umbracoApi.media.createDefaultMediaWithArticle(mediaName);
- expect(await umbracoApi.media.doesNameExist(mediaName)).toBeTruthy();
-
- const removedDataTypeValues = [{
- "alias": "startNodeId",
- "value": mediaId
- }];
-
- // Remove all existing values and add a start node to remove
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- dataTypeData.values = removedDataTypeValues;
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
-
- // Act
- await umbracoUi.dataType.goToDataType(dataTypeName);
- await umbracoUi.dataType.removeMediaStartNode(mediaName);
- await umbracoUi.dataType.clickSaveButton();
-
- // Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toEqual([]);
-
- // Clean
- await umbracoApi.media.ensureNameNotExists(mediaName);
- });
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(mediaPicker.type, 'filter', imageTypeData.id)).toBeTruthy();
+ }
+ if (!mediaPicker.type.includes('Multiple')) {
+ expect(await umbracoApi.dataType.doesMediaPickerHaveMinAndMaxAmount(mediaPicker.type, 0, 1)).toBeTruthy();
+ }
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(mediaPicker.type, 'startNodeId')).toBeFalsy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(mediaPicker.type, 'ignoreUserStartNodes')).toBeFalsy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(mediaPicker.type, 'crops')).toBeFalsy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(mediaPicker.type, 'enableLocalFocalPoint')).toBeFalsy();
});
}
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MemberPicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MemberPicker.spec.ts
new file mode 100644
index 0000000000..4e42cea6a8
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MemberPicker.spec.ts
@@ -0,0 +1,22 @@
+import {test} from '@umbraco/playwright-testhelpers';
+import {expect} from "@playwright/test";
+
+const dataTypeName = 'Member Picker';
+const editorAlias = 'Umbraco.MemberPicker';
+const editorUiAlias = 'Umb.PropertyEditorUi.MemberPicker';
+
+test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
+ await umbracoUi.dataType.goToDataType(dataTypeName);
+
+ // Assert
+ await umbracoUi.dataType.doesSettingsContainText('There is no configuration for this property editor.');
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias);
+ const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ expect(dataTypeDefaultData.editorAlias).toBe(editorAlias);
+ expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias);
+ expect(dataTypeDefaultData.values).toEqual([]);
+});
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MultiUrlPicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MultiUrlPicker.spec.ts
index 1cb557aba1..6900c55fa5 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MultiUrlPicker.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MultiUrlPicker.spec.ts
@@ -1,111 +1,101 @@
-import {test} from '@umbraco/playwright-testhelpers';
+import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
const dataTypeName = 'Multi URL Picker';
-let dataTypeDefaultData = null;
-let dataTypeData = null;
+const editorAlias = 'Umbraco.MultiUrlPicker';
+const editorUiAlias = 'Umb.PropertyEditorUi.MultiUrlPicker';
+const customDataTypeName = 'Custom Multi URL Picker';
test.beforeEach(async ({umbracoUi, umbracoApi}) => {
await umbracoUi.goToBackOffice();
await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
- await umbracoUi.dataType.goToDataType(dataTypeName);
- dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test.afterEach(async ({umbracoApi}) => {
- if (dataTypeDefaultData !== null) {
- await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData);
- }
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test('can update minimum number of items value', async ({umbracoApi, umbracoUi}) => {
// Arrange
const minimumValue = 2;
- const expectedDataTypeValues = {
- "alias": "minNumber",
- "value": minimumValue
- };
+ await umbracoApi.dataType.createDefaultMultiUrlPickerDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.enterMinimumNumberOfItemsValue(minimumValue.toString());
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'minNumber', minimumValue)).toBeTruthy();
});
test('can update maximum number of items value', async ({umbracoApi, umbracoUi}) => {
// Arrange
const maximumValue = 2;
- const expectedDataTypeValues = {
- "alias": "maxNumber",
- "value": maximumValue
- };
+ await umbracoApi.dataType.createDefaultMultiUrlPickerDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.enterMaximumNumberOfItemsValue(maximumValue.toString());
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'maxNumber', maximumValue)).toBeTruthy();
});
test('can enable ignore user start nodes', async ({umbracoApi, umbracoUi}) => {
// Arrange
- const expectedDataTypeValues = {
- "alias": "ignoreUserStartNodes",
- "value": true
- };
+ await umbracoApi.dataType.createDefaultMultiUrlPickerDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.clickIgnoreUserStartNodesToggle();
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'ignoreUserStartNodes', true)).toBeTruthy();
});
test('can update overlay size', async ({umbracoApi, umbracoUi}) => {
// Arrange
const overlaySizeValue = 'large';
- const expectedDataTypeValues = {
- "alias": "overlaySize",
- "value": overlaySizeValue
- };
+ await umbracoApi.dataType.createDefaultMultiUrlPickerDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.chooseOverlaySizeByValue(overlaySizeValue);
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'overlaySize', overlaySizeValue)).toBeTruthy();
});
test('can update hide anchor/query string input', async ({umbracoApi, umbracoUi}) => {
// Arrange
- const expectedDataTypeValues = {
- "alias": "hideAnchor",
- "value": true
- };
+ await umbracoApi.dataType.createDefaultMultiUrlPickerDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.clickHideAnchorQueryStringInputToggle();
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'hideAnchor', true)).toBeTruthy();
});
// TODO: Remove skip when the front-end is ready. Currently you still can update the minimum greater than the maximum.
-test.skip('cannot update the minimum number of items greater than the maximum', async ({umbracoUi}) => {
+test.skip('cannot update the minimum number of items greater than the maximum', async ({umbracoApi, umbracoUi}) => {
// Arrange
const minimumValue = 5;
const maximumValue = 2;
+ await umbracoApi.dataType.createDefaultMultiUrlPickerDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.enterMinimumNumberOfItemsValue(minimumValue.toString());
@@ -115,3 +105,23 @@ test.skip('cannot update the minimum number of items greater than the maximum',
// Assert
await umbracoUi.dataType.isErrorNotificationVisible();
});
+
+test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.dataType.goToDataType(dataTypeName);
+
+ // Assert
+ await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.multiURLPickerSettings);
+ await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.multiURLPickerSettings);
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias);
+ const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ expect(dataTypeDefaultData.editorAlias).toBe(editorAlias);
+ expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias);
+ expect(dataTypeDefaultData.values).toEqual([]);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'minNumber')).toBeFalsy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'maxNumber')).toBeFalsy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'ignoreUserStartNodes')).toBeFalsy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'overlaySize')).toBeFalsy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'hideAnchor')).toBeFalsy();
+});
\ No newline at end of file
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Numeric.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Numeric.spec.ts
index 2bd0da1404..d1b1de699a 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Numeric.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Numeric.spec.ts
@@ -1,95 +1,88 @@
-import {test} from '@umbraco/playwright-testhelpers';
+import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
const dataTypeName = 'Numeric';
-let dataTypeDefaultData = null;
-let dataTypeData = null;
+const editorAlias = 'Umbraco.Integer';
+const editorUiAlias = 'Umb.PropertyEditorUi.Integer';
+const customDataTypeName = 'Custom Numeric';
test.beforeEach(async ({umbracoUi, umbracoApi}) => {
await umbracoUi.goToBackOffice();
await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
- await umbracoUi.dataType.goToDataType(dataTypeName);
- dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test.afterEach(async ({umbracoApi}) => {
- if (dataTypeDefaultData !== null) {
- await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData);
- }
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test('can update minimum value', async ({umbracoApi, umbracoUi}) => {
// Arrange
const minimumValue = -5;
- const expectedDataTypeValues = {
- "alias": "min",
- "value": minimumValue
- };
+ await umbracoApi.dataType.createDefaultNumericDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.enterMinimumValue(minimumValue.toString());
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'min', minimumValue)).toBeTruthy();
});
test('can update Maximum value', async ({umbracoApi, umbracoUi}) => {
// Arrange
const maximumValue = 1000000;
- const expectedDataTypeValues = {
- "alias": "max",
- "value": maximumValue
- };
+ await umbracoApi.dataType.createDefaultNumericDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.enterMaximumValue(maximumValue.toString());
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'max', maximumValue)).toBeTruthy();
});
test('can update step size value', async ({umbracoApi, umbracoUi}) => {
// Arrange
const stepSizeValue = 5;
- const expectedDataTypeValues = {
- "alias": "step",
- "value": stepSizeValue
- };
+ await umbracoApi.dataType.createDefaultNumericDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.enterStepSizeValue(stepSizeValue.toString());
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'step', stepSizeValue)).toBeTruthy();
});
+// Skip this test as currently this setting is removed.
test.skip('can allow decimals', async ({umbracoApi, umbracoUi}) => {
// Arrange
- const expectedDataTypeValues = {
- "alias": "allowDecimals",
- "value": true
- };
+ await umbracoApi.dataType.createDefaultNumericDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.clickAllowDecimalsToggle();
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'allowDecimals', true)).toBeTruthy();
});
// TODO: Remove skip when the front-end is ready. Currently you still can update the minimum greater than the maximum.
-test.skip('cannot update the minimum greater than the maximum', async ({umbracoUi}) => {
+test.skip('cannot update the minimum greater than the maximum', async ({umbracoApi, umbracoUi}) => {
// Arrange
const minimumValue = 5;
const maximumValue = 2;
+ await umbracoApi.dataType.createDefaultNumericDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.enterMinimumValue(minimumValue.toString());
@@ -99,3 +92,21 @@ test.skip('cannot update the minimum greater than the maximum', async ({umbracoU
// Assert
await umbracoUi.dataType.isErrorNotificationVisible();
});
+
+test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.dataType.goToDataType(dataTypeName);
+
+ // Assert
+ await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.numericSettings);
+ await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.numericSettings);
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias);
+ const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ expect(dataTypeDefaultData.editorAlias).toBe(editorAlias);
+ expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias);
+ expect(dataTypeDefaultData.values).toEqual([]);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'min')).toBeFalsy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'max')).toBeFalsy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'step')).toBeFalsy();
+});
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Radiobox.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Radiobox.spec.ts
index 38c0cfdf8d..192dc71ff3 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Radiobox.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Radiobox.spec.ts
@@ -1,36 +1,26 @@
-import {test} from '@umbraco/playwright-testhelpers';
+import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
const dataTypeName = 'Radiobox';
-let dataTypeDefaultData = null;
-let dataTypeData = null;
+const editorAlias = 'Umbraco.RadioButtonList';
+const editorUiAlias = 'Umb.PropertyEditorUi.RadioButtonList';
+const customDataTypeName = 'Custom Radiobox';
test.beforeEach(async ({umbracoUi, umbracoApi}) => {
await umbracoUi.goToBackOffice();
await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
- dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test.afterEach(async ({umbracoApi}) => {
- if (dataTypeDefaultData !== null) {
- await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData);
- }
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test('can add option', async ({umbracoApi, umbracoUi}) => {
// Arrange
const optionName = 'Test option';
- const expectedDataTypeValues = [
- {
- "alias": "items",
- "value": [optionName]
- }
- ];
- // Remove all existing options
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- dataTypeData.values = [];
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
- await umbracoUi.dataType.goToDataType(dataTypeName);
+ await umbracoApi.dataType.createRadioboxDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.clickAddOptionButton();
@@ -38,30 +28,41 @@ test('can add option', async ({umbracoApi, umbracoUi}) => {
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'items', [optionName])).toBeTruthy();
});
test('can remove option', async ({umbracoApi, umbracoUi}) => {
// Arrange
const removedOptionName = 'Removed Option';
- const removedOptionValues = [
- {
- "alias": "items",
- "value": [removedOptionName]
- }
- ];
- // Remove all existing options and add an option to remove
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- dataTypeData.values = removedOptionValues;
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
- await umbracoUi.dataType.goToDataType(dataTypeName);
+ const customDataType = 'Custom Radiobox';
+ await umbracoApi.dataType.createRadioboxDataType(customDataType, [removedOptionName]);
+ await umbracoUi.dataType.goToDataType(customDataType);
// Act
await umbracoUi.dataType.removeOptionByName(removedOptionName);
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toEqual([]);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataType, 'items', [removedOptionName])).toBeFalsy();
+
+ // Clean
+ await umbracoApi.dataType.ensureNameNotExists(customDataType);
+});
+
+test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.dataType.goToDataType(dataTypeName);
+
+ // Assert
+ await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.radioboxSettings);
+ await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.radioboxSettings);
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias);
+ const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ expect(dataTypeDefaultData.editorAlias).toBe(editorAlias);
+ expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias);
+ expect(dataTypeDefaultData.values).toEqual([]);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'items')).toBeFalsy();
});
\ No newline at end of file
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/RichTextEditor.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/RichTextEditor.spec.ts
index 7d03341929..62b8dc3928 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/RichTextEditor.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/RichTextEditor.spec.ts
@@ -1,23 +1,77 @@
-import {test} from '@umbraco/playwright-testhelpers';
+import {ConstantHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
+const dataTypeName = 'Richtext editor';
+const tipTapPropertyEditorName = 'Rich Text Editor [Tiptap] Property Editor UI';
+const tipTapAlias = 'Umbraco.RichText';
+const tipTapUiAlias = 'Umb.PropertyEditorUi.Tiptap';
+const extensionsDefaultValue = [
+ "Umb.Tiptap.Embed",
+ "Umb.Tiptap.Link",
+ "Umb.Tiptap.Figure",
+ "Umb.Tiptap.Image",
+ "Umb.Tiptap.Subscript",
+ "Umb.Tiptap.Superscript",
+ "Umb.Tiptap.Table",
+ "Umb.Tiptap.Underline",
+ "Umb.Tiptap.TextAlign",
+ "Umb.Tiptap.MediaUpload"
+];
+
+const toolbarDefaultValue = [
+ [
+ [
+ "Umb.Tiptap.Toolbar.SourceEditor"
+ ],
+ [
+ "Umb.Tiptap.Toolbar.Bold",
+ "Umb.Tiptap.Toolbar.Italic",
+ "Umb.Tiptap.Toolbar.Underline"
+ ],
+ [
+ "Umb.Tiptap.Toolbar.TextAlignLeft",
+ "Umb.Tiptap.Toolbar.TextAlignCenter",
+ "Umb.Tiptap.Toolbar.TextAlignRight"
+ ],
+ [
+ "Umb.Tiptap.Toolbar.BulletList",
+ "Umb.Tiptap.Toolbar.OrderedList"
+ ],
+ [
+ "Umb.Tiptap.Toolbar.Blockquote",
+ "Umb.Tiptap.Toolbar.HorizontalRule"
+ ],
+ [
+ "Umb.Tiptap.Toolbar.Link",
+ "Umb.Tiptap.Toolbar.Unlink"
+ ],
+ [
+ "Umb.Tiptap.Toolbar.MediaPicker",
+ "Umb.Tiptap.Toolbar.EmbeddedMedia"
+ ]
+ ]
+];
+
test('tiptap is the default property editor in rich text editor', async ({umbracoApi, umbracoUi}) => {
// Arrange
- const dataTypeName = 'Richtext editor';
- const tipTapPropertyEditorName = 'Rich Text Editor [Tiptap] Property Editor UI';
- const tipTapAlias = 'Umbraco.RichText';
- const tipTapUiAlias = 'Umb.PropertyEditorUi.Tiptap';
await umbracoUi.goToBackOffice();
await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
// Act
await umbracoUi.dataType.goToDataType(dataTypeName);
- // Assert
+ // Assert
+ //await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.tipTapSettings);
+ //await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.tipTapSettings);
await umbracoUi.dataType.doesPropertyEditorHaveName(tipTapPropertyEditorName);
- await umbracoUi.dataType.doesPropertyEditorHaveSchemaAlias(tipTapAlias);
- await umbracoUi.dataType.doesPropertyEditorHaveAlias(tipTapUiAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(tipTapAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(tipTapUiAlias);
const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
expect(dataTypeData.editorAlias).toBe(tipTapAlias);
expect(dataTypeData.editorUiAlias).toBe(tipTapUiAlias);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'maxImageSize', 500)).toBeTruthy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'overlaySize', 'medium')).toBeTruthy();
+ expect(await umbracoApi.dataType.doesTiptapExtensionsItemsMatchCount(dataTypeName, extensionsDefaultValue.length)).toBeTruthy();
+ expect(await umbracoApi.dataType.doesTiptapExtensionsHaveItems(dataTypeName, extensionsDefaultValue)).toBeTruthy();
+ expect(await umbracoApi.dataType.doesTiptapToolbarHaveItems(dataTypeName, toolbarDefaultValue)).toBeTruthy();
});
\ No newline at end of file
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tags.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tags.spec.ts
index 2424b4d9d6..9dbf6b013e 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tags.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tags.spec.ts
@@ -1,53 +1,63 @@
-import {test} from '@umbraco/playwright-testhelpers';
+import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
const dataTypeName = 'Tags';
-let dataTypeDefaultData = null;
-let dataTypeData = null;
+const editorAlias = 'Umbraco.Tags';
+const editorUiAlias = 'Umb.PropertyEditorUi.Tags';
+const customDataTypeName = 'Custom Tags';
test.beforeEach(async ({umbracoUi, umbracoApi}) => {
await umbracoUi.goToBackOffice();
await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
- await umbracoUi.dataType.goToDataType(dataTypeName);
- dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test.afterEach(async ({umbracoApi}) => {
- if (dataTypeDefaultData !== null) {
- await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData);
- }
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test('can update define a tag group', async ({umbracoApi, umbracoUi}) => {
// Arrange
const tagGroup = 'testTagGroup';
- const expectedDataTypeValues = {
- "alias": "group",
- "value": tagGroup
- };
+ await umbracoApi.dataType.createDefaultTagsDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.enterDefineTagGroupValue(tagGroup);
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'group', tagGroup)).toBeTruthy();
});
test('can select storage type', async ({umbracoApi, umbracoUi}) => {
// Arrange
const storageType = 'Csv';
- const expectedDataTypeValues = {
- "alias": "storageType",
- "value": storageType
- };
+ await umbracoApi.dataType.createDefaultTagsDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.selectStorageTypeOption(storageType);
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'storageType', storageType)).toBeTruthy();
+});
+
+test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.dataType.goToDataType(dataTypeName);
+
+ // Assert
+ await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.tagsSettings);
+ await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.tagsSettings);
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias);
+ const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ expect(dataTypeDefaultData.editorAlias).toBe(editorAlias);
+ expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'group', 'default', dataTypeDefaultData)).toBeTruthy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'storageType', 'Json', dataTypeDefaultData)).toBeTruthy();
});
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Textarea.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Textarea.spec.ts
index 20d9a183dd..07309ea5af 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Textarea.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Textarea.spec.ts
@@ -1,55 +1,64 @@
-import {test} from '@umbraco/playwright-testhelpers';
+import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
const dataTypeName = 'Textarea';
-let dataTypeDefaultData = null;
-let dataTypeData = null;
+const editorAlias = 'Umbraco.TextArea';
+const editorUiAlias = 'Umb.PropertyEditorUi.TextArea';
+const customDataTypeName = 'Custom Textarea';
test.beforeEach(async ({umbracoUi, umbracoApi}) => {
await umbracoUi.goToBackOffice();
await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
- await umbracoUi.dataType.goToDataType(dataTypeName);
- dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test.afterEach(async ({umbracoApi}) => {
- if (dataTypeDefaultData !== null) {
- await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData);
- }
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test('can update maximum allowed characters value', async ({umbracoApi, umbracoUi}) => {
// Arrange
const maxCharsValue = 126;
- const expectedDataTypeValues = {
- "alias": "maxChars",
- "value": maxCharsValue
- };
+ await umbracoApi.dataType.createTextareaDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.enterMaximumAllowedCharactersValue(maxCharsValue.toString());
await umbracoUi.dataType.clickSaveButton();
// Assert
- await umbracoUi.dataType.isSuccessNotificationVisible();
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'maxChars', maxCharsValue)).toBeTruthy();
});
test('can update number of rows value', async ({umbracoApi, umbracoUi}) => {
// Arrange
const numberOfRowsValue = 9;
- const expectedDataTypeValues = {
- "alias": "rows",
- "value": numberOfRowsValue
- };
+ await umbracoApi.dataType.createTextareaDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.enterNumberOfRowsValue(numberOfRowsValue.toString());
await umbracoUi.dataType.clickSaveButton();
// Assert
- await umbracoUi.dataType.isSuccessNotificationVisible();
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'rows', numberOfRowsValue)).toBeTruthy();
+});
+
+test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.dataType.goToDataType(dataTypeName);
+
+ // Assert
+ await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.textareaSettings);
+ await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.textareaSettings);
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias);
+ const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ expect(dataTypeDefaultData.editorAlias).toBe(editorAlias);
+ expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias);
+ expect(dataTypeDefaultData.values).toEqual([]);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'maxChars')).toBeFalsy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'rows')).toBeFalsy();
});
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Textstring.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Textstring.spec.ts
index db6e54242d..24a8574002 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Textstring.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Textstring.spec.ts
@@ -1,36 +1,49 @@
-import {test} from '@umbraco/playwright-testhelpers';
+import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
const dataTypeName = 'Textstring';
-let dataTypeDefaultData = null;
-let dataTypeData = null;
+const editorAlias = 'Umbraco.TextBox';
+const editorUiAlias = 'Umb.PropertyEditorUi.TextBox';
+const customDataTypeName = 'Custom Textstring';
test.beforeEach(async ({umbracoUi, umbracoApi}) => {
await umbracoUi.goToBackOffice();
await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
- await umbracoUi.dataType.goToDataType(dataTypeName);
- dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test.afterEach(async ({umbracoApi}) => {
- if (dataTypeDefaultData !== null) {
- await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData);
- }
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test('can update maximum allowed characters value', async ({umbracoApi, umbracoUi}) => {
// Arrange
const maxCharsValue = 126;
- const expectedDataTypeValues = {
- "alias": "maxChars",
- "value": maxCharsValue
- };
+ await umbracoApi.dataType.createTextstringDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.enterMaximumAllowedCharactersValue(maxCharsValue.toString());
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'maxChars', maxCharsValue)).toBeTruthy();
+});
+
+// Remove fixme when the front-end is ready. The "Input type" should be removed.
+test.fixme('the default configuration is correct', async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.dataType.goToDataType(dataTypeName);
+
+ // Assert
+ await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.textstringSettings);
+ await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.textstringSettings);
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias);
+ const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ expect(dataTypeDefaultData.editorAlias).toBe(editorAlias);
+ expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias);
+ expect(dataTypeDefaultData.values).toEqual([]);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'maxChars')).toBeFalsy();
});
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TinyMCE.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TinyMCE.spec.ts
index 590df30a44..a6257ce9b8 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TinyMCE.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TinyMCE.spec.ts
@@ -1,4 +1,4 @@
-import {NotificationConstantHelper, test} from "@umbraco/playwright-testhelpers";
+import {ConstantHelper, NotificationConstantHelper, test} from "@umbraco/playwright-testhelpers";
import {expect} from "@playwright/test";
const tinyMCEName = 'TestTinyMCE';
@@ -19,6 +19,22 @@ test('can create a rich text editor with tinyMCE', {tag: '@smoke'}, async ({umbr
const tinyMCEFilterKeyword = 'Rich Text Editor';
const tinyMCEAlias = 'Umbraco.RichText';
const tinyMCEUiAlias = 'Umb.PropertyEditorUi.TinyMCE';
+ const toolbarValue = [
+ "styles",
+ "bold",
+ "italic",
+ "alignleft",
+ "aligncenter",
+ "alignright",
+ "bullist",
+ "numlist",
+ "outdent",
+ "indent",
+ "sourcecode",
+ "link",
+ "umbmediapicker",
+ "umbembeddialog"
+ ];
// Act
await umbracoUi.dataType.clickActionsMenuAtRoot();
@@ -32,9 +48,17 @@ test('can create a rich text editor with tinyMCE', {tag: '@smoke'}, async ({umbr
// Assert
await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created);
expect(await umbracoApi.dataType.doesNameExist(tinyMCEName)).toBeTruthy();
+ // Verify the default configuration
+ await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.tinyMCESettings);
+ await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.tinyMCESettings);
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(tinyMCEAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(tinyMCEUiAlias);
const dataTypeData = await umbracoApi.dataType.getByName(tinyMCEName);
expect(dataTypeData.editorAlias).toBe(tinyMCEAlias);
expect(dataTypeData.editorUiAlias).toBe(tinyMCEUiAlias);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'maxImageSize', 500)).toBeTruthy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'mode', 'Classic')).toBeTruthy();
+ expect(await umbracoApi.dataType.doesTinyMCEToolbarHaveItems(tinyMCEName, toolbarValue)).toBeTruthy();
});
test('can rename a rich text editor with tinyMCE', async ({umbracoApi, umbracoUi}) => {
@@ -89,12 +113,7 @@ test('can enable toolbar options', async ({umbracoApi, umbracoUi}) => {
test('can add stylesheet', async ({umbracoApi, umbracoUi}) => {
// Arrange
const stylesheetName = 'StylesheetForDataType.css';
- await umbracoApi.stylesheet.ensureNameNotExists(stylesheetName);
const stylesheetPath = await umbracoApi.stylesheet.createDefaultStylesheet(stylesheetName);
- const expectedTinyMCEValues = {
- "alias": "stylesheets",
- "value": [stylesheetPath]
- };
await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName);
await umbracoUi.dataType.goToDataType(tinyMCEName);
@@ -104,8 +123,7 @@ test('can add stylesheet', async ({umbracoApi, umbracoUi}) => {
// Assert
await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
- const tinyMCEData = await umbracoApi.dataType.getByName(tinyMCEName);
- expect(tinyMCEData.values).toContainEqual(expectedTinyMCEValues);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'stylesheets', [stylesheetPath])).toBeTruthy();
// Clean
await umbracoApi.stylesheet.ensureNameNotExists(stylesheetName);
@@ -115,13 +133,6 @@ test('can add dimensions', async ({umbracoApi, umbracoUi}) => {
// Arrange
const width = 100;
const height = 10;
- const expectedTinyMCEValues = {
- "alias": "dimensions",
- "value": {
- "width": width,
- "height": height
- }
- };
await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName);
await umbracoUi.dataType.goToDataType(tinyMCEName);
@@ -131,17 +142,12 @@ test('can add dimensions', async ({umbracoApi, umbracoUi}) => {
// Assert
await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
- const tinyMCEData = await umbracoApi.dataType.getByName(tinyMCEName);
- expect(tinyMCEData.values).toContainEqual(expectedTinyMCEValues);
+ expect(await umbracoApi.dataType.doesRTEHaveDimensions(tinyMCEName, width, height)).toBeTruthy();
});
test('can update maximum size for inserted images', async ({umbracoApi, umbracoUi}) => {
// Arrange
const maximumSize = 300;
- const expectedTinyMCEValues = {
- "alias": "maxImageSize",
- "value": maximumSize
- };
await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName);
await umbracoUi.dataType.goToDataType(tinyMCEName);
@@ -151,17 +157,12 @@ test('can update maximum size for inserted images', async ({umbracoApi, umbracoU
// Assert
await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
- const tinyMCEData = await umbracoApi.dataType.getByName(tinyMCEName);
- expect(tinyMCEData.values).toContainEqual(expectedTinyMCEValues);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'maxImageSize', maximumSize)).toBeTruthy();
});
test('can enable inline editing mode', async ({umbracoApi, umbracoUi}) => {
// Arrange
const mode = 'Inline';
- const expectedTinyMCEValues = {
- "alias": "mode",
- "value": mode
- };
await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName);
await umbracoUi.dataType.goToDataType(tinyMCEName);
@@ -171,23 +172,14 @@ test('can enable inline editing mode', async ({umbracoApi, umbracoUi}) => {
// Assert
await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
- const tinyMCEData = await umbracoApi.dataType.getByName(tinyMCEName);
- expect(tinyMCEData.values).toContainEqual(expectedTinyMCEValues);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'mode', mode)).toBeTruthy();
});
test('can add an available block', async ({umbracoApi, umbracoUi}) => {
// Arrange
const elementTypeName = 'TestElementType';
- await umbracoApi.documentType.ensureNameNotExists(elementTypeName);
const elementTypeId = await umbracoApi.documentType.createEmptyElementType(elementTypeName);
- const expectedTinyMCEValues = {
- alias: "blocks",
- value: [
- {
- contentElementTypeKey: elementTypeId,
- }
- ]
- };
+
await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName);
await umbracoUi.dataType.goToDataType(tinyMCEName);
@@ -197,8 +189,7 @@ test('can add an available block', async ({umbracoApi, umbracoUi}) => {
// Assert
await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
- const tinyMCEData = await umbracoApi.dataType.getByName(tinyMCEName);
- expect(tinyMCEData.values).toContainEqual(expectedTinyMCEValues);
+ expect(await umbracoApi.dataType.doesRTEContainBlocks(tinyMCEName, [elementTypeId])).toBeTruthy();
// Clean
await umbracoApi.documentType.ensureNameNotExists(elementTypeName);
@@ -207,10 +198,6 @@ test('can add an available block', async ({umbracoApi, umbracoUi}) => {
test('can select overlay size', async ({umbracoApi, umbracoUi}) => {
// Arrange
const overlaySizeValue = 'large';
- const expectedTinyMCEValues = {
- "alias": "overlaySize",
- "value": overlaySizeValue
- };
await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName);
await umbracoUi.dataType.goToDataType(tinyMCEName);
@@ -220,16 +207,11 @@ test('can select overlay size', async ({umbracoApi, umbracoUi}) => {
// Assert
await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
- const tinyMCEData = await umbracoApi.dataType.getByName(tinyMCEName);
- expect(tinyMCEData.values).toContainEqual(expectedTinyMCEValues);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'overlaySize', overlaySizeValue)).toBeTruthy();
});
test('can enable hide label', async ({umbracoApi, umbracoUi}) => {
// Arrange
- const expectedTinyMCEValues = {
- "alias": "hideLabel",
- "value": true
- };
await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName);
await umbracoUi.dataType.goToDataType(tinyMCEName);
@@ -239,19 +221,13 @@ test('can enable hide label', async ({umbracoApi, umbracoUi}) => {
// Assert
await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
- const tinyMCEData = await umbracoApi.dataType.getByName(tinyMCEName);
- expect(tinyMCEData.values).toContainEqual(expectedTinyMCEValues);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'hideLabel', true)).toBeTruthy();
});
test('can add image upload folder', async ({umbracoApi, umbracoUi}) => {
// Arrange
const mediaFolderName = 'TestMediaFolder';
- await umbracoApi.media.ensureNameNotExists(mediaFolderName);
const mediaFolderId = await umbracoApi.media.createDefaultMediaFolder(mediaFolderName);
- const expectedTinyMCEValues = {
- "alias": "mediaParentId",
- "value": mediaFolderId
- };
await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName);
await umbracoUi.dataType.goToDataType(tinyMCEName);
@@ -261,8 +237,7 @@ test('can add image upload folder', async ({umbracoApi, umbracoUi}) => {
// Assert
await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
- const tinyMCEData = await umbracoApi.dataType.getByName(tinyMCEName);
- expect(tinyMCEData.values).toContainEqual(expectedTinyMCEValues);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'mediaParentId', mediaFolderId)).toBeTruthy();
// Clean
await umbracoApi.media.ensureNameNotExists(mediaFolderName);
@@ -270,10 +245,6 @@ test('can add image upload folder', async ({umbracoApi, umbracoUi}) => {
test('can enable ignore user start nodes', async ({umbracoApi, umbracoUi}) => {
// Arrange
- const expectedTinyMCEValues = {
- "alias": "ignoreUserStartNodes",
- "value": true
- };
await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName);
await umbracoUi.dataType.goToDataType(tinyMCEName);
@@ -283,6 +254,5 @@ test('can enable ignore user start nodes', async ({umbracoApi, umbracoUi}) => {
// Assert
await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
- const tinyMCEData = await umbracoApi.dataType.getByName(tinyMCEName);
- expect(tinyMCEData.values).toContainEqual(expectedTinyMCEValues);
-});
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'ignoreUserStartNodes', true)).toBeTruthy();
+});
\ No newline at end of file
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tiptap.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tiptap.spec.ts
index d999675f0e..650859289e 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tiptap.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tiptap.spec.ts
@@ -71,13 +71,6 @@ test('can add dimensions', async ({umbracoApi, umbracoUi}) => {
// Arrange
const width = 100;
const height = 10;
- const expectedTiptapValues = {
- "alias": "dimensions",
- "value": {
- "width": width,
- "height": height
- }
- };
await umbracoApi.dataType.createDefaultTiptapDataType(tipTapName);
await umbracoUi.dataType.goToDataType(tipTapName);
@@ -87,17 +80,12 @@ test('can add dimensions', async ({umbracoApi, umbracoUi}) => {
// Assert
await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
- const tiptapData = await umbracoApi.dataType.getByName(tipTapName);
- expect(tiptapData.values).toContainEqual(expectedTiptapValues);
+expect(await umbracoApi.dataType.doesRTEHaveDimensions(tipTapName, width, height)).toBeTruthy();
});
test('can update maximum size for inserted images', async ({umbracoApi, umbracoUi}) => {
// Arrange
const maximumSize = 300;
- const expectedTiptapValues = {
- "alias": "maxImageSize",
- "value": maximumSize
- };
await umbracoApi.dataType.createDefaultTiptapDataType(tipTapName);
await umbracoUi.dataType.goToDataType(tipTapName);
@@ -107,17 +95,12 @@ test('can update maximum size for inserted images', async ({umbracoApi, umbracoU
// Assert
await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
- const tiptapData = await umbracoApi.dataType.getByName(tipTapName);
- expect(tiptapData.values).toContainEqual(expectedTiptapValues);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(tipTapName, 'maxImageSize', maximumSize)).toBeTruthy();
});
test('can select overlay size', async ({umbracoApi, umbracoUi}) => {
// Arrange
const overlaySizeValue = 'large';
- const expectedTiptapValues = {
- "alias": "overlaySize",
- "value": overlaySizeValue
- };
await umbracoApi.dataType.createDefaultTiptapDataType(tipTapName);
await umbracoUi.dataType.goToDataType(tipTapName);
@@ -127,23 +110,13 @@ test('can select overlay size', async ({umbracoApi, umbracoUi}) => {
// Assert
await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
- const tiptapData = await umbracoApi.dataType.getByName(tipTapName);
- expect(tiptapData.values).toContainEqual(expectedTiptapValues);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(tipTapName, 'overlaySize', overlaySizeValue)).toBeTruthy();
});
test('can add an available block', async ({umbracoApi, umbracoUi}) => {
// Arrange
const elementTypeName = 'TestElementType';
- await umbracoApi.documentType.ensureNameNotExists(elementTypeName);
const elementTypeId = await umbracoApi.documentType.createEmptyElementType(elementTypeName);
- const expectedTiptapValues = {
- alias: "blocks",
- value: [
- {
- contentElementTypeKey: elementTypeId,
- }
- ]
- };
await umbracoApi.dataType.createDefaultTiptapDataType(tipTapName);
await umbracoUi.dataType.goToDataType(tipTapName);
@@ -153,8 +126,7 @@ test('can add an available block', async ({umbracoApi, umbracoUi}) => {
// Assert
await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
- const tiptapData = await umbracoApi.dataType.getByName(tipTapName);
- expect(tiptapData.values).toContainEqual(expectedTiptapValues);
+ expect(await umbracoApi.dataType.doesRTEContainBlocks(tipTapName, [elementTypeId])).toBeTruthy();
// Clean
await umbracoApi.documentType.ensureNameNotExists(elementTypeName);
@@ -163,12 +135,7 @@ test('can add an available block', async ({umbracoApi, umbracoUi}) => {
test('can add image upload folder', async ({umbracoApi, umbracoUi}) => {
// Arrange
const mediaFolderName = 'TestMediaFolder';
- await umbracoApi.media.ensureNameNotExists(mediaFolderName);
const mediaFolderId = await umbracoApi.media.createDefaultMediaFolder(mediaFolderName);
- const expectedTiptapValues = {
- "alias": "mediaParentId",
- "value": mediaFolderId
- };
await umbracoApi.dataType.createDefaultTiptapDataType(tipTapName);
await umbracoUi.dataType.goToDataType(tipTapName);
@@ -178,8 +145,7 @@ test('can add image upload folder', async ({umbracoApi, umbracoUi}) => {
// Assert
await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
- const tiptapData = await umbracoApi.dataType.getByName(tipTapName);
- expect(tiptapData.values).toContainEqual(expectedTiptapValues);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(tipTapName, 'mediaParentId', mediaFolderId)).toBeTruthy();
// Clean
await umbracoApi.media.ensureNameNotExists(mediaFolderName);
@@ -187,10 +153,6 @@ test('can add image upload folder', async ({umbracoApi, umbracoUi}) => {
test('can enable ignore user start nodes', async ({umbracoApi, umbracoUi}) => {
// Arrange
- const expectedTiptapValues = {
- "alias": "ignoreUserStartNodes",
- "value": true
- };
await umbracoApi.dataType.createDefaultTiptapDataType(tipTapName);
await umbracoUi.dataType.goToDataType(tipTapName);
@@ -200,8 +162,7 @@ test('can enable ignore user start nodes', async ({umbracoApi, umbracoUi}) => {
// Assert
await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
- const tipTapData = await umbracoApi.dataType.getByName(tipTapName);
- expect(tipTapData.values).toContainEqual(expectedTiptapValues);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(tipTapName, 'ignoreUserStartNodes', true)).toBeTruthy();
});
test('can delete toolbar group', async ({umbracoApi, umbracoUi}) => {
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TrueFalse.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TrueFalse.spec.ts
index 3c8abcf0f2..3803a2c852 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TrueFalse.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TrueFalse.spec.ts
@@ -1,87 +1,94 @@
-import {test} from '@umbraco/playwright-testhelpers';
+import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
const dataTypeName = 'True/false';
-let dataTypeDefaultData = null;
-let dataTypeData = null;
+const editorAlias = 'Umbraco.TrueFalse';
+const editorUiAlias = 'Umb.PropertyEditorUi.Toggle';
+const customDataTypeName = 'Custom TrueFalse';
test.beforeEach(async ({umbracoUi, umbracoApi}) => {
await umbracoUi.goToBackOffice();
await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
- await umbracoUi.dataType.goToDataType(dataTypeName);
- dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test.afterEach(async ({umbracoApi}) => {
- if (dataTypeDefaultData !== null) {
- await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData);
- }
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test('can update preset value state', async ({umbracoApi, umbracoUi}) => {
// Arrange
- const expectedDataTypeValues = {
- "alias": "default",
- "value": true
- };
+ await umbracoApi.dataType.createDefaultTrueFalseDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
// Act
await umbracoUi.dataType.clickPresetValueToggle();
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'default', true)).toBeTruthy();
});
test('can update show toggle labels', async ({umbracoApi, umbracoUi}) => {
// Arrange
- const expectedDataTypeValues = {
- "alias": "showLabels",
- "value": true
- };
-
+ await umbracoApi.dataType.createDefaultTrueFalseDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
+
// Act
await umbracoUi.dataType.clickShowToggleLabelsToggle();
await umbracoUi.dataType.clickSaveButton();
// Assert
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'showLabels', true)).toBeTruthy();
});
test('can update label on', async ({umbracoApi, umbracoUi}) => {
// Arrange
const labelOnValue = 'Test Label On';
- const expectedDataTypeValues = {
- "alias": "labelOn",
- "value": labelOnValue
- };
-
+ await umbracoApi.dataType.createDefaultTrueFalseDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
+
// Act
await umbracoUi.dataType.enterLabelOnValue(labelOnValue);
await umbracoUi.dataType.clickSaveButton();
// Assert
- await umbracoUi.dataType.isSuccessNotificationVisible();
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'labelOn', labelOnValue)).toBeTruthy();
});
test('can update label off', async ({umbracoApi, umbracoUi}) => {
// Arrange
const labelOffValue = 'Test Label Off';
- const expectedDataTypeValues = {
- "alias": "labelOff",
- "value": labelOffValue
- };
-
+ await umbracoApi.dataType.createDefaultTrueFalseDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
+
// Act
await umbracoUi.dataType.enterLabelOffValue(labelOffValue);
await umbracoUi.dataType.clickSaveButton();
// Assert
- await umbracoUi.dataType.isSuccessNotificationVisible();
- dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- expect(dataTypeData.values).toContainEqual(expectedDataTypeValues);
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'labelOff', labelOffValue)).toBeTruthy();
});
+
+test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.dataType.goToDataType(dataTypeName);
+
+ // Assert
+ await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.trueFalseSettings);
+ await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.trueFalseSettings);
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias);
+ const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName);
+ expect(dataTypeDefaultData.editorAlias).toBe(editorAlias);
+ expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias);
+ expect(dataTypeDefaultData.values).toEqual([]);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'default')).toBeFalsy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'showLabels')).toBeFalsy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'labelOn')).toBeFalsy();
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'labelOff')).toBeFalsy();
+});
\ No newline at end of file
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Upload.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Upload.spec.ts
deleted file mode 100644
index f3eab5c438..0000000000
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Upload.spec.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-import { test } from "@umbraco/playwright-testhelpers";
-import { expect } from "@playwright/test";
-
-const uploadTypes = ['Upload Article', 'Upload Audio', 'Upload File', 'Upload Vector Graphics', 'Upload Video'];
-for (const uploadType of uploadTypes) {
- test.describe(`${uploadType} tests`, () => {
- let dataTypeDefaultData = null;
- let dataTypeData = null;
-
- test.beforeEach(async ({ umbracoUi, umbracoApi }) => {
- await umbracoUi.goToBackOffice();
- await umbracoUi.dataType.goToSettingsTreeItem("Data Types");
- dataTypeDefaultData = await umbracoApi.dataType.getByName(uploadType);
- });
-
- test.afterEach(async ({ umbracoApi }) => {
- if (dataTypeDefaultData !== null) {
- await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData);
- }
- });
-
- test('can add accepted file extension', async ({ umbracoApi, umbracoUi }) => {
- // Arrange
- const fileExtensionValue = 'zip';
- const expectedDataTypeValues = [
- {
- "alias": "fileExtensions",
- "value": [fileExtensionValue]
- }
- ];
- // Remove all existing accepted file extensions
- dataTypeData = await umbracoApi.dataType.getByName(uploadType);
- dataTypeData.values = [];
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
- await umbracoUi.dataType.goToDataType(uploadType);
-
- // Act
- await umbracoUi.waitForTimeout(500);
- await umbracoUi.dataType.clickAddAcceptedFileExtensionsButton();
- await umbracoUi.dataType.enterAcceptedFileExtensions(fileExtensionValue);
- await umbracoUi.waitForTimeout(500);
- await umbracoUi.dataType.clickSaveButton();
-
- // Assert
- await umbracoUi.dataType.isSuccessNotificationVisible();
- await umbracoUi.waitForTimeout(500);
- dataTypeData = await umbracoApi.dataType.getByName(uploadType);
- expect(dataTypeData.values).toEqual(expectedDataTypeValues);
- });
-
- test('can remove accepted file extension', async ({ umbracoApi, umbracoUi }) => {
- // Arrange
- const removedFileExtensionValue = "bat";
- const removedFileExtensionsValues = [
- {
- "alias": "fileExtensions",
- "value": [removedFileExtensionValue]
- }
- ];
- // Remove all existing accepted file extensions and add an file extension to remove
- dataTypeData = await umbracoApi.dataType.getByName(uploadType);
- dataTypeData.values = removedFileExtensionsValues;
- await umbracoApi.dataType.update(dataTypeData.id, dataTypeData);
- await umbracoUi.dataType.goToDataType(uploadType);
-
- // Act
- await umbracoUi.dataType.removeAcceptedFileExtensionsByValue(removedFileExtensionValue);
- await umbracoUi.dataType.clickSaveButton();
-
- // Assert
- await umbracoUi.dataType.isSuccessNotificationVisible();
- dataTypeData = await umbracoApi.dataType.getByName(uploadType);
- expect(dataTypeData.values).toEqual([]);
- });
- });
-}
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/UploadField.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/UploadField.spec.ts
new file mode 100644
index 0000000000..6832691e55
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/UploadField.spec.ts
@@ -0,0 +1,73 @@
+import { ConstantHelper, NotificationConstantHelper, test } from "@umbraco/playwright-testhelpers";
+import { expect } from "@playwright/test";
+
+const uploadTypes = [
+ {type: 'Upload Article', fileExtensions: ['pdf', 'docx', 'doc']},
+ {type: 'Upload Audio', fileExtensions: ['mp3', 'weba', 'oga', 'opus']},
+ {type: 'Upload File', fileExtensions: []},
+ {type: 'Upload Vector Graphics', fileExtensions: ['svg']},
+ {type: 'Upload Video', fileExtensions: ['mp4', 'webm', 'ogv']}
+];
+const customDataTypeName = 'Custom Upload Field';
+const editorAlias = 'Umbraco.UploadField';
+const editorUiAlias = 'Umb.PropertyEditorUi.UploadField';
+
+test.beforeEach(async ({ umbracoUi }) => {
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.dataType.goToSettingsTreeItem('Data Types');
+});
+
+test.afterEach(async ({umbracoApi}) => {
+ await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
+});
+
+test('can add accepted file extension', async ({ umbracoApi, umbracoUi }) => {
+ // Arrange
+ const fileExtensionValue = 'zip';
+ await umbracoApi.dataType.createUploadDataType(customDataTypeName);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
+
+ // Act
+ await umbracoUi.dataType.clickAddAcceptedFileExtensionsButton();
+ await umbracoUi.dataType.enterAcceptedFileExtensions(fileExtensionValue);
+ await umbracoUi.dataType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'fileExtensions', [fileExtensionValue])).toBeTruthy();
+});
+
+test('can remove accepted file extension', async ({ umbracoApi, umbracoUi }) => {
+ // Arrange
+ const removedFileExtensionValue = "bat";
+ await umbracoApi.dataType.createUploadDataType(customDataTypeName, [removedFileExtensionValue]);
+ await umbracoUi.dataType.goToDataType(customDataTypeName);
+
+ // Act
+ await umbracoUi.dataType.removeAcceptedFileExtensionsByValue(removedFileExtensionValue);
+ await umbracoUi.dataType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ const customDataTypeData = await umbracoApi.dataType.getByName(customDataTypeName);
+ expect(customDataTypeData.values).toEqual([]);
+});
+
+for (const uploadType of uploadTypes) {
+ test(`the default configuration of ${uploadType.type} is correct`, async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.dataType.goToDataType(uploadType.type);
+
+ // Assert
+ await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.uploadSettings);
+ await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.uploadSettings);
+ await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias);
+ await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias);
+ if (uploadType.fileExtensions.length > 0) {
+ expect(await umbracoApi.dataType.doesDataTypeHaveValue(uploadType.type, 'fileExtensions', uploadType.fileExtensions)).toBeTruthy();
+ } else {
+ const dataTypeDefaultData = await umbracoApi.dataType.getByName(uploadType.type);
+ expect(dataTypeDefaultData.values).toEqual([]);
+ }
+ });
+}
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts
index 5daad8769b..bd645179ee 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts
@@ -9,7 +9,7 @@ const colorValue = {label: "Test Label", value: "038c33"};
let dataTypeId = null;
test.beforeEach(async ({umbracoApi}) => {
- dataTypeId = await umbracoApi.dataType.createApprovedColorDataTypeWithOneItem(customDataTypeName, colorValue.label, colorValue.value);
+ dataTypeId = await umbracoApi.dataType.createDefaultApprovedColorDataTypeWithOneItem(customDataTypeName, colorValue.label, colorValue.value);
});
test.afterEach(async ({umbracoApi}) => {
From a71ebe1902c78dbf7cbefdca041043dd8d84f215 Mon Sep 17 00:00:00 2001
From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
Date: Thu, 27 Mar 2025 11:11:37 +0100
Subject: [PATCH 07/38] V15: Improve the dropzone for Image Cropper (#18838)
* feat: uses the umb-dropzone-input to render the dropzone
* feat: loads in the blob url rather than reading the file into memory AND appends the server url
* chore: lit 3 compat
* feat: uses the umb-dropzone-input to render the dropzone
* Revert "feat: uses the umb-dropzone-input to render the dropzone"
This reverts commit bc1a6ae7df2e3230a132ce1a3756c7b2348647f9.
* feat: creates an object url directly from the File rather than the Blob
* feat: revokes the file data url from object storage
* feat: revokes object url on disconnect
---
.../image-cropper-field.element.ts | 57 ++++++----
.../image-cropper-preview.element.ts | 6 +-
.../input-image-cropper.element.ts | 100 +++++++-----------
3 files changed, 81 insertions(+), 82 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/image-cropper-field.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/image-cropper-field.element.ts
index 5ba36c22b8..b8d403ba09 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/image-cropper-field.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/image-cropper-field.element.ts
@@ -1,3 +1,5 @@
+import type { UmbImageCropChangeEvent } from './crop-change.event.js';
+import type { UmbFocalPointChangeEvent } from './focalpoint-change.event.js';
import type { UmbImageCropperElement } from './image-cropper.element.js';
import type {
UmbImageCropperCrop,
@@ -5,15 +7,14 @@ import type {
UmbImageCropperFocalPoint,
UmbImageCropperPropertyEditorValue,
} from './types.js';
-import type { UmbImageCropChangeEvent } from './crop-change.event.js';
-import type { UmbFocalPointChangeEvent } from './focalpoint-change.event.js';
import { css, customElement, html, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
+import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app';
-import './image-cropper.element.js';
import './image-cropper-focus-setter.element.js';
import './image-cropper-preview.element.js';
+import './image-cropper.element.js';
@customElement('umb-image-cropper-field')
export class UmbInputImageCropperFieldElement extends UmbLitElement {
@@ -46,7 +47,19 @@ export class UmbInputImageCropperFieldElement extends UmbLitElement {
currentCrop?: UmbImageCropperCrop;
@property({ attribute: false })
- file?: File;
+ set file(file: File | undefined) {
+ this.#file = file;
+ if (file) {
+ this.fileDataUrl = URL.createObjectURL(file);
+ } else if (this.fileDataUrl) {
+ URL.revokeObjectURL(this.fileDataUrl);
+ this.fileDataUrl = undefined;
+ }
+ }
+ get file() {
+ return this.#file;
+ }
+ #file?: File;
@property()
fileDataUrl?: string;
@@ -60,25 +73,29 @@ export class UmbInputImageCropperFieldElement extends UmbLitElement {
@state()
src = '';
- get source() {
- if (this.fileDataUrl) return this.fileDataUrl;
- if (this.src) return this.src;
- return '';
+ @state()
+ private _serverUrl = '';
+
+ get source(): string {
+ if (this.src) {
+ return `${this._serverUrl}${this.src}`;
+ }
+
+ return this.fileDataUrl ?? '';
}
- override updated(changedProperties: Map) {
- super.updated(changedProperties);
+ constructor() {
+ super();
- if (changedProperties.has('file')) {
- if (this.file) {
- const reader = new FileReader();
- reader.onload = (event) => {
- this.fileDataUrl = event.target?.result as string;
- };
- reader.readAsDataURL(this.file);
- } else {
- this.fileDataUrl = undefined;
- }
+ this.consumeContext(UMB_APP_CONTEXT, (context) => {
+ this._serverUrl = context.getServerUrl();
+ });
+ }
+
+ override disconnectedCallback(): void {
+ super.disconnectedCallback();
+ if (this.fileDataUrl) {
+ URL.revokeObjectURL(this.fileDataUrl);
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/image-cropper-preview.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/image-cropper-preview.element.ts
index a092f0868c..d203a1af7e 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/image-cropper-preview.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/image-cropper-preview.element.ts
@@ -18,13 +18,13 @@ export class UmbImageCropperPreviewElement extends UmbLitElement {
label?: string;
@property({ attribute: false })
- get focalPoint() {
- return this.#focalPoint;
- }
set focalPoint(value) {
this.#focalPoint = value;
this.#onFocalPointUpdated();
}
+ get focalPoint() {
+ return this.#focalPoint;
+ }
#focalPoint: UmbImageCropperFocalPoint = { left: 0.5, top: 0.5 };
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/input-image-cropper.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/input-image-cropper.element.ts
index b90bd8dd20..1bf0a945c4 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/input-image-cropper.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/input-image-cropper.element.ts
@@ -1,21 +1,26 @@
import type { UmbImageCropperPropertyEditorValue } from './types.js';
import type { UmbInputImageCropperFieldElement } from './image-cropper-field.element.js';
-import { html, customElement, property, query, state, css, ifDefined } from '@umbraco-cms/backoffice/external/lit';
-import type { UUIFileDropzoneElement, UUIFileDropzoneEvent } from '@umbraco-cms/backoffice/external/uui';
-import { UmbId } from '@umbraco-cms/backoffice/id';
-import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
-import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
-import { UmbTemporaryFileManager } from '@umbraco-cms/backoffice/temporary-file';
+import { css, customElement, html, ifDefined, property, state } from '@umbraco-cms/backoffice/external/lit';
import { assignToFrozenObject } from '@umbraco-cms/backoffice/observable-api';
+import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
+import { UmbFileDropzoneItemStatus, UmbInputDropzoneDashedStyles } from '@umbraco-cms/backoffice/dropzone';
+import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
+import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
+import { UmbTemporaryFileConfigRepository } from '@umbraco-cms/backoffice/temporary-file';
import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
+import type {
+ UmbDropzoneChangeEvent,
+ UmbInputDropzoneElement,
+ UmbUploadableItem,
+} from '@umbraco-cms/backoffice/dropzone';
-import './image-cropper.element.js';
+import './image-cropper-field.element.js';
import './image-cropper-focus-setter.element.js';
import './image-cropper-preview.element.js';
-import './image-cropper-field.element.js';
+import './image-cropper.element.js';
const DefaultFocalPoint = { left: 0.5, top: 0.5 };
-const DefaultValue = {
+const DefaultValue: UmbImageCropperPropertyEditorValue = {
temporaryFileId: null,
src: '',
crops: [],
@@ -28,9 +33,6 @@ export class UmbInputImageCropperElement extends UmbFormControlMixin<
typeof UmbLitElement,
undefined
>(UmbLitElement, undefined) {
- @query('#dropzone')
- private _dropzone?: UUIFileDropzoneElement;
-
/**
* Sets the input to required, meaning validation will fail if the value is empty.
* @type {boolean}
@@ -45,10 +47,7 @@ export class UmbInputImageCropperElement extends UmbFormControlMixin<
crops: UmbImageCropperPropertyEditorValue['crops'] = [];
@state()
- file?: File;
-
- @state()
- fileUnique?: string;
+ private _file?: UmbUploadableItem;
@state()
private _accept?: string;
@@ -56,7 +55,7 @@ export class UmbInputImageCropperElement extends UmbFormControlMixin<
@state()
private _loading = true;
- #manager = new UmbTemporaryFileManager(this);
+ #config = new UmbTemporaryFileConfigRepository(this);
constructor() {
super();
@@ -76,9 +75,9 @@ export class UmbInputImageCropperElement extends UmbFormControlMixin<
}
async #observeAcceptedFileTypes() {
- const config = await this.#manager.getConfiguration();
+ await this.#config.initialized;
this.observe(
- config.part('imageFileTypes'),
+ this.#config.part('imageFileTypes'),
(imageFileTypes) => {
this._accept = imageFileTypes.join(',');
this._loading = false;
@@ -87,34 +86,27 @@ export class UmbInputImageCropperElement extends UmbFormControlMixin<
);
}
- #onUpload(e: UUIFileDropzoneEvent) {
- const file = e.detail.files[0];
- if (!file) return;
- const unique = UmbId.new();
+ #onUpload(e: UmbDropzoneChangeEvent) {
+ e.stopImmediatePropagation();
- this.file = file;
- this.fileUnique = unique;
+ const target = e.target as UmbInputDropzoneElement;
+ const file = target.value?.[0];
- this.value = assignToFrozenObject(this.value ?? DefaultValue, { temporaryFileId: unique });
+ if (file?.status !== UmbFileDropzoneItemStatus.COMPLETE) return;
- this.#manager?.uploadOne({ temporaryUnique: unique, file });
+ this._file = file;
+
+ this.value = assignToFrozenObject(this.value ?? DefaultValue, {
+ temporaryFileId: file.temporaryFile?.temporaryUnique,
+ });
this.dispatchEvent(new UmbChangeEvent());
}
- #onBrowse(e: Event) {
- if (!this._dropzone) return;
- e.stopImmediatePropagation();
- this._dropzone.browse();
- }
-
#onRemove = () => {
this.value = undefined;
- if (this.fileUnique) {
- this.#manager?.removeOne(this.fileUnique);
- }
- this.fileUnique = undefined;
- this.file = undefined;
+ this._file?.temporaryFile?.abortController?.abort();
+ this._file = undefined;
this.dispatchEvent(new UmbChangeEvent());
};
@@ -144,7 +136,7 @@ export class UmbInputImageCropperElement extends UmbFormControlMixin<
return html`
`;
}
- if (this.value?.src || this.file) {
+ if (this.value?.src || this._file) {
return this.#renderImageCropper();
}
@@ -153,14 +145,11 @@ export class UmbInputImageCropperElement extends UmbFormControlMixin<
#renderDropzone() {
return html`
-
-
-
+ disable-folder-upload
+ @change="${this.#onUpload}">
`;
}
@@ -184,31 +173,24 @@ export class UmbInputImageCropperElement extends UmbFormControlMixin<
}
#renderImageCropper() {
- return html`
+ return html`
${this.localize.term('content_uploadClear')}
`;
}
- static override styles = [
+ static override readonly styles = [
+ UmbTextStyles,
+ UmbInputDropzoneDashedStyles,
css`
#loader {
display: flex;
justify-content: center;
}
-
- uui-file-dropzone {
- position: relative;
- display: block;
- }
- uui-file-dropzone::after {
- content: '';
- position: absolute;
- inset: 0;
- cursor: pointer;
- border: 1px dashed var(--uui-color-divider-emphasis);
- }
`,
];
}
From 164868f32a21e9e8a50f3e1bd1c36e94a9fa62ea Mon Sep 17 00:00:00 2001
From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
Date: Thu, 27 Mar 2025 11:22:03 +0100
Subject: [PATCH 08/38] V15: Improve the dropzone for Upload Field (#18840)
* feat: maps up the CANCELLED status
* feat: uses the new dropzone input to render the dropzone
* feat: adds support for differing server urls
* chore: avoids a breaking change by storing the temporary file
---
.../media/dropzone/dropzone-manager.class.ts | 12 +-
.../input-upload-field.element.ts | 263 +++++-------------
2 files changed, 82 insertions(+), 193 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/dropzone-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/dropzone-manager.class.ts
index 52d4636371..64c6bfb6ca 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/dropzone-manager.class.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/dropzone-manager.class.ts
@@ -70,7 +70,6 @@ export class UmbDropzoneManager extends UmbControllerBase {
}
/**
- * @param isAllowed
* @deprecated Not used anymore; this method will be removed in Umbraco 17.
*/
public setIsFoldersAllowed(isAllowed: boolean) {
@@ -128,7 +127,9 @@ export class UmbDropzoneManager extends UmbControllerBase {
const uploaded = await this.#tempFileManager.uploadOne(item.temporaryFile);
// Update progress
- if (uploaded.status === TemporaryFileStatus.SUCCESS) {
+ if (uploaded.status === TemporaryFileStatus.CANCELLED) {
+ this.#updateStatus(item, UmbFileDropzoneItemStatus.CANCELLED);
+ } else if (uploaded.status === TemporaryFileStatus.SUCCESS) {
this.#updateStatus(item, UmbFileDropzoneItemStatus.COMPLETE);
} else {
this.#updateStatus(item, UmbFileDropzoneItemStatus.ERROR);
@@ -226,7 +227,8 @@ export class UmbDropzoneManager extends UmbControllerBase {
async #handleFile(item: UmbUploadableFile, mediaTypeUnique: string) {
// Upload the file as a temporary file and update progress.
- const temporaryFile = await this.#uploadAsTemporaryFile(item);
+ const temporaryFile = await this.#tempFileManager.uploadOne(item.temporaryFile);
+
if (temporaryFile.status === TemporaryFileStatus.CANCELLED) {
this.#updateStatus(item, UmbFileDropzoneItemStatus.CANCELLED);
return;
@@ -257,10 +259,6 @@ export class UmbDropzoneManager extends UmbControllerBase {
}
}
- #uploadAsTemporaryFile(item: UmbUploadableFile) {
- return this.#tempFileManager.uploadOne(item.temporaryFile);
- }
-
// Media types
async #getMediaTypeOptions(item: UmbUploadableItem): Promise> {
// Check the parent which children media types are allowed
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.element.ts
index 7c7cd25841..3ada651c56 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.element.ts
@@ -1,33 +1,28 @@
import type { MediaValueType } from '../../property-editors/upload-field/types.js';
import type { ManifestFileUploadPreview } from './file-upload-preview.extension.js';
import { getMimeTypeFromExtension } from './utils.js';
-import {
- css,
- html,
- nothing,
- ifDefined,
- customElement,
- property,
- query,
- state,
- when,
-} from '@umbraco-cms/backoffice/external/lit';
-import { formatBytes, stringOrStringArrayContains } from '@umbraco-cms/backoffice/utils';
-import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
+import { css, customElement, html, ifDefined, nothing, property, state } from '@umbraco-cms/backoffice/external/lit';
+import { stringOrStringArrayContains } from '@umbraco-cms/backoffice/utils';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { UmbExtensionsManifestInitializer } from '@umbraco-cms/backoffice/extension-api';
-import { UmbId } from '@umbraco-cms/backoffice/id';
+import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
+import { UmbFileDropzoneItemStatus, UmbInputDropzoneDashedStyles } from '@umbraco-cms/backoffice/dropzone';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
-import { UmbTemporaryFileManager, TemporaryFileStatus } from '@umbraco-cms/backoffice/temporary-file';
-import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
+import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
+import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app';
+import type {
+ UmbDropzoneChangeEvent,
+ UmbInputDropzoneElement,
+ UmbUploadableFile,
+} from '@umbraco-cms/backoffice/dropzone';
import type { UmbTemporaryFileModel } from '@umbraco-cms/backoffice/temporary-file';
-import type { UUIFileDropzoneElement, UUIFileDropzoneEvent } from '@umbraco-cms/backoffice/external/uui';
@customElement('umb-input-upload-field')
export class UmbInputUploadFieldElement extends UmbLitElement {
- @property({ type: Object })
+ @property({ type: Object, attribute: false })
set value(value: MediaValueType) {
this.#src = value?.src ?? '';
+ this.#setPreviewAlias();
}
get value(): MediaValueType {
return {
@@ -42,39 +37,43 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
* @type {Array}
* @default
*/
- @property({ type: Array })
- set allowedFileExtensions(value: Array) {
- this.#setExtensions(value);
- }
- get allowedFileExtensions(): Array | undefined {
- return this._extensions;
- }
+ @property({
+ type: Array,
+ attribute: 'allowed-file-extensions',
+ converter(value) {
+ if (typeof value === 'string') {
+ return value.split(',').map((ext) => ext.trim());
+ }
+ return value;
+ },
+ })
+ allowedFileExtensions?: Array;
@state()
public temporaryFile?: UmbTemporaryFileModel;
- @state()
- private _progress = 0;
-
@state()
private _extensions?: string[];
@state()
private _previewAlias?: string;
- @query('#dropzone')
- private _dropzone?: UUIFileDropzoneElement;
-
- #manager = new UmbTemporaryFileManager(this);
+ @state()
+ private _serverUrl = '';
#manifests: Array = [];
- override updated(changedProperties: PropertyValueMap | Map) {
- super.updated(changedProperties);
+ constructor() {
+ super();
- if (changedProperties.has('value') && changedProperties.get('value')?.src !== this.value.src) {
- this.#setPreviewAlias();
- }
+ this.consumeContext(UMB_APP_CONTEXT, (context) => {
+ this._serverUrl = context.getServerUrl();
+ });
+ }
+
+ override disconnectedCallback(): void {
+ super.disconnectedCallback();
+ this.#clearObjectUrl();
}
async #getManifests() {
@@ -87,15 +86,6 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
return this.#manifests;
}
- #setExtensions(extensions: Array) {
- if (!extensions?.length) {
- this._extensions = undefined;
- return;
- }
- // TODO: The dropzone uui component does not support file extensions without a dot. Remove this when it does.
- this._extensions = extensions?.map((extension) => `.${extension}`);
- }
-
async #setPreviewAlias(): Promise {
this._previewAlias = await this.#getPreviewElementAlias();
}
@@ -151,47 +141,22 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
return getMimeTypeFromExtension('.' + extension);
}
- async #onUpload(e: UUIFileDropzoneEvent) {
- try {
- //Property Editor for Upload field will always only have one file.
- this.temporaryFile = {
- temporaryUnique: UmbId.new(),
- status: TemporaryFileStatus.WAITING,
- file: e.detail.files[0],
- onProgress: (p) => {
- this._progress = Math.ceil(p);
- },
- abortController: new AbortController(),
- };
-
- const uploaded = await this.#manager.uploadOne(this.temporaryFile);
-
- if (uploaded.status === TemporaryFileStatus.SUCCESS) {
- this.temporaryFile.status = TemporaryFileStatus.SUCCESS;
-
- const blobUrl = URL.createObjectURL(this.temporaryFile.file);
- this.value = { src: blobUrl };
-
- this.dispatchEvent(new UmbChangeEvent());
- } else {
- this.temporaryFile.status = TemporaryFileStatus.ERROR;
- this.requestUpdate('temporaryFile');
- }
- } catch {
- // If we still have a temporary file, set it to error.
- if (this.temporaryFile) {
- this.temporaryFile.status = TemporaryFileStatus.ERROR;
- this.requestUpdate('temporaryFile');
- }
-
- // If the error was caused by the upload being aborted, do not show an error message.
- }
- }
-
- #handleBrowse(e: Event) {
- if (!this._dropzone) return;
+ async #onUpload(e: UmbDropzoneChangeEvent) {
e.stopImmediatePropagation();
- this._dropzone.browse();
+
+ const target = e.target as UmbInputDropzoneElement;
+ const file = target.value?.[0];
+
+ if (file?.status !== UmbFileDropzoneItemStatus.COMPLETE) return;
+
+ this.temporaryFile = (file as UmbUploadableFile).temporaryFile;
+
+ this.#clearObjectUrl();
+
+ const blobUrl = URL.createObjectURL(this.temporaryFile.file);
+ this.value = { src: blobUrl };
+
+ this.dispatchEvent(new UmbChangeEvent());
}
override render() {
@@ -199,69 +164,28 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
return this.#renderDropzone();
}
- return html`
- ${this.temporaryFile ? this.#renderUploader() : nothing}
- ${this.value.src && this._previewAlias ? this.#renderFile(this.value.src) : nothing}
- `;
+ if (this.value?.src && this._previewAlias) {
+ return this.#renderFile(this.value.src);
+ }
+
+ return nothing;
}
#renderDropzone() {
return html`
-
-
-
- `;
- }
-
- #renderUploader() {
- if (!this.temporaryFile) return nothing;
-
- return html`
-
-
- ${when(
- this.temporaryFile.status === TemporaryFileStatus.SUCCESS,
- () => html` `,
- )}
- ${when(
- this.temporaryFile.status === TemporaryFileStatus.ERROR,
- () => html` `,
- )}
-
-
-
${this.temporaryFile.file.name}
-
${formatBytes(this.temporaryFile.file.size, { decimals: 2 })}: ${this._progress}%
- ${when(
- this.temporaryFile.status === TemporaryFileStatus.WAITING,
- () => html`
`,
- )}
- ${when(
- this.temporaryFile.status === TemporaryFileStatus.ERROR,
- () => html`
An error occured
`,
- )}
-
-
- ${when(
- this.temporaryFile.status === TemporaryFileStatus.WAITING,
- () => html`
-
- ${this.localize.term('general_cancel')}
-
- `,
- () => this.#renderButtonRemove(),
- )}
-
-
+ disable-folder-upload
+ accept=${ifDefined(this._extensions?.join(','))}
+ @change=${this.#onUpload}>
`;
}
#renderFile(src: string) {
+ if (!src.startsWith('blob:')) {
+ src = this._serverUrl + src;
+ }
+
return html`
@@ -288,13 +212,25 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
// If the upload promise happens to be in progress, cancel it.
this.temporaryFile?.abortController?.abort();
+ this.#clearObjectUrl();
+
this.value = { src: undefined };
this.temporaryFile = undefined;
- this._progress = 0;
this.dispatchEvent(new UmbChangeEvent());
}
+ /**
+ * If there is a former File, revoke the object URL.
+ */
+ #clearObjectUrl(): void {
+ if (this.value?.src?.startsWith('blob:')) {
+ URL.revokeObjectURL(this.value.src);
+ }
+ }
+
static override readonly styles = [
+ UmbTextStyles,
+ UmbInputDropzoneDashedStyles,
css`
:host {
position: relative;
@@ -323,51 +259,6 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
width: fit-content;
max-width: 100%;
}
-
- #temporaryFile {
- display: grid;
- grid-template-columns: auto auto auto;
- width: fit-content;
- max-width: 100%;
- margin: var(--uui-size-layout-1) 0;
- padding: var(--uui-size-space-3);
- border: 1px dashed var(--uui-color-divider-emphasis);
- }
-
- #fileIcon,
- #fileActions {
- place-self: center center;
- padding: 0 var(--uui-size-layout-1);
- }
-
- #fileName {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- font-size: var(--uui-size-5);
- }
-
- #fileSize {
- font-size: var(--uui-font-size-small);
- color: var(--uui-color-text-alt);
- }
-
- #error {
- color: var(--uui-color-danger);
- }
-
- uui-file-dropzone {
- position: relative;
- display: block;
- padding: 3px; /** Dropzone background is blurry and covers slightly into other elements. Hack to avoid this */
- }
- uui-file-dropzone::after {
- content: '';
- position: absolute;
- inset: 0;
- cursor: pointer;
- border: 1px dashed var(--uui-color-divider-emphasis);
- }
`,
];
}
From 9761ef899b6744525056e286ce1dc92c4ca23c0b Mon Sep 17 00:00:00 2001
From: Andy Butland
Date: Thu, 27 Mar 2025 11:33:01 +0100
Subject: [PATCH 09/38] Populate parent key on move and copy notifications
(#18837)
* Populate parent key on move and copy notifications.
* Remove forgotten fixme
---------
Co-authored-by: nikolajlauridsen
---
src/Umbraco.Core/Services/ContentService.cs | 29 ++++++++++++++-------
1 file changed, 19 insertions(+), 10 deletions(-)
diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs
index c76e89381a..9b42428938 100644
--- a/src/Umbraco.Core/Services/ContentService.cs
+++ b/src/Umbraco.Core/Services/ContentService.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -2621,8 +2622,8 @@ public class ContentService : RepositoryService, IContentService
throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback
}
- // FIXME: Use MoveEventInfo that also takes a parent key when implementing move with parentKey.
- var moveEventInfo = new MoveEventInfo(content, content.Path, parentId);
+ TryGetParentKey(parentId, out Guid? parentKey);
+ var moveEventInfo = new MoveEventInfo(content, content.Path, parentId, parentKey);
var movingNotification = new ContentMovingNotification(moveEventInfo, eventMessages);
if (scope.Notifications.PublishCancelable(movingNotification))
@@ -2652,9 +2653,12 @@ public class ContentService : RepositoryService, IContentService
new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages));
// changes
- // FIXME: Use MoveEventInfo that also takes a parent key when implementing move with parentKey.
MoveEventInfo[] moveInfo = moves
- .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId))
+ .Select(x =>
+ {
+ TryGetParentKey(x.Item1.ParentId, out Guid? itemParentKey);
+ return new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId, itemParentKey);
+ })
.ToArray();
scope.Notifications.Publish(
@@ -2834,8 +2838,8 @@ public class ContentService : RepositoryService, IContentService
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
{
- // FIXME: Pass parent key in constructor too when proper Copy method is implemented
- if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(content, copy, parentId, eventMessages)))
+ TryGetParentKey(parentId, out Guid? parentKey);
+ if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(content, copy, parentId, parentKey, eventMessages)))
{
scope.Complete();
return null;
@@ -2907,8 +2911,7 @@ public class ContentService : RepositoryService, IContentService
IContent descendantCopy = descendant.DeepCloneWithResetIdentities();
descendantCopy.ParentId = parentId;
- // FIXME: Pass parent key in constructor too when proper Copy method is implemented
- if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(descendant, descendantCopy, parentId, eventMessages)))
+ if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(descendant, descendantCopy, parentId, parentKey, eventMessages)))
{
continue;
}
@@ -2945,8 +2948,7 @@ public class ContentService : RepositoryService, IContentService
new ContentTreeChangeNotification(copy, TreeChangeTypes.RefreshBranch, eventMessages));
foreach (Tuple x in CollectionsMarshal.AsSpan(copies))
{
- // FIXME: Pass parent key in constructor too when proper Copy method is implemented
- scope.Notifications.Publish(new ContentCopiedNotification(x.Item1, x.Item2, parentId, relateToOriginal, eventMessages));
+ scope.Notifications.Publish(new ContentCopiedNotification(x.Item1, x.Item2, parentId, parentKey, relateToOriginal, eventMessages));
}
Audit(AuditType.Copy, userId, content.Id);
@@ -2957,6 +2959,13 @@ public class ContentService : RepositoryService, IContentService
return copy;
}
+ private bool TryGetParentKey(int parentId, [NotNullWhen(true)] out Guid? parentKey)
+ {
+ Attempt parentKeyAttempt = _idKeyMap.GetKeyForId(parentId, UmbracoObjectTypes.Document);
+ parentKey = parentKeyAttempt.Success ? parentKeyAttempt.Result : null;
+ return parentKeyAttempt.Success;
+ }
+
///
/// Sends an to Publication, which executes handlers and events for the 'Send to Publication'
/// action.
From 47e09efec645d9bab63ce999f84da80c2221f466 Mon Sep 17 00:00:00 2001
From: Lee Kelleher
Date: Thu, 27 Mar 2025 12:39:09 +0000
Subject: [PATCH 10/38] Fixes Dropdown property-editor validation (#18845)
---
.../property-editor-ui-dropdown.element.ts | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.element.ts
index 9be8531819..00060a0a99 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.element.ts
@@ -1,4 +1,5 @@
import { css, customElement, html, map, nothing, property, state, when } from '@umbraco-cms/backoffice/external/lit';
+import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
import { UUISelectElement } from '@umbraco-cms/backoffice/external/uui';
@@ -6,8 +7,7 @@ import type {
UmbPropertyEditorConfigCollection,
UmbPropertyEditorUiElement,
} from '@umbraco-cms/backoffice/property-editor';
-import type { UUISelectEvent } from '@umbraco-cms/backoffice/external/uui';
-import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
+import type { UmbInputDropdownListElement } from '@umbraco-cms/backoffice/components';
/**
* @element umb-property-editor-ui-dropdown
@@ -30,7 +30,7 @@ export class UmbPropertyEditorUIDropdownElement
@property({ type: Array })
public override set value(value: Array | string | undefined) {
- this.#selection = Array.isArray(value) ? value : value ? [value] : [];
+ this.#selection = this.#ensureValueIsArray(value);
}
public override get value(): Array | undefined {
return this.#selection;
@@ -97,7 +97,11 @@ export class UmbPropertyEditorUIDropdownElement
}
}
- #onChange(event: UUISelectEvent) {
+ #ensureValueIsArray(value: Array | string | null | undefined): Array {
+ return Array.isArray(value) ? value : value ? [value] : [];
+ }
+
+ #onChange(event: CustomEvent & { target: UmbInputDropdownListElement }) {
const value = event.target.value as string;
this.#setValue(value ? [value] : []);
}
@@ -110,6 +114,8 @@ export class UmbPropertyEditorUIDropdownElement
#setValue(value: Array | string | null | undefined) {
if (!value) return;
+ const selection = this.#ensureValueIsArray(value);
+ this._options.forEach((item) => (item.selected = selection.includes(item.value)));
this.value = value;
this.dispatchEvent(new UmbChangeEvent());
}
From 52bf6bc41237b1a19e0316afe1f80f1595e7aa4e Mon Sep 17 00:00:00 2001
From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
Date: Thu, 27 Mar 2025 15:20:36 +0100
Subject: [PATCH 11/38] feat: disables the internal dropzone if multiple=false
and an upload is in progress (#18847)
you need to clear the files/queue before trying to upload something else, unless multiple=true
---
.../input-dropzone/input-dropzone.element.ts | 22 +++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/components/input-dropzone/input-dropzone.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/components/input-dropzone/input-dropzone.element.ts
index 8515f2f673..5754ba38a8 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/components/input-dropzone/input-dropzone.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/components/input-dropzone/input-dropzone.element.ts
@@ -74,6 +74,16 @@ export class UmbInputDropzoneElement extends UmbFormControlMixin 0);
+ }
+
constructor() {
super();
@@ -107,7 +117,7 @@ export class UmbInputDropzoneElement extends UmbFormControlMixin
@@ -132,7 +142,6 @@ export class UmbInputDropzoneElement extends UmbFormControlMixin
Date: Fri, 28 Mar 2025 08:49:38 +0100
Subject: [PATCH 12/38] Render Property "vary by segment"-toggle (#18813)
* render toggle for vary by segment
* use property-layout for appearance
* simplify labels
* remove duplicates
* render tag for vary by segment
---
.../src/assets/lang/en-us.ts | 10 ----
.../src/assets/lang/en.ts | 4 +-
...ent-type-design-editor-property.element.ts | 5 ++
...roperty-workspace-view-settings.element.ts | 55 ++++++++++++-------
4 files changed, 43 insertions(+), 31 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts
index bf7d91a548..df5bd8c8dc 100644
--- a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts
+++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts
@@ -1705,16 +1705,6 @@ export default {
compositionUsageHeading: 'Where is this composition used?',
compositionUsageSpecification:
'This composition is currently used in the composition of the following\n Content Types:\n ',
- variantsHeading: 'Allow variations',
- cultureVariantHeading: 'Allow vary by culture',
- segmentVariantHeading: 'Allow segmentation',
- cultureVariantLabel: 'Vary by culture',
- segmentVariantLabel: 'Vary by segments',
- variantsDescription: 'Allow editors to create content of this type in different languages.',
- cultureVariantDescription: 'Allow editors to create content of different languages.',
- segmentVariantDescription: 'Allow editors to create segments of this content.',
- allowVaryByCulture: 'Allow varying by culture',
- allowVaryBySegment: 'Allow segmentation',
elementType: 'Element Type',
elementHeading: 'Is an Element Type',
elementDescription:
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 4f29dcb7f1..39f7edc0ac 100644
--- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts
+++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts
@@ -1735,11 +1735,11 @@ export default {
compositionUsageHeading: 'Where is this composition used?',
compositionUsageSpecification:
'This composition is currently used in the composition of the following\n Content Types:\n ',
- variantsHeading: 'Allow variations',
+ variantsHeading: 'Variation',
cultureVariantHeading: 'Allow vary by culture',
segmentVariantHeading: 'Allow segmentation',
cultureVariantLabel: 'Vary by culture',
- segmentVariantLabel: 'Vary by segments',
+ segmentVariantLabel: 'Vary by segment',
variantsDescription: 'Allow editors to create content of this type in different languages.',
cultureVariantDescription: 'Allow editors to create content of different languages.',
segmentVariantDescription: 'Allow editors to create segments of this content.',
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts
index 63ef5a5567..5b8cf05294 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts
@@ -299,6 +299,11 @@ export class UmbContentTypeDesignEditorPropertyElement extends UmbLitElement {
${this.localize.term('contentTypeEditor_cultureVariantLabel')}
`
: nothing}
+ ${this.property.variesBySegment
+ ? html`
+ ${this.localize.term('contentTypeEditor_segmentVariantLabel')}
+ `
+ : nothing}
${this.property.appearance?.labelOnTop == true
? html`
${this.localize.term('contentTypeEditor_displaySettingsLabelOnTop')}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/views/settings/property-workspace-view-settings.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/views/settings/property-workspace-view-settings.element.ts
index 56db8cbf59..8c069f12d1 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/views/settings/property-workspace-view-settings.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/views/settings/property-workspace-view-settings.element.ts
@@ -203,9 +203,12 @@ export class UmbPropertyTypeWorkspaceViewSettingsElement extends UmbLitElement i
#onVaryByCultureChange(event: UUIBooleanInputEvent) {
const variesByCulture = event.target.checked;
- this.updateValue({
- variesByCulture,
- });
+ this.updateValue({ variesByCulture });
+ }
+
+ #onVaryBySegmentChange(event: UUIBooleanInputEvent) {
+ const variesBySegment = event.target.checked;
+ this.updateValue({ variesBySegment });
}
override render() {
@@ -267,14 +270,11 @@ export class UmbPropertyTypeWorkspaceViewSettingsElement extends UmbLitElement i
${this.#renderCustomValidation()}
-
${this.#renderVariationControls()}
-
-
- Appearance
-
-
${this.#renderAlignLeftIcon()} ${this.#renderAlignTopIcon()}
-
+
+ ${this.#renderAlignLeftIcon()} ${this.#renderAlignTopIcon()}
+
+
${this.#renderMemberTypeOptions()}
`;
@@ -405,18 +405,35 @@ export class UmbPropertyTypeWorkspaceViewSettingsElement extends UmbLitElement i
#renderVariationControls() {
return this._contentTypeVariesByCulture || this._contentTypeVariesBySegment
- ? html`
- Allow variations
- ${this._contentTypeVariesByCulture ? this.#renderVaryByCulture() : ''}
-
-
`
+ ? html`
+
+ ${this._contentTypeVariesByCulture ? this.#renderVaryByCulture() : nothing}
+ ${this._contentTypeVariesBySegment ? this.#renderVaryBySegment() : nothing}
+
+ `
: '';
}
+
#renderVaryByCulture() {
- return html`
`;
+ return html`
+
+
+
+ `;
+ }
+
+ #renderVaryBySegment() {
+ return html`
+
+
+
+ `;
}
static override styles = [
From 685a05827e797c22d2daff00fb5a6b8a944b1b8b Mon Sep 17 00:00:00 2001
From: Andy Butland
Date: Fri, 28 Mar 2025 11:16:03 +0100
Subject: [PATCH 13/38] Adds MemberTwoFactorLoginService. (#18810)
---
.../DependencyInjection/UmbracoBuilder.cs | 1 +
.../Services/IMemberTwoFactorLoginService.cs | 37 ++++++++++
.../Services/ITwoFactorLoginService.cs | 6 +-
.../Services/MemberTwoFactorLoginService.cs | 72 +++++++++++++++++++
.../Services/UserTwoFactorLoginService.cs | 6 +-
5 files changed, 116 insertions(+), 6 deletions(-)
create mode 100644 src/Umbraco.Core/Services/IMemberTwoFactorLoginService.cs
create mode 100644 src/Umbraco.Core/Services/MemberTwoFactorLoginService.cs
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
index 3d36c67d3a..24cc907339 100644
--- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
@@ -422,6 +422,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
// Two factor providers
Services.AddUnique();
Services.AddUnique();
+ Services.AddUnique();
// Add Query services
Services.AddUnique();
diff --git a/src/Umbraco.Core/Services/IMemberTwoFactorLoginService.cs b/src/Umbraco.Core/Services/IMemberTwoFactorLoginService.cs
new file mode 100644
index 0000000000..3a7ec99c8e
--- /dev/null
+++ b/src/Umbraco.Core/Services/IMemberTwoFactorLoginService.cs
@@ -0,0 +1,37 @@
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Security;
+using Umbraco.Cms.Core.Services.OperationStatus;
+
+namespace Umbraco.Cms.Core.Services;
+
+///
+/// A member specific Two factor service, that ensures the member exists before doing the job.
+///
+public interface IMemberTwoFactorLoginService
+{
+ ///
+ /// Disables a specific two factor provider on a specific member.
+ ///
+ Task> DisableAsync(Guid memberKey, string providerName);
+
+ ///
+ /// Gets the two factor providers on a specific member.
+ ///
+ Task, TwoFactorOperationStatus>> GetProviderNamesAsync(Guid memberKey);
+
+ ///
+ /// The returned type can be anything depending on the setup providers. You will need to cast it to the type handled by
+ /// the provider.
+ ///
+ Task> GetSetupInfoAsync(Guid memberKey, string providerName);
+
+ ///
+ /// Validates and Saves.
+ ///
+ Task> ValidateAndSaveAsync(string providerName, Guid memberKey, string modelSecret, string modelCode);
+
+ ///
+ /// Disables 2FA with Code.
+ ///
+ Task> DisableByCodeAsync(string providerName, Guid memberKey, string code);
+}
diff --git a/src/Umbraco.Core/Services/ITwoFactorLoginService.cs b/src/Umbraco.Core/Services/ITwoFactorLoginService.cs
index a5ad0d84e8..01558376e3 100644
--- a/src/Umbraco.Core/Services/ITwoFactorLoginService.cs
+++ b/src/Umbraco.Core/Services/ITwoFactorLoginService.cs
@@ -29,7 +29,7 @@ public interface ITwoFactorLoginService : IService
/// The returned type can be anything depending on the setup providers. You will need to cast it to the type handled by
/// the provider.
///
- [Obsolete("Use IUserTwoFactorLoginService.GetSetupInfoAsync. This will be removed in Umbraco 15.")]
+ [Obsolete("Use IUserTwoFactorLoginService.GetSetupInfoAsync or IMemberTwoFactorLoginService.GetSetupInfoAsync. Scheduled for removal in Umbraco 16.")]
Task GetSetupInfoAsync(Guid userOrMemberKey, string providerName);
///
@@ -60,13 +60,13 @@ public interface ITwoFactorLoginService : IService
///
/// Disables 2FA with Code.
///
- [Obsolete("Use IUserTwoFactorLoginService.DisableByCodeAsync. This will be removed in Umbraco 15.")]
+ [Obsolete("Use IUserTwoFactorLoginService.DisableByCodeAsync or IMemberTwoFactorLoginService.DisableByCodeAsync. Scheduled for removal in Umbraco 16.")]
Task DisableWithCodeAsync(string providerName, Guid userOrMemberKey, string code);
///
/// Validates and Saves.
///
- [Obsolete("Use IUserTwoFactorLoginService.ValidateAndSaveAsync. This will be removed in Umbraco 15.")]
+ [Obsolete("Use IUserTwoFactorLoginService.ValidateAndSaveAsync or IMemberTwoFactorLoginService.ValidateAndSaveAsync. Scheduled for removal in Umbraco 16.")]
Task ValidateAndSaveAsync(string providerName, Guid userKey, string secret, string code);
}
diff --git a/src/Umbraco.Core/Services/MemberTwoFactorLoginService.cs b/src/Umbraco.Core/Services/MemberTwoFactorLoginService.cs
new file mode 100644
index 0000000000..b21558b834
--- /dev/null
+++ b/src/Umbraco.Core/Services/MemberTwoFactorLoginService.cs
@@ -0,0 +1,72 @@
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Scoping;
+using Umbraco.Cms.Core.Security;
+using Umbraco.Cms.Core.Services.OperationStatus;
+
+namespace Umbraco.Cms.Core.Services;
+
+///
+internal class MemberTwoFactorLoginService : TwoFactorLoginServiceBase, IMemberTwoFactorLoginService
+{
+ private readonly IMemberService _memberService;
+
+ public MemberTwoFactorLoginService(
+ ITwoFactorLoginService twoFactorLoginService,
+ IEnumerable twoFactorSetupGenerators,
+ IMemberService memberService,
+ ICoreScopeProvider scopeProvider)
+ : base(twoFactorLoginService, twoFactorSetupGenerators, scopeProvider) =>
+ _memberService = memberService;
+
+ ///
+ public override async Task> DisableAsync(Guid memberKey, string providerName)
+ {
+ IMember? member = _memberService.GetByKey(memberKey);
+
+ if (member is null)
+ {
+ return Attempt.Fail(TwoFactorOperationStatus.UserNotFound);
+ }
+
+ return await base.DisableAsync(memberKey, providerName);
+ }
+
+ ///
+ public override async Task, TwoFactorOperationStatus>> GetProviderNamesAsync(Guid memberKey)
+ {
+ IMember? member = _memberService.GetByKey(memberKey);
+
+ if (member is null)
+ {
+ return Attempt.FailWithStatus(TwoFactorOperationStatus.UserNotFound, Enumerable.Empty());
+ }
+
+ return await base.GetProviderNamesAsync(memberKey);
+ }
+
+ ///
+ public override async Task> GetSetupInfoAsync(Guid memberKey, string providerName)
+ {
+ IMember? member = _memberService.GetByKey(memberKey);
+
+ if (member is null)
+ {
+ return Attempt.FailWithStatus(TwoFactorOperationStatus.UserNotFound, new NoopSetupTwoFactorModel());
+ }
+
+ return await base.GetSetupInfoAsync(memberKey, providerName);
+ }
+
+ ///
+ public override async Task> ValidateAndSaveAsync(string providerName, Guid memberKey, string secret, string code)
+ {
+ IMember? member = _memberService.GetByKey(memberKey);
+
+ if (member is null)
+ {
+ return Attempt.Fail(TwoFactorOperationStatus.UserNotFound);
+ }
+
+ return await base.ValidateAndSaveAsync(providerName, memberKey, secret, code);
+ }
+}
diff --git a/src/Umbraco.Core/Services/UserTwoFactorLoginService.cs b/src/Umbraco.Core/Services/UserTwoFactorLoginService.cs
index 89241d31f2..10a9de140f 100644
--- a/src/Umbraco.Core/Services/UserTwoFactorLoginService.cs
+++ b/src/Umbraco.Core/Services/UserTwoFactorLoginService.cs
@@ -32,7 +32,7 @@ internal class UserTwoFactorLoginService : TwoFactorLoginServiceBase, IUserTwoFa
return await base.DisableAsync(userKey, providerName);
}
- ///
+ ///
public override async Task, TwoFactorOperationStatus>> GetProviderNamesAsync(Guid userKey)
{
IUser? user = await _userService.GetAsync(userKey);
@@ -45,7 +45,7 @@ internal class UserTwoFactorLoginService : TwoFactorLoginServiceBase, IUserTwoFa
return await base.GetProviderNamesAsync(userKey);
}
- ///
+ ///
public override async Task> GetSetupInfoAsync(Guid userKey, string providerName)
{
IUser? user = await _userService.GetAsync(userKey);
@@ -58,7 +58,7 @@ internal class UserTwoFactorLoginService : TwoFactorLoginServiceBase, IUserTwoFa
return await base.GetSetupInfoAsync(userKey, providerName);
}
- ///
+ ///
public override async Task> ValidateAndSaveAsync(string providerName, Guid userKey, string secret, string code)
{
IUser? user = await _userService.GetAsync(userKey);
From aca908c549173d388e7fb49848768d7f016b7f47 Mon Sep 17 00:00:00 2001
From: Andy Butland
Date: Fri, 28 Mar 2025 11:16:03 +0100
Subject: [PATCH 14/38] Adds MemberTwoFactorLoginService. (#18810)
---
.../DependencyInjection/UmbracoBuilder.cs | 1 +
.../Services/IMemberTwoFactorLoginService.cs | 37 ++++++++++
.../Services/ITwoFactorLoginService.cs | 6 +-
.../Services/MemberTwoFactorLoginService.cs | 72 +++++++++++++++++++
.../Services/UserTwoFactorLoginService.cs | 6 +-
5 files changed, 116 insertions(+), 6 deletions(-)
create mode 100644 src/Umbraco.Core/Services/IMemberTwoFactorLoginService.cs
create mode 100644 src/Umbraco.Core/Services/MemberTwoFactorLoginService.cs
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
index 3d36c67d3a..24cc907339 100644
--- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
@@ -422,6 +422,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
// Two factor providers
Services.AddUnique();
Services.AddUnique();
+ Services.AddUnique();
// Add Query services
Services.AddUnique();
diff --git a/src/Umbraco.Core/Services/IMemberTwoFactorLoginService.cs b/src/Umbraco.Core/Services/IMemberTwoFactorLoginService.cs
new file mode 100644
index 0000000000..3a7ec99c8e
--- /dev/null
+++ b/src/Umbraco.Core/Services/IMemberTwoFactorLoginService.cs
@@ -0,0 +1,37 @@
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Security;
+using Umbraco.Cms.Core.Services.OperationStatus;
+
+namespace Umbraco.Cms.Core.Services;
+
+///
+/// A member specific Two factor service, that ensures the member exists before doing the job.
+///
+public interface IMemberTwoFactorLoginService
+{
+ ///
+ /// Disables a specific two factor provider on a specific member.
+ ///
+ Task> DisableAsync(Guid memberKey, string providerName);
+
+ ///
+ /// Gets the two factor providers on a specific member.
+ ///
+ Task, TwoFactorOperationStatus>> GetProviderNamesAsync(Guid memberKey);
+
+ ///
+ /// The returned type can be anything depending on the setup providers. You will need to cast it to the type handled by
+ /// the provider.
+ ///
+ Task> GetSetupInfoAsync(Guid memberKey, string providerName);
+
+ ///
+ /// Validates and Saves.
+ ///
+ Task> ValidateAndSaveAsync(string providerName, Guid memberKey, string modelSecret, string modelCode);
+
+ ///
+ /// Disables 2FA with Code.
+ ///
+ Task> DisableByCodeAsync(string providerName, Guid memberKey, string code);
+}
diff --git a/src/Umbraco.Core/Services/ITwoFactorLoginService.cs b/src/Umbraco.Core/Services/ITwoFactorLoginService.cs
index a5ad0d84e8..01558376e3 100644
--- a/src/Umbraco.Core/Services/ITwoFactorLoginService.cs
+++ b/src/Umbraco.Core/Services/ITwoFactorLoginService.cs
@@ -29,7 +29,7 @@ public interface ITwoFactorLoginService : IService
/// The returned type can be anything depending on the setup providers. You will need to cast it to the type handled by
/// the provider.
///
- [Obsolete("Use IUserTwoFactorLoginService.GetSetupInfoAsync. This will be removed in Umbraco 15.")]
+ [Obsolete("Use IUserTwoFactorLoginService.GetSetupInfoAsync or IMemberTwoFactorLoginService.GetSetupInfoAsync. Scheduled for removal in Umbraco 16.")]
Task GetSetupInfoAsync(Guid userOrMemberKey, string providerName);
///
@@ -60,13 +60,13 @@ public interface ITwoFactorLoginService : IService
///
/// Disables 2FA with Code.
///
- [Obsolete("Use IUserTwoFactorLoginService.DisableByCodeAsync. This will be removed in Umbraco 15.")]
+ [Obsolete("Use IUserTwoFactorLoginService.DisableByCodeAsync or IMemberTwoFactorLoginService.DisableByCodeAsync. Scheduled for removal in Umbraco 16.")]
Task DisableWithCodeAsync(string providerName, Guid userOrMemberKey, string code);
///
/// Validates and Saves.
///
- [Obsolete("Use IUserTwoFactorLoginService.ValidateAndSaveAsync. This will be removed in Umbraco 15.")]
+ [Obsolete("Use IUserTwoFactorLoginService.ValidateAndSaveAsync or IMemberTwoFactorLoginService.ValidateAndSaveAsync. Scheduled for removal in Umbraco 16.")]
Task ValidateAndSaveAsync(string providerName, Guid userKey, string secret, string code);
}
diff --git a/src/Umbraco.Core/Services/MemberTwoFactorLoginService.cs b/src/Umbraco.Core/Services/MemberTwoFactorLoginService.cs
new file mode 100644
index 0000000000..b21558b834
--- /dev/null
+++ b/src/Umbraco.Core/Services/MemberTwoFactorLoginService.cs
@@ -0,0 +1,72 @@
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Scoping;
+using Umbraco.Cms.Core.Security;
+using Umbraco.Cms.Core.Services.OperationStatus;
+
+namespace Umbraco.Cms.Core.Services;
+
+///
+internal class MemberTwoFactorLoginService : TwoFactorLoginServiceBase, IMemberTwoFactorLoginService
+{
+ private readonly IMemberService _memberService;
+
+ public MemberTwoFactorLoginService(
+ ITwoFactorLoginService twoFactorLoginService,
+ IEnumerable twoFactorSetupGenerators,
+ IMemberService memberService,
+ ICoreScopeProvider scopeProvider)
+ : base(twoFactorLoginService, twoFactorSetupGenerators, scopeProvider) =>
+ _memberService = memberService;
+
+ ///
+ public override async Task> DisableAsync(Guid memberKey, string providerName)
+ {
+ IMember? member = _memberService.GetByKey(memberKey);
+
+ if (member is null)
+ {
+ return Attempt.Fail(TwoFactorOperationStatus.UserNotFound);
+ }
+
+ return await base.DisableAsync(memberKey, providerName);
+ }
+
+ ///
+ public override async Task, TwoFactorOperationStatus>> GetProviderNamesAsync(Guid memberKey)
+ {
+ IMember? member = _memberService.GetByKey(memberKey);
+
+ if (member is null)
+ {
+ return Attempt.FailWithStatus(TwoFactorOperationStatus.UserNotFound, Enumerable.Empty());
+ }
+
+ return await base.GetProviderNamesAsync(memberKey);
+ }
+
+ ///
+ public override async Task> GetSetupInfoAsync(Guid memberKey, string providerName)
+ {
+ IMember? member = _memberService.GetByKey(memberKey);
+
+ if (member is null)
+ {
+ return Attempt.FailWithStatus(TwoFactorOperationStatus.UserNotFound, new NoopSetupTwoFactorModel());
+ }
+
+ return await base.GetSetupInfoAsync(memberKey, providerName);
+ }
+
+ ///
+ public override async Task> ValidateAndSaveAsync(string providerName, Guid memberKey, string secret, string code)
+ {
+ IMember? member = _memberService.GetByKey(memberKey);
+
+ if (member is null)
+ {
+ return Attempt.Fail(TwoFactorOperationStatus.UserNotFound);
+ }
+
+ return await base.ValidateAndSaveAsync(providerName, memberKey, secret, code);
+ }
+}
diff --git a/src/Umbraco.Core/Services/UserTwoFactorLoginService.cs b/src/Umbraco.Core/Services/UserTwoFactorLoginService.cs
index 89241d31f2..10a9de140f 100644
--- a/src/Umbraco.Core/Services/UserTwoFactorLoginService.cs
+++ b/src/Umbraco.Core/Services/UserTwoFactorLoginService.cs
@@ -32,7 +32,7 @@ internal class UserTwoFactorLoginService : TwoFactorLoginServiceBase, IUserTwoFa
return await base.DisableAsync(userKey, providerName);
}
- ///
+ ///
public override async Task, TwoFactorOperationStatus>> GetProviderNamesAsync(Guid userKey)
{
IUser? user = await _userService.GetAsync(userKey);
@@ -45,7 +45,7 @@ internal class UserTwoFactorLoginService : TwoFactorLoginServiceBase, IUserTwoFa
return await base.GetProviderNamesAsync(userKey);
}
- ///
+ ///
public override async Task> GetSetupInfoAsync(Guid userKey, string providerName)
{
IUser? user = await _userService.GetAsync(userKey);
@@ -58,7 +58,7 @@ internal class UserTwoFactorLoginService : TwoFactorLoginServiceBase, IUserTwoFa
return await base.GetSetupInfoAsync(userKey, providerName);
}
- ///
+ ///
public override async Task> ValidateAndSaveAsync(string providerName, Guid userKey, string secret, string code)
{
IUser? user = await _userService.GetAsync(userKey);
From 6dc4eca607f934dfd15c77cb5a98199b9a508397 Mon Sep 17 00:00:00 2001
From: Andy Butland
Date: Fri, 28 Mar 2025 11:24:52 +0100
Subject: [PATCH 15/38] Fixes null reference exception triggered when
configuring to hide disabled users in the backoffice (#18823)
* Fixes null reference exception triggered when configuring to hide disabled users in the backoffice.
* Updated integration test suppressions.
---
src/Umbraco.Core/Services/UserService.cs | 11 ++++----
.../CompatibilitySuppressions.xml | 7 +++++
.../Services/UserServiceCrudTests.Filter.cs | 26 ++++++++++++-------
3 files changed, 29 insertions(+), 15 deletions(-)
diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs
index 659a98ea07..c392011eca 100644
--- a/src/Umbraco.Core/Services/UserService.cs
+++ b/src/Umbraco.Core/Services/UserService.cs
@@ -1338,7 +1338,6 @@ internal partial class UserService : RepositoryService, IUserService
includedUserGroupAliases = userGroupKeyConversionAttempt.Result.ToArray();
}
-
if (mergedFilter.NameFilters is not null)
{
foreach (var nameFilter in mergedFilter.NameFilters)
@@ -1357,17 +1356,19 @@ internal partial class UserService : RepositoryService, IUserService
}
else
{
- includeUserStates = new HashSet(filter.IncludeUserStates!);
- includeUserStates.IntersectWith(baseFilter.IncludeUserStates);
+ includeUserStates = new HashSet(baseFilter.IncludeUserStates);
+ if (filter.IncludeUserStates is not null && filter.IncludeUserStates.Contains(UserState.All) is false)
+ {
+ includeUserStates.IntersectWith(filter.IncludeUserStates);
+ }
// This means that we've only chosen to include a user state that is not allowed, so we'll return an empty result
- if(includeUserStates.Count == 0)
+ if (includeUserStates.Count == 0)
{
return Attempt.SucceedWithStatus(UserOperationStatus.Success, new PagedModel());
}
}
-
PaginationHelper.ConvertSkipTakeToPaging(skip, take, out long pageNumber, out int pageSize);
Expression> orderByExpression = GetOrderByExpression(orderBy);
diff --git a/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml b/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml
index 918b3ea4fe..7cf519f60f 100644
--- a/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml
+++ b/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml
@@ -78,6 +78,13 @@
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)
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.Filter.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.Filter.cs
index 71b557d4d2..58d79bb2a2 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.Filter.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.Filter.cs
@@ -1,4 +1,4 @@
-using NUnit.Framework;
+using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
@@ -10,12 +10,14 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class UserServiceCrudTests
{
- [Test]
- [TestCase(UserState.Disabled)]
- [TestCase(UserState.All)]
- public async Task Cannot_Request_Disabled_If_Hidden(UserState includeState)
+ [TestCase(null, 1)] // Requesting no filter, will just get the admin user but not the created and disabled one.
+ // - verifies fix for https://github.com/umbraco/Umbraco-CMS/issues/18812
+ [TestCase(UserState.Inactive, 1)] // Requesting inactive, will just get the admin user but not the created and disabled one.
+ [TestCase(UserState.Disabled, 0)] // Requesting disabled, won't get any as admin user isn't disabled and, whilst the created one is, disabled users are hidden.
+ [TestCase(UserState.All, 1)] // Requesting all, will just get the admin user but not the created and disabled one.
+ public async Task Cannot_Request_Disabled_If_Hidden(UserState? includeState, int expectedCount)
{
- var userService = CreateUserService(new SecuritySettings {HideDisabledUsersInBackOffice = true});
+ var userService = CreateUserService(new SecuritySettings { HideDisabledUsersInBackOffice = true });
var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey);
var createModel = new UserCreateModel
@@ -23,21 +25,25 @@ public partial class UserServiceCrudTests
UserName = "editor@mail.com",
Email = "editor@mail.com",
Name = "Editor",
- UserGroupKeys = new HashSet { editorGroup.Key }
+ UserGroupKeys = new HashSet { editorGroup.Key },
};
var createAttempt = await userService.CreateAsync(Constants.Security.SuperUserKey, createModel, true);
Assert.IsTrue(createAttempt.Success);
var disableStatus =
- await userService.DisableAsync(Constants.Security.SuperUserKey, new HashSet{ createAttempt.Result.CreatedUser!.Key });
+ await userService.DisableAsync(Constants.Security.SuperUserKey, new HashSet { createAttempt.Result.CreatedUser!.Key });
Assert.AreEqual(UserOperationStatus.Success, disableStatus);
- var filter = new UserFilter {IncludeUserStates = new HashSet {includeState}};
+ var filter = new UserFilter();
+ if (includeState.HasValue)
+ {
+ filter.IncludeUserStates = new HashSet { includeState.Value };
+ }
var filterAttempt = await userService.FilterAsync(Constants.Security.SuperUserKey, filter, 0, 1000);
Assert.IsTrue(filterAttempt.Success);
- Assert.AreEqual(0, filterAttempt.Result.Items.Count());
+ Assert.AreEqual(expectedCount, filterAttempt.Result.Items.Count());
}
[Test]
From 6f63837dbccef643407c7dd4ec6f6c4536858187 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Niels=20Lyngs=C3=B8?=
Date: Fri, 28 Mar 2025 11:49:07 +0100
Subject: [PATCH 16/38] make load more 100% wide (#18821)
---
.../packages/core/tree/default/default-tree.element.ts | 10 ++++++++--
.../documents/documents/tree/document-tree.element.ts | 5 ++---
2 files changed, 10 insertions(+), 5 deletions(-)
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 3ad8bf25c8..cd1cd41961 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
@@ -9,7 +9,7 @@ import type { UmbTreeExpansionModel } from '../expansion-manager/types.js';
import type { UmbDefaultTreeContext } from './default-tree.context.js';
import { UMB_TREE_CONTEXT } from './default-tree.context-token.js';
import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
-import { html, nothing, customElement, property, state, repeat } from '@umbraco-cms/backoffice/external/lit';
+import { html, nothing, customElement, property, state, repeat, css } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@customElement('umb-default-tree')
@@ -171,8 +171,14 @@ export class UmbDefaultTreeElement extends UmbLitElement {
return nothing;
}
- return html` `;
+ return html` `;
}
+
+ static override styles = css`
+ #load-more {
+ width: 100%;
+ }
+ `;
}
export default UmbDefaultTreeElement;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/document-tree.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/document-tree.element.ts
index fd02f52a8d..a57478d279 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/document-tree.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/document-tree.element.ts
@@ -1,14 +1,13 @@
import { customElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbDefaultTreeElement } from '@umbraco-cms/backoffice/tree';
-const elementName = 'umb-document-tree';
-@customElement(elementName)
+@customElement('umb-document-tree')
export class UmbDocumentTreeElement extends UmbDefaultTreeElement {}
export { UmbDocumentTreeElement as element };
declare global {
interface HTMLElementTagNameMap {
- [elementName]: UmbDocumentTreeElement;
+ 'umb-document-tree': UmbDocumentTreeElement;
}
}
From 468ca9ae94fbd329440b9bb7288328603e79a8a0 Mon Sep 17 00:00:00 2001
From: Andy Butland
Date: Fri, 28 Mar 2025 16:34:25 +0100
Subject: [PATCH 17/38] Member relation tracking (#18852)
* Added member reference type model.
* Updated client-side types and sdk.
* Render member relations on the member info view.
* Add relation type for related member with migration.
* Extend tests for track relations to include member relations.
* Extend tests for relation repository.
* Extend tests for relation service.
* Addressed comments from Copilot review.
* Add relation notification to member deletion.
* Removed unused import.
* Updates from code review.
* export const
* Fixed failing integration tests.
* deprecate interface with wrong name
* fix import
---------
Co-authored-by: Mads Rasmussen
---
.../AreReferencedDocumentController.cs | 4 +-
.../ReferencedByDocumentController.cs | 2 +-
...ReferencedDescendantsDocumentController.cs | 4 +-
.../AreReferencedMediaController.cs | 4 +-
.../References/ReferencedByMediaController.cs | 2 +-
.../ReferencedDescendantsMediaController.cs | 4 +-
.../AreReferencedMemberController.cs | 51 ++++
.../ReferencedByMemberController.cs | 50 ++++
.../ReferencedDescendantsMemberController.cs | 50 ++++
.../RelationTypePresentationFactory.cs | 3 +-
...TrackedReferenceViewModelsMapDefinition.cs | 16 +-
src/Umbraco.Cms.Api.Management/OpenApi.json | 274 +++++++++++++++++-
.../DefaultReferenceResponseModel.cs | 8 +-
.../DocumentReferenceResponseModel.cs | 8 +-
.../MediaReferenceResponseModel.cs | 8 +-
.../MemberReferenceResponseModel.cs | 6 +
.../ReferenceResponseModel.cs | 8 +
.../TrackedReferenceMemberType.cs | 5 +
src/Umbraco.Core/Constants-Conventions.cs | 16 +-
.../Models/Editors/UmbracoEntityReference.cs | 3 +
.../Models/RelationTypeExtensions.cs | 1 +
.../DataValueReferenceFactoryCollection.cs | 2 +-
.../MemberPickerPropertyEditor.cs | 17 +-
.../Migrations/Install/DatabaseDataCreator.cs | 3 +
.../Migrations/Upgrade/UmbracoPlan.cs | 1 +
.../V_15_4_0/AddRelationTypeForMembers.cs | 46 +++
.../src/external/backend-api/src/sdk.gen.ts | 84 +++++-
.../src/external/backend-api/src/types.gen.ts | 43 ++-
.../src/mocks/data/tracked-reference.data.ts | 5 +-
.../workspace-info-app-layout.element.ts | 8 +-
.../document-reference-table.element.ts | 4 +
.../media/media/entity-actions/manifests.ts | 2 +-
...a-references-workspace-info-app.element.ts | 4 -
.../src/packages/members/member/constants.ts | 1 +
.../member/entity-actions/manifests.ts | 8 +-
.../members/member/item/repository/types.ts | 11 +-
.../src/packages/members/member/manifests.ts | 2 +
.../members/member/reference/constants.ts | 1 +
.../members/member/reference/index.ts | 1 +
.../member/reference/info-app/manifests.ts | 18 ++
...r-references-workspace-info-app.element.ts | 161 ++++++++++
.../members/member/reference/manifests.ts | 4 +
.../member/reference/repository/constants.ts | 1 +
.../member/reference/repository/index.ts | 1 +
.../member/reference/repository/manifests.ts | 19 ++
...ference-response.management-api.mapping.ts | 32 ++
.../repository/member-reference.repository.ts | 36 +++
.../member-reference.server.data.ts | 121 ++++++++
.../member/reference/repository/types.ts | 14 +
...mber-workspace-view-member-info.element.ts | 4 +-
.../member-workspace-view-member.element.ts | 7 +
.../relations/relations/reference/types.ts | 4 +-
.../src/packages/relations/relations/utils.ts | 9 +
.../Repositories/RelationRepositoryTest.cs | 49 ++--
.../RelationTypeRepositoryTest.cs | 6 +-
.../Services/RelationServiceTests.cs | 37 +++
.../Services/TrackRelationsTests.cs | 18 +-
57 files changed, 1238 insertions(+), 73 deletions(-)
create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Member/References/AreReferencedMemberController.cs
create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Member/References/ReferencedByMemberController.cs
create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Member/References/ReferencedDescendantsMemberController.cs
create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MemberReferenceResponseModel.cs
create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/ReferenceResponseModel.cs
create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/TrackedReferenceMemberType.cs
create mode 100644 src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_4_0/AddRelationTypeForMembers.cs
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/member/reference/constants.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/member/reference/index.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/member/reference/info-app/manifests.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/member/reference/info-app/member-references-workspace-info-app.element.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/member/reference/manifests.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/constants.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/index.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/manifests.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/member-reference-response.management-api.mapping.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/member-reference.repository.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/member-reference.server.data.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/types.ts
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/References/AreReferencedDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/References/AreReferencedDocumentController.cs
index ed593e9c9e..bc2353dc55 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Document/References/AreReferencedDocumentController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/References/AreReferencedDocumentController.cs
@@ -1,4 +1,4 @@
-using Asp.Versioning;
+using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
@@ -45,6 +45,6 @@ public class AreReferencedDocumentController : DocumentControllerBase
Items = _umbracoMapper.MapEnumerable(distinctByKeyItemsWithReferencedRelations.Items),
};
- return await Task.FromResult(pagedViewModel);
+ return pagedViewModel;
}
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedByDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedByDocumentController.cs
index 47df66fd6d..6a1d9b2824 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedByDocumentController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedByDocumentController.cs
@@ -45,6 +45,6 @@ public class ReferencedByDocumentController : DocumentControllerBase
Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items),
};
- return await Task.FromResult(pagedViewModel);
+ return pagedViewModel;
}
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedDescendantsDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedDescendantsDocumentController.cs
index 4b931632e1..138b919628 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedDescendantsDocumentController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/References/ReferencedDescendantsDocumentController.cs
@@ -1,4 +1,4 @@
-using Asp.Versioning;
+using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
@@ -45,6 +45,6 @@ public class ReferencedDescendantsDocumentController : DocumentControllerBase
Items = _umbracoMapper.MapEnumerable(relationItems.Items),
};
- return await Task.FromResult(pagedViewModel);
+ return pagedViewModel;
}
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/References/AreReferencedMediaController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/References/AreReferencedMediaController.cs
index 89e0c2d1ba..d10bdb9627 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Media/References/AreReferencedMediaController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/References/AreReferencedMediaController.cs
@@ -1,4 +1,4 @@
-using Asp.Versioning;
+using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
@@ -46,6 +46,6 @@ public class AreReferencedMediaController : MediaControllerBase
Items = _umbracoMapper.MapEnumerable(distinctByKeyItemsWithReferencedRelations.Items),
};
- return await Task.FromResult(pagedViewModel);
+ return pagedViewModel;
}
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/References/ReferencedByMediaController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/References/ReferencedByMediaController.cs
index 63748741b1..e9e62504ed 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Media/References/ReferencedByMediaController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/References/ReferencedByMediaController.cs
@@ -45,6 +45,6 @@ public class ReferencedByMediaController : MediaControllerBase
Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items),
};
- return await Task.FromResult(pagedViewModel);
+ return pagedViewModel;
}
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/References/ReferencedDescendantsMediaController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/References/ReferencedDescendantsMediaController.cs
index 2990602e02..c6c16cb222 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Media/References/ReferencedDescendantsMediaController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/References/ReferencedDescendantsMediaController.cs
@@ -1,4 +1,4 @@
-using Asp.Versioning;
+using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
@@ -45,6 +45,6 @@ public class ReferencedDescendantsMediaController : MediaControllerBase
Items = _umbracoMapper.MapEnumerable(relationItems.Items),
};
- return await Task.FromResult(pagedViewModel);
+ return pagedViewModel;
}
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Member/References/AreReferencedMemberController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Member/References/AreReferencedMemberController.cs
new file mode 100644
index 0000000000..1b46a981ce
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Member/References/AreReferencedMemberController.cs
@@ -0,0 +1,51 @@
+using Asp.Versioning;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Umbraco.Cms.Api.Common.ViewModels.Pagination;
+using Umbraco.Cms.Api.Management.ViewModels;
+using Umbraco.Cms.Core;
+using Umbraco.Cms.Core.Mapping;
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Services;
+
+namespace Umbraco.Cms.Api.Management.Controllers.Member.References;
+
+[ApiVersion("1.0")]
+public class AreReferencedMemberController : MemberControllerBase
+{
+ private readonly ITrackedReferencesService _trackedReferencesSkipTakeService;
+ private readonly IUmbracoMapper _umbracoMapper;
+
+ public AreReferencedMemberController(ITrackedReferencesService trackedReferencesSkipTakeService, IUmbracoMapper umbracoMapper)
+ {
+ _trackedReferencesSkipTakeService = trackedReferencesSkipTakeService;
+ _umbracoMapper = umbracoMapper;
+ }
+
+ ///
+ /// Gets a page list of the items used in any kind of relation from selected keys.
+ ///
+ ///
+ /// Used when bulk deleting content/media and bulk unpublishing content (delete and unpublish on List view).
+ /// This is basically finding children of relations.
+ ///
+ // [HttpGet("item")]
+ [HttpGet("are-referenced")]
+ [MapToApiVersion("1.0")]
+ [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)]
+ public async Task>> GetPagedReferencedItems(
+ CancellationToken cancellationToken,
+ [FromQuery(Name="id")] HashSet ids,
+ int skip = 0,
+ int take = 20)
+ {
+ PagedModel distinctByKeyItemsWithReferencedRelations = await _trackedReferencesSkipTakeService.GetPagedKeysWithDependentReferencesAsync(ids, Constants.ObjectTypes.Member, skip, take);
+ var pagedViewModel = new PagedViewModel
+ {
+ Total = distinctByKeyItemsWithReferencedRelations.Total,
+ Items = _umbracoMapper.MapEnumerable(distinctByKeyItemsWithReferencedRelations.Items),
+ };
+
+ return pagedViewModel;
+ }
+}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Member/References/ReferencedByMemberController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Member/References/ReferencedByMemberController.cs
new file mode 100644
index 0000000000..69237278a5
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Member/References/ReferencedByMemberController.cs
@@ -0,0 +1,50 @@
+using Asp.Versioning;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Umbraco.Cms.Api.Common.ViewModels.Pagination;
+using Umbraco.Cms.Api.Management.Factories;
+using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Services;
+
+namespace Umbraco.Cms.Api.Management.Controllers.Member.References;
+
+[ApiVersion("1.0")]
+public class ReferencedByMemberController : MemberControllerBase
+{
+ private readonly ITrackedReferencesService _trackedReferencesService;
+ private readonly IRelationTypePresentationFactory _relationTypePresentationFactory;
+
+ public ReferencedByMemberController(ITrackedReferencesService trackedReferencesService, IRelationTypePresentationFactory relationTypePresentationFactory)
+ {
+ _trackedReferencesService = trackedReferencesService;
+ _relationTypePresentationFactory = relationTypePresentationFactory;
+ }
+
+ ///
+ /// Gets a page list of tracked references for the current item, so you can see where an item is being used.
+ ///
+ ///
+ /// Used by info tabs on content, media etc. and for the delete and unpublish of single items.
+ /// This is basically finding parents of relations.
+ ///
+ [HttpGet("{id:guid}/referenced-by")]
+ [MapToApiVersion("1.0")]
+ [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)]
+ public async Task>> ReferencedBy(
+ CancellationToken cancellationToken,
+ Guid id,
+ int skip = 0,
+ int take = 20)
+ {
+ PagedModel relationItems = await _trackedReferencesService.GetPagedRelationsForItemAsync(id, skip, take, true);
+
+ var pagedViewModel = new PagedViewModel
+ {
+ Total = relationItems.Total,
+ Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items),
+ };
+
+ return pagedViewModel;
+ }
+}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Member/References/ReferencedDescendantsMemberController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Member/References/ReferencedDescendantsMemberController.cs
new file mode 100644
index 0000000000..aa86950e6e
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Member/References/ReferencedDescendantsMemberController.cs
@@ -0,0 +1,50 @@
+using Asp.Versioning;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Umbraco.Cms.Api.Common.ViewModels.Pagination;
+using Umbraco.Cms.Api.Management.ViewModels;
+using Umbraco.Cms.Core.Mapping;
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Services;
+
+namespace Umbraco.Cms.Api.Management.Controllers.Member.References;
+
+[ApiVersion("1.0")]
+public class ReferencedDescendantsMemberController : MemberControllerBase
+{
+ private readonly ITrackedReferencesService _trackedReferencesSkipTakeService;
+ private readonly IUmbracoMapper _umbracoMapper;
+
+ public ReferencedDescendantsMemberController(ITrackedReferencesService trackedReferencesSkipTakeService, IUmbracoMapper umbracoMapper)
+ {
+ _trackedReferencesSkipTakeService = trackedReferencesSkipTakeService;
+ _umbracoMapper = umbracoMapper;
+ }
+
+ ///
+ /// Gets a page list of the child nodes of the current item used in any kind of relation.
+ ///
+ ///
+ /// Used when deleting and unpublishing a single item to check if this item has any descending items that are in any
+ /// kind of relation.
+ /// This is basically finding the descending items which are children in relations.
+ ///
+ [HttpGet("{id:guid}/referenced-descendants")]
+ [MapToApiVersion("1.0")]
+ [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)]
+ public async Task>> ReferencedDescendants(
+ CancellationToken cancellationToken,
+ Guid id,
+ int skip = 0,
+ int take = 20)
+ {
+ PagedModel relationItems = await _trackedReferencesSkipTakeService.GetPagedDescendantsInReferencesAsync(id, skip, take, true);
+ var pagedViewModel = new PagedViewModel
+ {
+ Total = relationItems.Total,
+ Items = _umbracoMapper.MapEnumerable(relationItems.Items),
+ };
+
+ return pagedViewModel;
+ }
+}
diff --git a/src/Umbraco.Cms.Api.Management/Factories/RelationTypePresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/RelationTypePresentationFactory.cs
index 0c718bf663..a6fb1cbae8 100644
--- a/src/Umbraco.Cms.Api.Management/Factories/RelationTypePresentationFactory.cs
+++ b/src/Umbraco.Cms.Api.Management/Factories/RelationTypePresentationFactory.cs
@@ -1,4 +1,4 @@
-using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DependencyInjection;
@@ -58,6 +58,7 @@ public class RelationTypePresentationFactory : IRelationTypePresentationFactory
{
Constants.UdiEntityType.Document => MapDocumentReference(relationItemModel, slimEntities),
Constants.UdiEntityType.Media => _umbracoMapper.Map(relationItemModel),
+ Constants.UdiEntityType.Member => _umbracoMapper.Map(relationItemModel),
_ => _umbracoMapper.Map(relationItemModel),
}).WhereNotNull().ToArray();
diff --git a/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs
index 7a3874271c..e9f2700f5a 100644
--- a/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs
+++ b/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs
@@ -1,4 +1,4 @@
-using Umbraco.Cms.Api.Management.ViewModels;
+using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
@@ -11,6 +11,7 @@ public class TrackedReferenceViewModelsMapDefinition : IMapDefinition
{
mapper.Define((source, context) => new DocumentReferenceResponseModel(), Map);
mapper.Define((source, context) => new MediaReferenceResponseModel(), Map);
+ mapper.Define((source, context) => new MemberReferenceResponseModel(), Map);
mapper.Define((source, context) => new DefaultReferenceResponseModel(), Map);
mapper.Define((source, context) => new ReferenceByIdModel(), Map);
mapper.Define((source, context) => new ReferenceByIdModel(), Map);
@@ -43,6 +44,19 @@ public class TrackedReferenceViewModelsMapDefinition : IMapDefinition
};
}
+ // Umbraco.Code.MapAll
+ private void Map(RelationItemModel source, MemberReferenceResponseModel target, MapperContext context)
+ {
+ target.Id = source.NodeKey;
+ target.Name = source.NodeName;
+ target.MemberType = new TrackedReferenceMemberType
+ {
+ Alias = source.ContentTypeAlias,
+ Icon = source.ContentTypeIcon,
+ Name = source.ContentTypeName,
+ };
+ }
+
// Umbraco.Code.MapAll
private void Map(RelationItemModel source, DefaultReferenceResponseModel target, MapperContext context)
{
diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json
index ebecf3e9cb..44a69ccc95 100644
--- a/src/Umbraco.Cms.Api.Management/OpenApi.json
+++ b/src/Umbraco.Cms.Api.Management/OpenApi.json
@@ -6245,6 +6245,13 @@
"type": "string"
}
},
+ {
+ "name": "isElement",
+ "in": "query",
+ "schema": {
+ "type": "boolean"
+ }
+ },
{
"name": "skip",
"in": "query",
@@ -10069,6 +10076,13 @@
"type": "string"
}
},
+ {
+ "name": "trashed",
+ "in": "query",
+ "schema": {
+ "type": "boolean"
+ }
+ },
{
"name": "skip",
"in": "query",
@@ -15766,6 +15780,13 @@
"type": "string"
}
},
+ {
+ "name": "trashed",
+ "in": "query",
+ "schema": {
+ "type": "boolean"
+ }
+ },
{
"name": "skip",
"in": "query",
@@ -20170,6 +20191,134 @@
]
}
},
+ "/umbraco/management/api/v1/member/{id}/referenced-by": {
+ "get": {
+ "tags": [
+ "Member"
+ ],
+ "operationId": "GetMemberByIdReferencedBy",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid"
+ }
+ },
+ {
+ "name": "skip",
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "format": "int32",
+ "default": 0
+ }
+ },
+ {
+ "name": "take",
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "format": "int32",
+ "default": 20
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/PagedIReferenceResponseModel"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The resource is protected and requires an authentication token"
+ },
+ "403": {
+ "description": "The authenticated user does not have access to this resource"
+ }
+ },
+ "security": [
+ {
+ "Backoffice User": [ ]
+ }
+ ]
+ }
+ },
+ "/umbraco/management/api/v1/member/{id}/referenced-descendants": {
+ "get": {
+ "tags": [
+ "Member"
+ ],
+ "operationId": "GetMemberByIdReferencedDescendants",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid"
+ }
+ },
+ {
+ "name": "skip",
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "format": "int32",
+ "default": 0
+ }
+ },
+ {
+ "name": "take",
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "format": "int32",
+ "default": 20
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/PagedReferenceByIdModel"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The resource is protected and requires an authentication token"
+ },
+ "403": {
+ "description": "The authenticated user does not have access to this resource"
+ }
+ },
+ "security": [
+ {
+ "Backoffice User": [ ]
+ }
+ ]
+ }
+ },
"/umbraco/management/api/v1/member/{id}/validate": {
"put": {
"tags": [
@@ -20312,6 +20461,73 @@
]
}
},
+ "/umbraco/management/api/v1/member/are-referenced": {
+ "get": {
+ "tags": [
+ "Member"
+ ],
+ "operationId": "GetMemberAreReferenced",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "query",
+ "schema": {
+ "uniqueItems": true,
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "uuid"
+ }
+ }
+ },
+ {
+ "name": "skip",
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "format": "int32",
+ "default": 0
+ }
+ },
+ {
+ "name": "take",
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "format": "int32",
+ "default": 20
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/PagedReferenceByIdModel"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The resource is protected and requires an authentication token"
+ },
+ "403": {
+ "description": "The authenticated user does not have access to this resource"
+ }
+ },
+ "security": [
+ {
+ "Backoffice User": [ ]
+ }
+ ]
+ }
+ },
"/umbraco/management/api/v1/member/configuration": {
"get": {
"tags": [
@@ -40095,6 +40311,41 @@
],
"type": "string"
},
+ "MemberReferenceResponseModel": {
+ "required": [
+ "$type",
+ "id",
+ "memberType"
+ ],
+ "type": "object",
+ "properties": {
+ "$type": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "name": {
+ "type": "string",
+ "nullable": true
+ },
+ "memberType": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/TrackedReferenceMemberTypeModel"
+ }
+ ]
+ }
+ },
+ "additionalProperties": false,
+ "discriminator": {
+ "propertyName": "$type",
+ "mapping": {
+ "MemberReferenceResponseModel": "#/components/schemas/MemberReferenceResponseModel"
+ }
+ }
+ },
"MemberResponseModel": {
"required": [
"email",
@@ -41505,6 +41756,9 @@
},
{
"$ref": "#/components/schemas/MediaReferenceResponseModel"
+ },
+ {
+ "$ref": "#/components/schemas/MemberReferenceResponseModel"
}
]
}
@@ -44177,6 +44431,24 @@
},
"additionalProperties": false
},
+ "TrackedReferenceMemberTypeModel": {
+ "type": "object",
+ "properties": {
+ "icon": {
+ "type": "string",
+ "nullable": true
+ },
+ "alias": {
+ "type": "string",
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "nullable": true
+ }
+ },
+ "additionalProperties": false
+ },
"UnknownTypePermissionPresentationModel": {
"required": [
"$type",
@@ -46521,4 +46793,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/DefaultReferenceResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/DefaultReferenceResponseModel.cs
index 66780c9b47..f74b0d2840 100644
--- a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/DefaultReferenceResponseModel.cs
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/DefaultReferenceResponseModel.cs
@@ -1,11 +1,7 @@
-namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
+namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
-public class DefaultReferenceResponseModel : IReferenceResponseModel
+public class DefaultReferenceResponseModel : ReferenceResponseModel
{
- public Guid Id { get; set; }
-
- public string? Name { get; set; }
-
public string? Type { get; set; }
public string? Icon { get; set; }
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/DocumentReferenceResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/DocumentReferenceResponseModel.cs
index e2bb767f99..a24ab23990 100644
--- a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/DocumentReferenceResponseModel.cs
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/DocumentReferenceResponseModel.cs
@@ -1,13 +1,9 @@
-using Umbraco.Cms.Api.Management.ViewModels.Document;
+using Umbraco.Cms.Api.Management.ViewModels.Document;
namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
-public class DocumentReferenceResponseModel : IReferenceResponseModel
+public class DocumentReferenceResponseModel : ReferenceResponseModel
{
- public Guid Id { get; set; }
-
- public string? Name { get; set; }
-
public bool? Published { get; set; }
public TrackedReferenceDocumentType DocumentType { get; set; } = new();
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MediaReferenceResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MediaReferenceResponseModel.cs
index c9b910d0b1..5358088bb3 100644
--- a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MediaReferenceResponseModel.cs
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MediaReferenceResponseModel.cs
@@ -1,10 +1,6 @@
-namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
+namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
-public class MediaReferenceResponseModel : IReferenceResponseModel
+public class MediaReferenceResponseModel : ReferenceResponseModel
{
- public Guid Id { get; set; }
-
- public string? Name { get; set; }
-
public TrackedReferenceMediaType MediaType { get; set; } = new();
}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MemberReferenceResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MemberReferenceResponseModel.cs
new file mode 100644
index 0000000000..8e33a2a175
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MemberReferenceResponseModel.cs
@@ -0,0 +1,6 @@
+namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
+
+public class MemberReferenceResponseModel : ReferenceResponseModel
+{
+ public TrackedReferenceMemberType MemberType { get; set; } = new();
+}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/ReferenceResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/ReferenceResponseModel.cs
new file mode 100644
index 0000000000..dae755fd33
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/ReferenceResponseModel.cs
@@ -0,0 +1,8 @@
+namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
+
+public abstract class ReferenceResponseModel : IReferenceResponseModel
+{
+ public Guid Id { get; set; }
+
+ public string? Name { get; set; }
+}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/TrackedReferenceMemberType.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/TrackedReferenceMemberType.cs
new file mode 100644
index 0000000000..501ababd1f
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/TrackedReferenceMemberType.cs
@@ -0,0 +1,5 @@
+namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
+
+public class TrackedReferenceMemberType : TrackedReferenceContentType
+{
+}
diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs
index 647226b6d7..989b947adc 100644
--- a/src/Umbraco.Core/Constants-Conventions.cs
+++ b/src/Umbraco.Core/Constants-Conventions.cs
@@ -233,17 +233,27 @@ public static partial class Constants
public const string RelatedMediaName = "Related Media";
///
- /// Alias for default relation type "Related Media"
+ /// Alias for default relation type "Related Media".
///
public const string RelatedMediaAlias = "umbMedia";
+ ///
+ /// Name for default relation type "Related Member".
+ ///
+ public const string RelatedMemberName = "Related Member";
+
+ ///
+ /// Alias for default relation type "Related Member".
+ ///
+ public const string RelatedMemberAlias = "umbMember";
+
///
/// Name for default relation type "Related Document".
///
public const string RelatedDocumentName = "Related Document";
///
- /// Alias for default relation type "Related Document"
+ /// Alias for default relation type "Related Document".
///
public const string RelatedDocumentAlias = "umbDocument";
@@ -284,7 +294,7 @@ public static partial class Constants
/// Developers should not manually use these relation types since they will all be cleared whenever an entity
/// (content, media or member) is saved since they are auto-populated based on property values.
///
- public static string[] AutomaticRelationTypes { get; } = { RelatedMediaAlias, RelatedDocumentAlias };
+ public static string[] AutomaticRelationTypes { get; } = { RelatedMediaAlias, RelatedMemberAlias, RelatedDocumentAlias };
// TODO: return a list of built in types so we can use that to prevent deletion in the UI
}
diff --git a/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs b/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs
index 3f02be10b1..3c22042b99 100644
--- a/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs
+++ b/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs
@@ -34,6 +34,9 @@ public struct UmbracoEntityReference : IEquatable
case Constants.UdiEntityType.Media:
RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedMediaAlias;
break;
+ case Constants.UdiEntityType.Member:
+ RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedMemberAlias;
+ break;
default:
// No relation type alias convention for this entity type, so leave it empty
RelationTypeAlias = string.Empty;
diff --git a/src/Umbraco.Core/Models/RelationTypeExtensions.cs b/src/Umbraco.Core/Models/RelationTypeExtensions.cs
index 6a22f607be..81d6801330 100644
--- a/src/Umbraco.Core/Models/RelationTypeExtensions.cs
+++ b/src/Umbraco.Core/Models/RelationTypeExtensions.cs
@@ -11,6 +11,7 @@ public static class RelationTypeExtensions
public static bool IsSystemRelationType(this IRelationType relationType) =>
relationType.Alias == Constants.Conventions.RelationTypes.RelatedDocumentAlias
|| relationType.Alias == Constants.Conventions.RelationTypes.RelatedMediaAlias
+ || relationType.Alias == Constants.Conventions.RelationTypes.RelatedMemberAlias
|| relationType.Alias == Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias
|| relationType.Alias == Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias
|| relationType.Alias == Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias;
diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs
index 86c9c48fc0..c35ae6d2ae 100644
--- a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs
+++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs
@@ -91,7 +91,7 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase
DataValueEditorFactory.Create(Attribute!);
- private class MemberPickerPropertyValueEditor : DataValueEditor
+ private class MemberPickerPropertyValueEditor : DataValueEditor, IDataValueReference
{
private readonly IMemberService _memberService;
@@ -61,5 +61,20 @@ public class MemberPickerPropertyEditor : DataEditor
=> editorValue.Value is string stringValue && Guid.TryParse(stringValue, out Guid memberKey)
? new GuidUdi(Constants.UdiEntityType.Member, memberKey)
: null;
+
+ public IEnumerable GetReferences(object? value)
+ {
+ var asString = value is string str ? str : value?.ToString();
+
+ if (string.IsNullOrEmpty(asString))
+ {
+ yield break;
+ }
+
+ if (UdiParser.TryParse(asString, out Udi? udi))
+ {
+ yield return new UmbracoEntityReference(udi);
+ }
+ }
}
}
diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs
index 379a3a91da..68209ee155 100644
--- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs
@@ -2299,6 +2299,9 @@ internal class DatabaseDataCreator
Constants.Conventions.RelationTypes.RelatedMediaName, null, null, false, true);
CreateRelationTypeData(5, Constants.Conventions.RelationTypes.RelatedDocumentAlias,
Constants.Conventions.RelationTypes.RelatedDocumentName, null, null, false, true);
+ CreateRelationTypeData(6, Constants.Conventions.RelationTypes.RelatedMemberAlias,
+ Constants.Conventions.RelationTypes.RelatedMemberName, null, null, false, true);
+
}
private void CreateRelationTypeData(int id, string alias, string name, Guid? parentObjectType,
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
index fe14f785d8..395504cfdc 100644
--- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
@@ -114,5 +114,6 @@ public class UmbracoPlan : MigrationPlan
// To 15.4.0
To("{A9E72794-4036-4563-B543-1717C73B8879}");
+ To("{33D62294-D0DE-4A86-A830-991EB36B96DA}");
}
}
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_4_0/AddRelationTypeForMembers.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_4_0/AddRelationTypeForMembers.cs
new file mode 100644
index 0000000000..bcc3422946
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_4_0/AddRelationTypeForMembers.cs
@@ -0,0 +1,46 @@
+using Microsoft.Extensions.Logging;
+using Umbraco.Cms.Core;
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Infrastructure.Migrations.Install;
+
+namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_15_4_0;
+
+///
+/// Migration to add an automatic relation type for members if it doesn't already exist.
+///
+public class AddRelationTypeForMembers : MigrationBase
+{
+ private readonly IRelationService _relationService;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public AddRelationTypeForMembers(IMigrationContext context, IRelationService relationService)
+ : base(context) => _relationService = relationService;
+
+ ///
+ protected override void Migrate()
+ {
+ Logger.LogDebug("Adding automatic relation type for members if it doesn't already exist");
+
+ IRelationType? relationType = _relationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMemberAlias);
+ if (relationType != null)
+ {
+ Logger.LogDebug("Automatic relation type for members already exists.");
+ return;
+ }
+
+ // Generate a new unique relation type key that is the same as would have come from a new install.
+ Guid key = DatabaseDataCreator.CreateUniqueRelationTypeId(
+ Constants.Conventions.RelationTypes.RelatedMemberAlias,
+ Constants.Conventions.RelationTypes.RelatedMemberName);
+
+ // Create new relation type using service, so the repository cache gets updated as well.
+ relationType = new RelationType(Constants.Conventions.RelationTypes.RelatedMemberName, Constants.Conventions.RelationTypes.RelatedMemberAlias, false, null, null, true)
+ {
+ Key = key
+ };
+ _relationService.Save(relationType);
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/sdk.gen.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/sdk.gen.ts
index 9b36b8ffd2..ae4012fb62 100644
--- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/sdk.gen.ts
+++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/sdk.gen.ts
@@ -3,7 +3,7 @@
import type { CancelablePromise } from './core/CancelablePromise';
import { OpenAPI } from './core/OpenAPI';
import { request as __request } from './core/request';
-import type { GetCultureData, GetCultureResponse, PostDataTypeData, PostDataTypeResponse, GetDataTypeByIdData, GetDataTypeByIdResponse, DeleteDataTypeByIdData, DeleteDataTypeByIdResponse, PutDataTypeByIdData, PutDataTypeByIdResponse, PostDataTypeByIdCopyData, PostDataTypeByIdCopyResponse, GetDataTypeByIdIsUsedData, GetDataTypeByIdIsUsedResponse, PutDataTypeByIdMoveData, PutDataTypeByIdMoveResponse, GetDataTypeByIdReferencesData, GetDataTypeByIdReferencesResponse, GetDataTypeConfigurationResponse, PostDataTypeFolderData, PostDataTypeFolderResponse, GetDataTypeFolderByIdData, GetDataTypeFolderByIdResponse, DeleteDataTypeFolderByIdData, DeleteDataTypeFolderByIdResponse, PutDataTypeFolderByIdData, PutDataTypeFolderByIdResponse, GetFilterDataTypeData, GetFilterDataTypeResponse, GetItemDataTypeData, GetItemDataTypeResponse, GetItemDataTypeSearchData, GetItemDataTypeSearchResponse, GetTreeDataTypeAncestorsData, GetTreeDataTypeAncestorsResponse, GetTreeDataTypeChildrenData, GetTreeDataTypeChildrenResponse, GetTreeDataTypeRootData, GetTreeDataTypeRootResponse, GetDictionaryData, GetDictionaryResponse, PostDictionaryData, PostDictionaryResponse, GetDictionaryByIdData, GetDictionaryByIdResponse, DeleteDictionaryByIdData, DeleteDictionaryByIdResponse, PutDictionaryByIdData, PutDictionaryByIdResponse, GetDictionaryByIdExportData, GetDictionaryByIdExportResponse, PutDictionaryByIdMoveData, PutDictionaryByIdMoveResponse, PostDictionaryImportData, PostDictionaryImportResponse, GetItemDictionaryData, GetItemDictionaryResponse, GetTreeDictionaryAncestorsData, GetTreeDictionaryAncestorsResponse, GetTreeDictionaryChildrenData, GetTreeDictionaryChildrenResponse, GetTreeDictionaryRootData, GetTreeDictionaryRootResponse, GetCollectionDocumentByIdData, GetCollectionDocumentByIdResponse, PostDocumentData, PostDocumentResponse, GetDocumentByIdData, GetDocumentByIdResponse, DeleteDocumentByIdData, DeleteDocumentByIdResponse, PutDocumentByIdData, PutDocumentByIdResponse, GetDocumentByIdAuditLogData, GetDocumentByIdAuditLogResponse, PostDocumentByIdCopyData, PostDocumentByIdCopyResponse, GetDocumentByIdDomainsData, GetDocumentByIdDomainsResponse, PutDocumentByIdDomainsData, PutDocumentByIdDomainsResponse, PutDocumentByIdMoveData, PutDocumentByIdMoveResponse, PutDocumentByIdMoveToRecycleBinData, PutDocumentByIdMoveToRecycleBinResponse, GetDocumentByIdNotificationsData, GetDocumentByIdNotificationsResponse, PutDocumentByIdNotificationsData, PutDocumentByIdNotificationsResponse, PostDocumentByIdPublicAccessData, PostDocumentByIdPublicAccessResponse, DeleteDocumentByIdPublicAccessData, DeleteDocumentByIdPublicAccessResponse, GetDocumentByIdPublicAccessData, GetDocumentByIdPublicAccessResponse, PutDocumentByIdPublicAccessData, PutDocumentByIdPublicAccessResponse, PutDocumentByIdPublishData, PutDocumentByIdPublishResponse, PutDocumentByIdPublishWithDescendantsData, PutDocumentByIdPublishWithDescendantsResponse, GetDocumentByIdPublishedData, GetDocumentByIdPublishedResponse, GetDocumentByIdReferencedByData, GetDocumentByIdReferencedByResponse, GetDocumentByIdReferencedDescendantsData, GetDocumentByIdReferencedDescendantsResponse, PutDocumentByIdUnpublishData, PutDocumentByIdUnpublishResponse, PutDocumentByIdValidateData, PutDocumentByIdValidateResponse, PutUmbracoManagementApiV11DocumentByIdValidate11Data, PutUmbracoManagementApiV11DocumentByIdValidate11Response, GetDocumentAreReferencedData, GetDocumentAreReferencedResponse, GetDocumentConfigurationResponse, PutDocumentSortData, PutDocumentSortResponse, GetDocumentUrlsData, GetDocumentUrlsResponse, PostDocumentValidateData, PostDocumentValidateResponse, GetItemDocumentData, GetItemDocumentResponse, GetItemDocumentSearchData, GetItemDocumentSearchResponse, DeleteRecycleBinDocumentResponse, DeleteRecycleBinDocumentByIdData, DeleteRecycleBinDocumentByIdResponse, GetRecycleBinDocumentByIdOriginalParentData, GetRecycleBinDocumentByIdOriginalParentResponse, PutRecycleBinDocumentByIdRestoreData, PutRecycleBinDocumentByIdRestoreResponse, GetRecycleBinDocumentChildrenData, GetRecycleBinDocumentChildrenResponse, GetRecycleBinDocumentRootData, GetRecycleBinDocumentRootResponse, GetTreeDocumentAncestorsData, GetTreeDocumentAncestorsResponse, GetTreeDocumentChildrenData, GetTreeDocumentChildrenResponse, GetTreeDocumentRootData, GetTreeDocumentRootResponse, PostDocumentBlueprintData, PostDocumentBlueprintResponse, GetDocumentBlueprintByIdData, GetDocumentBlueprintByIdResponse, DeleteDocumentBlueprintByIdData, DeleteDocumentBlueprintByIdResponse, PutDocumentBlueprintByIdData, PutDocumentBlueprintByIdResponse, PutDocumentBlueprintByIdMoveData, PutDocumentBlueprintByIdMoveResponse, PostDocumentBlueprintFolderData, PostDocumentBlueprintFolderResponse, GetDocumentBlueprintFolderByIdData, GetDocumentBlueprintFolderByIdResponse, DeleteDocumentBlueprintFolderByIdData, DeleteDocumentBlueprintFolderByIdResponse, PutDocumentBlueprintFolderByIdData, PutDocumentBlueprintFolderByIdResponse, PostDocumentBlueprintFromDocumentData, PostDocumentBlueprintFromDocumentResponse, GetItemDocumentBlueprintData, GetItemDocumentBlueprintResponse, GetTreeDocumentBlueprintAncestorsData, GetTreeDocumentBlueprintAncestorsResponse, GetTreeDocumentBlueprintChildrenData, GetTreeDocumentBlueprintChildrenResponse, GetTreeDocumentBlueprintRootData, GetTreeDocumentBlueprintRootResponse, PostDocumentTypeData, PostDocumentTypeResponse, GetDocumentTypeByIdData, GetDocumentTypeByIdResponse, DeleteDocumentTypeByIdData, DeleteDocumentTypeByIdResponse, PutDocumentTypeByIdData, PutDocumentTypeByIdResponse, GetDocumentTypeByIdAllowedChildrenData, GetDocumentTypeByIdAllowedChildrenResponse, GetDocumentTypeByIdBlueprintData, GetDocumentTypeByIdBlueprintResponse, GetDocumentTypeByIdCompositionReferencesData, GetDocumentTypeByIdCompositionReferencesResponse, PostDocumentTypeByIdCopyData, PostDocumentTypeByIdCopyResponse, GetDocumentTypeByIdExportData, GetDocumentTypeByIdExportResponse, PutDocumentTypeByIdImportData, PutDocumentTypeByIdImportResponse, PutDocumentTypeByIdMoveData, PutDocumentTypeByIdMoveResponse, GetDocumentTypeAllowedAtRootData, GetDocumentTypeAllowedAtRootResponse, PostDocumentTypeAvailableCompositionsData, PostDocumentTypeAvailableCompositionsResponse, GetDocumentTypeConfigurationResponse, PostDocumentTypeFolderData, PostDocumentTypeFolderResponse, GetDocumentTypeFolderByIdData, GetDocumentTypeFolderByIdResponse, DeleteDocumentTypeFolderByIdData, DeleteDocumentTypeFolderByIdResponse, PutDocumentTypeFolderByIdData, PutDocumentTypeFolderByIdResponse, PostDocumentTypeImportData, PostDocumentTypeImportResponse, GetItemDocumentTypeData, GetItemDocumentTypeResponse, GetItemDocumentTypeSearchData, GetItemDocumentTypeSearchResponse, GetTreeDocumentTypeAncestorsData, GetTreeDocumentTypeAncestorsResponse, GetTreeDocumentTypeChildrenData, GetTreeDocumentTypeChildrenResponse, GetTreeDocumentTypeRootData, GetTreeDocumentTypeRootResponse, GetDocumentVersionData, GetDocumentVersionResponse, GetDocumentVersionByIdData, GetDocumentVersionByIdResponse, PutDocumentVersionByIdPreventCleanupData, PutDocumentVersionByIdPreventCleanupResponse, PostDocumentVersionByIdRollbackData, PostDocumentVersionByIdRollbackResponse, PostDynamicRootQueryData, PostDynamicRootQueryResponse, GetDynamicRootStepsResponse, GetHealthCheckGroupData, GetHealthCheckGroupResponse, GetHealthCheckGroupByNameData, GetHealthCheckGroupByNameResponse, PostHealthCheckGroupByNameCheckData, PostHealthCheckGroupByNameCheckResponse, PostHealthCheckExecuteActionData, PostHealthCheckExecuteActionResponse, GetHelpData, GetHelpResponse, GetImagingResizeUrlsData, GetImagingResizeUrlsResponse, GetImportAnalyzeData, GetImportAnalyzeResponse, GetIndexerData, GetIndexerResponse, GetIndexerByIndexNameData, GetIndexerByIndexNameResponse, PostIndexerByIndexNameRebuildData, PostIndexerByIndexNameRebuildResponse, GetInstallSettingsResponse, PostInstallSetupData, PostInstallSetupResponse, PostInstallValidateDatabaseData, PostInstallValidateDatabaseResponse, GetItemLanguageData, GetItemLanguageResponse, GetItemLanguageDefaultResponse, GetLanguageData, GetLanguageResponse, PostLanguageData, PostLanguageResponse, GetLanguageByIsoCodeData, GetLanguageByIsoCodeResponse, DeleteLanguageByIsoCodeData, DeleteLanguageByIsoCodeResponse, PutLanguageByIsoCodeData, PutLanguageByIsoCodeResponse, GetLogViewerLevelData, GetLogViewerLevelResponse, GetLogViewerLevelCountData, GetLogViewerLevelCountResponse, GetLogViewerLogData, GetLogViewerLogResponse, GetLogViewerMessageTemplateData, GetLogViewerMessageTemplateResponse, GetLogViewerSavedSearchData, GetLogViewerSavedSearchResponse, PostLogViewerSavedSearchData, PostLogViewerSavedSearchResponse, GetLogViewerSavedSearchByNameData, GetLogViewerSavedSearchByNameResponse, DeleteLogViewerSavedSearchByNameData, DeleteLogViewerSavedSearchByNameResponse, GetLogViewerValidateLogsSizeData, GetLogViewerValidateLogsSizeResponse, GetManifestManifestResponse, GetManifestManifestPrivateResponse, GetManifestManifestPublicResponse, GetCollectionMediaData, GetCollectionMediaResponse, GetItemMediaData, GetItemMediaResponse, GetItemMediaSearchData, GetItemMediaSearchResponse, PostMediaData, PostMediaResponse, GetMediaByIdData, GetMediaByIdResponse, DeleteMediaByIdData, DeleteMediaByIdResponse, PutMediaByIdData, PutMediaByIdResponse, GetMediaByIdAuditLogData, GetMediaByIdAuditLogResponse, PutMediaByIdMoveData, PutMediaByIdMoveResponse, PutMediaByIdMoveToRecycleBinData, PutMediaByIdMoveToRecycleBinResponse, GetMediaByIdReferencedByData, GetMediaByIdReferencedByResponse, GetMediaByIdReferencedDescendantsData, GetMediaByIdReferencedDescendantsResponse, PutMediaByIdValidateData, PutMediaByIdValidateResponse, GetMediaAreReferencedData, GetMediaAreReferencedResponse, GetMediaConfigurationResponse, PutMediaSortData, PutMediaSortResponse, GetMediaUrlsData, GetMediaUrlsResponse, PostMediaValidateData, PostMediaValidateResponse, DeleteRecycleBinMediaResponse, DeleteRecycleBinMediaByIdData, DeleteRecycleBinMediaByIdResponse, GetRecycleBinMediaByIdOriginalParentData, GetRecycleBinMediaByIdOriginalParentResponse, PutRecycleBinMediaByIdRestoreData, PutRecycleBinMediaByIdRestoreResponse, GetRecycleBinMediaChildrenData, GetRecycleBinMediaChildrenResponse, GetRecycleBinMediaRootData, GetRecycleBinMediaRootResponse, GetTreeMediaAncestorsData, GetTreeMediaAncestorsResponse, GetTreeMediaChildrenData, GetTreeMediaChildrenResponse, GetTreeMediaRootData, GetTreeMediaRootResponse, GetItemMediaTypeData, GetItemMediaTypeResponse, GetItemMediaTypeAllowedData, GetItemMediaTypeAllowedResponse, GetItemMediaTypeFoldersData, GetItemMediaTypeFoldersResponse, GetItemMediaTypeSearchData, GetItemMediaTypeSearchResponse, PostMediaTypeData, PostMediaTypeResponse, GetMediaTypeByIdData, GetMediaTypeByIdResponse, DeleteMediaTypeByIdData, DeleteMediaTypeByIdResponse, PutMediaTypeByIdData, PutMediaTypeByIdResponse, GetMediaTypeByIdAllowedChildrenData, GetMediaTypeByIdAllowedChildrenResponse, GetMediaTypeByIdCompositionReferencesData, GetMediaTypeByIdCompositionReferencesResponse, PostMediaTypeByIdCopyData, PostMediaTypeByIdCopyResponse, GetMediaTypeByIdExportData, GetMediaTypeByIdExportResponse, PutMediaTypeByIdImportData, PutMediaTypeByIdImportResponse, PutMediaTypeByIdMoveData, PutMediaTypeByIdMoveResponse, GetMediaTypeAllowedAtRootData, GetMediaTypeAllowedAtRootResponse, PostMediaTypeAvailableCompositionsData, PostMediaTypeAvailableCompositionsResponse, GetMediaTypeConfigurationResponse, PostMediaTypeFolderData, PostMediaTypeFolderResponse, GetMediaTypeFolderByIdData, GetMediaTypeFolderByIdResponse, DeleteMediaTypeFolderByIdData, DeleteMediaTypeFolderByIdResponse, PutMediaTypeFolderByIdData, PutMediaTypeFolderByIdResponse, PostMediaTypeImportData, PostMediaTypeImportResponse, GetTreeMediaTypeAncestorsData, GetTreeMediaTypeAncestorsResponse, GetTreeMediaTypeChildrenData, GetTreeMediaTypeChildrenResponse, GetTreeMediaTypeRootData, GetTreeMediaTypeRootResponse, GetFilterMemberData, GetFilterMemberResponse, GetItemMemberData, GetItemMemberResponse, GetItemMemberSearchData, GetItemMemberSearchResponse, PostMemberData, PostMemberResponse, GetMemberByIdData, GetMemberByIdResponse, DeleteMemberByIdData, DeleteMemberByIdResponse, PutMemberByIdData, PutMemberByIdResponse, PutMemberByIdValidateData, PutMemberByIdValidateResponse, GetMemberConfigurationResponse, PostMemberValidateData, PostMemberValidateResponse, GetItemMemberGroupData, GetItemMemberGroupResponse, GetMemberGroupData, GetMemberGroupResponse, PostMemberGroupData, PostMemberGroupResponse, GetMemberGroupByIdData, GetMemberGroupByIdResponse, DeleteMemberGroupByIdData, DeleteMemberGroupByIdResponse, PutMemberGroupByIdData, PutMemberGroupByIdResponse, GetTreeMemberGroupRootData, GetTreeMemberGroupRootResponse, GetItemMemberTypeData, GetItemMemberTypeResponse, GetItemMemberTypeSearchData, GetItemMemberTypeSearchResponse, PostMemberTypeData, PostMemberTypeResponse, GetMemberTypeByIdData, GetMemberTypeByIdResponse, DeleteMemberTypeByIdData, DeleteMemberTypeByIdResponse, PutMemberTypeByIdData, PutMemberTypeByIdResponse, GetMemberTypeByIdCompositionReferencesData, GetMemberTypeByIdCompositionReferencesResponse, PostMemberTypeByIdCopyData, PostMemberTypeByIdCopyResponse, PostMemberTypeAvailableCompositionsData, PostMemberTypeAvailableCompositionsResponse, GetMemberTypeConfigurationResponse, GetTreeMemberTypeRootData, GetTreeMemberTypeRootResponse, PostModelsBuilderBuildResponse, GetModelsBuilderDashboardResponse, GetModelsBuilderStatusResponse, GetObjectTypesData, GetObjectTypesResponse, GetOembedQueryData, GetOembedQueryResponse, PostPackageByNameRunMigrationData, PostPackageByNameRunMigrationResponse, GetPackageConfigurationResponse, GetPackageCreatedData, GetPackageCreatedResponse, PostPackageCreatedData, PostPackageCreatedResponse, GetPackageCreatedByIdData, GetPackageCreatedByIdResponse, DeletePackageCreatedByIdData, DeletePackageCreatedByIdResponse, PutPackageCreatedByIdData, PutPackageCreatedByIdResponse, GetPackageCreatedByIdDownloadData, GetPackageCreatedByIdDownloadResponse, GetPackageMigrationStatusData, GetPackageMigrationStatusResponse, GetItemPartialViewData, GetItemPartialViewResponse, PostPartialViewData, PostPartialViewResponse, GetPartialViewByPathData, GetPartialViewByPathResponse, DeletePartialViewByPathData, DeletePartialViewByPathResponse, PutPartialViewByPathData, PutPartialViewByPathResponse, PutPartialViewByPathRenameData, PutPartialViewByPathRenameResponse, PostPartialViewFolderData, PostPartialViewFolderResponse, GetPartialViewFolderByPathData, GetPartialViewFolderByPathResponse, DeletePartialViewFolderByPathData, DeletePartialViewFolderByPathResponse, GetPartialViewSnippetData, GetPartialViewSnippetResponse, GetPartialViewSnippetByIdData, GetPartialViewSnippetByIdResponse, GetTreePartialViewAncestorsData, GetTreePartialViewAncestorsResponse, GetTreePartialViewChildrenData, GetTreePartialViewChildrenResponse, GetTreePartialViewRootData, GetTreePartialViewRootResponse, DeletePreviewResponse, PostPreviewResponse, GetProfilingStatusResponse, PutProfilingStatusData, PutProfilingStatusResponse, GetPropertyTypeIsUsedData, GetPropertyTypeIsUsedResponse, PostPublishedCacheRebuildResponse, GetPublishedCacheRebuildStatusResponse, PostPublishedCacheReloadResponse, GetRedirectManagementData, GetRedirectManagementResponse, GetRedirectManagementByIdData, GetRedirectManagementByIdResponse, DeleteRedirectManagementByIdData, DeleteRedirectManagementByIdResponse, GetRedirectManagementStatusResponse, PostRedirectManagementStatusData, PostRedirectManagementStatusResponse, GetRelationByRelationTypeIdData, GetRelationByRelationTypeIdResponse, GetItemRelationTypeData, GetItemRelationTypeResponse, GetRelationTypeData, GetRelationTypeResponse, GetRelationTypeByIdData, GetRelationTypeByIdResponse, GetItemScriptData, GetItemScriptResponse, PostScriptData, PostScriptResponse, GetScriptByPathData, GetScriptByPathResponse, DeleteScriptByPathData, DeleteScriptByPathResponse, PutScriptByPathData, PutScriptByPathResponse, PutScriptByPathRenameData, PutScriptByPathRenameResponse, PostScriptFolderData, PostScriptFolderResponse, GetScriptFolderByPathData, GetScriptFolderByPathResponse, DeleteScriptFolderByPathData, DeleteScriptFolderByPathResponse, GetTreeScriptAncestorsData, GetTreeScriptAncestorsResponse, GetTreeScriptChildrenData, GetTreeScriptChildrenResponse, GetTreeScriptRootData, GetTreeScriptRootResponse, GetSearcherData, GetSearcherResponse, GetSearcherBySearcherNameQueryData, GetSearcherBySearcherNameQueryResponse, GetSecurityConfigurationResponse, PostSecurityForgotPasswordData, PostSecurityForgotPasswordResponse, PostSecurityForgotPasswordResetData, PostSecurityForgotPasswordResetResponse, PostSecurityForgotPasswordVerifyData, PostSecurityForgotPasswordVerifyResponse, GetSegmentData, GetSegmentResponse, GetServerConfigurationResponse, GetServerInformationResponse, GetServerStatusResponse, GetServerTroubleshootingResponse, GetServerUpgradeCheckResponse, GetItemStaticFileData, GetItemStaticFileResponse, GetTreeStaticFileAncestorsData, GetTreeStaticFileAncestorsResponse, GetTreeStaticFileChildrenData, GetTreeStaticFileChildrenResponse, GetTreeStaticFileRootData, GetTreeStaticFileRootResponse, GetItemStylesheetData, GetItemStylesheetResponse, PostStylesheetData, PostStylesheetResponse, GetStylesheetByPathData, GetStylesheetByPathResponse, DeleteStylesheetByPathData, DeleteStylesheetByPathResponse, PutStylesheetByPathData, PutStylesheetByPathResponse, PutStylesheetByPathRenameData, PutStylesheetByPathRenameResponse, PostStylesheetFolderData, PostStylesheetFolderResponse, GetStylesheetFolderByPathData, GetStylesheetFolderByPathResponse, DeleteStylesheetFolderByPathData, DeleteStylesheetFolderByPathResponse, GetTreeStylesheetAncestorsData, GetTreeStylesheetAncestorsResponse, GetTreeStylesheetChildrenData, GetTreeStylesheetChildrenResponse, GetTreeStylesheetRootData, GetTreeStylesheetRootResponse, GetTagData, GetTagResponse, GetTelemetryData, GetTelemetryResponse, GetTelemetryLevelResponse, PostTelemetryLevelData, PostTelemetryLevelResponse, GetItemTemplateData, GetItemTemplateResponse, GetItemTemplateSearchData, GetItemTemplateSearchResponse, PostTemplateData, PostTemplateResponse, GetTemplateByIdData, GetTemplateByIdResponse, DeleteTemplateByIdData, DeleteTemplateByIdResponse, PutTemplateByIdData, PutTemplateByIdResponse, GetTemplateConfigurationResponse, PostTemplateQueryExecuteData, PostTemplateQueryExecuteResponse, GetTemplateQuerySettingsResponse, GetTreeTemplateAncestorsData, GetTreeTemplateAncestorsResponse, GetTreeTemplateChildrenData, GetTreeTemplateChildrenResponse, GetTreeTemplateRootData, GetTreeTemplateRootResponse, PostTemporaryFileData, PostTemporaryFileResponse, GetTemporaryFileByIdData, GetTemporaryFileByIdResponse, DeleteTemporaryFileByIdData, DeleteTemporaryFileByIdResponse, GetTemporaryFileConfigurationResponse, PostUpgradeAuthorizeResponse, GetUpgradeSettingsResponse, GetFilterUserData, GetFilterUserResponse, GetItemUserData, GetItemUserResponse, PostUserData, PostUserResponse, DeleteUserData, DeleteUserResponse, GetUserData, GetUserResponse, GetUserByIdData, GetUserByIdResponse, DeleteUserByIdData, DeleteUserByIdResponse, PutUserByIdData, PutUserByIdResponse, GetUserById2FaData, GetUserById2FaResponse, DeleteUserById2FaByProviderNameData, DeleteUserById2FaByProviderNameResponse, GetUserByIdCalculateStartNodesData, GetUserByIdCalculateStartNodesResponse, PostUserByIdChangePasswordData, PostUserByIdChangePasswordResponse, PostUserByIdClientCredentialsData, PostUserByIdClientCredentialsResponse, GetUserByIdClientCredentialsData, GetUserByIdClientCredentialsResponse, DeleteUserByIdClientCredentialsByClientIdData, DeleteUserByIdClientCredentialsByClientIdResponse, PostUserByIdResetPasswordData, PostUserByIdResetPasswordResponse, DeleteUserAvatarByIdData, DeleteUserAvatarByIdResponse, PostUserAvatarByIdData, PostUserAvatarByIdResponse, GetUserConfigurationResponse, GetUserCurrentResponse, GetUserCurrent2FaResponse, DeleteUserCurrent2FaByProviderNameData, DeleteUserCurrent2FaByProviderNameResponse, PostUserCurrent2FaByProviderNameData, PostUserCurrent2FaByProviderNameResponse, GetUserCurrent2FaByProviderNameData, GetUserCurrent2FaByProviderNameResponse, PostUserCurrentAvatarData, PostUserCurrentAvatarResponse, PostUserCurrentChangePasswordData, PostUserCurrentChangePasswordResponse, GetUserCurrentConfigurationResponse, GetUserCurrentLoginProvidersResponse, GetUserCurrentPermissionsData, GetUserCurrentPermissionsResponse, GetUserCurrentPermissionsDocumentData, GetUserCurrentPermissionsDocumentResponse, GetUserCurrentPermissionsMediaData, GetUserCurrentPermissionsMediaResponse, PostUserDisableData, PostUserDisableResponse, PostUserEnableData, PostUserEnableResponse, PostUserInviteData, PostUserInviteResponse, PostUserInviteCreatePasswordData, PostUserInviteCreatePasswordResponse, PostUserInviteResendData, PostUserInviteResendResponse, PostUserInviteVerifyData, PostUserInviteVerifyResponse, PostUserSetUserGroupsData, PostUserSetUserGroupsResponse, PostUserUnlockData, PostUserUnlockResponse, PostUserDataData, PostUserDataResponse, GetUserDataData, GetUserDataResponse, PutUserDataData, PutUserDataResponse, GetUserDataByIdData, GetUserDataByIdResponse, GetFilterUserGroupData, GetFilterUserGroupResponse, GetItemUserGroupData, GetItemUserGroupResponse, DeleteUserGroupData, DeleteUserGroupResponse, PostUserGroupData, PostUserGroupResponse, GetUserGroupData, GetUserGroupResponse, GetUserGroupByIdData, GetUserGroupByIdResponse, DeleteUserGroupByIdData, DeleteUserGroupByIdResponse, PutUserGroupByIdData, PutUserGroupByIdResponse, DeleteUserGroupByIdUsersData, DeleteUserGroupByIdUsersResponse, PostUserGroupByIdUsersData, PostUserGroupByIdUsersResponse, GetItemWebhookData, GetItemWebhookResponse, GetWebhookData, GetWebhookResponse, PostWebhookData, PostWebhookResponse, GetWebhookByIdData, GetWebhookByIdResponse, DeleteWebhookByIdData, DeleteWebhookByIdResponse, PutWebhookByIdData, PutWebhookByIdResponse, GetWebhookByIdLogsData, GetWebhookByIdLogsResponse, GetWebhookEventsData, GetWebhookEventsResponse, GetWebhookLogsData, GetWebhookLogsResponse } from './types.gen';
+import type { GetCultureData, GetCultureResponse, PostDataTypeData, PostDataTypeResponse, GetDataTypeByIdData, GetDataTypeByIdResponse, DeleteDataTypeByIdData, DeleteDataTypeByIdResponse, PutDataTypeByIdData, PutDataTypeByIdResponse, PostDataTypeByIdCopyData, PostDataTypeByIdCopyResponse, GetDataTypeByIdIsUsedData, GetDataTypeByIdIsUsedResponse, PutDataTypeByIdMoveData, PutDataTypeByIdMoveResponse, GetDataTypeByIdReferencesData, GetDataTypeByIdReferencesResponse, GetDataTypeConfigurationResponse, PostDataTypeFolderData, PostDataTypeFolderResponse, GetDataTypeFolderByIdData, GetDataTypeFolderByIdResponse, DeleteDataTypeFolderByIdData, DeleteDataTypeFolderByIdResponse, PutDataTypeFolderByIdData, PutDataTypeFolderByIdResponse, GetFilterDataTypeData, GetFilterDataTypeResponse, GetItemDataTypeData, GetItemDataTypeResponse, GetItemDataTypeSearchData, GetItemDataTypeSearchResponse, GetTreeDataTypeAncestorsData, GetTreeDataTypeAncestorsResponse, GetTreeDataTypeChildrenData, GetTreeDataTypeChildrenResponse, GetTreeDataTypeRootData, GetTreeDataTypeRootResponse, GetDictionaryData, GetDictionaryResponse, PostDictionaryData, PostDictionaryResponse, GetDictionaryByIdData, GetDictionaryByIdResponse, DeleteDictionaryByIdData, DeleteDictionaryByIdResponse, PutDictionaryByIdData, PutDictionaryByIdResponse, GetDictionaryByIdExportData, GetDictionaryByIdExportResponse, PutDictionaryByIdMoveData, PutDictionaryByIdMoveResponse, PostDictionaryImportData, PostDictionaryImportResponse, GetItemDictionaryData, GetItemDictionaryResponse, GetTreeDictionaryAncestorsData, GetTreeDictionaryAncestorsResponse, GetTreeDictionaryChildrenData, GetTreeDictionaryChildrenResponse, GetTreeDictionaryRootData, GetTreeDictionaryRootResponse, GetCollectionDocumentByIdData, GetCollectionDocumentByIdResponse, PostDocumentData, PostDocumentResponse, GetDocumentByIdData, GetDocumentByIdResponse, DeleteDocumentByIdData, DeleteDocumentByIdResponse, PutDocumentByIdData, PutDocumentByIdResponse, GetDocumentByIdAuditLogData, GetDocumentByIdAuditLogResponse, PostDocumentByIdCopyData, PostDocumentByIdCopyResponse, GetDocumentByIdDomainsData, GetDocumentByIdDomainsResponse, PutDocumentByIdDomainsData, PutDocumentByIdDomainsResponse, PutDocumentByIdMoveData, PutDocumentByIdMoveResponse, PutDocumentByIdMoveToRecycleBinData, PutDocumentByIdMoveToRecycleBinResponse, GetDocumentByIdNotificationsData, GetDocumentByIdNotificationsResponse, PutDocumentByIdNotificationsData, PutDocumentByIdNotificationsResponse, PostDocumentByIdPublicAccessData, PostDocumentByIdPublicAccessResponse, DeleteDocumentByIdPublicAccessData, DeleteDocumentByIdPublicAccessResponse, GetDocumentByIdPublicAccessData, GetDocumentByIdPublicAccessResponse, PutDocumentByIdPublicAccessData, PutDocumentByIdPublicAccessResponse, PutDocumentByIdPublishData, PutDocumentByIdPublishResponse, PutDocumentByIdPublishWithDescendantsData, PutDocumentByIdPublishWithDescendantsResponse, GetDocumentByIdPublishedData, GetDocumentByIdPublishedResponse, GetDocumentByIdReferencedByData, GetDocumentByIdReferencedByResponse, GetDocumentByIdReferencedDescendantsData, GetDocumentByIdReferencedDescendantsResponse, PutDocumentByIdUnpublishData, PutDocumentByIdUnpublishResponse, PutDocumentByIdValidateData, PutDocumentByIdValidateResponse, PutUmbracoManagementApiV11DocumentByIdValidate11Data, PutUmbracoManagementApiV11DocumentByIdValidate11Response, GetDocumentAreReferencedData, GetDocumentAreReferencedResponse, GetDocumentConfigurationResponse, PutDocumentSortData, PutDocumentSortResponse, GetDocumentUrlsData, GetDocumentUrlsResponse, PostDocumentValidateData, PostDocumentValidateResponse, GetItemDocumentData, GetItemDocumentResponse, GetItemDocumentSearchData, GetItemDocumentSearchResponse, DeleteRecycleBinDocumentResponse, DeleteRecycleBinDocumentByIdData, DeleteRecycleBinDocumentByIdResponse, GetRecycleBinDocumentByIdOriginalParentData, GetRecycleBinDocumentByIdOriginalParentResponse, PutRecycleBinDocumentByIdRestoreData, PutRecycleBinDocumentByIdRestoreResponse, GetRecycleBinDocumentChildrenData, GetRecycleBinDocumentChildrenResponse, GetRecycleBinDocumentRootData, GetRecycleBinDocumentRootResponse, GetTreeDocumentAncestorsData, GetTreeDocumentAncestorsResponse, GetTreeDocumentChildrenData, GetTreeDocumentChildrenResponse, GetTreeDocumentRootData, GetTreeDocumentRootResponse, PostDocumentBlueprintData, PostDocumentBlueprintResponse, GetDocumentBlueprintByIdData, GetDocumentBlueprintByIdResponse, DeleteDocumentBlueprintByIdData, DeleteDocumentBlueprintByIdResponse, PutDocumentBlueprintByIdData, PutDocumentBlueprintByIdResponse, PutDocumentBlueprintByIdMoveData, PutDocumentBlueprintByIdMoveResponse, PostDocumentBlueprintFolderData, PostDocumentBlueprintFolderResponse, GetDocumentBlueprintFolderByIdData, GetDocumentBlueprintFolderByIdResponse, DeleteDocumentBlueprintFolderByIdData, DeleteDocumentBlueprintFolderByIdResponse, PutDocumentBlueprintFolderByIdData, PutDocumentBlueprintFolderByIdResponse, PostDocumentBlueprintFromDocumentData, PostDocumentBlueprintFromDocumentResponse, GetItemDocumentBlueprintData, GetItemDocumentBlueprintResponse, GetTreeDocumentBlueprintAncestorsData, GetTreeDocumentBlueprintAncestorsResponse, GetTreeDocumentBlueprintChildrenData, GetTreeDocumentBlueprintChildrenResponse, GetTreeDocumentBlueprintRootData, GetTreeDocumentBlueprintRootResponse, PostDocumentTypeData, PostDocumentTypeResponse, GetDocumentTypeByIdData, GetDocumentTypeByIdResponse, DeleteDocumentTypeByIdData, DeleteDocumentTypeByIdResponse, PutDocumentTypeByIdData, PutDocumentTypeByIdResponse, GetDocumentTypeByIdAllowedChildrenData, GetDocumentTypeByIdAllowedChildrenResponse, GetDocumentTypeByIdBlueprintData, GetDocumentTypeByIdBlueprintResponse, GetDocumentTypeByIdCompositionReferencesData, GetDocumentTypeByIdCompositionReferencesResponse, PostDocumentTypeByIdCopyData, PostDocumentTypeByIdCopyResponse, GetDocumentTypeByIdExportData, GetDocumentTypeByIdExportResponse, PutDocumentTypeByIdImportData, PutDocumentTypeByIdImportResponse, PutDocumentTypeByIdMoveData, PutDocumentTypeByIdMoveResponse, GetDocumentTypeAllowedAtRootData, GetDocumentTypeAllowedAtRootResponse, PostDocumentTypeAvailableCompositionsData, PostDocumentTypeAvailableCompositionsResponse, GetDocumentTypeConfigurationResponse, PostDocumentTypeFolderData, PostDocumentTypeFolderResponse, GetDocumentTypeFolderByIdData, GetDocumentTypeFolderByIdResponse, DeleteDocumentTypeFolderByIdData, DeleteDocumentTypeFolderByIdResponse, PutDocumentTypeFolderByIdData, PutDocumentTypeFolderByIdResponse, PostDocumentTypeImportData, PostDocumentTypeImportResponse, GetItemDocumentTypeData, GetItemDocumentTypeResponse, GetItemDocumentTypeSearchData, GetItemDocumentTypeSearchResponse, GetTreeDocumentTypeAncestorsData, GetTreeDocumentTypeAncestorsResponse, GetTreeDocumentTypeChildrenData, GetTreeDocumentTypeChildrenResponse, GetTreeDocumentTypeRootData, GetTreeDocumentTypeRootResponse, GetDocumentVersionData, GetDocumentVersionResponse, GetDocumentVersionByIdData, GetDocumentVersionByIdResponse, PutDocumentVersionByIdPreventCleanupData, PutDocumentVersionByIdPreventCleanupResponse, PostDocumentVersionByIdRollbackData, PostDocumentVersionByIdRollbackResponse, PostDynamicRootQueryData, PostDynamicRootQueryResponse, GetDynamicRootStepsResponse, GetHealthCheckGroupData, GetHealthCheckGroupResponse, GetHealthCheckGroupByNameData, GetHealthCheckGroupByNameResponse, PostHealthCheckGroupByNameCheckData, PostHealthCheckGroupByNameCheckResponse, PostHealthCheckExecuteActionData, PostHealthCheckExecuteActionResponse, GetHelpData, GetHelpResponse, GetImagingResizeUrlsData, GetImagingResizeUrlsResponse, GetImportAnalyzeData, GetImportAnalyzeResponse, GetIndexerData, GetIndexerResponse, GetIndexerByIndexNameData, GetIndexerByIndexNameResponse, PostIndexerByIndexNameRebuildData, PostIndexerByIndexNameRebuildResponse, GetInstallSettingsResponse, PostInstallSetupData, PostInstallSetupResponse, PostInstallValidateDatabaseData, PostInstallValidateDatabaseResponse, GetItemLanguageData, GetItemLanguageResponse, GetItemLanguageDefaultResponse, GetLanguageData, GetLanguageResponse, PostLanguageData, PostLanguageResponse, GetLanguageByIsoCodeData, GetLanguageByIsoCodeResponse, DeleteLanguageByIsoCodeData, DeleteLanguageByIsoCodeResponse, PutLanguageByIsoCodeData, PutLanguageByIsoCodeResponse, GetLogViewerLevelData, GetLogViewerLevelResponse, GetLogViewerLevelCountData, GetLogViewerLevelCountResponse, GetLogViewerLogData, GetLogViewerLogResponse, GetLogViewerMessageTemplateData, GetLogViewerMessageTemplateResponse, GetLogViewerSavedSearchData, GetLogViewerSavedSearchResponse, PostLogViewerSavedSearchData, PostLogViewerSavedSearchResponse, GetLogViewerSavedSearchByNameData, GetLogViewerSavedSearchByNameResponse, DeleteLogViewerSavedSearchByNameData, DeleteLogViewerSavedSearchByNameResponse, GetLogViewerValidateLogsSizeData, GetLogViewerValidateLogsSizeResponse, GetManifestManifestResponse, GetManifestManifestPrivateResponse, GetManifestManifestPublicResponse, GetCollectionMediaData, GetCollectionMediaResponse, GetItemMediaData, GetItemMediaResponse, GetItemMediaSearchData, GetItemMediaSearchResponse, PostMediaData, PostMediaResponse, GetMediaByIdData, GetMediaByIdResponse, DeleteMediaByIdData, DeleteMediaByIdResponse, PutMediaByIdData, PutMediaByIdResponse, GetMediaByIdAuditLogData, GetMediaByIdAuditLogResponse, PutMediaByIdMoveData, PutMediaByIdMoveResponse, PutMediaByIdMoveToRecycleBinData, PutMediaByIdMoveToRecycleBinResponse, GetMediaByIdReferencedByData, GetMediaByIdReferencedByResponse, GetMediaByIdReferencedDescendantsData, GetMediaByIdReferencedDescendantsResponse, PutMediaByIdValidateData, PutMediaByIdValidateResponse, GetMediaAreReferencedData, GetMediaAreReferencedResponse, GetMediaConfigurationResponse, PutMediaSortData, PutMediaSortResponse, GetMediaUrlsData, GetMediaUrlsResponse, PostMediaValidateData, PostMediaValidateResponse, DeleteRecycleBinMediaResponse, DeleteRecycleBinMediaByIdData, DeleteRecycleBinMediaByIdResponse, GetRecycleBinMediaByIdOriginalParentData, GetRecycleBinMediaByIdOriginalParentResponse, PutRecycleBinMediaByIdRestoreData, PutRecycleBinMediaByIdRestoreResponse, GetRecycleBinMediaChildrenData, GetRecycleBinMediaChildrenResponse, GetRecycleBinMediaRootData, GetRecycleBinMediaRootResponse, GetTreeMediaAncestorsData, GetTreeMediaAncestorsResponse, GetTreeMediaChildrenData, GetTreeMediaChildrenResponse, GetTreeMediaRootData, GetTreeMediaRootResponse, GetItemMediaTypeData, GetItemMediaTypeResponse, GetItemMediaTypeAllowedData, GetItemMediaTypeAllowedResponse, GetItemMediaTypeFoldersData, GetItemMediaTypeFoldersResponse, GetItemMediaTypeSearchData, GetItemMediaTypeSearchResponse, PostMediaTypeData, PostMediaTypeResponse, GetMediaTypeByIdData, GetMediaTypeByIdResponse, DeleteMediaTypeByIdData, DeleteMediaTypeByIdResponse, PutMediaTypeByIdData, PutMediaTypeByIdResponse, GetMediaTypeByIdAllowedChildrenData, GetMediaTypeByIdAllowedChildrenResponse, GetMediaTypeByIdCompositionReferencesData, GetMediaTypeByIdCompositionReferencesResponse, PostMediaTypeByIdCopyData, PostMediaTypeByIdCopyResponse, GetMediaTypeByIdExportData, GetMediaTypeByIdExportResponse, PutMediaTypeByIdImportData, PutMediaTypeByIdImportResponse, PutMediaTypeByIdMoveData, PutMediaTypeByIdMoveResponse, GetMediaTypeAllowedAtRootData, GetMediaTypeAllowedAtRootResponse, PostMediaTypeAvailableCompositionsData, PostMediaTypeAvailableCompositionsResponse, GetMediaTypeConfigurationResponse, PostMediaTypeFolderData, PostMediaTypeFolderResponse, GetMediaTypeFolderByIdData, GetMediaTypeFolderByIdResponse, DeleteMediaTypeFolderByIdData, DeleteMediaTypeFolderByIdResponse, PutMediaTypeFolderByIdData, PutMediaTypeFolderByIdResponse, PostMediaTypeImportData, PostMediaTypeImportResponse, GetTreeMediaTypeAncestorsData, GetTreeMediaTypeAncestorsResponse, GetTreeMediaTypeChildrenData, GetTreeMediaTypeChildrenResponse, GetTreeMediaTypeRootData, GetTreeMediaTypeRootResponse, GetFilterMemberData, GetFilterMemberResponse, GetItemMemberData, GetItemMemberResponse, GetItemMemberSearchData, GetItemMemberSearchResponse, PostMemberData, PostMemberResponse, GetMemberByIdData, GetMemberByIdResponse, DeleteMemberByIdData, DeleteMemberByIdResponse, PutMemberByIdData, PutMemberByIdResponse, GetMemberByIdReferencedByData, GetMemberByIdReferencedByResponse, GetMemberByIdReferencedDescendantsData, GetMemberByIdReferencedDescendantsResponse, PutMemberByIdValidateData, PutMemberByIdValidateResponse, GetMemberAreReferencedData, GetMemberAreReferencedResponse, GetMemberConfigurationResponse, PostMemberValidateData, PostMemberValidateResponse, GetItemMemberGroupData, GetItemMemberGroupResponse, GetMemberGroupData, GetMemberGroupResponse, PostMemberGroupData, PostMemberGroupResponse, GetMemberGroupByIdData, GetMemberGroupByIdResponse, DeleteMemberGroupByIdData, DeleteMemberGroupByIdResponse, PutMemberGroupByIdData, PutMemberGroupByIdResponse, GetTreeMemberGroupRootData, GetTreeMemberGroupRootResponse, GetItemMemberTypeData, GetItemMemberTypeResponse, GetItemMemberTypeSearchData, GetItemMemberTypeSearchResponse, PostMemberTypeData, PostMemberTypeResponse, GetMemberTypeByIdData, GetMemberTypeByIdResponse, DeleteMemberTypeByIdData, DeleteMemberTypeByIdResponse, PutMemberTypeByIdData, PutMemberTypeByIdResponse, GetMemberTypeByIdCompositionReferencesData, GetMemberTypeByIdCompositionReferencesResponse, PostMemberTypeByIdCopyData, PostMemberTypeByIdCopyResponse, PostMemberTypeAvailableCompositionsData, PostMemberTypeAvailableCompositionsResponse, GetMemberTypeConfigurationResponse, GetTreeMemberTypeRootData, GetTreeMemberTypeRootResponse, PostModelsBuilderBuildResponse, GetModelsBuilderDashboardResponse, GetModelsBuilderStatusResponse, GetObjectTypesData, GetObjectTypesResponse, GetOembedQueryData, GetOembedQueryResponse, PostPackageByNameRunMigrationData, PostPackageByNameRunMigrationResponse, GetPackageConfigurationResponse, GetPackageCreatedData, GetPackageCreatedResponse, PostPackageCreatedData, PostPackageCreatedResponse, GetPackageCreatedByIdData, GetPackageCreatedByIdResponse, DeletePackageCreatedByIdData, DeletePackageCreatedByIdResponse, PutPackageCreatedByIdData, PutPackageCreatedByIdResponse, GetPackageCreatedByIdDownloadData, GetPackageCreatedByIdDownloadResponse, GetPackageMigrationStatusData, GetPackageMigrationStatusResponse, GetItemPartialViewData, GetItemPartialViewResponse, PostPartialViewData, PostPartialViewResponse, GetPartialViewByPathData, GetPartialViewByPathResponse, DeletePartialViewByPathData, DeletePartialViewByPathResponse, PutPartialViewByPathData, PutPartialViewByPathResponse, PutPartialViewByPathRenameData, PutPartialViewByPathRenameResponse, PostPartialViewFolderData, PostPartialViewFolderResponse, GetPartialViewFolderByPathData, GetPartialViewFolderByPathResponse, DeletePartialViewFolderByPathData, DeletePartialViewFolderByPathResponse, GetPartialViewSnippetData, GetPartialViewSnippetResponse, GetPartialViewSnippetByIdData, GetPartialViewSnippetByIdResponse, GetTreePartialViewAncestorsData, GetTreePartialViewAncestorsResponse, GetTreePartialViewChildrenData, GetTreePartialViewChildrenResponse, GetTreePartialViewRootData, GetTreePartialViewRootResponse, DeletePreviewResponse, PostPreviewResponse, GetProfilingStatusResponse, PutProfilingStatusData, PutProfilingStatusResponse, GetPropertyTypeIsUsedData, GetPropertyTypeIsUsedResponse, PostPublishedCacheRebuildResponse, GetPublishedCacheRebuildStatusResponse, PostPublishedCacheReloadResponse, GetRedirectManagementData, GetRedirectManagementResponse, GetRedirectManagementByIdData, GetRedirectManagementByIdResponse, DeleteRedirectManagementByIdData, DeleteRedirectManagementByIdResponse, GetRedirectManagementStatusResponse, PostRedirectManagementStatusData, PostRedirectManagementStatusResponse, GetRelationByRelationTypeIdData, GetRelationByRelationTypeIdResponse, GetItemRelationTypeData, GetItemRelationTypeResponse, GetRelationTypeData, GetRelationTypeResponse, GetRelationTypeByIdData, GetRelationTypeByIdResponse, GetItemScriptData, GetItemScriptResponse, PostScriptData, PostScriptResponse, GetScriptByPathData, GetScriptByPathResponse, DeleteScriptByPathData, DeleteScriptByPathResponse, PutScriptByPathData, PutScriptByPathResponse, PutScriptByPathRenameData, PutScriptByPathRenameResponse, PostScriptFolderData, PostScriptFolderResponse, GetScriptFolderByPathData, GetScriptFolderByPathResponse, DeleteScriptFolderByPathData, DeleteScriptFolderByPathResponse, GetTreeScriptAncestorsData, GetTreeScriptAncestorsResponse, GetTreeScriptChildrenData, GetTreeScriptChildrenResponse, GetTreeScriptRootData, GetTreeScriptRootResponse, GetSearcherData, GetSearcherResponse, GetSearcherBySearcherNameQueryData, GetSearcherBySearcherNameQueryResponse, GetSecurityConfigurationResponse, PostSecurityForgotPasswordData, PostSecurityForgotPasswordResponse, PostSecurityForgotPasswordResetData, PostSecurityForgotPasswordResetResponse, PostSecurityForgotPasswordVerifyData, PostSecurityForgotPasswordVerifyResponse, GetSegmentData, GetSegmentResponse, GetServerConfigurationResponse, GetServerInformationResponse, GetServerStatusResponse, GetServerTroubleshootingResponse, GetServerUpgradeCheckResponse, GetItemStaticFileData, GetItemStaticFileResponse, GetTreeStaticFileAncestorsData, GetTreeStaticFileAncestorsResponse, GetTreeStaticFileChildrenData, GetTreeStaticFileChildrenResponse, GetTreeStaticFileRootData, GetTreeStaticFileRootResponse, GetItemStylesheetData, GetItemStylesheetResponse, PostStylesheetData, PostStylesheetResponse, GetStylesheetByPathData, GetStylesheetByPathResponse, DeleteStylesheetByPathData, DeleteStylesheetByPathResponse, PutStylesheetByPathData, PutStylesheetByPathResponse, PutStylesheetByPathRenameData, PutStylesheetByPathRenameResponse, PostStylesheetFolderData, PostStylesheetFolderResponse, GetStylesheetFolderByPathData, GetStylesheetFolderByPathResponse, DeleteStylesheetFolderByPathData, DeleteStylesheetFolderByPathResponse, GetTreeStylesheetAncestorsData, GetTreeStylesheetAncestorsResponse, GetTreeStylesheetChildrenData, GetTreeStylesheetChildrenResponse, GetTreeStylesheetRootData, GetTreeStylesheetRootResponse, GetTagData, GetTagResponse, GetTelemetryData, GetTelemetryResponse, GetTelemetryLevelResponse, PostTelemetryLevelData, PostTelemetryLevelResponse, GetItemTemplateData, GetItemTemplateResponse, GetItemTemplateSearchData, GetItemTemplateSearchResponse, PostTemplateData, PostTemplateResponse, GetTemplateByIdData, GetTemplateByIdResponse, DeleteTemplateByIdData, DeleteTemplateByIdResponse, PutTemplateByIdData, PutTemplateByIdResponse, GetTemplateConfigurationResponse, PostTemplateQueryExecuteData, PostTemplateQueryExecuteResponse, GetTemplateQuerySettingsResponse, GetTreeTemplateAncestorsData, GetTreeTemplateAncestorsResponse, GetTreeTemplateChildrenData, GetTreeTemplateChildrenResponse, GetTreeTemplateRootData, GetTreeTemplateRootResponse, PostTemporaryFileData, PostTemporaryFileResponse, GetTemporaryFileByIdData, GetTemporaryFileByIdResponse, DeleteTemporaryFileByIdData, DeleteTemporaryFileByIdResponse, GetTemporaryFileConfigurationResponse, PostUpgradeAuthorizeResponse, GetUpgradeSettingsResponse, GetFilterUserData, GetFilterUserResponse, GetItemUserData, GetItemUserResponse, PostUserData, PostUserResponse, DeleteUserData, DeleteUserResponse, GetUserData, GetUserResponse, GetUserByIdData, GetUserByIdResponse, DeleteUserByIdData, DeleteUserByIdResponse, PutUserByIdData, PutUserByIdResponse, GetUserById2FaData, GetUserById2FaResponse, DeleteUserById2FaByProviderNameData, DeleteUserById2FaByProviderNameResponse, GetUserByIdCalculateStartNodesData, GetUserByIdCalculateStartNodesResponse, PostUserByIdChangePasswordData, PostUserByIdChangePasswordResponse, PostUserByIdClientCredentialsData, PostUserByIdClientCredentialsResponse, GetUserByIdClientCredentialsData, GetUserByIdClientCredentialsResponse, DeleteUserByIdClientCredentialsByClientIdData, DeleteUserByIdClientCredentialsByClientIdResponse, PostUserByIdResetPasswordData, PostUserByIdResetPasswordResponse, DeleteUserAvatarByIdData, DeleteUserAvatarByIdResponse, PostUserAvatarByIdData, PostUserAvatarByIdResponse, GetUserConfigurationResponse, GetUserCurrentResponse, GetUserCurrent2FaResponse, DeleteUserCurrent2FaByProviderNameData, DeleteUserCurrent2FaByProviderNameResponse, PostUserCurrent2FaByProviderNameData, PostUserCurrent2FaByProviderNameResponse, GetUserCurrent2FaByProviderNameData, GetUserCurrent2FaByProviderNameResponse, PostUserCurrentAvatarData, PostUserCurrentAvatarResponse, PostUserCurrentChangePasswordData, PostUserCurrentChangePasswordResponse, GetUserCurrentConfigurationResponse, GetUserCurrentLoginProvidersResponse, GetUserCurrentPermissionsData, GetUserCurrentPermissionsResponse, GetUserCurrentPermissionsDocumentData, GetUserCurrentPermissionsDocumentResponse, GetUserCurrentPermissionsMediaData, GetUserCurrentPermissionsMediaResponse, PostUserDisableData, PostUserDisableResponse, PostUserEnableData, PostUserEnableResponse, PostUserInviteData, PostUserInviteResponse, PostUserInviteCreatePasswordData, PostUserInviteCreatePasswordResponse, PostUserInviteResendData, PostUserInviteResendResponse, PostUserInviteVerifyData, PostUserInviteVerifyResponse, PostUserSetUserGroupsData, PostUserSetUserGroupsResponse, PostUserUnlockData, PostUserUnlockResponse, PostUserDataData, PostUserDataResponse, GetUserDataData, GetUserDataResponse, PutUserDataData, PutUserDataResponse, GetUserDataByIdData, GetUserDataByIdResponse, GetFilterUserGroupData, GetFilterUserGroupResponse, GetItemUserGroupData, GetItemUserGroupResponse, DeleteUserGroupData, DeleteUserGroupResponse, PostUserGroupData, PostUserGroupResponse, GetUserGroupData, GetUserGroupResponse, GetUserGroupByIdData, GetUserGroupByIdResponse, DeleteUserGroupByIdData, DeleteUserGroupByIdResponse, PutUserGroupByIdData, PutUserGroupByIdResponse, DeleteUserGroupByIdUsersData, DeleteUserGroupByIdUsersResponse, PostUserGroupByIdUsersData, PostUserGroupByIdUsersResponse, GetItemWebhookData, GetItemWebhookResponse, GetWebhookData, GetWebhookResponse, PostWebhookData, PostWebhookResponse, GetWebhookByIdData, GetWebhookByIdResponse, DeleteWebhookByIdData, DeleteWebhookByIdResponse, PutWebhookByIdData, PutWebhookByIdResponse, GetWebhookByIdLogsData, GetWebhookByIdLogsResponse, GetWebhookEventsData, GetWebhookEventsResponse, GetWebhookLogsData, GetWebhookLogsResponse } from './types.gen';
export class CultureService {
/**
@@ -1491,6 +1491,7 @@ export class DocumentService {
/**
* @param data The data for the request.
* @param data.query
+ * @param data.trashed
* @param data.skip
* @param data.take
* @param data.parentId
@@ -1504,6 +1505,7 @@ export class DocumentService {
url: '/umbraco/management/api/v1/item/document/search',
query: {
query: data.query,
+ trashed: data.trashed,
skip: data.skip,
take: data.take,
parentId: data.parentId,
@@ -2504,6 +2506,7 @@ export class DocumentTypeService {
/**
* @param data The data for the request.
* @param data.query
+ * @param data.isElement
* @param data.skip
* @param data.take
* @returns unknown OK
@@ -2515,6 +2518,7 @@ export class DocumentTypeService {
url: '/umbraco/management/api/v1/item/document-type/search',
query: {
query: data.query,
+ isElement: data.isElement,
skip: data.skip,
take: data.take
},
@@ -3498,6 +3502,7 @@ export class MediaService {
/**
* @param data The data for the request.
* @param data.query
+ * @param data.trashed
* @param data.skip
* @param data.take
* @param data.parentId
@@ -3511,6 +3516,7 @@ export class MediaService {
url: '/umbraco/management/api/v1/item/media/search',
query: {
query: data.query,
+ trashed: data.trashed,
skip: data.skip,
take: data.take,
parentId: data.parentId,
@@ -4823,6 +4829,58 @@ export class MemberService {
});
}
+ /**
+ * @param data The data for the request.
+ * @param data.id
+ * @param data.skip
+ * @param data.take
+ * @returns unknown OK
+ * @throws ApiError
+ */
+ public static getMemberByIdReferencedBy(data: GetMemberByIdReferencedByData): CancelablePromise {
+ return __request(OpenAPI, {
+ method: 'GET',
+ url: '/umbraco/management/api/v1/member/{id}/referenced-by',
+ path: {
+ id: data.id
+ },
+ query: {
+ skip: data.skip,
+ take: data.take
+ },
+ errors: {
+ 401: 'The resource is protected and requires an authentication token',
+ 403: 'The authenticated user does not have access to this resource'
+ }
+ });
+ }
+
+ /**
+ * @param data The data for the request.
+ * @param data.id
+ * @param data.skip
+ * @param data.take
+ * @returns unknown OK
+ * @throws ApiError
+ */
+ public static getMemberByIdReferencedDescendants(data: GetMemberByIdReferencedDescendantsData): CancelablePromise {
+ return __request(OpenAPI, {
+ method: 'GET',
+ url: '/umbraco/management/api/v1/member/{id}/referenced-descendants',
+ path: {
+ id: data.id
+ },
+ query: {
+ skip: data.skip,
+ take: data.take
+ },
+ errors: {
+ 401: 'The resource is protected and requires an authentication token',
+ 403: 'The authenticated user does not have access to this resource'
+ }
+ });
+ }
+
/**
* @param data The data for the request.
* @param data.id
@@ -4849,6 +4907,30 @@ export class MemberService {
});
}
+ /**
+ * @param data The data for the request.
+ * @param data.id
+ * @param data.skip
+ * @param data.take
+ * @returns unknown OK
+ * @throws ApiError
+ */
+ public static getMemberAreReferenced(data: GetMemberAreReferencedData = {}): CancelablePromise {
+ return __request(OpenAPI, {
+ method: 'GET',
+ url: '/umbraco/management/api/v1/member/are-referenced',
+ query: {
+ id: data.id,
+ skip: data.skip,
+ take: data.take
+ },
+ errors: {
+ 401: 'The resource is protected and requires an authentication token',
+ 403: 'The authenticated user does not have access to this resource'
+ }
+ });
+ }
+
/**
* @returns unknown OK
* @throws ApiError
diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts
index 5e831e2da3..a1a97aa80d 100644
--- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts
+++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts
@@ -694,6 +694,7 @@ export type DocumentReferenceResponseModel = {
name?: (string) | null;
published?: (boolean) | null;
documentType: (TrackedReferenceDocumentTypeModel);
+ variants: Array<(DocumentVariantItemResponseModel)>;
};
export type DocumentResponseModel = {
@@ -1406,6 +1407,13 @@ export enum MemberKindModel {
API = 'Api'
}
+export type MemberReferenceResponseModel = {
+ $type: string;
+ id: string;
+ name?: (string) | null;
+ memberType: (TrackedReferenceMemberTypeModel);
+};
+
export type MemberResponseModel = {
values: Array<(MemberValueResponseModel)>;
variants: Array<(MemberVariantResponseModel)>;
@@ -1751,7 +1759,7 @@ export type PagedIndexResponseModel = {
export type PagedIReferenceResponseModel = {
total: number;
- items: Array<(DefaultReferenceResponseModel | DocumentReferenceResponseModel | MediaReferenceResponseModel)>;
+ items: Array<(DefaultReferenceResponseModel | DocumentReferenceResponseModel | MediaReferenceResponseModel | MemberReferenceResponseModel)>;
};
export type PagedLanguageResponseModel = {
@@ -2407,6 +2415,12 @@ export type TrackedReferenceMediaTypeModel = {
name?: (string) | null;
};
+export type TrackedReferenceMemberTypeModel = {
+ icon?: (string) | null;
+ alias?: (string) | null;
+ name?: (string) | null;
+};
+
export type UnknownTypePermissionPresentationModel = {
$type: string;
verbs: Array<(string)>;
@@ -3357,6 +3371,7 @@ export type GetItemDocumentSearchData = {
query?: string;
skip?: number;
take?: number;
+ trashed?: boolean;
};
export type GetItemDocumentSearchResponse = ((PagedModelDocumentItemResponseModel));
@@ -3640,6 +3655,7 @@ export type GetItemDocumentTypeData = {
export type GetItemDocumentTypeResponse = (Array<(DocumentTypeItemResponseModel)>);
export type GetItemDocumentTypeSearchData = {
+ isElement?: boolean;
query?: string;
skip?: number;
take?: number;
@@ -3927,6 +3943,7 @@ export type GetItemMediaSearchData = {
query?: string;
skip?: number;
take?: number;
+ trashed?: boolean;
};
export type GetItemMediaSearchResponse = ((PagedModelMediaItemResponseModel));
@@ -4308,6 +4325,22 @@ export type PutMemberByIdData = {
export type PutMemberByIdResponse = (string);
+export type GetMemberByIdReferencedByData = {
+ id: string;
+ skip?: number;
+ take?: number;
+};
+
+export type GetMemberByIdReferencedByResponse = ((PagedIReferenceResponseModel));
+
+export type GetMemberByIdReferencedDescendantsData = {
+ id: string;
+ skip?: number;
+ take?: number;
+};
+
+export type GetMemberByIdReferencedDescendantsResponse = ((PagedReferenceByIdModel));
+
export type PutMemberByIdValidateData = {
id: string;
requestBody?: (UpdateMemberRequestModel);
@@ -4315,6 +4348,14 @@ export type PutMemberByIdValidateData = {
export type PutMemberByIdValidateResponse = (string);
+export type GetMemberAreReferencedData = {
+ id?: Array<(string)>;
+ skip?: number;
+ take?: number;
+};
+
+export type GetMemberAreReferencedResponse = ((PagedReferenceByIdModel));
+
export type GetMemberConfigurationResponse = ((MemberConfigurationResponseModel));
export type PostMemberValidateData = {
diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/tracked-reference.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/tracked-reference.data.ts
index bbd02fb59b..1137b79c47 100644
--- a/src/Umbraco.Web.UI.Client/src/mocks/data/tracked-reference.data.ts
+++ b/src/Umbraco.Web.UI.Client/src/mocks/data/tracked-reference.data.ts
@@ -2,10 +2,11 @@ import type {
DefaultReferenceResponseModel,
DocumentReferenceResponseModel,
MediaReferenceResponseModel,
+ MemberReferenceResponseModel,
} from '@umbraco-cms/backoffice/external/backend-api';
export const items: Array<
- DefaultReferenceResponseModel | DocumentReferenceResponseModel | MediaReferenceResponseModel
+ DefaultReferenceResponseModel | DocumentReferenceResponseModel | MediaReferenceResponseModel | MemberReferenceResponseModel
> = [
{
$type: 'DocumentReferenceResponseModel',
@@ -17,6 +18,7 @@ export const items: Array<
icon: 'icon-document',
name: 'Simple Document Type',
},
+ variants: []
} satisfies DocumentReferenceResponseModel,
{
$type: 'DocumentReferenceResponseModel',
@@ -28,6 +30,7 @@ export const items: Array<
icon: 'icon-settings',
name: 'Image Block',
},
+ variants: []
} satisfies DocumentReferenceResponseModel,
{
$type: 'MediaReferenceResponseModel',
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/info-app/global-components/workspace-info-app-layout.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/info-app/global-components/workspace-info-app-layout.element.ts
index 3b6388299e..83a10560ff 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/info-app/global-components/workspace-info-app-layout.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/info-app/global-components/workspace-info-app-layout.element.ts
@@ -10,7 +10,9 @@ export class UmbWorkspaceInfoAppLayoutElement extends UmbLitElement {
return html`
-
+
+
+
`;
}
@@ -20,6 +22,10 @@ export class UmbWorkspaceInfoAppLayoutElement extends UmbLitElement {
uui-box {
--uui-box-default-padding: 0;
}
+
+ #container {
+ padding-left: var(--uui-size-space-4)
+ }
`,
];
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/reference/components/document-reference-table.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/reference/components/document-reference-table.element.ts
index 5e73b4c2e9..e1c2d80d7f 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/reference/components/document-reference-table.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/reference/components/document-reference-table.element.ts
@@ -6,6 +6,7 @@ import {
type UmbReferenceModel,
isDocumentReference,
isMediaReference,
+ isMemberReference,
isDefaultReference,
} from '@umbraco-cms/backoffice/relations';
@@ -61,6 +62,9 @@ export class UmbDocumentReferenceTableElement extends UmbLitElement {
if (isMediaReference(item)) {
return item.mediaType.icon ?? 'icon-picture';
}
+ if (isMemberReference(item)) {
+ return item.memberType.icon ?? 'icon-user';
+ }
if (isDefaultReference(item)) {
return item.icon ?? 'icon-document';
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/manifests.ts
index f1c4effd40..ec4f69d43c 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/manifests.ts
@@ -14,7 +14,7 @@ export const manifests: Array = [
{
type: 'entityAction',
alias: 'Umb.EntityAction.Media.Delete',
- name: 'Delete Media Entity Action ',
+ name: 'Delete Media Entity Action',
kind: 'deleteWithRelation',
forEntityTypes: [UMB_MEDIA_ENTITY_TYPE],
meta: {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/reference/info-app/media-references-workspace-info-app.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/reference/info-app/media-references-workspace-info-app.element.ts
index b1f46e6fd9..5a6151a8c4 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/reference/info-app/media-references-workspace-info-app.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/reference/info-app/media-references-workspace-info-app.element.ts
@@ -59,10 +59,6 @@ export class UmbMediaReferencesWorkspaceInfoAppElement extends UmbLitElement {
);
}
- protected override firstUpdated(): void {
- this.#getReferences();
- }
-
async #getReferences() {
if (!this.#mediaUnique) {
throw new Error('Media unique is required');
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/constants.ts
index c2e40ebfe7..cc342219ab 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/members/member/constants.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/constants.ts
@@ -4,6 +4,7 @@ export { UMB_MEMBER_VARIANT_CONTEXT } from './property-dataset-context/member-pr
export * from './collection/constants.js';
export * from './entity-actions/constants.js';
export * from './item/constants.js';
+export * from './reference/constants.js';
export * from './repository/constants.js';
export * from './search/constants.js';
export * from './workspace/constants.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/entity-actions/manifests.ts
index 933d3a563b..56072ee35b 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/members/member/entity-actions/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/entity-actions/manifests.ts
@@ -2,18 +2,20 @@ import { UMB_MEMBER_ITEM_REPOSITORY_ALIAS } from '../item/constants.js';
import { UMB_MEMBER_ENTITY_TYPE } from '../entity.js';
import { UMB_MEMBER_DETAIL_REPOSITORY_ALIAS } from '../repository/detail/manifests.js';
import { manifests as createManifests } from './create/manifests.js';
+import { UMB_MEMBER_REFERENCE_REPOSITORY_ALIAS } from '../reference/constants.js';
export const manifests: Array = [
{
type: 'entityAction',
- kind: 'delete',
alias: 'Umb.EntityAction.Member.Delete',
name: 'Delete Member Entity Action',
+ kind: 'deleteWithRelation',
forEntityTypes: [UMB_MEMBER_ENTITY_TYPE],
meta: {
- detailRepositoryAlias: UMB_MEMBER_DETAIL_REPOSITORY_ALIAS,
itemRepositoryAlias: UMB_MEMBER_ITEM_REPOSITORY_ALIAS,
+ detailRepositoryAlias: UMB_MEMBER_DETAIL_REPOSITORY_ALIAS,
+ referenceRepositoryAlias: UMB_MEMBER_REFERENCE_REPOSITORY_ALIAS,
},
},
...createManifests,
-];
+];
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/item/repository/types.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/item/repository/types.ts
index 660b7d84fc..2a105e08b4 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/members/member/item/repository/types.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/item/repository/types.ts
@@ -11,10 +11,19 @@ export interface UmbMemberItemModel {
icon: string;
collection: UmbReferenceByUnique | null;
};
- variants: Array;
+ variants: Array;
kind: UmbMemberKindType;
}
+export interface UmbMemberItemVariantModel {
+ name: string;
+ culture: string | null;
+}
+
+/**
+ * @deprecated Deprecated in favor of UmbMemberItemVariantModel. Will be removed in v17.0.0
+ * @interface UmbMemberVariantItemModel
+ */
export interface UmbMemberVariantItemModel {
name: string;
culture: string | null;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/manifests.ts
index 470f6503b3..7814ae8c8a 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/members/member/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/manifests.ts
@@ -5,6 +5,7 @@ import { manifests as memberPickerModalManifests } from './components/member-pic
import { manifests as menuItemManifests } from './menu-item/manifests.js';
import { manifests as pickerManifests } from './picker/manifests.js';
import { manifests as propertyEditorManifests } from './property-editor/manifests.js';
+import { manifests as referenceManifests } from './reference/manifests.js';
import { manifests as repositoryManifests } from './repository/manifests.js';
import { manifests as searchManifests } from './search/manifests.js';
import { manifests as workspaceManifests } from './workspace/manifests.js';
@@ -19,6 +20,7 @@ export const manifests: Array =
...menuItemManifests,
...pickerManifests,
...propertyEditorManifests,
+ ...referenceManifests,
...repositoryManifests,
...searchManifests,
...workspaceManifests,
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/constants.ts
new file mode 100644
index 0000000000..41a409dec1
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/constants.ts
@@ -0,0 +1 @@
+export * from './repository/constants.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/index.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/index.ts
new file mode 100644
index 0000000000..3d76f338dd
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/index.ts
@@ -0,0 +1 @@
+export * from './repository/index.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/info-app/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/info-app/manifests.ts
new file mode 100644
index 0000000000..7ebbb37e12
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/info-app/manifests.ts
@@ -0,0 +1,18 @@
+import { UMB_MEMBER_WORKSPACE_ALIAS } from '../../workspace/constants.js';
+import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
+
+export const manifests: Array = [
+ {
+ type: 'workspaceInfoApp',
+ name: 'Member References Workspace Info App',
+ alias: 'Umb.WorkspaceInfoApp.Member.References',
+ element: () => import('./member-references-workspace-info-app.element.js'),
+ weight: 90,
+ conditions: [
+ {
+ alias: UMB_WORKSPACE_CONDITION_ALIAS,
+ match: UMB_MEMBER_WORKSPACE_ALIAS,
+ },
+ ],
+ },
+];
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/info-app/member-references-workspace-info-app.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/info-app/member-references-workspace-info-app.element.ts
new file mode 100644
index 0000000000..23828e7026
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/info-app/member-references-workspace-info-app.element.ts
@@ -0,0 +1,161 @@
+import { UmbMemberReferenceRepository } from '../repository/index.js';
+import { UMB_MEMBER_WORKSPACE_CONTEXT } from '../../workspace/constants.js';
+import { css, customElement, html, nothing, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
+import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
+import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
+import type { UmbReferenceItemModel } from '@umbraco-cms/backoffice/relations';
+import type { UUIPaginationEvent } from '@umbraco-cms/backoffice/external/uui';
+import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity';
+
+@customElement('umb-member-references-workspace-info-app')
+export class UmbMemberReferencesWorkspaceInfoAppElement extends UmbLitElement {
+ #itemsPerPage = 10;
+
+ #referenceRepository;
+
+ @state()
+ private _currentPage = 1;
+
+ @state()
+ private _total = 0;
+
+ @state()
+ private _items?: Array = [];
+
+ @state()
+ private _loading = true;
+
+ #workspaceContext?: typeof UMB_MEMBER_WORKSPACE_CONTEXT.TYPE;
+ #memberUnique?: UmbEntityUnique;
+
+ constructor() {
+ super();
+ this.#referenceRepository = new UmbMemberReferenceRepository(this);
+
+ this.consumeContext(UMB_MEMBER_WORKSPACE_CONTEXT, (context) => {
+ this.#workspaceContext = context;
+ this.#observeMemberUnique();
+ });
+ }
+
+ #observeMemberUnique() {
+ this.observe(
+ this.#workspaceContext?.unique,
+ (unique) => {
+ if (!unique) {
+ this.#memberUnique = undefined;
+ this._items = [];
+ return;
+ }
+
+ if (this.#memberUnique === unique) {
+ return;
+ }
+
+ this.#memberUnique = unique;
+ this.#getReferences();
+ },
+ 'umbReferencesDocumentUniqueObserver',
+ );
+ }
+
+ async #getReferences() {
+ if (!this.#memberUnique) {
+ throw new Error('Member unique is required');
+ }
+
+ this._loading = true;
+
+ const { data } = await this.#referenceRepository.requestReferencedBy(
+ this.#memberUnique,
+ (this._currentPage - 1) * this.#itemsPerPage,
+ this.#itemsPerPage,
+ );
+
+ if (!data) return;
+
+ this._total = data.total;
+ this._items = data.items;
+
+ this._loading = false;
+ }
+
+ #onPageChange(event: UUIPaginationEvent) {
+ if (this._currentPage === event.target.current) return;
+ this._currentPage = event.target.current;
+
+ this.#getReferences();
+ }
+
+ override render() {
+ if (!this._items?.length) return nothing;
+ return html`
+
+ ${when(
+ this._loading,
+ () => html` `,
+ () => html`${this.#renderItems()} ${this.#renderPagination()}`,
+ )}
+
+ `;
+ }
+
+ #renderItems() {
+ if (!this._items) return;
+ return html`
+
+ ${repeat(
+ this._items,
+ (item) => item.unique,
+ (item) => html` `,
+ )}
+
+ `;
+ }
+
+ #renderPagination() {
+ if (!this._total) return nothing;
+
+ const totalPages = Math.ceil(this._total / this.#itemsPerPage);
+
+ if (totalPages <= 1) return nothing;
+
+ return html`
+
+ `;
+ }
+
+ static override styles = [
+ UmbTextStyles,
+ css`
+ :host {
+ display: contents;
+ }
+
+ uui-table-cell {
+ color: var(--uui-color-text-alt);
+ }
+
+ uui-pagination {
+ flex: 1;
+ display: inline-block;
+ }
+
+ .pagination {
+ display: flex;
+ justify-content: center;
+ margin-top: var(--uui-size-space-4);
+ }
+ `,
+ ];
+}
+
+export default UmbMemberReferencesWorkspaceInfoAppElement;
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-member-references-workspace-info-app': UmbMemberReferencesWorkspaceInfoAppElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/manifests.ts
new file mode 100644
index 0000000000..cad6350ec8
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/manifests.ts
@@ -0,0 +1,4 @@
+import { manifests as repositoryManifests } from './repository/manifests.js';
+import { manifests as infoAppManifests } from './info-app/manifests.js';
+
+export const manifests: Array = [...repositoryManifests, ...infoAppManifests];
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/constants.ts
new file mode 100644
index 0000000000..b715312e79
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/constants.ts
@@ -0,0 +1 @@
+export const UMB_MEMBER_REFERENCE_REPOSITORY_ALIAS = 'Umb.Repository.Member.Reference';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/index.ts
new file mode 100644
index 0000000000..25fce74586
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/index.ts
@@ -0,0 +1 @@
+export * from './member-reference.repository.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/manifests.ts
new file mode 100644
index 0000000000..745fc200fc
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/manifests.ts
@@ -0,0 +1,19 @@
+import { UMB_MEMBER_REFERENCE_REPOSITORY_ALIAS } from './constants.js';
+import { UMB_MANAGEMENT_API_DATA_SOURCE_ALIAS } from '@umbraco-cms/backoffice/repository';
+
+export const manifests: Array = [
+ {
+ type: 'repository',
+ alias: UMB_MEMBER_REFERENCE_REPOSITORY_ALIAS,
+ name: 'Member Reference Repository',
+ api: () => import('./member-reference.repository.js'),
+ },
+ {
+ type: 'dataSourceDataMapping',
+ alias: 'Umb.DataSourceDataMapping.ManagementApi.MemberReferenceResponse',
+ name: 'Member Reference Response Management Api Data Mapping',
+ api: () => import('./member-reference-response.management-api.mapping.js'),
+ forDataSource: UMB_MANAGEMENT_API_DATA_SOURCE_ALIAS,
+ forDataModel: 'MemberReferenceResponseModel',
+ },
+];
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/member-reference-response.management-api.mapping.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/member-reference-response.management-api.mapping.ts
new file mode 100644
index 0000000000..e6418503e8
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/member-reference-response.management-api.mapping.ts
@@ -0,0 +1,32 @@
+import { UMB_MEMBER_ENTITY_TYPE } from '../../entity.js';
+import type { UmbMemberReferenceModel } from './types.js';
+import type { MemberReferenceResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
+import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
+import type { UmbDataSourceDataMapping } from '@umbraco-cms/backoffice/repository';
+
+export class UmbMemberReferenceResponseManagementApiDataMapping
+ extends UmbControllerBase
+ implements UmbDataSourceDataMapping
+{
+ async map(data: MemberReferenceResponseModel): Promise {
+ return {
+ entityType: UMB_MEMBER_ENTITY_TYPE,
+ memberType: {
+ alias: data.memberType.alias,
+ icon: data.memberType.icon,
+ name: data.memberType.name,
+ },
+ name: data.name,
+ // TODO: this is a hardcoded array until the server can return the correct variants array
+ variants: [
+ {
+ culture: null,
+ name: data.name ?? '',
+ },
+ ],
+ unique: data.id,
+ };
+ }
+}
+
+export { UmbMemberReferenceResponseManagementApiDataMapping as api };
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/member-reference.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/member-reference.repository.ts
new file mode 100644
index 0000000000..4db055d64f
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/member-reference.repository.ts
@@ -0,0 +1,36 @@
+import { UmbMemberReferenceServerDataSource } from './member-reference.server.data.js';
+import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
+import type { UmbEntityReferenceRepository } from '@umbraco-cms/backoffice/relations';
+import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
+import type { UmbRepositoryResponse, UmbPagedModel } from '@umbraco-cms/backoffice/repository';
+
+export class UmbMemberReferenceRepository extends UmbControllerBase implements UmbEntityReferenceRepository {
+ #referenceSource: UmbMemberReferenceServerDataSource;
+
+ constructor(host: UmbControllerHost) {
+ super(host);
+ this.#referenceSource = new UmbMemberReferenceServerDataSource(this);
+ }
+
+ async requestReferencedBy(unique: string, skip = 0, take = 20) {
+ if (!unique) throw new Error(`unique is required`);
+ return this.#referenceSource.getReferencedBy(unique, skip, take);
+ }
+
+ async requestDescendantsWithReferences(unique: string, skip = 0, take = 20) {
+ if (!unique) throw new Error(`unique is required`);
+ return this.#referenceSource.getReferencedDescendants(unique, skip, take);
+ }
+
+ async requestAreReferenced(
+ uniques: Array,
+ skip?: number,
+ take?: number,
+ ): Promise>> {
+ if (!uniques || uniques.length === 0) throw new Error(`uniques is required`);
+ return this.#referenceSource.getAreReferenced(uniques, skip, take);
+ }
+}
+
+export default UmbMemberReferenceRepository;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/member-reference.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/member-reference.server.data.ts
new file mode 100644
index 0000000000..3ad6b90a6b
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/member-reference.server.data.ts
@@ -0,0 +1,121 @@
+import { UMB_MEMBER_ENTITY_TYPE } from '../../entity.js';
+import { MemberService } from '@umbraco-cms/backoffice/external/backend-api';
+import { UmbManagementApiDataMapper } from '@umbraco-cms/backoffice/repository';
+import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
+import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
+import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
+import type { UmbEntityReferenceDataSource, UmbReferenceItemModel } from '@umbraco-cms/backoffice/relations';
+import type { UmbPagedModel, UmbDataSourceResponse } from '@umbraco-cms/backoffice/repository';
+
+/**
+ * @class UmbMemberReferenceServerDataSource
+ * @implements {UmbEntityReferenceDataSource}
+ */
+export class UmbMemberReferenceServerDataSource extends UmbControllerBase implements UmbEntityReferenceDataSource {
+ #dataMapper = new UmbManagementApiDataMapper(this);
+
+ /**
+ * Fetches the item for the given unique from the server
+ * @param {string} unique - The unique identifier of the item to fetch
+ * @param {number} skip - The number of items to skip
+ * @param {number} take - The number of items to take
+ * @returns {Promise>>} - Items that are referenced by the given unique
+ * @memberof UmbMemberReferenceServerDataSource
+ */
+ async getReferencedBy(
+ unique: string,
+ skip: number = 0,
+ take: number = 20,
+ ): Promise>> {
+ const { data, error } = await tryExecuteAndNotify(
+ this,
+ MemberService.getMemberByIdReferencedBy({ id: unique, skip, take }),
+ );
+
+ if (data) {
+ const promises = data.items.map(async (item) => {
+ return this.#dataMapper.map({
+ forDataModel: item.$type,
+ data: item,
+ fallback: async () => {
+ return {
+ ...item,
+ unique: item.id,
+ entityType: 'unknown',
+ };
+ },
+ });
+ });
+
+ const items = await Promise.all(promises);
+
+ return { data: { items, total: data.total } };
+ }
+
+ return { data, error };
+ }
+
+ /**
+ * Checks if the items are referenced by other items
+ * @param {Array} uniques - The unique identifiers of the items to fetch
+ * @param {number} skip - The number of items to skip
+ * @param {number} take - The number of items to take
+ * @returns {Promise>>} - Items that are referenced by other items
+ * @memberof UmbMemberReferenceServerDataSource
+ */
+ async getAreReferenced(
+ uniques: Array,
+ skip: number = 0,
+ take: number = 20,
+ ): Promise>> {
+ const { data, error } = await tryExecuteAndNotify(
+ this,
+ MemberService.getMemberAreReferenced({ id: uniques, skip, take }),
+ );
+
+ if (data) {
+ const items: Array = data.items.map((item) => {
+ return {
+ unique: item.id,
+ entityType: UMB_MEMBER_ENTITY_TYPE,
+ };
+ });
+
+ return { data: { items, total: data.total } };
+ }
+
+ return { data, error };
+ }
+
+ /**
+ * Returns any descendants of the given unique that is referenced by other items
+ * @param {string} unique - The unique identifier of the item to fetch descendants for
+ * @param {number} skip - The number of items to skip
+ * @param {number} take - The number of items to take
+ * @returns {Promise>>} - Any descendants of the given unique that is referenced by other items
+ * @memberof UmbMemberReferenceServerDataSource
+ */
+ async getReferencedDescendants(
+ unique: string,
+ skip: number = 0,
+ take: number = 20,
+ ): Promise>> {
+ const { data, error } = await tryExecuteAndNotify(
+ this,
+ MemberService.getMemberByIdReferencedDescendants({ id: unique, skip, take }),
+ );
+
+ if (data) {
+ const items: Array = data.items.map((item) => {
+ return {
+ unique: item.id,
+ entityType: UMB_MEMBER_ENTITY_TYPE,
+ };
+ });
+
+ return { data: { items, total: data.total } };
+ }
+
+ return { data, error };
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/types.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/types.ts
new file mode 100644
index 0000000000..bc62433db5
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/reference/repository/types.ts
@@ -0,0 +1,14 @@
+import type { UmbMemberItemVariantModel } from '../../item/types.js';
+import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
+import type { TrackedReferenceMemberTypeModel } from '@umbraco-cms/backoffice/external/backend-api';
+
+export interface UmbMemberReferenceModel extends UmbEntityModel {
+ /**
+ * @deprecated use name on the variant array instead
+ * @type {(string | null)}
+ * @memberof UmbMemberReferenceModel
+ */
+ name?: string | null;
+ memberType: TrackedReferenceMemberTypeModel;
+ variants: Array;
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/views/member/member-workspace-view-member-info.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/views/member/member-workspace-view-member-info.element.ts
index 090f63cee1..4f6e625a1c 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/views/member/member-workspace-view-member-info.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/views/member/member-workspace-view-member-info.element.ts
@@ -8,7 +8,7 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/workspace';
import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace';
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
-import { UmbMemberTypeItemRepository } from '@umbraco-cms/backoffice/member-type';
+import { UMB_MEMBER_TYPE_ENTITY_TYPE, UmbMemberTypeItemRepository } from '@umbraco-cms/backoffice/member-type';
import { UMB_SECTION_USER_PERMISSION_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section';
import { UMB_SETTINGS_SECTION_ALIAS } from '@umbraco-cms/backoffice/settings';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
@@ -51,7 +51,7 @@ export class UmbMemberWorkspaceViewMemberInfoElement extends UmbLitElement imple
new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL)
.addAdditionalPath('member-type')
.onSetup(() => {
- return { data: { entityType: 'member-type', preset: {} } };
+ return { data: { entityType: UMB_MEMBER_TYPE_ENTITY_TYPE, preset: {} } };
})
.observeRouteBuilder((routeBuilder) => {
this._editMemberTypePath = routeBuilder({});
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/views/member/member-workspace-view-member.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/views/member/member-workspace-view-member.element.ts
index de613acd6f..b8a2ccb8cc 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/views/member/member-workspace-view-member.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/views/member/member-workspace-view-member.element.ts
@@ -198,6 +198,10 @@ export class UmbMemberWorkspaceViewMemberElement extends UmbLitElement implement
+
+
+
+
`;
}
@@ -269,6 +273,9 @@ export class UmbMemberWorkspaceViewMemberElement extends UmbLitElement implement
#left-column {
/* Is there a way to make the wrapped right column grow only when wrapped? */
flex: 9999 1 500px;
+ display: flex;
+ flex-direction: column;
+ gap: var(--uui-size-space-4);
}
#right-column {
flex: 1 1 350px;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/reference/types.ts b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/reference/types.ts
index 599ee7559b..2567a3ece5 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/reference/types.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/reference/types.ts
@@ -4,6 +4,7 @@ import type {
DefaultReferenceResponseModel,
DocumentReferenceResponseModel,
MediaReferenceResponseModel,
+ MemberReferenceResponseModel,
} from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbDataSourceResponse, UmbPagedModel, UmbRepositoryResponse } from '@umbraco-cms/backoffice/repository';
@@ -13,7 +14,8 @@ export interface UmbReferenceItemModel extends UmbEntityModel {}
export type UmbReferenceModel =
| DefaultReferenceResponseModel
| DocumentReferenceResponseModel
- | MediaReferenceResponseModel;
+ | MediaReferenceResponseModel
+ | MemberReferenceResponseModel;
export interface UmbEntityReferenceRepository extends UmbApi {
requestReferencedBy(
diff --git a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/utils.ts b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/utils.ts
index 6d1fab486c..cfe15b7fa7 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/utils.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/utils.ts
@@ -3,6 +3,7 @@ import type {
DefaultReferenceResponseModel,
DocumentReferenceResponseModel,
MediaReferenceResponseModel,
+ MemberReferenceResponseModel,
} from '@umbraco-cms/backoffice/external/backend-api';
/**
@@ -21,6 +22,14 @@ export function isMediaReference(item: UmbReferenceModel): item is MediaReferenc
return typeof (item as MediaReferenceResponseModel).mediaType !== 'undefined';
}
+/**
+ *
+ * @param item
+ */
+export function isMemberReference(item: UmbReferenceModel): item is MemberReferenceResponseModel {
+ return typeof (item as MemberReferenceResponseModel).memberType !== 'undefined';
+}
+
/**
*
* @param item
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs
index def5d3cafc..610bb87c2f 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs
@@ -225,20 +225,22 @@ public class RelationRepositoryTest : UmbracoIntegrationTest
RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMediaAlias);
var relatedContentRelType =
RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedDocumentAlias);
+ var relatedMemberRelType =
+ RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMemberAlias);
- parents = repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 0, 11, out totalRecords, new[] { relatedContentRelType.Id, relatedMediaRelType.Id }).ToList();
+ parents = repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 0, 11, out totalRecords, [relatedContentRelType.Id, relatedMediaRelType.Id, relatedMemberRelType.Id]).ToList();
Assert.AreEqual(6, totalRecords);
Assert.AreEqual(6, parents.Count);
- parents = repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 1, 11, out totalRecords, new[] { relatedContentRelType.Id, relatedMediaRelType.Id }).ToList();
+ parents = repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 1, 11, out totalRecords, [relatedContentRelType.Id, relatedMediaRelType.Id, relatedMemberRelType.Id]).ToList();
Assert.AreEqual(6, totalRecords);
Assert.AreEqual(0, parents.Count);
- parents = repository.GetPagedParentEntitiesByChildId(createdContent[0].Id, 0, 6, out totalRecords, new[] { relatedContentRelType.Id, relatedMediaRelType.Id }).ToList();
+ parents = repository.GetPagedParentEntitiesByChildId(createdContent[0].Id, 0, 6, out totalRecords, [relatedContentRelType.Id, relatedMediaRelType.Id, relatedMemberRelType.Id]).ToList();
Assert.AreEqual(3, totalRecords);
Assert.AreEqual(3, parents.Count);
- parents = repository.GetPagedParentEntitiesByChildId(createdContent[0].Id, 1, 6, out totalRecords, new[] { relatedContentRelType.Id, relatedMediaRelType.Id }).ToList();
+ parents = repository.GetPagedParentEntitiesByChildId(createdContent[0].Id, 1, 6, out totalRecords, [relatedContentRelType.Id, relatedMediaRelType.Id, relatedMemberRelType.Id]).ToList();
Assert.AreEqual(3, totalRecords);
Assert.AreEqual(0, parents.Count);
}
@@ -281,15 +283,15 @@ public class RelationRepositoryTest : UmbracoIntegrationTest
var repository = CreateRepository(ScopeProvider, out _);
// Get parent entities for child id
- var parents = repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 0, 6, out var totalRecords)
+ var parents = repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 0, 9, out var totalRecords)
.ToList();
- Assert.AreEqual(6, totalRecords);
- Assert.AreEqual(6, parents.Count);
+ Assert.AreEqual(9, totalRecords);
+ Assert.AreEqual(9, parents.Count);
// Add the next page
- parents.AddRange(repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 1, 6, out totalRecords));
- Assert.AreEqual(6, totalRecords);
- Assert.AreEqual(6, parents.Count);
+ parents.AddRange(repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 1, 9, out totalRecords));
+ Assert.AreEqual(9, totalRecords);
+ Assert.AreEqual(9, parents.Count);
var contentEntities = parents.OfType().ToList();
var mediaEntities = parents.OfType().ToList();
@@ -297,7 +299,7 @@ public class RelationRepositoryTest : UmbracoIntegrationTest
Assert.AreEqual(3, contentEntities.Count);
Assert.AreEqual(3, mediaEntities.Count);
- Assert.AreEqual(0, memberEntities.Count);
+ Assert.AreEqual(3, memberEntities.Count);
// only of a certain type
parents.AddRange(repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Media.GetGuid()));
@@ -307,20 +309,22 @@ public class RelationRepositoryTest : UmbracoIntegrationTest
Assert.AreEqual(3, totalRecords);
parents.AddRange(repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Member.GetGuid()));
- Assert.AreEqual(0, totalRecords);
+ Assert.AreEqual(3, totalRecords);
// Test getting relations of specified relation types
var relatedMediaRelType =
RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMediaAlias);
var relatedContentRelType =
RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedDocumentAlias);
+ var relatedMemberRelType =
+ RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMemberAlias);
- parents = repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 0, 6, out totalRecords, new[] { relatedContentRelType.Id, relatedMediaRelType.Id }).ToList();
- Assert.AreEqual(3, totalRecords);
- Assert.AreEqual(3, parents.Count);
+ parents = repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 0, 6, out totalRecords, [relatedContentRelType.Id, relatedMediaRelType.Id, relatedMemberRelType.Id]).ToList();
+ Assert.AreEqual(6, totalRecords);
+ Assert.AreEqual(6, parents.Count);
- parents = repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 1, 6, out totalRecords, new[] { relatedContentRelType.Id, relatedMediaRelType.Id }).ToList();
- Assert.AreEqual(3, totalRecords);
+ parents = repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 1, 6, out totalRecords, [relatedContentRelType.Id, relatedMediaRelType.Id, relatedMemberRelType.Id]).ToList();
+ Assert.AreEqual(6, totalRecords);
Assert.AreEqual(0, parents.Count);
}
}
@@ -368,6 +372,8 @@ public class RelationRepositoryTest : UmbracoIntegrationTest
RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMediaAlias);
var relatedContentRelType =
RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedDocumentAlias);
+ var relatedMemberRelType =
+ RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMemberAlias);
// Relate content to media
foreach (var content in createdContent)
@@ -387,6 +393,15 @@ public class RelationRepositoryTest : UmbracoIntegrationTest
}
}
+ // Relate content to member
+ foreach (var content in createdContent)
+ {
+ foreach (var member in createdMembers)
+ {
+ RelationService.Relate(content.Id, member.Id, relatedMemberRelType);
+ }
+ }
+
// Relate members to media
foreach (var member in createdMembers)
{
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationTypeRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationTypeRepositoryTest.cs
index 2fbf06bb6c..009a5efccd 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationTypeRepositoryTest.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationTypeRepositoryTest.cs
@@ -100,7 +100,7 @@ public class RelationTypeRepositoryTest : UmbracoIntegrationTest
var repository = CreateRepository(provider);
// Act
- var relationType = repository.Get(8) as IRelationTypeWithIsDependency;
+ var relationType = repository.Get(9) as IRelationTypeWithIsDependency;
// Assert
Assert.That(relationType, Is.Not.Null);
@@ -130,7 +130,7 @@ public class RelationTypeRepositoryTest : UmbracoIntegrationTest
Assert.That(relationTypes, Is.Not.Null);
Assert.That(relationTypes.Any(), Is.True);
Assert.That(relationTypes.Any(x => x == null), Is.False);
- Assert.That(relationTypes.Count(), Is.EqualTo(8));
+ Assert.That(relationTypes.Count(), Is.EqualTo(9));
}
}
@@ -165,7 +165,7 @@ public class RelationTypeRepositoryTest : UmbracoIntegrationTest
// Act
var exists = repository.Exists(3);
- var doesntExist = repository.Exists(9);
+ var doesntExist = repository.Exists(99);
// Assert
Assert.That(exists, Is.True);
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RelationServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RelationServiceTests.cs
index 06c39098ee..935915740b 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RelationServiceTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RelationServiceTests.cs
@@ -26,6 +26,10 @@ public class RelationServiceTests : UmbracoIntegrationTest
private IMediaService MediaService => GetRequiredService();
+ private IMemberTypeService MemberTypeService => GetRequiredService();
+
+ private IMemberService MemberService => GetRequiredService();
+
private IRelationService RelationService => GetRequiredService();
[Test]
@@ -115,6 +119,39 @@ public class RelationServiceTests : UmbracoIntegrationTest
Assert.AreEqual(6, entities.Count);
}
+ [Test]
+ public void Return_List_Of_Content_Items_Where_Member_Item_Referenced()
+ {
+ var memberType = MemberTypeBuilder.CreateSimpleMemberType("testMemberType", "Test Member Type");
+ MemberTypeService.Save(memberType);
+ var member = MemberBuilder.CreateSimpleMember(memberType, "Test Member", "test@test.com", "xxxxxxxx", "testMember");
+ MemberService.Save(member);
+
+ var ct = ContentTypeBuilder.CreateTextPageContentType("richTextTest");
+ ct.AllowedTemplates = Enumerable.Empty();
+ ContentTypeService.Save(ct);
+
+ void CreateContentWithMemberRefs()
+ {
+ var content = ContentBuilder.CreateTextpageContent(ct, "my content 2", -1);
+
+ // 'bodyText' is a property with a RTE property editor which we knows automatically tracks relations
+ content.Properties["bodyText"].SetValue(@"
");
+ ContentService.Save(content);
+ }
+
+ for (var i = 0; i < 6; i++)
+ {
+ CreateContentWithMemberRefs(); // create 6 content items referencing the same member
+ }
+
+ var relations = RelationService.GetByChildId(member.Id, Constants.Conventions.RelationTypes.RelatedMemberAlias).ToList();
+ Assert.AreEqual(6, relations.Count);
+
+ var entities = RelationService.GetParentEntitiesFromRelations(relations).ToList();
+ Assert.AreEqual(6, entities.Count);
+ }
+
[Test]
public void Can_Create_RelationType_Without_Name()
{
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackRelationsTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackRelationsTests.cs
index f992aa57b1..3b063ee9b0 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackRelationsTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackRelationsTests.cs
@@ -23,6 +23,10 @@ public class TrackRelationsTests : UmbracoIntegrationTestWithContent
private IMediaService MediaService => GetRequiredService();
+ private IMemberTypeService MemberTypeService => GetRequiredService();
+
+ private IMemberService MemberService => GetRequiredService();
+
private IRelationService RelationService => GetRequiredService();
// protected override void CustomTestSetup(IUmbracoBuilder builder)
@@ -42,6 +46,11 @@ public class TrackRelationsTests : UmbracoIntegrationTestWithContent
MediaService.Save(m1);
MediaService.Save(m2);
+ var memberType = MemberTypeBuilder.CreateSimpleMemberType("testMemberType", "Test Member Type");
+ MemberTypeService.Save(memberType);
+ var member = MemberBuilder.CreateSimpleMember(memberType, "Test Member", "test@test.com", "xxxxxxxx", "testMember");
+ MemberService.Save(member);
+
var template = TemplateBuilder.CreateTextPageTemplate();
FileService.SaveTemplate(template);
@@ -62,17 +71,24 @@ public class TrackRelationsTests : UmbracoIntegrationTestWithContent
hello
+
+
+
+
");
ContentService.Save(c2);
var relations = RelationService.GetByParentId(c2.Id).ToList();
- Assert.AreEqual(3, relations.Count);
+ Assert.AreEqual(4, relations.Count);
Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedMediaAlias, relations[0].RelationType.Alias);
Assert.AreEqual(m1.Id, relations[0].ChildId);
Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedMediaAlias, relations[1].RelationType.Alias);
Assert.AreEqual(m2.Id, relations[1].ChildId);
Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedDocumentAlias, relations[2].RelationType.Alias);
Assert.AreEqual(c1.Id, relations[2].ChildId);
+ Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedMemberAlias, relations[3].RelationType.Alias);
+ Assert.AreEqual(member.Id, relations[3].ChildId);
+
}
}
From eb5c57fb9429843d1b9466a339f83cd5f4785b31 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Niels=20Lyngs=C3=B8?=
Date: Fri, 28 Mar 2025 19:27:08 +0100
Subject: [PATCH 18/38] do not destroy instance
---
.../src/libs/context-api/provide/context-provider.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.ts
index 10ddd09b63..16d64ed11d 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.ts
@@ -103,8 +103,7 @@ export class UmbContextProvider
Date: Fri, 28 Mar 2025 20:27:26 +0100
Subject: [PATCH 19/38] enable changing data-path on an existing context
---
.../core/validation/controllers/validation.controller.ts | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts
index 6e497a22e7..d8231bc11d 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts
@@ -172,9 +172,6 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
setDataPath(dataPath: string): void {
if (this.#baseDataPath) {
if (this.#baseDataPath === dataPath) return;
- // Just fire an error, as I haven't made the right clean up jet. Or haven't thought about what should happen if it changes while already setup.
- // cause maybe all the messages should be removed as we are not interested in the old once any more. But then on the other side, some might be relevant as this is the same entity that changed its paths?
- throw new Error('Data path is already set, we do not support changing the context data-path as of now.');
}
if (!dataPath) {
this.#stopInheritance();
@@ -197,12 +194,12 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
this.#parent.removeValidator(this);
}
this.#parent = parent;
- this.#readyToSync();
this.messages.clear();
this.#localMessages = undefined;
this.#baseDataPath = dataPath;
+ this.#readyToSync();
// @deprecated - Will be removed in v.17
this.observe(
From e8b7d9daee245a02dafd471dbe66b441d04996dd Mon Sep 17 00:00:00 2001
From: Mads Rasmussen
Date: Mon, 31 Mar 2025 09:08:07 +0200
Subject: [PATCH 20/38] use uui-dialog-layout for all save + publishing dialogs
(#18871)
---
.../modals/save-modal/document-save-modal.element.ts | 4 ++--
.../modal/document-publish-with-descendants-modal.element.ts | 4 ++--
.../publish/modal/document-publish-modal.element.ts | 4 ++--
.../schedule-publish/modal/document-schedule-modal.element.ts | 4 ++--
.../unpublish/modal/document-unpublish-modal.element.ts | 4 ++--
5 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/save-modal/document-save-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/save-modal/document-save-modal.element.ts
index eeb910ea37..5b89bcfea9 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/save-modal/document-save-modal.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/save-modal/document-save-modal.element.ts
@@ -56,7 +56,7 @@ export class UmbDocumentSaveModalElement extends UmbModalBaseElement<
}
override render() {
- return html`
+ return html`
Choose which variants to be saved.
@@ -74,7 +74,7 @@ export class UmbDocumentSaveModalElement extends UmbModalBaseElement<
color="positive"
@click=${this.#submit}>
- `;
+ `;
}
static override styles = [
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.element.ts
index 6fb46c3812..56509ca263 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.element.ts
@@ -96,7 +96,7 @@ export class UmbDocumentPublishWithDescendantsModalElement extends UmbModalBaseE
}
override render() {
- return html`
+ return html`
${this._options.length === 1
? html`
-
`;
+ `;
}
static override styles = [
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.element.ts
index 0257004049..0ffd7f83dc 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.element.ts
@@ -88,7 +88,7 @@ export class UmbDocumentPublishModalElement extends UmbModalBaseElement<
}
override render() {
- return html`
+ return html`
Which variants would you like to publish?
@@ -107,7 +107,7 @@ export class UmbDocumentPublishModalElement extends UmbModalBaseElement<
?disabled=${this._hasNotSelectedMandatory}
@click=${this.#submit}>
- `;
+ `;
}
static override styles = [
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.element.ts
index 1b26cfdec1..8cac6664cc 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.element.ts
@@ -157,7 +157,7 @@ export class UmbDocumentScheduleModalElement extends UmbModalBaseElement<
}
override render() {
- return html`
+ return html`
${when(
this._options.length > 1,
@@ -184,7 +184,7 @@ export class UmbDocumentScheduleModalElement extends UmbModalBaseElement<
?disabled=${!this._selection.length || this._hasNotSelectedMandatory}
@click=${this.#submit}>
-
`;
+ `;
}
#renderOptions() {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.element.ts
index 359eac8ab4..8dcafc0edb 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.element.ts
@@ -144,7 +144,7 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
};
override render() {
- return html`
+ return html`
${!this._isInvariant
? html`
@@ -193,7 +193,7 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
color="warning"
@click=${this.#submit}>
-
`;
+ `;
}
static override styles = [
From 4d8406bb64b622fd1cda74d41c0dbe1839218379 Mon Sep 17 00:00:00 2001
From: Lan Nguyen Thuy
Date: Mon, 31 Mar 2025 11:33:31 +0700
Subject: [PATCH 21/38] fix fix select all checkbox in scheduled publishing
---
.../schedule-publish/modal/document-schedule-modal.element.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.element.ts
index 8cac6664cc..63d07f8f65 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.element.ts
@@ -72,6 +72,8 @@ export class UmbDocumentScheduleModalElement extends UmbModalBaseElement<
//Getting not published mandatory options — the options that are mandatory and not currently published.
const missingMandatoryOptions = this._options.filter(isNotPublishedMandatory);
this._hasNotSelectedMandatory = missingMandatoryOptions.some((option) => !selection.includes(option.unique));
+
+ this.requestUpdate();
},
'_selection',
);
From 0e3c8dab8f15ecd64c2f5bb7483ac1d858db5c6e Mon Sep 17 00:00:00 2001
From: Mads Rasmussen
Date: Mon, 31 Mar 2025 09:52:46 +0200
Subject: [PATCH 22/38] Unpublish Document: Align UX of referenced items with
trash and delete (#18860)
* Added member reference type model.
* Updated client-side types and sdk.
* Render member relations on the member info view.
* Add relation type for related member with migration.
* Extend tests for track relations to include member relations.
* Extend tests for relation repository.
* Extend tests for relation service.
* Addressed comments from Copilot review.
* Add relation notification to member deletion.
* Removed unused import.
* Updates from code review.
* make ref element globally available
* align naming
* use new reference list
* add interfaces for config
* export const
* Fixed failing integration tests.
* apply interface
* deprecate interface with wrong name
* fix import
* disable unpublish button when item or descendants are referenced
---------
Co-authored-by: Andy Butland
---
.../modal/document-unpublish-modal.element.ts | 85 +++++++++----------
.../document-reference-table.element.ts | 12 +++
...bulk-delete-with-relation-modal.element.ts | 6 +-
.../bulk-trash-with-relation-modal.element.ts | 6 +-
.../delete-with-relation-modal.element.ts | 5 +-
.../trash-with-relation-modal.element.ts | 6 +-
...action-modal-entity-references.element.ts} | 28 ++++--
...action-modal-entity-references.element.ts} | 10 ++-
.../relations/global-components/index.ts | 5 ++
.../relations/global-components/types.ts | 2 +
.../src/packages/relations/relations/index.ts | 3 +-
.../src/packages/relations/relations/types.ts | 1 +
12 files changed, 96 insertions(+), 73 deletions(-)
rename src/Umbraco.Web.UI.Client/src/packages/relations/relations/{entity-actions/local-components/confirm-action-entity-references.element.ts => global-components/confirm-action-modal-entity-references.element.ts} (89%)
rename src/Umbraco.Web.UI.Client/src/packages/relations/relations/{entity-actions/local-components/confirm-bulk-action-entity-references.element.ts => global-components/confirm-bulk-action-modal-entity-references.element.ts} (93%)
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/relations/relations/global-components/index.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/relations/relations/global-components/types.ts
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.element.ts
index 8dcafc0edb..8d68fcb438 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.element.ts
@@ -1,5 +1,5 @@
import { UmbDocumentVariantState, type UmbDocumentVariantOptionModel } from '../../../types.js';
-import { UmbDocumentReferenceRepository } from '../../../reference/index.js';
+import { UMB_DOCUMENT_ITEM_REPOSITORY_ALIAS, UMB_DOCUMENT_REFERENCE_REPOSITORY_ALIAS } from '../../../constants.js';
import { UMB_DOCUMENT_CONFIGURATION_CONTEXT } from '../../../global-contexts/index.js';
import type {
UmbDocumentUnpublishModalData,
@@ -9,6 +9,11 @@ import { css, customElement, html, nothing, state } from '@umbraco-cms/backoffic
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils';
+import type {
+ UmbConfirmActionModalEntityReferencesConfig,
+ UmbConfirmActionModalEntityReferencesElement,
+} from '@umbraco-cms/backoffice/relations';
+import type { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import '../../../modals/shared/document-variant-language-picker.element.js';
@@ -30,7 +35,6 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
UmbDocumentUnpublishModalValue
> {
protected readonly _selectionManager = new UmbSelectionManager(this);
- #referencesRepository = new UmbDocumentReferenceRepository(this);
@state()
_options: Array = [];
@@ -39,10 +43,7 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
_selection: Array = [];
@state()
- _hasReferences = false;
-
- @state()
- _hasUnpublishPermission = true;
+ _canUnpublish = true;
@state()
_hasInvalidSelection = true;
@@ -50,6 +51,9 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
@state()
_isInvariant = false;
+ @state()
+ _referencesConfig?: UmbConfirmActionModalEntityReferencesConfig;
+
#pickableFilter = (option: UmbDocumentVariantOptionModel) => {
if (!option.variant) {
return false;
@@ -58,7 +62,7 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
};
override firstUpdated() {
- this.#getReferences();
+ this.#configureReferences();
// If invariant, don't display the variant selection component.
if (this.data?.options.length === 1 && this.data.options[0].unique === 'invariant') {
@@ -70,6 +74,16 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
this.#configureSelectionManager();
}
+ #configureReferences() {
+ if (!this.data?.documentUnique) return;
+
+ this._referencesConfig = {
+ itemRepositoryAlias: UMB_DOCUMENT_ITEM_REPOSITORY_ALIAS,
+ referenceRepositoryAlias: UMB_DOCUMENT_REFERENCE_REPOSITORY_ALIAS,
+ unique: this.data.documentUnique,
+ };
+ }
+
async #configureSelectionManager() {
this._selectionManager.setMultiple(true);
this._selectionManager.setSelectable(true);
@@ -103,30 +117,8 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
);
}
- async #getReferences() {
- if (!this.data?.documentUnique) return;
-
- const { data, error } = await this.#referencesRepository.requestReferencedBy(this.data?.documentUnique, 0, 1);
-
- if (error) {
- console.error(error);
- return;
- }
-
- if (!data) return;
-
- this._hasReferences = data.total > 0;
-
- // If there are references, we also want to check if we are allowed to unpublish the document:
- if (this._hasReferences) {
- const documentConfigurationContext = await this.getContext(UMB_DOCUMENT_CONFIGURATION_CONTEXT);
- this._hasUnpublishPermission =
- (await documentConfigurationContext.getDocumentConfiguration())?.disableUnpublishWhenReferenced === false;
- }
- }
-
#submit() {
- if (this._hasUnpublishPermission) {
+ if (this._canUnpublish) {
const selection = this._isInvariant ? ['invariant'] : this._selection;
this.value = { selection };
this.modalContext?.submit();
@@ -139,6 +131,19 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
this.modalContext?.reject();
}
+ async #onReferencesChange(event: UmbChangeEvent) {
+ event.stopPropagation();
+ const target = event.target as UmbConfirmActionModalEntityReferencesElement;
+ const getReferencedByTotal = target.getTotalReferencedBy();
+ const descendantsWithReferencesTotal = target.getTotalDescendantsWithReferences();
+ const total = getReferencedByTotal + descendantsWithReferencesTotal;
+
+ if (total > 0) {
+ const context = await this.getContext(UMB_DOCUMENT_CONFIGURATION_CONTEXT);
+ this._canUnpublish = (await context.getDocumentConfiguration())?.disableUnpublishWhenReferenced === false;
+ }
+ }
+
private _requiredFilter = (variantOption: UmbDocumentVariantOptionModel): boolean => {
return variantOption.language.isMandatory && !this._selection.includes(variantOption.unique);
};
@@ -166,20 +171,10 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
- ${this.data?.documentUnique
- ? html`
-
- `
- : nothing}
- ${this._hasReferences
- ? html`
-
- This item or its descendants is being referenced. Unpublishing can lead to broken links on your website.
- Please take the appropriate actions.
-
- `
+ ${this._referencesConfig
+ ? html` `
: nothing}
@@ -187,7 +182,7 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
or element instead
+ * @class UmbDocumentReferenceTableElement
+ */
@customElement('umb-document-reference-table')
export class UmbDocumentReferenceTableElement extends UmbLitElement {
#documentReferenceRepository = new UmbDocumentReferenceRepository(this);
@@ -32,6 +37,13 @@ export class UmbDocumentReferenceTableElement extends UmbLitElement {
_errorMessage = '';
override firstUpdated() {
+ new UmbDeprecation({
+ removeInVersion: '17',
+ deprecated: ' element',
+ solution:
+ 'For modals use the or element instead',
+ }).warn();
+
this.#getReferences();
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/bulk-delete/modal/bulk-delete-with-relation-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/bulk-delete/modal/bulk-delete-with-relation-modal.element.ts
index c7d2a22c79..a6fb984818 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/bulk-delete/modal/bulk-delete-with-relation-modal.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/bulk-delete/modal/bulk-delete-with-relation-modal.element.ts
@@ -1,3 +1,4 @@
+import type { UmbConfirmBulkActionModalEntityReferencesConfig } from '../../../global-components/types.js';
import type {
UmbBulkDeleteWithRelationConfirmModalData,
UmbBulkDeleteWithRelationConfirmModalValue,
@@ -15,16 +16,13 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import { umbFocus } from '@umbraco-cms/backoffice/lit-element';
-// import of local component
-import '../../local-components/confirm-bulk-action-entity-references.element.js';
-
@customElement('umb-bulk-delete-with-relation-confirm-modal')
export class UmbBulkDeleteWithRelationConfirmModalElement extends UmbModalBaseElement<
UmbBulkDeleteWithRelationConfirmModalData,
UmbBulkDeleteWithRelationConfirmModalValue
> {
@state()
- _referencesConfig?: any;
+ _referencesConfig?: UmbConfirmBulkActionModalEntityReferencesConfig;
protected override firstUpdated(_changedProperties: PropertyValues): void {
super.firstUpdated(_changedProperties);
diff --git a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/bulk-trash/modal/bulk-trash-with-relation-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/bulk-trash/modal/bulk-trash-with-relation-modal.element.ts
index 8785fe42e1..6468189296 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/bulk-trash/modal/bulk-trash-with-relation-modal.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/bulk-trash/modal/bulk-trash-with-relation-modal.element.ts
@@ -1,3 +1,4 @@
+import type { UmbConfirmBulkActionModalEntityReferencesConfig } from '../../../global-components/types.js';
import type {
UmbBulkTrashWithRelationConfirmModalData,
UmbBulkTrashWithRelationConfirmModalValue,
@@ -15,16 +16,13 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import { umbFocus } from '@umbraco-cms/backoffice/lit-element';
-// import of local component
-import '../../local-components/confirm-bulk-action-entity-references.element.js';
-
@customElement('umb-bulk-trash-with-relation-confirm-modal')
export class UmbBulkTrashWithRelationConfirmModalElement extends UmbModalBaseElement<
UmbBulkTrashWithRelationConfirmModalData,
UmbBulkTrashWithRelationConfirmModalValue
> {
@state()
- _referencesConfig?: any;
+ _referencesConfig?: UmbConfirmBulkActionModalEntityReferencesConfig;
protected override firstUpdated(_changedProperties: PropertyValues): void {
super.firstUpdated(_changedProperties);
diff --git a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/delete/modal/delete-with-relation-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/delete/modal/delete-with-relation-modal.element.ts
index 539568c271..9df34a33b0 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/delete/modal/delete-with-relation-modal.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/delete/modal/delete-with-relation-modal.element.ts
@@ -1,3 +1,4 @@
+import type { UmbConfirmActionModalEntityReferencesConfig } from '../../../global-components/types.js';
import type {
UmbDeleteWithRelationConfirmModalData,
UmbDeleteWithRelationConfirmModalValue,
@@ -17,8 +18,6 @@ import { umbFocus } from '@umbraco-cms/backoffice/lit-element';
import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
-import '../../local-components/confirm-action-entity-references.element.js';
-
@customElement('umb-delete-with-relation-confirm-modal')
export class UmbDeleteWithRelationConfirmModalElement extends UmbModalBaseElement<
UmbDeleteWithRelationConfirmModalData,
@@ -28,7 +27,7 @@ export class UmbDeleteWithRelationConfirmModalElement extends UmbModalBaseElemen
_name?: string;
@state()
- _referencesConfig?: any;
+ _referencesConfig?: UmbConfirmActionModalEntityReferencesConfig;
#itemRepository?: UmbItemRepository;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/trash/modal/trash-with-relation-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/trash/modal/trash-with-relation-modal.element.ts
index 61f40cf2bc..e6f3e21a42 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/trash/modal/trash-with-relation-modal.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/trash/modal/trash-with-relation-modal.element.ts
@@ -1,3 +1,4 @@
+import type { UmbConfirmActionModalEntityReferencesConfig } from '../../../global-components/types.js';
import type {
UmbTrashWithRelationConfirmModalData,
UmbTrashWithRelationConfirmModalValue,
@@ -17,9 +18,6 @@ import { umbFocus } from '@umbraco-cms/backoffice/lit-element';
import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
-// import of local component
-import '../../local-components/confirm-action-entity-references.element.js';
-
@customElement('umb-trash-with-relation-confirm-modal')
export class UmbTrashWithRelationConfirmModalElement extends UmbModalBaseElement<
UmbTrashWithRelationConfirmModalData,
@@ -29,7 +27,7 @@ export class UmbTrashWithRelationConfirmModalElement extends UmbModalBaseElement
_name?: string;
@state()
- _referencesConfig?: any;
+ _referencesConfig?: UmbConfirmActionModalEntityReferencesConfig;
#itemRepository?: UmbItemRepository;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/local-components/confirm-action-entity-references.element.ts b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/global-components/confirm-action-modal-entity-references.element.ts
similarity index 89%
rename from src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/local-components/confirm-action-entity-references.element.ts
rename to src/Umbraco.Web.UI.Client/src/packages/relations/relations/global-components/confirm-action-modal-entity-references.element.ts
index 282d2c2987..37cac2a5f2 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/local-components/confirm-action-entity-references.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/global-components/confirm-action-modal-entity-references.element.ts
@@ -1,4 +1,4 @@
-import type { UmbEntityReferenceRepository, UmbReferenceItemModel } from '../../reference/types.js';
+import type { UmbEntityReferenceRepository, UmbReferenceItemModel } from '../reference/types.js';
import {
html,
customElement,
@@ -12,16 +12,18 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
+import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
+
+export interface UmbConfirmActionModalEntityReferencesConfig {
+ itemRepositoryAlias: string;
+ referenceRepositoryAlias: string;
+ unique: string;
+}
@customElement('umb-confirm-action-modal-entity-references')
export class UmbConfirmActionModalEntityReferencesElement extends UmbLitElement {
@property({ type: Object, attribute: false })
- config?: {
- itemRepositoryAlias: string;
- referenceRepositoryAlias: string;
- entityType: string;
- unique: string;
- };
+ config?: UmbConfirmActionModalEntityReferencesConfig;
@state()
_referencedByItems: Array = [];
@@ -40,6 +42,14 @@ export class UmbConfirmActionModalEntityReferencesElement extends UmbLitElement
#limitItems = 3;
+ getTotalReferencedBy() {
+ return this._totalReferencedByItems;
+ }
+
+ getTotalDescendantsWithReferences() {
+ return this._totalDescendantsWithReferences;
+ }
+
protected override firstUpdated(_changedProperties: PropertyValues): void {
super.firstUpdated(_changedProperties);
this.#initData();
@@ -88,6 +98,7 @@ export class UmbConfirmActionModalEntityReferencesElement extends UmbLitElement
if (data) {
this._referencedByItems = [...data.items];
this._totalReferencedByItems = data.total;
+ this.dispatchEvent(new UmbChangeEvent());
}
}
@@ -118,6 +129,7 @@ export class UmbConfirmActionModalEntityReferencesElement extends UmbLitElement
const uniques = data.items.map((item) => item.unique).filter((unique) => unique) as Array;
const { data: items } = await this.#itemRepository.requestItems(uniques);
this._descendantsWithReferences = items ?? [];
+ this.dispatchEvent(new UmbChangeEvent());
}
}
@@ -163,8 +175,6 @@ export class UmbConfirmActionModalEntityReferencesElement extends UmbLitElement
];
}
-export { UmbConfirmActionModalEntityReferencesElement as element };
-
declare global {
interface HTMLElementTagNameMap {
'umb-confirm-action-modal-entity-references': UmbConfirmActionModalEntityReferencesElement;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/local-components/confirm-bulk-action-entity-references.element.ts b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/global-components/confirm-bulk-action-modal-entity-references.element.ts
similarity index 93%
rename from src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/local-components/confirm-bulk-action-entity-references.element.ts
rename to src/Umbraco.Web.UI.Client/src/packages/relations/relations/global-components/confirm-bulk-action-modal-entity-references.element.ts
index 4d07370a05..4c7f4f75b4 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/entity-actions/local-components/confirm-bulk-action-entity-references.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/global-components/confirm-bulk-action-modal-entity-references.element.ts
@@ -1,4 +1,4 @@
-import type { UmbEntityReferenceRepository } from '../../reference/types.js';
+import type { UmbEntityReferenceRepository } from '../reference/types.js';
import {
html,
customElement,
@@ -13,6 +13,12 @@ import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
+export interface UmbConfirmBulkActionModalEntityReferencesConfig {
+ uniques: Array;
+ itemRepositoryAlias: string;
+ referenceRepositoryAlias: string;
+}
+
@customElement('umb-confirm-bulk-action-modal-entity-references')
export class UmbConfirmBulkActionModalEntityReferencesElement extends UmbLitElement {
@property({ type: Object, attribute: false })
@@ -125,8 +131,6 @@ export class UmbConfirmBulkActionModalEntityReferencesElement extends UmbLitElem
];
}
-export { UmbConfirmBulkActionModalEntityReferencesElement as element };
-
declare global {
interface HTMLElementTagNameMap {
'umb-confirm-bulk-action-modal-entity-references': UmbConfirmBulkActionModalEntityReferencesElement;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/global-components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/global-components/index.ts
new file mode 100644
index 0000000000..5c2fd4749d
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/global-components/index.ts
@@ -0,0 +1,5 @@
+import './confirm-action-modal-entity-references.element.js';
+import './confirm-bulk-action-modal-entity-references.element.js';
+
+export * from './confirm-action-modal-entity-references.element.js';
+export * from './confirm-bulk-action-modal-entity-references.element.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/global-components/types.ts b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/global-components/types.ts
new file mode 100644
index 0000000000..44d4676fc7
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/global-components/types.ts
@@ -0,0 +1,2 @@
+export type * from './confirm-action-modal-entity-references.element.js';
+export type * from './confirm-bulk-action-modal-entity-references.element.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/index.ts b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/index.ts
index 52a1c94b33..6bea1f6141 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/index.ts
@@ -1,6 +1,7 @@
-export * from './constants.js';
export * from './collection/index.js';
+export * from './constants.js';
export * from './entity.js';
+export * from './global-components/index.js';
export * from './utils.js';
export type * from './types.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/types.ts b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/types.ts
index f82c4baafe..3306f451e1 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/relations/relations/types.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/relations/relations/types.ts
@@ -1,4 +1,5 @@
import type { UmbRelationEntityType } from './entity.js';
+export type * from './global-components/types.js';
export type * from './reference/types.js';
export interface UmbRelationDetailModel {
From 3359562e3271532da490ebfb8d84a8c99db12e9b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Niels=20Lyngs=C3=B8?=
Date: Mon, 31 Mar 2025 09:56:42 +0200
Subject: [PATCH 23/38] make the deprecation discoverable from TSC (#18856)
---
.../block-grid-manager/block-grid-manager.context.ts | 3 +--
.../block/block-list/context/block-list-manager.context.ts | 3 +--
.../block/block-rte/context/block-rte-manager.context.ts | 3 +--
.../src/packages/block/block/context/block-manager.context.ts | 2 +-
4 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/block-grid-manager/block-grid-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/block-grid-manager/block-grid-manager.context.ts
index 602b3186c5..c60942be31 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/block-grid-manager/block-grid-manager.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/block-grid-manager/block-grid-manager.context.ts
@@ -105,9 +105,8 @@ export class UmbBlockGridManagerContext<
// This property is used by some implementations, but not used in this. Do not remove. [NL]
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_originData?: UmbBlockGridWorkspaceOriginData,
- ) {
+ ): never {
throw new Error('Method deparecated use createWithPresets');
- return {} as UmbBlockDataObjectModel;
}
async createWithPresets(
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts
index c8c66a1a3a..356d0cd8af 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts
@@ -35,9 +35,8 @@ export class UmbBlockListManagerContext<
// This property is used by some implementations, but not used in this. Do not remove. [NL]
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_originData?: UmbBlockListWorkspaceOriginData,
- ) {
+ ): never {
throw new Error('Method deparecated use createWithPresets');
- return {} as UmbBlockDataObjectModel;
}
async createWithPresets(
contentElementTypeKey: string,
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts
index 79cfb81b40..22f41b45e7 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts
@@ -32,9 +32,8 @@ export class UmbBlockRteManagerContext<
// This property is used by some implementations, but not used in this. Do not remove. [NL]
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_originData?: UmbBlockRteWorkspaceOriginData,
- ) {
+ ): never {
throw new Error('Method deparecated use createWithPresets');
- return {} as UmbBlockDataObjectModel;
}
async createWithPresets(
contentElementTypeKey: string,
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts
index f851d49ee5..4bc15730a5 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts
@@ -334,7 +334,7 @@ export abstract class UmbBlockManagerContext<
contentElementTypeKey: string,
partialLayoutEntry?: Omit,
originData?: BlockOriginDataType,
- ): UmbBlockDataObjectModel | undefined;
+ ): never;
abstract createWithPresets(
contentElementTypeKey: string,
From ae87f9ccd42723ed84cacc25a61af6df340f3723 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Niels=20Lyngs=C3=B8?=
Date: Mon, 31 Mar 2025 10:02:25 +0200
Subject: [PATCH 24/38] Implement context base for a few last contexts (#18865)
* do not destroy instance
* UmbDashboardHealthCheckElement
* correct last contexts to use context-base
* informing user that Umb.Condition.MenuAlias does not work
* parse host to Section Context
---
.../counter-workspace-context.ts | 14 ++++++++------
.../components/backoffice-main.element.ts | 2 +-
.../installer/error/installer-error.stories.ts | 10 +++++++---
.../src/apps/installer/installer.context.ts | 7 +++++--
.../src/apps/installer/installer.element.ts | 9 ++-------
.../apps/installer/shared/utils.story-helpers.ts | 12 +++++++-----
.../backoffice-modal-container.element.ts | 5 ++++-
.../packages/core/menu/components/menu/index.ts | 1 -
.../core/menu/components/menu/menu.context.ts | 15 ---------------
.../core/menu/components/menu/menu.element.ts | 5 -----
.../core/menu/conditions/menu-alias.condition.ts | 6 +++++-
.../src/packages/core/section/section.context.ts | 7 +++++--
.../document-configuration.context.ts | 10 ++++++----
.../dashboard-health-check.element.ts | 8 ++++----
.../health-check-dashboard.context.ts | 14 +++++++++-----
.../workspace/logviewer-workspace.context.ts | 12 +++++++-----
16 files changed, 70 insertions(+), 67 deletions(-)
delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.context.ts
diff --git a/src/Umbraco.Web.UI.Client/examples/workspace-context-counter/counter-workspace-context.ts b/src/Umbraco.Web.UI.Client/examples/workspace-context-counter/counter-workspace-context.ts
index 86525593be..fb1d017015 100644
--- a/src/Umbraco.Web.UI.Client/examples/workspace-context-counter/counter-workspace-context.ts
+++ b/src/Umbraco.Web.UI.Client/examples/workspace-context-counter/counter-workspace-context.ts
@@ -1,17 +1,19 @@
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
-import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
+import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbNumberState } from '@umbraco-cms/backoffice/observable-api';
// The Example Workspace Context Controller:
-export class WorkspaceContextCounter extends UmbControllerBase {
+export class WorkspaceContextCounterElement extends UmbContextBase<
+ WorkspaceContextCounterElement,
+ typeof EXAMPLE_COUNTER_CONTEXT
+> {
// We always keep our states private, and expose the values as observables:
#counter = new UmbNumberState(0);
readonly counter = this.#counter.asObservable();
constructor(host: UmbControllerHost) {
- super(host, EXAMPLE_COUNTER_CONTEXT.toString());
- this.provideContext(EXAMPLE_COUNTER_CONTEXT, this);
+ super(host, EXAMPLE_COUNTER_CONTEXT);
}
// Lets expose methods to update the state:
@@ -21,10 +23,10 @@ export class WorkspaceContextCounter extends UmbControllerBase {
}
// Declare a api export, so Extension Registry can initialize this class:
-export const api = WorkspaceContextCounter;
+export const api = WorkspaceContextCounterElement;
// Declare a Context Token that other elements can use to request the WorkspaceContextCounter:
-export const EXAMPLE_COUNTER_CONTEXT = new UmbContextToken(
+export const EXAMPLE_COUNTER_CONTEXT = new UmbContextToken(
'UmbWorkspaceContext',
'example.workspaceContext.counter',
);
diff --git a/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts b/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts
index 717a07f654..87081f11b1 100644
--- a/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts
@@ -95,7 +95,7 @@ export class UmbBackofficeMainElement extends UmbLitElement {
private _provideSectionContext(sectionManifest: ManifestSection) {
if (!this._sectionContext) {
- this._sectionContext = new UmbSectionContext(sectionManifest);
+ this._sectionContext = new UmbSectionContext(this, sectionManifest);
this.provideContext(UMB_SECTION_CONTEXT, this._sectionContext);
} else {
this._sectionContext.setManifest(sectionManifest);
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 6008563367..d60a58df9f 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
@@ -5,6 +5,7 @@ import type { Meta, StoryFn } from '@storybook/web-components';
import { html } from '@umbraco-cms/backoffice/external/lit';
import './installer-error.element.js';
+import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
const error = {
type: 'validation',
@@ -20,14 +21,17 @@ const error = {
},
};
-const installerContext = new UmbInstallerContext();
-installerContext.setInstallStatus(error);
+const installerContextMethod = (host: UmbControllerHostElement) => {
+ const installerContext = new UmbInstallerContext(host);
+ installerContext.setInstallStatus(error);
+ return installerContext;
+};
export default {
title: 'Apps/Installer/Steps',
component: 'umb-installer-error',
id: 'umb-installer-error',
- decorators: [(story) => installerContextProvider(story, installerContext)],
+ decorators: [(story) => installerContextProvider(story, installerContextMethod)],
} as Meta;
export const Step5Error: StoryFn = () => html` `;
diff --git a/src/Umbraco.Web.UI.Client/src/apps/installer/installer.context.ts b/src/Umbraco.Web.UI.Client/src/apps/installer/installer.context.ts
index ad203dbf29..0193999416 100644
--- a/src/Umbraco.Web.UI.Client/src/apps/installer/installer.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/apps/installer/installer.context.ts
@@ -8,12 +8,14 @@ import { InstallService, TelemetryLevelModel } from '@umbraco-cms/backoffice/ext
import { tryExecute } from '@umbraco-cms/backoffice/resources';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { UmbObjectState, UmbNumberState } from '@umbraco-cms/backoffice/observable-api';
+import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
/**
* Context API for the installer
* @class UmbInstallerContext
*/
-export class UmbInstallerContext {
+export class UmbInstallerContext extends UmbContextBase {
private _data = new UmbObjectState({
user: { name: '', email: '', password: '', subscribeToNewsletter: false },
database: { id: '', providerName: '', useIntegratedAuthentication: false, trustServerCertificate: false },
@@ -30,7 +32,8 @@ export class UmbInstallerContext {
private _installStatus = new UmbObjectState(null);
public readonly installStatus = this._installStatus.asObservable();
- constructor() {
+ constructor(host: UmbControllerHost) {
+ super(host, UMB_INSTALLER_CONTEXT);
this._loadInstallerSettings();
}
diff --git a/src/Umbraco.Web.UI.Client/src/apps/installer/installer.element.ts b/src/Umbraco.Web.UI.Client/src/apps/installer/installer.element.ts
index bbd49dbcc0..7de17887b0 100644
--- a/src/Umbraco.Web.UI.Client/src/apps/installer/installer.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/apps/installer/installer.element.ts
@@ -1,4 +1,4 @@
-import { UmbInstallerContext, UMB_INSTALLER_CONTEXT } from './installer.context.js';
+import { UmbInstallerContext } from './installer.context.js';
import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@@ -14,12 +14,7 @@ export class UmbInstallerElement extends UmbLitElement {
@state()
step = 1;
- private _umbInstallerContext = new UmbInstallerContext();
-
- constructor() {
- super();
- this.provideContext(UMB_INSTALLER_CONTEXT, this._umbInstallerContext);
- }
+ private _umbInstallerContext = new UmbInstallerContext(this);
override connectedCallback(): void {
super.connectedCallback();
diff --git a/src/Umbraco.Web.UI.Client/src/apps/installer/shared/utils.story-helpers.ts b/src/Umbraco.Web.UI.Client/src/apps/installer/shared/utils.story-helpers.ts
index e22e315b24..2828cb638d 100644
--- a/src/Umbraco.Web.UI.Client/src/apps/installer/shared/utils.story-helpers.ts
+++ b/src/Umbraco.Web.UI.Client/src/apps/installer/shared/utils.story-helpers.ts
@@ -1,14 +1,16 @@
import { UmbInstallerContext } from '../installer.context.js';
+import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { html, type TemplateResult } from '@umbraco-cms/backoffice/external/lit';
export const installerContextProvider = (
story: () => Node | string | TemplateResult,
- installerContext = new UmbInstallerContext(),
+ createContextMethod = (host: UmbControllerHostElement) => {
+ return new UmbInstallerContext(host);
+ },
) => html`
-
+ .create=${createContextMethod}>
${story()}
-
+
`;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/backoffice-modal-container/backoffice-modal-container.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/backoffice-modal-container/backoffice-modal-container.element.ts
index 61cd3892a0..1295413273 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/components/backoffice-modal-container/backoffice-modal-container.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/backoffice-modal-container/backoffice-modal-container.element.ts
@@ -49,8 +49,11 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement {
oldModals.forEach((modal) => {
// TODO: I would not think this works as expected, the callback method has to be the exact same instance as the one added: [NL]
- this._modalElementMap.get(modal.key)?.removeEventListener('close-end', this.#onCloseEnd.bind(this, modal.key));
+ const modalElement = this._modalElementMap.get(modal.key);
+ modalElement?.removeEventListener('close-end', this.#onCloseEnd.bind(this, modal.key));
+ modalElement?.destroy();
this._modalElementMap.delete(modal.key);
+ modal.destroy();
});
if (this._modals.length === 0) {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/index.ts
index ed60e33603..242a263f7a 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/index.ts
@@ -1,2 +1 @@
export * from './menu.element.js';
-export * from './menu.context.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.context.ts
deleted file mode 100644
index c96cdbb94e..0000000000
--- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.context.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import type { ManifestMenu } from '../../menu.extension.js';
-import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
-import { UmbDeepState } from '@umbraco-cms/backoffice/observable-api';
-
-export class UmbMenuContext {
- #manifest = new UmbDeepState(undefined);
- public readonly manifest = this.#manifest.asObservable();
- public readonly alias = this.#manifest.asObservablePart((x) => x?.alias);
-
- public setManifest(manifest: ManifestMenu | undefined) {
- this.#manifest.setValue(manifest);
- }
-}
-
-export const UMB_MENU_CONTEXT = new UmbContextToken('UMB_MENU_CONTEXT');
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.element.ts
index 026a6277f3..7e051db0e0 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.element.ts
@@ -10,11 +10,6 @@ export class UmbMenuElement extends UmbLitElement {
@property({ attribute: false })
manifest?: ManifestMenu;
- constructor() {
- super();
- //this.provideContext(UMB_MENU_CONTEXT, new UmbMenuContext());
- }
-
override render() {
return html`
) {
super(host, args);
+ console.error(
+ 'Condition of alias `Umb.Condition.MenuAlias` is not implemented. Please report this issue if you where expecting this condition to work.',
+ );
+ /*
this.consumeContext(UMB_MENU_CONTEXT, (context) => {
this.observe(
context.alias,
@@ -21,6 +24,7 @@ export class UmbMenuAliasCondition extends UmbConditionBase {
#manifestAlias = new UmbStringState(undefined);
#manifestPathname = new UmbStringState(undefined);
#manifestLabel = new UmbStringState(undefined);
@@ -10,7 +12,8 @@ export class UmbSectionContext {
public readonly pathname = this.#manifestPathname.asObservable();
public readonly label = this.#manifestLabel.asObservable();
- constructor(manifest: ManifestSection) {
+ constructor(host: UmbControllerHost, manifest: ManifestSection) {
+ super(host, UMB_SECTION_CONTEXT);
this.setManifest(manifest);
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-configuration.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-configuration.context.ts
index b69994d8a9..8829599ee0 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-configuration.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/global-contexts/document-configuration.context.ts
@@ -1,4 +1,4 @@
-import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
+import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
@@ -10,15 +10,17 @@ import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
* A context for fetching and caching the document configuration.
* @deprecated Do not use this one, it will have ot change in near future.
*/
-export class UmbDocumentConfigurationContext extends UmbControllerBase implements UmbApi {
+export class UmbDocumentConfigurationContext
+ extends UmbContextBase
+ implements UmbApi
+{
/**
* The cached document configuration.
*/
static #DocumentConfiguration: Promise;
constructor(host: UmbControllerHost) {
- super(host);
- this.provideContext(UMB_DOCUMENT_CONFIGURATION_CONTEXT, this);
+ super(host, UMB_DOCUMENT_CONFIGURATION_CONTEXT);
}
/**
diff --git a/src/Umbraco.Web.UI.Client/src/packages/health-check/dashboard-health-check.element.ts b/src/Umbraco.Web.UI.Client/src/packages/health-check/dashboard-health-check.element.ts
index cb468209f7..fd5e06405f 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/health-check/dashboard-health-check.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/health-check/dashboard-health-check.element.ts
@@ -1,7 +1,7 @@
import type { UmbDashboardHealthCheckGroupElement } from './views/health-check-group.element.js';
-import { UmbHealthCheckDashboardContext, UMB_HEALTHCHECK_DASHBOARD_CONTEXT } from './health-check-dashboard.context.js';
+import { UmbHealthCheckDashboardContext } from './health-check-dashboard.context.js';
import type { ManifestHealthCheck } from './health-check.extension.js';
-import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
+import { html, customElement, state, type PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
import type { HealthCheckGroupResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import { HealthCheckService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbRoute } from '@umbraco-cms/backoffice/router';
@@ -35,14 +35,14 @@ export class UmbDashboardHealthCheckElement extends UmbLitElement {
constructor() {
super();
- this.provideContext(UMB_HEALTHCHECK_DASHBOARD_CONTEXT, this._healthCheckDashboardContext);
this.observe(umbExtensionsRegistry.byType('healthCheck'), (healthCheckManifests) => {
this._healthCheckDashboardContext.manifests = healthCheckManifests;
});
}
- protected override firstUpdated() {
+ protected override firstUpdated(_changedProperties: PropertyValueMap | Map) {
+ super.firstUpdated(_changedProperties);
this.#registerHealthChecks();
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/health-check/health-check-dashboard.context.ts b/src/Umbraco.Web.UI.Client/src/packages/health-check/health-check-dashboard.context.ts
index 2801a13365..516bf4b191 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/health-check/health-check-dashboard.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/health-check/health-check-dashboard.context.ts
@@ -1,9 +1,14 @@
+import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { UmbHealthCheckContext } from './health-check.context.js';
import type { ManifestHealthCheck } from './health-check.extension.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { loadManifestApi } from '@umbraco-cms/backoffice/extension-api';
+import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
-export class UmbHealthCheckDashboardContext {
+export class UmbHealthCheckDashboardContext extends UmbContextBase<
+ UmbHealthCheckDashboardContext,
+ typeof UMB_HEALTHCHECK_DASHBOARD_CONTEXT
+> {
#manifests: ManifestHealthCheck[] = [];
set manifests(value: ManifestHealthCheck[]) {
this.#manifests = value;
@@ -14,10 +19,9 @@ export class UmbHealthCheckDashboardContext {
}
public apis = new Map();
- public host: HTMLElement;
- constructor(host: HTMLElement) {
- this.host = host;
+ constructor(host: UmbControllerHost) {
+ super(host, UMB_HEALTHCHECK_DASHBOARD_CONTEXT);
}
async checkAll() {
@@ -32,7 +36,7 @@ export class UmbHealthCheckDashboardContext {
if (!manifest.api) return;
const api = await loadManifestApi(manifest.api);
if (!api) return;
- const apiInstance = new api(this.host);
+ const apiInstance = new api(this);
if (api && UmbHealthCheckContext.isInstanceLike(apiInstance)) this.apis.set(manifest.meta.label, apiInstance);
});
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/logviewer-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/logviewer-workspace.context.ts
index 0c1f7c84f2..664927d4a6 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/logviewer-workspace.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/logviewer-workspace.context.ts
@@ -11,7 +11,7 @@ import type {
} from '@umbraco-cms/backoffice/external/backend-api';
import { DirectionModel, LogLevelModel } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
-import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
+import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { query } from '@umbraco-cms/backoffice/router';
import type { UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace';
import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
@@ -27,7 +27,10 @@ export interface LogViewerDateRange {
}
// TODO: Revisit usage of workspace for this case...
-export class UmbLogViewerWorkspaceContext extends UmbControllerBase implements UmbWorkspaceContext {
+export class UmbLogViewerWorkspaceContext
+ extends UmbContextBase
+ implements UmbWorkspaceContext
+{
public readonly workspaceAlias: string = 'Umb.Workspace.LogViewer';
#repository: UmbLogViewerRepository;
@@ -104,10 +107,9 @@ export class UmbLogViewerWorkspaceContext extends UmbControllerBase implements U
currentPage = 1;
constructor(host: UmbControllerHost) {
- super(host);
+ super(host, UMB_APP_LOG_VIEWER_CONTEXT);
+ // TODO: Revisit usage of workspace for this case... currently no other workspace context provides them self with their own token, we need to update UMB_APP_LOG_VIEWER_CONTEXT to become a workspace context. [NL]
this.provideContext(UMB_WORKSPACE_CONTEXT, this);
- // TODO: Revisit usage of workspace for this case... currently no other workspace context provides them self with their own token.
- this.provideContext(UMB_APP_LOG_VIEWER_CONTEXT, this);
this.#repository = new UmbLogViewerRepository(host);
}
From c422c82f21d7d511e7ec61a9931fd5d1e59747c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Niels=20Lyngs=C3=B8?=
Date: Mon, 31 Mar 2025 10:09:10 +0200
Subject: [PATCH 25/38] await setup method (#18831)
---
.../src/external/router-slot/util/router.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Umbraco.Web.UI.Client/src/external/router-slot/util/router.ts b/src/Umbraco.Web.UI.Client/src/external/router-slot/util/router.ts
index 0602181f10..7c731abe59 100644
--- a/src/Umbraco.Web.UI.Client/src/external/router-slot/util/router.ts
+++ b/src/Umbraco.Web.UI.Client/src/external/router-slot/util/router.ts
@@ -160,7 +160,7 @@ export async function resolvePageComponent(route: IComponentRoute, info: IRoutin
// Setup the component using the callback.
if (route.setup != null) {
- route.setup(component, info);
+ await route.setup(component, info);
}
return component;
From 13d95f31f23bec858e1bb8ba7b3381e957f69e76 Mon Sep 17 00:00:00 2001
From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
Date: Mon, 31 Mar 2025 10:14:50 +0200
Subject: [PATCH 26/38] test: removes two tests that fails because of a
non-fixated runtime (#18876)
i.e. the tests depend on a specific time interval, but the test runner does not guarantee that, and the tests are not strictly necessary anyway
---
.../localization.controller.test.ts | 23 -------------------
1 file changed, 23 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/libs/localization-api/localization.controller.test.ts b/src/Umbraco.Web.UI.Client/src/libs/localization-api/localization.controller.test.ts
index bb1376e6a8..a01f922f72 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/localization-api/localization.controller.test.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/localization-api/localization.controller.test.ts
@@ -309,32 +309,9 @@ describe('UmbLocalizeController', () => {
expect(controller.duration(inTenSeconds, now)).to.equal('10 seconds');
});
- it('should compare between two dates', () => {
- const twoDaysAgo = new Date();
- twoDaysAgo.setDate(twoDaysAgo.getDate() - 2);
- const inTwoDays = new Date();
- inTwoDays.setDate(inTwoDays.getDate() + 2);
-
- expect(controller.duration(inTwoDays, twoDaysAgo)).to.equal('4 days');
- });
-
it('should return a negative duration', () => {
expect(controller.duration('2020-01-01', '2019-12-30')).to.equal('2 days');
});
-
- it('should update the relative compounded time when the language changes', async () => {
- const now = new Date();
- const inTwoDays = new Date();
- inTwoDays.setDate(inTwoDays.getDate() + 2);
-
- expect(controller.duration(inTwoDays, now)).to.equal('2 days');
-
- // Switch browser to Danish
- document.documentElement.lang = danishRegional.$code;
- await aTimeout(0);
-
- expect(controller.duration(inTwoDays, now)).to.equal('2 dage');
- });
});
describe('list format', () => {
From e18cdab18510b3a9c4eac5440637dedafcd32e79 Mon Sep 17 00:00:00 2001
From: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>
Date: Mon, 31 Mar 2025 16:33:17 +0700
Subject: [PATCH 27/38] V15 QA Added acceptance tests for inline editing mode
(#18874)
* Added tests for block list with inline editing mode
* Added tests to create a document type witha block with inline editing mode
* Added tests for block grid inline editing mode
* Bumped version
* Changed npm commands
* Reverted npm command
---
.../package-lock.json | 8 +--
.../Umbraco.Tests.AcceptanceTest/package.json | 2 +-
.../BlockGrid/ContentWithBlockGrid.spec.ts | 47 +++++++++++++
.../BlockList/ContentWithBlockList.spec.ts | 61 +++++++++++++++--
.../DocumentTypeDesignTab.spec.ts | 66 +++++++++++++------
5 files changed, 152 insertions(+), 32 deletions(-)
diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json
index 3454d8ffe7..fddf89e58a 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.31",
- "@umbraco/playwright-testhelpers": "^15.0.40",
+ "@umbraco/playwright-testhelpers": "^15.0.41",
"camelize": "^1.0.0",
"dotenv": "^16.3.1",
"node-fetch": "^2.6.7"
@@ -67,9 +67,9 @@
}
},
"node_modules/@umbraco/playwright-testhelpers": {
- "version": "15.0.40",
- "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.40.tgz",
- "integrity": "sha512-dxXCCYeUH0rlASdHHNu8gQQrhK52gxGcwb/K1BlXFsr7Z7dz1U5eYMPUiVjDVg6LNCbqmQ/tmZqoAZLU5zDzIw==",
+ "version": "15.0.41",
+ "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.41.tgz",
+ "integrity": "sha512-yZEhC3iSqT+O/2TBz0QGGEZyKleZ+qIW4YHTpm2nxPSdBAUaKqE4lb6UwylcQZtYnZVssXdi62jbzRPbG8XBlw==",
"dependencies": {
"@umbraco/json-models-builders": "2.0.31",
"node-fetch": "^2.6.7"
diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json
index 63aafd3813..38315b91a4 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.31",
- "@umbraco/playwright-testhelpers": "^15.0.40",
+ "@umbraco/playwright-testhelpers": "^15.0.41",
"camelize": "^1.0.0",
"dotenv": "^16.3.1",
"node-fetch": "^2.6.7"
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockGrid/ContentWithBlockGrid.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockGrid/ContentWithBlockGrid.spec.ts
index 6f4939d88e..2bec221ba9 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockGrid/ContentWithBlockGrid.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockGrid/ContentWithBlockGrid.spec.ts
@@ -265,3 +265,50 @@ test.skip('can add settings model for the block in the content', async ({umbraco
test.skip('can move blocks in the content', async ({umbracoApi, umbracoUi}) => {
// TODO: Implement it later
});
+
+test('can create content with a block grid with the inline editing mode enabled', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const customDataTypeId = await umbracoApi.dataType.createBlockGridWithABlockWithInlineEditingMode(customDataTypeName, elementTypeId);
+ await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.content.goToSection(ConstantHelper.sections.content);
+
+ // Act
+ await umbracoUi.content.clickActionsMenuAtRoot();
+ await umbracoUi.content.clickCreateButton();
+ await umbracoUi.content.chooseDocumentType(documentTypeName);
+ await umbracoUi.content.enterContentName(contentName);
+ await umbracoUi.content.clickSaveButton();
+
+ // Assert
+ await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created);
+ expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
+});
+
+test('can add a block element with inline editing mode enabled', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const inputText = 'This is block test';
+ const customDataTypeId = await umbracoApi.dataType.createBlockGridWithABlockWithInlineEditingMode(customDataTypeName, elementTypeId);
+ const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
+ await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.content.goToSection(ConstantHelper.sections.content);
+
+ // Act
+ await umbracoUi.content.goToContentWithName(contentName);
+ await umbracoUi.content.clickAddBlockElementButton();
+ await umbracoUi.content.clickTextButtonWithName(elementTypeName);
+ await umbracoUi.content.enterTextstring(inputText);
+ await umbracoUi.content.clickCreateModalButton();
+ await umbracoUi.content.clickSaveAndPublishButton();
+
+ // Assert
+ await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
+ expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
+ const contentData = await umbracoApi.document.getByName(contentName);
+ expect(contentData.values[0].value.contentData[0].values[0].value).toEqual(inputText);
+ const blockListValue = contentData.values.find(item => item.editorAlias === "Umbraco.BlockGrid")?.value;
+ expect(blockListValue).toBeTruthy();
+ await umbracoUi.content.doesPropertyContainValue(propertyInBlock, inputText);
+});
\ No newline at end of file
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockList/ContentWithBlockList.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockList/ContentWithBlockList.spec.ts
index fccdb0f9de..5d8b87da74 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockList/ContentWithBlockList.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockList/ContentWithBlockList.spec.ts
@@ -81,7 +81,7 @@ test('can add a block element in the content', async ({umbracoApi, umbracoUi}) =
// Act
await umbracoUi.content.goToContentWithName(contentName);
await umbracoUi.content.clickAddBlockElementButton();
- await umbracoUi.content.clickTextButtonWithName(elementTypeName);
+ await umbracoUi.content.clickBlockElementWithName(elementTypeName);
await umbracoUi.content.enterTextstring(inputText);
await umbracoUi.content.clickCreateModalButton();
await umbracoUi.content.clickSaveButton();
@@ -132,9 +132,9 @@ test('can delete block element in the content', async ({umbracoApi, umbracoUi})
expect(blockGridValue).toBeFalsy();
});
-// Skip this flaky tests as sometimes the modal to choose block item is not displayed
-test.skip('cannot add number of block element greater than the maximum amount', async ({umbracoApi, umbracoUi}) => {
+test('cannot add number of block element greater than the maximum amount', async ({umbracoApi, umbracoUi}) => {
// Arrange
+ const inputText = 'This is block test';
const customDataTypeId = await umbracoApi.dataType.createBlockListWithABlockAndMinAndMaxAmount(customDataTypeName, elementTypeId, 0, 1);
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
@@ -144,10 +144,12 @@ test.skip('cannot add number of block element greater than the maximum amount',
// Act
await umbracoUi.content.goToContentWithName(contentName);
await umbracoUi.content.clickAddBlockElementButton();
- await umbracoUi.content.clickTextButtonWithName(elementTypeName);
+ await umbracoUi.content.clickBlockElementWithName(elementTypeName);
+ await umbracoUi.content.enterTextstring(inputText);
await umbracoUi.content.clickCreateModalButton();
await umbracoUi.content.clickAddBlockElementButton();
await umbracoUi.content.clickTextButtonWithName(elementTypeName);
+ await umbracoUi.content.enterTextstring(inputText);
await umbracoUi.content.clickCreateModalButton();
// Assert
@@ -155,8 +157,7 @@ test.skip('cannot add number of block element greater than the maximum amount',
await umbracoUi.content.doesFormValidationMessageContainText('too many');
});
-// Skip this flaky tests as sometimes the modal to choose block item is not displayed
-test.skip('can set the label of block element in the content', async ({umbracoApi, umbracoUi}) => {
+test('can set the label of block element in the content', async ({umbracoApi, umbracoUi}) => {
// Arrange
const blockLabel = 'Test Block Label';
const customDataTypeId = await umbracoApi.dataType.createBlockListDataTypeWithLabel(customDataTypeName, elementTypeId, blockLabel);
@@ -168,7 +169,7 @@ test.skip('can set the label of block element in the content', async ({umbracoAp
// Act
await umbracoUi.content.goToContentWithName(contentName);
await umbracoUi.content.clickAddBlockElementButton();
- await umbracoUi.content.clickTextButtonWithName(elementTypeName);
+ await umbracoUi.content.clickBlockElementWithName(elementTypeName);
await umbracoUi.content.clickCreateModalButton();
await umbracoUi.content.clickSaveButton();
@@ -214,3 +215,49 @@ test.skip('can add settings model for the block in the content', async ({umbraco
test.skip('can move blocks in the content', async ({umbracoApi, umbracoUi}) => {
// TODO: Implement it later
});
+
+test('can create content with a block list with the inline editing mode enabled', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const customDataTypeId = await umbracoApi.dataType.createBlockListDataTypeWithInlineEditingMode(customDataTypeName, elementTypeId);
+ await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.content.goToSection(ConstantHelper.sections.content);
+
+ // Act
+ await umbracoUi.content.clickActionsMenuAtRoot();
+ await umbracoUi.content.clickCreateButton();
+ await umbracoUi.content.chooseDocumentType(documentTypeName);
+ await umbracoUi.content.enterContentName(contentName);
+ await umbracoUi.content.clickSaveButton();
+
+ // Assert
+ await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created);
+ expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
+});
+
+test('can add a block element with inline editing mode enabled', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const inputText = 'This is block test';
+ const customDataTypeId = await umbracoApi.dataType.createBlockListDataTypeWithInlineEditingModeAndABlock(customDataTypeName, elementTypeId);
+ const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
+ await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.content.goToSection(ConstantHelper.sections.content);
+
+ // Act
+ await umbracoUi.content.goToContentWithName(contentName);
+ await umbracoUi.content.clickAddBlockElementButton();
+ await umbracoUi.content.clickTextButtonWithName(elementTypeName);
+ await umbracoUi.content.clickInlineBlockCaretButtonForName(elementTypeName);
+ await umbracoUi.content.enterTextstring(inputText);
+ await umbracoUi.content.clickSaveAndPublishButton();
+
+ // Assert
+ await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
+ expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
+ const contentData = await umbracoApi.document.getByName(contentName);
+ expect(contentData.values[0].value.contentData[0].values[0].value).toEqual(inputText);
+ const blockListValue = contentData.values.find(item => item.editorAlias === "Umbraco.BlockList")?.value;
+ expect(blockListValue).toBeTruthy();
+});
\ No newline at end of file
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 f0ce32091b..e022b8b726 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
@@ -1,4 +1,4 @@
-import {ConstantHelper, test} from "@umbraco/playwright-testhelpers";
+import {ConstantHelper, NotificationConstantHelper, test} from "@umbraco/playwright-testhelpers";
import {expect} from "@playwright/test";
const documentTypeName = 'TestDocumentType';
@@ -28,7 +28,7 @@ test('can add a property to a document type', {tag: '@smoke'}, async ({umbracoAp
await umbracoUi.documentType.clickSaveButton();
// Assert
- await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
const dataType = await umbracoApi.dataType.getByName(dataTypeName);
@@ -49,7 +49,7 @@ test('can update a property in a document type', {tag: '@smoke'}, async ({umbrac
await umbracoUi.documentType.clickSaveButton();
// Assert
- await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
const dataType = await umbracoApi.dataType.getByName(newDataTypeName);
@@ -70,7 +70,7 @@ test('can update group name in a document type', async ({umbracoApi, umbracoUi})
await umbracoUi.documentType.clickSaveButton();
// Assert
- await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
expect(documentTypeData.containers[0].name).toBe(newGroupName);
@@ -89,7 +89,7 @@ test('can delete a group in a document type', {tag: '@smoke'}, async ({umbracoAp
await umbracoUi.documentType.clickSaveButton();
// Assert
- await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
expect(documentTypeData.containers.length).toBe(0);
expect(documentTypeData.properties.length).toBe(0);
@@ -108,7 +108,7 @@ test('can delete a tab in a document type', async ({umbracoApi, umbracoUi}) => {
await umbracoUi.documentType.clickSaveButton();
// Assert
- await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
expect(documentTypeData.containers.length).toBe(0);
@@ -126,7 +126,7 @@ test('can delete a property editor in a document type', {tag: '@smoke'}, async (
await umbracoUi.documentType.clickSaveButton();
// Assert
- await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
expect(documentTypeData.properties.length).toBe(0);
@@ -147,7 +147,7 @@ test('can create a document type with a property in a tab', {tag: '@smoke'}, asy
await umbracoUi.documentType.clickSaveButton();
// Assert
- await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
expect(await umbracoApi.documentType.doesTabContainCorrectPropertyEditorInGroup(documentTypeName, dataTypeName, documentTypeData.properties[0].dataType.id, tabName, groupName)).toBeTruthy();
@@ -170,7 +170,7 @@ test('can create a document type with multiple groups', async ({umbracoApi, umbr
await umbracoUi.documentType.clickSaveButton();
// Assert
- await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
expect(await umbracoApi.documentType.doesGroupContainCorrectPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, groupName)).toBeTruthy();
expect(await umbracoApi.documentType.doesGroupContainCorrectPropertyEditor(documentTypeName, secondDataTypeName, secondDataType.id, secondGroupName)).toBeTruthy();
@@ -196,7 +196,7 @@ test('can create a document type with multiple tabs', async ({umbracoApi, umbrac
await umbracoUi.documentType.clickSaveButton();
// Assert
- await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
expect(await umbracoApi.documentType.doesTabContainCorrectPropertyEditorInGroup(documentTypeName, dataTypeName, dataTypeData.id, tabName, groupName)).toBeTruthy();
expect(await umbracoApi.documentType.doesTabContainCorrectPropertyEditorInGroup(documentTypeName, secondDataTypeName, secondDataType.id, secondTabName, secondGroupName)).toBeTruthy();
@@ -220,7 +220,7 @@ test('can create a document type with a composition', {tag: '@smoke'}, async ({u
await umbracoUi.documentType.clickSaveButton();
// Assert
- await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
expect(umbracoUi.documentType.doesGroupHaveValue(groupName)).toBeTruthy();
// Checks if the composition in the document type is correct
const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
@@ -249,7 +249,7 @@ test('can remove a composition from a document type', async ({umbracoApi, umbrac
await umbracoUi.documentType.clickSaveButton();
// Assert
- await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
await umbracoUi.documentType.isGroupVisible(groupName, false);
const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
expect(documentTypeData.compositions).toEqual([]);
@@ -275,7 +275,7 @@ test('can reorder groups in a document type', async ({umbracoApi, umbracoUi}) =>
await umbracoUi.documentType.clickSaveButton();
// Assert
- await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
// Since we swapped sorting order, the firstGroupValue should have sortOrder 1 and the secondGroupValue should have sortOrder 0
expect(await umbracoApi.documentType.doesDocumentTypeGroupNameContainCorrectSortOrder(documentTypeName, secondGroupValue, 0)).toBeTruthy();
expect(await umbracoApi.documentType.doesDocumentTypeGroupNameContainCorrectSortOrder(documentTypeName, firstGroupValue, 1)).toBeTruthy();
@@ -300,7 +300,7 @@ test.skip('can reorder properties in a document type', async ({umbracoApi, umbra
await umbracoUi.documentType.clickSaveButton();
// Assert
- await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
expect(documentTypeData.properties[0].name).toBe(dataTypeNameTwo);
expect(documentTypeData.properties[1].name).toBe(dataTypeName);
@@ -324,7 +324,7 @@ test.skip('can reorder tabs in a document type', {tag: '@smoke'}, async ({umbrac
await umbracoUi.documentType.clickSaveButton();
// Assert
- await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
expect(await umbracoApi.documentType.doesDocumentTypeTabNameContainCorrectSortOrder(documentTypeName, secondTabName, 0)).toBeTruthy();
expect(await umbracoApi.documentType.doesDocumentTypeTabNameContainCorrectSortOrder(documentTypeName, tabName, 1)).toBeTruthy();
});
@@ -344,7 +344,7 @@ test('can add a description to a property in a document type', async ({umbracoAp
await umbracoUi.documentType.clickSaveButton();
// Assert
- await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
await expect(umbracoUi.documentType.enterDescriptionTxt).toBeVisible();
expect(umbracoUi.documentType.doesDescriptionHaveValue(descriptionText)).toBeTruthy();
const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
@@ -365,7 +365,7 @@ test('can set is mandatory for a property in a document type', {tag: '@smoke'},
await umbracoUi.documentType.clickSaveButton();
// Assert
- await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
expect(documentTypeData.properties[0].validation.mandatory).toBeTruthy();
});
@@ -388,7 +388,7 @@ test('can enable validation for a property in a document type', async ({umbracoA
await umbracoUi.documentType.clickSaveButton();
// Assert
- await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
expect(documentTypeData.properties[0].validation.regEx).toBe(regex);
expect(documentTypeData.properties[0].validation.regExMessage).toBe(regexMessage);
@@ -408,7 +408,7 @@ test('can allow vary by culture for a property in a document type', {tag: '@smok
await umbracoUi.documentType.clickSaveButton();
// Assert
- await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
expect(documentTypeData.properties[0].variesByCulture).toBeTruthy();
});
@@ -427,7 +427,33 @@ test('can set appearance to label on top for a property in a document type', asy
await umbracoUi.documentType.clickSaveButton();
// Assert
- await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
expect(documentTypeData.properties[0].appearance.labelOnTop).toBeTruthy();
});
+
+test('can add a block list property with inline editing mode to a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const blockListDataTypeName = 'TestBlockList';
+ await umbracoApi.documentType.createDefaultDocumentType(documentTypeName);
+ await umbracoApi.dataType.createBlockListDataTypeWithInlineEditingMode(blockListDataTypeName, true);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickAddGroupButton();
+ await umbracoUi.documentType.addPropertyEditor(blockListDataTypeName);
+ await umbracoUi.documentType.enterGroupName(groupName);
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
+ expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ const blockListDataTypeData = await umbracoApi.dataType.getByName(blockListDataTypeName);
+ // Checks if the correct property was added to the document type
+ expect(documentTypeData.properties[0].dataType.id).toBe(blockListDataTypeData.id);
+
+ // Clean
+ await umbracoApi.dataType.ensureNameNotExists(blockListDataTypeName);
+});
From 88b1b9de131b3571a616f94b86b9624969e1cad1 Mon Sep 17 00:00:00 2001
From: NguyenThuyLan <116753400+NguyenThuyLan@users.noreply.github.com>
Date: Mon, 31 Mar 2025 13:50:23 +0200
Subject: [PATCH 28/38] V15: bump @umbraco-ui/uui from 1.12.2 to 1.13.0
Co-authored-by: Lan Nguyen Thuy
---
src/Umbraco.Web.UI.Client/package-lock.json | 1051 +++++++++----------
src/Umbraco.Web.UI.Client/package.json | 4 +-
2 files changed, 485 insertions(+), 570 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json
index d0ba73c6bb..1c5441c6dc 100644
--- a/src/Umbraco.Web.UI.Client/package-lock.json
+++ b/src/Umbraco.Web.UI.Client/package-lock.json
@@ -28,8 +28,8 @@
"@tiptap/pm": "2.11.5",
"@tiptap/starter-kit": "2.11.5",
"@types/diff": "^7.0.1",
- "@umbraco-ui/uui": "1.13.0-rc.2",
- "@umbraco-ui/uui-css": "1.13.0-rc.2",
+ "@umbraco-ui/uui": "^1.13.0",
+ "@umbraco-ui/uui-css": "^1.13.0",
"diff": "^7.0.0",
"dompurify": "^3.2.4",
"element-internals-polyfill": "^1.3.13",
@@ -4455,908 +4455,824 @@
"link": true
},
"node_modules/@umbraco-ui/uui": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.13.0-rc.2.tgz",
- "integrity": "sha512-b5iIPr6wZ0/op4YZC0mhs12HlBw6GdE/oW8NFMDBClfm/yX8ImuOiFc7VmcrpL1bQ3sq643FOAyYE7A2+m82Tw==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.13.0.tgz",
+ "integrity": "sha512-O/RvFeW+Mjn24ckmWJeTzMZKYbVrnaHscl9zKGKkMSva3j3mnJs/Q9N6BfihQy3qdZP5ED+2lGomezxfoLjZ7g==",
"dependencies": {
- "@umbraco-ui/uui-action-bar": "1.13.0-rc.2",
- "@umbraco-ui/uui-avatar": "1.13.0-rc.2",
- "@umbraco-ui/uui-avatar-group": "1.13.0-rc.2",
- "@umbraco-ui/uui-badge": "1.13.0-rc.2",
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-boolean-input": "1.13.0-rc.2",
- "@umbraco-ui/uui-box": "1.13.0-rc.2",
- "@umbraco-ui/uui-breadcrumbs": "1.13.0-rc.2",
- "@umbraco-ui/uui-button": "1.13.0-rc.2",
- "@umbraco-ui/uui-button-copy-text": "1.13.0-rc.2",
- "@umbraco-ui/uui-button-group": "1.13.0-rc.2",
- "@umbraco-ui/uui-button-inline-create": "1.13.0-rc.2",
- "@umbraco-ui/uui-card": "1.13.0-rc.2",
- "@umbraco-ui/uui-card-block-type": "1.13.0-rc.2",
- "@umbraco-ui/uui-card-content-node": "1.13.0-rc.2",
- "@umbraco-ui/uui-card-media": "1.13.0-rc.2",
- "@umbraco-ui/uui-card-user": "1.13.0-rc.2",
- "@umbraco-ui/uui-caret": "1.13.0-rc.2",
- "@umbraco-ui/uui-checkbox": "1.13.0-rc.2",
- "@umbraco-ui/uui-color-area": "1.13.0-rc.2",
- "@umbraco-ui/uui-color-picker": "1.13.0-rc.2",
- "@umbraco-ui/uui-color-slider": "1.13.0-rc.2",
- "@umbraco-ui/uui-color-swatch": "1.13.0-rc.2",
- "@umbraco-ui/uui-color-swatches": "1.13.0-rc.2",
- "@umbraco-ui/uui-combobox": "1.13.0-rc.2",
- "@umbraco-ui/uui-combobox-list": "1.13.0-rc.2",
- "@umbraco-ui/uui-css": "1.13.0-rc.2",
- "@umbraco-ui/uui-dialog": "1.13.0-rc.2",
- "@umbraco-ui/uui-dialog-layout": "1.13.0-rc.2",
- "@umbraco-ui/uui-file-dropzone": "1.13.0-rc.2",
- "@umbraco-ui/uui-file-preview": "1.13.0-rc.2",
- "@umbraco-ui/uui-form": "1.13.0-rc.2",
- "@umbraco-ui/uui-form-layout-item": "1.13.0-rc.2",
- "@umbraco-ui/uui-form-validation-message": "1.13.0-rc.2",
- "@umbraco-ui/uui-icon": "1.13.0-rc.2",
- "@umbraco-ui/uui-icon-registry": "1.13.0-rc.2",
- "@umbraco-ui/uui-icon-registry-essential": "1.13.0-rc.2",
- "@umbraco-ui/uui-input": "1.13.0-rc.2",
- "@umbraco-ui/uui-input-file": "1.13.0-rc.2",
- "@umbraco-ui/uui-input-lock": "1.13.0-rc.2",
- "@umbraco-ui/uui-input-password": "1.13.0-rc.2",
- "@umbraco-ui/uui-keyboard-shortcut": "1.13.0-rc.2",
- "@umbraco-ui/uui-label": "1.13.0-rc.2",
- "@umbraco-ui/uui-loader": "1.13.0-rc.2",
- "@umbraco-ui/uui-loader-bar": "1.13.0-rc.2",
- "@umbraco-ui/uui-loader-circle": "1.13.0-rc.2",
- "@umbraco-ui/uui-menu-item": "1.13.0-rc.2",
- "@umbraco-ui/uui-modal": "1.13.0-rc.2",
- "@umbraco-ui/uui-pagination": "1.13.0-rc.2",
- "@umbraco-ui/uui-popover": "1.13.0-rc.2",
- "@umbraco-ui/uui-popover-container": "1.13.0-rc.2",
- "@umbraco-ui/uui-progress-bar": "1.13.0-rc.2",
- "@umbraco-ui/uui-radio": "1.13.0-rc.2",
- "@umbraco-ui/uui-range-slider": "1.13.0-rc.2",
- "@umbraco-ui/uui-ref": "1.13.0-rc.2",
- "@umbraco-ui/uui-ref-list": "1.13.0-rc.2",
- "@umbraco-ui/uui-ref-node": "1.13.0-rc.2",
- "@umbraco-ui/uui-ref-node-data-type": "1.13.0-rc.2",
- "@umbraco-ui/uui-ref-node-document-type": "1.13.0-rc.2",
- "@umbraco-ui/uui-ref-node-form": "1.13.0-rc.2",
- "@umbraco-ui/uui-ref-node-member": "1.13.0-rc.2",
- "@umbraco-ui/uui-ref-node-package": "1.13.0-rc.2",
- "@umbraco-ui/uui-ref-node-user": "1.13.0-rc.2",
- "@umbraco-ui/uui-scroll-container": "1.13.0-rc.2",
- "@umbraco-ui/uui-select": "1.13.0-rc.2",
- "@umbraco-ui/uui-slider": "1.13.0-rc.2",
- "@umbraco-ui/uui-symbol-expand": "1.13.0-rc.2",
- "@umbraco-ui/uui-symbol-file": "1.13.0-rc.2",
- "@umbraco-ui/uui-symbol-file-dropzone": "1.13.0-rc.2",
- "@umbraco-ui/uui-symbol-file-thumbnail": "1.13.0-rc.2",
- "@umbraco-ui/uui-symbol-folder": "1.13.0-rc.2",
- "@umbraco-ui/uui-symbol-lock": "1.13.0-rc.2",
- "@umbraco-ui/uui-symbol-more": "1.13.0-rc.2",
- "@umbraco-ui/uui-symbol-sort": "1.13.0-rc.2",
- "@umbraco-ui/uui-table": "1.13.0-rc.2",
- "@umbraco-ui/uui-tabs": "1.13.0-rc.2",
- "@umbraco-ui/uui-tag": "1.13.0-rc.2",
- "@umbraco-ui/uui-textarea": "1.13.0-rc.2",
- "@umbraco-ui/uui-toast-notification": "1.13.0-rc.2",
- "@umbraco-ui/uui-toast-notification-container": "1.13.0-rc.2",
- "@umbraco-ui/uui-toast-notification-layout": "1.13.0-rc.2",
- "@umbraco-ui/uui-toggle": "1.13.0-rc.2",
- "@umbraco-ui/uui-visually-hidden": "1.13.0-rc.2"
+ "@umbraco-ui/uui-action-bar": "1.13.0",
+ "@umbraco-ui/uui-avatar": "1.13.0",
+ "@umbraco-ui/uui-avatar-group": "1.13.0",
+ "@umbraco-ui/uui-badge": "1.13.0",
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-boolean-input": "1.13.0",
+ "@umbraco-ui/uui-box": "1.13.0",
+ "@umbraco-ui/uui-breadcrumbs": "1.13.0",
+ "@umbraco-ui/uui-button": "1.13.0",
+ "@umbraco-ui/uui-button-copy-text": "1.13.0",
+ "@umbraco-ui/uui-button-group": "1.13.0",
+ "@umbraco-ui/uui-button-inline-create": "1.13.0",
+ "@umbraco-ui/uui-card": "1.13.0",
+ "@umbraco-ui/uui-card-block-type": "1.13.0",
+ "@umbraco-ui/uui-card-content-node": "1.13.0",
+ "@umbraco-ui/uui-card-media": "1.13.0",
+ "@umbraco-ui/uui-card-user": "1.13.0",
+ "@umbraco-ui/uui-caret": "1.13.0",
+ "@umbraco-ui/uui-checkbox": "1.13.0",
+ "@umbraco-ui/uui-color-area": "1.13.0",
+ "@umbraco-ui/uui-color-picker": "1.13.0",
+ "@umbraco-ui/uui-color-slider": "1.13.0",
+ "@umbraco-ui/uui-color-swatch": "1.13.0",
+ "@umbraco-ui/uui-color-swatches": "1.13.0",
+ "@umbraco-ui/uui-combobox": "1.13.0",
+ "@umbraco-ui/uui-combobox-list": "1.13.0",
+ "@umbraco-ui/uui-css": "1.13.0",
+ "@umbraco-ui/uui-dialog": "1.13.0",
+ "@umbraco-ui/uui-dialog-layout": "1.13.0",
+ "@umbraco-ui/uui-file-dropzone": "1.13.0",
+ "@umbraco-ui/uui-file-preview": "1.13.0",
+ "@umbraco-ui/uui-form": "1.13.0",
+ "@umbraco-ui/uui-form-layout-item": "1.13.0",
+ "@umbraco-ui/uui-form-validation-message": "1.13.0",
+ "@umbraco-ui/uui-icon": "1.13.0",
+ "@umbraco-ui/uui-icon-registry": "1.13.0",
+ "@umbraco-ui/uui-icon-registry-essential": "1.13.0",
+ "@umbraco-ui/uui-input": "1.13.0",
+ "@umbraco-ui/uui-input-file": "1.13.0",
+ "@umbraco-ui/uui-input-lock": "1.13.0",
+ "@umbraco-ui/uui-input-password": "1.13.0",
+ "@umbraco-ui/uui-keyboard-shortcut": "1.13.0",
+ "@umbraco-ui/uui-label": "1.13.0",
+ "@umbraco-ui/uui-loader": "1.13.0",
+ "@umbraco-ui/uui-loader-bar": "1.13.0",
+ "@umbraco-ui/uui-loader-circle": "1.13.0",
+ "@umbraco-ui/uui-menu-item": "1.13.0",
+ "@umbraco-ui/uui-modal": "1.13.0",
+ "@umbraco-ui/uui-pagination": "1.13.0",
+ "@umbraco-ui/uui-popover": "1.13.0",
+ "@umbraco-ui/uui-popover-container": "1.13.0",
+ "@umbraco-ui/uui-progress-bar": "1.13.0",
+ "@umbraco-ui/uui-radio": "1.13.0",
+ "@umbraco-ui/uui-range-slider": "1.13.0",
+ "@umbraco-ui/uui-ref": "1.13.0",
+ "@umbraco-ui/uui-ref-list": "1.13.0",
+ "@umbraco-ui/uui-ref-node": "1.13.0",
+ "@umbraco-ui/uui-ref-node-data-type": "1.13.0",
+ "@umbraco-ui/uui-ref-node-document-type": "1.13.0",
+ "@umbraco-ui/uui-ref-node-form": "1.13.0",
+ "@umbraco-ui/uui-ref-node-member": "1.13.0",
+ "@umbraco-ui/uui-ref-node-package": "1.13.0",
+ "@umbraco-ui/uui-ref-node-user": "1.13.0",
+ "@umbraco-ui/uui-scroll-container": "1.13.0",
+ "@umbraco-ui/uui-select": "1.13.0",
+ "@umbraco-ui/uui-slider": "1.13.0",
+ "@umbraco-ui/uui-symbol-expand": "1.13.0",
+ "@umbraco-ui/uui-symbol-file": "1.13.0",
+ "@umbraco-ui/uui-symbol-file-dropzone": "1.13.0",
+ "@umbraco-ui/uui-symbol-file-thumbnail": "1.13.0",
+ "@umbraco-ui/uui-symbol-folder": "1.13.0",
+ "@umbraco-ui/uui-symbol-lock": "1.13.0",
+ "@umbraco-ui/uui-symbol-more": "1.13.0",
+ "@umbraco-ui/uui-symbol-sort": "1.13.0",
+ "@umbraco-ui/uui-table": "1.13.0",
+ "@umbraco-ui/uui-tabs": "1.13.0",
+ "@umbraco-ui/uui-tag": "1.13.0",
+ "@umbraco-ui/uui-textarea": "1.13.0",
+ "@umbraco-ui/uui-toast-notification": "1.13.0",
+ "@umbraco-ui/uui-toast-notification-container": "1.13.0",
+ "@umbraco-ui/uui-toast-notification-layout": "1.13.0",
+ "@umbraco-ui/uui-toggle": "1.13.0",
+ "@umbraco-ui/uui-visually-hidden": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-action-bar": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.13.0-rc.2.tgz",
- "integrity": "sha512-o9itNsWQdN0fIn+8W3bNwPEV/IVb4sjW0NFBmL3sa6TOgiHHPyH4/A73xjNy5zexeH0VbNb8IJCgKXv4hpSgCw==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.13.0.tgz",
+ "integrity": "sha512-0AGQ1zsUZT1wHKx+01JkRKLNtpjCS/SqEy/NVHUyYIGPimr6NQDM9Ok00LZKpZVwxcvArdy38XaAz6SijlaTqg==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-button-group": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-button-group": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-avatar": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.13.0-rc.2.tgz",
- "integrity": "sha512-msdEZLVKwKqC+pD1ymVcs1NKQKvloCx+Og5UnIg5J/KlOnvhrII1BqW99tp1hjcrh6/l+FecbNq1oCZOyYYyUw==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.13.0.tgz",
+ "integrity": "sha512-w+DwB9PUcnR0y0CzeNQA2638PjF2Dswiyuoxa2ryggcy38ihypj0Fj8FpzRSe5rax2JMtpJnuoDPwUpqVwGfOQ==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-avatar-group": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.13.0-rc.2.tgz",
- "integrity": "sha512-jUbDvaa6+fQ2T5JQKQh8L599pP/xD6uDj8ARlFVdPxVAjQ61ayqAgp/2JulUrdg3rcE+w7p2g5pXXMEUBjYIIQ==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.13.0.tgz",
+ "integrity": "sha512-G8lIknUuhy+swW9Xz7qN3fp0L5Xhx4d5C2Q9WbW316GeseLYCm2eRhXDLpiEzIMxoVYtA9P0gbkuxLFDkznc+Q==",
"dependencies": {
- "@umbraco-ui/uui-avatar": "1.13.0-rc.2",
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-avatar": "1.13.0",
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-badge": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.13.0-rc.2.tgz",
- "integrity": "sha512-t9Rturd0/eQkJ6t6GFPsSpuGmlwTJYHOeCEy4lDjwRumNGWXnPTlULrxeSsQPvPyNuFoa69kgTeL1xd2fn/fRg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.13.0.tgz",
+ "integrity": "sha512-z7Z5IZwcfFJDFIPnBDfuCv+YkBHafn15oi4rNmNVynaM/iFJ+W3NAE7EmdWMDZzuDeQngbFpoRx1Ub7I4mqsng==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-base": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.13.0-rc.2.tgz",
- "integrity": "sha512-+bMilDGWT0LhH2hCQn0rImT1pECncl3sF4iBKGZ7IJyxaQ80v571f+gsMAJzkxb0ClsDQUQQ/MZyFxymSG6twA==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.13.0.tgz",
+ "integrity": "sha512-VG0xlVzuq74qKaWn+eaTcTblR6HCi9YyrNohLLhHVAJuelmcgoPwHdNzkjoaWXlq16XKnB5Kmb6BgEtVmSQZ5w==",
"peerDependencies": {
"lit": ">=2.8.0"
}
},
"node_modules/@umbraco-ui/uui-boolean-input": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.13.0-rc.2.tgz",
- "integrity": "sha512-UlK7V2LZJQA5MpfJWDlpxTaD1JVu3mmu7P33yj9arWgjvTUcIu6RVUMbOoie3BhOdUe3ryZdQXdMNtTBp8X55w==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.13.0.tgz",
+ "integrity": "sha512-WsP+W5/4Fcp9sg0gFlfh8FyIzaczRC4kc2LxT3haljflgQTMVwV4MGGadOYg89hVpD0C4dZaqp69sskLWc6fWQ==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-box": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.13.0-rc.2.tgz",
- "integrity": "sha512-fleYkQ+EhkSJFXZTYn9k4qfScbm3aNbnkZWkKMIHamvGk29ocI+0tE75QC4w8rtbvtIzIJrRKV8Rx9aHN4R22g==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.13.0.tgz",
+ "integrity": "sha512-msIz5NerPKx7bnTyEyMjihnfxSTlslU+FyE4DsUUwZT6vtFxT2Dt74UbO8cg0ut9GoBjR1wpn4qNTW3xRxfdiA==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-css": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-css": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-breadcrumbs": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.13.0-rc.2.tgz",
- "integrity": "sha512-ih0h9CvkF7qCXC3pi3xxCMBAN5cyYUAaa644DcyNXb4mNsP7MDuKcMnLiXf3kgkoboebKhWyWhnM99fg/+p46g==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.13.0.tgz",
+ "integrity": "sha512-DG/m4bZ3bPTryrN6mDQMmabXPmvKcVlsfjuhJ/UDazq6T/4DVfB6YrXk6q+4N6X4njg88CO/V6ObnyB7RE+flQ==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-button": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.13.0-rc.2.tgz",
- "integrity": "sha512-gG4aoIwddJsltcopHr3ZB8t0/DiSFbP++Jo4QMtMRvgxOZ72AgWtSGXNHlPIHhtyt6EhXJH5tPdztRlz8x1mZA==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.13.0.tgz",
+ "integrity": "sha512-dJWr9fKQFB4CRMJ23oRmkuzN45ESuxDn1nwgLw0TLhJrAWo5uoyTL1k/IuNdg0C3+BqNIzC5B8m5YC2S+BpPlA==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-icon-registry-essential": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-icon-registry-essential": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-button-copy-text": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-copy-text/-/uui-button-copy-text-1.13.0-rc.2.tgz",
- "integrity": "sha512-948Vy0bEpBVu+9Z5BH+fNTlG0yDiOPbg2e2zywmfB1RMkndyj7h1pCrnq51ZcyK7W0oma4eQpOoy0S2vzTJmcw==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-copy-text/-/uui-button-copy-text-1.13.0.tgz",
+ "integrity": "sha512-/4j4PnxNRAyeD2LYA+dyyCZurOPnGioQfl2iFIE/B2znBvJR2JHrnCLwcAqawI+YhHxGgyKNck7BCKihYQxkow==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-button": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-button": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-button-group": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.13.0-rc.2.tgz",
- "integrity": "sha512-I31TdnsrkXX8yZybKF6uhZI5byBYkC+OhFv4/93PvcdMNZ7ObENUzhnkd205WmfSyp/JnWSK0x18StBhDeXtkA==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.13.0.tgz",
+ "integrity": "sha512-Pksx35rtKriOUO9IP1ETnQDoBnoiRzwheM8fmqeo44jSPsr7emaQrI3BOwqeOuD7KfPRIVnzwLdm14K4Zw6tZA==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-button-inline-create": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.13.0-rc.2.tgz",
- "integrity": "sha512-HzoKX835wYiDup2EQ6ufVN4dJKyJx5slFHjto1abPpVLfp90OXs+68AJRhNm93VcQB/cnXCzlnPAf0v/hgsCsQ==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.13.0.tgz",
+ "integrity": "sha512-6XtJ/nZpVDkYFiWEqbr5uz5CJ2Yqled4W7zAsh53QuCCYFgyU6yU9AFtrhPRwC9I27XzmBTQRZgCkQFWuEuL5A==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-card": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.13.0-rc.2.tgz",
- "integrity": "sha512-yLezCU19XxniXBC4vZCPG5HnSi47SjipRIsFXF55GUjGokJ9ISLDnU6DLfHTPF7BNV2EXBUXlIN2Pznf4J44gg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.13.0.tgz",
+ "integrity": "sha512-fBskLWqFoquKfgFK6bJ4lM0V30XZCZcJjjwTUmSjRFvklyF3csL7W9bKB9hs+aFu0/GDQlVqOBa5tA4RLpcj0w==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-card-block-type": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-block-type/-/uui-card-block-type-1.13.0-rc.2.tgz",
- "integrity": "sha512-PqrtLkTSRcsDi/99xFngcjOfcuEUdjKydG5nR0vchPpIoQjyvYOjk27gTTBB9u5Ee7hNNOBkUtgSnkl3V7bNRQ==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-block-type/-/uui-card-block-type-1.13.0.tgz",
+ "integrity": "sha512-YBDHZ+76oz27+P9v8YSr3OwWs3eftqy2d3Gg/sxh3Y6n9gI2TdXtJgev9GVL2FpifZXM2A1ySzh8MscC2HLJIA==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-card": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-card": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-card-content-node": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.13.0-rc.2.tgz",
- "integrity": "sha512-WLstvJBGk9s0qqsQKyPopet0Kj4JefYcPUoxTKPMdXlCXkvvyQvGG2ZQCKPhz+TOTodDceGbh3XpnpYMDA87Wg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.13.0.tgz",
+ "integrity": "sha512-IYe/AUaJ7Pspd+zSQlJMRIUzzF7+dLnq6ApezC9b93mEEhB4WwX+BbzfHbbhyNxMv9Za9gBKZljIH3RluPWnog==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-card": "1.13.0-rc.2",
- "@umbraco-ui/uui-icon": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-card": "1.13.0",
+ "@umbraco-ui/uui-icon": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-card-media": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.13.0-rc.2.tgz",
- "integrity": "sha512-EaJ2ZxwhfW0lKHNP+k35rMHOXqVCwRacy/RL604d5Ch1e+V6Wc2t8tWyzEwBuUkPqEALdMH4qBq5U08u3/MZkQ==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.13.0.tgz",
+ "integrity": "sha512-ohRFE1FqmYNJ7VXKjzMqjhCfzfabL9bLOpJet0+VXCMHUomHZv9UHQTI1ibp71BMp934vWT3kqGgco6RYqujSQ==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-card": "1.13.0-rc.2",
- "@umbraco-ui/uui-symbol-file": "1.13.0-rc.2",
- "@umbraco-ui/uui-symbol-folder": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-card": "1.13.0",
+ "@umbraco-ui/uui-symbol-file": "1.13.0",
+ "@umbraco-ui/uui-symbol-folder": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-card-user": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.13.0-rc.2.tgz",
- "integrity": "sha512-L0hR1TM2eFJnY4Q129J/X2Qn/o8ZXYvpDTKFivWocRLEHiEfVzzMycZuZMgZBZwePTVx3x4COizy44YoCmfWPg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.13.0.tgz",
+ "integrity": "sha512-lAB2IuXvNK8l/n+D9s9cNNUUvBdZE2Uy3UDc0QJla3qo2RLsyM4pSgVeS0Ve+GOI1A4vyK8Sfx68cDptW04Vaw==",
"dependencies": {
- "@umbraco-ui/uui-avatar": "1.13.0-rc.2",
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-card": "1.13.0-rc.2"
+ "@umbraco-ui/uui-avatar": "1.13.0",
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-card": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-caret": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.13.0-rc.2.tgz",
- "integrity": "sha512-4o09l3848epNm5w2ZN0fs6pd+24hHvGB+BLyDLt0i2u8jqQmnqbR0Cx1KjttkHfmJw9t0e88kWVZTDl5m6fMgw==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.13.0.tgz",
+ "integrity": "sha512-OCrJISFcRvB6V1+xPS+AjGEp+ue3vyegTRJsLEOVbyCHbrzFwNUKLc2EFYz2rxOGjcFs7Z9M8I6eoLUuMxDQAQ==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-checkbox": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.13.0-rc.2.tgz",
- "integrity": "sha512-6GvzgVSAm+A7peb4ay4rS2RBtDJKDH19l2TdTrOR/C/buIVBZr73a9KX4JaU54JQV67GJIs+sbjQr2YbCdFhaQ==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.13.0.tgz",
+ "integrity": "sha512-9ywXUZgC8kMLEgsx1JFH0iftSeI8zzVDVECYq36+dVuz0iWtXfUjb5ygSoUX0guiACVY5gNba/H+t9R+3FbUgw==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-boolean-input": "1.13.0-rc.2",
- "@umbraco-ui/uui-icon-registry-essential": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-boolean-input": "1.13.0",
+ "@umbraco-ui/uui-icon-registry-essential": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-color-area": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.13.0-rc.2.tgz",
- "integrity": "sha512-BZ0b2kkl6N1uuS4kejXM2qc2Vw4T+JEGZVLvEiJzowgKmiMsfRyAXlw3/vlwep+mFpOO66PLHAznF4FoWN90cw==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.13.0.tgz",
+ "integrity": "sha512-X7CyxicQYE5VR5iXSY0IsPym0pSYWFLQ9KDgzVIDM3fvoM+KpiGYrDhGTgrHrTpJ3EE8JO06fPrx/mJ2NyxOyQ==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
+ "@umbraco-ui/uui-base": "1.13.0",
"colord": "^2.9.3"
}
},
"node_modules/@umbraco-ui/uui-color-picker": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.13.0-rc.2.tgz",
- "integrity": "sha512-UKpu9ErjjhdBjkIHuelP9loQDQ0PBvpC5GuqObBhkfggvPe4Aep2bO/Jp+c3QzOhX/Z7E0ZN3gVx/nHc9dyuwg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.13.0.tgz",
+ "integrity": "sha512-ROkz+5+ecZ9GbctpaynL9CeMdXhamY2wPfwjVHyp/QERLXsvhlXIojD0n11Fp4i9FzQsiHb328T5aTnBZ3tqcw==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-popover-container": "1.13.0-rc.2",
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-popover-container": "1.13.0",
"colord": "^2.9.3"
}
},
"node_modules/@umbraco-ui/uui-color-slider": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.13.0-rc.2.tgz",
- "integrity": "sha512-MoqWEwY8obkfMTJeh7DUJxSsQbKpADyXXg3q6itzDPbn5jssaeBTmm26M3x6WLHOmraU2HkKxjVjtk6OjP1AqQ==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.13.0.tgz",
+ "integrity": "sha512-om/OwXDVDNsy0HZIuIv6VXoi5aFBU7KtHfiq7/OLnnWtO5MQREwBCTVthhSFfe7LaZSZnFhFn89hrmw7hfhljQ==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-color-swatch": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.13.0-rc.2.tgz",
- "integrity": "sha512-tUzIQoK5iqmTHbubefV1RNdecWCbiF/ru+gZE4Txy9Y4MaE7uyYSBKdeUS7Ch89QfUVA/Of78ESiJLj3wmx0QA==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.13.0.tgz",
+ "integrity": "sha512-tiT274ldYjDMFeBQBx3yGu7HgYaNrxjNIrcktlsddfWxxjJ3UNu08YdoP4DqJOi6limQhadBllCBa9oyz4iOig==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-icon-registry-essential": "1.13.0-rc.2",
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-icon-registry-essential": "1.13.0",
"colord": "^2.9.3"
}
},
"node_modules/@umbraco-ui/uui-color-swatches": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.13.0-rc.2.tgz",
- "integrity": "sha512-scBuY13syfletW1iMsWSzEUhugeporBe0kGivNZh1naKtjC0EtRvb8ThFi/YH5Xk0Zi1PgVWWB0vpnPDE5sGFg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.13.0.tgz",
+ "integrity": "sha512-DAZ9cAxIp+kGFeGteDCgt+Om0vcuacmjtT98N1meP/EkPgJf6y21o3y4oySeQMAhWXznr3DBxyHHKN1Jt3do8Q==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-color-swatch": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-color-swatch": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-combobox": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.13.0-rc.2.tgz",
- "integrity": "sha512-IdcttSnL+OsZ9SjiEus+prAloAiVR7QLlAvtWcXxbbxTtPXUldfMYP00RL5mzXRNkTQycB3O13goI7rKEnwPeg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.13.0.tgz",
+ "integrity": "sha512-8lKmqQMQkh+yMA4iFonDLwpNf6EZ+pYXhJ2Xcum14WT6UI6BgiQvuM2nmRrkWhqA7Wx0tTAAdP5ILPAl5lENRQ==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-button": "1.13.0-rc.2",
- "@umbraco-ui/uui-combobox-list": "1.13.0-rc.2",
- "@umbraco-ui/uui-icon": "1.13.0-rc.2",
- "@umbraco-ui/uui-popover-container": "1.13.0-rc.2",
- "@umbraco-ui/uui-scroll-container": "1.13.0-rc.2",
- "@umbraco-ui/uui-symbol-expand": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-button": "1.13.0",
+ "@umbraco-ui/uui-combobox-list": "1.13.0",
+ "@umbraco-ui/uui-icon": "1.13.0",
+ "@umbraco-ui/uui-popover-container": "1.13.0",
+ "@umbraco-ui/uui-scroll-container": "1.13.0",
+ "@umbraco-ui/uui-symbol-expand": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-combobox-list": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.13.0-rc.2.tgz",
- "integrity": "sha512-YnwCSZ1GFD3tZeHAYqAsCRjIPc/bwltK4nGjaeUyqpSSz0asonDrxbvd/5b+IISqDhYnznGD7jGO0+8HJHmt7w==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.13.0.tgz",
+ "integrity": "sha512-ZVRouGMb7VH5wD8a0kE1t71oUMD1gUvFdACPWTjunpgM0ZXk1wOGGtS3vsEaTAkbQ8gABPpsYnCaWBt0MR+RpA==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-css": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.13.0-rc.2.tgz",
- "integrity": "sha512-ny7Roxjr3Go02q6olVW4rkxBAa2hi/PoBi2D0mNOAU+XE2WoYWuCbxnObI9eAoud7L9aJvvFqEnOoQGE2/AGyA==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.13.0.tgz",
+ "integrity": "sha512-6crDukueGm9t5CBU+d/icouGELQQIQsfi/qT7J6qISRZTvBjoT0FxUxUtpXsIQs1H0qgULhwx8PTKnfQ/AMZFA==",
"peerDependencies": {
"lit": ">=2.8.0"
}
},
"node_modules/@umbraco-ui/uui-dialog": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.13.0-rc.2.tgz",
- "integrity": "sha512-7cVVgdBnNPwedtLvRURfOO/WOgCygAL/UcPER1TpQIerB3j4NDITtvwn6VrUpJRIDpiqAGE9tiVCFMt/NNbzZA==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.13.0.tgz",
+ "integrity": "sha512-RzePOwJrhBEYBZAwgvNkIro+cVirLxgaIGNFUgnvoWIovHJNOuSso65MtcGON3nvuQ4JxE8SIOTE/hwT04R7Ag==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-css": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-css": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-dialog-layout": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.13.0-rc.2.tgz",
- "integrity": "sha512-c9KnMcSZ+YlsXLaN/tU/1wlh+hj45XtBWiIOHM8cCFkxe73ZMsNew8n7qaTHwDnxAgHn6zrjE9ba3gtsjbLlEg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.13.0.tgz",
+ "integrity": "sha512-m8eoCEz0dugWmqrmRw2vHae3k7oYjr53JiOkb8viCMh7veQo4EM0zqZgdCwADs1wES8woOX5zdttp9JtqYenRw==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-file-dropzone": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.13.0-rc.2.tgz",
- "integrity": "sha512-Yg8Vfg/KGUTeCxXBtLdunbERwQokIIdv4aF+6BUp7GFdmz/9KWrSipQg5dMV3I2mD+ArnspwBkwyGF8OEBJdzw==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.13.0.tgz",
+ "integrity": "sha512-1TFkyeNB3qWWhgc7NYudxXOc3v0cBRuRpVYPA3xocfVkqCG2PgEc7ePW18CtUuuGntGwv0E0Oni2bfSLrqVmuQ==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-symbol-file-dropzone": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-symbol-file-dropzone": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-file-preview": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.13.0-rc.2.tgz",
- "integrity": "sha512-xXpghwcVVTzN+XddoZ5tt0g5m9MRXisYg1CkiVOE1OGDgpph+LMlkSCNNAi4datNpMwtYWxmHoxBmVELVhkEOA==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.13.0.tgz",
+ "integrity": "sha512-ZEW2q6If0+3WWHnQp9UPdL+rcI4zUKlyvELDU1JDzx/IVDFJb8f7fI5qhzQjl4kXCVI54Ch4WkBie6RDpNSqVg==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-symbol-file": "1.13.0-rc.2",
- "@umbraco-ui/uui-symbol-file-thumbnail": "1.13.0-rc.2",
- "@umbraco-ui/uui-symbol-folder": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-symbol-file": "1.13.0",
+ "@umbraco-ui/uui-symbol-file-thumbnail": "1.13.0",
+ "@umbraco-ui/uui-symbol-folder": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-form": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.13.0-rc.2.tgz",
- "integrity": "sha512-Y68o0tynYhJFPQaKNbyYxuzApj62+5sUHB14b+y2S8Mn8TxYq1wvZF0dYHOKAZsO07se4YQj1xU961j/uiQUlA==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.13.0.tgz",
+ "integrity": "sha512-Y5Wgl3AcixLbPHJJK2yqdga5NMHx5Jv3YvG69+AdPkgzyNmCtdbDitV8ex2ysNYMO3WbBRdYIjbI5pYRl3xn5Q==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-form-layout-item": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.13.0-rc.2.tgz",
- "integrity": "sha512-09onpcp/x+dx6wRKHSXe+LdrCaQrREah57KxFPOPOzdMOIWmHpg2Ork7TehjA+SRBrptVpKrlpkN44Ho2Q006w==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.13.0.tgz",
+ "integrity": "sha512-GKNjsvUBbl2ba9e7b88Vk7HuMO9exnGDRpmQ94PSH/rOUF44ri4mPTPFU2k9DCvIkSs7lxDvotXE7kQ5IPQYBw==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-form-validation-message": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-form-validation-message": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-form-validation-message": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.13.0-rc.2.tgz",
- "integrity": "sha512-mMg94awkBAIBJKYOKrWx7gMseAPF9hA5dPpYeYGM0hg1ZG2LYZOPCtfrXqNMkFr7l4CRpBOMZKsJtgnmLmkLLg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.13.0.tgz",
+ "integrity": "sha512-x1E84q6L8DbpBkoS+ykdvmoEUcObXYhym6nhh2lK2TAn7vZu+XD+Osd5rgy5ycZ4YtYnCqetlaPwQwAFqFiSHA==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-icon": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.13.0-rc.2.tgz",
- "integrity": "sha512-jAMFme59JZjWEgtT/xb6Y/ZtABi5m55lDRyxqZ23ob5iraIkNwQ7WEvtBA/FMy5t0JRDyDg3k1zDFJl8P+oWYA==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.13.0.tgz",
+ "integrity": "sha512-TKmQi4n8ZV6v228U6zi9f38g/Nu4ok1cbvoIiSfSvmSYNXD1weuWK5y7Ph7EGr6jL5T5vKbDhjcZUIjzBOVWAA==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-icon-registry": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.13.0-rc.2.tgz",
- "integrity": "sha512-XdES9Wsgnka1E0mRvPIb2uFNM/I9mTNLof9ax+/Ia2NpKGS5PwlS/RAFLaLGGgCaupm6HdjwnEkTq+/f0SfGRg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.13.0.tgz",
+ "integrity": "sha512-/w7EN7Exi7ST0olPuxFLFm8rw3Mt2QhZsqQWQXXYnb4hTC+ot27IDahQmlLOx85+h/KH3Qz+Tn2NgM3BEdQQ5w==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-icon": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-icon": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-icon-registry-essential": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.13.0-rc.2.tgz",
- "integrity": "sha512-HkuRPb2/5ybQYCsmF3M15gDmsnHIBdBijel8DDuNquiAjcU2/jT+uFAP5HaBOnR7Twu8agY/OFrzrnWAvteIvA==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.13.0.tgz",
+ "integrity": "sha512-CcuBNg06ewGM6OhwjXCQKm5QDYXySCcc7TQajJ14kfMXtdcO8ls6eI2D8t+Hkc4YN7TQaUeGgzMF746f4TiiNQ==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-icon-registry": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-icon-registry": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-input": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.13.0-rc.2.tgz",
- "integrity": "sha512-dydYwzCcMSKnzcJE5mt/e00QZMptXV/F8lWAOgcWv3eAJ7CldKAsMi4X27c43IFtO5c62hMZ5wtZOcXvud96bw==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.13.0.tgz",
+ "integrity": "sha512-2GwLio1SDBofYLZjds47X6Fxq29ORgQBZaD9xwowFaoWCsG+WIFsE7VaE4KgPASUOQYoMMzFZX3F2TdvbjPEAg==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-input-file": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.13.0-rc.2.tgz",
- "integrity": "sha512-OJ30KIbNAqoJYxgy3E5QhJQ4yy76d50wUIIc5dsEfi3OJDk3eZueU4sdWmH4Izg3hKociOhbFDHLn19Yk7xA3g==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.13.0.tgz",
+ "integrity": "sha512-wKmFAzThstZPeCOtNtdAX8SZ09T0mJQEA1g+l6EaCV5ikPLSO0kiqmv3P0p0IDf6WSX29+UhcSp2hOVzR+cELg==",
"dependencies": {
- "@umbraco-ui/uui-action-bar": "1.13.0-rc.2",
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-button": "1.13.0-rc.2",
- "@umbraco-ui/uui-file-dropzone": "1.13.0-rc.2",
- "@umbraco-ui/uui-icon": "1.13.0-rc.2",
- "@umbraco-ui/uui-icon-registry-essential": "1.13.0-rc.2"
+ "@umbraco-ui/uui-action-bar": "1.13.0",
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-button": "1.13.0",
+ "@umbraco-ui/uui-file-dropzone": "1.13.0",
+ "@umbraco-ui/uui-icon": "1.13.0",
+ "@umbraco-ui/uui-icon-registry-essential": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-input-lock": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.13.0-rc.2.tgz",
- "integrity": "sha512-Xg+j6rD76mx8uaKzOg7mMCodcODjn9aRBppfQejuY69D4azT+6zEIwEUGWKsGxjfrL/ebjJhHs0r02TICU0Mmg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.13.0.tgz",
+ "integrity": "sha512-qChhwO5NsA8es9X41HJ73sXtmvKUF90WBBL8PYgESLkL7zQdvhe9wfJhVjZ1WMJkOc6F7uTAJbawuEVXSX0uKA==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-button": "1.13.0-rc.2",
- "@umbraco-ui/uui-icon": "1.13.0-rc.2",
- "@umbraco-ui/uui-input": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-button": "1.13.0",
+ "@umbraco-ui/uui-icon": "1.13.0",
+ "@umbraco-ui/uui-input": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-input-password": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.13.0-rc.2.tgz",
- "integrity": "sha512-hA63lzi03LhkXyhxtRRLW4WSNc1KtmUd8l9HUWB0V6Nae2a3HghrNr1LY8841jJGYnJb37et6BqADc1qES7jDg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.13.0.tgz",
+ "integrity": "sha512-1ljgXM1Ux2J73J2mNd01Xh1Bk7Por0MXk6fQ4TP/qp4A5ohF6CmiBVNWSBkWLfEY7TNHfvOIIIiDGfc0Ko0UFw==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-icon-registry-essential": "1.13.0-rc.2",
- "@umbraco-ui/uui-input": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-icon-registry-essential": "1.13.0",
+ "@umbraco-ui/uui-input": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-keyboard-shortcut": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.13.0-rc.2.tgz",
- "integrity": "sha512-9QQLwpiumqOEd/5xwkuexmxf6dqg8ON9vsCwf4kT22nGVQ+7tbU5hUt9Q/Mdme/ivcUZC/RAaLILnXVmGt3YZA==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.13.0.tgz",
+ "integrity": "sha512-zKh674a19swyaZiLI/vCws56H4P+lUCIQxu+/U3280zGQqp35vCj0RhvbU2zA4QCCvTEWSrOOQwyu019zEEz5w==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-label": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.13.0-rc.2.tgz",
- "integrity": "sha512-CGGYEiQ4VIs0tkHFra0ECjDVypmxSLQ+Onta6iD25ttYknxTCMmU5b1hNWcTomIQ3kpj0loIEa0MWB0c4if/jw==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.13.0.tgz",
+ "integrity": "sha512-BcfvqdFybY0Vb7TVinfHDLrAyhmtayz5ZGXwnTZpwyg7IP+pPZrFunrhcPPTPIrvJEw/j7qgpfC2AKMsiaZq7A==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-loader": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.13.0-rc.2.tgz",
- "integrity": "sha512-3em8IE/PwKZUJqGaeW9xvH9pPxbRyjevbqgHNDfYVp5BMMt+p8cdbtbr+4Izs3fYhH1xUkCjktAUVo+QDptJfw==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.13.0.tgz",
+ "integrity": "sha512-AmNcIX7XNtW9dc29TdlMgTcTJBxU7MCae9iivNLLfFrg3VblltCPeeCOcLx77rw/k9o/IWrhLOsN81Q0HPfs7g==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-loader-bar": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.13.0-rc.2.tgz",
- "integrity": "sha512-X0QNZr8xQvUZJmzhILPTzZtnb+DHhF3nMkpkSEnabsYLCq01a4aPKq6HTc/CnFb5fQHBXAjv8qOR+5kvMcd9Ug==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.13.0.tgz",
+ "integrity": "sha512-wRJl2n6VesXV5z7EOz3W8DKDo2XLbpb4a9HZHauDtnGl9aNQggcFYBXTrLAvqg2Nuir2g52kQT9mDcQiTDxJLQ==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-loader-circle": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.13.0-rc.2.tgz",
- "integrity": "sha512-my5lkXCzEhNVehFYLUZAIFaxXLND9XlV/5uqTwyFlFWWKjXZU4cloSWFriwoh3bXEXmKf65cBeGu151bNyOtxQ==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.13.0.tgz",
+ "integrity": "sha512-efDzwo7uC7bs60boAvJNtqI7CFTe/4R5xdyi9khSF9w/0WnMRgIsY9h7xQLWCycjC/Nvoe/UwBZQ6P5khkdfaw==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-menu-item": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.13.0-rc.2.tgz",
- "integrity": "sha512-O0rJ2W70Wz9KyVyOnv7g4/GJjCDW8qaOUy9dPySlGPF+cg94K0liCiKmsOFCPnqPe2Pq7BsB7KcrYb6CEbSrwA==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.13.0.tgz",
+ "integrity": "sha512-Rl3y+gXyN4ZLdzhhmCW1dWWC53erFVnVV1OTm2paYk1w13du/T4S+X7J0uyobrc1E3iFTjAFNh0UuvHxRsxtcQ==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-loader-bar": "1.13.0-rc.2",
- "@umbraco-ui/uui-symbol-expand": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-loader-bar": "1.13.0",
+ "@umbraco-ui/uui-symbol-expand": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-modal": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.13.0-rc.2.tgz",
- "integrity": "sha512-HvMVbMor48akQljQGTqAasZNiCGxiyUMx43DrbiRfM69ZBMiZndxOnuSNnZEUFBDXC0s8dWu7LmwTZXcu7ATXA==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.13.0.tgz",
+ "integrity": "sha512-uiBmQg4gE3S9lreABkLbr4kSRdZAbkxzavBZ27DTDWjeez5Zn+sqy+FckPGct+HZheTdXgGF+M4YRypZj7gOhg==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-pagination": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.13.0-rc.2.tgz",
- "integrity": "sha512-USdMP45lbFF3KtonULFDpIRuJrD7XhnUC7Vqd0S71BI4XUxDiQkl+qHHLIUR0cqyY9goOtMY/dO6R6saxMwufA==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.13.0.tgz",
+ "integrity": "sha512-RtD+szI8P+7y2tKBLLPJyCOlfS504LgQqD4pUOZbxemsQmMe37OZ1CiiqfrNJVEv4TbMHP0WvoRiLFDawICXnw==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-button": "1.13.0-rc.2",
- "@umbraco-ui/uui-button-group": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-button": "1.13.0",
+ "@umbraco-ui/uui-button-group": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-popover": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.13.0-rc.2.tgz",
- "integrity": "sha512-qbBcIwk7sxBG2broK/cfilep3BSoTUE0lYmoG9tKvrv9syoJpB+2/iNvRZauzO9JE+sjJHHvi1xAVUn9mlG6Jg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.13.0.tgz",
+ "integrity": "sha512-fpN0x+9p7cxrKiBYzEbpAYuIFYxWlUDrv4jYw3+oEI1ZP2wlS4dKphxhNtLreGrbaYsSUXe8Vgx9wy3eFawfug==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-popover-container": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.13.0-rc.2.tgz",
- "integrity": "sha512-hdpRFUqeR6LkJT14vqRfIKff3vkmomZ0fijK1HRxLHcZ3fg+bXt03B9k+SUV+DCf+HGq8o6qMpW6pEQxjbdnEw==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.13.0.tgz",
+ "integrity": "sha512-pNvfRLjFzRY7j8bJ1LDGROBZ1+h4HbKqr7O4bs8z8ZfdrxqHb1k/BssbSNt25JFmoHDSRZbFs3yBL9jhVEr/Xg==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-progress-bar": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.13.0-rc.2.tgz",
- "integrity": "sha512-4lgMfXHRw/am1ZAlnKNeaCVg0m4BZPo+YSpYqdcaNNm/n8pFoFkY0vhpeBwI2kHAbgtlGgr3AO2rXj8kNS500A==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.13.0.tgz",
+ "integrity": "sha512-QRgydD21AJfmv89WWiim8O/7XR0BTsWP73lga2Tbj3OU/8jjw0vcqmjzf0uhOir5SL1Y0Y1CT/SPUjgxc0VC0g==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-radio": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.13.0-rc.2.tgz",
- "integrity": "sha512-ysjuxkgADGNeQeRc4S6zfGGKaN9SCRBqaDj7oTX4Dof7cAYMlG3Sn1lOWZSYPko16Aq5P8R236PgEAAH/9b5yw==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.13.0.tgz",
+ "integrity": "sha512-SsSqyte7n2KEqEjmtI2ajUX6m0AL6nreSZ53IGViMBim8bTcW4oBq5Wbp3dll+s88WvExppozE2iA1kLgjijOw==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-range-slider": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.13.0-rc.2.tgz",
- "integrity": "sha512-VoreL2wykxjXFguQWZeUpNVb7/VQ2ejr7IrqzdfIGZj0kS0Mu86e7dypc5iIZGDAdzkCylljn90kSHbuQQkxfg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.13.0.tgz",
+ "integrity": "sha512-gplKOzASnz2spVVkwgj1kYAPFRqp4KRuDFlWyr2IY5luvTajUZC6JOB4aQDs5+OMbgYvF4G+PKFEapuYnR7gNg==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-ref": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.13.0-rc.2.tgz",
- "integrity": "sha512-tAuYfBC/F0t3vP2HXPhoYbuh87vAx5yRhk9ZqqKliJjX2mQTPpxypAShxEaWjmxRrMDnpLO5/xASI8IQhgKL0g==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.13.0.tgz",
+ "integrity": "sha512-jgVgHFa7/5zcg86Rtkecp7XO9FENQUQ7uMZyCAUHYCPur/n0CKNBrVjwQ/PEI0o1VR+eRGUG5iByZgQW1yWTtQ==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-ref-list": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.13.0-rc.2.tgz",
- "integrity": "sha512-sT2psZE7T4iQRzmCWXADX1K1kSeyRipOLohDZfKMw1djjtcdwWuCTNwrb4DdyxwCY47/TNudz2w7/26VXcxd1w==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.13.0.tgz",
+ "integrity": "sha512-9Nw03JwymtGnkqvnEeRhmSS+F7Xlzp7yef4R/WdZEIYASV42vwCIAj5Wdj2JI+Apc0ZVZ4hCQhbfNVsr+e7ddQ==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-ref-node": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.13.0-rc.2.tgz",
- "integrity": "sha512-N2Efv7YB2rzGgwr8ZwvzKgGtGsX4yGWJGZ2B3ZP6d5m5OPkh6/zyQdOuqXuh2pynIMX7/H85hddr/WlSM+2jxg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.13.0.tgz",
+ "integrity": "sha512-otjHMgmaekLl3iPwDjLgJ6H7HIWF3QqNLNAiHnGZ1pdShJyLfajvHxnDULeUuI6zRDdjavzz3fhPUnjzyraYpQ==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-icon": "1.13.0-rc.2",
- "@umbraco-ui/uui-ref": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-icon": "1.13.0",
+ "@umbraco-ui/uui-ref": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-ref-node-data-type": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.13.0-rc.2.tgz",
- "integrity": "sha512-MQaWjANvOqfoIqmJLdVhq0155A1t6huxPpvzjL9OB0f8sqIJfWq18zDREqlcfi6lTlXT7UvswNCa6yzDwLNMGA==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.13.0.tgz",
+ "integrity": "sha512-M5+7ekzpoNJmjD8YvH5TQPb1ENbIwnjyXyUv7IiXV2AtTF/H/g0t4pEU+SYhlrAe61VyW5TedtAMWK+oDKrWUg==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-ref-node": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-ref-node": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-ref-node-document-type": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.13.0-rc.2.tgz",
- "integrity": "sha512-Ca4NHFIQ71/DhRL883bgip0c98Dtqwe3TOiMjOX9qek5VnOik+ZyFQyVBN3jZGk+ndjkTf8P2wB7Hmz/bHaFmQ==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.13.0.tgz",
+ "integrity": "sha512-AsTjfuAKak/cdaZaJCjuu19YyYwC2FunPP2fz2PuXPr7ULcmo78oYIZY6YJPUsPlBSMN5PIIr9iox3ob6Dd+Rg==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-ref-node": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-ref-node": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-ref-node-form": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.13.0-rc.2.tgz",
- "integrity": "sha512-aqvYjfNmZO5wdy79L2Da9bYih8nuib6GC1T9NWWKHnXSITo3f7i50U0I1Q5u1Odd73U2iT9ISqQoXJAy3Ad25Q==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.13.0.tgz",
+ "integrity": "sha512-ySHrq0xH4l69NH12pXzfPrrMG9fRnHF7ul+iKSrPvqUdWnsNpEzYakGvt6XXji1x3ogZEKnKMlzAXrHZDL8LoA==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-ref-node": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-ref-node": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-ref-node-member": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.13.0-rc.2.tgz",
- "integrity": "sha512-OLcrEXzQQ6L9lCTQCZBLpww3Gtde7G7R2U6OiYnpU1Ch6q84G2XrEPU3FLplmEUToS709gaIfeNbZDb59jwUEg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.13.0.tgz",
+ "integrity": "sha512-UiSAxsPjpivbI0Hm1fZ1O7nTgkSjPigi9z5RWT4P0viiYetrc8ggJpZ5cDFEQH2xBe40qfBQQGr8vJ4Gsz3opg==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-ref-node": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-ref-node": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-ref-node-package": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.13.0-rc.2.tgz",
- "integrity": "sha512-PcWGz56HYpb6pxe1rDI7W3LBuhUthzu5hL2O9svcia52wKJV0uwGJ6sYUjQSCvhS/qIjGQgB+5Dq8Y/Y1vTI/w==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.13.0.tgz",
+ "integrity": "sha512-iANsUZDFjLQdalKVI007LuNDlEsruh88peWiBrDN47HtRZlZ/tLV67Ljd5oRjZhAFZLjjFQ1jl0DOkkD3lKIFw==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-ref-node": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-ref-node": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-ref-node-user": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.13.0-rc.2.tgz",
- "integrity": "sha512-amM+ovH7G2h/M69d+Ip7QmjyGg6MD0zN5rOw4CNN9t4WqAKb4YjlmQPDMTK/fOzUazyChgCfAIHV41x6NYGqxw==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.13.0.tgz",
+ "integrity": "sha512-XckwV6nrJjWeeBZX7j28fJdJZlzugyhfXIacp6+AnFHrL5NXjsb3Hi4ZLt00CurcxmhVVta+J5uvmOLSVi7Myw==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-ref-node": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-ref-node": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-scroll-container": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.13.0-rc.2.tgz",
- "integrity": "sha512-de9JYsi+Cp8J3sgKhwkP0c7r3n9/GQGsDESoSht10VzH/UuxQBdCneUGXCB8cMfJe5An6dm/p0IFR23cby3DtA==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.13.0.tgz",
+ "integrity": "sha512-3Qnl6aGxRs2FYvZzskZYFXnDsej5vBHalu/0b7VKfTPdUMJuAtR+1rz+veLvm9hL5pf9sJbSx4IZe+BubrYmnw==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-select": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.13.0-rc.2.tgz",
- "integrity": "sha512-Qp7wg1o6Vg1zlPrI8xbUOQho3tYX4S2+2BICRY69KUa8D8zUuvu0EL5OesDjpWmaPtPg88ku9gTc12PB/pCDuQ==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.13.0.tgz",
+ "integrity": "sha512-JF4Jtgc/H61tdPVD01kkBRkUofWatrUG9diRMuaGPvQsWEVNvbCJKXJ+Fww9pMQts25EidLhhtqG2hX3ZSsgYA==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-slider": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.13.0-rc.2.tgz",
- "integrity": "sha512-sDM53G8Ji+SnTjfL0u49TRn62da2hoZcNGy6AwyYSFpbccRZtWmfom/CdkLpVQxN/gJReOWsfOL8nqajSxah5Q==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.13.0.tgz",
+ "integrity": "sha512-r4QE+V0LTyn1NAyHsLBkHAvu1jzqfpQ9mOIzwt7ekpuKUrlbaB+LWVo+lxiX7ShUVHxL+0squ0/1CNrLquz0AA==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-symbol-expand": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.13.0-rc.2.tgz",
- "integrity": "sha512-kEhXQ3rg5TH/m3hNvQNhwmY6SWwWuRfb6WdKj7QfLm2WEfJ6n0y2E8O0DGzkWttD39WTURcGOZlL63ZrPHuPgg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.13.0.tgz",
+ "integrity": "sha512-nR6Ld0ZrWQX0Ijk1y+3sRXMaAh87uaQkhcIHgS9Ziz+F1JbCf5WCygla3Xux5t+zpxhPVy6yvZc3iWJxQMp1TA==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-symbol-file": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.13.0-rc.2.tgz",
- "integrity": "sha512-ekaK5KWRhtTUGtgRd/PEWGWZShgtizmjx9OfXkx8e6lQZn+eXnzTv5ErxauDKOxK/cPDp42oM2OLDRi73ewMTg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.13.0.tgz",
+ "integrity": "sha512-F3+MyQaGDUYr+Z0VyBmZaIirPKSkON2Gu6jrb8kX04UuqVPIRvoxjubGTmu6wU5M0ATRt/NIG5CzYJp33R8bGA==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-symbol-file-dropzone": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.13.0-rc.2.tgz",
- "integrity": "sha512-5+tQ9xNnO/asfvhHxAr+O2+8oB0UWeymnebc4CuygNtuQ0A4S+8ypUFY/xvPUxzhCkKVYD9nrwoaDYvT8cMvBg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.13.0.tgz",
+ "integrity": "sha512-ko3+WSdgd3pG3SI5eUPJ/VbOYTW86AW6EmYDrsALAdRdKhQ5Kusbe7M8Uds8BB3EJ9GT9+xcjocLNMCTxV8soA==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-symbol-file-thumbnail": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.13.0-rc.2.tgz",
- "integrity": "sha512-Bz8fIKiFIbNtniAjerhqjbi4BKAVvnnh8aM/9eHnW/Gbtha8bzTN5zBPDLxzHsEe2VW8EEIE95NC2Gp0MkCscQ==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.13.0.tgz",
+ "integrity": "sha512-fgdJcecVz39MuFTTneD9yI8K/4KZQkHaARfvcWmc2rvRD8S5HzGcp/a+y0zOmzLIpKi3Sjlwc/4d123nE3V0NQ==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-symbol-folder": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.13.0-rc.2.tgz",
- "integrity": "sha512-XqOxR9xmSsWHFzGVFjA7vd7isdmeEe/t0mt8/ePJTgv+m+pmlqjietaXulYMywJoF5zE3Q2nT4azXLzL/6mTng==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.13.0.tgz",
+ "integrity": "sha512-TIh45JTtvv6l+/7+UtSQxxyvtIyiv9tVv1QC4SKesW19opUkWiaNd5awaKlshi+Iu9CbXvCVwxTJ6TK5z3hsaA==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-symbol-lock": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.13.0-rc.2.tgz",
- "integrity": "sha512-W4wECvErayB31p15Eh6rP+LGxLfwO6HqEGIuvQXZVuXGxQDL6ObRc3L1bpy6LT/VgVFT6fSlYEBOC9110xCTZQ==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.13.0.tgz",
+ "integrity": "sha512-/39U8n0DfHNI4I2X1WX8dJv6pSOHeJMvpyS1Cla54Q45gtt7RHMU55aNEGBZoF19oPV2W74gl7vfcHGTtnPKNQ==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-symbol-more": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.13.0-rc.2.tgz",
- "integrity": "sha512-YVTBbhSuz2+lDhoP4hLi3Ru8ZLEiUJ1dT7AXfkM3CRvHeAL2oqYlNyCkz2Hwr/DcCJ8kappxkoEesKdi3NtqBw==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.13.0.tgz",
+ "integrity": "sha512-mEwbSezTZfG77MUdWrFGXkMaSBHpC899lToDONTnQurkvLBxbBRBlT+xhHo54psAzJX7C6NLRvExTMztGU1OeQ==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-symbol-sort": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.13.0-rc.2.tgz",
- "integrity": "sha512-wgW1zTHhnPXEwSmVp0PYFfli856Jta+rvfuxK5LlNbfS+TZX/enOOE1RzdpBfw0eCeMA1RyqpGTZnBZOzs9axg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.13.0.tgz",
+ "integrity": "sha512-BDcvHXrueX3d6sFcQa5jzxlV1C0OdhymN1Q5GzXpby2xLJTjNbeGISdgHGCxjLPsmHUAAZ7XCGR8pqI0F+8Hpg==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-table": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.13.0-rc.2.tgz",
- "integrity": "sha512-3XTkx08WtRJcA5TwJrcIYFMOC20qCcsfLuDNaTzUtkE9420iHv4uwVYCuLez8tY6aDS4Lzp8B2ej9EsTLGMJkg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.13.0.tgz",
+ "integrity": "sha512-CKoPIsqURMtR6PwaSs4UvB56LVLMTop93gArI//yN9Ox1/w7awxnnkRN2skpKIbtDHrbEBI//NJE50jPoS82eg==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-tabs": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.13.0-rc.2.tgz",
- "integrity": "sha512-A+PwNIGHeA2QT74WSXjVoP/rTfZnfNOM58NLKqjHGUSZwNb8PM+ANClqflKUSvvAAWy9KTMQdJe+Pt8F63FMtQ==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.13.0.tgz",
+ "integrity": "sha512-b50xJ1Xka8nu51GCR8n2RZtCFjwTYDXV5zQF+s5KXpgC6A8mahCvzmmINHdgGnKm1JNG3c8abhwrueyxxVdI8Q==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-button": "1.13.0-rc.2",
- "@umbraco-ui/uui-popover-container": "1.13.0-rc.2",
- "@umbraco-ui/uui-symbol-more": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-button": "1.13.0",
+ "@umbraco-ui/uui-popover-container": "1.13.0",
+ "@umbraco-ui/uui-symbol-more": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-tag": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.13.0-rc.2.tgz",
- "integrity": "sha512-672d/39UffEjScpgWz3lvpDJMEPeWi9WYpONrBtpOlTW/qBrqBke50njSPjyby60TqVNxTmvCMLzoXpyNeffjg==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.13.0.tgz",
+ "integrity": "sha512-SBY9Mi9A89jIag7URKQqXW3omDk5Eczw2LuNF7VnkXmQCuvsiRP6/BzSBCh9m0RrD4QOLSXpYGgSoLSpS7MitA==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-textarea": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.13.0-rc.2.tgz",
- "integrity": "sha512-MTEkXgMt8ttyXk5qPYDJ3mMHbgxFE8mejR5Do2dT9UJPKJkbP73SKpvQNru95slbrumdTwpj4lxAlPS6ZEZg7Q==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.13.0.tgz",
+ "integrity": "sha512-H4XChy1m5gq43eySQ3Zp/AsBvh35Gk0VLijFxdhCfV+HHpuyrt0fJsYnjq1W1xoqhyt7h84YRpNIJMyAIm2WHQ==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-toast-notification": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.13.0-rc.2.tgz",
- "integrity": "sha512-aGLdQbkWEj9C+fBXREFv+OR/1LPZhbGx586UiEykMK57SusT41091imfYmyW+sf3cJ/7jlHO/ykBd6mT3cM6yQ==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.13.0.tgz",
+ "integrity": "sha512-o45G8hWXgqcfGaJM+nhCTDSpevREJd+gPKT5XhTkD2wA99/kevdedmlYIgKS+9wONLk5A0j8qnsbWntinbb+rw==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-button": "1.13.0-rc.2",
- "@umbraco-ui/uui-css": "1.13.0-rc.2",
- "@umbraco-ui/uui-icon": "1.13.0-rc.2",
- "@umbraco-ui/uui-icon-registry-essential": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-button": "1.13.0",
+ "@umbraco-ui/uui-css": "1.13.0",
+ "@umbraco-ui/uui-icon": "1.13.0",
+ "@umbraco-ui/uui-icon-registry-essential": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-toast-notification-container": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.13.0-rc.2.tgz",
- "integrity": "sha512-W2UBjLbDQBh220aIlKThDmdw8sKPxj9Ux4GRuH1Tgh0QZSC5C7PC5gX5MKcrFWKGeyvn/HCfgni8GaED2Jmnxw==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.13.0.tgz",
+ "integrity": "sha512-9O0t73v7qkb3+VE8i0pD1vo33tNt1U7t3L6699jNMZZr+7R6a5YOAVrFt+gs+kQcQXWt0HCfQxhKJ8opLoBOyw==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-toast-notification": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-toast-notification": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-toast-notification-layout": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.13.0-rc.2.tgz",
- "integrity": "sha512-qnLmsAB0KmZ28O3ULg8ieuPERka6GG3Ck80Z8jOPCWNHPnIVTjG7gr7jnB/bkkd3OSh67XPq87CYb9ldSnmABw==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.13.0.tgz",
+ "integrity": "sha512-yhz8msOc1ngA//oBDefrR8pagTbvAenBiyk/fPuEwGQriM43e8bbVCJvhmrsTuAzAL8nn/ilKhAU5lrkn2rAmg==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-css": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-css": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-toggle": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.13.0-rc.2.tgz",
- "integrity": "sha512-/Uku6MgByYXdW34jfsO5C2qsqXvFJCdzoYl/nxXISB8PzdEYfGWYkWTcF25hfxIzZyoo3e/dDLdFsfhk0gBcXA==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.13.0.tgz",
+ "integrity": "sha512-tHzG/Lh9vRLjPu7EhFupaD7jkpVenyEM3iIsA24wBVKmqJGxacpuuuOwpTv6vGGiIYSKfRDXTDk07Q6MHDSy4g==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2",
- "@umbraco-ui/uui-boolean-input": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-boolean-input": "1.13.0"
}
},
"node_modules/@umbraco-ui/uui-visually-hidden": {
- "version": "1.13.0-rc.2",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-visually-hidden/-/uui-visually-hidden-1.13.0-rc.2.tgz",
- "integrity": "sha512-jwYrycIDPNQ/pqD6S1GWbKz/3761d0Q7fPZir2psRcJlSA6UDN7esL/lo1nNgNK4gXzC7MLbXPNnG90g1FZT/A==",
- "license": "MIT",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-visually-hidden/-/uui-visually-hidden-1.13.0.tgz",
+ "integrity": "sha512-1ayTJylWnpAl0VQE7X2PBJCKLZ15R+xfZ3yy4ygT751k4wML26nvdWscp/tYfl4MteqrHtNJKTRTFoQ1Dn/r/g==",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0-rc.2"
+ "@umbraco-ui/uui-base": "1.13.0"
}
},
"node_modules/@vitest/expect": {
@@ -7176,8 +7092,7 @@
"node_modules/colord": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
- "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==",
- "license": "MIT"
+ "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw=="
},
"node_modules/command-line-args": {
"version": "5.2.1",
diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json
index f17919f040..507540834f 100644
--- a/src/Umbraco.Web.UI.Client/package.json
+++ b/src/Umbraco.Web.UI.Client/package.json
@@ -218,8 +218,8 @@
"@tiptap/pm": "2.11.5",
"@tiptap/starter-kit": "2.11.5",
"@types/diff": "^7.0.1",
- "@umbraco-ui/uui": "1.13.0-rc.2",
- "@umbraco-ui/uui-css": "1.13.0-rc.2",
+ "@umbraco-ui/uui": "^1.13.0",
+ "@umbraco-ui/uui-css": "^1.13.0",
"diff": "^7.0.0",
"dompurify": "^3.2.4",
"element-internals-polyfill": "^1.3.13",
From 3e835cd9492709cf91126abdaa118b6a25a032e5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Niels=20Lyngs=C3=B8?=
Date: Mon, 31 Mar 2025 13:55:26 +0200
Subject: [PATCH 29/38] set circular dependency limit to 9 (#18786)
---
.../devops/circular/index.js | 16 ++++++++++++----
.../tree-expansion-manager.test.ts | 1 -
2 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/devops/circular/index.js b/src/Umbraco.Web.UI.Client/devops/circular/index.js
index 4683c12e7c..ac7c0f4970 100644
--- a/src/Umbraco.Web.UI.Client/devops/circular/index.js
+++ b/src/Umbraco.Web.UI.Client/devops/circular/index.js
@@ -7,9 +7,9 @@
import madge from 'madge';
import { join } from 'path';
-import { mkdirSync } from 'fs';
+//import { mkdirSync } from 'fs';
-const __dirname = import.meta.dirname;
+//const __dirname = import.meta.dirname;
const IS_GITHUB_ACTIONS = process.env.GITHUB_ACTIONS === 'true';
const IS_AZURE_PIPELINES = process.env.TF_BUILD === 'true';
const baseDir = process.argv[2] || 'src';
@@ -40,15 +40,23 @@ if (circular.length) {
}
console.error('\nPlease fix the circular dependencies before proceeding.\n');
+ /*
+ // Curently disabled as we don't have Graphviz installed on the CI servers neither do we use this visualization currently.
+ // Ideally its an opt in feature that is triggered by a environment variable.
try {
const imagePath = join(__dirname, '../../madge');
mkdirSync(imagePath, { recursive: true });
const image = await madgeSetup.image(join(imagePath, 'circular.svg'), true);
console.log('Circular dependencies graph generated:', image);
} catch { console.warn('No image generated. Make sure Graphviz is in your $PATH if you want a visualization'); }
+ */
- // TODO: Set this to 1 when we have fixed all circular dependencies
- process.exit(0);
+ // TODO: Remove this check and set an exit with argument 1 when we have fixed all circular dependencies.
+ if (circular.length > 11) {
+ process.exit(1);
+ } else {
+ process.exit(0);
+ }
}
console.log('\nNo circular dependencies detected.\n');
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/tree-expansion-manager.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/tree-expansion-manager.test.ts
index 9f9adb5d7a..178834786d 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/tree-expansion-manager.test.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/tree-expansion-manager.test.ts
@@ -57,7 +57,6 @@ describe('UmbTreeExpansionManager', () => {
const isExpanded = manager.isExpanded(item);
expect(isExpanded).to.be.an.instanceOf(Observable);
manager.isExpanded(item).subscribe((value) => {
- console.log('VALUE', value);
expect(value).to.be.true;
done();
});
From 4cd43b264dd11ff815b45b266ad5ee2fa3f2caac Mon Sep 17 00:00:00 2001
From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
Date: Mon, 31 Mar 2025 14:06:45 +0200
Subject: [PATCH 30/38] fix: adds localization for "Unnamed"
---
.../collection/views/grid/user-grid-collection-view.element.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts
index 812790a01f..06e8e96027 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts
@@ -78,7 +78,7 @@ export class UmbUserGridCollectionViewElement extends UmbLitElement {
return html`
0}
From 8c1dfe5431e644f126df308855096f7046d219ad Mon Sep 17 00:00:00 2001
From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
Date: Mon, 31 Mar 2025 14:07:32 +0200
Subject: [PATCH 31/38] chore: fix lint errors
---
.../block-grid/block-grid-manager/block-grid-manager.context.ts | 2 +-
.../block/block-list/context/block-list-manager.context.ts | 2 +-
.../block/block-rte/context/block-rte-manager.context.ts | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/block-grid-manager/block-grid-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/block-grid-manager/block-grid-manager.context.ts
index c60942be31..51676cad72 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/block-grid-manager/block-grid-manager.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/block-grid-manager/block-grid-manager.context.ts
@@ -10,7 +10,7 @@ import {
import { transformServerPathToClientPath } from '@umbraco-cms/backoffice/utils';
import { UmbBlockManagerContext } from '@umbraco-cms/backoffice/block';
import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app';
-import type { UmbBlockDataModel, UmbBlockDataObjectModel } from '@umbraco-cms/backoffice/block';
+import type { UmbBlockDataModel } from '@umbraco-cms/backoffice/block';
import type { UmbBlockTypeGroup } from '@umbraco-cms/backoffice/block-type';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts
index 356d0cd8af..1f9b8da98f 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts
@@ -2,7 +2,7 @@ import type { UmbBlockListLayoutModel, UmbBlockListTypeModel } from '../types.js
import type { UmbBlockListWorkspaceOriginData } from '../index.js';
import type { UmbBlockDataModel } from '../../block/types.js';
import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api';
-import { UmbBlockManagerContext, type UmbBlockDataObjectModel } from '@umbraco-cms/backoffice/block';
+import { UmbBlockManagerContext } from '@umbraco-cms/backoffice/block';
/**
* A implementation of the Block Manager specifically for the Block List Editor.
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts
index 22f41b45e7..c18d7b9b7c 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts
@@ -1,7 +1,7 @@
import type { UmbBlockRteWorkspaceOriginData } from '../workspace/block-rte-workspace.modal-token.js';
import type { UmbBlockRteLayoutModel, UmbBlockRteTypeModel } from '../types.js';
import type { UmbBlockDataModel } from '../../block/types.js';
-import { UmbBlockManagerContext, type UmbBlockDataObjectModel } from '@umbraco-cms/backoffice/block';
+import { UmbBlockManagerContext } from '@umbraco-cms/backoffice/block';
import '../components/block-rte-entry/index.js';
From c5a3778ce71b671bcb93e4eaac6effd0ca21f2b9 Mon Sep 17 00:00:00 2001
From: NguyenThuyLan <116753400+NguyenThuyLan@users.noreply.github.com>
Date: Mon, 31 Mar 2025 14:41:32 +0200
Subject: [PATCH 32/38] Fix issue text overflow when user name is too long
(#18587)
* Fix issue text overflow when user name is too long
* add ellipsis at the end of the text
* Bumped version of test helper
* Fixed document type design tab tests due to test helper changes
* Amends based on Niels' feedback
+ other code formatting tweaks.
* chore: remove unknown attribute on uui-tag
* fix: adds fallback text and uses ` ` where applicable
---------
Co-authored-by: Lan Nguyen Thuy
Co-authored-by: Nhu Dinh
Co-authored-by: leekelleher
Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
---
.../grid/user-grid-collection-view.element.ts | 82 +++++++++++--------
.../DocumentTypeDesignTab.spec.ts | 2 +-
2 files changed, 50 insertions(+), 34 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts
index 06e8e96027..1a1797488f 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts
@@ -1,15 +1,24 @@
-import { getDisplayStateFromUserStatus } from '../../../utils.js';
-import type { UmbUserCollectionContext } from '../../user-collection.context.js';
-import type { UmbUserDetailModel } from '../../../types.js';
+import { getDisplayStateFromUserStatus, TimeFormatOptions } from '../../../utils.js';
+import { UmbUserKind } from '../../../utils/index.js';
import { UMB_USER_COLLECTION_CONTEXT } from '../../user-collection.context-token.js';
import { UMB_USER_WORKSPACE_PATH } from '../../../paths.js';
-import { UmbUserKind } from '../../../utils/index.js';
-import { css, html, nothing, customElement, state, repeat, ifDefined } from '@umbraco-cms/backoffice/external/lit';
-import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
+import type { UmbUserCollectionContext } from '../../user-collection.context.js';
+import type { UmbUserDetailModel } from '../../../types.js';
+import {
+ css,
+ customElement,
+ html,
+ ifDefined,
+ nothing,
+ repeat,
+ state,
+ when,
+} from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
+import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
+import { UmbUserGroupCollectionRepository } from '@umbraco-cms/backoffice/user-group';
import { UserStateModel } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbUserGroupDetailModel } from '@umbraco-cms/backoffice/user-group';
-import { UmbUserGroupCollectionRepository } from '@umbraco-cms/backoffice/user-group';
@customElement('umb-user-grid-collection-view')
export class UmbUserGridCollectionViewElement extends UmbLitElement {
@@ -23,6 +32,7 @@ export class UmbUserGridCollectionViewElement extends UmbLitElement {
private _loading = false;
#userGroups: Array = [];
+
#collectionContext?: UmbUserCollectionContext;
// TODO: we need to use the item repository here
@@ -33,11 +43,13 @@ export class UmbUserGridCollectionViewElement extends UmbLitElement {
this.consumeContext(UMB_USER_COLLECTION_CONTEXT, (instance) => {
this.#collectionContext = instance;
+
this.observe(
this.#collectionContext.selection.selection,
(selection) => (this._selection = selection),
'umbCollectionSelectionObserver',
);
+
this.observe(this.#collectionContext.items, (items) => (this._users = items), 'umbCollectionItemsObserver');
});
@@ -48,7 +60,9 @@ export class UmbUserGridCollectionViewElement extends UmbLitElement {
this._loading = true;
const { data } = await this.#userGroupCollectionRepository.requestCollection();
+
this.#userGroups = data?.items ?? [];
+
this._loading = false;
}
@@ -74,12 +88,10 @@ export class UmbUserGridCollectionViewElement extends UmbLitElement {
}
#renderUserCard(user: UmbUserDetailModel) {
- const href = UMB_USER_WORKSPACE_PATH + '/edit/' + user.unique;
-
return html`
0}
?selected=${this.#collectionContext?.selection.isSelected(user.unique)}
@@ -90,8 +102,7 @@ export class UmbUserGridCollectionViewElement extends UmbLitElement {
slot="avatar"
.name=${user.name}
.kind=${user.kind}
- .imgUrls=${user.avatarUrls}
- style="font-size: 1.6rem;">
+ .imgUrls=${user.avatarUrls}>
`;
}
@@ -102,13 +113,11 @@ export class UmbUserGridCollectionViewElement extends UmbLitElement {
}
const statusLook = user.state ? getDisplayStateFromUserStatus(user.state) : undefined;
- return html`
-
- `;
+ return html`
+
+
+
+ `;
}
#renderUserGroupNames(user: UmbUserDetailModel) {
@@ -122,17 +131,18 @@ export class UmbUserGridCollectionViewElement extends UmbLitElement {
#renderUserLoginDate(user: UmbUserDetailModel) {
if (user.kind === UmbUserKind.API) return nothing;
-
- if (!user.lastLoginDate) {
- return html`${`${user.name} ${this.localize.term('user_noLogin')}`}
`;
- }
- const lastLoggedinLocalTime: Date = new Date(user.lastLoginDate);
- const formattedTime = lastLoggedinLocalTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
-
- return html`
-
- ${this.localize.date(user.lastLoginDate)} ${formattedTime}
-
`;
+ return html`
+
+ ${when(
+ user.lastLoginDate,
+ (lastLoginDate) => html`
+ Last login
+ ${this.localize.date(lastLoginDate, TimeFormatOptions)}
+ `,
+ () => html`has not logged in yet `,
+ )}
+
+ `;
}
static override styles = [
@@ -145,19 +155,23 @@ export class UmbUserGridCollectionViewElement extends UmbLitElement {
#user-grid {
display: grid;
- grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: var(--uui-size-space-4);
}
uui-card-user {
width: 100%;
- height: 180px;
justify-content: normal;
padding-top: var(--uui-size-space-5);
+ flex-direction: column;
+
+ umb-user-avatar {
+ font-size: 1.6rem;
+ }
}
.user-login-time {
- margin-top: auto;
+ margin-top: var(--uui-size-1);
}
`,
];
@@ -165,6 +179,8 @@ export class UmbUserGridCollectionViewElement extends UmbLitElement {
export default UmbUserGridCollectionViewElement;
+export { UmbUserGridCollectionViewElement as element };
+
declare global {
interface HTMLElementTagNameMap {
'umb-user-grid-collection-view': UmbUserGridCollectionViewElement;
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 e022b8b726..65cd18cde2 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
@@ -397,7 +397,7 @@ test('can enable validation for a property in a document type', async ({umbracoA
test('can allow vary by culture for a property in a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
// Arrange
const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
- await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, groupName, true);
+ await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, groupName, true, false);
await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
// Act
From f200da48d82eed52a94ea1a456f775e812bc452c Mon Sep 17 00:00:00 2001
From: Andy Butland
Date: Mon, 31 Mar 2025 15:16:41 +0200
Subject: [PATCH 33/38] Revert rather than prevent updates to sensitive
properties on members without sensitive data access (#18794)
* Revert rather than prevent updates to sensitive properties on members without sensitive data access.
* Added suppression for integration test updates.
---
.../Services/MemberEditingService.cs | 11 ++--
.../member-workspace-view-member.element.ts | 45 ++++++++-----
.../CompatibilitySuppressions.xml | 14 +++++
.../Services/MemberEditingServiceTests.cs | 63 +------------------
4 files changed, 50 insertions(+), 83 deletions(-)
diff --git a/src/Umbraco.Infrastructure/Services/MemberEditingService.cs b/src/Umbraco.Infrastructure/Services/MemberEditingService.cs
index 4b6a50fae0..d87c9d43ce 100644
--- a/src/Umbraco.Infrastructure/Services/MemberEditingService.cs
+++ b/src/Umbraco.Infrastructure/Services/MemberEditingService.cs
@@ -149,12 +149,11 @@ internal sealed class MemberEditingService : IMemberEditingService
if (user.HasAccessToSensitiveData() is false)
{
- // handle sensitive data. certain member properties (IsApproved, IsLockedOut) are subject to "sensitive data" rules.
- if (member.IsLockedOut != updateModel.IsLockedOut || member.IsApproved != updateModel.IsApproved)
- {
- status.ContentEditingOperationStatus = ContentEditingOperationStatus.NotAllowed;
- return Attempt.FailWithStatus(status, new MemberUpdateResult());
- }
+ // Handle sensitive data. Certain member properties (IsApproved, IsLockedOut) are subject to "sensitive data" rules.
+ // The client won't have received these, so will always be false.
+ // We should reset them back to their original values before proceeding with the update.
+ updateModel.IsApproved = member.IsApproved;
+ updateModel.IsLockedOut = member.IsLockedOut;
}
MemberIdentityUser? identityMember = await _memberManager.FindByIdAsync(member.Id.ToString());
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/views/member/member-workspace-view-member.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/views/member/member-workspace-view-member.element.ts
index b8a2ccb8cc..f048becc14 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/views/member/member-workspace-view-member.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/views/member/member-workspace-view-member.element.ts
@@ -9,6 +9,7 @@ import type { UUIBooleanInputEvent } from '@umbraco-cms/backoffice/external/uui'
import './member-workspace-view-member-info.element.js';
import type { UmbInputMemberGroupElement } from '@umbraco-cms/backoffice/member-group';
+import { UMB_CURRENT_USER_CONTEXT } from '@umbraco-cms/backoffice/current-user';
@customElement('umb-member-workspace-view-member')
export class UmbMemberWorkspaceViewMemberElement extends UmbLitElement implements UmbWorkspaceViewElement {
@@ -24,6 +25,12 @@ export class UmbMemberWorkspaceViewMemberElement extends UmbLitElement implement
this._isNew = !!isNew;
});
});
+
+ this.consumeContext(UMB_CURRENT_USER_CONTEXT, (context) => {
+ this.observe(context.hasAccessToSensitiveData, (hasAccessToSensitiveData) => {
+ this._hasAccessToSensitiveData = hasAccessToSensitiveData === true;
+ });
+ });
}
@state()
@@ -35,6 +42,9 @@ export class UmbMemberWorkspaceViewMemberElement extends UmbLitElement implement
@state()
private _isNew = true;
+ @state()
+ private _hasAccessToSensitiveData = false;
+
#onChange(propertyName: keyof UmbMemberDetailModel, value: UmbMemberDetailModel[keyof UmbMemberDetailModel]) {
if (!this._workspaceContext) return;
@@ -172,23 +182,26 @@ export class UmbMemberWorkspaceViewMemberElement extends UmbLitElement implement
.selection=${this._workspaceContext.memberGroups}>
-
- this.#onChange('isApproved', e.target.checked)}>
-
-
-
-
- this.#onChange('isLockedOut', e.target.checked)}>
-
-
+ ${when(this._hasAccessToSensitiveData,
+ () => html`
+
+ this.#onChange('isApproved', e.target.checked)}>
+
+
+
+ this.#onChange('isLockedOut', e.target.checked)}>
+
+
+ `)
+ }
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
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MemberEditingServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MemberEditingServiceTests.cs
index 0727f9c0ad..0b6829f1dd 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MemberEditingServiceTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MemberEditingServiceTests.cs
@@ -1,4 +1,4 @@
-using NUnit.Framework;
+using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
@@ -341,7 +341,6 @@ public class MemberEditingServiceTests : UmbracoIntegrationTest
{
Email = "test-updated@test.com",
Username = "test-updated",
- IsApproved = member.IsApproved,
InvariantName = "T. Est Updated",
InvariantProperties = new[]
{
@@ -363,67 +362,9 @@ public class MemberEditingServiceTests : UmbracoIntegrationTest
Assert.AreEqual("test-updated@test.com", member.Email);
Assert.AreEqual("test-updated", member.Username);
Assert.AreEqual("T. Est Updated", member.Name);
- }
- [Test]
- public async Task Cannot_Change_IsApproved_Without_Access()
- {
- // this user does NOT have access to sensitive data
- var user = UserBuilder.CreateUser();
- UserService.Save(user);
-
- var member = await CreateMemberAsync();
-
- var updateModel = new MemberUpdateModel
- {
- Email = member.Email,
- Username = member.Username,
- IsApproved = false,
- InvariantName = member.Name,
- InvariantProperties = member.Properties.Select(property => new PropertyValueModel
- {
- Alias = property.Alias,
- Value = property.GetValue()
- })
- };
-
- var result = await MemberEditingService.UpdateAsync(member.Key, updateModel, user);
- Assert.IsFalse(result.Success);
- Assert.AreEqual(ContentEditingOperationStatus.NotAllowed, result.Status.ContentEditingOperationStatus);
-
- member = await MemberEditingService.GetAsync(member.Key);
- Assert.IsNotNull(member);
+ // IsApproved and IsLockedOut are always sensitive properties.
Assert.IsTrue(member.IsApproved);
- }
-
- [Test]
- public async Task Cannot_Change_IsLockedOut_Without_Access()
- {
- // this user does NOT have access to sensitive data
- var user = UserBuilder.CreateUser();
- UserService.Save(user);
-
- var member = await CreateMemberAsync();
-
- var updateModel = new MemberUpdateModel
- {
- Email = member.Email,
- Username = member.Username,
- IsLockedOut = true,
- InvariantName = member.Name,
- InvariantProperties = member.Properties.Select(property => new PropertyValueModel
- {
- Alias = property.Alias,
- Value = property.GetValue()
- })
- };
-
- var result = await MemberEditingService.UpdateAsync(member.Key, updateModel, user);
- Assert.IsFalse(result.Success);
- Assert.AreEqual(ContentEditingOperationStatus.NotAllowed, result.Status.ContentEditingOperationStatus);
-
- member = await MemberEditingService.GetAsync(member.Key);
- Assert.IsNotNull(member);
Assert.IsFalse(member.IsLockedOut);
}
From 7200b7b623caf6c6805b424f9d311dd08c218ef8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Niels=20Lyngs=C3=B8?=
Date: Mon, 31 Mar 2025 16:18:47 +0200
Subject: [PATCH 34/38] Bugfix: #18473 (#18765)
* add more icons
* add group id for inspection
* make outline style to make it not look disabled
* ensure that inherited has entries
* ensure current route is updated accordingly
* data marks
* shared across cultures tag
* fix sidebar group headline size
---
.../devops/icons/index.js | 3 +-
.../src/assets/lang/da-dk.ts | 6 +-
.../src/assets/lang/en.ts | 2 +
...ontent-type-design-editor-group.element.ts | 9 +-
...t-type-design-editor-properties.element.ts | 15 +-
...ent-type-design-editor-property.element.ts | 27 +++-
.../content-type-design-editor-tab.element.ts | 4 +-
.../content-type-design-editor.element.ts | 89 ++++++++----
.../core/icon-registry/icon-dictionary.json | 108 ++++++++++++++
.../src/packages/core/icon-registry/icons.ts | 135 ++++++++++++++++++
.../core/icon-registry/icons/icon-blend.ts | 1 +
.../icon-registry/icons/icon-columns-2.ts | 1 +
.../icon-registry/icons/icon-columns-3.ts | 1 +
.../icon-registry/icons/icon-columns-4.ts | 1 +
.../core/icon-registry/icons/icon-grid-2.ts | 1 +
.../core/icon-registry/icons/icon-grid-3.ts | 1 +
.../icon-registry/icons/icon-land-plot.ts | 1 +
.../icons/icon-layout-dislocated.ts | 1 +
.../icon-registry/icons/icon-layout-grid.ts | 1 +
.../icon-registry/icons/icon-layout-list.ts | 1 +
.../icons/icon-layout-masonry.ts | 1 +
.../icons/icon-layout-panel-left.ts | 1 +
.../core/icon-registry/icons/icon-rows-2.ts | 1 +
.../core/icon-registry/icons/icon-rows-3.ts | 1 +
.../core/icon-registry/icons/icon-rows-4.ts | 1 +
.../icon-registry/icons/icon-shape-circle.ts | 1 +
.../icons/icon-shape-cylinder.ts | 1 +
.../icon-registry/icons/icon-shape-hexagon.ts | 1 +
.../icons/icon-shape-rectangle-horizontal.ts | 1 +
.../icons/icon-shape-rectangle-vertical.ts | 1 +
.../icon-registry/icons/icon-shape-square.ts | 1 +
.../icons/icon-shape-triangle-right.ts | 1 +
.../icons/icon-shape-triangle.ts | 1 +
.../core/icon-registry/icons/icon-shapes.ts | 1 +
.../icon-registry/icons/icon-shared-value.ts | 1 +
.../icon-registry/icons/icon-spray-can.ts | 1 +
.../icon-registry/icons/icon-swatch-book.ts | 1 +
.../section-sidebar-menu.element.ts | 2 +-
38 files changed, 378 insertions(+), 49 deletions(-)
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-blend.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-columns-2.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-columns-3.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-columns-4.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-grid-2.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-grid-3.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-land-plot.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-dislocated.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-grid.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-list.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-masonry.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-panel-left.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-rows-2.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-rows-3.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-rows-4.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-circle.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-cylinder.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-hexagon.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-rectangle-horizontal.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-rectangle-vertical.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-square.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-triangle-right.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-triangle.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shapes.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shared-value.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-spray-can.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-swatch-book.ts
diff --git a/src/Umbraco.Web.UI.Client/devops/icons/index.js b/src/Umbraco.Web.UI.Client/devops/icons/index.js
index 1f97286e69..b3c90e057c 100644
--- a/src/Umbraco.Web.UI.Client/devops/icons/index.js
+++ b/src/Umbraco.Web.UI.Client/devops/icons/index.js
@@ -186,10 +186,11 @@ const generateJS = (icons) => {
const iconDescriptors = icons.map((icon) => {
// remove legacy for v.17 (Deprecated)
+ // Notice how legacy also makes an icon hidden. Legacy will be removed in v.17, but still used in the dictionary for legacy icons. But outward they are both hidden. [NL]
return `{
name: "${icon.name}",
${icon.legacy ? 'legacy: true,' : ''}
- ${icon.hidden ? 'hidden: true,' : ''}
+ ${icon.hidden || icon.legacy ? 'hidden: true,' : ''}
path: () => import("./icons/${icon.fileName}.js"),
}`.replace(/\t/g, '').replace(/^\s*[\r\n]/gm, ''); // Regex removes white space [NL] // + regex that removes empty lines. [NL]
});
diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts
index 473292c787..649d485181 100644
--- a/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts
+++ b/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts
@@ -1673,8 +1673,10 @@ export default {
variantsHeading: 'Tillad variationer',
cultureVariantHeading: 'Tillad sprogvariation',
segmentVariantHeading: 'Tillad segmentering',
- cultureVariantLabel: 'Tillader sprogvariationer',
- segmentVariantLabel: 'Tillader segmentering',
+ cultureInvariantLabel: 'Delt på tværs af sprog',
+ segmentInvariantLabel: 'Delt på tværs af segmenteringer',
+ cultureVariantLabel: 'Sprog varierende',
+ segmentVariantLabel: 'Segment varierende',
variantsDescription: 'Tillad at redaktører kan oprette indhold af denne type på flere sprog.',
cultureVariantDescription: 'Tillad at redaktører kan oprette dette indhold på flere sprog.',
segmentVariantDescription: 'Tillad at redaktører kan oprette flere udgaver af denne type 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 39f7edc0ac..734ff12f12 100644
--- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts
+++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts
@@ -1738,6 +1738,8 @@ export default {
variantsHeading: 'Variation',
cultureVariantHeading: 'Allow vary by culture',
segmentVariantHeading: 'Allow segmentation',
+ cultureInvariantLabel: 'Shared across cultures',
+ segmentInvariantLabel: 'Shared across segments',
cultureVariantLabel: 'Vary by culture',
segmentVariantLabel: 'Vary by segment',
variantsDescription: 'Allow editors to create content of this type in different languages.',
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts
index e464c08456..95f1b9a127 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts
@@ -63,19 +63,12 @@ export class UmbContentTypeWorkspaceViewEditGroupElement extends UmbLitElement {
const hasAOwnerContainer = !!ownerContainer;
const pureOwnerContainer = hasAOwnerContainer && containers.length === 1;
- // TODO: Check if requestUpdate is needed here, I do not think it is when i added it, but I just wanted to be safe when debugging [NL]
- const oldHasOwnerContainer = this._hasOwnerContainer;
- const oldInherited = this._inherited;
- const oldInheritedFrom = this._inheritedFrom;
this._hasOwnerContainer = hasAOwnerContainer;
this._inherited = !pureOwnerContainer;
this._inheritedFrom = containers
.filter((con) => con.id !== ownerContainer?.id)
.map((con) => this.groupStructureHelper!.getContentTypeOfContainer(con.id))
.filter((contentType) => contentType !== undefined) as Array;
- this.requestUpdate('_hasOwnerContainer', oldHasOwnerContainer);
- this.requestUpdate('_inherited', oldInherited);
- this.requestUpdate('_inheritedFrom', oldInheritedFrom);
},
'observeGroupContainers',
);
@@ -175,7 +168,7 @@ export class UmbContentTypeWorkspaceViewEditGroupElement extends UmbLitElement {
${when(
- this._hasOwnerContainer === false && this._inheritedFrom,
+ this._hasOwnerContainer === false && this._inheritedFrom && this._inheritedFrom.length > 0,
() => html`
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-properties.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-properties.element.ts
index 7e8f9043aa..a495bbab47 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-properties.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-properties.element.ts
@@ -134,6 +134,9 @@ export class UmbContentTypeDesignEditorPropertiesElement extends UmbLitElement {
@state()
private _ownerContentTypeUnique?: string;
+ @state()
+ private _ownerContentTypeVariesByCulture?: boolean;
+
@state()
private _newPropertyPath?: string;
@@ -168,6 +171,14 @@ export class UmbContentTypeDesignEditorPropertiesElement extends UmbLitElement {
this._ownerContentTypeUnique = workspaceContext.structure.getOwnerContentTypeUnique();
this.createPropertyTypeWorkspaceRoutes();
+
+ this.observe(
+ workspaceContext.variesByCulture,
+ (variesByCulture) => {
+ this._ownerContentTypeVariesByCulture = variesByCulture;
+ },
+ 'observeOwnerVariesByCulture',
+ );
});
this.observe(this.#propertyStructureHelper.propertyStructure, (propertyStructure) => {
this._properties = propertyStructure;
@@ -251,11 +262,13 @@ export class UmbContentTypeDesignEditorPropertiesElement extends UmbLitElement {
return html`
+ .property=${property}
+ .ownerVariesByCulture=${this._ownerContentTypeVariesByCulture}>
`;
},
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts
index 5b8cf05294..560c05bced 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts
@@ -62,6 +62,9 @@ export class UmbContentTypeDesignEditorPropertyElement extends UmbLitElement {
@property({ attribute: false })
public editContentTypePath?: string;
+ @property({ attribute: false })
+ public ownerVariesByCulture?: boolean;
+
@property({ type: Boolean, reflect: true, attribute: '_inherited' })
public _inherited?: boolean;
@@ -241,7 +244,7 @@ export class UmbContentTypeDesignEditorPropertyElement extends UmbLitElement {
@@ -294,10 +297,18 @@ export class UmbContentTypeDesignEditorPropertyElement extends UmbLitElement {
return this.property
? html`
${this.property.dataType?.unique ? html`
${this._dataTypeName} ` : nothing}
- ${this.property.variesByCulture
- ? html`
- ${this.localize.term('contentTypeEditor_cultureVariantLabel')}
- `
+ ${this.ownerVariesByCulture
+ ? this.property.variesByCulture
+ ? html`
+ ${this.localize.term(
+ 'contentTypeEditor_cultureVariantLabel',
+ )}
+ `
+ : html`
+ ${this.localize.term(
+ 'contentTypeEditor_cultureInvariantLabel',
+ )}
+ `
: nothing}
${this.property.variesBySegment
? html`
@@ -420,6 +431,12 @@ export class UmbContentTypeDesignEditorPropertyElement extends UmbLitElement {
#editor {
position: relative;
+ --uui-button-background-color: var(--uui-color-background);
+ --uui-button-background-color-hover: var(--uui-color-background);
+ }
+ #editor uui-action-bar {
+ --uui-button-background-color: var(--uui-color-surface);
+ --uui-button-background-color-hover: var(--uui-color-surface);
}
#alias-input,
#label-input,
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-tab.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-tab.element.ts
index 583a3f8242..73a923bd4a 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-tab.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-tab.element.ts
@@ -189,7 +189,9 @@ export class UmbContentTypeDesignEditorTabElement extends UmbLitElement {
?sort-mode-active=${this._sortModeActive}
.editContentTypePath=${this._editContentTypePath}
.group=${group}
- .groupStructureHelper=${this.#groupStructureHelper as any}>
+ .groupStructureHelper=${this.#groupStructureHelper as any}
+ data-umb-group-id=${group.id}
+ data-mark="group:${group.name}">
`,
)}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts
index c1b6d275da..8b7c275c43 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts
@@ -12,7 +12,12 @@ import type { UUIInputElement, UUIInputEvent, UUITabElement } from '@umbraco-cms
import { encodeFolderName } from '@umbraco-cms/backoffice/router';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { CompositionTypeModel } from '@umbraco-cms/backoffice/external/backend-api';
-import type { UmbRoute, UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router';
+import type {
+ IComponentRoute,
+ UmbRoute,
+ UmbRouterSlotChangeEvent,
+ UmbRouterSlotInitEvent,
+} from '@umbraco-cms/backoffice/router';
import type {
ManifestWorkspaceViewContentTypeDesignEditorKind,
UmbWorkspaceViewElement,
@@ -78,6 +83,8 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
#workspaceContext?: (typeof UMB_CONTENT_TYPE_WORKSPACE_CONTEXT)['TYPE'];
#designContext = new UmbContentTypeDesignEditorContext(this);
#tabsStructureHelper = new UmbContentTypeContainerStructureHelper(this);
+ #currentTabComponent?: UmbContentTypeDesignEditorTabElement;
+ #processingTabId?: string;
set manifest(value: ManifestWorkspaceViewContentTypeDesignEditorKind) {
this._compositionRepositoryAlias = value.meta.compositionRepositoryAlias;
@@ -94,7 +101,7 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
private _routes: UmbRoute[] = [];
@state()
- _tabs?: Array;
+ private _tabs?: Array;
@state()
private _routerPath?: string;
@@ -102,8 +109,6 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
@state()
private _activePath = '';
- private _activeTabId?: string;
-
@state()
private _sortModeActive?: boolean;
@@ -132,7 +137,7 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
this.observe(this.#tabsStructureHelper.mergedContainers, (tabs) => {
this._tabs = tabs;
this.#sorter.setModel(tabs);
- this._createRoutes();
+ this.#createRoutes();
});
// _hasRootProperties can be gotten via _tabsStructureHelper.hasProperties. But we do not support root properties currently.
@@ -156,13 +161,13 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
await this.#workspaceContext.structure.hasRootContainers('Group'),
(hasRootGroups) => {
this._hasRootGroups = hasRootGroups;
- this._createRoutes();
+ this.#createRoutes();
},
'_observeGroups',
);
}
- private _createRoutes() {
+ #createRoutes() {
// TODO: How about storing a set of elements based on tab ids? to prevent re-initializing the element when renaming..[NL]
if (!this.#workspaceContext || !this._tabs || this._hasRootGroups === undefined) return;
const routes: UmbRoute[] = [];
@@ -173,14 +178,15 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
if (this._tabs.length > 0) {
this._tabs?.forEach((tab) => {
const tabName = tab.name && tab.name !== '' ? tab.name : '-';
- if (tab.id === this._activeTabId) {
+ if (tab.id === this.#processingTabId) {
activeTabName = tabName;
}
routes.push({
path: `tab/${encodeFolderName(tabName)}`,
component: () => import('./content-type-design-editor-tab.element.js'),
setup: (component) => {
- (component as UmbContentTypeDesignEditorTabElement).containerId = tab.id;
+ this.#currentTabComponent = component as UmbContentTypeDesignEditorTabElement;
+ this.#currentTabComponent.containerId = tab.id;
},
});
});
@@ -191,19 +197,20 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
path: 'root',
component: () => import('./content-type-design-editor-tab.element.js'),
setup: (component) => {
- (component as UmbContentTypeDesignEditorTabElement).containerId = null;
+ this.#currentTabComponent = component as UmbContentTypeDesignEditorTabElement;
+ this.#currentTabComponent.containerId = null;
},
});
routes.push({
path: '',
redirectTo: 'root',
- guards: [() => this._activeTabId === undefined],
+ guards: [() => this.#processingTabId === undefined],
});
} else {
routes.push({
path: '',
redirectTo: routes[0]?.path,
- guards: [() => this._activeTabId === undefined],
+ guards: [() => this.#processingTabId === undefined],
});
}
@@ -211,10 +218,31 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
routes.push({
path: `**`,
component: async () => (await import('@umbraco-cms/backoffice/router')).UmbRouteNotFoundElement,
- guards: [() => this._activeTabId === undefined],
+ guards: [() => this.#processingTabId === undefined],
+ setup: () => {
+ this.#currentTabComponent = undefined;
+ },
});
}
+ routes.push({
+ path: `**`,
+ component: async () => (await import('@umbraco-cms/backoffice/router')).UmbRouteNotFoundElement,
+ setup: () => {
+ this.#currentTabComponent = undefined;
+ },
+ });
+
+ this._routes = routes;
+
+ // If we have a active tab, then we want to make sure its up to date with latest tab id, as an already active route is not getting its setup method triggered again [NL]
+ if (this._activePath && this.#currentTabComponent) {
+ const route = routes.find((x) => this._routerPath + '/' + x.path === this._activePath) as
+ | IComponentRoute
+ | undefined;
+ route?.setup?.(this.#currentTabComponent, undefined as any);
+ }
+
// If we have an active tab name, then we might have a active tab name re-name, then we will redirect to the new name if it has been changed: [NL]
if (activeTabName !== undefined) {
if (this._activePath && this._routerPath) {
@@ -225,18 +253,12 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
this._activePath = this._routerPath + newPath;
// Update the current URL, so we are still on this specific tab: [NL]
window.history.replaceState(null, '', this._activePath);
+
// TODO: We have some flickering when renaming, this could potentially be fixed if we cache the view and re-use it if the same is requested [NL]
// Or maybe its just about we just send the updated tabName to the view, and let it handle the update itself [NL]
}
}
}
-
- routes.push({
- path: `**`,
- component: async () => (await import('@umbraco-cms/backoffice/router')).UmbRouteNotFoundElement,
- });
-
- this._routes = routes;
}
async #requestDeleteTab(tab: UmbPropertyTypeContainerModel | undefined) {
@@ -267,8 +289,8 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
#deleteTab(tabId?: string) {
if (!tabId) return;
this.#workspaceContext?.structure.removeContainer(null, tabId);
- if (this._activeTabId === tabId) {
- this._activeTabId = undefined;
+ if (this.#processingTabId === tabId) {
+ this.#processingTabId = undefined;
}
}
async #addTab() {
@@ -307,7 +329,7 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
}
async #tabNameChanged(event: InputEvent, tab: UmbPropertyTypeContainerModel) {
- this._activeTabId = tab.id;
+ this.#processingTabId = tab.id;
let newName = (event.target as HTMLInputElement).value;
const changedName = this.#workspaceContext?.structure.makeContainerNameUniqueForOwnerContentType(
@@ -329,10 +351,10 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
}
async #tabNameBlur(event: FocusEvent, tab: UmbPropertyTypeContainerModel) {
- if (!this._activeTabId) return;
+ if (!this.#processingTabId) return;
const newName = (event.target as HTMLInputElement | undefined)?.value;
if (newName === '') {
- const changedName = this.#workspaceContext!.structure.makeEmptyContainerName(this._activeTabId, 'Tab');
+ const changedName = this.#workspaceContext!.structure.makeEmptyContainerName(this.#processingTabId, 'Tab');
(event.target as HTMLInputElement).value = changedName;
@@ -341,7 +363,7 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
});
}
- this._activeTabId = undefined;
+ this.#processingTabId = undefined;
}
async #openCompositionModal() {
@@ -386,8 +408,8 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
return html`
${this.renderTabInner(tab, tabActive, ownedTab)}
`;
@@ -559,6 +583,11 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
`;
}
+ override destroy(): void {
+ this.#currentTabComponent = undefined;
+ super.destroy();
+ }
+
static override styles = [
UmbTextStyles,
css`
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json
index b26e6c586a..d4badf6fb5 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json
@@ -468,6 +468,38 @@
"name": "icon-columns",
"file": "tally-3.svg"
},
+ {
+ "name": "icon-columns-2",
+ "file": "columns-2.svg"
+ },
+ {
+ "name": "icon-columns-3",
+ "file": "columns-3.svg"
+ },
+ {
+ "name": "icon-columns-4",
+ "file": "columns-4.svg"
+ },
+ {
+ "name": "icon-rows-2",
+ "file": "rows-2.svg"
+ },
+ {
+ "name": "icon-rows-3",
+ "file": "rows-3.svg"
+ },
+ {
+ "name": "icon-rows-4",
+ "file": "rows-4.svg"
+ },
+ {
+ "name": "icon-grid-2",
+ "file": "grid-2x2.svg"
+ },
+ {
+ "name": "icon-grid-3",
+ "file": "grid-3x3.svg"
+ },
{
"name": "icon-combination-lock-open",
"file": "lock-keyhole-open.svg"
@@ -2452,6 +2484,82 @@
{
"name": "icon-document-play",
"file": "file-video.svg"
+ },
+ {
+ "name": "icon-shared-value",
+ "file": "repeat-2.svg"
+ },
+ {
+ "name": "icon-layout-masonry",
+ "file": "layout-dashboard.svg"
+ },
+ {
+ "name": "icon-layout-grid",
+ "file": "layout-grid.svg"
+ },
+ {
+ "name": "icon-layout-list",
+ "file": "layout-list.svg"
+ },
+ {
+ "name": "icon-layout-panel-left",
+ "file": "layout-panel-left.svg"
+ },
+ {
+ "name": "icon-spray-can",
+ "file": "spray-can.svg"
+ },
+ {
+ "name": "icon-swatch-book",
+ "file": "swatch-book.svg"
+ },
+ {
+ "name": "icon-shape-cylinder",
+ "file": "cylinder.svg"
+ },
+ {
+ "name": "icon-shape-triangle-right",
+ "file": "triangle-right.svg"
+ },
+ {
+ "name": "icon-shape-triangle",
+ "file": "triangle.svg"
+ },
+ {
+ "name": "icon-shape-circle",
+ "file": "circle.svg"
+ },
+ {
+ "name": "icon-shape-square",
+ "file": "square.svg"
+ },
+ {
+ "name": "icon-shape-hexagon",
+ "file": "hexagon.svg"
+ },
+ {
+ "name": "icon-shape-rectangle-horizontal",
+ "file": "rectangle-horizontal.svg"
+ },
+ {
+ "name": "icon-shape-rectangle-vertical",
+ "file": "rectangle-vertical.svg"
+ },
+ {
+ "name": "icon-shapes",
+ "file": "shapes.svg"
+ },
+ {
+ "name": "icon-layout-dislocated",
+ "file": "ungroup.svg"
+ },
+ {
+ "name": "icon-blend",
+ "file": "blend.svg"
+ },
+ {
+ "name": "icon-land-plot",
+ "file": "land-plot.svg"
}
],
"simpleIcons": [
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts
index 4d77047860..03eb3c6d1b 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts
@@ -357,6 +357,30 @@ path: () => import("./icons/icon-colorpicker.js"),
name: "icon-columns",
path: () => import("./icons/icon-columns.js"),
},{
+name: "icon-columns-2",
+path: () => import("./icons/icon-columns-2.js"),
+},{
+name: "icon-columns-3",
+path: () => import("./icons/icon-columns-3.js"),
+},{
+name: "icon-columns-4",
+path: () => import("./icons/icon-columns-4.js"),
+},{
+name: "icon-rows-2",
+path: () => import("./icons/icon-rows-2.js"),
+},{
+name: "icon-rows-3",
+path: () => import("./icons/icon-rows-3.js"),
+},{
+name: "icon-rows-4",
+path: () => import("./icons/icon-rows-4.js"),
+},{
+name: "icon-grid-2",
+path: () => import("./icons/icon-grid-2.js"),
+},{
+name: "icon-grid-3",
+path: () => import("./icons/icon-grid-3.js"),
+},{
name: "icon-combination-lock-open",
path: () => import("./icons/icon-combination-lock-open.js"),
},{
@@ -1998,6 +2022,63 @@ path: () => import("./icons/icon-document-play.js"),
name: "icon-document-play",
path: () => import("./icons/icon-document-play.js"),
},{
+name: "icon-shared-value",
+path: () => import("./icons/icon-shared-value.js"),
+},{
+name: "icon-layout-masonry",
+path: () => import("./icons/icon-layout-masonry.js"),
+},{
+name: "icon-layout-grid",
+path: () => import("./icons/icon-layout-grid.js"),
+},{
+name: "icon-layout-list",
+path: () => import("./icons/icon-layout-list.js"),
+},{
+name: "icon-layout-panel-left",
+path: () => import("./icons/icon-layout-panel-left.js"),
+},{
+name: "icon-spray-can",
+path: () => import("./icons/icon-spray-can.js"),
+},{
+name: "icon-swatch-book",
+path: () => import("./icons/icon-swatch-book.js"),
+},{
+name: "icon-shape-cylinder",
+path: () => import("./icons/icon-shape-cylinder.js"),
+},{
+name: "icon-shape-triangle-right",
+path: () => import("./icons/icon-shape-triangle-right.js"),
+},{
+name: "icon-shape-triangle",
+path: () => import("./icons/icon-shape-triangle.js"),
+},{
+name: "icon-shape-circle",
+path: () => import("./icons/icon-shape-circle.js"),
+},{
+name: "icon-shape-square",
+path: () => import("./icons/icon-shape-square.js"),
+},{
+name: "icon-shape-hexagon",
+path: () => import("./icons/icon-shape-hexagon.js"),
+},{
+name: "icon-shape-rectangle-horizontal",
+path: () => import("./icons/icon-shape-rectangle-horizontal.js"),
+},{
+name: "icon-shape-rectangle-vertical",
+path: () => import("./icons/icon-shape-rectangle-vertical.js"),
+},{
+name: "icon-shapes",
+path: () => import("./icons/icon-shapes.js"),
+},{
+name: "icon-layout-dislocated",
+path: () => import("./icons/icon-layout-dislocated.js"),
+},{
+name: "icon-blend",
+path: () => import("./icons/icon-blend.js"),
+},{
+name: "icon-land-plot",
+path: () => import("./icons/icon-land-plot.js"),
+},{
name: "icon-facebook",
path: () => import("./icons/icon-facebook.js"),
},{
@@ -2021,218 +2102,272 @@ path: () => import("./icons/icon-twitter-x.js"),
},{
name: "icon-art-easel",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-art-easel.js"),
},{
name: "icon-article",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-article.js"),
},{
name: "icon-auction-hammer",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-auction-hammer.js"),
},{
name: "icon-badge-count",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-badge-count.js"),
},{
name: "icon-band-aid",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-band-aid.js"),
},{
name: "icon-baby-stroller",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-baby-stroller.js"),
},{
name: "icon-bill-dollar",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-bill-dollar.js"),
},{
name: "icon-bill-euro",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-bill-euro.js"),
},{
name: "icon-bill-pound",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-bill-pound.js"),
},{
name: "icon-bill-yen",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-bill-yen.js"),
},{
name: "icon-bill",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-bill.js"),
},{
name: "icon-billboard",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-billboard.js"),
},{
name: "icon-bills-dollar",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-bills-dollar.js"),
},{
name: "icon-bills-euro",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-bills-euro.js"),
},{
name: "icon-bills-pound",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-bills-pound.js"),
},{
name: "icon-bills-yen",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-bills-yen.js"),
},{
name: "icon-bills",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-bills.js"),
},{
name: "icon-blueprint",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-blueprint.js"),
},{
name: "icon-bomb",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-bomb.js"),
},{
name: "icon-cash-register",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-cash-register.js"),
},{
name: "icon-checkbox-dotted-active",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-checkbox-dotted-active.js"),
},{
name: "icon-chess",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-chess.js"),
},{
name: "icon-circus",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-circus.js"),
},{
name: "icon-clothes-hanger",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-clothes-hanger.js"),
},{
name: "icon-coin",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-coin.js"),
},{
name: "icon-coins-dollar-alt",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-coins-dollar-alt.js"),
},{
name: "icon-coins-dollar",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-coins-dollar.js"),
},{
name: "icon-coins-euro-alt",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-coins-euro-alt.js"),
},{
name: "icon-coins-euro",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-coins-euro.js"),
},{
name: "icon-coins-pound-alt",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-coins-pound-alt.js"),
},{
name: "icon-coins-pound",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-coins-pound.js"),
},{
name: "icon-coins-yen-alt",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-coins-yen-alt.js"),
},{
name: "icon-coins-yen",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-coins-yen.js"),
},{
name: "icon-comb",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-comb.js"),
},{
name: "icon-desk",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-desk.js"),
},{
name: "icon-dollar-bag",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-dollar-bag.js"),
},{
name: "icon-eject",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-eject.js"),
},{
name: "icon-euro-bag",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-euro-bag.js"),
},{
name: "icon-female-symbol",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-female-symbol.js"),
},{
name: "icon-firewall",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-firewall.js"),
},{
name: "icon-folder-open",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-folder-open.js"),
},{
name: "icon-folder-outline",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-folder-outline.js"),
},{
name: "icon-handprint",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-handprint.js"),
},{
name: "icon-hat",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-hat.js"),
},{
name: "icon-hd",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-hd.js"),
},{
name: "icon-inactive-line",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-inactive-line.js"),
},{
name: "icon-keychain",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-keychain.js"),
},{
name: "icon-keyhole",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-keyhole.js"),
},{
name: "icon-linkedin",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-linkedin.js"),
},{
name: "icon-linux-tux",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-linux-tux.js"),
},{
name: "icon-male-and-female",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-male-and-female.js"),
},{
name: "icon-male-symbol",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-male-symbol.js"),
},{
name: "icon-molecular-network",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-molecular-network.js"),
},{
name: "icon-molecular",
legacy: true,
+hidden: true,
path: () => import("./icons/icon-molecular.js"),
},{
name: "icon-umbraco",
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-blend.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-blend.ts
new file mode 100644
index 0000000000..f9c8f6a31b
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-blend.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-columns-2.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-columns-2.ts
new file mode 100644
index 0000000000..bff8b48811
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-columns-2.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-columns-3.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-columns-3.ts
new file mode 100644
index 0000000000..430c4d086e
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-columns-3.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-columns-4.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-columns-4.ts
new file mode 100644
index 0000000000..085fdae254
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-columns-4.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-grid-2.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-grid-2.ts
new file mode 100644
index 0000000000..da991ffa65
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-grid-2.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-grid-3.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-grid-3.ts
new file mode 100644
index 0000000000..43ba6fb46e
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-grid-3.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-land-plot.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-land-plot.ts
new file mode 100644
index 0000000000..8587a8d2ad
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-land-plot.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-dislocated.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-dislocated.ts
new file mode 100644
index 0000000000..1541e45734
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-dislocated.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-grid.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-grid.ts
new file mode 100644
index 0000000000..03e907cb20
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-grid.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-list.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-list.ts
new file mode 100644
index 0000000000..c6052c20a7
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-list.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-masonry.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-masonry.ts
new file mode 100644
index 0000000000..fe1d3a813e
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-masonry.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-panel-left.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-panel-left.ts
new file mode 100644
index 0000000000..a4f14cc3a8
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-layout-panel-left.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-rows-2.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-rows-2.ts
new file mode 100644
index 0000000000..27fee1c195
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-rows-2.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-rows-3.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-rows-3.ts
new file mode 100644
index 0000000000..c5c1d06148
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-rows-3.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-rows-4.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-rows-4.ts
new file mode 100644
index 0000000000..832f390672
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-rows-4.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-circle.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-circle.ts
new file mode 100644
index 0000000000..54a5f21f46
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-circle.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-cylinder.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-cylinder.ts
new file mode 100644
index 0000000000..e0d798d46f
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-cylinder.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-hexagon.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-hexagon.ts
new file mode 100644
index 0000000000..66a79ad049
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-hexagon.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-rectangle-horizontal.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-rectangle-horizontal.ts
new file mode 100644
index 0000000000..7bb01005de
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-rectangle-horizontal.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-rectangle-vertical.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-rectangle-vertical.ts
new file mode 100644
index 0000000000..8d0152c6ff
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-rectangle-vertical.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-square.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-square.ts
new file mode 100644
index 0000000000..9d3fea8e28
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-square.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-triangle-right.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-triangle-right.ts
new file mode 100644
index 0000000000..98fb6b8807
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-triangle-right.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-triangle.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-triangle.ts
new file mode 100644
index 0000000000..01a64a1f42
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shape-triangle.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shapes.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shapes.ts
new file mode 100644
index 0000000000..8330aeddcd
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shapes.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shared-value.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shared-value.ts
new file mode 100644
index 0000000000..1f6c2e798e
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-shared-value.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-spray-can.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-spray-can.ts
new file mode 100644
index 0000000000..1b5ca39690
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-spray-can.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-swatch-book.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-swatch-book.ts
new file mode 100644
index 0000000000..9d788a1218
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-swatch-book.ts
@@ -0,0 +1 @@
+export default ` `;
\ No newline at end of file
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 7a41f5ea99..2c7c75e351 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
@@ -50,7 +50,7 @@ export class UmbSectionSidebarMenuElement<
h3 {
margin: var(--uui-size-5) 0;
padding: var(--uui-size-4) var(--uui-size-8);
- font-size: 15px;
+ font-size: 14px;
}
`,
];
From d978a10b61f17bef067245e507c46b0ff33b5119 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Niels=20Lyngs=C3=B8?=
Date: Mon, 31 Mar 2025 20:41:36 +0200
Subject: [PATCH 35/38] Fix: #18707 (#18859)
* observe condition changes
# Conflicts:
# src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.context.ts
* enable extension to be updated by late appended conditions
* update route when routes change
* remove commented code
* Update src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.test.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Revert "Update src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.test.ts"
This reverts commit b8d1dd793d75ac514d4f05c8dbdb94834a0db695.
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.../src/apps/backoffice/backoffice.context.ts | 43 +++++----
.../components/backoffice-main.element.ts | 72 ++++++---------
.../src/external/router-slot/router-slot.ts | 13 ++-
.../registry/extension.registry.test.ts | 88 ++++++++++++++++++-
.../registry/extension.registry.ts | 75 ++++++++++------
.../packages/core/section/section.context.ts | 3 +-
6 files changed, 187 insertions(+), 107 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.context.ts b/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.context.ts
index 13ce2e72f8..ff5086c83c 100644
--- a/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.context.ts
@@ -34,29 +34,26 @@ export class UmbBackofficeContext extends UmbContextBase {
});
});
- this.#init();
- }
-
- async #init() {
- const userContext = await this.getContext(UMB_CURRENT_USER_CONTEXT);
- this.observe(
- userContext.allowedSections,
- (allowedSections) => {
- if (!allowedSections) return;
- // TODO: Please be aware that we re-initialize this initializer based on user permissions. I suggest we should solve this specific case should be improved by the ability to change the filter [NL]
- new UmbExtensionsManifestInitializer(
- this,
- umbExtensionsRegistry,
- 'section',
- (manifest) => allowedSections.includes(manifest.alias),
- async (sections) => {
- this.#allowedSections.setValue([...sections]);
- },
- 'umbAllowedSectionsManifestInitializer',
- );
- },
- 'umbAllowedSectionsObserver',
- );
+ this.consumeContext(UMB_CURRENT_USER_CONTEXT, (userContext) => {
+ this.observe(
+ userContext.allowedSections,
+ (allowedSections) => {
+ if (!allowedSections) return;
+ // TODO: Please be aware that we re-initialize this initializer based on user permissions. I suggest we should solve this specific case should be improved by the ability to change the filter [NL]
+ new UmbExtensionsManifestInitializer(
+ this,
+ umbExtensionsRegistry,
+ 'section',
+ (manifest) => allowedSections.includes(manifest.alias),
+ async (sections) => {
+ this.#allowedSections.setValue(sections);
+ },
+ 'umbAllowedSectionsManifestInitializer',
+ );
+ },
+ 'umbAllowedSectionsObserver',
+ );
+ });
}
async #getVersion() {
diff --git a/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts b/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts
index 87081f11b1..3b5d890065 100644
--- a/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts
@@ -1,8 +1,8 @@
import type { UmbBackofficeContext } from '../backoffice.context.js';
import { UMB_BACKOFFICE_CONTEXT } from '../backoffice.context.js';
import { css, html, customElement, state, nothing } from '@umbraco-cms/backoffice/external/lit';
-import { UmbSectionContext, UMB_SECTION_CONTEXT, UMB_SECTION_PATH_PATTERN } from '@umbraco-cms/backoffice/section';
-import type { PageComponent, UmbRoute, UmbRouterSlotChangeEvent } from '@umbraco-cms/backoffice/router';
+import { UmbSectionContext, UMB_SECTION_PATH_PATTERN } from '@umbraco-cms/backoffice/section';
+import type { PageComponent, UmbRoute } from '@umbraco-cms/backoffice/router';
import type { ManifestSection, UmbSectionElement } from '@umbraco-cms/backoffice/section';
import type { UmbExtensionManifestInitializer } from '@umbraco-cms/backoffice/extension-api';
import { createExtensionElement } from '@umbraco-cms/backoffice/extension-api';
@@ -33,7 +33,7 @@ export class UmbBackofficeMainElement extends UmbLitElement {
this.observe(
this._backofficeContext.allowedSections,
(sections) => {
- this._sections = sections;
+ this._sections = sections.filter((x) => x.manifest);
this._createRoutes();
},
'observeAllowedSections',
@@ -45,67 +45,47 @@ export class UmbBackofficeMainElement extends UmbLitElement {
if (!this._sections) return;
// TODO: Refactor this for re-use across the app where the routes are re-generated at any time.
- const newRoutes = this._sections
- .filter((x) => x.manifest)
- .map((section) => {
- const existingRoute = this._routes.find(
- (r) => r.path === UMB_SECTION_PATH_PATTERN.generateLocal({ sectionName: section.manifest!.meta.pathname }),
- );
- if (existingRoute) {
- return existingRoute;
- } else {
- return {
- //alias: section.alias,
- path: UMB_SECTION_PATH_PATTERN.generateLocal({ sectionName: section.manifest!.meta.pathname }),
- component: () => createExtensionElement(section.manifest!, 'umb-section-default'),
- setup: (component: PageComponent) => {
- (component as UmbSectionElement).manifest = section.manifest;
- },
- };
- }
- });
+ const newRoutes: Array = this._sections.map((section) => {
+ return {
+ path: UMB_SECTION_PATH_PATTERN.generateLocal({ sectionName: section.manifest!.meta.pathname }),
+ component: () => createExtensionElement(section.manifest!, 'umb-section-default'),
+ setup: (component: PageComponent) => {
+ const manifest = section.manifest;
+ if (manifest) {
+ (component as UmbSectionElement).manifest = section.manifest;
+
+ this._backofficeContext?.setActiveSectionAlias(manifest.alias);
+ this._provideSectionContext(manifest);
+ }
+ },
+ };
+ });
if (newRoutes.length > 0) {
newRoutes.push({
- ...newRoutes[0],
- path: ``,
+ path: `**`,
+ component: async () => (await import('@umbraco-cms/backoffice/router')).UmbRouteNotFoundElement,
});
newRoutes.push({
- path: `**`,
- component: async () => (await import('@umbraco-cms/backoffice/router')).UmbRouteNotFoundElement,
+ redirectTo: newRoutes[0].path,
+ path: ``,
});
}
this._routes = newRoutes;
}
- private _onRouteChange = async (event: UmbRouterSlotChangeEvent) => {
- const currentPath = event.target.localActiveViewPath || '';
- const section = this._sections.find(
- (s) => UMB_SECTION_PATH_PATTERN.generateLocal({ sectionName: s.manifest!.meta.pathname }) === currentPath,
- );
- if (!section) return;
- await section.asPromise();
- if (section.manifest) {
- this._backofficeContext?.setActiveSectionAlias(section.alias);
- this._provideSectionContext(section.manifest);
- }
- };
-
private _provideSectionContext(sectionManifest: ManifestSection) {
if (!this._sectionContext) {
- this._sectionContext = new UmbSectionContext(this, sectionManifest);
- this.provideContext(UMB_SECTION_CONTEXT, this._sectionContext);
- } else {
- this._sectionContext.setManifest(sectionManifest);
+ this._sectionContext = new UmbSectionContext(this);
}
+
+ this._sectionContext.setManifest(sectionManifest);
}
override render() {
- return this._routes.length > 0
- ? html` `
- : nothing;
+ return this._routes.length > 0 ? html` ` : nothing;
}
static override styles = [
diff --git a/src/Umbraco.Web.UI.Client/src/external/router-slot/router-slot.ts b/src/Umbraco.Web.UI.Client/src/external/router-slot/router-slot.ts
index b93152ba63..007c8d6c32 100644
--- a/src/Umbraco.Web.UI.Client/src/external/router-slot/router-slot.ts
+++ b/src/Umbraco.Web.UI.Client/src/external/router-slot/router-slot.ts
@@ -204,13 +204,12 @@ export class RouterSlot extends HTMLElement implements IRouter
this._routes.push(...routes);
if (navigate === undefined) {
- // If navigate is not determined, then we will check if we have a route match. If not then we will re-render. [NL]
- navigate = this._routeMatch === null;
- if (navigate === false) {
- if (this.isConnected) {
- const newMatch = this.getRouteMatch();
- // Check if this match matches the current match (aka. If the path has changed), if so we should navigate. [NL]
- if (newMatch) {
+ if (this.isConnected) {
+ // If navigate is not determined, then we will check if we have a route match, and if the new match is different from current. [NL]
+ const newMatch = this.getRouteMatch();
+ if (newMatch) {
+ if (this._routeMatch?.route.path !== newMatch.route.path) {
+ // Check if this match matches the current match (aka. If the path has changed), if so we should navigate. [NL]
navigate = shouldNavigate(this.match, newMatch);
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.test.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.test.ts
index d406b128b5..2edacb159b 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.test.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.test.ts
@@ -65,6 +65,10 @@ describe('UmbExtensionRegistry', () => {
manifests.forEach((manifest) => extensionRegistry.register(manifest));
});
+ afterEach(() => {
+ extensionRegistry.clear();
+ });
+
it('should register an extension', () => {
const registeredExtensions = extensionRegistry['_extensions'].getValue();
expect(registeredExtensions).to.have.lengthOf(4);
@@ -329,6 +333,9 @@ describe('UmbExtensionRegistry with kinds', () => {
manifests.forEach((manifest) => extensionRegistry.register(manifest));
});
+ afterEach(() => {
+ extensionRegistry.clear();
+ });
it('should say that an extension kind is registered', () => {
expect(extensionRegistry.isRegistered('Umb.Test.Kind')).to.be.true;
@@ -517,6 +524,10 @@ describe('Add Conditions', () => {
});
});
+ afterEach(() => {
+ extensionRegistry.clear();
+ });
+
it('should have the extensions registered', () => {
expect(extensionRegistry.isRegistered('Umb.Test.Section.1')).to.be.true;
expect(extensionRegistry.isRegistered('Umb.Test.Section.2')).to.be.true;
@@ -539,7 +550,7 @@ describe('Add Conditions', () => {
const conditionToAdd: UmbConditionConfigBase = {
alias: 'Umb.Test.Condition.Valid',
};
- await extensionRegistry.appendCondition('Umb.Test.Section.1', conditionToAdd);
+ extensionRegistry.appendCondition('Umb.Test.Section.1', conditionToAdd);
// Check new condition is registered
expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.true;
@@ -560,7 +571,7 @@ describe('Add Conditions', () => {
match: 'Umb.Workspace.Document',
};
- await extensionRegistry.appendCondition('Umb.Test.Section.2', workspaceCondition);
+ extensionRegistry.appendCondition('Umb.Test.Section.2', workspaceCondition);
const updatedWorkspaceExt = extensionRegistry.getByAlias('Umb.Test.Section.2') as ManifestWithDynamicConditions;
expect(updatedWorkspaceExt.conditions?.length).to.equal(1);
@@ -581,7 +592,7 @@ describe('Add Conditions', () => {
} as WorkspaceAliasConditionConfig,
];
- await extensionRegistry.appendConditions('Umb.Test.Section.1', conditions);
+ extensionRegistry.appendConditions('Umb.Test.Section.1', conditions);
const extUpdated = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions;
expect(extUpdated.conditions?.length).to.equal(3);
@@ -701,4 +712,75 @@ describe('Add Conditions', () => {
expect(extUpdateSecondTime.conditions?.length).to.equal(1);
expect(extUpdateSecondTime.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Valid');
});
+
+ it('should only update extensions when adding new conditions to one that matters', (done) => {
+ let amountOfTimesTriggered = -1;
+
+ let subscription: any = undefined;
+ subscription = extensionRegistry
+ .byTypeAndAliases('section', ['Umb.Test.Section.1'])
+ .subscribe(async (extensions) => {
+ amountOfTimesTriggered++;
+ expect(extensions).to.have.lengthOf(1);
+
+ if (amountOfTimesTriggered === 0) {
+ expect(extensions?.[0]?.alias).to.be.equal('Umb.Test.Section.1');
+ expect(extensions?.[0]?.conditions.length).to.be.equal(1);
+ // Add a condition with a specific config to Section2
+
+ const workspaceCondition1 = {
+ alias: 'Umb.Test.Condition.Invalid',
+ };
+
+ const workspaceCondition2 = {
+ alias: 'Umb.Test.Condition.Valid',
+ };
+
+ await Promise.resolve();
+
+ extensionRegistry.appendCondition('Umb.Test.Section.2', workspaceCondition1);
+
+ await Promise.resolve();
+
+ extensionRegistry.appendCondition('Umb.Test.Section.1', workspaceCondition2);
+ } else if (amountOfTimesTriggered === 1) {
+ expect(extensions?.[0]?.alias).to.be.equal('Umb.Test.Section.1');
+ expect(extensions?.[0]?.conditions.length).to.be.equal(2);
+ expect(extensions?.[0]?.conditions[1].alias).to.be.equal('Umb.Test.Condition.Valid');
+
+ subscription.unsubscribe();
+ done();
+ }
+ });
+ });
+ it('should update extensions when adding new conditions', (done) => {
+ let amountOfTimesTriggered = -1;
+
+ extensionRegistry
+ .byType('section')
+ .subscribe(async (extensions) => {
+ amountOfTimesTriggered++;
+ expect(extensions).to.have.lengthOf(2);
+
+ if (amountOfTimesTriggered === 0) {
+ expect(extensions?.[1]?.alias).to.be.equal('Umb.Test.Section.1');
+ expect(extensions?.[1]?.conditions.length).to.be.equal(1);
+ expect(extensions?.[0]?.alias).to.be.equal('Umb.Test.Section.2');
+ expect(extensions?.[0]?.conditions).to.be.undefined;
+ // Add a condition with a specific config to Section2
+ const workspaceCondition: WorkspaceAliasConditionConfig = {
+ alias: UMB_WORKSPACE_CONDITION_ALIAS,
+ match: 'Umb.Workspace.Document',
+ };
+
+ extensionRegistry.appendCondition('Umb.Test.Section.2', workspaceCondition);
+ } else if (amountOfTimesTriggered === 1) {
+ expect(extensions?.[0]?.alias).to.be.equal('Umb.Test.Section.2');
+ expect(extensions?.[0]?.conditions).to.not.be.undefined;
+ expect(extensions?.[0]?.conditions.length).to.be.equal(1);
+ done();
+ }
+ })
+ .unsubscribe();
+ });
});
diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts
index 8bbe1307e5..655c06ce8f 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts
@@ -11,36 +11,50 @@ import { map, distinctUntilChanged, combineLatest, of, switchMap } from '@umbrac
/**
*
- * @param {Array} previousValue - previous value
- * @param {Array} currentValue - current value
+ * @param {Array} previousValues - previous value
+ * @param {Array} currentValues - current value
* @returns {boolean} - true if value is assumed to be the same as previous value.
*/
function extensionArrayMemoization>(
- previousValue: Array,
- currentValue: Array,
+ previousValues: Array,
+ currentValues: Array,
): boolean {
// If length is different, data is different:
- if (previousValue.length !== currentValue.length) {
+ if (previousValues.length !== currentValues.length) {
return false;
}
- // previousValue has an alias that is not present in currentValue:
- if (previousValue.find((p) => !currentValue.find((c) => c.alias === p.alias))) {
+
+ // is there some properties that differs?:
+ if (
+ previousValues.some((p) => {
+ const n = currentValues.find((c) => c.alias === p.alias);
+ if (!n) {
+ return true;
+ }
+
+ if ((p as any).conditions?.length !== (n as any).conditions?.length) {
+ return true;
+ }
+ return !(p === n);
+ })
+ ) {
return false;
}
+
return true;
}
/**
*
- * @param {Array} previousValue - previous value
- * @param {Array} currentValue - current value
+ * @param {Array} previousValues - previous value
+ * @param {Array} currentValues - current value
* @returns {boolean} - true if value is assumed to be the same as previous value.
*/
function extensionAndKindMatchArrayMemoization<
T extends Pick & { __isMatchedWithKind?: boolean },
->(previousValue: Array, currentValue: Array): boolean {
+>(previousValues: Array, currentValues: Array): boolean {
// If length is different, data is different:
- if (previousValue.length !== currentValue.length) {
+ if (previousValues.length !== currentValues.length) {
return false;
}
// previousValue has an alias that is not present in currentValue:
@@ -50,11 +64,17 @@ function extensionAndKindMatchArrayMemoization<
}*/
// if previousValue has different meta values:
if (
- currentValue.find((newValue: T) => {
- const oldValue = previousValue.find((c) => c.alias === newValue.alias);
+ currentValues.some((currentValue: T) => {
+ const previousValue = previousValues.find((c) => c.alias === currentValue.alias);
// First check if we found a previous value, matching this alias.
// Then checking __isMatchedWithKind, as this is much more performant than checking the whole object. (I assume the only change happening to an extension is the match with a kind, we do not want to watch for other changes)
- return oldValue === undefined || newValue.__isMatchedWithKind !== oldValue.__isMatchedWithKind;
+ return (
+ previousValue === undefined ||
+ previousValue !== currentValue ||
+ previousValue.alias !== currentValue.alias ||
+ previousValue.__isMatchedWithKind !== currentValue.__isMatchedWithKind ||
+ (previousValue as any).conditions?.length !== (currentValue as any).conditions?.length
+ );
})
) {
return false;
@@ -72,10 +92,12 @@ function extensionSingleMemoization>(
previousValue: T | undefined,
currentValue: T | undefined,
): boolean {
- if (previousValue && currentValue) {
- return previousValue.alias === currentValue.alias;
- }
- return previousValue === currentValue;
+ return !(
+ previousValue === undefined ||
+ previousValue !== currentValue ||
+ previousValue.alias !== currentValue.alias ||
+ (previousValue as any).conditions?.length !== (currentValue as any).conditions?.length
+ );
}
/**
@@ -87,13 +109,13 @@ function extensionSingleMemoization>(
function extensionAndKindMatchSingleMemoization<
T extends Pick & { __isMatchedWithKind?: boolean },
>(previousValue: T | undefined, currentValue: T | undefined): boolean {
- if (previousValue && currentValue) {
- return (
- previousValue.alias === currentValue.alias &&
- previousValue.__isMatchedWithKind === currentValue.__isMatchedWithKind
- );
- }
- return previousValue === currentValue;
+ return !(
+ previousValue === undefined ||
+ previousValue !== currentValue ||
+ previousValue.alias !== currentValue.alias ||
+ previousValue.__isMatchedWithKind !== currentValue.__isMatchedWithKind ||
+ (previousValue as any).conditions?.length !== (currentValue as any).conditions?.length
+ );
}
const sortExtensions = (a: ManifestBase, b: ManifestBase): number => (b.weight || 0) - (a.weight || 0);
@@ -117,6 +139,7 @@ export class UmbExtensionRegistry<
#appendAdditionalConditions(manifest: ManifestTypes) {
const newConditions = this.#additionalConditions.get(manifest.alias);
if (newConditions) {
+ manifest = { ...manifest };
// Append the condition to the extensions conditions array
if ((manifest as ManifestWithDynamicConditions).conditions) {
for (const condition of newConditions) {
@@ -586,7 +609,7 @@ export class UmbExtensionRegistry<
existingConditionsToBeAdded ? [...existingConditionsToBeAdded, ...newConditions] : newConditions,
);
- const allExtensions = this._extensions.getValue();
+ const allExtensions = [...this._extensions.getValue()];
for (const extension of allExtensions) {
if (extension.alias === alias) {
// Replace the existing extension with the updated one
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section.context.ts
index 23dc2c91a1..ce8625b2c4 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section.context.ts
@@ -12,9 +12,8 @@ export class UmbSectionContext extends UmbContextBase
Date: Tue, 1 Apr 2025 09:06:24 +0200
Subject: [PATCH 36/38] Ensures date comparisons in schedule integration tests
are made only on the datetime part to the second (#18894)
* Ensures date comparisons in schedule integration tests are made only on the date part.
* Include time part to the second.
* Ensure Kind is retained when truncating a date.
* Retain Kind for all truncation levels.
---
.../Extensions/DateTimeExtensions.cs | 21 +++++++++++++------
.../Services/ContentPublishingServiceTests.cs | 5 ++---
2 files changed, 17 insertions(+), 9 deletions(-)
diff --git a/src/Umbraco.Core/Extensions/DateTimeExtensions.cs b/src/Umbraco.Core/Extensions/DateTimeExtensions.cs
index 35c9f600e5..00191e5a76 100644
--- a/src/Umbraco.Core/Extensions/DateTimeExtensions.cs
+++ b/src/Umbraco.Core/Extensions/DateTimeExtensions.cs
@@ -7,6 +7,9 @@ namespace Umbraco.Extensions;
public static class DateTimeExtensions
{
+ ///
+ /// Defines the levels to truncate a date to.
+ ///
public enum DateTruncate
{
Year,
@@ -25,33 +28,39 @@ public static class DateTimeExtensions
public static string ToIsoString(this DateTime dt) =>
dt.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
+ ///
+ /// Truncates the date to the specified level, i.e. if you pass in DateTruncate.Hour it will truncate the date to the hour.
+ ///
+ /// The date.
+ /// The level to truncate the date to.
+ /// The truncated date.
public static DateTime TruncateTo(this DateTime dt, DateTruncate truncateTo)
{
if (truncateTo == DateTruncate.Year)
{
- return new DateTime(dt.Year, 1, 1);
+ return new DateTime(dt.Year, 1, 1, 0, 0, 0, dt.Kind);
}
if (truncateTo == DateTruncate.Month)
{
- return new DateTime(dt.Year, dt.Month, 1);
+ 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);
+ 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);
+ 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);
+ 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);
+ return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, dt.Kind);
}
}
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentPublishingServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentPublishingServiceTests.cs
index 0853bca8c5..050b08582c 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentPublishingServiceTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentPublishingServiceTests.cs
@@ -1,4 +1,3 @@
-using Bogus.DataSets;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
@@ -23,8 +22,8 @@ public class ContentPublishingServiceTests : UmbracoIntegrationTestWithContent
{
private const string UnknownCulture = "ke-Ke";
- private readonly DateTime _schedulePublishDate = DateTime.UtcNow.AddDays(1);
- private readonly DateTime _scheduleUnPublishDate = DateTime.UtcNow.AddDays(2);
+ private readonly DateTime _schedulePublishDate = DateTime.UtcNow.AddDays(1).TruncateTo(DateTimeExtensions.DateTruncate.Second);
+ private readonly DateTime _scheduleUnPublishDate = DateTime.UtcNow.AddDays(2).TruncateTo(DateTimeExtensions.DateTruncate.Second);
[SetUp]
public new void Setup() => ContentRepositoryBase.ThrowOnWarning = true;
From b1dbd80a07814f5182b49964aecee57e03aaab48 Mon Sep 17 00:00:00 2001
From: Andy Butland
Date: Tue, 1 Apr 2025 09:07:08 +0200
Subject: [PATCH 37/38] Adds allow-same-origin to preview IFRAME. (#18895)
---
src/Umbraco.Web.UI.Client/src/apps/preview/preview.element.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Umbraco.Web.UI.Client/src/apps/preview/preview.element.ts b/src/Umbraco.Web.UI.Client/src/apps/preview/preview.element.ts
index 3a49b33073..4897f21190 100644
--- a/src/Umbraco.Web.UI.Client/src/apps/preview/preview.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/apps/preview/preview.element.ts
@@ -63,7 +63,7 @@ export class UmbPreviewElement extends UmbLitElement {
src=${this._previewUrl}
title="Page preview"
@load=${this.#onIFrameLoad}
- sandbox="allow-scripts">
+ sandbox="allow-scripts allow-same-origin">