From d868263be435ab2d8428193ec0e6001a674af26c Mon Sep 17 00:00:00 2001 From: Nikolaj Date: Wed, 19 Oct 2022 09:23:08 +0200 Subject: [PATCH 01/85] Bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 87470ec09b..cce7045350 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "10.3.0-rc", + "version": "10.3.0", "assemblyVersion": { "precision": "build" }, From fdbf3df05799d72b2033c7c7fe8b95942c7abe5c Mon Sep 17 00:00:00 2001 From: Mole Date: Thu, 20 Oct 2022 15:19:13 +0200 Subject: [PATCH 02/85] Add PrivateAssets="all" to mangement api project reference (#13249) --- .../Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index 76f030d6bc..e616f11dad 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -20,7 +20,7 @@ - + From 86fb24ad32c729541d3b618e22ebcd05962b788c Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Thu, 20 Oct 2022 15:21:25 +0200 Subject: [PATCH 03/85] Bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index cce7045350..500ace9f4f 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "10.3.0", + "version": "10.3.1", "assemblyVersion": { "precision": "build" }, From 8954b7f745f21172bd6db7639e80d788240661e7 Mon Sep 17 00:00:00 2001 From: patrickdemooij9 Date: Wed, 12 Oct 2022 02:59:38 +0200 Subject: [PATCH 04/85] Use the actual save function for saving the copy (#13066) * Use the actual save function for saving the copy * PR feedback (cherry picked from commit 3802097a32a24817681377d5530c2da1a66b6cd8) --- src/Umbraco.Core/Services/DataTypeService.cs | 36 +++++++++---------- src/Umbraco.Core/Services/IDataTypeService.cs | 3 +- .../Controllers/DataTypeController.cs | 4 ++- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index de3f96d834..a8a9e1fd3b 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -14,6 +14,7 @@ using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; +using static System.Formats.Asn1.AsnWriter; namespace Umbraco.Cms.Core.Services.Implement { @@ -441,34 +442,31 @@ namespace Umbraco.Cms.Core.Services.Implement return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs); } - public Attempt?> Copy(IDataType copying, int containerId) + public Attempt?> Copy(IDataType copying, int containerId, int userId = Constants.Security.SuperUserId) { var evtMsgs = EventMessagesFactory.Get(); IDataType copy; - using (var scope = ScopeProvider.CreateCoreScope()) + try { - try + if (containerId > 0) { - if (containerId > 0) + var container = GetContainer(containerId); + if (container is null) { - var container = _dataTypeContainerRepository.Get(containerId); - if (container is null) - { - throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); // causes rollback - } + throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); // causes rollback } - copy = copying.DeepCloneWithResetIdentities(); + } + copy = copying.DeepCloneWithResetIdentities(); - copy.Name += " (copy)"; // might not be unique - copy.ParentId = containerId; - _dataTypeRepository.Save(copy); - scope.Complete(); - } - catch (DataOperationException ex) - { - return OperationResult.Attempt.Fail(ex.Operation, evtMsgs); // causes rollback - } + copy.Name += " (copy)"; // might not be unique + copy.ParentId = containerId; + + Save(copy, userId); + } + catch (DataOperationException ex) + { + return OperationResult.Attempt.Fail(ex.Operation, evtMsgs); // causes rollback } return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs, copy); diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index 02bc51a8f6..11fa1fc964 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -107,8 +107,9 @@ public interface IDataTypeService : IService /// /// The data type that will be copied /// The container ID under where the data type will be copied + /// The user that did the Copy action /// /// - Attempt?> Copy(IDataType copying, int containerId) => throw new NotImplementedException(); + Attempt?> Copy(IDataType copying, int containerId, int userId = Constants.Security.SuperUserId) => throw new NotImplementedException(); } diff --git a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs index 7de06dfe6d..376d6af3fa 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using NPoco; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Mapping; @@ -391,7 +392,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return NotFound(); } - Attempt?> result = _dataTypeService.Copy(toCopy, copy.ParentId); + var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; + Attempt?> result = _dataTypeService.Copy(toCopy, copy.ParentId, currentUser?.Id ?? Constants.Security.SuperUserId); if (result.Success) { return Content(toCopy.Path, MediaTypeNames.Text.Plain, Encoding.UTF8); From 0258f247ebc45308739d32d45471f7a80a3220e1 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 25 Oct 2022 14:18:50 +0200 Subject: [PATCH 05/85] Unbreak breaking change in #13066 (#13288) (cherry picked from commit e4741b0c647b29e33fe4b2bd803107934f707e4f) --- src/Umbraco.Core/Services/DataTypeService.cs | 6 ++++++ src/Umbraco.Core/Services/IDataTypeService.cs | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index a8a9e1fd3b..4fd698236a 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -442,6 +442,12 @@ namespace Umbraco.Cms.Core.Services.Implement return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs); } + [Obsolete("Use the method which specifies the userId parameter")] + public Attempt?> Copy(IDataType copying, int containerId) + { + return Copy(copying, containerId, Constants.Security.SuperUserId); + } + public Attempt?> Copy(IDataType copying, int containerId, int userId = Constants.Security.SuperUserId) { var evtMsgs = EventMessagesFactory.Get(); diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index 11fa1fc964..035c7e8a7b 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -16,7 +16,7 @@ public interface IDataTypeService : IService IReadOnlyDictionary> GetReferences(int id); Attempt?> CreateContainer(int parentId, Guid key, string name, int userId = Constants.Security.SuperUserId); - + Attempt SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId); EntityContainer? GetContainer(int containerId); @@ -101,6 +101,9 @@ public interface IDataTypeService : IService Attempt?> Move(IDataType toMove, int parentId); + [Obsolete("Use the method which specifies the userId parameter")] + Attempt?> Copy(IDataType copying, int containerId) => Copy(copying, containerId, Constants.Security.SuperUserId); + /// /// Copies the give to a given container /// We have the default implementation here to avoid breaking changes for the user From de2a927f0cdb181c6117947a5ce8a396350dacc9 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 25 Oct 2022 14:40:55 +0200 Subject: [PATCH 06/85] Bump version to 10.3.2 --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 500ace9f4f..993b17ecd7 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "10.3.1", + "version": "10.3.2", "assemblyVersion": { "precision": "build" }, From e7fae144c2bdedfd78ccd53c27ec97b3fa41604b Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 25 Oct 2022 09:18:38 +0200 Subject: [PATCH 07/85] Parse lockId as invariant (#13284) Co-authored-by: Zeegaan (cherry picked from commit 272e9220205d562a964fce9de25cf4bb201ebf26) --- .../Services/SqliteDistributedLockingMechanism.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs index a4a31416fa..c3d6f8b29d 100644 --- a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs +++ b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDistributedLockingMechanism.cs @@ -1,5 +1,6 @@ using System.Data; using System.Data.Common; +using System.Globalization; using Microsoft.Data.SqlClient; using Microsoft.Data.Sqlite; using Microsoft.Extensions.Logging; @@ -142,7 +143,7 @@ public class SqliteDistributedLockingMechanism : IDistributedLockingMechanism "SqliteDistributedLockingMechanism requires a transaction to function."); } - var query = @$"UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id = {LockId}"; + var query = @$"UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id = {LockId.ToString(CultureInfo.InvariantCulture)}"; DbCommand command = db.CreateCommand(db.Connection, CommandType.Text, query); From 7204c039ad6532660461489cfafedd6db422b526 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 31 Oct 2022 08:06:06 +0100 Subject: [PATCH 08/85] Allow for configuration of additional languages to install and ensure one is set as default. (#13290) * Allow for configuration of additional languages to install and ensure one is set as default. * Amended install default language method to handle invalid ISO codes and ensure the first specified language is always made the default. * Removed unnecessary using. * Apply suggestions from code review Co-authored-by: Ronald Barendse * Clean up. * Update src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Co-authored-by: Ronald Barendse Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> --- .../Migrations/Install/DatabaseDataCreator.cs | 65 +++++++++++++++++-- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs index 1b01a3ba47..cc2e20fa02 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs @@ -1,3 +1,5 @@ +using System.Diagnostics.CodeAnalysis; +using System.Globalization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NPoco; @@ -1745,13 +1747,62 @@ internal class DatabaseDataCreator } } - private void CreateLanguageData() => - ConditionalInsert( - Constants.Configuration.NamedOptions.InstallDefaultData.Languages, - "en-us", - new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "English (United States)", IsDefault = true }, - Constants.DatabaseSchema.Tables.Language, - "id"); + private void CreateLanguageData() + { + // For languages we support the installation of records that are additional to the default installed data. + // We can do this as they are specified by ISO code, which is enough to fully detail them. + // All other customizable install data is specified by GUID, and hence we only know about the set that are installed by default. + InstallDefaultDataSettings? languageInstallDefaultDataSettings = _installDefaultDataSettings.Get(Constants.Configuration.NamedOptions.InstallDefaultData.Languages); + if (languageInstallDefaultDataSettings?.InstallData == InstallDefaultDataOption.Values) + { + // Insert the specified languages, ensuring the first is marked as default. + bool isDefault = true; + foreach (var isoCode in languageInstallDefaultDataSettings.Values) + { + if (!TryCreateCulture(isoCode, out CultureInfo? culture)) + { + continue; + } + + var dto = new LanguageDto + { + IsoCode = culture.Name, + CultureName = culture.EnglishName, + IsDefault = isDefault, + }; + _database.Insert(Constants.DatabaseSchema.Tables.Language, "id", true, dto); + isDefault = false; + } + } + else + { + // Conditionally insert the default language. + if (TryCreateCulture("en-US", out CultureInfo? culture)) + { + ConditionalInsert( + Constants.Configuration.NamedOptions.InstallDefaultData.Languages, + culture.Name, + new LanguageDto { Id = 1, IsoCode = culture.Name, CultureName = culture.EnglishName, IsDefault = true }, + Constants.DatabaseSchema.Tables.Language, + "id"); + } + } + } + + private bool TryCreateCulture(string isoCode, [NotNullWhen(true)] out CultureInfo? culture) + { + try + { + culture = CultureInfo.GetCultureInfo(isoCode); + return true; + } + catch (CultureNotFoundException ex) + { + _logger.LogWarning(ex, "CultureInfo could not be created because culture '{IsoCode}' is not available. The language will not be created.", isoCode); + culture = null; + return false; + } + } private void CreateContentChildTypeData() { From e0b76acde5658fcc1f3f6714495cfee476568ecd Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 31 Oct 2022 08:47:04 +0100 Subject: [PATCH 09/85] Updated Smidge (#13331) --- src/Umbraco.Web.Common/Umbraco.Web.Common.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index 8d1a22b7ae..8f7ea5f935 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -17,8 +17,8 @@ - - + + From ed52298099057e6698d2e1a0248e3824ceb82d1b Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 1 Nov 2022 08:41:50 +0100 Subject: [PATCH 10/85] Fix casing in fileSystemBasePath (#13306) --- src/Umbraco.Web.BackOffice/Controllers/BlockGridSampleHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/BlockGridSampleHelper.cs b/src/Umbraco.Web.BackOffice/Controllers/BlockGridSampleHelper.cs index c1e976204b..44030cdaf0 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BlockGridSampleHelper.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BlockGridSampleHelper.cs @@ -172,7 +172,7 @@ public sealed class BlockGridSampleHelper internal void CreateSamplePartialViews() { var embeddedBasePath = $"{_partialViewPopulator.CoreEmbeddedPath}.BlockGrid.Components"; - var fileSystemBasePath = "/Views/partials/blockgrid/Components"; + var fileSystemBasePath = "/Views/Partials/blockgrid/Components"; var filesToMove = new[] { "umbBlockGridDemoHeadlineBlock.cshtml", From 2a952c7374a4c3f56c4344f5b48063867efa2cda Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 2 Nov 2022 13:09:55 +0100 Subject: [PATCH 11/85] Add guard statement (#13340) --- src/Umbraco.Core/Extensions/ContentExtensions.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Extensions/ContentExtensions.cs b/src/Umbraco.Core/Extensions/ContentExtensions.cs index df0e58d878..ba736c5b13 100644 --- a/src/Umbraco.Core/Extensions/ContentExtensions.cs +++ b/src/Umbraco.Core/Extensions/ContentExtensions.cs @@ -269,11 +269,17 @@ public static class ContentExtensions /// /// to retrieve ancestors for /// An Enumerable list of integer ids - public static IEnumerable? GetAncestorIds(this IContent content) => - content.Path?.Split(Constants.CharArrays.Comma) + public static IEnumerable? GetAncestorIds(this IContent content) + { + if (string.IsNullOrWhiteSpace(content.Path)) + { + return null; + } + + return content.Path.Split(Constants.CharArrays.Comma) .Where(x => x != Constants.System.RootString && x != content.Id.ToString(CultureInfo.InvariantCulture)) - .Select(s => - int.Parse(s, CultureInfo.InvariantCulture)); + .Select(s => int.Parse(s, CultureInfo.InvariantCulture)); + } #endregion From b5842d6a7dcf4ac5a41116db2cf4d270cd585dad Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 8 Nov 2022 13:34:12 +0100 Subject: [PATCH 12/85] Move block grid single area rendering to its own dedicated view (#13359) --- .../EmbeddedResources/BlockGrid/area.cshtml | 10 +++++++ .../EmbeddedResources/BlockGrid/areas.cshtml | 8 +---- .../Migrations/Upgrade/UmbracoPlan.cs | 6 ++-- .../V_10_4_0/AddBlockGridPartialViews.cs | 29 +++++++++++++++++++ .../Extensions/BlockGridTemplateExtensions.cs | 26 +++++++++++++++++ .../Views/Partials/blockgrid/area.cshtml | 10 +++++++ .../Views/Partials/blockgrid/areas.cshtml | 8 +---- 7 files changed, 81 insertions(+), 16 deletions(-) create mode 100644 src/Umbraco.Core/EmbeddedResources/BlockGrid/area.cshtml create mode 100644 src/Umbraco.Infrastructure/Migrations/Upgrade/V_10_4_0/AddBlockGridPartialViews.cs create mode 100644 src/Umbraco.Web.UI/Views/Partials/blockgrid/area.cshtml diff --git a/src/Umbraco.Core/EmbeddedResources/BlockGrid/area.cshtml b/src/Umbraco.Core/EmbeddedResources/BlockGrid/area.cshtml new file mode 100644 index 0000000000..361484757c --- /dev/null +++ b/src/Umbraco.Core/EmbeddedResources/BlockGrid/area.cshtml @@ -0,0 +1,10 @@ +@using Umbraco.Extensions +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage + +
+ @await Html.GetBlockGridItemsHtmlAsync(Model) +
diff --git a/src/Umbraco.Core/EmbeddedResources/BlockGrid/areas.cshtml b/src/Umbraco.Core/EmbeddedResources/BlockGrid/areas.cshtml index 1afd941a76..cf65b36a3a 100644 --- a/src/Umbraco.Core/EmbeddedResources/BlockGrid/areas.cshtml +++ b/src/Umbraco.Core/EmbeddedResources/BlockGrid/areas.cshtml @@ -8,12 +8,6 @@ style="--umb-block-grid--area-grid-columns: @(Model.AreaGridColumns?.ToString() ?? Model.GridColumns?.ToString() ?? "12");"> @foreach (var area in Model.Areas) { -
- @await Html.GetBlockGridItemsHtmlAsync(area) -
+ @await Html.GetBlockGridItemAreaHtmlAsync(area) } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index d0aefade84..c80d70fa1f 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -5,7 +5,6 @@ using Umbraco.Cms.Core.Semver; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.Common; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_0_0; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_2_0; -using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_3_0; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_0_0; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_0_1; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_1_0; @@ -296,6 +295,9 @@ public class UmbracoPlan : MigrationPlan To("{79D8217B-5920-4C0E-8E9A-3CF8FA021882}"); // To 10.3.0 - To("{56833770-3B7E-4FD5-A3B6-3416A26A7A3F}"); + To("{56833770-3B7E-4FD5-A3B6-3416A26A7A3F}"); + + // To 10.4.0 + To("{3F5D492A-A3DB-43F9-A73E-9FEE3B180E6C}"); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_10_4_0/AddBlockGridPartialViews.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_10_4_0/AddBlockGridPartialViews.cs new file mode 100644 index 0000000000..fafd5b1a6f --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_10_4_0/AddBlockGridPartialViews.cs @@ -0,0 +1,29 @@ +using Umbraco.Cms.Infrastructure.Templates.PartialViews; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_4_0; + +public class AddBlockGridPartialViews : MigrationBase +{ + private readonly IPartialViewPopulator _partialViewPopulator; + private const string FolderPath = "/Views/Partials/blockgrid"; + private static readonly string[] _filesToAdd = + { + "area.cshtml", + }; + + public AddBlockGridPartialViews(IMigrationContext context, IPartialViewPopulator partialViewPopulator) : base(context) + => _partialViewPopulator = partialViewPopulator; + + protected override void Migrate() + { + var embeddedBasePath = _partialViewPopulator.CoreEmbeddedPath + ".BlockGrid"; + + foreach (var fileName in _filesToAdd) + { + _partialViewPopulator.CopyPartialViewIfNotExists( + _partialViewPopulator.GetCoreAssembly(), + $"{embeddedBasePath}.{fileName}", + $"{FolderPath}/{fileName}"); + } + } +} diff --git a/src/Umbraco.Web.Common/Extensions/BlockGridTemplateExtensions.cs b/src/Umbraco.Web.Common/Extensions/BlockGridTemplateExtensions.cs index ee0375da4f..5951c6e009 100644 --- a/src/Umbraco.Web.Common/Extensions/BlockGridTemplateExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/BlockGridTemplateExtensions.cs @@ -14,6 +14,7 @@ public static class BlockGridTemplateExtensions public const string DefaultTemplate = "default"; public const string DefaultItemsTemplate = "items"; public const string DefaultItemAreasTemplate = "areas"; + public const string DefaultItemAreaTemplate = "area"; #region Async @@ -60,6 +61,20 @@ public static class BlockGridTemplateExtensions public static async Task GetBlockGridItemAreasHtmlAsync(this IHtmlHelper html, BlockGridItem item, string template = DefaultItemAreasTemplate) => await html.PartialAsync(DefaultFolderTemplate(template), item); + public static async Task GetBlockGridItemAreaHtmlAsync(this IHtmlHelper html, BlockGridArea area, string template = DefaultItemAreaTemplate) + => await html.PartialAsync(DefaultFolderTemplate(template), area); + + public static async Task GetBlockGridItemAreaHtmlAsync(this IHtmlHelper html, BlockGridItem item, string areaAlias, string template = DefaultItemAreaTemplate) + { + BlockGridArea? area = item.Areas.FirstOrDefault(a => a.Alias == areaAlias); + if (area == null) + { + return new HtmlString(string.Empty); + } + + return await GetBlockGridItemAreaHtmlAsync(html, area, template); + } + #endregion #region Sync @@ -95,6 +110,17 @@ public static class BlockGridTemplateExtensions public static IHtmlContent GetBlockGridItemAreasHtml(this IHtmlHelper html, BlockGridItem item, string template = DefaultItemAreasTemplate) => html.Partial(DefaultFolderTemplate(template), item); + public static IHtmlContent GetBlockGridItemAreaHtml(this IHtmlHelper html, BlockGridArea area, string template = DefaultItemAreaTemplate) + => html.Partial(DefaultFolderTemplate(template), area); + + public static IHtmlContent GetBlockGridItemAreaHtml(this IHtmlHelper html, BlockGridItem item, string areaAlias, string template = DefaultItemAreaTemplate) + { + BlockGridArea? area = item.Areas.FirstOrDefault(a => a.Alias == areaAlias); + return area != null + ? GetBlockGridItemAreaHtml(html, area, template) + : new HtmlString(string.Empty); + } + #endregion private static string DefaultFolderTemplate(string template) => $"{DefaultFolder}{template}"; diff --git a/src/Umbraco.Web.UI/Views/Partials/blockgrid/area.cshtml b/src/Umbraco.Web.UI/Views/Partials/blockgrid/area.cshtml new file mode 100644 index 0000000000..361484757c --- /dev/null +++ b/src/Umbraco.Web.UI/Views/Partials/blockgrid/area.cshtml @@ -0,0 +1,10 @@ +@using Umbraco.Extensions +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage + +
+ @await Html.GetBlockGridItemsHtmlAsync(Model) +
diff --git a/src/Umbraco.Web.UI/Views/Partials/blockgrid/areas.cshtml b/src/Umbraco.Web.UI/Views/Partials/blockgrid/areas.cshtml index 94eef55ad8..30f987ce1e 100644 --- a/src/Umbraco.Web.UI/Views/Partials/blockgrid/areas.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/blockgrid/areas.cshtml @@ -8,12 +8,6 @@ style="--umb-block-grid--area-grid-columns: @(Model.AreaGridColumns?.ToString() ?? Model.GridColumns?.ToString() ?? "12");"> @foreach (var area in Model.Areas) { -
- @await Html.GetBlockGridItemsHtmlAsync(area) -
+ @await Html.GetBlockGridItemAreaHtmlAsync(area) } From 47c0bf988404f201f67453384f32241832f71345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 11 Nov 2022 22:15:07 +0100 Subject: [PATCH 13/85] Block Grid Editor Improvements (#13282) --- .../umbBlockGridDemoHeadlineBlock.html | 10 +- .../umbBlockGridDemoImageBlock.html | 30 +-- .../umbBlockGridDemoRichTextBlock.html | 11 +- .../umbBlockGridDemoTwoColumnLayoutBlock.html | 49 +---- .../EmbeddedResources/Lang/da.xml | 12 +- .../EmbeddedResources/Lang/en.xml | 14 +- .../EmbeddedResources/Lang/en_us.xml | 12 +- .../PropertyEditors/BlockGridConfiguration.cs | 6 + .../property/umbproperty.directive.js | 3 +- .../property/umbpropertyactions.component.js | 45 ++-- .../filters/mediaItemResolver.filter.js | 65 ++++++ .../subheader/umb-editor-sub-header.less | 6 + src/Umbraco.Web.UI.Client/src/less/rte.less | 2 +- .../components/property/umb-property.html | 4 +- .../gridblock/gridblock.editor.html | 19 +- .../gridinlineblock.editor.controller.js | 64 +++++- .../gridinlineblock.editor.html | 74 ++++--- .../gridinlineblock.editor.less | 173 --------------- .../gridsortblock/gridsortblock.editor.html | 92 ++++++++ .../heroblock/heroblock.editor.controller.js | 45 ---- .../mediablock.editor.controller.js | 47 ---- .../blockgrid/blockgridui.less | 207 ++++++------------ ...ckconfiguration.area.overlay.controller.js | 12 + ...kgrid.blockconfiguration.area.overlay.html | 9 +- .../blockgrid.blockconfiguration.html | 14 +- .../blockgrid.blockconfiguration.less | 27 +-- .../blockgrid.blockconfiguration.overlay.html | 15 +- .../blockgrid.groupconfiguration.html | 6 +- .../umb-block-grid-area-allowance-editor.html | 14 +- .../umb-block-grid-area-allowance-editor.less | 7 +- .../umbBlockGridAreaEditor.component.js | 38 +++- .../blockgrid/umb-block-grid-entries.html | 4 +- .../blockgrid/umb-block-grid-entry.html | 31 +-- .../umb-block-grid-property-editor.html | 19 +- .../umb-block-grid-property-editor.less | 4 + .../umb-block-grid-render-area-slots.html | 3 + .../umbBlockGridPropertyEditor.component.js | 148 +++++++++++-- .../blockgrid/umbblockgridblock.component.js | 2 +- .../umbblockgridentries.component.js | 37 +++- .../blockgrid/umbblockgridentry.component.js | 207 ++++++++++++++---- .../umbblockgridrenderareaslots.directive.js | 20 ++ .../blockgrid/umbblockgridroot.component.js | 4 +- .../blockgrid/umbraco-blockgridlayout.css | 9 +- .../propertyeditors/rte/rte.controller.js | 20 +- .../propertyeditors/rte-controller.spec.js | 6 +- 45 files changed, 934 insertions(+), 712 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/filters/mediaItemResolver.filter.js delete mode 100644 src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.less create mode 100644 src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridsortblock/gridsortblock.editor.html delete mode 100644 src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/heroblock/heroblock.editor.controller.js delete mode 100644 src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/mediablock/mediablock.editor.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-render-area-slots.html create mode 100644 src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridrenderareaslots.directive.js diff --git a/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoHeadlineBlock.html b/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoHeadlineBlock.html index 523b0fe7d3..495c704e28 100644 --- a/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoHeadlineBlock.html +++ b/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoHeadlineBlock.html @@ -1,11 +1,4 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoRichTextBlock.html b/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoRichTextBlock.html index dbe157cf84..b3362fcda9 100644 --- a/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoRichTextBlock.html +++ b/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoRichTextBlock.html @@ -1,13 +1,5 @@
-
- \ No newline at end of file + \ No newline at end of file diff --git a/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoTwoColumnLayoutBlock.html b/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoTwoColumnLayoutBlock.html index ccc3af22e8..bc290fe8fb 100644 --- a/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoTwoColumnLayoutBlock.html +++ b/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoTwoColumnLayoutBlock.html @@ -1,50 +1,3 @@ -
- - +
\ No newline at end of file diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml index 387f1ef652..049b617f93 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml @@ -2201,6 +2201,8 @@ Mange hilsner fra Umbraco robotten Avanceret Skjul indholdseditoren Skjul indholds redigerings knappen samt indholdseditoren i Blok Redigerings vinduet + Direkte redigering + Tilføjer direkte redigering a det første felt. Yderligere felter optræder kun i redigerings vinduet. Du har lavet ændringer til dette indhold. Er du sikker på at du vil kassere dem? Annuller oprettelse? @@ -2218,6 +2220,7 @@ Mange hilsner fra Umbraco robotten Tillad kun specifikke blok-typer Tilladte blok-typer Vælg de blok-typer, der er tilladt i dette område, og evt. også hvor mange af hver type, redaktørerne skal tilføje til området. + Når denne er tom er alle block-typer tilladt for områder tilladt. Er du sikker på, at du vil slette dette område? Alle blokke, der er oprettet i dette område, vil blive slettet. Layout-opsætning @@ -2255,9 +2258,9 @@ Mange hilsner fra Umbraco robotten Tilføj katalog udseende Tilføj Blok Tilføj gruppe - Tilføj gruppe eller block - Sæt minimum krav for denne tilladelse - Set maksimum krav for denne tilladelse + Tilføj gruppe eller Blok + Sæt minimum krav + Sæt maksimum krav Blok Blok Indstillinger @@ -2267,6 +2270,9 @@ Mange hilsner fra Umbraco robotten Installer demo konfiguration Dette indeholder Blokke for Overskrift, Beriget-Tekst, Billede og To-Koloners-Layout.]]> Installer + Sortings tilstand + Afslut sortings tilstand + Dette område alias skal være unikt sammenlignet med andre områder af denne Blok. Hvad er Indholdsskabeloner? diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 1e41048126..3d3116afc7 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -2749,7 +2749,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Settings Advanced Hide content editor - Hide the content edit button and the content editor from the Block Editor overlay + Hide the content edit button and the content editor from the Block Editor overlay. + Inline editing + Enables inline editing for the first Property. Additional properties can be edited in the overlay. You have made changes to this content. Are you sure you want to discard them? Discard creation? @@ -2781,6 +2783,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Make this block available in the root of the layout. Allow in areas Make this block available within other Blocks. + When empty all Blocks allowed for Areas can be created. Areas Grid Columns for Areas Define how many columns that will be available for areas. If not defined, the number of columns defined for the entire layout will be used. @@ -2807,17 +2810,20 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Add Block Add group Pick group or Block - Set minimum requirement for this allowance - Set maximum requirement for this allowance + Set a minimum requirement + Set a maximum requirement Block Block Settings Areas Advanced - Allowance + Permissions Install Sample Configuration Install + Sort mode + End sort mode + This Areas Alias must be unique compared to the other Areas of this Block. What are Content Templates? diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index ed6bd37157..29817fc8ed 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -2853,6 +2853,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Advanced Hide content editor Hide the content edit button and the content editor from the Block Editor overlay + Inline editing + Enables inline editing for the first Property. Additional properties can be edited in the overlay. You have made changes to this content. Are you sure you want to discard them? Discard creation? @@ -2884,6 +2886,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Make this block available in the root of the layout. Allow in areas Make this block available within other Blocks. + When empty all Blocks allowed for Areas can be created. Areas Grid Columns for Areas Define how many columns that will be available for areas. If not defined, the number of columns defined for the entire layout will be used. @@ -2910,17 +2913,20 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Add Block Add group Pick group or Block - Set minimum requirement for this allowance - Set maximum requirement for this allowance + Set a minimum requirement + Set a maximum requirement Block Block Settings Areas Advanced - Allowance + permissions Install Sample Configuration Install + Sort mode + End sort mode + This Areas Alias must be unique compared to the other Areas of this Block. What are Content Templates? diff --git a/src/Umbraco.Core/PropertyEditors/BlockGridConfiguration.cs b/src/Umbraco.Core/PropertyEditors/BlockGridConfiguration.cs index 0a5bacad14..f635eb6324 100644 --- a/src/Umbraco.Core/PropertyEditors/BlockGridConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/BlockGridConfiguration.cs @@ -40,6 +40,9 @@ public class BlockGridConfiguration [DataMember(Name ="columnSpanOptions")] public BlockGridColumnSpanOption[] ColumnSpanOptions { get; set; } = Array.Empty(); + [DataMember(Name ="rowMinSpan")] + public int? RowMinSpan { get; set; } + [DataMember(Name ="rowMaxSpan")] public int? RowMaxSpan { get; set; } @@ -82,6 +85,9 @@ public class BlockGridConfiguration [DataMember(Name ="editorSize")] public string? EditorSize { get; set; } + [DataMember(Name ="inlineEditing")] + public bool InlineEditing { get; set; } + [DataMember(Name ="forceHideContentEditorInOverlay")] public bool ForceHideContentEditorInOverlay { get; set; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js index 702cd5aeda..5a30f81d4b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js @@ -24,7 +24,8 @@ // optional, if set this will be used for the property alias validation path (hack required because NC changes the actual property.alias :/) propertyAlias: "@", showInherit: "<", - inheritsFrom: "<" + inheritsFrom: "<", + hideLabel: " { - - if (action.labelKey) { - localizationService.localize(action.labelKey, (action.labelTokens || []), action.label).then(data => { - action.label = data; - }); - - action.useLegacyIcon = action.useLegacyIcon === false ? false : true; - action.icon = (action.useLegacyIcon ? 'icon-' : '') + action.icon; - } - }); + updateActions(); } } + + function updateActions() { + + Utilities.forEach(vm.actions || [], action => { + + if (action.labelKey) { + localizationService.localize(action.labelKey, (action.labelTokens || []), action.label).then(data => { + action.label = data; + }); + + action.useLegacyIcon = action.useLegacyIcon === false ? false : true; + action.icon = (action.useLegacyIcon && action.icon.indexOf('icon-') !== 0 ? 'icon-' : '') + action.icon; + } + }); + } } var umbPropertyActionsComponent = { diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/mediaItemResolver.filter.js b/src/Umbraco.Web.UI.Client/src/common/filters/mediaItemResolver.filter.js new file mode 100644 index 0000000000..be0338093c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/filters/mediaItemResolver.filter.js @@ -0,0 +1,65 @@ +(function () { + "use strict"; + + function mediaItemResolverFilterService(mediaResource, eventsService) { + + var mediaKeysRequest = []; + var mediaItemCache = []; + + var service = { + + getByKey: function (key) { + // Is it cached, then get that: + const cachedMediaItem = mediaItemCache.find(cache => key === cache.key); + if(cachedMediaItem) { + return cachedMediaItem; + } + + // check its not already being loaded, and then start loading: + if(mediaKeysRequest.indexOf(key) === -1) { + mediaKeysRequest.push(key); + mediaResource.getById(key).then(function (mediaItem) { + if(mediaItem) { + mediaItemCache.push(mediaItem); + } + }); + } + + return null; + } + }; + + eventsService.on("editors.media.saved", function (name, args) { + const index = mediaItemCache.findIndex(cache => cache.key === args.media.key); + if(index !== -1) { + mediaItemCache[index] = args.media; + } + }); + + return service; + + } + + angular.module("umbraco.filters").factory("mediaItemResolverFilterService", mediaItemResolverFilterService); + + + // Filter loads Media Item Model from a Media Key. + // Usage: {{ myMediaProperty[0].mediaKey | mediaItemResolver }} + angular.module("umbraco.filters").filter("mediaItemResolver", function (mediaItemResolverFilterService) { + + mediaItemResolverFilter.$stateful = true; + function mediaItemResolverFilter(input) { + + // Check we have a value at all + if (typeof input === 'string' && input.length > 0) { + return mediaItemResolverFilterService.getByKey(input); + } + + return null; + } + + return mediaItemResolverFilter; + + }); + +})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less index 0dd7bfc7f4..4eaf00724c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less @@ -20,6 +20,12 @@ background-color: @white; border-color: @white; } +.umb-editor-sub-header--blue { + background-color: @ui-selected-border; + border-color: @ui-selected-border; + color: @white; + border-radius: 3px; +} .umb-editor-sub-header.--state-selection { padding-left: 10px; diff --git a/src/Umbraco.Web.UI.Client/src/less/rte.less b/src/Umbraco.Web.UI.Client/src/less/rte.less index f9406c72b8..c082e5c2c8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/rte.less +++ b/src/Umbraco.Web.UI.Client/src/less/rte.less @@ -20,7 +20,7 @@ } .umb-rte.--initialized .umb-rte-editor-con { height:auto; - min-height: 100px; + min-height: 95px; visibility: visible; } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html index f00ba725b2..cc41896a0f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html @@ -1,13 +1,13 @@
+ ng-class="{'hidelabel':vm.hideLabel || vm.property.hideLabel, '--label-on-top':vm.property.labelOnTop, 'umb-control-group__listview': vm.property.alias === '_umb_containerView'}">
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridblock/gridblock.editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridblock/gridblock.editor.html index a4efb41717..710dd15e53 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridblock/gridblock.editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridblock/gridblock.editor.html @@ -53,6 +53,23 @@ padding-top:2px; padding-bottom:2px; } + + :host { + --inherited--column-gap: var(--umb-block-grid--column-gap, 10px); + --inherited--row-gap: var(--umb-block-grid--row-gap, 10px); + --inherited--areas-column-gap: var(--umb-block-grid--areas-column-gap, 10px); + --inherited--areas-row-gap: var(--umb-block-grid--areas-row-gap, 10px); + } + + [part='area-container'] { + box-sizing: border-box; + padding: 10px; + --umb-block-grid--column-gap: var(--inherited--column-gap, 10px); + --umb-block-grid--row-gap: var(--inherited--row-gap, 10px); + --umb-block-grid--areas-column-gap: var(--inherited--areas-column-gap, 10px); + --umb-block-grid--areas-row-gap: var(--inherited--areas-row-gap, 10px); + } +
@@ -64,6 +81,6 @@ {{block.label}} - +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.controller.js index 2a77e81b5c..3d55ea1836 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.controller.js @@ -1,27 +1,71 @@ (function () { 'use strict'; - function GridInlineBlockEditor($scope, $element) { + function GridInlineBlockEditor($scope, $compile, $element) { const vm = this; + var propertyEditorElement; + vm.$onInit = function() { - const host = $element[0].getRootNode(); + + vm.property = $scope.block.content.variants[0].tabs[0]?.properties[0]; - console.log(document.styleSheets) + if (vm.property) { + vm.propertySlotName = "umbBlockGridProxy_" + vm.property.alias + "_" + String.CreateGuid(); + + propertyEditorElement = $('
'); + propertyEditorElement.html( + ` + - for (const stylesheet of document.styleSheets) { + + - console.log(stylesheet); - const styleEl = document.createElement('link'); - styleEl.setAttribute('rel', 'stylesheet'); - styleEl.setAttribute('type', stylesheet.type); - styleEl.setAttribute('href', stylesheet.href); + + ` + ); + + $element[0].addEventListener('umb-rte-focus', onRteFocus); + $element[0].addEventListener('umb-rte-blur', onRteBlur); - host.appendChild(styleEl); + const connectedCallback = () => { + + $compile(propertyEditorElement)($scope) + }; + + const event = new CustomEvent("UmbBlockGrid_AppendProperty", {composed: true, bubbles: true, detail: {'property': propertyEditorElement[0], 'contentUdi': $scope.block.layout.contentUdi, 'slotName': vm.propertySlotName, 'connectedCallback':connectedCallback}}); + + $element[0].dispatchEvent(event); + } } + function onRteFocus() { + $element[0].classList.add('umb-block-grid--force-focus'); + } + function onRteBlur() { + $element[0].classList.remove('umb-block-grid--force-focus'); + } + + vm.$onDestroy = function() { + if (vm.property) { + const event = new CustomEvent("UmbBlockGrid_RemoveProperty", {composed: true, bubbles: true, detail: {'slotName': vm.propertySlotName}}); + $element[0].dispatchEvent(event); + } + + $element[0].removeEventListener('umb-rte-focus', onRteFocus); + $element[0].removeEventListener('umb-rte-blur', onRteBlur); + propertyEditorElement = null; + } + } angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockEditor.GridInlineBlockEditor", GridInlineBlockEditor); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.html index ca12c9dd98..3a67a1be88 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.html @@ -1,24 +1,6 @@ -
- -
- -
-
- - - - -
- +
+ + {{block.label}} +
- + - +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.less deleted file mode 100644 index b8ffcff3ec..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.less +++ /dev/null @@ -1,173 +0,0 @@ -.blockelement-inlineblock-editor { - display: block; - margin-bottom: 4px; - margin-top: 4px; - border: 1px solid @gray-9; - border-radius: @baseBorderRadius; - transition: border-color 120ms, background-color 120ms; - - .umb-block-list__block:not(.--active) &:hover { - border-color: @gray-8; - } - - .umb-editor-tab-bar { - margin: 0; - position: static; - padding: 0; - } - - > button { - width: 100%; - min-height: 48px; - cursor: pointer; - color: @ui-action-discreet-type; - text-align: left; - padding-left: 10px; - padding-bottom: 2px; - user-select: none; - background-color: white; - - .caret { - vertical-align: middle; - transform: rotate(-90deg); - transition: transform 80ms ease-out; - } - - .icon { - font-size: 1.1rem; - display: inline-block; - vertical-align: middle; - } - - span.name { - position: relative; - display: inline-block; - vertical-align: middle; - } - - &:hover { - color: @ui-action-discreet-type-hover; - border-color: @gray-8; - } - } - - ng-form.ng-invalid-val-server-match-content > .umb-block-list__block > .umb-block-list__block--content > div > & { - > button { - color: @formErrorText; - - .show-validation-type-warning & { - color: @formWarningText; - } - - span.caret { - border-top-color: @formErrorText; - - .show-validation-type-warning & { - border-top-color: @formWarningText; - } - } - } - } - - ng-form.ng-invalid-val-server-match-content > .umb-block-list__block:not(.--active) > .umb-block-list__block--content > div > & { - > button { - span.name { - &::after { - content: "!"; - text-align: center; - position: absolute; - top: -6px; - right: -15px; - min-width: 10px; - color: @white; - background-color: @ui-active-type; - border: 2px solid @white; - border-radius: 50%; - font-size: 10px; - font-weight: bold; - padding: 2px; - line-height: 10px; - background-color: @formErrorText; - - .show-validation-type-warning & { - background-color: @formWarningText; - } - - font-weight: 900; - animation-duration: 1.4s; - animation-iteration-count: infinite; - animation-name: blockelement-inlineblock-editor--badge-bounce; - animation-timing-function: ease; - - @keyframes blockelement-inlineblock-editor--badge-bounce { - 0% { - transform: translateY(0); - } - - 20% { - transform: translateY(-4px); - } - - 40% { - transform: translateY(0); - } - - 55% { - transform: translateY(-2px); - } - - 70% { - transform: translateY(0); - } - - 100% { - transform: translateY(0); - } - } - } - } - } - } -} - -.umb-block-list__block.--active { - border-color: @gray-8; - box-shadow: 0 0 2px 0px rgba(0, 0, 0, 0.05); - - > .umb-block-list__block--content { - > .umb-block-list__block--view { - > .blockelement-inlineblock-editor { - > button { - > .caret { - transform: rotate(0deg); - } - } - } - } - } -} - -.blockelement-inlineblock-editor__inner { - border-top: 1px solid @gray-8; - background-color: @gray-12; - - > * > * > * > .umb-group-panel { - background-color: transparent; - box-shadow: none; - margin-top: 10px; - margin-bottom: 0; - > .umb-group-panel__content .umb-property { - margin-bottom: 20px; - } - } - .umb-group-panel + .umb-group-panel { - margin-top: 20px; - } - &.--singleGroup > * > * > * > .umb-group-panel { - margin-top: 0; - > .umb-group-panel__header { - display: none; - } - } - -} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridsortblock/gridsortblock.editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridsortblock/gridsortblock.editor.html new file mode 100644 index 0000000000..bd6eebfac6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridsortblock/gridsortblock.editor.html @@ -0,0 +1,92 @@ + + +
+ + + + +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/heroblock/heroblock.editor.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/heroblock/heroblock.editor.controller.js deleted file mode 100644 index 2dc717d8ef..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/heroblock/heroblock.editor.controller.js +++ /dev/null @@ -1,45 +0,0 @@ -(function () { - 'use strict'; - - function HeroBlockEditor($scope, mediaResource, mediaHelper) { - - var unsubscribe = []; - - const bc = this; - - $scope.$watch("block.data.image", function(newValue, oldValue) { - if (newValue !== oldValue) { - bc.retrieveMedia(); - } - }, true); - - bc.retrieveMedia = function() { - - if($scope.block.data.image && $scope.block.data.image.length > 0) { - mediaResource.getById($scope.block.data.image[0].mediaKey).then(function (mediaEntity) { - - var mediaPath = mediaEntity.mediaLink; - - //set a property on the 'scope' for the returned media object. - bc.mediaName = mediaEntity.name; - bc.isImage = mediaHelper.detectIfImageByExtension(mediaPath); - bc.imageSource = mediaPath; - }); - } - } - - bc.retrieveMedia(); - - - - $scope.$on("$destroy", function () { - for (const subscription of unsubscribe) { - subscription(); - } - }); - - } - - angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockEditor.HeroBlockEditor", HeroBlockEditor); - -})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/mediablock/mediablock.editor.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/mediablock/mediablock.editor.controller.js deleted file mode 100644 index 2f79f3b9f1..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/mediablock/mediablock.editor.controller.js +++ /dev/null @@ -1,47 +0,0 @@ -(function () { - 'use strict'; - - function MediaBlockEditor($scope, mediaResource, mediaHelper) { - - var unsubscribe = []; - - const bc = this; - - $scope.$watch("block.data.image", function(newValue, oldValue) { - if (newValue !== oldValue) { - bc.retrieveMedia(); - } - }, true); - - bc.retrieveMedia = function() { - - if($scope.block.data.image && $scope.block.data.image.length > 0) { - mediaResource.getById($scope.block.data.image[0].mediaKey).then(function (mediaEntity) { - - var mediaPath = mediaEntity.mediaLink; - - //set a property on the 'scope' for the returned media object - bc.icon = mediaEntity.contentType.icon; - bc.mediaName = mediaEntity.name; - bc.fileExtension = mediaHelper.getFileExtension(mediaPath); - bc.isImage = mediaHelper.detectIfImageByExtension(mediaPath); - bc.imageSource = mediaHelper.getThumbnailFromPath(mediaPath); - }); - } - } - - bc.retrieveMedia(); - - - - $scope.$on("$destroy", function () { - for (const subscription of unsubscribe) { - subscription(); - } - }); - - } - - angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockEditor.MediaBlockEditor", MediaBlockEditor); - -})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridui.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridui.less index 6cfdb05482..38ff8086c6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridui.less +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridui.less @@ -21,13 +21,18 @@ .umb-block-grid__layout-item { position: relative; &:hover { - z-index: 3; -/* - > .umb-block-grid__force-left, - > .umb-block-grid__force-right { + + > ng-form > .umb-block-grid__block--context { z-index: 4; } - */ + + > ng-form > .umb-block-grid__block--inline-create-button, + > ng-form > .umb-block-grid__block--validation-border, + > ng-form > .umb-block-grid__block--actions, + > ng-form > .umb-block-grid__force-left, + > ng-form > .umb-block-grid__force-right { + z-index: 3; + } } } @@ -49,52 +54,12 @@ ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--acti pointer-events: none; } -/*.umb-block-grid__block--validation-badge { - display:none; -} -ng-form.ng-invalid-val-server-match-settings > .umb-block-grid__block:not(.--active) > .umb-block-grid__block--validation-badge, -ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--active) > .umb-block-grid__block--validation-badge { - display:block; - text-align: center; - position: absolute; - top: -9px; - right: -9px; - min-width: 10px; - color: @white; - border: 2px solid @white; - border-radius: 50%; - font-size: 10px; - font-weight: bold; - padding: 2px; - line-height: 10px; - background-color: @formErrorText; - .show-validation-type-warning & { - background-color: @formWarningText; - } - font-weight: 900; - pointer-events: none; - - animation-duration: 1.4s; - animation-iteration-count: infinite; - animation-name: blockelement-inlineblock-editor--badge-bounce; - animation-timing-function: ease; - @keyframes blockelement-inlineblock-editor--badge-bounce { - 0% { transform: translateY(0); } - 20% { transform: translateY(-4px); } - 40% { transform: translateY(0); } - 55% { transform: translateY(-2px); } - 70% { transform: translateY(0); } - 100% { transform: translateY(0); } - } -} -*/ - .umb-block-grid__block { position: relative; width: 100%; height: 100%; - --umb-block-grid__block--show-ui: 0;// Publicly available. + --umb-block-grid--block-ui-opacity: 0; --umb-block-grid--hint-area-ui: 0; &::after { @@ -136,7 +101,6 @@ ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--acti --umb-block-grid--hint-area-ui: 1; &::after { - /*border-color: @blueDark;*/ display: var(--umb-block-grid--block-ui-display, block); animation: umb-block-grid__block__border-pulse 400ms ease-in-out alternate infinite; @keyframes umb-block-grid__block__border-pulse { @@ -211,18 +175,21 @@ ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--acti } } &.--block-ui-visible { + > .umb-block-grid__block--context { /* take full width to prevent interaction with elements behind.*/ left: 0; } .umb-block-grid__area-container, .umb-block-grid__block--view::part(area-container) { --umb-block-grid--block-ui-display: none; - .umb-block-grid__layout-item { - pointer-events: none; - } - .umb-block-grid__block { - pointer-events: none; - } + pointer-events: none; + } + .umb-block-grid__layout-item { + pointer-events: none; + } + .umb-block-grid__block { + pointer-events: none; + --umb-block-grid--block-ui-opacity: 0; } } @@ -236,48 +203,34 @@ ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--acti &.--active { /** Avoid displaying hover when dragging-mode */ - --umb-block-grid--block_ui-opacity: calc(1 - var(--umb-block-grid--dragging-mode, 0)); + --umb-block-grid--block-ui-opacity: calc(1 - var(--umb-block-grid--dragging-mode, 0)); > .umb-block-grid__block--context { - opacity: var(--umb-block-grid--block_ui-opacity); + opacity: var(--umb-block-grid--block-ui-opacity); } &:not(.--scale-mode) { > .umb-block-grid__block--actions { - opacity: var(--umb-block-grid--block_ui-opacity); + opacity: var(--umb-block-grid--block-ui-opacity); } > umb-block-grid-block > umb-block-grid-entries > .umb-block-grid__layout-container > .umb-block-grid__area-actions { - opacity: var(--umb-block-grid--block_ui-opacity); + opacity: var(--umb-block-grid--block-ui-opacity); } } > .umb-block-grid__scale-handler { - opacity: var(--umb-block-grid--block_ui-opacity); + opacity: var(--umb-block-grid--block-ui-opacity); } > .umb-block-grid__force-left, > .umb-block-grid__force-right { - opacity: var(--umb-block-grid--block_ui-opacity); + opacity: var(--umb-block-grid--block-ui-opacity); } } - - /* - &.--show-validation { - ng-form.ng-invalid-val-server-match-content > & { - border: 2px solid @formErrorText; - border-radius: @baseBorderRadius; - } - } - */ } ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__block--actions { opacity: 1; } -/* -ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__block--context { - opacity: 1; -} -*/ .umb-block-grid__block--view { height: 100%; @@ -291,10 +244,20 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl top: -20px; right: 0; font-size: 12px; - z-index: 2; + z-index: 4; display: var(--umb-block-grid--block-ui-display, flex); justify-content: end; + /** prevent interaction with inline-create button just beneath the context-bar: */ + ::after { + content: ''; + position: absolute; + top: 100%; + left: 0; + right: 0; + height: 12px; + } + .__context-bar { padding: 0 9px; padding-top: 1px; @@ -509,27 +472,6 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl } } -/* -umb-block-grid-block { - - > div { - position: relative; - width: 100%; - min-height: @umb-block-grid__item_minimum_height; - background-color: @white; - border-radius: @baseBorderRadius; - box-sizing: border-box; - } - -} -*/ - -/* -.blockelement__draggable-element { - cursor: grab; -} -*/ - .umb-block-grid__scale-handler { cursor: nwse-resize; @@ -586,7 +528,7 @@ umb-block-grid-block { .umb-block-grid__block--inline-create-button { top: 0px; position: absolute; - z-index: 1; + z-index: 1; /** overwritten for the first one of an area. */ /** Avoid showing inline-create in dragging-mode */ opacity: calc(1 - var(--umb-block-grid--dragging-mode, 0)); @@ -594,6 +536,12 @@ umb-block-grid-block { .umb-block-grid__block--inline-create-button.--above { left: 0; width: 100%; + + top: calc(var(--umb-block-grid--row-gap, 0px) * -0.5); +} +.umb-block-grid__layout-item:first-of-type .umb-block-grid__block--inline-create-button.--above { + /* Do not use row-gap if the first one. */ + top: 0; } .umb-block-grid__block--inline-create-button.--above.--at-root { /* If at root, and full-width then become 40px wider: */ @@ -601,14 +549,9 @@ umb-block-grid-block { left: calc(-20px * var(--calc)); width: calc(100% + 40px * var(--calc)); } + .umb-block-grid__block--inline-create-button.--after { - right: 1px; -} -.umb-block-grid__block--inline-create-button.--after.--detector { - width: 10px; - margin-right: -10px; - height: 100%; - z-index: 0; + right: calc(1px - (var(--umb-block-grid--column-gap, 0px) * 0.5)); } .umb-block-grid__block--inline-create-button.--after.--at-root { /* If at root, and full-width then move a little out to the right: */ @@ -624,8 +567,8 @@ umb-block-grid-block { pointer-events: none; } -.umb-block-grid__block--after-inline-create-button { - z-index:2; +.umb-block-grid__block--last-inline-create-button { + z-index:4; width: 100%; /* Move inline create button slightly up, to avoid collision with others*/ margin-bottom: -7px; @@ -746,13 +689,11 @@ umb-block-grid-block { align-items: center; justify-content: center; - /* TODO: dont use --umb-text-color, its temporary to inherit UI */ - color: var(--umb-text-color, @ui-action-discreet-type); + color: var(--umb-block-grid--text-color, @ui-action-discreet-type); font-weight: bold; padding: 5px 15px; - /* TODO: dont use --umb-text-color, its temporary to inherit UI */ - border: 1px dashed var(--umb-text-color, @ui-action-discreet-border); + border: 1px dashed var(--umb-block-grid--text-color, @ui-action-discreet-border); border-radius: @baseBorderRadius; box-sizing: border-box; @@ -760,24 +701,14 @@ umb-block-grid-block { height: 100%; &:hover { - color: var(--umb-text-color, @ui-action-discreet-type-hover); - border-color: var(--umb-text-color, @ui-action-discreet-border-hover); + color: var(--umb-block-grid--text-color-hover, @ui-action-discreet-type-hover); + border-color: var(--umb-block-grid--text-color-hover, @ui-action-discreet-border-hover); text-decoration: none; z-index: 1; } } } - -/** make sure block with areas stay on top, so they don't look like they are 'not-allowed'*/ -/* -.umb-block-grid__layout-container.--droppable-indication { - .umb-block-grid__area-actions { - display: none; - } -} -*/ - .umb-block-grid__layout-item-placeholder { background: transparent; border-radius: 3px; @@ -859,16 +790,19 @@ umb-block-grid-block { content: ''; position: absolute; inset: 0; - /* Moved slightly in to align with the inline-create button, which is moved slightly in to avoid collision with other create buttons. */ - top:2px; - bottom: 2px; + top:0; + bottom: 0; border-radius: 3px; border: 1px solid rgba(@gray-5, 0.3); pointer-events: none; opacity: var(--umb-block-grid--show-area-ui, 0); transition: opacity 240ms; + z-index:3; } .umb-block-grid__area.--highlight::after { + /* Moved slightly in to align with the inline-create button, which is moved slightly in to avoid collision with other create buttons. */ + top:2px; + bottom: 2px; /** Avoid displaying highlight when in dragging-mode */ opacity: calc(1 - var(--umb-block-grid--dragging-mode, 0)); border-color: @blueDark; @@ -899,31 +833,10 @@ umb-block-grid-block { z-index: 1; cursor: nwse-resize; } -/* -.umb-block-grid__scalebox { - position: absolute; - top:0; - left:0; - z-index: 10; - cursor: nwse-resize; - - transition: background-color 240ms ease-in; - animation: umb-block-grid__scalebox__pulse 400ms ease-in-out alternate infinite; - @keyframes umb-block-grid__scalebox__pulse { - 0% { background-color: rgba(@blueMidLight, 0.33); } - 100% { background-color: rgba(@blueMidLight, 0.22); } - } -} -*/ -/* -.umb-block-grid__layout-container { - -} -*/ /** make sure block with areas stay on top, so they don't look like they are 'not-allowed'*/ @@ -934,8 +847,10 @@ umb-block-grid-block { } .umb-block-grid__layout-container .umb-block-grid__layout-item:not([depth='0']):first-of-type .umb-block-grid__block--inline-create-button.--above { - /* Move first above inline create button slightly up, to avoid collision with others*/ + /* Move the above inline create button slightly down, to avoid collision with others*/ margin-top: -7px; + + z-index:4; } .umb-block-grid__not-allowed-box { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.area.overlay.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.area.overlay.controller.js index 6a466cf052..f20f14f227 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.area.overlay.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.area.overlay.controller.js @@ -25,13 +25,25 @@ value: {min:vm.area.minAllowed, max:vm.area.maxAllowed} } + unsubscribe.push($scope.$watch('vm.area.alias', (newVal, oldVal) => { + $scope.model.updateTitle(); + if($scope.blockGridBlockConfigurationAreaForm.alias) { + $scope.blockGridBlockConfigurationAreaForm.alias.$setValidity("alias", $scope.model.otherAreaAliases.indexOf(newVal) === -1); + } + })); + vm.submit = function() { + if($scope.blockGridBlockConfigurationAreaForm.$valid === false) { + $scope.submitButtonState = "error"; + return; + } if ($scope.model && $scope.model.submit) { // Transfer minMaxModel to area: vm.area.minAllowed = vm.minMaxModel.value.min; vm.area.maxAllowed = vm.minMaxModel.value.max; + $scope.submitButtonState = "success"; $scope.model.submit($scope.model); } }; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.area.overlay.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.area.overlay.html index cccceb7afb..90918d71e1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.area.overlay.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.area.overlay.html @@ -27,11 +27,18 @@
+ * The alias will be printed by GetBlockGridHTML(), use the alias to target the Element representing this area. Ex. .umb-block-grid__area[data-area-alias="MyAreaAlias"] { ... }
- + +
+
+
+ This Areas Alias must be unique compared to the other Areas of this Block. +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.html index bfb9fdc90c..e78d94d486 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.html @@ -6,6 +6,7 @@
+
@@ -30,9 +31,14 @@
- + + + + Install demo Blocks + +
@@ -82,9 +88,9 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.less index aa09b5238e..43151fcabb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.less +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.less @@ -2,29 +2,10 @@ margin-bottom: 20px; - .__add-button { - position: relative; - display: inline-flex; - width: 100%; - height: 100%; - margin-right: 20px; - margin-bottom: 20px; - - color: @ui-action-discreet-type; - border: 1px dashed @ui-action-discreet-border; - border-radius: @doubleBorderRadius; - - align-items: center; - justify-content: center; - - padding: 5px 15px; - box-sizing: border-box; - font-weight: bold; - } - - .__add-button:hover { - color: @ui-action-discreet-type-hover; - border-color: @ui-action-discreet-border-hover; + uui-button { + font-weight: 700; + --uui-button-border-radius: 6px; + min-height: 80px; } .__get-sample-box { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.html index 4b05e4ad43..27c1a2f006 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.html @@ -355,12 +355,25 @@ + +
+
+ + + Hide the content edit button and the content editor from the Block Editor overlay. + +
+ +
+
+
+
- Define the range of layout rows this block is allowed to span across. + Hide the content edit button and the content editor from the Block Editor overlay.
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.groupconfiguration.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.groupconfiguration.html index 86c975639c..25a7282ca2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.groupconfiguration.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.groupconfiguration.html @@ -1,5 +1,5 @@ -
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umb-block-grid-area-allowance-editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umb-block-grid-area-allowance-editor.html index c288afc3ff..55ceb941bb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umb-block-grid-area-allowance-editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umb-block-grid-area-allowance-editor.html @@ -2,13 +2,11 @@ -
- +
+ ng-click="vm.deleteAllowance(allowance);"> Delete @@ -56,12 +51,15 @@ +
+ When empty all Blocks allowed for Areas can be created. +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umb-block-grid-area-allowance-editor.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umb-block-grid-area-allowance-editor.less index 9389906fac..2ce5a2ef16 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umb-block-grid-area-allowance-editor.less +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umb-block-grid-area-allowance-editor.less @@ -5,8 +5,11 @@ width: 100%; } -.umb-block-grid-area-allowance-editor .__list.--disabled { - +.umb-block-grid-area-allowance-editor .__empty-label { + font-size: 12px; + color: @gray-6; + line-height: 1.5em; + padding-top: 5px; } .umb-block-grid-area-allowance-editor__entry { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umbBlockGridAreaEditor.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umbBlockGridAreaEditor.component.js index e8ae592d8f..9126a6cc54 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umbBlockGridAreaEditor.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umbBlockGridAreaEditor.component.js @@ -56,15 +56,32 @@ function initializeSortable() { - const gridLayoutContainerEl = $element[0].querySelector('.umb-block-grid-area-editor__grid-wrapper'); + function _sync(evt) { - const sortable = Sortable.create(gridLayoutContainerEl, { + const oldIndex = evt.oldIndex, + newIndex = evt.newIndex; + + vm.model.splice(newIndex, 0, vm.model.splice(oldIndex, 1)[0]); + + } + + const gridContainerEl = $element[0].querySelector('.umb-block-grid-area-editor__grid-wrapper'); + + const sortable = Sortable.create(gridContainerEl, { sort: true, // sorting inside list animation: 150, // ms, animation speed moving items when sorting, `0` — without animation easing: "cubic-bezier(1, 0, 0, 1)", // Easing for animation. Defaults to null. See https://easings.net/ for examples. cancel: '', draggable: ".umb-block-grid-area-editor__area", // Specifies which items inside the element should be draggable - ghostClass: "umb-block-grid-area-editor__area-placeholder" + ghostClass: "umb-block-grid-area-editor__area-placeholder", + onAdd: function (evt) { + _sync(evt); + $scope.$evalAsync(); + }, + onUpdate: function (evt) { + _sync(evt); + $scope.$evalAsync(); + } }); // TODO: setDirty if sort has happend. @@ -130,14 +147,23 @@ vm.openAreaOverlay = function (area) { // TODO: use the right localization key: - localizationService.localize("blockEditor_blockConfigurationOverlayTitle", [area.alias]).then(function (localized) { + localizationService.localize("blockEditor_blockConfigurationOverlayTitle").then(function (localized) { var clonedAreaData = Utilities.copy(area); vm.openArea = area; + function updateTitle() { + overlayModel.title = localizationService.tokenReplace(localized, [clonedAreaData.alias]); + } + + const areaIndex = vm.model.indexOf(area); + const otherAreas = [...vm.model]; + otherAreas.splice(areaIndex, 1); + var overlayModel = { + otherAreaAliases: otherAreas.map(x => x.alias), area: clonedAreaData, - title: localized, + updateTitle: updateTitle, allBlockTypes: vm.allBlockTypes, allBlockGroups: vm.allBlockGroups, loadedElementTypes: vm.loadedElementTypes, @@ -154,6 +180,8 @@ } }; + updateTitle(); + // open property settings editor editorService.open(overlayModel); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-entries.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-entries.html index b4ef7b9266..301208c6f2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-entries.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-entries.html @@ -60,7 +60,7 @@ @@ -119,7 +119,7 @@ key="{{(invalidBlockType.amount < invalidBlockType.minRequirement) ? 'blockEditor_areaValidationEntriesShort' : 'blockEditor_areaValidationEntriesExceed'}}" tokens="[invalidBlockType.name, invalidBlockType.amount, invalidBlockType.minRequirement, invalidBlockType.maxRequirement]" watch-tokens="true" - >%0% must be present between %2% – %3% times. + >%0% must be present between %2%–%3% times.
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-entry.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-entry.html index 9c8860eaa5..e3f439a891 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-entry.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-entry.html @@ -1,10 +1,11 @@ + ng-click="vm.clickInlineCreateAbove()" + ng-mouseover="vm.mouseOverInlineCreate()" + ng-mouseleave="vm.mouseOutInlineCreate()"> @@ -29,9 +30,15 @@ parent-form="vm.blockForm" style="--umb-block-grid--area-grid-columns: {{vm.areaGridColumns}}" > + +
-
@@ -159,7 +165,7 @@ -
{{vm.layoutEntry.columnSpan}} x {{vm.layoutEntry.rowSpan}}
@@ -180,10 +186,7 @@ class="umb-block-grid__block--inline-create-button --after" ng-class="{'--at-root': vm.depth === '0'}" ng-click="vm.clickInlineCreateAfter($event)" - ng-mouseover="vm.mouseOverInlineCreateAfter()" - ng-mouseleave="vm.blockEditorApi.internal.hideAreaHighlight(vm.parentBlock, vm.areaKey)" + ng-mouseover="vm.mouseOverInlineCreate()" + ng-mouseleave="vm.mouseOutInlineCreate()" vertical> - -
-
+ \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-property-editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-property-editor.html index 767be6d559..834f55e685 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-property-editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-property-editor.html @@ -4,7 +4,19 @@
-
+ + + + + + + +
+ entries="vm.layout" + loading="vm.loading" + >
- diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-property-editor.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-property-editor.less index be3d1cc9ec..d06b6377c3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-property-editor.less +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-property-editor.less @@ -1,4 +1,8 @@ .umb-block-grid__wrapper { position: relative; max-width: 1200px; +} + +.umb-block-grid__wrapper .umb-rte { + max-width: 100%; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-render-area-slots.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-render-area-slots.html new file mode 100644 index 0000000000..e8d53c7457 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-render-area-slots.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbBlockGridPropertyEditor.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbBlockGridPropertyEditor.component.js index 57ddea9757..829fc918b6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbBlockGridPropertyEditor.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbBlockGridPropertyEditor.component.js @@ -13,6 +13,31 @@ return null; } + function closestColumnSpanOption(target, map, max) { + if(map.length > 0) { + const result = map.reduce((a, b) => { + if (a.columnSpan > max) { + return b; + } + let aDiff = Math.abs(a.columnSpan - target); + let bDiff = Math.abs(b.columnSpan - target); + + if (aDiff === bDiff) { + return a.columnSpan < b.columnSpan ? a : b; + } else { + return bDiff < aDiff ? b : a; + } + }); + if(result) { + return result; + } + } + return null; + } + + + const DefaultViewFolderPath = "views/propertyeditors/blockgrid/blockgridentryeditors/"; + /** * @ngdoc directive @@ -44,14 +69,20 @@ var unsubscribe = []; var modelObject; + var gridRootEl; // Property actions: + var propertyActions = null; + var enterSortModeAction = null; + var exitSortModeAction = null; var copyAllBlocksAction = null; var deleteAllBlocksAction = null; var liveEditing = true; var shadowRoot; + var firstLayoutContainer; + var vm = this; @@ -107,6 +138,8 @@ vm.options = { createFlow: false }; + vm.sortMode = false; + vm.sortModeView = DefaultViewFolderPath + "gridsortblock/gridsortblock.editor.html";; localizationService.localizeMany(["grid_addElement", "content_createEmpty", "blockEditor_addThis"]).then(function (data) { vm.labels.grid_addElement = data[0]; @@ -114,8 +147,23 @@ vm.labels.blockEditor_addThis = data[2] }); + vm.onAppendProxyProperty = (event) => { + event.stopPropagation(); + gridRootEl.appendChild(event.detail.property); + event.detail.connectedCallback(); + }; + vm.onRemoveProxyProperty = (event) => { + event.stopPropagation(); + const el = gridRootEl.querySelector(`:scope > [slot='${event.detail.slotName}']`); + gridRootEl.removeChild(el); + }; + vm.$onInit = function() { + gridRootEl = $element[0].querySelector('umb-block-grid-root'); + + $element[0].addEventListener("UmbBlockGrid_AppendProperty", vm.onAppendProxyProperty); + $element[0].addEventListener("UmbBlockGrid_RemoveProperty", vm.onRemoveProxyProperty); //listen for form validation changes vm.valFormManager.onValidationStatusChanged(function (evt, args) { @@ -177,6 +225,19 @@ scopeOfExistence = vm.umbElementEditorContent.getScope(); } + enterSortModeAction = { + labelKey: 'blockEditor_actionEnterSortMode', + icon: 'navigation-vertical', + method: enableSortMode, + isDisabled: false + }; + exitSortModeAction = { + labelKey: 'blockEditor_actionExitSortMode', + icon: 'navigation-vertical', + method: exitSortMode, + isDisabled: false + }; + copyAllBlocksAction = { labelKey: "clipboard_labelForCopyAllEntries", labelTokens: [vm.model.label], @@ -187,13 +248,13 @@ deleteAllBlocksAction = { labelKey: 'clipboard_labelForRemoveAllEntries', - labelTokens: [], icon: 'trash', method: requestDeleteAllBlocks, isDisabled: true }; - var propertyActions = [ + propertyActions = [ + enterSortModeAction, copyAllBlocksAction, deleteAllBlocksAction ]; @@ -223,7 +284,6 @@ } - function onLoaded() { // Store a reference to the layout model, because we need to maintain this model. @@ -241,6 +301,7 @@ window.requestAnimationFrame(() => { shadowRoot = $element[0].querySelector('umb-block-grid-root').shadowRoot; + firstLayoutContainer = shadowRoot.querySelector('.umb-block-grid__layout-container'); }) } @@ -314,21 +375,31 @@ } } - // if no columnSpan, then we set one: - if (!layoutEntry.columnSpan) { + // Ensure Areas are ordered like the area configuration is: + layoutEntry.areas.sort((left, right) => { + return block.config.areas?.findIndex(config => config.key === left.key) < block.config.areas?.findIndex(config => config.key === right.key) ? -1 : 1; + }); - const contextColumns = getContextColumns(parentBlock, areaKey) - if (block.config.columnSpanOptions.length > 0) { - // set columnSpan to minimum allowed span for this BlockType: - const minimumColumnSpan = block.config.columnSpanOptions.reduce((prev, option) => Math.min(prev, option.columnSpan), vm.gridColumns); + const contextColumns = getContextColumns(parentBlock, areaKey); + const relevantColumnSpanOptions = block.config.columnSpanOptions.filter(option => option.columnSpan <= contextColumns); - // If minimumColumnSpan is larger than contextColumns, then we will make it fit within context anyway: - layoutEntry.columnSpan = Math.min(minimumColumnSpan, contextColumns) + // if no columnSpan or no columnSpanOptions configured, then we set(or rewrite) one: + if (!layoutEntry.columnSpan || layoutEntry.columnSpan > contextColumns || relevantColumnSpanOptions.length === 0) { + if (relevantColumnSpanOptions.length > 0) { + // Find greatest columnSpanOption within contextColumns, or fallback to contextColumns. + layoutEntry.columnSpan = relevantColumnSpanOptions.reduce((prev, option) => Math.max(prev, option.columnSpan), 0) || contextColumns; } else { layoutEntry.columnSpan = contextColumns; } + } else { + // Check that columnSpanOption still is available or equal contextColumns, or find closest option fitting: + if (relevantColumnSpanOptions.find(option => option.columnSpan === layoutEntry.columnSpan) === undefined || layoutEntry.columnSpan !== contextColumns) { + console.log(layoutEntry.columnSpan, closestColumnSpanOption(layoutEntry.columnSpan, relevantColumnSpanOptions, contextColumns)?.columnSpan || contextColumns); + layoutEntry.columnSpan = closestColumnSpanOption(layoutEntry.columnSpan, relevantColumnSpanOptions, contextColumns)?.columnSpan || contextColumns; + } } + // if no rowSpan, then we set one: if (!layoutEntry.rowSpan) { layoutEntry.rowSpan = 1; @@ -375,12 +446,12 @@ function applyDefaultViewForBlock(block) { - var defaultViewFolderPath = "views/propertyeditors/blockgrid/blockgridentryeditors/"; - if (block.config.unsupported === true) { - block.view = defaultViewFolderPath + "unsupportedblock/unsupportedblock.editor.html"; + block.view = DefaultViewFolderPath + "unsupportedblock/unsupportedblock.editor.html"; + } else if (block.config.inlineEditing) { + block.view = DefaultViewFolderPath + "gridinlineblock/gridinlineblock.editor.html"; } else { - block.view = defaultViewFolderPath + "gridblock/gridblock.editor.html"; + block.view = DefaultViewFolderPath + "gridblock/gridblock.editor.html"; } } @@ -430,9 +501,11 @@ block.showCopy = vm.supportCopy && block.config.contentElementTypeKey != null; block.blockUiVisibility = false; - block.showBlockUI = function () { + block.showBlockUI = () => { delete block.__timeout; - shadowRoot.querySelector('*[data-element-udi="'+block.layout.contentUdi+'"] .umb-block-grid__block > .umb-block-grid__block--context').scrollIntoView({block: "nearest", inline: "nearest", behavior: "smooth"}); + $timeout(() => { + shadowRoot.querySelector('*[data-element-udi="'+block.layout.contentUdi+'"] > ng-form > .umb-block-grid__block > .umb-block-grid__block--context').scrollIntoView({block: "nearest", inline: "nearest", behavior: "smooth"}); + }, 100); block.blockUiVisibility = true; }; block.onMouseLeave = function () { @@ -778,6 +851,8 @@ vm.requestShowCreate = requestShowCreate; function requestShowCreate(parentBlock, areaKey, createIndex, mouseEvent, options) { + vm.hideAreaHighlight(parentBlock, areaKey); + if (vm.blockTypePickerIsOpen === true) { return; } @@ -1254,6 +1329,38 @@ } } + function enableSortMode() { + vm.sortMode = true; + propertyActions.splice(propertyActions.indexOf(enterSortModeAction), 1, exitSortModeAction); + if (vm.umbProperty) { + vm.umbProperty.setPropertyActions(propertyActions); + } + } + + vm.exitSortMode = exitSortMode; + function exitSortMode() { + vm.sortMode = false; + propertyActions.splice(propertyActions.indexOf(exitSortModeAction), 1, enterSortModeAction); + if (vm.umbProperty) { + vm.umbProperty.setPropertyActions(propertyActions); + } + } + + vm.startDraggingMode = startDraggingMode; + function startDraggingMode() { + + document.documentElement.style.setProperty("--umb-block-grid--dragging-mode", 1); + firstLayoutContainer.style.minHeight = firstLayoutContainer.getBoundingClientRect().height + "px"; + + } + vm.exitDraggingMode = exitDraggingMode; + function exitDraggingMode() { + + document.documentElement.style.setProperty("--umb-block-grid--dragging-mode", 0); + firstLayoutContainer.style.minHeight = ""; + + } + function onAmountOfBlocksChanged() { // enable/disable property actions @@ -1278,9 +1385,16 @@ unsubscribe.push($scope.$watch(() => vm.layout.length, onAmountOfBlocksChanged)); $scope.$on("$destroy", function () { + + $element[0].removeEventListener("UmbBlockGrid_AppendProperty", vm.onAppendProxyProperty); + $element[0].removeEventListener("UmbBlockGrid_RemoveProperty", vm.onRemoveProxyProperty); + for (const subscription of unsubscribe) { subscription(); } + + firstLayoutContainer = null; + gridRootEl = null; }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridblock.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridblock.component.js index 2c4a4bb262..1a918638ca 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridblock.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridblock.component.js @@ -58,7 +58,7 @@
+ ng-include="api.internal.sortMode ? api.internal.sortModeView : '${model.view}'">
`; $compile(shadowRoot)($scope); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentries.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentries.component.js index c1e2c43619..3f597292e8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentries.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentries.component.js @@ -76,6 +76,7 @@ vm.movingLayoutEntry = null; vm.layoutColumnsInt = 0; + vm.containedPropertyEditorProxies = []; vm.$onInit = function () { initializeSortable(); @@ -93,7 +94,6 @@ vm.layoutColumnsInt = parseInt(vm.layoutColumns, 10); })); - function onLocalAmountOfBlocksChanged() { if (vm.entriesForm && vm.areaConfig) { @@ -153,6 +153,11 @@ } } + + vm.notifyVisualUpdate = function () { + $scope.$broadcast("blockGridEditorVisualUpdate", {areaKey: vm.areaKey}); + } + vm.acceptBlock = function(contentTypeKey) { return vm.blockEditorApi.internal.isElementTypeKeyAllowedAt(vm.parentBlock, vm.areaKey, contentTypeKey); } @@ -210,6 +215,11 @@ var nextSibling; + function _removePropertyProxy(eventTarget, slotName) { + const event = new CustomEvent("UmbBlockGrid_RemoveProperty", {composed: true, bubbles: true, detail: {'slotName': slotName}}); + eventTarget.dispatchEvent(event); + } + // Borrowed concept from, its not identical as more has been implemented: https://github.com/SortableJS/angular-legacy-sortablejs/blob/master/angular-legacy-sortable.js function _sync(evt) { @@ -222,8 +232,15 @@ const prevEntries = fromCtrl.entries; const syncEntry = prevEntries[oldIndex]; - // Perform the transfer: + // Make sure Property Editor Proxies are destroyed, as we need to establish new when moving context: + + // unregister all property editor proxies via events: + fromCtrl.containedPropertyEditorProxies.forEach(slotName => { + _removePropertyProxy(evt.from, slotName); + }); + + // Perform the transfer: if (Sortable.active && Sortable.active.lastPullMode === 'clone') { syncEntry = Utilities.copy(syncEntry); prevEntries.splice(Sortable.utils.index(evt.clone, sortable.options.draggable), 0, prevEntries.splice(oldIndex, 1)[0]); @@ -231,7 +248,6 @@ else { prevEntries.splice(oldIndex, 1); } - vm.entries.splice(newIndex, 0, syncEntry); const contextColumns = vm.blockEditorApi.internal.getContextColumns(vm.parentBlock, vm.areaKey); @@ -261,6 +277,7 @@ function _indication(contextVM, movingEl) { + // Remove old indication: if(_lastIndicationContainerVM !== contextVM && _lastIndicationContainerVM !== null) { _lastIndicationContainerVM.hideNotAllowed(); _lastIndicationContainerVM.revertIndicateDroppable(); @@ -269,7 +286,7 @@ if(contextVM.acceptBlock(movingEl.dataset.contentElementTypeKey) === true) { _lastIndicationContainerVM.hideNotAllowed(); - _lastIndicationContainerVM.indicateDroppable();// This block is accepted to we will indicate a good drop. + _lastIndicationContainerVM.indicateDroppable();// This block is accepted so we will indicate a good drop. return true; } @@ -557,6 +574,10 @@ forceAutoScrollFallback: true, onStart: function (evt) { + + // TODO: This does not work correctly jet with SortableJS. With the replacement we should be able to call this before DOM is changed. + vm.blockEditorApi.internal.startDraggingMode(); + nextSibling = evt.from === evt.item.parentNode ? evt.item.nextSibling : evt.clone.nextSibling; var contextVM = vm; @@ -577,6 +598,7 @@ vm.movingLayoutEntry.$block.__scope.$evalAsync();// needed for the block to be updated ghostEl = evt.item; + vm.containedPropertyEditorProxies = Array.from(ghostEl.querySelectorAll('slot[data-is-property-editor-proxy]')).map(x => x.getAttribute('name')); targetRect = evt.to.getBoundingClientRect(); ghostRect = ghostEl.getBoundingClientRect(); @@ -587,8 +609,6 @@ window.addEventListener('drag', _onDragMove); window.addEventListener('dragover', _onDragMove); - document.documentElement.style.setProperty("--umb-block-grid--dragging-mode", 1); - $scope.$evalAsync(); }, // Called by any change to the list (add / update / remove) @@ -619,7 +639,7 @@ } window.removeEventListener('drag', _onDragMove); window.removeEventListener('dragover', _onDragMove); - document.documentElement.style.setProperty("--umb-block-grid--dragging-mode", 0); + vm.blockEditorApi.internal.exitDraggingMode(); if(ghostElIndicateForceLeft) { ghostEl.removeChild(ghostElIndicateForceLeft); @@ -643,6 +663,9 @@ ghostRect = null; ghostEl = null; relatedEl = null; + vm.containedPropertyEditorProxies = []; + + vm.notifyVisualUpdate(); } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentry.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentry.component.js index a7c45ecdd8..d5b3ce5474 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentry.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentry.component.js @@ -44,23 +44,25 @@ } function closestColumnSpanOption(target, map, max) { - const result = map.reduce((a, b) => { - if (a.columnSpan > max) { - return b; + if(map.length > 0) { + const result = map.reduce((a, b) => { + if (a.columnSpan > max) { + return b; + } + let aDiff = Math.abs(a.columnSpan - target); + let bDiff = Math.abs(b.columnSpan - target); + + if (aDiff === bDiff) { + return a.columnSpan < b.columnSpan ? a : b; + } else { + return bDiff < aDiff ? b : a; + } + }); + if(result) { + return result; } - let aDiff = Math.abs(a.columnSpan - target); - let bDiff = Math.abs(b.columnSpan - target); - - if (aDiff === bDiff) { - return a.columnSpan < b.columnSpan ? a : b; - } else { - return bDiff < aDiff ? b : a; - } - }); - if(result) { - return result; } - return max; + return null; } @@ -86,11 +88,17 @@ areaKey: "<", propertyEditorForm: " { + // Only insert a proxy slot for the direct Block of this entry (as all the blocks share the same ShadowDom though they are slotted into each other when nested through areas.) + if (event.detail.contentUdi === vm.layoutEntry.contentUdi) { + vm.proxyProperties.push({ + slotName: event.detail.slotName + }); + $scope.$evalAsync(); + } + }; + vm.onRemoveProxyProperty = (event) => { + // Only react to proxies from the direct Block of this entry: + if (event.detail.contentUdi === vm.layoutEntry.contentUdi) { + const index = vm.proxyProperties.findIndex(x => x.slotName === event.detail.slotName); + if(index !== -1) { + vm.proxyProperties.splice(index, 1); + } + $scope.$evalAsync(); + } + }; vm.$onInit = function() { + $element[0].addEventListener("UmbBlockGrid_AppendProperty", vm.onAppendProxyProperty); + $element[0].addEventListener("UmbBlockGrid_RemoveProperty", vm.onRemoveProxyProperty); + vm.childDepth = parseInt(vm.depth) + 1; if(vm.layoutEntry.$block.config.areaGridColumns) { @@ -110,13 +145,29 @@ vm.areaGridColumns = vm.blockEditorApi.internal.gridColumns.toString(); } - vm.layoutColumnsInt = parseInt(vm.layoutColumns, 10) + vm.layoutColumnsInt = parseInt(vm.layoutColumns, 10); + + vm.relevantColumnSpanOptions = vm.layoutEntry.$block.config.columnSpanOptions.filter(x => x.columnSpan <= vm.layoutColumnsInt).sort((a,b) => (a.columnSpan > b.columnSpan) ? 1 : ((b.columnSpan > a.columnSpan) ? -1 : 0)); + const hasRelevantColumnSpanOptions = vm.relevantColumnSpanOptions.length > 1; + const hasRowSpanOptions = vm.layoutEntry.$block.config.rowMinSpan && vm.layoutEntry.$block.config.rowMaxSpan && vm.layoutEntry.$block.config.rowMaxSpan !== vm.layoutEntry.$block.config.rowMinSpan; + vm.canScale = (hasRelevantColumnSpanOptions || hasRowSpanOptions); + + unsubscribe.push(vm.layoutEntry.$block.__scope.$watch(() => vm.layoutEntry.$block.index, visualUpdateCallback)); + unsubscribe.push($scope.$on("blockGridEditorVisualUpdate", (evt, data) => {if(data.areaKey === vm.areaKey) { visualUpdateCallback()}})); + + updateInlineCreateTimeout = $timeout(updateInlineCreate, 500); $scope.$evalAsync(); } unsubscribe.push($scope.$watch("depth", (newVal, oldVal) => { vm.childDepth = parseInt(vm.depth) + 1; })); + + function visualUpdateCallback() { + cancelAnimationFrame(updateInlineCreateRaf); + updateInlineCreateRaf = requestAnimationFrame(updateInlineCreate); + } + /** * We want to only show the validation errors on the specific Block, not the parent blocks. * So we need to avoid having a Block as the parent to the Block Form. @@ -157,9 +208,12 @@ // Block sizing functionality: let layoutContainer = null; let gridColumns = null; + let columnGap = 0; + let rowGap = 0; let gridRows = null; + let lockedGridRows = 0; let scaleBoxBackdropEl = null; - + let raf = null; function getNewSpans(startX, startY, endX, endY) { @@ -171,7 +225,8 @@ let newColumnSpan = Math.max(blockEndCol-blockStartCol, 1); // Find nearest allowed Column: - newColumnSpan = closestColumnSpanOption(newColumnSpan , vm.layoutEntry.$block.config.columnSpanOptions, gridColumns.length - blockStartCol).columnSpan; + const bestColumnSpanOption = closestColumnSpanOption(newColumnSpan , vm.relevantColumnSpanOptions, vm.layoutColumnsInt - blockStartCol) + newColumnSpan = bestColumnSpanOption ? bestColumnSpanOption.columnSpan : vm.layoutColumnsInt; let newRowSpan = Math.round(Math.max(blockEndRow-blockStartRow, vm.layoutEntry.$block.config.rowMinSpan || 1)); if(vm.layoutEntry.$block.config.rowMaxSpan != null) { @@ -181,10 +236,14 @@ return {'columnSpan': newColumnSpan, 'rowSpan': newRowSpan, 'startCol': blockStartCol, 'startRow': blockStartRow}; } - function updateGridLayoutData(layoutContainerRect, layoutItemRect) { + function updateGridLayoutData(layoutContainerRect, layoutItemRect, updateRowTemplate) { const computedStyles = window.getComputedStyle(layoutContainer); + + columnGap = Number(computedStyles.columnGap.split("px")[0]) || 0; + rowGap = Number(computedStyles.rowGap.split("px")[0]) || 0; + gridColumns = computedStyles.gridTemplateColumns.trim().split("px").map(x => Number(x)); gridRows = computedStyles.gridTemplateRows.trim().split("px").map(x => Number(x)); @@ -192,6 +251,18 @@ gridColumns = gridColumns.filter(n => n > 0); gridRows = gridRows.filter(n => n > 0); + // We use this code to lock the templateRows, while scaling. otherwise scaling Rows is too crazy. + if(updateRowTemplate || gridRows.length > lockedGridRows) { + lockedGridRows = gridRows.length; + layoutContainer.style.gridTemplateRows = computedStyles.gridTemplateRows; + } + + // add gaps: + const gridColumnsLen = gridColumns.length; + gridColumns = gridColumns.map((n, i) => gridColumnsLen === i ? n : n + columnGap); + const gridRowsLen = gridRows.length; + gridRows = gridRows.map((n, i) => gridRowsLen === i ? n : n + rowGap); + // ensure all columns are there. // This will also ensure handling non-css-grid mode, // use container width divided by amount of columns( or the item width divided by its amount of columnSpan) @@ -226,25 +297,28 @@ gridRows.push(50); gridRows.push(50); gridRows.push(50); + gridRows.push(50); + gridRows.push(50); } vm.scaleHandlerMouseDown = function($event) { $event.originalEvent.preventDefault(); + + layoutContainer = $element[0].closest('.umb-block-grid__layout-container'); + if(!layoutContainer) { + console.error($element[0], 'could not find parent layout-container'); + return; + } + vm.isScaleMode = true; window.addEventListener('mousemove', vm.onMouseMove); window.addEventListener('mouseup', vm.onMouseUp); window.addEventListener('mouseleave', vm.onMouseUp); - - layoutContainer = $element[0].closest('.umb-block-grid__layout-container'); - if(!layoutContainer) { - console.error($element[0], 'could not find parent layout-container'); - } - const layoutContainerRect = layoutContainer.getBoundingClientRect(); const layoutItemRect = $element[0].getBoundingClientRect(); - updateGridLayoutData(layoutContainerRect, layoutItemRect); + updateGridLayoutData(layoutContainerRect, layoutItemRect, true); scaleBoxBackdropEl = document.createElement('div'); @@ -256,7 +330,6 @@ const layoutContainerRect = layoutContainer.getBoundingClientRect(); const layoutItemRect = $element[0].getBoundingClientRect(); - updateGridLayoutData(layoutContainerRect, layoutItemRect); const startX = layoutItemRect.left - layoutContainerRect.left; @@ -266,6 +339,18 @@ const newSpans = getNewSpans(startX, startY, endX, endY); + const updateRowTemplate = vm.layoutEntry.columnSpan !== newSpans.columnSpan; + + if(updateRowTemplate) { + // If we like to update we need to first remove the lock, make the browser render onces and then update. + layoutContainer.style.gridTemplateRows = ""; + } + cancelAnimationFrame(raf); + raf = requestAnimationFrame(() => { + // As mentioned above we need to wait until the browser has rendered DOM without the lock of gridTemplateRows. + updateGridLayoutData(layoutContainerRect, layoutItemRect, updateRowTemplate); + }) + // update as we go: vm.layoutEntry.columnSpan = newSpans.columnSpan; vm.layoutEntry.rowSpan = newSpans.rowSpan; @@ -275,7 +360,13 @@ vm.onMouseUp = function(e) { - vm.isScaleMode = false; + cancelAnimationFrame(raf); + + // Remove listeners: + window.removeEventListener('mousemove', vm.onMouseMove); + window.removeEventListener('mouseup', vm.onMouseUp); + window.removeEventListener('mouseleave', vm.onMouseUp); + const layoutContainerRect = layoutContainer.getBoundingClientRect(); const layoutItemRect = $element[0].getBoundingClientRect(); @@ -287,22 +378,24 @@ const newSpans = getNewSpans(startX, startY, endX, endY); - // Remove listeners: - window.removeEventListener('mousemove', vm.onMouseMove); - window.removeEventListener('mouseup', vm.onMouseUp); - window.removeEventListener('mouseleave', vm.onMouseUp); + // release the lock of gridTemplateRows: layoutContainer.removeChild(scaleBoxBackdropEl); + layoutContainer.style.gridTemplateRows = ""; + vm.isScaleMode = false; // Clean up variables: layoutContainer = null; gridColumns = null; gridRows = null; + lockedGridRows = 0; scaleBoxBackdropEl = null; // Update block size: vm.layoutEntry.columnSpan = newSpans.columnSpan; vm.layoutEntry.rowSpan = newSpans.rowSpan; + + vm.umbBlockGridEntries.notifyVisualUpdate(); vm.blockEditorApi.internal.setDirty(); $scope.$evalAsync(); } @@ -331,8 +424,8 @@ } if(addColIndex !== 0) { - if (vm.layoutEntry.$block.config.columnSpanOptions.length > 0) { - const sortOptions = vm.layoutEntry.$block.config.columnSpanOptions.sort((a,b) => (a.columnSpan > b.columnSpan) ? 1 : ((b.columnSpan > a.columnSpan) ? -1 : 0)); + if (vm.relevantColumnSpanOptions.length > 0) { + const sortOptions = vm.relevantColumnSpanOptions; const currentColIndex = sortOptions.findIndex(x => x.columnSpan === vm.layoutEntry.columnSpan); const newColIndex = Math.min(Math.max(currentColIndex + addColIndex, 0), sortOptions.length-1); vm.layoutEntry.columnSpan = sortOptions[newColIndex].columnSpan; @@ -346,18 +439,30 @@ } vm.layoutEntry.rowSpan = newRowSpan; + vm.umbBlockGridEntries.notifyVisualUpdate(); vm.blockEditorApi.internal.setDirty(); $event.originalEvent.stopPropagation(); } + vm.clickInlineCreateAbove = function($event) { + if(vm.hideInlineCreateAbove === false) { + vm.blockEditorApi.requestShowCreate(vm.parentBlock, vm.areaKey, vm.index, $event); + } + } vm.clickInlineCreateAfter = function($event) { if(vm.hideInlineCreateAfter === false) { vm.blockEditorApi.requestShowCreate(vm.parentBlock, vm.areaKey, vm.index+1, $event, {'fitInRow': true}); } } - vm.mouseOverInlineCreateAfter = function() { - + vm.mouseOverInlineCreate = function() { + vm.blockEditorApi.internal.showAreaHighlight(vm.parentBlock, vm.areaKey); + } + vm.mouseOutInlineCreate = function() { + vm.blockEditorApi.internal.hideAreaHighlight(vm.parentBlock, vm.areaKey); + } + + function updateInlineCreate() { layoutContainer = $element[0].closest('.umb-block-grid__layout-container'); if(!layoutContainer) { return; @@ -366,17 +471,39 @@ const layoutContainerRect = layoutContainer.getBoundingClientRect(); const layoutItemRect = $element[0].getBoundingClientRect(); - if(layoutItemRect.right > layoutContainerRect.right - 5) { + if(layoutContainerRect.width === 0) { + $timeout.cancel(updateInlineCreateTimeout); + vm.hideInlineCreateAbove = true; vm.hideInlineCreateAfter = true; + vm.inlineCreateAboveWidth = ""; + $scope.$evalAsync(); + updateInlineCreateTimeout = $timeout(updateInlineCreate, 500); return; } - vm.hideInlineCreateAfter = false; - vm.blockEditorApi.internal.showAreaHighlight(vm.parentBlock, vm.areaKey); + if(layoutItemRect.right > layoutContainerRect.right - 5) { + vm.hideInlineCreateAfter = true; + } else { + vm.hideInlineCreateAfter = false; + } + if(layoutItemRect.left > layoutContainerRect.left + 5) { + vm.hideInlineCreateAbove = true; + vm.inlineCreateAboveWidth = ""; + } else { + vm.inlineCreateAboveWidth = getComputedStyle(layoutContainer).width; + vm.hideInlineCreateAbove = false; + } + $scope.$evalAsync(); } $scope.$on("$destroy", function () { + + $timeout.cancel(updateInlineCreateTimeout); + + $element[0].removeEventListener("UmbBlockGrid_AppendProperty", vm.onAppendProxyProperty); + $element[0].removeEventListener("UmbBlockGrid_RemoveProperty", vm.onRemoveProxyProperty); + for (const subscription of unsubscribe) { subscription(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridrenderareaslots.directive.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridrenderareaslots.directive.js new file mode 100644 index 0000000000..43837e9c5c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridrenderareaslots.directive.js @@ -0,0 +1,20 @@ +(function () { + 'use strict'; + + function UmbBlockGridRenderAreaSlots() { + + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/propertyeditors/blockgrid/umb-block-grid-render-area-slots.html', + scope: false + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbBlockGridRenderAreaSlots', UmbBlockGridRenderAreaSlots); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridroot.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridroot.component.js index 130150a3ae..fbda5cf92e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridroot.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridroot.component.js @@ -20,7 +20,8 @@ stylesheet: "@", blockEditorApi: "<", propertyEditorForm: "
hello

"}; + element = $("
"); })); @@ -31,7 +32,8 @@ describe('RTE controller tests', function () { it('should define the default properties on construction', function () { controllerFactory('Umbraco.PropertyEditors.RTEController', { $scope: scope, - $routeParams: routeParams + $routeParams: routeParams, + $element: element }); }); From fe2cab0daa44bb39fed72c67a143d8598e146f59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 14 Nov 2022 12:14:25 +0100 Subject: [PATCH 14/85] remove console log --- .../blockgrid/umbBlockGridPropertyEditor.component.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbBlockGridPropertyEditor.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbBlockGridPropertyEditor.component.js index 829fc918b6..ebd4625c3b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbBlockGridPropertyEditor.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbBlockGridPropertyEditor.component.js @@ -395,7 +395,6 @@ } else { // Check that columnSpanOption still is available or equal contextColumns, or find closest option fitting: if (relevantColumnSpanOptions.find(option => option.columnSpan === layoutEntry.columnSpan) === undefined || layoutEntry.columnSpan !== contextColumns) { - console.log(layoutEntry.columnSpan, closestColumnSpanOption(layoutEntry.columnSpan, relevantColumnSpanOptions, contextColumns)?.columnSpan || contextColumns); layoutEntry.columnSpan = closestColumnSpanOption(layoutEntry.columnSpan, relevantColumnSpanOptions, contextColumns)?.columnSpan || contextColumns; } } From f9e5d6308ffd60746d5bae1ad63e8c8386def60c Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 15 Nov 2022 15:24:52 +0100 Subject: [PATCH 15/85] Updated references for Forms and Deploy in JSON schema project. (#13411) --- src/JsonSchema/JsonSchema.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/JsonSchema/JsonSchema.csproj b/src/JsonSchema/JsonSchema.csproj index 69aeb39ecf..91402a9fd1 100644 --- a/src/JsonSchema/JsonSchema.csproj +++ b/src/JsonSchema/JsonSchema.csproj @@ -12,7 +12,7 @@ - - + + From b4115132cd94ce1ba002d35bbf0b05617a15a13a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Kottal?= Date: Wed, 16 Nov 2022 10:02:06 +0100 Subject: [PATCH 16/85] Enable single block mode (#13216) * Enable single block mode * Fixes tests, and adds test for single block mode output type * Update src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> * Update src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> * Fix breaking change Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> --- .../PropertyEditors/BlockListConfiguration.cs | 7 +++ .../BlockListPropertyValueConverter.cs | 61 ++++++++++++++++++- .../blockpicker/blockpicker.controller.js | 18 +++++- .../umb-block-list-property-editor.html | 3 +- .../blocklist/umb-block-list-row.html | 2 +- .../umbBlockListPropertyEditor.component.js | 55 +++++++++++++++-- .../BlockListPropertyValueConverterTests.cs | 26 +++++++- 7 files changed, 158 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs b/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs index 1dec9946d3..1184f2524f 100644 --- a/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs @@ -16,6 +16,13 @@ public class BlockListConfiguration [ConfigurationField("validationLimit", "Amount", "numberrange", Description = "Set a required range of blocks")] public NumberRange ValidationLimit { get; set; } = new(); + [ConfigurationField("useSingleBlockMode", "Single block mode", "boolean", + Description = @"When in Single block mode, the output will be BlockListItem<>, instead of BlockListModel. + +**NOTE:** +Single block mode requires a maximum of one available block, and an amount set to minimum 1 and maximum 1 blocks.")] + public bool UseSingleBlockMode { get; set; } + [ConfigurationField("useLiveEditing", "Live editing mode", "boolean", Description = "Live editing in editor overlays for live updated custom views or labels using custom expression.")] public bool UseLiveEditing { get; set; } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs index af1c51c37e..b56f43e0ef 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs @@ -1,9 +1,14 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; using static Umbraco.Cms.Core.PropertyEditors.BlockListConfiguration; @@ -12,18 +17,70 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; [DefaultPropertyValueConverter(typeof(JsonValueConverter))] public class BlockListPropertyValueConverter : BlockPropertyValueConverterBase { + private readonly IContentTypeService _contentTypeService; + private readonly BlockEditorConverter _blockConverter; + private readonly BlockListEditorDataConverter _blockListEditorDataConverter; private readonly IProfilingLogger _proflog; - public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter) + [Obsolete("Use the constructor with the IContentTypeService")] + public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter) : this(proflog, blockConverter, StaticServiceProvider.Instance.GetRequiredService()) { } + + public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter, IContentTypeService contentTypeService) : base(blockConverter) { _proflog = proflog; + _blockConverter = blockConverter; + _blockListEditorDataConverter = new BlockListEditorDataConverter(); + _contentTypeService = contentTypeService; } /// public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.BlockList); + /// + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + { + var isSingleBlockMode = IsSingleBlockMode(propertyType.DataType); + if (isSingleBlockMode) + { + BlockListConfiguration.BlockConfiguration? block = + ConfigurationEditor.ConfigurationAs(propertyType.DataType.Configuration)?.Blocks.FirstOrDefault(); + + ModelType? contentElementType = block?.ContentElementTypeKey is Guid contentElementTypeKey && _contentTypeService.Get(contentElementTypeKey) is IContentType contentType ? ModelType.For(contentType.Alias) : null; + ModelType? settingsElementType = block?.SettingsElementTypeKey is Guid settingsElementTypeKey && _contentTypeService.Get(settingsElementTypeKey) is IContentType settingsType ? ModelType.For(settingsType.Alias) : null; + + if (contentElementType is not null) + { + if (settingsElementType is not null) + { + return typeof(BlockListItem<,>).MakeGenericType(contentElementType, settingsElementType); + } + + return typeof(BlockListItem<>).MakeGenericType(contentElementType); + } + + return typeof(BlockListItem); + } + + return typeof(BlockListModel); + } + + private bool IsSingleBlockMode(PublishedDataType dataType) + { + BlockListConfiguration? config = + ConfigurationEditor.ConfigurationAs(dataType.Configuration); + return (config?.UseSingleBlockMode ?? false) && config?.Blocks.Length == 1 && config?.ValidationLimit?.Min == 1 && config?.ValidationLimit?.Max == 1; + } + + /// + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + /// + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) + => source?.ToString(); + /// public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) { @@ -44,7 +101,7 @@ public class BlockListPropertyValueConverter : BlockPropertyValueConverterBase
@@ -28,7 +29,7 @@
-
+
-