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/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/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/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/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/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.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/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. 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/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/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); 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.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/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/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/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/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/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 84802225f6..9dd13970cc 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -219,8 +219,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", 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 717a07f654..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(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/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/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">
${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..580bd6300b 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,9 +297,22 @@ export class UmbContentTypeDesignEditorPropertyElement extends UmbLitElement { return this.property ? html`
${this.property.dataType?.unique ? html`${this._dataTypeName}` : nothing} - ${this.property.variesByCulture + ${this.ownerVariesByCulture + ? this.property.variesByCulture + ? html` + ${this.localize.term( + 'contentTypeEditor_cultureVariantLabel', + )} + ` + : html` + ${this.localize.term( + 'contentTypeEditor_cultureInvariantLabel', + )} + ` + : nothing} + ${this.property.variesBySegment ? html` - ${this.localize.term('contentTypeEditor_cultureVariantLabel')} + ${this.localize.term('contentTypeEditor_segmentVariantLabel')} ` : nothing} ${this.property.variesBySegment @@ -420,6 +436,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/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,8 +12,8 @@ export class UmbSectionContext { public readonly pathname = this.#manifestPathname.asObservable(); public readonly label = this.#manifestLabel.asObservable(); - constructor(manifest: ManifestSection) { - this.setManifest(manifest); + constructor(host: UmbControllerHost) { + super(host, UMB_SECTION_CONTEXT); } public setManifest(manifest?: ManifestSection) { 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/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(); }); 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( 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/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/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..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', ); @@ -157,7 +159,7 @@ export class UmbDocumentScheduleModalElement extends UmbModalBaseElement< } override render() { - return html` + return html`

${when( this._options.length > 1, @@ -184,7 +186,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..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,12 +131,25 @@ 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); }; override render() { - return html` + return html` ${!this._isInvariant ? html`

@@ -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,13 +182,13 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
-
`; + `; } static override styles = [ 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..1574e85a49 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,9 +6,15 @@ import { type UmbReferenceModel, isDocumentReference, isMediaReference, + isMemberReference, isDefaultReference, } from '@umbraco-cms/backoffice/relations'; +import { UmbDeprecation } from '@umbraco-cms/backoffice/utils'; +/** + * @deprecated Deprecated from 15.4. The element will be removed in v17.0.0. For modals use the or element instead + * @class UmbDocumentReferenceTableElement + */ @customElement('umb-document-reference-table') export class UmbDocumentReferenceTableElement extends UmbLitElement { #documentReferenceRepository = new UmbDocumentReferenceRepository(this); @@ -31,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(); } @@ -61,6 +74,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/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; } } 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); } 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> { // Check the parent which children media types are allowed 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); - } `, ]; } 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); - } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/utils.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/utils.ts index 42b8375b24..8f65e11141 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/utils.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/utils.ts @@ -744,6 +744,7 @@ export function getMimeTypeFromExtension(extension: string): string | null { '.wbxml': 'application/vnd.wap.wbxml', '.wcm': 'application/vnd.ms-works', '.wdb': 'application/vnd.ms-works', + '.webp': 'image/webp', '.wiz': 'application/msword', '.wks': 'application/vnd.ms-works', '.wm': 'video/x-ms-wm', 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..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)}> + + + `) + } + +
+ +
`; } @@ -269,6 +286,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/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()); } 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/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/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 { 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/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..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``; - } - const lastLoggedinLocalTime: Date = new Date(user.lastLoginDate); - const formattedTime = lastLoggedinLocalTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); - - return html``; + return html` + + `; } 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/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": { diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index bf03842d46..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.39", + "@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.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.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 82bbc68831..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.39", + "@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/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}) => { 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..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 @@ -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); @@ -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 @@ -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); +}); 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.Integration/CompatibilitySuppressions.xml b/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml index 918b3ea4fe..9a446cdb11 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) @@ -85,6 +92,20 @@ 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.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; 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] 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/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(); 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); } 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); + } } 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); + } +} 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]); + } +}