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>
This commit is contained in:
Søren Kottal
2022-11-16 10:02:06 +01:00
committed by GitHub
parent f9e5d6308f
commit b4115132cd
7 changed files with 158 additions and 14 deletions

View File

@@ -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; }

View File

@@ -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<BlockListModel, BlockListItem, BlockListLayoutItem, BlockConfiguration>
{
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<IContentTypeService>()) { }
public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter, IContentTypeService contentTypeService)
: base(blockConverter)
{
_proflog = proflog;
_blockConverter = blockConverter;
_blockListEditorDataConverter = new BlockListEditorDataConverter();
_contentTypeService = contentTypeService;
}
/// <inheritdoc />
public override bool IsConverter(IPublishedPropertyType propertyType)
=> propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.BlockList);
/// <inheritdoc />
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
{
var isSingleBlockMode = IsSingleBlockMode(propertyType.DataType);
if (isSingleBlockMode)
{
BlockListConfiguration.BlockConfiguration? block =
ConfigurationEditor.ConfigurationAs<BlockListConfiguration>(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<BlockListConfiguration>(dataType.Configuration);
return (config?.UseSingleBlockMode ?? false) && config?.Blocks.Length == 1 && config?.ValidationLimit?.Min == 1 && config?.ValidationLimit?.Max == 1;
}
/// <inheritdoc />
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.Element;
/// <inheritdoc />
public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
=> source?.ToString();
/// <inheritdoc />
public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview)
{
@@ -44,7 +101,7 @@ public class BlockListPropertyValueConverter : BlockPropertyValueConverterBase<B
BlockListModel blockModel = UnwrapBlockModel(referenceCacheLevel, inter, preview, configuration.Blocks, CreateEmptyModel, CreateModel);
return blockModel;
return IsSingleBlockMode(propertyType.DataType) ? blockModel.FirstOrDefault() : blockModel;
}
}

View File

@@ -38,12 +38,18 @@ angular.module("umbraco")
"disabled": vm.model.clipboardItems.length === 0
}];
if (vm.model.openClipboard === true) {
if (vm.model.singleBlockMode === true && vm.model.openClipboard === true) {
vm.navigation.splice(0,1);
vm.activeTab = vm.navigation[0];
}
else if (vm.model.openClipboard === true) {
vm.activeTab = vm.navigation[1];
} else {
vm.activeTab = vm.navigation[0];
}
vm.activeTab.active = true;
}
);
@@ -55,10 +61,16 @@ angular.module("umbraco")
};
vm.clickClearClipboard = function () {
vm.onNavigationChanged(vm.navigation[0]);
vm.navigation[1].disabled = true;// disabled ws determined when creating the navigation, so we need to update it here.
vm.model.clipboardItems = [];// This dialog is not connected via the clipboardService events, so we need to update manually.
vm.model.clickClearClipboard();
if (vm.model.singleBlockMode !== true && vm.model.openClipboard !== true)
{
vm.onNavigationChanged(vm.navigation[0]);
vm.navigation[1].disabled = true;// disabled ws determined when creating the navigation, so we need to update it here.
}
else {
vm.close();
}
};
vm.model = $scope.model;

View File

@@ -13,6 +13,7 @@
ng-click="vm.requestShowCreate($index, $event)"
ng-controller="Umbraco.PropertyEditors.BlockListPropertyEditor.CreateButtonController as inlineCreateButtonCtrl"
ng-mousemove="inlineCreateButtonCtrl.onMouseMove($event)"
ng-if="!vm.singleBlockMode"
ng-show="!vm.readonly">
<div class="__plus" ng-style="{'left':inlineCreateButtonCtrl.plusPosX}">
<umb-icon icon="icon-add" class="icon"></umb-icon>
@@ -28,7 +29,7 @@
</div>
</div>
<div class="umb-block-list__actions" ng-if="vm.loading !== true">
<div class="umb-block-list__actions" ng-if="vm.loading !== true && !vm.singleBlockMode">
<button
id="{{vm.model.alias}}"
type="button"

View File

@@ -30,7 +30,7 @@
<localize key="general_copy">Copy</localize>
</span>
</button>
<button ng-if="!vm.blockEditorApi.readonly" type="button" class="btn-reset umb-outline action --delete" localize="title" title="actions_delete"
<button ng-if="!vm.blockEditorApi.readonly && (!vm.blockEditorApi.singleBlockMode || vm.index > 0)" type="button" class="btn-reset umb-outline action --delete" localize="title" title="actions_delete"
ng-click="vm.blockEditorApi.requestDeleteBlock(vm.layout.$block);">
<umb-icon icon="icon-trash" class="icon"></umb-icon>
<span class="sr-only">

View File

@@ -36,6 +36,7 @@
// Property actions:
let copyAllBlocksAction = null;
let deleteAllBlocksAction = null;
let pasteSingleBlockAction = null;
var inlineEditing = false;
var liveEditing = true;
@@ -43,6 +44,7 @@
var vm = this;
vm.readonly = false;
vm.singleBlockMode = false;
$attrs.$observe('readonly', (value) => {
vm.readonly = value !== undefined;
@@ -105,6 +107,12 @@
inlineEditing = vm.model.config.useInlineEditingAsDefault;
liveEditing = vm.model.config.useLiveEditing;
vm.singleBlockMode =
vm.model.config.validationLimit.min == 1 &&
vm.model.config.validationLimit.max == 1 &&
vm.model.config.blocks.length == 1 &&
vm.model.config.useSingleBlockMode;
vm.blockEditorApi.singleBlockMode = vm.singleBlockMode;
vm.validationLimit = vm.model.config.validationLimit;
@@ -144,13 +152,25 @@
useLegacyIcon: false
};
var propertyActions = [
copyAllBlocksAction,
deleteAllBlocksAction
];
pasteSingleBlockAction = {
labelKey: "content_createFromClipboard",
labelTokens: [],
icon: "icon-paste-in",
method: requestShowClipboard,
isDisabled: false,
useLegacyIcon: false
};
var propertyActions = [copyAllBlocksAction, deleteAllBlocksAction];
var propertyActionsForSingleBlockMode = [pasteSingleBlockAction];
if (vm.umbProperty) {
if (vm.singleBlockMode) {
vm.umbProperty.setPropertyActions(propertyActionsForSingleBlockMode);
} else {
vm.umbProperty.setPropertyActions(propertyActions);
}
}
// Create Model Object, to manage our data for this Block Editor.
@@ -218,6 +238,20 @@
updateClipboard(true);
if (vm.singleBlockMode && vm.layout.length == 0) {
var wasAdded = false;
var blockType = vm.availableBlockTypes[0];
wasAdded = addNewBlock(1, blockType.blockConfigModel.contentElementTypeKey);
if (wasAdded && inlineEditing === true) {
var blockObject = vm.layout[0]?.$block;
if (blockObject) {
blockObject.activate();
}
}
}
vm.loading = false;
$scope.$evalAsync();
@@ -528,6 +562,7 @@
availableItems: vm.availableBlockTypes,
title: vm.labels.grid_addElement,
openClipboard: openClipboard,
singleBlockMode: vm.singleBlockMode,
orderBy: "$index",
view: "views/common/infiniteeditors/blockpicker/blockpicker.html",
size: (amountOfAvailableTypes > 8 ? "medium" : "small"),
@@ -644,6 +679,8 @@
if(firstTime !== true && vm.clipboardItems.length > oldAmount) {
jumpClipboard();
}
pasteSingleBlockAction.isDisabled = vm.clipboardItems.length === 0;
}
var jumpClipboardTimeout;
@@ -706,6 +743,13 @@
return false;
}
if (vm.singleBlockMode) {
if (vm.layout.length > 0) {
deleteBlock(vm.layout[0].$block);
index = 1;
}
}
var layoutEntry;
if (pasteType === clipboardService.TYPES.ELEMENT_TYPE) {
layoutEntry = modelObject.createFromElementType(pasteEntry);
@@ -787,7 +831,8 @@
requestDeleteBlock: requestDeleteBlock,
deleteBlock: deleteBlock,
openSettingsForBlock: openSettingsForBlock,
readonly: vm.readonly
readonly: vm.readonly,
singleBlockMode: vm.singleBlockMode
};
vm.sortableOptions = {

View File

@@ -1,7 +1,6 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core;
@@ -11,6 +10,7 @@ using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors;
@@ -64,7 +64,8 @@ public class BlockListPropertyValueConverterTests
var publishedModelFactory = new NoopPublishedModelFactory();
var editor = new BlockListPropertyValueConverter(
Mock.Of<IProfilingLogger>(),
new BlockEditorConverter(publishedSnapshotAccessor, publishedModelFactory));
new BlockEditorConverter(publishedSnapshotAccessor, publishedModelFactory),
Mock.Of<IContentTypeService>());
return editor;
}
@@ -90,6 +91,13 @@ public class BlockListPropertyValueConverterTests
Blocks = new[] { new BlockListConfiguration.BlockConfiguration { ContentElementTypeKey = _contentKey1 } },
};
private BlockListConfiguration ConfigForSingleBlockMode() => new()
{
Blocks = new[] { new BlockListConfiguration.BlockConfiguration { ContentElementTypeKey = _contentKey1 } },
ValidationLimit = new() { Min = 1, Max = 1 },
UseSingleBlockMode = true,
};
private IPublishedPropertyType GetPropertyType(BlockListConfiguration config)
{
var dataType = new PublishedDataType(1, "test", new Lazy<object>(() => config));
@@ -139,6 +147,20 @@ public class BlockListPropertyValueConverterTests
Assert.AreEqual(typeof(BlockListModel), valueType);
}
[Test]
public void Get_Value_Type_SingleBlockMode()
{
var editor = CreateConverter();
var config = ConfigForSingleBlockMode();
var dataType = new PublishedDataType(1, "test", new Lazy<object>(() => config));
var propType = Mock.Of<IPublishedPropertyType>(x => x.DataType == dataType);
var valueType = editor.GetPropertyValueType(propType);
Assert.AreEqual(typeof(BlockListItem), valueType);
}
[Test]
public void Convert_Null_Empty()
{