Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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)
{
Expand All @@ -44,7 +101,7 @@ public override bool IsConverter(IPublishedPropertyType propertyType)

BlockListModel blockModel = UnwrapBlockModel(referenceCacheLevel, inter, preview, configuration.Blocks, CreateEmptyModel, CreateModel);

return blockModel;
return IsSingleBlockMode(propertyType.DataType) ? blockModel.FirstOrDefault() : blockModel;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
);
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@
// Property actions:
let copyAllBlocksAction = null;
let deleteAllBlocksAction = null;
let pasteSingleBlockAction = null;

var inlineEditing = false;
var liveEditing = true;

var vm = this;

vm.readonly = false;
vm.singleBlockMode = false;

$attrs.$observe('readonly', (value) => {
vm.readonly = value !== undefined;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -644,6 +679,8 @@
if(firstTime !== true && vm.clipboardItems.length > oldAmount) {
jumpClipboard();
}

pasteSingleBlockAction.isDisabled = vm.clipboardItems.length === 0;
}

var jumpClipboardTimeout;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -787,7 +831,8 @@
requestDeleteBlock: requestDeleteBlock,
deleteBlock: deleteBlock,
openSettingsForBlock: openSettingsForBlock,
readonly: vm.readonly
readonly: vm.readonly,
singleBlockMode: vm.singleBlockMode
};

vm.sortableOptions = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.

using System;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core;
Expand All @@ -11,6 +10,7 @@
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;

Expand Down Expand Up @@ -64,7 +64,8 @@ private BlockListPropertyValueConverter CreateConverter()
var publishedModelFactory = new NoopPublishedModelFactory();
var editor = new BlockListPropertyValueConverter(
Mock.Of<IProfilingLogger>(),
new BlockEditorConverter(publishedSnapshotAccessor, publishedModelFactory));
new BlockEditorConverter(publishedSnapshotAccessor, publishedModelFactory),
Mock.Of<IContentTypeService>());
return editor;
}

Expand All @@ -90,6 +91,13 @@ private BlockListPropertyValueConverter CreateConverter()
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));
Expand Down Expand Up @@ -139,6 +147,20 @@ public void Get_Value_Type_Single()
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()
{
Expand Down