diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml
index a33ee744f7..28e6ed25b8 100644
--- a/build/azure-pipelines.yml
+++ b/build/azure-pipelines.yml
@@ -432,6 +432,33 @@ stages:
displayName: Start SQL Server Docker image (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
+ - powershell: |
+ $maxAttempts = 12
+ $attempt = 0
+ $status = ""
+
+ while (($status -ne 'running') -and ($attempt -lt $maxAttempts)) {
+ Start-Sleep -Seconds 5
+ # We use the docker inspect command to check the status of the container. If the container is not running, we wait 5 seconds and try again. And if reaches 12 attempts, we fail the build.
+ $status = docker inspect -f '{{.State.Status}}' mssql
+
+ if ($status -ne 'running') {
+ Write-Host "Waiting for SQL Server to be ready... Attempt $($attempt + 1)"
+ $attempt++
+ }
+ }
+
+ if ($status -eq 'running') {
+ Write-Host "SQL Server container is running"
+ docker ps -a
+ } else {
+ Write-Host "SQL Server did not become ready in time. Last known status: $status"
+ docker logs mssql
+ exit 1
+ }
+ displayName: Wait for SQL Server to be ready (Linux)
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
+
- pwsh: SqlLocalDB start MSSQLLocalDB
displayName: Start SQL Server LocalDB (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
@@ -540,7 +567,8 @@ stages:
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
URL=$(ASPNETCORE_URLS)
- STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
+ STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json
+ CONSOLE_ERRORS_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/console-errors.json" | Out-File .env
displayName: Generate .env
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
@@ -623,6 +651,14 @@ stages:
displayName: Copy Playwright results
condition: succeededOrFailed()
+ # Copy console error log
+ - pwsh: |
+ if (Test-Path tests/Umbraco.Tests.AcceptanceTest/console-errors.json) {
+ Copy-Item tests/Umbraco.Tests.AcceptanceTest/console-errors.json $(Build.ArtifactStagingDirectory)
+ }
+ displayName: Copy console error log
+ condition: succeededOrFailed()
+
# Publish test artifacts
- task: PublishPipelineArtifact@1
displayName: Publish test artifacts
@@ -698,7 +734,8 @@ stages:
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
URL=$(ASPNETCORE_URLS)
- STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
+ STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json
+ CONSOLE_ERRORS_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/console-errors.json" | Out-File .env
displayName: Generate .env
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
@@ -799,6 +836,14 @@ stages:
displayName: Copy Playwright results
condition: succeededOrFailed()
+ # Copy console error log
+ - pwsh: |
+ if (Test-Path tests/Umbraco.Tests.AcceptanceTest/console-errors.json) {
+ Copy-Item tests/Umbraco.Tests.AcceptanceTest/console-errors.json $(Build.ArtifactStagingDirectory)
+ }
+ displayName: Copy console error log
+ condition: succeededOrFailed()
+
# Publish test artifacts
- task: PublishPipelineArtifact@1
displayName: Publish test artifacts
diff --git a/build/nightly-E2E-test-pipelines.yml b/build/nightly-E2E-test-pipelines.yml
index 83b22aaef4..629b7ac841 100644
--- a/build/nightly-E2E-test-pipelines.yml
+++ b/build/nightly-E2E-test-pipelines.yml
@@ -8,10 +8,17 @@ schedules:
displayName: Daily midnight build
branches:
include:
- - v14/dev
- v15/dev
+ - release/16.0
- main
+parameters:
+ # Skipped due to DB locks
+ - name: sqliteAcceptanceTests
+ displayName: Run SQLite Acceptance Tests
+ type: boolean
+ default: false
+
variables:
nodeVersion: 20
solution: umbraco.sln
@@ -122,6 +129,7 @@ stages:
# E2E Tests
- job:
displayName: E2E Tests (SQLite)
+ condition: eq(${{parameters.sqliteAcceptanceTests}}, True)
timeoutInMinutes: 180
variables:
# Connection string
@@ -131,22 +139,22 @@ stages:
matrix:
LinuxPart1Of3:
vmImage: "ubuntu-latest"
- testCommand: "npm run test -- --shard=1/3"
+ testCommand: "npm run testSqlite -- --shard=1/3"
LinuxPart2Of3:
vmImage: "ubuntu-latest"
- testCommand: "npm run test -- --shard=2/3"
+ testCommand: "npm run testSqlite -- --shard=2/3"
LinuxPart3Of3:
vmImage: "ubuntu-latest"
- testCommand: "npm run test -- --shard=3/3"
+ testCommand: "npm run testSqlite -- --shard=3/3"
WindowsPart1Of3:
vmImage: "windows-latest"
- testCommand: "npm run test -- --shard=1/3"
+ testCommand: "npm run testSqlite -- --shard=1/3"
WindowsPart2Of3:
vmImage: "windows-latest"
- testCommand: "npm run test -- --shard=2/3"
+ testCommand: "npm run testSqlite -- --shard=2/3"
WindowsPart3Of3:
vmImage: "windows-latest"
- testCommand: "npm run test -- --shard=3/3"
+ testCommand: "npm run testSqlite -- --shard=3/3"
pool:
vmImage: $(vmImage)
steps:
@@ -172,7 +180,8 @@ stages:
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
URL=$(ASPNETCORE_URLS)
- STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
+ STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json
+ CONSOLE_ERRORS_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/console-errors.json" | Out-File .env
displayName: Generate .env
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
@@ -260,6 +269,14 @@ stages:
displayName: Copy Playwright results
condition: succeededOrFailed()
+ # Copy console error log
+ - pwsh: |
+ if (Test-Path tests/Umbraco.Tests.AcceptanceTest/console-errors.json) {
+ Copy-Item tests/Umbraco.Tests.AcceptanceTest/console-errors.json $(Build.ArtifactStagingDirectory)
+ }
+ displayName: Copy console error log
+ condition: succeededOrFailed()
+
# Publish
- task: PublishPipelineArtifact@1
displayName: Publish test artifacts
@@ -288,29 +305,29 @@ stages:
strategy:
matrix:
LinuxPart1Of3:
- testCommand: "npm run testSqlite -- --shard=1/3"
+ testCommand: "npm run test -- --shard=1/3"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
- CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
+ CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
LinuxPart2Of3:
- testCommand: "npm run testSqlite -- --shard=2/3"
+ testCommand: "npm run test -- --shard=2/3"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
- CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
+ CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
LinuxPart3Of3:
- testCommand: "npm run testSqlite -- --shard=3/3"
+ testCommand: "npm run test -- --shard=3/3"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
- CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
+ CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
WindowsPart1Of3:
vmImage: "windows-latest"
- testCommand: "npm run testSqlite -- --shard=1/3"
+ testCommand: "npm run test -- --shard=1/3"
WindowsPart2Of3:
vmImage: "windows-latest"
- testCommand: "npm run testSqlite -- --shard=2/3"
+ testCommand: "npm run test -- --shard=2/3"
WindowsPart3Of3:
vmImage: "windows-latest"
- testCommand: "npm run testSqlite -- --shard=3/3"
+ testCommand: "npm run test -- --shard=3/3"
pool:
vmImage: $(vmImage)
steps:
@@ -335,7 +352,8 @@ stages:
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
URL=$(ASPNETCORE_URLS)
- STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
+ STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json
+ CONSOLE_ERRORS_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/console-errors.json" | Out-File .env
displayName: Generate .env
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
@@ -441,6 +459,14 @@ stages:
displayName: Copy Playwright results
condition: succeededOrFailed()
+ # Copy console error log
+ - pwsh: |
+ if (Test-Path tests/Umbraco.Tests.AcceptanceTest/console-errors.json) {
+ Copy-Item tests/Umbraco.Tests.AcceptanceTest/console-errors.json $(Build.ArtifactStagingDirectory)
+ }
+ displayName: Copy console error log
+ condition: succeededOrFailed()
+
# Publish
- task: PublishPipelineArtifact@1
displayName: Publish test artifacts
diff --git a/src/Umbraco.Cms.Api.Delivery/Services/RoutingServiceBase.cs b/src/Umbraco.Cms.Api.Delivery/Services/RoutingServiceBase.cs
index 4a05521b22..8168d296db 100644
--- a/src/Umbraco.Cms.Api.Delivery/Services/RoutingServiceBase.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Services/RoutingServiceBase.cs
@@ -36,7 +36,7 @@ internal abstract class RoutingServiceBase
}
protected static string GetContentRoute(DomainAndUri domainAndUri, Uri contentRoute)
- => $"{domainAndUri.ContentId}{DomainUtilities.PathRelativeToDomain(domainAndUri.Uri, contentRoute.AbsolutePath)}";
+ => $"{domainAndUri.ContentId}{DomainUtilities.PathRelativeToDomain(domainAndUri.Uri, contentRoute.LocalPath)}"; // Use LocalPath over AbsolutePath to keep the path decoded.
protected DomainAndUri? GetDomainAndUriForRoute(Uri contentUrl)
{
diff --git a/src/Umbraco.Cms.Api.Delivery/Umbraco.Cms.Api.Delivery.csproj b/src/Umbraco.Cms.Api.Delivery/Umbraco.Cms.Api.Delivery.csproj
index 37816e2f6e..b275c6ad14 100644
--- a/src/Umbraco.Cms.Api.Delivery/Umbraco.Cms.Api.Delivery.csproj
+++ b/src/Umbraco.Cms.Api.Delivery/Umbraco.Cms.Api.Delivery.csproj
@@ -22,6 +22,9 @@
<_Parameter1>Umbraco.Tests.UnitTests
+
+ <_Parameter1>Umbraco.Tests.Integration
+
<_Parameter1>DynamicProxyGenAssembly2
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/CopyDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/CopyDocumentController.cs
index 7300e6f88a..8d8c6fd57d 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Document/CopyDocumentController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/CopyDocumentController.cs
@@ -1,4 +1,4 @@
-using Asp.Versioning;
+using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -42,12 +42,16 @@ public class CopyDocumentController : DocumentControllerBase
Guid id,
CopyDocumentRequestModel copyDocumentRequestModel)
{
- AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
+ AuthorizationResult sourceAuthorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
- ContentPermissionResource.WithKeys(ActionCopy.ActionLetter, new[] { copyDocumentRequestModel.Target?.Id, id }),
+ ContentPermissionResource.WithKeys(ActionCopy.ActionLetter, [id]),
+ AuthorizationPolicies.ContentPermissionByResource);
+ AuthorizationResult destinationAuthorizationResult = await _authorizationService.AuthorizeResourceAsync(
+ User,
+ ContentPermissionResource.WithKeys(ActionNew.ActionLetter, [copyDocumentRequestModel.Target?.Id]),
AuthorizationPolicies.ContentPermissionByResource);
- if (!authorizationResult.Succeeded)
+ if (sourceAuthorizationResult.Succeeded is false || destinationAuthorizationResult.Succeeded is false)
{
return Forbidden();
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/SearchDocumentItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/SearchDocumentItemController.cs
index 62e15d7e74..39fafe590b 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/SearchDocumentItemController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/SearchDocumentItemController.cs
@@ -28,12 +28,13 @@ public class SearchDocumentItemController : DocumentItemControllerBase
CancellationToken cancellationToken,
string query,
bool? trashed = null,
+ string? culture = null,
int skip = 0,
int take = 100,
Guid? parentId = null,
[FromQuery] IEnumerable? allowedDocumentTypes = null)
{
- PagedModel searchResult = await _indexedEntitySearchService.SearchAsync(UmbracoObjectTypes.Document, query, parentId, allowedDocumentTypes, trashed, skip, take);
+ PagedModel searchResult = await _indexedEntitySearchService.SearchAsync(UmbracoObjectTypes.Document, query, parentId, allowedDocumentTypes, trashed, culture, skip, take);
var result = new PagedModel
{
Items = searchResult.Items.OfType().Select(_documentPresentationFactory.CreateItemResponseModel),
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/MoveDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/MoveDocumentController.cs
index aa777a57f0..1141b34b5c 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Document/MoveDocumentController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/MoveDocumentController.cs
@@ -1,4 +1,4 @@
-using Asp.Versioning;
+using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -39,6 +39,20 @@ public class MoveDocumentController : DocumentControllerBase
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task Move(CancellationToken cancellationToken, Guid id, MoveDocumentRequestModel moveDocumentRequestModel)
{
+ AuthorizationResult sourceAuthorizationResult = await _authorizationService.AuthorizeResourceAsync(
+ User,
+ ContentPermissionResource.WithKeys(ActionMove.ActionLetter, [id]),
+ AuthorizationPolicies.ContentPermissionByResource);
+ AuthorizationResult destinationAuthorizationResult = await _authorizationService.AuthorizeResourceAsync(
+ User,
+ ContentPermissionResource.WithKeys(ActionNew.ActionLetter, [moveDocumentRequestModel.Target?.Id]),
+ AuthorizationPolicies.ContentPermissionByResource);
+
+ if (sourceAuthorizationResult.Succeeded is false || destinationAuthorizationResult.Succeeded is false)
+ {
+ return Forbidden();
+ }
+
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ContentPermissionResource.WithKeys(ActionMove.ActionLetter, new[] { moveDocumentRequestModel.Target?.Id, id }),
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/ManagementApiControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/ManagementApiControllerBase.cs
index aacdec5b3a..24fdbae544 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/ManagementApiControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/ManagementApiControllerBase.cs
@@ -13,6 +13,7 @@ using Umbraco.Cms.Core.Features;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Web.Common.Authorization;
+using Umbraco.Cms.Web.Common.Filters;
namespace Umbraco.Cms.Api.Management.Controllers;
@@ -22,6 +23,7 @@ namespace Umbraco.Cms.Api.Management.Controllers;
[MapToApi(ManagementApiConfiguration.ApiName)]
[JsonOptionsName(Constants.JsonOptionsNames.BackOffice)]
[AppendEventMessages]
+[DisableBrowserCache]
[Produces("application/json")]
public abstract class ManagementApiControllerBase : Controller, IUmbracoFeature
{
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/SearchMediaItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/SearchMediaItemController.cs
index b5da421abe..d3202e1df1 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/SearchMediaItemController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/SearchMediaItemController.cs
@@ -21,17 +21,20 @@ public class SearchMediaItemController : MediaItemControllerBase
_mediaPresentationFactory = mediaPresentationFactory;
}
- [NonAction]
- [Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
- public Task SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable? allowedMediaTypes = null)
- => SearchFromParentWithAllowedTypes(cancellationToken, query, null, skip, take, parentId);
-
[HttpGet("search")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedModel), StatusCodes.Status200OK)]
- public async Task SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, bool? trashed = null, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable? allowedMediaTypes = null)
+ public async Task SearchFromParentWithAllowedTypes(
+ CancellationToken cancellationToken,
+ string query,
+ bool? trashed = null,
+ string? culture = null,
+ int skip = 0,
+ int take = 100,
+ Guid? parentId = null,
+ [FromQuery]IEnumerable? allowedMediaTypes = null)
{
- PagedModel searchResult = await _indexedEntitySearchService.SearchAsync(UmbracoObjectTypes.Media, query, parentId, allowedMediaTypes, trashed, skip, take);
+ PagedModel searchResult = await _indexedEntitySearchService.SearchAsync(UmbracoObjectTypes.Media, query, parentId, allowedMediaTypes, trashed, culture, skip, take);
var result = new PagedModel
{
Items = searchResult.Items.OfType().Select(_mediaPresentationFactory.CreateItemResponseModel),
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Segment/AllSegmentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Segment/AllSegmentController.cs
index 48542c6b31..c97e6c8d90 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Segment/AllSegmentController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Segment/AllSegmentController.cs
@@ -14,7 +14,6 @@ using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Segment;
[ApiVersion("1.0")]
-[Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)]
public class AllSegmentController : SegmentControllerBase
{
private readonly ISegmentService _segmentService;
diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs
index 15dd835fa0..cff8182232 100644
--- a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs
+++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs
@@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using Umbraco.Cms.Api.Management.NotificationHandlers;
using Umbraco.Cms.Api.Management.Security;
using Umbraco.Cms.Api.Management.Services;
using Umbraco.Cms.Api.Management.Telemetry;
@@ -12,6 +13,7 @@ using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Net;
+using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Security;
@@ -74,6 +76,9 @@ public static partial class UmbracoBuilderExtensions
services.AddScoped();
+ // Register a notification handler to interrogate the registered external login providers at startup.
+ builder.AddNotificationHandler();
+
return builder;
}
diff --git a/src/Umbraco.Cms.Api.Management/Factories/DocumentVersionPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/DocumentVersionPresentationFactory.cs
index 5006ccbaa7..144be70a4c 100644
--- a/src/Umbraco.Cms.Api.Management/Factories/DocumentVersionPresentationFactory.cs
+++ b/src/Umbraco.Cms.Api.Management/Factories/DocumentVersionPresentationFactory.cs
@@ -1,5 +1,6 @@
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Api.Management.ViewModels.Document;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Extensions;
@@ -19,17 +20,26 @@ internal sealed class DocumentVersionPresentationFactory : IDocumentVersionPrese
_userIdKeyResolver = userIdKeyResolver;
}
- public async Task CreateAsync(ContentVersionMeta contentVersion) =>
- new(
+ public async Task CreateAsync(ContentVersionMeta contentVersion)
+ {
+ ReferenceByIdModel userReference = contentVersion.UserId switch
+ {
+ Constants.Security.UnknownUserId => new ReferenceByIdModel(),
+ _ => new ReferenceByIdModel(await _userIdKeyResolver.GetAsync(contentVersion.UserId)),
+ };
+
+ return new DocumentVersionItemResponseModel(
contentVersion.VersionId.ToGuid(), // this is a magic guid since versions do not have keys in the DB
new ReferenceByIdModel(_entityService.GetKey(contentVersion.ContentId, UmbracoObjectTypes.Document).Result),
- new ReferenceByIdModel(_entityService.GetKey(contentVersion.ContentTypeId, UmbracoObjectTypes.DocumentType)
- .Result),
- new ReferenceByIdModel(await _userIdKeyResolver.GetAsync(contentVersion.UserId)),
+ new ReferenceByIdModel(
+ _entityService.GetKey(contentVersion.ContentTypeId, UmbracoObjectTypes.DocumentType)
+ .Result),
+ userReference,
new DateTimeOffset(contentVersion.VersionDate),
contentVersion.CurrentPublishedVersion,
contentVersion.CurrentDraftVersion,
contentVersion.PreventCleanup);
+ }
public async Task> CreateMultipleAsync(IEnumerable contentVersions)
=> await CreateMultipleImplAsync(contentVersions).ToArrayAsync();
diff --git a/src/Umbraco.Cms.Api.Management/Middleware/BackOfficeAuthorizationInitializationMiddleware.cs b/src/Umbraco.Cms.Api.Management/Middleware/BackOfficeAuthorizationInitializationMiddleware.cs
index e4d3c236de..5bf884899c 100644
--- a/src/Umbraco.Cms.Api.Management/Middleware/BackOfficeAuthorizationInitializationMiddleware.cs
+++ b/src/Umbraco.Cms.Api.Management/Middleware/BackOfficeAuthorizationInitializationMiddleware.cs
@@ -65,13 +65,21 @@ public class BackOfficeAuthorizationInitializationMiddleware : IMiddleware
return;
}
- if (_knownHosts.Add($"{context.Request.Scheme}://{context.Request.Host}") is false)
+ var host = $"{context.Request.Scheme}://{context.Request.Host}";
+ if (_knownHosts.Contains(host))
{
return;
}
await _firstBackOfficeRequestLocker.WaitAsync();
+ // NOTE: _knownHosts is not thread safe; check again after entering the semaphore
+ if (_knownHosts.Add(host) is false)
+ {
+ _firstBackOfficeRequestLocker.Release();
+ return;
+ }
+
// ensure we explicitly add UmbracoApplicationUrl if configured (https://github.com/umbraco/Umbraco-CMS/issues/16179)
if (_webRoutingSettings.UmbracoApplicationUrl.IsNullOrWhiteSpace() is false)
{
diff --git a/src/Umbraco.Cms.Api.Management/NotificationHandlers/ExternalLoginProviderStartupHandler.cs b/src/Umbraco.Cms.Api.Management/NotificationHandlers/ExternalLoginProviderStartupHandler.cs
new file mode 100644
index 0000000000..b538f7a947
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/NotificationHandlers/ExternalLoginProviderStartupHandler.cs
@@ -0,0 +1,44 @@
+using Umbraco.Cms.Api.Management.Security;
+using Umbraco.Cms.Core;
+using Umbraco.Cms.Core.Events;
+using Umbraco.Cms.Core.Notifications;
+using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Core.Sync;
+
+namespace Umbraco.Cms.Api.Management.NotificationHandlers;
+
+///
+/// Invalidates backoffice sessions and clears external logins for removed providers if the external login
+/// provider setup has changed.
+///
+internal sealed class ExternalLoginProviderStartupHandler : INotificationHandler
+{
+ private readonly IBackOfficeExternalLoginProviders _backOfficeExternalLoginProviders;
+ private readonly IRuntimeState _runtimeState;
+ private readonly IServerRoleAccessor _serverRoleAccessor;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ExternalLoginProviderStartupHandler(
+ IBackOfficeExternalLoginProviders backOfficeExternalLoginProviders,
+ IRuntimeState runtimeState,
+ IServerRoleAccessor serverRoleAccessor)
+ {
+ _backOfficeExternalLoginProviders = backOfficeExternalLoginProviders;
+ _runtimeState = runtimeState;
+ _serverRoleAccessor = serverRoleAccessor;
+ }
+
+ ///
+ public void Handle(UmbracoApplicationStartingNotification notification)
+ {
+ if (_runtimeState.Level != RuntimeLevel.Run ||
+ _serverRoleAccessor.CurrentServerRole == ServerRole.Subscriber)
+ {
+ return;
+ }
+
+ _backOfficeExternalLoginProviders.InvalidateSessionsIfExternalLoginProvidersChanged();
+ }
+}
diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json
index 9d7a521046..2370516f51 100644
--- a/src/Umbraco.Cms.Api.Management/OpenApi.json
+++ b/src/Umbraco.Cms.Api.Management/OpenApi.json
@@ -10159,6 +10159,13 @@
"type": "boolean"
}
},
+ {
+ "name": "culture",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
{
"name": "skip",
"in": "query",
@@ -15918,6 +15925,13 @@
"type": "boolean"
}
},
+ {
+ "name": "culture",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
{
"name": "skip",
"in": "query",
diff --git a/src/Umbraco.Cms.Api.Management/Security/BackOfficeExternalLoginProviders.cs b/src/Umbraco.Cms.Api.Management/Security/BackOfficeExternalLoginProviders.cs
index 2338dace96..7ecb178db5 100644
--- a/src/Umbraco.Cms.Api.Management/Security/BackOfficeExternalLoginProviders.cs
+++ b/src/Umbraco.Cms.Api.Management/Security/BackOfficeExternalLoginProviders.cs
@@ -1,5 +1,9 @@
using Microsoft.AspNetCore.Authentication;
using Umbraco.Cms.Core.Security;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Umbraco.Cms.Core.DependencyInjection;
+using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Security;
@@ -8,13 +12,37 @@ public class BackOfficeExternalLoginProviders : IBackOfficeExternalLoginProvider
{
private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;
private readonly Dictionary _externalLogins;
+ private readonly IKeyValueService _keyValueService;
+ private readonly IExternalLoginWithKeyService _externalLoginWithKeyService;
+ private readonly ILogger _logger;
+ private const string ExternalLoginProvidersKey = "Umbraco.Cms.Web.BackOffice.Security.BackOfficeExternalLoginProviders";
+
+ [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
public BackOfficeExternalLoginProviders(
IEnumerable externalLogins,
IAuthenticationSchemeProvider authenticationSchemeProvider)
+ : this(
+ externalLogins,
+ authenticationSchemeProvider,
+ StaticServiceProvider.Instance.GetRequiredService(),
+ StaticServiceProvider.Instance.GetRequiredService(),
+ StaticServiceProvider.Instance.GetRequiredService>())
+ {
+ }
+
+ public BackOfficeExternalLoginProviders(
+ IEnumerable externalLogins,
+ IAuthenticationSchemeProvider authenticationSchemeProvider,
+ IKeyValueService keyValueService,
+ IExternalLoginWithKeyService externalLoginWithKeyService,
+ ILogger logger)
{
_externalLogins = externalLogins.ToDictionary(x => x.AuthenticationType);
_authenticationSchemeProvider = authenticationSchemeProvider;
+ _keyValueService = keyValueService;
+ _externalLoginWithKeyService = externalLoginWithKeyService;
+ _logger = logger;
}
///
@@ -60,4 +88,25 @@ public class BackOfficeExternalLoginProviders : IBackOfficeExternalLoginProvider
var found = _externalLogins.Values.Where(x => x.Options.DenyLocalLogin).ToList();
return found.Count > 0;
}
+
+ ///
+ public void InvalidateSessionsIfExternalLoginProvidersChanged()
+ {
+ var previousExternalLoginProvidersValue = _keyValueService.GetValue(ExternalLoginProvidersKey);
+ var currentExternalLoginProvidersValue = string.Join("|", _externalLogins.Keys.OrderBy(key => key));
+
+ if ((previousExternalLoginProvidersValue ?? string.Empty) != currentExternalLoginProvidersValue)
+ {
+ _logger.LogWarning(
+ "The configured external login providers have changed. Existing backoffice sessions using the removed providers will be invalidated and external login data removed.");
+
+ _externalLoginWithKeyService.PurgeLoginsForRemovedProviders(_externalLogins.Keys);
+
+ _keyValueService.SetValue(ExternalLoginProvidersKey, currentExternalLoginProvidersValue);
+ }
+ else if (previousExternalLoginProvidersValue is null)
+ {
+ _keyValueService.SetValue(ExternalLoginProvidersKey, string.Empty);
+ }
+ }
}
diff --git a/src/Umbraco.Cms.Api.Management/Security/IBackOfficeExternalLoginProviders.cs b/src/Umbraco.Cms.Api.Management/Security/IBackOfficeExternalLoginProviders.cs
index 5dba04684c..2ff70a6015 100644
--- a/src/Umbraco.Cms.Api.Management/Security/IBackOfficeExternalLoginProviders.cs
+++ b/src/Umbraco.Cms.Api.Management/Security/IBackOfficeExternalLoginProviders.cs
@@ -23,4 +23,11 @@ public interface IBackOfficeExternalLoginProviders
///
///
bool HasDenyLocalLogin();
+
+ ///
+ /// Used during startup to see if the configured external login providers is different from the persisted information.
+ /// If they are different, this will invalidate backoffice sessions and clear external logins for removed providers
+ /// if the external login provider setup has changed.
+ ///
+ void InvalidateSessionsIfExternalLoginProvidersChanged() { }
}
diff --git a/src/Umbraco.Core/Cache/AppCacheExtensions.cs b/src/Umbraco.Core/Cache/AppCacheExtensions.cs
index 55079ba018..480b677f24 100644
--- a/src/Umbraco.Core/Cache/AppCacheExtensions.cs
+++ b/src/Umbraco.Core/Cache/AppCacheExtensions.cs
@@ -41,7 +41,7 @@ public static class AppCacheExtensions
public static T? GetCacheItem(this IAppCache provider, string cacheKey)
{
var result = provider.Get(cacheKey);
- if (result == null)
+ if (IsRetrievedItemNull(result))
{
return default;
}
@@ -52,7 +52,7 @@ public static class AppCacheExtensions
public static T? GetCacheItem(this IAppCache provider, string cacheKey, Func getCacheItem)
{
var result = provider.Get(cacheKey, () => getCacheItem());
- if (result == null)
+ if (IsRetrievedItemNull(result))
{
return default;
}
@@ -60,6 +60,8 @@ public static class AppCacheExtensions
return result.TryConvertTo().Result;
}
+ private static bool IsRetrievedItemNull(object? result) => result is null or (object)Cms.Core.Constants.Cache.NullRepresentationInCache;
+
public static async Task GetCacheItemAsync(
this IAppPolicyCache provider,
string cacheKey,
diff --git a/src/Umbraco.Core/Cache/PartialViewCacheInvalidators/IMemberPartialViewCacheInvalidator.cs b/src/Umbraco.Core/Cache/PartialViewCacheInvalidators/IMemberPartialViewCacheInvalidator.cs
new file mode 100644
index 0000000000..289dad5288
--- /dev/null
+++ b/src/Umbraco.Core/Cache/PartialViewCacheInvalidators/IMemberPartialViewCacheInvalidator.cs
@@ -0,0 +1,16 @@
+namespace Umbraco.Cms.Core.Cache.PartialViewCacheInvalidators;
+
+///
+/// Defines behaviours for clearing of cached partials views that are configured to be cached individually by member.
+///
+public interface IMemberPartialViewCacheInvalidator
+{
+ ///
+ /// Clears the partial view cache items for the specified member ids.
+ ///
+ /// The member Ids to clear the cache for.
+ ///
+ /// Called from the when a member is saved or deleted.
+ ///
+ void ClearPartialViewCacheItems(IEnumerable memberIds);
+}
diff --git a/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs
index 1c19f62576..3e4181feac 100644
--- a/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs
+++ b/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs
@@ -1,10 +1,12 @@
+using Microsoft.Extensions.DependencyInjection;
+using Umbraco.Cms.Core.Cache.PartialViewCacheInvalidators;
+using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
-using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Cache;
@@ -13,10 +15,32 @@ public sealed class MemberCacheRefresher : PayloadCacheRefresherBase
+ : this(
+ appCaches,
+ serializer,
+ idKeyMap,
+ eventAggregator,
+ factory,
+ StaticServiceProvider.Instance.GetRequiredService())
+ {
+ }
+
+ public MemberCacheRefresher(
+ AppCaches appCaches,
+ IJsonSerializer serializer,
+ IIdKeyMap idKeyMap,
+ IEventAggregator eventAggregator,
+ ICacheRefresherNotificationFactory factory,
+ IMemberPartialViewCacheInvalidator memberPartialViewCacheInvalidator)
+ : base(appCaches, serializer, eventAggregator, factory)
+ {
_idKeyMap = idKeyMap;
+ _memberPartialViewCacheInvalidator = memberPartialViewCacheInvalidator;
+ }
#region Indirect
@@ -65,7 +89,8 @@ public sealed class MemberCacheRefresher : PayloadCacheRefresherBase p.Id));
+
Attempt memberCache = AppCaches.IsolatedCaches.Get();
foreach (JsonPayload p in payloads)
diff --git a/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs b/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs
index 9c1f5faef9..94e2968056 100644
--- a/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs
+++ b/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs
@@ -14,8 +14,8 @@ public static class ContentSettingsExtensions
/// true if the file extension is allowed for upload; otherwise, false .
///
public static bool IsFileAllowedForUpload(this ContentSettings contentSettings, string extension)
- => contentSettings.AllowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension)) ||
- (contentSettings.AllowedUploadedFileExtensions.Any() == false && contentSettings.DisallowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension)) == false);
+ => contentSettings.AllowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension.Trim())) ||
+ (contentSettings.AllowedUploadedFileExtensions.Any() == false && contentSettings.DisallowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension.Trim())) == false);
///
/// Gets the auto-fill configuration for a specified property alias.
diff --git a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs
index d359f6d208..bfff570c4f 100644
--- a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs
@@ -28,8 +28,8 @@ public class SecuritySettings
internal const int StaticMemberDefaultLockoutTimeInMinutes = 30 * 24 * 60;
internal const int StaticUserDefaultLockoutTimeInMinutes = 30 * 24 * 60;
- private const long StaticUserDefaultFailedLoginDurationInMilliseconds = 1000;
- private const long StaticUserMinimumFailedLoginDurationInMilliseconds = 250;
+ internal const long StaticUserDefaultFailedLoginDurationInMilliseconds = 1000;
+ internal const long StaticUserMinimumFailedLoginDurationInMilliseconds = 250;
internal const string StaticAuthorizeCallbackPathName = "/umbraco/oauth_complete";
internal const string StaticAuthorizeCallbackLogoutPathName = "/umbraco/logout";
internal const string StaticAuthorizeCallbackErrorPathName = "/umbraco/error";
diff --git a/src/Umbraco.Core/Constants-Cache.cs b/src/Umbraco.Core/Constants-Cache.cs
index ccc347111e..874605c123 100644
--- a/src/Umbraco.Core/Constants-Cache.cs
+++ b/src/Umbraco.Core/Constants-Cache.cs
@@ -1,4 +1,4 @@
-namespace Umbraco.Cms.Core;
+namespace Umbraco.Cms.Core;
public static partial class Constants
{
public static class Cache
@@ -9,5 +9,13 @@ public static partial class Constants
public const string Media = "media";
}
+
+ ///
+ /// Defines the string used to represent a null value in the cache.
+ ///
+ ///
+ /// Used in conjunction with the option to cache null values on the repository caches, so we
+ /// can distinguish a true null "not found" value and a cached null value.
+ public const string NullRepresentationInCache = "*NULL*";
}
}
diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs
index 947899bee7..5db4d71efc 100644
--- a/src/Umbraco.Core/Constants-DataTypes.cs
+++ b/src/Umbraco.Core/Constants-DataTypes.cs
@@ -107,6 +107,7 @@ public static partial class Constants
///
/// Guid for List View - Members as string
///
+ [Obsolete("No longer used in Umbraco. Scheduled for removal in Umbraco 17.")]
public const string ListViewMembers = "AA2C52A0-CE87-4E65-A47C-7DF09358585D";
///
diff --git a/src/Umbraco.Core/Extensions/IntExtensions.cs b/src/Umbraco.Core/Extensions/IntExtensions.cs
index 6bdb3c6435..5b744e26e2 100644
--- a/src/Umbraco.Core/Extensions/IntExtensions.cs
+++ b/src/Umbraco.Core/Extensions/IntExtensions.cs
@@ -1,15 +1,17 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
+using System.Diagnostics.CodeAnalysis;
+
namespace Umbraco.Extensions;
public static class IntExtensions
{
///
- /// Does something 'x' amount of times
+ /// Does something 'x' amount of times.
///
- ///
- ///
+ /// Number of times to execute the action.
+ /// The action to execute.
public static void Times(this int n, Action action)
{
for (var i = 0; i < n; i++)
@@ -19,11 +21,11 @@ public static class IntExtensions
}
///
- /// Creates a Guid based on an integer value
+ /// Creates a Guid based on an integer value.
///
- /// value to convert
+ /// The value to convert.
///
- ///
+ /// The converted .
///
public static Guid ToGuid(this int value)
{
@@ -31,4 +33,25 @@ public static class IntExtensions
BitConverter.GetBytes(value).CopyTo(bytes);
return new Guid(bytes);
}
+
+ ///
+ /// Restores a GUID previously created from an integer value using .
+ ///
+ /// The value to convert.
+ /// The converted .
+ ///
+ /// True if the value could be created, otherwise false.
+ ///
+ public static bool TryParseFromGuid(Guid value, [NotNullWhen(true)] out int? result)
+ {
+ if (value.ToString().EndsWith("-0000-0000-0000-000000000000") is false)
+ {
+ // We have a proper GUID, not one converted from an integer.
+ result = null;
+ return false;
+ }
+
+ result = BitConverter.ToInt32(value.ToByteArray());
+ return true;
+ }
}
diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs
index 8f602b4446..dd5a41bafa 100644
--- a/src/Umbraco.Core/Extensions/StringExtensions.cs
+++ b/src/Umbraco.Core/Extensions/StringExtensions.cs
@@ -1576,4 +1576,22 @@ public static class StringExtensions
// this is by far the fastest way to find string needles in a string haystack
public static int CountOccurrences(this string haystack, string needle)
=> haystack.Length - haystack.Replace(needle, string.Empty).Length;
+
+ ///
+ /// Verifies the provided string is a valid culture code and returns it in a consistent casing.
+ ///
+ /// Culture code.
+ /// Culture code in standard casing.
+ public static string? EnsureCultureCode(this string? culture)
+ {
+ if (string.IsNullOrEmpty(culture) || culture == "*")
+ {
+ return culture;
+ }
+
+ // Create as CultureInfo instance from provided name so we can ensure consistent casing of culture code when persisting.
+ // This will accept mixed case but once created have a `Name` property that is consistently and correctly cased.
+ // Will throw in an invalid culture code is provided.
+ return new CultureInfo(culture).Name;
+ }
}
diff --git a/src/Umbraco.Core/IO/MediaPathSchemes/UniqueMediaPathScheme.cs b/src/Umbraco.Core/IO/MediaPathSchemes/UniqueMediaPathScheme.cs
index 353636c186..b0c2a35ae2 100644
--- a/src/Umbraco.Core/IO/MediaPathSchemes/UniqueMediaPathScheme.cs
+++ b/src/Umbraco.Core/IO/MediaPathSchemes/UniqueMediaPathScheme.cs
@@ -13,6 +13,18 @@ public class UniqueMediaPathScheme : IMediaPathScheme
///
public string GetFilePath(MediaFileManager fileManager, Guid itemGuid, Guid propertyGuid, string filename)
{
+ // Shortening of the Guid to 8 chars risks collisions with GUIDs, which are expected to be very rare.
+ // However with GUID 7, the chance is much higher, as those created in a short period of time could have
+ // the same first 8 characters.
+ // Such GUIDs would have been created via Guid.CreateVersion7() rather than Guid.NewGuid().
+ // We should detect that, throw, and recommend creation of standard GUIDs or the use of a custom IMediaPathScheme instead.
+ if (itemGuid.Version == 7 || propertyGuid.Version == 7)
+ {
+ throw new ArgumentException(
+ "The UniqueMediaPathScheme cannot be used with version 7 GUIDs due to an increased risk of collisions in the generated file paths. " +
+ "Please use version 4 GUIDs created via Guid.NewGuid() or implement and register a different IMediaPathScheme.");
+ }
+
Guid combinedGuid = GuidUtils.Combine(itemGuid, propertyGuid);
var directory = GuidUtils.ToBase32String(combinedGuid, DirectoryLength);
diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs
index 768e631359..f9fa7263ba 100644
--- a/src/Umbraco.Core/Models/ContentBase.cs
+++ b/src/Umbraco.Core/Models/ContentBase.cs
@@ -1,5 +1,7 @@
-using System.Collections.Specialized;
+using System.Collections.Specialized;
using System.Diagnostics;
+using System.Globalization;
+using System.Runtime.ConstrainedExecution;
using System.Runtime.Serialization;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Extensions;
@@ -288,6 +290,8 @@ public abstract class ContentBase : TreeEntityBase, IContentBase
// set on variant content type
if (ContentType.VariesByCulture())
{
+ culture = culture.EnsureCultureCode();
+
// invariant is ok
if (culture.IsNullOrWhiteSpace())
{
@@ -297,7 +301,7 @@ public abstract class ContentBase : TreeEntityBase, IContentBase
// clear
else if (name.IsNullOrWhiteSpace())
{
- ClearCultureInfo(culture!);
+ ClearCultureInfo(culture);
}
// set
@@ -322,11 +326,6 @@ public abstract class ContentBase : TreeEntityBase, IContentBase
private void ClearCultureInfo(string culture)
{
- if (culture == null)
- {
- throw new ArgumentNullException(nameof(culture));
- }
-
if (string.IsNullOrWhiteSpace(culture))
{
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(culture));
@@ -455,6 +454,7 @@ public abstract class ContentBase : TreeEntityBase, IContentBase
$"No PropertyType exists with the supplied alias \"{propertyTypeAlias}\".");
}
+ culture = culture.EnsureCultureCode();
var updated = property.SetValue(value, culture, segment);
if (updated)
{
diff --git a/src/Umbraco.Core/Persistence/Repositories/IExternalLoginWithKeyRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginWithKeyRepository.cs
index ec9a79530c..0329ceb33b 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IExternalLoginWithKeyRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginWithKeyRepository.cs
@@ -3,23 +3,29 @@ using Umbraco.Cms.Core.Security;
namespace Umbraco.Cms.Core.Persistence.Repositories;
///
-/// Repository for external logins with Guid as key, so it can be shared for members and users
+/// Repository for external logins with Guid as key, so it can be shared for members and users.
///
public interface IExternalLoginWithKeyRepository : IReadWriteQueryRepository,
IQueryRepository
{
///
- /// Replaces all external login providers for the user/member key
+ /// Replaces all external login providers for the user/member key.
///
void Save(Guid userOrMemberKey, IEnumerable logins);
///
- /// Replaces all external login provider tokens for the providers specified for the user/member key
+ /// Replaces all external login provider tokens for the providers specified for the user/member key.
///
void Save(Guid userOrMemberKey, IEnumerable tokens);
///
- /// Deletes all external logins for the specified the user/member key
+ /// Deletes all external logins for the specified the user/member key.
///
void DeleteUserLogins(Guid userOrMemberKey);
+
+ ///
+ /// Deletes external logins that aren't associated with the current collection of providers.
+ ///
+ /// The names of the currently configured providers.
+ void DeleteUserLoginsForRemovedProviders(IEnumerable currentLoginProviders) { }
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs
index 94f9bcac6f..456769e7e4 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs
@@ -42,4 +42,11 @@ public interface IMemberRepository : IContentRepository
int GetCountByQuery(IQuery? query);
Task> GetPagedByFilterAsync(MemberFilter memberFilter,int skip, int take, Ordering? ordering = null);
+
+ ///
+ /// Saves only the properties related to login for the member, using an optimized, non-locking update.
+ ///
+ /// The member to update.
+ /// Used to avoid the full save of the member object after a login operation.
+ Task UpdateLoginPropertiesAsync(IMember member) => Task.CompletedTask;
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs
index 0f9cc14f79..4b006ecb1c 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs
@@ -1,4 +1,4 @@
-using System.Linq.Expressions;
+using System.Linq.Expressions;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Persistence.Querying;
@@ -160,4 +160,10 @@ public interface IUserRepository : IReadWriteQueryRepository
bool RemoveClientId(int id, string clientId);
IUser? GetByClientId(string clientId);
+
+ ///
+ /// Invalidates sessions for users that aren't associated with the current collection of providers.
+ ///
+ /// The names of the currently configured providers.
+ void InvalidateSessionsForRemovedProviders(IEnumerable currentLoginProviders) { }
}
diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs
index 888b1dfdf2..d11e08bad3 100644
--- a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs
+++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs
@@ -1,4 +1,7 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Composing;
+using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Editors;
@@ -12,14 +15,27 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase _logger;
+
///
/// Initializes a new instance of the class.
///
/// The items.
+ [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
public DataValueReferenceFactoryCollection(Func> items)
- : base(items)
+ : this(
+ items,
+ StaticServiceProvider.Instance.GetRequiredService>())
{ }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The items.
+ /// The logger.
+ public DataValueReferenceFactoryCollection(Func> items, ILogger logger)
+ : base(items) => _logger = logger;
+
///
/// Gets all unique references from the specified properties.
///
@@ -33,7 +49,7 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase();
// Group by property editor alias to avoid duplicate lookups and optimize value parsing
- foreach (var propertyValuesByPropertyEditorAlias in properties.GroupBy(x => x.PropertyType.PropertyEditorAlias, x => x.Values))
+ foreach (IGrouping> propertyValuesByPropertyEditorAlias in properties.GroupBy(x => x.PropertyType.PropertyEditorAlias, x => x.Values))
{
if (!propertyEditors.TryGet(propertyValuesByPropertyEditorAlias.Key, out IDataEditor? dataEditor))
{
@@ -48,7 +64,7 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase
public ISet GetReferences(IDataEditor dataEditor, IEnumerable values) =>
- GetReferencesEnumerable(dataEditor, values).ToHashSet();
- private IEnumerable GetReferencesEnumerable(IDataEditor dataEditor, IEnumerable values)
+ GetReferencesEnumerable(dataEditor, values, null).ToHashSet();
+
+ private ISet GetReferences(IDataEditor dataEditor, IEnumerable values, string propertyEditorAlias) =>
+ GetReferencesEnumerable(dataEditor, values, propertyEditorAlias).ToHashSet();
+
+ private IEnumerable GetReferencesEnumerable(IDataEditor dataEditor, IEnumerable values, string? propertyEditorAlias)
{
// TODO: We will need to change this once we support tracking via variants/segments
// for now, we are tracking values from ALL variants
if (dataEditor.GetValueEditor() is IDataValueReference dataValueReference)
{
- foreach (UmbracoEntityReference reference in values.SelectMany(dataValueReference.GetReferences))
+ foreach (UmbracoEntityReference reference in GetReferencesFromPropertyValues(values, dataValueReference, propertyEditorAlias))
{
yield return reference;
}
@@ -107,6 +127,38 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase GetReferencesFromPropertyValues(IEnumerable values, IDataValueReference dataValueReference, string? propertyEditorAlias)
+ {
+ var result = new List();
+ foreach (var value in values)
+ {
+ // When property editors on data types are changed, we could have values that are incompatible with the new editor.
+ // Leading to issues such as:
+ // - https://github.com/umbraco/Umbraco-CMS/issues/17628
+ // - https://github.com/umbraco/Umbraco-CMS/issues/17725
+ // Although some changes like this are not intended to be compatible, we should handle them gracefully and not
+ // error in retrieving references, which would prevent manipulating or deleting the content that uses the data type.
+ try
+ {
+ IEnumerable references = dataValueReference.GetReferences(value);
+ result.AddRange(references);
+ }
+ catch (Exception ex)
+ {
+ // Log the exception but don't throw, continue with the next value.
+ _logger.LogError(
+ ex,
+ "Error getting references from value {Value} with data editor {DataEditor} and property editor alias {PropertyEditorAlias}.",
+ value,
+ dataValueReference.GetType().FullName,
+ propertyEditorAlias ?? "n/a");
+ throw;
+ }
+ }
+
+ return result;
+ }
+
///
/// Gets all relation type aliases that are automatically tracked.
///
@@ -114,6 +166,10 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase
/// All relation type aliases that are automatically tracked.
///
+ [Obsolete("Use GetAllAutomaticRelationTypesAliases. This will be removed in Umbraco 15.")]
+ public ISet GetAutomaticRelationTypesAliases(PropertyEditorCollection propertyEditors) =>
+ GetAllAutomaticRelationTypesAliases(propertyEditors);
+
public ISet GetAllAutomaticRelationTypesAliases(PropertyEditorCollection propertyEditors)
{
// Always add default automatic relation types
diff --git a/src/Umbraco.Core/Routing/NewDefaultUrlProvider.cs b/src/Umbraco.Core/Routing/NewDefaultUrlProvider.cs
index c3e779b0d7..4c4a3dbca6 100644
--- a/src/Umbraco.Core/Routing/NewDefaultUrlProvider.cs
+++ b/src/Umbraco.Core/Routing/NewDefaultUrlProvider.cs
@@ -15,24 +15,69 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Routing;
///
-/// Provides urls.
+/// Provides URLs.
///
public class NewDefaultUrlProvider : IUrlProvider
{
- private readonly ILocalizationService _localizationService;
private readonly IPublishedContentCache _publishedContentCache;
private readonly IDomainCache _domainCache;
private readonly IIdKeyMap _idKeyMap;
private readonly IDocumentUrlService _documentUrlService;
private readonly IDocumentNavigationQueryService _navigationQueryService;
private readonly IPublishedContentStatusFilteringService _publishedContentStatusFilteringService;
- private readonly ILocalizedTextService? _localizedTextService;
private readonly ILogger _logger;
private readonly ISiteDomainMapper _siteDomainMapper;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly UriUtility _uriUtility;
private RequestHandlerSettings _requestSettings;
+ private readonly ILanguageService _languageService;
+ // TODO (V17): When removing the obsolete constructors, remove the unused localizationService parameter from the constructor (we can't do it now
+ // because it is used in the obsolete constructors and leads to an ambigious constructor error).
+ // See also if we can make GetUrlFromRoute asynchronous and avoid the GetAwaiter().GetResult() in when using ILanguageService.
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public NewDefaultUrlProvider(
+ IOptionsMonitor requestSettings,
+ ILogger logger,
+ ISiteDomainMapper siteDomainMapper,
+ IUmbracoContextAccessor umbracoContextAccessor,
+ UriUtility uriUtility,
+#pragma warning disable CS0618 // Type or member is obsolete
+#pragma warning disable IDE0060 // Remove unused parameter
+ ILocalizationService localizationService,
+#pragma warning restore IDE0060 // Remove unused parameter
+#pragma warning restore CS0618 // Type or member is obsolete
+ IPublishedContentCache publishedContentCache,
+ IDomainCache domainCache,
+ IIdKeyMap idKeyMap,
+ IDocumentUrlService documentUrlService,
+ IDocumentNavigationQueryService navigationQueryService,
+ IPublishedContentStatusFilteringService publishedContentStatusFilteringService,
+ ILanguageService languageService)
+ {
+ _requestSettings = requestSettings.CurrentValue;
+ _logger = logger;
+ _siteDomainMapper = siteDomainMapper;
+ _umbracoContextAccessor = umbracoContextAccessor;
+ _uriUtility = uriUtility;
+ _publishedContentCache = publishedContentCache;
+ _domainCache = domainCache;
+ _idKeyMap = idKeyMap;
+ _documentUrlService = documentUrlService;
+ _navigationQueryService = navigationQueryService;
+ _publishedContentStatusFilteringService = publishedContentStatusFilteringService;
+ _languageService = languageService;
+
+ requestSettings.OnChange(x => _requestSettings = x);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ [Obsolete("Use the non-obsolete constructor. Scheduled for removal in V17.")]
public NewDefaultUrlProvider(
IOptionsMonitor requestSettings,
ILogger logger,
@@ -46,23 +91,26 @@ public class NewDefaultUrlProvider : IUrlProvider
IDocumentUrlService documentUrlService,
IDocumentNavigationQueryService navigationQueryService,
IPublishedContentStatusFilteringService publishedContentStatusFilteringService)
+ : this(
+ requestSettings,
+ logger,
+ siteDomainMapper,
+ umbracoContextAccessor,
+ uriUtility,
+ localizationService,
+ publishedContentCache,
+ domainCache,
+ idKeyMap,
+ documentUrlService,
+ navigationQueryService,
+ publishedContentStatusFilteringService,
+ StaticServiceProvider.Instance.GetRequiredService())
{
- _requestSettings = requestSettings.CurrentValue;
- _logger = logger;
- _siteDomainMapper = siteDomainMapper;
- _umbracoContextAccessor = umbracoContextAccessor;
- _uriUtility = uriUtility;
- _localizationService = localizationService;
- _publishedContentCache = publishedContentCache;
- _domainCache = domainCache;
- _idKeyMap = idKeyMap;
- _documentUrlService = documentUrlService;
- _navigationQueryService = navigationQueryService;
- _publishedContentStatusFilteringService = publishedContentStatusFilteringService;
-
- requestSettings.OnChange(x => _requestSettings = x);
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
[Obsolete("Use the non-obsolete constructor. Scheduled for removal in V17.")]
public NewDefaultUrlProvider(
IOptionsMonitor requestSettings,
@@ -92,8 +140,6 @@ public class NewDefaultUrlProvider : IUrlProvider
{
}
- #region GetOtherUrls
-
///
/// Gets the other URLs of a published content.
///
@@ -108,14 +154,14 @@ public class NewDefaultUrlProvider : IUrlProvider
///
public virtual IEnumerable GetOtherUrls(int id, Uri current)
{
- var keyAttempt = _idKeyMap.GetKeyForId(id, UmbracoObjectTypes.Document);
+ Attempt keyAttempt = _idKeyMap.GetKeyForId(id, UmbracoObjectTypes.Document);
if (keyAttempt.Success is false)
{
yield break;
}
- var key = keyAttempt.Result;
+ Guid key = keyAttempt.Result;
IPublishedContent? node = _publishedContentCache.GetById(key);
if (node == null)
@@ -156,7 +202,7 @@ public class NewDefaultUrlProvider : IUrlProvider
// need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned)
var pos = route.IndexOf('/', StringComparison.Ordinal);
- var path = pos == 0 ? route : route.Substring(pos);
+ var path = pos == 0 ? route : route[pos..];
var uri = new Uri(CombinePaths(d.Uri.GetLeftPart(UriPartial.Path), path));
uri = _uriUtility.UriFromUmbraco(uri, _requestSettings);
@@ -178,17 +224,9 @@ public class NewDefaultUrlProvider : IUrlProvider
private string GetLegacyRouteFormatById(Guid key, string? culture)
{
var isDraft = _umbracoContextAccessor.GetRequiredUmbracoContext().InPreviewMode;
-
-
return _documentUrlService.GetLegacyRouteFormat(key, culture, isDraft);
-
-
}
- #endregion
-
- #region GetUrl
-
///
public virtual UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current)
{
@@ -217,6 +255,9 @@ public class NewDefaultUrlProvider : IUrlProvider
return GetUrlFromRoute(route, content.Id, current, mode, culture);
}
+ ///
+ /// Gets the URL from the provided route.
+ ///
internal UrlInfo? GetUrlFromRoute(
string? route,
int id,
@@ -226,7 +267,7 @@ public class NewDefaultUrlProvider : IUrlProvider
{
if (string.IsNullOrWhiteSpace(route) || route.Equals("#"))
{
- if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug))
+ if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug(
"Couldn't find any page with nodeId={NodeId}. This is most likely caused by the page not being published.",
@@ -248,7 +289,7 @@ public class NewDefaultUrlProvider : IUrlProvider
current,
culture);
- var defaultCulture = _localizationService.GetDefaultLanguageIsoCode();
+ var defaultCulture = _languageService.GetDefaultIsoCodeAsync().GetAwaiter().GetResult();
if (domainUri is not null ||
string.IsNullOrEmpty(culture) ||
culture.Equals(defaultCulture, StringComparison.InvariantCultureIgnoreCase))
@@ -260,10 +301,6 @@ public class NewDefaultUrlProvider : IUrlProvider
return null;
}
- #endregion
-
- #region Utilities
-
private Uri AssembleUrl(DomainAndUri? domainUri, string path, Uri current, UrlMode mode)
{
Uri uri;
@@ -331,6 +368,4 @@ public class NewDefaultUrlProvider : IUrlProvider
var path = path1.TrimEnd(Constants.CharArrays.ForwardSlash) + path2;
return path == "/" ? path : path.TrimEnd(Constants.CharArrays.ForwardSlash);
}
-
- #endregion
}
diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs
index c72218e8f3..f246b9c3f0 100644
--- a/src/Umbraco.Core/Services/ContentService.cs
+++ b/src/Umbraco.Core/Services/ContentService.cs
@@ -1184,6 +1184,8 @@ public class ContentService : RepositoryService, IContentService
throw new ArgumentException("Cultures cannot be null or whitespace", nameof(cultures));
}
+ cultures = cultures.Select(x => x.EnsureCultureCode()!).ToArray();
+
EventMessages evtMsgs = EventMessagesFactory.Get();
// we need to guard against unsaved changes before proceeding; the content will be saved, but we're not firing any saved notifications
@@ -1263,7 +1265,7 @@ public class ContentService : RepositoryService, IContentService
EventMessages evtMsgs = EventMessagesFactory.Get();
- culture = culture?.NullOrWhiteSpaceAsNull();
+ culture = culture?.NullOrWhiteSpaceAsNull().EnsureCultureCode();
PublishedState publishedState = content.PublishedState;
if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished)
@@ -2063,7 +2065,7 @@ public class ContentService : RepositoryService, IContentService
cultures = ["*"];
}
- return cultures;
+ return cultures.Select(x => x.EnsureCultureCode()!).ToArray();
}
private static bool ProvidedCulturesIndicatePublishAll(string[] cultures) => cultures.Length == 0 || (cultures.Length == 1 && cultures[0] == "invariant");
diff --git a/src/Umbraco.Core/Services/ExternalLoginService.cs b/src/Umbraco.Core/Services/ExternalLoginService.cs
index be49927b36..df64e2e355 100644
--- a/src/Umbraco.Core/Services/ExternalLoginService.cs
+++ b/src/Umbraco.Core/Services/ExternalLoginService.cs
@@ -5,21 +5,40 @@ using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Security;
-using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Services;
public class ExternalLoginService : RepositoryService, IExternalLoginWithKeyService
{
private readonly IExternalLoginWithKeyRepository _externalLoginRepository;
+ private readonly IUserRepository _userRepository;
+ [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
public ExternalLoginService(
ICoreScopeProvider provider,
ILoggerFactory loggerFactory,
IEventMessagesFactory eventMessagesFactory,
IExternalLoginWithKeyRepository externalLoginRepository)
- : base(provider, loggerFactory, eventMessagesFactory) =>
+ : this(
+ provider,
+ loggerFactory,
+ eventMessagesFactory,
+ externalLoginRepository,
+ StaticServiceProvider.Instance.GetRequiredService())
+ {
+ }
+
+ public ExternalLoginService(
+ ICoreScopeProvider provider,
+ ILoggerFactory loggerFactory,
+ IEventMessagesFactory eventMessagesFactory,
+ IExternalLoginWithKeyRepository externalLoginRepository,
+ IUserRepository userRepository)
+ : base(provider, loggerFactory, eventMessagesFactory)
+ {
_externalLoginRepository = externalLoginRepository;
+ _userRepository = userRepository;
+ }
public IEnumerable Find(string loginProvider, string providerKey)
{
@@ -80,4 +99,15 @@ public class ExternalLoginService : RepositoryService, IExternalLoginWithKeyServ
scope.Complete();
}
}
+
+ ///
+ public void PurgeLoginsForRemovedProviders(IEnumerable currentLoginProviders)
+ {
+ using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+ {
+ _userRepository.InvalidateSessionsForRemovedProviders(currentLoginProviders);
+ _externalLoginRepository.DeleteUserLoginsForRemovedProviders(currentLoginProviders);
+ scope.Complete();
+ }
+ }
}
diff --git a/src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs b/src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs
index 42f0708aaa..deaa135f16 100644
--- a/src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs
+++ b/src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs
@@ -5,47 +5,53 @@ namespace Umbraco.Cms.Core.Services;
public interface IExternalLoginWithKeyService : IService
{
///
- /// Returns all user logins assigned
+ /// Returns all user logins assigned.
///
IEnumerable GetExternalLogins(Guid userOrMemberKey);
///
- /// Returns all user login tokens assigned
+ /// Returns all user login tokens assigned.
///
IEnumerable GetExternalLoginTokens(Guid userOrMemberKey);
///
/// Returns all logins matching the login info - generally there should only be one but in some cases
- /// there might be more than one depending on if an administrator has been editing/removing members
+ /// there might be more than one depending on if an administrator has been editing/removing members.
///
IEnumerable Find(string loginProvider, string providerKey);
///
- /// Saves the external logins associated with the user
+ /// Saves the external logins associated with the user.
///
///
- /// The user or member key associated with the logins
+ /// The user or member key associated with the logins.
///
///
///
- /// This will replace all external login provider information for the user
+ /// This will replace all external login provider information for the user.
///
void Save(Guid userOrMemberKey, IEnumerable logins);
///
- /// Saves the external login tokens associated with the user
+ /// Saves the external login tokens associated with the user.
///
///
- /// The user or member key associated with the logins
+ /// The user or member key associated with the logins.
///
///
///
- /// This will replace all external login tokens for the user
+ /// This will replace all external login tokens for the user.
///
void Save(Guid userOrMemberKey, IEnumerable tokens);
///
- /// Deletes all user logins - normally used when a member is deleted
+ /// Deletes all user logins - normally used when a member is deleted.
///
void DeleteUserLogins(Guid userOrMemberKey);
+
+ ///
+ /// Deletes external logins and invalidates sessions for users that aren't associated with the current collection of providers.
+ ///
+ /// The names of the currently configured providers.
+ void PurgeLoginsForRemovedProviders(IEnumerable currentLoginProviders) { }
}
diff --git a/src/Umbraco.Core/Services/IIndexedEntitySearchService.cs b/src/Umbraco.Core/Services/IIndexedEntitySearchService.cs
index 06ff99c35c..4063c6ad87 100644
--- a/src/Umbraco.Core/Services/IIndexedEntitySearchService.cs
+++ b/src/Umbraco.Core/Services/IIndexedEntitySearchService.cs
@@ -35,7 +35,7 @@ public interface IIndexedEntitySearchService
int skip = 0,
int take = 100,
bool ignoreUserStartNodes = false)
- => Search(objectType,query, skip, take, ignoreUserStartNodes);
+ => Search(objectType, query, skip, take, ignoreUserStartNodes);
Task> SearchAsync(
UmbracoObjectTypes objectType,
@@ -43,6 +43,7 @@ public interface IIndexedEntitySearchService
Guid? parentId,
IEnumerable? contentTypeIds,
bool? trashed,
+ string? culture = null,
int skip = 0,
int take = 100,
bool ignoreUserStartNodes = false);
diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs
index a84d7de07d..c44095a40f 100644
--- a/src/Umbraco.Core/Services/IMemberService.cs
+++ b/src/Umbraco.Core/Services/IMemberService.cs
@@ -427,4 +427,11 @@ public interface IMemberService : IMembershipMemberService, IContentServiceBase<
///
///
IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact);
+
+ ///
+ /// Saves only the properties related to login for the member, using an optimized, non-locking update.
+ ///
+ /// The member to update.
+ /// Used to avoid the full save of the member object after a login operation.
+ Task UpdateLoginPropertiesAsync(IMember member) => Task.CompletedTask;
}
diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs
index c98ba53267..f2ee90696b 100644
--- a/src/Umbraco.Core/Services/MediaService.cs
+++ b/src/Umbraco.Core/Services/MediaService.cs
@@ -421,7 +421,7 @@ namespace Umbraco.Cms.Core.Services
}
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
- scope.ReadLock(Constants.Locks.ContentTree);
+ scope.ReadLock(Constants.Locks.MediaTree);
return _mediaRepository.GetPage(Query()?.Where(x => x.ContentTypeId == contentTypeId), pageIndex, pageSize, out totalRecords, filter, ordering);
}
@@ -444,7 +444,7 @@ namespace Umbraco.Cms.Core.Services
}
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
- scope.ReadLock(Constants.Locks.ContentTree);
+ scope.ReadLock(Constants.Locks.MediaTree);
return _mediaRepository.GetPage(
Query()?.Where(x => contentTypeIds.Contains(x.ContentTypeId)), pageIndex, pageSize, out totalRecords, filter, ordering);
}
diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs
index 86efb2abfd..c2499a9efe 100644
--- a/src/Umbraco.Core/Services/MemberService.cs
+++ b/src/Umbraco.Core/Services/MemberService.cs
@@ -854,6 +854,48 @@ namespace Umbraco.Cms.Core.Services
public void Save(IEnumerable members)
=> Save(members, Constants.Security.SuperUserId);
+ ///
+ ///
+ ///
+ /// Note that in this optimized member save operation for use in the login process, where we only handle login related
+ /// properties, we aren't taking any locks. If we were updating "content" properties, that could have relations between each
+ /// other, we should following what we do for documents and lock.
+ /// But here we are just updating these system fields, and it's fine if they work in a "last one wins" fashion without locking.
+ ///
+ ///
+ /// Note also that we aren't calling "Audit" here (as well as to optimize performance, this is deliberate, because this is not
+ /// a full save operation on the member that we'd want to audit who made the changes via the backoffice or API; rather it's
+ /// just the member logging in as themselves).
+ ///
+ ///
+ /// We are though publishing notifications, to maintain backwards compatibility for any solutions using these for
+ /// processing following a member login.
+ ///
+ ///
+ /// These notification handlers will ensure that the records to umbracoLog are also added in the same way as they
+ /// are for a full save operation.
+ ///
+ ///
+ public async Task UpdateLoginPropertiesAsync(IMember member)
+ {
+ EventMessages evtMsgs = EventMessagesFactory.Get();
+
+ using ICoreScope scope = ScopeProvider.CreateCoreScope();
+ var savingNotification = new MemberSavingNotification(member, evtMsgs);
+ savingNotification.State.Add("LoginPropertiesOnly", true);
+ if (scope.Notifications.PublishCancelable(savingNotification))
+ {
+ scope.Complete();
+ return;
+ }
+
+ await _memberRepository.UpdateLoginPropertiesAsync(member);
+
+ scope.Notifications.Publish(new MemberSavedNotification(member, evtMsgs).WithStateFrom(savingNotification));
+
+ scope.Complete();
+ }
+
#endregion
#region Delete
diff --git a/src/Umbraco.Core/Templates/HtmlImageSourceParser.cs b/src/Umbraco.Core/Templates/HtmlImageSourceParser.cs
index aa0e9a09bf..83098ed5ab 100644
--- a/src/Umbraco.Core/Templates/HtmlImageSourceParser.cs
+++ b/src/Umbraco.Core/Templates/HtmlImageSourceParser.cs
@@ -7,7 +7,11 @@ namespace Umbraco.Cms.Core.Templates;
public sealed class HtmlImageSourceParser
{
private static readonly Regex ResolveImgPattern = new(
- @"( ]*src="")([^""\?]*)((?:\?[^""]*)?""[^>]*data-udi="")([^""]*)(""[^>]*>)",
+ @" ]*(data-udi=""([^""]*)"")[^>]*>",
+ RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
+
+ private static readonly Regex SrcAttributeRegex = new(
+ @"src=""([^""\?]*)(\?[^""]*)?""",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
private static readonly Regex DataUdiAttributeRegex = new(
@@ -61,17 +65,26 @@ public sealed class HtmlImageSourceParser
return ResolveImgPattern.Replace(text, match =>
{
// match groups:
- // - 1 = from the beginning of the image tag until src attribute value begins
- // - 2 = the src attribute value excluding the querystring (if present)
- // - 3 = anything after group 2 and before the data-udi attribute value begins
- // - 4 = the data-udi attribute value
- // - 5 = anything after group 4 until the image tag is closed
- var udi = match.Groups[4].Value;
+ // - 1 = the data-udi attribute
+ // - 2 = the data-udi attribute value
+ var udi = match.Groups[2].Value;
if (udi.IsNullOrWhiteSpace() || UdiParser.TryParse(udi, out GuidUdi? guidUdi) == false)
{
return match.Value;
}
+ // Find the src attribute
+ // src match groups:
+ // - 1 = the src attribute value until the query string
+ // - 2 = the src attribute query string including the '?'
+ Match src = SrcAttributeRegex.Match(match.Value);
+
+ if (src.Success == false)
+ {
+ // the src attribute isn't found, return the original value
+ return match.Value;
+ }
+
var mediaUrl = _getMediaUrl(guidUdi.Guid);
if (mediaUrl == null)
{
@@ -80,7 +93,9 @@ public sealed class HtmlImageSourceParser
return match.Value;
}
- return $"{match.Groups[1].Value}{mediaUrl}{match.Groups[3].Value}{udi}{match.Groups[5].Value}";
+ var newImgTag = match.Value.Replace(src.Value, $"src=\"{mediaUrl}{src.Groups[2].Value}\"");
+
+ return newImgTag;
});
}
@@ -91,6 +106,16 @@ public sealed class HtmlImageSourceParser
///
public string RemoveImageSources(string text)
- // see comment in ResolveMediaFromTextString for group reference
- => ResolveImgPattern.Replace(text, "$1$3$4$5");
+ // find each ResolveImgPattern match in the text, then find each
+ // SrcAttributeRegex match in the match value, then replace the src
+ // attribute value with an empty string
+ // (see comment in ResolveMediaFromTextString for group reference)
+ => ResolveImgPattern.Replace(text, match =>
+ {
+ // Find the src attribute
+ Match src = SrcAttributeRegex.Match(match.Value);
+
+ return src.Success == false || string.IsNullOrWhiteSpace(src.Groups[1].Value) ?
+ match.Value : match.Value.Replace(src.Groups[1].Value, string.Empty);
+ });
}
diff --git a/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs
index 9494ed2eea..0ac9f89b79 100644
--- a/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs
+++ b/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs
@@ -24,8 +24,6 @@ public class DefaultRepositoryCachePolicy : RepositoryCachePolicyB
private static readonly TEntity[] _emptyEntities = new TEntity[0]; // const
private readonly RepositoryCachePolicyOptions _options;
- private const string NullRepresentationInCache = "*NULL*";
-
public DefaultRepositoryCachePolicy(IAppPolicyCache cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options)
: base(cache, scopeAccessor) =>
_options = options ?? throw new ArgumentNullException(nameof(options));
@@ -139,10 +137,8 @@ public class DefaultRepositoryCachePolicy : RepositoryCachePolicyB
return fromCache;
}
- // Because TEntity can never be a string, we will never be in a position where the proxy value collides withs a real value.
- // Therefore this point can only be reached if there is a proxy null value => becomes null when cast to TEntity above OR the item simply does not exist.
// If we've cached a "null" value, return null.
- if (_options.CacheNullValues && Cache.GetCacheItem(cacheKey) == NullRepresentationInCache)
+ if (_options.CacheNullValues && Cache.GetCacheItem(cacheKey) == Constants.Cache.NullRepresentationInCache)
{
return null;
}
@@ -273,7 +269,7 @@ public class DefaultRepositoryCachePolicy : RepositoryCachePolicyB
// a value that does exist but isn't yet cached, or a value that has been explicitly cached with a null value.
// Both would return null when we retrieve from the cache and we couldn't distinguish between the two.
// So we cache a special value that represents null, and then we can check for that value when we retrieve from the cache.
- Cache.Insert(cacheKey, () => NullRepresentationInCache, TimeSpan.FromMinutes(5), true);
+ Cache.Insert(cacheKey, () => Constants.Cache.NullRepresentationInCache, TimeSpan.FromMinutes(5), true);
}
protected virtual void InsertEntities(TId[]? ids, TEntity[]? entities)
diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs
index 54dc6b0b85..b54976ff40 100644
--- a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs
+++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs
@@ -98,9 +98,9 @@ internal sealed class ApiRichTextElementParser : ApiRichTextParserBase, IApiRich
// - non-#comment nodes
// - non-#text nodes
// - non-empty #text nodes
- // - empty #text between inline elements (see #17037)
+ // - empty #text between inline elements (see #17037) but not #text with only newlines (see #19388)
HtmlNode[] childNodes = element.ChildNodes
- .Where(c => c.Name != CommentNodeName && (c.Name != TextNodeName || c.NextSibling is not null || string.IsNullOrWhiteSpace(c.InnerText) is false))
+ .Where(c => c.Name != CommentNodeName && (c.Name != TextNodeName || IsNonEmptyElement(c)))
.ToArray();
var tag = TagName(element);
@@ -121,6 +121,9 @@ internal sealed class ApiRichTextElementParser : ApiRichTextParserBase, IApiRich
return createElement(tag, attributes, childElements);
}
+ private static bool IsNonEmptyElement(HtmlNode htmlNode) =>
+ string.IsNullOrWhiteSpace(htmlNode.InnerText) is false || htmlNode.InnerText.Any(c => c != '\n' && c != '\r');
+
private static string TagName(HtmlNode htmlNode) => htmlNode.Name;
private void ReplaceLocalLinks(IPublishedContentCache contentCache, IPublishedMediaCache mediaCache, Dictionary attributes)
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
index 0d7ebc0d50..f593bab5c2 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
@@ -248,6 +248,8 @@ public static partial class UmbracoBuilderExtensions
builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
+
return builder;
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs
index 2c0f0f8c2d..766d654c08 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs
@@ -56,6 +56,12 @@ internal class ExternalLoginRepository : EntityRepositoryBase
Database.Delete("WHERE userOrMemberKey=@userOrMemberKey", new { userOrMemberKey });
+ ///
+ public void DeleteUserLoginsForRemovedProviders(IEnumerable currentLoginProviders) =>
+ Database.Execute(Sql()
+ .Delete()
+ .WhereNotIn(x => x.LoginProvider, currentLoginProviders));
+
///
public void Save(Guid userOrMemberKey, IEnumerable logins)
{
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs
index 9adc60426f..beff74a5e3 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs
@@ -947,5 +947,50 @@ public class MemberRepository : ContentRepositoryBase
+ public async Task UpdateLoginPropertiesAsync(IMember member)
+ {
+ var updatedLastLoginDate = member.IsPropertyDirty(nameof(member.LastLoginDate));
+ var updatedSecurityStamp = member.IsPropertyDirty(nameof(member.SecurityStamp));
+ if (updatedLastLoginDate is false && updatedSecurityStamp is false)
+ {
+ return;
+ }
+
+ NPocoSqlExtensions.SqlUpd GetMemberSetExpression(IMember member, NPocoSqlExtensions.SqlUpd m)
+ {
+ var setExpression = new NPocoSqlExtensions.SqlUpd(SqlContext);
+ if (updatedLastLoginDate)
+ {
+ setExpression.Set(x => x.LastLoginDate, member.LastLoginDate);
+ }
+
+ if (updatedSecurityStamp)
+ {
+ setExpression.Set(x => x.SecurityStampToken, member.SecurityStamp);
+ }
+
+ return setExpression;
+ }
+
+ member.UpdatingEntity();
+
+ Sql updateMemberQuery = Sql()
+ .Update(m => GetMemberSetExpression(member, m))
+ .Where(m => m.NodeId == member.Id);
+ await Database.ExecuteAsync(updateMemberQuery);
+
+ Sql updateContentVersionQuery = Sql()
+ .Update(m => m.Set(x => x.VersionDate, member.UpdateDate))
+ .Where(m => m.NodeId == member.Id && m.Current == true);
+ await Database.ExecuteAsync(updateContentVersionQuery);
+
+ OnUowRefreshedEntity(new MemberRefreshNotification(member, new EventMessages()));
+
+ _memberByUsernameCachePolicy.DeleteByUserName(CacheKeys.MemberUserNameCachePrefix, member.Username);
+
+ member.ResetDirtyProperties();
+ }
+
#endregion
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs
index af28b8325c..d0d688d332 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs
@@ -128,10 +128,13 @@ internal class TagRepository : EntityRepositoryBase, ITagRepository
var group = SqlSyntax.GetQuotedColumnName("group");
// insert tags
+ // - Note we are checking in the subquery for the existence of the tag, so we don't insert duplicates, using a case-insensitive comparison (the
+ // LOWER keyword is consistent across SQLite and SQLServer). This ensures consistent behavior across databases as by default, SQLServer will
+ // perform a case-insensitive comparison, while SQLite will not.
var sql1 = $@"INSERT INTO cmsTags (tag, {group}, languageId)
SELECT tagSet.tag, tagSet.{group}, tagSet.languageId
FROM {tagSetSql}
-LEFT OUTER JOIN cmsTags ON (tagSet.tag = cmsTags.tag AND tagSet.{group} = cmsTags.{group} AND COALESCE(tagSet.languageId, -1) = COALESCE(cmsTags.languageId, -1))
+LEFT OUTER JOIN cmsTags ON (LOWER(tagSet.tag) = LOWER(cmsTags.tag) AND LOWER(tagSet.{group}) = LOWER(cmsTags.{group}) AND COALESCE(tagSet.languageId, -1) = COALESCE(cmsTags.languageId, -1))
WHERE cmsTags.id IS NULL";
Database.Execute(sql1);
@@ -142,7 +145,7 @@ SELECT {contentId}, {propertyTypeId}, tagSet2.Id
FROM (
SELECT t.Id
FROM {tagSetSql}
- INNER JOIN cmsTags as t ON (tagSet.tag = t.tag AND tagSet.{group} = t.{group} AND COALESCE(tagSet.languageId, -1) = COALESCE(t.languageId, -1))
+ INNER JOIN cmsTags as t ON (LOWER(tagSet.tag) = LOWER(t.tag) AND LOWER(tagSet.{group}) = LOWER(t.{group}) AND COALESCE(tagSet.languageId, -1) = COALESCE(t.languageId, -1))
) AS tagSet2
LEFT OUTER JOIN cmsTagRelationship r ON (tagSet2.id = r.tagId AND r.nodeId = {contentId} AND r.propertyTypeID = {propertyTypeId})
WHERE r.tagId IS NULL";
@@ -246,14 +249,18 @@ WHERE r.tagId IS NULL";
{
public bool Equals(ITag? x, ITag? y) =>
ReferenceEquals(x, y) // takes care of both being null
- || (x != null && y != null && x.Text == y.Text && x.Group == y.Group && x.LanguageId == y.LanguageId);
+ || (x != null &&
+ y != null &&
+ string.Equals(x.Text, y.Text, StringComparison.OrdinalIgnoreCase) &&
+ string.Equals(x.Group, y.Group, StringComparison.OrdinalIgnoreCase) &&
+ x.LanguageId == y.LanguageId);
public int GetHashCode(ITag obj)
{
unchecked
{
- var h = obj.Text.GetHashCode();
- h = (h * 397) ^ obj.Group.GetHashCode();
+ var h = StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Text);
+ h = (h * 397) ^ StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Group);
h = (h * 397) ^ (obj.LanguageId?.GetHashCode() ?? 0);
return h;
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs
index a1d5a41bea..187d5c5ce2 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs
@@ -12,7 +12,6 @@ using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Persistence.Querying;
using Umbraco.Cms.Core.Persistence.Repositories;
-using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
@@ -21,7 +20,6 @@ using Umbraco.Cms.Infrastructure.Persistence.Mappers;
using Umbraco.Cms.Infrastructure.Persistence.Querying;
using Umbraco.Cms.Infrastructure.Scoping;
using Umbraco.Extensions;
-using IScope = Umbraco.Cms.Infrastructure.Scoping.IScope;
namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
///
@@ -1268,5 +1266,32 @@ SELECT 4 AS [Key], COUNT(id) AS [Value] FROM umbracoUser WHERE userDisabled = 0
return sql;
}
+
+ ///
+ public void InvalidateSessionsForRemovedProviders(IEnumerable currentLoginProviders)
+ {
+ // Get all the user or member keys associated with the removed providers.
+ Sql idsQuery = SqlContext.Sql()
+ .Select(x => x.UserOrMemberKey)
+ .From()
+ .WhereNotIn(x => x.LoginProvider, currentLoginProviders);
+ List userAndMemberKeysAssociatedWithRemovedProviders = Database.Fetch(idsQuery);
+ if (userAndMemberKeysAssociatedWithRemovedProviders.Count == 0)
+ {
+ return;
+ }
+
+ // Invalidate the security stamps on the users associated with the removed providers.
+ Sql updateSecurityStampsQuery = Sql()
+ .Update(u => u.Set(x => x.SecurityStampToken, "0".PadLeft(32, '0')))
+ .WhereIn(x => x.Key, userAndMemberKeysAssociatedWithRemovedProviders);
+ Database.Execute(updateSecurityStampsQuery);
+
+ // Delete the OpenIddict tokens for the users associated with the removed providers.
+ // The following is safe from SQL injection as we are dealing with GUIDs, not strings.
+ var userKeysForInClause = string.Join("','", userAndMemberKeysAssociatedWithRemovedProviders.Select(x => x.ToString()));
+ Database.Execute("DELETE FROM umbracoOpenIddictTokens WHERE Subject IN ('" + userKeysForInClause + "')");
+ }
+
#endregion
}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs
index a8fa01de69..3777d84eb4 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs
@@ -71,17 +71,17 @@ public abstract class BlockValuePropertyValueEditorBase : DataV
continue;
}
- var districtValues = valuesByPropertyEditorAlias.Distinct().ToArray();
+ var distinctValues = valuesByPropertyEditorAlias.Distinct().ToArray();
if (dataEditor.GetValueEditor() is IDataValueReference reference)
{
- foreach (UmbracoEntityReference value in districtValues.SelectMany(reference.GetReferences))
+ foreach (UmbracoEntityReference value in distinctValues.SelectMany(reference.GetReferences))
{
result.Add(value);
}
}
- IEnumerable references = _dataValueReferenceFactoryCollection.GetReferences(dataEditor, districtValues);
+ IEnumerable references = _dataValueReferenceFactoryCollection.GetReferences(dataEditor, distinctValues);
foreach (UmbracoEntityReference value in references)
{
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs
index 92c0a05e94..7d75725ef6 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs
@@ -93,6 +93,7 @@ public class RichTextPropertyEditor : DataEditor
private readonly RichTextEditorPastedImages _pastedImages;
private readonly IJsonSerializer _jsonSerializer;
private readonly IRichTextRequiredValidator _richTextRequiredValidator;
+ private readonly IRichTextRegexValidator _richTextRegexValidator;
private readonly ILogger _logger;
public RichTextPropertyValueEditor(
@@ -111,6 +112,7 @@ public class RichTextPropertyEditor : DataEditor
IPropertyValidationService propertyValidationService,
DataValueReferenceFactoryCollection dataValueReferenceFactoryCollection,
IRichTextRequiredValidator richTextRequiredValidator,
+ IRichTextRegexValidator richTextRegexValidator,
BlockEditorVarianceHandler blockEditorVarianceHandler,
ILanguageService languageService,
IIOHelper ioHelper)
@@ -122,6 +124,7 @@ public class RichTextPropertyEditor : DataEditor
_pastedImages = pastedImages;
_htmlSanitizer = htmlSanitizer;
_richTextRequiredValidator = richTextRequiredValidator;
+ _richTextRegexValidator = richTextRegexValidator;
_jsonSerializer = jsonSerializer;
_logger = logger;
@@ -131,6 +134,8 @@ public class RichTextPropertyEditor : DataEditor
public override IValueRequiredValidator RequiredValidator => _richTextRequiredValidator;
+ public override IValueFormatValidator FormatValidator => _richTextRegexValidator;
+
protected override RichTextBlockValue CreateWithLayout(IEnumerable layout) => new(layout);
///
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/Validators/IRichTextRegexValidator.cs b/src/Umbraco.Infrastructure/PropertyEditors/Validators/IRichTextRegexValidator.cs
new file mode 100644
index 0000000000..8b69554322
--- /dev/null
+++ b/src/Umbraco.Infrastructure/PropertyEditors/Validators/IRichTextRegexValidator.cs
@@ -0,0 +1,5 @@
+namespace Umbraco.Cms.Core.PropertyEditors.Validators;
+
+internal interface IRichTextRegexValidator : IValueFormatValidator
+{
+}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/Validators/RichTextRegexValidator.cs b/src/Umbraco.Infrastructure/PropertyEditors/Validators/RichTextRegexValidator.cs
new file mode 100644
index 0000000000..4e6a5c049e
--- /dev/null
+++ b/src/Umbraco.Infrastructure/PropertyEditors/Validators/RichTextRegexValidator.cs
@@ -0,0 +1,28 @@
+using System.ComponentModel.DataAnnotations;
+using Microsoft.Extensions.Logging;
+using Umbraco.Cms.Core.Serialization;
+
+namespace Umbraco.Cms.Core.PropertyEditors.Validators;
+
+internal class RichTextRegexValidator : IRichTextRegexValidator
+{
+ private readonly RegexValidator _regexValidator;
+ private readonly IJsonSerializer _jsonSerializer;
+ private readonly ILogger _logger;
+
+ public RichTextRegexValidator(
+ IJsonSerializer jsonSerializer,
+ ILogger logger)
+ {
+ _jsonSerializer = jsonSerializer;
+ _logger = logger;
+ _regexValidator = new RegexValidator();
+ }
+
+ public IEnumerable ValidateFormat(object? value, string? valueType, string format) => _regexValidator.ValidateFormat(GetValue(value), valueType, format);
+
+ private object? GetValue(object? value) =>
+ RichTextPropertyEditorHelper.TryParseRichTextEditorValue(value, _jsonSerializer, _logger, out RichTextEditorValue? richTextEditorValue)
+ ? richTextEditorValue?.Markup
+ : value;
+}
diff --git a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs
index b86458427f..a9c293b19c 100644
--- a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs
+++ b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs
@@ -162,7 +162,7 @@ public class MemberUserStore : UmbracoUserStore
- public override Task UpdateAsync(
+ public override async Task UpdateAsync(
MemberIdentityUser user,
CancellationToken cancellationToken = default)
{
@@ -190,9 +190,21 @@ public class MemberUserStore : UmbracoUserStore propertiesUpdated = UpdateMemberProperties(found, user, out var updateRoles);
+
+ if (propertiesUpdated.Count > 0)
{
- _memberService.Save(found);
+ // As part of logging in members we update the last login date, and, if concurrent logins are disabled, the security stamp.
+ // If and only if we are updating these properties, we can avoid the overhead of a full save of the member with the associated
+ // locking, property updates, tag handling etc., and make a more efficient update.
+ if (UpdatingOnlyLoginProperties(propertiesUpdated))
+ {
+ await _memberService.UpdateLoginPropertiesAsync(found);
+ }
+ else
+ {
+ _memberService.Save(found);
+ }
if (updateRoles)
{
@@ -223,15 +235,21 @@ public class MemberUserStore : UmbracoUserStore propertiesUpdated)
+ {
+ string[] loginPropertyUpdates = [nameof(MemberIdentityUser.LastLoginDateUtc), nameof(MemberIdentityUser.SecurityStamp)];
+ return (propertiesUpdated.Count == 2 && propertiesUpdated.ContainsAll(loginPropertyUpdates)) ||
+ (propertiesUpdated.Count == 1 && propertiesUpdated.ContainsAny(loginPropertyUpdates));
+ }
+
///
public override Task DeleteAsync(
MemberIdentityUser user,
@@ -704,9 +722,9 @@ public class MemberUserStore : UmbracoUserStore UpdateMemberProperties(IMember member, MemberIdentityUser identityUser, out bool updateRoles)
{
- var anythingChanged = false;
+ var updatedProperties = new List();
updateRoles = false;
// don't assign anything if nothing has changed as this will trigger the track changes of the model
@@ -715,7 +733,7 @@ public class MemberUserStore : UmbracoUserStore
diff --git a/src/Umbraco.Infrastructure/Serialization/JsonBlockValueConverter.cs b/src/Umbraco.Infrastructure/Serialization/JsonBlockValueConverter.cs
index 6716dfae71..71624b6529 100644
--- a/src/Umbraco.Infrastructure/Serialization/JsonBlockValueConverter.cs
+++ b/src/Umbraco.Infrastructure/Serialization/JsonBlockValueConverter.cs
@@ -189,9 +189,36 @@ public class JsonBlockValueConverter : JsonConverter
else
{
// ignore this layout - forward the reader to the end of the array and look for the next one
- while (reader.TokenType is not JsonTokenType.EndArray)
+
+ // Read past the current StartArray token before we start counting
+ _ = reader.Read();
+
+ // Keep track of the number of open arrays to ensure we find the correct EndArray token
+ var openCount = 0;
+ while (true)
{
- reader.Read();
+ if (reader.TokenType is JsonTokenType.EndArray && openCount == 0)
+ {
+ break;
+ }
+
+ if (reader.TokenType is JsonTokenType.StartArray)
+ {
+ openCount++;
+ }
+ else if (reader.TokenType is JsonTokenType.EndArray)
+ {
+ openCount--;
+ if (openCount < 0)
+ {
+ throw new JsonException($"Malformed JSON: Encountered more closing array tokens than opening ones while processing block editor alias: {blockEditorAlias}.");
+ }
+ }
+
+ if (!reader.Read())
+ {
+ throw new JsonException($"Unexpected end of JSON while looking for the end of the layout items array for block editor alias: {blockEditorAlias}.");
+ }
}
}
}
diff --git a/src/Umbraco.Infrastructure/Services/Implement/IndexedEntitySearchService.cs b/src/Umbraco.Infrastructure/Services/Implement/IndexedEntitySearchService.cs
index 2febcc68c0..c72771ba67 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/IndexedEntitySearchService.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/IndexedEntitySearchService.cs
@@ -73,7 +73,7 @@ internal sealed class IndexedEntitySearchService : IIndexedEntitySearchService
int skip = 0,
int take = 100,
bool ignoreUserStartNodes = false)
- => SearchAsync(objectType, query, parentId, contentTypeIds, trashed, skip, take, ignoreUserStartNodes).GetAwaiter().GetResult();
+ => SearchAsync(objectType, query, parentId, contentTypeIds, trashed, null, skip, take, ignoreUserStartNodes).GetAwaiter().GetResult();
public Task> SearchAsync(
UmbracoObjectTypes objectType,
@@ -81,6 +81,7 @@ internal sealed class IndexedEntitySearchService : IIndexedEntitySearchService
Guid? parentId,
IEnumerable? contentTypeIds,
bool? trashed,
+ string? culture = null,
int skip = 0,
int take = 100,
bool ignoreUserStartNodes = false)
diff --git a/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs b/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs
index cd82fa6c8b..3d46df8f01 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs
@@ -326,8 +326,9 @@ public class PackagingService : IPackagingService
PackageName = group.Key.PackageName,
};
+ var packageKey = Constants.Conventions.Migrations.KeyValuePrefix + (group.Key.PackageId ?? group.Key.PackageName);
var currentState = keyValues?
- .GetValueOrDefault(Constants.Conventions.Migrations.KeyValuePrefix + group.Key.PackageId);
+ .GetValueOrDefault(packageKey);
package.PackageMigrationPlans = group
.Select(plan => new InstalledPackageMigrationPlans
diff --git a/src/Umbraco.Web.Common/Security/ConfigureSecurityStampOptions.cs b/src/Umbraco.Web.Common/Security/ConfigureSecurityStampOptions.cs
index e214ba1c23..01f7d7cfb9 100644
--- a/src/Umbraco.Web.Common/Security/ConfigureSecurityStampOptions.cs
+++ b/src/Umbraco.Web.Common/Security/ConfigureSecurityStampOptions.cs
@@ -31,7 +31,7 @@ public class ConfigureSecurityStampOptions : IConfigureOptions {
+ // Use glob to find the workspace path
+ const localWorkspace = workspaceGlob.replace(/\.\/src/, './dist-cms');
+ const workspacePaths = await glob(localWorkspace, { cwd: './', absolute: true });
+
+ workspacePaths.forEach(workspace => {
+ const workspacePackageFile = join(workspace, 'package.json');
+
+ // Ensure the workspace package.json exists
+ if (!existsSync(workspacePackageFile)) {
+ // If the package.json does not exist, log a warning and continue
+ console.warn(`No package.json found in workspace: ${workspace}`);
+ return;
+ }
+
+ const workspacePackageJson = JSON.parse(readFileSync(workspacePackageFile, 'utf8'));
+
+ // Move dependencies from the workspace to the root package.json
+ if (workspacePackageJson.dependencies) {
+ Object.entries(workspacePackageJson.dependencies).forEach(([key, value]) => {
+ console.log('Hoisting dependency:', key, 'from workspace:', workspace, 'with version:', value);
+ packageJson.peerDependencies[key] = value;
+ });
+ }
+ })
+});
+
+// Wait for all workspace processing to complete
+await Promise.all(workspacePromises);
+
+// Remove the workspaces field from the root package.json
+delete packageJson.workspaces;
+
// Write the package.json back to disk
writeFileSync(packageFile, JSON.stringify(packageJson, null, 2), 'utf8');
diff --git a/src/Umbraco.Web.UI.Client/examples/block-custom-view/block-custom-view.ts b/src/Umbraco.Web.UI.Client/examples/block-custom-view/block-custom-view.ts
index 8f0e4a11e7..5da08329ab 100644
--- a/src/Umbraco.Web.UI.Client/examples/block-custom-view/block-custom-view.ts
+++ b/src/Umbraco.Web.UI.Client/examples/block-custom-view/block-custom-view.ts
@@ -29,7 +29,9 @@ export class ExampleBlockCustomView extends UmbElementMixin(LitElement) implemen
UmbTextStyles,
css`
:host {
+ position: relative;
display: block;
+ z-index: 10000;
height: 100%;
box-sizing: border-box;
background-color: red;
@@ -38,6 +40,12 @@ export class ExampleBlockCustomView extends UmbElementMixin(LitElement) implemen
padding: 12px;
}
+ :host > div {
+ position: relative;
+ display: block;
+ z-index: 10000;
+ }
+
.align-center {
text-align: center;
}
diff --git a/src/Umbraco.Web.UI.Client/examples/modal-routed/dashboard.element.ts b/src/Umbraco.Web.UI.Client/examples/modal-routed/dashboard.element.ts
index 967a4a7a88..61c0f6862f 100644
--- a/src/Umbraco.Web.UI.Client/examples/modal-routed/dashboard.element.ts
+++ b/src/Umbraco.Web.UI.Client/examples/modal-routed/dashboard.element.ts
@@ -17,6 +17,7 @@ export class UmbDashboardElement extends UmbElementMixin(LitElement) {
},
{
path: '',
+ pathMatch: 'full',
redirectTo: 'tab1',
},
];
diff --git a/src/Umbraco.Web.UI.Client/examples/modal-routed/modal/example-modal.element.ts b/src/Umbraco.Web.UI.Client/examples/modal-routed/modal/example-modal.element.ts
index e1f0f5bdd4..45c0229e37 100644
--- a/src/Umbraco.Web.UI.Client/examples/modal-routed/modal/example-modal.element.ts
+++ b/src/Umbraco.Web.UI.Client/examples/modal-routed/modal/example-modal.element.ts
@@ -17,6 +17,7 @@ export class UmbExampleModal extends UmbModalBaseElement {
},
{
path: '',
+ pathMatch: 'full',
redirectTo: 'modalOverview',
},
];
diff --git a/src/Umbraco.Web.UI.Client/examples/validation-context/validation-context-dashboard.ts b/src/Umbraco.Web.UI.Client/examples/validation-context/validation-context-dashboard.ts
index cf50893f30..2d49d0e9c2 100644
--- a/src/Umbraco.Web.UI.Client/examples/validation-context/validation-context-dashboard.ts
+++ b/src/Umbraco.Web.UI.Client/examples/validation-context/validation-context-dashboard.ts
@@ -45,21 +45,21 @@ export class UmbExampleValidationContextDashboardElement extends UmbLitElement {
},
'observeValidationMessages',
);
+ });
- // Observe all errors
- this.validation.messages.messagesOfPathAndDescendant('$.form').subscribe((value) => {
- this.totalErrorCount = [...new Set(value.map((x) => x.path))].length;
- });
+ // Observe all errors
+ this.observe(this.validation.messages.messagesOfPathAndDescendant('$.form'), (value) => {
+ this.totalErrorCount = [...new Set(value.map((x) => x.path))].length;
+ });
- // Observe errors for tab1, note that we only use part of the full JSONPath
- this.validation.messages.messagesOfPathAndDescendant('$.form.tab1').subscribe((value) => {
- this.tab1ErrorCount = [...new Set(value.map((x) => x.path))].length;
- });
+ // Observe errors for tab1, note that we only use part of the full JSONPath
+ this.observe(this.validation.messages.messagesOfPathAndDescendant('$.form.tab1'), (value) => {
+ this.tab1ErrorCount = [...new Set(value.map((x) => x.path))].length;
+ });
- // Observe errors for tab2, note that we only use part of the full JSONPath
- this.validation.messages.messagesOfPathAndDescendant('$.form.tab2').subscribe((value) => {
- this.tab2ErrorCount = [...new Set(value.map((x) => x.path))].length;
- });
+ // Observe errors for tab2, note that we only use part of the full JSONPath
+ this.observe(this.validation.messages.messagesOfPathAndDescendant('$.form.tab2'), (value) => {
+ this.tab2ErrorCount = [...new Set(value.map((x) => x.path))].length;
});
}
diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json
index ba050ebcdf..d7a17cb69c 100644
--- a/src/Umbraco.Web.UI.Client/package-lock.json
+++ b/src/Umbraco.Web.UI.Client/package-lock.json
@@ -27,8 +27,8 @@
"@tiptap/extension-underline": "2.11.7",
"@tiptap/pm": "2.11.7",
"@tiptap/starter-kit": "2.11.7",
- "@umbraco-ui/uui": "^1.13.0",
- "@umbraco-ui/uui-css": "^1.13.0",
+ "@umbraco-ui/uui": "1.14.0-rc.4",
+ "@umbraco-ui/uui-css": "1.14.0-rc.4",
"dompurify": "^3.2.5",
"element-internals-polyfill": "^3.0.2",
"lit": "^3.3.0",
@@ -1043,7 +1043,6 @@
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.10.0.tgz",
"integrity": "sha512-C7vzj4t52qPiHCqjn1l8cRTI2p4pZCd7ViLtJDTHr5ZwI4sWOYC1tmv6bd529qqY6HFFbhGCz4TAZSwKAMJncg==",
- "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/hey-api"
@@ -4468,6 +4467,14 @@
"resolved": "src/packages/search",
"link": true
},
+ "node_modules/@umbraco-backoffice/segment": {
+ "resolved": "src/packages/segment",
+ "link": true
+ },
+ "node_modules/@umbraco-backoffice/settings": {
+ "resolved": "src/packages/settings",
+ "link": true
+ },
"node_modules/@umbraco-backoffice/static-file": {
"resolved": "src/packages/static-file",
"link": true
@@ -4492,6 +4499,10 @@
"resolved": "src/packages/tiptap",
"link": true
},
+ "node_modules/@umbraco-backoffice/translation": {
+ "resolved": "src/packages/translation",
+ "link": true
+ },
"node_modules/@umbraco-backoffice/ufm": {
"resolved": "src/packages/ufm",
"link": true
@@ -4509,824 +4520,908 @@
"link": true
},
"node_modules/@umbraco-ui/uui": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.13.0.tgz",
- "integrity": "sha512-O/RvFeW+Mjn24ckmWJeTzMZKYbVrnaHscl9zKGKkMSva3j3mnJs/Q9N6BfihQy3qdZP5ED+2lGomezxfoLjZ7g==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.14.0-rc.4.tgz",
+ "integrity": "sha512-oC0tDbzcfCsoc1Hb5yHI2wui5/FA7yLNNIxPBP2yAtek1GIWDSIj3cEY08SNKLOv49y052tl3bH/QZr8hrji2w==",
+ "license": "MIT",
"dependencies": {
- "@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"
+ "@umbraco-ui/uui-action-bar": "1.14.0-rc.4",
+ "@umbraco-ui/uui-avatar": "1.14.0-rc.4",
+ "@umbraco-ui/uui-avatar-group": "1.14.0-rc.4",
+ "@umbraco-ui/uui-badge": "1.14.0-rc.4",
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-boolean-input": "1.14.0-rc.4",
+ "@umbraco-ui/uui-box": "1.14.0-rc.4",
+ "@umbraco-ui/uui-breadcrumbs": "1.14.0-rc.4",
+ "@umbraco-ui/uui-button": "1.14.0-rc.4",
+ "@umbraco-ui/uui-button-copy-text": "1.14.0-rc.4",
+ "@umbraco-ui/uui-button-group": "1.14.0-rc.4",
+ "@umbraco-ui/uui-button-inline-create": "1.14.0-rc.4",
+ "@umbraco-ui/uui-card": "1.14.0-rc.4",
+ "@umbraco-ui/uui-card-block-type": "1.14.0-rc.4",
+ "@umbraco-ui/uui-card-content-node": "1.14.0-rc.4",
+ "@umbraco-ui/uui-card-media": "1.14.0-rc.4",
+ "@umbraco-ui/uui-card-user": "1.14.0-rc.4",
+ "@umbraco-ui/uui-caret": "1.14.0-rc.4",
+ "@umbraco-ui/uui-checkbox": "1.14.0-rc.4",
+ "@umbraco-ui/uui-color-area": "1.14.0-rc.4",
+ "@umbraco-ui/uui-color-picker": "1.14.0-rc.4",
+ "@umbraco-ui/uui-color-slider": "1.14.0-rc.4",
+ "@umbraco-ui/uui-color-swatch": "1.14.0-rc.4",
+ "@umbraco-ui/uui-color-swatches": "1.14.0-rc.4",
+ "@umbraco-ui/uui-combobox": "1.14.0-rc.4",
+ "@umbraco-ui/uui-combobox-list": "1.14.0-rc.4",
+ "@umbraco-ui/uui-css": "1.14.0-rc.4",
+ "@umbraco-ui/uui-dialog": "1.14.0-rc.4",
+ "@umbraco-ui/uui-dialog-layout": "1.14.0-rc.4",
+ "@umbraco-ui/uui-file-dropzone": "1.14.0-rc.4",
+ "@umbraco-ui/uui-file-preview": "1.14.0-rc.4",
+ "@umbraco-ui/uui-form": "1.14.0-rc.4",
+ "@umbraco-ui/uui-form-layout-item": "1.14.0-rc.4",
+ "@umbraco-ui/uui-form-validation-message": "1.14.0-rc.4",
+ "@umbraco-ui/uui-icon": "1.14.0-rc.4",
+ "@umbraco-ui/uui-icon-registry": "1.14.0-rc.4",
+ "@umbraco-ui/uui-icon-registry-essential": "1.14.0-rc.4",
+ "@umbraco-ui/uui-input": "1.14.0-rc.4",
+ "@umbraco-ui/uui-input-file": "1.14.0-rc.4",
+ "@umbraco-ui/uui-input-lock": "1.14.0-rc.4",
+ "@umbraco-ui/uui-input-password": "1.14.0-rc.4",
+ "@umbraco-ui/uui-keyboard-shortcut": "1.14.0-rc.4",
+ "@umbraco-ui/uui-label": "1.14.0-rc.4",
+ "@umbraco-ui/uui-loader": "1.14.0-rc.4",
+ "@umbraco-ui/uui-loader-bar": "1.14.0-rc.4",
+ "@umbraco-ui/uui-loader-circle": "1.14.0-rc.4",
+ "@umbraco-ui/uui-menu-item": "1.14.0-rc.4",
+ "@umbraco-ui/uui-modal": "1.14.0-rc.4",
+ "@umbraco-ui/uui-pagination": "1.14.0-rc.4",
+ "@umbraco-ui/uui-popover": "1.14.0-rc.4",
+ "@umbraco-ui/uui-popover-container": "1.14.0-rc.4",
+ "@umbraco-ui/uui-progress-bar": "1.14.0-rc.4",
+ "@umbraco-ui/uui-radio": "1.14.0-rc.4",
+ "@umbraco-ui/uui-range-slider": "1.14.0-rc.4",
+ "@umbraco-ui/uui-ref": "1.14.0-rc.4",
+ "@umbraco-ui/uui-ref-list": "1.14.0-rc.4",
+ "@umbraco-ui/uui-ref-node": "1.14.0-rc.4",
+ "@umbraco-ui/uui-ref-node-data-type": "1.14.0-rc.4",
+ "@umbraco-ui/uui-ref-node-document-type": "1.14.0-rc.4",
+ "@umbraco-ui/uui-ref-node-form": "1.14.0-rc.4",
+ "@umbraco-ui/uui-ref-node-member": "1.14.0-rc.4",
+ "@umbraco-ui/uui-ref-node-package": "1.14.0-rc.4",
+ "@umbraco-ui/uui-ref-node-user": "1.14.0-rc.4",
+ "@umbraco-ui/uui-scroll-container": "1.14.0-rc.4",
+ "@umbraco-ui/uui-select": "1.14.0-rc.4",
+ "@umbraco-ui/uui-slider": "1.14.0-rc.4",
+ "@umbraco-ui/uui-symbol-expand": "1.14.0-rc.4",
+ "@umbraco-ui/uui-symbol-file": "1.14.0-rc.4",
+ "@umbraco-ui/uui-symbol-file-dropzone": "1.14.0-rc.4",
+ "@umbraco-ui/uui-symbol-file-thumbnail": "1.14.0-rc.4",
+ "@umbraco-ui/uui-symbol-folder": "1.14.0-rc.4",
+ "@umbraco-ui/uui-symbol-lock": "1.14.0-rc.4",
+ "@umbraco-ui/uui-symbol-more": "1.14.0-rc.4",
+ "@umbraco-ui/uui-symbol-sort": "1.14.0-rc.4",
+ "@umbraco-ui/uui-table": "1.14.0-rc.4",
+ "@umbraco-ui/uui-tabs": "1.14.0-rc.4",
+ "@umbraco-ui/uui-tag": "1.14.0-rc.4",
+ "@umbraco-ui/uui-textarea": "1.14.0-rc.4",
+ "@umbraco-ui/uui-toast-notification": "1.14.0-rc.4",
+ "@umbraco-ui/uui-toast-notification-container": "1.14.0-rc.4",
+ "@umbraco-ui/uui-toast-notification-layout": "1.14.0-rc.4",
+ "@umbraco-ui/uui-toggle": "1.14.0-rc.4",
+ "@umbraco-ui/uui-visually-hidden": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-action-bar": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.14.0-rc.4.tgz",
+ "integrity": "sha512-zESDURH6TbAPOs3yV7KtLJ1XbDq7EWUwFgZ7jn3thu7Ue0LepcV+BoqyaYZgisUCig/wkp3xc0HPddyZHn5uZA==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-button-group": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-button-group": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-avatar": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.13.0.tgz",
- "integrity": "sha512-w+DwB9PUcnR0y0CzeNQA2638PjF2Dswiyuoxa2ryggcy38ihypj0Fj8FpzRSe5rax2JMtpJnuoDPwUpqVwGfOQ==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.14.0-rc.4.tgz",
+ "integrity": "sha512-q7FYC/njV+w0al01Nsaw42KeLVsOKjjqtCnuB7GUGgCEbbXFXIUjrbeg62feyC6KRM1x+1qqqO+JIDWeGUFspA==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-avatar-group": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.14.0-rc.4.tgz",
+ "integrity": "sha512-HcuGGYvssq24qipPH1mn46E7QD1WyWL+GBNvlI2GmTrgxxcQKIDpqaiFOrLSM90mTLVBnZkhkoQ4Rf/Fb+OKEA==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-avatar": "1.13.0",
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-avatar": "1.14.0-rc.4",
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-badge": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.14.0-rc.4.tgz",
+ "integrity": "sha512-nVq4qvsZVIieq2qpNTgzrytkKNF4/3VM2qxgzOsyRC/u0bc+hpGtIenzQDUuwgcUC1kkLPii40dXTu6fVVCGHg==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-base": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.13.0.tgz",
- "integrity": "sha512-VG0xlVzuq74qKaWn+eaTcTblR6HCi9YyrNohLLhHVAJuelmcgoPwHdNzkjoaWXlq16XKnB5Kmb6BgEtVmSQZ5w==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.14.0-rc.4.tgz",
+ "integrity": "sha512-UXBJ1o3fdn/aGKY/Nn597EzTHxrPVsEg/gjcnRQi5u+NKEf38jXAHQ8HZsjDpfIgxnkFg2PztLQaZcapgEEOjA==",
+ "license": "MIT",
"peerDependencies": {
"lit": ">=2.8.0"
}
},
"node_modules/@umbraco-ui/uui-boolean-input": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.14.0-rc.4.tgz",
+ "integrity": "sha512-UBeRk80W77iojie9NQIu1TTT5p4oghU4Vf497y1vglsuXFkKTawH/0kYOzDuapJMyMraAtUqOA0hwugrFY1T/g==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-box": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.13.0.tgz",
- "integrity": "sha512-msIz5NerPKx7bnTyEyMjihnfxSTlslU+FyE4DsUUwZT6vtFxT2Dt74UbO8cg0ut9GoBjR1wpn4qNTW3xRxfdiA==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.14.0-rc.4.tgz",
+ "integrity": "sha512-u1XQYgG/UIrNPEhIjR7e8e8lLkp5M/Ao7zIgm3BleNkYHAQU2NQqb2bLowyjpT5GrufWrW2jmVO8pVJ66vyrxw==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-css": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-css": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-breadcrumbs": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.14.0-rc.4.tgz",
+ "integrity": "sha512-pt1ZA7XrpmXJctitb+UPhjdAqEQa5E/tDtaEQbW/wTK/iPC6dv6BnHdThlC69FvhddIng2PPgbXeBojjx9Pk6w==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-button": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.14.0-rc.4.tgz",
+ "integrity": "sha512-lmYxADppNZW+4Rg5XIqWFrMd8EQ65h+peCedx/tjJ4gERq4uTl5iw/INWLGWFqPhNgye8ZH3oSsHa4VUPSCJJg==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-icon-registry-essential": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-icon-registry-essential": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-button-copy-text": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-copy-text/-/uui-button-copy-text-1.14.0-rc.4.tgz",
+ "integrity": "sha512-+9GHDLSPpb8rRVDLZMwCg18I16m3eos2mg+f0dRbHTZOrYgeAkk+QsFdy7sTQVj2cgML0JXrDg8y5N1eihce1Q==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-button": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-button": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-button-group": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.13.0.tgz",
- "integrity": "sha512-Pksx35rtKriOUO9IP1ETnQDoBnoiRzwheM8fmqeo44jSPsr7emaQrI3BOwqeOuD7KfPRIVnzwLdm14K4Zw6tZA==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.14.0-rc.4.tgz",
+ "integrity": "sha512-Cb5faAo2ln3KFgQ1xA+l4KvdZJ2dQ6ThjUWUe9uYdhS/9w2IfUOzkIJsUCXCpzhUU0kSuJpo1AfX8z7XkrVUUw==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-button-inline-create": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.14.0-rc.4.tgz",
+ "integrity": "sha512-D5nXcswss/4IOzXbQF1dRsLH7hHwaEkVmiqZd21421ZrxGJ7XejeM3s4afe3AkVDi+wAyS4kl2e2K7/8lyiNHA==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-card": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.13.0.tgz",
- "integrity": "sha512-fBskLWqFoquKfgFK6bJ4lM0V30XZCZcJjjwTUmSjRFvklyF3csL7W9bKB9hs+aFu0/GDQlVqOBa5tA4RLpcj0w==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.14.0-rc.4.tgz",
+ "integrity": "sha512-57lXGDrFUc0uuS2G9csLpRhFJyJEt2SNvc38/RTlE8liDL1KrtZk5tbsx8MNlq48e01q80zn64hy4Wu4Rxox3w==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-card-block-type": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-block-type/-/uui-card-block-type-1.14.0-rc.4.tgz",
+ "integrity": "sha512-5GDFxbUiJrGaxvCjGAfGojtCc+t8wQFRxLPZnEKDuI5fFr87WvRBd84HOdXCZs9/6jR+N+ZvIWSkEysVwVlHYg==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-card": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-card": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-card-content-node": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.14.0-rc.4.tgz",
+ "integrity": "sha512-v7ujHGkDkk9/RwxFWtO0FGoVdR6kHGfvtZ05udl5hRVjcpR6u8Jij7vylSizOBF7kW4j7IboN+vgAbwlsJ0BYw==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-card": "1.13.0",
- "@umbraco-ui/uui-icon": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-card": "1.14.0-rc.4",
+ "@umbraco-ui/uui-icon": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-card-media": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.14.0-rc.4.tgz",
+ "integrity": "sha512-3aeWUeJgDDD+xahHiZdsdBzQTvuXyBDIlFPFa2vNDEPJ7VRU31E1FlZ3zr3B3MNZtB30HcuUKTilUsTkmX3D5g==",
+ "license": "MIT",
"dependencies": {
- "@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"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-card": "1.14.0-rc.4",
+ "@umbraco-ui/uui-symbol-file": "1.14.0-rc.4",
+ "@umbraco-ui/uui-symbol-folder": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-card-user": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.14.0-rc.4.tgz",
+ "integrity": "sha512-jf/gisfoE17A1aSyUUCUkAJ72RlkZTorKFyHbw4uhQBOY9su3twHK7FfpdVfvhPTT1WiLrIj6hJfT4LvsTRIYg==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-avatar": "1.13.0",
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-card": "1.13.0"
+ "@umbraco-ui/uui-avatar": "1.14.0-rc.4",
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-card": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-caret": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.14.0-rc.4.tgz",
+ "integrity": "sha512-3leJJlN4vBomZr4Y382nQ44/meeQI7mD+pjz/GRqmDagRAezq8olql0Z+3FEXJjzm7ycz2TtP2Fjn7L2nUulBQ==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-checkbox": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.14.0-rc.4.tgz",
+ "integrity": "sha512-p69yFqM8UEGmDWC1XVcKCY7htsF1vxPBO8L6YXGKNExLxwv/DLAvmLtIa8tnqX5M/51DpZ41SoDDf7Kl0kd5HA==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-boolean-input": "1.13.0",
- "@umbraco-ui/uui-icon-registry-essential": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-boolean-input": "1.14.0-rc.4",
+ "@umbraco-ui/uui-icon-registry-essential": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-color-area": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.14.0-rc.4.tgz",
+ "integrity": "sha512-4XbHjgAEnefVyw9xBeKIuy6EEKRORFIhekRwhmvGsr3kqbhG4TMdZzhT4BSBi4mfVVa+VMiZ+lMhkT6eeDoKDw==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
"colord": "^2.9.3"
}
},
"node_modules/@umbraco-ui/uui-color-picker": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.14.0-rc.4.tgz",
+ "integrity": "sha512-vD+k43g/iyj5MDbs7v1e4AWTih8Q3bMoJZnyEoS1IrhZ3l1QnzxmvFRAs3kZQLBzCBUct9vWG88uvavygU5j4g==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-popover-container": "1.13.0",
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-popover-container": "1.14.0-rc.4",
"colord": "^2.9.3"
}
},
"node_modules/@umbraco-ui/uui-color-slider": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.14.0-rc.4.tgz",
+ "integrity": "sha512-H7N8ep0L8GxUEChyFa6eFFzuR8yqppqeIuBNujLPhtl6aV1hV7V4wBnp2Wh5ttWtt5Ns/VMW4ZKzfrlG7f6JGA==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-color-swatch": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.13.0.tgz",
- "integrity": "sha512-tiT274ldYjDMFeBQBx3yGu7HgYaNrxjNIrcktlsddfWxxjJ3UNu08YdoP4DqJOi6limQhadBllCBa9oyz4iOig==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.14.0-rc.4.tgz",
+ "integrity": "sha512-dUFJk1/xD2W+ztOb+4QRGU6rgnXP4HvGZhg6V6B1qd15MU+9flSMri9HbxuclVmR9cqt3kuO6bwajMbp6Q2aTQ==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-icon-registry-essential": "1.13.0",
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-icon-registry-essential": "1.14.0-rc.4",
"colord": "^2.9.3"
}
},
"node_modules/@umbraco-ui/uui-color-swatches": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.14.0-rc.4.tgz",
+ "integrity": "sha512-aGvlKkW2DebrS51itO7JzGTRPyUrDmOYS6QZQxs03BDLMfBcmBBRBDnKN4Prx7fEbioC4bXOBPTwH73R06j2Xg==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-color-swatch": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-color-swatch": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-combobox": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.13.0.tgz",
- "integrity": "sha512-8lKmqQMQkh+yMA4iFonDLwpNf6EZ+pYXhJ2Xcum14WT6UI6BgiQvuM2nmRrkWhqA7Wx0tTAAdP5ILPAl5lENRQ==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.14.0-rc.4.tgz",
+ "integrity": "sha512-GI+fi+jnX2Ihxwtsmk0QtsDZKQICtP7hrSv4dyMSvbXmACT8X31tKLvkzEE+/UyJnVbWKe3M5dkFaoM0dFsIPg==",
+ "license": "MIT",
"dependencies": {
- "@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"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-button": "1.14.0-rc.4",
+ "@umbraco-ui/uui-combobox-list": "1.14.0-rc.4",
+ "@umbraco-ui/uui-icon": "1.14.0-rc.4",
+ "@umbraco-ui/uui-popover-container": "1.14.0-rc.4",
+ "@umbraco-ui/uui-scroll-container": "1.14.0-rc.4",
+ "@umbraco-ui/uui-symbol-expand": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-combobox-list": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.14.0-rc.4.tgz",
+ "integrity": "sha512-c996vWj/fCzaxn/P7JlmewG11UiJt0HuB7X+dq6mIrxOiGPYOLoM/SVg3z3g8+jiObkeJHn2IaXTd4wIL/OQyQ==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-css": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.14.0-rc.4.tgz",
+ "integrity": "sha512-nZZ9HCPh9SpS1ibxvJHhEcWW5hB4xmCev9p4vCvVBwsvnGAs2pyDaJJUwbMzs1LOPUP/AbohM3g1R6y1gGgFrg==",
+ "license": "MIT",
"peerDependencies": {
"lit": ">=2.8.0"
}
},
"node_modules/@umbraco-ui/uui-dialog": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.13.0.tgz",
- "integrity": "sha512-RzePOwJrhBEYBZAwgvNkIro+cVirLxgaIGNFUgnvoWIovHJNOuSso65MtcGON3nvuQ4JxE8SIOTE/hwT04R7Ag==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.14.0-rc.4.tgz",
+ "integrity": "sha512-9O65Hxgj6BTVfhB04KxpXvJsVCJa9ScfzIMN71nbPf+IhZ/iM/2k3dO29BCtW4oSyw64Fu43idq7y5sktSSZcQ==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-css": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-css": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-dialog-layout": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.13.0.tgz",
- "integrity": "sha512-m8eoCEz0dugWmqrmRw2vHae3k7oYjr53JiOkb8viCMh7veQo4EM0zqZgdCwADs1wES8woOX5zdttp9JtqYenRw==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.14.0-rc.4.tgz",
+ "integrity": "sha512-4DGWIoKWwI0pDz7+E2s0RDojYXngxYFGvcPnt4p+GfBQUkjOdoinIE+rsQkoQcZKs5mw8604exXkp1DmvaZaLg==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-file-dropzone": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.13.0.tgz",
- "integrity": "sha512-1TFkyeNB3qWWhgc7NYudxXOc3v0cBRuRpVYPA3xocfVkqCG2PgEc7ePW18CtUuuGntGwv0E0Oni2bfSLrqVmuQ==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.14.0-rc.4.tgz",
+ "integrity": "sha512-gmDgk8Tzmnmf+YUiEvDKskbgdFKvyfcCRfsW6TEdmFMmWAKEn8A52mg/AprSZB6ReoWLsIHFxhmggeIPbt0QHQ==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-symbol-file-dropzone": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-symbol-file-dropzone": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-file-preview": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.14.0-rc.4.tgz",
+ "integrity": "sha512-BeZ2AlEwqHiycgaspWqHdjeDcA3Uv84ZwxxPrlXXO5gshND3S5B6q7xkWe92KCGLvAAB/0sEe3kLfFS82AciiQ==",
+ "license": "MIT",
"dependencies": {
- "@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"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-symbol-file": "1.14.0-rc.4",
+ "@umbraco-ui/uui-symbol-file-thumbnail": "1.14.0-rc.4",
+ "@umbraco-ui/uui-symbol-folder": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-form": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.13.0.tgz",
- "integrity": "sha512-Y5Wgl3AcixLbPHJJK2yqdga5NMHx5Jv3YvG69+AdPkgzyNmCtdbDitV8ex2ysNYMO3WbBRdYIjbI5pYRl3xn5Q==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.14.0-rc.4.tgz",
+ "integrity": "sha512-D/yHES83/gCUoUbpW7CvokDjCEm8Delo1AM718SoCPOJNt1DyUaQtMJ+MPlfnJCJGelcnOSGCKOsPpCdTBQZlw==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-form-layout-item": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.14.0-rc.4.tgz",
+ "integrity": "sha512-M8UczkVX9c2U/sc+cPuZ0YUBTyKpHwN3wlv/R2XaBE3mA6gKV/N3cRMDvNz9g7KBeaoAvREbF2LM8XxfC0v/tw==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-form-validation-message": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-form-validation-message": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-form-validation-message": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.14.0-rc.4.tgz",
+ "integrity": "sha512-tZ48nDLldzkxQgEK7bmWOzqKRtSCMZbY68wnr4jNhPgRj48NMHkOwA3bzdyVpEXkQCO7kRvsNcxIecbS3TUhyA==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-icon": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.13.0.tgz",
- "integrity": "sha512-TKmQi4n8ZV6v228U6zi9f38g/Nu4ok1cbvoIiSfSvmSYNXD1weuWK5y7Ph7EGr6jL5T5vKbDhjcZUIjzBOVWAA==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.14.0-rc.4.tgz",
+ "integrity": "sha512-U+2Gh1N6kAtCFVFkSnh6GmYf0ZQhX1KFlBKt7KIZ/uw4LMop2qLnOz3V0GLKsHwWGMAUuDpdveAHFS3MCVJ4Xw==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-icon-registry": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.14.0-rc.4.tgz",
+ "integrity": "sha512-rKX5YquEU8Hg9MpPJTKvLjl6OH/S/EojGWnSfXpWnTDT9zWdeNzoJwubae0MILmlcMDnMoI1pmcASgfdHpFZWw==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-icon": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-icon": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-icon-registry-essential": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.14.0-rc.4.tgz",
+ "integrity": "sha512-M9jV2buP2+C5GLzixXA87rCuZy+32GAi6/Q0W6SGzBHIzHQhNU7R6xbIeIc/Ki/i1lVBghXJ1JrvBL6Qjom31A==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-icon-registry": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-icon-registry": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-input": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.13.0.tgz",
- "integrity": "sha512-2GwLio1SDBofYLZjds47X6Fxq29ORgQBZaD9xwowFaoWCsG+WIFsE7VaE4KgPASUOQYoMMzFZX3F2TdvbjPEAg==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.14.0-rc.4.tgz",
+ "integrity": "sha512-zxWvaaAwZ7Rihe+Ca/ezGZmoNRl0jzp6Tls20vQI/CRHQGR+anJiY4JmsQZbErMOtUib7o04REGZdfEzs1vJ7Q==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-input-file": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.14.0-rc.4.tgz",
+ "integrity": "sha512-mawTTpQG/hU3+Ug5cDpCmJjsiFzv1KreQ+TRQTcWx4lkGjgAoOECXuZMXTq3Clg34aCf63aGlafSlDI3z59J5Q==",
+ "license": "MIT",
"dependencies": {
- "@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"
+ "@umbraco-ui/uui-action-bar": "1.14.0-rc.4",
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-button": "1.14.0-rc.4",
+ "@umbraco-ui/uui-file-dropzone": "1.14.0-rc.4",
+ "@umbraco-ui/uui-icon": "1.14.0-rc.4",
+ "@umbraco-ui/uui-icon-registry-essential": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-input-lock": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.13.0.tgz",
- "integrity": "sha512-qChhwO5NsA8es9X41HJ73sXtmvKUF90WBBL8PYgESLkL7zQdvhe9wfJhVjZ1WMJkOc6F7uTAJbawuEVXSX0uKA==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.14.0-rc.4.tgz",
+ "integrity": "sha512-TUCh15WKDqbz10KdLzIOo2POcYYreQvwcicGx6H3jyLrZZwttE4VP8xZPBln7C7sewLCJNtH/gw2YeLLJDzTmg==",
+ "license": "MIT",
"dependencies": {
- "@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"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-button": "1.14.0-rc.4",
+ "@umbraco-ui/uui-icon": "1.14.0-rc.4",
+ "@umbraco-ui/uui-input": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-input-password": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.14.0-rc.4.tgz",
+ "integrity": "sha512-rcRLKVPyUkZUUk2r5yeURfP57tw/KMZeMg3rYNXZszRTE+i/WDpoRBodzt9xvfKKnbYjsoZ6VZ83L3c0CUeMrw==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-icon-registry-essential": "1.13.0",
- "@umbraco-ui/uui-input": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-icon-registry-essential": "1.14.0-rc.4",
+ "@umbraco-ui/uui-input": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-keyboard-shortcut": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.14.0-rc.4.tgz",
+ "integrity": "sha512-Tcy1EUQTob8Ds/5hAfG2iMHsrVwrS12fjzRy+p29K8BUhnp87JgJ1OJt6W3kQHQskLQlYb4/ZSIaBBD5pEEPEQ==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-label": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.13.0.tgz",
- "integrity": "sha512-BcfvqdFybY0Vb7TVinfHDLrAyhmtayz5ZGXwnTZpwyg7IP+pPZrFunrhcPPTPIrvJEw/j7qgpfC2AKMsiaZq7A==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.14.0-rc.4.tgz",
+ "integrity": "sha512-YfRGdCqFaJk6Cguh36mxrHfScbtrIKpxfQlCzwjfZ8DcxXh6cYYSZkQkWHrbjuiDWG/8853FrKUA36lV2oGItA==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-loader": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.13.0.tgz",
- "integrity": "sha512-AmNcIX7XNtW9dc29TdlMgTcTJBxU7MCae9iivNLLfFrg3VblltCPeeCOcLx77rw/k9o/IWrhLOsN81Q0HPfs7g==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.14.0-rc.4.tgz",
+ "integrity": "sha512-DovO8MVZV29ZivLDWgHYPrHLxJjJLS261RnFDJrIxflVfE0E4S60T9/3IE3pQT/ztUf241uq+ddNICNU/7vbTw==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-loader-bar": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.13.0.tgz",
- "integrity": "sha512-wRJl2n6VesXV5z7EOz3W8DKDo2XLbpb4a9HZHauDtnGl9aNQggcFYBXTrLAvqg2Nuir2g52kQT9mDcQiTDxJLQ==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.14.0-rc.4.tgz",
+ "integrity": "sha512-TPWAoZ8WJiQXYo2dpuOCltq5npTOt+h+ahaQwkQpk9wNAwTr9y299HxiOK4c26efHGQ+9O797paCMi7knNgW+w==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-loader-circle": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.14.0-rc.4.tgz",
+ "integrity": "sha512-MYVvbs2az+hA7gAfjLR+gu1VuILzC/zsAZtTuNCpMoFozvp5BirWzme9DGYlMD6hlW8pTSLdE9SOV19+ptK7yw==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-menu-item": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.14.0-rc.4.tgz",
+ "integrity": "sha512-FXjNrrpBUUkF7t7Q9ikT+Mw3DV9QvEsMF2XPl01XI6zhsCEZDvp3OxNLlWkkhv99Scf2teZqdulLQ8A+LmkwqA==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-loader-bar": "1.13.0",
- "@umbraco-ui/uui-symbol-expand": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-loader-bar": "1.14.0-rc.4",
+ "@umbraco-ui/uui-symbol-expand": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-modal": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.14.0-rc.4.tgz",
+ "integrity": "sha512-Qq/WQbHahMIyZ2wnmrR98SV2fc3iE3AXySFXbL7uEB5M3pmU8SC1JpLGA90/B5lDSyttA64mbqdces1vzxPvaQ==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-pagination": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.13.0.tgz",
- "integrity": "sha512-RtD+szI8P+7y2tKBLLPJyCOlfS504LgQqD4pUOZbxemsQmMe37OZ1CiiqfrNJVEv4TbMHP0WvoRiLFDawICXnw==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.14.0-rc.4.tgz",
+ "integrity": "sha512-JaPFK/IIr4bygmTftEUv8iV0GVfWLez+/YvNDRii9pViE0nn4MsF20PDUpu1CN3XvYOCqqu5ptFnmwIWhXf/Mg==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-button": "1.13.0",
- "@umbraco-ui/uui-button-group": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-button": "1.14.0-rc.4",
+ "@umbraco-ui/uui-button-group": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-popover": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.13.0.tgz",
- "integrity": "sha512-fpN0x+9p7cxrKiBYzEbpAYuIFYxWlUDrv4jYw3+oEI1ZP2wlS4dKphxhNtLreGrbaYsSUXe8Vgx9wy3eFawfug==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.14.0-rc.4.tgz",
+ "integrity": "sha512-UGBplMad24pvornzgk3xBACl/DszLlwIl+I4+fRWknLpIgwjFnQHhD/ary7RbjJncfC78GyeqogVapRITc4oRQ==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-popover-container": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.14.0-rc.4.tgz",
+ "integrity": "sha512-nZCyIQOMmBwgOPFWedsoUhxiKr5+i7P/9x+WYRPjDouu1KwW85y3D50j2ELQRZ5jSpt16KrF29hucxTKMmYrHg==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-progress-bar": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.14.0-rc.4.tgz",
+ "integrity": "sha512-CZSogzxLbbcfdg9ik3vKrpnGsE2IB0nRZ3xr485QOcFPY7MCVbdVF+z/jicokvjz0MT24k80sAw1/RqD6LZS3g==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-radio": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.13.0.tgz",
- "integrity": "sha512-SsSqyte7n2KEqEjmtI2ajUX6m0AL6nreSZ53IGViMBim8bTcW4oBq5Wbp3dll+s88WvExppozE2iA1kLgjijOw==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.14.0-rc.4.tgz",
+ "integrity": "sha512-DtfexpuS2tkrU3xM203nfrJg6CUqFXWJHry4/veuSlO7TBIaMEzhDrfAboyOUvXJF5q5130CmFhN3i69/bgFLw==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-range-slider": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.14.0-rc.4.tgz",
+ "integrity": "sha512-QqFcYCeKwYm6ahwe+60oZs0uzdELMk1zcCcQRHdspze7vx4fqDwYtBL64IjGoKQF/S1T+s3AEq7PG8eqR086Dw==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-ref": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.14.0-rc.4.tgz",
+ "integrity": "sha512-DcGm5JYTMFZgWfBPzRp/RgUVtfJ+s1idK5tkwKRgQyXECEbBHXzpXwz/rSiMeiEuWVrS/49vHtFY8YeolzVEXw==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-ref-list": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.14.0-rc.4.tgz",
+ "integrity": "sha512-OjQlNzCBhJVseV2o99uxSd003tGQjSOpYIlTZT+Dh8Gqfe+6mJSnFwfHMUuLstgFP204i6CEcixtIM0x2Gl9/A==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-ref-node": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.13.0.tgz",
- "integrity": "sha512-otjHMgmaekLl3iPwDjLgJ6H7HIWF3QqNLNAiHnGZ1pdShJyLfajvHxnDULeUuI6zRDdjavzz3fhPUnjzyraYpQ==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.14.0-rc.4.tgz",
+ "integrity": "sha512-/JUk5L9k6rOQIlIk/wykeTyQZ+pUhSAl6zlzkPjgU2DoLkVTaetfdggmA6NVDsMesZQrxtg+e0UD36FAJtx9Qw==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-icon": "1.13.0",
- "@umbraco-ui/uui-ref": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-icon": "1.14.0-rc.4",
+ "@umbraco-ui/uui-ref": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-ref-node-data-type": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.14.0-rc.4.tgz",
+ "integrity": "sha512-V/OGX8mRNP/93vZmPdCL8djdicRAwSxPSVTgHJ5QsnCkwo15FfZmjqnIkU162aI4Levgs9B9JFGIgkaELmi7hg==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-ref-node": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-ref-node": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-ref-node-document-type": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.14.0-rc.4.tgz",
+ "integrity": "sha512-CVQFGFdoJroLlSkDXajQG1t6gDkxoB2IuldTzqZv3M6rN1/UwjIrChbfFIpMwgRtLwMPKsX8v9PyjYlvjbnJkA==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-ref-node": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-ref-node": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-ref-node-form": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.14.0-rc.4.tgz",
+ "integrity": "sha512-JKSSi9XIrNbTjFE9NIYWMfQPjv3zXYwMyCTMwDGc3/qNCPI/1Vp+Q8PVWPX6tHBSPHmMnWb85Tolik7+k+Qtew==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-ref-node": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-ref-node": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-ref-node-member": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.14.0-rc.4.tgz",
+ "integrity": "sha512-FCdk4TDYITLWU32uQMIxWIPrj3gH7GIj34dOfuKtIeHroUvH1t9blrSRUvbxWo4IS0lxl11g/q+w2Ffy8nBy4g==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-ref-node": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-ref-node": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-ref-node-package": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.14.0-rc.4.tgz",
+ "integrity": "sha512-unE0mQkxk0UOPFevU3XWftj4Zg5rtnuvx2T4jvKU63DfwAPvtXjPVyG1Np5z4LLCI3kbTdqigOdoINfRo7L3+Q==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-ref-node": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-ref-node": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-ref-node-user": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.14.0-rc.4.tgz",
+ "integrity": "sha512-TFN+tV2AxPKKlDtnSQYvDkM12D/VISW03SPR+AZwoxBmEycPHT8yEwOY5s3V2wwOEUTOnJ5dOw9SWCgzTXvmRg==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-ref-node": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-ref-node": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-scroll-container": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.14.0-rc.4.tgz",
+ "integrity": "sha512-8o1mRxWjpsfOoZ7itGaTJzvrCWhh1AVZ/cWne6Sh/wfHqQX2iXm9W7TVJ1o5t3fQEbMERFTXEB3tzkb9x+WKyQ==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-select": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.13.0.tgz",
- "integrity": "sha512-JF4Jtgc/H61tdPVD01kkBRkUofWatrUG9diRMuaGPvQsWEVNvbCJKXJ+Fww9pMQts25EidLhhtqG2hX3ZSsgYA==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.14.0-rc.4.tgz",
+ "integrity": "sha512-G7JMfmMdOEGE2BRGfFuYcvDp3hiJdmxR85xvrbi0gz1deB/TvY0pHhHXlXk+kApkQJQveJIT89CjQ/u2AyvbLQ==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-slider": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.14.0-rc.4.tgz",
+ "integrity": "sha512-sB+JUieP/oRGOfHonfnSG4mtSPB4SCfOmeFq8shqzR9ucvtaFH75ei/cjYheFabhdxOPIzzC40PMyIBL/nsdBA==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-symbol-expand": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.14.0-rc.4.tgz",
+ "integrity": "sha512-5+B8L2qeYKIAqjPoAAEqi6qAwxt17iNUwVYiqyidMBh/zis4cDym+5y5ccC/hbuGzCeBHwFWSRBfFpEOPTYrzg==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-symbol-file": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.14.0-rc.4.tgz",
+ "integrity": "sha512-sC7IeABeoMjxgtMy/HqLCL1Zzm3A5sn7cxHaOztNbI4PO+DRc9rHKq6IIZAMbFgz53PDQvKs/ok2bnlhloFVjQ==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-symbol-file-dropzone": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.14.0-rc.4.tgz",
+ "integrity": "sha512-bYanzwC8H028GoVtHL/lQngPnK4WY1QvOz4oCK9EVzaWsLCAr6ktQm8bEO3LO/vWM/HQJ4nkS7yaNCOc+so7Rw==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-symbol-file-thumbnail": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.14.0-rc.4.tgz",
+ "integrity": "sha512-pqut/KGBor3csD+Zvj6CRGRerhXcS7/UTAcEQSTWjhpohz5iTPd7sLRuuUAdWcAEAkNQXykbJuhuQ9GhoQKjyQ==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-symbol-folder": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.14.0-rc.4.tgz",
+ "integrity": "sha512-9b/XEd6I2WsjaHJ6s2hZfl9GDfDA5ZlD4YGYRhnoVyWatsA9W4f4OdBcgzHOKA7I4m2IbdRc3F6Jc0SA/osjmA==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-symbol-lock": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.13.0.tgz",
- "integrity": "sha512-/39U8n0DfHNI4I2X1WX8dJv6pSOHeJMvpyS1Cla54Q45gtt7RHMU55aNEGBZoF19oPV2W74gl7vfcHGTtnPKNQ==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.14.0-rc.4.tgz",
+ "integrity": "sha512-r+zi5yU5WiKVAkLrzLQgi7wvEPJka9aYYw8SeTGko2OiQRXgBCqrssW+FGaRvElWwX3+klv34uRNlr6Hr7Q0Xw==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-symbol-more": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.14.0-rc.4.tgz",
+ "integrity": "sha512-IirqSEyLg7edOJlqpyECTVrZPUkkBj2+7l0bBLWnvNZ3xZ8Zd2hXMVXVTOsIcnUYZlyL3SpYSaHS7XkR4Uxy6Q==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-symbol-sort": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.14.0-rc.4.tgz",
+ "integrity": "sha512-+HyeiY9TEuenbKlxS+T46t5qwvf+20vT71XcXjHufjPgo0C05HqqaLWZ5dVZMQs/TyEY1OrR7Vh9K7EJ/71vQg==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-table": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.14.0-rc.4.tgz",
+ "integrity": "sha512-4KDA6pDUfRoXA2PhM6pS3V4CMdQ3GGP9SbtBSs6rWj71rPt2J4PpzNsRdyNQXpR2iWsoL5h2MVSXf1X1zJWk3A==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-tabs": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.13.0.tgz",
- "integrity": "sha512-b50xJ1Xka8nu51GCR8n2RZtCFjwTYDXV5zQF+s5KXpgC6A8mahCvzmmINHdgGnKm1JNG3c8abhwrueyxxVdI8Q==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.14.0-rc.4.tgz",
+ "integrity": "sha512-cNfwUN1Swj0MUUGTXta8vJ3bicciiLb1Ep2K6DVDVAurgJTsZMwreYTJ7FUhiYNsunB6KYICmBA7tykuYS5fsw==",
+ "license": "MIT",
"dependencies": {
- "@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"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-button": "1.14.0-rc.4",
+ "@umbraco-ui/uui-popover-container": "1.14.0-rc.4",
+ "@umbraco-ui/uui-symbol-more": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-tag": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.13.0.tgz",
- "integrity": "sha512-SBY9Mi9A89jIag7URKQqXW3omDk5Eczw2LuNF7VnkXmQCuvsiRP6/BzSBCh9m0RrD4QOLSXpYGgSoLSpS7MitA==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.14.0-rc.4.tgz",
+ "integrity": "sha512-N2DyheYdOseh7ep4RUzwSlASVc4p9MsFlBGcsbPjlfT+Iz2rX8t1LF6ZzqrQXewZsW5fflIuO78+xADkjqL5ww==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-textarea": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.13.0.tgz",
- "integrity": "sha512-H4XChy1m5gq43eySQ3Zp/AsBvh35Gk0VLijFxdhCfV+HHpuyrt0fJsYnjq1W1xoqhyt7h84YRpNIJMyAIm2WHQ==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.14.0-rc.4.tgz",
+ "integrity": "sha512-AP1tGEvzmrstw8kxi3kuSTVBTtGC3rqepZ29V1Lw6I7LNr10Oeo8rWpMYjCQYTkgAe/dMqR0IHefYyrdTAvuYg==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-toast-notification": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.14.0-rc.4.tgz",
+ "integrity": "sha512-Uq61neNnYzgGqKMnOd5X6aGMIi+5PfwM7E/DcGdAYNWR5t5NgU1uc1/GuxEHPXrWatiC15UT5G2ETiaIPUGX+A==",
+ "license": "MIT",
"dependencies": {
- "@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"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-button": "1.14.0-rc.4",
+ "@umbraco-ui/uui-css": "1.14.0-rc.4",
+ "@umbraco-ui/uui-icon": "1.14.0-rc.4",
+ "@umbraco-ui/uui-icon-registry-essential": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-toast-notification-container": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.14.0-rc.4.tgz",
+ "integrity": "sha512-H0rQnkt6OlbBCZQ5rAb31WByydZBMqXA8UBKCRzQL4JOqOjO8XSxMag1sZLorul3QPXfL5A40IDbwyVw0/FO1g==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-toast-notification": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-toast-notification": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-toast-notification-layout": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.14.0-rc.4.tgz",
+ "integrity": "sha512-CCugRovI3Kglzjr4ejkMfWS7KjVWOhewY2kc9yixc8d+UTW5QZsGlSFwovuADi6gRMYMaOkO6hiB0Ejgd0HL6g==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-css": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-css": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-toggle": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.13.0.tgz",
- "integrity": "sha512-tHzG/Lh9vRLjPu7EhFupaD7jkpVenyEM3iIsA24wBVKmqJGxacpuuuOwpTv6vGGiIYSKfRDXTDk07Q6MHDSy4g==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.14.0-rc.4.tgz",
+ "integrity": "sha512-W7dvUAgHh1gRxtSfMh6BwmIUSLlH9ZajesFkfHVqy0ZtMfs+d3Glnw+MIETRN/QuqBy/wl0bOxPqcujyc+8iQw==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0",
- "@umbraco-ui/uui-boolean-input": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4",
+ "@umbraco-ui/uui-boolean-input": "1.14.0-rc.4"
}
},
"node_modules/@umbraco-ui/uui-visually-hidden": {
- "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==",
+ "version": "1.14.0-rc.4",
+ "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-visually-hidden/-/uui-visually-hidden-1.14.0-rc.4.tgz",
+ "integrity": "sha512-0Zhi67ZRUMCgPpiS44+mYlXey1apzib8B0YMK8Dgy2Gu3HpVqsFl+yPUWTOEfUcRFBzQnZqewEudo2OYhewtJw==",
+ "license": "MIT",
"dependencies": {
- "@umbraco-ui/uui-base": "1.13.0"
+ "@umbraco-ui/uui-base": "1.14.0-rc.4"
}
},
"node_modules/@vitest/expect": {
@@ -7118,7 +7213,8 @@
"node_modules/colord": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
- "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw=="
+ "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==",
+ "license": "MIT"
},
"node_modules/command-line-args": {
"version": "5.2.1",
@@ -15660,9 +15756,9 @@
}
},
"node_modules/tar-fs": {
- "version": "3.0.8",
- "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz",
- "integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==",
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.9.tgz",
+ "integrity": "sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -17480,12 +17576,12 @@
"src/packages/core": {
"name": "@umbraco-backoffice/core",
"dependencies": {
+ "@hey-api/client-fetch": "^0.10.0",
"@types/diff": "^7.0.2",
"diff": "^7.0.0",
"uuid": "^11.1.0"
},
"devDependencies": {
- "@hey-api/client-fetch": "^0.10.0",
"@hey-api/openapi-ts": "^0.66.6"
}
},
@@ -17560,6 +17656,12 @@
"src/packages/search": {
"name": "@umbraco-backoffice/search"
},
+ "src/packages/segment": {
+ "name": "@umbraco-backoffice/segment"
+ },
+ "src/packages/settings": {
+ "name": "@umbraco-backoffice/settings"
+ },
"src/packages/static-file": {
"name": "@umbraco-backoffice/static-file"
},
@@ -17578,6 +17680,9 @@
"src/packages/tiptap": {
"name": "@umbraco-backoffice/tiptap"
},
+ "src/packages/translation": {
+ "name": "@umbraco-backoffice/translation"
+ },
"src/packages/ufm": {
"name": "@umbraco-backoffice/ufm"
},
diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json
index 2c66ff2dfb..d91b23c19b 100644
--- a/src/Umbraco.Web.UI.Client/package.json
+++ b/src/Umbraco.Web.UI.Client/package.json
@@ -215,8 +215,8 @@
"@tiptap/extension-underline": "2.11.7",
"@tiptap/pm": "2.11.7",
"@tiptap/starter-kit": "2.11.7",
- "@umbraco-ui/uui": "^1.13.0",
- "@umbraco-ui/uui-css": "^1.13.0",
+ "@umbraco-ui/uui": "1.14.0-rc.4",
+ "@umbraco-ui/uui-css": "1.14.0-rc.4",
"dompurify": "^3.2.5",
"element-internals-polyfill": "^3.0.2",
"lit": "^3.3.0",
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 db4951ef64..6b7b1b3a6c 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
@@ -77,7 +77,10 @@ export class UmbBackofficeContext extends UmbContextBase {
}
public async serverUpgradeCheck() {
- const version = await this.observe(this.version).asPromise();
+ const version = await this.observe(this.version)
+ .asPromise()
+ .catch(() => null);
+ if (!version) return null;
const repository = new UmbSysinfoRepository(this);
return repository.serverUpgradeCheck(version);
}
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 9435f94a1b..0550e6c52c 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
@@ -63,8 +63,10 @@ export class UmbBackofficeMainElement extends UmbLitElement {
if (newRoutes.length > 0) {
newRoutes.push({
+ path: '',
+ pathMatch: 'full',
+ awaitStability: true,
redirectTo: newRoutes[0].path,
- path: ``,
});
newRoutes.push({
diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts
index 6086096973..18666f252f 100644
--- a/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts
+++ b/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts
@@ -62,8 +62,9 @@ export default {
unlock: 'Lås op',
createblueprint: 'Opret indholdsskabelon',
resendInvite: 'Gensend Invitation',
- editContent: 'Edit content',
- chooseWhereToImport: 'Choose where to import',
+ editContent: 'Rediger indhold',
+ chooseWhereToImport: 'Vælg hvor du vil importere',
+ viewActionsFor: (name) => (name ? `Se handlinger for '${name}'` : 'Se handlinger'),
},
actionCategories: {
content: 'Indhold',
diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts
index 425c4f5f6a..7101a54399 100644
--- a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts
+++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts
@@ -16,7 +16,7 @@ export default {
'In order to improve Umbraco and add new functionality based on as relevant information as possible, we would like to collect system- and usage information from your installation. Aggregate data will be shared on a regular basis as well as learnings from these metrics. Hopefully, you will help us collect some valuable data. We WILL NOT collect any personal data such as content, code, user information, and all data will be fully anonymized.',
basicLevelDescription: 'We will send an anonymized site ID, Umbraco version, and packages installed',
detailedLevelDescription:
- 'We will send: li>Anonymized site ID, Umbraco version, and packages installed.Number of: Root nodes, Content nodes, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, Backoffice external login providers, and Property Editors in use. System information: Webserver, server OS, server framework, server OS language, and database provider. Configuration settings: ModelsBuilder mode, if custom Umbraco path exists, ASP environment, whether the delivery API is enabled, and allows public access, and if you are in debug mode. We might change what we send on the Detailed level in the future. If so, it will be listed above. By choosing "Detailed" you agree to current and future anonymized information being collected. ',
+ 'We will send: Anonymized site ID, Umbraco version, and packages installed. Number of: Root nodes, Content nodes, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, Backoffice external login providers, and Property Editors in use. System information: Webserver, server OS, server framework, server OS language, and database provider. Configuration settings: ModelsBuilder mode, if custom Umbraco path exists, ASP environment, whether the delivery API is enabled, and allows public access, and if you are in debug mode. We might change what we send on the Detailed level in the future. If so, it will be listed above. By choosing "Detailed" you agree to current and future anonymized information being collected. ',
minimalLevelDescription: 'We will only send an anonymized site ID to let us know that the site exists.',
},
blockEditor: {
diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts
index 663d40f260..69d3c4e5a7 100644
--- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts
+++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts
@@ -72,6 +72,7 @@ export default {
wasCopiedTo: 'was copied to',
wasDeleted: 'was deleted',
wasMovedTo: 'was moved to',
+ viewActionsFor: (name) => (name ? `View actions for '${name}'` : 'View actions'),
},
actionCategories: {
content: 'Content',
@@ -159,7 +160,7 @@ export default {
saveAndPublish: 'Save and publish',
saveToPublish: 'Save and send for approval',
saveListView: 'Save list view',
- schedulePublish: 'Schedule',
+ schedulePublish: 'Schedule publish',
saveAndPreview: 'Save and preview',
showPageDisabled: "Preview is disabled because there's no template assigned",
styleChoose: 'Choose style',
@@ -271,7 +272,7 @@ export default {
publishedPendingChanges: 'Published (pending changes)',
publishStatus: 'Publication Status',
publishDescendantsHelp:
- 'Publish %0% and all content items underneath and thereby making their content publicly available.',
+ 'Publish %0% and all items underneath and thereby making their content publicly available.',
publishDescendantsWithVariantsHelp:
'Publish variants and variants of same type underneath and thereby making their content publicly available.',
noVariantsToProcess: 'There are no available variants',
@@ -319,7 +320,7 @@ export default {
addTextBox: 'Add another text box',
removeTextBox: 'Remove this text box',
contentRoot: 'Content root',
- includeUnpublished: 'Include unpublished content items.',
+ includeUnpublished: 'Include unpublished items.',
isSensitiveValue:
'This value is hidden. If you need access to view this value please contact your website administrator.',
isSensitiveValue_short: 'This value is hidden.',
@@ -348,6 +349,8 @@ export default {
variantUnpublishNotAllowed: 'Unpublish is not allowed',
selectAllVariants: 'Select all variants',
saveModalTitle: 'Save',
+ saveAndPublishModalTitle: 'Save and publish',
+ publishModalTitle: 'Publish',
},
blueprints: {
createBlueprintFrom: "Create a new Document Blueprint from '%0%'",
@@ -475,10 +478,11 @@ export default {
discardChanges: 'Discard changes',
unsavedChanges: 'Discard unsaved changes',
unsavedChangesWarning: 'Are you sure you want to navigate away from this page? You have unsaved changes',
- confirmListViewPublish: 'Publishing will make the selected items visible on the site.',
- confirmListViewUnpublish: 'Unpublishing will remove the selected items and all their descendants from the site.',
- confirmPublish: 'Publishing will make this page and all its published descendants visible on the site.',
- confirmUnpublish: 'Unpublishing will remove this page and all its descendants from the site.',
+ confirmListViewPublish: 'Publishing will make the selected items publicly available.',
+ confirmListViewUnpublish:
+ 'Unpublishing will make the selected items and all their descendants publicly unavailable.',
+ confirmPublish: 'Publishing will make this content and all its published descendants publicly available.',
+ confirmUnpublish: 'Unpublishing will make this content publicly unavailable.',
doctypeChangeWarning: 'You have unsaved changes. Making changes to the Document Type will discard the changes.',
},
bulk: {
@@ -911,7 +915,7 @@ export default {
retrieve: 'Retrieve',
retry: 'Retry',
rights: 'Permissions',
- scheduledPublishing: 'Scheduled Publishing',
+ scheduledPublishing: 'Schedule publish',
umbracoInfo: 'Umbraco info',
search: 'Search',
searchNoResult: 'Sorry, we can not find what you are looking for.',
@@ -2795,7 +2799,7 @@ export default {
modalSource: 'Source',
modalManual: 'Manual',
modalAnchorValidationMessage:
- 'Please enter an anchor or querystring, or select a published document or media item, or manually configure the URL.',
+ 'Please enter an anchor or querystring, select a document or media item, or manually configure the URL.',
resetUrlHeadline: 'Reset URL?',
resetUrlMessage: 'Are you sure you want to reset this URL?',
resetUrlLabel: 'Reset',
diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/pt-br.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/pt-br.ts
index cb54906ecd..cf78cbe561 100644
--- a/src/Umbraco.Web.UI.Client/src/assets/lang/pt-br.ts
+++ b/src/Umbraco.Web.UI.Client/src/assets/lang/pt-br.ts
@@ -15,58 +15,32 @@ export default {
auditTrail: 'Caminho de Auditoria',
browse: 'Navegar o Nó',
copy: 'Copiar',
- create: 'Criar',
- createPackage: 'Criar Pacote',
delete: 'Remover',
disable: 'Desabilitar',
emptyrecyclebin: 'Esvaziar Lixeira',
- exportDocumentType: 'Exportar Tipo de Documento',
- importdocumenttype: 'Importar Tipo de Documento',
- importPackage: 'Importar Pacote',
liveEdit: 'Editar na Tela',
logout: 'Sair',
move: 'Mover',
- notify: 'Notificações',
protect: 'Acesso público',
- publish: 'Publicar',
refreshNode: 'Recarregar nós',
- republish: 'Republicar site inteiro',
- rights: 'Permissões',
rollback: 'Reversão',
- sendtopublish: 'Enviar para Publicação',
- sendToTranslate: 'Enviar para Tradução',
sort: 'Classificar',
- translate: 'Traduzir',
- update: 'Atualizar',
},
assignDomain: {
- addNew: 'Adicionar novo Domínio',
- domain: 'Domínio',
- domainCreated: "Novo domínio '%0%' foi criado",
domainDeleted: "Domínio '%0%' foi removido",
domainExists: "Domínio '%0%' já foi designado",
- domainUpdated: "Domínio '%0%' foi atualizado",
- orEdit: 'Editar Domínios Atuais',
},
auditTrails: {
atViewingFor: 'Visão para',
},
buttons: {
- bold: 'Negrito',
deindent: 'Remover Travessão de Parágrafo',
- formFieldInsert: 'Inserir campo de formulário',
graphicHeadline: 'Inserir manchete de gráfico',
- htmlEdit: 'Editar Html',
indent: 'Travessão de Parágrafo',
- italic: 'Itálico',
- justifyCenter: 'Centro',
- justifyLeft: 'Justificar à Esquerda',
- justifyRight: 'Justificar à Direita',
linkInsert: 'Inserir Link',
linkLocal: 'Inserir link local (âncora)',
listBullet: 'Lista de tópicos',
listNumeric: 'Lista numérica',
- macroInsert: 'Inserir macro',
pictureInsert: 'Inserir figura',
relations: 'Editar relacionamentos',
save: 'Salvar',
@@ -74,43 +48,22 @@ export default {
saveToPublish: 'Salvar e mandar para aprovação',
saveAndPreview: 'Prévia',
styleChoose: 'Escolha estilo',
- styleShow: 'Mostrar estilos',
- tableInsert: 'Inserir tabela',
},
content: {
- about: 'Sobre esta página',
alias: 'Link alternativo',
alternativeTextHelp: '(como você descreveria a imagem pelo telefone)',
alternativeUrls: 'Links Alternativos',
- clickToEdit: 'Clique para editar este item',
- createBy: 'Criado por',
- createDate: 'Criado',
- documentType: 'Tipo de Documento',
editing: 'Editando',
- expireDate: 'Remover em',
- itemChanged: 'Este item foi alterado após a publicação',
- itemNotPublished: 'Este item não está publicado',
- lastPublished: 'Última publicação',
mediatype: 'Tipo de Mídia',
membergroup: 'Grupo do Membro',
- memberrole: 'Função',
- membertype: 'Tipo de Membro',
- noDate: 'Nenhuma data escolhida',
nodeName: 'Título da Página',
- otherElements: 'Propriedades',
- parentNotPublished: "Este documento está publicado mas não está visível porque o pai '%0%' não está publicado",
- publish: 'Publicar',
publishStatus: 'Status da Publicação',
releaseDate: 'Publicado em ',
removeDate: 'Remover Data',
sortDone: 'Ordem de classificação está atualizada',
sortHelp:
"Para classificar os nós simplesmente arraste os nós ou clique em um dos títulos de colunas. Você pode selecionar múltiplos nós ao pressionar e segurar 'shift' ou 'control' durante a seleção",
- statistics: 'Estatísticas',
- titleOptional: 'Título (opcional)',
- type: 'Tipo',
unpublish: 'Des-Publicar',
- updateDate: 'Última edição',
uploadClear: 'Remover arquivo',
urls: 'Link ao documento',
saveModalTitle: 'Salvar',
@@ -118,44 +71,16 @@ export default {
create: {
chooseNode: 'Onde você quer criar seu novo(a) %0%',
createUnder: 'Criado em',
- updateData: 'Escolha um tipo e um título',
},
dashboard: {
browser: 'Navegue seu site',
dontShowAgain: '- Esconder',
nothinghappens: 'Se Umbraco não estiver abrindo talvez você precise hablitar pop-ups para este site',
openinnew: 'foi aberto em uma nova janela',
- restart: 'Reiniciar',
- visit: 'Visitar',
welcome: 'Bem Vindo(a)',
},
- bulk: {
- done: 'Done',
- deletedItem: 'Deleted %0% item',
- deletedItems: 'Deleted %0% items',
- deletedItemOfItem: 'Deleted %0% out of %1% item',
- deletedItemOfItems: 'Deleted %0% out of %1% items',
- publishedItem: 'Published %0% item',
- publishedItems: 'Published %0% items',
- publishedItemOfItem: 'Published %0% out of %1% item',
- publishedItemOfItems: 'Published %0% out of %1% items',
- unpublishedItem: 'Unpublished %0% item',
- unpublishedItems: 'Unpublished %0% items',
- unpublishedItemOfItem: 'Unpublished %0% out of %1% item',
- unpublishedItemOfItems: 'Unpublished %0% out of %1% items',
- movedItem: 'Moved %0% item',
- movedItems: 'Moved %0% items',
- movedItemOfItem: 'Moved %0% out of %1% item',
- movedItemOfItems: 'Moved %0% out of %1% items',
- copiedItem: 'Copied %0% item',
- copiedItems: 'Copied %0% items',
- copiedItemOfItem: 'Copied %0% out of %1% item',
- copiedItemOfItems: 'Copied %0% out of %1% items',
- },
defaultdialogs: {
- anchorInsert: 'Nome',
assignDomain: 'Gerenciar hostnames',
- closeThisWindow: 'Fechar esta janela',
confirmdelete: 'Certeza em remover',
confirmdisable: 'Certeza em desabilitar',
confirmlogout: 'Tem certeza',
@@ -176,7 +101,6 @@ export default {
linklocaltip: 'Ao usar links locais insira "#" na frente do link',
linknewwindow: 'Abrir em nova janela?',
macroDoesNotHaveProperties: 'Este macro não contém nenhuma propriedade que possa ser editada',
- paste: 'Colar',
permissionsEdit: 'Editar Permissões para',
recycleBinDeleting:
'Os itens na lixeira agora estão sendo removidos. Favor não fechar esta janela enquanto este processo é concluído',
@@ -186,15 +110,12 @@ export default {
"O serviço web regexlib.com está no momento sofrendo dificuldades dos quais não temos controle. Pedimos desculpas pela inconveniência.",
regexSearchHelp:
"Busque por uma expressão regular para adicionar validação à um campo de formulário. Exemplo: 'email', 'zip-code' (código postal), 'URL'",
- removeMacro: 'Remover Macro',
requiredField: 'Campo obrigatório',
sitereindexed: 'Site foi re-indexado',
siterepublished:
'O cache do website foi atualizado. Todo conteúdo publicado está atualizado agora. No entanto, todo conteúdo não publicado ainda permanecerá invisível',
siterepublishHelp:
'O cache do website será atualizado. Todo conteúdo publicado será atualizado, enquanto o conteúdo que não foi publicado permanecerá invisível',
- tableColumns: 'Número de colunas',
- tableRows: 'Número de linhas',
thumbnailimageclickfororiginal: 'Clique para ver a imagem em seu tamanho original',
treepicker: 'Escolha item',
viewCacheItem: 'Ver Item em Cache',
@@ -202,27 +123,23 @@ export default {
dictionaryItem: {
description:
"Editar as diferente versões de linguagem para o item de dicionário '%0%' abaixo. Você pode adicionar mais linguagens sob 'linguagens' no menu à esquerda.",
- displayName: 'Nome da Cultura',
},
editdatatype: {
addPrevalue: 'Adicionar valor prévio',
dataBaseDatatype: 'Tipo de Dados do Banco de Dados',
guid: 'GUID do Editor de Propriedades',
renderControl: 'Editor de Propriedades',
- rteButtons: 'Botões',
rteEnableAdvancedSettings: 'Habilitar configurações avançadas para',
rteEnableContextMenu: 'Habilitar menu de contexto',
rteMaximumDefaultImgSize: 'Tamanho padrão máximo para imagens inseridas',
rteRelatedStylesheets: 'Stylesheets relacionadas',
rteShowLabel: 'Mostrar Rótulo',
- rteWidthAndHeight: 'Largura e altura',
},
errorHandling: {
errorButDataWasSaved:
'Seus dados foram salvos mas antes que possa publicar esta página existem alguns erros que precisam ser concertados:',
errorChangingProviderPassword:
- 'O provedor de membros (Membership provider) atual não suporta alterações de senha (EnablePasswordRetrieval tem que estar definica como true)',
- errorExistsWithoutTab: '%0% já existe',
+ 'O provedor de membros (Membership provider) atual não suporta alterações de senha (EnablePasswordRetrieval tem que estar definida como true)',
errorHeader: 'Houve erros:',
errorHeaderWithoutTab: 'Houve erros:',
errorInPasswordFormat:
@@ -241,118 +158,51 @@ export default {
missingTitle: 'Favor digitar um título',
missingType: 'Favor escolher um tipo',
pictureResizeBiggerThanOrg:
- 'Você está prestes a tornar esta figura maior que o tamanho original. Tem certeza que deseja proceguir?',
+ 'Você está prestes a tornar esta figura maior que o tamanho original. Tem certeza que deseja prosseguir?',
startNodeDoesNotExists: 'Nó inicial removido, favor entrar em contato com seu administrador',
stylesMustMarkBeforeSelect: 'Favor marcar conteúdo antes de alterar o estilo',
- stylesNoStylesOnPage: 'Nenhum estilo ativo disponível',
tableColMergeLeft: 'Favor colocar o cursos à esquerda das duas células que deseja mesclar',
tableSplitNotSplittable: 'Você não pode dividir uma célula que não foi mesclada.',
},
general: {
- about: 'Sobre',
- action: 'Ação',
- add: 'Adicionar',
alias: 'Apelido',
areyousure: 'Tem certeza?',
- border: 'Borda',
- by: 'por',
- cancel: 'Cancelar',
- cellMargin: 'Margem da célula',
- choose: 'Escolher',
- close: 'Fechar',
- closewindow: 'Fechar Janela',
- comment: 'Comentário',
- confirm: 'Confirmar',
constrainProportions: 'Restrições de proporções',
- continue: 'Continuar',
- copy: 'Copiar',
- create: 'Criar',
database: 'Banco de Dados',
- date: 'Data',
default: 'Padrão',
delete: 'Remover',
deleted: 'Removido',
deleting: 'Removendo...',
design: 'Desenho',
- dimensions: 'Dimensões',
down: 'Abaixo',
download: 'Download',
- edit: 'Editar',
- edited: 'Editado',
- elements: 'Elementos',
- email: 'Email',
- error: 'Erro',
findDocument: 'Buscar',
- height: 'Altura',
- help: 'Ajuda',
- icon: 'Ícone',
- import: 'Importar',
- innerMargin: 'Margem interna',
- insert: 'Inserir',
- install: 'Instalar',
- justify: 'Justificar',
- language: 'Idioma',
layout: 'Esboço',
loading: 'Carregando',
locked: 'Travado',
- login: 'Login',
logoff: 'Sair',
logout: 'Logout',
- macro: 'Macro',
- move: 'Mover',
- name: 'Nome',
- new: 'Novo',
next: 'Próximo',
- no: 'Não',
- of: 'de',
- ok: 'OK',
- open: 'Abrir',
- or: 'ou',
password: 'Senha',
- path: 'Caminho',
pleasewait: 'Um momento por favor...',
previous: 'Prévio',
- properties: 'Propriedades',
- reciept: 'Email para receber dados do formulário',
recycleBin: 'Lixeira',
remaining: 'Remanescentes',
rename: 'Renomear',
- renew: 'Renovar',
- retry: 'Tentar novamente',
- rights: 'Permissões',
search: 'Busca',
- server: 'Servidor',
- show: 'Mostrar',
showPageOnSend: 'Mostrar página durante envio',
- size: 'Tamanho',
sort: 'Classificar',
submit: 'Submit',
- type: 'Tipo',
typeToSearch: 'Digite para buscar...',
up: 'Acima',
- update: 'Atualizar',
- upgrade: 'Atualizar',
upload: 'Subir (Upload)',
- url: 'URL',
user: 'Usuário',
username: 'Usuário',
- value: 'Valor',
- view: 'Ver',
welcome: 'Bem Vindo(a)...',
- width: 'Largura',
- yes: 'Sim',
- reorder: 'Reorder',
- reorderDone: 'I am done reordering',
},
graphicheadline: {
- backgroundcolor: 'Cor de fundo',
- bold: 'Negrito',
color: 'Cor do Texto',
font: 'Fonte',
- text: 'Texto',
- },
- headers: {
- page: 'Página',
},
installer: {
databaseErrorCannotConnect: 'O instalador não pôde conectar-se ao banco de dados.',
@@ -426,61 +276,39 @@ export default {
theEndOpenUmbraco:
'Lançar Umbraco \nPara gerenciar seu website, simplesmente abra a área administrativa do Umbraco para começar adicionando conteúdo, atualizando modelos e stylesheets e adicionando nova funcionalidade',
Unavailable: 'Conexão ao banco falhou.',
- Version3: 'Umbraco Versão 3',
- Version4: 'Umbraco Versão 4',
watch: 'Assistir',
welcomeIntro:
'Este assistente irá guiá-lo pelo processo de configuração do Umbraco %0% para uma nova instalação ou atualizando desde verão 3.0.\n \nPressione "próximo" para iniciar o assistente.',
},
- language: {
- cultureCode: 'Código da Cultura',
- displayName: 'Nome da Cultura',
- },
lockout: {
lockoutWillOccur: 'Você está inativo e logout irá ocorrer automaticamente em',
renewSession: 'Renovar agora para salvar seu trabalho',
},
login: {
- greeting0: 'Bem Vindo(a)',
- greeting1: 'Bem Vindo(a)',
- greeting2: 'Bem Vindo(a)',
- greeting3: 'Bem Vindo(a)',
- greeting4: 'Bem Vindo(a)',
- greeting5: 'Bem Vindo(a)',
- greeting6: 'Bem Vindo(a)',
bottomText:
'© 2001 - %0% umbraco.com
',
},
main: {
dashboard: 'Painel',
sections: 'Seções',
- tree: 'Conteúdo',
},
moveOrCopy: {
choose: 'Escolha página acima...',
- copyDone: '%0% foi copiado para %1%',
copyTo: 'Selecione onde o documento %0% deve ser copiado abaixo',
- moveDone: '%0% foi movido para %1%',
moveTo: 'Selecione onde o documento %0% dever ser movido abaixo',
nodeSelected: "foi selecionado como raíz do seu novo conteúdo, clique 'ok' abaixo.",
noNodeSelected: "Nenhum nó selecionado, favor selecionar um nó na lista acima antes de clicar em 'ok'",
notAllowedByContentType: 'O nó atual não é permitido embaixo do nó escolhido por causa de seu tipo',
notAllowedByPath: 'O nó atual não pode ser movido para uma de suas sub-páginas',
- notValid:
- "TRANSLATE ME: 'The action isn't allowed since you have insufficient permissions on 1 or more child documents.'",
},
notifications: {
editNotifications: 'Editar sua notificação para %0%',
notificationsSavedFor: 'Notificações salvas para %0%',
- notifications: 'Notificações',
},
packager: {
chooseLocalPackageText:
'Selecione o pacote em sua máquina clicando no botão Navegar e localizando o pacote. Pacotes Umbraco tem extensão ".umb" ou ".zip".',
- packageAuthor: 'Autor',
- packageDocumentation: 'Documentação',
packageMetaData: 'Dado meta do pacote',
- packageName: 'Nome do pacote',
packageNoItemsHeader: 'Pacote não contém nenhum item',
packageNoItemsText:
'Este arquivo de pacote não contém nenhum item a ser desinstalado. \nVocê pode remover com segurança do seu sistema clicando em "desinstalar pacote" abaixo.',
@@ -489,18 +317,13 @@ export default {
packageRepository: 'Repositório do pacote',
packageUninstallConfirm: 'Confirmar desinstalação',
packageUninstalledHeader: 'Pacote foi desinstalado',
- packageUninstalledText: 'O pacote foi desinstalado com sucesso',
- packageUninstallHeader: 'Desinstalar pacote',
packageUninstallText:
'Você pode de-selecionar itens que não deseja remover neste momento, abaixo. Quando clicar em "confirmar desinstalação" todos os itens selecionados serão removido. \nAviso: quaisquer documentos, mídia, etc dependentes dos itens que forem removidos vão parar de funcionar e podem levar à instabilidade do sistema. Então desinstale com cuidado. Se tiver dúvidas, contate o autor do pacote',
- packageVersion: 'Versão do pacote',
},
paste: {
- doNothing: 'Colar com formatação completa (Não recomendado)',
errorMessage:
'O texto que você está tentando colar contém caracteres ou formatação especial. Isto pode ser causado ao copiar textos diretamente do Microsoft Word. Umbraco pode remover os caracteres ou formatação especial automaticamente para que o conteúdo colado seja mais adequado para a internet.',
removeAll: 'Colar como texto crú sem nenhuma formatação',
- removeSpecialFormattering: 'Colar, mas remover formatação (Recomendado)',
},
publicAccess: {
paAdvanced: 'Proteção baseada em função',
@@ -508,12 +331,10 @@ export default {
'Se você deseja controlar o acesso à página usando autenticação baseada em funções, usando grupos de membros do Umbraco.',
paAdvancedNoGroups:
'Você precisa criar um grupo de membros antes que possa usar autenticação baseada em função.',
- paErrorPage: 'Página de Erro',
paErrorPageHelp: 'Usado quando as pessoas estão logadas, mas não para ter acesso',
paHowWould: 'Escolha como restringir o acesso à esta página',
paIsProtected: '%0% agora está protegido',
paIsRemoved: 'Proteção removida de %0%',
- paLoginPage: 'Página de Login',
paLoginPageHelp: 'Escolha a página que tem o formulário de login',
paRemoveProtection: 'Remover Proteção',
paSelectPages: 'Selecione as páginas que contém o formulário de login e mensagens de erro',
@@ -526,7 +347,6 @@ export default {
contentPublishedFailedByEvent: '%0% não pode ser publicado devido à uma extensão de terceiros que cancelou a ação.',
includeUnpublished: 'Incluir páginas filhas ainda não publicadas',
inProgress: 'Publicação em progresso - favor aguardar...',
- inProgressCounter: '%0% de %1% páginas foram publicadas...',
nodePublish: '%0% foi publicada',
nodePublishAll: '%0% e sub-páginas foram publicadas',
publishAll: 'Publicar %0% e todoas suas sub-páginas',
@@ -537,7 +357,6 @@ export default {
addExternal: 'Adicionar link externo',
addInternal: 'Adicionar link interno',
addlink: 'Adicionar',
- caption: 'Legenda',
internalPage: 'Página interna',
linkurl: 'URL',
modeDown: 'Mover Abaixo',
@@ -553,23 +372,19 @@ export default {
'Isto mostra a versão selecionada como html se você deseja ver as diferenças entre as 2 versões ao mesmo tempo use a visão em diff',
rollbackTo: 'Reverter à',
selectVersion: 'Selecione versão',
- view: 'Ver',
},
scripts: {
editscript: 'Editar arquivo de script',
},
sections: {
concierge: 'Porteiro',
- content: 'Conteúdo',
courier: 'Mensageiro',
developer: 'Desenvolvedor',
installer: 'Assistente de Configuração Umbraco',
media: 'Mídia',
- member: 'Membros',
newsletters: 'Boletins Informativos',
settings: 'Configurações',
statistics: 'Estatísticas',
- translation: 'Tradução',
users: 'Usuários',
},
settings: {
@@ -578,7 +393,6 @@ export default {
'Para importar um tipo de documento encontre o arquivo ".udt" em seu computador clicando em "Navegar" e depois clicando em "Importar"(você pode confirmar na próxima tela)',
newtabname: 'Novo Título da Guia',
nodetype: 'Tipo de Nó',
- objecttype: 'Tipo',
stylesheet: 'Stylesheet',
tab: 'Guia',
tabname: 'Título da Guia',
@@ -612,7 +426,6 @@ export default {
editContentPublishedText: 'e visível no website',
editContentSavedHeader: 'Conteúdo salvo',
editContentSavedText: 'Lembre-se de publicar para tornar as mudanças visíveis',
- editContentSendToPublish: 'Enviado para Aprovação',
editContentSendToPublishText: 'Alterações foram enviadas para aprovação',
editMemberSaved: 'Membro salvo',
editStylesheetPropertySaved: 'Propriedade de Stylesheet salva',
@@ -636,11 +449,9 @@ export default {
editstylesheetproperty: 'Editar propriedade do stylesheet',
nameHelp: 'Nome para identificar a propriedade de estilo no editor de texto rico (richtext)',
preview: 'Prévia',
- styles: 'Estilos',
},
template: {
edittemplate: 'Editar modelo',
- insertContentArea: 'Inserir área de conteúdo',
insertContentAreaPlaceHolder: 'Inserir área de conteúdo em espaço reservado',
insertDictionaryItem: 'Inserir item de dicionário',
insertMacro: 'Inserir Macro',
@@ -649,38 +460,6 @@ export default {
quickGuide: 'Guia rápido para etiquetas de modelos Umbraco',
template: 'Modelo',
},
- grid: {
- media: 'Image',
- macro: 'Macro',
- insertControl: 'Choose type of content',
- chooseLayout: 'Choose a layout',
- addRows: 'Add a row',
- addElement: 'Add content',
- dropElement: 'Drop content',
- settingsApplied: 'Settings applied',
- contentNotAllowed: 'This content is not allowed here',
- contentAllowed: 'This content is allowed here',
- clickToEmbed: 'Click to embed',
- clickToInsertImage: 'Click to insert image',
- placeholderWriteHere: 'Write here...',
- gridLayouts: 'Grid Layouts',
- gridLayoutsDetail:
- 'Layouts are the overall work area for the grid editor, usually you only need one or two different layouts',
- addGridLayout: 'Add Grid Layout',
- addGridLayoutDetail: 'Adjust the layout by setting column widths and adding additional sections',
- rowConfigurations: 'Row configurations',
- rowConfigurationsDetail: 'Rows are predefined cells arranged horizontally',
- addRowConfiguration: 'Add row configuration',
- addRowConfigurationDetail: 'Adjust the row by setting cell widths and adding additional cells',
- columns: 'Columns',
- columnsDetails: 'Total combined number of columns in the grid layout',
- settings: 'Settings',
- settingsDetails: 'Configure what settings editors can change',
- styles: 'Styles',
- stylesDetails: 'Configure what styling editors can change',
- allowAllEditors: 'Allow all editors',
- allowAllRowConfigurations: 'Allow all row configurations',
- },
templateEditor: {
alternativeField: 'Campo alternativo',
alternativeText: 'Texto alternativo',
@@ -689,20 +468,14 @@ export default {
convertLineBreaks: 'Converter Quebra de Linhas',
convertLineBreaksHelp: 'Substitui quebra de linhas com a etiqueta html <br>',
dateOnly: 'Sim, Data somente',
- formatAsDate: 'Formatar como data',
- htmlEncode: 'Codificar HTML',
htmlEncodeHelp: 'Vai substituir caracteres especiais por seus equivalentes em HTML.',
insertedAfter: 'Será inserida após o valor do campo',
insertedBefore: 'Será inserida antes do valor do campo',
lowercase: 'Minúscula',
- none: 'Nenhum',
postContent: 'Inserir após campo',
- preContent: 'Inserir antes do campo',
- recursive: 'Recursivo',
removeParagraph: 'Remover etiquetas de parágrafo',
removeParagraphHelp: 'Removerá quaisquer <P> do começo ao fim do texto',
uppercase: 'Maiúscula',
- urlEncode: 'Codificar URL',
urlEncodeHelp: 'Vai formatar caracteres especiais em URLs',
usedIfAllEmpty: 'Será usado somente quando os valores nos campos acima estiverem vazios',
usedIfEmpty: 'Este campo somente será usado se o campo primário estiver em vazio',
@@ -717,43 +490,24 @@ export default {
"Olá %0%\n\n Este é um email automatizado para informar que o documento '%1%' foi enviado para ser traduzido em '%5%' por %2%.\n\n Vá para http://%3%/translation/details.aspx?id=%4% para editar.\n\n Ou visite o Umbraco para ter uma visão geral das tarefas de tradução\n http://%3%\n\n Tenha um bom dia!\n\n Saudações do robô Umbraco\n ",
noTranslators:
'Nenhum usuário tradutor encontrado. Favor criar um usuário tradutor antes que possa começar a enviar conteúdo para tradução',
- pageHasBeenSendToTranslation: "A página '%0%' foi enviada para tradução",
sendToTranslate: "Enviar página '%0%' para tradução",
- totalWords: 'Total de palavras',
- translateTo: 'Traduzir para',
- translationDone: 'Tradução concluída.',
translationDoneHelp:
'Você pode visualizar as páginas que acaba de traduzir ao clicar abaixo. Se a página original for encontrada você poderá fazer a comparação entre as 2 páginas.',
translationFailed: 'Tradução falhou, o arquivo xml pode estar corrupto',
translationOptions: 'Opções de Tradução',
- translator: 'Tradutor',
uploadTranslationXml: 'Upload Xml de Tradução',
},
treeHeaders: {
- cacheBrowser: 'Navegador de Cache',
contentRecycleBin: 'Lixeira',
- createdPackages: 'Pacotes criados',
dataTypes: 'Tipo de Dado',
- dictionary: 'Dicionário',
- installedPackages: 'Pacotes instalados',
- installSkin: 'Instalar tema',
installStarterKit: 'Instalar kit de iniciante',
languages: 'Linguagens',
- localPackage: 'Instalar pacote local',
- macros: 'Macros',
mediaTypes: 'Tipos de Mídia',
- member: 'Membros',
- memberGroups: 'Grupos de Membros',
memberRoles: 'Funções',
memberTypes: 'Tipo de Membro',
documentTypes: 'Tipos de Documentos',
- packager: 'Pacotes',
- packages: 'Pacotes',
repositories: 'Instalar desde o repositório',
- runway: 'Instalar Runway',
- runwayModules: 'Módulos Runway',
scripting: 'Arquivos de Script',
- scripts: 'Scripts',
stylesheets: 'Stylesheets',
templates: 'Modelos',
userPermissions: 'Permissões de usuário',
@@ -761,24 +515,17 @@ export default {
users: 'Usuários',
},
update: {
- updateAvailable: 'Nova atualização pronta',
updateDownloadText: '%0% está pronto, clique aqui para download',
updateNoServer: 'Nenhuma conexão ao servidor',
updateNoServerError:
'Erro ao procurar por atualização. Favor revisar os detalhes (stack-trace) para mais informações',
},
user: {
- administrators: 'Administrador',
categoryField: 'Campo de Categoria',
changePassword: 'Alterar Sua Senha',
changePasswordDescription:
"você pode alterar sua senha para acessar a área administrativa do Umbraco preenchendo o formulário abaixo e clicando no botão 'Alterar Senha'",
- contentChannel: 'Canal de Conteúdo',
- descriptionField: 'Campo de descrição',
disabled: 'Desabilitar Usuário',
- documentType: 'Tipo de Documento',
- editors: 'Editor',
- excerptField: 'Campo de excerto',
language: 'Linguagem',
loginname: 'Login',
mediastartnode: 'Nó Inicial na Biblioteca de Mídia',
@@ -801,11 +548,8 @@ export default {
usertype: 'Tipo de usuário',
userTypes: 'Tipos de usuários',
writer: 'Escrevente',
- sortCreateDateAscending: 'Mais antigo',
- sortCreateDateDescending: 'Mais recente',
},
logViewer: {
selectAllLogLevelFilters: 'Selecionar tudo',
- deselectAllLogLevelFilters: 'Desmarcar todos',
},
} as UmbLocalizationDictionary;
diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/pt.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/pt.ts
index b079395a31..c830525cbc 100644
--- a/src/Umbraco.Web.UI.Client/src/assets/lang/pt.ts
+++ b/src/Umbraco.Web.UI.Client/src/assets/lang/pt.ts
@@ -9,4 +9,2825 @@
* Language Culture: pt-PT
*/
import type { UmbLocalizationDictionary } from '@umbraco-cms/backoffice/localization-api';
-export default {} as UmbLocalizationDictionary;
+
+export default {
+ actions: {
+ assigndomain: 'Cultura e Domínios',
+ auditTrail: 'Trilho de Auditoria',
+ browse: 'Navegar',
+ changeDataType: 'Alterar Tipo de Dados',
+ changeDocType: 'Alterar Tipo de Documento',
+ chooseWhereToCopy: 'Escolher para onde copiar',
+ chooseWhereToImport: 'Escolher para onde importar',
+ chooseWhereToMove: 'Escolher para onde mover',
+ copy: 'Duplicar',
+ copyTo: 'Duplicar para',
+ create: 'Criar',
+ createblueprint: 'Criar Modelo de Documento',
+ createGroup: 'Criar grupo',
+ createPackage: 'Criar Pacote',
+ delete: 'Eliminar',
+ disable: 'Desativar',
+ editContent: 'Editar conteúdo',
+ editSettings: 'Editar definições',
+ emptyrecyclebin: 'Esvaziar reciclagem',
+ enable: 'Ativar',
+ export: 'Exportar',
+ exportDocumentType: 'Exportar Tipo de Documento',
+ folderCreate: 'Criar pasta',
+ folderDelete: 'Eliminar',
+ folderRename: 'Mudar o nome',
+ import: 'Importar',
+ importdocumenttype: 'Importar Tipo de Documento',
+ importPackage: 'Importar Pacote',
+ infiniteEditorChooseWhereToCopy: 'Escolher para onde copiar os items selecionados',
+ infiniteEditorChooseWhereToMove: 'Escolher para onde mover os items selecionados',
+ liveEdit: 'Editar em Tela',
+ logout: 'Terminar sessão',
+ move: 'Mover para',
+ notify: 'Notificações',
+ protect: 'Acesso Público',
+ publish: 'Publicar',
+ read: 'Ler',
+ readOnly: 'Apenas leitura',
+ refreshNode: 'Atualizar filhos',
+ remove: 'Remover',
+ rename: 'Mudar o nome',
+ republish: 'Republicar site inteiro',
+ resendInvite: 'Reenviar Convite',
+ restore: 'Restaurar',
+ rights: 'Permissões',
+ rollback: 'Reverter',
+ sendtopublish: 'Enviar para Publicação',
+ sendToTranslate: 'Enviar para Tradução',
+ setGroup: 'Definir grupo',
+ setPermissions: 'Definir permissões',
+ sort: 'Ordenar filhos',
+ toInTheTreeStructureBelow: 'para na estrutura de árvore abaixo',
+ translate: 'Traduzir',
+ trash: 'Lixo',
+ unlock: 'Desbloquear',
+ unpublish: 'Despublicar',
+ update: 'Atualizar',
+ wasCopiedTo: 'foi copiado para',
+ wasDeleted: 'foi eliminado',
+ wasMovedTo: 'foi movido para',
+ viewActionsFor: (name) => (name ? `Ver ações para '${name}'` : 'Ver ações'),
+ },
+ actionCategories: {
+ content: 'Conteúdo',
+ administration: 'Administração',
+ structure: 'Estrutura',
+ other: 'Outro',
+ },
+ actionDescriptions: {
+ assignDomain: 'Permitir acesso para atribuir cultura e domínios',
+ auditTrail: 'Permitir acesso para visualizar os logs de histórico de um Documento',
+ browse: 'Permitir acesso para visualizar um Documento',
+ changeDocType: 'Permitir acesso para alterar o Tipo de Documento de um documento',
+ copy: 'Permitir acesso para copiar um Documento',
+ create: 'Permitir acesso para criar um Documento',
+ delete: 'Permitir acesso para eliminar um Documento',
+ move: 'Permitir acesso para mover um Documento',
+ protect: 'Permitir acesso para definir e alterar restrições de acesso para um Documento',
+ publish: 'Permitir acesso para publicar um Documento',
+ unpublish: 'Permitir acesso para despublicar um Documento',
+ read: 'Permitir acesso para ler um Documento',
+ rights: 'Permitir acesso para alterar permissões de um Documento',
+ rollback: 'Permitir acesso para reverter um Documento para um estado anterior',
+ sendtopublish: 'Permitir acesso para enviar um Documento para aprovação antes de publicar',
+ sendToTranslate: 'Permitir acesso para enviar um Documento para tradução',
+ sort: 'Permitir acesso para alterar a ordem de Documentos',
+ translate: 'Permitir acesso para traduzir um Documento',
+ update: 'Permitir acesso para guardar um Documento',
+ createblueprint: 'Permitir acesso para criar um Modelo de Documento',
+ notify: 'Permitir acesso para configurar notificações para Documentos',
+ },
+ apps: {
+ umbContent: 'Conteúdo',
+ umbInfo: 'Informação',
+ },
+ assignDomain: {
+ permissionDenied: 'Permissão negada.',
+ addNew: 'Adicionar novo domínio',
+ addCurrent: 'Adicionar domínio atual',
+ remove: 'remover',
+ invalidNode: 'Nó inválido.',
+ invalidDomain: 'Um ou mais domínios têm um formato inválido.',
+ duplicateDomain: 'O domínio já foi atribuído.',
+ language: 'Idioma',
+ domain: 'Domínio',
+ domainCreated: "Novo domínio '%0%' foi criado",
+ domainDeleted: "Domínio '%0%' foi eliminado",
+ domainExists: "Domínio '%0%' já foi atribuído",
+ domainUpdated: "Domínio '%0%' foi atualizado",
+ orEdit: 'Editar Domínios Atuais',
+ domainHelpWithVariants:
+ 'Nomes de domínio válidos são: "example.com", "www.example.com", "example.com:8080", ou "https://www.example.com/". Além disso, também são suportados caminhos de um nível em domínios, por exemplo, "example.com/en" ou "/en".',
+ inherit: 'Herdar',
+ setLanguage: 'Cultura',
+ setLanguageHelp:
+ 'Defina a cultura para os nós abaixo do nó atual, ou herde a cultura dos nós pais. Também se aplicará ao nó atual, a menos que um domínio abaixo também se aplique.',
+ setDomains: 'Domínios',
+ },
+ buttons: {
+ clearSelection: 'Limpar seleção',
+ select: 'Selecionar',
+ choose: 'Escolher',
+ somethingElse: 'Fazer outra coisa',
+ bold: 'Negrito',
+ deindent: 'Cancelar Indentação de Parágrafo',
+ formFieldInsert: 'Inserir campo de formulário',
+ graphicHeadline: 'Inserir título gráfico',
+ htmlEdit: 'Editar Html',
+ indent: 'Indentar Parágrafo',
+ italic: 'Itálico',
+ justifyCenter: 'Centrar',
+ justifyLeft: 'Justificar à Esquerda',
+ justifyRight: 'Justificar à Direita',
+ linkInsert: 'Inserir Link',
+ linkLocal: 'Inserir link local (âncora)',
+ listBullet: 'Lista com Marcas',
+ listNumeric: 'Lista Numérica',
+ macroInsert: 'Inserir macro',
+ pictureInsert: 'Inserir imagem',
+ publishAndClose: 'Publicar e fechar',
+ publishDescendants: 'Publicar com descendentes',
+ relations: 'Editar relações',
+ returnToList: 'Voltar à lista',
+ save: 'Guardar',
+ saveAndClose: 'Guardar e fechar',
+ saveAndPublish: 'Guardar e publicar',
+ saveToPublish: 'Guardar e enviar para aprovação',
+ saveListView: 'Guardar vista de lista',
+ schedulePublish: 'Agendar publicação',
+ saveAndPreview: 'Guardar e pré-visualizar',
+ showPageDisabled: 'A pré-visualização está desativada porque não há nenhum modelo atribuído',
+ styleChoose: 'Escolher estilo',
+ styleShow: 'Mostrar estilos',
+ tableInsert: 'Inserir tabela',
+ generateModelsAndClose: 'Gerar modelos e fechar',
+ saveAndGenerateModels: 'Guardar e gerar modelos',
+ undo: 'Anular',
+ redo: 'Refazer',
+ deleteTag: 'Eliminar tag',
+ confirmActionCancel: 'Cancelar',
+ confirmActionConfirm: 'Confirmar',
+ morePublishingOptions: 'Mais opções de publicação',
+ submitChanges: 'Submeter',
+ },
+ auditTrailsMedia: {
+ delete: 'Multimédia eliminada',
+ move: 'Multimédia movida',
+ copy: 'Multimédia copiada',
+ save: 'Multimédia guardada',
+ },
+ auditTrails: {
+ assigndomain: 'Domínio atribuído: %0%',
+ atViewingFor: 'A visualizar para',
+ delete: 'Conteúdo eliminado',
+ unpublish: 'Conteúdo despublicado',
+ unpublishvariant: 'Conteúdo despublicado para os idiomas: %0%',
+ publish: 'Conteúdo guardado e Publicado',
+ publishvariant: 'Conteúdo guardado e publicado para os idiomas: %0%',
+ save: 'Conteúdo guardado',
+ savevariant: 'Conteúdo guardado para os idiomas: %0%',
+ move: 'Conteúdo movido',
+ copy: 'Conteúdo copiado',
+ rollback: 'Conteúdo revertido',
+ sendtopublish: 'Conteúdo enviado para publicação',
+ sendtopublishvariant: 'Conteúdo enviado para publicação para os idiomas: %0%',
+ sort: 'Ordenação de itens filhos realizada pelo utilizador',
+ custom: '%0%',
+ contentversionpreventcleanup: 'Limpeza desativada para a versão: %0%',
+ contentversionenablecleanup: 'Limpeza ativada para a versão: %0%',
+ smallAssignDomain: 'Atribuir Domínio',
+ smallCopy: 'Copiar',
+ smallPublish: 'Publicar',
+ smallPublishVariant: 'Publicar',
+ smallMove: 'Mover',
+ smallSave: 'Guardar',
+ smallSaveVariant: 'Guardar',
+ smallDelete: 'Eliminar',
+ smallUnpublish: 'Despublicar',
+ smallUnpublishVariant: 'Despublicar',
+ smallRollBack: 'Reverter',
+ smallSendToPublish: 'Enviar para Publicação',
+ smallSendToPublishVariant: 'Enviar para Publicação',
+ smallSort: 'Ordenar',
+ smallCustom: 'Personalizado',
+ smallContentVersionPreventCleanup: 'Guardar',
+ smallContentVersionEnableCleanup: 'Guardar',
+ historyIncludingVariants: 'Histórico (todas as variantes)',
+ },
+ codefile: {
+ createFolderIllegalChars: 'O nome da pasta não pode conter caracteres ilegais.',
+ deleteItemFailed: 'Falha ao eliminar o item: %0%',
+ },
+ collection: {
+ noItemsTitle: 'Sem itens',
+ addCollectionConfiguration: 'Adicionar coleção',
+ },
+ content: {
+ isPublished: 'Está Publicado',
+ about: 'Sobre esta página',
+ alias: 'Alias',
+ alternativeTextHelp: '(como descreveria a imagem por telefone)',
+ alternativeUrls: 'Links Alternativos',
+ clickToEdit: 'Clique para editar este item',
+ createBy: 'Criado por',
+ createByDesc: 'Autor original',
+ updatedBy: 'Atualizado por',
+ createDate: 'Criado',
+ createDateDesc: 'Data/hora em que este documento foi criado',
+ documentType: 'Tipo de Documento',
+ editing: 'A editar',
+ expireDate: 'Remover em',
+ itemChanged: 'Este item foi alterado após a publicação',
+ itemNotPublished: 'Este item não está publicado',
+ lastPublished: 'Última publicação',
+ noItemsToShow: 'Não há itens para mostrar',
+ listViewNoItems: 'Não há itens para mostrar na lista.',
+ listViewNoContent: 'Nenhum conteúdo foi adicionado',
+ listViewNoMembers: 'Nenhum membro foi adicionado',
+ mediatype: 'Tipo de Multimédia',
+ mediaLinks: 'Link para itens de multimédia',
+ membergroup: 'Grupo de Membros',
+ memberrole: 'Função',
+ membertype: 'Tipo de Membro',
+ noChanges: 'Nenhuma alteração foi feita',
+ noDate: 'Nenhuma data escolhida',
+ nodeName: 'Título da página',
+ noMediaLink: 'Este item de multimédia não tem links',
+ noProperties: 'Nenhum conteúdo pode ser adicionado para este item',
+ otherElements: 'Propriedades',
+ parentNotPublished: "Este documento está publicado mas não está visível porque o pai '%0%' não está publicado",
+ parentCultureNotPublished:
+ "Esta cultura está publicada mas não está visível porque não está publicada no pai '%0%'",
+ parentNotPublishedAnomaly: 'Este documento está publicado mas não está na cache',
+ getUrlException: 'Não foi possível obter o URL',
+ routeError: 'Este documento está publicado mas o seu URL colidiria com o conteúdo %0%',
+ routeErrorCannotRoute: 'Este documento está publicado mas o seu URL não pode ser mapeado',
+ publish: 'Publicar',
+ published: 'Publicado',
+ publishedPendingChanges: 'Publicado (alterações pendentes)',
+ publishStatus: 'Estado da Publicação',
+ publishDescendantsHelp:
+ 'Publicar %0% e todos os itens abaixo e, assim, tornar o seu conteúdo publicamente disponível.',
+ publishDescendantsWithVariantsHelp:
+ 'Publicar variantes e variantes do mesmo tipo abaixo e, assim, tornar o seu conteúdo publicamente disponível.',
+ noVariantsToProcess: 'Não há variantes disponíveis',
+ releaseDate: 'Publicar em',
+ unpublishDate: 'Despublicar em',
+ removeDate: 'Limpar data',
+ setDate: 'Definir data',
+ sortDone: 'A ordem foi atualizada',
+ sortHelp:
+ 'Para ordenar os nós, simplesmente arraste os nós ou clique num dos cabeçalhos das colunas. Pode selecionar múltiplos nós mantendo premida a tecla "shift" ou "control" enquanto seleciona',
+ statistics: 'Estatísticas',
+ titleOptional: 'Título (opcional)',
+ altTextOptional: 'Texto alternativo (opcional)',
+ captionTextOptional: 'Legenda (opcional)',
+ type: 'Tipo',
+ unpublish: 'Despublicar',
+ unpublished: 'Despublicado',
+ notCreated: 'Não criado',
+ updateDate: 'Última edição',
+ updateDateDesc: 'Data/hora em que este documento foi editado',
+ uploadClear: 'Remover ficheiro(s)',
+ uploadClearImageContext: 'Clique aqui para remover a imagem do item de multimédia',
+ uploadClearFileContext: 'Clique aqui para remover o ficheiro do item de multimédia',
+ urls: 'Link para o documento',
+ memberof: 'Membro de grupo(s)',
+ notmemberof: 'Não é membro de grupo(s)',
+ childItems: 'Itens filhos',
+ target: 'Destino',
+ scheduledPendingChanges: 'Este agendamento tem alterações que entrarão em vigor quando clicar em "%0%".',
+ scheduledPublishServerTime: 'Isto traduz-se na seguinte hora no servidor:',
+ scheduledPublishDocumentation:
+ 'O que significa isto? ',
+ nestedContentDeleteItem: 'Tem a certeza que quer eliminar este item?',
+ nestedContentDeleteAllItems: 'Tem a certeza que quer eliminar todos os itens?',
+ nestedContentEditorNotSupported: 'A propriedade %0% usa o editor %1% que não é suportado pelo Nested Content.',
+ nestedContentNoContentTypes: 'Nenhum Tipo de Conteúdo está configurado para esta propriedade.',
+ nestedContentAddElementType: 'Adicionar Tipo de Elemento',
+ nestedContentSelectElementTypeModalTitle: 'Selecionar Tipo de Elemento',
+ nestedContentGroupHelpText:
+ 'Selecione o grupo cujas propriedades devem ser exibidas. Se deixado em branco, o primeiro grupo no Tipo de Elemento será usado.',
+ nestedContentTemplateHelpTextPart1: 'Insira uma expressão angular para avaliar em cada item para o seu nome. Use',
+ nestedContentTemplateHelpTextPart2: 'para exibir o índice do item',
+ nestedContentNoGroups:
+ 'O tipo de elemento selecionado não contém quaisquer grupos suportados (os separadores não são suportados por este editor, altere-os para grupos ou use o editor de Block List).',
+ addTextBox: 'Adicionar outra caixa de texto',
+ removeTextBox: 'Remover esta caixa de texto',
+ contentRoot: 'Raiz do conteúdo',
+ includeUnpublished: 'Incluir itens não publicados.',
+ isSensitiveValue:
+ 'Este valor está oculto. Se precisar de acesso para visualizar este valor, entre em contacto com o administrador do seu site.',
+ isSensitiveValue_short: 'Este valor está oculto.',
+ languagesToPublish: 'Que idiomas gostaria de publicar?',
+ languagesToSendForApproval: 'Que idiomas gostaria de enviar para aprovação?',
+ languagesToSchedule: 'Que idiomas gostaria de agendar?',
+ languagesToUnpublish:
+ 'Selecione os idiomas a despublicar. Despublicar um idioma obrigatório despublicará todos os idiomas.',
+ variantsWillBeSaved: 'Todas as novas variantes serão guardadas.',
+ variantsToPublish: 'Quais variantes gostaria de publicar?',
+ variantsToSave: 'Escolha quais variantes devem ser guardadas.',
+ publishRequiresVariants: 'As seguintes variantes são necessárias para que a publicação ocorra:',
+ notReadyToPublish: 'Não estamos prontos para Publicar',
+ readyToPublish: 'Pronto para publicar?',
+ readyToSave: 'Pronto para Guardar?',
+ resetFocalPoint: 'Redefinir ponto focal',
+ sendForApproval: 'Enviar para aprovação',
+ schedulePublishHelp: 'Selecione a data e hora para publicar e/ou despublicar o item de conteúdo.',
+ createEmpty: 'Criar novo',
+ createFromClipboard: 'Colar da área de transferência',
+ nodeIsInTrash: 'Este item está na Reciclagem',
+ variantSaveNotAllowed: 'Guardar não é permitido',
+ variantPublishNotAllowed: 'Publicar não é permitido',
+ variantSendForApprovalNotAllowed: 'Enviar para aprovação não é permitido',
+ variantScheduleNotAllowed: 'Agendar não é permitido',
+ variantUnpublishNotAllowed: 'Despublicar não é permitido',
+ selectAllVariants: 'Selecionar todas as variantes',
+ saveModalTitle: 'Guardar',
+ saveAndPublishModalTitle: 'Guardar e publicar',
+ publishModalTitle: 'Publicar',
+ },
+ blueprints: {
+ createBlueprintFrom: "Criar um novo Modelo de Documento a partir de '%0%'",
+ createBlueprintItemUnder: "Criar um novo item em '%0%'",
+ createBlueprintFolderUnder: "Criar uma nova pasta em '%0%'",
+ blankBlueprint: 'Em Branco',
+ selectBlueprint: 'Selecionar um Modelo de Documento',
+ createdBlueprintHeading: 'Modelo de Documento criado',
+ createdBlueprintMessage: "Um Modelo de Documento foi criado a partir de '%0%'",
+ duplicateBlueprintMessage: 'Já existe outro Modelo de Documento com o mesmo nome',
+ blueprintDescription:
+ 'Um Modelo de Documento é conteúdo predefinido que um editor pode selecionar para usar como base para criar novo conteúdo',
+ },
+ entityDetail: {
+ notFoundTitle: (entityType: string) => {
+ const entityName = entityType ?? 'Item';
+ return `${entityName} não encontrado`;
+ },
+ notFoundDescription: (entityType: string) => {
+ const entityName = entityType ?? 'item';
+ return `O ${entityName} solicitado não pôde ser encontrado. Por favor, verifique o URL e tente novamente.`;
+ },
+ },
+ media: {
+ clickToUpload: 'Clique para carregar',
+ orClickHereToUpload: 'ou clique aqui para escolher ficheiros',
+ disallowedFileType: 'Não é possível carregar este ficheiro, não tem um tipo de ficheiro aprovado',
+ disallowedMediaType:
+ "Não é possível carregar este ficheiro, o tipo de multimédia com o alias '%0%' não é permitido aqui",
+ invalidFileName: 'Não é possível carregar este ficheiro, não tem um nome de ficheiro válido',
+ invalidFileSize: 'Não é possível carregar este ficheiro, é demasiado grande',
+ maxFileSize: 'O tamanho máximo do ficheiro é',
+ mediaRoot: 'Raiz da multimédia',
+ createFolderFailed: 'Falha ao criar uma pasta sob o pai com id %0%',
+ renameFolderFailed: 'Falha ao mudar o nome da pasta com o id %0%',
+ dragAndDropYourFilesIntoTheArea: 'Arraste e largue o(s) seu(s) ficheiro(s) na área',
+ fileSecurityValidationFailure: 'Uma ou mais validações de segurança de ficheiros falharam',
+ moveToSameFolderFailed: 'As pastas pai e destino não podem ser as mesmas',
+ uploadNotAllowed: 'O carregamento não é permitido nesta localização.',
+ },
+ member: {
+ '2fa': 'Autenticação de Dois Fatores',
+ allMembers: 'Todos os Membros',
+ createNewMember: 'Criar novo membro',
+ duplicateMemberLogin: 'Já existe um membro com este login',
+ kind: 'Tipo',
+ memberGroupNoProperties: 'Os grupos de membros não têm propriedades adicionais para edição.',
+ memberHasGroup: "O membro já está no grupo '%0%'",
+ memberHasPassword: 'O membro já tem uma palavra-passe definida',
+ memberKindDefault: 'Membro',
+ memberKindApi: 'Membro API',
+ memberLockoutNotEnabled: 'O bloqueio não está ativado para este membro',
+ memberNotInGroup: "O membro não está no grupo '%0%'",
+ },
+ contentType: {
+ copyFailed: 'Falha ao copiar tipo de conteúdo',
+ moveFailed: 'Falha ao mover tipo de conteúdo',
+ contentTypes: 'Tipos de Conteúdo',
+ },
+ mediaType: {
+ copyFailed: 'Falha ao copiar tipo de multimédia',
+ moveFailed: 'Falha ao mover tipo de multimédia',
+ autoPickMediaType: 'Escolha automática',
+ },
+ memberType: {
+ copyFailed: 'Falha ao copiar tipo de membro',
+ },
+ create: {
+ chooseNode: 'Onde quer criar o novo %0%',
+ createUnder: 'Criar um item em',
+ createContentBlueprint: 'Selecione o Tipo de Documento para o qual quer criar um Modelo de Documento',
+ enterFolderName: 'Introduza um nome de pasta',
+ updateData: 'Escolha um tipo e um título',
+ noDocumentTypes:
+ 'Não existem Tipos de Documento permitidos disponíveis para criar conteúdo aqui. Deve ativá-los em Tipos de Documento na secção Definições , editando os Tipos de nó filho permitidos em Permissões .',
+ noDocumentTypesAtRoot:
+ 'Não existem Tipos de Documento disponíveis para criar conteúdo aqui. Deve criá-los em Tipos de Documento na secção Definições .',
+ noDocumentTypesWithNoSettingsAccess:
+ 'A página selecionada na árvore de conteúdo não permite a criação de nenhuma página abaixo dela.',
+ noDocumentTypesEditPermissions: 'Editar permissões para este Tipo de Documento',
+ noDocumentTypesCreateNew: 'Criar um novo Tipo de Documento',
+ noDocumentTypesAllowedAtRoot:
+ 'Não existem Tipos de Documento permitidos disponíveis para criar conteúdo aqui. Deve ativá-los em Tipos de Documento na secção Definições , alterando a opção Permitir como raiz em Permissões .',
+ noMediaTypes:
+ 'Não existem Tipos de Multimédia permitidos disponíveis para criar multimédia aqui. Deve ativá-los em Tipos de Multimédia na secção Definições , editando os Tipos de nó filho permitidos em Permissões .',
+ noMediaTypesWithNoSettingsAccess:
+ 'A multimédia selecionada na árvore não permite a criação de nenhuma outra multimédia abaixo dela.',
+ noMediaTypesEditPermissions: 'Editar permissões para este Tipo de Multimédia',
+ documentTypeWithoutTemplate: 'Tipo de Documento sem template',
+ documentTypeWithTemplate: 'Tipo de Documento com Template',
+ documentTypeWithTemplateDescription:
+ 'A definição de dados para uma página de conteúdo que pode ser criada por editores na árvore de conteúdo e é diretamente acessível através de um URL.',
+ documentType: 'Tipo de Documento',
+ documentTypeDescription:
+ 'A definição de dados para um componente de conteúdo que pode ser criado por editores na árvore de conteúdo e ser escolhido noutras páginas, mas não tem URL direto.',
+ elementType: 'Tipo de Elemento',
+ elementTypeDescription:
+ "Define o esquema para um conjunto repetido de propriedades, por exemplo, num editor de propriedades 'Block List' ou 'Block Grid'.",
+ composition: 'Composição',
+ compositionDescription:
+ "Define um conjunto reutilizável de propriedades que pode ser incluído na definição de múltiplos outros Tipos de Documento. Por exemplo, um conjunto de 'Definições Comuns de Página'.",
+ folder: 'Pasta',
+ folderDescription: 'Usado para organizar itens e outras pastas. Mantém os itens estruturados e de fácil acesso.',
+ newFolder: 'Nova pasta',
+ newDataType: 'Novo Tipo de Dados',
+ newDataTypeDescription: 'Usado para definir uma configuração para um Tipo de Propriedade num Tipo de Conteúdo.',
+ newJavascriptFile: 'Novo ficheiro JavaScript',
+ newEmptyPartialView: 'Nova Vista Parcial Vazia',
+ newPartialViewMacro: 'Nova Macro de Vista Parcial',
+ newPartialViewFromSnippet: 'Nova vista parcial a partir de fragmento',
+ newPartialViewMacroFromSnippet: 'Nova macro de vista parcial a partir de fragmento',
+ newPartialViewMacroNoMacro: 'Nova macro de vista parcial (sem macro)',
+ newStyleSheetFile: 'Nova Stylesheet',
+ },
+ dashboard: {
+ browser: 'Navegue no seu website',
+ dontShowAgain: '- Ocultar',
+ nothinghappens: 'Se o Umbraco não estiver a abrir, poderá ter de permitir popups deste site',
+ openinnew: 'abriu numa nova janela',
+ restart: 'Reiniciar',
+ visit: 'Visitar',
+ welcome: 'Bem-vindo(a)',
+ },
+ prompt: {
+ stay: 'Ficar',
+ discardChanges: 'Descartar alterações',
+ unsavedChanges: 'Descartar alterações não guardadas',
+ unsavedChangesWarning: 'Tem a certeza que quer sair desta página? Tem alterações não guardadas',
+ confirmListViewPublish: 'A publicação tornará os itens selecionados publicamente disponíveis.',
+ confirmListViewUnpublish:
+ 'A despublicação tornará os itens selecionados e todos os seus descendentes publicamente indisponíveis.',
+ confirmPublish:
+ 'A publicação tornará este conteúdo e todos os seus descendentes publicados publicamente disponíveis.',
+ confirmUnpublish: 'A despublicação tornará este conteúdo publicamente indisponível.',
+ doctypeChangeWarning:
+ 'Tem alterações não guardadas. Fazer alterações ao Tipo de Documento descartará as alterações.',
+ },
+ bulk: {
+ done: 'Concluído',
+ deletedItem: 'Eliminado %0% item',
+ deletedItems: 'Eliminados %0% itens',
+ deletedItemOfItem: 'Eliminado %0% de %1% item',
+ deletedItemOfItems: 'Eliminados %0% de %1% itens',
+ publishedItem: 'Publicado %0% item',
+ publishedItems: 'Publicados %0% itens',
+ publishedItemOfItem: 'Publicado %0% de %1% item',
+ publishedItemOfItems: 'Publicados %0% de %1% itens',
+ unpublishedItem: 'Despublicado %0% item',
+ unpublishedItems: 'Despublicados %0% itens',
+ unpublishedItemOfItem: 'Despublicado %0% de %1% item',
+ unpublishedItemOfItems: 'Despublicados %0% de %1% itens',
+ movedItem: 'Movido %0% item',
+ movedItems: 'Movidos %0% itens',
+ movedItemOfItem: 'Movido %0% de %1% item',
+ movedItemOfItems: 'Movidos %0% de %1% itens',
+ copiedItem: 'Copiado %0% item',
+ copiedItems: 'Copiados %0% itens',
+ copiedItemOfItem: 'Copiado %0% de %1% item',
+ copiedItemOfItems: 'Copiados %0% de %1% itens',
+ },
+ defaultdialogs: {
+ nodeNameLinkPicker: 'Título',
+ urlLinkPicker: 'Link',
+ anchorLinkPicker: 'Âncora ou querystring',
+ anchorInsert: 'Nome',
+ closeThisWindow: 'Fechar esta janela',
+ confirmdelete: (name: string) => `Tem a certeza que quer eliminar${name ? ` ${name} ` : ''}?`,
+ confirmdeleteNumberOfItems: 'Tem a certeza que quer eliminar %0% de %1% itens',
+ confirmdisable: 'Tem a certeza que quer desativar',
+ confirmremove: 'Tem a certeza que quer remover',
+ confirmremoveusageof: 'Tem a certeza que quer remover o uso de %0% ',
+ confirmlogout: 'Tem a certeza?',
+ confirmSure: 'Tem a certeza?',
+ confirmTrash: (name: string) => `Tem a certeza que quer mover ${name} para a Reciclagem?`,
+ confirmBulkTrash: (total: number) =>
+ `Tem a certeza que quer mover ${total} ${total === 1 ? 'item' : 'itens'} para a Reciclagem?`,
+ confirmBulkDelete: (total: number) =>
+ `Tem a certeza que quer eliminar ${total} ${total === 1 ? 'item' : 'itens'} ?`,
+ cut: 'Cortar',
+ editDictionary: 'Editar item do dicionário',
+ editLanguage: 'Editar idioma',
+ editSelectedMedia: 'Editar multimédia selecionada',
+ editWebhook: 'Editar webhook',
+ insertAnchor: 'Inserir link local',
+ insertCharacter: 'Inserir carácter',
+ insertgraphicheadline: 'Inserir título gráfico',
+ insertimage: 'Inserir imagem',
+ insertlink: 'Inserir link',
+ insertMacro: 'Clique para adicionar uma Macro',
+ inserttable: 'Inserir tabela',
+ languagedeletewarning: 'Isto eliminará o idioma e todo o conteúdo relacionado com o idioma',
+ languageChangeWarning:
+ 'Alterar a cultura de um idioma pode ser uma operação dispendiosa e resultará na reconstrução da cache de conteúdo e dos índices',
+ lastEdited: 'Última edição',
+ link: 'Link',
+ linkinternal: 'Link interno',
+ linklocaltip: 'Ao usar links locais, insira "#" à frente do link',
+ linknewwindow: 'Abrir numa nova janela?',
+ macroDoesNotHaveProperties: 'Esta macro não contém quaisquer propriedades que possa editar',
+ paste: 'Colar',
+ permissionsEdit: 'Editar permissões para',
+ permissionsSet: 'Definir permissões para',
+ permissionsSetForGroup: 'Definir permissões para %0% para o grupo de utilizadores %1%',
+ permissionsHelp: 'Selecione os grupos de utilizadores para os quais quer definir permissões',
+ recycleBinDeleting:
+ 'Os itens na reciclagem estão agora a ser eliminados. Por favor, não feche esta janela enquanto esta operação está a decorrer',
+ recycleBinIsEmpty: 'A reciclagem está agora vazia',
+ recycleBinWarning: 'Quando os itens são eliminados da reciclagem, desaparecerão para sempre',
+ regexSearchError:
+ "O serviço web de regexlib.com está atualmente com alguns problemas, sobre os quais não temos controlo. Lamentamos muito este inconveniente.",
+ regexSearchHelp:
+ "Procure uma expressão regular para adicionar validação a um campo de formulário. Exemplo: 'email, 'código postal', 'URL'.",
+ removeMacro: 'Remover Macro',
+ requiredField: 'Campo Obrigatório',
+ sitereindexed: 'O site foi reindexado',
+ siterepublished:
+ 'A cache do website foi atualizada. Todo o conteúdo publicado está agora atualizado. Enquanto todo o conteúdo não publicado continua não publicado',
+ siterepublishHelp:
+ 'A cache do website será atualizada. Todo o conteúdo publicado será atualizado, enquanto o conteúdo não publicado permanecerá não publicado.',
+ tableColumns: 'Número de colunas',
+ tableRows: 'Número de linhas',
+ thumbnailimageclickfororiginal: 'Clique na imagem para ver o tamanho completo',
+ treepicker: 'Escolher item',
+ viewCacheItem: 'Ver Item da Cache',
+ relateToOriginalLabel: 'Relacionar com o original',
+ includeDescendants: 'Incluir descendentes',
+ theFriendliestCommunity: 'A comunidade mais amigável',
+ linkToPage: 'Link para o documento',
+ openInNewWindow: 'Abre o link numa nova janela ou separador',
+ linkToMedia: 'Link para multimédia',
+ selectContentStartNode: 'Selecionar nó de início de conteúdo',
+ selectEvent: 'Selecionar evento',
+ selectMedia: 'Selecionar multimédia',
+ chooseMedia: 'Escolher multimédia',
+ chooseMediaStartNode: 'Escolher nós de início de Multimédia',
+ selectMediaType: 'Selecionar tipo de multimédia',
+ selectIcon: 'Selecionar ícone',
+ selectItem: 'Selecionar item',
+ selectLink: 'Configurar link',
+ addLink: 'Adicionar Link',
+ updateLink: 'Atualizar Link',
+ selectMacro: 'Selecionar macro',
+ selectContent: 'Selecionar conteúdo',
+ selectContentType: 'Selecionar tipo de conteúdo',
+ selectMediaStartNode: 'Selecionar nó de início de multimédia',
+ selectMember: 'Escolher membro',
+ selectMembers: 'Escolher membros',
+ selectMemberGroup: 'Selecionar grupo de membros',
+ chooseMemberGroup: 'Escolher grupo de membros',
+ selectMemberType: 'Selecionar tipo de membro',
+ selectNode: 'Selecionar nó',
+ selectLanguages: 'Selecionar idiomas',
+ selectSections: 'Selecionar secções',
+ selectUser: 'Selecionar utilizador',
+ selectUsers: 'Selecionar utilizadores',
+ chooseUsers: 'Escolher utilizadores',
+ noIconsFound: 'Nenhum ícone foi encontrado',
+ noMacroParams: 'Não existem parâmetros para esta macro',
+ noMacros: 'Não existem macros disponíveis para inserir',
+ externalLoginProviders: 'Logins externos',
+ exceptionDetail: 'Detalhes da Exceção',
+ stacktrace: 'Rastreio da Pilha',
+ innerException: 'Exceção Interna',
+ linkYour: 'Ligue a sua conta {0}',
+ linkYourConfirm: 'Está prestes a ligar as suas contas Umbraco e {0} e será redirecionado para {0} para confirmar.',
+ unLinkYour: 'Desligue a sua conta {0}',
+ unLinkYourConfirm: 'Está prestes a desligar as suas contas Umbraco e {0} e será desconectado.',
+ linkedToService: 'A sua conta está ligada a este serviço',
+ selectEditor: 'Selecionar editor',
+ selectEditorConfiguration: 'Selecionar configuração',
+ selectSnippet: 'Selecionar fragmento',
+ variantdeletewarning:
+ 'Isto eliminará o nó e todos os seus idiomas. Se quiser eliminar apenas um idioma, deve despublicar o nó nesse idioma.',
+ propertyuserpickerremovewarning: 'Isto removerá o utilizador %0% .',
+ userremovewarning: 'Isto removerá o utilizador %0% do grupo %1% ',
+ yesRemove: 'Sim, remover',
+ deleteLayout: 'Está a eliminar o layout',
+ deletingALayout:
+ 'Modificar o layout resultará na perda de dados para qualquer conteúdo existente que seja baseado nesta configuração.',
+ seeErrorAction: 'Ver erro',
+ seeErrorDialogHeadline: 'Detalhes do erro',
+ },
+ dictionary: {
+ importDictionaryItemHelp:
+ 'Para importar um item do dicionário, encontre o ficheiro ".udt" no seu computador clicando no botão "Adicionar" (ser-lhe-á pedida confirmação no ecrã seguinte).',
+ itemDoesNotExists: 'O item do dicionário não existe.',
+ parentDoesNotExists: 'O item pai não existe.',
+ noItems: 'Não existem itens no dicionário.',
+ noItemsInFile: 'Não existem itens do dicionário neste ficheiro.',
+ noItemsFound: 'Não foram encontrados itens do dicionário.',
+ createNew: 'Criar item do dicionário',
+ pickFile: 'Selecionar ficheiro',
+ pickFileRequired: 'Por favor, selecione um ficheiro ".udt"',
+ },
+ dictionaryItem: {
+ description: "Edite as diferentes versões de idioma para o item do dicionário '%0%' abaixo",
+ displayName: 'Nome da Cultura',
+ changeKeyError: "A chave '%0%' já existe.",
+ overviewTitle: 'Visão geral do dicionário',
+ },
+ examineManagement: {
+ configuredSearchers: 'Pesquisadores Configurados',
+ configuredSearchersDescription:
+ 'Mostra propriedades e ferramentas para qualquer Pesquisador configurado (por exemplo, um pesquisador de múltiplos índices)',
+ fieldValues: 'Valores do campo',
+ healthStatus: 'Estado de saúde',
+ healthStatusDescription: 'O estado de saúde do índice e se pode ser lido',
+ indexers: 'Indexadores',
+ indexInfo: 'Informação do índice',
+ contentInIndex: 'Conteúdo no índice',
+ indexInfoDescription: 'Lista as propriedades do índice',
+ manageIndexes: 'Gerir índices Examine',
+ manageIndexesDescription:
+ 'Permite visualizar os detalhes de cada índice e fornece algumas ferramentas para gerir os índices',
+ rebuildIndex: 'Reconstruir índice',
+ rebuildIndexWarning:
+ 'Isto fará com que o índice seja reconstruído. Dependendo da quantidade de conteúdo no seu site, isto pode demorar algum tempo. Não é recomendado reconstruir um índice durante períodos de tráfego elevado no website ou quando os editores estão a editar conteúdo.',
+ searchers: 'Pesquisadores',
+ searchDescription: 'Pesquisar no índice e visualizar os resultados',
+ tools: 'Ferramentas',
+ toolsDescription: 'Ferramentas para gerir o índice',
+ fields: 'campos',
+ indexCannotRead: 'O índice não pode ser lido e precisará de ser reconstruído',
+ processIsTakingLonger:
+ 'O processo está a demorar mais do que o esperado, verifique o log do Umbraco para ver se ocorreram erros durante esta operação',
+ indexCannotRebuild: 'Este índice não pode ser reconstruído porque não tem nenhum atribuído',
+ iIndexPopulator: 'IIndexPopulator',
+ noResults: 'Nenhum resultado foi encontrado',
+ searchResultsFound: 'A mostrar %0% - %1% de %2% resultado(s) - Página %3% de %4%',
+ corruptStatus: 'Possível índice corrompido detetado',
+ corruptErrorDescription: 'Erro recebido ao avaliar o índice:',
+ },
+ placeholders: {
+ username: 'Introduza o seu nome de utilizador',
+ password: 'Introduza a sua palavra-passe',
+ confirmPassword: 'Confirme a sua palavra-passe',
+ nameentity: 'Nomeie o %0%...',
+ entername: 'Introduza um nome...',
+ enteremail: 'Introduza um email...',
+ enterusername: 'Introduza um nome de utilizador...',
+ enterdate: 'Defina uma data...',
+ label: 'Etiqueta...',
+ enterDescription: 'Introduza uma descrição...',
+ search: 'Escreva para pesquisar...',
+ filter: 'Escreva para filtrar...',
+ enterTags: 'Escreva para adicionar tags (prima enter após cada etiqueta)...',
+ email: 'Introduza o seu email',
+ enterMessage: 'Introduza uma mensagem...',
+ usernameHint: 'O seu nome de utilizador é geralmente o seu email',
+ anchor: 'Introduza uma âncora ou querystring, #valor ou ?chave=valor',
+ enterAlias: 'Introduza o alias...',
+ generatingAlias: 'A gerar alias...',
+ a11yCreateItem: 'Criar item',
+ a11yEdit: 'Editar',
+ a11yName: 'Nome',
+ rteParagraph: 'Escreva algo incrível...',
+ rteHeading: 'Qual é o título?',
+ enterUrl: 'Introduza um URL...',
+ },
+ editcontenttype: {
+ createListView: 'Criar vista de lista personalizada',
+ removeListView: 'Remover vista de lista personalizada',
+ aliasAlreadyExists: 'Já existe um Tipo de Conteúdo, Tipo de Multimédia ou Tipo de Membro com este alias',
+ },
+ renamecontainer: {
+ renamed: 'Nome alterado',
+ enterNewFolderName: 'Introduza um novo nome de pasta aqui',
+ folderWasRenamed: '%0% foi alterado para %1%',
+ },
+ editdatatype: {
+ canChangePropertyEditorHelp:
+ 'Alterar um editor de propriedades num tipo de dados com valores armazenados está desativado. Para permitir isto, pode alterar a definição Umbraco:CMS:DataTypes:CanBeChanged em appsettings.json.',
+ addPrevalue: 'Adicionar pré-valor',
+ dataBaseDatatype: 'Tipo de dados da base de dados',
+ guid: 'Editor de propriedades GUID',
+ renderControl: 'Editor de propriedades',
+ rteButtons: 'Botões',
+ rteEnableAdvancedSettings: 'Ativar definições avançadas para',
+ rteEnableContextMenu: 'Ativar menu de contexto',
+ rteMaximumDefaultImgSize: 'Tamanho máximo predefinido de imagens inseridas',
+ rteRelatedStylesheets: 'Folhas de estilo relacionadas',
+ rteShowLabel: 'Mostrar etiqueta',
+ rteWidthAndHeight: 'Largura e altura',
+ selectFolder: 'Selecione a pasta para mover',
+ inTheTree: 'para na estrutura de árvore abaixo',
+ wasMoved: 'foi movido para baixo de',
+ hasReferencesDeleteConsequence:
+ 'Eliminar %0% eliminará as propriedades e os seus dados dos seguintes itens',
+ acceptDeleteConsequence:
+ 'Compreendo que esta ação eliminará as propriedades e os dados baseados neste Tipo de Dados',
+ noConfiguration: 'Não existe configuração para este editor de propriedades.',
+ },
+ errorHandling: {
+ errorButDataWasSaved:
+ 'Os seus dados foram guardados, mas antes de poder publicar esta página, existem alguns erros que precisa de corrigir primeiro:',
+ errorChangingProviderPassword:
+ 'O fornecedor de membros atual não suporta a alteração de palavra-passe (EnablePasswordRetrieval precisa de ser verdadeiro)',
+ errorExistsWithoutTab: '%0% já existe',
+ errorHeader: 'Ocorreram erros:',
+ errorHeaderWithoutTab: 'Ocorreram erros:',
+ errorInPasswordFormat:
+ 'A palavra-passe deve ter um mínimo de %0% caracteres e conter pelo menos %1% carácter(es) não alfanumérico(s)',
+ errorIntegerWithoutTab: '%0% deve ser um número inteiro',
+ errorMandatory: 'O campo %0% no separador %1% é obrigatório',
+ errorMandatoryWithoutTab: '%0% é um campo obrigatório',
+ errorRegExp: '%0% em %1% não está num formato correto',
+ errorRegExpWithoutTab: '%0% não está num formato correto',
+ },
+ errors: {
+ defaultError: 'Ocorreu uma falha desconhecida',
+ concurrencyError: 'Falha de concorrência otimista, o objeto foi modificado',
+ receivedErrorFromServer: 'Recebido um erro do servidor',
+ dissallowedMediaType: 'O tipo de ficheiro especificado foi desautorizado pelo administrador',
+ codemirroriewarning:
+ 'NOTA! Embora o CodeMirror esteja ativado por configuração, está desativado no Internet Explorer porque não é suficientemente estável.',
+ contentTypeAliasAndNameNotNull: 'Por favor, preencha o alias e o nome no novo tipo de propriedade!',
+ filePermissionsError: 'Existe um problema com o acesso de leitura/escrita a um ficheiro ou pasta específica',
+ macroErrorLoadingPartialView: 'Erro ao carregar o script da Vista Parcial (ficheiro: %0%)',
+ missingTitle: 'Por favor, introduza um título',
+ missingType: 'Por favor, escolha um tipo',
+ pictureResizeBiggerThanOrg:
+ 'Está prestes a tornar a imagem maior do que o tamanho original. Tem a certeza que quer prosseguir?',
+ startNodeDoesNotExists: 'Nó de início eliminado, por favor contacte o seu administrador',
+ stylesMustMarkBeforeSelect: 'Por favor, marque o conteúdo antes de alterar o estilo',
+ stylesNoStylesOnPage: 'Nenhum estilo ativo disponível',
+ tableColMergeLeft: 'Por favor, coloque o cursor à esquerda das duas células que deseja unir',
+ tableSplitNotSplittable: 'Não pode dividir uma célula que não foi unida.',
+ propertyHasErrors: 'Esta propriedade é inválida',
+ externalLoginError: 'Login externo',
+ unauthorized: 'Não estava autorizado antes de realizar esta ação',
+ userNotFound: 'O utilizador local não foi encontrado na base de dados',
+ externalInfoNotFound: 'O servidor não conseguiu comunicar com o fornecedor de login externo',
+ externalLoginFailed:
+ 'O servidor falhou ao autorizá-lo junto do fornecedor de login externo. Por favor, feche a janela e tente novamente.',
+ externalLoginSuccess: 'Iniciou sessão com sucesso. Pode agora fechar esta janela.',
+ externalLoginRedirectSuccess: 'Iniciou sessão com sucesso. Será redirecionado em breve.',
+ },
+ openidErrors: {
+ accessDenied: 'Acesso negado',
+ invalidRequest: 'Pedido inválido',
+ invalidClient: 'Cliente inválido',
+ invalidGrant: 'Concessão inválida',
+ unauthorizedClient: 'Cliente não autorizado',
+ unsupportedGrantType: 'Tipo de concessão não suportado',
+ invalidScope: 'Âmbito inválido',
+ serverError: 'Erro do servidor',
+ temporarilyUnavailable: 'O serviço está temporariamente indisponível',
+ },
+ general: {
+ about: 'Sobre',
+ action: 'Ação',
+ actions: 'Ações',
+ add: 'Adicionar',
+ alias: 'Alias',
+ all: 'Todos',
+ areyousure: 'Tem a certeza?',
+ back: 'Voltar',
+ backToOverview: 'Voltar à visão geral',
+ border: 'Borda',
+ by: 'por',
+ cancel: 'Cancelar',
+ cellMargin: 'Margem da célula',
+ choose: 'Escolher',
+ clear: 'Limpar',
+ close: 'Fechar',
+ closewindow: 'Fechar Janela',
+ closepane: 'Fechar Painel',
+ comment: 'Comentário',
+ confirm: 'Confirmar',
+ constrain: 'Restringir',
+ constrainProportions: 'Restringir proporções',
+ content: 'Conteúdo',
+ continue: 'Continuar',
+ copy: 'Copiar',
+ create: 'Criar',
+ database: 'Base de dados',
+ date: 'Data',
+ default: 'Predefinido',
+ delete: 'Eliminar',
+ deleted: 'Eliminado',
+ deleting: 'A eliminar...',
+ design: 'Design',
+ details: 'Detalhes',
+ dictionary: 'Dicionário',
+ dimensions: 'Dimensões',
+ discard: 'Descartar',
+ document: 'Documento',
+ down: 'Baixo',
+ download: 'Transferir',
+ edit: 'Editar',
+ edited: 'Editado',
+ elements: 'Elementos',
+ email: 'Email',
+ error: 'Erro',
+ field: 'Campo',
+ fieldFor: 'Campo para %0%',
+ toggleFor: 'Alternar para %0%',
+ findDocument: 'Encontrar',
+ first: 'Primeiro',
+ focalPoint: 'Ponto focal',
+ general: 'Geral',
+ groups: 'Grupos',
+ group: 'Grupo',
+ height: 'Altura',
+ help: 'Ajuda',
+ hide: 'Ocultar',
+ history: 'Histórico',
+ icon: 'Ícone',
+ id: 'Id',
+ import: 'Importar',
+ excludeFromSubFolders: 'Pesquisar apenas nesta pasta',
+ info: 'Informação',
+ innerMargin: 'Margem interna',
+ insert: 'Inserir',
+ install: 'Instalar',
+ invalid: 'Inválido',
+ justify: 'Justificar',
+ label: 'Etiqueta',
+ language: 'Idioma',
+ last: 'Último',
+ layout: 'Layout',
+ links: 'Links',
+ loading: 'A carregar',
+ locked: 'Bloqueado',
+ login: 'Login',
+ logoff: 'Terminar sessão',
+ logout: 'Terminar sessão',
+ macro: 'Macro',
+ mandatory: 'Obrigatório',
+ manifest: 'Manifesto',
+ message: 'Mensagem',
+ move: 'Mover',
+ name: 'Nome',
+ never: 'Nunca',
+ new: 'Novo',
+ next: 'Seguinte',
+ no: 'Não',
+ nodeName: 'Nome do Nó',
+ notFound: 'Não encontrado',
+ of: 'de',
+ off: 'Desligado',
+ ok: 'OK',
+ open: 'Abrir',
+ options: 'Opções',
+ on: 'No',
+ or: 'ou',
+ orderBy: 'Ordenar por',
+ password: 'Palavra-passe',
+ path: 'Caminho',
+ pixels: 'píxeis',
+ pleasewait: 'Um momento, por favor...',
+ previous: 'Anterior',
+ properties: 'Propriedades',
+ readMore: 'Ler mais',
+ rebuild: 'Reconstruir',
+ reciept: 'Email para receber dados do formulário',
+ recycleBin: 'Reciclagem',
+ recycleBinEmpty: 'A sua reciclagem está vazia',
+ reload: 'Recarregar',
+ remaining: 'Restante',
+ remove: 'Remover',
+ rename: 'Mudar o nome',
+ renew: 'Renovar',
+ required: 'Obrigatório',
+ retrieve: 'Recuperar',
+ retry: 'Tentar novamente',
+ rights: 'Permissões',
+ scheduledPublishing: 'Agendar publicação',
+ umbracoInfo: 'Informação Umbraco',
+ search: 'Pesquisar',
+ searchNoResult: 'Lamentamos, não conseguimos encontrar o que procura.',
+ noItemsInList: 'Nenhum item foi adicionado',
+ server: 'Servidor',
+ settings: 'Definições',
+ shared: 'Partilhado',
+ show: 'Mostrar',
+ showPageOnSend: 'Mostrar página ao Enviar',
+ size: 'Tamanho',
+ sort: 'Ordenar',
+ status: 'Estado',
+ submit: 'Submeter',
+ success: 'Sucesso',
+ type: 'Tipo',
+ typeName: 'Nome do Tipo',
+ typeToSearch: 'Escreva para pesquisar...',
+ unknown: 'Desconhecido',
+ unknownUser: 'Utilizador desconhecido',
+ under: 'em',
+ unnamed: 'Sem nome',
+ up: 'Cima',
+ update: 'Atualizar',
+ upgrade: 'Atualizar',
+ upload: 'Carregar',
+ url: 'URL',
+ user: 'Utilizador',
+ users: 'Utilizadores',
+ username: 'Nome de utilizador',
+ value: 'Valor',
+ view: 'Ver',
+ welcome: 'Bem-vindo(a)...',
+ width: 'Largura',
+ yes: 'Sim',
+ folder: 'Pasta',
+ searchResults: 'Resultados da pesquisa',
+ reorder: 'Reordenar',
+ reorderDone: 'Terminei de reordenar',
+ preview: 'Pré-visualizar',
+ changePassword: 'Alterar palavra-passe',
+ to: 'para',
+ listView: 'Vista de lista',
+ saving: 'A guardar...',
+ current: 'atual',
+ embed: 'Incorporar',
+ addEditLink: 'Adicionar/Editar Link',
+ removeLink: 'Remover Link',
+ mediaPicker: 'Seletor de Multimédia',
+ viewSourceCode: 'Ver Código Fonte',
+ selected: 'selecionado',
+ other: 'Outro',
+ articles: 'Artigos',
+ videos: 'Vídeos',
+ avatar: 'Avatar para',
+ header: 'Cabeçalho',
+ systemField: 'Campo de Sistema',
+ lastUpdated: 'Última atualização',
+ selectAll: 'Selecionar tudo',
+ skipToMenu: 'Saltar para o menu',
+ skipToContent: 'Saltar para o conteúdo',
+ readOnly: 'Apenas leitura',
+ restore: 'Restaurar',
+ primary: 'Primário',
+ change: 'Alterar',
+ cropSection: 'Secção de recorte',
+ generic: 'Genérico',
+ media: 'Multimédia',
+ revert: 'Reverter',
+ validate: 'Validar',
+ newVersionAvailable: 'Nova versão disponível',
+ duration: (duration: string, date: Date | string, now: Date | string) => {
+ if (new Date(date).getTime() < new Date(now).getTime()) return `${duration} atrás`;
+ return `em ${duration}`;
+ },
+ },
+ colors: {
+ blue: 'Azul',
+ },
+ shortcuts: {
+ addGroup: 'Adicionar grupo',
+ addProperty: 'Adicionar propriedade',
+ addEditor: 'Adicionar editor',
+ addTemplate: 'Adicionar modelo',
+ addChildNode: 'Adicionar nó filho',
+ addChild: 'Adicionar filho',
+ editDataType: 'Editar tipo de dados',
+ navigateSections: 'Navegar secções',
+ shortcut: 'Atalhos',
+ showShortcuts: 'mostrar atalhos',
+ toggleListView: 'Alternar vista de lista',
+ toggleAllowAsRoot: 'Alternar permitir como raiz',
+ commentLine: 'Comentar/Descomentar linhas',
+ removeLine: 'Remover linha',
+ copyLineUp: 'Copiar Linhas para Cima',
+ copyLineDown: 'Copiar Linhas para Baixo',
+ moveLineUp: 'Mover Linhas para Cima',
+ moveLineDown: 'Mover Linhas para Baixo',
+ generalHeader: 'Geral',
+ editorHeader: 'Editor',
+ toggleAllowCultureVariants: 'Alternar permitir variantes de cultura',
+ addTab: 'Adicionar separador',
+ },
+ graphicheadline: {
+ backgroundcolor: 'Cor de fundo',
+ bold: 'Negrito',
+ color: 'Cor do texto',
+ font: 'Tipo de letra',
+ text: 'Texto',
+ },
+ globalSearch: {
+ navigateSearchProviders: 'Navegar fornecedores de pesquisa',
+ navigateSearchResults: 'Navegar resultados da pesquisa',
+ },
+ headers: {
+ page: 'Página',
+ },
+ installer: {
+ databaseErrorCannotConnect: 'O instalador não consegue ligar-se à base de dados.',
+ databaseErrorWebConfig:
+ 'Não foi possível guardar o ficheiro web.config. Por favor, modifique a "connection string" manualmente.',
+ databaseFound: 'A sua base de dados foi encontrada e é identificada como',
+ databaseHeader: 'Configuração da base de dados',
+ databaseInstall: 'Pressione o botão instalar para instalar a base de dados Umbraco %0%',
+ databaseInstallDone:
+ 'O Umbraco %0% foi agora copiado para a sua base de dados. Pressione Seguinte para prosseguir.',
+ databaseNotFound:
+ 'Base de dados não encontrada! Por favor, verifique se a informação na "connection string" do ficheiro "web.config" está correta.
Para prosseguir, por favor edite o ficheiro "web.config" (usando o Visual Studio ou o seu editor de texto favorito), desloque-se até ao final, adicione a cadeia de ligação para a sua base de dados na chave chamada "UmbracoDbDSN" e guarde o ficheiro.
Clique no botão tentar novamente quando terminar.Mais informação sobre como editar o web.config aqui .
',
+ databaseText:
+ 'Para completar este passo, deve saber alguma informação sobre o seu servidor de base de dados ("connection string"). Por favor, contacte o seu ISP, se necessário. Se estiver a instalar numa máquina ou servidor local, poderá precisar de informação do seu administrador de sistema.',
+ databaseUpgrade:
+ ' Pressione o botão atualizar para atualizar a sua base de dados para Umbraco %0%
Não se preocupe - nenhum conteúdo será eliminado e tudo continuará a funcionar depois!
',
+ databaseUpgradeDone:
+ 'A sua base de dados foi atualizada para a versão final %0%. Pressione Seguinte para prosseguir.',
+ databaseUpToDate:
+ 'A sua base de dados atual está atualizada!. Clique em seguinte para continuar o assistente de configuração',
+ defaultUserChangePass: 'A palavra-passe do utilizador Predefinido precisa de ser alterada! ',
+ defaultUserDisabled:
+ 'O utilizador Predefinido foi desativado ou não tem acesso ao Umbraco!
Nenhuma ação adicional é necessária. Clique em Seguinte para prosseguir.',
+ defaultUserPassChanged:
+ 'A palavra-passe do utilizador Predefinido foi alterada com sucesso desde a instalação!
Nenhuma ação adicional é necessária. Clique em Seguinte para prosseguir.',
+ defaultUserPasswordChanged: 'A palavra-passe foi alterada!',
+ greatStart: 'Comece em grande, veja os nossos vídeos de introdução',
+ licenseText:
+ 'Ao clicar no botão seguinte (ou modificar o umbracoConfigurationStatus no web.config), aceita a licença para este software conforme especificado na caixa abaixo. Note que esta distribuição Umbraco consiste em duas licenças diferentes, a licença MIT de código aberto para o framework e a licença freeware Umbraco que cobre a UI.',
+ None: 'Ainda não instalado.',
+ permissionsAffectedFolders: 'Ficheiros e pastas afetados',
+ permissionsAffectedFoldersMoreInfo: 'Mais informação sobre como configurar permissões para o Umbraco aqui',
+ permissionsAffectedFoldersText:
+ 'Precisa de conceder permissões de modificação ASP.NET aos seguintes ficheiros/pastas',
+ permissionsAlmostPerfect:
+ 'As suas definições de permissão estão quase perfeitas! Pode executar o Umbraco sem problemas, mas não poderá instalar pacotes, o que é recomendado para tirar o máximo partido do Umbraco.',
+ permissionsHowtoResolve: 'Como Resolver',
+ permissionsHowtoResolveLink: 'Clique aqui para ler a versão em texto',
+ permissionsHowtoResolveText:
+ 'Veja o nosso tutorial em vídeo sobre como configurar permissões de pasta para o Umbraco ou leia a versão em texto.',
+ permissionsMaybeAnIssue:
+ 'As suas definições de permissão podem ser um problema! Pode executar o Umbraco sem problemas, mas não poderá criar pastas ou instalar pacotes, o que é recomendado para tirar o máximo partido do Umbraco.',
+ permissionsNotReady:
+ 'As suas definições de permissão não estão prontas para o Umbraco! Para executar o Umbraco, terá de atualizar as suas definições de permissão.',
+ permissionsPerfect:
+ 'As suas definições de permissão estão perfeitas! Está pronto para executar o Umbraco e instalar pacotes!',
+ permissionsResolveFolderIssues: 'Resolver problemas de pasta',
+ permissionsResolveFolderIssuesLink:
+ 'Siga este link para mais informação sobre problemas com ASP.NET e criação de pastas',
+ permissionsSettingUpPermissions: 'Configurar permissões de pasta',
+ permissionsText:
+ ' O Umbraco precisa de acesso de escrita/modificação a certos diretórios para armazenar ficheiros como imagens e PDFs. Também armazena dados temporários (também conhecidos como: cache) для otimizar o desempenho do seu website.',
+ runwayFromScratch: 'Quero começar do zero',
+ runwayFromScratchText:
+ 'O seu website está completamente vazio neste momento, o que é perfeito se quiser começar do zero e criar os seus próprios Tipos de Documento e modelos. (aprenda como ) Ainda pode optar por instalar o Runway mais tarde. Por favor, vá à secção Desenvolvedor e escolha Pacotes.',
+ runwayHeader: 'Acabou de configurar uma plataforma Umbraco limpa. O que quer fazer a seguir?',
+ runwayInstalled: 'O Runway está instalado',
+ runwayInstalledText:
+ 'Tem a base instalada. Selecione que módulos deseja instalar sobre ela. Esta é a nossa lista de módulos recomendados, marque aqueles que gostaria de instalar, ou veja a lista completa de módulos ',
+ runwayOnlyProUsers: 'Apenas recomendado para utilizadores experientes',
+ runwaySimpleSite: 'Quero começar com um website simples',
+ runwaySimpleSiteText:
+ '
"Runway" é um website simples que fornece alguns Tipos de Documento e modelos básicos. O instalador pode configurar o Runway para si automaticamente, mas pode facilmente editá-lo, estendê-lo ou removê-lo. Não é necessário e pode perfeitamente usar o Umbraco sem ele. No entanto, o Runway oferece uma base fácil baseada nas melhores práticas para começar mais rapidamente do que nunca. Se optar por instalar o Runway, pode opcionalmente selecionar blocos de construção básicos chamados Módulos Runway para melhorar as suas páginas Runway.
Incluído com o Runway: Página inicial, Página de Introdução, Página de Instalação de Módulos. Módulos Opcionais: Navegação Superior, Mapa do Site, Contacto, Galeria. ',
+ runwayWhatIsRunway: 'O que é o Runway',
+ step1: 'Passo 1/5 Aceitar licença',
+ step2: 'Passo 2/5: Configuração da base de dados',
+ step3: 'Passo 3/5: Validar Permissões de Ficheiro',
+ step4: 'Passo 4/5: Verificar segurança do Umbraco',
+ step5: 'Passo 5/5: O Umbraco está pronto para começar',
+ thankYou: 'Obrigado por escolher o Umbraco',
+ theEndBrowseSite:
+ 'Navegue no seu novo site Instalou o Runway, então porque não ver como está o seu novo website.',
+ theEndFurtherHelp:
+ 'Ajuda e informação adicional \nObtenha ajuda da nossa comunidade premiada, navegue na documentação ou veja alguns vídeos gratuitos sobre como construir um site simples, como usar pacotes e um guia rápido sobre a terminologia do Umbraco',
+ theEndHeader: 'O Umbraco %0% está instalado e pronto a usar',
+ theEndInstallFailed:
+ "Para terminar a instalação, terá de editar manualmente o ficheiro /web.config e atualizar a chave AppSetting UmbracoConfigurationStatus no final para o valor de '%0%' .",
+ theEndInstallSuccess:
+ 'Pode começar instantaneamente clicando no botão "Iniciar Umbraco" abaixo. Se for novo no Umbraco , pode encontrar muitos recursos nas nossas páginas de introdução.',
+ theEndOpenUmbraco:
+ 'Iniciar Umbraco \nPara gerir o seu website, simplesmente abra o backoffice do Umbraco e comece a adicionar conteúdo, atualizar os modelos e folhas de estilo ou adicionar nova funcionalidade',
+ Unavailable: 'Falha na ligação à base de dados.',
+ Version3: 'Umbraco Versão 3',
+ Version4: 'Umbraco Versão 4',
+ watch: 'Ver',
+ welcomeIntro:
+ 'Este assistente irá guiá-lo através do processo de configuração do Umbraco %0% para uma nova instalação ou atualização da versão 3.0. Pressione "seguinte" para iniciar o assistente.',
+ },
+ language: {
+ cultureCode: 'Código da Cultura',
+ displayName: 'Nome da Cultura',
+ noFallbackLanguages: 'Não existem outros idiomas para escolher',
+ },
+ lockout: {
+ lockoutWillOccur: 'Esteve inativo e a sua sessão será terminada automaticamente em',
+ renewSession: 'Renove agora para guardar o seu trabalho',
+ },
+ login: {
+ greeting0: 'Bem-vindo(a)',
+ greeting1: 'Bem-vindo(a)',
+ greeting2: 'Bem-vindo(a)',
+ greeting3: 'Bem-vindo(a)',
+ greeting4: 'Bem-vindo(a)',
+ greeting5: 'Bem-vindo(a)',
+ greeting6: 'Bem-vindo(a)',
+ instruction: 'Iniciar sessão no Umbraco',
+ signInWith: 'Iniciar sessão com {0}',
+ timeout: 'A sua sessão expirou. Por favor, inicie sessão novamente abaixo.',
+ },
+ main: {
+ dashboard: 'Painel de Controlo',
+ sections: 'Secções',
+ tree: 'Conteúdo',
+ },
+ moveOrCopy: {
+ choose: 'Escolha a página acima...',
+ copyDone: '%0% foi copiado para %1%',
+ copyTo: 'Selecione para onde o documento %0% deve ser copiado abaixo',
+ moveDone: '%0% foi movido para %1%',
+ moveTo: 'Selecione para onde o documento %0% deve ser movido abaixo',
+ nodeSelected: "foi selecionado como a raiz do seu novo conteúdo, clique em 'ok' abaixo.",
+ noNodeSelected: "Nenhum nó selecionado ainda, por favor selecione um nó na lista acima antes de clicar em 'ok'",
+ notAllowedByContentType: 'O nó atual não é permitido sob o nó escolhido devido ao seu tipo',
+ notAllowedByPath:
+ 'O nó atual não pode ser movido para uma das suas subpáginas, nem o pai e o destino podem ser os mesmos',
+ notAllowedAtRoot: 'O nó atual não pode existir na raiz',
+ notValid: 'A ação não é permitida porque tem permissões insuficientes em 1 ou mais documentos filhos.',
+ relateToOriginal: 'Relacionar itens copiados com o original',
+ },
+ notifications: {
+ editNotifications: 'Selecione a sua notificação para %0%',
+ notificationsSavedFor: 'Definições de notificação guardadas para %0%',
+ notifications: 'Notificações',
+ },
+ packager: {
+ actions: 'Ações',
+ created: 'Criado',
+ createPackage: 'Criar pacote',
+ chooseLocalPackageText:
+ 'Escolha o Pacote da sua máquina, clicando no botão Procurar e localizando o pacote. Os pacotes Umbraco geralmente têm uma extensão ".umb" ou ".zip". ',
+ deletewarning: 'Isto eliminará o pacote',
+ includeAllChildNodes: 'Incluir todos os nós filhos',
+ installed: 'Instalado',
+ installedPackages: 'Pacotes instalados',
+ installInstructions: 'Instruções de instalação',
+ noConfigurationView: 'Este pacote não tem vista de configuração',
+ noPackagesCreated: 'Nenhum pacote foi criado ainda',
+ noPackages: 'Nenhum pacote foi instalado',
+ noPackagesDescription:
+ "Navegue pelos pacotes disponíveis usando o ícone 'Pacotes' no canto superior direito do seu ecrã",
+ packageContent: 'Conteúdo do Pacote',
+ packageLicense: 'Licença',
+ packageSearch: 'Procurar pacotes',
+ packageSearchResults: 'Resultados para',
+ packageNoResults: 'Não conseguimos encontrar nada para',
+ packageNoResultsDescription: 'Por favor, tente procurar outro pacote ou navegue pelas categorias',
+ packagesPopular: 'Popular',
+ packagesPromoted: 'Promovido',
+ packagesNew: 'Novos lançamentos',
+ packageHas: 'tem',
+ packageKarmaPoints: 'pontos de karma',
+ packageInfo: 'Informação',
+ packageOwner: 'Proprietário',
+ packageContrib: 'Contribuidores',
+ packageCreated: 'Criado',
+ packageCurrentVersion: 'Versão atual',
+ packageNetVersion: 'Versão .NET',
+ packageDownloads: 'Transferências',
+ packageLikes: 'Gostos',
+ packageCompatibility: 'Compatibilidade',
+ packageCompatibilityDescription:
+ 'Este pacote é compatível com as seguintes versões do Umbraco, conforme reportado por membros da comunidade. A compatibilidade total não pode ser garantida para versões reportadas abaixo de 100%',
+ packageExternalSources: 'Fontes externas',
+ packageAuthor: 'Autor',
+ packageDocumentation: 'Documentação',
+ packageMetaData: 'Metadados do pacote',
+ packageName: 'Nome do pacote',
+ packageNoItemsHeader: 'O pacote não contém quaisquer itens',
+ packageNoItemsText:
+ 'Este ficheiro de pacote não contém quaisquer itens para desinstalar. Pode removê-lo com segurança do sistema clicando em "desinstalar pacote" abaixo.',
+ packageOptions: 'Opções do pacote',
+ packageMigrationsRun: 'Executar migrações de pacote pendentes',
+ packageMigrationsConfirmText: 'Deseja executar as migrações de pacote pendentes?',
+ packageMigrationsComplete: 'As migrações de pacote foram concluídas com sucesso.',
+ packageMigrationsNonePending: 'Todas as migrações de pacote foram concluídas com sucesso.',
+ packageReadme: 'Readme do pacote',
+ packageRepository: 'Repositório de pacotes',
+ packageUninstallConfirm: 'Confirmar desinstalação do pacote',
+ packageUninstalledHeader: 'O pacote foi desinstalado',
+ packageUninstalledText: 'O pacote foi desinstalado com sucesso',
+ packageUninstallHeader: 'Desinstalar pacote',
+ packageUninstallText:
+ 'Pode desmarcar os itens que não deseja remover, neste momento, abaixo. Quando clicar em "confirmar desinstalação", todos os itens marcados serão removidos. Aviso: quaisquer documentos, multimédia, etc., dependentes dos itens que remover, deixarão de funcionar e poderão levar à instabilidade do sistema, por isso desinstale com precaução. Em caso de dúvida, contacte o autor do pacote.',
+ packageVersion: 'Versão do pacote',
+ verifiedToWorkOnUmbracoCloud: 'Verificado para funcionar no Umbraco Cloud',
+ },
+ paste: {
+ doNothing: 'Colar com formatação completa (Não recomendado)',
+ errorMessage:
+ 'O texto que está a tentar colar contém caracteres especiais ou formatação. Isto pode ser causado pela cópia de texto do Microsoft Word. O Umbraco pode remover caracteres especiais ou formatação automaticamente, para que o conteúdo colado seja mais adequado para a web.',
+ removeAll: 'Colar como texto simples sem qualquer formatação',
+ removeSpecialFormattering: 'Colar, mas remover formatação (Recomendado)',
+ },
+ publicAccess: {
+ paGroups: 'Proteção baseada em grupo',
+ paGroupsHelp: 'Se quiser conceder acesso a todos os membros de grupos de membros específicos',
+ paGroupsNoGroups: 'Precisa de criar um grupo de membros antes de poder usar autenticação baseada em grupo',
+ paErrorPage: 'Página de Erro',
+ paErrorPageHelp: 'Usado quando as pessoas estão autenticadas, mas não têm acesso',
+ paHowWould: 'Escolha como restringir o acesso à página %0% ',
+ paIsProtected: '%0% está agora protegido',
+ paIsRemoved: 'Proteção removida de %0% ',
+ paLoginPage: 'Página de Login',
+ paLoginPageHelp: 'Escolha a página que contém o formulário de login',
+ paRemoveProtection: 'Remover proteção...',
+ paRemoveProtectionConfirm: 'Tem a certeza que quer remover a proteção da página %0% ?',
+ paSelectPages: 'Selecione as páginas que contêm o formulário de login e mensagens de erro',
+ paSelectGroups: 'Selecione os grupos que têm acesso à página %0% ',
+ paSelectMembers: 'Selecione os membros que têm acesso à página %0% ',
+ paMembers: 'Proteção de membros específicos',
+ paMembersHelp: 'Se desejar conceder acesso a membros específicos',
+ },
+ publish: {
+ contentPublishedFailedIsTrashed: '%0% não pôde ser publicado porque o item está na reciclagem.',
+ contentPublishedFailedAwaitingRelease: '%0% não pôde ser publicado porque o item está agendado para lançamento.',
+ contentPublishedFailedExpired: '%0% não pôde ser publicado porque o item expirou.',
+ contentPublishedFailedInvalid:
+ '%0% não pôde ser publicado porque estas propriedades: %1% não passaram nas regras de validação.',
+ contentPublishedFailedByEvent: '%0% não pôde ser publicado, um suplemento de terceiros cancelou a ação.',
+ contentPublishedFailedByParent: '%0% não pode ser publicado, porque uma página pai não está publicada.',
+ contentPublishedFailedByMissingName: '%0% não pode ser publicado, porque falta um nome.',
+ includeUnpublished: 'Incluir subpáginas não publicadas',
+ inProgress: 'Publicação em curso - por favor aguarde...',
+ inProgressCounter: '%0% de %1% páginas foram publicadas...',
+ nodePublish: '%0% foi publicado',
+ nodePublishAll: '%0% e subpáginas foram publicadas',
+ publishAll: 'Publicar %0% e todas as suas subpáginas',
+ publishHelp:
+ 'Clique em Publicar para publicar %0% e assim tornar o seu conteúdo publicamente disponível. Pode publicar esta página e todas as suas subpáginas marcando Incluir subpáginas não publicadas abaixo. ',
+ invalidPublishBranchPermissions:
+ 'Permissões de utilizador insuficientes para publicar todos os documentos descendentes',
+ contentPublishedFailedReqCultureValidationError:
+ "Falha na validação para o idioma obrigatório '%0%'. Este idioma foi guardado mas não publicado.",
+ },
+ colorpicker: {
+ noColors: 'Não configurou nenhuma cor aprovada',
+ },
+ colorPickerConfigurations: {
+ colorsTitle: 'Cores',
+ colorsDescription: 'Adicionar, remover ou ordenar cores',
+ showLabelTitle: 'Incluir etiquetas?',
+ showLabelDescription:
+ 'Armazena cores como um objeto JSON contendo tanto a string hexadecimal da cor como a etiqueta, em vez de apenas a string hexadecimal.',
+ },
+ contentPicker: {
+ allowedItemTypes: 'Só pode selecionar itens do(s) tipo(s): %0%',
+ pickedTrashedItem: 'Selecionou um item de conteúdo atualmente eliminado ou na reciclagem',
+ pickedTrashedItems: 'Selecionou itens de conteúdo atualmente eliminados ou na reciclagem',
+ specifyPickerRootTitle: 'Especificar raiz',
+ defineRootNode: 'Escolher nó raiz',
+ defineXPathOrigin: 'Especificar via XPath',
+ defineDynamicRoot: 'Especificar uma Raiz Dinâmica',
+ unsupportedHeadline: (type?: string) =>
+ `Itens de ${type ?? 'conteúdo'} não suportados O seguinte conteúdo já não é suportado neste Editor.`,
+ unsupportedMessage:
+ 'Se ainda precisar deste conteúdo, entre em contacto com o seu administrador. Caso contrário, pode removê-lo.',
+ unsupportedRemove: 'Remover itens não suportados?',
+ },
+ dynamicRoot: {
+ configurationTitle: 'Consulta de Raiz Dinâmica',
+ pickDynamicRootOriginTitle: 'Escolher origem',
+ pickDynamicRootOriginDesc: 'Defina a origem para a sua Consulta de Raiz Dinâmica',
+ originRootTitle: 'Raiz',
+ originRootDesc: 'Nó raiz desta sessão de edição',
+ originParentTitle: 'Pai',
+ originParentDesc: 'O nó pai da origem nesta sessão de edição',
+ originCurrentTitle: 'Atual',
+ originCurrentDesc: 'O nó de conteúdo que é a origem para esta sessão de edição',
+ originSiteTitle: 'Site',
+ originSiteDesc: 'Encontrar o nó mais próximo com um nome de anfitrião',
+ originByKeyTitle: 'Nó Específico',
+ originByKeyDesc: 'Escolha um Nó específico como origem para esta consulta',
+ pickDynamicRootQueryStepTitle: 'Anexar passo à consulta',
+ pickDynamicRootQueryStepDesc: 'Defina o próximo passo da sua Consulta de Raiz Dinâmica',
+ queryStepNearestAncestorOrSelfTitle: 'Ascendente ou Atual Mais Próximo',
+ queryStepNearestAncestorOrSelfDesc:
+ 'Consulte o ascendente ou atual mais próximo que se enquadre num dos tipos configurados',
+ queryStepFurthestAncestorOrSelfTitle: 'Ascendente ou Atual Mais Distante',
+ queryStepFurthestAncestorOrSelfDesc:
+ 'Consulte o ascendente ou atual mais distante que se enquadre num dos tipos configurados',
+ queryStepNearestDescendantOrSelfTitle: 'Descendente ou Atual Mais Próximo',
+ queryStepNearestDescendantOrSelfDesc:
+ 'Consulte o descendente ou atual mais próximo que se enquadre num dos tipos configurados',
+ queryStepFurthestDescendantOrSelfTitle: 'Descendente ou Atual Mais Distante',
+ queryStepFurthestDescendantOrSelfDesc:
+ 'Consulte o descendente ou atual mais distante que se enquadre num dos tipos configurados',
+ queryStepCustomTitle: 'Personalizado',
+ queryStepCustomDesc: 'Consulte usando um Passo de Consulta personalizado',
+ addQueryStep: 'Adicionar passo de consulta',
+ queryStepTypes: 'Que corresponde aos tipos: ',
+ noValidStartNodeTitle: 'Nenhum conteúdo correspondente',
+ noValidStartNodeDesc:
+ 'A configuração desta propriedade não corresponde a nenhum conteúdo. Crie o conteúdo em falta ou contacte o seu administrador para ajustar as definições de Raiz Dinâmica para esta propriedade.',
+ cancelAndClearQuery: 'Cancelar e limpar consulta',
+ },
+ mediaPicker: {
+ deletedItem: 'Item eliminado',
+ pickedTrashedItem: 'Selecionou um item de multimédia atualmente eliminado ou na reciclagem',
+ pickedTrashedItems: 'Selecionou itens de multimédia atualmente eliminados ou na reciclagem',
+ trashed: 'No lixo',
+ openMedia: 'Abrir na Biblioteca de Multimédia',
+ changeMedia: 'Alterar Item de Multimédia',
+ editMediaEntryLabel: 'Editar %0% em %1%',
+ confirmCancelMediaEntryCreationHeadline: 'Descartar criação?',
+ confirmCancelMediaEntryCreationMessage: 'Tem a certeza que quer cancelar a criação.',
+ confirmCancelMediaEntryHasChanges: 'Fez alterações a este conteúdo. Tem a certeza que quer descartá-las?',
+ confirmRemoveAllMediaEntryMessage: 'Remover todas as multimédias?',
+ tabClipboard: 'Área de Transferência',
+ notAllowed: 'Não permitido',
+ openMediaPicker: 'Abrir seletor de multimédia',
+ },
+ propertyEditorPicker: {
+ title: 'Selecionar um editor de propriedades',
+ openPropertyEditorPicker: 'Selecionar uma UI de editor de propriedades',
+ },
+ relatedlinks: {
+ enterExternal: 'introduzir link externo',
+ chooseInternal: 'escolher página interna',
+ caption: 'Legenda',
+ link: 'Link',
+ newWindow: 'Abrir numa nova janela',
+ captionPlaceholder: 'introduza a legenda de exibição',
+ externalLinkPlaceholder: 'Introduza o link',
+ },
+ imagecropper: {
+ reset: 'Redefinir recorte',
+ updateEditCrop: 'Concluído',
+ undoEditCrop: 'Anular edições',
+ customCrop: 'Definido pelo utilizador',
+ },
+ rollback: {
+ changes: 'Alterações',
+ created: 'Criado',
+ currentVersion: 'Versão atual',
+ diffHelp:
+ 'Isto mostra as diferenças entre a versão atual (rascunho) e a versão selecionadaTexto a vermelho será removido na versão selecionada, texto a verde será adicionado',
+ noDiff: 'Não existem diferenças entre a versão atual (rascunho) e a versão selecionada',
+ documentRolledBack: 'O documento foi revertido',
+ headline: 'Selecione uma versão para comparar com a versão atual',
+ htmlHelp:
+ 'Isto exibe a versão selecionada como HTML, se desejar ver a diferença entre 2 versões ao mesmo tempo, use a vista de diferenças',
+ rollbackTo: 'Reverter para',
+ selectVersion: 'Selecionar versão',
+ view: 'Ver',
+ pagination: 'A mostrar versão %0% a %1% de %2% versões',
+ versions: 'Versões',
+ currentDraftVersion: 'Versão de rascunho atual',
+ currentPublishedVersion: 'Versão publicada atual',
+ },
+ scripts: {
+ editscript: 'Editar ficheiro de script',
+ },
+ sections: {
+ content: 'Conteúdo',
+ media: 'Multimédia',
+ member: 'Membros',
+ packages: 'Pacotes',
+ marketplace: 'Marketplace',
+ settings: 'Definições',
+ translation: 'Tradução',
+ users: 'Utilizadores',
+ },
+ help: {
+ tours: 'Visitas Guiadas',
+ theBestUmbracoVideoTutorials: 'Os melhores tutoriais em vídeo do Umbraco',
+ umbracoForum: 'Visite our.umbraco.com',
+ umbracoTv: 'Visite umbraco.tv',
+ umbracoLearningBase: 'Veja os nossos tutoriais em vídeo gratuitos',
+ umbracoLearningBaseDescription: 'na Base de Aprendizagem Umbraco',
+ },
+ settings: {
+ defaulttemplate: 'Modelo predefinido',
+ importDocumentTypeHelp:
+ 'Para importar um Tipo de Documento, encontre o ficheiro ".udt" no seu computador clicando no botão "Importar" (ser-lhe-á pedida confirmação no ecrã seguinte)',
+ newtabname: 'Novo Título do Separador',
+ nodetype: 'Tipo de nó',
+ objecttype: 'Tipo',
+ stylesheet: 'Stylesheet',
+ script: 'Script',
+ tab: 'Separador',
+ tabname: 'Título do Separador',
+ tabs: 'Separadores',
+ contentTypeEnabled: 'Tipo de Conteúdo Principal ativado',
+ contentTypeUses: 'Este Tipo de Conteúdo usa',
+ noPropertiesDefinedOnTab:
+ 'Nenhuma propriedade definida neste separador. Clique no link "adicionar uma nova propriedade" no topo para criar uma nova propriedade.',
+ createMatchingTemplate: 'Criar modelo correspondente',
+ addIcon: 'Adicionar ícone',
+ },
+ sort: {
+ sortOrder: 'ordem',
+ sortCreationDate: 'Data de criação',
+ sortDone: 'Ordenação concluída.',
+ sortHelp:
+ 'Arraste os diferentes itens para cima ou para baixo abaixo para definir como devem ser organizados. Ou clique nos cabeçalhos das colunas para ordenar toda a coleção de itens',
+ sortPleaseWait: 'Por favor, aguarde. Os itens estão a ser ordenados, isto pode demorar um pouco.',
+ sortEmptyState: 'Este nó não tem nós filhos para ordenar',
+ },
+ speechBubbles: {
+ validationFailedHeader: 'Validação',
+ validationFailedMessage: 'Os erros de validação devem ser corrigidos antes que o item possa ser guardado',
+ operationFailedHeader: 'Falhou',
+ operationSavedHeader: 'Guardado',
+ operationSavedHeaderReloadUser: 'Guardado. Para ver as alterações, por favor recarregue o seu browser',
+ invalidUserPermissionsText: 'Permissões de utilizador insuficientes, não foi possível concluir a operação',
+ operationCancelledHeader: 'Cancelado',
+ operationCancelledText: 'A operação foi cancelada por um suplemento de terceiros',
+ folderUploadNotAllowed:
+ 'Este ficheiro está a ser carregado como parte de uma pasta, mas a criação de uma nova pasta não é permitida aqui',
+ folderCreationNotAllowed: 'A criação de uma nova pasta não é permitida aqui',
+ contentPublishedFailedByEvent: 'O documento não pôde ser publicado, um suplemento de terceiros cancelou a ação',
+ contentTypeDublicatePropertyType: 'O tipo de propriedade já existe',
+ contentTypePropertyTypeCreated: 'Tipo de propriedade criado',
+ contentTypePropertyTypeCreatedText: 'Nome: %0% Tipo de Dados: %1%',
+ contentTypePropertyTypeDeleted: 'Tipo de propriedade eliminado',
+ contentTypeSavedHeader: 'Tipo de Documento guardado',
+ contentTypeTabCreated: 'Separador criado',
+ contentTypeTabDeleted: 'Separador eliminado',
+ contentTypeTabDeletedText: 'Separador com id: %0% eliminado',
+ cssErrorHeader: 'Stylesheet não guardada',
+ cssSavedHeader: 'Stylesheet guardada',
+ cssSavedText: 'Stylesheet guardada sem erros',
+ dataTypeSaved: 'Tipo de dados guardado',
+ dictionaryItemSaved: 'Item do dicionário guardado',
+ editContentPublishedFailedByValidation: 'O documento não pôde ser publicado, mas guardámo-lo para si',
+ editContentPublishedFailedByParent: 'O documento não pôde ser publicado, porque uma página pai não está publicada',
+ editContentPublishedHeader: 'Documento publicado',
+ editContentPublishedText: 'e está visível no website',
+ editContentUnpublishedHeader: 'Documento despublicado',
+ editContentUnpublishedText: 'e já não está visível no website',
+ editVariantPublishedText: '%0% publicado e está visível no website',
+ editVariantSavedText: '%0% guardado',
+ editBlueprintSavedHeader: 'Modelo de Documento guardado',
+ editBlueprintSavedText: 'As alterações foram guardadas com sucesso',
+ editContentSavedHeader: 'Documento guardado',
+ editContentSavedText: 'Lembre-se de publicar para tornar as alterações visíveis',
+ editContentSendToPublish: 'Enviado para Aprovação',
+ editContentSendToPublishText: 'As alterações foram enviadas para aprovação',
+ editMediaSaved: 'Multimédia guardada',
+ editMediaSavedText: 'Multimédia guardada sem erros',
+ editMemberSaved: 'Membro guardado',
+ editStylesheetPropertySaved: 'Propriedade da Stylesheet Guardada',
+ editStylesheetSaved: 'Stylesheet guardada',
+ editTemplateSaved: 'Template guardado',
+ editUserError: 'Erro ao guardar utilizador (verifique os logs)',
+ editUserSaved: 'Utilizador Guardado',
+ editUserTypeSaved: 'Tipo de utilizador guardado',
+ editUserGroupSaved: 'Grupo de utilizadores guardado',
+ editCulturesAndHostnamesSaved: 'Culturas e domínios guardados',
+ editCulturesAndHostnamesError: 'Erro ao guardar culturas e domínios',
+ fileErrorHeader: 'Ficheiro não guardado',
+ fileErrorText: 'o ficheiro não pôde ser guardado. Por favor, verifique as permissões do ficheiro',
+ fileSavedHeader: 'Ficheiro guardado',
+ fileSavedText: 'Ficheiro guardado sem erros',
+ languageSaved: 'Idioma guardado',
+ mediaTypeSavedHeader: 'Tipo de Multimédia guardado',
+ memberTypeSavedHeader: 'Tipo de Membro guardado',
+ memberGroupSavedHeader: 'Grupo de Membros guardado',
+ memberGroupNameDuplicate: 'Já existe outro Grupo de Membros com o mesmo nome',
+ templateErrorHeader: 'Template não guardado',
+ templateErrorText: 'Por favor, certifique-se de que não tem 2 templates com o mesmo alias',
+ templateSavedHeader: 'Template guardado',
+ templateSavedText: 'Template guardado sem erros!',
+ contentUnpublished: 'Conteúdo despublicado',
+ contentMandatoryCultureUnpublished:
+ "O idioma obrigatório '%0%' foi despublicado. Todos os idiomas para este item de conteúdo estão agora despublicados.",
+ partialViewSavedHeader: 'Vista parcial guardada',
+ partialViewSavedText: 'Vista parcial guardada sem erros!',
+ partialViewErrorHeader: 'Vista parcial não guardada',
+ partialViewErrorText: 'Ocorreu um erro ao guardar o ficheiro.',
+ permissionsSavedFor: 'Permissões guardadas para',
+ deleteUserGroupsSuccess: 'Eliminados %0% grupos de utilizadores',
+ deleteUserGroupSuccess: '%0% foi eliminado',
+ enableUsersSuccess: 'Ativados %0% utilizadores',
+ disableUsersSuccess: 'Desativados %0% utilizadores',
+ enableUserSuccess: '%0% está agora ativado',
+ disableUserSuccess: '%0% está agora desativado',
+ setUserGroupOnUsersSuccess: 'Os grupos de utilizadores foram definidos',
+ unlockUsersSuccess: 'Desbloqueados %0% utilizadores',
+ unlockUserSuccess: '%0% está agora desbloqueado',
+ memberExportedSuccess: 'O membro foi exportado para o ficheiro',
+ memberExportedError: 'Ocorreu um erro ao exportar o membro',
+ deleteUserSuccess: 'O utilizador %0% foi eliminado',
+ resendInviteHeader: 'Convidar utilizador',
+ resendInviteSuccess: 'O convite foi reenviado para %0%',
+ contentReqCulturePublishError:
+ "Não é possível publicar o documento uma vez que o '%0%' obrigatório não está publicado",
+ documentTypeExportedSuccess: 'O Tipo de Documento foi exportado para o ficheiro',
+ documentTypeExportedError: 'Ocorreu um erro ao exportar o Tipo de Documento',
+ dictionaryItemExportedSuccess: 'O(s) item(ns) do dicionário foi(ram) exportado(s) para o ficheiro',
+ dictionaryItemExportedError: 'Ocorreu um erro ao exportar o(s) item(ns) do dicionário',
+ dictionaryItemImported: 'O(s) seguinte(s) item(ns) do dicionário foi(ram) importado(s)!',
+ publishWithNoDomains:
+ 'Os domínios não estão configurados para o site multilingue, por favor contacte um administrador, veja os logs para mais informação',
+ publishWithMissingDomain:
+ 'Não existe nenhum domínio configurado para %0%, por favor contacte um administrador, veja os logs para mais informação',
+ copySuccessMessage: 'A informação do seu sistema foi copiada com sucesso para a área de transferência',
+ cannotCopyInformation: 'Não foi possível copiar a informação do seu sistema para a área de transferência',
+ webhookSaved: 'Webhook guardado',
+ editMultiContentPublishedText: '%0% documentos publicados e estão visíveis no website',
+ editMultiContentUnpublishedText: '%0% documentos despublicados e já não estão visíveis no website',
+ editVariantUnpublishedText: '%0% despublicado e já não está visível no website',
+ editMultiVariantPublishedText: '%0% documentos publicados para os idiomas %1% e estão visíveis no website',
+ editMultiVariantUnpublishedText:
+ '%0% documentos despublicados para os idiomas %1% e já não estão visíveis no website',
+ editContentScheduledSavedText: 'Um agendamento para publicação foi atualizado',
+ editContentScheduledNotSavedText: 'O agendamento para publicação não pôde ser atualizado',
+ editVariantSendToPublishText: '%0% alterações foram enviadas para aprovação',
+ contentCultureUnpublished: 'Variação de conteúdo %0% despublicada',
+ contentCultureValidationError: "Falha na validação para o idioma '%0%'",
+ scheduleErrReleaseDate1: 'A data de lançamento não pode ser no passado',
+ scheduleErrReleaseDate2:
+ "Não é possível agendar o documento para publicação uma vez que o '%0%' obrigatório não está publicado",
+ scheduleErrReleaseDate3:
+ "Não é possível agendar o documento para publicação uma vez que o '%0%' obrigatório tem uma data de publicação posterior a um idioma não obrigatório",
+ scheduleErrExpireDate1: 'A data de expiração não pode ser no passado',
+ scheduleErrExpireDate2: 'A data de expiração não pode ser anterior à data de lançamento',
+ preventCleanupEnableError: 'Ocorreu um erro ao ativar a limpeza de versões para %0%',
+ preventCleanupDisableError: 'Ocorreu um erro ao desativar a limpeza de versões para %0%',
+ offlineHeadline: 'Offline',
+ offlineMessage: 'Está atualmente offline. Por favor, verifique a sua ligação à internet.',
+ onlineHeadline: 'Online',
+ onlineMessage: 'Está agora online. Pode continuar a trabalhar.',
+ },
+ stylesheet: {
+ addRule: 'Adicionar estilo',
+ editRule: 'Editar estilo',
+ editorRules: 'Estilos do editor de texto rico',
+ editorRulesHelp: 'Defina os estilos que devem estar disponíveis no editor de texto rico para esta Stylesheet',
+ editstylesheet: 'Editar Stylesheet',
+ editstylesheetproperty: 'Editar propriedade da Stylesheet',
+ nameHelp: 'O nome exibido no seletor de estilos do editor',
+ preview: 'Pré-visualizar',
+ previewHelp: 'Como o texto aparecerá no editor de texto rico.',
+ selector: 'Seletor',
+ selectorHelp: 'Usa sintaxe CSS, por exemplo, "h1" ou ".redHeader"',
+ styles: 'Estilos',
+ stylesHelp: 'O CSS que deve ser aplicado no editor de texto rico, por exemplo, "color:red;"',
+ tabCode: 'Código',
+ tabRules: 'Editor',
+ },
+ template: {
+ runtimeModeProduction: 'O conteúdo não é editável ao usar o modo de runtime Produção.',
+ deleteByIdFailed: 'Falha ao eliminar o template com o ID %0%',
+ edittemplate: 'Editar template',
+ insertSections: 'Secções',
+ insertContentArea: 'Inserir área de conteúdo',
+ insertContentAreaPlaceHolder: 'Inserir marcador de posição da área de conteúdo',
+ insert: 'Inserir',
+ insertDesc: 'Escolha o que inserir no seu template',
+ insertDictionaryItem: 'Item do dicionário',
+ insertDictionaryItemDesc:
+ 'Um item do dicionário é um marcador de posição para um pedaço de texto traduzível, o que facilita a criação de designs para websites multilingues.',
+ insertMacro: 'Macro',
+ insertMacroDesc:
+ 'Uma Macro é um componente configurável que é ótimo para partes reutilizáveis do seu design, onde precisa da opção de fornecer parâmetros, como galerias, formulários e listas.',
+ insertPageField: 'Valor',
+ insertPageFieldDesc:
+ 'Exibe o valor de um campo nomeado da página atual, com opções para modificar o valor ou recorrer a valores alternativos.',
+ insertPartialView: 'Vista Parcial',
+ insertPartialViewDesc:
+ 'Uma Vista Parcial é um ficheiro de modelo separado que pode ser renderizado dentro de outro template, é ótimo para reutilizar marcação ou para separar modelos complexos em ficheiros separados.',
+ mastertemplate: 'Template principal',
+ quickGuide: 'Guia rápido para tags de template',
+ noMaster: 'Sem template principal',
+ renderBody: 'Renderizar template filho',
+ renderBodyDesc:
+ 'Renderiza o conteúdo de um template filho, inserindo um marcador de posição @RenderBody().',
+ defineSection: 'Definir uma secção nomeada',
+ defineSectionDesc:
+ 'Define uma parte do seu template como uma secção nomeada, envolvendo-a em @section { ... }. Isto pode ser renderizado numa área específica do pai deste template, usando @RenderSection.',
+ renderSection: 'Renderizar uma secção nomeada',
+ renderSectionDesc:
+ 'Renderiza uma área nomeada de um template filho, inserindo um marcador de posição @RenderSection(name). Isto renderiza uma área de um template filho que está envolvida numa definição @section [name]{ ... } correspondente.',
+ sectionName: 'Nome da Secção',
+ sectionMandatory: 'A secção é obrigatória',
+ sectionMandatoryDesc:
+ 'Se for obrigatório, o template filho deve conter uma definição @section, caso contrário é mostrado um erro.',
+ queryBuilder: 'Construtor de consultas',
+ itemsReturned: 'itens retornados, em',
+ publishedItemsReturned: 'Atualmente %0% itens publicados retornados, em %1% ms',
+ iWant: 'Eu quero',
+ allContent: 'todo o conteúdo',
+ contentOfType: 'conteúdo do tipo "%0%"',
+ from: 'de',
+ websiteRoot: 'o meu website',
+ where: 'onde',
+ and: 'e',
+ is: 'é',
+ isNot: 'não é',
+ before: 'antes de',
+ beforeIncDate: 'antes de (incluindo data selecionada)',
+ after: 'depois de',
+ afterIncDate: 'depois de (incluindo data selecionada)',
+ equals: 'igual a',
+ doesNotEqual: 'diferente de',
+ contains: 'contém',
+ doesNotContain: 'não contém',
+ greaterThan: 'maior que',
+ greaterThanEqual: 'maior ou igual a',
+ lessThan: 'menor que',
+ lessThanEqual: 'menor ou igual a',
+ id: 'Id',
+ name: 'Nome',
+ createdDate: 'Data de Criação',
+ lastUpdatedDate: 'Data da Última Atualização',
+ orderBy: 'ordenar por',
+ ascending: 'ascendente',
+ descending: 'descendente',
+ template: 'Template',
+ systemFields: 'Campos de Sistema',
+ },
+ grid: {
+ media: 'Imagem',
+ macro: 'Macro',
+ insertControl: 'Escolha o tipo de conteúdo',
+ chooseLayout: 'Escolha um layout',
+ addRows: 'Adicionar uma linha',
+ addElement: 'Adicionar conteúdo',
+ dropElement: 'Largar conteúdo',
+ settingsApplied: 'Definições aplicadas',
+ contentNotAllowed: 'Este conteúdo não é permitido aqui',
+ contentAllowed: 'Este conteúdo é permitido aqui',
+ clickToEmbed: 'Clique para incorporar',
+ clickToInsertImage: 'Clique para inserir imagem',
+ clickToInsertMacro: 'Clique para inserir macro',
+ placeholderWriteHere: 'Escreva aqui...',
+ gridLayouts: 'Layouts de Grelha',
+ gridLayoutsDetail:
+ 'Os layouts são a área de trabalho geral para o editor de grelha, geralmente só precisa de um ou dois layouts diferentes',
+ addGridLayout: 'Adicionar Layout de Grelha',
+ editGridLayout: 'Editar Layout de Grelha',
+ addGridLayoutDetail: 'Ajuste o layout definindo larguras de coluna e adicionando secções adicionais',
+ rowConfigurations: 'Configurações de linha',
+ rowConfigurationsDetail: 'As linhas são células predefinidas dispostas horizontalmente',
+ addRowConfiguration: 'Adicionar configuração de linha',
+ editRowConfiguration: 'Editar configuração de linha',
+ addRowConfigurationDetail: 'Ajuste a linha definindo larguras de célula e adicionando células adicionais',
+ noConfiguration: 'Nenhuma configuração adicional disponível',
+ columns: 'Colunas',
+ columnsDetails: 'Número total combinado de colunas no layout da grelha',
+ settings: 'Definições',
+ settingsDetails: 'Configure que definições os editores podem alterar',
+ styles: 'Estilos',
+ stylesDetails: 'Configure que estilos os editores podem alterar',
+ allowAllEditors: 'Permitir todos os editores',
+ allowAllRowConfigurations: 'Permitir todas as configurações de linha',
+ maxItems: 'Máximo de itens',
+ maxItemsDescription: 'Deixe em branco ou defina como 0 para ilimitado',
+ setAsDefault: 'Definir como predefinido',
+ chooseExtra: 'Escolher extra',
+ chooseDefault: 'Escolher predefinido',
+ areAdded: 'são adicionados',
+ warning: 'Aviso',
+ warningText:
+ 'Modificar um nome de configuração de linha resultará na perda de dados para qualquer conteúdo existente que seja baseado nesta configuração.
Modificar apenas a etiqueta não resultará em perda de dados.
',
+ youAreDeleting: 'Está a eliminar a configuração da linha',
+ deletingARow:
+ 'Eliminar um nome de configuração de linha resultará na perda de dados para qualquer conteúdo existente que seja baseado nesta configuração.',
+ deleteLayout: 'Está a eliminar o layout',
+ deletingALayout:
+ 'Modificar um layout resultará na perda de dados para qualquer conteúdo existente que seja baseado nesta configuração.',
+ },
+ contentTypeEditor: {
+ compositions: 'Composições',
+ group: 'Grupo',
+ groupReorderSameAliasError:
+ 'Não pode mover o grupo %0% para este separador porque o grupo terá o mesmo alias que um separador: "%1%". Renomeie o grupo para continuar.',
+ noGroups: 'Não adicionou quaisquer grupos',
+ addGroup: 'Adicionar grupo',
+ inheritedFrom: 'Herdado de',
+ addProperty: 'Adicionar propriedade',
+ editProperty: 'Editar propriedade',
+ requiredLabel: 'Etiqueta obrigatória',
+ enableListViewHeading: 'Ativar Vista de Lista',
+ enableListViewDescription:
+ 'Configura o item de conteúdo para mostrar uma lista ordenável e pesquisável dos seus filhos.',
+ allowedTemplatesHeading: 'Template Permitidos',
+ allowedTemplatesDescription: 'Escolha que templates os editores podem usar em conteúdo deste tipo',
+ allowAtRootHeading: 'Permitir na raiz',
+ allowAtRootDescription: 'Permitir que os editores criem conteúdo deste tipo na raiz da árvore de conteúdo.',
+ childNodesHeading: 'Tipos de nós filhos permitidos',
+ childNodesDescription: 'Permitir que conteúdo dos tipos especificados seja criado sob conteúdo deste tipo.',
+ chooseChildNode: 'Escolher nó filho',
+ compositionsDescription:
+ 'Herde separadores e propriedades de um Tipo de Documento existente. Novos separadores serão adicionados ao Tipo de Documento atual ou fundidos se existir um separador com um nome idêntico.',
+ compositionInUse: 'Este Tipo de Conteúdo é usado numa composição e, portanto, não pode ser composto ele próprio.',
+ noAvailableCompositions: 'Não existem Tipos de Conteúdo disponíveis para usar como composição.',
+ compositionRemoveWarning:
+ 'Remover uma composição eliminará todos os dados de propriedade associados. Depois de guardar o Tipo de Documento, não há como voltar atrás.',
+ availableEditors: 'Criar novo',
+ reuse: 'Usar existente',
+ editorSettings: 'Definições do editor',
+ searchResultSettings: 'Configurações disponíveis',
+ searchResultEditors: 'Criar uma nova configuração',
+ configuration: 'Configuração',
+ yesDelete: 'Sim, eliminar',
+ movedUnderneath: 'foi movido para baixo de',
+ copiedUnderneath: 'foi copiado para baixo de',
+ folderToMove: 'Selecione a pasta para mover',
+ folderToCopy: 'Selecione a pasta para copiar',
+ structureBelow: 'para na estrutura de árvore abaixo',
+ allDocumentTypes: 'Todos os Tipos de Documento',
+ allDocuments: 'Todos os Documentos',
+ allMediaItems: 'Todos os itens de multimédia',
+ usingThisDocument:
+ 'usando este Tipo de Documento será eliminado permanentemente, por favor confirme que quer eliminá-los também.',
+ usingThisMedia:
+ 'usando este Tipo de Multimédia será eliminado permanentemente, por favor confirme que quer eliminá-los também.',
+ usingThisMember:
+ 'usando este Tipo de Membro será eliminado permanentemente, por favor confirme que quer eliminá-los também',
+ andAllDocuments: 'e todos os documentos usando este tipo',
+ andAllMediaItems: 'e todos os itens de multimédia usando este tipo',
+ andAllMembers: 'e todos os membros usando este tipo',
+ memberCanEdit: 'O membro pode editar',
+ memberCanEditDescription: 'Permitir que este valor de propriedade seja editado pelo membro na sua página de perfil',
+ isSensitiveData: 'São dados sensíveis',
+ isSensitiveDataDescription:
+ 'Ocultar este valor de propriedade de editores de conteúdo que não têm acesso para visualizar informação sensível',
+ showOnMemberProfile: 'Mostrar no perfil do membro',
+ showOnMemberProfileDescription: 'Permitir que este valor de propriedade seja exibido na página de perfil do membro',
+ tabHasNoSortOrder: 'o separador não tem ordem',
+ compositionUsageHeading: 'Onde é usada esta composição?',
+ compositionUsageSpecification: 'Esta composição é atualmente usada na composição dos seguintes Tipos de Conteúdo:',
+ variantsHeading: 'Variação',
+ cultureVariantHeading: 'Permitir variar por cultura',
+ segmentVariantHeading: 'Permitir segmentação',
+ cultureInvariantLabel: 'Partilhado entre culturas',
+ segmentInvariantLabel: 'Partilhado entre segmentos',
+ cultureAndVariantInvariantLabel: 'Partilhado entre culturas e segmentos',
+ cultureVariantLabel: 'Variar por cultura',
+ segmentVariantLabel: 'Variar por segmento',
+ variantsDescription: 'Permitir que os editores criem conteúdo deste tipo em diferentes idiomas.',
+ cultureVariantDescription: 'Permitir que os editores criem conteúdo de diferentes idiomas.',
+ segmentVariantDescription: 'Permitir que os editores criem segmentos deste conteúdo.',
+ allowVaryByCulture: 'Permitir variação por cultura',
+ allowVaryBySegment: 'Permitir segmentação',
+ elementType: 'Tipo de Elemento',
+ elementHeading: 'É um Tipo de Elemento',
+ elementDescription:
+ 'Um Tipo de Elemento destina-se a ser usado noutros Tipos de Documento, e não na árvore de Conteúdo.',
+ elementCannotToggle:
+ 'Um Tipo de Documento não pode ser alterado para um Tipo de Elemento depois de ter sido usado para criar um ou mais itens de conteúdo.',
+ elementDoesNotSupport: 'Isto não é aplicável a um Tipo de Elemento',
+ propertyHasChanges: 'Fez alterações a esta propriedade. Tem a certeza que quer descartá-las?',
+ displaySettingsHeadline: 'Aparência',
+ displaySettingsLabelOnLeft: 'Etiqueta à esquerda',
+ displaySettingsLabelOnTop: 'Etiqueta acima (largura total)',
+ confirmDeleteTabMessage: 'Tem a certeza que quer eliminar o separador %0% ?',
+ confirmDeleteGroupMessage: 'Tem a certeza que quer eliminar o grupo %0% ?',
+ confirmDeletePropertyMessage: 'Tem a certeza que quer eliminar a propriedade %0% ?',
+ confirmDeleteTabNotice: 'Isto também eliminará todos os itens abaixo deste separador.',
+ confirmDeleteGroupNotice: 'Isto também eliminará todos os itens abaixo deste grupo.',
+ addTab: 'Adicionar separador',
+ convertToTab: 'Converter para separador',
+ tabDirectPropertiesDropZone: 'Arraste propriedades aqui para colocar diretamente no separador',
+ removeChildNode: 'Está a remover o nó filho',
+ removeChildNodeWarning:
+ 'Remover um nó filho limitará as opções dos editores para criar diferentes tipos de conteúdo abaixo de um nó.',
+ usingEditor: 'usando este editor será atualizado com as novas definições.',
+ historyCleanupHeading: 'Limpeza de Histórico',
+ historyCleanupDescription: 'Permitir substituir as definições globais de limpeza do histórico.',
+ historyCleanupKeepAllVersionsNewerThanDays: 'Manter todas as versões mais recentes do que dias',
+ historyCleanupKeepLatestVersionPerDayForDays: 'Manter a versão mais recente por dia durante dias',
+ historyCleanupPreventCleanup: 'Prevenir limpeza',
+ historyCleanupEnableCleanup: 'Ativar limpeza',
+ historyCleanupGloballyDisabled:
+ 'NOTA! A limpeza de versões históricas de conteúdo está desativada globalmente. Estas definições não terão efeito antes de ser ativada.',
+ changeDataTypeHelpText:
+ 'Alterar um tipo de dados com valores armazenados está desativado. Para permitir isto, pode alterar a definição Umbraco:CMS:DataTypes:CanBeChanged em appsettings.json.',
+ collection: 'Coleção',
+ collectionDescription: 'Configura uma visão geral do conteúdo filho.',
+ collections: 'Coleções',
+ collectionsDescription: 'Configura o item de conteúdo para mostrar uma lista dos seus filhos.',
+ structure: 'Estrutura',
+ presentation: 'Apresentação',
+ },
+ webhooks: {
+ addWebhook: 'Criar webhook',
+ addWebhookHeader: 'Adicionar header',
+ addDocumentType: 'Adicionar Tipo de Documento',
+ addMediaType: 'Adicionar Tipo de Multimédia',
+ createHeader: 'Criar header',
+ deliveries: 'Entregas',
+ noHeaders: 'Nenhum header foi adicionado',
+ noEventsFound: 'Nenhum evento foi encontrado.',
+ enabled: 'Ativado',
+ disabled: 'Desativado',
+ events: 'Eventos',
+ event: 'Evento',
+ url: 'URL',
+ types: 'Tipos de Conteúdo',
+ webhookKey: 'Chave do webhook',
+ retryCount: 'Número de tentativas',
+ urlDescription: 'O URL a chamar quando o webhook é acionado.',
+ eventDescription: 'Os eventos para os quais o webhook deve ser acionado.',
+ contentTypeDescription: 'Apenas acionar o webhook para um tipo de conteúdo específico.',
+ enabledDescription: 'O webhook está ativado?',
+ headersDescription: 'Headers personalizados a incluir no pedido do webhook.',
+ contentType: 'Tipo de Conteúdo',
+ headers: 'Headers',
+ selectEventFirst: 'Por favor, selecione um evento primeiro.',
+ selectEvents: 'Selecionar eventos',
+ statusCode: 'Código de estado',
+ unnamedWebhook: 'Webhook sem nome',
+ },
+ languages: {
+ addLanguage: 'Adicionar idioma',
+ culture: 'Código ISO',
+ mandatoryLanguage: 'Idioma obrigatório',
+ mandatoryLanguageHelp: 'As propriedades neste idioma têm de ser preenchidas antes que o nó possa ser publicado.',
+ defaultLanguage: 'Idioma predefinido',
+ defaultLanguageHelp: 'Um site Umbraco só pode ter um idioma predefinido definido.',
+ changingDefaultLanguageWarning: 'Mudar o idioma predefinido pode resultar na falta de conteúdo predefinido.',
+ fallsbackToLabel: 'Recorre a',
+ noFallbackLanguageOption: 'Sem idioma de recurso',
+ fallbackLanguageDescription:
+ 'Para permitir que o conteúdo multilingue recorra a outro idioma se não estiver presente no idioma solicitado, selecione-o aqui.',
+ fallbackLanguage: 'Idioma de recurso',
+ none: 'nenhum',
+ invariantPropertyUnlockHelp: '%0% é partilhado entre idiomas e segmentos.',
+ invariantCulturePropertyUnlockHelp: '%0% é partilhado entre todos os idiomas.',
+ invariantSegmentPropertyUnlockHelp: '%0% é partilhado entre todos os segmentos.',
+ invariantLanguageProperty: 'Partilhado: Idiomas',
+ invariantSegmentProperty: 'Partilhado: Segmentos',
+ },
+ macro: {
+ addParameter: 'Adicionar parâmetro',
+ editParameter: 'Editar parâmetro',
+ enterMacroName: 'Introduza o nome da macro',
+ parameters: 'Parâmetros',
+ parametersDescription: 'Defina os parâmetros que devem estar disponíveis ao usar esta macro.',
+ selectViewFile: 'Selecionar Ficheiro de Macro de Vista Parcial',
+ },
+ modelsBuilder: {
+ buildingModels: 'A Construir Modelos',
+ waitingMessage: 'isto pode demorar um pouco, não se preocupe',
+ modelsGenerated: 'Modelos gerados',
+ modelsGeneratedError: 'Os modelos não puderam ser gerados',
+ modelsExceptionInUlog: 'A geração de modelos falhou, veja a exceção nos logs U',
+ },
+ templateEditor: {
+ addDefaultValue: 'Adicionar valor predefinido',
+ defaultValue: 'Valor predefinido',
+ alternativeField: 'Campo de recurso',
+ alternativeText: 'Valor predefinido',
+ casing: 'Maiúsculas/Minúsculas',
+ encoding: 'Codificação',
+ chooseField: 'Escolher campo',
+ convertLineBreaks: 'Converter quebras de linha',
+ convertLineBreaksHelp: "Substitui quebras de linha pela tag html 'br'",
+ customFields: 'Campos Personalizados',
+ dateOnly: 'Apenas data',
+ formatAsDate: 'Formatar como data',
+ htmlEncode: 'Codificar HTML',
+ htmlEncodeHelp: 'Substituirá caracteres especiais pelo seu equivalente HTML.',
+ insertedAfter: 'Será inserido após o valor do campo',
+ insertedBefore: 'Será inserido antes do valor do campo',
+ lowercase: 'Minúsculas',
+ none: 'Nenhum',
+ outputSample: 'Amostra de saída',
+ postContent: 'Inserir após o campo',
+ preContent: 'Inserir antes do campo',
+ recursive: 'Recursivo',
+ recursiveDescr: 'Sim, torná-lo recursivo',
+ removeParagraph: 'Remover tags de parágrafo',
+ removeParagraphHelp: 'Removerá tags de parágrafo do valor do campo',
+ standardFields: 'Campos Padrão',
+ uppercase: 'Maiúsculas',
+ urlEncode: 'Codificar URL',
+ urlEncodeHelp: 'Formatará caracteres especiais em URLs',
+ usedIfAllEmpty: 'Só será usado quando os valores dos campos acima estiverem vazios',
+ usedIfEmpty: 'Este campo só será usado se o campo primário estiver vazio',
+ withTime: 'Data e hora',
+ },
+ translation: {
+ details: 'Detalhes da tradução',
+ DownloadXmlDTD: 'Transferir XML DTD',
+ fields: 'Campos',
+ includeSubpages: 'Incluir subpáginas',
+ noTranslators:
+ 'Nenhum utilizador tradutor encontrado. Por favor, crie um utilizador tradutor antes de começar a enviar conteúdo para tradução',
+ pageHasBeenSendToTranslation: "A página '%0%' foi enviada para tradução",
+ sendToTranslate: "Enviar a página '%0%' para tradução",
+ totalWords: 'Total de palavras',
+ translateTo: 'Traduzir para',
+ translationDone: 'Tradução concluída.',
+ translationDoneHelp:
+ 'Pode pré-visualizar as páginas que acabou de traduzir, clicando abaixo. Se a página original for encontrada, obterá uma comparação das 2 páginas.',
+ translationFailed: 'A tradução falhou, o ficheiro XML pode estar corrompido',
+ translationOptions: 'Opções de tradução',
+ translator: 'Tradutor',
+ uploadTranslationXml: 'Carregar XML de tradução',
+ },
+ treeHeaders: {
+ content: 'Conteúdo',
+ contentBlueprints: 'Modelos de Documento',
+ media: 'Multimédia',
+ cacheBrowser: 'Navegador de Cache',
+ contentRecycleBin: 'Reciclagem',
+ createdPackages: 'Pacotes criados',
+ dataTypes: 'Tipos de Dados',
+ dictionary: 'Dicionário',
+ installedPackages: 'Pacotes instalados',
+ installSkin: 'Instalar tema',
+ installStarterKit: 'Instalar kit inicial',
+ languages: 'Idiomas',
+ localPackage: 'Instalar pacote local',
+ macros: 'Macros',
+ mediaTypes: 'Tipos de Multimédia',
+ member: 'Membros',
+ memberGroups: 'Grupos de Membros',
+ memberRoles: 'Funções de Membro',
+ memberTypes: 'Tipos de Membro',
+ documentTypes: 'Tipos de Documento',
+ relationTypes: 'Tipos de Relação',
+ packager: 'Pacotes',
+ packages: 'Pacotes',
+ partialViews: 'Vistas Parciais',
+ partialViewMacros: 'Ficheiros de Macro de Vista Parcial',
+ repositories: 'Instalar do repositório',
+ relations: 'Relações',
+ runway: 'Instalar Runway',
+ runwayModules: 'Módulos Runway',
+ scripting: 'Ficheiros de Scripting',
+ scripts: 'Scripts',
+ stylesheets: 'Folhas de Estilo',
+ templates: 'Templates',
+ logViewer: 'Visualizador de Logs',
+ userPermissions: 'Permissões do utilizador',
+ userTypes: 'Tipos de utilizador',
+ users: 'Utilizadores',
+ settingsGroup: 'Definições',
+ templatingGroup: 'Templates',
+ thirdPartyGroup: 'Terceiros',
+ structureGroup: 'Estrutura',
+ advancedGroup: 'Avançado',
+ webhooks: 'Webhooks',
+ },
+ update: {
+ updateAvailable: 'Nova atualização pronta',
+ updateDownloadText: '%0% está pronto, clique aqui para transferir',
+ updateNoServer: 'Sem ligação ao servidor',
+ updateNoServerError: 'Erro ao verificar atualização. Por favor, reveja o stack trace para mais informação',
+ },
+ user: {
+ access: 'Acesso',
+ accessHelp: 'Com base nos grupos atribuídos e nós de início, o utilizador tem acesso aos seguintes nós',
+ assignAccess: 'Atribuir acesso',
+ administrators: 'Administrador',
+ categoryField: 'Campo de categoria',
+ createDate: 'Utilizador criado',
+ createUserHeadline: (kind: string) => {
+ return kind === 'Api' ? 'Criar utilizador API' : 'Criar utilizador';
+ },
+ createUserDescription: (kind: string) => {
+ const defaultUserText = `Crie um utilizador para lhe dar acesso ao Umbraco. Quando um utilizador é criado, será gerada uma palavra-passe que pode partilhar com ele.`;
+ const apiUserText = `Crie um Utilizador API para permitir que serviços externos se autentiquem com a API de Gestão do Umbraco.`;
+ return kind === 'Api' ? apiUserText : defaultUserText;
+ },
+ changePassword: 'Altere a sua palavra-passe',
+ changePhoto: 'Alterar foto',
+ configureMfa: 'Configurar MFA',
+ emailRequired: 'Obrigatório - introduza um endereço de email para este utilizador',
+ emailDescription: (usernameIsEmail: boolean) => {
+ return usernameIsEmail
+ ? 'O endereço de email é usado para notificações, recuperação de palavra-passe e como nome de utilizador para iniciar sessão'
+ : 'O endereço de email é usado para notificações e recuperação de palavra-passe';
+ },
+ kind: 'Tipo',
+ newPassword: 'Nova palavra-passe',
+ newPasswordFormatLengthTip: 'Mínimo %0% carácter(es) para ir!',
+ newPasswordFormatNonAlphaTip: 'Deverá haver pelo menos %0% carácter(es) especiais.',
+ noLockouts: 'não foi bloqueado',
+ noPasswordChange: 'A palavra-passe não foi alterada',
+ confirmNewPassword: 'Confirmar nova palavra-passe',
+ changePasswordDescription:
+ "Pode alterar a sua palavra-passe para aceder ao backoffice do Umbraco preenchendo o formulário abaixo e clicando no botão 'Alterar Palavra-passe'",
+ contentChannel: 'Canal de Conteúdo',
+ createAnotherUser: 'Criar outro utilizador',
+ createUserHelp:
+ 'Crie novos utilizadores para lhes dar acesso ao Umbraco. Quando um novo utilizador é criado, será gerada uma palavra-passe que pode partilhar com o utilizador.',
+ descriptionField: 'Campo de descrição',
+ disabled: 'Desativar Utilizador',
+ documentType: 'Tipo de Documento',
+ duplicateLogin: 'Já existe um utilizador com este login',
+ editors: 'Editor',
+ excerptField: 'Campo de excerto',
+ failedPasswordAttempts: 'Tentativas de login falhadas',
+ goToProfile: 'Ir para o perfil do utilizador',
+ groupsHelp: 'Adicione grupos para atribuir acesso e permissões',
+ invite: 'Convidar',
+ inviteAnotherUser: 'Convidar outro utilizador',
+ inviteUserHelp:
+ 'Convide novos utilizadores para lhes dar acesso ao Umbraco. Será enviado um email de convite ao utilizador com informação sobre como iniciar sessão no Umbraco. Os convites duram 72 horas.',
+ language: 'Cultura da UI',
+ languageHelp: 'Defina a cultura que verá nos menus e diálogos',
+ languageNotFound: (culture: string, baseCulture: string) =>
+ `A cultura especificada "${culture}" não foi encontrada, a cultura base de "${baseCulture}" será usada.`,
+ languageNotFoundFallback: (culture: string, baseCulture: string) =>
+ `Nem a cultura especificada "${culture}" nem a cultura base "${baseCulture}" foram encontradas, a cultura de recurso predefinida de "Inglês (Reino Unido)" será usada.`,
+ lastLockoutDate: 'Última data de bloqueio',
+ lastLogin: 'Último login',
+ lastPasswordChangeDate: 'Palavra-passe alterada pela última vez',
+ loginname: 'Nome de utilizador',
+ loginnameRequired: 'Obrigatório - introduza um nome de utilizador para este utilizador',
+ loginnameDescription: 'O nome de utilizador é usado para iniciar sessão',
+ mediastartnode: 'Nó de início de Multimédia',
+ mediastartnodehelp: 'Limitar a biblioteca de multimédia a um nó de início específico',
+ mediastartnodes: 'Nós de início de multimédia',
+ mediastartnodeshelp: 'Limitar a biblioteca de multimédia a nós de início específicos',
+ modules: 'Secções',
+ nameRequired: 'Obrigatório - introduza um nome para este utilizador',
+ noConsole: 'Desativar Acesso Umbraco',
+ noLogin: 'ainda não iniciou sessão',
+ oldPassword: 'Palavra-passe antiga',
+ password: 'Palavra-passe',
+ resetPassword: 'Redefinir palavra-passe',
+ passwordChanged: 'A sua palavra-passe foi alterada!',
+ passwordChangedGeneric: 'Palavra-passe alterada',
+ passwordConfirm: 'Por favor, confirme a nova palavra-passe',
+ passwordEnterNew: 'Introduza a sua nova palavra-passe',
+ passwordIsBlank: 'A sua nova palavra-passe não pode estar em branco!',
+ passwordCurrent: 'Palavra-passe atual',
+ passwordInvalid: 'Palavra-passe atual inválida',
+ passwordIsDifferent:
+ 'Houve uma diferença entre a nova palavra-passe e a palavra-passe confirmada. Por favor, tente novamente!',
+ passwordMismatch: 'A palavra-passe confirmada não corresponde à nova palavra-passe!',
+ passwordRequiresDigit: "A palavra-passe deve ter pelo menos um dígito ('0'-'9')",
+ passwordRequiresLower: "A palavra-passe deve ter pelo menos uma letra minúscula ('a'-'z')",
+ passwordRequiresNonAlphanumeric: 'A palavra-passe deve ter pelo menos um carácter não alfanumérico',
+ passwordRequiresUniqueChars: 'A palavra-passe deve usar pelo menos %0% caracteres diferentes',
+ passwordRequiresUpper: "A palavra-passe deve ter pelo menos uma letra maiúscula ('A'-'Z')",
+ passwordTooShort: 'A palavra-passe deve ter pelo menos %0% caracteres',
+ permissionReplaceChildren: 'Substituir permissões de nó filho',
+ permissionSelectedPages: 'Está atualmente a modificar permissões para as páginas:',
+ permissionSelectPages: 'Selecione páginas para modificar as suas permissões',
+ removePhoto: 'Remover foto',
+ permissionsDefault: 'Permissões predefinidas',
+ permissionsGranular: 'Permissões granulares',
+ permissionsGranularHelp: 'Definir permissões para nós específicos',
+ granularRightsLabel: 'Documentos',
+ granularRightsDescription: 'Atribuir permissões a documentos específicos',
+ permissionsEntityGroup_document: 'Documento',
+ permissionsEntityGroup_media: 'Multimédia',
+ permissionsEntityGroup_member: 'Membro',
+ 'permissionsEntityGroup_document-property-value': 'Valor da Propriedade do Documento',
+ permissionNoVerbs: 'Sem permissões permitidas',
+ profile: 'Perfil',
+ searchAllChildren: 'Pesquisar todos os filhos',
+ languagesHelp: 'Limitar os idiomas que os utilizadores têm acesso para editar',
+ allowAccessToAllLanguages: 'Permitir acesso a todos os idiomas',
+ allowAccessToAllDocuments: 'Permitir acesso a todos os documentos',
+ allowAccessToAllMedia: 'Permitir acesso a toda a multimédia',
+ sectionsHelp: 'Adicione secções para dar acesso aos utilizadores',
+ selectUserGroup: (multiple: boolean) => {
+ return multiple ? 'Selecionar Grupos de Utilizadores' : 'Selecionar Grupo de Utilizadores';
+ },
+ chooseUserGroup: (multiple: boolean) => {
+ return multiple ? 'Escolher Grupos de Utilizadores' : 'Escolher Grupo de Utilizadores';
+ },
+ entityPermissionsLabel: 'Permissões',
+ entityPermissionsDescription: 'Atribuir permissões para ações',
+ noStartNode: 'Nenhum nó de início selecionado',
+ noStartNodes: 'Nenhum nó de início selecionado',
+ startnode: 'Nó de Início de Conteúdo',
+ startnodehelp: 'Limitar a árvore de conteúdo a um nó de início específico',
+ startnodes: 'Nós de início de conteúdo',
+ startnodeshelp: 'Limitar a árvore de conteúdo a nós de início específicos',
+ updateDate: 'Utilizador atualizado pela última vez',
+ userCreated: 'foi criado',
+ userCreatedSuccessHelp:
+ 'O novo utilizador foi criado com sucesso. Para iniciar sessão no Umbraco, use a palavra-passe abaixo.',
+ userHasPassword: 'O utilizador já tem uma palavra-passe definida',
+ userHasGroup: "O utilizador já está no grupo '%0%'",
+ userLockoutNotEnabled: 'O bloqueio não está ativado para este utilizador',
+ userManagement: 'Gestão de utilizadores',
+ username: 'Nome',
+ userNotInGroup: "O utilizador não está no grupo '%0%'",
+ userPermissions: 'Permissões do utilizador',
+ usergroup: 'Grupo de utilizadores',
+ usergroups: 'Grupos de utilizadores',
+ userInvited: 'foi convidado',
+ userInvitedSuccessHelp:
+ 'Foi enviado um convite ao novo utilizador com detalhes sobre como iniciar sessão no Umbraco.',
+ userinviteWelcomeMessage:
+ 'Olá e bem-vindo(a) ao Umbraco! Em apenas 1 minuto estará pronto, só precisamos que configure uma palavra-passe e adicione uma imagem para o seu avatar.',
+ userinviteExpiredMessage:
+ 'Bem-vindo(a) ao Umbraco! Infelizmente, o seu convite expirou. Por favor, contacte o seu administrador e peça-lhe para o reenviar.',
+ userinviteAvatarMessage:
+ 'Carregar uma foto sua facilitará o reconhecimento por outros utilizadores. Clique no círculo acima para carregar a sua foto.',
+ writer: 'Escritor',
+ configureTwoFactor: 'Configurar Dois Fatores',
+ change: 'Alterar',
+ yourProfile: 'O seu perfil',
+ yourHistory: 'O seu histórico recente',
+ sessionExpires: 'A sessão expira em',
+ inviteUser: 'Convidar utilizador',
+ createUser: 'Criar utilizador',
+ sendInvite: 'Enviar convite',
+ backToUsers: 'Voltar aos utilizadores',
+ defaultInvitationMessage: 'A reenviar convite...',
+ deleteUser: 'Eliminar Utilizador',
+ deleteUserConfirmation: 'Tem a certeza que deseja eliminar esta conta de utilizador?',
+ stateAll: 'Todos',
+ stateActive: 'Ativo',
+ stateDisabled: 'Desativado',
+ stateLockedOut: 'Bloqueado',
+ stateApproved: 'Aprovado',
+ stateInvited: 'Convidado',
+ stateInactive: 'Inativo',
+ sortNameAscending: 'Nome (A-Z)',
+ sortNameDescending: 'Nome (Z-A)',
+ sortCreateDateDescending: 'Mais recente',
+ sortCreateDateAscending: 'Mais antigo',
+ sortLastLoginDateDescending: 'Último login',
+ userKindDefault: 'Utilizador',
+ userKindApi: 'Utilizador API',
+ noUserGroupsAdded: 'Nenhum grupo de utilizadores foi adicionado',
+ '2faDisableText':
+ 'Se desejar desativar este fornecedor de autenticação de dois fatores, deve introduzir o código mostrado no seu dispositivo de autenticação:',
+ '2faProviderIsEnabled': 'Este fornecedor de autenticação de dois fatores está ativado',
+ '2faProviderIsEnabledMsg': '{0} está agora ativado',
+ '2faProviderIsNotEnabledMsg': 'Algo correu mal ao tentar ativar {0}',
+ '2faProviderIsDisabledMsg': '{0} está agora desativado',
+ '2faProviderIsNotDisabledMsg': 'Algo correu mal ao tentar desativar {0}',
+ '2faDisableForUser': 'Deseja desativar "{0}" neste utilizador?',
+ '2faQrCodeAlt': 'Código QR para autenticação de dois fatores com {0}',
+ '2faQrCodeTitle': 'Código QR para autenticação de dois fatores com {0}',
+ '2faQrCodeDescription':
+ 'Digitalize este código QR com a sua aplicação de autenticação para ativar a autenticação de dois fatores',
+ '2faCodeInput': 'Código de verificação',
+ '2faCodeInputHelp': 'Por favor, introduza o código de verificação',
+ '2faInvalidCode': 'Código introduzido inválido',
+ },
+ validation: {
+ validation: 'Validação',
+ validateNothing: 'Sem validação',
+ validateAsEmail: 'Validar como um endereço de email',
+ validateAsNumber: 'Validar como um número',
+ validateAsUrl: 'Validar como um URL',
+ enterCustomValidation: '...ou introduza uma validação personalizada',
+ fieldIsMandatory: 'Campo obrigatório',
+ mandatoryMessage: 'Introduza uma mensagem de erro de validação personalizada (opcional)',
+ validationRegExp: 'Introduza uma expressão regular',
+ validationRegExpMessage: 'Introduza uma mensagem de erro de validação personalizada (opcional)',
+ minCount: 'Precisa de adicionar pelo menos',
+ maxCount: 'Só pode ter',
+ addUpTo: 'Adicionar até',
+ items: 'itens',
+ urls: 'URL(s)',
+ urlsSelected: 'URL(s) selecionado(s)',
+ itemsSelected: 'item(ns) selecionado(s)',
+ invalidDate: 'Data inválida',
+ invalidNumber: 'Não é um número',
+ invalidNumberStepSize: 'Não é um tamanho de passo numérico válido',
+ invalidEmail: 'Email inválido',
+ invalidNull: 'O valor não pode ser nulo',
+ invalidEmpty: 'O valor não pode estar vazio',
+ invalidFalse: 'Este campo deve estar ativado',
+ invalidPattern: 'O valor é inválido, não corresponde ao padrão correto',
+ customValidation: 'Validação personalizada',
+ entriesShort: 'Mínimo %0% entradas, requer %1% mais.',
+ entriesExceed: 'Máximo %0% entradas, introduziu %1% a mais.',
+ entriesAreasMismatch: 'Os requisitos de quantidade de conteúdo não são cumpridos para uma ou mais áreas.',
+ invalidMemberGroupName: 'Nome de grupo de membros inválido',
+ invalidUserGroupName: 'Nome de grupo de utilizadores inválido',
+ invalidToken: 'Token inválido',
+ invalidUsername: 'Nome de utilizador inválido',
+ duplicateEmail: "O email '%0%' já está em uso",
+ duplicateUserGroupName: "O nome do grupo de utilizadores '%0%' já está em uso",
+ duplicateMemberGroupName: "O nome do grupo de membros '%0%' já está em uso",
+ duplicateUsername: "O nome de utilizador '%0%' já está em uso",
+ legacyOption: 'Opção legada',
+ legacyOptionDescription: 'Esta opção já não é suportada, por favor selecione outra coisa',
+ numberMinimum: "O valor deve ser maior ou igual a '%0%'.",
+ numberMaximum: "O valor deve ser menor ou igual a '%0%'.",
+ numberMisconfigured: "O valor mínimo '%0%'deve ser menor que o valor máximo '%1%'.",
+ invalidExtensions: 'Uma ou mais extensões são inválidas.',
+ allowedExtensions: 'As extensões permitidas são:',
+ disallowedExtensions: 'As extensões não permitidas são:',
+ },
+ healthcheck: {
+ checkSuccessMessage: "O valor está definido para o valor recomendado: '%0%'.",
+ checkErrorMessageDifferentExpectedValue:
+ "Valor esperado '%1%' para '%2%' no ficheiro de configuração '%3%', mas encontrado '%0%'.",
+ checkErrorMessageUnexpectedValue: "Encontrado valor inesperado '%0%' para '%2%' no ficheiro de configuração '%3%'.",
+ macroErrorModeCheckSuccessMessage: "MacroErrors estão definidos como '%0%'.",
+ macroErrorModeCheckErrorMessage:
+ "MacroErrors estão definidos como '%0%', o que impedirá que algumas ou todas as páginas do seu site carreguem completamente se houver erros nas macros. Corrigir isto definirá o valor para '%1%'.",
+ httpsCheckValidCertificate: 'O certificado do seu website é válido.',
+ httpsCheckInvalidCertificate: "Erro de validação do certificado: '%0%'",
+ httpsCheckExpiredCertificate: 'O certificado SSL do seu website expirou.',
+ httpsCheckExpiringCertificate: 'O certificado SSL do seu website expira em %0% dias.',
+ healthCheckInvalidUrl: "Erro ao contactar o URL %0% - '%1%'",
+ httpsCheckIsCurrentSchemeHttps: 'Está atualmente a %0% visualizar o site usando o esquema HTTPS.',
+ httpsCheckConfigurationRectifyNotPossible:
+ "A appSetting 'Umbraco:CMS:Global:UseHttps' está definida como 'false' no seu ficheiro appSettings.json. Assim que aceder a este site usando o esquema HTTPS, deve ser definida como 'true'.",
+ httpsCheckConfigurationCheckResult:
+ "A appSetting 'Umbraco:CMS:Global:UseHttps' está definida como '%0%' no seu ficheiro appSettings.json, os seus cookies estão %1% marcados como seguros.",
+ compilationDebugCheckSuccessMessage: 'O modo de compilação de debug está desativado.',
+ compilationDebugCheckErrorMessage:
+ 'O modo de compilação de debug está atualmente ativado. Recomenda-se desativar esta definição antes de entrar em produção.',
+ umbracoApplicationUrlCheckResultTrue:
+ "A appSetting 'Umbraco:CMS:WebRouting:UmbracoApplicationUrl' está definida como %0% .",
+ umbracoApplicationUrlCheckResultFalse:
+ "A appSetting 'Umbraco:CMS:WebRouting:UmbracoApplicationUrl' não está definida.",
+ clickJackingCheckHeaderFound:
+ 'O header ou meta-tag X-Frame-Options usado para controlar se um site pode ser IFRAMEd por outro foi encontrado.',
+ clickJackingCheckHeaderNotFound:
+ 'O header ou meta-tag X-Frame-Options usado para controlar se um site pode ser IFRAMEd por outro não foi encontrado.',
+ noSniffCheckHeaderFound:
+ 'O header ou meta-tag X-Content-Type-Options usado para proteger contra vulnerabilidades de MIME sniffing foi encontrado.',
+ noSniffCheckHeaderNotFound:
+ 'O header ou meta-tag X-Content-Type-Options usado para proteger contra vulnerabilidades de MIME sniffing não foi encontrado.',
+ hSTSCheckHeaderFound:
+ 'O header Strict-Transport-Security , também conhecido como header HSTS, foi encontrado.',
+ hSTSCheckHeaderNotFound: 'O header Strict-Transport-Security não foi encontrado.',
+ hSTSCheckHeaderFoundOnLocalhost:
+ 'O header Strict-Transport-Security , também conhecido como header HSTS, foi encontrado. Este header não deve estar presente no localhost. ',
+ hSTSCheckHeaderNotFoundOnLocalhost:
+ 'O header Strict-Transport-Security não foi encontrado. Este header não deve estar presente no localhost.',
+ xssProtectionCheckHeaderFound:
+ 'O header X-XSS-Protection foi encontrado. Recomenda-se não adicionar este header ao seu website . Pode ler sobre isto no website da Mozilla ',
+ xssProtectionCheckHeaderNotFound: 'O header X-XSS-Protection não foi encontrado.',
+ excessiveHeadersFound:
+ 'Os seguintes headers que revelam informação sobre a tecnologia do website foram encontrados: %0% .',
+ excessiveHeadersNotFound: 'Nenhum header revelando informação sobre a tecnologia do website foi encontrado.',
+ smtpMailSettingsNotFound: "A configuração 'Umbraco:CMS:Global:Smtp' não pôde ser encontrada.",
+ smtpMailSettingsHostNotConfigured: "A configuração 'Umbraco:CMS:Global:Smtp:Host' não pôde ser encontrada.",
+ smtpMailSettingsConnectionSuccess:
+ 'As definições SMTP estão configuradas corretamente e o serviço está a funcionar como esperado.',
+ smtpMailSettingsConnectionFail:
+ "O servidor SMTP configurado com o domínio '%0%' e a porta '%1%' não pôde ser alcançado. Por favor, verifique se as definições SMTP na configuração 'Umbraco:CMS:Global:Smtp' estão corretas.",
+ notificationEmailsCheckSuccessMessage: 'O email de notificação foi definido para %0% .',
+ notificationEmailsCheckErrorMessage:
+ 'O email de notificação ainda está definido para o valor predefinido de %0% .',
+ checkGroup: 'Verificar grupo',
+ helpText:
+ 'O verificador de saúde avalia várias áreas do seu site para configurações de melhores práticas, configuração, problemas potenciais, etc. Pode facilmente corrigir problemas pressionando um botão. Pode adicionar as suas próprias verificações de saúde, consulte a documentação para mais informação sobre verificações de saúde personalizadas.
',
+ },
+ redirectUrls: {
+ disableUrlTracker: 'Desativar Monitorizador de URLs',
+ enableUrlTracker: 'Ativar Monitorizador de URLs',
+ originalUrl: 'URL Original',
+ redirectedTo: 'Redirecionado Para',
+ redirectUrlManagement: 'Gestão de URL de Redirecionamento',
+ panelInformation: 'Os seguintes URLs redirecionam para este item de conteúdo:',
+ noRedirects: 'Nenhum redirecionamento foi feito',
+ noRedirectsDescription:
+ 'Quando uma página publicada é renomeada ou movida, um redirecionamento será automaticamente feito para a nova página.',
+ redirectRemoved: 'URL de redirecionamento removido.',
+ redirectRemoveError: 'Erro ao remover URL de redirecionamento.',
+ redirectRemoveWarning: 'Isto removerá o redirecionamento',
+ confirmDisable: 'Tem a certeza que quer desativar o monitorizador de URL?',
+ disabledConfirm: 'O monitorizador de URLs foi agora desativado.',
+ disableError:
+ 'Erro ao desativar o monitorizador de URLs, mais informação pode ser encontrada no seu ficheiro de logs.',
+ enabledConfirm: 'O monitorizador de URLs foi agora ativado.',
+ enableError: 'Erro ao ativar o monitorizador de URLs, mais informação pode ser encontrada no seu ficheiro de logs.',
+ culture: 'Cultura',
+ },
+ emptyStates: {
+ emptyDictionaryTree: 'Nenhum item do Dicionário para escolher',
+ },
+ textbox: {
+ characters_left: '%0% caracteres restantes.',
+ characters_exceed: 'Máximo %0% caracteres, %1% a mais.',
+ },
+ recycleBin: {
+ contentTrashed: 'Conteúdo no lixo com Id: {0} relacionado com o conteúdo pai original com Id: {1}',
+ mediaTrashed: 'Multimédia no lixo com Id: {0} relacionada com o item de multimédia pai original com Id: {1}',
+ itemCannotBeRestored: 'Não é possível restaurar automaticamente este item',
+ itemCannotBeRestoredHelpText:
+ 'Não existe nenhuma localização onde este item possa ser restaurado automaticamente. Pode mover o item manualmente usando a árvore abaixo.',
+ wasRestored: 'foi restaurado em',
+ },
+ relationType: {
+ direction: 'Direção',
+ parentToChild: 'Pai para filho',
+ bidirectional: 'Bidirecional',
+ parent: 'Pai',
+ child: 'Filho',
+ count: 'Contagem',
+ relation: 'Relação',
+ relations: 'Relações',
+ created: 'Criado',
+ comment: 'Comentário',
+ name: 'Nome',
+ noRelations: 'Sem relações para este Tipo de Relação',
+ tabRelationType: 'Tipo de Relação',
+ tabRelations: 'Relações',
+ isDependency: 'É Dependência',
+ dependency: 'Sim',
+ noDependency: 'Não',
+ },
+ dashboardTabs: {
+ contentIntro: 'Introdução',
+ contentRedirectManager: 'Gestão de URL de Redirecionamento',
+ mediaFolderBrowser: 'Conteúdo',
+ settingsWelcome: 'Bem-vindo(a)',
+ settingsExamine: 'Gerir Examine',
+ settingsPublishedStatus: 'Estado de Publicação',
+ settingsModelsBuilder: 'Gerador de Modelos',
+ settingsHealthCheck: 'Verificação de Saúde',
+ settingsProfiler: 'Perfil de Desempenho',
+ memberIntro: 'Introdução',
+ settingsAnalytics: 'Dados de telemetria',
+ },
+ visuallyHiddenTexts: {
+ goBack: 'Voltar',
+ activeListLayout: 'Layout ativo:',
+ jumpTo: 'Saltar para',
+ group: 'grupo',
+ passed: 'passou',
+ warning: 'aviso',
+ failed: 'falhou',
+ suggestion: 'sugestão',
+ checkPassed: 'Verificação passou',
+ checkFailed: 'Verificação falhou',
+ openBackofficeSearch: 'Abrir pesquisa do backoffice',
+ openCloseBackofficeHelp: 'Abrir/Fechar ajuda do backoffice',
+ openCloseBackofficeProfileOptions: 'Abrir/Fechar as opções do seu perfil',
+ assignDomainDescription: 'Configurar Cultura e Domínios para %0%',
+ createDescription: 'Criar novo nó em %0%',
+ protectDescription: 'Configurar restrições de acesso em %0%',
+ rightsDescription: 'Configurar Permissões em %0%',
+ sortDescription: 'Alterar ordem para %0%',
+ createblueprintDescription: 'Criar Modelo de Documento baseado em %0%',
+ openContextMenu: 'Abrir menu de contexto para',
+ currentLanguage: 'Idioma atual',
+ switchLanguage: 'Mudar idioma para',
+ createNewFolder: 'Criar nova pasta',
+ newPartialView: 'Vista Parcial',
+ newPartialViewMacro: 'Macro de Vista Parcial',
+ newMember: 'Membro',
+ newDataType: 'Tipo de Dados',
+ redirectDashboardSearchLabel: 'Pesquisar no painel de controlo de redirecionamentos',
+ userGroupSearchLabel: 'Pesquisar na secção de grupos de utilizadores',
+ userSearchLabel: 'Pesquisar na secção de utilizadores',
+ createItem: 'Criar item',
+ create: 'Criar',
+ edit: 'Editar',
+ name: 'Nome',
+ addNewRow: 'Adicionar nova linha',
+ tabExpand: 'Ver mais opções',
+ searchOverlayTitle: 'Pesquisar no backoffice do Umbraco',
+ searchOverlayDescription: 'Pesquisar por nós de conteúdo, nós de multimédia, etc. em todo o backoffice.',
+ searchInputDescription:
+ 'Quando os resultados da autocompletação estiverem disponíveis, prima as setas para cima e para baixo, ou use a tecla tab e use a tecla enter para selecionar.',
+ path: 'Caminho:',
+ foundIn: 'Encontrado em',
+ hasTranslation: 'Tem tradução',
+ noTranslation: 'Tradução em falta',
+ dictionaryListCaption: 'Itens do dicionário',
+ contextMenuDescription: 'Selecione uma das opções para editar o nó.',
+ contextDialogDescription: 'Executar ação %0% no nó %1%',
+ addImageCaption: 'Adicionar legenda da imagem',
+ searchContentTree: 'Pesquisar Árvore de Conteúdo',
+ maxAmount: 'Quantidade máxima',
+ expandChildItems: 'Expandir itens filhos para',
+ openContextNode: 'Abrir nó de contexto para',
+ },
+ references: {
+ tabName: 'Referências',
+ DataTypeNoReferences: 'Este Tipo de Dados não tem referências.',
+ itemHasNoReferences: 'Este item não tem referências.',
+ labelUsedByDocumentTypes: 'Usado em Tipos de Documento',
+ labelUsedByMediaTypes: 'Usado em Tipos de Multimédia',
+ labelUsedByMemberTypes: 'Usado em Tipos de Membro',
+ usedByProperties: 'Usado por',
+ labelUsedByDocuments: 'Usado em Documentos',
+ labelUsedByMembers: 'Usado em Membros',
+ labelUsedByMedia: 'Usado em Multimédia',
+ labelUsedItems: 'Itens em uso',
+ labelUsedDescendants: 'Descendentes em uso',
+ deleteWarning:
+ 'Este item ou os seus descendentes estão a ser usados. A eliminação pode levar a links quebrados no seu website.',
+ unpublishWarning:
+ 'Este item ou os seus descendentes estão a ser usados. A despublicação pode levar a links quebrados no seu website. Por favor, tome as medidas apropriadas.',
+ deleteDisabledWarning:
+ 'Este item ou os seus descendentes estão a ser usados. Portanto, a eliminação foi desativada.',
+ listViewDialogWarning: 'Os seguintes itens que está a tentar %0% são usados por outro conteúdo.',
+ labelUsedByItems: 'Referenciado pelos seguintes itens',
+ labelDependsOnThis: 'Os seguintes itens dependem disto',
+ labelDependentDescendants: 'Os seguintes itens descendentes têm dependências',
+ labelMoreReferences: (count: number) => {
+ if (count === 1) return '...e mais um item';
+ return `...e mais ${count} itens`;
+ },
+ },
+ logViewer: {
+ deleteSavedSearch: 'Eliminar Pesquisa Guardada',
+ logLevels: 'Níveis de Log',
+ selectAllLogLevelFilters: 'Selecionar todos',
+ deselectAllLogLevelFilters: 'Desmarcar todos',
+ savedSearches: 'Pesquisas Guardadas',
+ saveSearch: 'Guardar Pesquisa',
+ saveSearchDescription: 'Introduza um nome amigável para a sua consulta de pesquisa',
+ filterSearch: 'Filtrar Pesquisa',
+ totalItems: 'Total de Itens',
+ timestamp: 'Timestamp',
+ level: 'Nível',
+ machine: 'Máquina',
+ message: 'Mensagem',
+ exception: 'Exceção',
+ properties: 'Propriedades',
+ searchWithGoogle: 'Pesquisar Com Google',
+ searchThisMessageWithGoogle: 'Pesquisar esta mensagem com Google',
+ searchWithBing: 'Pesquisar Com Bing',
+ searchThisMessageWithBing: 'Pesquisar esta mensagem com Bing',
+ searchOurUmbraco: 'Pesquisar Our Umbraco',
+ searchThisMessageOnOurUmbracoForumsAndDocs: 'Pesquisar esta mensagem nos fóruns e docs do Our Umbraco',
+ searchOurUmbracoWithGoogle: 'Pesquisar Our Umbraco com Google',
+ searchOurUmbracoForumsUsingGoogle: 'Pesquisar nos fóruns do Our Umbraco usando Google',
+ searchUmbracoSource: 'Pesquisar Código Fonte Umbraco',
+ searchWithinUmbracoSourceCodeOnGithub: 'Pesquisar no código fonte do Umbraco no GitHub',
+ searchUmbracoIssues: 'Pesquisar Problemas Umbraco',
+ searchUmbracoIssuesOnGithub: 'Pesquisar Problemas Umbraco no GitHub',
+ deleteThisSearch: 'Eliminar esta pesquisa',
+ findLogsWithRequestId: 'Encontrar Logs com ID de Pedido',
+ findLogsWithNamespace: 'Encontrar Logs com Namespace',
+ findLogsWithMachineName: 'Encontrar Logs com Nome da Máquina',
+ open: 'Abrir',
+ polling: 'Polling',
+ every2: 'A cada 2 segundos',
+ every5: 'A cada 5 segundos',
+ every10: 'A cada 10 segundos',
+ every20: 'A cada 20 segundos',
+ every30: 'A cada 30 segundos',
+ pollingEvery2: 'Polling a cada 2s',
+ pollingEvery5: 'Polling a cada 5s',
+ pollingEvery10: 'Polling a cada 10s',
+ pollingEvery20: 'Polling a cada 20s',
+ pollingEvery30: 'Polling a cada 30s',
+ },
+ clipboard: {
+ labelForCopyAllEntries: 'Copiar %0%',
+ labelForArrayOfItemsFrom: '%0% de %1%',
+ labelForArrayOfItems: 'Coleção de %0%',
+ labelForRemoveAllEntries: 'Remover todos os itens',
+ labelForClearClipboard: 'Limpar área de transferência',
+ labelForCopyToClipboard: 'Copiar para a área de transferência',
+ confirmDeleteHeadline: 'Eliminar da área de transferência',
+ confirmDeleteDescription: 'Tem a certeza que quer eliminar {0} da área de transferência?',
+ copySuccessHeadline: 'Copiado para a área de transferência',
+ },
+ propertyActions: {
+ tooltipForPropertyActionsMenu: 'Abrir Ações da Propriedade',
+ tooltipForPropertyActionsMenuClose: 'Fechar Ações da Propriedade',
+ },
+ nuCache: {
+ refreshStatus: 'Atualizar estado',
+ memoryCache: 'Cache de Memória',
+ memoryCacheDescription:
+ 'Este botão permite recarregar a cache na memória, recarregando-a inteiramente da cache da base de dados (mas não reconstrói essa cache da base de dados). Isto é relativamente rápido. Use-o quando achar que a cache de memória não foi devidamente atualizada, após alguns eventos acionados — o que indicaria um pequeno problema no Umbraco. (nota: aciona o recarregamento em todos os servidores num ambiente LB, e limpará a cache de segundo nível se a tiver ativada).',
+ reload: 'Recarregar',
+ databaseCache: 'Cache da Base de Dados',
+ databaseCacheDescription:
+ 'Este botão permite reconstruir a cache da base de dados, por exemplo, o conteúdo da tabela cmsContentNu. A reconstrução pode ser dispendiosa. Use-o quando o recarregamento não for suficiente, e achar que a cache da base de dados não foi devidamente gerada — o que indicaria algum problema crítico no Umbraco.',
+ rebuild: 'Reconstruir',
+ internals: 'Internos',
+ internalsDescription:
+ 'Este botão permite acionar uma coleção de instantâneos NuCache (depois de executar um GC CLR completo). A menos que saiba o que isso significa, provavelmente não precisa de o usar.',
+ collect: 'Recolher',
+ publishedCacheStatus: 'Estado da Cache Publicada',
+ caches: 'Caches',
+ },
+ profiling: {
+ performanceProfiling: 'Criação de perfil de desempenho',
+ performanceProfilingDescription:
+ 'O Umbraco está atualmente a ser executado em modo de debug. Isto significa que pode usar o criador de perfil de desempenho integrado para avaliar o desempenho ao renderizar páginas.
Se quiser ativar o criador de perfil para uma renderização de página específica, simplesmente adicione umbDebug=true à querystring ao solicitar a página.
Se quiser que o criador de perfil seja ativado por predefinição para todas as renderizações de página, pode usar o botão de alternância abaixo. Ele definirá um cookie no seu browser, que ativa o criador de perfil automaticamente. Por outras palavras, o criador de perfil só estará ativo por predefinição no seu browser - não no de todos os outros.
',
+ activateByDefault: 'Ativar o criador de perfil por predefinição',
+ reminder: 'Lembrete amigável',
+ reminderDescription:
+ 'Nunca deve deixar um site de produção a ser executado em modo de debug. O modo de debug é desativado definindo Umbraco:CMS:Hosting:Debug como false em appsettings.json, appsettings.{Environment}.json ou através de uma variável de ambiente.
',
+ profilerEnabledDescription:
+ 'O Umbraco não está atualmente a ser executado em modo de debug, por isso não pode usar o criador de perfil integrado. É assim que deve ser para um site de produção.
O modo de debug é ativado definindo Umbraco:CMS:Hosting:Debug como true em appsettings.json, appsettings.{Environment}.json ou através de uma variável de ambiente.
',
+ },
+ settingsDashboardVideos: {
+ trainingHeadline: 'Horas de vídeos de formação Umbraco estão apenas a um clique de distância',
+ trainingDescription:
+ 'Quer dominar o Umbraco? Gaste alguns minutos a aprender algumas das melhores práticas vendo um destes vídeos sobre como usar o Umbraco. E visite umbraco.tv para ainda mais vídeos Umbraco
',
+ learningBaseDescription:
+ ' Quer dominar o Umbraco? Gaste alguns minutos a aprender algumas das melhores práticas visitando o canal de Youtube da Base de Aprendizagem Umbraco . Aqui pode encontrar muito material em vídeo cobrindo muitos aspetos do Umbraco.
',
+ getStarted: 'Para começar',
+ },
+ settingsDashboard: {
+ documentationHeader: 'Documentação',
+ documentationDescription: 'Leia mais sobre como trabalhar com os itens nas Definições na nossa Documentação.',
+ communityHeader: 'Comunidade',
+ communityDescription: 'Faça uma pergunta no fórum da comunidade ou na nossa comunidade Discord.',
+ trainingHeader: 'Formação',
+ trainingDescription: 'Descubra oportunidades de formação presencial e certificação',
+ supportHeader: 'Suporte',
+ supportDescription:
+ 'Expanda a sua equipa com um grupo altamente qualificado e apaixonado de especialistas em Umbraco.',
+ videosHeader: 'Vídeos',
+ videosDescription:
+ 'Veja os nossos tutoriais em vídeo gratuitos no canal de Youtube da Base de Aprendizagem Umbraco, para se familiarizar rapidamente com o Umbraco.',
+ getHelp: 'Obtenha a ajuda de que precisa',
+ getCertified: 'Obtenha Certificação',
+ goForum: 'Ir para o fórum',
+ chatWithCommunity: 'Converse com a comunidade',
+ watchVideos: 'Veja os vídeos',
+ },
+ startupDashboard: {
+ fallbackHeadline: 'Bem-vindo(a) ao CMS Amigável',
+ fallbackDescription:
+ 'Obrigado por escolher o Umbraco - achamos que isto pode ser o início de algo bonito. Embora possa parecer esmagador no início, fizemos muito para tornar a curva de aprendizagem o mais suave e rápida possível.',
+ },
+ welcomeDashboard: {
+ ourUmbracoHeadline: 'Our Umbraco - A Comunidade Mais Amigável',
+ ourUmbracoDescription:
+ 'Our Umbraco, o site oficial da comunidade, é o seu balcão único para tudo relacionado com o Umbraco. Quer precise de uma resposta a uma pergunta, plugins interessantes ou um guia de como fazer algo no Umbraco, a melhor e mais amigável comunidade do mundo está apenas a um clique de distância.',
+ ourUmbracoButton: 'Visitar Our Umbraco',
+ documentationHeadline: 'Documentação',
+ documentationDescription: 'Encontre as respostas para todas as suas perguntas sobre o Umbraco',
+ communityHeadline: 'Comunidade',
+ communityDescription: 'Obtenha suporte e inspiração de especialistas em Umbraco motivados',
+ resourcesHeadline: 'Recursos',
+ resourcesDescription: 'Tutoriais em vídeo gratuitos para impulsionar a sua jornada com o CMS',
+ trainingHeadline: 'Formação',
+ trainingDescription: 'Formação presencial e certificações oficiais Umbraco',
+ },
+ blockEditor: {
+ headlineCreateBlock: 'Escolher Tipo de Elemento',
+ headlineAddSettingsElementType: 'Anexar um Tipo de Elemento de definições',
+ headlineAddCustomView: 'Selecionar vista',
+ headlineAddCustomStylesheet: 'Selecionar Stylesheet',
+ headlineAddThumbnail: 'Escolher miniatura',
+ labelcreateNewElementType: 'Criar novo Tipo de Elemento',
+ labelCustomStylesheet: 'Stylesheet personalizada',
+ addCustomStylesheet: 'Adicionar Stylesheet',
+ headlineEditorAppearance: 'Aparência do bloco',
+ headlineDataModels: 'Modelos de dados',
+ headlineCatalogueAppearance: 'Aparência do catálogo',
+ labelBackgroundColor: 'Cor de fundo',
+ labelIconColor: 'Cor do ícone',
+ labelContentElementType: 'Modelo de Conteúdo',
+ labelLabelTemplate: 'Etiqueta',
+ labelCustomView: 'Vista personalizada',
+ labelCustomViewInfoTitle: 'Mostrar descrição da vista personalizada',
+ labelCustomViewDescription:
+ 'Substitua como este bloco aparece na UI do backoffice. Escolha um ficheiro .html contendo a sua apresentação.',
+ labelSettingsElementType: 'Modelo de Definições',
+ labelEditorSize: 'Tamanho do editor de sobreposição',
+ addCustomView: 'Adicionar vista personalizada',
+ addSettingsElementType: 'Adicionar definições',
+ confirmDeleteBlockTitle: 'Eliminar %0%?',
+ confirmDeleteBlockMessage: 'Tem a certeza que quer eliminar este %0%?',
+ confirmDeleteBlockTypeMessage: 'Tem a certeza que quer eliminar a configuração do bloco %0% ?',
+ confirmDeleteBlockTypeNotice:
+ 'O conteúdo deste bloco continuará presente, a edição deste conteúdo já não estará disponível e será mostrada como conteúdo não suportado.',
+ confirmDeleteBlockGroupTitle: 'Eliminar grupo?',
+ confirmDeleteBlockGroupMessage:
+ 'Tem a certeza que quer eliminar o grupo %0% e todas as configurações de Bloco deste?',
+ confirmDeleteBlockGroupNotice:
+ 'O conteúdo destes Blocos continuará presente, a edição deste conteúdo já não estará disponível e será mostrada como conteúdo não suportado.',
+ blockConfigurationOverlayTitle: "Configuração de '%0%'",
+ elementTypeDoesNotExist: 'Não pode ser editado porque o Tipo de Elemento não existe.',
+ thumbnail: 'Miniatura',
+ addThumbnail: 'Adicionar miniatura',
+ tabCreateEmpty: 'Criar vazio',
+ tabClipboard: 'Área de Transferência',
+ tabBlockSettings: 'Definições',
+ headlineAdvanced: 'Avançado',
+ headlineCustomView: 'Vista Personalizada',
+ forceHideContentEditor: 'Ocultar editor de conteúdo',
+ forceHideContentEditorHelp:
+ 'Ocultar o botão de edição de conteúdo e o editor de conteúdo da sobreposição do Editor de Blocos.',
+ gridInlineEditing: 'Edição em linha',
+ gridInlineEditingHelp:
+ 'Ativa a edição em linha para a primeira Propriedade. Propriedades adicionais podem ser editadas na sobreposição.',
+ blockHasChanges: 'Fez alterações a este conteúdo. Tem a certeza que quer descartá-las?',
+ confirmCancelBlockCreationHeadline: 'Descartar criação?',
+ confirmCancelBlockCreationMessage: 'Tem a certeza que quer cancelar a criação.',
+ elementTypeDoesNotExistHeadline: 'Erro!',
+ elementTypeDoesNotExistDescription: 'O Tipo de Elemento deste bloco já não existe',
+ addBlock: 'Adicionar conteúdo',
+ addThis: 'Adicionar %0%',
+ propertyEditorNotSupported: "A propriedade '%0%' usa o editor '%1%' que não é suportado em blocos.",
+ focusParentBlock: 'Definir foco no bloco contentor',
+ areaIdentification: 'Identificação',
+ areaValidation: 'Validação',
+ areaValidationEntriesShort: '%0% deve estar presente pelo menos %2% vez(es).',
+ areaValidationEntriesExceed: '%0% deve estar presente no máximo %3% vez(es).',
+ areaNumberOfBlocks: 'Número de blocos',
+ areaDisallowAllBlocks: 'Apenas permitir tipos de bloco específicos',
+ areaAllowedBlocks: 'Tipos de Blocos Permitidos',
+ areaAllowedBlocksHelp:
+ 'Defina os tipos de blocos que são permitidos nesta área, e opcionalmente quantos de cada tipo devem estar presentes.',
+ confirmDeleteBlockAreaMessage: 'Tem a certeza que quer eliminar esta área?',
+ confirmDeleteBlockAreaNotice: 'Quaisquer blocos atualmente criados dentro desta área serão eliminados.',
+ layoutOptions: 'Opções de layout',
+ structuralOptions: 'Estrutural',
+ sizeOptions: 'Opções de tamanho',
+ sizeOptionsHelp: 'Defina uma ou mais opções de tamanho, isto ativa o redimensionamento do Bloco',
+ allowedBlockColumns: 'Extensões de coluna disponíveis',
+ allowedBlockColumnsHelp:
+ 'Defina o número diferente de colunas que este bloco pode abranger. Isto não impede que os Blocos sejam colocados em Áreas com uma extensão de coluna menor.',
+ allowedBlockRows: 'Extensões de linha disponíveis',
+ allowedBlockRowsHelp: 'Defina o intervalo de linhas de layout que este bloco pode abranger.',
+ allowBlockInRoot: 'Permitir na raiz',
+ allowBlockInRootHelp: 'Tornar este bloco disponível na raiz do layout.',
+ allowBlockInAreas: 'Permitir em áreas',
+ allowBlockInAreasHelp:
+ 'Tornar este bloco disponível por predefinição dentro das áreas de outros Blocos (a menos que permissões explícitas sejam definidas para estas áreas).',
+ areaAllowedBlocksEmpty:
+ 'Por predefinição, todos os tipos de bloco são permitidos numa Área. Use esta opção para permitir apenas tipos selecionados.',
+ areas: 'Áreas',
+ areasLayoutColumns: 'Colunas de Grelha para Áreas',
+ areasLayoutColumnsHelp:
+ 'Defina quantas colunas estarão disponíveis para áreas. Se não definido, será usado o número de colunas definido para todo o layout.',
+ areasConfigurations: 'Áreas',
+ areasConfigurationsHelp:
+ "Para ativar o aninhamento de blocos dentro deste bloco, defina uma ou mais áreas. As áreas seguem o layout definido pela sua própria configuração de coluna de grelha. A 'extensão de coluna' e 'extensão de linha' para cada área podem ser ajustadas usando a caixa do manipulador de escala no canto inferior direito da área selecionada.",
+ invalidDropPosition: '%0% não é permitido neste local.',
+ defaultLayoutStylesheet: 'Stylesheet de Layout Predefinida',
+ confirmPasteDisallowedNestedBlockHeadline: 'Conteúdo não permitido foi rejeitado',
+ confirmPasteDisallowedNestedBlockMessage:
+ 'O conteúdo inserido continha conteúdo não permitido, que não foi criado. Gostaria de manter o resto deste conteúdo mesmo assim?',
+ areaAliasHelp:
+ 'Ao usar GetBlockGridHTML() para renderizar a Block Grid, o alias será renderizado na marcação como um atributo \'data-area-alias\'. Use o atributo alias para direcionar o elemento para a área. Exemplo: .umb-block-grid__area[data-area-alias="MinhaAreaAlias"] { ... }',
+ scaleHandlerButtonTitle: 'Arraste para escalar',
+ areaCreateLabelTitle: 'Etiqueta do Botão Criar',
+ areaCreateLabelHelp:
+ "Substitua o texto da etiqueta para adicionar um novo Bloco a esta Área, Exemplo: 'Adicionar Widget'",
+ showSizeOptions: 'Mostrar opções de tamanho',
+ addBlockType: 'Adicionar Bloco',
+ addBlockGroup: 'Adicionar grupo',
+ pickSpecificAllowance: 'Escolher grupo ou Bloco',
+ allowanceMinimum: 'Definir um requisito mínimo',
+ allowanceMaximum: 'Definir um requisito máximo',
+ block: 'Bloco',
+ tabBlock: 'Bloco',
+ tabBlockTypeSettings: 'Definições',
+ tabAreas: 'Áreas',
+ tabAdvanced: 'Avançado',
+ headlineAllowance: 'Permissões',
+ getSampleHeadline: 'Instalar Configuração de Amostra',
+ getSampleDescription:
+ 'Isto adicionará Blocos básicos e ajudá-lo-á a começar com o Editor de Block Grid. Terá Blocos para Título, Texto Rico, Imagem, bem como um Layout de Duas Colunas.',
+ getSampleButton: 'Instalar',
+ actionEnterSortMode: 'Modo de ordenação',
+ actionExitSortMode: 'Terminar modo de ordenação',
+ areaAliasIsNotUnique: 'O Alias desta Área deve ser único em comparação com as outras Áreas deste Bloco.',
+ configureArea: 'Configurar área',
+ deleteArea: 'Eliminar área',
+ addColumnSpanOption: 'Adicionar opção de abranger %0% colunas',
+ createThisFor: (name: string, variantName: string) =>
+ variantName ? `Criar ${name} para ${variantName}` : `Criar ${name}`,
+ insertBlock: 'Inserir Bloco',
+ labelInlineMode: 'Exibir em linha com o texto',
+ notExposedLabel: 'Rascunho',
+ notExposedDescription: 'Este Bloco ainda não foi criado para esta variante',
+ areaValidationEntriesNotAllowed: '%0% não é permitido nesta área.',
+ rootValidationEntriesNotAllowed: '%0% não é permitido na raiz desta propriedade.',
+ unsupportedBlockName: 'Não suportado',
+ unsupportedBlockDescription:
+ 'Este conteúdo já não é suportado neste Editor. Se lhe falta este conteúdo, por favor contacte o seu administrador. Caso contrário, elimine-o.',
+ blockVariantConfigurationNotSupported:
+ 'Um ou mais Tipos de Bloco deste Editor de Blocos estão a usar um Tipo de Elemento que está configurado para Variar por Cultura ou Variar por Segmento. Isto não é suportado num item de Conteúdo que não varia por Cultura ou Segmento.',
+ },
+ contentTemplatesDashboard: {
+ whatHeadline: 'O que são Modelos de Documento?',
+ whatDescription:
+ 'Modelos de Documento são conteúdo predefinido que pode ser selecionado ao criar um novo nó de conteúdo.',
+ createHeadline: 'Como crio um Modelo de Documento?',
+ createDescription:
+ 'Existem duas formas de criar um Modelo de Documento:
Clique com o botão direito num nó de conteúdo e selecione "Criar Modelo de Documento" para criar um novo Modelo de Documento. Clique com o botão direito na árvore de Modelos de Documento na secção Definições e selecione o Tipo de Documento para o qual quer criar um Modelo de Documento. Depois de lhe ser dado um nome, os editores podem começar a usar o Modelo de Documento como base para a sua nova página.
',
+ manageHeadline: 'Como gero Modelos de Documento?',
+ manageDescription:
+ 'Pode editar e eliminar Modelos de Documento da árvore "Modelos de Documento" na secção Definições. Expanda o Tipo de Documento no qual o Modelo de Documento se baseia e clique nele para editar ou eliminar.',
+ },
+ preview: {
+ endLabel: 'Terminar',
+ endTitle: 'Terminar modo de pré-visualização',
+ openWebsiteLabel: 'Pré-visualizar website',
+ openWebsiteTitle: 'Abrir website em modo de pré-visualização',
+ returnToPreviewHeadline: 'Pré-visualizar website?',
+ returnToPreviewDescription:
+ 'Terminou o modo de pré-visualização, quer ativá-lo novamente para ver a última versão guardada do seu website?',
+ returnToPreviewAcceptButton: 'Pré-visualizar última versão',
+ returnToPreviewDeclineButton: 'Ver versão publicada',
+ viewPublishedContentHeadline: 'Ver versão publicada?',
+ viewPublishedContentDescription:
+ 'Está em Modo de Pré-visualização, quer sair para ver a versão publicada do seu website?',
+ viewPublishedContentAcceptButton: 'Ver versão publicada',
+ viewPublishedContentDeclineButton: 'Permanecer em modo de pré-visualização',
+ },
+ permissions: {
+ FolderCreation: 'Criação de pasta',
+ FileWritingForPackages: 'Escrita de ficheiros para pacotes',
+ FileWriting: 'Escrita de ficheiros',
+ MediaFolderCreation: 'Criação de pasta de multimédia',
+ },
+ treeSearch: {
+ searchResult: 'item retornado',
+ searchResults: 'itens retornados',
+ },
+ analytics: {
+ consentForAnalytics: 'Consentimento para dados de telemetria',
+ analyticsLevelSavedSuccess: 'Nível de telemetria guardado!',
+ analyticsDescription:
+ 'Para melhorar o Umbraco e adicionar nova funcionalidade com base na informação mais relevante possível, gostaríamos de recolher informação de sistema e de utilização da sua instalação. Dados agregados serão partilhados regularmente, bem como aprendizagens destas métricas. Esperamos que nos ajude a recolher alguns dados valiosos.NÃO recolheremos quaisquer dados pessoais como conteúdo, código, informação do utilizador, e todos os dados serão totalmente anonimizados.',
+ minimalLevelDescription: 'Apenas enviaremos um ID de site anonimizado para nos informar que o site existe.',
+ basicLevelDescription: 'Enviaremos um ID de site anonimizado, versão do Umbraco e pacotes instalados',
+ detailedLevelDescription:
+ 'Enviaremos: ID de site anonimizado, versão do Umbraco e pacotes instalados. Número de: Nós raiz, Nós de conteúdo, Multimédia, Tipos de Documento, Modelos, Idiomas, Domínios, Grupo de utilizadores, Utilizadores, Membros, Fornecedores de login externo do backoffice e Editores de propriedade em uso. Informação do sistema: Servidor web, SO do servidor, framework do servidor, idioma do SO do servidor e fornecedor da base de dados. Definições de configuração: Modo ModelsBuilder, se existe caminho Umbraco personalizado, ambiente ASP, se a API de entrega está ativada e permite acesso público, e se está em modo de debug. Poderemos alterar o que enviamos no nível Detalhado no futuro. Se assim for, será listado acima. Ao escolher "Detalhado", concorda com a recolha de informação anonimizada atual e futura. ',
+ },
+ routing: {
+ routeNotFoundTitle: 'Não encontrado',
+ routeNotFoundDescription:
+ 'A rota solicitada não pôde ser encontrada. Por favor, verifique o URL e tente novamente.',
+ },
+ codeEditor: {
+ label: 'Editor de código',
+ languageConfigLabel: 'Idioma',
+ languageConfigDescription: 'Selecione o idioma para realce de sintaxe e IntelliSense.',
+ heightConfigLabel: 'Altura',
+ heightConfigDescription: 'Defina a altura do editor de código em píxeis.',
+ lineNumbersConfigLabel: 'Números de linha',
+ lineNumbersConfigDescription: 'Mostrar números de linha no editor de código.',
+ minimapConfigLabel: 'Minimapa',
+ minimapConfigDescription: 'Mostrar um minimapa no editor de código.',
+ wordWrapConfigLabel: 'Quebra de linha',
+ wordWrapConfigDescription: 'Ativar quebra de linha no editor de código.',
+ },
+ rte: {
+ config_blocks: 'Blocos Disponíveis',
+ config_blocks_description: 'Defina os blocos disponíveis.',
+ config_ignoreUserStartNodes: 'Ignorar Nós de Início do Utilizador',
+ config_maxImageSize: 'Tamanho máximo para imagens inseridas',
+ config_maxImageSize_description: 'Largura ou altura máxima - introduza 0 para desativar o redimensionamento.',
+ config_mediaParentId: 'Pasta de Upload de Imagens',
+ config_mediaParentId_description: 'Escolha a localização de carregamento de imagens coladas.',
+ config_overlaySize: 'Tamanho da sobreposição',
+ config_overlaySize_description: 'Selecione a largura da sobreposição (seletor de links).',
+ },
+ tiptap: {
+ anchor: 'Âncora',
+ anchor_input: 'Introduza um ID de âncora',
+ config_dimensions_description:
+ 'Defina a largura e altura máximas do editor. Isto exclui a altura da barra de ferramentas.',
+ config_extensions: 'Capacidades',
+ config_statusbar: 'Barra de estado',
+ config_toolbar: 'Barra de ferramentas',
+ extGroup_formatting: 'Formatação de Texto',
+ extGroup_interactive: 'Elementos Interativos',
+ extGroup_media: 'Incorporações e Multimédia',
+ extGroup_structure: 'Estrutura do Conteúdo',
+ extGroup_unknown: 'Não categorizado',
+ statusbar_availableItems: 'Estados disponíveis',
+ statusbar_availableItemsEmpty: 'Não existem extensões de barra de estado para mostrar',
+ toobar_availableItems: 'Ações disponíveis',
+ toobar_availableItemsEmpty: 'Não existem extensões de barra de ferramentas para mostrar',
+ toolbar_designer: 'Designer de barra de ferramentas',
+ toolbar_addRow: 'Adicionar linha',
+ toolbar_addGroup: 'Adicionar grupo',
+ toolbar_addItems: 'Adicionar ações',
+ toolbar_removeRow: 'Remover linha',
+ toolbar_removeGroup: 'Remover grupo',
+ toolbar_removeItem: 'Remover ação',
+ toolbar_emptyGroup: 'Vazio',
+ sourceCodeEdit: 'Editar Código Fonte',
+ charmap: 'Mapa de caracteres',
+ charmap_headline: 'Carácter especial',
+ charmap_currency: 'Moeda',
+ charmap_text: 'Texto',
+ charmap_quotations: 'Citações',
+ charmap_maths: 'Matemática',
+ charmap_extlatin: 'Latim Estendido',
+ charmap_symbols: 'Símbolos',
+ charmap_arrows: 'Setas',
+ },
+ linkPicker: {
+ modalSource: 'Fonte',
+ modalManual: 'Manual',
+ modalAnchorValidationMessage:
+ 'Por favor, introduza uma âncora ou querystring, selecione um documento ou item de multimédia, ou configure manualmente o URL.',
+ resetUrlHeadline: 'Redefinir URL?',
+ resetUrlMessage: 'Tem a certeza que quer redefinir este URL?',
+ resetUrlLabel: 'Redefinir',
+ },
+} as UmbLocalizationDictionary;
diff --git a/src/Umbraco.Web.UI.Client/src/external/rxjs/index.ts b/src/Umbraco.Web.UI.Client/src/external/rxjs/index.ts
index 24fa9d1a5a..36d1337320 100644
--- a/src/Umbraco.Web.UI.Client/src/external/rxjs/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/external/rxjs/index.ts
@@ -1,22 +1,24 @@
export {
+ BehaviorSubject,
+ Observable,
ReplaySubject,
Subject,
- Observable,
- BehaviorSubject,
Subscription,
- map,
- distinctUntilChanged,
+ catchError,
combineLatest,
- shareReplay,
- takeUntil,
debounceTime,
- tap,
- of,
- lastValueFrom,
- firstValueFrom,
- switchMap,
+ distinctUntilChanged,
filter,
- startWith,
- skip,
first,
+ firstValueFrom,
+ from,
+ lastValueFrom,
+ map,
+ of,
+ shareReplay,
+ skip,
+ startWith,
+ switchMap,
+ takeUntil,
+ tap,
} from 'rxjs';
diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.test.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.test.ts
index 11a35bd31e..2e95b20553 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.test.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.test.ts
@@ -2,7 +2,7 @@ import { UmbContextProvider } from '../provide/context-provider.js';
import { UmbContextToken } from '../token/context-token.js';
import type { UmbContextMinimal } from '../types.js';
import { UmbContextConsumer } from './context-consumer.js';
-import type { UmbContextRequestEventImplementation } from './context-request.event.js';
+import { UmbContextRequestEventImplementation } from './context-request.event.js';
import { UMB_CONTEXT_REQUEST_EVENT_TYPE } from './context-request.event.js';
import { assert, expect, oneEvent } from '@open-wc/testing';
@@ -266,7 +266,7 @@ describe('UmbContextConsumer', () => {
localConsumer.hostConnected();
});
- it('does not respond to a non existing api alias', (done) => {
+ it('does not respond to a non existing api alias', async () => {
const provider = new UmbContextProvider(
document.body,
testContextAliasAndApiAlias,
@@ -277,21 +277,19 @@ describe('UmbContextConsumer', () => {
let callbackCount = 0;
const localConsumer = new UmbContextConsumer(element, testContextAliasAndNotExistingApiAlias, (context) => {
- callbackCount++;
- if (callbackCount === 1) {
- expect(context).to.be.undefined;
- done();
- } else {
- assert.fail('Callback should not be called more than once');
- }
+ assert.fail('Callback should not be called more than once');
});
+ const requestEvent = oneEvent(localConsumer.getHostElement(), UMB_CONTEXT_REQUEST_EVENT_TYPE);
localConsumer.hostConnected();
+ await requestEvent;
+ await Promise.resolve();
+
// Delayed check to make sure the callback is not called.
- Promise.resolve().then(() => {
- localConsumer.hostDisconnected();
- provider.hostDisconnected();
- });
+
+ expect(callbackCount).to.equal(0, 'Callback should never have been called');
+ localConsumer.hostDisconnected();
+ provider.hostDisconnected();
});
});
@@ -368,7 +366,7 @@ describe('UmbContextConsumer', () => {
localConsumer.hostConnected();
});
- it('disapproving discriminator does not fire callback', (done) => {
+ it('disapproving discriminator does not fire callback', async () => {
const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass());
provider.hostConnected();
@@ -379,24 +377,21 @@ describe('UmbContextConsumer', () => {
new UmbContextToken(testContextAlias, undefined, badDiscriminator),
(_instance) => {
callbackCount++;
- if (callbackCount === 1) {
- expect(_instance).to.be.undefined;
- done();
- } else {
- assert.fail('Callback should not be called more than once');
- }
+ assert.fail('Callback should not be called more than once');
},
);
+ const requestEvent = oneEvent(localConsumer.getHostElement(), UMB_CONTEXT_REQUEST_EVENT_TYPE);
localConsumer.hostConnected();
// Wait for to ensure the above request didn't succeed:
- Promise.resolve().then(() => {
- localConsumer.hostDisconnected();
- provider.hostDisconnected();
- });
+ await requestEvent;
+ await Promise.resolve();
+ expect(callbackCount).to.equal(0, 'Callback should never have been called');
+ localConsumer.hostDisconnected();
+ provider.hostDisconnected();
});
- it('context api of same context alias will prevent request from propagating', (done) => {
+ it('context api of same context alias will prevent request from propagating', async () => {
const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass());
provider.hostConnected();
@@ -414,19 +409,18 @@ describe('UmbContextConsumer', () => {
new UmbContextToken(testContextAlias, undefined, discriminator),
(_instance) => {
callbackCount++;
- if (callbackCount === 1) {
- expect(_instance).to.be.undefined;
- done();
- }
},
);
+ const requestEvent = oneEvent(localConsumer.getHostElement(), UMB_CONTEXT_REQUEST_EVENT_TYPE);
localConsumer.hostConnected();
+ await requestEvent;
+ await Promise.resolve();
// Wait for to ensure the above request didn't succeed:
- Promise.resolve().then(() => {
- localConsumer.hostDisconnected();
- provider.hostDisconnected();
- });
+
+ expect(callbackCount).to.equal(0, 'Callback should never have been called');
+ localConsumer.hostDisconnected();
+ provider.hostDisconnected();
});
it('context api of same context alias will NOT prevent request from propagating when set to passContextAliasMatches', (done) => {
diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts
index 5334cc6a22..bcc6ded360 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts
@@ -219,8 +219,7 @@ export class UmbContextConsumer<
this.#raf = undefined;
}
- this.#instance = undefined;
- this.#callback?.(undefined);
+ this.#unprovide();
if (this.#promiseRejecter) {
const hostElement = this._retrieveHost();
this.#promiseRejecter(
diff --git a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host.mixin.ts b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host.mixin.ts
index a2763432c0..f7947b5648 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host.mixin.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host.mixin.ts
@@ -60,8 +60,8 @@ export const UmbControllerHostMixin = (superClass: T
if (this.#attached) {
// If a controller is created on a already attached element, then it will be added directly. This might not be optimal. As the controller it self has not finished its constructor method jet. therefor i postpone the call: [NL]
Promise.resolve().then(() => {
- // Extra check to see if we are still attached at this point:
- if (this.#attached) {
+ // Extra check to see if we are still attached and still added at this point:
+ if (this.#attached && this.#controllers.includes(ctrl)) {
ctrl.hostConnected();
}
});
diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/server-extension-registrator.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/server-extension-registrator.controller.ts
index 0f5af1c0b1..bd366ddb06 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/server-extension-registrator.controller.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/server-extension-registrator.controller.ts
@@ -34,7 +34,9 @@ export class UmbServerExtensionRegistrator extends UmbControllerBase {
* @remark Users must have the BACKOFFICE_ACCESS permission to access this method.
*/
public async registerPrivateExtensions() {
- const { data: packages } = await tryExecute(this, ManifestService.getManifestManifestPrivate());
+ const { data: packages } = await tryExecute(this, ManifestService.getManifestManifestPrivate(), {
+ disableNotifications: true,
+ });
if (packages) {
await this.#loadServerPackages(packages);
}
@@ -46,7 +48,9 @@ export class UmbServerExtensionRegistrator extends UmbControllerBase {
* @remark Any user can access this method without any permissions.
*/
public async registerPublicExtensions() {
- const { data: packages } = await tryExecute(this, ManifestService.getManifestManifestPublic());
+ const { data: packages } = await tryExecute(this, ManifestService.getManifestManifestPublic(), {
+ disableNotifications: true,
+ });
if (packages) {
await this.#loadServerPackages(packages);
}
diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/manifest-base.interface.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/manifest-base.interface.ts
index cfe485004f..79e3f31ded 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/manifest-base.interface.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/manifest-base.interface.ts
@@ -21,7 +21,7 @@ export interface ManifestBase {
name: string;
/**
- * Extensions such as dashboards are ordered by weight with lower numbers being first in the list
+ * Extensions such as dashboards are ordered by weight with higher numbers being first in the list
*/
weight?: number;
}
diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/array-state.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/array-state.ts
index 3fdf1b10b0..a057b8bb15 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/array-state.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/array-state.ts
@@ -84,7 +84,7 @@ export class UmbArrayState extends UmbDeepState {
/**
* @function remove
- * @param {unknown[]} uniques - The unique values to remove.
+ * @param {U[]} uniques - The unique values to remove.
* @returns {UmbArrayState} Reference to it self.
* @description - Remove some new data of this Subject.
* @example Example remove entry with id '1' and '2'
@@ -95,7 +95,7 @@ export class UmbArrayState extends UmbDeepState {
* const myState = new UmbArrayState(data, (x) => x.id);
* myState.remove([1, 2]);
*/
- remove(uniques: unknown[]) {
+ remove(uniques: U[]) {
if (this.getUniqueMethod) {
let next = this.getValue();
if (!next) return this;
@@ -114,7 +114,7 @@ export class UmbArrayState extends UmbDeepState {
/**
* @function removeOne
- * @param {unknown} unique - The unique value to remove.
+ * @param {U} unique - The unique value to remove.
* @returns {UmbArrayState} Reference to it self.
* @description - Remove some new data of this Subject.
* @example Example remove entry with id '1'
@@ -125,7 +125,7 @@ export class UmbArrayState extends UmbDeepState {
* const myState = new UmbArrayState(data, (x) => x.id);
* myState.removeOne(1);
*/
- removeOne(unique: unknown) {
+ removeOne(unique: U) {
if (this.getUniqueMethod) {
let next = this.getValue();
if (!next) return this;
@@ -251,7 +251,7 @@ export class UmbArrayState extends UmbDeepState {
/**
* @function updateOne
- * @param {unknown} unique - Unique value to find entry to update.
+ * @param {U} unique - Unique value to find entry to update.
* @param {Partial} entry - new data to be added in this Subject.
* @returns {UmbArrayState} Reference to it self.
* @description - Update a item with some new data, requires the ArrayState to be constructed with a getUnique method.
@@ -263,7 +263,7 @@ export class UmbArrayState extends UmbDeepState {
* const myState = new UmbArrayState(data, (x) => x.key);
* myState.updateOne(2, {value: 'updated-bar'});
*/
- updateOne(unique: unknown, entry: Partial) {
+ updateOne(unique: U, entry: Partial) {
if (!this.getUniqueMethod) {
throw new Error("Can't partial update an ArrayState without a getUnique method provided when constructed.");
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts
index 1f30e8298e..f0f5a9de01 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts
@@ -427,6 +427,7 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
#extensionSlotRenderMethod = (ext: UmbExtensionElementInitializer) => {
if (ext.component) {
ext.component.classList.add('umb-block-grid__block--view');
+ ext.component.setAttribute('part', 'component');
}
if (this._exposed) {
return ext.component;
@@ -641,6 +642,11 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
border-color: var(--uui-color-invalid);
}
+ umb-extension-slot::part(component) {
+ position: relative;
+ z-index: 0;
+ }
+
#invalidLocation {
position: absolute;
top: -1em;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts
index 805a2ef48c..6242f413a3 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts
@@ -16,7 +16,7 @@ import type {
UmbBlockEditorCustomViewProperties,
} from '@umbraco-cms/backoffice/block-custom-view';
import type { UmbExtensionElementInitializer } from '@umbraco-cms/backoffice/extension-api';
-import { UUIBlinkAnimationValue } from '@umbraco-cms/backoffice/external/uui';
+import { UUIBlinkAnimationValue, UUIBlinkKeyframes } from '@umbraco-cms/backoffice/external/uui';
import { UMB_PROPERTY_CONTEXT, UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
import { UMB_CLIPBOARD_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/clipboard';
@@ -346,6 +346,7 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper
};
#extensionSlotRenderMethod = (ext: UmbExtensionElementInitializer) => {
+ ext.component?.setAttribute('part', 'component');
if (this._exposed) {
return ext.component;
} else {
@@ -480,6 +481,7 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper
}
static override styles = [
+ UUIBlinkKeyframes,
css`
:host {
position: relative;
@@ -511,6 +513,11 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper
border-color: var(--uui-color-invalid);
}
+ umb-extension-slot::part(component) {
+ position: relative;
+ z-index: 0;
+ }
+
uui-action-bar {
position: absolute;
top: var(--uui-size-2);
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts
index a056c7045f..6970c554e2 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts
@@ -243,6 +243,7 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert
};
#extensionSlotRenderMethod = (ext: UmbExtensionElementInitializer) => {
+ ext.component?.setAttribute('part', 'component');
if (this._exposed) {
return ext.component;
} else {
@@ -345,6 +346,12 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert
outline: 3px solid var(--uui-color-focus);
}
}
+
+ umb-extension-slot::part(component) {
+ position: relative;
+ z-index: 0;
+ }
+
uui-action-bar {
position: absolute;
top: var(--uui-size-2);
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts
index d4d60b54c6..446c08a35f 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts
@@ -116,8 +116,7 @@ export class UmbInputBlockTypeElement<
// Only pick elements:
docType.isElement &&
// Prevent picking the an already used element type:
- this.#filter &&
- this.#filter.find((x) => x.contentElementTypeKey === docType.unique) === undefined,
+ this.#filter?.find((x) => x.contentElementTypeKey === docType.unique) === undefined,
},
value: {
selection: [],
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts
index f12767755e..65a6b33eb6 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts
@@ -548,18 +548,27 @@ export abstract class UmbBlockEntryContext<
abstract _gotContentType(contentType: UmbContentTypeModel | undefined): void;
async #observeVariantId() {
- if (!this._manager) return;
+ if (!this._manager) {
+ this.removeUmbControllerByAlias('observeVariantId');
+ return;
+ }
await this.#contentStructurePromise;
if (!this.#contentStructure) {
throw new Error('No contentStructure found');
}
+ if (!this._manager) {
+ // The manager maybe got removed while we awaited the promise above.
+ this.removeUmbControllerByAlias('observeVariantId');
+ return;
+ }
+
// observe variantId:
this.observe(
observeMultiple([
this._manager.variantId,
- this.#contentStructure?.ownerContentTypeObservablePart((x) => x?.variesByCulture),
- this.#contentStructure?.ownerContentTypeObservablePart((x) => x?.variesBySegment),
+ this.#contentStructure.ownerContentTypeObservablePart((x) => x?.variesByCulture),
+ this.#contentStructure.ownerContentTypeObservablePart((x) => x?.variesBySegment),
]),
([variantId, variesByCulture, variesBySegment]) => {
if (!variantId || variesByCulture === undefined || variesBySegment === undefined) return;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts
index 801babf652..1ce6295ebc 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts
@@ -39,7 +39,7 @@ export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement {
_dataOwner?: UmbBlockElementManager;
@state()
- _variantId?: UmbVariantId;
+ _workspaceVariantId?: UmbVariantId;
@state()
_visibleProperties?: Array;
@@ -56,7 +56,7 @@ export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement {
this.observe(
workspaceContext?.variantId,
(variantId) => {
- this._variantId = variantId;
+ this._workspaceVariantId = variantId;
this.#processPropertyStructure();
},
'observeVariantId',
@@ -83,16 +83,19 @@ export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement {
}
#processPropertyStructure() {
- if (!this._dataOwner || !this.#properties || !this.#propertyStructureHelper) {
+ if (!this._dataOwner || !this.#properties || !this.#propertyStructureHelper || !this._workspaceVariantId) {
return;
}
const propertyViewGuard = this._dataOwner.propertyViewGuard;
this.#properties.forEach((property) => {
- const propertyVariantId = new UmbVariantId(this._variantId?.culture, this._variantId?.segment);
+ const propertyVariantId = new UmbVariantId(
+ property.variesByCulture ? this._workspaceVariantId!.culture : null,
+ property.variesBySegment ? this._workspaceVariantId!.segment : null,
+ );
this.observe(
- propertyViewGuard.isPermittedForVariantAndProperty(propertyVariantId, property),
+ propertyViewGuard.isPermittedForVariantAndProperty(propertyVariantId, property, this._workspaceVariantId!),
(permitted) => {
if (permitted) {
this.#visiblePropertiesUniques.push(property.unique);
@@ -117,7 +120,7 @@ export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement {
}
override render() {
- return this._variantId && this._visibleProperties
+ return this._workspaceVariantId && this._visibleProperties
? repeat(
this._visibleProperties,
(property) => property.alias,
@@ -126,7 +129,7 @@ export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement {
class="property"
.ownerContext=${this._dataOwner}
.ownerEntityType=${this._ownerEntityType}
- .variantId=${this._variantId}
+ .variantId=${this._workspaceVariantId}
.property=${property}>`,
)
: nothing;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-property.element.ts
index 20ea00482b..fb592580eb 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-property.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-property.element.ts
@@ -38,7 +38,11 @@ export class UmbBlockWorkspaceViewEditPropertyElement extends UmbLitElement {
})}].value`;
this.observe(
- this.ownerContext.propertyWriteGuard.isPermittedForVariantAndProperty(propertyVariantId, this.property),
+ this.ownerContext.propertyWriteGuard.isPermittedForVariantAndProperty(
+ propertyVariantId,
+ this.property,
+ this.variantId,
+ ),
(write) => {
this._writeable = write;
},
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit.element.ts
index 21205661e5..e9ce9d3152 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit.element.ts
@@ -123,6 +123,7 @@ export class UmbBlockWorkspaceViewEditElement extends UmbLitElement implements U
if (!this._hasRootGroups) {
routes.push({
path: '',
+ pathMatch: 'full',
redirectTo: routes[0]?.path,
});
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/modals/manifest-viewer/manifest-viewer-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/modals/manifest-viewer/manifest-viewer-modal.element.ts
index 931b876ce3..9768497f17 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/modals/manifest-viewer/manifest-viewer-modal.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/modals/manifest-viewer/manifest-viewer-modal.element.ts
@@ -19,6 +19,10 @@ export class UmbManifestViewerModalElement extends UmbModalBaseElement<
value = JSON.stringify(value);
} else if (typeof value === 'object') {
value = this.#stringify(value);
+ } else if (typeof value === 'number') {
+ value = `${value}`;
+ } else if (typeof value === 'boolean') {
+ value = `${value}`;
} else {
value = `"${value}"`;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/clipboard/clipboard-entry/picker/clipboard-entry-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/clipboard/clipboard-entry/picker/clipboard-entry-picker.element.ts
index 0f0185994a..c5c98d5f1a 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/clipboard/clipboard-entry/picker/clipboard-entry-picker.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/clipboard/clipboard-entry/picker/clipboard-entry-picker.element.ts
@@ -150,7 +150,7 @@ export class UmbClipboardEntryPickerElement extends UmbLitElement {
slot="actions"
.entityType=${item.entityType}
.unique=${item.unique}
- .label=${item.name}>
+ .label=${this.localize.term('actions_viewActionsFor', [item.name])}>
`;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/value-translator/copy/clipboard-copy-translator-value-resolver.ts b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/value-translator/copy/clipboard-copy-translator-value-resolver.ts
index 14cb3d0f3e..621580bb05 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/value-translator/copy/clipboard-copy-translator-value-resolver.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/clipboard/property/value-translator/copy/clipboard-copy-translator-value-resolver.ts
@@ -24,7 +24,14 @@ export class UmbClipboardCopyPropertyValueTranslatorValueResolver extends UmbCon
}
// Create translators
- const apiPromises = manifests.map((manifest) => createExtensionApi(this, manifest));
+ const apiPromises = manifests.map((manifest) =>
+ createExtensionApi(this, manifest).then((api) => {
+ if (api) {
+ (api as any).manifest = manifest;
+ }
+ return api;
+ }),
+ );
const apis = await Promise.all(apiPromises);
// Translate values
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/modals/composition-picker/composition-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/modals/composition-picker/composition-picker-modal.element.ts
index e0cf37273d..9e968cc865 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/modals/composition-picker/composition-picker-modal.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/modals/composition-picker/composition-picker-modal.element.ts
@@ -39,6 +39,9 @@ export class UmbCompositionPickerModalElement extends UmbModalBaseElement<
@state()
private _usedForInheritance: Array = [];
+ @state()
+ private _usedForComposition: Array = [];
+
override connectedCallback() {
super.connectedCallback();
@@ -53,6 +56,7 @@ export class UmbCompositionPickerModalElement extends UmbModalBaseElement<
this._selection = this.data?.selection ?? [];
this._usedForInheritance = this.data?.usedForInheritance ?? [];
+ this._usedForComposition = this.data?.usedForComposition ?? [];
this.modalContext?.setValue({ selection: this._selection });
const isNew = this.data!.isNew;
@@ -111,11 +115,12 @@ export class UmbCompositionPickerModalElement extends UmbModalBaseElement<
if (!data) return;
- const folders = Array.from(new Set(data.map((c) => '/' + c.folderPath.join('/'))));
- this._compatibleCompositions = folders.map((path) => ({
- path,
- compositions: data.filter((c) => '/' + c.folderPath.join('/') === path),
- }));
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-expect-error
+ const grouped = Object.groupBy(data, (item) => '/' + item.folderPath.join('/'));
+ this._compatibleCompositions = Object.keys(grouped)
+ .sort((a, b) => a.localeCompare(b))
+ .map((key) => ({ path: key, compositions: grouped[key] }));
}
#onSelectionAdd(unique: string) {
@@ -131,7 +136,9 @@ export class UmbCompositionPickerModalElement extends UmbModalBaseElement<
override render() {
return html`
- ${this._references.length ? this.#renderHasReference() : this.#renderAvailableCompositions()}
+
+ ${this._references.length ? this.#renderHasReference() : this.#renderAvailableCompositions()}
+
${!this._references.length
@@ -213,11 +220,16 @@ export class UmbCompositionPickerModalElement extends UmbModalBaseElement<
(compositions) => compositions.unique,
(compositions) => {
const usedForInheritance = this._usedForInheritance.includes(compositions.unique);
+ const usedForComposition = this._usedForComposition.includes(compositions.unique);
+ /* The server will return isCompatible as false if the Doc Type is currently being used in a composition.
+ Therefore, we need to account for this in the "isDisabled" check to ensure it remains enabled.
+ Otherwise, it would become disabled and couldn't be deselected by the user. */
+ const isDisabled = usedForInheritance || (compositions.isCompatible === false && !usedForComposition);
return html`
this.#onSelectionAdd(compositions.unique)}
@deselected=${() => this.#onSelectionRemove(compositions.unique)}
?selected=${this._selection.find((unique) => unique === compositions.unique)}>
@@ -251,6 +263,10 @@ export class UmbCompositionPickerModalElement extends UmbModalBaseElement<
align-items: center;
gap: var(--uui-size-3);
}
+
+ .compositions-list {
+ margin-block: var(--uui-size-3);
+ }
`,
];
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/modals/composition-picker/composition-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/modals/composition-picker/composition-picker-modal.token.ts
index a711e4e4df..994d2d1912 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/modals/composition-picker/composition-picker-modal.token.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/modals/composition-picker/composition-picker-modal.token.ts
@@ -5,6 +5,7 @@ export interface UmbCompositionPickerModalData {
compositionRepositoryAlias: string;
selection: Array;
usedForInheritance: Array;
+ usedForComposition: Array;
unique: string | null;
isElement: boolean;
currentPropertyAliases: Array;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/structure/content-type-container-structure-helper.class.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/structure/content-type-container-structure-helper.class.ts
index 2dbe7595c0..58afbe2cb0 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/structure/content-type-container-structure-helper.class.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/structure/content-type-container-structure-helper.class.ts
@@ -194,6 +194,7 @@ export class UmbContentTypeContainerStructureHelper([], (x) => x.unique);
readonly propertyStructure = this.#propertyStructure.asObservable();
+ readonly propertyAliases = this.#propertyStructure.asObservablePart((x) => x.map((e) => e.alias));
constructor(host: UmbControllerHost) {
super(host);
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/structure/content-type-structure-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/structure/content-type-structure-manager.class.ts
index 8b85120378..1a71834059 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/structure/content-type-structure-manager.class.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/structure/content-type-structure-manager.class.ts
@@ -24,6 +24,7 @@ import { incrementString } from '@umbraco-cms/backoffice/utils';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api';
import { umbExtensionsRegistry, type ManifestRepository } from '@umbraco-cms/backoffice/extension-registry';
+import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs';
type UmbPropertyTypeUnique = UmbPropertyTypeModel['unique'];
@@ -114,12 +115,8 @@ export class UmbContentTypeStructureManager<
readonly variesByCulture = createObservablePart(this.ownerContentType, (x) => x?.variesByCulture);
readonly variesBySegment = createObservablePart(this.ownerContentType, (x) => x?.variesBySegment);
- #containers: UmbArrayState = new UmbArrayState(
- [],
- (x) => x.id,
- );
containerById(id: string) {
- return this.#containers.asObservablePart((x) => x.find((y) => y.id === id));
+ return createObservablePart(this.#contentTypeContainers, (x) => x.find((y) => y.id === id));
}
constructor(host: UmbControllerHost, typeRepository: UmbDetailRepository | string) {
@@ -143,11 +140,20 @@ export class UmbContentTypeStructureManager<
this.#repoManager.entries,
(entries) => {
// Prevent updating once that are have edited here.
- entries = entries.filter(
+ const entriesToBeUpdated = entries.filter(
(x) => !(this.#editedTypes.getHasOne(x.unique) && this.#contentTypes.getHasOne(x.unique)),
);
- this.#contentTypes.append(entries);
+ // Remove entries based on no-longer existing uniques:
+ const entriesToBeRemoved = this.#contentTypes
+ .getValue()
+ .filter((entry) => !entries.some((x) => x.unique === entry.unique))
+ .map((x) => x.unique);
+
+ this.#contentTypes.mute();
+ this.#contentTypes.remove(entriesToBeRemoved);
+ this.#contentTypes.append(entriesToBeUpdated);
+ this.#contentTypes.unmute();
},
null,
);
@@ -161,13 +167,6 @@ export class UmbContentTypeStructureManager<
},
null,
);
- this.observe(
- this.#contentTypeContainers,
- (contentTypeContainers) => {
- this.#containers.setValue(contentTypeContainers);
- },
- null,
- );
}
/**
@@ -183,7 +182,7 @@ export class UmbContentTypeStructureManager<
return { data: this.getOwnerContentType(), asObservable: () => this.ownerContentType };
}
await this.#initRepository;
- this.#clear();
+ this.clear();
this.#ownerContentTypeUnique = unique;
if (!unique) {
this.#initRejection?.(`Content Type structure manager could not load: ${unique}`);
@@ -200,7 +199,7 @@ export class UmbContentTypeStructureManager<
public async createScaffold(preset?: Partial): Promise> {
await this.#initRepository;
- this.#clear();
+ this.clear();
const repsonse = await this.#repository!.createScaffold(preset);
const { data } = repsonse;
@@ -266,6 +265,8 @@ export class UmbContentTypeStructureManager<
}
async #loadContentTypeCompositions(contentTypeCompositions: T['compositions'] | undefined) {
+ // Important to wait a JS-cycle, cause this is called by an observation of a state and this results in setting the value for the state(potentially in the same JS-cycle) then we need to make sure we don't trigger a new update before the old subscription chain is completed. [NL]
+ await Promise.resolve();
const ownerUnique = this.getOwnerContentTypeUnique();
if (!ownerUnique) return;
const compositionUniques = contentTypeCompositions?.map((x) => x.contentType.unique) ?? [];
@@ -359,7 +360,7 @@ export class UmbContentTypeStructureManager<
this.#editedTypes.appendOne(toContentTypeUnique);
// Find container.
- const container = this.#containers.getValue().find((x) => x.id === containerId);
+ const container = (await firstValueFrom(this.#contentTypeContainers)).find((x) => x.id === containerId);
if (!container) throw new Error('Container to clone was not found');
const clonedContainer: UmbPropertyTypeContainerModel = {
@@ -433,38 +434,37 @@ export class UmbContentTypeStructureManager<
sortOrder: sortOrder ?? 0,
};
- // Ensure
- this.ensureContainerNames(contentTypeUnique, type, parentId);
-
- const contentTypes = this.#contentTypes.getValue();
- const containers = [...(contentTypes.find((x) => x.unique === contentTypeUnique)?.containers ?? [])];
- containers.push(container);
-
- this.#contentTypes.updateOne(contentTypeUnique, { containers } as Partial);
-
- return container;
+ return this.insertContainer(contentTypeUnique, container);
}
- /*async insertContainer(contentTypeUnique: string | null, container: UmbPropertyTypeContainerModel) {
+ async insertContainer(contentTypeUnique: string | null, container: UmbPropertyTypeContainerModel) {
await this.#init;
contentTypeUnique = contentTypeUnique ?? this.#ownerContentTypeUnique!;
+ const newContainer = { ...container };
+ const type = newContainer.type;
+ const parentId = newContainer.parent?.id ?? null;
// If we have a parent, we need to ensure it exists, and then update the parent property with the new container id.
- if (container.parent) {
- const parentContainer = await this.ensureContainerOf(container.parent.id, contentTypeUnique);
+ if (newContainer.parent) {
+ const parentContainer = await this.ensureContainerOf(newContainer.parent.id, contentTypeUnique);
if (!parentContainer) {
throw new Error('Container for inserting property could not be found or created');
}
- container.parent.id = parentContainer.id;
+ newContainer.parent.id = parentContainer.id;
}
+ // Ensure
+ this.ensureContainerNames(contentTypeUnique, type, parentId);
+
const frozenContainers =
this.#contentTypes.getValue().find((x) => x.unique === contentTypeUnique)?.containers ?? [];
- const containers = appendToFrozenArray(frozenContainers, container, (x) => x.id === container.id);
+ const containers = appendToFrozenArray(frozenContainers, newContainer, (x) => x.id === newContainer.id);
this.#contentTypes.updateOne(contentTypeUnique, { containers } as Partial);
- }*/
+
+ return newContainer;
+ }
makeEmptyContainerName(
containerId: string,
@@ -537,7 +537,11 @@ export class UmbContentTypeStructureManager<
this.#contentTypes.updateOne(contentTypeUnique, { containers });
}
- async removeContainer(contentTypeUnique: string | null, containerId: string | null = null) {
+ async removeContainer(
+ contentTypeUnique: string | null,
+ containerId: string | null = null,
+ args?: { preventRemovingProperties?: boolean },
+ ): Promise {
await this.#init;
contentTypeUnique = contentTypeUnique ?? this.#ownerContentTypeUnique!;
this.#editedTypes.appendOne(contentTypeUnique);
@@ -552,12 +556,15 @@ export class UmbContentTypeStructureManager<
.map((x) => x.id);
const containers = frozenContainers.filter((x) => x.id !== containerId && x.parent?.id !== containerId);
- const frozenProperties = contentType.properties;
- const properties = frozenProperties.filter((x) =>
- x.container ? !removedContainerIds.some((ids) => ids === x.container?.id) : true,
- );
+ const updates: Partial = { containers } as Partial;
- this.#contentTypes.updateOne(contentTypeUnique, { containers, properties } as Partial);
+ if (args?.preventRemovingProperties !== true) {
+ updates.properties = contentType.properties.filter((x) =>
+ x.container ? !removedContainerIds.some((ids) => ids === x.container?.id) : true,
+ );
+ }
+
+ this.#contentTypes.updateOne(contentTypeUnique, updates);
}
async insertProperty(contentTypeUnique: string | null, property: UmbPropertyTypeModel) {
@@ -655,6 +662,11 @@ export class UmbContentTypeStructureManager<
return undefined;
}
+ async getOwnerPropertyById(propertyUnique: string | null): Promise {
+ await this.#init;
+ return this.getOwnerContentType()?.properties?.find((property) => property.unique === propertyUnique);
+ }
+
async getPropertyStructureByAlias(propertyAlias: string) {
await this.#init;
for (const docType of this.#contentTypes.getValue()) {
@@ -695,17 +707,19 @@ export class UmbContentTypeStructureManager<
}
rootContainers(containerType: UmbPropertyContainerTypes) {
- return this.#containers.asObservablePart((data) => {
+ return createObservablePart(this.#contentTypeContainers, (data) => {
return data.filter((x) => x.parent === null && x.type === containerType);
});
}
- getRootContainers(containerType: UmbPropertyContainerTypes) {
- return this.#containers.getValue().filter((x) => x.parent === null && x.type === containerType);
+ async getRootContainers(containerType: UmbPropertyContainerTypes) {
+ return (await firstValueFrom(this.#contentTypeContainers)).filter(
+ (x) => x.parent === null && x.type === containerType,
+ );
}
async hasRootContainers(containerType: UmbPropertyContainerTypes) {
- return this.#containers.asObservablePart((data) => {
+ return createObservablePart(this.#contentTypeContainers, (data) => {
return data.filter((x) => x.parent === null && x.type === containerType).length > 0;
});
}
@@ -719,7 +733,14 @@ export class UmbContentTypeStructureManager<
);
}
- getOwnerContainers(containerType: UmbPropertyContainerTypes, parentId: string | null) {
+ getOwnerContainerById(id: string | null): UmbPropertyTypeContainerModel | undefined {
+ return this.getOwnerContentType()?.containers?.find((x) => x.id === id);
+ }
+
+ getOwnerContainers(
+ containerType: UmbPropertyContainerTypes,
+ parentId: string | null,
+ ): Array | undefined {
return this.getOwnerContentType()?.containers?.filter(
(x) => (parentId ? x.parent?.id === parentId : x.parent === null) && x.type === containerType,
);
@@ -730,14 +751,14 @@ export class UmbContentTypeStructureManager<
}
containersOfParentId(parentId: string, containerType: UmbPropertyContainerTypes) {
- return this.#containers.asObservablePart((data) => {
+ return createObservablePart(this.#contentTypeContainers, (data) => {
return data.filter((x) => x.parent?.id === parentId && x.type === containerType);
});
}
// In future this might need to take parentName(parentId lookup) into account as well? otherwise containers that share same name and type will always be merged, but their position might be different and they should not be merged. [NL]
containersByNameAndType(name: string, containerType: UmbPropertyContainerTypes) {
- return this.#containers.asObservablePart((data) => {
+ return createObservablePart(this.#contentTypeContainers, (data) => {
return data.filter((x) => x.name === name && x.type === containerType);
});
}
@@ -748,7 +769,7 @@ export class UmbContentTypeStructureManager<
parentName: string | null,
parentType?: UmbPropertyContainerTypes,
) {
- return this.#containers.asObservablePart((data) => {
+ return createObservablePart(this.#contentTypeContainers, (data) => {
return data.filter(
(x) =>
// Match name and type:
@@ -795,17 +816,27 @@ export class UmbContentTypeStructureManager<
);
}
- #clear() {
+ /**
+ * Get all property aliases for the content type including inherited and composed content types.
+ * @returns {Promise>} - A promise that will be resolved with the list of all content type property aliases.
+ */
+ async getContentTypePropertyAliases() {
+ return this.#contentTypes
+ .getValue()
+ .flatMap((x) => x.properties?.map((y) => y.alias) ?? [])
+ .filter(UmbFilterDuplicateStrings);
+ }
+
+ public clear() {
this.#contentTypeObservers.forEach((observer) => observer.destroy());
this.#contentTypeObservers = [];
- this.#containers.setValue([]);
this.#repoManager?.clear();
this.#contentTypes.setValue([]);
+ this.#ownerContentTypeUnique = undefined;
}
public override destroy() {
this.#contentTypes.destroy();
- this.#containers.destroy();
super.destroy();
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/content-type-workspace-context-base.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/content-type-workspace-context-base.ts
index a2b0cd669f..068eaed33c 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/content-type-workspace-context-base.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/content-type-workspace-context-base.ts
@@ -82,7 +82,7 @@ export abstract class UmbContentTypeWorkspaceContextBase<
): Promise {
this.resetState();
this.loading.addState({ unique: LOADING_STATE_UNIQUE, message: `Creating ${this.getEntityType()} scaffold` });
- this.setParent(args.parent);
+ this._internal_setCreateUnderParent(args.parent);
const request = this.structure.createScaffold(args.preset);
this._getDataPromise = request;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/views/design/content-type-design-editor-properties.element.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/views/design/content-type-design-editor-properties.element.ts
index 0f92a8e7a6..cbfedf1878 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/views/design/content-type-design-editor-properties.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/views/design/content-type-design-editor-properties.element.ts
@@ -97,6 +97,35 @@ export class UmbContentTypeDesignEditorPropertiesElement extends UmbLitElement {
i++;
}
},
+ onRequestDrop: async ({ unique }) => {
+ const context = await this.getContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT);
+ if (!context) {
+ throw new Error('Could not get Workspace Context');
+ }
+ return context.structure.getOwnerPropertyById(unique);
+ },
+ requestExternalRemove: async ({ item }) => {
+ const context = await this.getContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT);
+ if (!context) {
+ throw new Error('Could not get Workspace Context');
+ }
+ return await context.structure.removeProperty(null, item.unique).then(
+ () => true,
+ () => false,
+ );
+ },
+ requestExternalInsert: async ({ item }) => {
+ const context = await this.getContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT);
+ if (!context) {
+ throw new Error('Could not get Workspace Context');
+ }
+ const parent = this._containerId ? { id: this._containerId } : null;
+ const updatedItem = { ...item, parent };
+ return await context.structure.insertProperty(null, updatedItem).then(
+ () => true,
+ () => false,
+ );
+ },
});
private _containerId: string | null | undefined;
@@ -152,7 +181,7 @@ export class UmbContentTypeDesignEditorPropertiesElement extends UmbLitElement {
constructor() {
super();
- this.#sorter.disable();
+ //this.#sorter.disable();
this.consumeContext(UMB_CONTENT_TYPE_DESIGN_EDITOR_CONTEXT, (context) => {
this.observe(
@@ -160,9 +189,9 @@ export class UmbContentTypeDesignEditorPropertiesElement extends UmbLitElement {
(isSorting) => {
this._sortModeActive = isSorting;
if (isSorting) {
- this.#sorter.enable();
+ //this.#sorter.enable();
} else {
- this.#sorter.disable();
+ //this.#sorter.disable();
}
},
'_observeIsSorting',
@@ -305,6 +334,16 @@ export class UmbContentTypeDesignEditorPropertiesElement extends UmbLitElement {
static override styles = [
UmbTextStyles,
css`
+ :host {
+ display: block;
+ }
+
+ #property-list {
+ /* enables dropping things into this despite it begin empty. */
+ margin-top: -20px;
+ padding-top: 20px;
+ }
+
#btn-add {
width: 100%;
--uui-button-height: var(--uui-size-14);
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/views/design/content-type-design-editor-tab.element.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/views/design/content-type-design-editor-tab.element.ts
index 7494c47cf6..78a74aa86f 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/views/design/content-type-design-editor-tab.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/views/design/content-type-design-editor-tab.element.ts
@@ -72,6 +72,35 @@ export class UmbContentTypeDesignEditorTabElement extends UmbLitElement {
i++;
}
},
+ onRequestDrop: async ({ unique }) => {
+ const context = await this.getContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT);
+ if (!context) {
+ throw new Error('Could not get Workspace Context');
+ }
+ return context.structure.getOwnerContainerById(unique);
+ },
+ requestExternalRemove: async ({ item }) => {
+ const context = await this.getContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT);
+ if (!context) {
+ throw new Error('Could not get Workspace Context');
+ }
+ return await context.structure.removeContainer(null, item.id, { preventRemovingProperties: true }).then(
+ () => true,
+ () => false,
+ );
+ },
+ requestExternalInsert: async ({ item }) => {
+ const context = await this.getContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT);
+ if (!context) {
+ throw new Error('Could not get Workspace Context');
+ }
+ const parent = this.#containerId ? { id: this.#containerId } : null;
+ const updatedItem = { ...item, parent };
+ return await context.structure.insertContainer(null, updatedItem).then(
+ () => true,
+ () => false,
+ );
+ },
});
#workspaceModal?: UmbModalRouteRegistrationController<
@@ -231,9 +260,10 @@ export class UmbContentTypeDesignEditorTabElement extends UmbLitElement {
.container-list {
display: grid;
gap: 10px;
+ align-content: start;
}
- #convert-to-tab {
+ .container-list #convert-to-tab {
margin-bottom: var(--uui-size-layout-1);
display: flex;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/views/design/content-type-design-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/views/design/content-type-design-editor.element.ts
index 909af69582..d37eef19a0 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/views/design/content-type-design-editor.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/views/design/content-type-design-editor.element.ts
@@ -205,12 +205,14 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
});
routes.push({
path: '',
+ pathMatch: 'full',
redirectTo: 'root',
guards: [() => this.#processingTabId === undefined],
});
} else {
routes.push({
path: '',
+ pathMatch: 'full',
redirectTo: routes[0]?.path,
guards: [() => this.#processingTabId === undefined],
});
@@ -225,16 +227,16 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
this.#currentTabComponent = undefined;
},
});
+ } else {
+ routes.push({
+ path: `**`,
+ component: async () => (await import('@umbraco-cms/backoffice/router')).UmbRouteNotFoundElement,
+ 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]
@@ -389,17 +391,25 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
const currentOwnerCompositionCompositions = currentOwnerCompositions.filter(
(composition) => composition.compositionType === CompositionTypeModel.COMPOSITION,
);
+
+ const currentOwnerCompositionCompositionUniques = currentOwnerCompositionCompositions.map(
+ (composition) => composition.contentType.unique,
+ );
+
const currentOwnerInheritanceCompositions = currentOwnerCompositions.filter(
(composition) => composition.compositionType === CompositionTypeModel.INHERITANCE,
);
+ const currentPropertyAliases = await this.#workspaceContext.structure.getContentTypePropertyAliases();
+
const compositionConfiguration = {
compositionRepositoryAlias: this._compositionRepositoryAlias,
unique: unique,
- selection: currentOwnerCompositionCompositions.map((composition) => composition.contentType.unique),
+ selection: currentOwnerCompositionCompositionUniques,
usedForInheritance: currentInheritanceCompositions.map((composition) => composition.contentType.unique),
+ usedForComposition: currentOwnerCompositionCompositionUniques,
isElement: ownerContentType.isElement,
- currentPropertyAliases: [],
+ currentPropertyAliases,
isNew: this.#workspaceContext.getIsNew()!,
};
@@ -423,6 +433,12 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
]);
}
+ #onDragOver(event: DragEvent, path: string) {
+ if (this._activePath === path) return;
+ event.preventDefault();
+ window.history.replaceState(null, '', path);
+ }
+
override render() {
return html`
@@ -499,8 +515,8 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
}
renderRootTab() {
- const rootTabPath = this._routerPath + '/root';
- const rootTabActive = rootTabPath === this._activePath;
+ const path = this._routerPath + '/root';
+ const rootTabActive = path === this._activePath;
if (!this._hasRootGroups && !this._sortModeActive) {
// If we don't have any root groups and we are not in sort mode, then we don't want to render the root tab.
return nothing;
@@ -512,7 +528,8 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
class=${this._hasRootGroups || rootTabActive ? '' : 'content-tab-is-empty'}
label=${this.localize.term('general_generic')}
.active=${rootTabActive}
- href=${rootTabPath}>
+ href=${path}
+ @dragover=${(event: DragEvent) => this.#onDragOver(event, path)}>
${this.localize.term('general_generic')}
`;
@@ -529,7 +546,8 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
href=${path}
data-umb-tab-id=${ifDefined(tab.id)}
data-mark="tab:${tab.name}"
- ?sortable=${ownedTab}>
+ ?sortable=${ownedTab}
+ @dragover=${(event: DragEvent) => this.#onDragOver(event, path)}>
${this.renderTabInner(tab, tabActive, ownedTab)}
`;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/components/property-type-based-property/index.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/components/property-type-based-property/index.ts
index 2eac1fce0d..abab62bf9e 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/content/content/components/property-type-based-property/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/components/property-type-based-property/index.ts
@@ -1 +1,3 @@
export * from './property-type-based-property.element.js';
+export * from './property-type-based-property.context-token.js';
+export * from './property-type-based-property.context.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/components/property-type-based-property/property-type-based-property.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/components/property-type-based-property/property-type-based-property.context-token.ts
new file mode 100644
index 0000000000..e8009a5a90
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/components/property-type-based-property/property-type-based-property.context-token.ts
@@ -0,0 +1,14 @@
+import type { UmbPropertyTypeBasedPropertyContext } from './property-type-based-property.context.js';
+import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
+
+export const UMB_PROPERTY_TYPE_BASED_PROPERTY_CONTEXT = new UmbContextToken(
+ 'UmbPropertyTypeBasedPropertyContext',
+);
+
+/**
+ * @deprecated Use `UMB_PROPERTY_TYPE_BASED_PROPERTY_CONTEXT` instead.
+ * This will be removed in v.18
+ */
+export const UMB_CONTENT_PROPERTY_CONTEXT = new UmbContextToken(
+ 'UmbPropertyTypeBasedPropertyContext',
+);
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/content-property.context.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/components/property-type-based-property/property-type-based-property.context.ts
similarity index 58%
rename from src/Umbraco.Web.UI.Client/src/packages/content/content/content-property.context.ts
rename to src/Umbraco.Web.UI.Client/src/packages/content/content/components/property-type-based-property/property-type-based-property.context.ts
index b5380a0923..e1e101b4b7 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/content/content/content-property.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/components/property-type-based-property/property-type-based-property.context.ts
@@ -1,18 +1,24 @@
-import { UMB_CONTENT_PROPERTY_CONTEXT } from './content-property.context-token.js';
+import { UMB_PROPERTY_TYPE_BASED_PROPERTY_CONTEXT } from './property-type-based-property.context-token.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import type { UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type';
-export class UmbContentPropertyContext extends UmbContextBase {
+export class UmbPropertyTypeBasedPropertyContext extends UmbContextBase {
#dataType = new UmbObjectState(undefined);
dataType = this.#dataType.asObservable();
constructor(host: UmbControllerHost) {
- super(host, UMB_CONTENT_PROPERTY_CONTEXT);
+ super(host, UMB_PROPERTY_TYPE_BASED_PROPERTY_CONTEXT);
}
setDataType(dataType: UmbPropertyTypeModel['dataType'] | undefined) {
this.#dataType.setValue(dataType);
}
}
+
+/**
+ * @deprecated Use `UmbPropertyTypeBasedPropertyContext` instead.
+ * This will be removed in v.18
+ */
+export { UmbPropertyTypeBasedPropertyContext as UmbContentPropertyContext };
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/components/property-type-based-property/property-type-based-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/components/property-type-based-property/property-type-based-property.element.ts
index 37eae27d6f..fea5d1fa32 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/content/content/components/property-type-based-property/property-type-based-property.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/components/property-type-based-property/property-type-based-property.element.ts
@@ -1,4 +1,4 @@
-import { UmbContentPropertyContext } from '../../content-property.context.js';
+import { UmbPropertyTypeBasedPropertyContext } from './property-type-based-property.context.js';
import type { UmbPropertyEditorConfig } from '@umbraco-cms/backoffice/property-editor';
import { css, customElement, html, ifDefined, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbDataTypeDetailRepository } from '@umbraco-cms/backoffice/data-type';
@@ -57,17 +57,18 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement {
private _isUnsupported?: boolean;
@state()
- private _dataTypeData?: UmbPropertyEditorConfig;
+ private _dataTypeValues?: UmbPropertyEditorConfig;
private _dataTypeDetailRepository = new UmbDataTypeDetailRepository(this);
private _dataTypeObserver?: UmbObserverController;
- #contentPropertyContext = new UmbContentPropertyContext(this);
+ #context = new UmbPropertyTypeBasedPropertyContext(this);
private async _checkSchemaSupport() {
if (!this._ownerEntityType || !this._propertyEditorSchemaAlias) return;
if (this._ownerEntityType in UMB_UNSUPPORTED_EDITOR_SCHEMA_ALIASES) {
+ // TODO: We should get rid of this system, f your reading this please dont rely on this, we will get rid of it in the future. [NL]
this._isUnsupported = UMB_UNSUPPORTED_EDITOR_SCHEMA_ALIASES[this._ownerEntityType].includes(
this._propertyEditorSchemaAlias,
);
@@ -83,9 +84,9 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement {
await this._dataTypeDetailRepository.byUnique(dataTypeUnique),
(dataType) => {
const contextValue = dataType ? { unique: dataType.unique } : undefined;
- this.#contentPropertyContext.setDataType(contextValue);
+ this.#context.setDataType(contextValue);
- this._dataTypeData = dataType?.values;
+ this._dataTypeValues = dataType?.values;
this._propertyEditorUiAlias = dataType?.editorUiAlias || undefined;
this._propertyEditorSchemaAlias = dataType?.editorAlias || undefined;
this._checkSchemaSupport();
@@ -127,7 +128,7 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement {
.description=${this._property.description ?? undefined}
.appearance=${this._property.appearance}
property-editor-ui-alias=${ifDefined(this._propertyEditorUiAlias)}
- .config=${this._dataTypeData}
+ .config=${this._dataTypeValues}
.validation=${this._property.validation}
?readonly=${this.readonly}>
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/content-property.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/content-property.context-token.ts
deleted file mode 100644
index 8e9bf8626a..0000000000
--- a/src/Umbraco.Web.UI.Client/src/packages/content/content/content-property.context-token.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import type { UmbContentPropertyContext } from './content-property.context.js';
-import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
-
-export const UMB_CONTENT_PROPERTY_CONTEXT = new UmbContextToken('UmbContentPropertyContext');
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/controller/merge-content-variant-data.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/controller/merge-content-variant-data.controller.ts
index 515cbc4c7c..ba46942905 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/content/content/controller/merge-content-variant-data.controller.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/controller/merge-content-variant-data.controller.ts
@@ -136,6 +136,7 @@ export class UmbMergeContentVariantDataController extends UmbControllerBase {
// If api is not to be found, then we can continue using the draftValue as is.
return draftValue;
}
+ (api as any).manifest = manifest;
let newValue = draftValue;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/global-components/content-workspace-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/global-components/content-workspace-property.element.ts
new file mode 100644
index 0000000000..df128bc490
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/global-components/content-workspace-property.element.ts
@@ -0,0 +1,136 @@
+import { UMB_CONTENT_WORKSPACE_CONTEXT } from '../constants.js';
+import { html, customElement, property, state, nothing } from '@umbraco-cms/backoffice/external/lit';
+import type { UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type';
+import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
+import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
+import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
+import { UmbDataPathPropertyValueQuery } from '@umbraco-cms/backoffice/validation';
+
+@customElement('umb-content-workspace-property')
+export class UmbContentWorkspacePropertyElement extends UmbLitElement {
+ private _alias?: string | undefined;
+
+ @property({ type: String, attribute: 'alias' })
+ public get alias(): string | undefined {
+ return this._alias;
+ }
+ public set alias(value: string | undefined) {
+ this._alias = value;
+ this.#observePropertyType();
+ }
+
+ @state()
+ _datasetVariantId?: UmbVariantId;
+
+ @state()
+ _dataPath?: string;
+
+ @state()
+ _viewable?: boolean;
+
+ @state()
+ _writeable?: boolean;
+
+ @state()
+ _workspaceContext?: typeof UMB_CONTENT_WORKSPACE_CONTEXT.TYPE;
+
+ @state()
+ _propertyType?: UmbPropertyTypeModel;
+
+ constructor() {
+ super();
+
+ // The Property Dataset is local to the active variant, we use this to retrieve the variant we like to gather the value from.
+ this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, (datasetContext) => {
+ this._datasetVariantId = datasetContext?.getVariantId();
+ });
+
+ // The Content Workspace Context is used to retrieve the property type we like to observe.
+ // This gives us the configuration from the property type as part of the data type.
+ this.consumeContext(UMB_CONTENT_WORKSPACE_CONTEXT, async (workspaceContext) => {
+ this._workspaceContext = workspaceContext;
+ this.#observePropertyType();
+ });
+ }
+
+ async #observePropertyType() {
+ if (!this._alias || !this._workspaceContext) return;
+
+ this.observe(await this._workspaceContext?.structure.propertyStructureByAlias(this._alias), (propertyType) => {
+ this._propertyType = propertyType;
+ this.#checkViewGuard();
+ });
+ }
+
+ #checkViewGuard() {
+ if (!this._workspaceContext || !this._propertyType || !this._datasetVariantId) return;
+
+ const propertyVariantId = new UmbVariantId(
+ this._propertyType.variesByCulture ? this._datasetVariantId.culture : null,
+ this._propertyType.variesBySegment ? this._datasetVariantId.segment : null,
+ );
+
+ this.observe(
+ this._workspaceContext.propertyViewGuard.isPermittedForVariantAndProperty(
+ propertyVariantId,
+ this._propertyType,
+ this._datasetVariantId,
+ ),
+ (permitted) => {
+ this._viewable = permitted;
+ },
+ `umbObservePropertyViewGuard`,
+ );
+ }
+
+ override willUpdate(changedProperties: Map) {
+ super.willUpdate(changedProperties);
+ if (
+ changedProperties.has('_propertyType') ||
+ changedProperties.has('_datasetVariantId') ||
+ changedProperties.has('_workspaceContext')
+ ) {
+ if (this._datasetVariantId && this._propertyType && this._workspaceContext) {
+ const propertyVariantId = new UmbVariantId(
+ this._propertyType.variesByCulture ? this._datasetVariantId.culture : null,
+ this._propertyType.variesBySegment ? this._datasetVariantId.segment : null,
+ );
+ this._dataPath = `$.values[${UmbDataPathPropertyValueQuery({
+ alias: this._propertyType.alias,
+ culture: propertyVariantId.culture,
+ segment: propertyVariantId.segment,
+ })}].value`;
+
+ this.observe(
+ this._workspaceContext.propertyWriteGuard.isPermittedForVariantAndProperty(
+ propertyVariantId,
+ this._propertyType,
+ propertyVariantId,
+ ),
+ (write) => {
+ this._writeable = write;
+ },
+ 'observeView',
+ );
+ }
+ }
+ }
+
+ override render() {
+ if (!this._viewable) return nothing;
+ if (!this._dataPath || this._writeable === undefined) return nothing;
+
+ return html` `;
+ }
+}
+
+export default UmbContentWorkspacePropertyElement;
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-content-workspace-property': UmbContentWorkspacePropertyElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/global-components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/global-components/index.ts
new file mode 100644
index 0000000000..4ad34abcf3
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/global-components/index.ts
@@ -0,0 +1 @@
+export * from './content-workspace-property.element.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/index.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/index.ts
index a532e7485e..f87b3be75a 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/content/content/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/index.ts
@@ -1,13 +1,12 @@
-export { UMB_CONTENT_PROPERTY_CONTEXT } from './content-property.context-token.js';
-export { UmbContentPropertyContext } from './content-property.context.js';
-
export * from './collection/index.js';
export * from './components/index.js';
export * from './constants.js';
export * from './controller/merge-content-variant-data.controller.js';
+export * from './global-components/index.js';
export * from './manager/index.js';
export * from './property-dataset-context/index.js';
export * from './workspace/index.js';
+
export type * from './repository/index.js';
export type * from './types.js';
export type * from './variant-picker/index.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/property-dataset-context/element-property-dataset.context.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/property-dataset-context/element-property-dataset.context.ts
index 96381eb39f..3ba55f14b9 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/content/content/property-dataset-context/element-property-dataset.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/property-dataset-context/element-property-dataset.context.ts
@@ -12,7 +12,7 @@ import {
createObservablePart,
mergeObservables,
} from '@umbraco-cms/backoffice/observable-api';
-import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
+import { UmbVariantContext, UmbVariantId } from '@umbraco-cms/backoffice/variant';
import type { UmbContentTypeModel, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type';
import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity';
@@ -47,6 +47,8 @@ export abstract class UmbElementPropertyDatasetContext<
protected _readOnly = new UmbBooleanState(false);
public readOnly = this._readOnly.asObservable();
+ #variantContext = new UmbVariantContext(this).inherit();
+
getEntityType(): string {
return this._dataOwner.getEntityType();
}
@@ -64,6 +66,7 @@ export abstract class UmbElementPropertyDatasetContext<
super(host, UMB_PROPERTY_DATASET_CONTEXT);
this._dataOwner = dataOwner;
this.#variantId = variantId ?? UmbVariantId.CreateInvariant();
+ this.#variantContext.setVariantId(this.#variantId);
this.#propertyVariantIdPromise = new Promise((resolve) => {
this.#propertyVariantIdPromiseResolver = resolve as any;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts
index 3c257fbb1c..db63689544 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts
@@ -714,6 +714,10 @@ export abstract class UmbContentDetailWorkspaceContextBase<
*/
public async runMandatoryValidationForSaveData(saveData: DetailModelType, variantIds: Array = []) {
// Check that the data is valid before we save it.
+ // If we vary by culture then we do not want to validate the invariant variant.
+ if (this.getVariesByCulture()) {
+ variantIds = variantIds.filter((variant) => !variant.isCultureInvariant());
+ }
const missingVariants = variantIds.filter((variant) => {
return !saveData.variants.some((y) => variant.compare(y));
});
@@ -754,7 +758,7 @@ export abstract class UmbContentDetailWorkspaceContextBase<
// We ask the server first to get a concatenated set of validation messages. So we see both front-end and back-end validation messages [NL]
if (this.getIsNew()) {
- const parent = this.getParent();
+ const parent = this._internal_getCreateUnderParent();
if (!parent) throw new Error('Parent is not set');
await this.#serverValidation.askServerForValidation(
saveData,
@@ -885,7 +889,7 @@ export abstract class UmbContentDetailWorkspaceContextBase<
async #create(variantIds: Array, saveData: DetailModelType) {
if (!this._detailRepository) throw new Error('Detail repository is not set');
- const parent = this.getParent();
+ const parent = this._internal_getCreateUnderParent();
if (!parent) throw new Error('Parent is not set');
const { data, error } = await this._detailRepository.create(saveData, parent.unique);
@@ -914,6 +918,7 @@ export abstract class UmbContentDetailWorkspaceContextBase<
variantIdsIncludingInvariant,
);
this._data.setCurrent(newCurrentData);
+ this.setIsNew(false);
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!eventContext) {
@@ -924,7 +929,6 @@ export abstract class UmbContentDetailWorkspaceContextBase<
unique: parent.unique,
});
eventContext.dispatchEvent(event);
- this.setIsNew(false);
}
async #update(variantIds: Array, saveData: DetailModelType) {
@@ -978,6 +982,7 @@ export abstract class UmbContentDetailWorkspaceContextBase<
override resetState() {
super.resetState();
+ this.structure.clear();
this.readOnlyGuard.clearRules();
this.propertyViewGuard.clearRules();
this.propertyWriteGuard.clearRules();
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor-properties.element.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor-properties.element.ts
index 0e5fc652a9..451be57c5a 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor-properties.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor-properties.element.ts
@@ -1,5 +1,5 @@
import { UMB_CONTENT_WORKSPACE_CONTEXT } from '../../content-workspace.context-token.js';
-import { css, html, customElement, property, state, repeat, nothing } from '@umbraco-cms/backoffice/external/lit';
+import { css, html, customElement, property, state, repeat } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type {
UmbContentTypeModel,
@@ -8,17 +8,10 @@ import type {
} from '@umbraco-cms/backoffice/content-type';
import { UmbContentTypePropertyStructureHelper } from '@umbraco-cms/backoffice/content-type';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
-import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
-import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
-
-import './content-editor-property.element.js';
@customElement('umb-content-workspace-view-edit-properties')
export class UmbContentWorkspaceViewEditPropertiesElement extends UmbLitElement {
- #workspaceContext?: typeof UMB_CONTENT_WORKSPACE_CONTEXT.TYPE;
#propertyStructureHelper = new UmbContentTypePropertyStructureHelper(this);
- #properties?: Array;
- #visiblePropertiesUniques: Array = [];
@property({ type: String, attribute: 'container-id', reflect: false })
public get containerId(): string | null | undefined {
@@ -29,83 +22,36 @@ export class UmbContentWorkspaceViewEditPropertiesElement extends UmbLitElement
}
@state()
- _variantId?: UmbVariantId;
+ _properties: Array = [];
@state()
_visibleProperties?: Array;
constructor() {
super();
-
- this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, (datasetContext) => {
- this._variantId = datasetContext?.getVariantId();
- this.#processPropertyStructure();
- });
-
this.consumeContext(UMB_CONTENT_WORKSPACE_CONTEXT, (workspaceContext) => {
- this.#workspaceContext = workspaceContext;
this.#propertyStructureHelper.setStructureManager(
// Assuming its the same content model type that we are working with here... [NL]
workspaceContext?.structure as unknown as UmbContentTypeStructureManager,
);
this.observe(
- this.#propertyStructureHelper.propertyStructure,
+ this.#propertyStructureHelper.propertyAliases,
(properties) => {
- this.#properties = properties;
- this.#processPropertyStructure();
+ this._properties = properties;
},
'observePropertyStructure',
);
});
}
- #processPropertyStructure() {
- if (!this.#workspaceContext || !this.#properties || !this.#propertyStructureHelper) {
- return;
- }
-
- const propertyViewGuard = this.#workspaceContext.propertyViewGuard;
-
- this.#properties.forEach((property) => {
- const propertyVariantId = new UmbVariantId(this._variantId?.culture, this._variantId?.segment);
- this.observe(
- propertyViewGuard.isPermittedForVariantAndProperty(propertyVariantId, property),
- (permitted) => {
- if (permitted) {
- this.#visiblePropertiesUniques.push(property.unique);
- this.#calculateVisibleProperties();
- } else {
- const index = this.#visiblePropertiesUniques.indexOf(property.unique);
- if (index !== -1) {
- this.#visiblePropertiesUniques.splice(index, 1);
- this.#calculateVisibleProperties();
- }
- }
- },
- `propertyViewGuard-permittedForVariantAndProperty-${property.unique}`,
- );
- });
- }
-
- #calculateVisibleProperties() {
- this._visibleProperties = this.#properties!.filter((property) =>
- this.#visiblePropertiesUniques.includes(property.unique),
- );
- }
-
override render() {
- return this._variantId && this._visibleProperties
- ? repeat(
- this._visibleProperties,
- (property) => property.alias,
- (property) =>
- html` `,
- )
- : nothing;
+ return repeat(
+ this._properties,
+ (property) => property,
+ (property) =>
+ html` `,
+ );
}
static override styles = [
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor-property.element.ts
deleted file mode 100644
index b7f3396900..0000000000
--- a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor-property.element.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { UMB_CONTENT_WORKSPACE_CONTEXT } from '../../content-workspace.context-token.js';
-import { html, customElement, property, state, nothing } from '@umbraco-cms/backoffice/external/lit';
-import type { UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type';
-import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
-import { UmbDataPathPropertyValueQuery } from '@umbraco-cms/backoffice/validation';
-import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
-
-@customElement('umb-content-workspace-view-edit-property')
-export class UmbContentWorkspaceViewEditPropertyElement extends UmbLitElement {
- //
- @property({ attribute: false })
- variantId?: UmbVariantId;
-
- @property({ attribute: false })
- property?: UmbPropertyTypeModel;
-
- @state()
- _dataPath?: string;
-
- @state()
- _writeable?: boolean;
-
- @state()
- _context?: typeof UMB_CONTENT_WORKSPACE_CONTEXT.TYPE;
-
- constructor() {
- super();
-
- this.consumeContext(UMB_CONTENT_WORKSPACE_CONTEXT, (context) => {
- this._context = context;
- });
- }
-
- override willUpdate(changedProperties: Map) {
- super.willUpdate(changedProperties);
- if (changedProperties.has('type') || changedProperties.has('variantId') || changedProperties.has('_context')) {
- if (this.variantId && this.property && this._context) {
- const propertyVariantId = new UmbVariantId(
- this.property.variesByCulture ? this.variantId.culture : null,
- this.property.variesBySegment ? this.variantId.segment : null,
- );
- this._dataPath = `$.values[${UmbDataPathPropertyValueQuery({
- alias: this.property.alias,
- culture: propertyVariantId.culture,
- segment: propertyVariantId.segment,
- })}].value`;
-
- this.observe(
- this._context.propertyWriteGuard.isPermittedForVariantAndProperty(propertyVariantId, this.property),
- (write) => {
- this._writeable = write;
- },
- 'observeView',
- );
- }
- }
- }
-
- override render() {
- if (!this._dataPath || this._writeable === undefined) return nothing;
-
- return html` `;
- }
-}
-
-export default UmbContentWorkspaceViewEditPropertyElement;
-
-declare global {
- interface HTMLElementTagNameMap {
- 'umb-content-workspace-view-edit-property': UmbContentWorkspaceViewEditPropertyElement;
- }
-}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor.element.ts
index 6f9bb5f542..693808923e 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor.element.ts
@@ -109,6 +109,7 @@ export class UmbContentWorkspaceViewEditElement extends UmbLitElement implements
if (routes.length !== 0) {
routes.push({
path: '',
+ pathMatch: 'full',
redirectTo: routes[0].path,
});
diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/property-type/workspace/property-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/content/property-type/workspace/property-type-workspace.context.ts
index d3c218dc28..bd4e7ed78b 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/content/property-type/workspace/property-type-workspace.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/content/property-type/workspace/property-type-workspace.context.ts
@@ -72,7 +72,11 @@ export class UmbPropertyTypeWorkspaceContext
this.#contentTypeContext = context;
})
.skipHost()
- .asPromise({ preventTimeout: true });
+ .asPromise({ preventTimeout: true })
+ .catch(() => {
+ // If the context is not available, we can assume that the context is not available.
+ this.#contentTypeContext = undefined;
+ });
this.routes.setRoutes([
{
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts b/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts
index 883677cc43..d442a7c153 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts
@@ -6700,6 +6700,7 @@ export type GetItemDocumentSearchData = {
query?: {
query?: string;
trashed?: boolean;
+ culture?: string;
skip?: number;
take?: number;
parentId?: string;
@@ -8991,6 +8992,7 @@ export type GetItemMediaSearchData = {
query?: {
query?: string;
trashed?: boolean;
+ culture?: string;
skip?: number;
take?: number;
parentId?: string;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-view.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-view.manager.ts
index 2c7832a555..bb0134824b 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-view.manager.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-view.manager.ts
@@ -101,12 +101,12 @@ export class UmbCollectionViewManager extends UmbControllerBase {
this.setCurrentView(fallbackView);
},
});
- }
- routes.push({
- path: `**`,
- component: async () => (await import('@umbraco-cms/backoffice/router')).UmbRouteNotFoundElement,
- });
+ routes.push({
+ path: `**`,
+ component: async () => (await import('@umbraco-cms/backoffice/router')).UmbRouteNotFoundElement,
+ });
+ }
}
this.#routes.setValue(routes);
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-filter-field.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-filter-field.element.ts
index 7f1d3f9f8b..923f5cc01a 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-filter-field.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-filter-field.element.ts
@@ -34,7 +34,7 @@ export class UmbCollectionFilterFieldElement extends UmbLitElement {
static override readonly styles = [
css`
uui-input {
- display: block;
+ width: 100%;
}
`,
];
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts
index 9967dead34..4edbcf886c 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts
@@ -25,7 +25,7 @@ import {
} from '@umbraco-cms/backoffice/entity-action';
import type { UmbActionEventContext } from '@umbraco-cms/backoffice/action';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
-import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity';
+import { UMB_ENTITY_CONTEXT, UmbParentEntityContext, type UmbEntityModel } from '@umbraco-cms/backoffice/entity';
import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace';
import { UmbModalRouteRegistrationController, type UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router';
@@ -83,6 +83,7 @@ export class UmbDefaultCollectionContext<
});
#actionEventContext: UmbActionEventContext | undefined;
+ #parentEntityContext = new UmbParentEntityContext(this);
constructor(host: UmbControllerHost, defaultViewAlias: string, defaultFilter: Partial = {}) {
super(host, UMB_COLLECTION_CONTEXT);
@@ -92,6 +93,23 @@ export class UmbDefaultCollectionContext<
this.pagination.addEventListener(UmbChangeEvent.TYPE, this.#onPageChange);
this.#listenToEntityEvents();
+
+ // The parent entity context is used to get the parent entity for the collection items
+ // All items in the collection are children of the current entity context
+ this.consumeContext(UMB_ENTITY_CONTEXT, (context) => {
+ const currentEntityUnique = context?.getUnique();
+ const currentEntityType = context?.getEntityType();
+
+ const parent: UmbEntityModel | undefined =
+ currentEntityUnique && currentEntityType
+ ? {
+ unique: currentEntityUnique,
+ entityType: currentEntityType,
+ }
+ : undefined;
+
+ this.#parentEntityContext?.setParent(parent);
+ });
}
setupView(viewElement: UmbControllerHost) {
@@ -225,6 +243,10 @@ export class UmbDefaultCollectionContext<
return this._manifest;
}
+ public getEmptyLabel(): string {
+ return this.manifest?.meta.noItemsLabel ?? this.#config?.noItemsLabel ?? '#collection_noItemsTitle';
+ }
+
/**
* Requests the collection from the repository.
* @returns {*}
@@ -261,6 +283,10 @@ export class UmbDefaultCollectionContext<
this.requestCollection();
}
+ public updateFilter(filter: Partial) {
+ this._filter.setValue({ ...this._filter.getValue(), ...filter });
+ }
+
public getLastSelectedView(unique: string | undefined): string | undefined {
if (!unique) return;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts
index aa08150a8b..94440fa140 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts
@@ -23,6 +23,9 @@ umbExtensionsRegistry.register(manifest);
@customElement('umb-collection-default')
export class UmbCollectionDefaultElement extends UmbLitElement {
+ //
+ #collectionContext?: UmbDefaultCollectionContext;
+
@state()
private _routes: Array = [];
@@ -32,7 +35,8 @@ export class UmbCollectionDefaultElement extends UmbLitElement {
@state()
private _isDoneLoading = false;
- #collectionContext?: UmbDefaultCollectionContext;
+ @state()
+ private _emptyLabel?: string;
constructor() {
super();
@@ -40,6 +44,7 @@ export class UmbCollectionDefaultElement extends UmbLitElement {
this.#collectionContext = context;
this.#observeCollectionRoutes();
this.#observeTotalItems();
+ this.#getEmptyStateLabel();
await this.#collectionContext?.requestCollection();
this._isDoneLoading = true;
});
@@ -69,6 +74,10 @@ export class UmbCollectionDefaultElement extends UmbLitElement {
);
}
+ #getEmptyStateLabel() {
+ this._emptyLabel = this.#collectionContext?.getEmptyLabel();
+ }
+
override render() {
return this._routes
? html`
@@ -98,9 +107,10 @@ export class UmbCollectionDefaultElement extends UmbLitElement {
#renderEmptyState() {
if (!this._isDoneLoading) return nothing;
+
return html`
-
+ ${this.localize.string(this._emptyLabel)}
`;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/extensions/collection.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/extensions/collection.extension.ts
index 7532fe9acc..56011a2cb8 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/extensions/collection.extension.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/extensions/collection.extension.ts
@@ -9,6 +9,7 @@ export interface ManifestCollection
export interface MetaCollection {
repositoryAlias: string;
+ noItemsLabel?: string;
}
declare global {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/types.ts
index f449868688..3314fef940 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/types.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/types.ts
@@ -25,6 +25,7 @@ export interface UmbCollectionConfiguration {
orderBy?: string;
orderDirection?: string;
pageSize?: number;
+ noItemsLabel?: string;
userDefinedProperties?: Array;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/dropdown/dropdown.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/dropdown/dropdown.element.ts
index a9a064d6d3..cc8a844b48 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/components/dropdown/dropdown.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/dropdown/dropdown.element.ts
@@ -12,13 +12,11 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
// TODO: maybe move this to UI Library.
@customElement('umb-dropdown')
export class UmbDropdownElement extends UmbLitElement {
- @query('#dropdown-popover')
- popoverContainerElement?: UUIPopoverContainerElement;
@property({ type: Boolean, reflect: true })
open = false;
@property()
- label = '';
+ label?: string;
@property()
look: UUIInterfaceLook = 'default';
@@ -35,19 +33,16 @@ export class UmbDropdownElement extends UmbLitElement {
@property({ type: Boolean, attribute: 'hide-expand' })
hideExpand = false;
+ @query('#dropdown-popover')
+ popoverContainerElement?: UUIPopoverContainerElement;
+
protected override updated(_changedProperties: PropertyValueMap | Map): void {
super.updated(_changedProperties);
if (_changedProperties.has('open') && this.popoverContainerElement) {
if (this.open) {
- // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet.
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- this.popoverContainerElement.showPopover();
+ this.openDropdown();
} else {
- // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet.
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- this.popoverContainerElement.hidePopover();
+ this.closeDropdown();
}
}
}
@@ -59,14 +54,29 @@ export class UmbDropdownElement extends UmbLitElement {
this.open = event.newState === 'open';
}
+ openDropdown() {
+ // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet.
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ this.popoverContainerElement?.showPopover();
+ }
+
+ closeDropdown() {
+ // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet.
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ this.popoverContainerElement?.hidePopover();
+ }
+
override render() {
return html`
${when(
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/entity-actions-bundle/entity-actions-bundle.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/entity-actions-bundle/entity-actions-bundle.element.ts
index 1467fee2b9..a8d70cbac6 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/components/entity-actions-bundle/entity-actions-bundle.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/entity-actions-bundle/entity-actions-bundle.element.ts
@@ -1,7 +1,17 @@
import { UmbEntityContext } from '../../entity/entity.context.js';
+import type { UmbDropdownElement } from '../dropdown/index.js';
import type { UmbEntityAction, ManifestEntityActionDefaultKind } from '@umbraco-cms/backoffice/entity-action';
import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
-import { html, nothing, customElement, property, state, ifDefined, css } from '@umbraco-cms/backoffice/external/lit';
+import {
+ html,
+ nothing,
+ customElement,
+ property,
+ state,
+ ifDefined,
+ css,
+ query,
+} from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { UmbExtensionsManifestInitializer, createExtensionApi } from '@umbraco-cms/backoffice/extension-api';
@@ -29,8 +39,8 @@ export class UmbEntityActionsBundleElement extends UmbLitElement {
@state()
private _firstActionHref?: string;
- @state()
- _dropdownIsOpen = false;
+ @query('#action-modal')
+ private _dropdownElement?: UmbDropdownElement;
// TODO: provide the entity context on a higher level, like the root element of this entity, tree-item/workspace/... [NL]
#entityContext = new UmbEntityContext(this);
@@ -64,8 +74,10 @@ export class UmbEntityActionsBundleElement extends UmbLitElement {
this._firstActionApi = await createExtensionApi(this, this._firstActionManifest, [
{ unique: this.unique, entityType: this.entityType, meta: this._firstActionManifest.meta },
]);
-
- this._firstActionHref = await this._firstActionApi?.getHref();
+ if (this._firstActionApi) {
+ (this._firstActionApi as any).manifest = this._firstActionManifest;
+ this._firstActionHref = await this._firstActionApi.getHref();
+ }
}
async #onFirstActionClick(event: PointerEvent) {
@@ -79,7 +91,7 @@ export class UmbEntityActionsBundleElement extends UmbLitElement {
}
#onActionExecuted() {
- this._dropdownIsOpen = false;
+ this._dropdownElement?.closeDropdown();
}
#onDropdownClick(event: Event) {
@@ -95,25 +107,27 @@ export class UmbEntityActionsBundleElement extends UmbLitElement {
if (this._numberOfActions === 1) return nothing;
return html`
-
-
+
+
+ .unique=${this.unique}
+ .label=${this.label}>
`;
}
#renderFirstAction() {
- if (!this._firstActionApi) return nothing;
+ if (!this._firstActionApi || !this._firstActionManifest) return nothing;
return html`
-
+
`;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-number-range/input-number-range.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-number-range/input-number-range.element.ts
index d9a961b5aa..b2c1a71335 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-number-range/input-number-range.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-number-range/input-number-range.element.ts
@@ -47,7 +47,21 @@ export class UmbInputNumberRangeElement extends UmbFormControlMixin(UmbLitElemen
}
@property({ type: Object })
- validationRange?: UmbNumberRangeValueType;
+ public set validationRange(value: UmbNumberRangeValueType | undefined) {
+ this.#validationRange = value;
+ this._minPlaceholder = value?.min !== undefined ? String(value?.min) : '';
+ this._maxPlaceholder = value?.max !== undefined && value.max !== Infinity ? String(value.max) : '∞';
+ }
+ public get validationRange(): UmbNumberRangeValueType | undefined {
+ return this.#validationRange;
+ }
+ #validationRange?: UmbNumberRangeValueType | undefined;
+
+ @state()
+ private _minPlaceholder: string = '';
+
+ @state()
+ private _maxPlaceholder: string = '';
private updateValue() {
const newValue =
@@ -114,7 +128,7 @@ export class UmbInputNumberRangeElement extends UmbFormControlMixin(UmbLitElemen
label=${this.minLabel}
min=${ifDefined(this.validationRange?.min)}
max=${ifDefined(this.validationRange?.max)}
- placeholder=${this.validationRange?.min ?? ''}
+ placeholder=${this._minPlaceholder}
.value=${this._minValue}
@input=${this.#onMinInput}>
–
@@ -123,13 +137,20 @@ export class UmbInputNumberRangeElement extends UmbFormControlMixin(UmbLitElemen
label=${this.maxLabel}
min=${ifDefined(this.validationRange?.min)}
max=${ifDefined(this.validationRange?.max)}
- placeholder=${this.validationRange?.max ?? '∞'}
+ placeholder=${this._maxPlaceholder}
.value=${this._maxValue}
@input=${this.#onMaxInput}>
`;
}
static override styles = css`
+ :host {
+ display: flex;
+ align-items: center;
+ }
+ b {
+ margin: 0 var(--uui-size-space-1);
+ }
:host(:invalid:not([pristine])) {
color: var(--uui-color-invalid);
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-with-alias/input-with-alias.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-with-alias/input-with-alias.element.ts
index fc9ae5ba82..a91c9ac5d4 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-with-alias/input-with-alias.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-with-alias/input-with-alias.element.ts
@@ -138,8 +138,6 @@ export class UmbInputWithAliasElement extends UmbFormControlMixin
- ${this.manifest?.meta.icon
- ? html` `
- : nothing}
+ ${this.manifest.meta.icon ? html` ` : nothing}
`;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/entity-actions-table-column-view/entity-actions-table-column-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/entity-actions-table-column-view/entity-actions-table-column-view.element.ts
index f1b112ff09..d2ca007786 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/entity-actions-table-column-view/entity-actions-table-column-view.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/entity-actions-table-column-view/entity-actions-table-column-view.element.ts
@@ -1,17 +1,20 @@
-import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
+import type { UmbEntityModel, UmbNamedEntityModel } from '@umbraco-cms/backoffice/entity';
import { html, nothing, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@customElement('umb-entity-actions-table-column-view')
export class UmbEntityActionsTableColumnViewElement extends UmbLitElement {
@property({ attribute: false })
- value?: UmbEntityModel;
+ value?: UmbEntityModel | UmbNamedEntityModel;
override render() {
if (!this.value) return nothing;
return html`
-
+
`;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/entity-item-ref/entity-item-ref.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/entity-item-ref/entity-item-ref.element.ts
index f239f3ebe4..275fdf48fe 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/entity-item-ref/entity-item-ref.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/entity-item-ref/entity-item-ref.element.ts
@@ -8,6 +8,7 @@ import { UmbRoutePathAddendumContext } from '@umbraco-cms/backoffice/router';
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
import './default-item-ref.element.js';
+import { UmbDeselectedEvent, UmbSelectedEvent } from '@umbraco-cms/backoffice/event';
@customElement('umb-entity-item-ref')
export class UmbEntityItemRefElement extends UmbLitElement {
@@ -41,7 +42,7 @@ export class UmbEntityItemRefElement extends UmbLitElement {
}
#readonly = false;
- @property({ type: Boolean, attribute: 'readonly' })
+ @property({ type: Boolean, reflect: true })
public get readonly() {
return this.#readonly;
}
@@ -54,7 +55,7 @@ export class UmbEntityItemRefElement extends UmbLitElement {
}
#standalone = false;
- @property({ type: Boolean, attribute: 'standalone' })
+ @property({ type: Boolean, reflect: true })
public get standalone() {
return this.#standalone;
}
@@ -66,8 +67,74 @@ export class UmbEntityItemRefElement extends UmbLitElement {
}
}
+ #selectOnly = false;
+ @property({ type: Boolean, attribute: 'select-only', reflect: true })
+ public get selectOnly() {
+ return this.#selectOnly;
+ }
+ public set selectOnly(value) {
+ this.#selectOnly = value;
+
+ if (this._component) {
+ this._component.selectOnly = this.#selectOnly;
+ }
+ }
+
+ #selectable = false;
+ @property({ type: Boolean, reflect: true })
+ public get selectable() {
+ return this.#selectable;
+ }
+ public set selectable(value) {
+ this.#selectable = value;
+
+ if (this._component) {
+ this._component.selectable = this.#selectable;
+ }
+ }
+
+ #selected = false;
+ @property({ type: Boolean, reflect: true })
+ public get selected() {
+ return this.#selected;
+ }
+ public set selected(value) {
+ this.#selected = value;
+
+ if (this._component) {
+ this._component.selected = this.#selected;
+ }
+ }
+
+ #disabled = false;
+ @property({ type: Boolean, reflect: true })
+ public get disabled() {
+ return this.#disabled;
+ }
+ public set disabled(value) {
+ this.#disabled = value;
+
+ if (this._component) {
+ this._component.disabled = this.#disabled;
+ }
+ }
+
#pathAddendum = new UmbRoutePathAddendumContext(this);
+ #onSelected(event: UmbSelectedEvent) {
+ event.stopPropagation();
+ const unique = this.#item?.unique;
+ if (!unique) throw new Error('No unique id found for item');
+ this.dispatchEvent(new UmbSelectedEvent(unique));
+ }
+
+ #onDeselected(event: UmbDeselectedEvent) {
+ event.stopPropagation();
+ const unique = this.#item?.unique;
+ if (!unique) throw new Error('No unique id found for item');
+ this.dispatchEvent(new UmbDeselectedEvent(unique));
+ }
+
protected override firstUpdated(_changedProperties: PropertyValueMap | Map): void {
super.firstUpdated(_changedProperties);
this.setAttribute(UMB_MARK_ATTRIBUTE_NAME, 'entity-item-ref');
@@ -91,6 +158,13 @@ export class UmbEntityItemRefElement extends UmbLitElement {
component.item = this.#item;
component.readonly = this.readonly;
component.standalone = this.standalone;
+ component.selectOnly = this.selectOnly;
+ component.selectable = this.selectable;
+ component.selected = this.selected;
+ component.disabled = this.disabled;
+
+ component.addEventListener(UmbSelectedEvent.TYPE, this.#onSelected.bind(this));
+ component.addEventListener(UmbDeselectedEvent.TYPE, this.#onDeselected.bind(this));
// Proxy the actions slot to the component
const slotElement = document.createElement('slot');
@@ -110,6 +184,12 @@ export class UmbEntityItemRefElement extends UmbLitElement {
return html`${this._component}`;
}
+ override destroy(): void {
+ this._component?.removeEventListener(UmbSelectedEvent.TYPE, this.#onSelected.bind(this));
+ this._component?.removeEventListener(UmbDeselectedEvent.TYPE, this.#onDeselected.bind(this));
+ super.destroy();
+ }
+
static override styles = [
css`
:host {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/index.ts
index 5ada9eda75..a0ee69f820 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/index.ts
@@ -1 +1,2 @@
+export * from './item-data-api-get-request-controller/index.js';
export * from './entity-item-ref/index.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/index.ts
new file mode 100644
index 0000000000..5bd7a5f06f
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/index.ts
@@ -0,0 +1 @@
+export * from './item-data-api-get-request.controller.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/item-data-api-get-request.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/item-data-api-get-request.controller.ts
new file mode 100644
index 0000000000..b10a783b97
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/item-data-api-get-request.controller.ts
@@ -0,0 +1,66 @@
+import type { UmbItemDataApiGetRequestControllerArgs } from './types.js';
+import {
+ batchTryExecute,
+ tryExecute,
+ UmbError,
+ type UmbApiError,
+ type UmbCancelError,
+ type UmbDataApiResponse,
+} from '@umbraco-cms/backoffice/resources';
+import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { batchArray } from '@umbraco-cms/backoffice/utils';
+import { umbPeekError } from '@umbraco-cms/backoffice/notification';
+import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
+
+export class UmbItemDataApiGetRequestController<
+ ResponseModelType extends UmbDataApiResponse,
+> extends UmbControllerBase {
+ #apiCallback: (args: { uniques: Array }) => Promise;
+ #uniques: Array;
+ #batchSize: number = 40;
+
+ constructor(host: UmbControllerHost, args: UmbItemDataApiGetRequestControllerArgs) {
+ super(host);
+ this.#apiCallback = args.api;
+ this.#uniques = args.uniques;
+ }
+
+ async request() {
+ if (!this.#uniques) throw new Error('Uniques are missing');
+
+ let data: ResponseModelType['data'] | undefined;
+ let error: UmbError | UmbApiError | UmbCancelError | Error | undefined;
+
+ if (this.#uniques.length > this.#batchSize) {
+ const chunks = batchArray(this.#uniques, this.#batchSize);
+ const results = await batchTryExecute(this, chunks, (chunk) => this.#apiCallback({ uniques: chunk }));
+
+ const errors = results.filter((promiseResult) => promiseResult.status === 'rejected');
+
+ if (errors.length > 0) {
+ error = await this.#getAndHandleErrorResult(errors);
+ }
+
+ data = results
+ .filter((promiseResult) => promiseResult.status === 'fulfilled')
+ .flatMap((promiseResult) => promiseResult.value.data);
+ } else {
+ const result = await tryExecute(this, this.#apiCallback({ uniques: this.#uniques }));
+ data = result.data;
+ error = result.error;
+ }
+
+ return { data, error };
+ }
+
+ async #getAndHandleErrorResult(errors: Array) {
+ // TODO: We currently expect all the errors to be the same, but we should handle this better in the future.
+ const error = errors[0];
+ await umbPeekError(this, {
+ headline: 'Error fetching items',
+ message: 'An error occurred while fetching items.',
+ });
+
+ return new UmbError(error.reason);
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/types.ts
new file mode 100644
index 0000000000..fc4f1a47e4
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/item-data-api-get-request-controller/types.ts
@@ -0,0 +1,6 @@
+import type { UmbDataApiResponse } from '@umbraco-cms/backoffice/resources';
+
+export interface UmbItemDataApiGetRequestControllerArgs {
+ api: (args: { uniques: Array }) => Promise;
+ uniques: Array;
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/types.ts
index 31240f7f67..78aae14245 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/types.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/types.ts
@@ -1,4 +1,5 @@
import type { UmbNamedEntityModel } from '@umbraco-cms/backoffice/entity';
+export type * from './item-data-api-get-request-controller/types.js';
export interface UmbDefaultItemModel extends UmbNamedEntityModel {
icon?: string;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/constants.ts
index 3ed4ee95ed..f804358649 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/entity/constants.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/constants.ts
@@ -1 +1,2 @@
export * from './contexts/ancestors/constants.js';
+export * from './contexts/parent/constants.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/constants.ts
new file mode 100644
index 0000000000..31caa05f93
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/constants.ts
@@ -0,0 +1 @@
+export { UMB_PARENT_ENTITY_CONTEXT } from './parent.entity-context-token.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/index.ts
new file mode 100644
index 0000000000..d499a54ed5
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/index.ts
@@ -0,0 +1 @@
+export { UmbParentEntityContext } from './parent.entity-context.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/parent.entity-context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/parent.entity-context-token.ts
new file mode 100644
index 0000000000..8a28553f5a
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/parent.entity-context-token.ts
@@ -0,0 +1,4 @@
+import type { UmbParentEntityContext } from './parent.entity-context.js';
+import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
+
+export const UMB_PARENT_ENTITY_CONTEXT = new UmbContextToken('UmbParentEntityContext');
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/parent.entity-context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/parent.entity-context.ts
new file mode 100644
index 0000000000..efc1fed494
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/parent.entity-context.ts
@@ -0,0 +1,38 @@
+import type { UmbEntityModel } from '../../types.js';
+import { UMB_PARENT_ENTITY_CONTEXT } from './parent.entity-context-token.js';
+import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
+import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
+
+/**
+ * A entity context for the parent
+ * @class UmbParentEntityContext
+ * @augments {UmbContextBase}
+ * @implements {UmbParentEntityContext}
+ */
+export class UmbParentEntityContext extends UmbContextBase {
+ #parent = new UmbObjectState(undefined);
+ parent = this.#parent.asObservable();
+
+ constructor(host: UmbControllerHost) {
+ super(host, UMB_PARENT_ENTITY_CONTEXT);
+ }
+
+ /**
+ * Gets the parent state
+ * @returns {UmbEntityModel | undefined} - The parent state
+ * @memberof UmbParentEntityContext
+ */
+ getParent(): UmbEntityModel | undefined {
+ return this.#parent.getValue();
+ }
+
+ /**
+ * Sets the parent state
+ * @param {UmbEntityModel | undefined} parent - The parent state
+ * @memberof UmbParentEntityContext
+ */
+ setParent(parent: UmbEntityModel | undefined): void {
+ this.#parent.setValue(parent);
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/index.ts
index d064b7c97a..046856fa0e 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/entity/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/index.ts
@@ -2,4 +2,6 @@ export { UMB_ENTITY_CONTEXT } from './entity.context-token.js';
export { UmbEntityContext } from './entity.context.js';
export * from './constants.js';
export * from './contexts/ancestors/index.js';
+export * from './contexts/parent/index.js';
+
export type * from './types.js';
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 b2d09a9378..d095317698 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
@@ -1402,8 +1402,7 @@
},
{
"name": "icon-next",
- "file": "play.svg",
- "legacy": true
+ "file": "skip-forward.svg"
},
{
"name": "icon-nodes",
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 6efeae5314..353fe7c641 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
@@ -1,2556 +1,3260 @@
-export default [{
-name: "icon-activity",
-path: () => import("./icons/icon-activity.js"),
-},{
-name: "icon-add",
-path: () => import("./icons/icon-add.js"),
-},{
-name: "icon-addressbook",
-path: () => import("./icons/icon-addressbook.js"),
-},{
-name: "icon-alarm-clock",
-path: () => import("./icons/icon-alarm-clock.js"),
-},{
-name: "icon-alert-alt",
-path: () => import("./icons/icon-alert-alt.js"),
-},{
-name: "icon-alert",
-path: () => import("./icons/icon-alert.js"),
-},{
-name: "icon-alt",
-path: () => import("./icons/icon-alt.js"),
-},{
-name: "icon-anchor",
-path: () => import("./icons/icon-anchor.js"),
-},{
-name: "icon-app",
-path: () => import("./icons/icon-app.js"),
-},{
-name: "icon-application-error",
-path: () => import("./icons/icon-application-error.js"),
-},{
-name: "icon-application-window-alt",
-path: () => import("./icons/icon-application-window-alt.js"),
-},{
-name: "icon-application-window",
-path: () => import("./icons/icon-application-window.js"),
-},{
-name: "icon-arrivals",
-path: () => import("./icons/icon-arrivals.js"),
-},{
-name: "icon-arrow-down",
-path: () => import("./icons/icon-arrow-down.js"),
-},{
-name: "icon-arrow-left",
-path: () => import("./icons/icon-arrow-left.js"),
-},{
-name: "icon-arrow-right",
-path: () => import("./icons/icon-arrow-right.js"),
-},{
-name: "icon-arrow-up",
-path: () => import("./icons/icon-arrow-up.js"),
-},{
-name: "icon-attachment",
-path: () => import("./icons/icon-attachment.js"),
-},{
-name: "icon-audio-lines",
-path: () => import("./icons/icon-audio-lines.js"),
-},{
-name: "icon-autofill",
-path: () => import("./icons/icon-autofill.js"),
-},{
-name: "icon-award",
-path: () => import("./icons/icon-award.js"),
-},{
-name: "icon-axis-rotation-2",
-path: () => import("./icons/icon-axis-rotation-2.js"),
-},{
-name: "icon-axis-rotation-3",
-path: () => import("./icons/icon-axis-rotation-3.js"),
-},{
-name: "icon-axis-rotation",
-path: () => import("./icons/icon-axis-rotation.js"),
-},{
-name: "icon-backspace",
-path: () => import("./icons/icon-backspace.js"),
-},{
-name: "icon-badge-add",
-path: () => import("./icons/icon-badge-add.js"),
-},{
-name: "icon-badge-remove",
-path: () => import("./icons/icon-badge-remove.js"),
-},{
-name: "icon-badge-restricted",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-badge-restricted.js"),
-},{
-name: "icon-ball",
-path: () => import("./icons/icon-ball.js"),
-},{
-name: "icon-bar-chart",
-path: () => import("./icons/icon-bar-chart.js"),
-},{
-name: "icon-barcode",
-path: () => import("./icons/icon-barcode.js"),
-},{
-name: "icon-bars",
-path: () => import("./icons/icon-bars.js"),
-},{
-name: "icon-battery-full",
-path: () => import("./icons/icon-battery-full.js"),
-},{
-name: "icon-battery-low",
-path: () => import("./icons/icon-battery-low.js"),
-},{
-name: "icon-beer-glass",
-path: () => import("./icons/icon-beer-glass.js"),
-},{
-name: "icon-bell-off",
-path: () => import("./icons/icon-bell-off.js"),
-},{
-name: "icon-bell",
-path: () => import("./icons/icon-bell.js"),
-},{
-name: "icon-binarycode",
-path: () => import("./icons/icon-binarycode.js"),
-},{
-name: "icon-binoculars",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-binoculars.js"),
-},{
-name: "icon-bird",
-path: () => import("./icons/icon-bird.js"),
-},{
-name: "icon-birthday-cake",
-path: () => import("./icons/icon-birthday-cake.js"),
-},{
-name: "icon-block",
-path: () => import("./icons/icon-block.js"),
-},{
-name: "icon-blockquote",
-path: () => import("./icons/icon-blockquote.js"),
-},{
-name: "icon-bluetooth",
-path: () => import("./icons/icon-bluetooth.js"),
-},{
-name: "icon-boat-shipping",
-path: () => import("./icons/icon-boat-shipping.js"),
-},{
-name: "icon-bold",
-path: () => import("./icons/icon-bold.js"),
-},{
-name: "icon-bones",
-path: () => import("./icons/icon-bones.js"),
-},{
-name: "icon-book-alt-2",
-path: () => import("./icons/icon-book-alt-2.js"),
-},{
-name: "icon-book-alt",
-path: () => import("./icons/icon-book-alt.js"),
-},{
-name: "icon-book",
-path: () => import("./icons/icon-book.js"),
-},{
-name: "icon-bookmark",
-path: () => import("./icons/icon-bookmark.js"),
-},{
-name: "icon-books",
-path: () => import("./icons/icon-books.js"),
-},{
-name: "icon-box-alt",
-path: () => import("./icons/icon-box-alt.js"),
-},{
-name: "icon-box-open",
-path: () => import("./icons/icon-box-open.js"),
-},{
-name: "icon-box",
-path: () => import("./icons/icon-box.js"),
-},{
-name: "icon-brackets",
-path: () => import("./icons/icon-brackets.js"),
-},{
-name: "icon-brick",
-path: () => import("./icons/icon-brick.js"),
-},{
-name: "icon-briefcase",
-path: () => import("./icons/icon-briefcase.js"),
-},{
-name: "icon-browser-window",
-path: () => import("./icons/icon-browser-window.js"),
-},{
-name: "icon-brush-alt-2",
-path: () => import("./icons/icon-brush-alt-2.js"),
-},{
-name: "icon-brush-alt",
-path: () => import("./icons/icon-brush-alt.js"),
-},{
-name: "icon-brush",
-path: () => import("./icons/icon-brush.js"),
-},{
-name: "icon-bug",
-path: () => import("./icons/icon-bug.js"),
-},{
-name: "icon-bulleted-list",
-path: () => import("./icons/icon-bulleted-list.js"),
-},{
-name: "icon-burn",
-path: () => import("./icons/icon-burn.js"),
-},{
-name: "icon-bus",
-path: () => import("./icons/icon-bus.js"),
-},{
-name: "icon-calculator",
-path: () => import("./icons/icon-calculator.js"),
-},{
-name: "icon-calendar-alt",
-path: () => import("./icons/icon-calendar-alt.js"),
-},{
-name: "icon-calendar",
-path: () => import("./icons/icon-calendar.js"),
-},{
-name: "icon-camcorder",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-camcorder.js"),
-},{
-name: "icon-camera-roll",
-path: () => import("./icons/icon-camera-roll.js"),
-},{
-name: "icon-candy",
-path: () => import("./icons/icon-candy.js"),
-},{
-name: "icon-caps-lock",
-path: () => import("./icons/icon-caps-lock.js"),
-},{
-name: "icon-car",
-path: () => import("./icons/icon-car.js"),
-},{
-name: "icon-categories",
-path: () => import("./icons/icon-categories.js"),
-},{
-name: "icon-certificate",
-path: () => import("./icons/icon-certificate.js"),
-},{
-name: "icon-chart-curve",
-path: () => import("./icons/icon-chart-curve.js"),
-},{
-name: "icon-chart",
-path: () => import("./icons/icon-chart.js"),
-},{
-name: "icon-chat-active",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-chat-active.js"),
-},{
-name: "icon-chat",
-path: () => import("./icons/icon-chat.js"),
-},{
-name: "icon-check",
-path: () => import("./icons/icon-check.js"),
-},{
-name: "icon-checkbox-dotted",
-path: () => import("./icons/icon-checkbox-dotted.js"),
-},{
-name: "icon-checkbox-empty",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-checkbox-empty.js"),
-},{
-name: "icon-checkbox",
-path: () => import("./icons/icon-checkbox.js"),
-},{
-name: "icon-chip-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-chip-alt.js"),
-},{
-name: "icon-chip",
-path: () => import("./icons/icon-chip.js"),
-},{
-name: "icon-cinema",
-path: () => import("./icons/icon-cinema.js"),
-},{
-name: "icon-circle-dotted-active",
-path: () => import("./icons/icon-circle-dotted-active.js"),
-},{
-name: "icon-circle-dotted",
-path: () => import("./icons/icon-circle-dotted.js"),
-},{
-name: "icon-circuits",
-path: () => import("./icons/icon-circuits.js"),
-},{
-name: "icon-clear-formatting",
-path: () => import("./icons/icon-clear-formatting.js"),
-},{
-name: "icon-client",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-client.js"),
-},{
-name: "icon-clipboard",
-path: () => import("./icons/icon-clipboard.js"),
-},{
-name: "icon-clipboard-copy",
-path: () => import("./icons/icon-clipboard-copy.js"),
-},{
-name: "icon-clipboard-entry",
-path: () => import("./icons/icon-clipboard-entry.js"),
-},{
-name: "icon-clipboard-paste",
-path: () => import("./icons/icon-clipboard-paste.js"),
-},{
-name: "icon-cloud-drive",
-path: () => import("./icons/icon-cloud-drive.js"),
-},{
-name: "icon-cloud-upload",
-path: () => import("./icons/icon-cloud-upload.js"),
-},{
-name: "icon-cloud",
-path: () => import("./icons/icon-cloud.js"),
-},{
-name: "icon-cloudy",
-path: () => import("./icons/icon-cloudy.js"),
-},{
-name: "icon-clubs",
-path: () => import("./icons/icon-clubs.js"),
-},{
-name: "icon-cocktail",
-path: () => import("./icons/icon-cocktail.js"),
-},{
-name: "icon-code",
-path: () => import("./icons/icon-code.js"),
-},{
-name: "icon-code-xml",
-path: () => import("./icons/icon-code-xml.js"),
-},{
-name: "icon-coffee",
-path: () => import("./icons/icon-coffee.js"),
-},{
-name: "icon-coin-dollar",
-path: () => import("./icons/icon-coin-dollar.js"),
-},{
-name: "icon-coin-euro",
-path: () => import("./icons/icon-coin-euro.js"),
-},{
-name: "icon-coin-pound",
-path: () => import("./icons/icon-coin-pound.js"),
-},{
-name: "icon-coin-yen",
-path: () => import("./icons/icon-coin-yen.js"),
-},{
-name: "icon-coins-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-coins-alt.js"),
-},{
-name: "icon-coins",
-path: () => import("./icons/icon-coins.js"),
-},{
-name: "icon-color-bucket",
-path: () => import("./icons/icon-color-bucket.js"),
-},{
-name: "icon-colorpicker",
-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"),
-},{
-name: "icon-combination-lock",
-path: () => import("./icons/icon-combination-lock.js"),
-},{
-name: "icon-command",
-path: () => import("./icons/icon-command.js"),
-},{
-name: "icon-company",
-path: () => import("./icons/icon-company.js"),
-},{
-name: "icon-compress",
-path: () => import("./icons/icon-compress.js"),
-},{
-name: "icon-connection",
-path: () => import("./icons/icon-connection.js"),
-},{
-name: "icon-console",
-path: () => import("./icons/icon-console.js"),
-},{
-name: "icon-contrast",
-path: () => import("./icons/icon-contrast.js"),
-},{
-name: "icon-conversation-alt",
-path: () => import("./icons/icon-conversation-alt.js"),
-},{
-name: "icon-conversation",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-conversation.js"),
-},{
-name: "icon-coverflow",
-path: () => import("./icons/icon-coverflow.js"),
-},{
-name: "icon-credit-card-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-credit-card-alt.js"),
-},{
-name: "icon-credit-card",
-path: () => import("./icons/icon-credit-card.js"),
-},{
-name: "icon-crop",
-path: () => import("./icons/icon-crop.js"),
-},{
-name: "icon-crosshair",
-path: () => import("./icons/icon-crosshair.js"),
-},{
-name: "icon-crown-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-crown-alt.js"),
-},{
-name: "icon-crown",
-path: () => import("./icons/icon-crown.js"),
-},{
-name: "icon-cupcake",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-cupcake.js"),
-},{
-name: "icon-curve",
-path: () => import("./icons/icon-curve.js"),
-},{
-name: "icon-cut",
-path: () => import("./icons/icon-cut.js"),
-},{
-name: "icon-dashboard",
-path: () => import("./icons/icon-dashboard.js"),
-},{
-name: "icon-defrag",
-path: () => import("./icons/icon-defrag.js"),
-},{
-name: "icon-delete-key",
-path: () => import("./icons/icon-delete-key.js"),
-},{
-name: "icon-delete",
-path: () => import("./icons/icon-delete.js"),
-},{
-name: "icon-departure",
-path: () => import("./icons/icon-departure.js"),
-},{
-name: "icon-desktop",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-desktop.js"),
-},{
-name: "icon-diagnostics",
-path: () => import("./icons/icon-diagnostics.js"),
-},{
-name: "icon-diagonal-arrow-alt",
-path: () => import("./icons/icon-diagonal-arrow-alt.js"),
-},{
-name: "icon-diagonal-arrow",
-path: () => import("./icons/icon-diagonal-arrow.js"),
-},{
-name: "icon-diamond",
-path: () => import("./icons/icon-diamond.js"),
-},{
-name: "icon-diamonds",
-path: () => import("./icons/icon-diamonds.js"),
-},{
-name: "icon-dice",
-path: () => import("./icons/icon-dice.js"),
-},{
-name: "icon-diploma-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-diploma-alt.js"),
-},{
-name: "icon-diploma",
-path: () => import("./icons/icon-diploma.js"),
-},{
-name: "icon-directions-alt",
-path: () => import("./icons/icon-directions-alt.js"),
-},{
-name: "icon-directions",
-path: () => import("./icons/icon-directions.js"),
-},{
-name: "icon-disc",
-path: () => import("./icons/icon-disc.js"),
-},{
-name: "icon-disk-image",
-path: () => import("./icons/icon-disk-image.js"),
-},{
-name: "icon-display",
-path: () => import("./icons/icon-display.js"),
-},{
-name: "icon-dna",
-path: () => import("./icons/icon-dna.js"),
-},{
-name: "icon-dock-connector",
-path: () => import("./icons/icon-dock-connector.js"),
-},{
-name: "icon-document-dashed-line",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-document-dashed-line.js"),
-},{
-name: "icon-document",
-path: () => import("./icons/icon-document.js"),
-},{
-name: "icon-documents",
-path: () => import("./icons/icon-documents.js"),
-},{
-name: "icon-donate",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-donate.js"),
-},{
-name: "icon-door-open-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-door-open-alt.js"),
-},{
-name: "icon-door-open",
-path: () => import("./icons/icon-door-open.js"),
-},{
-name: "icon-download-alt",
-path: () => import("./icons/icon-download-alt.js"),
-},{
-name: "icon-download",
-path: () => import("./icons/icon-download.js"),
-},{
-name: "icon-drop",
-path: () => import("./icons/icon-drop.js"),
-},{
-name: "icon-eco",
-path: () => import("./icons/icon-eco.js"),
-},{
-name: "icon-economy",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-economy.js"),
-},{
-name: "icon-edit",
-path: () => import("./icons/icon-edit.js"),
-},{
-name: "icon-embed",
-path: () => import("./icons/icon-embed.js"),
-},{
-name: "icon-employee",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-employee.js"),
-},{
-name: "icon-energy-saving-bulb",
-path: () => import("./icons/icon-energy-saving-bulb.js"),
-},{
-name: "icon-enter",
-path: () => import("./icons/icon-enter.js"),
-},{
-name: "icon-equalizer",
-path: () => import("./icons/icon-equalizer.js"),
-},{
-name: "icon-escape",
-path: () => import("./icons/icon-escape.js"),
-},{
-name: "icon-ethernet",
-path: () => import("./icons/icon-ethernet.js"),
-},{
-name: "icon-eye",
-path: () => import("./icons/icon-eye.js"),
-},{
-name: "icon-exit-fullscreen",
-path: () => import("./icons/icon-exit-fullscreen.js"),
-},{
-name: "icon-facebook-like",
-path: () => import("./icons/icon-facebook-like.js"),
-},{
-name: "icon-factory",
-path: () => import("./icons/icon-factory.js"),
-},{
-name: "icon-favorite",
-path: () => import("./icons/icon-favorite.js"),
-},{
-name: "icon-file-cabinet",
-path: () => import("./icons/icon-file-cabinet.js"),
-},{
-name: "icon-files",
-path: () => import("./icons/icon-files.js"),
-},{
-name: "icon-filter-arrows",
-path: () => import("./icons/icon-filter-arrows.js"),
-},{
-name: "icon-filter",
-path: () => import("./icons/icon-filter.js"),
-},{
-name: "icon-fingerprint",
-path: () => import("./icons/icon-fingerprint.js"),
-},{
-name: "icon-fire",
-path: () => import("./icons/icon-fire.js"),
-},{
-name: "icon-firewire",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-firewire.js"),
-},{
-name: "icon-flag-alt",
-path: () => import("./icons/icon-flag-alt.js"),
-},{
-name: "icon-flag",
-path: () => import("./icons/icon-flag.js"),
-},{
-name: "icon-flash",
-path: () => import("./icons/icon-flash.js"),
-},{
-name: "icon-flashlight",
-path: () => import("./icons/icon-flashlight.js"),
-},{
-name: "icon-flowerpot",
-path: () => import("./icons/icon-flowerpot.js"),
-},{
-name: "icon-folder",
-path: () => import("./icons/icon-folder.js"),
-},{
-name: "icon-folders",
-path: () => import("./icons/icon-folders.js"),
-},{
-name: "icon-font",
-path: () => import("./icons/icon-font.js"),
-},{
-name: "icon-food",
-path: () => import("./icons/icon-food.js"),
-},{
-name: "icon-footprints",
-path: () => import("./icons/icon-footprints.js"),
-},{
-name: "icon-forking",
-path: () => import("./icons/icon-forking.js"),
-},{
-name: "icon-frame-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-frame-alt.js"),
-},{
-name: "icon-frame",
-path: () => import("./icons/icon-frame.js"),
-},{
-name: "icon-fullscreen-alt",
-path: () => import("./icons/icon-fullscreen-alt.js"),
-},{
-name: "icon-fullscreen",
-path: () => import("./icons/icon-fullscreen.js"),
-},{
-name: "icon-game",
-path: () => import("./icons/icon-game.js"),
-},{
-name: "icon-geometry",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-geometry.js"),
-},{
-name: "icon-gift",
-path: () => import("./icons/icon-gift.js"),
-},{
-name: "icon-glasses",
-path: () => import("./icons/icon-glasses.js"),
-},{
-name: "icon-globe-alt",
-path: () => import("./icons/icon-globe-alt.js"),
-},{
-name: "icon-globe-asia",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-globe-asia.js"),
-},{
-name: "icon-globe-europe-africa",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-globe-europe-africa.js"),
-},{
-name: "icon-globe-inverted-america",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-globe-inverted-america.js"),
-},{
-name: "icon-globe-inverted-asia",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-globe-inverted-asia.js"),
-},{
-name: "icon-globe-inverted-europe-africa",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-globe-inverted-europe-africa.js"),
-},{
-name: "icon-globe",
-path: () => import("./icons/icon-globe.js"),
-},{
-name: "icon-gps",
-path: () => import("./icons/icon-gps.js"),
-},{
-name: "icon-graduate",
-path: () => import("./icons/icon-graduate.js"),
-},{
-name: "icon-grid",
-path: () => import("./icons/icon-grid.js"),
-},{
-name: "icon-grip",
-hidden: true,
-path: () => import("./icons/icon-grip.js"),
-},{
-name: "icon-hammer",
-path: () => import("./icons/icon-hammer.js"),
-},{
-name: "icon-hand-active-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-hand-active-alt.js"),
-},{
-name: "icon-hand-active",
-path: () => import("./icons/icon-hand-active.js"),
-},{
-name: "icon-hand-pointer-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-hand-pointer-alt.js"),
-},{
-name: "icon-hand-pointer",
-path: () => import("./icons/icon-hand-pointer.js"),
-},{
-name: "icon-handshake",
-path: () => import("./icons/icon-handshake.js"),
-},{
-name: "icon-handtool-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-handtool-alt.js"),
-},{
-name: "icon-handtool",
-path: () => import("./icons/icon-handtool.js"),
-},{
-name: "icon-hard-drive-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-hard-drive-alt.js"),
-},{
-name: "icon-hard-drive",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-hard-drive.js"),
-},{
-name: "icon-heading-1",
-path: () => import("./icons/icon-heading-1.js"),
-},{
-name: "icon-heading-2",
-path: () => import("./icons/icon-heading-2.js"),
-},{
-name: "icon-heading-3",
-path: () => import("./icons/icon-heading-3.js"),
-},{
-name: "icon-heading-4",
-path: () => import("./icons/icon-heading-4.js"),
-},{
-name: "icon-headphones",
-path: () => import("./icons/icon-headphones.js"),
-},{
-name: "icon-headset",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-headset.js"),
-},{
-name: "icon-hearts",
-path: () => import("./icons/icon-hearts.js"),
-},{
-name: "icon-height",
-path: () => import("./icons/icon-height.js"),
-},{
-name: "icon-help-alt",
-path: () => import("./icons/icon-help-alt.js"),
-},{
-name: "icon-help",
-path: () => import("./icons/icon-help.js"),
-},{
-name: "icon-history",
-path: () => import("./icons/icon-history.js"),
-},{
-name: "icon-home",
-path: () => import("./icons/icon-home.js"),
-},{
-name: "icon-horizontal-rule",
-path: () => import("./icons/icon-horizontal-rule.js"),
-},{
-name: "icon-hourglass",
-path: () => import("./icons/icon-hourglass.js"),
-},{
-name: "icon-imac",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-imac.js"),
-},{
-name: "icon-image-up",
-path: () => import("./icons/icon-image-up.js"),
-},{
-name: "icon-inbox-full",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-inbox-full.js"),
-},{
-name: "icon-inbox",
-path: () => import("./icons/icon-inbox.js"),
-},{
-name: "icon-indent",
-path: () => import("./icons/icon-indent.js"),
-},{
-name: "icon-infinity",
-path: () => import("./icons/icon-infinity.js"),
-},{
-name: "icon-info",
-path: () => import("./icons/icon-info.js"),
-},{
-name: "icon-invoice",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-invoice.js"),
-},{
-name: "icon-ipad",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-ipad.js"),
-},{
-name: "icon-iphone",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-iphone.js"),
-},{
-name: "icon-italic",
-path: () => import("./icons/icon-italic.js"),
-},{
-name: "icon-item-arrangement",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-item-arrangement.js"),
-},{
-name: "icon-junk",
-path: () => import("./icons/icon-junk.js"),
-},{
-name: "icon-key",
-path: () => import("./icons/icon-key.js"),
-},{
-name: "icon-keyboard",
-path: () => import("./icons/icon-keyboard.js"),
-},{
-name: "icon-lab",
-path: () => import("./icons/icon-lab.js"),
-},{
-name: "icon-laptop",
-path: () => import("./icons/icon-laptop.js"),
-},{
-name: "icon-layers-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-layers-alt.js"),
-},{
-name: "icon-layers",
-path: () => import("./icons/icon-layers.js"),
-},{
-name: "icon-layout",
-path: () => import("./icons/icon-layout.js"),
-},{
-name: "icon-left-double-arrow",
-path: () => import("./icons/icon-left-double-arrow.js"),
-},{
-name: "icon-legal",
-path: () => import("./icons/icon-legal.js"),
-},{
-name: "icon-lense",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-lense.js"),
-},{
-name: "icon-library",
-path: () => import("./icons/icon-library.js"),
-},{
-name: "icon-light-down",
-path: () => import("./icons/icon-light-down.js"),
-},{
-name: "icon-light-up",
-path: () => import("./icons/icon-light-up.js"),
-},{
-name: "icon-lightbulb-active",
-path: () => import("./icons/icon-lightbulb-active.js"),
-},{
-name: "icon-lightbulb",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-lightbulb.js"),
-},{
-name: "icon-lightning",
-path: () => import("./icons/icon-lightning.js"),
-},{
-name: "icon-link",
-path: () => import("./icons/icon-link.js"),
-},{
-name: "icon-list",
-path: () => import("./icons/icon-list.js"),
-},{
-name: "icon-load",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-load.js"),
-},{
-name: "icon-loading",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-loading.js"),
-},{
-name: "icon-locate",
-path: () => import("./icons/icon-locate.js"),
-},{
-name: "icon-location-near-me",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-location-near-me.js"),
-},{
-name: "icon-location-nearby",
-path: () => import("./icons/icon-location-nearby.js"),
-},{
-name: "icon-lock",
-path: () => import("./icons/icon-lock.js"),
-},{
-name: "icon-log-out",
-path: () => import("./icons/icon-log-out.js"),
-},{
-name: "icon-logout",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-logout.js"),
-},{
-name: "icon-loupe",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-loupe.js"),
-},{
-name: "icon-magnet",
-path: () => import("./icons/icon-magnet.js"),
-},{
-name: "icon-mailbox",
-path: () => import("./icons/icon-mailbox.js"),
-},{
-name: "icon-map-alt",
-path: () => import("./icons/icon-map-alt.js"),
-},{
-name: "icon-map-location",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-map-location.js"),
-},{
-name: "icon-map-marker",
-path: () => import("./icons/icon-map-marker.js"),
-},{
-name: "icon-map",
-path: () => import("./icons/icon-map.js"),
-},{
-name: "icon-medal",
-path: () => import("./icons/icon-medal.js"),
-},{
-name: "icon-medical-emergency",
-path: () => import("./icons/icon-medical-emergency.js"),
-},{
-name: "icon-medicine",
-path: () => import("./icons/icon-medicine.js"),
-},{
-name: "icon-meeting",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-meeting.js"),
-},{
-name: "icon-megaphone",
-path: () => import("./icons/icon-megaphone.js"),
-},{
-name: "icon-merge",
-path: () => import("./icons/icon-merge.js"),
-},{
-name: "icon-message-open",
-path: () => import("./icons/icon-message-open.js"),
-},{
-name: "icon-message-unopened",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-message-unopened.js"),
-},{
-name: "icon-message",
-path: () => import("./icons/icon-message.js"),
-},{
-name: "icon-microscope",
-path: () => import("./icons/icon-microscope.js"),
-},{
-name: "icon-mindmap",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-mindmap.js"),
-},{
-name: "icon-mobile",
-path: () => import("./icons/icon-mobile.js"),
-},{
-name: "icon-mountain",
-path: () => import("./icons/icon-mountain.js"),
-},{
-name: "icon-mouse-cursor",
-path: () => import("./icons/icon-mouse-cursor.js"),
-},{
-name: "icon-mouse",
-path: () => import("./icons/icon-mouse.js"),
-},{
-name: "icon-movie-alt",
-path: () => import("./icons/icon-movie-alt.js"),
-},{
-name: "icon-movie",
-path: () => import("./icons/icon-movie.js"),
-},{
-name: "icon-multiple-credit-cards",
-path: () => import("./icons/icon-multiple-credit-cards.js"),
-},{
-name: "icon-multiple-windows",
-path: () => import("./icons/icon-multiple-windows.js"),
-},{
-name: "icon-music",
-path: () => import("./icons/icon-music.js"),
-},{
-name: "icon-name-badge",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-name-badge.js"),
-},{
-name: "icon-navigation-bottom",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-navigation-bottom.js"),
-},{
-name: "icon-navigation-down",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-navigation-down.js"),
-},{
-name: "icon-navigation-first",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-navigation-first.js"),
-},{
-name: "icon-navigation-horizontal",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-navigation-horizontal.js"),
-},{
-name: "icon-navigation-last",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-navigation-last.js"),
-},{
-name: "icon-navigation-left",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-navigation-left.js"),
-},{
-name: "icon-navigation-right",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-navigation-right.js"),
-},{
-name: "icon-navigation-road",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-navigation-road.js"),
-},{
-name: "icon-navigation-top",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-navigation-top.js"),
-},{
-name: "icon-navigation-up",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-navigation-up.js"),
-},{
-name: "icon-navigation-vertical",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-navigation-vertical.js"),
-},{
-name: "icon-navigation",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-navigation.js"),
-},{
-name: "icon-navigational-arrow",
-path: () => import("./icons/icon-navigational-arrow.js"),
-},{
-name: "icon-network-alt",
-path: () => import("./icons/icon-network-alt.js"),
-},{
-name: "icon-newspaper-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-newspaper-alt.js"),
-},{
-name: "icon-newspaper",
-path: () => import("./icons/icon-newspaper.js"),
-},{
-name: "icon-next-media",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-next-media.js"),
-},{
-name: "icon-next",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-next.js"),
-},{
-name: "icon-nodes",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-nodes.js"),
-},{
-name: "icon-notepad-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-notepad-alt.js"),
-},{
-name: "icon-notepad",
-path: () => import("./icons/icon-notepad.js"),
-},{
-name: "icon-old-key",
-path: () => import("./icons/icon-old-key.js"),
-},{
-name: "icon-old-phone",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-old-phone.js"),
-},{
-name: "icon-omega",
-path: () => import("./icons/icon-omega.js"),
-},{
-name: "icon-operator",
-path: () => import("./icons/icon-operator.js"),
-},{
-name: "icon-ordered-list",
-path: () => import("./icons/icon-ordered-list.js"),
-},{
-name: "icon-origami",
-path: () => import("./icons/icon-origami.js"),
-},{
-name: "icon-out",
-path: () => import("./icons/icon-out.js"),
-},{
-name: "icon-outbox",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-outbox.js"),
-},{
-name: "icon-outdent",
-path: () => import("./icons/icon-outdent.js"),
-},{
-name: "icon-page-add",
-path: () => import("./icons/icon-page-add.js"),
-},{
-name: "icon-page-down",
-path: () => import("./icons/icon-page-down.js"),
-},{
-name: "icon-page-remove",
-path: () => import("./icons/icon-page-remove.js"),
-},{
-name: "icon-page-restricted",
-path: () => import("./icons/icon-page-restricted.js"),
-},{
-name: "icon-page-up",
-path: () => import("./icons/icon-page-up.js"),
-},{
-name: "icon-paint-roller",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-paint-roller.js"),
-},{
-name: "icon-palette",
-path: () => import("./icons/icon-palette.js"),
-},{
-name: "icon-panel-show",
-path: () => import("./icons/icon-panel-show.js"),
-},{
-name: "icon-pannel-close",
-path: () => import("./icons/icon-pannel-close.js"),
-},{
-name: "icon-paper-bag",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-paper-bag.js"),
-},{
-name: "icon-paper-plane-alt",
-path: () => import("./icons/icon-paper-plane-alt.js"),
-},{
-name: "icon-paper-plane",
-path: () => import("./icons/icon-paper-plane.js"),
-},{
-name: "icon-partly-cloudy",
-path: () => import("./icons/icon-partly-cloudy.js"),
-},{
-name: "icon-paragraph",
-path: () => import("./icons/icon-paragraph.js"),
-},{
-name: "icon-paste-in",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-paste-in.js"),
-},{
-name: "icon-pause",
-path: () => import("./icons/icon-pause.js"),
-},{
-name: "icon-pc",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-pc.js"),
-},{
-name: "icon-people-alt-2",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-people-alt-2.js"),
-},{
-name: "icon-people-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-people-alt.js"),
-},{
-name: "icon-people-female",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-people-female.js"),
-},{
-name: "icon-people",
-path: () => import("./icons/icon-people.js"),
-},{
-name: "icon-phone-ring",
-path: () => import("./icons/icon-phone-ring.js"),
-},{
-name: "icon-phone",
-path: () => import("./icons/icon-phone.js"),
-},{
-name: "icon-photo-album",
-path: () => import("./icons/icon-photo-album.js"),
-},{
-name: "icon-picture",
-path: () => import("./icons/icon-picture.js"),
-},{
-name: "icon-pictures-alt-2",
-path: () => import("./icons/icon-pictures-alt-2.js"),
-},{
-name: "icon-pictures-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-pictures-alt.js"),
-},{
-name: "icon-pictures",
-path: () => import("./icons/icon-pictures.js"),
-},{
-name: "icon-pie-chart",
-path: () => import("./icons/icon-pie-chart.js"),
-},{
-name: "icon-piggy-bank",
-path: () => import("./icons/icon-piggy-bank.js"),
-},{
-name: "icon-pin-location",
-path: () => import("./icons/icon-pin-location.js"),
-},{
-name: "icon-plane",
-path: () => import("./icons/icon-plane.js"),
-},{
-name: "icon-planet",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-planet.js"),
-},{
-name: "icon-play",
-path: () => import("./icons/icon-play.js"),
-},{
-name: "icon-playing-cards",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-playing-cards.js"),
-},{
-name: "icon-playlist",
-path: () => import("./icons/icon-playlist.js"),
-},{
-name: "icon-plugin",
-path: () => import("./icons/icon-plugin.js"),
-},{
-name: "icon-podcast",
-path: () => import("./icons/icon-podcast.js"),
-},{
-name: "icon-poll",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-poll.js"),
-},{
-name: "icon-post-it",
-path: () => import("./icons/icon-post-it.js"),
-},{
-name: "icon-power-outlet",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-power-outlet.js"),
-},{
-name: "icon-power",
-path: () => import("./icons/icon-power.js"),
-},{
-name: "icon-presentation",
-path: () => import("./icons/icon-presentation.js"),
-},{
-name: "icon-previous-media",
-path: () => import("./icons/icon-previous-media.js"),
-},{
-name: "icon-previous",
-path: () => import("./icons/icon-previous.js"),
-},{
-name: "icon-price-dollar",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-price-dollar.js"),
-},{
-name: "icon-price-euro",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-price-euro.js"),
-},{
-name: "icon-price-pound",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-price-pound.js"),
-},{
-name: "icon-price-yen",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-price-yen.js"),
-},{
-name: "icon-print",
-path: () => import("./icons/icon-print.js"),
-},{
-name: "icon-printer-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-printer-alt.js"),
-},{
-name: "icon-projector",
-path: () => import("./icons/icon-projector.js"),
-},{
-name: "icon-pulse",
-path: () => import("./icons/icon-pulse.js"),
-},{
-name: "icon-pushpin",
-path: () => import("./icons/icon-pushpin.js"),
-},{
-name: "icon-qr-code",
-path: () => import("./icons/icon-qr-code.js"),
-},{
-name: "icon-quote",
-path: () => import("./icons/icon-quote.js"),
-},{
-name: "icon-radio-alt",
-path: () => import("./icons/icon-radio-alt.js"),
-},{
-name: "icon-radio-receiver",
-path: () => import("./icons/icon-radio-receiver.js"),
-},{
-name: "icon-radio",
-path: () => import("./icons/icon-radio.js"),
-},{
-name: "icon-rain",
-path: () => import("./icons/icon-rain.js"),
-},{
-name: "icon-rate",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-rate.js"),
-},{
-name: "icon-re-post",
-path: () => import("./icons/icon-re-post.js"),
-},{
-name: "icon-readonly",
-path: () => import("./icons/icon-readonly.js"),
-},{
-name: "icon-receipt-alt",
-path: () => import("./icons/icon-receipt-alt.js"),
-},{
-name: "icon-reception",
-path: () => import("./icons/icon-reception.js"),
-},{
-name: "icon-record",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-record.js"),
-},{
-name: "icon-rectangle-ellipsis",
-path: () => import("./icons/icon-rectangle-ellipsis.js"),
-},{
-name: "icon-redo",
-path: () => import("./icons/icon-redo.js"),
-},{
-name: "icon-refresh",
-path: () => import("./icons/icon-refresh.js"),
-},{
-name: "icon-remote",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-remote.js"),
-},{
-name: "icon-remove",
-path: () => import("./icons/icon-remove.js"),
-},{
-name: "icon-repeat-one",
-path: () => import("./icons/icon-repeat-one.js"),
-},{
-name: "icon-repeat",
-path: () => import("./icons/icon-repeat.js"),
-},{
-name: "icon-reply-arrow",
-path: () => import("./icons/icon-reply-arrow.js"),
-},{
-name: "icon-resize",
-path: () => import("./icons/icon-resize.js"),
-},{
-name: "icon-return-to-top",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-return-to-top.js"),
-},{
-name: "icon-right-double-arrow",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-right-double-arrow.js"),
-},{
-name: "icon-roadsign",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-roadsign.js"),
-},{
-name: "icon-rocket",
-path: () => import("./icons/icon-rocket.js"),
-},{
-name: "icon-rss",
-path: () => import("./icons/icon-rss.js"),
-},{
-name: "icon-ruler-alt",
-path: () => import("./icons/icon-ruler-alt.js"),
-},{
-name: "icon-ruler",
-path: () => import("./icons/icon-ruler.js"),
-},{
-name: "icon-satellite-dish",
-path: () => import("./icons/icon-satellite-dish.js"),
-},{
-name: "icon-save",
-path: () => import("./icons/icon-save.js"),
-},{
-name: "icon-scan",
-path: () => import("./icons/icon-scan.js"),
-},{
-name: "icon-school",
-path: () => import("./icons/icon-school.js"),
-},{
-name: "icon-screensharing",
-path: () => import("./icons/icon-screensharing.js"),
-},{
-name: "icon-script-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-script-alt.js"),
-},{
-name: "icon-script",
-path: () => import("./icons/icon-script.js"),
-},{
-name: "icon-scull",
-path: () => import("./icons/icon-scull.js"),
-},{
-name: "icon-search",
-path: () => import("./icons/icon-search.js"),
-},{
-name: "icon-sensor",
-path: () => import("./icons/icon-sensor.js"),
-},{
-name: "icon-server-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-server-alt.js"),
-},{
-name: "icon-server",
-path: () => import("./icons/icon-server.js"),
-},{
-name: "icon-settings-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-settings-alt.js"),
-},{
-name: "icon-settings",
-path: () => import("./icons/icon-settings.js"),
-},{
-name: "icon-share-alt",
-path: () => import("./icons/icon-share-alt.js"),
-},{
-name: "icon-share",
-path: () => import("./icons/icon-share.js"),
-},{
-name: "icon-sharing-iphone",
-path: () => import("./icons/icon-sharing-iphone.js"),
-},{
-name: "icon-shield",
-path: () => import("./icons/icon-shield.js"),
-},{
-name: "icon-shift",
-path: () => import("./icons/icon-shift.js"),
-},{
-name: "icon-shipping-box",
-path: () => import("./icons/icon-shipping-box.js"),
-},{
-name: "icon-shipping",
-path: () => import("./icons/icon-shipping.js"),
-},{
-name: "icon-shoe",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-shoe.js"),
-},{
-name: "icon-shopping-basket-alt-2",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-shopping-basket-alt-2.js"),
-},{
-name: "icon-shopping-basket-alt",
-path: () => import("./icons/icon-shopping-basket-alt.js"),
-},{
-name: "icon-shopping-basket",
-path: () => import("./icons/icon-shopping-basket.js"),
-},{
-name: "icon-shuffle",
-path: () => import("./icons/icon-shuffle.js"),
-},{
-name: "icon-sience",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-sience.js"),
-},{
-name: "icon-science",
-path: () => import("./icons/icon-science.js"),
-},{
-name: "icon-single-note",
-path: () => import("./icons/icon-single-note.js"),
-},{
-name: "icon-sitemap",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-sitemap.js"),
-},{
-name: "icon-sleep",
-path: () => import("./icons/icon-sleep.js"),
-},{
-name: "icon-slideshow",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-slideshow.js"),
-},{
-name: "icon-smiley-inverted",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-smiley-inverted.js"),
-},{
-name: "icon-smiley",
-path: () => import("./icons/icon-smiley.js"),
-},{
-name: "icon-snow",
-path: () => import("./icons/icon-snow.js"),
-},{
-name: "icon-sound-low",
-path: () => import("./icons/icon-sound-low.js"),
-},{
-name: "icon-sound-medium",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-sound-medium.js"),
-},{
-name: "icon-sound-off",
-path: () => import("./icons/icon-sound-off.js"),
-},{
-name: "icon-sound-waves",
-path: () => import("./icons/icon-sound-waves.js"),
-},{
-name: "icon-sound",
-path: () => import("./icons/icon-sound.js"),
-},{
-name: "icon-spades",
-path: () => import("./icons/icon-spades.js"),
-},{
-name: "icon-speaker",
-path: () => import("./icons/icon-speaker.js"),
-},{
-name: "icon-speed-gauge",
-path: () => import("./icons/icon-speed-gauge.js"),
-},{
-name: "icon-split-alt",
-path: () => import("./icons/icon-split-alt.js"),
-},{
-name: "icon-split",
-path: () => import("./icons/icon-split.js"),
-},{
-name: "icon-sprout",
-path: () => import("./icons/icon-sprout.js"),
-},{
-name: "icon-squiggly-line",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-squiggly-line.js"),
-},{
-name: "icon-ssd",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-ssd.js"),
-},{
-name: "icon-stacked-disks",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-stacked-disks.js"),
-},{
-name: "icon-stamp",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-stamp.js"),
-},{
-name: "icon-stop-alt",
-path: () => import("./icons/icon-stop-alt.js"),
-},{
-name: "icon-stop-hand",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-stop-hand.js"),
-},{
-name: "icon-stop",
-path: () => import("./icons/icon-stop.js"),
-},{
-name: "icon-store",
-path: () => import("./icons/icon-store.js"),
-},{
-name: "icon-stream",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-stream.js"),
-},{
-name: "icon-strikethrough",
-path: () => import("./icons/icon-strikethrough.js"),
-},{
-name: "icon-subscript",
-path: () => import("./icons/icon-subscript.js"),
-},{
-name: "icon-superscript",
-path: () => import("./icons/icon-superscript.js"),
-},{
-name: "icon-sunny",
-path: () => import("./icons/icon-sunny.js"),
-},{
-name: "icon-sweatshirt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-sweatshirt.js"),
-},{
-name: "icon-sync",
-path: () => import("./icons/icon-sync.js"),
-},{
-name: "icon-t-shirt",
-path: () => import("./icons/icon-t-shirt.js"),
-},{
-name: "icon-tab-key",
-path: () => import("./icons/icon-tab-key.js"),
-},{
-name: "icon-table",
-path: () => import("./icons/icon-table.js"),
-},{
-name: "icon-tag",
-path: () => import("./icons/icon-tag.js"),
-},{
-name: "icon-tags",
-path: () => import("./icons/icon-tags.js"),
-},{
-name: "icon-takeaway-cup",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-takeaway-cup.js"),
-},{
-name: "icon-target",
-path: () => import("./icons/icon-target.js"),
-},{
-name: "icon-temperatrure-alt",
-path: () => import("./icons/icon-temperatrure-alt.js"),
-},{
-name: "icon-temperature",
-path: () => import("./icons/icon-temperature.js"),
-},{
-name: "icon-terminal",
-path: () => import("./icons/icon-terminal.js"),
-},{
-name: "icon-text-align-center",
-path: () => import("./icons/icon-text-align-center.js"),
-},{
-name: "icon-text-align-justify",
-path: () => import("./icons/icon-text-align-justify.js"),
-},{
-name: "icon-text-align-left",
-path: () => import("./icons/icon-text-align-left.js"),
-},{
-name: "icon-text-align-right",
-path: () => import("./icons/icon-text-align-right.js"),
-},{
-name: "icon-text-direction-ltr",
-path: () => import("./icons/icon-text-direction-ltr.js"),
-},{
-name: "icon-text-direction-rtl",
-path: () => import("./icons/icon-text-direction-rtl.js"),
-},{
-name: "icon-theater",
-path: () => import("./icons/icon-theater.js"),
-},{
-name: "icon-thumb-down",
-path: () => import("./icons/icon-thumb-down.js"),
-},{
-name: "icon-thumb-up",
-path: () => import("./icons/icon-thumb-up.js"),
-},{
-name: "icon-thumbnail-list",
-path: () => import("./icons/icon-thumbnail-list.js"),
-},{
-name: "icon-thumbnails-small",
-path: () => import("./icons/icon-thumbnails-small.js"),
-},{
-name: "icon-thumbnails",
-path: () => import("./icons/icon-thumbnails.js"),
-},{
-name: "icon-ticket",
-path: () => import("./icons/icon-ticket.js"),
-},{
-name: "icon-time",
-path: () => import("./icons/icon-time.js"),
-},{
-name: "icon-timer",
-path: () => import("./icons/icon-timer.js"),
-},{
-name: "icon-tools",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-tools.js"),
-},{
-name: "icon-top",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-top.js"),
-},{
-name: "icon-traffic-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-traffic-alt.js"),
-},{
-name: "icon-trafic",
-path: () => import("./icons/icon-trafic.js"),
-},{
-name: "icon-train",
-path: () => import("./icons/icon-train.js"),
-},{
-name: "icon-trash-alt-2",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-trash-alt-2.js"),
-},{
-name: "icon-trash-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-trash-alt.js"),
-},{
-name: "icon-trash",
-path: () => import("./icons/icon-trash.js"),
-},{
-name: "icon-tree",
-path: () => import("./icons/icon-tree.js"),
-},{
-name: "icon-trophy",
-path: () => import("./icons/icon-trophy.js"),
-},{
-name: "icon-truck",
-path: () => import("./icons/icon-truck.js"),
-},{
-name: "icon-tv-old",
-path: () => import("./icons/icon-tv-old.js"),
-},{
-name: "icon-tv",
-path: () => import("./icons/icon-tv.js"),
-},{
-name: "icon-umb-content",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-umb-content.js"),
-},{
-name: "icon-umb-developer",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-umb-developer.js"),
-},{
-name: "icon-umb-media",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-umb-media.js"),
-},{
-name: "icon-umb-settings",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-umb-settings.js"),
-},{
-name: "icon-umb-users",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-umb-users.js"),
-},{
-name: "icon-umbrella",
-path: () => import("./icons/icon-umbrella.js"),
-},{
-name: "icon-undo",
-path: () => import("./icons/icon-undo.js"),
-},{
-name: "icon-underline",
-path: () => import("./icons/icon-underline.js"),
-},{
-name: "icon-unlink",
-path: () => import("./icons/icon-unlink.js"),
-},{
-name: "icon-unlocked",
-path: () => import("./icons/icon-unlocked.js"),
-},{
-name: "icon-unplug",
-path: () => import("./icons/icon-unplug.js"),
-},{
-name: "icon-untitled",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-untitled.js"),
-},{
-name: "icon-usb-connector",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-usb-connector.js"),
-},{
-name: "icon-usb",
-path: () => import("./icons/icon-usb.js"),
-},{
-name: "icon-user-female",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-user-female.js"),
-},{
-name: "icon-user-females-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-user-females-alt.js"),
-},{
-name: "icon-user-females",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-user-females.js"),
-},{
-name: "icon-user-glasses",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-user-glasses.js"),
-},{
-name: "icon-user",
-path: () => import("./icons/icon-user.js"),
-},{
-name: "icon-users-alt",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-users-alt.js"),
-},{
-name: "icon-users",
-path: () => import("./icons/icon-users.js"),
-},{
-name: "icon-utilities",
-path: () => import("./icons/icon-utilities.js"),
-},{
-name: "icon-vcard",
-path: () => import("./icons/icon-vcard.js"),
-},{
-name: "icon-video",
-path: () => import("./icons/icon-video.js"),
-},{
-name: "icon-voice",
-path: () => import("./icons/icon-voice.js"),
-},{
-name: "icon-wall-plug",
-path: () => import("./icons/icon-wall-plug.js"),
-},{
-name: "icon-wallet",
-path: () => import("./icons/icon-wallet.js"),
-},{
-name: "icon-wand",
-path: () => import("./icons/icon-wand.js"),
-},{
-name: "icon-webhook",
-path: () => import("./icons/icon-webhook.js"),
-},{
-name: "icon-weight",
-path: () => import("./icons/icon-weight.js"),
-},{
-name: "icon-width",
-path: () => import("./icons/icon-width.js"),
-},{
-name: "icon-wifi",
-path: () => import("./icons/icon-wifi.js"),
-},{
-name: "icon-window-popin",
-path: () => import("./icons/icon-window-popin.js"),
-},{
-name: "icon-window-popout",
-path: () => import("./icons/icon-window-popout.js"),
-},{
-name: "icon-window-sizes",
-path: () => import("./icons/icon-window-sizes.js"),
-},{
-name: "icon-wine-glass",
-path: () => import("./icons/icon-wine-glass.js"),
-},{
-name: "icon-wrench",
-path: () => import("./icons/icon-wrench.js"),
-},{
-name: "icon-wrong",
-path: () => import("./icons/icon-wrong.js"),
-},{
-name: "icon-zip",
-path: () => import("./icons/icon-zip.js"),
-},{
-name: "icon-zom-out",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-zom-out.js"),
-},{
-name: "icon-zoom-in",
-path: () => import("./icons/icon-zoom-in.js"),
-},{
-name: "icon-zoom-out",
-path: () => import("./icons/icon-zoom-out.js"),
-},{
-name: "icon-star",
-path: () => import("./icons/icon-star.js"),
-},{
-name: "icon-database",
-path: () => import("./icons/icon-database.js"),
-},{
-name: "icon-umb-manifest",
-hidden: true,
-path: () => import("./icons/icon-umb-manifest.js"),
-},{
-name: "icon-puzzle-piece",
-path: () => import("./icons/icon-puzzle-piece.js"),
-},{
-name: "icon-document-3d",
-path: () => import("./icons/icon-document-3d.js"),
-},{
-name: "icon-document-medal",
-path: () => import("./icons/icon-document-medal.js"),
-},{
-name: "icon-document-chart-bar",
-path: () => import("./icons/icon-document-chart-bar.js"),
-},{
-name: "icon-document-chart-graph",
-path: () => import("./icons/icon-document-chart-graph.js"),
-},{
-name: "icon-document-html",
-path: () => import("./icons/icon-document-html.js"),
-},{
-name: "icon-document-js",
-path: () => import("./icons/icon-document-js.js"),
-},{
-name: "icon-document-key",
-path: () => import("./icons/icon-document-key.js"),
-},{
-name: "icon-document-search",
-path: () => import("./icons/icon-document-search.js"),
-},{
-name: "icon-document-settings",
-path: () => import("./icons/icon-document-settings.js"),
-},{
-name: "icon-document-spreadsheet",
-path: () => import("./icons/icon-document-spreadsheet.js"),
-},{
-name: "icon-document-command",
-path: () => import("./icons/icon-document-command.js"),
-},{
-name: "icon-document-command",
-path: () => import("./icons/icon-document-command.js"),
-},{
-name: "icon-document-font",
-path: () => import("./icons/icon-document-font.js"),
-},{
-name: "icon-document-user",
-path: () => import("./icons/icon-document-user.js"),
-},{
-name: "icon-document-image",
-path: () => import("./icons/icon-document-image.js"),
-},{
-name: "icon-document-play",
-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"),
-},{
-name: "icon-gitbook",
-path: () => import("./icons/icon-gitbook.js"),
-},{
-name: "icon-github",
-path: () => import("./icons/icon-github.js"),
-},{
-name: "icon-gitlab",
-path: () => import("./icons/icon-gitlab.js"),
-},{
-name: "icon-google",
-path: () => import("./icons/icon-google.js"),
-},{
-name: "icon-mastodon",
-path: () => import("./icons/icon-mastodon.js"),
-},{
-name: "icon-twitter-x",
-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",
-path: () => import("./icons/icon-umbraco.js"),
-},{
-name: "icon-azure",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-azure.js"),
-},{
-name: "icon-microsoft",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-microsoft.js"),
-},{
-name: "icon-os-x",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-os-x.js"),
-},{
-name: "icon-pants",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-pants.js"),
-},{
-name: "icon-parachute-drop",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-parachute-drop.js"),
-},{
-name: "icon-parental-control",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-parental-control.js"),
-},{
-name: "icon-path",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-path.js"),
-},{
-name: "icon-piracy",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-piracy.js"),
-},{
-name: "icon-poker-chip",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-poker-chip.js"),
-},{
-name: "icon-pound-bag",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-pound-bag.js"),
-},{
-name: "icon-receipt-dollar",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-receipt-dollar.js"),
-},{
-name: "icon-receipt-euro",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-receipt-euro.js"),
-},{
-name: "icon-receipt-pound",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-receipt-pound.js"),
-},{
-name: "icon-receipt-yen",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-receipt-yen.js"),
-},{
-name: "icon-road",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-road.js"),
-},{
-name: "icon-safe",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-safe.js"),
-},{
-name: "icon-safedial",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-safedial.js"),
-},{
-name: "icon-sandbox-toys",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-sandbox-toys.js"),
-},{
-name: "icon-security-camera",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-security-camera.js"),
-},{
-name: "icon-settings-alt-2",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-settings-alt-2.js"),
-},{
-name: "icon-share-alt-2",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-share-alt-2.js"),
-},{
-name: "icon-shorts",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-shorts.js"),
-},{
-name: "icon-simcard",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-simcard.js"),
-},{
-name: "icon-tab",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-tab.js"),
-},{
-name: "icon-tactics",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-tactics.js"),
-},{
-name: "icon-theif",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-theif.js"),
-},{
-name: "icon-thought-bubble",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-thought-bubble.js"),
-},{
-name: "icon-twitter",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-twitter.js"),
-},{
-name: "icon-umb-contour",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-umb-contour.js"),
-},{
-name: "icon-umb-deploy",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-umb-deploy.js"),
-},{
-name: "icon-umb-members",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-umb-members.js"),
-},{
-name: "icon-universal",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-universal.js"),
-},{
-name: "icon-war",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-war.js"),
-},{
-name: "icon-windows",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-windows.js"),
-},{
-name: "icon-yen-bag",
-legacy: true,
-hidden: true,
-path: () => import("./icons/icon-yen-bag.js"),
-}];
\ No newline at end of file
+export default [
+ {
+ name: 'icon-activity',
+ path: () => import('./icons/icon-activity.js'),
+ },
+ {
+ name: 'icon-add',
+ path: () => import('./icons/icon-add.js'),
+ },
+ {
+ name: 'icon-addressbook',
+ path: () => import('./icons/icon-addressbook.js'),
+ },
+ {
+ name: 'icon-alarm-clock',
+ path: () => import('./icons/icon-alarm-clock.js'),
+ },
+ {
+ name: 'icon-alert-alt',
+ path: () => import('./icons/icon-alert-alt.js'),
+ },
+ {
+ name: 'icon-alert',
+ path: () => import('./icons/icon-alert.js'),
+ },
+ {
+ name: 'icon-alt',
+ path: () => import('./icons/icon-alt.js'),
+ },
+ {
+ name: 'icon-anchor',
+ path: () => import('./icons/icon-anchor.js'),
+ },
+ {
+ name: 'icon-app',
+ path: () => import('./icons/icon-app.js'),
+ },
+ {
+ name: 'icon-application-error',
+ path: () => import('./icons/icon-application-error.js'),
+ },
+ {
+ name: 'icon-application-window-alt',
+ path: () => import('./icons/icon-application-window-alt.js'),
+ },
+ {
+ name: 'icon-application-window',
+ path: () => import('./icons/icon-application-window.js'),
+ },
+ {
+ name: 'icon-arrivals',
+ path: () => import('./icons/icon-arrivals.js'),
+ },
+ {
+ name: 'icon-arrow-down',
+ path: () => import('./icons/icon-arrow-down.js'),
+ },
+ {
+ name: 'icon-arrow-left',
+ path: () => import('./icons/icon-arrow-left.js'),
+ },
+ {
+ name: 'icon-arrow-right',
+ path: () => import('./icons/icon-arrow-right.js'),
+ },
+ {
+ name: 'icon-arrow-up',
+ path: () => import('./icons/icon-arrow-up.js'),
+ },
+ {
+ name: 'icon-attachment',
+ path: () => import('./icons/icon-attachment.js'),
+ },
+ {
+ name: 'icon-audio-lines',
+ path: () => import('./icons/icon-audio-lines.js'),
+ },
+ {
+ name: 'icon-autofill',
+ path: () => import('./icons/icon-autofill.js'),
+ },
+ {
+ name: 'icon-award',
+ path: () => import('./icons/icon-award.js'),
+ },
+ {
+ name: 'icon-axis-rotation-2',
+ path: () => import('./icons/icon-axis-rotation-2.js'),
+ },
+ {
+ name: 'icon-axis-rotation-3',
+ path: () => import('./icons/icon-axis-rotation-3.js'),
+ },
+ {
+ name: 'icon-axis-rotation',
+ path: () => import('./icons/icon-axis-rotation.js'),
+ },
+ {
+ name: 'icon-backspace',
+ path: () => import('./icons/icon-backspace.js'),
+ },
+ {
+ name: 'icon-badge-add',
+ path: () => import('./icons/icon-badge-add.js'),
+ },
+ {
+ name: 'icon-badge-remove',
+ path: () => import('./icons/icon-badge-remove.js'),
+ },
+ {
+ name: 'icon-badge-restricted',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-badge-restricted.js'),
+ },
+ {
+ name: 'icon-ball',
+ path: () => import('./icons/icon-ball.js'),
+ },
+ {
+ name: 'icon-bar-chart',
+ path: () => import('./icons/icon-bar-chart.js'),
+ },
+ {
+ name: 'icon-barcode',
+ path: () => import('./icons/icon-barcode.js'),
+ },
+ {
+ name: 'icon-bars',
+ path: () => import('./icons/icon-bars.js'),
+ },
+ {
+ name: 'icon-battery-full',
+ path: () => import('./icons/icon-battery-full.js'),
+ },
+ {
+ name: 'icon-battery-low',
+ path: () => import('./icons/icon-battery-low.js'),
+ },
+ {
+ name: 'icon-beer-glass',
+ path: () => import('./icons/icon-beer-glass.js'),
+ },
+ {
+ name: 'icon-bell-off',
+ path: () => import('./icons/icon-bell-off.js'),
+ },
+ {
+ name: 'icon-bell',
+ path: () => import('./icons/icon-bell.js'),
+ },
+ {
+ name: 'icon-binarycode',
+ path: () => import('./icons/icon-binarycode.js'),
+ },
+ {
+ name: 'icon-binoculars',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-binoculars.js'),
+ },
+ {
+ name: 'icon-bird',
+ path: () => import('./icons/icon-bird.js'),
+ },
+ {
+ name: 'icon-birthday-cake',
+ path: () => import('./icons/icon-birthday-cake.js'),
+ },
+ {
+ name: 'icon-block',
+ path: () => import('./icons/icon-block.js'),
+ },
+ {
+ name: 'icon-blockquote',
+ path: () => import('./icons/icon-blockquote.js'),
+ },
+ {
+ name: 'icon-bluetooth',
+ path: () => import('./icons/icon-bluetooth.js'),
+ },
+ {
+ name: 'icon-boat-shipping',
+ path: () => import('./icons/icon-boat-shipping.js'),
+ },
+ {
+ name: 'icon-bold',
+ path: () => import('./icons/icon-bold.js'),
+ },
+ {
+ name: 'icon-bones',
+ path: () => import('./icons/icon-bones.js'),
+ },
+ {
+ name: 'icon-book-alt-2',
+ path: () => import('./icons/icon-book-alt-2.js'),
+ },
+ {
+ name: 'icon-book-alt',
+ path: () => import('./icons/icon-book-alt.js'),
+ },
+ {
+ name: 'icon-book',
+ path: () => import('./icons/icon-book.js'),
+ },
+ {
+ name: 'icon-bookmark',
+ path: () => import('./icons/icon-bookmark.js'),
+ },
+ {
+ name: 'icon-books',
+ path: () => import('./icons/icon-books.js'),
+ },
+ {
+ name: 'icon-box-alt',
+ path: () => import('./icons/icon-box-alt.js'),
+ },
+ {
+ name: 'icon-box-open',
+ path: () => import('./icons/icon-box-open.js'),
+ },
+ {
+ name: 'icon-box',
+ path: () => import('./icons/icon-box.js'),
+ },
+ {
+ name: 'icon-brackets',
+ path: () => import('./icons/icon-brackets.js'),
+ },
+ {
+ name: 'icon-brick',
+ path: () => import('./icons/icon-brick.js'),
+ },
+ {
+ name: 'icon-briefcase',
+ path: () => import('./icons/icon-briefcase.js'),
+ },
+ {
+ name: 'icon-browser-window',
+ path: () => import('./icons/icon-browser-window.js'),
+ },
+ {
+ name: 'icon-brush-alt-2',
+ path: () => import('./icons/icon-brush-alt-2.js'),
+ },
+ {
+ name: 'icon-brush-alt',
+ path: () => import('./icons/icon-brush-alt.js'),
+ },
+ {
+ name: 'icon-brush',
+ path: () => import('./icons/icon-brush.js'),
+ },
+ {
+ name: 'icon-bug',
+ path: () => import('./icons/icon-bug.js'),
+ },
+ {
+ name: 'icon-bulleted-list',
+ path: () => import('./icons/icon-bulleted-list.js'),
+ },
+ {
+ name: 'icon-burn',
+ path: () => import('./icons/icon-burn.js'),
+ },
+ {
+ name: 'icon-bus',
+ path: () => import('./icons/icon-bus.js'),
+ },
+ {
+ name: 'icon-calculator',
+ path: () => import('./icons/icon-calculator.js'),
+ },
+ {
+ name: 'icon-calendar-alt',
+ path: () => import('./icons/icon-calendar-alt.js'),
+ },
+ {
+ name: 'icon-calendar',
+ path: () => import('./icons/icon-calendar.js'),
+ },
+ {
+ name: 'icon-camcorder',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-camcorder.js'),
+ },
+ {
+ name: 'icon-camera-roll',
+ path: () => import('./icons/icon-camera-roll.js'),
+ },
+ {
+ name: 'icon-candy',
+ path: () => import('./icons/icon-candy.js'),
+ },
+ {
+ name: 'icon-caps-lock',
+ path: () => import('./icons/icon-caps-lock.js'),
+ },
+ {
+ name: 'icon-car',
+ path: () => import('./icons/icon-car.js'),
+ },
+ {
+ name: 'icon-categories',
+ path: () => import('./icons/icon-categories.js'),
+ },
+ {
+ name: 'icon-certificate',
+ path: () => import('./icons/icon-certificate.js'),
+ },
+ {
+ name: 'icon-chart-curve',
+ path: () => import('./icons/icon-chart-curve.js'),
+ },
+ {
+ name: 'icon-chart',
+ path: () => import('./icons/icon-chart.js'),
+ },
+ {
+ name: 'icon-chat-active',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-chat-active.js'),
+ },
+ {
+ name: 'icon-chat',
+ path: () => import('./icons/icon-chat.js'),
+ },
+ {
+ name: 'icon-check',
+ path: () => import('./icons/icon-check.js'),
+ },
+ {
+ name: 'icon-checkbox-dotted',
+ path: () => import('./icons/icon-checkbox-dotted.js'),
+ },
+ {
+ name: 'icon-checkbox-empty',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-checkbox-empty.js'),
+ },
+ {
+ name: 'icon-checkbox',
+ path: () => import('./icons/icon-checkbox.js'),
+ },
+ {
+ name: 'icon-chip-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-chip-alt.js'),
+ },
+ {
+ name: 'icon-chip',
+ path: () => import('./icons/icon-chip.js'),
+ },
+ {
+ name: 'icon-cinema',
+ path: () => import('./icons/icon-cinema.js'),
+ },
+ {
+ name: 'icon-circle-dotted-active',
+ path: () => import('./icons/icon-circle-dotted-active.js'),
+ },
+ {
+ name: 'icon-circle-dotted',
+ path: () => import('./icons/icon-circle-dotted.js'),
+ },
+ {
+ name: 'icon-circuits',
+ path: () => import('./icons/icon-circuits.js'),
+ },
+ {
+ name: 'icon-clear-formatting',
+ path: () => import('./icons/icon-clear-formatting.js'),
+ },
+ {
+ name: 'icon-client',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-client.js'),
+ },
+ {
+ name: 'icon-clipboard',
+ path: () => import('./icons/icon-clipboard.js'),
+ },
+ {
+ name: 'icon-clipboard-copy',
+ path: () => import('./icons/icon-clipboard-copy.js'),
+ },
+ {
+ name: 'icon-clipboard-entry',
+ path: () => import('./icons/icon-clipboard-entry.js'),
+ },
+ {
+ name: 'icon-clipboard-paste',
+ path: () => import('./icons/icon-clipboard-paste.js'),
+ },
+ {
+ name: 'icon-cloud-drive',
+ path: () => import('./icons/icon-cloud-drive.js'),
+ },
+ {
+ name: 'icon-cloud-upload',
+ path: () => import('./icons/icon-cloud-upload.js'),
+ },
+ {
+ name: 'icon-cloud',
+ path: () => import('./icons/icon-cloud.js'),
+ },
+ {
+ name: 'icon-cloudy',
+ path: () => import('./icons/icon-cloudy.js'),
+ },
+ {
+ name: 'icon-clubs',
+ path: () => import('./icons/icon-clubs.js'),
+ },
+ {
+ name: 'icon-cocktail',
+ path: () => import('./icons/icon-cocktail.js'),
+ },
+ {
+ name: 'icon-code',
+ path: () => import('./icons/icon-code.js'),
+ },
+ {
+ name: 'icon-code-xml',
+ path: () => import('./icons/icon-code-xml.js'),
+ },
+ {
+ name: 'icon-coffee',
+ path: () => import('./icons/icon-coffee.js'),
+ },
+ {
+ name: 'icon-coin-dollar',
+ path: () => import('./icons/icon-coin-dollar.js'),
+ },
+ {
+ name: 'icon-coin-euro',
+ path: () => import('./icons/icon-coin-euro.js'),
+ },
+ {
+ name: 'icon-coin-pound',
+ path: () => import('./icons/icon-coin-pound.js'),
+ },
+ {
+ name: 'icon-coin-yen',
+ path: () => import('./icons/icon-coin-yen.js'),
+ },
+ {
+ name: 'icon-coins-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-coins-alt.js'),
+ },
+ {
+ name: 'icon-coins',
+ path: () => import('./icons/icon-coins.js'),
+ },
+ {
+ name: 'icon-color-bucket',
+ path: () => import('./icons/icon-color-bucket.js'),
+ },
+ {
+ name: 'icon-colorpicker',
+ 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'),
+ },
+ {
+ name: 'icon-combination-lock',
+ path: () => import('./icons/icon-combination-lock.js'),
+ },
+ {
+ name: 'icon-command',
+ path: () => import('./icons/icon-command.js'),
+ },
+ {
+ name: 'icon-company',
+ path: () => import('./icons/icon-company.js'),
+ },
+ {
+ name: 'icon-compress',
+ path: () => import('./icons/icon-compress.js'),
+ },
+ {
+ name: 'icon-connection',
+ path: () => import('./icons/icon-connection.js'),
+ },
+ {
+ name: 'icon-console',
+ path: () => import('./icons/icon-console.js'),
+ },
+ {
+ name: 'icon-contrast',
+ path: () => import('./icons/icon-contrast.js'),
+ },
+ {
+ name: 'icon-conversation-alt',
+ path: () => import('./icons/icon-conversation-alt.js'),
+ },
+ {
+ name: 'icon-conversation',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-conversation.js'),
+ },
+ {
+ name: 'icon-coverflow',
+ path: () => import('./icons/icon-coverflow.js'),
+ },
+ {
+ name: 'icon-credit-card-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-credit-card-alt.js'),
+ },
+ {
+ name: 'icon-credit-card',
+ path: () => import('./icons/icon-credit-card.js'),
+ },
+ {
+ name: 'icon-crop',
+ path: () => import('./icons/icon-crop.js'),
+ },
+ {
+ name: 'icon-crosshair',
+ path: () => import('./icons/icon-crosshair.js'),
+ },
+ {
+ name: 'icon-crown-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-crown-alt.js'),
+ },
+ {
+ name: 'icon-crown',
+ path: () => import('./icons/icon-crown.js'),
+ },
+ {
+ name: 'icon-cupcake',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-cupcake.js'),
+ },
+ {
+ name: 'icon-curve',
+ path: () => import('./icons/icon-curve.js'),
+ },
+ {
+ name: 'icon-cut',
+ path: () => import('./icons/icon-cut.js'),
+ },
+ {
+ name: 'icon-dashboard',
+ path: () => import('./icons/icon-dashboard.js'),
+ },
+ {
+ name: 'icon-defrag',
+ path: () => import('./icons/icon-defrag.js'),
+ },
+ {
+ name: 'icon-delete-key',
+ path: () => import('./icons/icon-delete-key.js'),
+ },
+ {
+ name: 'icon-delete',
+ path: () => import('./icons/icon-delete.js'),
+ },
+ {
+ name: 'icon-departure',
+ path: () => import('./icons/icon-departure.js'),
+ },
+ {
+ name: 'icon-desktop',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-desktop.js'),
+ },
+ {
+ name: 'icon-diagnostics',
+ path: () => import('./icons/icon-diagnostics.js'),
+ },
+ {
+ name: 'icon-diagonal-arrow-alt',
+ path: () => import('./icons/icon-diagonal-arrow-alt.js'),
+ },
+ {
+ name: 'icon-diagonal-arrow',
+ path: () => import('./icons/icon-diagonal-arrow.js'),
+ },
+ {
+ name: 'icon-diamond',
+ path: () => import('./icons/icon-diamond.js'),
+ },
+ {
+ name: 'icon-diamonds',
+ path: () => import('./icons/icon-diamonds.js'),
+ },
+ {
+ name: 'icon-dice',
+ path: () => import('./icons/icon-dice.js'),
+ },
+ {
+ name: 'icon-diploma-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-diploma-alt.js'),
+ },
+ {
+ name: 'icon-diploma',
+ path: () => import('./icons/icon-diploma.js'),
+ },
+ {
+ name: 'icon-directions-alt',
+ path: () => import('./icons/icon-directions-alt.js'),
+ },
+ {
+ name: 'icon-directions',
+ path: () => import('./icons/icon-directions.js'),
+ },
+ {
+ name: 'icon-disc',
+ path: () => import('./icons/icon-disc.js'),
+ },
+ {
+ name: 'icon-disk-image',
+ path: () => import('./icons/icon-disk-image.js'),
+ },
+ {
+ name: 'icon-display',
+ path: () => import('./icons/icon-display.js'),
+ },
+ {
+ name: 'icon-dna',
+ path: () => import('./icons/icon-dna.js'),
+ },
+ {
+ name: 'icon-dock-connector',
+ path: () => import('./icons/icon-dock-connector.js'),
+ },
+ {
+ name: 'icon-document-dashed-line',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-document-dashed-line.js'),
+ },
+ {
+ name: 'icon-document',
+ path: () => import('./icons/icon-document.js'),
+ },
+ {
+ name: 'icon-documents',
+ path: () => import('./icons/icon-documents.js'),
+ },
+ {
+ name: 'icon-donate',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-donate.js'),
+ },
+ {
+ name: 'icon-door-open-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-door-open-alt.js'),
+ },
+ {
+ name: 'icon-door-open',
+ path: () => import('./icons/icon-door-open.js'),
+ },
+ {
+ name: 'icon-download-alt',
+ path: () => import('./icons/icon-download-alt.js'),
+ },
+ {
+ name: 'icon-download',
+ path: () => import('./icons/icon-download.js'),
+ },
+ {
+ name: 'icon-drop',
+ path: () => import('./icons/icon-drop.js'),
+ },
+ {
+ name: 'icon-eco',
+ path: () => import('./icons/icon-eco.js'),
+ },
+ {
+ name: 'icon-economy',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-economy.js'),
+ },
+ {
+ name: 'icon-edit',
+ path: () => import('./icons/icon-edit.js'),
+ },
+ {
+ name: 'icon-embed',
+ path: () => import('./icons/icon-embed.js'),
+ },
+ {
+ name: 'icon-employee',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-employee.js'),
+ },
+ {
+ name: 'icon-energy-saving-bulb',
+ path: () => import('./icons/icon-energy-saving-bulb.js'),
+ },
+ {
+ name: 'icon-enter',
+ path: () => import('./icons/icon-enter.js'),
+ },
+ {
+ name: 'icon-equalizer',
+ path: () => import('./icons/icon-equalizer.js'),
+ },
+ {
+ name: 'icon-escape',
+ path: () => import('./icons/icon-escape.js'),
+ },
+ {
+ name: 'icon-ethernet',
+ path: () => import('./icons/icon-ethernet.js'),
+ },
+ {
+ name: 'icon-eye',
+ path: () => import('./icons/icon-eye.js'),
+ },
+ {
+ name: 'icon-exit-fullscreen',
+ path: () => import('./icons/icon-exit-fullscreen.js'),
+ },
+ {
+ name: 'icon-facebook-like',
+ path: () => import('./icons/icon-facebook-like.js'),
+ },
+ {
+ name: 'icon-factory',
+ path: () => import('./icons/icon-factory.js'),
+ },
+ {
+ name: 'icon-favorite',
+ path: () => import('./icons/icon-favorite.js'),
+ },
+ {
+ name: 'icon-file-cabinet',
+ path: () => import('./icons/icon-file-cabinet.js'),
+ },
+ {
+ name: 'icon-files',
+ path: () => import('./icons/icon-files.js'),
+ },
+ {
+ name: 'icon-filter-arrows',
+ path: () => import('./icons/icon-filter-arrows.js'),
+ },
+ {
+ name: 'icon-filter',
+ path: () => import('./icons/icon-filter.js'),
+ },
+ {
+ name: 'icon-fingerprint',
+ path: () => import('./icons/icon-fingerprint.js'),
+ },
+ {
+ name: 'icon-fire',
+ path: () => import('./icons/icon-fire.js'),
+ },
+ {
+ name: 'icon-firewire',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-firewire.js'),
+ },
+ {
+ name: 'icon-flag-alt',
+ path: () => import('./icons/icon-flag-alt.js'),
+ },
+ {
+ name: 'icon-flag',
+ path: () => import('./icons/icon-flag.js'),
+ },
+ {
+ name: 'icon-flash',
+ path: () => import('./icons/icon-flash.js'),
+ },
+ {
+ name: 'icon-flashlight',
+ path: () => import('./icons/icon-flashlight.js'),
+ },
+ {
+ name: 'icon-flowerpot',
+ path: () => import('./icons/icon-flowerpot.js'),
+ },
+ {
+ name: 'icon-folder',
+ path: () => import('./icons/icon-folder.js'),
+ },
+ {
+ name: 'icon-folders',
+ path: () => import('./icons/icon-folders.js'),
+ },
+ {
+ name: 'icon-font',
+ path: () => import('./icons/icon-font.js'),
+ },
+ {
+ name: 'icon-food',
+ path: () => import('./icons/icon-food.js'),
+ },
+ {
+ name: 'icon-footprints',
+ path: () => import('./icons/icon-footprints.js'),
+ },
+ {
+ name: 'icon-forking',
+ path: () => import('./icons/icon-forking.js'),
+ },
+ {
+ name: 'icon-frame-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-frame-alt.js'),
+ },
+ {
+ name: 'icon-frame',
+ path: () => import('./icons/icon-frame.js'),
+ },
+ {
+ name: 'icon-fullscreen-alt',
+ path: () => import('./icons/icon-fullscreen-alt.js'),
+ },
+ {
+ name: 'icon-fullscreen',
+ path: () => import('./icons/icon-fullscreen.js'),
+ },
+ {
+ name: 'icon-game',
+ path: () => import('./icons/icon-game.js'),
+ },
+ {
+ name: 'icon-geometry',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-geometry.js'),
+ },
+ {
+ name: 'icon-gift',
+ path: () => import('./icons/icon-gift.js'),
+ },
+ {
+ name: 'icon-glasses',
+ path: () => import('./icons/icon-glasses.js'),
+ },
+ {
+ name: 'icon-globe-alt',
+ path: () => import('./icons/icon-globe-alt.js'),
+ },
+ {
+ name: 'icon-globe-asia',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-globe-asia.js'),
+ },
+ {
+ name: 'icon-globe-europe-africa',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-globe-europe-africa.js'),
+ },
+ {
+ name: 'icon-globe-inverted-america',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-globe-inverted-america.js'),
+ },
+ {
+ name: 'icon-globe-inverted-asia',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-globe-inverted-asia.js'),
+ },
+ {
+ name: 'icon-globe-inverted-europe-africa',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-globe-inverted-europe-africa.js'),
+ },
+ {
+ name: 'icon-globe',
+ path: () => import('./icons/icon-globe.js'),
+ },
+ {
+ name: 'icon-gps',
+ path: () => import('./icons/icon-gps.js'),
+ },
+ {
+ name: 'icon-graduate',
+ path: () => import('./icons/icon-graduate.js'),
+ },
+ {
+ name: 'icon-grid',
+ path: () => import('./icons/icon-grid.js'),
+ },
+ {
+ name: 'icon-grip',
+ hidden: true,
+ path: () => import('./icons/icon-grip.js'),
+ },
+ {
+ name: 'icon-hammer',
+ path: () => import('./icons/icon-hammer.js'),
+ },
+ {
+ name: 'icon-hand-active-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-hand-active-alt.js'),
+ },
+ {
+ name: 'icon-hand-active',
+ path: () => import('./icons/icon-hand-active.js'),
+ },
+ {
+ name: 'icon-hand-pointer-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-hand-pointer-alt.js'),
+ },
+ {
+ name: 'icon-hand-pointer',
+ path: () => import('./icons/icon-hand-pointer.js'),
+ },
+ {
+ name: 'icon-handshake',
+ path: () => import('./icons/icon-handshake.js'),
+ },
+ {
+ name: 'icon-handtool-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-handtool-alt.js'),
+ },
+ {
+ name: 'icon-handtool',
+ path: () => import('./icons/icon-handtool.js'),
+ },
+ {
+ name: 'icon-hard-drive-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-hard-drive-alt.js'),
+ },
+ {
+ name: 'icon-hard-drive',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-hard-drive.js'),
+ },
+ {
+ name: 'icon-heading-1',
+ path: () => import('./icons/icon-heading-1.js'),
+ },
+ {
+ name: 'icon-heading-2',
+ path: () => import('./icons/icon-heading-2.js'),
+ },
+ {
+ name: 'icon-heading-3',
+ path: () => import('./icons/icon-heading-3.js'),
+ },
+ {
+ name: 'icon-heading-4',
+ path: () => import('./icons/icon-heading-4.js'),
+ },
+ {
+ name: 'icon-headphones',
+ path: () => import('./icons/icon-headphones.js'),
+ },
+ {
+ name: 'icon-headset',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-headset.js'),
+ },
+ {
+ name: 'icon-hearts',
+ path: () => import('./icons/icon-hearts.js'),
+ },
+ {
+ name: 'icon-height',
+ path: () => import('./icons/icon-height.js'),
+ },
+ {
+ name: 'icon-help-alt',
+ path: () => import('./icons/icon-help-alt.js'),
+ },
+ {
+ name: 'icon-help',
+ path: () => import('./icons/icon-help.js'),
+ },
+ {
+ name: 'icon-history',
+ path: () => import('./icons/icon-history.js'),
+ },
+ {
+ name: 'icon-home',
+ path: () => import('./icons/icon-home.js'),
+ },
+ {
+ name: 'icon-horizontal-rule',
+ path: () => import('./icons/icon-horizontal-rule.js'),
+ },
+ {
+ name: 'icon-hourglass',
+ path: () => import('./icons/icon-hourglass.js'),
+ },
+ {
+ name: 'icon-imac',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-imac.js'),
+ },
+ {
+ name: 'icon-image-up',
+ path: () => import('./icons/icon-image-up.js'),
+ },
+ {
+ name: 'icon-inbox-full',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-inbox-full.js'),
+ },
+ {
+ name: 'icon-inbox',
+ path: () => import('./icons/icon-inbox.js'),
+ },
+ {
+ name: 'icon-indent',
+ path: () => import('./icons/icon-indent.js'),
+ },
+ {
+ name: 'icon-infinity',
+ path: () => import('./icons/icon-infinity.js'),
+ },
+ {
+ name: 'icon-info',
+ path: () => import('./icons/icon-info.js'),
+ },
+ {
+ name: 'icon-invoice',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-invoice.js'),
+ },
+ {
+ name: 'icon-ipad',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-ipad.js'),
+ },
+ {
+ name: 'icon-iphone',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-iphone.js'),
+ },
+ {
+ name: 'icon-italic',
+ path: () => import('./icons/icon-italic.js'),
+ },
+ {
+ name: 'icon-item-arrangement',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-item-arrangement.js'),
+ },
+ {
+ name: 'icon-junk',
+ path: () => import('./icons/icon-junk.js'),
+ },
+ {
+ name: 'icon-key',
+ path: () => import('./icons/icon-key.js'),
+ },
+ {
+ name: 'icon-keyboard',
+ path: () => import('./icons/icon-keyboard.js'),
+ },
+ {
+ name: 'icon-lab',
+ path: () => import('./icons/icon-lab.js'),
+ },
+ {
+ name: 'icon-laptop',
+ path: () => import('./icons/icon-laptop.js'),
+ },
+ {
+ name: 'icon-layers-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-layers-alt.js'),
+ },
+ {
+ name: 'icon-layers',
+ path: () => import('./icons/icon-layers.js'),
+ },
+ {
+ name: 'icon-layout',
+ path: () => import('./icons/icon-layout.js'),
+ },
+ {
+ name: 'icon-left-double-arrow',
+ path: () => import('./icons/icon-left-double-arrow.js'),
+ },
+ {
+ name: 'icon-legal',
+ path: () => import('./icons/icon-legal.js'),
+ },
+ {
+ name: 'icon-lense',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-lense.js'),
+ },
+ {
+ name: 'icon-library',
+ path: () => import('./icons/icon-library.js'),
+ },
+ {
+ name: 'icon-light-down',
+ path: () => import('./icons/icon-light-down.js'),
+ },
+ {
+ name: 'icon-light-up',
+ path: () => import('./icons/icon-light-up.js'),
+ },
+ {
+ name: 'icon-lightbulb-active',
+ path: () => import('./icons/icon-lightbulb-active.js'),
+ },
+ {
+ name: 'icon-lightbulb',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-lightbulb.js'),
+ },
+ {
+ name: 'icon-lightning',
+ path: () => import('./icons/icon-lightning.js'),
+ },
+ {
+ name: 'icon-link',
+ path: () => import('./icons/icon-link.js'),
+ },
+ {
+ name: 'icon-list',
+ path: () => import('./icons/icon-list.js'),
+ },
+ {
+ name: 'icon-load',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-load.js'),
+ },
+ {
+ name: 'icon-loading',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-loading.js'),
+ },
+ {
+ name: 'icon-locate',
+ path: () => import('./icons/icon-locate.js'),
+ },
+ {
+ name: 'icon-location-near-me',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-location-near-me.js'),
+ },
+ {
+ name: 'icon-location-nearby',
+ path: () => import('./icons/icon-location-nearby.js'),
+ },
+ {
+ name: 'icon-lock',
+ path: () => import('./icons/icon-lock.js'),
+ },
+ {
+ name: 'icon-log-out',
+ path: () => import('./icons/icon-log-out.js'),
+ },
+ {
+ name: 'icon-logout',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-logout.js'),
+ },
+ {
+ name: 'icon-loupe',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-loupe.js'),
+ },
+ {
+ name: 'icon-magnet',
+ path: () => import('./icons/icon-magnet.js'),
+ },
+ {
+ name: 'icon-mailbox',
+ path: () => import('./icons/icon-mailbox.js'),
+ },
+ {
+ name: 'icon-map-alt',
+ path: () => import('./icons/icon-map-alt.js'),
+ },
+ {
+ name: 'icon-map-location',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-map-location.js'),
+ },
+ {
+ name: 'icon-map-marker',
+ path: () => import('./icons/icon-map-marker.js'),
+ },
+ {
+ name: 'icon-map',
+ path: () => import('./icons/icon-map.js'),
+ },
+ {
+ name: 'icon-medal',
+ path: () => import('./icons/icon-medal.js'),
+ },
+ {
+ name: 'icon-medical-emergency',
+ path: () => import('./icons/icon-medical-emergency.js'),
+ },
+ {
+ name: 'icon-medicine',
+ path: () => import('./icons/icon-medicine.js'),
+ },
+ {
+ name: 'icon-meeting',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-meeting.js'),
+ },
+ {
+ name: 'icon-megaphone',
+ path: () => import('./icons/icon-megaphone.js'),
+ },
+ {
+ name: 'icon-merge',
+ path: () => import('./icons/icon-merge.js'),
+ },
+ {
+ name: 'icon-message-open',
+ path: () => import('./icons/icon-message-open.js'),
+ },
+ {
+ name: 'icon-message-unopened',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-message-unopened.js'),
+ },
+ {
+ name: 'icon-message',
+ path: () => import('./icons/icon-message.js'),
+ },
+ {
+ name: 'icon-microscope',
+ path: () => import('./icons/icon-microscope.js'),
+ },
+ {
+ name: 'icon-mindmap',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-mindmap.js'),
+ },
+ {
+ name: 'icon-mobile',
+ path: () => import('./icons/icon-mobile.js'),
+ },
+ {
+ name: 'icon-mountain',
+ path: () => import('./icons/icon-mountain.js'),
+ },
+ {
+ name: 'icon-mouse-cursor',
+ path: () => import('./icons/icon-mouse-cursor.js'),
+ },
+ {
+ name: 'icon-mouse',
+ path: () => import('./icons/icon-mouse.js'),
+ },
+ {
+ name: 'icon-movie-alt',
+ path: () => import('./icons/icon-movie-alt.js'),
+ },
+ {
+ name: 'icon-movie',
+ path: () => import('./icons/icon-movie.js'),
+ },
+ {
+ name: 'icon-multiple-credit-cards',
+ path: () => import('./icons/icon-multiple-credit-cards.js'),
+ },
+ {
+ name: 'icon-multiple-windows',
+ path: () => import('./icons/icon-multiple-windows.js'),
+ },
+ {
+ name: 'icon-music',
+ path: () => import('./icons/icon-music.js'),
+ },
+ {
+ name: 'icon-name-badge',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-name-badge.js'),
+ },
+ {
+ name: 'icon-navigation-bottom',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-navigation-bottom.js'),
+ },
+ {
+ name: 'icon-navigation-down',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-navigation-down.js'),
+ },
+ {
+ name: 'icon-navigation-first',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-navigation-first.js'),
+ },
+ {
+ name: 'icon-navigation-horizontal',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-navigation-horizontal.js'),
+ },
+ {
+ name: 'icon-navigation-last',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-navigation-last.js'),
+ },
+ {
+ name: 'icon-navigation-left',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-navigation-left.js'),
+ },
+ {
+ name: 'icon-navigation-right',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-navigation-right.js'),
+ },
+ {
+ name: 'icon-navigation-road',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-navigation-road.js'),
+ },
+ {
+ name: 'icon-navigation-top',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-navigation-top.js'),
+ },
+ {
+ name: 'icon-navigation-up',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-navigation-up.js'),
+ },
+ {
+ name: 'icon-navigation-vertical',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-navigation-vertical.js'),
+ },
+ {
+ name: 'icon-navigation',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-navigation.js'),
+ },
+ {
+ name: 'icon-navigational-arrow',
+ path: () => import('./icons/icon-navigational-arrow.js'),
+ },
+ {
+ name: 'icon-network-alt',
+ path: () => import('./icons/icon-network-alt.js'),
+ },
+ {
+ name: 'icon-newspaper-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-newspaper-alt.js'),
+ },
+ {
+ name: 'icon-newspaper',
+ path: () => import('./icons/icon-newspaper.js'),
+ },
+ {
+ name: 'icon-next-media',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-next-media.js'),
+ },
+ {
+ name: 'icon-next',
+ path: () => import('./icons/icon-next.js'),
+ },
+ {
+ name: 'icon-nodes',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-nodes.js'),
+ },
+ {
+ name: 'icon-notepad-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-notepad-alt.js'),
+ },
+ {
+ name: 'icon-notepad',
+ path: () => import('./icons/icon-notepad.js'),
+ },
+ {
+ name: 'icon-old-key',
+ path: () => import('./icons/icon-old-key.js'),
+ },
+ {
+ name: 'icon-old-phone',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-old-phone.js'),
+ },
+ {
+ name: 'icon-omega',
+ path: () => import('./icons/icon-omega.js'),
+ },
+ {
+ name: 'icon-operator',
+ path: () => import('./icons/icon-operator.js'),
+ },
+ {
+ name: 'icon-ordered-list',
+ path: () => import('./icons/icon-ordered-list.js'),
+ },
+ {
+ name: 'icon-origami',
+ path: () => import('./icons/icon-origami.js'),
+ },
+ {
+ name: 'icon-out',
+ path: () => import('./icons/icon-out.js'),
+ },
+ {
+ name: 'icon-outbox',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-outbox.js'),
+ },
+ {
+ name: 'icon-outdent',
+ path: () => import('./icons/icon-outdent.js'),
+ },
+ {
+ name: 'icon-page-add',
+ path: () => import('./icons/icon-page-add.js'),
+ },
+ {
+ name: 'icon-page-down',
+ path: () => import('./icons/icon-page-down.js'),
+ },
+ {
+ name: 'icon-page-remove',
+ path: () => import('./icons/icon-page-remove.js'),
+ },
+ {
+ name: 'icon-page-restricted',
+ path: () => import('./icons/icon-page-restricted.js'),
+ },
+ {
+ name: 'icon-page-up',
+ path: () => import('./icons/icon-page-up.js'),
+ },
+ {
+ name: 'icon-paint-roller',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-paint-roller.js'),
+ },
+ {
+ name: 'icon-palette',
+ path: () => import('./icons/icon-palette.js'),
+ },
+ {
+ name: 'icon-panel-show',
+ path: () => import('./icons/icon-panel-show.js'),
+ },
+ {
+ name: 'icon-pannel-close',
+ path: () => import('./icons/icon-pannel-close.js'),
+ },
+ {
+ name: 'icon-paper-bag',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-paper-bag.js'),
+ },
+ {
+ name: 'icon-paper-plane-alt',
+ path: () => import('./icons/icon-paper-plane-alt.js'),
+ },
+ {
+ name: 'icon-paper-plane',
+ path: () => import('./icons/icon-paper-plane.js'),
+ },
+ {
+ name: 'icon-partly-cloudy',
+ path: () => import('./icons/icon-partly-cloudy.js'),
+ },
+ {
+ name: 'icon-paragraph',
+ path: () => import('./icons/icon-paragraph.js'),
+ },
+ {
+ name: 'icon-paste-in',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-paste-in.js'),
+ },
+ {
+ name: 'icon-pause',
+ path: () => import('./icons/icon-pause.js'),
+ },
+ {
+ name: 'icon-pc',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-pc.js'),
+ },
+ {
+ name: 'icon-people-alt-2',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-people-alt-2.js'),
+ },
+ {
+ name: 'icon-people-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-people-alt.js'),
+ },
+ {
+ name: 'icon-people-female',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-people-female.js'),
+ },
+ {
+ name: 'icon-people',
+ path: () => import('./icons/icon-people.js'),
+ },
+ {
+ name: 'icon-phone-ring',
+ path: () => import('./icons/icon-phone-ring.js'),
+ },
+ {
+ name: 'icon-phone',
+ path: () => import('./icons/icon-phone.js'),
+ },
+ {
+ name: 'icon-photo-album',
+ path: () => import('./icons/icon-photo-album.js'),
+ },
+ {
+ name: 'icon-picture',
+ path: () => import('./icons/icon-picture.js'),
+ },
+ {
+ name: 'icon-pictures-alt-2',
+ path: () => import('./icons/icon-pictures-alt-2.js'),
+ },
+ {
+ name: 'icon-pictures-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-pictures-alt.js'),
+ },
+ {
+ name: 'icon-pictures',
+ path: () => import('./icons/icon-pictures.js'),
+ },
+ {
+ name: 'icon-pie-chart',
+ path: () => import('./icons/icon-pie-chart.js'),
+ },
+ {
+ name: 'icon-piggy-bank',
+ path: () => import('./icons/icon-piggy-bank.js'),
+ },
+ {
+ name: 'icon-pin-location',
+ path: () => import('./icons/icon-pin-location.js'),
+ },
+ {
+ name: 'icon-plane',
+ path: () => import('./icons/icon-plane.js'),
+ },
+ {
+ name: 'icon-planet',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-planet.js'),
+ },
+ {
+ name: 'icon-play',
+ path: () => import('./icons/icon-play.js'),
+ },
+ {
+ name: 'icon-playing-cards',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-playing-cards.js'),
+ },
+ {
+ name: 'icon-playlist',
+ path: () => import('./icons/icon-playlist.js'),
+ },
+ {
+ name: 'icon-plugin',
+ path: () => import('./icons/icon-plugin.js'),
+ },
+ {
+ name: 'icon-podcast',
+ path: () => import('./icons/icon-podcast.js'),
+ },
+ {
+ name: 'icon-poll',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-poll.js'),
+ },
+ {
+ name: 'icon-post-it',
+ path: () => import('./icons/icon-post-it.js'),
+ },
+ {
+ name: 'icon-power-outlet',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-power-outlet.js'),
+ },
+ {
+ name: 'icon-power',
+ path: () => import('./icons/icon-power.js'),
+ },
+ {
+ name: 'icon-presentation',
+ path: () => import('./icons/icon-presentation.js'),
+ },
+ {
+ name: 'icon-previous-media',
+ path: () => import('./icons/icon-previous-media.js'),
+ },
+ {
+ name: 'icon-previous',
+ path: () => import('./icons/icon-previous.js'),
+ },
+ {
+ name: 'icon-price-dollar',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-price-dollar.js'),
+ },
+ {
+ name: 'icon-price-euro',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-price-euro.js'),
+ },
+ {
+ name: 'icon-price-pound',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-price-pound.js'),
+ },
+ {
+ name: 'icon-price-yen',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-price-yen.js'),
+ },
+ {
+ name: 'icon-print',
+ path: () => import('./icons/icon-print.js'),
+ },
+ {
+ name: 'icon-printer-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-printer-alt.js'),
+ },
+ {
+ name: 'icon-projector',
+ path: () => import('./icons/icon-projector.js'),
+ },
+ {
+ name: 'icon-pulse',
+ path: () => import('./icons/icon-pulse.js'),
+ },
+ {
+ name: 'icon-pushpin',
+ path: () => import('./icons/icon-pushpin.js'),
+ },
+ {
+ name: 'icon-qr-code',
+ path: () => import('./icons/icon-qr-code.js'),
+ },
+ {
+ name: 'icon-quote',
+ path: () => import('./icons/icon-quote.js'),
+ },
+ {
+ name: 'icon-radio-alt',
+ path: () => import('./icons/icon-radio-alt.js'),
+ },
+ {
+ name: 'icon-radio-receiver',
+ path: () => import('./icons/icon-radio-receiver.js'),
+ },
+ {
+ name: 'icon-radio',
+ path: () => import('./icons/icon-radio.js'),
+ },
+ {
+ name: 'icon-rain',
+ path: () => import('./icons/icon-rain.js'),
+ },
+ {
+ name: 'icon-rate',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-rate.js'),
+ },
+ {
+ name: 'icon-re-post',
+ path: () => import('./icons/icon-re-post.js'),
+ },
+ {
+ name: 'icon-readonly',
+ path: () => import('./icons/icon-readonly.js'),
+ },
+ {
+ name: 'icon-receipt-alt',
+ path: () => import('./icons/icon-receipt-alt.js'),
+ },
+ {
+ name: 'icon-reception',
+ path: () => import('./icons/icon-reception.js'),
+ },
+ {
+ name: 'icon-record',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-record.js'),
+ },
+ {
+ name: 'icon-rectangle-ellipsis',
+ path: () => import('./icons/icon-rectangle-ellipsis.js'),
+ },
+ {
+ name: 'icon-redo',
+ path: () => import('./icons/icon-redo.js'),
+ },
+ {
+ name: 'icon-refresh',
+ path: () => import('./icons/icon-refresh.js'),
+ },
+ {
+ name: 'icon-remote',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-remote.js'),
+ },
+ {
+ name: 'icon-remove',
+ path: () => import('./icons/icon-remove.js'),
+ },
+ {
+ name: 'icon-repeat-one',
+ path: () => import('./icons/icon-repeat-one.js'),
+ },
+ {
+ name: 'icon-repeat',
+ path: () => import('./icons/icon-repeat.js'),
+ },
+ {
+ name: 'icon-reply-arrow',
+ path: () => import('./icons/icon-reply-arrow.js'),
+ },
+ {
+ name: 'icon-resize',
+ path: () => import('./icons/icon-resize.js'),
+ },
+ {
+ name: 'icon-return-to-top',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-return-to-top.js'),
+ },
+ {
+ name: 'icon-right-double-arrow',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-right-double-arrow.js'),
+ },
+ {
+ name: 'icon-roadsign',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-roadsign.js'),
+ },
+ {
+ name: 'icon-rocket',
+ path: () => import('./icons/icon-rocket.js'),
+ },
+ {
+ name: 'icon-rss',
+ path: () => import('./icons/icon-rss.js'),
+ },
+ {
+ name: 'icon-ruler-alt',
+ path: () => import('./icons/icon-ruler-alt.js'),
+ },
+ {
+ name: 'icon-ruler',
+ path: () => import('./icons/icon-ruler.js'),
+ },
+ {
+ name: 'icon-satellite-dish',
+ path: () => import('./icons/icon-satellite-dish.js'),
+ },
+ {
+ name: 'icon-save',
+ path: () => import('./icons/icon-save.js'),
+ },
+ {
+ name: 'icon-scan',
+ path: () => import('./icons/icon-scan.js'),
+ },
+ {
+ name: 'icon-school',
+ path: () => import('./icons/icon-school.js'),
+ },
+ {
+ name: 'icon-screensharing',
+ path: () => import('./icons/icon-screensharing.js'),
+ },
+ {
+ name: 'icon-script-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-script-alt.js'),
+ },
+ {
+ name: 'icon-script',
+ path: () => import('./icons/icon-script.js'),
+ },
+ {
+ name: 'icon-scull',
+ path: () => import('./icons/icon-scull.js'),
+ },
+ {
+ name: 'icon-search',
+ path: () => import('./icons/icon-search.js'),
+ },
+ {
+ name: 'icon-sensor',
+ path: () => import('./icons/icon-sensor.js'),
+ },
+ {
+ name: 'icon-server-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-server-alt.js'),
+ },
+ {
+ name: 'icon-server',
+ path: () => import('./icons/icon-server.js'),
+ },
+ {
+ name: 'icon-settings-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-settings-alt.js'),
+ },
+ {
+ name: 'icon-settings',
+ path: () => import('./icons/icon-settings.js'),
+ },
+ {
+ name: 'icon-share-alt',
+ path: () => import('./icons/icon-share-alt.js'),
+ },
+ {
+ name: 'icon-share',
+ path: () => import('./icons/icon-share.js'),
+ },
+ {
+ name: 'icon-sharing-iphone',
+ path: () => import('./icons/icon-sharing-iphone.js'),
+ },
+ {
+ name: 'icon-shield',
+ path: () => import('./icons/icon-shield.js'),
+ },
+ {
+ name: 'icon-shift',
+ path: () => import('./icons/icon-shift.js'),
+ },
+ {
+ name: 'icon-shipping-box',
+ path: () => import('./icons/icon-shipping-box.js'),
+ },
+ {
+ name: 'icon-shipping',
+ path: () => import('./icons/icon-shipping.js'),
+ },
+ {
+ name: 'icon-shoe',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-shoe.js'),
+ },
+ {
+ name: 'icon-shopping-basket-alt-2',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-shopping-basket-alt-2.js'),
+ },
+ {
+ name: 'icon-shopping-basket-alt',
+ path: () => import('./icons/icon-shopping-basket-alt.js'),
+ },
+ {
+ name: 'icon-shopping-basket',
+ path: () => import('./icons/icon-shopping-basket.js'),
+ },
+ {
+ name: 'icon-shuffle',
+ path: () => import('./icons/icon-shuffle.js'),
+ },
+ {
+ name: 'icon-sience',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-sience.js'),
+ },
+ {
+ name: 'icon-science',
+ path: () => import('./icons/icon-science.js'),
+ },
+ {
+ name: 'icon-single-note',
+ path: () => import('./icons/icon-single-note.js'),
+ },
+ {
+ name: 'icon-sitemap',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-sitemap.js'),
+ },
+ {
+ name: 'icon-sleep',
+ path: () => import('./icons/icon-sleep.js'),
+ },
+ {
+ name: 'icon-slideshow',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-slideshow.js'),
+ },
+ {
+ name: 'icon-smiley-inverted',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-smiley-inverted.js'),
+ },
+ {
+ name: 'icon-smiley',
+ path: () => import('./icons/icon-smiley.js'),
+ },
+ {
+ name: 'icon-snow',
+ path: () => import('./icons/icon-snow.js'),
+ },
+ {
+ name: 'icon-sound-low',
+ path: () => import('./icons/icon-sound-low.js'),
+ },
+ {
+ name: 'icon-sound-medium',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-sound-medium.js'),
+ },
+ {
+ name: 'icon-sound-off',
+ path: () => import('./icons/icon-sound-off.js'),
+ },
+ {
+ name: 'icon-sound-waves',
+ path: () => import('./icons/icon-sound-waves.js'),
+ },
+ {
+ name: 'icon-sound',
+ path: () => import('./icons/icon-sound.js'),
+ },
+ {
+ name: 'icon-spades',
+ path: () => import('./icons/icon-spades.js'),
+ },
+ {
+ name: 'icon-speaker',
+ path: () => import('./icons/icon-speaker.js'),
+ },
+ {
+ name: 'icon-speed-gauge',
+ path: () => import('./icons/icon-speed-gauge.js'),
+ },
+ {
+ name: 'icon-split-alt',
+ path: () => import('./icons/icon-split-alt.js'),
+ },
+ {
+ name: 'icon-split',
+ path: () => import('./icons/icon-split.js'),
+ },
+ {
+ name: 'icon-sprout',
+ path: () => import('./icons/icon-sprout.js'),
+ },
+ {
+ name: 'icon-squiggly-line',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-squiggly-line.js'),
+ },
+ {
+ name: 'icon-ssd',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-ssd.js'),
+ },
+ {
+ name: 'icon-stacked-disks',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-stacked-disks.js'),
+ },
+ {
+ name: 'icon-stamp',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-stamp.js'),
+ },
+ {
+ name: 'icon-stop-alt',
+ path: () => import('./icons/icon-stop-alt.js'),
+ },
+ {
+ name: 'icon-stop-hand',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-stop-hand.js'),
+ },
+ {
+ name: 'icon-stop',
+ path: () => import('./icons/icon-stop.js'),
+ },
+ {
+ name: 'icon-store',
+ path: () => import('./icons/icon-store.js'),
+ },
+ {
+ name: 'icon-stream',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-stream.js'),
+ },
+ {
+ name: 'icon-strikethrough',
+ path: () => import('./icons/icon-strikethrough.js'),
+ },
+ {
+ name: 'icon-subscript',
+ path: () => import('./icons/icon-subscript.js'),
+ },
+ {
+ name: 'icon-superscript',
+ path: () => import('./icons/icon-superscript.js'),
+ },
+ {
+ name: 'icon-sunny',
+ path: () => import('./icons/icon-sunny.js'),
+ },
+ {
+ name: 'icon-sweatshirt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-sweatshirt.js'),
+ },
+ {
+ name: 'icon-sync',
+ path: () => import('./icons/icon-sync.js'),
+ },
+ {
+ name: 'icon-t-shirt',
+ path: () => import('./icons/icon-t-shirt.js'),
+ },
+ {
+ name: 'icon-tab-key',
+ path: () => import('./icons/icon-tab-key.js'),
+ },
+ {
+ name: 'icon-table',
+ path: () => import('./icons/icon-table.js'),
+ },
+ {
+ name: 'icon-tag',
+ path: () => import('./icons/icon-tag.js'),
+ },
+ {
+ name: 'icon-tags',
+ path: () => import('./icons/icon-tags.js'),
+ },
+ {
+ name: 'icon-takeaway-cup',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-takeaway-cup.js'),
+ },
+ {
+ name: 'icon-target',
+ path: () => import('./icons/icon-target.js'),
+ },
+ {
+ name: 'icon-temperatrure-alt',
+ path: () => import('./icons/icon-temperatrure-alt.js'),
+ },
+ {
+ name: 'icon-temperature',
+ path: () => import('./icons/icon-temperature.js'),
+ },
+ {
+ name: 'icon-terminal',
+ path: () => import('./icons/icon-terminal.js'),
+ },
+ {
+ name: 'icon-text-align-center',
+ path: () => import('./icons/icon-text-align-center.js'),
+ },
+ {
+ name: 'icon-text-align-justify',
+ path: () => import('./icons/icon-text-align-justify.js'),
+ },
+ {
+ name: 'icon-text-align-left',
+ path: () => import('./icons/icon-text-align-left.js'),
+ },
+ {
+ name: 'icon-text-align-right',
+ path: () => import('./icons/icon-text-align-right.js'),
+ },
+ {
+ name: 'icon-text-direction-ltr',
+ path: () => import('./icons/icon-text-direction-ltr.js'),
+ },
+ {
+ name: 'icon-text-direction-rtl',
+ path: () => import('./icons/icon-text-direction-rtl.js'),
+ },
+ {
+ name: 'icon-theater',
+ path: () => import('./icons/icon-theater.js'),
+ },
+ {
+ name: 'icon-thumb-down',
+ path: () => import('./icons/icon-thumb-down.js'),
+ },
+ {
+ name: 'icon-thumb-up',
+ path: () => import('./icons/icon-thumb-up.js'),
+ },
+ {
+ name: 'icon-thumbnail-list',
+ path: () => import('./icons/icon-thumbnail-list.js'),
+ },
+ {
+ name: 'icon-thumbnails-small',
+ path: () => import('./icons/icon-thumbnails-small.js'),
+ },
+ {
+ name: 'icon-thumbnails',
+ path: () => import('./icons/icon-thumbnails.js'),
+ },
+ {
+ name: 'icon-ticket',
+ path: () => import('./icons/icon-ticket.js'),
+ },
+ {
+ name: 'icon-time',
+ path: () => import('./icons/icon-time.js'),
+ },
+ {
+ name: 'icon-timer',
+ path: () => import('./icons/icon-timer.js'),
+ },
+ {
+ name: 'icon-tools',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-tools.js'),
+ },
+ {
+ name: 'icon-top',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-top.js'),
+ },
+ {
+ name: 'icon-traffic-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-traffic-alt.js'),
+ },
+ {
+ name: 'icon-trafic',
+ path: () => import('./icons/icon-trafic.js'),
+ },
+ {
+ name: 'icon-train',
+ path: () => import('./icons/icon-train.js'),
+ },
+ {
+ name: 'icon-trash-alt-2',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-trash-alt-2.js'),
+ },
+ {
+ name: 'icon-trash-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-trash-alt.js'),
+ },
+ {
+ name: 'icon-trash',
+ path: () => import('./icons/icon-trash.js'),
+ },
+ {
+ name: 'icon-tree',
+ path: () => import('./icons/icon-tree.js'),
+ },
+ {
+ name: 'icon-trophy',
+ path: () => import('./icons/icon-trophy.js'),
+ },
+ {
+ name: 'icon-truck',
+ path: () => import('./icons/icon-truck.js'),
+ },
+ {
+ name: 'icon-tv-old',
+ path: () => import('./icons/icon-tv-old.js'),
+ },
+ {
+ name: 'icon-tv',
+ path: () => import('./icons/icon-tv.js'),
+ },
+ {
+ name: 'icon-umb-content',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-umb-content.js'),
+ },
+ {
+ name: 'icon-umb-developer',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-umb-developer.js'),
+ },
+ {
+ name: 'icon-umb-media',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-umb-media.js'),
+ },
+ {
+ name: 'icon-umb-settings',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-umb-settings.js'),
+ },
+ {
+ name: 'icon-umb-users',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-umb-users.js'),
+ },
+ {
+ name: 'icon-umbrella',
+ path: () => import('./icons/icon-umbrella.js'),
+ },
+ {
+ name: 'icon-undo',
+ path: () => import('./icons/icon-undo.js'),
+ },
+ {
+ name: 'icon-underline',
+ path: () => import('./icons/icon-underline.js'),
+ },
+ {
+ name: 'icon-unlink',
+ path: () => import('./icons/icon-unlink.js'),
+ },
+ {
+ name: 'icon-unlocked',
+ path: () => import('./icons/icon-unlocked.js'),
+ },
+ {
+ name: 'icon-unplug',
+ path: () => import('./icons/icon-unplug.js'),
+ },
+ {
+ name: 'icon-untitled',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-untitled.js'),
+ },
+ {
+ name: 'icon-usb-connector',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-usb-connector.js'),
+ },
+ {
+ name: 'icon-usb',
+ path: () => import('./icons/icon-usb.js'),
+ },
+ {
+ name: 'icon-user-female',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-user-female.js'),
+ },
+ {
+ name: 'icon-user-females-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-user-females-alt.js'),
+ },
+ {
+ name: 'icon-user-females',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-user-females.js'),
+ },
+ {
+ name: 'icon-user-glasses',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-user-glasses.js'),
+ },
+ {
+ name: 'icon-user',
+ path: () => import('./icons/icon-user.js'),
+ },
+ {
+ name: 'icon-users-alt',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-users-alt.js'),
+ },
+ {
+ name: 'icon-users',
+ path: () => import('./icons/icon-users.js'),
+ },
+ {
+ name: 'icon-utilities',
+ path: () => import('./icons/icon-utilities.js'),
+ },
+ {
+ name: 'icon-vcard',
+ path: () => import('./icons/icon-vcard.js'),
+ },
+ {
+ name: 'icon-video',
+ path: () => import('./icons/icon-video.js'),
+ },
+ {
+ name: 'icon-voice',
+ path: () => import('./icons/icon-voice.js'),
+ },
+ {
+ name: 'icon-wall-plug',
+ path: () => import('./icons/icon-wall-plug.js'),
+ },
+ {
+ name: 'icon-wallet',
+ path: () => import('./icons/icon-wallet.js'),
+ },
+ {
+ name: 'icon-wand',
+ path: () => import('./icons/icon-wand.js'),
+ },
+ {
+ name: 'icon-webhook',
+ path: () => import('./icons/icon-webhook.js'),
+ },
+ {
+ name: 'icon-weight',
+ path: () => import('./icons/icon-weight.js'),
+ },
+ {
+ name: 'icon-width',
+ path: () => import('./icons/icon-width.js'),
+ },
+ {
+ name: 'icon-wifi',
+ path: () => import('./icons/icon-wifi.js'),
+ },
+ {
+ name: 'icon-window-popin',
+ path: () => import('./icons/icon-window-popin.js'),
+ },
+ {
+ name: 'icon-window-popout',
+ path: () => import('./icons/icon-window-popout.js'),
+ },
+ {
+ name: 'icon-window-sizes',
+ path: () => import('./icons/icon-window-sizes.js'),
+ },
+ {
+ name: 'icon-wine-glass',
+ path: () => import('./icons/icon-wine-glass.js'),
+ },
+ {
+ name: 'icon-wrench',
+ path: () => import('./icons/icon-wrench.js'),
+ },
+ {
+ name: 'icon-wrong',
+ path: () => import('./icons/icon-wrong.js'),
+ },
+ {
+ name: 'icon-zip',
+ path: () => import('./icons/icon-zip.js'),
+ },
+ {
+ name: 'icon-zom-out',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-zom-out.js'),
+ },
+ {
+ name: 'icon-zoom-in',
+ path: () => import('./icons/icon-zoom-in.js'),
+ },
+ {
+ name: 'icon-zoom-out',
+ path: () => import('./icons/icon-zoom-out.js'),
+ },
+ {
+ name: 'icon-star',
+ path: () => import('./icons/icon-star.js'),
+ },
+ {
+ name: 'icon-database',
+ path: () => import('./icons/icon-database.js'),
+ },
+ {
+ name: 'icon-umb-manifest',
+ hidden: true,
+ path: () => import('./icons/icon-umb-manifest.js'),
+ },
+ {
+ name: 'icon-puzzle-piece',
+ path: () => import('./icons/icon-puzzle-piece.js'),
+ },
+ {
+ name: 'icon-document-3d',
+ path: () => import('./icons/icon-document-3d.js'),
+ },
+ {
+ name: 'icon-document-medal',
+ path: () => import('./icons/icon-document-medal.js'),
+ },
+ {
+ name: 'icon-document-chart-bar',
+ path: () => import('./icons/icon-document-chart-bar.js'),
+ },
+ {
+ name: 'icon-document-chart-graph',
+ path: () => import('./icons/icon-document-chart-graph.js'),
+ },
+ {
+ name: 'icon-document-html',
+ path: () => import('./icons/icon-document-html.js'),
+ },
+ {
+ name: 'icon-document-js',
+ path: () => import('./icons/icon-document-js.js'),
+ },
+ {
+ name: 'icon-document-key',
+ path: () => import('./icons/icon-document-key.js'),
+ },
+ {
+ name: 'icon-document-search',
+ path: () => import('./icons/icon-document-search.js'),
+ },
+ {
+ name: 'icon-document-settings',
+ path: () => import('./icons/icon-document-settings.js'),
+ },
+ {
+ name: 'icon-document-spreadsheet',
+ path: () => import('./icons/icon-document-spreadsheet.js'),
+ },
+ {
+ name: 'icon-document-command',
+ path: () => import('./icons/icon-document-command.js'),
+ },
+ {
+ name: 'icon-document-command',
+ path: () => import('./icons/icon-document-command.js'),
+ },
+ {
+ name: 'icon-document-font',
+ path: () => import('./icons/icon-document-font.js'),
+ },
+ {
+ name: 'icon-document-user',
+ path: () => import('./icons/icon-document-user.js'),
+ },
+ {
+ name: 'icon-document-image',
+ path: () => import('./icons/icon-document-image.js'),
+ },
+ {
+ name: 'icon-document-play',
+ 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'),
+ },
+ {
+ name: 'icon-gitbook',
+ path: () => import('./icons/icon-gitbook.js'),
+ },
+ {
+ name: 'icon-github',
+ path: () => import('./icons/icon-github.js'),
+ },
+ {
+ name: 'icon-gitlab',
+ path: () => import('./icons/icon-gitlab.js'),
+ },
+ {
+ name: 'icon-google',
+ path: () => import('./icons/icon-google.js'),
+ },
+ {
+ name: 'icon-mastodon',
+ path: () => import('./icons/icon-mastodon.js'),
+ },
+ {
+ name: 'icon-twitter-x',
+ 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',
+ path: () => import('./icons/icon-umbraco.js'),
+ },
+ {
+ name: 'icon-azure',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-azure.js'),
+ },
+ {
+ name: 'icon-microsoft',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-microsoft.js'),
+ },
+ {
+ name: 'icon-os-x',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-os-x.js'),
+ },
+ {
+ name: 'icon-pants',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-pants.js'),
+ },
+ {
+ name: 'icon-parachute-drop',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-parachute-drop.js'),
+ },
+ {
+ name: 'icon-parental-control',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-parental-control.js'),
+ },
+ {
+ name: 'icon-path',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-path.js'),
+ },
+ {
+ name: 'icon-piracy',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-piracy.js'),
+ },
+ {
+ name: 'icon-poker-chip',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-poker-chip.js'),
+ },
+ {
+ name: 'icon-pound-bag',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-pound-bag.js'),
+ },
+ {
+ name: 'icon-receipt-dollar',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-receipt-dollar.js'),
+ },
+ {
+ name: 'icon-receipt-euro',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-receipt-euro.js'),
+ },
+ {
+ name: 'icon-receipt-pound',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-receipt-pound.js'),
+ },
+ {
+ name: 'icon-receipt-yen',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-receipt-yen.js'),
+ },
+ {
+ name: 'icon-road',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-road.js'),
+ },
+ {
+ name: 'icon-safe',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-safe.js'),
+ },
+ {
+ name: 'icon-safedial',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-safedial.js'),
+ },
+ {
+ name: 'icon-sandbox-toys',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-sandbox-toys.js'),
+ },
+ {
+ name: 'icon-security-camera',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-security-camera.js'),
+ },
+ {
+ name: 'icon-settings-alt-2',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-settings-alt-2.js'),
+ },
+ {
+ name: 'icon-share-alt-2',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-share-alt-2.js'),
+ },
+ {
+ name: 'icon-shorts',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-shorts.js'),
+ },
+ {
+ name: 'icon-simcard',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-simcard.js'),
+ },
+ {
+ name: 'icon-tab',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-tab.js'),
+ },
+ {
+ name: 'icon-tactics',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-tactics.js'),
+ },
+ {
+ name: 'icon-theif',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-theif.js'),
+ },
+ {
+ name: 'icon-thought-bubble',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-thought-bubble.js'),
+ },
+ {
+ name: 'icon-twitter',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-twitter.js'),
+ },
+ {
+ name: 'icon-umb-contour',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-umb-contour.js'),
+ },
+ {
+ name: 'icon-umb-deploy',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-umb-deploy.js'),
+ },
+ {
+ name: 'icon-umb-members',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-umb-members.js'),
+ },
+ {
+ name: 'icon-universal',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-universal.js'),
+ },
+ {
+ name: 'icon-war',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-war.js'),
+ },
+ {
+ name: 'icon-windows',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-windows.js'),
+ },
+ {
+ name: 'icon-yen-bag',
+ legacy: true,
+ hidden: true,
+ path: () => import('./icons/icon-yen-bag.js'),
+ },
+];
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-next.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-next.ts
index 81331a086a..d8a3a4e89d 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-next.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-next.ts
@@ -1 +1 @@
-export default ` `;
\ No newline at end of file
+export default ` `;
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/localization/localize.element.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/localization/localize.element.test.ts
index 66f5487a1d..74a09003e7 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/localization/localize.element.test.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/localization/localize.element.test.ts
@@ -3,11 +3,13 @@ import { aTimeout, elementUpdated, expect, fixture, html } from '@open-wc/testin
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';
import { umbLocalizationRegistry } from './registry/localization.registry.js';
+import type { ManifestLocalization } from './extensions/localization.extension.js';
-const english = {
+const english: ManifestLocalization = {
type: 'localization',
alias: 'test.en',
name: 'Test English',
+ weight: 100,
meta: {
culture: 'en',
localizations: {
@@ -27,7 +29,75 @@ const english = {
},
};
-const danish = {
+const englishUs: ManifestLocalization = {
+ type: 'localization',
+ alias: 'test.en-us',
+ name: 'Test English (US)',
+ weight: 100,
+ meta: {
+ culture: 'en-us',
+ localizations: {
+ general: {
+ close: 'Close US',
+ overridden: 'Overridden',
+ },
+ },
+ },
+};
+
+// This is a factory function that returns the localization object.
+const asyncFactory = async (localizations: Record, delay: number) => {
+ await aTimeout(delay); // Simulate async loading
+ return {
+ // Simulate a JS module that exports a localization object.
+ default: localizations,
+ };
+};
+
+// This is an async localization that overrides the previous one.
+const englishAsyncOverride: ManifestLocalization = {
+ type: 'localization',
+ alias: 'test.en.async-override',
+ name: 'Test English Async Override',
+ weight: -100,
+ meta: {
+ culture: 'en-us',
+ },
+ js: () =>
+ asyncFactory(
+ {
+ general: {
+ close: 'Close Async',
+ overridden: 'Overridden Async',
+ },
+ },
+ 100,
+ ),
+};
+
+// This is another async localization that loads later than the previous one and overrides it because of a lower weight.
+const english2AsyncOverride: ManifestLocalization = {
+ type: 'localization',
+ alias: 'test.en.async-override-2',
+ name: 'Test English Async Override 2',
+ weight: -200,
+ meta: {
+ culture: 'en-us',
+ },
+ js: () =>
+ asyncFactory(
+ {
+ general: {
+ close: 'Another Async Close',
+ },
+ },
+ 200, // This will load after the first async override
+ // so it should override the close translation.
+ // The overridden translation should not be overridden.
+ ),
+};
+
+const danish: ManifestLocalization = {
type: 'localization',
alias: 'test.da',
name: 'Test Danish',
@@ -53,8 +123,7 @@ describe('umb-localize', () => {
});
describe('localization', () => {
- umbExtensionsRegistry.register(english);
- umbExtensionsRegistry.register(danish);
+ umbExtensionsRegistry.registerMany([english, englishUs, danish]);
beforeEach(async () => {
umbLocalizationRegistry.loadLanguage(english.meta.culture);
@@ -123,13 +192,50 @@ describe('umb-localize', () => {
it('should change the value if the language is changed', async () => {
expect(element.shadowRoot?.innerHTML).to.contain('Close');
+ // Change to Danish
umbLocalizationRegistry.loadLanguage(danish.meta.culture);
await aTimeout(0);
await elementUpdated(element);
-
expect(element.shadowRoot?.innerHTML).to.contain('Luk');
});
+ it('should fall back to the fallback language if the key is not found', async () => {
+ expect(element.shadowRoot?.innerHTML).to.contain('Close');
+
+ // Change to US English
+ umbLocalizationRegistry.loadLanguage(englishUs.meta.culture);
+ await aTimeout(0);
+ await elementUpdated(element);
+ expect(element.shadowRoot?.innerHTML).to.contain('Close US');
+
+ element.key = 'general_overridden';
+ await elementUpdated(element);
+ expect(element.shadowRoot?.innerHTML).to.contain('Overridden');
+
+ element.key = 'general_logout';
+ await elementUpdated(element);
+ expect(element.shadowRoot?.innerHTML).to.contain('Log out');
+ });
+
+ it('should accept a lazy loaded localization', async () => {
+ umbExtensionsRegistry.registerMany([englishAsyncOverride, english2AsyncOverride]);
+ umbLocalizationRegistry.loadLanguage(englishAsyncOverride.meta.culture);
+ await aTimeout(200); // Wait for the async override to load
+
+ await elementUpdated(element);
+ expect(element.shadowRoot?.innerHTML).to.contain(
+ 'Another Async Close',
+ '(async) Should have overridden the close (from first language)',
+ );
+
+ element.key = 'general_overridden';
+ await elementUpdated(element);
+ expect(element.shadowRoot?.innerHTML).to.contain(
+ 'Overridden Async',
+ '(async) Should not have overridden the overridden (from first language)',
+ );
+ });
+
it('should use the slot if translation is not found', async () => {
element.key = 'non-existing-key';
await elementUpdated(element);
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/localization/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/localization/manifests.ts
index c393e2f3db..c1d88ac009 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/localization/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/localization/manifests.ts
@@ -4,7 +4,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.AR',
- weight: -100,
+ weight: 100,
name: 'Arabic Backoffice UI Localization',
meta: {
culture: 'ar',
@@ -14,7 +14,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.BS',
- weight: -100,
+ weight: 100,
name: 'Bosnian Backoffice UI Localization',
meta: {
culture: 'bs',
@@ -24,7 +24,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.CS',
- weight: -100,
+ weight: 100,
name: 'Czech Backoffice UI Localization',
meta: {
culture: 'cs',
@@ -34,7 +34,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.CY',
- weight: -100,
+ weight: 100,
name: 'Welsh Backoffice UI Localization',
meta: {
culture: 'cy',
@@ -44,7 +44,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.DA',
- weight: -100,
+ weight: 100,
name: 'Danish Backoffice UI Localization',
meta: {
culture: 'da',
@@ -54,7 +54,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.DE',
- weight: -100,
+ weight: 100,
name: 'German Backoffice UI Localization',
meta: {
culture: 'de',
@@ -64,7 +64,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.EN',
- weight: -100,
+ weight: 100,
name: 'English (United Kingdom) Backoffice UI Localization',
meta: {
culture: 'en',
@@ -74,7 +74,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.EN_US',
- weight: -100,
+ weight: 100,
name: 'English (United States) Backoffice UI Localization',
meta: {
culture: 'en-US',
@@ -84,7 +84,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.ES',
- weight: -100,
+ weight: 100,
name: 'Spanish Backoffice UI Localization',
meta: {
culture: 'es',
@@ -94,7 +94,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.FR',
- weight: -100,
+ weight: 100,
name: 'French Backoffice UI Localization',
meta: {
culture: 'fr',
@@ -104,7 +104,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.HE',
- weight: -100,
+ weight: 100,
name: 'Hebrew Backoffice UI Localization',
meta: {
culture: 'he',
@@ -114,7 +114,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.HR',
- weight: -100,
+ weight: 100,
name: 'Croatian Backoffice UI Localization',
meta: {
culture: 'hr',
@@ -124,7 +124,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.IT',
- weight: -100,
+ weight: 100,
name: 'Italian Backoffice UI Localization',
meta: {
culture: 'it',
@@ -134,7 +134,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.JA',
- weight: -100,
+ weight: 100,
name: 'Japanese Backoffice UI Localization',
meta: {
culture: 'ja',
@@ -144,7 +144,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.KO',
- weight: -100,
+ weight: 100,
name: 'Korean Backoffice UI Localization',
meta: {
culture: 'ko',
@@ -154,7 +154,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.NB',
- weight: -100,
+ weight: 100,
name: 'Norwegian Backoffice UI Localization',
meta: {
culture: 'nb',
@@ -164,7 +164,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.NL',
- weight: -100,
+ weight: 100,
name: 'Dutch Backoffice UI Localization',
meta: {
culture: 'nl',
@@ -174,7 +174,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.PL',
- weight: -100,
+ weight: 100,
name: 'Polish Backoffice UI Localization',
meta: {
culture: 'pl',
@@ -184,7 +184,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.PT',
- weight: -100,
+ weight: 100,
name: 'Portuguese Backoffice UI Localization',
meta: {
culture: 'pt',
@@ -194,7 +194,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.PT_BR',
- weight: -100,
+ weight: 100,
name: 'Portuguese (Brazil) Backoffice UI Localization',
meta: {
culture: 'pt-BR',
@@ -204,7 +204,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.RO',
- weight: -100,
+ weight: 100,
name: 'Romanian Backoffice UI Localization',
meta: {
culture: 'ro',
@@ -214,7 +214,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.RU',
- weight: -100,
+ weight: 100,
name: 'Russian Backoffice UI Localization',
meta: {
culture: 'ru',
@@ -224,7 +224,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.SV',
- weight: -100,
+ weight: 100,
name: 'Swedish Backoffice UI Localization',
meta: {
culture: 'sv',
@@ -234,7 +234,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.TR',
- weight: -100,
+ weight: 100,
name: 'Turkish Backoffice UI Localization',
meta: {
culture: 'tr',
@@ -244,7 +244,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.UK',
- weight: -100,
+ weight: 100,
name: 'Ukrainian Backoffice UI Localization',
meta: {
culture: 'uk',
@@ -254,7 +254,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.ZH',
- weight: -100,
+ weight: 100,
name: 'Chinese Backoffice UI Localization',
meta: {
culture: 'zh',
@@ -264,7 +264,7 @@ export const manifests: Array = [
{
type: 'localization',
alias: 'Umb.Localization.ZH_TW',
- weight: -100,
+ weight: 100,
name: 'Chinese (Taiwan) Backoffice UI Localization',
meta: {
culture: 'zh-TW',
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/localization/registry/localization.registry.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/localization/registry/localization.registry.test.ts
index 62e7959a9a..3aad872415 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/localization/registry/localization.registry.test.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/localization/registry/localization.registry.test.ts
@@ -8,8 +8,10 @@ const englishUk: ManifestLocalization = {
type: 'localization',
alias: 'test.en',
name: 'Test English (UK)',
+ weight: 100,
meta: {
culture: 'en',
+ direction: 'ltr',
localizations: {
general: {
color: 'Colour',
@@ -22,6 +24,7 @@ const english: ManifestLocalization = {
type: 'localization',
alias: 'test.en-us',
name: 'Test English (US)',
+ weight: 100,
meta: {
culture: 'en-us',
direction: 'ltr',
@@ -46,16 +49,43 @@ const englishOverride: ManifestLocalization = {
type: 'localization',
alias: 'test.en.override',
name: 'Test English',
+ weight: 0,
meta: {
culture: 'en-us',
localizations: {
general: {
close: 'Close 2',
+ overridden: 'Overridden',
},
},
},
};
+// This is a factory function that returns the localization object.
+const englishAsyncFactory = async () => {
+ await aTimeout(100); // Simulate async loading
+ return {
+ // Simulate a JS module that exports a localization object.
+ default: {
+ general: {
+ close: 'Close Async',
+ overridden: 'Overridden Async',
+ },
+ },
+ };
+};
+
+const englishAsyncOverride: ManifestLocalization = {
+ type: 'localization',
+ alias: 'test.en.async-override',
+ name: 'Test English Async Override',
+ weight: -100,
+ meta: {
+ culture: 'en-us',
+ },
+ js: englishAsyncFactory,
+};
+
const danish: ManifestLocalization = {
type: 'localization',
alias: 'test.da',
@@ -87,10 +117,7 @@ const danishRegional: ManifestLocalization = {
//#endregion
describe('UmbLocalizeController', () => {
- umbExtensionsRegistry.register(englishUk);
- umbExtensionsRegistry.register(english);
- umbExtensionsRegistry.register(danish);
- umbExtensionsRegistry.register(danishRegional);
+ umbExtensionsRegistry.registerMany([englishUk, english, danish, danishRegional]);
let registry: UmbLocalizationRegistry;
@@ -102,6 +129,16 @@ describe('UmbLocalizeController', () => {
afterEach(() => {
registry.localizations.clear();
+ registry.destroy();
+ });
+
+ it('should register into the localization manager', async () => {
+ expect(registry.localizations.size).to.equal(2, 'Should have registered the 2 original iso codes (en, en-us)');
+
+ // Register an additional language to test the registry.
+ registry.loadLanguage(danish.meta.culture);
+ await aTimeout(0);
+ expect(registry.localizations.size).to.equal(3, 'Should have registered the 3rd language (da)');
});
it('should set the document language and direction', async () => {
@@ -122,9 +159,48 @@ describe('UmbLocalizeController', () => {
await aTimeout(0);
- const current = registry.localizations.get(english.meta.culture);
- expect(current).to.have.property('general_close', 'Close 2');
- expect(current).to.have.property('general_logout', 'Log out');
+ const current = registry.localizations.get(englishOverride.meta.culture);
+ expect(current).to.have.property(
+ 'general_close',
+ 'Close 2',
+ 'Should have overridden the close (from first language)',
+ );
+ expect(current).to.have.property('general_logout', 'Log out', 'Should not have overridden the logout');
+
+ umbExtensionsRegistry.unregister(englishOverride.alias);
+ });
+
+ it('should load translations based on weight (lowest weight overrides)', async () => {
+ // set weight to 200, so it will not override the existing translation
+ const englishOverrideLowWeight = { ...englishOverride, weight: 200 } satisfies ManifestLocalization;
+ umbExtensionsRegistry.register(englishOverrideLowWeight);
+ await aTimeout(0);
+
+ let current = registry.localizations.get(englishOverrideLowWeight.meta.culture);
+ expect(current).to.have.property(
+ 'general_close',
+ 'Close',
+ 'Should not have overridden the close (from first language)',
+ );
+ expect(current).to.have.property('general_overridden', 'Overridden', 'Should be able to register its own keys');
+
+ // Now register a new async override with a lower weight
+ umbExtensionsRegistry.register(englishAsyncOverride);
+ await aTimeout(200); // Wait for the async override to load
+ current = registry.localizations.get(englishOverrideLowWeight.meta.culture);
+ expect(current).to.have.property(
+ 'general_close',
+ 'Close Async',
+ '(async) Should have overridden the close (from first language)',
+ );
+ expect(current).to.have.property(
+ 'general_overridden',
+ 'Overridden Async',
+ '(async) Should have overridden the overridden',
+ );
+
+ umbExtensionsRegistry.unregister(englishOverrideLowWeight.alias);
+ umbExtensionsRegistry.unregister(englishAsyncOverride.alias);
});
it('should be able to switch to the fallback language', async () => {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/localization/registry/localization.registry.ts b/src/Umbraco.Web.UI.Client/src/packages/core/localization/registry/localization.registry.ts
index ef76a9af67..a51d3651b8 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/localization/registry/localization.registry.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/localization/registry/localization.registry.ts
@@ -1,22 +1,30 @@
import type { ManifestLocalization } from '../extensions/localization.extension.js';
import {
- type UmbLocalizationSetBase,
- type UmbLocalizationDictionary,
- type UmbLocalizationFlatDictionary,
- UMB_DEFAULT_LOCALIZATION_CULTURE,
-} from '@umbraco-cms/backoffice/localization-api';
-import { umbLocalizationManager } from '@umbraco-cms/backoffice/localization-api';
-import type { UmbBackofficeExtensionRegistry } from '@umbraco-cms/backoffice/extension-registry';
-import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
-import { UmbStringState } from '@umbraco-cms/backoffice/observable-api';
-import { combineLatest } from '@umbraco-cms/backoffice/external/rxjs';
+ catchError,
+ distinctUntilChanged,
+ filter,
+ from,
+ map,
+ of,
+ switchMap,
+} from '@umbraco-cms/backoffice/external/rxjs';
import { hasDefaultExport, loadManifestPlainJs } from '@umbraco-cms/backoffice/extension-api';
+import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
+import { umbLocalizationManager, UMB_DEFAULT_LOCALIZATION_CULTURE } from '@umbraco-cms/backoffice/localization-api';
+import { UmbStringState } from '@umbraco-cms/backoffice/observable-api';
+import type { UmbBackofficeExtensionRegistry } from '@umbraco-cms/backoffice/extension-registry';
+import type {
+ UmbLocalizationSetBase,
+ UmbLocalizationDictionary,
+ UmbLocalizationFlatDictionary,
+} from '@umbraco-cms/backoffice/localization-api';
+import type { Subscription } from '@umbraco-cms/backoffice/external/rxjs';
/**
- *
- * @param innerDictionary
- * @param dictionaryName
- * @param dictionary
+ * Adds or updates a dictionary in the inner dictionary.
+ * @param {UmbLocalizationFlatDictionary} innerDictionary The inner dictionary to add or update the dictionary in.
+ * @param {string} dictionaryName The name of the dictionary to add or update.
+ * @param {UmbLocalizationDictionary['value']} dictionary The dictionary to add or update.
*/
function addOrUpdateDictionary(
innerDictionary: UmbLocalizationFlatDictionary,
@@ -34,63 +42,85 @@ export class UmbLocalizationRegistry {
);
readonly currentLanguage = this.#currentLanguage.asObservable();
- #loadedExtAliases: Array = [];
-
/**
* Get the current registered translations.
* @returns {Map} Returns the registered translations
*/
- get localizations() {
+ get localizations(): Map {
return umbLocalizationManager.localizations;
}
+ #subscription: Subscription;
+
constructor(extensionRegistry: UmbBackofficeExtensionRegistry) {
- combineLatest([this.currentLanguage, extensionRegistry.byType('localization')]).subscribe(
- async ([currentLanguage, extensions]) => {
- const locale = new Intl.Locale(currentLanguage);
- const currentLanguageExtensions = extensions.filter(
- (ext) =>
- ext.meta.culture.toLowerCase() === locale.baseName.toLowerCase() ||
- ext.meta.culture.toLowerCase() === locale.language.toLowerCase(),
- );
+ // Store the locale in a variable to use when setting the document language and direction
+ let locale: Intl.Locale | undefined = undefined;
- // If there are no extensions for the current language, return early
- if (!currentLanguageExtensions.length) return;
-
- // Register the new translations only if they have not been registered before
- const diff = currentLanguageExtensions.filter((ext) => !this.#loadedExtAliases.includes(ext.alias));
-
- // Load all localizations
- const translations = await Promise.all(currentLanguageExtensions.map(this.#loadExtension));
-
- // If there are no translations, return early
- if (!translations.length) return;
-
- if (diff.length) {
- const filteredTranslations = translations.filter((t) =>
- diff.some((ext) => ext.meta.culture.toLowerCase() === t.$code),
+ this.#subscription = this.currentLanguage
+ .pipe(
+ // Ensure the current language is not empty
+ filter((currentLanguage) => !!currentLanguage),
+ // Use distinctUntilChanged to avoid unnecessary re-renders when the language hasn't changed
+ distinctUntilChanged(),
+ // Switch to the extensions registry to get the current language and the extensions for that language
+ // Note: This also cancels the previous subscription if the language changes
+ switchMap((currentLanguage) => {
+ return extensionRegistry.byType('localization').pipe(
+ // Filter the extensions to only those that match the current language
+ map((extensions) => {
+ locale = new Intl.Locale(currentLanguage);
+ return extensions.filter(
+ (ext) =>
+ ext.meta.culture.toLowerCase() === locale!.baseName.toLowerCase() ||
+ ext.meta.culture.toLowerCase() === locale!.language.toLowerCase(),
+ );
+ }),
);
- umbLocalizationManager.registerManyLocalizations(filteredTranslations);
- }
+ }),
+ // Ensure we only process extensions that are registered
+ filter((extensions) => extensions.length > 0),
+ // Ensure we only process extensions that have not been loaded before
+ distinctUntilChanged((prev, curr) => {
+ const prevAliases = prev.map((ext) => ext.alias).sort();
+ const currAliases = curr.map((ext) => ext.alias).sort();
+ return this.#arraysEqual(prevAliases, currAliases);
+ }),
+ // With switchMap, if a new language is selected before the previous translations finish loading,
+ // the previous promise is canceled (unsubscribed), and only the latest one is processed.
+ // This prevents race conditions and stale state.
+ switchMap((extensions) =>
+ from(
+ (async () => {
+ // Load all localizations
+ const translations = await Promise.all(extensions.map(this.#loadExtension));
- // Set the document language
- const newLang = locale.baseName.toLowerCase();
- if (document.documentElement.lang.toLowerCase() !== newLang) {
- document.documentElement.lang = newLang;
- }
+ // If there are no translations, return early
+ if (!translations.length) return;
- // Set the document direction to the direction of the primary language
- const newDir = translations[0].$dir ?? 'ltr';
- if (document.documentElement.dir !== newDir) {
- document.documentElement.dir = newDir;
- }
- },
- );
+ // Sort translations by their original extension weight (highest-to-lowest)
+ // This ensures that the translations with the lowest weight override the others
+ translations.sort((a, b) => b.$weight - a.$weight);
+
+ // Load the translations into the localization manager
+ umbLocalizationManager.registerManyLocalizations(translations);
+
+ // Set the browser language and direction based on the translations
+ this.#setBrowserLanguage(locale!, translations);
+ })(),
+ ),
+ ),
+ // Catch any errors that occur while loading the translations
+ // This is important to ensure that the observable does not error out and stop the subscription
+ catchError((error) => {
+ console.error('Error loading translations:', error);
+ return of([]);
+ }),
+ )
+ // Subscribe to the observable to trigger the loading of translations
+ .subscribe();
}
#loadExtension = async (extension: ManifestLocalization) => {
- this.#loadedExtAliases.push(extension.alias);
-
const innerDictionary: UmbLocalizationFlatDictionary = {};
// If extension contains a dictionary, add it to the inner dictionary.
@@ -115,16 +145,63 @@ export class UmbLocalizationRegistry {
return {
$code: extension.meta.culture.toLowerCase(),
$dir: extension.meta.direction ?? 'ltr',
+ $weight: extension.weight ?? 100,
...innerDictionary,
- } satisfies UmbLocalizationSetBase;
+ } satisfies UmbLocalizationSetBase & { $weight: number };
};
+ #setBrowserLanguage(locale: Intl.Locale, translations: UmbLocalizationSetBase[]) {
+ // Set the document language
+ const newLang = locale.baseName.toLowerCase();
+ if (document.documentElement.lang.toLowerCase() !== newLang) {
+ document.documentElement.lang = newLang;
+ }
+
+ // We need to find the direction of the new language, so we look for the best match
+ // If the new language is not found, we default to 'ltr'
+ const reverseTranslations = translations.slice().reverse();
+
+ // Look for a direct match first
+ const directMatch = reverseTranslations.find((t) => t.$code.toLowerCase() === newLang);
+ if (directMatch) {
+ document.documentElement.dir = directMatch.$dir;
+ return;
+ }
+
+ // If no direct match, look for a match with the language code only
+ const langOnlyDirectMatch = reverseTranslations.find(
+ (t) => t.$code.toLowerCase() === locale.language.toLowerCase(),
+ );
+ if (langOnlyDirectMatch) {
+ document.documentElement.dir = langOnlyDirectMatch.$dir;
+ return;
+ }
+
+ // If no match is found, default to 'ltr'
+ if (document.documentElement.dir !== 'ltr') {
+ document.documentElement.dir = 'ltr';
+ }
+ }
+
+ #arraysEqual(a: string[], b: string[]) {
+ if (a.length !== b.length) return false;
+ for (let i = 0; i < a.length; i++) {
+ if (a[i] !== b[i]) return false;
+ }
+ return true;
+ }
+
/**
* Load a language from the extension registry.
* @param {string} locale The locale to load.
*/
loadLanguage(locale: string) {
- this.#currentLanguage.setValue(locale.toLowerCase());
+ const canonicalLocale = Intl.getCanonicalLocales(locale)[0];
+ this.#currentLanguage.setValue(canonicalLocale);
+ }
+
+ destroy() {
+ this.#subscription.unsubscribe();
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item-layout/menu-item-layout.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item-layout/menu-item-layout.element.ts
index b6471e064d..fdc2c07b7a 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item-layout/menu-item-layout.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item-layout/menu-item-layout.element.ts
@@ -1,5 +1,6 @@
import { html, customElement, property, ifDefined, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
+import { ensureSlash } from '@umbraco-cms/backoffice/router';
import { debounce } from '@umbraco-cms/backoffice/utils';
/**
@@ -62,8 +63,12 @@ export class UmbMenuItemLayoutElement extends UmbLitElement {
return;
}
- const location = window.location.pathname;
- this._isActive = location.includes(this.href);
+ /* Check if the current location includes the href of this menu item
+ We ensure that the paths ends with a slash to avoid collisions with paths like /path-1 and /path-1-2 where /path is in both.
+ Instead we compare /path-1/ with /path-1-2/ which wont collide.*/
+ const location = ensureSlash(window.location.pathname);
+ const compareHref = ensureSlash(this.href);
+ this._isActive = location.includes(compareHref);
}
override render() {
@@ -80,7 +85,7 @@ export class UmbMenuItemLayoutElement extends UmbLitElement {
slot="actions"
.entityType=${this.entityType}
.unique=${null}
- .label=${this.label}>
+ .label=${this.localize.term('actions_viewActionsFor', [this.label])}>
`
: ''}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/index.ts
index 46a6c42b4b..c7d6c36f9e 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/index.ts
@@ -1,5 +1,7 @@
export * from './components/index.js';
export * from './menu-tree-structure-workspace-context-base.js';
+export * from './menu-structure-workspace-context.context-token.js';
+export * from './menu-variant-structure-workspace-context.context-token.js';
export * from './menu-variant-tree-structure-workspace-context-base.js';
export type * from './types.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-structure-workspace-context.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-structure-workspace-context.context-token.ts
new file mode 100644
index 0000000000..e04fca4af2
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-structure-workspace-context.context-token.ts
@@ -0,0 +1,7 @@
+import type { UmbMenuStructureWorkspaceContext } from './menu-structure-workspace-context.interface.js';
+import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
+
+export const UMB_MENU_STRUCTURE_WORKSPACE_CONTEXT = new UmbContextToken
(
+ 'UmbWorkspaceContext',
+ 'UmbMenuStructure',
+);
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-tree-structure-workspace-context-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-tree-structure-workspace-context-base.ts
index d461bff391..5ebd0dc2ae 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-tree-structure-workspace-context-base.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-tree-structure-workspace-context-base.ts
@@ -1,32 +1,41 @@
import type { UmbStructureItemModel } from './types.js';
+import { UMB_MENU_STRUCTURE_WORKSPACE_CONTEXT } from './menu-structure-workspace-context.context-token.js';
import type { UmbTreeRepository, UmbTreeItemModel, UmbTreeRootModel } from '@umbraco-cms/backoffice/tree';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
-import { UMB_SUBMITTABLE_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
+import { UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
import { UmbArrayState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbAncestorsEntityContext, UmbParentEntityContext, type UmbEntityModel } from '@umbraco-cms/backoffice/entity';
interface UmbMenuTreeStructureWorkspaceContextBaseArgs {
treeRepositoryAlias: string;
}
+// TODO: introduce base class for all menu structure workspaces to handle ancestors and parent
export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContextBase {
- #workspaceContext?: typeof UMB_SUBMITTABLE_WORKSPACE_CONTEXT.TYPE;
+ #workspaceContext?: typeof UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT.TYPE;
#args: UmbMenuTreeStructureWorkspaceContextBaseArgs;
#structure = new UmbArrayState([], (x) => x.unique);
public readonly structure = this.#structure.asObservable();
#parent = new UmbObjectState(undefined);
+ /**
+ * @deprecated Will be removed in v.18: Use UMB_PARENT_ENTITY_CONTEXT instead.
+ */
public readonly parent = this.#parent.asObservable();
+ #parentContext = new UmbParentEntityContext(this);
+ #ancestorContext = new UmbAncestorsEntityContext(this);
+
constructor(host: UmbControllerHost, args: UmbMenuTreeStructureWorkspaceContextBaseArgs) {
- // TODO: set up context token
- super(host, 'UmbMenuStructureWorkspaceContext');
+ super(host, UMB_MENU_STRUCTURE_WORKSPACE_CONTEXT);
+ // 'UmbMenuStructureWorkspaceContext' is Obsolete, will be removed in v.18
+ this.provideContext('UmbMenuStructureWorkspaceContext', this);
this.#args = args;
- // TODO: set up context token that supports parentEntityType, parentUnique, entityType.
- this.consumeContext(UMB_SUBMITTABLE_WORKSPACE_CONTEXT, (instance) => {
+ this.consumeContext(UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT, (instance) => {
this.#workspaceContext = instance;
this.observe(this.#workspaceContext?.unique, (value) => {
if (!value) return;
@@ -59,14 +68,16 @@ export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContex
const isNew = this.#workspaceContext?.getIsNew();
const entityTypeObservable = isNew
- ? (this.#workspaceContext as any)?.parentEntityType
- : (this.#workspaceContext as any).entityType;
+ ? this.#workspaceContext?._internal_createUnderParentEntityType
+ : this.#workspaceContext?.entityType;
const entityType = (await this.observe(entityTypeObservable, () => {})?.asPromise()) as string;
if (!entityType) throw new Error('Entity type is not available');
// If the entity type is different from the root entity type, then we can request the ancestors.
if (entityType !== root?.entityType) {
- const uniqueObservable = isNew ? (this.#workspaceContext as any)?.parentUnique : this.#workspaceContext?.unique;
+ const uniqueObservable = isNew
+ ? this.#workspaceContext?._internal_createUnderParentEntityUnique
+ : this.#workspaceContext?.unique;
const unique = (await this.observe(uniqueObservable, () => {})?.asPromise()) as string;
if (!unique) throw new Error('Unique is not available');
@@ -83,11 +94,49 @@ export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContex
});
structureItems.push(...ancestorItems);
+
+ this.#structure.setValue(structureItems);
+ this.#setParentData(structureItems);
+ this.#setAncestorData(data);
}
}
+ }
- const parent = structureItems[structureItems.length - 2];
+ #setParentData(structureItems: Array) {
+ /* If the item is not new, the current item is the last item in the array.
+ We filter out the current item unique to handle any case where it could show up */
+ const parent = structureItems.filter((item) => item.unique !== this.#workspaceContext?.getUnique()).pop();
+
+ // TODO: remove this when the parent gets removed from the structure interface
this.#parent.setValue(parent);
- this.#structure.setValue(structureItems);
+
+ const parentEntity = parent
+ ? {
+ unique: parent.unique,
+ entityType: parent.entityType,
+ }
+ : undefined;
+
+ this.#parentContext.setParent(parentEntity);
+ }
+
+ /* Notice: ancestors are based on the server "data" ancestors and are not based on the full Menu (UI) structure.
+ This will mean that any item placed in the data root will not have any ancestors. But will have a parent based on the UI structure.
+ */
+ #setAncestorData(ancestors: Array) {
+ const ancestorEntities = ancestors
+ .map((treeItem) => {
+ const entity: UmbEntityModel = {
+ unique: treeItem.unique,
+ entityType: treeItem.entityType,
+ };
+
+ return entity;
+ })
+ /* If the item is not new, the current item is the last item in the array.
+ We filter out the current item unique to handle any case where it could show up */
+ .filter((item) => item.unique !== this.#workspaceContext?.getUnique());
+
+ this.#ancestorContext.setAncestors(ancestorEntities);
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-structure-workspace-context.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-structure-workspace-context.context-token.ts
new file mode 100644
index 0000000000..8cd8e1c750
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-structure-workspace-context.context-token.ts
@@ -0,0 +1,10 @@
+import type { UmbMenuVariantStructureWorkspaceContext } from './menu-variant-structure-workspace-context.interface.js';
+import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
+
+export const UMB_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT =
+ new UmbContextToken(
+ 'UmbWorkspaceContext',
+ 'UmbMenuStructure',
+ (context): context is UmbMenuVariantStructureWorkspaceContext =>
+ 'IS_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT' in context,
+ );
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-structure-workspace-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-structure-workspace-context.interface.ts
new file mode 100644
index 0000000000..ade079e3cc
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-structure-workspace-context.interface.ts
@@ -0,0 +1,7 @@
+import type { UmbVariantStructureItemModel } from './types.js';
+import type { UmbContext } from '@umbraco-cms/backoffice/class-api';
+import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
+
+export interface UmbMenuVariantStructureWorkspaceContext extends UmbContext {
+ structure: Observable;
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts
index 8268a9accc..db207951b0 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts
@@ -1,36 +1,44 @@
import type { UmbVariantStructureItemModel } from './types.js';
-import type { UmbTreeRepository, UmbTreeRootModel } from '@umbraco-cms/backoffice/tree';
+import { UMB_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT } from './menu-variant-structure-workspace-context.context-token.js';
+import type { UmbTreeItemModel, UmbTreeRepository, UmbTreeRootModel } from '@umbraco-cms/backoffice/tree';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
-import { UMB_VARIANT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
import { UmbArrayState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
-import { UmbAncestorsEntityContext } from '@umbraco-cms/backoffice/entity';
+import { UmbAncestorsEntityContext, UmbParentEntityContext, type UmbEntityModel } from '@umbraco-cms/backoffice/entity';
+import { UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
interface UmbMenuVariantTreeStructureWorkspaceContextBaseArgs {
treeRepositoryAlias: string;
}
+// TODO: introduce base class for all menu structure workspaces to handle ancestors and parent
export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends UmbContextBase {
- // TODO: add correct interface
- #workspaceContext?: typeof UMB_VARIANT_WORKSPACE_CONTEXT.TYPE;
+ //
+ #workspaceContext?: typeof UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT.TYPE;
#args: UmbMenuVariantTreeStructureWorkspaceContextBaseArgs;
#structure = new UmbArrayState([], (x) => x.unique);
public readonly structure = this.#structure.asObservable();
#parent = new UmbObjectState(undefined);
+ /**
+ * @deprecated Will be removed in v.18: Use UMB_PARENT_ENTITY_CONTEXT instead.
+ */
public readonly parent = this.#parent.asObservable();
+ #parentContext = new UmbParentEntityContext(this);
#ancestorContext = new UmbAncestorsEntityContext(this);
+ public readonly IS_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT = true;
+
constructor(host: UmbControllerHost, args: UmbMenuVariantTreeStructureWorkspaceContextBaseArgs) {
- // TODO: set up context token
- super(host, 'UmbMenuStructureWorkspaceContext');
+ super(host, UMB_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT);
+ // 'UmbMenuStructureWorkspaceContext' is Obsolete, will be removed in v.18
+ this.provideContext('UmbMenuStructureWorkspaceContext', this);
this.#args = args;
- // TODO: Implement a Context Token that supports parentUnique, parentEntityType, entityType
- this.consumeContext(UMB_VARIANT_WORKSPACE_CONTEXT, (instance) => {
+ this.consumeContext(UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT, (instance) => {
this.#workspaceContext = instance;
this.observe(
this.#workspaceContext?.unique,
@@ -45,10 +53,12 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um
async #requestStructure() {
const isNew = this.#workspaceContext?.getIsNew();
- const uniqueObservable = isNew ? (this.#workspaceContext as any)?.parentUnique : this.#workspaceContext?.unique;
+ const uniqueObservable = isNew
+ ? this.#workspaceContext?._internal_createUnderParentEntityUnique
+ : this.#workspaceContext?.unique;
const entityTypeObservable = isNew
- ? (this.#workspaceContext as any)?.parentEntityType
- : (this.#workspaceContext as any)?.entityType;
+ ? this.#workspaceContext?._internal_createUnderParentEntityType
+ : this.#workspaceContext?.entityType;
let structureItems: Array = [];
@@ -58,7 +68,7 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um
const entityType = (await this.observe(entityTypeObservable, () => {})?.asPromise()) as string;
if (!entityType) throw new Error('Entity type is not available');
- // TODO: add correct tree variant item model
+ // TODO: introduce variant tree item model
const treeRepository = await createExtensionApiByAlias>(
this,
this.#args.treeRepositoryAlias,
@@ -79,7 +89,7 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um
const { data } = await treeRepository.requestTreeItemAncestors({ treeItem: { unique, entityType } });
if (data) {
- const ancestorItems = data.map((treeItem) => {
+ const treeItemAncestors = data.map((treeItem) => {
return {
unique: treeItem.unique,
entityType: treeItem.entityType,
@@ -93,20 +103,49 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um
};
});
- const ancestorEntities = data.map((treeItem) => {
- return {
+ structureItems.push(...treeItemAncestors);
+
+ this.#structure.setValue(structureItems);
+ this.#setParentData(structureItems);
+ this.#setAncestorData(data);
+ }
+ }
+
+ #setParentData(structureItems: Array) {
+ /* If the item is not new, the current item is the last item in the array.
+ We filter out the current item unique to handle any case where it could show up */
+ const parent = structureItems.filter((item) => item.unique !== this.#workspaceContext?.getUnique()).pop();
+
+ // TODO: remove this when the parent gets removed from the structure interface
+ this.#parent.setValue(parent);
+
+ const parentEntity = parent
+ ? {
+ unique: parent.unique,
+ entityType: parent.entityType,
+ }
+ : undefined;
+
+ this.#parentContext.setParent(parentEntity);
+ }
+
+ /* Notice: ancestors are based on the server "data" ancestors and are not based on the full Menu (UI) structure.
+ This will mean that any item placed in the data root will not have any ancestors. But will have a parent based on the UI structure.
+ */
+ #setAncestorData(ancestors: Array) {
+ const ancestorEntities = ancestors
+ .map((treeItem) => {
+ const entity: UmbEntityModel = {
unique: treeItem.unique,
entityType: treeItem.entityType,
};
- });
- this.#ancestorContext.setAncestors(ancestorEntities);
+ return entity;
+ })
+ /* If the item is not new, the current item is the last item in the array.
+ We filter out the current item unique to handle any case where it could show up */
+ .filter((item) => item.unique !== this.#workspaceContext?.getUnique());
- structureItems.push(...ancestorItems);
-
- const parent = structureItems[structureItems.length - 2];
- this.#parent.setValue(parent);
- this.#structure.setValue(structureItems);
- }
+ this.#ancestorContext.setAncestors(ancestorEntities);
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/section-sidebar-menu-with-entity-actions.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/section-sidebar-menu-with-entity-actions.element.ts
index d54c73fa52..84d46fe2a3 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/section-sidebar-menu-with-entity-actions.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/section-sidebar-menu-with-entity-actions.element.ts
@@ -1,8 +1,9 @@
import { UmbSectionSidebarMenuElement } from '../section-sidebar-menu/section-sidebar-menu.element.js';
import type { ManifestSectionSidebarAppMenuWithEntityActionsKind } from '../section-sidebar-menu/types.js';
-import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit';
+import { css, html, customElement, type PropertyValues, state } from '@umbraco-cms/backoffice/external/lit';
import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
+import { UmbParentEntityContext } from '@umbraco-cms/backoffice/entity';
const manifestWithEntityActions: UmbExtensionManifestKind = {
type: 'kind',
@@ -18,15 +19,30 @@ umbExtensionsRegistry.register(manifestWithEntityActions);
@customElement('umb-section-sidebar-menu-with-entity-actions')
export class UmbSectionSidebarMenuWithEntityActionsElement extends UmbSectionSidebarMenuElement {
+ @state()
+ _unique = null;
+
+ @state()
+ _entityType?: string | null;
+
+ #parentContext = new UmbParentEntityContext(this);
+
+ protected override updated(_changedProperties: PropertyValues): void {
+ if (_changedProperties.has('manifest')) {
+ const entityType = this.manifest?.meta.entityType;
+ this.#parentContext.setParent(entityType ? { unique: this._unique, entityType } : undefined);
+ }
+ }
+
override renderHeader() {
return html`
`;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/package.json b/src/Umbraco.Web.UI.Client/src/packages/core/package.json
index 4b957ca961..fb8160f45b 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/package.json
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/package.json
@@ -9,10 +9,10 @@
"dependencies": {
"@types/diff": "^7.0.2",
"diff": "^7.0.0",
- "uuid": "^11.1.0"
+ "uuid": "^11.1.0",
+ "@hey-api/client-fetch": "^0.10.0"
},
"devDependencies": {
- "@hey-api/client-fetch": "^0.10.0",
"@hey-api/openapi-ts": "^0.66.6"
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/picker-search-result.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/picker-search-result.element.ts
index 4437d9b0b9..fa3a4b8751 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/picker-search-result.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/picker-search-result.element.ts
@@ -68,14 +68,13 @@ export class UmbPickerSearchResultElement extends UmbLitElement {
}
#renderResultItem(item: UmbEntityModel) {
- console.log('pickableFilter', this.pickableFilter(item));
return html`
manifest.forEntityTypes.includes(item.entityType)}
.elementProps=${{
item,
- disabled: !this.pickableFilter(item),
+ disabled: this.pickableFilter ? !this.pickableFilter(item) : undefined,
}}>
`;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-action/components/property-action-menu/property-action-menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-action/components/property-action-menu/property-action-menu.element.ts
index 2827c932ce..db81eab555 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/property-action/components/property-action-menu/property-action-menu.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-action/components/property-action-menu/property-action-menu.element.ts
@@ -44,7 +44,12 @@ export class UmbPropertyActionMenuElement extends UmbLitElement {
override render() {
if (!this._actions?.length) return nothing;
return html`
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/extensions/property-editor-ui-element.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/extensions/property-editor-ui-element.interface.ts
index f96ed7be1b..836b06bfd3 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/extensions/property-editor-ui-element.interface.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/extensions/property-editor-ui-element.interface.ts
@@ -1,6 +1,8 @@
import type { UmbPropertyEditorConfigCollection } from '../config/index.js';
+import type { ManifestPropertyEditorUi } from './property-editor.extension.js';
export interface UmbPropertyEditorUiElement extends HTMLElement {
+ manifest?: ManifestPropertyEditorUi;
name?: string;
value?: unknown;
config?: UmbPropertyEditorConfigCollection;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property/property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property/property.element.ts
index b902640439..a20fe487d4 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property/property.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property/property.element.ts
@@ -334,6 +334,7 @@ export class UmbPropertyElement extends UmbLitElement {
this._element.addEventListener('change', this._onPropertyEditorChange as any as EventListener);
this._element.addEventListener('property-value-change', this._onPropertyEditorChange as any as EventListener);
// No need to observe mandatory or label, as we already do so and set it on the _element if present: [NL]
+ this._element.manifest = manifest;
this._element.mandatory = this._mandatory;
this._element.name = this._label;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-dataset/property-dataset-base-context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-dataset/property-dataset-base-context.ts
index 9fbdd695bf..446496c0cf 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-dataset/property-dataset-base-context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-dataset/property-dataset-base-context.ts
@@ -5,7 +5,7 @@ import type { UmbNameablePropertyDatasetContext } from './nameable-property-data
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { UmbArrayState, UmbBooleanState, UmbStringState } from '@umbraco-cms/backoffice/observable-api';
-import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
+import { UmbVariantContext, UmbVariantId } from '@umbraco-cms/backoffice/variant';
/**
* A base property dataset context implementation.
@@ -32,26 +32,34 @@ export class UmbPropertyDatasetContextBase
#readOnly = new UmbBooleanState(false);
public readOnly = this.#readOnly.asObservable();
+ #variantId: UmbVariantId = UmbVariantId.CreateInvariant();
+ #variantContext = new UmbVariantContext(this).inherit();
+
getEntityType() {
return this._entityType;
}
+
getUnique() {
return this._unique;
}
+
getName() {
return this.#name.getValue();
}
+
setName(name: string | undefined) {
this.#name.setValue(name);
}
+
getVariantId() {
- return UmbVariantId.CreateInvariant();
+ return this.#variantId;
}
// variant id for a specific property?
constructor(host: UmbControllerHost) {
// The controller alias, is a very generic name cause we want only one of these for this controller host.
super(host, UMB_PROPERTY_DATASET_CONTEXT);
+ this.#variantContext.setVariantId(this.getVariantId());
}
/**
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-guard-manager/variant-property-guard.manager.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-guard-manager/variant-property-guard.manager.test.ts
index e4a95e3ca2..a0f04cc985 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-guard-manager/variant-property-guard.manager.test.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-guard-manager/variant-property-guard.manager.test.ts
@@ -70,7 +70,7 @@ describe('UmbVariantPropertyGuardManager', () => {
it('is not permitted for a variant when no states', (done) => {
manager
- .isPermittedForVariantAndProperty(invariantVariant, propB)
+ .isPermittedForVariantAndProperty(invariantVariant, propB, invariantVariant)
.subscribe((value) => {
expect(value).to.be.false;
done();
@@ -82,7 +82,7 @@ describe('UmbVariantPropertyGuardManager', () => {
manager.addRule(ruleEn);
manager
- .isPermittedForVariantAndProperty(englishVariant, propB)
+ .isPermittedForVariantAndProperty(englishVariant, propB, invariantVariant)
.subscribe((value) => {
expect(value).to.be.true;
done();
@@ -94,7 +94,7 @@ describe('UmbVariantPropertyGuardManager', () => {
manager.addRule(ruleInv);
manager
- .isPermittedForVariantAndProperty(englishVariant, propB)
+ .isPermittedForVariantAndProperty(englishVariant, propB, invariantVariant)
.subscribe((value) => {
expect(value).to.be.false;
done();
@@ -106,7 +106,7 @@ describe('UmbVariantPropertyGuardManager', () => {
manager.addRule(statePropAInv);
manager
- .isPermittedForVariantAndProperty(englishVariant, propB)
+ .isPermittedForVariantAndProperty(englishVariant, propB, invariantVariant)
.subscribe((value) => {
expect(value).to.be.false;
done();
@@ -117,7 +117,7 @@ describe('UmbVariantPropertyGuardManager', () => {
manager.addRule(statePropAInv);
manager
- .isPermittedForVariantAndProperty(invariantVariant, propB)
+ .isPermittedForVariantAndProperty(invariantVariant, propB, invariantVariant)
.subscribe((value) => {
expect(value).to.be.false;
done();
@@ -129,7 +129,7 @@ describe('UmbVariantPropertyGuardManager', () => {
manager.addRule(statePropAInv);
manager
- .isPermittedForVariantAndProperty(englishVariant, propA)
+ .isPermittedForVariantAndProperty(englishVariant, propA, invariantVariant)
.subscribe((value) => {
expect(value).to.be.false;
done();
@@ -141,7 +141,7 @@ describe('UmbVariantPropertyGuardManager', () => {
manager.addRule(rulePlain);
manager
- .isPermittedForVariantAndProperty(englishVariant, propB)
+ .isPermittedForVariantAndProperty(englishVariant, propB, invariantVariant)
.subscribe((value) => {
expect(value).to.be.true;
done();
@@ -154,7 +154,7 @@ describe('UmbVariantPropertyGuardManager', () => {
manager.addRule(ruleNoEn);
manager
- .isPermittedForVariantAndProperty(englishVariant, propB)
+ .isPermittedForVariantAndProperty(englishVariant, propB, invariantVariant)
.subscribe((value) => {
expect(value).to.be.false;
done();
@@ -166,7 +166,7 @@ describe('UmbVariantPropertyGuardManager', () => {
manager.addRule(ruleNoPlain);
manager
- .isPermittedForVariantAndProperty(englishVariant, propB)
+ .isPermittedForVariantAndProperty(englishVariant, propB, invariantVariant)
.subscribe((value) => {
expect(value).to.be.false;
done();
@@ -179,7 +179,7 @@ describe('UmbVariantPropertyGuardManager', () => {
manager.addRule(ruleEn);
manager
- .isPermittedForVariantAndProperty(englishVariant, propB)
+ .isPermittedForVariantAndProperty(englishVariant, propB, invariantVariant)
.subscribe((value) => {
expect(value).to.be.false;
done();
@@ -193,7 +193,7 @@ describe('UmbVariantPropertyGuardManager', () => {
manager.addRule(ruleNoEn);
manager
- .isPermittedForVariantAndProperty(englishVariant, propB)
+ .isPermittedForVariantAndProperty(englishVariant, propB, invariantVariant)
.subscribe((value) => {
expect(value).to.be.false;
done();
@@ -206,7 +206,7 @@ describe('UmbVariantPropertyGuardManager', () => {
manager.addRule(rulePlain);
manager
- .isPermittedForVariantAndProperty(englishVariant, propB)
+ .isPermittedForVariantAndProperty(englishVariant, propB, invariantVariant)
.subscribe((value) => {
expect(value).to.be.false;
done();
@@ -220,7 +220,7 @@ describe('UmbVariantPropertyGuardManager', () => {
manager.addRule(statePropAInv);
manager
- .isPermittedForVariantAndProperty(invariantVariant, propA)
+ .isPermittedForVariantAndProperty(invariantVariant, propA, invariantVariant)
.subscribe((value) => {
expect(value).to.be.false;
done();
@@ -234,7 +234,7 @@ describe('UmbVariantPropertyGuardManager', () => {
manager.addRule(stateNoPropAInv);
manager
- .isPermittedForVariantAndProperty(invariantVariant, propA)
+ .isPermittedForVariantAndProperty(invariantVariant, propA, invariantVariant)
.subscribe((value) => {
expect(value).to.be.false;
done();
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-guard-manager/variant-property-guard.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-guard-manager/variant-property-guard.manager.ts
index fb2612f537..49a0a4c922 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-guard-manager/variant-property-guard.manager.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-guard-manager/variant-property-guard.manager.ts
@@ -5,21 +5,39 @@ import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
import { UmbGuardManagerBase } from '@umbraco-cms/backoffice/utils';
export interface UmbVariantPropertyGuardRule extends UmbPropertyGuardRule {
+ /**
+ * @description - The variant id of the property.
+ * @type {UmbVariantId}
+ * @memberof UmbVariantPropertyGuardRule
+ */
variantId?: UmbVariantId;
+
+ /**
+ * @description - The variant id of the dataset. This is used to determine if the rule applies to the current dataset.
+ * @type {UmbVariantId}
+ * @memberof UmbVariantPropertyGuardRule
+ */
+ datasetVariantId?: UmbVariantId;
}
/**
*
- * @param rule
- * @param variantId
- * @param propertyType
+ * @param {UmbVariantPropertyGuardRule} rule - The rule to check.
+ * @param {UmbVariantId} variantId - The property variant id to check.
+ * @param {UmbReferenceByUnique} propertyType - The property type to check.
+ * @param {UmbVariantId} datasetVariantId - The variant id of the dataset. This is used to determine if the rule applies to the current dataset.
+ * @returns {boolean} - Returns true if the rule applies to the given conditions.
*/
-function findRule(rule: UmbVariantPropertyGuardRule, variantId: UmbVariantId, propertyType: UmbReferenceByUnique) {
+function findRule(
+ rule: UmbVariantPropertyGuardRule,
+ variantId: UmbVariantId,
+ propertyType: UmbReferenceByUnique,
+ datasetVariantId: UmbVariantId,
+) {
return (
- (rule.variantId?.compare(variantId) && rule.propertyType?.unique === propertyType.unique) ||
- (rule.variantId === undefined && rule.propertyType?.unique === propertyType.unique) ||
- (rule.variantId?.compare(variantId) && rule.propertyType === undefined) ||
- (rule.variantId === undefined && rule.propertyType === undefined)
+ (rule.variantId === undefined || rule.variantId.culture === variantId.culture) &&
+ (rule.propertyType === undefined || rule.propertyType.unique === propertyType.unique) &&
+ (rule.datasetVariantId === undefined || rule.datasetVariantId.culture === datasetVariantId.culture)
);
}
@@ -34,33 +52,54 @@ export class UmbVariantPropertyGuardManager extends UmbGuardManagerBase} - Returns an observable that emits true if the variant and propertyType is permitted, false otherwise.
* @memberof UmbVariantPropertyGuardManager
*/
- isPermittedForVariantAndProperty(variantId: UmbVariantId, propertyType: UmbReferenceByUnique): Observable {
- return this._rules.asObservablePart((rules) => this.#resolvePermission(rules, variantId, propertyType));
+ isPermittedForVariantAndProperty(
+ variantId: UmbVariantId,
+ propertyType: UmbReferenceByUnique,
+ datasetVariantId: UmbVariantId,
+ ): Observable {
+ return this._rules.asObservablePart((rules) =>
+ this.#resolvePermission(rules, variantId, propertyType, datasetVariantId),
+ );
}
/**
* Checks if the variant and propertyType is permitted.
* @param {UmbVariantId} variantId - The variant id to check.
* @param {UmbReferenceByUnique} propertyType - The property type to check.
+ * @param {UmbVariantId} datasetVariantId - The dataset variant id to check.
* @returns {boolean} - Returns true if the variant and propertyType is permitted, false otherwise.
* @memberof UmbVariantPropertyGuardManager
*/
- getIsPermittedForVariantAndProperty(variantId: UmbVariantId, propertyType: UmbReferenceByUnique): boolean {
- return this.#resolvePermission(this._rules.getValue(), variantId, propertyType);
+ getIsPermittedForVariantAndProperty(
+ variantId: UmbVariantId,
+ propertyType: UmbReferenceByUnique,
+ datasetVariantId: UmbVariantId,
+ ): boolean {
+ return this.#resolvePermission(this._rules.getValue(), variantId, propertyType, datasetVariantId);
}
#resolvePermission(
rules: UmbVariantPropertyGuardRule[],
variantId: UmbVariantId,
propertyType: UmbReferenceByUnique,
+ datasetVariantId: UmbVariantId,
) {
- if (rules.filter((x) => x.permitted === false).some((rule) => findRule(rule, variantId, propertyType))) {
+ if (
+ rules
+ .filter((x) => x.permitted === false)
+ .some((rule) => findRule(rule, variantId, propertyType, datasetVariantId))
+ ) {
return false;
}
- if (rules.filter((x) => x.permitted === true).some((rule) => findRule(rule, variantId, propertyType))) {
+ if (
+ rules
+ .filter((x) => x.permitted === true)
+ .some((rule) => findRule(rule, variantId, propertyType, datasetVariantId))
+ ) {
return true;
}
return this._fallback;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-cloner/property-value-clone.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-cloner/property-value-clone.controller.ts
index 581fa269ab..21a3faadc9 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-cloner/property-value-clone.controller.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-cloner/property-value-clone.controller.ts
@@ -50,6 +50,8 @@ export class UmbPropertyValueCloneController extends UmbControllerBase {
return incomingProperty;
}
+ (api as any).manifest = manifest;
+
let clonedProperty = incomingProperty;
if (api.cloneValue) {
@@ -86,6 +88,8 @@ export class UmbPropertyValueCloneController extends UmbControllerBase {
return incomingProperty;
}
+ (api as any).manifest = manifest;
+
if (api.processValues) {
return (
(await api.processValues(incomingProperty, async (properties) => {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-builder.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-builder.controller.ts
index a95f45d5c1..778a921acf 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-builder.controller.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-builder.controller.ts
@@ -54,9 +54,18 @@ export class UmbPropertyValuePresetBuilderController<
// Find a preset for this editor alias:
const manifests = umbExtensionsRegistry.getByTypeAndFilter('propertyValuePreset', filter);
- const apis = (await Promise.all(manifests.map((x) => createExtensionApi(this, x)))).filter(
- (x) => x !== undefined,
- ) as Array;
+ const apis = (
+ await Promise.all(
+ manifests.map((x) =>
+ createExtensionApi(this, x).then((x) => {
+ if (x) {
+ (x as any).manifest = x;
+ }
+ return x;
+ }),
+ ),
+ )
+ ).filter((x) => x !== undefined) as Array;
const result = await this._generatePropertyValues(apis, propertyType);
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/detail-data-source.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/detail-data-source.interface.ts
index 5b92e4de68..78895a6c88 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/detail-data-source.interface.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/detail-data-source.interface.ts
@@ -1,5 +1,6 @@
import type { UmbDataSourceResponse } from '../data-source-response.interface.js';
import type { UmbReadDetailDataSource } from './read/index.js';
+import type { UmbDeepPartialObject } from '@umbraco-cms/backoffice/utils';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export interface UmbDetailDataSourceConstructor<
@@ -10,7 +11,7 @@ export interface UmbDetailDataSourceConstructor<
}
export interface UmbDetailDataSource extends UmbReadDetailDataSource {
- createScaffold(preset?: Partial): Promise>;
+ createScaffold(preset?: UmbDeepPartialObject): Promise>;
create(data: DetailType, parentUnique: string | null): Promise>;
update(data: DetailType): Promise>;
delete(unique: string): Promise>;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/detail-repository-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/detail-repository-base.ts
index cad291bbcc..418d95f0f4 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/detail-repository-base.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/detail-repository-base.ts
@@ -7,6 +7,7 @@ import type { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import type { UmbDetailStore } from '@umbraco-cms/backoffice/store';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
+import type { UmbDeepPartialObject } from '@umbraco-cms/backoffice/utils';
export abstract class UmbDetailRepositoryBase<
DetailModelType extends UmbEntityModel,
@@ -33,22 +34,23 @@ export abstract class UmbDetailRepositoryBase<
this.detailDataSource = new detailSource(host) as UmbDetailDataSourceType;
// TODO: ideally no preventTimeouts here.. [NL]
- this.#init = Promise.all([
- this.consumeContext(detailStoreContextAlias, (instance) => {
- if (instance) {
- this.#detailStore = instance;
- }
- }).asPromise({ preventTimeout: true }),
- ]);
+ this.#init = this.consumeContext(detailStoreContextAlias, (instance) => {
+ this.#detailStore = instance;
+ })
+ .asPromise({ preventTimeout: true })
+ // Ignore the error, we can assume that the flow was stopped (asPromise failed), but it does not mean that the consumption was not successful.
+ .catch(() => undefined);
}
/**
* Creates a scaffold
- * @param {Partial} [preset]
+ * @param {UmbDeepPartialObject} [preset]
* @returns {*}
* @memberof UmbDetailRepositoryBase
*/
- async createScaffold(preset?: Partial): Promise> {
+ async createScaffold(
+ preset?: UmbDeepPartialObject,
+ ): Promise> {
return this.detailDataSource.createScaffold(preset);
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository-base.ts
index bae7c8f487..1f502778cd 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository-base.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository-base.ts
@@ -4,6 +4,7 @@ import type { UmbItemRepository } from './item-repository.interface.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbItemStore } from '@umbraco-cms/backoffice/store';
import type { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
+import { of } from '@umbraco-cms/backoffice/external/rxjs';
export class UmbItemRepositoryBase
extends UmbRepositoryBase
@@ -22,10 +23,11 @@ export class UmbItemRepositoryBase
this.#itemSource = new itemSource(host);
this._init = this.consumeContext(itemStoreContextAlias, (instance) => {
- if (instance) {
- this._itemStore = instance as UmbItemStore;
- }
- }).asPromise({ preventTimeout: true });
+ this._itemStore = instance;
+ })
+ .asPromise({ preventTimeout: true })
+ // Ignore the error, we can assume that the flow was stopped (asPromise failed), but it does not mean that the consumption was not successful.
+ .catch(() => undefined);
}
/**
@@ -36,20 +38,25 @@ export class UmbItemRepositoryBase
*/
async requestItems(uniques: Array) {
if (!uniques) throw new Error('Uniques are missing');
- await this._init;
+ try {
+ await this._init;
+ } catch {
+ return {};
+ }
- const { data, error: _error } = await this.#itemSource.getItems(uniques);
+ const { data, error } = await this.#itemSource.getItems(uniques);
if (!this._itemStore) {
// If store is gone, then we are most likely in a disassembled state.
return {};
}
- const error: any = _error;
+
if (data) {
- this._itemStore!.appendItems(data);
+ this._itemStore.appendItems(data);
}
- return { data, error, asObservable: () => this._itemStore!.items(uniques) };
+ // TODO: Fix the type of error, it should be UmbApiError, but currently it is any.
+ return { data, error: error as any, asObservable: () => this._itemStore!.items(uniques) };
}
/**
@@ -59,7 +66,17 @@ export class UmbItemRepositoryBase
* @memberof UmbItemRepositoryBase
*/
async items(uniques: Array) {
- await this._init;
- return this._itemStore!.items(uniques);
+ try {
+ await this._init;
+ } catch {
+ return undefined;
+ }
+
+ if (!this._itemStore) {
+ // If store is gone, then we are most likely in a disassembled state.
+ return of([]);
+ }
+
+ return this._itemStore.items(uniques);
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository.interface.ts
index c4a12f83a9..1c4bcfe070 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository.interface.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository.interface.ts
@@ -8,5 +8,5 @@ export interface UmbItemRepository extends UmbApi {
error?: UmbProblemDetails | undefined;
asObservable?: () => Observable>;
}>;
- items: (uniques: string[]) => Promise>>;
+ items: (uniques: string[]) => Promise> | undefined>;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-server-data-source-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-server-data-source-base.ts
index 02df1f5429..8e3f1eeea5 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-server-data-source-base.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-server-data-source-base.ts
@@ -1,10 +1,11 @@
+import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbDataSourceResponse } from '../data-source-response.interface.js';
import type { UmbItemDataSource } from './item-data-source.interface.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { tryExecute } from '@umbraco-cms/backoffice/resources';
export interface UmbItemServerDataSourceBaseArgs {
- getItems: (uniques: Array) => Promise>>;
+ getItems?: (uniques: Array) => Promise>>;
mapper: (item: ServerItemType) => ClientItemType;
}
@@ -14,10 +15,10 @@ export interface UmbItemServerDataSourceBaseArgs
+ extends UmbControllerBase
implements UmbItemDataSource
{
- #host: UmbControllerHost;
- #getItems: (uniques: Array) => Promise>>;
+ #getItems?: (uniques: Array) => Promise>>;
#mapper: (item: ServerItemType) => ClientItemType;
/**
@@ -27,7 +28,7 @@ export abstract class UmbItemServerDataSourceBase) {
- this.#host = host;
+ super(host);
this.#getItems = args.getItems;
this.#mapper = args.mapper;
}
@@ -39,14 +40,17 @@ export abstract class UmbItemServerDataSourceBase) {
+ if (!this.#getItems) throw new Error('getItems is not implemented');
if (!uniques) throw new Error('Uniques are missing');
- const { data, error } = await tryExecute(this.#host, this.#getItems(uniques));
- if (data) {
- const items = data.map((item) => this.#mapper(item));
- return { data: items };
- }
+ const { data, error } = await tryExecute(this, this.#getItems(uniques));
- return { error };
+ return { data: this._getMappedItems(data), error };
+ }
+
+ protected _getMappedItems(items: Array | undefined): Array | undefined {
+ if (!items) return undefined;
+ if (!this.#mapper) throw new Error('Mapper is not implemented');
+ return items.map((item) => this.#mapper(item));
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-details.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-details.manager.ts
index 72663e8ad0..7d31d0ad83 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-details.manager.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-details.manager.ts
@@ -33,10 +33,10 @@ export class UmbRepositoryDetailsManager
return this.#init;
}
- #uniques = new UmbArrayState([], (x) => x);
+ #uniques = new UmbArrayState([], (x) => x);
uniques = this.#uniques.asObservable();
- #entries = new UmbArrayState([], (x) => x.unique);
+ #entries = new UmbArrayState([], (x) => x.unique);
entries = this.#entries.asObservable();
#statuses = new UmbArrayState([], (x) => x.unique);
@@ -77,11 +77,15 @@ export class UmbRepositoryDetailsManager
this.uniques,
(uniques) => {
// remove entries based on no-longer existing uniques:
- const removedEntries = this.#entries.getValue().filter((entry) => !uniques.includes(entry.unique));
- this.#entries.remove(removedEntries);
+ const removedEntries = this.#entries
+ .getValue()
+ .filter((entry) => !uniques.includes(entry.unique))
+ .map((x) => x.unique);
+
this.#statuses.remove(removedEntries);
+ this.#entries.remove(removedEntries);
removedEntries.forEach((entry) => {
- this.removeUmbControllerByAlias('observeEntry_' + entry.unique);
+ this.removeUmbControllerByAlias('observeEntry_' + entry);
});
this.#requestNewDetails();
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/api-interceptor.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/api-interceptor.controller.ts
index 7e84e24d0c..00c5a524c4 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/api-interceptor.controller.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/api-interceptor.controller.ts
@@ -16,6 +16,7 @@ export class UmbApiInterceptorController extends UmbControllerBase {
this.addAuthResponseInterceptor(client);
this.addUmbGeneratedResourceInterceptor(client);
this.addUmbNotificationsInterceptor(client);
+ this.addForbiddenResponseInterceptor(client);
this.addErrorInterceptor(client);
}
@@ -38,6 +39,24 @@ export class UmbApiInterceptorController extends UmbControllerBase {
});
}
+ /**
+ * Interceptor which checks responses for 403 errors and displays them as a notification.
+ * @param {umbHttpClient} client The OpenAPI client to add the interceptor to. It can be any client supporting Response and Request interceptors.
+ * @internal
+ */
+ addForbiddenResponseInterceptor(client: typeof umbHttpClient) {
+ client.interceptors.response.use(async (response: Response) => {
+ if (response.status === 403) {
+ const headline = 'Permission Denied';
+ const message = 'You do not have the necessary permissions to complete the requested action. If you believe this is in error, please reach out to your administrator.';
+
+ this.#peekError(headline, message, null);
+ }
+
+ return response;
+ });
+ }
+
/**
* Interceptor which checks responses for the Umb-Generated-Resource header and replaces the value into the response body.
* @param {umbHttpClient} client The OpenAPI client to add the interceptor to. It can be any client supporting Response and Request interceptors.
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/data-api/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/data-api/types.ts
new file mode 100644
index 0000000000..dfade83718
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/data-api/types.ts
@@ -0,0 +1,3 @@
+export interface UmbDataApiResponse {
+ data: ResponseType['data'];
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/index.ts
index e1e1756111..04e74539c4 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/index.ts
@@ -1,12 +1,9 @@
export * from './api-interceptor.controller.js';
-export * from './resource.controller.js';
-export * from './try-execute.controller.js';
-export * from './tryExecute.function.js';
-export * from './tryExecuteAndNotify.function.js';
-export * from './tryXhrRequest.function.js';
-export * from './extractUmbNotificationColor.function.js';
-export * from './extractUmbColorVariable.function.js';
-export * from './isUmbNotifications.function.js';
export * from './apiTypeValidators.function.js';
+export * from './extractUmbColorVariable.function.js';
+export * from './extractUmbNotificationColor.function.js';
+export * from './isUmbNotifications.function.js';
+export * from './resource.controller.js';
+export * from './try-execute/index.js';
export * from './umb-error.js';
export type * from './types.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/batch-try-execute.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/batch-try-execute.function.ts
new file mode 100644
index 0000000000..47a810ceb8
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/batch-try-execute.function.ts
@@ -0,0 +1,17 @@
+import { tryExecute } from './tryExecute.function.js';
+import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+
+/**
+ * Batches promises and returns a promise that resolves to an array of results
+ * @param {UmbControllerHost} host - The host to use for the request and where notifications will be shown
+ * @param {Array>} chunks - The array of chunks to process
+ * @param {(chunk: Array) => Promise} callback - The function to call for each chunk
+ * @returns {Promise[]>} - A promise that resolves to an array of results
+ */
+export function batchTryExecute(
+ host: UmbControllerHost,
+ chunks: Array>,
+ callback: (chunk: Array) => Promise,
+): Promise[]> {
+ return Promise.allSettled(chunks.map((chunk) => tryExecute(host, callback(chunk), { disableNotifications: true })));
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/index.ts
new file mode 100644
index 0000000000..18d2ee9f97
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/index.ts
@@ -0,0 +1,5 @@
+export * from './batch-try-execute.function.js';
+export * from './try-execute.controller.js';
+export * from './tryExecute.function.js';
+export * from './tryExecuteAndNotify.function.js';
+export * from './tryXhrRequest.function.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/try-execute.controller.ts
similarity index 86%
rename from src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute.controller.ts
rename to src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/try-execute.controller.ts
index 353c081138..afca34ad55 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute.controller.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/try-execute.controller.ts
@@ -1,7 +1,7 @@
-import { isProblemDetailsLike } from './apiTypeValidators.function.js';
-import { UmbResourceController } from './resource.controller.js';
-import type { UmbApiResponse, UmbTryExecuteOptions } from './types.js';
-import { UmbApiError, UmbCancelError } from './umb-error.js';
+import { isProblemDetailsLike } from '../apiTypeValidators.function.js';
+import { UmbResourceController } from '../resource.controller.js';
+import type { UmbApiResponse, UmbTryExecuteOptions } from '../types.js';
+import { UmbApiError, UmbCancelError } from '../umb-error.js';
export class UmbTryExecuteController extends UmbResourceController {
#abortSignal?: AbortSignal;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/tryExecute.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/tryExecute.function.ts
similarity index 93%
rename from src/Umbraco.Web.UI.Client/src/packages/core/resources/tryExecute.function.ts
rename to src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/tryExecute.function.ts
index 07c4b26cfd..7deca7c0e4 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/tryExecute.function.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/tryExecute.function.ts
@@ -1,5 +1,5 @@
+import type { UmbApiResponse, UmbTryExecuteOptions } from '../types.js';
import { UmbTryExecuteController } from './try-execute.controller.js';
-import type { UmbApiResponse, UmbTryExecuteOptions } from './types.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
/**
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/tryExecuteAndNotify.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/tryExecuteAndNotify.function.ts
similarity index 96%
rename from src/Umbraco.Web.UI.Client/src/packages/core/resources/tryExecuteAndNotify.function.ts
rename to src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/tryExecuteAndNotify.function.ts
index 1708d759c0..6bddf902de 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/tryExecuteAndNotify.function.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/tryExecuteAndNotify.function.ts
@@ -1,5 +1,5 @@
+import type { UmbApiResponse } from '../types.js';
import { UmbTryExecuteController } from './try-execute.controller.js';
-import type { UmbApiResponse } from './types.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbDeprecation } from '@umbraco-cms/backoffice/utils';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/tryXhrRequest.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/tryXhrRequest.function.ts
similarity index 94%
rename from src/Umbraco.Web.UI.Client/src/packages/core/resources/tryXhrRequest.function.ts
rename to src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/tryXhrRequest.function.ts
index a2e96d5d40..2be9e84fa7 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/tryXhrRequest.function.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/try-execute/tryXhrRequest.function.ts
@@ -1,8 +1,8 @@
+import { UmbCancelablePromise } from '../cancelable-promise.js';
+import { UmbApiError } from '../umb-error.js';
+import { isProblemDetailsLike } from '../apiTypeValidators.function.js';
+import type { UmbApiResponse, XhrRequestOptions } from '../types.js';
import { UmbTryExecuteController } from './try-execute.controller.js';
-import { UmbCancelablePromise } from './cancelable-promise.js';
-import { UmbApiError } from './umb-error.js';
-import { isProblemDetailsLike } from './apiTypeValidators.function.js';
-import type { UmbApiResponse, XhrRequestOptions } from './types.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { umbHttpClient } from '@umbraco-cms/backoffice/http-client';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/types.ts
index fd7413e8f1..6a438f0bab 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/types.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/types.ts
@@ -1,4 +1,5 @@
import type { UmbApiError, UmbCancelError, UmbError } from './umb-error.js';
+export type * from './data-api/types.js';
export interface XhrRequestOptions extends UmbTryExecuteOptions {
baseUrl?: string;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/route/router-slot.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/route/router-slot.element.ts
index b440cc2caf..8b9886f5e8 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/router/route/router-slot.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/route/router-slot.element.ts
@@ -95,9 +95,13 @@ export class UmbRouterSlotElement extends UmbLitElement {
}
override disconnectedCallback() {
- super.disconnectedCallback();
window.removeEventListener('navigationsuccess', this._onNavigationChanged);
this.#listening = false;
+
+ // Close modals opened by this router slot.
+ this.#routeContext._internal_modalRouterChanged(undefined);
+
+ super.disconnectedCallback();
}
protected override firstUpdated(_changedProperties: PropertyValueMap | Map): void {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/router-slot/model.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/router-slot/model.ts
index 72e669027c..4b9892946a 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/router/router-slot/model.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/router-slot/model.ts
@@ -75,6 +75,9 @@ export interface IRedirectRoute extends IRouteBase {
// The paths the route should redirect to. Can either be relative or absolute.
redirectTo: string;
+ // First redirect when the routes appears stable. Delaying the redirect so other routes get the change to resolve first.
+ awaitStability?: boolean;
+
// Whether the query should be preserved when redirecting.
preserveQuery?: boolean;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/router-slot/router-slot.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/router-slot/router-slot.ts
index 3f3a0b9a8d..de09de1d2a 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/router/router-slot/router-slot.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/router-slot/router-slot.ts
@@ -176,6 +176,8 @@ export class RouterSlot extends HTMLElement implements IRouter
* Tears down the element.
*/
override disconnectedCallback() {
+ this._setParent(null);
+ this._cancelNavigation?.();
this.detachListeners();
}
@@ -314,6 +316,24 @@ export class RouterSlot extends HTMLElement implements IRouter
}
}
+ private getRedirectDelay() {
+ if ('connection' in navigator) {
+ const connection =
+ navigator.connection || (navigator as any).mozConnection || (navigator as any).webkitConnection;
+
+ switch (connection.effectiveType) {
+ case 'slow-2g':
+ case '2g':
+ return 1200;
+ case '3g':
+ return 800;
+ case '4g':
+ return 200;
+ }
+ }
+ return 400;
+ }
+
/**
* Loads a new path based on the routes.
* Returns true if a navigation was made to a new page.
@@ -387,6 +407,19 @@ export class RouterSlot extends HTMLElement implements IRouter
// Redirect if necessary
if (isRedirectRoute(route)) {
cleanup();
+ if (route.awaitStability === true) {
+ // await until browser is done loading, based on a guess:
+ const delay = this.getRedirectDelay();
+ await new Promise((resolve) => setTimeout(resolve, delay));
+ if (navigationInvalidated) {
+ return cancel();
+ }
+ }
+ // Check if the current route is still matching the browser URL.:
+ if (!window.location.href.includes(this.constructAbsolutePath(''))) {
+ // If the parent is active, we should not redirect.
+ return cancel();
+ }
handleRedirect(this, route);
return false;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-default.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-default.element.ts
index 4bacf832bd..7161de8a2a 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-default.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-default.element.ts
@@ -83,6 +83,10 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio
extensionsWithElement.map(async (extensionController) => {
const api = await createExtensionApi(this, extensionController.manifest);
+ if (api) {
+ (api as any).manifest = extensionController.manifest;
+ }
+
return {
path:
api?.getPath?.() ||
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts
index 64c5db6c34..2d797072c7 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts
@@ -46,7 +46,7 @@ describe('UmbSorterController', () => {
beforeEach(async () => {
element = await fixture(html` `);
- await aTimeout(10);
+ //await aTimeout(10);
});
it('is defined with its own instance', () => {
@@ -104,8 +104,8 @@ describe('UmbSorterController', () => {
expect(element.sorter).to.have.property('notifyDisallowed').that.is.a('function');
});
- it('has a notifyRequestDrop method', () => {
- expect(element.sorter).to.have.property('notifyRequestDrop').that.is.a('function');
+ it('has a notifyRequestMove method', () => {
+ expect(element.sorter).to.have.property('notifyRequestMove').that.is.a('function');
});
it('has a destroy method', () => {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts
index 9382acd2eb..8b7546a4fc 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts
@@ -7,8 +7,9 @@ const autoScrollSpeed = 16;
/**
*
- * @param el
- * @param includeSelf
+ * @param {Element} el - The element to check for ability to scroll
+ * @param {Boolean} includeSelf - If true, the element itself will be included in the check
+ * @returns {Element | null}
*/
function getParentScrollElement(el: Element, includeSelf: boolean) {
if (!el || !el.getBoundingClientRect) return null;
@@ -45,8 +46,8 @@ function getParentScrollElement(el: Element, includeSelf: boolean) {
/**
*
- * @param element
- * @param ignorerSelectors
+ * @param {HTMLElement} element - The element to check
+ * @param {string} ignorerSelectors - A comma separated list of selectors to ignore
*/
function setupIgnorerElements(element: HTMLElement, ignorerSelectors: string) {
const selectors = ignorerSelectors.split(',');
@@ -57,8 +58,8 @@ function setupIgnorerElements(element: HTMLElement, ignorerSelectors: string) {
}
/**
*
- * @param element
- * @param ignorerSelectors
+ * @param {HTMLElement} element - The element to check
+ * @param {string} ignorerSelectors - A comma separated list of selectors to ignore
*/
function destroyIgnorerElements(element: HTMLElement, ignorerSelectors: string) {
const selectors = ignorerSelectors.split(',');
@@ -69,7 +70,7 @@ function destroyIgnorerElements(element: HTMLElement, ignorerSelectors: string)
}
/**
*
- * @param element
+ * @param {Element} element - The element to check
*/
function setupPreventEvent(element: Element) {
(element as HTMLElement).draggable = false;
@@ -77,7 +78,7 @@ function setupPreventEvent(element: Element) {
}
/**
*
- * @param element
+ * @param {Element} element - The element to check
*/
function destroyPreventEvent(element: Element) {
(element as HTMLElement).draggable = false;
@@ -97,7 +98,7 @@ export type UmbSorterResolvePlacementArgs = {
/**
* This callback is executed when an item is moved from another container to this container.
*/
- onContainerChange?: (argument: { item: T; model: Array; from: UmbSorterController }) => void;
+ onContainerChange?: (argument: {
+ item: T;
+ model: Array;
+ from: UmbSorterController | undefined;
+ }) => void;
onEnd?: (argument: { item: T; element: ElementType }) => void;
itemHasNestedContainersResolver?: (element: HTMLElement) => boolean;
/**
@@ -190,6 +195,18 @@ type INTERNAL_UmbSorterConfig = {
* Callback when user tries to move an item from another Sorter to this Sorter, return true or false to allow or disallow the move.
*/
onRequestMove?: (argument: { item: T }) => boolean;
+ /**
+ * Callback when user tries to drop an item from another window/tab/source.
+ */
+ onRequestDrop?: (argument: { unique: string }) => Promise;
+ /**
+ * Callback when user tries to remove an item from another Sorter to this Sorter, return true or false to allow or disallow the move.
+ */
+ requestExternalRemove?: (argument: { item: T }) => Promise;
+ /**
+ * Callback when user tries to remove an item from another Sorter to this Sorter, return true or false to allow or disallow the move.
+ */
+ requestExternalInsert?: (argument: { item: T }) => Promise;
/**
* This callback is executed when an item is hovered within this container.
* The callback should return true if the item should be placed after the hovered item, or false if it should be placed before the hovered item.
@@ -337,7 +354,7 @@ export class UmbSorterController}
+ * @returns {Array} The model of this sorter.
* @memberof UmbSorterController
*/
getModel(): Array {
@@ -376,6 +393,7 @@ export class UmbSorterController {
if (this.#isConnected === false) return;
+ if (this.#containerElement) {
+ // This can happen, so no need to show an error as it seems to be happening in some cases. We will just reject. [NL]
+ //console.error('Container element already initialized', this.#containerElement);
+ return;
+ }
- const containerEl =
- (this.#config.containerSelector
- ? this.#host.shadowRoot!.querySelector(this.#config.containerSelector)
- : this.#host) ?? this.#host;
+ const containerEl = this.#config.containerSelector
+ ? this.#host.shadowRoot!.querySelector(this.#config.containerSelector)
+ : this.#host;
+
+ if (!containerEl) {
+ if (this.#config.containerSelector) {
+ throw new Error(
+ `Sorter could not find the container element, using this query selector '${this.#config.containerSelector}'.`,
+ );
+ } else {
+ throw new Error('Sorter could not get its host element.');
+ }
+ return;
+ }
this.#containerElement = containerEl as HTMLElement;
this.#useContainerShadowRoot = this.#containerElement === this.#host;
@@ -405,6 +438,7 @@ export class UmbSorterController)) {
+ UmbSorterController.activeSorter = undefined;
+ if (UmbSorterController.activeElement) {
+ this.#handleDragEnd();
+ }
+ }
+
+ if (UmbSorterController.dropSorter === (this as unknown as UmbSorterController)) {
+ // If we are the drop sorter, we can now remove out self to get into pure Native Drag n' drop.
+ UmbSorterController.dropSorter = undefined;
+ }
+
+ if (UmbSorterController.lastIndicationSorter === (this as unknown as UmbSorterController)) {
+ // If we are the lastIndicationSorter, we can now remove out self to get into pure Native Drag n' drop.
+ UmbSorterController.lastIndicationSorter = undefined;
+ }
+
this.#observer.disconnect();
+
+ // For auto scroller:
+ this.#scrollElement = null;
+
if (this.#containerElement) {
// Only look at the shadowRoot if the containerElement is host.
const containerElement = this.#useContainerShadowRoot
@@ -429,15 +484,75 @@ export class UmbSorterController this.destroyItem(item));
}
- #itemDraggedOver = (e: DragEvent) => {
- //if(UmbSorterController.activeSorter === this) return;
+ async #obtainIncomingItem(e: DragEvent) {
+ if (
+ !UmbSorterController.dropSorter &&
+ e.dataTransfer?.types.includes('text/umb-sorter-identifier#' + this.identifier.toString())
+ ) {
+ // If we have no drop-sorter, and we share the same identifier, then we like to accept this drag.
+ const activeType: string | undefined = e.dataTransfer?.types.find((x) =>
+ x.startsWith('text/umb-sorter-item-unique#'),
+ );
+ if (activeType) {
+ const activeUnique = activeType.split('#')?.[1];
+
+ let activeItem = this.#model.find((x) => this.#config.getUniqueOfModel(x) === activeUnique);
+ if (activeItem) {
+ UmbSorterController.activeSorter = this as unknown as UmbSorterController;
+ }
+ // test if unique is already in the model:
+ if (!activeItem) {
+ // Find the active item:
+ activeItem = await this.#config.onRequestDrop?.({ unique: activeUnique });
+ UmbSorterController.activeSorter = undefined; // Important as we use this to know if we can remove the item via these Sorter references or if it is a Native Drop.
+ if (!activeItem) {
+ // Then we assume this item was not part of this sorters scope. This is the spot where inserting a new item from dataTransfer could be implemented.
+ return false;
+ }
+ }
+
+ if (this.hasItem(activeUnique)) {
+ return false;
+ }
+
+ e.dataTransfer.setData('text/umb-sorter-item-accepted', 'true');
+
+ // Set states:
+ UmbSorterController.activeItem = activeItem;
+ UmbSorterController.activeElement = undefined;
+ UmbSorterController.activeDragElement = undefined;
+ UmbSorterController.dropSorter = this as unknown as UmbSorterController;
+ UmbSorterController.originalIndex = undefined;
+ UmbSorterController.originalSorter = undefined;
+
+ //UmbSorterController.activeSorter = this as unknown as UmbSorterController;
+ //UmbSorterController.originalSorter = this as unknown as UmbSorterController;
+ window.addEventListener('mouseup', this.#handleMouseUp);
+ window.addEventListener('mouseout', this.#handleMouseUp);
+ window.addEventListener('mouseleave', this.#handleMouseUp);
+ window.addEventListener('mousemove', this.#handleMouseMove);
+
+ if (!this.#scrollElement) {
+ this.#scrollElement = getParentScrollElement(this.#containerElement, true);
+ }
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ #itemDraggedOver = async (e: DragEvent) => {
+ const newDrop = await this.#obtainIncomingItem(e);
const dropSorter = UmbSorterController.dropSorter as unknown as UmbSorterController;
+
if (!dropSorter || dropSorter.identifier !== this.identifier) return;
if (dropSorter === this) {
@@ -447,7 +562,7 @@ export class UmbSorterController {
+ this.#handleMoveEnd();
+ };
+
#getDraggableElement(element: HTMLElement) {
if (this.#config.draggableSelector) {
// Concept for enabling getting element within ShadowRoot: (But it might need to be configurable, so its still possible to get light dom element(slotted), despite the host is a web-component with shadow-dom.) [NL]
@@ -509,6 +629,10 @@ export class UmbSorterController;
// Notice, it is acceptable here to get index via object reference, but only cause there has been no change at this stage, otherwise we cannot trust the object instance is represented in the model — it could have mutated or been cloned [NL]
- UmbSorterController.originalIndex = this.#model.indexOf(UmbSorterController.activeItem);
-
- if (!UmbSorterController.activeItem) {
- console.error('Could not find item related to this element.', UmbSorterController.activeElement);
- return;
- }
+ UmbSorterController.originalIndex = this.#model.findIndex((x) => this.#config.getUniqueOfModel(x) === activeUnique);
// Get the current index of the item:
UmbSorterController.activeIndex = UmbSorterController.originalIndex;
@@ -711,8 +841,11 @@ export class UmbSorterController {
UmbSorterController.rqaId = undefined;
- if (!UmbSorterController.activeElement || !UmbSorterController.activeItem) {
+ if (!UmbSorterController.activeItem) {
return;
}
@@ -820,7 +962,7 @@ export class UmbSorterController this.#config.getUniqueOfModel(x) === activeUnique);
if (activeIndex === -1) {
activeIndex = null;
}
@@ -912,7 +1055,7 @@ export class UmbSorterController this.#config.getUniqueOfModel(x) !== itemUnique);
+ if (this.#model.length !== newModel.length) {
this.#model = newModel;
this.#config.onChange?.({ model: newModel, item });
return true;
@@ -1118,7 +1261,7 @@ export class UmbSorterController x !== item).length > 0;
}
- public async moveItemInModel(newIndex: number, fromCtrl: UmbSorterController) {
+ public async moveItemInModel(newIndex: number, fromCtrl: UmbSorterController | undefined) {
if (!UmbSorterController.activeItem) {
console.error('There is no active item to move');
return false;
@@ -1128,33 +1271,80 @@ export class UmbSorterController this.#config.getUniqueOfModel(x) === itemUnique) as T | undefined;
+ if (!item) {
+ return false;
+ }
}
- if (this.notifyRequestDrop({ item }) === false) {
+ // If we do not have a formCtrl, then it means that we dont know where it comes from, like via native drag across the Sorters awareness.
+
+ if (this.notifyRequestMove({ item }) === false) {
return false;
}
- const localMove = fromCtrl === (this as any);
+ let localMove = fromCtrl === (this as any);
if (!localMove) {
// Not a local move, so we have to switch container to continue:
- if ((await fromCtrl.removeItem(item)) !== true) {
- console.error('Sync could not remove item when moving to a new container');
- return false;
- }
+ // Notice if fromCtrl is not defined this is properly a native drop.
+ if (fromCtrl) {
+ if ((await fromCtrl.removeItem(item)) !== true) {
+ console.error('Sorter could not remove item before moving to a new container');
+ return false;
+ }
+ } else if (!fromCtrl) {
+ // Before we react to the external factor, lets see if we already got it in our model.
+ // Remove from the external model.
+ if (!this.#config.requestExternalRemove) {
+ console.error(
+ 'Sorter needs the requestExternalRemove to be defined, therefor we cannot drop the external item',
+ );
+ return false;
+ }
+ UmbSorterController.activeSorter = this as unknown as UmbSorterController;
+ fromCtrl = this as unknown as UmbSorterController;
+
+ if ((await this.#config.requestExternalRemove({ item })) !== true) {
+ console.error('Sorter could not remove the item before moving to a new container');
+ return false;
+ }
+ if ((await this.#config.requestExternalInsert?.({ item })) !== true) {
+ console.error('Sorter could not insert the item into the new container');
+ return false;
+ }
+ // This requestExternalInsert ^^ could have updated the model already. if so we should skip ahead to just move the item as a local move.
+ if (this.#model.find((x) => this.#config.getUniqueOfModel(x) === itemUnique)) {
+ localMove = true;
+ }
+ }
+ }
+ if (!localMove) {
if (this.#config.performItemInsert) {
const result = await this.#config.performItemInsert({ item, newIndex });
if (result === false) {
console.error('Sync could not insert after a move a new container');
return false;
}
+
+ // If everything went well, we can set the new activeSorter (and dropSorter) to this, as we are switching container. [NL]
+ UmbSorterController.activeSorter = this as unknown as UmbSorterController;
+ UmbSorterController.dropSorter = this as unknown as UmbSorterController;
+ UmbSorterController.activeIndex =
+ this.#model.findIndex((x) => this.#config.getUniqueOfModel(x) === itemUnique) ?? 0;
} else {
const newModel = [...this.#model];
newModel.splice(newIndex, 0, item);
@@ -1163,7 +1353,7 @@ export class UmbSorterController,
+ from: fromCtrl as unknown as UmbSorterController | undefined,
});
this.#config.onChange?.({ model: newModel, item });
@@ -1178,7 +1368,7 @@ export class UmbSorterController this.#config.getUniqueOfModel(x) === itemUnique);
if (oldIndex === -1) {
console.error('Could not find item in model when performing internal move', this.getHostElement(), this.#model);
return false;
@@ -1213,7 +1403,7 @@ export class UmbSorterController;
- if (this.notifyRequestDrop({ item: item }) === true) {
+ if (this.notifyRequestMove({ item: item }) === true) {
this.notifyAllowed();
return true;
}
@@ -1305,27 +1495,10 @@ export class UmbSorterController}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/config/config.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/config/config.server.data-source.ts
index 3f3ac52aa9..6e4c7cc03f 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/config/config.server.data-source.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/config/config.server.data-source.ts
@@ -13,6 +13,6 @@ export class UmbTemporaryFileConfigServerDataSource {
* Get the temporary file configuration.
*/
getConfig() {
- return tryExecute(this.#host, TemporaryFileService.getTemporaryFileConfiguration());
+ return tryExecute(this.#host, TemporaryFileService.getTemporaryFileConfiguration(), { disableNotifications: true });
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file-manager.class.ts
index 7c3251f276..5125f1bff5 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file-manager.class.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file-manager.class.ts
@@ -188,10 +188,20 @@ export class UmbTemporaryFileManager<
return TemporaryFileStatus.CANCELLED;
}
- if (error instanceof UmbApiError && error.status === 413) {
- // Special handling for when the request body is too large
- const maxFileSizeGuestimate = parseInt(/(\d+) bytes/.exec(error.problemDetails.title)?.[1] ?? '0', 10);
- this.#notifyOnFileSizeLimitExceeded(maxFileSizeGuestimate, item);
+ if (UmbApiError.isUmbApiError(error)) {
+ // Handle the error based on the status code
+ if (error.status === 413) {
+ // Special handling for when the request body is too large
+ const maxFileSizeGuestimate = parseInt(/(\d+) bytes/.exec(error.problemDetails.title)?.[1] ?? '0', 10);
+ this.#notifyOnFileSizeLimitExceeded(maxFileSizeGuestimate, item);
+ } else {
+ this.#notificationContext?.peek('danger', {
+ data: {
+ headline: this.#localization.term('errors_receivedErrorFromServer'),
+ message: error.problemDetails.title,
+ },
+ });
+ }
} else {
this.#notificationContext?.peek('danger', {
data: {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository-base.ts
index 4a1c1aaece..e9b4b8ba58 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository-base.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository-base.ts
@@ -12,6 +12,7 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import type { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import type { UmbProblemDetails } from '@umbraco-cms/backoffice/resources';
+import { of } from '@umbraco-cms/backoffice/external/rxjs';
/**
* Base class for a tree repository.
@@ -61,10 +62,11 @@ export abstract class UmbTreeRepositoryBase<
this._treeSource = new treeSourceConstructor(this);
this._init = this.consumeContext(treeStoreContextAlias, (instance) => {
- if (instance) {
- this._treeStore = instance;
- }
- }).asPromise({ preventTimeout: true });
+ this._treeStore = instance;
+ })
+ .asPromise({ preventTimeout: true })
+ // Ignore the error, we can assume that the flow was stopped (asPromise failed), but it does not mean that the consumption was not successful.
+ .catch(() => undefined);
}
/**
@@ -84,16 +86,18 @@ export abstract class UmbTreeRepositoryBase<
await this._init;
const { data, error } = await this._treeSource.getRootItems(args);
+
if (!this._treeStore) {
// If the tree store is not available, then we most likely are in a destructed setting.
return {};
}
+
if (data) {
- this._treeStore?.appendItems(data.items);
+ this._treeStore.appendItems(data.items);
}
- // TODO: Notice we are casting the error here, is that right?
- return { data, error: error as unknown as UmbProblemDetails, asObservable: () => this._treeStore!.rootItems };
+ // TODO: Fix the type of error, it should be UmbApiError, but currently it is any.
+ return { data, error: error as any, asObservable: () => this._treeStore!.rootItems };
}
/**
@@ -109,13 +113,19 @@ export abstract class UmbTreeRepositoryBase<
if (args.parent.entityType === null) throw new Error('Parent entity type is missing');
await this._init;
- const { data, error: _error } = await this._treeSource.getChildrenOf(args);
- const error: any = _error;
- if (data) {
- this._treeStore?.appendItems(data.items);
+ const { data, error } = await this._treeSource.getChildrenOf(args);
+
+ if (!this._treeStore) {
+ // If the tree store is not available, then we most likely are in a destructed setting.
+ return {};
}
- return { data, error, asObservable: () => this._treeStore!.childrenOf(args.parent.unique) };
+ if (data) {
+ this._treeStore.appendItems(data.items);
+ }
+
+ // TODO: Fix the type of error, it should be UmbApiError, but currently it is any.
+ return { data, error: error as any, asObservable: () => this._treeStore!.childrenOf(args.parent.unique) };
}
/**
@@ -128,10 +138,11 @@ export abstract class UmbTreeRepositoryBase<
if (args.treeItem.unique === undefined) throw new Error('Descendant unique is missing');
await this._init;
- const { data, error: _error } = await this._treeSource.getAncestorsOf(args);
- const error: any = _error;
+ const { data, error } = await this._treeSource.getAncestorsOf(args);
+
// TODO: implement observable for ancestor items in the store
- return { data, error };
+ // TODO: Fix the type of error, it should be UmbApiError, but currently it is any.
+ return { data, error: error as any };
}
/**
@@ -141,7 +152,13 @@ export abstract class UmbTreeRepositoryBase<
*/
async rootTreeItems() {
await this._init;
- return this._treeStore!.rootItems;
+
+ if (!this._treeStore) {
+ // If the tree store is not available, then we most likely are in a destructed setting.
+ return of([]);
+ }
+
+ return this._treeStore.rootItems;
}
/**
@@ -153,6 +170,12 @@ export abstract class UmbTreeRepositoryBase<
async treeItemsOf(parentUnique: string | null) {
if (parentUnique === undefined) throw new Error('Parent unique is missing');
await this._init;
- return this._treeStore!.childrenOf(parentUnique);
+
+ if (!this._treeStore) {
+ // If the tree store is not available, then we most likely are in a destructed setting.
+ return of([]);
+ }
+
+ return this._treeStore.childrenOf(parentUnique);
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-context-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-context-base.ts
index ed019eec8b..6ddd369c03 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-context-base.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-context-base.ts
@@ -19,7 +19,8 @@ import {
import type { UmbEntityActionEvent } from '@umbraco-cms/backoffice/entity-action';
import { UmbDeprecation, UmbPaginationManager, debounce } from '@umbraco-cms/backoffice/utils';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
-import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity';
+import { UmbParentEntityContext, type UmbEntityModel, type UmbEntityUnique } from '@umbraco-cms/backoffice/entity';
+import { ensureSlash } from '@umbraco-cms/backoffice/router';
export abstract class UmbTreeItemContextBase<
TreeItemType extends UmbTreeItemModel,
@@ -81,6 +82,7 @@ export abstract class UmbTreeItemContextBase<
#actionEventContext?: typeof UMB_ACTION_EVENT_CONTEXT.TYPE;
#hasChildrenContext = new UmbHasChildrenEntityContext(this);
+ #parentContext = new UmbParentEntityContext(this);
// TODO: get this from the tree context
#paging = {
@@ -144,6 +146,13 @@ export abstract class UmbTreeItemContextBase<
this.#hasChildren.setValue(hasChildren);
this.#hasChildrenContext.setHasChildren(hasChildren);
+ const parentEntity: UmbEntityModel | undefined = treeItem.parent
+ ? {
+ entityType: treeItem.parent.entityType,
+ unique: treeItem.parent.unique,
+ }
+ : undefined;
+ this.#parentContext.setParent(parentEntity);
this._treeItem.setValue(treeItem);
// Update observers:
@@ -441,9 +450,12 @@ export abstract class UmbTreeItemContextBase<
return;
}
- const path = this.#path.getValue();
- const location = window.location.pathname;
- const isActive = location.includes(path);
+ /* Check if the current location includes the path of this tree item.
+ We ensure that the paths ends with a slash to avoid collisions with paths like /path-1 and /path-1-2 where /path-1 is in both.
+ Instead we compare /path-1/ with /path-1-2/ which wont collide.*/
+ const location = ensureSlash(window.location.pathname);
+ const comparePath = ensureSlash(this.#path.getValue());
+ const isActive = location.includes(comparePath);
this.#isActive.setValue(isActive);
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts
index d5be31e98c..a5e6a1b01d 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts
@@ -13,6 +13,7 @@ export abstract class UmbTreeItemElementBase<
this._item = newVal;
if (this._item) {
+ this._label = this.localize.string(this._item?.name ?? '');
this.#initTreeItem();
}
}
@@ -21,6 +22,9 @@ export abstract class UmbTreeItemElementBase<
}
protected _item?: TreeItemModelType;
+ @state()
+ _label?: string;
+
@property({ type: Object, attribute: false })
public set api(value: TreeItemContextType | undefined) {
this.#api = value;
@@ -67,7 +71,7 @@ export abstract class UmbTreeItemElementBase<
private _isSelectable = false;
@state()
- private _isSelected = false;
+ protected _isSelected = false;
@state()
private _hasChildren = false;
@@ -119,7 +123,6 @@ export abstract class UmbTreeItemElementBase<
// Note: Currently we want to prevent opening when the item is in a selectable context, but this might change in the future.
// If we like to be able to open items in selectable context, then we might want to make it as a menu item action, so you have to click ... and chose an action called 'Edit'
override render() {
- const label = this.localize.string(this._item?.name ?? '');
return html`
${this.renderIconContainer()} ${this.renderLabel()} ${this.#renderActions()} ${this.#renderChildItems()}
@@ -162,10 +165,9 @@ export abstract class UmbTreeItemElementBase<
#renderIcon() {
const icon = this._item?.icon;
const isFolder = this._item?.isFolder;
- const iconWithoutColor = icon?.split(' ')[0];
- if (icon && iconWithoutColor) {
- return html` `;
+ if (icon) {
+ return html` `;
}
if (isFolder) {
@@ -175,6 +177,11 @@ export abstract class UmbTreeItemElementBase<
return html` `;
}
+ protected _getIconToRender(icon: string) {
+ const iconWithoutColor = icon.split(' ')[0];
+ return this._isActive || this._isSelected ? iconWithoutColor : icon;
+ }
+
renderLabel() {
return html` `;
}
@@ -187,7 +194,7 @@ export abstract class UmbTreeItemElementBase<
slot="actions"
.entityType=${this.#api.entityType}
.unique=${this.#api.unique}
- .label=${this._item.name}>
+ .label=${this.localize.term('actions_viewActionsFor', [this._label])}>
`;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item.element.ts
index 54ae3c4580..90ab9c2cdc 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item.element.ts
@@ -31,7 +31,7 @@ export class UmbTreeItemElement extends UmbExtensionElementAndApiSlotElementBase
// This method gets all extensions based on a type, then filters them based on the entity type. and then we get the alias of the first one [NL]
createObservablePart(
umbExtensionsRegistry.byTypeAndFilter(this.getExtensionType(), filterByEntityType),
- (x) => x[0].alias,
+ (x) => x[0]?.alias,
),
(alias) => {
this.alias = alias;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/array/batch-array.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/array/batch-array.test.ts
new file mode 100644
index 0000000000..e958f3fea1
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/array/batch-array.test.ts
@@ -0,0 +1,25 @@
+import { expect } from '@open-wc/testing';
+import { batchArray } from './batch-array.js';
+
+describe('batchArray', () => {
+ it('should split an array into chunks of the specified size', () => {
+ const array = [1, 2, 3, 4, 5];
+ const batchSize = 2;
+ const result = batchArray(array, batchSize);
+ expect(result).to.deep.equal([[1, 2], [3, 4], [5]]);
+ });
+
+ it('should handle arrays smaller than the batch size', () => {
+ const array = [1];
+ const batchSize = 2;
+ const result = batchArray(array, batchSize);
+ expect(result).to.deep.equal([[1]]);
+ });
+
+ it('should handle empty arrays', () => {
+ const array: number[] = [];
+ const batchSize = 2;
+ const result = batchArray(array, batchSize);
+ expect(result).to.deep.equal([]);
+ });
+});
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/array/batch-array.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/array/batch-array.ts
new file mode 100644
index 0000000000..fde5b6703d
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/array/batch-array.ts
@@ -0,0 +1,16 @@
+/**
+ * Splits an array into chunks of a specified size
+ * @param { Array } array - The array to split
+ * @param {number }batchSize - The size of each chunk
+ * @returns {Array>} - An array of chunks
+ */
+export function batchArray(
+ array: Array,
+ batchSize: number,
+): Array> {
+ const chunks: Array> = [];
+ for (let i = 0; i < array.length; i += batchSize) {
+ chunks.push(array.slice(i, i + batchSize));
+ }
+ return chunks;
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/array/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/array/index.ts
new file mode 100644
index 0000000000..106dce2d85
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/array/index.ts
@@ -0,0 +1 @@
+export * from './batch-array.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts
index aa191db2e7..a59e5dc990 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts
@@ -1,3 +1,4 @@
+export * from './array/index.js';
export * from './bytes/bytes.function.js';
export * from './debounce/debounce.function.js';
export * from './deprecation/index.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/media/image-size.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/media/image-size.function.ts
index 8958da1c0b..c5ee719cfc 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/media/image-size.function.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/media/image-size.function.ts
@@ -1,43 +1,39 @@
/**
* Get the dimensions of an image from a URL.
* @param {string} url The URL of the image. It can be a local file (blob url) or a remote file.
- * @param {{maxWidth?: number}} opts Options for the image size.
- * @param {number} opts.maxWidth The maximum width of the image. If the image is wider than this, it will be scaled down to this width while keeping the aspect ratio.
- * @returns {Promise<{width: number, height: number, naturalWidth: number, naturalHeight: number}>} The width and height of the image as downloaded from the URL. The width and height can differ from the natural numbers if maxImageWidth is given.
+ * @param {{maxWidth?: number, maxHeight?: number}} opts Options for the image size.
+ * @param {number} opts.maxWidth The maximum width of the image.
+ * @param {number} opts.maxHeight The maximum height of the image.
+ * @returns {Promise<{width: number, height: number, naturalWidth: number, naturalHeight: number}>} The dimensions of the image.
*/
export function imageSize(
url: string,
- opts?: { maxWidth?: number },
+ opts?: { maxWidth?: number; maxHeight?: number },
): Promise<{ width: number; height: number; naturalWidth: number; naturalHeight: number }> {
const img = new Image();
const promise = new Promise<{ width: number; height: number; naturalWidth: number; naturalHeight: number }>(
(resolve, reject) => {
img.onload = () => {
- // Natural size is the actual image size regardless of rendering.
- // The 'normal' `width`/`height` are for the **rendered** size.
const naturalWidth = img.naturalWidth;
const naturalHeight = img.naturalHeight;
let width = naturalWidth;
let height = naturalHeight;
- if (opts?.maxWidth && opts.maxWidth > 0 && width > opts?.maxWidth) {
- const ratio = opts.maxWidth / naturalWidth;
- width = opts.maxWidth;
+ if ((opts?.maxWidth && opts.maxWidth > 0) || (opts?.maxHeight && opts.maxHeight > 0)) {
+ const widthRatio = opts?.maxWidth ? opts.maxWidth / naturalWidth : 1;
+ const heightRatio = opts?.maxHeight ? opts.maxHeight / naturalHeight : 1;
+ const ratio = Math.min(widthRatio, heightRatio, 1); // Never upscale
+ width = Math.round(naturalWidth * ratio);
height = Math.round(naturalHeight * ratio);
}
- // Resolve promise with the width and height
resolve({ width, height, naturalWidth, naturalHeight });
};
-
- // Reject promise on error
img.onerror = reject;
},
);
- // Setting the source makes it start downloading and eventually call `onload`
img.src = url;
-
return promise;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/media/image-size.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/media/image-size.test.ts
new file mode 100644
index 0000000000..57b9cdbd36
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/media/image-size.test.ts
@@ -0,0 +1,83 @@
+import { imageSize } from './image-size.function';
+import { expect } from '@open-wc/testing';
+
+describe('imageSize', () => {
+ let OriginalImage: typeof Image;
+
+ before(() => {
+ OriginalImage = window.Image;
+ });
+
+ after(() => {
+ window.Image = OriginalImage;
+ });
+
+ function mockImage(naturalWidth: number, naturalHeight: number) {
+ class MockImage {
+ naturalWidth = naturalWidth;
+ naturalHeight = naturalHeight;
+ onload: (() => void) | null = null;
+ onerror: (() => void) | null = null;
+ set src(_url: string) {
+ setTimeout(() => this.onload && this.onload(), 0);
+ }
+ }
+ // @ts-ignore
+ window.Image = MockImage;
+ }
+
+ it('returns natural size if no maxWidth or maxHeight is given', async () => {
+ mockImage(800, 600);
+ const result = await imageSize('fake-url');
+ expect(result).to.deep.equal({
+ width: 800,
+ height: 600,
+ naturalWidth: 800,
+ naturalHeight: 600,
+ });
+ });
+
+ it('scales down to maxWidth and maxHeight, ratio locked', async () => {
+ mockImage(800, 600);
+ const result = await imageSize('fake-url', { maxWidth: 400, maxHeight: 300 });
+ expect(result).to.deep.equal({
+ width: 400,
+ height: 300,
+ naturalWidth: 800,
+ naturalHeight: 600,
+ });
+ });
+
+ it('never upscales if maxWidth/maxHeight are larger than natural', async () => {
+ mockImage(800, 600);
+ const result = await imageSize('fake-url', { maxWidth: 1000, maxHeight: 1000 });
+ expect(result).to.deep.equal({
+ width: 800,
+ height: 600,
+ naturalWidth: 800,
+ naturalHeight: 600,
+ });
+ });
+
+ it('scales down by width if width is limiting', async () => {
+ mockImage(800, 600);
+ const result = await imageSize('fake-url', { maxWidth: 400 });
+ expect(result).to.deep.equal({
+ width: 400,
+ height: 300,
+ naturalWidth: 800,
+ naturalHeight: 600,
+ });
+ });
+
+ it('scales down by height if height is limiting', async () => {
+ mockImage(800, 600);
+ const result = await imageSize('fake-url', { maxHeight: 150 });
+ expect(result).to.deep.equal({
+ width: 200,
+ height: 150,
+ naturalWidth: 800,
+ naturalHeight: 600,
+ });
+ });
+});
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts
index 0c2593fd78..cb93af9066 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts
@@ -31,7 +31,7 @@ export class UmbBindServerValidationToFormControl extends UmbControllerBase {
this.#value = value;
// Only remove server validations from validation context [NL]
const toRemove = this.#messages.filter((x) => x.type === 'server').map((msg) => msg.key);
- this.#context?.messages.removeMessageByKeys(toRemove);
+ this.#context?.messages?.removeMessageByKeys(toRemove);
}
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/form-control-validator.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/form-control-validator.controller.ts
index 67bc5523ec..8a1e5282c9 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/form-control-validator.controller.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/form-control-validator.controller.ts
@@ -30,7 +30,7 @@ export class UmbFormControlValidator extends UmbControllerBase implements UmbVal
this.#context = context;
context?.addValidator(this);
// If we have a message already, then un-pristine the control:
- if (dataPath && context?.messages.getHasMessagesOfPathAndDescendant(dataPath)) {
+ if (dataPath && context?.messages?.getHasMessagesOfPathAndDescendant(dataPath)) {
formControl.pristine = false;
}
});
@@ -46,11 +46,11 @@ export class UmbFormControlValidator extends UmbControllerBase implements UmbVal
if (this.#dataPath) {
if (newVal) {
- this.#context?.messages.removeMessagesByTypeAndPath('client', this.#dataPath);
+ this.#context?.messages?.removeMessagesByTypeAndPath('client', this.#dataPath);
} else {
// We only want to add the message if it is not already there. (this could be a custom or server message that got binded to the control, we do not want that double.)
- if (!this.#context?.messages.getHasMessageOfPathAndBody(this.#dataPath, this.#control.validationMessage)) {
- this.#context?.messages.addMessage('client', this.#dataPath, this.#control.validationMessage);
+ if (!this.#context?.messages?.getHasMessageOfPathAndBody(this.#dataPath, this.#control.validationMessage)) {
+ this.#context?.messages?.addMessage('client', this.#dataPath, this.#control.validationMessage);
}
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation-path-translation/validation-property-path-translation.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation-path-translation/validation-property-path-translation.controller.ts
index e3fd51c0f3..69d4d87d21 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation-path-translation/validation-property-path-translation.controller.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation-path-translation/validation-property-path-translation.controller.ts
@@ -55,6 +55,8 @@ export class UmbValidationPropertyPathTranslationController extends UmbControlle
return propertyPaths;
}
+ (api as any).manifest = manifest;
+
propertyPaths = (await api.translate(propertyPaths, propertyData)) ?? propertyPaths;
return propertyPaths;
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 821324469a..7d18b212a8 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
@@ -27,7 +27,7 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
>;
#inUnprovidingState: boolean = false;
- // @reprecated - Will be removed in v.17
+ // @deprecated - Will be removed in v.17
// Local version of the data send to the server, only use-case is for translation.
#translationData = new UmbObjectState(undefined);
/**
@@ -81,7 +81,7 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
setVariantId(variantId: UmbVariantId): void {
this.#variantId = variantId;
// @.culture == null && @.segment == null
- this.messages.filter((msg) => {
+ this.messages?.filter((msg) => {
// Figure out how many times '@.culture ==' is present in the path:
//const cultureMatches = (msg.path.match(/@\.culture ==/g) || []);
// I like a Regex that finds all the @.culture == and @.segment == in the path. they are adjacent. and I like to know the value following '== '
@@ -113,7 +113,7 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
* @param translator
*/
async addTranslator(translator: UmbValidationMessageTranslator) {
- this.messages.addTranslator(translator);
+ this.messages?.addTranslator(translator);
}
/**
@@ -454,6 +454,7 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
}
override destroy(): void {
+ super.destroy();
this.#validationMode = false;
if (this.#inUnprovidingState === true) {
return;
@@ -468,6 +469,5 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
this.#localMessages = undefined;
this.#parentMessages = undefined;
this.#parent = undefined;
- super.destroy();
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/constants.ts
new file mode 100644
index 0000000000..9c6a2415c8
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/constants.ts
@@ -0,0 +1 @@
+export * from './context/constants.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/context/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/context/constants.ts
new file mode 100644
index 0000000000..0b49906ceb
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/context/constants.ts
@@ -0,0 +1 @@
+export { UMB_VARIANT_CONTEXT } from './variant.context.token.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/context/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/context/index.ts
new file mode 100644
index 0000000000..85a9b48dce
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/context/index.ts
@@ -0,0 +1 @@
+export { UmbVariantContext } from './variant.context.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/context/variant.context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/context/variant.context.token.ts
new file mode 100644
index 0000000000..d2733dac1b
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/context/variant.context.token.ts
@@ -0,0 +1,4 @@
+import type { UmbVariantContext } from './variant.context.js';
+import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
+
+export const UMB_VARIANT_CONTEXT = new UmbContextToken('UmbVariantContext');
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/context/variant.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/context/variant.context.ts
new file mode 100644
index 0000000000..a71881beed
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/context/variant.context.ts
@@ -0,0 +1,164 @@
+import { UmbVariantId } from '../variant-id.class.js';
+import { UMB_VARIANT_CONTEXT } from './variant.context.token.js';
+import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
+import { mergeObservables, UmbClassState, UmbStringState } from '@umbraco-cms/backoffice/observable-api';
+
+/**
+ * A context for the current variant state.
+ * @class UmbVariantContext
+ * @augments {UmbContextBase}
+ * @implements {UmbVariantContext}
+ */
+export class UmbVariantContext extends UmbContextBase {
+ #variantId = new UmbClassState(undefined);
+ public readonly variantId = this.#variantId.asObservable();
+ public readonly culture = this.#variantId.asObservablePart((x) => x?.culture);
+ public readonly segment = this.#variantId.asObservablePart((x) => x?.segment);
+
+ #fallbackCulture = new UmbStringState(undefined);
+ public fallbackCulture = this.#fallbackCulture.asObservable();
+
+ #appCulture = new UmbStringState(undefined);
+ public appCulture = this.#appCulture.asObservable();
+
+ public readonly displayCulture = mergeObservables([this.culture, this.appCulture], ([culture, appCulture]) => {
+ return culture ?? appCulture;
+ });
+
+ constructor(host: UmbControllerHost) {
+ super(host, UMB_VARIANT_CONTEXT);
+ }
+
+ /**
+ * Inherit values from the parent variant context
+ * @returns {UmbVariantContext} - The current instance of the context
+ * @memberof UmbVariantContext
+ */
+ inherit(): UmbVariantContext {
+ this.consumeContext(UMB_VARIANT_CONTEXT, (context) => {
+ this.observe(
+ context?.fallbackCulture,
+ (fallbackCulture) => {
+ if (!fallbackCulture) return;
+ this.#fallbackCulture.setValue(fallbackCulture);
+ },
+ 'observeFallbackCulture',
+ );
+
+ this.observe(
+ context?.appCulture,
+ (appCulture) => {
+ if (!appCulture) return;
+ this.#appCulture.setValue(appCulture);
+ },
+ 'observeAppCulture',
+ );
+ }).skipHost();
+
+ return this;
+ }
+
+ /**
+ * Sets the variant id state
+ * @param {UmbVariantId | undefined} variantId - The variant to set
+ * @memberof UmbVariantContext
+ */
+ async setVariantId(variantId: UmbVariantId | undefined): Promise {
+ this.#variantId.setValue(variantId);
+ }
+
+ /**
+ * Gets variant state
+ * @returns {Promise} - The variant state
+ * @memberof UmbVariantContext
+ */
+ async getVariantId(): Promise {
+ return this.#variantId.getValue();
+ }
+
+ /**
+ * Gets the culture state
+ * @returns {(Promise)} - The culture state
+ * @memberof UmbVariantContext
+ */
+ async getCulture(): Promise {
+ return this.#variantId.getValue()?.culture;
+ }
+
+ /**
+ * Sets the variant culture state
+ * @param {string | undefined} culture - The culture to set
+ * @memberof UmbVariantContext
+ */
+ async setCulture(culture: string | null): Promise {
+ const variantId = new UmbVariantId(culture, this.#variantId.getValue()?.segment);
+ this.#variantId.setValue(variantId);
+ }
+
+ /**
+ * Gets the variant segment state
+ * @returns {(Promise)} - The segment state
+ * @memberof UmbVariantContext
+ */
+ async getSegment(): Promise {
+ return this.#variantId.getValue()?.segment;
+ }
+
+ /**
+ * Sets the variant segment state
+ * @param {string | undefined} segment - The segment to set
+ * @memberof UmbVariantContext
+ */
+ async setSegment(segment: string | null): Promise {
+ const variantId = new UmbVariantId(this.#variantId.getValue()?.culture, segment);
+ this.#variantId.setValue(variantId);
+ }
+
+ /**
+ * Gets the fallback culture state
+ * @returns {(Promise)} - The fallback culture state
+ * @memberof UmbVariantContext
+ */
+ async getFallbackCulture(): Promise {
+ return this.#fallbackCulture.getValue();
+ }
+
+ /**
+ * Sets the fallback culture state
+ * @param {string | undefined} culture - The fallback culture to set
+ * @memberof UmbVariantContext
+ */
+ async setFallbackCulture(culture: string | null): Promise {
+ this.removeUmbControllerByAlias('observeFallbackCulture');
+ this.#fallbackCulture.setValue(culture);
+ }
+
+ /**
+ * Gets the app culture state
+ * @returns {(Promise)} - The app culture state
+ * @memberof UmbVariantContext
+ */
+ async getAppCulture(): Promise {
+ return this.#appCulture.getValue();
+ }
+
+ /**
+ * Sets the app culture state
+ * @param {string | undefined} culture - The app culture to set
+ * @memberof UmbVariantContext
+ */
+ async setAppCulture(culture: string | null): Promise {
+ this.removeUmbControllerByAlias('observeAppCulture');
+ this.#appCulture.setValue(culture);
+ }
+
+ /**
+ * Gets the display culture state
+ * @returns {(Promise)} - The app culture state
+ * @memberof UmbVariantContext
+ */
+ async getDisplayCulture(): Promise {
+ return this.observe(this.displayCulture).asPromise();
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/index.ts
index d190db617d..9d50648397 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/variant/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/index.ts
@@ -1,3 +1,5 @@
+export * from './constants.js';
+export * from './context/index.js';
export * from './variant-id.class.js';
export * from './variant-object-compare.function.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/save/save.action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/save/save.action.ts
index 530fc13438..e4b9905186 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/save/save.action.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/save/save.action.ts
@@ -22,7 +22,9 @@ export class UmbSaveWorkspaceAction<
this.#observeUnique();
this._gotWorkspaceContext();
},
- ).asPromise();
+ )
+ .asPromise()
+ .catch(() => undefined);
}
#observeUnique() {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-menu-breadcrumb/workspace-menu-breadcrumb.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-menu-breadcrumb/workspace-menu-breadcrumb.element.ts
index ffae6d8183..21b55ca72e 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-menu-breadcrumb/workspace-menu-breadcrumb.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-menu-breadcrumb/workspace-menu-breadcrumb.element.ts
@@ -3,7 +3,11 @@ import { css, customElement, html, ifDefined, map, state } from '@umbraco-cms/ba
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UMB_SECTION_CONTEXT } from '@umbraco-cms/backoffice/section';
-import type { UmbMenuStructureWorkspaceContext, UmbStructureItemModel } from '@umbraco-cms/backoffice/menu';
+import {
+ UMB_MENU_STRUCTURE_WORKSPACE_CONTEXT,
+ type UmbMenuStructureWorkspaceContext,
+ type UmbStructureItemModel,
+} from '@umbraco-cms/backoffice/menu';
@customElement('umb-workspace-breadcrumb')
export class UmbWorkspaceBreadcrumbElement extends UmbLitElement {
@@ -16,7 +20,7 @@ export class UmbWorkspaceBreadcrumbElement extends UmbLitElement {
// TODO: figure out the correct context type
#workspaceContext?: any;
#sectionContext?: typeof UMB_SECTION_CONTEXT.TYPE;
- #structureContext?: UmbMenuStructureWorkspaceContext;
+ #structureContext?: typeof UMB_MENU_STRUCTURE_WORKSPACE_CONTEXT.TYPE;
constructor() {
super();
@@ -31,9 +35,7 @@ export class UmbWorkspaceBreadcrumbElement extends UmbLitElement {
this.#observeName();
});
- // TODO: set up context token
- this.consumeContext('UmbMenuStructureWorkspaceContext', (instance) => {
- // TODO: get the correct interface from the context token
+ this.consumeContext(UMB_MENU_STRUCTURE_WORKSPACE_CONTEXT, (instance) => {
this.#structureContext = instance;
this.#observeStructure();
});
@@ -46,9 +48,7 @@ export class UmbWorkspaceBreadcrumbElement extends UmbLitElement {
this.observe(
this.#structureContext.structure,
(value) => {
- // TODO: get the type from the context
- const structure = value as Array;
- this._structure = isNew ? structure : structure.slice(0, -1);
+ this._structure = isNew ? value : value.slice(0, -1);
},
'menuStructureObserver',
);
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-variant-menu-breadcrumb/workspace-variant-menu-breadcrumb.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-variant-menu-breadcrumb/workspace-variant-menu-breadcrumb.element.ts
index b554170121..98973543b4 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-variant-menu-breadcrumb/workspace-variant-menu-breadcrumb.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-variant-menu-breadcrumb/workspace-variant-menu-breadcrumb.element.ts
@@ -6,7 +6,10 @@ import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
import { UMB_APP_LANGUAGE_CONTEXT } from '@umbraco-cms/backoffice/language';
import { UMB_SECTION_CONTEXT } from '@umbraco-cms/backoffice/section';
import type { UmbAppLanguageContext } from '@umbraco-cms/backoffice/language';
-import type { UmbVariantStructureItemModel } from '@umbraco-cms/backoffice/menu';
+import {
+ UMB_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT,
+ type UmbVariantStructureItemModel,
+} from '@umbraco-cms/backoffice/menu';
@customElement('umb-workspace-variant-menu-breadcrumb')
export class UmbWorkspaceVariantMenuBreadcrumbElement extends UmbLitElement {
@@ -25,7 +28,7 @@ export class UmbWorkspaceVariantMenuBreadcrumbElement extends UmbLitElement {
#sectionContext?: typeof UMB_SECTION_CONTEXT.TYPE;
#workspaceContext?: UmbVariantDatasetWorkspaceContext;
#appLanguageContext?: UmbAppLanguageContext;
- #structureContext?: any;
+ #structureContext?: typeof UMB_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT.TYPE;
constructor() {
super();
@@ -46,8 +49,7 @@ export class UmbWorkspaceVariantMenuBreadcrumbElement extends UmbLitElement {
this.#observeStructure();
});
- // TODO: set up context token
- this.consumeContext('UmbMenuStructureWorkspaceContext', (instance) => {
+ this.consumeContext(UMB_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT, (instance) => {
if (!instance) return;
this.#structureContext = instance;
this.#observeStructure();
@@ -56,12 +58,12 @@ export class UmbWorkspaceVariantMenuBreadcrumbElement extends UmbLitElement {
#observeStructure() {
if (!this.#structureContext || !this.#workspaceContext) return;
- const isNew = this.#workspaceContext.getIsNew();
this.observe(this.#structureContext.structure, (value) => {
- // TODO: get the type from the context
- const structure = value as Array;
- this._structure = isNew ? structure : structure.slice(0, -1);
+ if (!this.#workspaceContext) return;
+ const unique = this.#workspaceContext.getUnique();
+ // exclude the current unique from the structure. We append this with an observer of the name
+ this._structure = value.filter((structureItem) => structureItem.unique !== unique);
});
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts
index 4cb31ea938..194319a1e9 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts
@@ -299,6 +299,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement<
data-mark="input:entity-name"
placeholder=${this.localize.term('placeholders_entername')}
label=${this.localize.term('placeholders_entername')}
+ autocomplete="off"
.value=${this.#getNameValue()}
@input=${this.#handleInput}
required
@@ -595,8 +596,8 @@ export class UmbWorkspaceSplitViewVariantSelectorElement<
}
.switch-button:hover {
- background: var(--uui-palette-sand);
- color: var(--uui-palette-space-cadet-light);
+ background: var(--uui-color-surface-emphasis);
+ color: var(--uui-color-interactive-emphasis);
}
.switch-button .variant-info {
flex-grow: 1;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view.element.ts
index 4deb134665..25b0c70f8d 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view.element.ts
@@ -67,6 +67,7 @@ export class UmbWorkspaceSplitViewElement extends UmbLitElement {
back-path=${ifDefined(this.backPath)}
.hideNavigation=${!this.displayNavigation}
.enforceNoFooter=${true}>
+