Block Editor List (#8273)

* add style to create-option in itempicker + removing overflow hidden

* style adjustment

* clean up of html

* correct sentence to use the number 7

* correct overlays, so they can use size

* numberrange prevalue editor

* add confirmRemove overlay

* correcting primary btn colors

* move confirmMessage below content of overlay.

* min max validation for numberrange

* remove comment

* improved actions for block list

* use file tree for view picker

* style adjustment to border of creator item in item-picker

* vertical align

* clean up + validation limit range

* rename ElementTypes to Blocks

* implement block list editor

* renaming

* use Chrome Headless for unit tests

* test for blockEditorService

* safer code

* block list editor

* rename view to overlayView

* block editor work

* Revert "rename view to overlayView"

This reverts commit 5b910c178a.

* block editor implementation

* sync models

* block list editor copy paste feature

* order var declarations

* remove unused paste function

* block list editor better naming

* simpler label generation

* clean up

* compile config for test mode

* Chrome Debug for VS code

* promise test working

* space change

* another two tests

* more tests for block list editor

* provide key on blockModel for angularJS performance optimization

* syncronization from infinite editing layers

* use an isolated scope

* more tests

* fix C# test

* remove unused block watcher component

* clean css

* only show on hover or focus for block-actions

* clean up and prepare for implementing settings

* remove console

* Add ability to render block list editor using Grid style rendering extension

* Enable Block List Editor settings editing

* Add Stacked Content to Block List migration

* Block Editor: Clean-up, refactoring, one step closer being ready for Content-Apps

* changes naming to Submit, to avoid misunderstanding.

* use a common variable on the block model for isOpen, to be able to make Actions show when open.

* NotSupported property editor, to be used when an editor is not supported in the given context.

* remove unused controller

* Hide group header if only one group is presented

* rename notsupport property editor css class

* smaller header for property group

* hide description if no description is presented

* css adjustments

* Inline create button styling: Better spacing, darker blue color for Focus Outline, moving the plus icon to mouse position for better visual appearance.

* css correction

* Add references for picked items

* Revert commit 45e892f350 - Changes api to GetData

* Use the .Data propertry as opposed to GetData in this PartialView

* Fix block list test failures

* Just parsing layout as model for partial views.

* minor adjustments

* Remove DB migrations so that they can be reviewed as a block

* Add migrations for new block editor

* Update default rendering partial view

* Add error handling to default template

* Handle color picker data in stacked content

* BlockList PreValue Editor opens configurations as overlay

* translation for prevalue editor property group headlines

* blockcard corrections

* block list prevalue corrections

* revert agressive clean up

* Block Picker

* MaxPropertyWidth PreValue + Implementation

* Incorporate latest block list editor changes, update migration for changed configuration

* Change declared converter type

* Handle invalid data type references

* Remove code duplicated from PR #7957

* use ElementModel for the ContentModel of an ElementType. So we can use ElementTypeModel for the ModelDefinition aka. the Type.

* do still show itempicker for BlockConfiguration even though there is no ElementTypes to pick. This enables the option to create a new ElementType to be used.

* use the right wrapper, for correct spacing

* parse item

* correct fallback for label

* removed unused callback

* paste feature for block-picker

* localize block-picker tabs

* Slightly change for shadow on block-picker item hover

* Localization of BlockEditor Settings Tab

* localizationService

* only filter when more than 8 items available

* Add multiple blocks if hold down CTRL or SuperKey

* adds notes

* ability to add a scoped stylesheet for block view

* make scoped block draggable + style adjustments

* provide index for custom view

* rename contentModel to data + rename layoutModel to layout

* clean up

* more localization

* openSettings option for block editor

* minor changes for a better developer experience

* paste mistake corrected

* only manipulate content-content-app if its present

* make small overlays slightly bigger

* moved block list entry editor into block list editor folder

* limit labelinterpretator to only runs ones pr. edit. and lets make sure to have a label thought we dont have any properties.

* fixed inline views gulp watcher

* changed vm to a better controller instance name

* make watch for views work again.

* able to re run watch

* make js up to date

* fix white background of image-picker

* media-picker container class

* loading indication

* adjust unit tests to latest interface

* getting started on JS Docs

* converting code to use contentTypeKey instead of contentTypeAlias, still missing a few TODOs.

* revert change

* add todo

* use Guid for Key

* use key

* Updates the caching layer to handle GUID keys for content types while preserving backwards compat, fixes unit tests, removes the strongly typed lists for the block editor value since it's unecessary

* Reverts the nested content changes, fixes up the GetEmptyByKey

* Returns ContentTypeKey from the server, updates js to use this everywhere and fix all js tests.

* Allows key in SimpleContentType

* correct for the new spelling

* appended this. since the method is a non-static class method.

* only add background-image if value isnt null

* simplifyed limits validation

* clean up

* no need to execute a digest.

* define the full model for new configurations

* removed setDataFromBlockModel and general clean up and added documentation

* default size should be medium

* use retriveValuesFrom method to transfer data.

* ability to disable an navigation item

* createElementTypeAndCallback working again for settings.

* still have the ability to retrive a scaffold model by alias, since we are still using alias in clipboard service.

* disable clipboard tab if no available paste options

* ups, should stay as alias

* disable clipboard when empty

* use property alias for content add button

* use a grey that can be seen on top of grey and on top of white.

* some accessibility improvements

* rename entry to block

* appended ' and added space in Element Type

* use background color for hover to corospond with active state

* make nested content unsupported

* Moving BlockEditorModelObject into its own file for Documentation purpose. Same goes for renaming BlockModel to BlockObject. and a lot of documentation written.

* fix links in js docs

* added a blocklistentryeditor for unsupported blocks

* ability to retrive the $scope from UmbVariantContentEditors, needed for Block Editor to get a scope existing across variants and splitview.

* Appending the block objects to layout, to share it across variants and in split-view.

* removed trailing comma

* Unsupported block

* Dont copy unsupported blocks

* use grey color for unsupported block

* text correction

* we dont have this fallback anymore

* sort properties

* Text change

* css correction

* use active to determine if an inline editor is expanded. To enable the inline editor to be expanded on creation.

* using udi instead of key.

* bringing the runtime block key back

* live editing ability

* live editing for settings data

* call formSubmit before property editor are removed from DOM. Both for overlay-editing and inline-editing. Fire an postFormSubmitting event after formSubmitting event to let editors know that data could have been updated.

* make sure settings object exists

* only set active to false if it was false before opening the editor.

* update test with new scope parameter

* move destroy responsibility to blockObject

* rename onDestroy to destroy

* added some JS-Docs

* correction jsDocs

* Update ElementType Resource to not use hardcoded URL but to use the Umbraco.Sys.ServerVariables.umbracoUrls instead

* Remove partially completed ConvertToElement migration, fixed in issue 7939 instead.

* Remove external property editor migration

* corrected naming of umbBlockListScopedBlock and umbBlockListBlock

* correct ngdoc type

* removed vscode specific configuration of karma

* Finished Todo, gets name of documentType if copying all entities in an infinite editor

* changed comment from TODO to something that explains the state.

* stop tracking build output files.

* rename files to match file name conventions

* this should not happen.

* remove dublicated code

* rename requestCopyBlock to copyBlock

* make sure images does not repeat.

* scale thumbnail for block showcase

* renamed blockcard.component to umb-block-card and moved it.

* removed inline style

* correct style location

* corrected filepath

* corrected file path

* keep elementTypes up to date through the EventService.

* mark Umbraco.BlockList as unsupported inside Nested Content

* correct js docs name

* remove comment

* remove comment

* remove unused controller

* rename inline method name

* corrected spelling mistake

* remove not very used vars

* make binding one-way

* split in multiple lines

* corrected default rendering

* removing documentation that is relevant for developers of new block editors. this documentation will be transfered to Our documentation.

* added danish translation

* corrected blog to blok

* Remove invalid using statement

* use native forEach

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Benjamin Carleski <benjamin@proworks.com>
Co-authored-by: Warren Buckley <warren@umbraco.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.com>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
Co-authored-by: Claus <claus@claus.nu>
This commit is contained in:
Shannon Deminick
2020-06-30 19:52:42 +10:00
committed by GitHub
parent ee7d62e583
commit df3dc32de6
141 changed files with 22008 additions and 239 deletions
@@ -36,6 +36,11 @@ namespace Umbraco.Core
/// </summary>
public static class Aliases
{
/// <summary>
/// Block List.
/// </summary>
public const string BlockList = "Umbraco.BlockList";
/// <summary>
/// CheckBox List.
/// </summary>
@@ -25,6 +25,7 @@ namespace Umbraco.Core
{ Unknown, UdiType.Unknown },
{ AnyGuid, UdiType.GuidUdi },
{ Element, UdiType.GuidUdi },
{ Document, UdiType.GuidUdi },
{ DocumentBlueprint, UdiType.GuidUdi },
{ Media, UdiType.GuidUdi },
@@ -64,6 +65,8 @@ namespace Umbraco.Core
public const string AnyGuid = "any-guid"; // that one is for tests
public const string Element = "element";
public const string Document = "document";
public const string DocumentBlueprint = "document-blueprint";
@@ -191,7 +191,7 @@ namespace Umbraco.Core.Migrations.Upgrade
To<MissingContentVersionsIndexes>("{EE288A91-531B-4995-8179-1D62D9AA3E2E}");
To<AddMainDomLock>("{2AB29964-02A1-474D-BD6B-72148D2A53A2}");
// to 8.7.0...
To<MissingDictionaryIndex>("{a78e3369-8ea3-40ec-ad3f-5f76929d2b20}");
//FINAL
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Core.Models.Blocks
{
/// <summary>
/// The base class for any strongly typed model for a Block editor implementation
/// </summary>
public abstract class BlockEditorModel
{
protected BlockEditorModel(IEnumerable<IPublishedElement> data)
{
Data = data ?? throw new ArgumentNullException(nameof(data));
}
public BlockEditorModel()
{
}
/// <summary>
/// The data items of the Block List editor
/// </summary>
[DataMember(Name = "data")]
public IEnumerable<IPublishedElement> Data { get; set; }
}
}
@@ -0,0 +1,41 @@
using System;
using System.Runtime.Serialization;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Core.Models.Blocks
{
/// <summary>
/// Represents a layout item for the Block List editor
/// </summary>
[DataContract(Name = "blockListLayout", Namespace = "")]
public class BlockListLayoutReference : IBlockElement<IPublishedElement>
{
public BlockListLayoutReference(Udi udi, IPublishedElement data, IPublishedElement settings)
{
Udi = udi ?? throw new ArgumentNullException(nameof(udi));
Data = data ?? throw new ArgumentNullException(nameof(data));
Settings = settings; // can be null
}
/// <summary>
/// The Id of the data item
/// </summary>
[DataMember(Name = "udi")]
public Udi Udi { get; }
/// <summary>
/// The settings for the layout item
/// </summary>
[DataMember(Name = "settings")]
public IPublishedElement Settings { get; }
/// <summary>
/// The data item referenced
/// </summary>
/// <remarks>
/// This is ignored from serialization since it is just a reference to the actual data element
/// </remarks>
[IgnoreDataMember]
public IPublishedElement Data { get; }
}
}
@@ -0,0 +1,27 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Core.Models.Blocks
{
/// <summary>
/// The strongly typed model for the Block List editor
/// </summary>
[DataContract(Name = "blockList", Namespace = "")]
public class BlockListModel : BlockEditorModel
{
public BlockListModel(IEnumerable<IPublishedElement> data, IEnumerable<BlockListLayoutReference> layout)
: base(data)
{
Layout = layout;
}
/// <summary>
/// The layout items of the Block List editor
/// </summary>
[DataMember(Name = "layout")]
public IEnumerable<BlockListLayoutReference> Layout { get; }
}
}
@@ -0,0 +1,11 @@
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
namespace Umbraco.Core.Models.Blocks
{
public interface IBlockEditorDataHelper
{
IEnumerable<IBlockReference> GetBlockReferences(JObject layout);
bool IsEditorSpecificPropertyKey(string propertyKey);
}
}
@@ -0,0 +1,27 @@
namespace Umbraco.Core.Models.Blocks
{
/// <summary>
/// Represents a data item reference for a Block Editor implementation
/// </summary>
/// <remarks>
/// see: https://github.com/umbraco/rfcs/blob/907f3758cf59a7b6781296a60d57d537b3b60b8c/cms/0011-block-data-structure.md#strongly-typed
/// </remarks>
public interface IBlockReference
{
Udi Udi { get; }
}
// TODO: IBlockElement doesn't make sense, this is a reference to an actual element with some settings
// and always has to do with the "Layout", should possibly be called IBlockReference or IBlockLayout or IBlockLayoutReference
/// <summary>
/// Represents a data item for a Block editor implementation
/// </summary>
/// <typeparam name="TSettings"></typeparam>
/// <remarks>
/// see: https://github.com/umbraco/rfcs/blob/907f3758cf59a7b6781296a60d57d537b3b60b8c/cms/0011-block-data-structure.md#strongly-typed
/// </remarks>
public interface IBlockElement<TSettings> : IBlockReference
{
TSettings Settings { get; }
}
}
@@ -1,7 +1,21 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
namespace Umbraco.Core.Models.PublishedContent
{
/// <summary>
/// Represents an <see cref="IPublishedElement"/> type.
/// </summary>
/// <remarks>Instances implementing the <see cref="IPublishedContentType"/> interface should be
/// immutable, ie if the content type changes, then a new instance needs to be created.</remarks>
public interface IPublishedContentType2 : IPublishedContentType
{
/// <summary>
/// Gets the unique key for the content type.
/// </summary>
Guid Key { get; }
}
/// <summary>
/// Represents an <see cref="IPublishedElement"/> type.
/// </summary>
@@ -1,5 +1,6 @@
namespace Umbraco. Core.Models.PublishedContent
{
/// <summary>
/// Creates published content types.
/// </summary>
@@ -3,6 +3,7 @@ using System.Collections;
namespace Umbraco.Core.Models.PublishedContent
{
/// <summary>
/// Provides the published model creation service.
/// </summary>
@@ -3,6 +3,7 @@ using Umbraco.Core.Composing;
namespace Umbraco.Core.Models.PublishedContent
{
/// <summary>
/// Provides strongly typed published content models services.
/// </summary>
@@ -9,7 +9,7 @@ namespace Umbraco.Core.Models.PublishedContent
/// </summary>
/// <remarks>Instances of the <see cref="PublishedContentType"/> class are immutable, ie
/// if the content type changes, then a new class needs to be created.</remarks>
public class PublishedContentType : IPublishedContentType
public class PublishedContentType : IPublishedContentType2
{
private readonly IPublishedPropertyType[] _propertyTypes;
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Models.PublishedContent
/// Initializes a new instance of the <see cref="PublishedContentType"/> class with a content type.
/// </summary>
public PublishedContentType(IContentTypeComposition contentType, IPublishedContentTypeFactory factory)
: this(contentType.Id, contentType.Alias, contentType.GetItemType(), contentType.CompositionAliases(), contentType.Variations, contentType.IsElement)
: this(contentType.Key, contentType.Id, contentType.Alias, contentType.GetItemType(), contentType.CompositionAliases(), contentType.Variations, contentType.IsElement)
{
var propertyTypes = contentType.CompositionPropertyTypes
.Select(x => factory.CreatePropertyType(this, x))
@@ -40,8 +40,20 @@ namespace Umbraco.Core.Models.PublishedContent
/// <remarks>
/// <para>Values are assumed to be consistent and are not checked.</para>
/// </remarks>
public PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, IEnumerable<string> compositionAliases, IEnumerable<PublishedPropertyType> propertyTypes, ContentVariation variations, bool isElement = false)
: this(key, id, alias, itemType, compositionAliases, variations, isElement)
{
var propertyTypesA = propertyTypes.ToArray();
foreach (var propertyType in propertyTypesA)
propertyType.ContentType = this;
_propertyTypes = propertyTypesA;
InitializeIndexes();
}
[Obsolete("Use the overload specifying a key instead")]
public PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable<string> compositionAliases, IEnumerable<PublishedPropertyType> propertyTypes, ContentVariation variations, bool isElement = false)
: this (id, alias, itemType, compositionAliases, variations, isElement)
: this (Guid.Empty, id, alias, itemType, compositionAliases, variations, isElement)
{
var propertyTypesA = propertyTypes.ToArray();
foreach (var propertyType in propertyTypesA)
@@ -57,16 +69,26 @@ namespace Umbraco.Core.Models.PublishedContent
/// <remarks>
/// <para>Values are assumed to be consistent and are not checked.</para>
/// </remarks>
public PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, IEnumerable<string> compositionAliases, Func<IPublishedContentType, IEnumerable<IPublishedPropertyType>> propertyTypes, ContentVariation variations, bool isElement = false)
: this(key, id, alias, itemType, compositionAliases, variations, isElement)
{
_propertyTypes = propertyTypes(this).ToArray();
InitializeIndexes();
}
[Obsolete("Use the overload specifying a key instead")]
public PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable<string> compositionAliases, Func<IPublishedContentType, IEnumerable<IPublishedPropertyType>> propertyTypes, ContentVariation variations, bool isElement = false)
: this(id, alias, itemType, compositionAliases, variations, isElement)
: this(Guid.Empty, id, alias, itemType, compositionAliases, variations, isElement)
{
_propertyTypes = propertyTypes(this).ToArray();
InitializeIndexes();
}
private PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable<string> compositionAliases, ContentVariation variations, bool isElement)
private PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, IEnumerable<string> compositionAliases, ContentVariation variations, bool isElement)
{
Key = key;
Id = id;
Alias = alias;
ItemType = itemType;
@@ -116,6 +138,9 @@ namespace Umbraco.Core.Models.PublishedContent
#region Content type
/// <inheritdoc />
public Guid Key { get; }
/// <inheritdoc />
public int Id { get; }
@@ -0,0 +1,24 @@
using System;
namespace Umbraco.Core.Models.PublishedContent
{
public static class PublishedContentTypeExtensions
{
/// <summary>
/// Get the GUID key from an <see cref="IPublishedContentType"/>
/// </summary>
/// <param name="publishedContentType"></param>
/// <param name="key"></param>
/// <returns></returns>
public static bool TryGetKey(this IPublishedContentType publishedContentType, out Guid key)
{
if (publishedContentType is IPublishedContentType2 contentTypeWithKey)
{
key = contentTypeWithKey.Key;
return true;
}
key = Guid.Empty;
return false;
}
}
}
@@ -35,18 +35,18 @@ namespace Umbraco.Core.Models.PublishedContent
/// This method is for tests and is not intended to be used directly from application code.
/// </summary>
/// <remarks>Values are assumed to be consisted and are not checked.</remarks>
internal IPublishedContentType CreateContentType(int id, string alias, Func<IPublishedContentType, IEnumerable<IPublishedPropertyType>> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false)
internal IPublishedContentType CreateContentType(Guid key, int id, string alias, Func<IPublishedContentType, IEnumerable<IPublishedPropertyType>> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false)
{
return new PublishedContentType(id, alias, PublishedItemType.Content, Enumerable.Empty<string>(), propertyTypes, variations, isElement);
return new PublishedContentType(key, id, alias, PublishedItemType.Content, Enumerable.Empty<string>(), propertyTypes, variations, isElement);
}
/// <summary>
/// This method is for tests and is not intended to be used directly from application code.
/// </summary>
/// <remarks>Values are assumed to be consisted and are not checked.</remarks>
internal IPublishedContentType CreateContentType(int id, string alias, IEnumerable<string> compositionAliases, Func<IPublishedContentType, IEnumerable<IPublishedPropertyType>> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false)
internal IPublishedContentType CreateContentType(Guid key, int id, string alias, IEnumerable<string> compositionAliases, Func<IPublishedContentType, IEnumerable<IPublishedPropertyType>> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false)
{
return new PublishedContentType(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, variations, isElement);
return new PublishedContentType(key, id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, variations, isElement);
}
/// <inheritdoc />
+6 -2
View File
@@ -37,6 +37,7 @@ namespace Umbraco.Core.Models
if (contentType == null) throw new ArgumentNullException(nameof(contentType));
Id = contentType.Id;
Key = contentType.Key;
Alias = contentType.Alias;
Variations = contentType.Variations;
Icon = contentType.Icon;
@@ -51,6 +52,8 @@ namespace Umbraco.Core.Models
public int Id { get; }
public Guid Key { get; }
/// <inheritdoc />
public ITemplate DefaultTemplate { get; }
@@ -109,13 +112,14 @@ namespace Umbraco.Core.Models
string ITreeEntity.Name { get => this.Name; set => throw new NotImplementedException(); }
int IEntity.Id { get => this.Id; set => throw new NotImplementedException(); }
bool IEntity.HasIdentity => this.Id != default;
Guid IEntity.Key { get => this.Key; set => throw new NotImplementedException(); }
int ITreeEntity.CreatorId { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
int ITreeEntity.ParentId { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
int ITreeEntity.Level { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
string ITreeEntity.Path { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
int ITreeEntity.SortOrder { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
bool ITreeEntity.Trashed => throw new NotImplementedException();
Guid IEntity.Key { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
bool ITreeEntity.Trashed => throw new NotImplementedException();
DateTime IEntity.CreateDate { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
DateTime IEntity.UpdateDate { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
DateTime? IEntity.DeleteDate { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
@@ -30,18 +30,23 @@ namespace Umbraco.Core.PropertyEditors
return value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false);
}
/// <inheritdoc />
public virtual Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> typeof (object);
/// <inheritdoc />
public virtual PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.Snapshot;
/// <inheritdoc />
public virtual object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview)
=> source;
/// <inheritdoc />
public virtual object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
=> inter;
/// <inheritdoc />
public virtual object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
=> inter?.ToString() ?? string.Empty;
}
+6
View File
@@ -132,15 +132,21 @@
<Compile Include="Migrations\Upgrade\V_8_0_0\Models\ContentTypeDto80.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\Models\PropertyDataDto80.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\Models\PropertyTypeDto80.cs" />
<Compile Include="Models\Blocks\IBlockEditorDataHelper.cs" />
<Compile Include="Models\ContentDataIntegrityReport.cs" />
<Compile Include="Models\ContentDataIntegrityReportEntry.cs" />
<Compile Include="Models\ContentDataIntegrityReportOptions.cs" />
<Compile Include="Models\InstallLog.cs" />
<Compile Include="Models\PublishedContent\PublishedContentTypeExtensions.cs" />
<Compile Include="Models\RelationTypeExtensions.cs" />
<Compile Include="Persistence\Repositories\IInstallationRepository.cs" />
<Compile Include="Persistence\Repositories\Implement\InstallationRepository.cs" />
<Compile Include="Services\Implement\InstallationService.cs" />
<Compile Include="Migrations\Upgrade\V_8_6_0\AddMainDomLock.cs" />
<Compile Include="Models\Blocks\BlockEditorModel.cs" />
<Compile Include="Models\Blocks\BlockListLayoutReference.cs" />
<Compile Include="Models\Blocks\BlockListModel.cs" />
<Compile Include="Models\Blocks\IBlockElement.cs" />
<Compile Include="Models\UpgradeResult.cs" />
<Compile Include="Persistence\Repositories\Implement\UpgradeCheckRepository.cs" />
<Compile Include="Persistence\Repositories\IUpgradeCheckRepository.cs" />
@@ -42,9 +42,9 @@ namespace Umbraco.Tests.Cache.PublishedCache
protected override void Initialize()
{
base.Initialize();
var type = new AutoPublishedContentType(22, "myType", new PublishedPropertyType[] { });
var image = new AutoPublishedContentType(23, "Image", new PublishedPropertyType[] { });
var testMediaType = new AutoPublishedContentType(24, "TestMediaType", new PublishedPropertyType[] { });
var type = new AutoPublishedContentType(Guid.NewGuid(), 22, "myType", new PublishedPropertyType[] { });
var image = new AutoPublishedContentType(Guid.NewGuid(), 23, "Image", new PublishedPropertyType[] { });
var testMediaType = new AutoPublishedContentType(Guid.NewGuid(), 24, "TestMediaType", new PublishedPropertyType[] { });
_mediaTypes = new Dictionary<string, PublishedContentType>
{
{ type.Alias, type },
@@ -268,7 +268,7 @@ AnotherContentFinder
public void GetDataEditors()
{
var types = _typeLoader.GetDataEditors();
Assert.AreEqual(38, types.Count());
Assert.AreEqual(39, types.Count());
}
/// <summary>
@@ -14,7 +14,7 @@ using Umbraco.Web.Routing;
namespace Umbraco.Tests.LegacyXmlPublishedCache
{
internal class PublishedContentCache : PublishedCacheBase, IPublishedContentCache
internal class PublishedContentCache : PublishedCacheBase, IPublishedContentCache2
{
private readonly IAppCache _appCache;
private readonly IGlobalSettings _globalSettings;
@@ -532,15 +532,11 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache
#region Content types
public override IPublishedContentType GetContentType(int id)
{
return _contentTypeCache.Get(PublishedItemType.Content, id);
}
public override IPublishedContentType GetContentType(int id) => _contentTypeCache.Get(PublishedItemType.Content, id);
public override IPublishedContentType GetContentType(string alias)
{
return _contentTypeCache.Get(PublishedItemType.Content, alias);
}
public override IPublishedContentType GetContentType(string alias) => _contentTypeCache.Get(PublishedItemType.Content, alias);
public override IPublishedContentType GetContentType(Guid key) => _contentTypeCache.Get(PublishedItemType.Content, key);
#endregion
}
@@ -28,7 +28,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache
/// <remarks>
/// NOTE: In the future if we want to properly cache all media this class can be extended or replaced when these classes/interfaces are exposed publicly.
/// </remarks>
internal class PublishedMediaCache : PublishedCacheBase, IPublishedMediaCache
internal class PublishedMediaCache : PublishedCacheBase, IPublishedMediaCache2
{
private readonly IMediaService _mediaService;
private readonly IUserService _userService;
@@ -612,15 +612,11 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache
#region Content types
public override IPublishedContentType GetContentType(int id)
{
return _contentTypeCache.Get(PublishedItemType.Media, id);
}
public override IPublishedContentType GetContentType(int id) => _contentTypeCache.Get(PublishedItemType.Media, id);
public override IPublishedContentType GetContentType(string alias)
{
return _contentTypeCache.Get(PublishedItemType.Media, alias);
}
public override IPublishedContentType GetContentType(string alias) => _contentTypeCache.Get(PublishedItemType.Media, alias);
public override IPublishedContentType GetContentType(Guid key) => _contentTypeCache.Get(PublishedItemType.Media, key);
public override IEnumerable<IPublishedContent> GetByContentType(IPublishedContentType contentType)
{
@@ -0,0 +1,339 @@
using Moq;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Blocks;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
using Umbraco.Web.PropertyEditors;
using Umbraco.Web.PropertyEditors.ValueConverters;
using Umbraco.Web.PublishedCache;
namespace Umbraco.Tests.PropertyEditors
{
[TestFixture]
public class BlockListPropertyValueConverterTests
{
private readonly Guid Key1 = Guid.NewGuid();
private readonly Guid Key2 = Guid.NewGuid();
private readonly string Alias1 = "Test1";
private readonly string Alias2 = "Test2";
/// <summary>
/// Setup mocks for IPublishedSnapshotAccessor
/// </summary>
/// <returns></returns>
private IPublishedSnapshotAccessor GetPublishedSnapshotAccessor()
{
var test1ContentType = Mock.Of<IPublishedContentType2>(x =>
x.IsElement == true
&& x.Key == Key1
&& x.Alias == Alias1);
var test2ContentType = Mock.Of<IPublishedContentType2>(x =>
x.IsElement == true
&& x.Key == Key2
&& x.Alias == Alias2);
var contentCache = new Mock<IPublishedContentCache2>();
contentCache.Setup(x => x.GetContentType(Key1)).Returns(test1ContentType);
contentCache.Setup(x => x.GetContentType(Key2)).Returns(test2ContentType);
var publishedSnapshot = Mock.Of<IPublishedSnapshot>(x => x.Content == contentCache.Object);
var publishedSnapshotAccessor = Mock.Of<IPublishedSnapshotAccessor>(x => x.PublishedSnapshot == publishedSnapshot);
return publishedSnapshotAccessor;
}
private BlockListPropertyValueConverter CreateConverter()
{
var publishedSnapshotAccessor = GetPublishedSnapshotAccessor();
var publishedModelFactory = new NoopPublishedModelFactory();
var editor = new BlockListPropertyValueConverter(
Mock.Of<IProfilingLogger>(),
new BlockEditorConverter(publishedSnapshotAccessor, publishedModelFactory));
return editor;
}
private BlockListConfiguration ConfigForMany() => new BlockListConfiguration
{
Blocks = new[] {
new BlockListConfiguration.BlockConfiguration
{
Key = Key1
},
new BlockListConfiguration.BlockConfiguration
{
Key = Key2
}
}
};
private BlockListConfiguration ConfigForSingle() => new BlockListConfiguration
{
Blocks = new[] {
new BlockListConfiguration.BlockConfiguration
{
Key = Key1
}
}
};
private IPublishedPropertyType GetPropertyType(BlockListConfiguration config)
{
var dataType = new PublishedDataType(1, "test", new Lazy<object>(() => config));
var propertyType = Mock.Of<IPublishedPropertyType>(x =>
x.EditorAlias == Constants.PropertyEditors.Aliases.BlockList
&& x.DataType == dataType);
return propertyType;
}
[Test]
public void Is_Converter_For()
{
var editor = CreateConverter();
Assert.IsTrue(editor.IsConverter(Mock.Of<IPublishedPropertyType>(x => x.EditorAlias == Constants.PropertyEditors.Aliases.BlockList)));
Assert.IsFalse(editor.IsConverter(Mock.Of<IPublishedPropertyType>(x => x.EditorAlias == Constants.PropertyEditors.Aliases.NestedContent)));
}
[Test]
public void Get_Value_Type_Multiple()
{
var editor = CreateConverter();
var config = ConfigForMany();
var dataType = new PublishedDataType(1, "test", new Lazy<object>(() => config));
var propType = Mock.Of<IPublishedPropertyType>(x => x.DataType == dataType);
var valueType = editor.GetPropertyValueType(propType);
// the result is always block list model
Assert.AreEqual(typeof(BlockListModel), valueType);
}
[Test]
public void Get_Value_Type_Single()
{
var editor = CreateConverter();
var config = ConfigForSingle();
var dataType = new PublishedDataType(1, "test", new Lazy<object>(() => config));
var propType = Mock.Of<IPublishedPropertyType>(x => x.DataType == dataType);
var valueType = editor.GetPropertyValueType(propType);
// the result is always block list model
Assert.AreEqual(typeof(BlockListModel), valueType);
}
[Test]
public void Convert_Null_Empty()
{
var editor = CreateConverter();
var config = ConfigForMany();
var propertyType = GetPropertyType(config);
var publishedElement = Mock.Of<IPublishedElement>();
string json = null;
var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
Assert.IsNotNull(converted);
Assert.AreEqual(0, converted.Data.Count());
Assert.AreEqual(0, converted.Layout.Count());
json = string.Empty;
converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
Assert.IsNotNull(converted);
Assert.AreEqual(0, converted.Data.Count());
Assert.AreEqual(0, converted.Layout.Count());
}
[Test]
public void Convert_Valid_Empty_Json()
{
var editor = CreateConverter();
var config = ConfigForMany();
var propertyType = GetPropertyType(config);
var publishedElement = Mock.Of<IPublishedElement>();
var json = "{}";
var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
Assert.IsNotNull(converted);
Assert.AreEqual(0, converted.Data.Count());
Assert.AreEqual(0, converted.Layout.Count());
json = @"{
layout: [],
data: []}";
converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
Assert.IsNotNull(converted);
Assert.AreEqual(0, converted.Data.Count());
Assert.AreEqual(0, converted.Layout.Count());
// Even though there is a layout, there is no data, so the conversion will result in zero elements in total
json = @"
{
layout: {
'" + Constants.PropertyEditors.Aliases.BlockList + @"': [
{
'udi': 'umb://element/e7dba547615b4e9ab4ab2a7674845bc9',
'settings': {}
}
]
},
data: []
}";
converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
Assert.IsNotNull(converted);
Assert.AreEqual(0, converted.Data.Count());
Assert.AreEqual(0, converted.Layout.Count());
// Even though there is a layout and data, the data is invalid (missing required keys) so the conversion will result in zero elements in total
json = @"
{
layout: {
'" + Constants.PropertyEditors.Aliases.BlockList + @"': [
{
'udi': 'umb://element/e7dba547615b4e9ab4ab2a7674845bc9',
'settings': {}
}
]
},
data: [
{
'udi': 'umb://element/e7dba547615b4e9ab4ab2a7674845bc9'
}
]
}";
converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
Assert.IsNotNull(converted);
Assert.AreEqual(0, converted.Data.Count());
Assert.AreEqual(0, converted.Layout.Count());
// Everthing is ok except the udi reference in the layout doesn't match the data so it will be empty
json = @"
{
layout: {
'" + Constants.PropertyEditors.Aliases.BlockList + @"': [
{
'udi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D',
'settings': {}
}
]
},
data: [
{
'contentTypeKey': '" + Key1 + @"',
'key': '1304E1DD-0000-4396-84FE-8A399231CB3D'
}
]
}";
converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
Assert.IsNotNull(converted);
Assert.AreEqual(1, converted.Data.Count());
Assert.AreEqual(0, converted.Layout.Count());
}
[Test]
public void Convert_Valid_Json()
{
var editor = CreateConverter();
var config = ConfigForMany();
var propertyType = GetPropertyType(config);
var publishedElement = Mock.Of<IPublishedElement>();
var json = @"
{
layout: {
'" + Constants.PropertyEditors.Aliases.BlockList + @"': [
{
'udi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D',
'settings': {}
}
]
},
data: [
{
'contentTypeKey': '" + Key1 + @"',
'udi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D'
}
]
}";
var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
Assert.IsNotNull(converted);
Assert.AreEqual(1, converted.Data.Count());
var item0 = converted.Data.ElementAt(0);
Assert.AreEqual(Guid.Parse("1304E1DD-AC87-4396-84FE-8A399231CB3D"), item0.Key);
Assert.AreEqual("Test1", item0.ContentType.Alias);
Assert.AreEqual(1, converted.Layout.Count());
var layout0 = converted.Layout.ElementAt(0);
Assert.IsNull(layout0.Settings);
Assert.AreEqual(Udi.Parse("umb://element/1304E1DDAC87439684FE8A399231CB3D"), layout0.Udi);
}
[Test]
public void Get_Data_From_Layout_Item()
{
var editor = CreateConverter();
var config = ConfigForMany();
var propertyType = GetPropertyType(config);
var publishedElement = Mock.Of<IPublishedElement>();
var json = @"
{
layout: {
'" + Constants.PropertyEditors.Aliases.BlockList + @"': [
{
'udi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D',
'settings': {}
},
{
'udi': 'umb://element/0A4A416E547D464FABCC6F345C17809A',
'settings': {}
}
]
},
data: [
{
'contentTypeKey': '" + Key1 + @"',
'udi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D'
},
{
'contentTypeKey': '" + Key2 + @"',
'udi': 'umb://element/E05A034704424AB3A520E048E6197E79'
},
{
'contentTypeKey': '" + Key2 + @"',
'udi': 'umb://element/0A4A416E547D464FABCC6F345C17809A'
}
]
}";
var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
Assert.IsNotNull(converted);
Assert.AreEqual(3, converted.Data.Count());
Assert.AreEqual(2, converted.Layout.Count());
var item0 = converted.Layout.ElementAt(0);
Assert.AreEqual(Guid.Parse("1304E1DD-AC87-4396-84FE-8A399231CB3D"), item0.Data.Key);
Assert.AreEqual("Test1", item0.Data.ContentType.Alias);
var item1 = converted.Layout.ElementAt(1);
Assert.AreEqual(Guid.Parse("0A4A416E-547D-464F-ABCC-6F345C17809A"), item1.Data.Key);
Assert.AreEqual("Test2", item1.Data.ContentType.Alias);
}
}
}
@@ -93,7 +93,7 @@ namespace Umbraco.Tests.PropertyEditors
})));
var publishedPropType = new PublishedPropertyType(
new PublishedContentType(1234, "test", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing),
new PublishedContentType(Guid.NewGuid(), 1234, "test", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing),
new PropertyType("test", ValueStorageType.Nvarchar) { DataTypeId = 123 },
new PropertyValueConverterCollection(Enumerable.Empty<IPropertyValueConverter>()),
Mock.Of<IPublishedModelFactory>(), mockPublishedContentTypeFactory.Object);
+21 -21
View File
@@ -40,7 +40,7 @@ namespace Umbraco.Tests.Published
yield return contentTypeFactory.CreatePropertyType(contentType, "prop1", 1);
}
var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", CreatePropertyTypes);
var elementType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "element1", CreatePropertyTypes);
var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary<string, object> { { "prop1", "1234" } }, false);
@@ -74,7 +74,7 @@ namespace Umbraco.Tests.Published
=> propertyType.EditorAlias.InvariantEquals("Umbraco.Void");
public Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> typeof (int);
=> typeof(int);
public PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.Element;
@@ -83,10 +83,10 @@ namespace Umbraco.Tests.Published
=> int.TryParse(source as string, out int i) ? i : 0;
public object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
=> (int) inter;
=> (int)inter;
public object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
=> ((int) inter).ToString();
=> ((int)inter).ToString();
}
#endregion
@@ -120,11 +120,11 @@ namespace Umbraco.Tests.Published
yield return contentTypeFactory.CreatePropertyType(contentType, "prop1", 1);
}
var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", CreatePropertyTypes);
var elementType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "element1", CreatePropertyTypes);
var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary<string, object> { { "prop1", "1234" } }, false);
var cntType1 = contentTypeFactory.CreateContentType(1001, "cnt1", t => Enumerable.Empty<PublishedPropertyType>());
var cntType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1001, "cnt1", t => Enumerable.Empty<PublishedPropertyType>());
var cnt1 = new SolidPublishedContent(cntType1) { Id = 1234 };
cacheContent[cnt1.Id] = cnt1;
@@ -143,7 +143,7 @@ namespace Umbraco.Tests.Published
}
public bool? IsValue(object value, PropertyValueLevel level)
=> value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false);
=> value != null && (!(value is string) || string.IsNullOrWhiteSpace((string)value) == false);
public bool IsConverter(IPublishedPropertyType propertyType)
=> propertyType.EditorAlias.InvariantEquals("Umbraco.Void");
@@ -162,10 +162,10 @@ namespace Umbraco.Tests.Published
=> int.TryParse(source as string, out int i) ? i : -1;
public object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
=> _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById((int) inter);
=> _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById((int)inter);
public object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
=> ((int) inter).ToString();
=> ((int)inter).ToString();
}
#endregion
@@ -215,10 +215,10 @@ namespace Umbraco.Tests.Published
yield return contentTypeFactory.CreatePropertyType(contentType, "prop" + i, i);
}
var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", t => CreatePropertyTypes(t, 1));
var elementType2 = contentTypeFactory.CreateContentType(1001, "element2", t => CreatePropertyTypes(t, 2));
var contentType1 = contentTypeFactory.CreateContentType(1002, "content1", t => CreatePropertyTypes(t, 1));
var contentType2 = contentTypeFactory.CreateContentType(1003, "content2", t => CreatePropertyTypes(t, 2));
var elementType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "element1", t => CreatePropertyTypes(t, 1));
var elementType2 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1001, "element2", t => CreatePropertyTypes(t, 2));
var contentType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1002, "content1", t => CreatePropertyTypes(t, 1));
var contentType2 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1003, "content2", t => CreatePropertyTypes(t, 2));
var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary<string, object> { { "prop1", "val1" } }, false);
var element2 = new PublishedElement(elementType2, Guid.NewGuid(), new Dictionary<string, object> { { "prop2", "1003" } }, false);
@@ -239,22 +239,22 @@ namespace Umbraco.Tests.Published
// can get the actual property Clr type
// ie ModelType gets properly mapped by IPublishedContentModelFactory
// must test ModelClrType with special equals 'cos they are not ref-equals
Assert.IsTrue(ModelType.Equals(typeof (IEnumerable<>).MakeGenericType(ModelType.For("content1")), contentType2.GetPropertyType("prop2").ModelClrType));
Assert.AreEqual(typeof (IEnumerable<PublishedSnapshotTestObjects.TestContentModel1>), contentType2.GetPropertyType("prop2").ClrType);
Assert.IsTrue(ModelType.Equals(typeof(IEnumerable<>).MakeGenericType(ModelType.For("content1")), contentType2.GetPropertyType("prop2").ModelClrType));
Assert.AreEqual(typeof(IEnumerable<PublishedSnapshotTestObjects.TestContentModel1>), contentType2.GetPropertyType("prop2").ClrType);
// can create a model for an element
var model1 = factory.CreateModel(element1);
Assert.IsInstanceOf<PublishedSnapshotTestObjects.TestElementModel1>(model1);
Assert.AreEqual("val1", ((PublishedSnapshotTestObjects.TestElementModel1) model1).Prop1);
Assert.AreEqual("val1", ((PublishedSnapshotTestObjects.TestElementModel1)model1).Prop1);
// can create a model for a published content
var model2 = factory.CreateModel(element2);
Assert.IsInstanceOf<PublishedSnapshotTestObjects.TestElementModel2>(model2);
var mmodel2 = (PublishedSnapshotTestObjects.TestElementModel2) model2;
var mmodel2 = (PublishedSnapshotTestObjects.TestElementModel2)model2;
// and get direct property
Assert.IsInstanceOf<PublishedSnapshotTestObjects.TestContentModel1[]>(model2.Value("prop2"));
Assert.AreEqual(1, ((PublishedSnapshotTestObjects.TestContentModel1[]) model2.Value("prop2")).Length);
Assert.AreEqual(1, ((PublishedSnapshotTestObjects.TestContentModel1[])model2.Value("prop2")).Length);
// and get model property
Assert.IsInstanceOf<IEnumerable<PublishedSnapshotTestObjects.TestContentModel1>>(mmodel2.Prop2);
@@ -271,7 +271,7 @@ namespace Umbraco.Tests.Published
=> propertyType.EditorAlias == "Umbraco.Void";
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> typeof (string);
=> typeof(string);
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.Element;
@@ -290,7 +290,7 @@ namespace Umbraco.Tests.Published
=> propertyType.EditorAlias == "Umbraco.Void.2";
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> typeof (IEnumerable<>).MakeGenericType(ModelType.For("content1"));
=> typeof(IEnumerable<>).MakeGenericType(ModelType.For("content1"));
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.Elements;
@@ -303,7 +303,7 @@ namespace Umbraco.Tests.Published
public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
{
return ((int[]) inter).Select(x => (PublishedSnapshotTestObjects.TestContentModel1) _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(x)).ToArray();
return ((int[])inter).Select(x => (PublishedSnapshotTestObjects.TestContentModel1)_publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(x)).ToArray();
}
}
@@ -100,8 +100,8 @@ namespace Umbraco.Tests.Published
.Returns((string alias) =>
{
return alias == "contentN1"
? (IList) new List<TestElementModel>()
: (IList) new List<IPublishedElement>();
? (IList)new List<TestElementModel>()
: (IList)new List<IPublishedElement>();
});
var contentCache = new Mock<IPublishedContentCache>();
@@ -142,9 +142,9 @@ namespace Umbraco.Tests.Published
yield return factory.CreatePropertyType(contentType, "propertyN1", 3);
}
var contentType1 = factory.CreateContentType(1, "content1", CreatePropertyTypes1);
var contentType2 = factory.CreateContentType(2, "content2", CreatePropertyTypes2);
var contentTypeN1 = factory.CreateContentType(2, "contentN1", CreatePropertyTypesN1, isElement: true);
var contentType1 = factory.CreateContentType(Guid.NewGuid(), 1, "content1", CreatePropertyTypes1);
var contentType2 = factory.CreateContentType(Guid.NewGuid(), 2, "content2", CreatePropertyTypes2);
var contentTypeN1 = factory.CreateContentType(Guid.NewGuid(), 2, "contentN1", CreatePropertyTypesN1, isElement: true);
// mocked content cache returns content types
contentCache
@@ -164,7 +164,7 @@ namespace Umbraco.Tests.Published
(var contentType1, _) = CreateContentTypes();
// nested single converter returns the proper value clr type TestModel, and cache level
Assert.AreEqual(typeof (TestElementModel), contentType1.GetPropertyType("property1").ClrType);
Assert.AreEqual(typeof(TestElementModel), contentType1.GetPropertyType("property1").ClrType);
Assert.AreEqual(PropertyCacheLevel.Element, contentType1.GetPropertyType("property1").CacheLevel);
var key = Guid.NewGuid();
@@ -172,7 +172,7 @@ namespace Umbraco.Tests.Published
var content = new SolidPublishedContent(contentType1)
{
Key = key,
Properties = new []
Properties = new[]
{
new TestPublishedProperty(contentType1.GetPropertyType("property1"), $@"[
{{ ""key"": ""{keyA}"", ""propertyN1"": ""foo"", ""ncContentTypeAlias"": ""contentN1"" }}
@@ -183,7 +183,7 @@ namespace Umbraco.Tests.Published
// nested single converter returns proper TestModel value
Assert.IsInstanceOf<TestElementModel>(value);
var valueM = (TestElementModel) value;
var valueM = (TestElementModel)value;
Assert.AreEqual("foo", valueM.PropValue);
Assert.AreEqual(keyA, valueM.Key);
}
@@ -194,7 +194,7 @@ namespace Umbraco.Tests.Published
(_, var contentType2) = CreateContentTypes();
// nested many converter returns the proper value clr type IEnumerable<TestModel>, and cache level
Assert.AreEqual(typeof (IEnumerable<TestElementModel>), contentType2.GetPropertyType("property2").ClrType);
Assert.AreEqual(typeof(IEnumerable<TestElementModel>), contentType2.GetPropertyType("property2").ClrType);
Assert.AreEqual(PropertyCacheLevel.Element, contentType2.GetPropertyType("property2").CacheLevel);
var key = Guid.NewGuid();
@@ -216,7 +216,7 @@ namespace Umbraco.Tests.Published
// nested many converter returns proper IEnumerable<TestModel> value
Assert.IsInstanceOf<IEnumerable<IPublishedElement>>(value);
Assert.IsInstanceOf<IEnumerable<TestElementModel>>(value);
var valueM = ((IEnumerable<TestElementModel>) value).ToArray();
var valueM = ((IEnumerable<TestElementModel>)value).ToArray();
Assert.AreEqual("foo", valueM[0].PropValue);
Assert.AreEqual(keyA, valueM[0].Key);
Assert.AreEqual("bar", valueM[1].PropValue);
@@ -41,7 +41,7 @@ namespace Umbraco.Tests.Published
yield return publishedContentTypeFactory.CreatePropertyType(contentType, "prop1", 1);
}
var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes);
var setType1 = publishedContentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "set1", CreatePropertyTypes);
// PublishedElementPropertyBase.GetCacheLevels:
//
@@ -122,7 +122,7 @@ namespace Umbraco.Tests.Published
yield return publishedContentTypeFactory.CreatePropertyType(contentType, "prop1", 1);
}
var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes);
var setType1 = publishedContentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "set1", CreatePropertyTypes);
var elementsCache = new FastDictionaryAppCache();
var snapshotCache = new FastDictionaryAppCache();
@@ -199,7 +199,7 @@ namespace Umbraco.Tests.Published
yield return publishedContentTypeFactory.CreatePropertyType(contentType, "prop1", 1);
}
var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes);
var setType1 = publishedContentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "set1", CreatePropertyTypes);
Assert.Throws<Exception>(() =>
{
@@ -98,7 +98,7 @@ namespace Umbraco.Tests.PublishedContent
var doc = GetContent(true, 1);
//change a doc type alias
var c = (SolidPublishedContent)doc.Children.ElementAt(0);
c.ContentType = new PublishedContentType(22, "DontMatch", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
c.ContentType = new PublishedContentType(Guid.NewGuid(), 22, "DontMatch", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var dt = doc.ChildrenAsTable(Current.Services, "Child");
@@ -129,7 +129,7 @@ namespace Umbraco.Tests.PublishedContent
var factory = new PublishedContentTypeFactory(Mock.Of<IPublishedModelFactory>(), new PropertyValueConverterCollection(Array.Empty<IPropertyValueConverter>()), dataTypeService);
var contentTypeAlias = createChildren ? "Parent" : "Child";
var contentType = new PublishedContentType(22, contentTypeAlias, PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var contentType = new PublishedContentType(Guid.NewGuid(), 22, contentTypeAlias, PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var d = new SolidPublishedContent(contentType)
{
CreateDate = DateTime.Now,
@@ -73,14 +73,14 @@ namespace Umbraco.Tests.PublishedContent
yield return factory.CreatePropertyType(contentType, "noprop", 1, variations: ContentVariation.Culture);
}
var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty<string>(), CreatePropertyTypes1);
var contentType1 = factory.CreateContentType(Guid.NewGuid(), 1, "ContentType1", Enumerable.Empty<string>(), CreatePropertyTypes1);
IEnumerable<IPublishedPropertyType> CreatePropertyTypes2(IPublishedContentType contentType)
{
yield return factory.CreatePropertyType(contentType, "prop3", 1, variations: ContentVariation.Culture);
}
var contentType2 = factory.CreateContentType(2, "contentType2", Enumerable.Empty<string>(), CreatePropertyTypes2);
var contentType2 = factory.CreateContentType(Guid.NewGuid(), 2, "contentType2", Enumerable.Empty<string>(), CreatePropertyTypes2);
var prop1 = new SolidPublishedPropertyWithLanguageVariants
{
@@ -7,6 +7,7 @@ using Umbraco.Web;
using Umbraco.Core;
using Umbraco.Tests.Testing;
using Umbraco.Web.Composing;
using System;
namespace Umbraco.Tests.PublishedContent
{
@@ -21,9 +22,9 @@ namespace Umbraco.Tests.PublishedContent
yield return factory.CreatePropertyType(contentType, "prop1", 1);
}
var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty<string>(), CreatePropertyTypes);
var contentType2 = factory.CreateContentType(2, "ContentType2", Enumerable.Empty<string>(), CreatePropertyTypes);
var contentType2Sub = factory.CreateContentType(3, "ContentType2Sub", Enumerable.Empty<string>(), CreatePropertyTypes);
var contentType1 = factory.CreateContentType(Guid.NewGuid(), 1, "ContentType1", Enumerable.Empty<string>(), CreatePropertyTypes);
var contentType2 = factory.CreateContentType(Guid.NewGuid(), 2, "ContentType2", Enumerable.Empty<string>(), CreatePropertyTypes);
var contentType2Sub = factory.CreateContentType(Guid.NewGuid(), 3, "ContentType2Sub", Enumerable.Empty<string>(), CreatePropertyTypes);
var content = new SolidPublishedContent(contentType1)
{
@@ -13,6 +13,7 @@ using Umbraco.Core.Services;
using Umbraco.Web;
using Umbraco.Web.Templates;
using Umbraco.Web.Models;
using System;
namespace Umbraco.Tests.PublishedContent
{
@@ -56,7 +57,7 @@ namespace Umbraco.Tests.PublishedContent
yield return publishedContentTypeFactory.CreatePropertyType(contentType, "content", 1);
}
var type = new AutoPublishedContentType(0, "anything", CreatePropertyTypes);
var type = new AutoPublishedContentType(Guid.NewGuid(), 0, "anything", CreatePropertyTypes);
ContentTypesCache.GetPublishedContentTypeByAlias = alias => type;
var umbracoContext = GetUmbracoContext("/test");
@@ -83,8 +83,8 @@ namespace Umbraco.Tests.PublishedContent
}
var compositionAliases = new[] { "MyCompositionAlias" };
var anythingType = new AutoPublishedContentType(0, "anything", compositionAliases, CreatePropertyTypes);
var homeType = new AutoPublishedContentType(0, "home", compositionAliases, CreatePropertyTypes);
var anythingType = new AutoPublishedContentType(Guid.NewGuid(), 0, "anything", compositionAliases, CreatePropertyTypes);
var homeType = new AutoPublishedContentType(Guid.NewGuid(), 0, "home", compositionAliases, CreatePropertyTypes);
ContentTypesCache.GetPublishedContentTypeByAlias = alias => alias.InvariantEquals("home") ? homeType : anythingType;
}
@@ -398,8 +398,8 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Children_GroupBy_DocumentTypeAlias()
{
var home = new AutoPublishedContentType(22, "Home", new PublishedPropertyType[] { });
var custom = new AutoPublishedContentType(23, "CustomDocument", new PublishedPropertyType[] { });
var home = new AutoPublishedContentType(Guid.NewGuid(), 22, "Home", new PublishedPropertyType[] { });
var custom = new AutoPublishedContentType(Guid.NewGuid(), 23, "CustomDocument", new PublishedPropertyType[] { });
var contentTypes = new Dictionary<string, PublishedContentType>
{
{ home.Alias, home },
@@ -419,8 +419,8 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Children_Where_DocumentTypeAlias()
{
var home = new AutoPublishedContentType(22, "Home", new PublishedPropertyType[] { });
var custom = new AutoPublishedContentType(23, "CustomDocument", new PublishedPropertyType[] { });
var home = new AutoPublishedContentType(Guid.NewGuid(), 22, "Home", new PublishedPropertyType[] { });
var custom = new AutoPublishedContentType(Guid.NewGuid(), 23, "CustomDocument", new PublishedPropertyType[] { });
var contentTypes = new Dictionary<string, PublishedContentType>
{
{ home.Alias, home },
@@ -903,7 +903,7 @@ namespace Umbraco.Tests.PublishedContent
yield return factory.CreatePropertyType(contentType, "detached", 1003);
}
var ct = factory.CreateContentType(0, "alias", CreatePropertyTypes);
var ct = factory.CreateContentType(Guid.NewGuid(), 0, "alias", CreatePropertyTypes);
var pt = ct.GetPropertyType("detached");
var prop = new PublishedElementPropertyBase(pt, null, false, PropertyCacheLevel.None, 5548);
Assert.IsInstanceOf<int>(prop.GetValue());
@@ -935,7 +935,7 @@ namespace Umbraco.Tests.PublishedContent
var guid = Guid.NewGuid();
var ct = factory.CreateContentType(0, "alias", CreatePropertyTypes);
var ct = factory.CreateContentType(Guid.NewGuid(), 0, "alias", CreatePropertyTypes);
var c = new ImageWithLegendModel(ct, guid, new Dictionary<string, object>
{
@@ -66,7 +66,7 @@ namespace Umbraco.Tests.PublishedContent
pc.Setup(content => content.Path).Returns("-1,1");
pc.Setup(content => content.Parent).Returns(() => null);
pc.Setup(content => content.Properties).Returns(new Collection<IPublishedProperty>());
pc.Setup(content => content.ContentType).Returns(new PublishedContentType(22, "anything", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing));
pc.Setup(content => content.ContentType).Returns(new PublishedContentType(Guid.NewGuid(), 22, "anything", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing));
return pc;
}
}
@@ -44,7 +44,7 @@ namespace Umbraco.Tests.PublishedContent
{ }
}
class SolidPublishedContentCache : PublishedCacheBase, IPublishedContentCache, IPublishedMediaCache
class SolidPublishedContentCache : PublishedCacheBase, IPublishedContentCache2, IPublishedMediaCache2
{
private readonly Dictionary<int, IPublishedContent> _content = new Dictionary<int, IPublishedContent>();
@@ -150,6 +150,11 @@ namespace Umbraco.Tests.PublishedContent
throw new NotImplementedException();
}
public override IPublishedContentType GetContentType(Guid key)
{
throw new NotImplementedException();
}
public override IEnumerable<IPublishedContent> GetByContentType(IPublishedContentType contentType)
{
throw new NotImplementedException();
@@ -378,7 +383,7 @@ namespace Umbraco.Tests.PublishedContent
#endregion
}
class PublishedContentStrong1 : PublishedContentModel
internal class PublishedContentStrong1 : PublishedContentModel
{
public PublishedContentStrong1(IPublishedContent content)
: base(content)
@@ -387,7 +392,7 @@ namespace Umbraco.Tests.PublishedContent
public int StrongValue => this.Value<int>("strongValue");
}
class PublishedContentStrong1Sub : PublishedContentStrong1
internal class PublishedContentStrong1Sub : PublishedContentStrong1
{
public PublishedContentStrong1Sub(IPublishedContent content)
: base(content)
@@ -396,7 +401,7 @@ namespace Umbraco.Tests.PublishedContent
public int AnotherValue => this.Value<int>("anotherValue");
}
class PublishedContentStrong2 : PublishedContentModel
internal class PublishedContentStrong2 : PublishedContentModel
{
public PublishedContentStrong2(IPublishedContent content)
: base(content)
@@ -405,7 +410,7 @@ namespace Umbraco.Tests.PublishedContent
public int StrongValue => this.Value<int>("strongValue");
}
class AutoPublishedContentType : PublishedContentType
internal class AutoPublishedContentType : PublishedContentType
{
private static readonly IPublishedPropertyType Default;
@@ -418,20 +423,20 @@ namespace Umbraco.Tests.PublishedContent
Default = factory.CreatePropertyType("*", 666);
}
public AutoPublishedContentType(int id, string alias, IEnumerable<PublishedPropertyType> propertyTypes)
: base(id, alias, PublishedItemType.Content, Enumerable.Empty<string>(), propertyTypes, ContentVariation.Nothing)
public AutoPublishedContentType(Guid key, int id, string alias, IEnumerable<PublishedPropertyType> propertyTypes)
: base(key, id, alias, PublishedItemType.Content, Enumerable.Empty<string>(), propertyTypes, ContentVariation.Nothing)
{ }
public AutoPublishedContentType(int id, string alias, Func<IPublishedContentType, IEnumerable<IPublishedPropertyType>> propertyTypes)
: base(id, alias, PublishedItemType.Content, Enumerable.Empty<string>(), propertyTypes, ContentVariation.Nothing)
public AutoPublishedContentType(Guid key, int id, string alias, Func<IPublishedContentType, IEnumerable<IPublishedPropertyType>> propertyTypes)
: base(key, id, alias, PublishedItemType.Content, Enumerable.Empty<string>(), propertyTypes, ContentVariation.Nothing)
{ }
public AutoPublishedContentType(int id, string alias, IEnumerable<string> compositionAliases, IEnumerable<PublishedPropertyType> propertyTypes)
: base(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing)
public AutoPublishedContentType(Guid key, int id, string alias, IEnumerable<string> compositionAliases, IEnumerable<PublishedPropertyType> propertyTypes)
: base(key, id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing)
{ }
public AutoPublishedContentType(int id, string alias, IEnumerable<string> compositionAliases, Func<IPublishedContentType, IEnumerable<IPublishedPropertyType>> propertyTypes)
: base(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing)
public AutoPublishedContentType(Guid key, int id, string alias, IEnumerable<string> compositionAliases, Func<IPublishedContentType, IEnumerable<IPublishedPropertyType>> propertyTypes)
: base(key, id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing)
{ }
public override IPublishedPropertyType GetPropertyType(string alias)
@@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
@@ -27,7 +28,7 @@ namespace Umbraco.Tests.Routing
Mock.Of<IPublishedModelFactory>(),
Mock.Of<IPublishedContentTypeFactory>()),
};
_publishedContentType = new PublishedContentType(0, "Doc", PublishedItemType.Content, Enumerable.Empty<string>(), properties, ContentVariation.Nothing);
_publishedContentType = new PublishedContentType(Guid.NewGuid(), 0, "Doc", PublishedItemType.Content, Enumerable.Empty<string>(), properties, ContentVariation.Nothing);
}
protected override PublishedContentType GetPublishedContentTypeByAlias(string alias)
@@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
@@ -25,7 +26,7 @@ namespace Umbraco.Tests.Routing
Mock.Of<IPublishedModelFactory>(),
Mock.Of<IPublishedContentTypeFactory>()),
};
_publishedContentType = new PublishedContentType(0, "Doc", PublishedItemType.Content, Enumerable.Empty<string>(), properties, ContentVariation.Nothing);
_publishedContentType = new PublishedContentType(Guid.NewGuid(), 0, "Doc", PublishedItemType.Content, Enumerable.Empty<string>(), properties, ContentVariation.Nothing);
}
protected override PublishedContentType GetPublishedContentTypeByAlias(string alias)
@@ -139,7 +139,7 @@ namespace Umbraco.Tests.Routing
property.SetSourceValue("en", enMediaUrl, true);
property.SetSourceValue("da", daMediaUrl);
var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), new [] { umbracoFilePropertyType }, ContentVariation.Culture);
var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), new [] { umbracoFilePropertyType }, ContentVariation.Culture);
var publishedContent = new SolidPublishedContent(contentType) {Properties = new[] {property}};
var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, UrlMode.Auto, "da");
@@ -150,7 +150,7 @@ namespace Umbraco.Tests.Routing
{
var umbracoFilePropertyType = CreatePropertyType(propertyEditorAlias, dataTypeConfiguration, ContentVariation.Nothing);
var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(),
var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(),
new[] {umbracoFilePropertyType}, ContentVariation.Nothing);
return new SolidPublishedContent(contentType)
@@ -140,7 +140,7 @@ namespace Umbraco.Tests.Routing
frequest.TemplateModel = template;
var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext);
var type = new AutoPublishedContentType(22, "CustomDocument", new PublishedPropertyType[] { });
var type = new AutoPublishedContentType(Guid.NewGuid(), 22, "CustomDocument", new PublishedPropertyType[] { });
ContentTypesCache.GetPublishedContentTypeByAlias = alias => type;
var handler = new RenderRouteHandler(umbracoContext, new TestControllerFactory(umbracoContextAccessor, Mock.Of<ILogger>(), context =>
@@ -158,7 +158,7 @@ namespace Umbraco.Tests.Routing
var umbracoSettings = Current.Configs.Settings();
var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Culture);
var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Culture);
var publishedContent = new SolidPublishedContent(contentType) { Id = 1234 };
var publishedContentCache = new Mock<IPublishedContentCache>();
@@ -203,7 +203,7 @@ namespace Umbraco.Tests.Routing
var umbracoSettings = Current.Configs.Settings();
var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Culture);
var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Culture);
var publishedContent = new SolidPublishedContent(contentType) { Id = 1234 };
var publishedContentCache = new Mock<IPublishedContentCache>();
@@ -257,7 +257,7 @@ namespace Umbraco.Tests.Routing
var umbracoSettings = Current.Configs.Settings();
var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Culture);
var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Culture);
var publishedContent = new SolidPublishedContent(contentType) { Id = 1234 };
var publishedContentCache = new Mock<IPublishedContentCache>();
@@ -65,7 +65,7 @@ namespace Umbraco.Tests.Templates
{
//setup a mock url provider which we'll use for testing
var mediaType = new PublishedContentType(777, "image", PublishedItemType.Media, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var mediaType = new PublishedContentType(Guid.NewGuid(), 777, "image", PublishedItemType.Media, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var media = new Mock<IPublishedContent>();
media.Setup(x => x.ContentType).Returns(mediaType);
var mediaUrlProvider = new Mock<IMediaUrlProvider>();
@@ -54,12 +54,12 @@ namespace Umbraco.Tests.Templates
contentUrlProvider
.Setup(x => x.GetUrl(It.IsAny<UmbracoContext>(), It.IsAny<IPublishedContent>(), It.IsAny<UrlMode>(), It.IsAny<string>(), It.IsAny<Uri>()))
.Returns(UrlInfo.Url("/my-test-url"));
var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var publishedContent = new Mock<IPublishedContent>();
publishedContent.Setup(x => x.Id).Returns(1234);
publishedContent.Setup(x => x.ContentType).Returns(contentType);
var mediaType = new PublishedContentType(777, "image", PublishedItemType.Media, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var mediaType = new PublishedContentType(Guid.NewGuid(), 777, "image", PublishedItemType.Media, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var media = new Mock<IPublishedContent>();
media.Setup(x => x.ContentType).Returns(mediaType);
var mediaUrlProvider = new Mock<IMediaUrlProvider>();
+1 -1
View File
@@ -42,7 +42,7 @@ namespace Umbraco.Tests.TestHelpers
new DataType(new VoidEditor(Mock.Of<ILogger>())) { Id = 1 });
var factory = new PublishedContentTypeFactory(Mock.Of<IPublishedModelFactory>(), new PropertyValueConverterCollection(Array.Empty<IPropertyValueConverter>()), dataTypeService);
var type = new AutoPublishedContentType(0, "anything", new PublishedPropertyType[] { });
var type = new AutoPublishedContentType(Guid.NewGuid(), 0, "anything", new PublishedPropertyType[] { });
ContentTypesCache.GetPublishedContentTypeByAlias = alias => GetPublishedContentTypeByAlias(alias) ?? type;
}
@@ -86,7 +86,7 @@ namespace Umbraco.Tests.Testing.TestingTests
var theUrlProvider = new UrlProvider(umbracoContext, new [] { urlProvider }, Enumerable.Empty<IMediaUrlProvider>(), umbracoContext.VariationContextAccessor);
var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var publishedContent = Mock.Of<IPublishedContent>();
Mock.Get(publishedContent).Setup(x => x.ContentType).Returns(contentType);
+1
View File
@@ -147,6 +147,7 @@
<Compile Include="Persistence\Mappers\MapperTestBase.cs" />
<Compile Include="Persistence\Repositories\DocumentRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\EntityRepositoryTest.cs" />
<Compile Include="PropertyEditors\BlockListPropertyValueConverterTests.cs" />
<Compile Include="PropertyEditors\DataValueReferenceFactoryCollectionTests.cs" />
<Compile Include="PropertyEditors\NestedContentPropertyComponentTests.cs" />
<Compile Include="PublishedContent\NuCacheChildrenTests.cs" />
File diff suppressed because it is too large Load Diff
@@ -37,6 +37,8 @@
vm.selectApp = selectApp;
vm.selectAppAnchor = selectAppAnchor;
vm.getScope = getScope;// used by property editors to get a scope that is the root of split view, content apps etc.
//Used to track how many content views there are (for split view there will be 2, it could support more in theory)
vm.editors = [];
@@ -243,6 +245,10 @@
}
}
function getScope() {
return $scope;
}
}
angular.module('umbraco.directives').component('umbVariantContentEditors', umbVariantContentEditors);
@@ -2,13 +2,20 @@ angular.module("umbraco.directives").directive('focusWhen', function ($timeout)
return {
restrict: 'A',
link: function (scope, elm, attrs, ctrl) {
var delayTimer;
attrs.$observe("focusWhen", function (newValue) {
if (newValue === "true") {
$timeout(function () {
elm.trigger("focus");
});
if (newValue === "true" && document.activeelement !== elm[0]) {
delayTimer = $timeout(function () {
elm[0].focus();
});
}
});
scope.$on('$destroy', function() {
$timeout.cancel(delayTimer);
});
}
};
});
@@ -280,6 +280,177 @@ angular.module('umbraco.mocks').
return node;
},
/** Creats a mock variant content object */
getMockVariantContent: function(id) {
var node = {
name: "My content with id: " + id,
updateDate: new Date().toIsoDateTimeString(),
publishDate: new Date().toIsoDateTimeString(),
createDate: new Date().toIsoDateTimeString(),
id: id,
parentId: 1234,
icon: "icon-umb-content",
owner: { name: "Administrator", id: 0 },
updater: { name: "Per Ploug Krogslund", id: 1 },
path: "-1,1234,2455",
allowedActions: ["U", "H", "A"],
contentTypeAlias: "testAlias",
contentTypeKey: "7C5B74D1-E2F9-45A3-AE4B-FC7A829BF8AB",
variants: [
{
name: "",
language: null,
segment: null,
state: "NotCreated",
updateDate: "0001-01-01 00:00:00",
createDate: "0001-01-01 00:00:00",
publishDate: null,
releaseDate: null,
expireDate: null,
notifications: [],
tabs: [
{
label: "Content",
id: 2,
properties: [
{ alias: "testproperty", label: "Test property", view: "textbox", value: "asdfghjk" },
{ alias: "valTest", label: "Validation test", view: "validationtest", value: "asdfasdf" },
{ alias: "bodyText", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "<p>askjdkasj lasjd</p>", config: {} },
{ alias: "textarea", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } },
{ alias: "media", label: "Media picker", view: "mediapicker", value: "1234,23242,23232,23231", config: {multiPicker: 1} }
]
},
{
label: "Sample Editor",
id: 3,
properties: [
{ alias: "datepicker", label: "Datepicker", view: "datepicker", config: { pickTime: false, format: "yyyy-MM-dd" } },
{ alias: "tags", label: "Tags", view: "tags", value: "" }
]
},
{
label: "This",
id: 4,
properties: [
{ alias: "valTest4", label: "Validation test", view: "validationtest", value: "asdfasdf" },
{ alias: "bodyText4", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "<p>askjdkasj lasjd</p>", config: {} },
{ alias: "textarea4", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } },
{ alias: "content4", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" }
]
},
{
label: "Is",
id: 5,
properties: [
{ alias: "valTest5", label: "Validation test", view: "validationtest", value: "asdfasdf" },
{ alias: "bodyText5", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "<p>askjdkasj lasjd</p>", config: {} },
{ alias: "textarea5", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } },
{ alias: "content5", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" }
]
},
{
label: "Overflown",
id: 6,
properties: [
{ alias: "valTest6", label: "Validation test", view: "validationtest", value: "asdfasdf" },
{ alias: "bodyText6", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "<p>askjdkasj lasjd</p>", config: {} },
{ alias: "textarea6", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } },
{ alias: "content6", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" }
]
},
{
label: "Generic Properties",
id: 0,
properties: [
{
label: 'Id',
value: 1234,
view: "readonlyvalue",
alias: "_umb_id"
},
{
label: 'Created by',
description: 'Original author',
value: "Administrator",
view: "readonlyvalue",
alias: "_umb_createdby"
},
{
label: 'Created',
description: 'Date/time this document was created',
value: new Date().toIsoDateTimeString(),
view: "readonlyvalue",
alias: "_umb_createdate"
},
{
label: 'Updated',
description: 'Date/time this document was created',
value: new Date().toIsoDateTimeString(),
view: "readonlyvalue",
alias: "_umb_updatedate"
},
{
label: 'Document Type',
value: "Home page",
view: "readonlyvalue",
alias: "_umb_doctype"
},
{
label: 'Publish at',
description: 'Date/time to publish this document',
value: new Date().toIsoDateTimeString(),
view: "datepicker",
alias: "_umb_releasedate"
},
{
label: 'Unpublish at',
description: 'Date/time to un-publish this document',
value: new Date().toIsoDateTimeString(),
view: "datepicker",
alias: "_umb_expiredate"
},
{
label: 'Template',
value: "myTemplate",
view: "dropdown",
alias: "_umb_template",
config: {
items: {
"" : "-- Choose template --",
"myTemplate" : "My Templates",
"home" : "Home Page",
"news" : "News Page"
}
}
},
{
label: 'Link to document',
value: ["/testing" + id, "http://localhost/testing" + id, "http://mydomain.com/testing" + id].join(),
view: "urllist",
alias: "_umb_urllist"
},
{
alias: "test", label: "Stuff", view: "test", value: "",
config: {
fields: [
{ alias: "embedded", label: "Embbeded", view: "textstring", value: "" },
{ alias: "embedded2", label: "Embbeded 2", view: "contentpicker", value: "" },
{ alias: "embedded3", label: "Embbeded 3", view: "textarea", value: "" },
{ alias: "embedded4", label: "Embbeded 4", view: "datepicker", value: "" }
]
}
}
]
}
]
}
]
};
return node;
},
getMockEntity : function(id){
return {name: "hello", id: id, icon: "icon-file"};
},
@@ -0,0 +1,56 @@
angular.module('umbraco.mocks').
factory('variantContentMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) {
'use strict';
function returnEmptyVariantNode(status, data, headers) {
if (!mocksUtils.checkAuth()) {
return [401, null, null];
}
var response = returnVariantNodebyId(200, "", null);
var node = response[1];
var parentId = mocksUtils.getParameterByName(data, "parentId") || 1234;
node.name = "";
node.id = 0;
node.parentId = parentId;
node.tabs.forEach(function(tab) {
tab.properties.forEach(function(property) {
property.value = "";
});
});
return response;
}
function returnVariantNodebyId(status, data, headers) {
if (!mocksUtils.checkAuth()) {
return [401, null, null];
}
var id = mocksUtils.getParameterByName(data, "id") || "1234";
id = parseInt(id, 10);
var node = mocksUtils.getMockVariantContent(id);
return [200, node, null];
}
return {
register: function () {
$httpBackend
.whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetById?'))
.respond(returnVariantNodebyId);
$httpBackend
.whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetEmpty'))
.respond(returnEmptyVariantNode);
}
};
}]);
@@ -388,7 +388,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"GetBlueprintById",
[{ id: id }])),
{ id: id })),
'Failed to retrieve data for content id ' + id)
.then(function (result) {
return $q.when(umbDataFormatter.formatContentGetData(result));
@@ -401,7 +401,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"GetNotificationOptions",
[{ contentId: id }])),
{ contentId: id })),
'Failed to retrieve data for content id ' + id);
},
@@ -502,12 +502,57 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"GetEmpty",
[{ contentTypeAlias: alias }, { parentId: parentId }])),
{ contentTypeAlias: alias, parentId: parentId })),
'Failed to retrieve data for empty content item type ' + alias)
.then(function (result) {
return $q.when(umbDataFormatter.formatContentGetData(result));
});
},
/**
* @ngdoc method
* @name umbraco.resources.contentResource#getScaffoldByKey
* @methodOf umbraco.resources.contentResource
*
* @description
* Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias.
*
* - Parent Id must be provided so umbraco knows where to store the content
* - Content Type Id must be provided so umbraco knows which properties to put on the content scaffold
*
* The scaffold is used to build editors for content that has not yet been populated with data.
*
* ##usage
* <pre>
* contentResource.getScaffoldByKey(1234, '...')
* .then(function(scaffold) {
* var myDoc = scaffold;
* myDoc.name = "My new document";
*
* contentResource.publish(myDoc, true)
* .then(function(content){
* alert("Retrieved, updated and published again");
* });
* });
* </pre>
*
* @param {Int} parentId id of content item to return
* @param {String} contentTypeGuid contenttype guid to base the scaffold on
* @returns {Promise} resourcePromise object containing the content scaffold.
*
*/
getScaffoldByKey: function (parentId, contentTypeKey) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"GetEmptyByKey",
{ contentTypeKey: contentTypeKey, parentId: parentId })),
'Failed to retrieve data for empty content item id ' + contentTypeKey)
.then(function (result) {
return $q.when(umbDataFormatter.formatContentGetData(result));
});
},
getBlueprintScaffold: function (parentId, blueprintId) {
@@ -516,7 +561,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"GetEmpty",
[{ blueprintId: blueprintId }, { parentId: parentId }])),
{ blueprintId: blueprintId, parentId: parentId })),
'Failed to retrieve blueprint for id ' + blueprintId)
.then(function (result) {
return $q.when(umbDataFormatter.formatContentGetData(result));
@@ -0,0 +1,24 @@
/**
* @ngdoc service
* @name umbraco.resources.elementTypeResource
* @description Loads in data for element types
**/
function elementTypeResource($q, $http, umbRequestHelper) {
return {
getAll: function () {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"elementTypeApiBaseUrl",
"GetAll")),
"Failed to retrieve element types");
}
};
}
angular.module("umbraco.resources").factory("elementTypeResource", elementTypeResource);
@@ -0,0 +1,59 @@
/**
* @ngdoc service
* @name umbraco.services.blockEditorService
*
* @description
* <b>Added in Umbraco 8.7</b>. Service for dealing with Block Editors.
*
* Block Editor Service provides the basic features for a block editor.
* The main feature is the ability to create a Model Object which takes care of your data for your Block Editor.
*
*
* ##Samples
*
* ####Instantiate a Model Object for your property editor:
*
* <pre>
* modelObject = blockEditorService.createModelObject(vm.model.value, vm.model.editor, vm.model.config.blocks, $scope);
* modelObject.load().then(onLoaded);
* </pre>
*
*
* See {@link umbraco.services.blockEditorModelObject BlockEditorModelObject} for more samples.
*
*/
(function () {
'use strict';
function blockEditorService(blockEditorModelObject) {
/**
* @ngdocs function
* @name createModelObject
* @methodOf umbraco.services.blockEditorService
*
* @description
* Create a new Block Editor Model Object.
* See {@link umbraco.services.blockEditorModelObject blockEditorModelObject}
*
* @see umbraco.services.blockEditorModelObject
* @param {object} propertyModelValue data object of the property editor, usually model.value.
* @param {string} propertyEditorAlias alias of the property.
* @param {object} blockConfigurations block configurations.
* @param {angular-scope} scopeOfExistance A local angularJS scope that exists as long as the data exists.
* @param {angular-scope} propertyEditorScope A local angularJS scope that represents the property editors scope.
* @return {blockEditorModelObject} A instance of the BlockEditorModelObject class.
*/
function createModelObject(propertyModelValue, propertyEditorAlias, blockConfigurations, scopeOfExistance, propertyEditorScope) {
return new blockEditorModelObject(propertyModelValue, propertyEditorAlias, blockConfigurations, scopeOfExistance, propertyEditorScope);
}
return {
createModelObject: createModelObject
}
}
angular.module('umbraco.services').service('blockEditorService', blockEditorService);
})();
@@ -0,0 +1,671 @@
/**
* @ngdoc service
* @name umbraco.services.blockEditorModelObject
*
* @description
* <b>Added in Umbraco 8.7</b>. Model Object for dealing with data of Block Editors.
*
* Block Editor Model Object provides the basic features for editing data of a block editor.<br/>
* Use the Block Editor Service to instantiate the Model Object.<br/>
* See {@link umbraco.services.blockEditorService blockEditorService}
*
*/
(function () {
'use strict';
function blockEditorModelObjectFactory($interpolate, udiService, contentResource) {
/**
* Simple mapping from property model content entry to editing model,
* needs to stay simple to avoid deep watching.
*/
function mapToElementModel(elementModel, dataModel) {
if (!elementModel || !elementModel.variants || !elementModel.variants.length) { return; }
var variant = elementModel.variants[0];
for (var t = 0; t < variant.tabs.length; t++) {
var tab = variant.tabs[t];
for (var p = 0; p < tab.properties.length; p++) {
var prop = tab.properties[p];
if (dataModel[prop.alias]) {
prop.value = dataModel[prop.alias];
}
}
}
}
/**
* Simple mapping from elementModel to property model content entry,
* needs to stay simple to avoid deep watching.
*/
function mapToPropertyModel(elementModel, dataModel) {
if (!elementModel || !elementModel.variants || !elementModel.variants.length) { return; }
var variant = elementModel.variants[0];
for (var t = 0; t < variant.tabs.length; t++) {
var tab = variant.tabs[t];
for (var p = 0; p < tab.properties.length; p++) {
var prop = tab.properties[p];
if (prop.value) {
dataModel[prop.alias] = prop.value;
}
}
}
}
/**
* Map property values from an ElementModel to another ElementModel.
* Used to tricker watchers for synchronization.
* @param {Object} fromModel ElementModel to recive property values from.
* @param {Object} toModel ElementModel to recive property values from.
*/
function mapElementValues(fromModel, toModel) {
if (!fromModel || !fromModel.variants) {
toModel.variants = null;
return;
}
if (!fromModel.variants.length) {
toModel.variants = [];
return;
}
var fromVariant = fromModel.variants[0];
if (!fromVariant) {
toModel.variants = [null];
return;
}
var toVariant = toModel.variants[0];
for (var t = 0; t < fromVariant.tabs.length; t++) {
var fromTab = fromVariant.tabs[t];
var toTab = toVariant.tabs[t];
for (var p = 0; p < fromTab.properties.length; p++) {
var fromProp = fromTab.properties[p];
var toProp = toTab.properties[p];
toProp.value = fromProp.value;
}
}
}
/**
* Generate label for Block, uses either the labelInterpolator or falls back to the contentTypeName.
* @param {Object} blockObject BlockObject to recive data values from.
*/
function getBlockLabel(blockObject) {
if(blockObject.labelInterpolator !== undefined) {
// We are just using the data model, since its a key/value object that is live synced. (if we need to add additional vars, we could make a shallow copy and apply those.)
return blockObject.labelInterpolator(blockObject.data);
}
return blockObject.content.contentTypeName;
}
/**
* Used to add watchers on all properties in a content or settings model
*/
function addWatchers(blockObject, isolatedScope, forSettings) {
var model = forSettings ? blockObject.settings : blockObject.content;
if (!model || !model.variants || !model.variants.length) { return; }
// Start watching each property value.
var variant = model.variants[0];
var field = forSettings ? "settings" : "content";
var watcherCreator = forSettings ? createSettingsModelPropWatcher : createContentModelPropWatcher;
for (var t = 0; t < variant.tabs.length; t++) {
var tab = variant.tabs[t];
for (var p = 0; p < tab.properties.length; p++) {
var prop = tab.properties[p];
// Watch value of property since this is the only value we want to keep synced.
// Do notice that it is not performing a deep watch, meaning that we are only watching primatives and changes directly to the object of property-value.
// But we like to sync non-primative values as well! Yes, and this does happen, just not through this code, but through the nature of JavaScript.
// Non-primative values act as references to the same data and are therefor synced.
blockObject.__watchers.push(isolatedScope.$watch("blockObjects._" + blockObject.key + "." + field + ".variants[0].tabs[" + t + "].properties[" + p + "].value", watcherCreator(blockObject, prop)));
// We also like to watch our data model to be able to capture changes coming from other places.
if (forSettings === true) {
blockObject.__watchers.push(isolatedScope.$watch("blockObjects._" + blockObject.key + "." + "layout.settings" + "." + prop.alias, createLayoutSettingsModelWatcher(blockObject, prop)));
} else {
blockObject.__watchers.push(isolatedScope.$watch("blockObjects._" + blockObject.key + "." + "data" + "." + prop.alias, createDataModelWatcher(blockObject, prop)));
}
}
}
if (blockObject.__watchers.length === 0) {
// If no watcher where created, it means we have no properties to watch. This means that nothing will activate our generate the label, since its only triggered by watchers.
blockObject.updateLabel();
}
}
/**
* Used to create a prop watcher for the data in the property editor data model.
*/
function createDataModelWatcher(blockObject, prop) {
return function() {
// sync data:
prop.value = blockObject.data[prop.alias];
blockObject.updateLabel();
}
}
/**
* Used to create a prop watcher for the settings in the property editor data model.
*/
function createLayoutSettingsModelWatcher(blockObject, prop) {
return function() {
// sync data:
prop.value = blockObject.layout.settings[prop.alias];
}
}
/**
* Used to create a scoped watcher for a content property on a blockObject.
*/
function createContentModelPropWatcher(blockObject, prop) {
return function() {
// sync data:
blockObject.data[prop.alias] = prop.value;
blockObject.updateLabel();
}
}
/**
* Used to create a scoped watcher for a settings property on a blockObject.
*/
function createSettingsModelPropWatcher(blockObject, prop) {
return function() {
// sync data:
blockObject.layout.settings[prop.alias] = prop.value;
}
}
/**
* Used to highlight unsupported properties for the user, changes unsupported properties into a unsupported-property.
*/
var notSupportedProperties = [
"Umbraco.Tags",
"Umbraco.UploadField",
"Umbraco.ImageCropper"
];
function replaceUnsupportedProperties(scaffold) {
scaffold.variants.forEach((variant) => {
variant.tabs.forEach((tab) => {
tab.properties.forEach((property) => {
if (notSupportedProperties.indexOf(property.editor) !== -1) {
property.view = "notsupported";
}
});
});
});
return scaffold;
}
/**
* @ngdoc method
* @name constructor
* @methodOf umbraco.services.blockEditorModelObject
* @description Constructor of the model object used to handle Block Editor data.
* @param {object} propertyModelValue data object of the property editor, usually model.value.
* @param {string} propertyEditorAlias alias of the property.
* @param {object} blockConfigurations block configurations.
* @param {angular-scope} scopeOfExistance A local angularJS scope that exists as long as the data exists.
* @param {angular-scope} propertyEditorScope A local angularJS scope that represents the property editors scope.
* @returns {BlockEditorModelObject} A instance of BlockEditorModelObject.
*/
function BlockEditorModelObject(propertyModelValue, propertyEditorAlias, blockConfigurations, scopeOfExistance, propertyEditorScope) {
if (!propertyModelValue) {
throw new Error("propertyModelValue cannot be undefined, to ensure we keep the binding to the angular model we need minimum an empty object.");
}
this.__watchers = [];
// ensure basic part of data-structure is in place:
this.value = propertyModelValue;
this.value.layout = this.value.layout || {};
this.value.data = this.value.data || [];
this.propertyEditorAlias = propertyEditorAlias;
this.blockConfigurations = blockConfigurations;
this.scaffolds = [];
this.isolatedScope = scopeOfExistance.$new(true);
this.isolatedScope.blockObjects = {};
this.__watchers.push(this.isolatedScope.$on("$destroy", this.destroy.bind(this)));
this.__watchers.push(propertyEditorScope.$on("postFormSubmitting", this.sync.bind(this)));
};
BlockEditorModelObject.prototype = {
/**
* @ngdoc method
* @name getBlockConfiguration
* @methodOf umbraco.services.blockEditorModelObject
* @description Get block configuration object for a given contentTypeKey.
* @param {string} key contentTypeKey to recive the configuration model for.
* @returns {Object | null} Configuration model for the that specific block. Or ´null´ if the contentTypeKey isnt available in the current block configurations.
*/
getBlockConfiguration: function(key) {
return this.blockConfigurations.find(bc => bc.contentTypeKey === key) || null;
},
/**
* @ngdoc method
* @name load
* @methodOf umbraco.services.blockEditorModelObject
* @description Load the scaffolding models for the given configuration, these are needed to provide usefull models for each block.
* @param {Object} blockObject BlockObject to recive data values from.
* @returns {Promise} A Promise object which resolves when all scaffold models are loaded.
*/
load: function() {
var tasks = [];
var scaffoldKeys = [];
this.blockConfigurations.forEach(blockConfiguration => {
scaffoldKeys.push(blockConfiguration.contentTypeKey);
if (blockConfiguration.settingsElementTypeKey != null) {
scaffoldKeys.push(blockConfiguration.settingsElementTypeKey);
}
});
// removing dublicates.
scaffoldKeys = scaffoldKeys.filter((value, index, self) => self.indexOf(value) === index);
scaffoldKeys.forEach((contentTypeKey => {
tasks.push(contentResource.getScaffoldByKey(-20, contentTypeKey).then(scaffold => {
this.scaffolds.push(replaceUnsupportedProperties(scaffold));
}));
}));
return Promise.all(tasks);
},
/**
* @ngdoc method
* @name getAvailableAliasesForBlockContent
* @methodOf umbraco.services.blockEditorModelObject
* @description Retrive a list of aliases that are available for content of blocks in this property editor, does not contain aliases of block settings.
* @return {Array} array of strings representing alias.
*/
getAvailableAliasesForBlockContent: function() {
return this.blockConfigurations.map(blockConfiguration => this.getScaffoldFromKey(blockConfiguration.contentTypeKey).contentTypeAlias);
},
/**
* @ngdoc method
* @name getAvailableBlocksForBlockPicker
* @methodOf umbraco.services.blockEditorModelObject
* @description Retrive a list of available blocks, the list containing object with the confirugation model(blockConfigModel) and the element type model(elementTypeModel).
* The purpose of this data is to provide it for the Block Picker.
* @return {Array} array of objects representing available blocks, each object containing properties blockConfigModel and elementTypeModel.
*/
getAvailableBlocksForBlockPicker: function() {
var blocks = [];
this.blockConfigurations.forEach(blockConfiguration => {
var scaffold = this.getScaffoldFromKey(blockConfiguration.contentTypeKey);
if(scaffold) {
blocks.push({
blockConfigModel: blockConfiguration,
elementTypeModel: scaffold.documentType
});
}
});
return blocks;
},
/**
* @ngdoc method
* @name getScaffoldFromKey
* @methodOf umbraco.services.blockEditorModelObject
* @description Get scaffold model for a given contentTypeKey.
* @param {string} key contentTypeKey to recive the scaffold model for.
* @returns {Object | null} Scaffold model for the that content type. Or null if the scaffolding model dosnt exist in this context.
*/
getScaffoldFromKey: function(contentTypeKey) {
return this.scaffolds.find(o => o.contentTypeKey === contentTypeKey);
},
/**
* @ngdoc method
* @name getScaffoldFromAlias
* @methodOf umbraco.services.blockEditorModelObject
* @description Get scaffold model for a given contentTypeAlias, used by clipboardService.
* @param {string} alias contentTypeAlias to recive the scaffold model for.
* @returns {Object | null} Scaffold model for the that content type. Or null if the scaffolding model dosnt exist in this context.
*/
getScaffoldFromAlias: function(contentTypeAlias) {
return this.scaffolds.find(o => o.contentTypeAlias === contentTypeAlias);
},
/**
* @ngdoc method
* @name getBlockObject
* @methodOf umbraco.services.blockEditorModelObject
* @description Retrieve a Block Object for the given layout entry.
* The Block Object offers the nesecary data to display and edit a block.
* The Block Object setups live syncronization of content and settings models back to the data of your Property Editor model.
* The returned object, named ´BlockObject´, contains several usefull models to make editing of this block happen.
* The ´BlockObject´ contains the following properties:
* - key {string}: runtime generated key, usefull for tracking of this object
* - content {Object}: Content model, the content data in a ElementType model.
* - settings {Object}: Settings model, the settings data in a ElementType model.
* - config {Object}: A local deep copy of the block configuration model.
* - label {string}: The label for this block.
* - updateLabel {Method}: Method to trigger an update of the label for this block.
* - data {Object}: A reference to the content data object from your property editor model.
* - settingsData {Object}: A reference to the settings data object from your property editor model.
* - layout {Object}: A refernce to the layout entry from your property editor model.
* @param {Object} layoutEntry the layout entry object to build the block model from.
* @return {Object | null} The BlockObject for the given layout entry. Or null if data or configuration wasnt found for this block.
*/
getBlockObject: function(layoutEntry) {
var udi = layoutEntry.udi;
var dataModel = this._getDataByUdi(udi);
if (dataModel === null) {
console.error("Couldnt find content model of " + udi)
return null;
}
var blockConfiguration = this.getBlockConfiguration(dataModel.contentTypeKey);
var contentScaffold;
if (blockConfiguration === null) {
console.error("The block entry of "+udi+" is not begin initialized cause its contentTypeKey is not allowed for this PropertyEditor");
} else {
var contentScaffold = this.getScaffoldFromKey(blockConfiguration.contentTypeKey);
if(contentScaffold === null) {
console.error("The block entry of "+udi+" is not begin initialized cause its Element Type was not loaded.");
}
}
if (blockConfiguration === null || contentScaffold === null) {
blockConfiguration = {
label: "Unsupported Block",
unsupported: true
};
contentScaffold = {};
}
var blockObject = {};
// Set an angularJS cloneNode method, to avoid this object begin cloned.
blockObject.cloneNode = function() {
return null;// angularJS accept this as a cloned value as long as the
}
blockObject.key = String.CreateGuid().replace(/-/g, "");
blockObject.config = Utilities.copy(blockConfiguration);
if (blockObject.config.label && blockObject.config.label !== "") {
blockObject.labelInterpolator = $interpolate(blockObject.config.label);
}
blockObject.__scope = this.isolatedScope;
blockObject.updateLabel = _.debounce(function () {this.__scope.$evalAsync(function() {
this.label = getBlockLabel(this);
}.bind(this))}.bind(blockObject), 10);
// make basics from scaffold
blockObject.content = Utilities.copy(contentScaffold);
blockObject.content.udi = udi;
mapToElementModel(blockObject.content, dataModel);
blockObject.data = dataModel;
blockObject.layout = layoutEntry;
blockObject.__watchers = [];
if (blockConfiguration.settingsElementTypeKey) {
var settingsScaffold = this.getScaffoldFromKey(blockConfiguration.settingsElementTypeKey);
if (settingsScaffold !== null) {
layoutEntry.settings = layoutEntry.settings || {};
blockObject.settingsData = layoutEntry.settings;
// make basics from scaffold
blockObject.settings = Utilities.copy(settingsScaffold);
layoutEntry.settings = layoutEntry.settings || {};
if (!layoutEntry.settings.key) { layoutEntry.settings.key = String.CreateGuid(); }
if (!layoutEntry.settings.contentTypeKey) { layoutEntry.settings.contentTypeKey = blockConfiguration.settingsElementTypeKey; }
mapToElementModel(blockObject.settings, layoutEntry.settings);
}
}
blockObject.retriveValuesFrom = function(content, settings) {
if (this.content !== null) {
mapElementValues(content, this.content);
}
if (this.config.settingsElementTypeKey !== null) {
mapElementValues(settings, this.settings);
}
}
blockObject.sync = function() {
if (this.content !== null) {
mapToPropertyModel(this.content, this.data);
}
if (this.config.settingsElementTypeKey !== null) {
mapToPropertyModel(this.settings, this.layout.settings);
}
}
// first time instant update of label.
blockObject.label = getBlockLabel(blockObject);
// Add blockObject to our isolated scope to enable watching its values:
this.isolatedScope.blockObjects["_" + blockObject.key] = blockObject;
addWatchers(blockObject, this.isolatedScope);
addWatchers(blockObject, this.isolatedScope, true);
blockObject.destroy = function() {
// remove property value watchers:
this.__watchers.forEach(w => { w(); });
delete this.__watchers;
// help carbage collector:
delete this.layout;
delete this.data;
// remove model from isolatedScope.
delete this.__scope.blockObjects["_" + this.key];
delete this.__scope;
// removes this method, making it unposible to destroy again.
delete this.destroy;
}
return blockObject;
},
/**
* @ngdoc method
* @name removeDataAndDestroyModel
* @methodOf umbraco.services.blockEditorModelObject
* @description Removes the data and destroys the Block Model.
* Notice this method does not remove the block from your layout, this will need to be handlede by the Property Editor since this services donst know about your layout structure.
* @param {Object} blockObject The BlockObject to be removed and destroyed.
*/
removeDataAndDestroyModel: function (blockObject) {
this.destroyBlockObject(blockObject);
this.removeDataByUdi(blockObject.content.udi);
},
/**
* @ngdoc method
* @name destroyBlockObject
* @methodOf umbraco.services.blockEditorModelObject
* @description Destroys the Block Model, but all data is kept.
* @param {Object} blockObject The BlockObject to be destroyed.
*/
destroyBlockObject: function(blockObject) {
blockObject.destroy();
},
/**
* @ngdoc method
* @name getLayout
* @methodOf umbraco.services.blockEditorModelObject
* @description Retrieve the layout object from this specific property editor model.
* @param {object} defaultStructure if no data exist the layout of your poerty editor will be set to this object.
* @return {Object} Layout object, structure depends on the model of your property editor.
*/
getLayout: function(defaultStructure) {
if (!this.value.layout[this.propertyEditorAlias]) {
this.value.layout[this.propertyEditorAlias] = defaultStructure;
}
return this.value.layout[this.propertyEditorAlias];
},
/**
* @ngdoc method
* @name create
* @methodOf umbraco.services.blockEditorModelObject
* @description Create a empty layout entry, notice the layout entry is not added to the property editors model layout object, since the layout sturcture depends on the property editor.
* @param {string} contentTypeKey the contentTypeKey of the block you wish to create, if contentTypeKey is not avaiable in the block configuration then ´null´ will be returned.
* @return {Object | null} Layout entry object, to be inserted at a decired location in the layout object. Or null if contentTypeKey is unavaiaible.
*/
create: function(contentTypeKey) {
var blockConfiguration = this.getBlockConfiguration(contentTypeKey);
if(blockConfiguration === null) {
return null;
}
var entry = {
udi: this._createDataEntry(contentTypeKey)
}
if (blockConfiguration.settingsElementTypeKey != null) {
entry.settings = { key: String.CreateGuid(), contentTypeKey: blockConfiguration.settingsElementTypeKey };
}
return entry;
},
/**
* @ngdoc method
* @name createFromElementType
* @methodOf umbraco.services.blockEditorModelObject
* @description Insert data from ElementType Model
* @return {Object | null} Layout entry object, to be inserted at a decired location in the layout object. Or ´null´ if the given ElementType isnt supported by the block configuration.
*/
createFromElementType: function(elementTypeDataModel) {
elementTypeDataModel = Utilities.copy(elementTypeDataModel);
var contentTypeKey = elementTypeDataModel.contentTypeKey;
var layoutEntry = this.create(contentTypeKey);
if(layoutEntry === null) {
return null;
}
var dataModel = this._getDataByUdi(layoutEntry.udi);
if(dataModel === null) {
return null;
}
mapToPropertyModel(elementTypeDataModel, dataModel);
return layoutEntry;
},
/**
* @ngdoc method
* @name sync
* @methodOf umbraco.services.blockEditorModelObject
* @description Force immidiate update of the blockobject models to the property model.
*/
sync: function() {
for (const key in this.isolatedScope.blockObjects) {
this.isolatedScope.blockObjects[key].sync();
}
},
// private
_createDataEntry: function(elementTypeKey) {
var content = {
contentTypeKey: elementTypeKey,
udi: udiService.create("element")
};
this.value.data.push(content);
return content.udi;
},
// private
_getDataByUdi: function(udi) {
return this.value.data.find(entry => entry.udi === udi) || null;
},
/**
* @ngdoc method
* @name removeDataByUdi
* @methodOf umbraco.services.blockEditorModelObject
* @description Removes the data of a given UDI.
* Notice this method does not remove the block from your layout, this will need to be handlede by the Property Editor since this services donst know about your layout structure.
* @param {string} udi The UDI of the data to be removed.
*/
removeDataByUdi: function(udi) {
const index = this.value.data.findIndex(o => o.udi === udi);
if (index !== -1) {
this.value.data.splice(index, 1);
}
},
/**
* @ngdoc method
* @name destroy
* @methodOf umbraco.services.blockEditorModelObject
* @description Notice you should not need to destroy the BlockEditorModelObject since it will automaticly be destroyed when the scope of existance gets destroyed.
*/
destroy: function() {
this.__watchers.forEach(w => { w(); });
for (const key in this.isolatedScope.blockObjects) {
this.destroyBlockObject(this.isolatedScope.blockObjects[key]);
}
delete this.__watchers;
delete this.value;
delete this.propertyEditorAlias;
delete this.blockConfigurations;
delete this.scaffolds;
this.isolatedScope.$destroy();
delete this.isolatedScope;
delete this.destroy;
}
}
return BlockEditorModelObject;
}
angular.module('umbraco.services').service('blockEditorModelObject', blockEditorModelObjectFactory);
})();
@@ -44,6 +44,9 @@ function formHelper(angularHelper, serverValidationManager, notificationsService
//the first thing any form must do is broadcast the formSubmitting event
args.scope.$broadcast("formSubmitting", { scope: args.scope, action: args.action });
// Some property editors need to performe an action after all property editors have reacted to the formSubmitting.
args.scope.$broadcast("postFormSubmitting", { scope: args.scope, action: args.action });
//then check if the form is valid
if (!args.skipValidation) {
if (currentForm.$invalid) {
@@ -76,7 +76,12 @@
case "delete":
if (!overlay.confirmMessageStyle) overlay.confirmMessageStyle = "danger";
if (!overlay.submitButtonStyle) overlay.submitButtonStyle = "danger";
if (!overlay.submitButtonLabelKey) overlay.submitButtonLabelKey = "contentTypeEditor_yesDelete";
if (!overlay.submitButtonLabelKey) overlay.submitButtonLabelKey = "actions_delete";
break;
case "remove":
if (!overlay.submitButtonStyle) overlay.submitButtonStyle = "primary";
if (!overlay.submitButtonLabelKey) overlay.submitButtonLabelKey = "actions_remove";
break;
default:
@@ -93,12 +98,18 @@
confirm(overlay);
}
function confirmRemove(overlay) {
overlay.confirmType = "remove";
confirm(overlay);
}
var service = {
open: open,
close: close,
ysod: ysod,
confirm: confirm,
confirmDelete: confirmDelete
confirmDelete: confirmDelete,
confirmRemove: confirmRemove
};
return service;
@@ -0,0 +1,32 @@
(function () {
"use strict";
/**
* @ngdoc service
* @name umbraco.services.udiService
* @description A service for UDIs
**/
function udiService() {
return {
/**
* @ngdoc method
* @name umbraco.services.udiService#create
* @methodOf umbraco.services.udiService
* @function
*
* @description
* Generates a Udi string.
*
* @param {string} entityType The entityType as a string.
* @returns {string} The generated UDI
*/
create: function(entityType) {
return "umb://" + entityType + "/" + (String.CreateGuid().replace(/-/g, ""));
}
}
}
angular.module("umbraco.services").factory("udiService", udiService);
})();
@@ -199,6 +199,18 @@
@import "components/umbemailmarketing.less";
// Property Editors
@import "../views/components/blockcard/umb-block-card-grid.less";
@import "../views/components/blockcard/umb-block-card.less";
@import "../views/propertyeditors/blocklist/umb-block-list-property-editor.less";
@import "../views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.less";
@import "../views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.less";
@import "../views/propertyeditors/notsupported/notsupported.less";
@import "../views/propertyeditors/blocklist/blocklistentryeditors/unsupportedblock/unsupportedblock.editor.less";
@import "../views/propertyeditors/blocklist/blocklistentryeditors/labelblock/labelblock.editor.less";
@import "../views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.less";
// Utilities
@import "utilities/layout/_display.less";
@import "utilities/theme/_opacity.less";
@@ -221,6 +233,7 @@
// Used for prevalue editors
@import "components/prevalues/multivalues.less";
@import "../views/prevalueeditors/numberrange.less";
// Dashboards
@import "dashboards/getstarted.less";
@@ -111,7 +111,7 @@
}
.umb-card-grid li {
overflow: hidden;
font-size: 12px;
text-align: center;
box-sizing: border-box;
@@ -163,11 +163,23 @@
}
.umb-card-grid .umb-card-grid-item:hover,
.umb-card-grid .umb-card-grid-item:focus {
.umb-card-grid .umb-card-grid-item:hover {
background-color: @ui-option-hover;
color: @ui-option-type-hover;
}
.umb-card-grid .umb-card-grid-item:focus {
color: @ui-option-type-hover;
}
.umb-card-grid .umb-card-grid-item.--creator {
> span {
border: 2px dashed @ui-action-discreet-border;
border-radius: @baseBorderRadius;
&:hover {
border-color: @ui-action-discreet-border-hover;
}
}
}
.umb-card-grid .umb-card-grid-item-slot {
position: relative;
@@ -8,11 +8,11 @@
.umb-group-panel__header {
padding: 12px 20px;
font-weight: bold;
font-size: 16px;
font-size: 14px;
display: flex;
align-items: center;
justify-content: space-between;
color: @grayDark;
color: @grayDarker;
border-bottom: 1px solid @gray-9;
}
@@ -130,8 +130,12 @@
box-sizing: border-box;
border-radius: @baseBorderRadius;
&.umb-overlay--small {
width: 420px;
}
&.umb-overlay--medium {
width: 480px;
width: 520px;
}
}
@@ -20,12 +20,18 @@
justify-content: center;
height: calc(~'@{editorHeaderHeight}'- ~'1px'); // need to offset the 1px border-bottom on .umb-editor-header - avoids overflowing top of the container
color: @ui-active-type;
user-select: none;
&:hover {
color: @ui-active-type-hover !important;
text-decoration: none;
}
&:disabled {
pointer-events: none;
color: @gray-6;
}
&::before {
content: "";
position: absolute;
@@ -320,6 +320,11 @@
box-shadow:none !important;
}
.umb-sortable-thumbnails-container {
display: flex;
flex-wrap: wrap;
background-color: @white;
}
.umb-sortable-thumbnails {
list-style-type: none;
@@ -199,11 +199,13 @@
@ui-btn-positive-type: @white;
@ui-btn-negative: @red;
@ui-btn-negative-type: @white;
@ui-btn-negative-hover: @red;
@ui-icon: @blueNight;
@ui-icon-hover: @blueMid;
@ui-drop-area-color: @blueMidLight;
// Scaffolding
@@ -254,6 +256,7 @@
// Disabled this to keep consistency throughout the backoffice UI. Untill a better solution is thought up, this will do.
@baseBorderRadius: 3px; // 2px;
@doubleBorderRadius: 6px;
@borderRadiusLarge: 3px; // 6px;
@borderRadiusSmall: 3px; // 3px;
@@ -271,8 +274,8 @@
@btnBackgroundHighlight: @gray-10;
@btnBorder: @gray-9;
@btnPrimaryBackground: @ui-btn-positive;
@btnPrimaryBackgroundHighlight: @ui-btn-positive-hover;
@btnPrimaryBackground: @ui-btn;
@btnPrimaryBackgroundHighlight: @ui-btn-hover;
@btnInfoType: @ui-btn-type;// updated 2019
@btnInfoTypeHover: @ui-btn-type;// updated 2019
@@ -31,13 +31,17 @@ function MainController($scope, $location, appState, treeService, notificationsS
// For more information about this approach, see https://hackernoon.com/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2
function handleFirstTab(evt) {
if (evt.keyCode === 9) {
$scope.tabbingActive = true;
$scope.$digest();
window.removeEventListener('keydown', handleFirstTab);
window.addEventListener('mousedown', disableTabbingActive);
enableTabbingActive();
}
}
function enableTabbingActive() {
$scope.tabbingActive = true;
$scope.$digest();
window.addEventListener('mousedown', disableTabbingActive);
window.removeEventListener("keydown", handleFirstTab);
}
function disableTabbingActive(evt) {
$scope.tabbingActive = false;
$scope.$digest();
@@ -47,6 +51,12 @@ function MainController($scope, $location, appState, treeService, notificationsS
window.addEventListener("keydown", handleFirstTab);
$scope.$on("showFocusOutline", function() {
$scope.tabbingActive = true;
window.addEventListener('mousedown', disableTabbingActive);
window.removeEventListener("keydown", handleFirstTab);
});
$scope.removeNotification = function (index) {
notificationsService.remove(index);
@@ -0,0 +1 @@
<umb-element-editor-content model="model.content"></umb-element-editor-content>
@@ -0,0 +1,75 @@
angular.module("umbraco")
.controller("Umbraco.Editors.BlockEditorController",
function ($scope, localizationService, formHelper) {
var vm = this;
vm.model = $scope.model;
localizationService.localizeMany([
$scope.model.liveEditing ? "prompt_discardChanges" : "general_close",
$scope.model.liveEditing ? "buttons_confirmActionConfirm" : "buttons_submitChanges"
]).then(function (data) {
vm.closeLabel = data[0];
vm.submitLabel = data[1];
});
vm.tabs = [];
if ($scope.model.content && $scope.model.content.variants) {
var apps = $scope.model.content.apps;
vm.tabs = apps;
// replace view of content app.
var contentApp = apps.find(entry => entry.alias === "umbContent");
if(contentApp) {
contentApp.view = "views/common/infiniteeditors/blockeditor/blockeditor.content.html";
if($scope.model.hideContent) {
apps.splice(apps.indexOf(contentApp), 1);
} else if ($scope.model.openSettings !== true) {
contentApp.active = true;
}
}
// remove info app:
var infoAppIndex = apps.findIndex(entry => entry.alias === "umbInfo");
apps.splice(infoAppIndex, 1);
}
if ($scope.model.settings && $scope.model.settings.variants) {
localizationService.localize("blockEditor_tabBlockSettings").then(
function (settingsName) {
var settingsTab = {
"name": settingsName,
"alias": "settings",
"icon": "icon-settings",
"view": "views/common/infiniteeditors/blockeditor/blockeditor.settings.html"
};
vm.tabs.push(settingsTab);
if ($scope.model.openSettings) {
settingsTab.active = true;
}
}
);
}
vm.submitAndClose = function () {
if ($scope.model && $scope.model.submit) {
if (formHelper.submitForm({ scope: $scope })) {
$scope.model.submit($scope.model);
}
}
}
vm.close = function() {
if ($scope.model && $scope.model.close) {
// TODO: check if content/settings has changed and ask user if they are sure.
$scope.model.close($scope.model);
}
}
}
);
@@ -0,0 +1,56 @@
<div class="umb-block-editor" ng-controller="Umbraco.Editors.BlockEditorController as vm">
<ng-form name="blockForm" val-form-manager>
<umb-editor-view umb-tabs ng-if="!page.loading">
<umb-editor-header
name="vm.model.title"
name-locked="true"
navigation="vm.tabs"
hide-alias="true"
hide-icon="true"
hide-description="true">
</umb-editor-header>
<div class="umb-editor-container umb-panel-body umb-scrollable row-fluid">
<!-- Missing Deleted Message Bar (Displayed when viewing node in recycle bin), but Blocks dosnt go into trash-bin jet...
<div ng-show="vm.content.trashed" class="umb-editor--trashed-message">
<i class="icon icon-trash"></i> <localize key="content_nodeIsInTrash">This item is in the Recycle Bin</localize>
</div>
-->
<div class="umb-pane">
<umb-editor-sub-views sub-views="vm.tabs" model="vm.model">
</umb-editor-sub-views>
</div>
</div>
<umb-editor-footer>
<!-- Missing breadcrumbs -->
<umb-editor-footer-content-right>
<umb-button
action="vm.close()"
button-style="link"
label="{{vm.closeLabel}}"
type="button">
</umb-button>
<umb-button
action="vm.submitAndClose()"
button-style="primary"
state="submitButtonState"
label="{{vm.submitLabel}}"
type="button">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
</umb-editor-view>
</ng-form>
</div>
@@ -0,0 +1 @@
<umb-element-editor-content model="model.settings"></umb-element-editor-content>
@@ -0,0 +1,60 @@
angular.module("umbraco")
.controller("Umbraco.Editors.BlockPickerController",
function ($scope, localizationService) {
var vm = this;
vm.navigation = [];
localizationService.localizeMany(["blockEditor_tabCreateEmpty", "blockEditor_tabClipboard"]).then(
function (data) {
vm.navigation = [{
"alias": "empty",
"name": data[0],
"icon": "icon-add",
"active": true,
"view": ""
},
{
"alias": "clipboard",
"name": data[1],
"icon": "icon-paste-in",
"view": "",
"disabled": vm.model.clipboardItems.length === 0
}];
vm.activeTab = vm.navigation[0];
}
);
vm.onNavigationChanged = function(tab) {
vm.activeTab.active = false;
vm.activeTab = tab;
vm.activeTab.active = true;
}
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();
}
vm.model = $scope.model;
vm.selectItem = function(item, $event) {
vm.model.selectedItem = item;
vm.model.submit($scope.model, $event);
}
vm.close = function() {
if ($scope.model && $scope.model.close) {
$scope.model.close($scope.model);
}
}
}
);
@@ -0,0 +1,81 @@
<div class="umb-block-editor" ng-controller="Umbraco.Editors.BlockPickerController as vm">
<umb-editor-view ng-if="!page.loading">
<umb-editor-header
name="vm.model.title"
navigation="vm.navigation"
on-select-navigation-item="vm.onNavigationChanged(item)"
name-locked="true"
hide-alias="true"
hide-icon="true"
hide-description="true">
</umb-editor-header>
<div class="umb-editor-container umb-panel-body umb-scrollable row-fluid">
<div class="umb-pane" ng-if="vm.activeTab.alias === 'empty'">
<div class="form-search" ng-hide="vm.model.filter === false" style="margin-bottom: 20px;">
<i class="icon-search"></i>
<input type="text"
ng-model="searchTerm"
class="umb-search-field search-query input-block-level -full-width-input"
localize="placeholder"
placeholder="@placeholders_filter"
umb-auto-focus
no-dirty-check />
</div>
<div class="umb-block-card-grid">
<umb-block-card
class="umb-outline"
ng-repeat="block in vm.model.availableItems | filter:searchTerm"
block-config-model="block.blockConfigModel"
element-type-model="block.elementTypeModel"
ng-click="vm.selectItem(block, $event)">
</umb-block-card>
</div>
</div>
<div class="umb-pane" ng-if="vm.activeTab.alias === 'clipboard'">
<div style="margin-bottom:20px; text-align: right;">
<umb-button
type="button"
icon="icon-trash"
button-style="link"
label-key="clipboard_labelForClearClipboard"
action="vm.clickClearClipboard()">
</umb-button>
</div>
<div class="umb-block-card-grid">
<umb-block-card
block-config-model="block.blockConfigModel"
element-type-model="block.elementTypeModel"
ng-repeat="block in vm.model.clipboardItems"
ng-click="vm.model.clickPasteItem(block, $event)">
</umb-block-card>
</div>
</div>
</div>
<umb-editor-footer>
<umb-editor-footer-content-right>
<umb-button
action="vm.close()"
button-style="link"
label-key="general_cancel"
type="button">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
</umb-editor-view>
</div>
@@ -1,13 +1,13 @@
<div>
<p ng-bind-html="model.content"></p>
<div ng-if="model.confirmMessage"
class="umb-alert mb2"
ng-class="{ 'umb-alert--warning': model.confirmMessageStyle === 'warning',
'umb-alert--danger': model.confirmMessageStyle === 'danger',
'umb-alert--info': model.confirmMessageStyle === 'info'}">
{{model.confirmMessage}}
'umb-alert--info': model.confirmMessageStyle === 'info'}"
ng-bind-html="model.confirmMessage">
</div>
<p>{{model.content}}</p>
</div>
@@ -42,6 +42,14 @@
</span>
</button>
</li>
<li ng-if="model.createNewItem">
<button class="umb-card-grid-item btn-reset --creator" ng-click="model.createNewItem.action()">
<span>
<i class="{{ model.createNewItem.icon }}"></i>
{{ model.createNewItem.name }}
</span>
</button>
</li>
</ul>
<umb-tooltip ng-if="tooltip.show" event="tooltip.event">
@@ -0,0 +1,15 @@
.umb-block-card-grid {
/* FlexBox Fallback */
display: flex;
flex-wrap: wrap;
> * {
flex: 1 1 240px;
}
/* Grid Setup */
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-auto-rows: minmax(200px, auto);
grid-gap: 20px;
}
@@ -0,0 +1,11 @@
<div class="__showcase" ng-style="{'background-color':vm.blockConfigModel.backgroundColor, 'background-image': vm.blockConfigModel.thumbnail ? 'url('+vm.blockConfigModel.thumbnail+'?upscale=false&width=400)' : 'transparent'}">
<i ng-if="vm.blockConfigModel.thumbnail == null && vm.elementTypeModel.icon" class="__icon {{ vm.elementTypeModel.icon }}" ng-style="{'color':vm.blockConfigModel.iconColor}" aria-hidden="true"></i>
</div>
<div class="__info">
<div class="__name" ng-bind="vm.elementTypeModel.name"></div>
<div class="__subname" ng-if="vm.elementTypeModel.description" ng-bind="vm.elementTypeModel.description"></div>
</div>
<ng-transclude></ng-transclude>
@@ -0,0 +1,129 @@
.umb-block-card,
umb-block-card {
position: relative;
display: inline-block;
width: 100%;
height: auto;
background-color: white;
border-radius: @doubleBorderRadius;
box-shadow: 0 1px 2px rgba(0,0,0,.2);
overflow: hidden;
transition: box-shadow 120ms;
cursor: pointer;
&:hover {
box-shadow: 0 1px 3px rgba(@ui-action-type-hover, .5);
}
&.--isOpen {
&::after {
content: "";
position: absolute;
border: 2px solid @ui-active-border;
border-radius: @doubleBorderRadius;
top:0;
bottom: 0;
left: 0;
right: 0;
}
}
&.--sortable-placeholder {
&::after {
content: "";
position: absolute;
background-color:rgba(@ui-drop-area-color, .05);
border: 2px solid rgba(@ui-drop-area-color, .1);
border-radius: @doubleBorderRadius;
box-shadow: 0 0 4px rgba(@ui-drop-area-color, 0.05);
top:0;
bottom: 0;
left: 0;
right: 0;
animation: umb-block-card--sortable-placeholder 400ms ease-in-out alternate infinite;
@keyframes umb-block-card--sortable-placeholder {
0% { opacity: 1; }
100% { opacity: 0.5; }
}
}
box-shadow: none;
}
.__showcase {
position: relative;
width: 100%;
padding-bottom: 10/16*100%;
background-color: @gray-12;
background-size: cover;
background-position: 50% 50%;
background-repeat: no-repeat;
.__icon {
position: absolute;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size:42px;
}
}
.__info {
width: 100%;
background-color: #fff;
padding-bottom: 6px;
.__name {
font-weight: bold;
font-size: 14px;
color: @ui-action-type;
margin-left: 16px;
margin-top: 8px;
margin-bottom: -1px;
}
.__subname {
color: @gray-4;
font-size: 12px;
margin-left: 16px;
margin-bottom: -1px;
}
}
&:hover {
.__info {
.__name {
color: @ui-action-type-hover;
}
}
}
.__actions {
position: absolute;
top: 10px;
right: 0;
opacity: 0;
transition: opacity 120ms;
.__action {
display: inline-block;
border-radius: 50%;
width: 28px;
height: 28px;
margin-right: 10px;
background-color: white;
color:@ui-action-type;
&:hover {
color: @ui-action-type-hover;
}
}
}
&:hover, &:focus, &:focus-within {
.__actions {
opacity: 1;
}
}
}
@@ -0,0 +1,23 @@
(function () {
"use strict";
angular
.module("umbraco")
.component("umbBlockCard", {
templateUrl: "views/components/blockcard/umb-block-card.html",
controller: BlockCardController,
controllerAs: "vm",
transclude: true,
bindings: {
blockConfigModel: "<",
elementTypeModel: "<"
}
});
function BlockCardController() {
var vm = this;
}
})();
@@ -4,6 +4,7 @@
hotkey="{{::vm.hotkey}}"
hotkey-when-hidden="true"
ng-class="{'is-active': vm.item.active, '-has-error': vm.item.hasError}"
ng-disabled="vm.item.disabled"
class="umb-sub-views-nav-item__action umb-outline umb-outline--thin">
<i class="icon {{ vm.item.icon }}" aria-hidden="true"></i>
<span class="umb-sub-views-nav-item-text">{{ vm.item.name }}</span>
@@ -0,0 +1,38 @@
<div class="form-horizontal">
<ng-form name="elementTypeContentForm">
<div class="umb-group-panel"
data-element="group-{{group.alias}}"
ng-repeat="group in vm.model.variants[0].tabs track by group.label">
<div class="umb-group-panel__header">
<div id="group-{{group.id}}">{{ group.label }}</div>
</div>
<div class="umb-group-panel__content">
<umb-property
data-element="property-{{property.alias}}"
ng-repeat="property in group.properties track by property.alias"
property="property"
show-inherit="vm.model.variants.length > 1 && !property.culture && !activeVariant.language.isDefault"
inherits-from="defaultVariant.language.name">
<div ng-class="{'o-40 cursor-not-allowed': vm.model.variants.length > 1 && !activeVariant.language.isDefault && !property.culture && !property.unlockInvariantValue}">
<umb-property-editor
model="property"
preview="vm.model.variants.length > 1 && !activeVariant.language.isDefault && !property.culture && !property.unlockInvariantValue">
</umb-property-editor>
</div>
</umb-property>
</div>
</div>
<umb-empty-state
ng-if="vm.model.tabs.length === 0"
position="center">
<localize key="content_noProperties"></localize>
</umb-empty-state>
</ng-form>
</div>
@@ -0,0 +1,21 @@
(function () {
'use strict';
angular
.module('umbraco.directives')
.component('umbElementEditorContent', {
templateUrl: 'views/components/elementeditor/umb-element-editor-content.component.html',
controller: ElementEditorContentComponentController,
controllerAs: 'vm',
bindings: {
model: '='
}
});
function ElementEditorContentComponentController() {
// We need a controller for the component to work.
}
})();
@@ -1,4 +1,4 @@
<div data-element="overlay" class="umb-overlay umb-overlay-{{position}} umb-overlay--{{size}}" on-outside-click="outSideClick()" role="dialog" aria-labelledby="umb-overlay-title" aria-describedby="umb-overlay-description">
<div data-element="overlay" class="umb-overlay umb-overlay-{{position}} umb-overlay--{{model.size}}" on-outside-click="outSideClick()" role="dialog" aria-labelledby="umb-overlay-title" aria-describedby="umb-overlay-description">
<ng-form class="umb-overlay__form" name="overlayForm" novalidate val-form-manager>
<div data-element="overlay-header" class="umb-overlay-header" ng-show="!model.hideHeader">
<h1 class="umb-overlay__title" id="umb-overlay-title">{{model.title}}</h1>
@@ -23,7 +23,7 @@
<umb-property-actions actions="propertyActions"></umb-property-actions>
<small class="control-description" ng-bind-html="property.description | preserveNewLineInHtml"></small>
<small class="control-description" ng-if="property.description" ng-bind-html="property.description | preserveNewLineInHtml"></small>
</div>
<div class="controls" ng-transclude>
@@ -0,0 +1,24 @@
<div class="umb-property-editor umb-prevalues-numberrange">
<ng-form name="prevalueNumberRangeForm">
<input name="numberFieldMin" class="__min-input"
type="number"
ng-model="model.value.min"
placeholder="0"
ng-max="model.value.max"
fix-number />
<span class="__enDash"></span>
<input name="numberFieldMax" class="__max-input"
type="number"
ng-model="model.value.max"
placeholder="∞"
ng-min="model.value.min"
fix-number />
<span ng-messages="prevalueNumberRangeForm.numberFieldMin.$error" show-validation-on-submit >
<span class="help-inline" ng-message="number">Not a number</span>
</span>
<span ng-messages="prevalueNumberRangeForm.numberFieldMax.$error" show-validation-on-submit >
<span class="help-inline" ng-message="number">Not a number</span>
</span>
</ng-form>
</div>
@@ -0,0 +1,14 @@
.umb-prevalues-numberrange {
.__min-input {
}
.__enDash {
display: inline-block;
vertical-align: middle;
margin-top: 5px;
}
.__max-input {
}
}
@@ -0,0 +1 @@
<umb-block-list-property-editor property-form="propertyForm" model="model"/>
@@ -0,0 +1,25 @@
(function () {
'use strict';
function InlineBlockEditor($scope) {
const bc = this;
bc.openBlock = function(block) {
// if we are closing:
if (block.active === true) {
// boardcast the formSubmitting event to trigger syncronization or none-live property-editors
$scope.$broadcast("formSubmitting", { scope: $scope });
// Some property editors need to performe an action after all property editors have reacted to the formSubmitting.
$scope.$broadcast("postFormSubmitting", { scope: $scope });
}
block.active = !block.active;
}
}
angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockEditor.InlineBlockEditor", InlineBlockEditor);
})();
@@ -0,0 +1,10 @@
<div class="blockelement-inlineblock-editor" ng-controller="Umbraco.PropertyEditors.BlockEditor.InlineBlockEditor as vm">
<button type="button" class="btn-reset umb-outline blockelement__draggable-element" ng-click="vm.openBlock(block)" ng-focus="block.focus">
<span class="caret"></span>
<i class="icon {{block.content.icon}}"></i>
<span>{{block.label}}</span>
</button>
<div class="blockelement-inlineblock-editor__inner" ng-class="{'--singleGroup':block.content.variants[0].tabs.length === 1}" ng-if="block.active === true">
<umb-element-editor-content model="block.content"></umb-element-editor-content>
</div>
</div>
@@ -0,0 +1,80 @@
.blockelement-inlineblock-editor {
display: block;
margin-bottom: 4px;
margin-top: 4px;
border: 1px solid @gray-9;
border-radius: @baseBorderRadius;
transition: border-color 120ms;
.umb-block-list__block:not(.--active) &:hover {
border-color: @gray-8;
}
> 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;
.caret {
transform: rotate(-90deg);
transition: transform 80ms ease-out;
}
i {
font-size: 22px;
display: inline-block;
vertical-align: middle;
}
span {
display: inline-block;
vertical-align: middle;
}
&:hover {
color: @ui-action-discreet-type-hover;
border-color: @gray-8;
}
}
.umb-block-list__block.--active & {
border-color: @gray-8;
box-shadow: 0 0 2px 0px rgba(0, 0, 0, 0.05);
> 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;
}
}
}
@@ -0,0 +1,4 @@
<button type="button" class="btn-reset umb-outline blockelement-labelblock-editor blockelement__draggable-element" ng-click="api.editBlock(block)" ng-focus="block.focus" ng-class="{'--active':block.active}">
<i class="icon {{block.content.icon}}"></i>
<span>{{block.label}}</span>
</button>
@@ -0,0 +1,44 @@
.blockelement-labelblock-editor {
position: relative;
display: block;
margin-bottom: 4px;
margin-top: 4px;
width: 100%;
min-height: 48px;
border: 1px solid @gray-9;
border-radius: @baseBorderRadius;
cursor: pointer;
color: @ui-action-discreet-type;
text-align: left;
padding-left: 20px;
padding-bottom: 2px;
user-select: none;
transition: border-color 120ms;
i {
font-size: 22px;
margin-right: 5px;
display: inline-block;
vertical-align: middle;
}
span {
display: inline-block;
vertical-align: middle;
}
&:hover {
color: @ui-action-discreet-type-hover;
background-color: @ui-action-hover;
}
&.--active {
color: @ui-active-type;
border-color: @ui-active;
background-color: @ui-active;
}
}
@@ -0,0 +1,13 @@
<div class="blockelement-unsupportedblock-editor blockelement__draggable-element" ng-focus="block.focus">
<div class="__header">
<i class="icon icon-alert"></i>
<span>{{block.label}}</span>
</div>
<div class="__body">
This Block is no longer supported in this context.<br/>
You might want to remove this block, or contact your developer to take actions for making this block available again.<br/><br/>
<a href="http://our.umbraco.com" target="_blank">Learn about this circumstance</a>
<h5>Block data:</h5>
<pre ng-bind="block.data | json : 4"></pre>
</div>
</div>
@@ -0,0 +1,58 @@
.blockelement-unsupportedblock-editor {
position: relative;
display: block;
box-sizing: border-box;
margin-bottom: 4px;
margin-top: 4px;
width: 100%;
border: 1px solid @gray-9;
border-radius: @baseBorderRadius;
> .__header {
display: flex;
align-items: center;
padding-left: 20px;
padding-bottom: 2px;
min-height: 48px;
border-bottom: 1px solid @gray-9;
background-color: @gray-11;
color: @gray-6;
i {
font-size: 22px;
margin-right: 5px;
display: inline-block;
vertical-align: middle;
}
span {
display: inline-block;
vertical-align: middle;
}
}
> .__body {
padding: 20px;
background-color: @gray-11;
a {
text-decoration: underline;
color: @ui-action-type;
&:hover {
color:@ui-action-type-hover;
}
}
pre {
border: none;
padding: 0;
background-color: transparent;
}
}
}
@@ -0,0 +1,219 @@
/**
* @ngdoc controller
* @name Umbraco.Editors.BlockList.BlockConfigurationController
* @function
*
* @description
* The controller for the content type editor property settings dialog
*/
(function () {
"use strict";
function TransferProperties(fromObject, toObject) {
for (var p in fromObject) {
toObject[p] = fromObject[p];
}
}
function BlockConfigurationController($scope, elementTypeResource, overlayService, localizationService, editorService, eventsService) {
var unsubscribe = [];
var vm = this;
vm.openBlock = null;
function onInit() {
if (!$scope.model.value) {
$scope.model.value = [];
}
loadElementTypes();
}
function loadElementTypes() {
return elementTypeResource.getAll().then(function (elementTypes) {
vm.elementTypes = elementTypes;
});
}
function updateUsedElementTypes(event, args) {
var key = args.documentType.key;
for (var i = 0; i<vm.elementTypes.length; i++) {
if (vm.elementTypes[i].key === key) {
vm.elementTypes[i] = args.documentType;
}
}
}
unsubscribe.push(eventsService.on("editors.documentType.saved", updateUsedElementTypes));
vm.requestRemoveBlockByIndex = function (index) {
localizationService.localizeMany(["general_delete", "blockEditor_confirmDeleteBlockMessage", "blockEditor_confirmDeleteBlockNotice"]).then(function (data) {
var contentElementType = vm.getElementTypeByKey($scope.model.value[index].contentTypeKey);
overlayService.confirmDelete({
title: data[0],
content: localizationService.tokenReplace(data[1], [contentElementType.name]),
confirmMessage: data[2],
close: function () {
overlayService.close();
},
submit: function () {
vm.removeBlockByIndex(index);
overlayService.close();
}
});
});
}
vm.removeBlockByIndex = function (index) {
$scope.model.value.splice(index, 1);
};
vm.sortableOptions = {
"ui-floating": true,
items: "umb-block-card",
cursor: "grabbing",
placeholder: 'umb-block-card --sortable-placeholder'
};
vm.getAvailableElementTypes = function () {
return vm.elementTypes.filter(function (type) {
return !$scope.model.value.find(function (entry) {
return type.key === entry.contentTypeKey;
});
});
};
vm.getElementTypeByKey = function(key) {
if (vm.elementTypes) {
return vm.elementTypes.find(function (type) {
return type.key === key;
});
}
};
vm.openAddDialog = function ($event, entry) {
//we have to add the 'alias' property to the objects, to meet the data requirements of itempicker.
var selectedItems = Utilities.copy($scope.model.value).forEach((obj) => {
obj.alias = vm.getElementTypeByKey(obj.contentTypeKey).alias;
return obj;
});
var availableItems = vm.getAvailableElementTypes()
localizationService.localizeMany(["blockEditor_headlineCreateBlock", "blockEditor_labelcreateNewElementType"]).then(function(localized) {
var elemTypeSelectorOverlay = {
view: "itempicker",
title: localized[0],
availableItems: availableItems,
selectedItems: selectedItems,
createNewItem: {
action: function() {
overlayService.close();
vm.createElementTypeAndCallback(vm.addBlockFromElementTypeKey);
},
icon: "icon-add",
name: localized[1]
},
position: "target",
event: $event,
size: availableItems.length < 7 ? "small" : "medium",
submit: function (overlay) {
vm.addBlockFromElementTypeKey(overlay.selectedItem.key);
overlayService.close();
},
close: function () {
overlayService.close();
}
};
overlayService.open(elemTypeSelectorOverlay);
});
};
vm.createElementTypeAndCallback = function(callback) {
const editor = {
create: true,
infiniteMode: true,
isElement: true,
submit: function (model) {
loadElementTypes().then( function () {
callback(model.documentTypeKey);
});
editorService.close();
},
close: function () {
editorService.close();
}
};
editorService.documentTypeEditor(editor);
}
vm.addBlockFromElementTypeKey = function(key) {
var entry = {
"contentTypeKey": key,
"settingsElementTypeKey": null,
"labelTemplate": "",
"view": null,
"stylesheet": null,
"editorSize": "medium",
"iconColor": null,
"backgroundColor": null,
"thumbnail": null
};
$scope.model.value.push(entry);
};
vm.openBlockOverlay = function (block) {
localizationService.localize("blockEditor_blockConfigurationOverlayTitle", [vm.getElementTypeByKey(block.contentTypeKey).name]).then(function (data) {
var clonedBlockData = Utilities.copy(block);
vm.openBlock = block;
var overlayModel = {
block: clonedBlockData,
title: data,
view: "views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.html",
size: "small",
submit: function(overlayModel) {
loadElementTypes()// lets load elementType again, to ensure we are up to date.
TransferProperties(overlayModel.block, block);// transfer properties back to block object. (Doing this cause we dont know if block object is added to model jet, therefor we cant use index or replace the object.)
overlayModel.close();
},
close: function() {
editorService.close();
vm.openBlock = null;
}
};
// open property settings editor
editorService.open(overlayModel);
});
};
$scope.$on('$destroy', function () {
unsubscribe.forEach(u => { u(); });
});
onInit();
}
angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockList.BlockConfigurationController", BlockConfigurationController);
})();
@@ -0,0 +1,30 @@
<div
class="umb-block-list-block-configuration"
ng-controller="Umbraco.PropertyEditors.BlockList.BlockConfigurationController as vm"
>
<div
class="umb-block-card-grid"
ui-sortable="vm.sortableOptions"
ng-model="model.value"
>
<umb-block-card
block-config-model="block"
element-type-model="vm.getElementTypeByKey(block.contentTypeKey)"
ng-repeat="block in model.value"
ng-class="{'--isOpen':vm.openBlock === block}"
ng-click="vm.openBlockOverlay(block)">
<div class="__actions">
<button type="button" class="btn-reset __action umb-outline" ng-click="vm.requestRemoveBlockByIndex($index); $event.stopPropagation();">
<i class="icon icon-trash" aria-hidden="true"></i>
<localize key="general_delete" class="sr-only">Delete</localize>
</button>
</div>
</umb-block-card>
<button id="{{model.alias}}" type="button" class="btn-reset __add-button" ng-click="vm.openAddDialog($event)">
<localize key="general_add">Add</localize>
</button>
</div>
</div>
@@ -0,0 +1,28 @@
.umb-block-list-block-configuration {
.__add-button {
position: relative;
display: inline-flex;
width: 100%;
height: auto;
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;
}
}
@@ -0,0 +1,292 @@
/**
* @ngdoc controller
* @name Umbraco.Editors.BlockList.BlockConfigurationOverlayController
* @function
*
* @description
* The controller for the content type editor property settings dialog
*/
(function () {
"use strict";
function BlockConfigurationOverlayController($scope, overlayService, localizationService, editorService, elementTypeResource, eventsService) {
var unsubscribe = [];
var vm = this;
vm.block = $scope.model.block;
loadElementTypes();
function loadElementTypes() {
return elementTypeResource.getAll().then(function (elementTypes) {
vm.elementTypes = elementTypes;
vm.contentPreview = vm.getElementTypeByKey(vm.block.contentTypeKey);
vm.settingsPreview = vm.getElementTypeByKey(vm.block.settingsElementTypeKey);
});
}
vm.getElementTypeByKey = function(key) {
return vm.elementTypes.find(function (type) {
return type.key === key;
});
};
vm.openElementType = function(elementTypeKey) {
var elementTypeId = vm.getElementTypeByKey(elementTypeKey).id;
const editor = {
id: elementTypeId,
submit: function (model) {
editorService.close();
},
close: function () {
editorService.close();
}
};
editorService.documentTypeEditor(editor);
}
vm.createElementTypeAndCallback = function(callback) {
const editor = {
create: true,
infiniteMode: true,
isElement: true,
submit: function (model) {
callback(model.documentTypeKey);
editorService.close();
},
close: function () {
editorService.close();
}
};
editorService.documentTypeEditor(editor);
}
vm.addSettingsForBlock = function ($event, block) {
localizationService.localizeMany(["blockEditor_headlineAddSettingsElementType", "blockEditor_labelcreateNewElementType"]).then(function(localized) {
var elemTypeSelectorOverlay = {
view: "itempicker",
title: localized[0],
availableItems: vm.elementTypes,
position: "target",
event: $event,
size: vm.elementTypes.length < 7 ? "small" : "medium",
createNewItem: {
action: function() {
overlayService.close();
vm.createElementTypeAndCallback((key) => {
vm.applySettingsToBlock(block, key);
});
},
icon: "icon-add",
name: localized[1]
},
submit: function (overlay) {
vm.applySettingsToBlock(block, overlay.selectedItem.key);
overlayService.close();
},
close: function () {
overlayService.close();
}
};
overlayService.open(elemTypeSelectorOverlay);
});
};
vm.applySettingsToBlock = function(block, key) {
block.settingsElementTypeKey = key;
};
vm.requestRemoveSettingsForBlock = function(block) {
localizationService.localizeMany(["general_remove", "defaultdialogs_confirmremoveusageof"]).then(function (data) {
var settingsElementType = vm.getElementTypeByKey(block.settingsElementTypeKey);
overlayService.confirmRemove({
title: data[0],
content: localizationService.tokenReplace(data[1], [settingsElementType.name]),
close: function () {
overlayService.close();
},
submit: function () {
vm.removeSettingsForBlock(block);
overlayService.close();
}
});
});
};
vm.removeSettingsForBlock = function(block) {
block.settingsElementTypeKey = null;
};
function updateUsedElementTypes(event, args) {
var key = args.documentType.key;
for (var i = 0; i<vm.elementTypes.length; i++) {
if (vm.elementTypes[i].key === key) {
vm.elementTypes[i] = args.documentType;
}
}
if (vm.contentPreview.key === key) {
vm.contentPreview = args.documentType;
$scope.$evalAsync();
}
if (vm.settingsPreview.key === key) {
vm.settingsPreview = args.documentType;
$scope.$evalAsync();
}
}
unsubscribe.push(eventsService.on("editors.documentType.saved", updateUsedElementTypes));
vm.addViewForBlock = function(block) {
localizationService.localize("blockEditor_headlineSelectView").then(function(localizedTitle) {
const filePicker = {
title: localizedTitle,
section: "settings",
treeAlias: "files",
entityType: "file",
isDialog: true,
filter: function (i) {
return !(i.name.indexOf(".html") !== -1);
},
select: function (node) {
const filepath = decodeURIComponent(node.id.replace(/\+/g, " "));
block.view = filepath;
editorService.close();
},
close: function () {
editorService.close();
}
};
editorService.treePicker(filePicker);
});
}
vm.requestRemoveViewForBlock = function(block) {
localizationService.localizeMany(["general_remove", "defaultdialogs_confirmremoveusageof"]).then(function (data) {
overlayService.confirmRemove({
title: data[0],
content: localizationService.tokenReplace(data[1], [block.view]),
close: function () {
overlayService.close();
},
submit: function () {
vm.removeViewForBlock(block);
overlayService.close();
}
});
});
};
vm.removeViewForBlock = function(block) {
block.view = null;
};
vm.addStylesheetForBlock = function(block) {
localizationService.localize("blockEditor_headlineAddCustomStylesheet").then(function(localizedTitle) {
const filePicker = {
title: localizedTitle,
section: "settings",
treeAlias: "files",
entityType: "file",
isDialog: true,
filter: function (i) {
return !(i.name.indexOf(".css") !== -1);
},
select: function (node) {
const filepath = decodeURIComponent(node.id.replace(/\+/g, " "));
block.stylesheet = filepath;
editorService.close();
},
close: function () {
editorService.close();
}
};
editorService.treePicker(filePicker);
});
}
vm.requestRemoveStylesheetForBlock = function(block) {
localizationService.localizeMany(["general_remove", "defaultdialogs_confirmremoveusageof"]).then(function (data) {
overlayService.confirmRemove({
title: data[0],
content: localizationService.tokenReplace(data[1], [block.stylesheet]),
close: function () {
overlayService.close();
},
submit: function () {
vm.removeStylesheetForBlock(block);
overlayService.close();
}
});
});
};
vm.removeStylesheetForBlock = function(block) {
block.stylesheet = null;
};
vm.addThumbnailForBlock = function(block) {
localizationService.localize("blockEditor_headlineAddThumbnail").then(function(localizedTitle) {
const thumbnailPicker = {
title: localizedTitle,
section: "settings",
treeAlias: "files",
entityType: "file",
isDialog: true,
filter: function (i) {
return !(i.name.indexOf(".jpg") !== -1 || i.name.indexOf(".jpeg") !== -1 || i.name.indexOf(".png") !== -1 || i.name.indexOf(".svg") !== -1 || i.name.indexOf(".webp") !== -1 || i.name.indexOf(".gif") !== -1);
},
select: function (file) {
block.thumbnail = file.name;
editorService.close();
},
close: function () {
editorService.close();
}
};
editorService.treePicker(thumbnailPicker);
});
}
vm.removeThumbnailForBlock = function(entry) {
entry.thumbnail = null;
};
vm.submit = function () {
if ($scope.model && $scope.model.submit) {
$scope.model.submit($scope.model);
}
}
vm.close = function() {
if ($scope.model && $scope.model.close) {
$scope.model.close($scope.model);
}
}
$scope.$on('$destroy', function () {
unsubscribe.forEach(u => { u(); });
});
}
angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockList.BlockConfigurationOverlayController", BlockConfigurationOverlayController);
})();
@@ -0,0 +1,226 @@
<div class="umb-block-list-block-configuration-overlay" ng-controller="Umbraco.PropertyEditors.BlockList.BlockConfigurationOverlayController as vm">
<form novalidate name="blockListBlockConfigurationForm" val-form-manager>
<umb-editor-view ng-if="!page.loading">
<umb-editor-header
name="model.title"
name-locked="true"
hide-alias="true"
hide-icon="true"
hide-description="true">
</umb-editor-header>
<umb-editor-container>
<div class="form-vertical">
<div class="umb-group-panel">
<div class="umb-group-panel__header">
<localize key="blockEditor_headlineEditorAppearance">Editor appearance</localize>
</div>
<div class="umb-group-panel__content">
<!-- label -->
<div class="control-group umb-control-group -no-border">
<div class="umb-el-wrap">
<label class="control-label" for="label"><localize key="blockEditor_labelLabelTemplate">label</localize></label>
<div class="controls">
<input type="text" name="label" ng-model="vm.block.label" style="width:100%" umb-auto-focus/>
</div>
</div>
</div>
<!-- custom view -->
<div class="control-group umb-control-group -no-border">
<div class="umb-el-wrap">
<label class="control-label" for="iconcolor"><localize key="blockEditor_labelCustomView">Custom view</localize></label>
<div class="controls">
<div class="__settings-input --hasValue" ng-if="vm.block.view !== null" >
<umb-node-preview icon="'icon-document'" name="vm.block.view"></umb-node-preview>
<div class="__control-actions">
<button type="button" class="btn-reset __control-actions-btn --remove umb-outline" ng-click="vm.requestRemoveViewForBlock(vm.block)">
<i class="icon icon-wrong"></i>
</button>
</div>
</div>
<button type="button" class="btn-reset __settings-input --noValue umb-outline" ng-if="vm.block.view === null" ng-click="vm.addViewForBlock(vm.block)">
<localize key="blockEditor_addCustomView">Add custom view</localize>
</button>
</div>
</div>
</div>
<!-- custom stylesheet -->
<div class="control-group umb-control-group -no-border">
<div class="umb-el-wrap">
<label class="control-label" for="iconcolor"><localize key="blockEditor_labelCustomStylesheet">Custom stylesheet</localize></label>
<div class="controls">
<div class="__settings-input --hasValue" ng-if="vm.block.stylesheet !== null" >
<umb-node-preview icon="'icon-document'" name="vm.block.stylesheet"></umb-node-preview>
<div class="__control-actions">
<button type="button" class="btn-reset __control-actions-btn --remove umb-outline" ng-click="vm.requestRemoveStylesheetForBlock(vm.block)">
<i class="icon icon-wrong"></i>
</button>
</div>
</div>
<button type="button" class="btn-reset __settings-input --noValue umb-outline" ng-if="vm.block.stylesheet === null" ng-click="vm.addStylesheetForBlock(vm.block)">
<localize key="blockEditor_addCustomStylesheet">Add custom stylesheet</localize>
</button>
</div>
</div>
</div>
<!-- editor overlay size -->
<div class="control-group umb-control-group -no-border">
<div class="umb-el-wrap">
<label class="control-label" for="editorSize"><localize key="blockEditor_labelEditorSize">Editor size</localize></label>
<div class="controls">
<select id="editorSize" ng-model="vm.block.editorSize">
<option value="small">Small</option>
<option value="medium">Medium</option>
<option value="large">Large</option>
</select>
</div>
</div>
</div>
</div>
</div>
<div class="umb-group-panel">
<div class="umb-group-panel__header">
<localize key="blockEditor_headlineDataModels">Data Models</localize>
</div>
<div class="umb-group-panel__content">
<!-- Content Element Type -->
<div class="control-group umb-control-group -no-border">
<div class="umb-el-wrap">
<label class="control-label"><localize key="blockEditor_labelContentElementType">Content ElementType</localize></label>
<div class="controls">
<div class="__settings-input --hasValue" ng-if="vm.block.contentTypeKey !== null" >
<umb-node-preview icon="vm.contentPreview.icon" name="vm.contentPreview.name" alias="vm.contentPreview.alias"></umb-node-preview>
<div class="__control-actions">
<button type="button" class="btn-reset __control-actions-btn --open umb-outline" ng-click="vm.openElementType(vm.block.contentTypeKey)">
<i class="icon icon-edit"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Settings Element Type -->
<div class="control-group umb-control-group -no-border">
<div class="umb-el-wrap">
<label class="control-label"><localize key="blockEditor_labelSettingsElementType">Settings Element Type</localize></label>
<div class="controls">
<div class="__settings-input --hasValue" ng-if="vm.block.settingsElementTypeKey !== null">
<umb-node-preview icon="vm.settingsPreview.icon" name="vm.settingsPreview.name" alias="vm.settingsPreview.alias"></umb-node-preview>
<div class="__control-actions">
<button type="button" class="btn-reset __control-actions-btn --open umb-outline" ng-click="vm.openElementType(vm.block.settingsElementTypeKey)">
<i class="icon icon-edit"></i>
</button>
<button type="button" class="btn-reset __control-actions-btn --remove umb-outline" ng-click="vm.requestRemoveSettingsForBlock(vm.block)">
<i class="icon icon-wrong"></i>
</button>
</div>
</div>
<button type="button" class="btn-reset __settings-input --noValue umb-outline" ng-if="vm.block.settingsElementTypeKey === null" ng-click="vm.addSettingsForBlock($event, vm.block)">
<localize key="blockEditor_addSettingsElementType">Add settings</localize>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="umb-group-panel">
<div class="umb-group-panel__header">
<localize key="blockEditor_headlineCatalogueAppearance">Catalogue appearance</localize>
</div>
<div class="umb-group-panel__content">
<!-- backgroundColor -->
<div class="control-group umb-control-group -no-border">
<div class="umb-el-wrap">
<label class="control-label" for="backgroundColor"><localize key="blockEditor_labelBackgroundColor">Background Color</localize></label>
<div class="controls">
<input type="text" name="backgroundColor" ng-model="vm.block.backgroundColor"/>
</div>
</div>
</div>
<!-- iconColor -->
<div class="control-group umb-control-group -no-border">
<div class="umb-el-wrap">
<label class="control-label" for="iconColor"><localize key="blockEditor_labelIconColor">Icon Color</localize></label>
<div class="controls">
<input type="text" name="iconColor" ng-model="vm.block.iconColor"/>
</div>
</div>
</div>
<!-- thumbnail -->
<div class="control-group umb-control-group -no-border">
<div class="umb-el-wrap">
<label class="control-label" for="iconcolor"><localize key="blockEditor_thumbnail">Thumbnail</localize></label>
<div class="controls">
<div class="__settings-input --hasValue" ng-if="vm.block.thumbnail !== null" >
<umb-node-preview icon="'icon-document'" name="vm.block.thumbnail"></umb-node-preview>
<div class="__control-actions">
<button type="button" class="btn-reset __control-actions-btn --remove umb-outline" ng-click="vm.removeThumbnailForBlock(vm.block)">
<i class="icon icon-wrong"></i>
</button>
</div>
</div>
<button type="button" class="btn-reset __settings-input --noValue umb-outline" ng-if="vm.block.thumbnail === null" ng-click="vm.addThumbnailForBlock(vm.block)">
<localize key="blockEditor_addThumbnail">Add thumbnail</localize>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</umb-editor-container>
<umb-editor-footer>
<umb-editor-footer-content-right>
<umb-button
action="vm.close()"
button-style="link"
label-key="general_close"
type="button">
</umb-button>
<umb-button
action="vm.submit()"
button-style="primary"
state="submitButtonState"
label-key="buttons_submitChanges"
type="button">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
</umb-editor-view>
</form>
</div>
@@ -0,0 +1,100 @@
.umb-block-list-block-configuration-overlay {
.umb-node-preview {
flex-grow: 1;
}
.__control-actions {
position: absolute;
display: flex;
align-items: center;
top:0;
bottom: 0;
right: 0;
background-color: rgba(255, 255, 255, 0.8);
opacity: 0;
transition: opacity 120ms;
}
.control-group:hover,
.control-group:focus,
.control-group:focus-within {
.__control-actions {
opacity: 1;
}
}
.__control-actions-btn {
position: relative;
color: @ui-action-discreet-type;
height: 32px;
width: 26px;
&:hover {
color: @ui-action-discreet-type-hover;
}
&:last-of-type {
margin-right: 7px;
}
}
.umb-node-preview {
border-bottom: none;
}
.__settings-input {
position: relative;
padding: 5px 8px;
margin-bottom: 10px;
color: @ui-action-discreet-type;
border: 1px dashed @ui-action-discreet-border;
width: 100%;
font-weight: bold;
display: inline-flex;
flex-flow: row nowrap;
localize {
width: 100%;
}
.umb-node-preview {
padding: 3px 0;
margin-left: 5px;
overflow: hidden;
}
&.--noValue {
text-align: center;
border-radius: @baseBorderRadius;
color: white;
transition: color 120ms;
&:hover, &:focus {
color: @ui-action-discreet-type-hover;
border-color: @ui-action-discreet-border-hover;
}
}
&.--hasValue {
border: 1px solid @inputBorder;
padding: 0;
}
}
.__add-button {
width:100%;
color: @ui-action-discreet-type;
border: 1px dashed @ui-action-discreet-border;
border-radius: @baseBorderRadius;
display: flex;
align-items: center;
justify-content: center;
padding: 5px 15px;
box-sizing: border-box;
margin: 20px 0;
font-weight: bold;
}
.__add-button:hover {
color: @ui-action-discreet-type-hover;
border-color: @ui-action-discreet-border-hover;
}
}
@@ -0,0 +1,35 @@
(function () {
"use strict";
/**
* @ngdoc directive
* @name umbraco.directives.directive:umbBlockListBlock
* @description
* The component for a style-inheriting block of the block list property editor.
*/
angular
.module("umbraco")
.component("umbBlockListBlock", {
template: '<div ng-include="model.view"></div>',
controller: BlockListBlockController,
controllerAs: "model",
bindings: {
view: "@",
block: "=",
api: "<",
index: "<"
}
}
);
function BlockListBlockController($scope) {
var model = this;
model.$onInit = function() {
$scope.block = model.block;
$scope.api = model.api;
};
}
})();
@@ -0,0 +1,101 @@
<div class="umb-block-list">
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
<div class="umb-block-list__wrapper" ng-style="vm.listWrapperStyles">
<div ui-sortable="vm.sortableOptions" ng-model="vm.layout" ng-if="vm.loading !== true">
<div ng-repeat="layout in vm.layout track by layout.$block.key">
<button
type="button"
class="btn-reset umb-block-list__block--create-button"
ng-click="vm.showCreateDialog($index, $event)"
ng-controller="Umbraco.PropertyEditors.BlockListPropertyEditor.CreateButtonController as inlineCreateButtonCtrl"
ng-mousemove="inlineCreateButtonCtrl.onMouseMove($event)"
>
<div class="__plus" ng-style="{'left':inlineCreateButtonCtrl.plusPosX}">+</div>
</button>
<div class="umb-block-list__block" ng-class="{'--active':layout.$block.active}">
<umb-block-list-scoped-block
ng-if="layout.$block.config.stylesheet"
class="umb-block-list__block--content blockelement__draggable-element"
view="{{layout.$block.view}}"
stylesheet="/{{::layout.$block.config.stylesheet}}"
api="vm.blockEditorApi"
block="layout.$block"
index="$index">
</umb-block-list-scoped-block>
<umb-block-list-block
ng-if="!layout.$block.config.stylesheet"
class="umb-block-list__block--content"
view="{{layout.$block.view}}"
api="vm.blockEditorApi"
block="layout.$block"
index="$index">
</umb-block-list-block>
<div class="umb-block-list__block--actions">
<button type="button" class="btn-reset umb-outline action --settings" localize="title" title="actions_editSettings" ng-click="vm.blockEditorApi.openSettingsForBlock(layout.$block);" ng-if="layout.$block.showSettings === true">
<i class="icon icon-settings" aria-hidden="true"></i>
<span class="sr-only">
<localize key="general_settings">Settings</localize>
</span>
</button>
<button type="button" class="btn-reset umb-outline action --copy" localize="title" title="actions_copy" ng-click="vm.blockEditorApi.copyBlock(layout.$block);" ng-if="layout.$block.showCopy === true">
<i class="icon icon-documents" aria-hidden="true"></i>
<span class="sr-only">
<localize key="general_copy">Copy</localize>
</span>
</button>
<button type="button" class="btn-reset umb-outline action --delete" localize="title" title="actions_delete" ng-click="vm.blockEditorApi.requestDeleteBlock(layout.$block);">
<i class="icon icon-trash" aria-hidden="true"></i>
<span class="sr-only">
<localize key="general_delete">Delete</localize>
</span>
</button>
</div>
</div>
</div>
</div>
<button
ng-if="vm.loading !== true"
id="{{vm.model.alias}}"
type="button"
class="btn-reset umb-block-list__create-button umb-outline"
ng-class="{ '--disabled': vm.availableBlockTypes.length === 0 }"
ng-click="vm.showCreateDialog(vm.layout.length, $event)"
>
<localize key="grid_addElement"></localize>
</button>
<input type="hidden" name="minCount" ng-model="vm.layout" />
<input type="hidden" name="maxCount" ng-model="vm.layout" />
<div ng-messages="vm.propertyForm.minCount.$error" show-validation-on-submit>
<div class="help text-error" ng-message="minCount">
<localize key="validation_entriesShort" tokens="[vm.validationLimit.min, vm.validationLimit.min - vm.layout.length]" watch-tokens="true">Minimum %0% entries, needs <strong>%1%</strong> more.</localize>
</div>
</div>
<div ng-if="vm.propertyForm.maxCount.$error === true && vm.layout.length > vm.validationLimit.max">
<div class="help text-error">
<localize key="validation_entriesExceed" tokens="[vm.validationLimit.max, vm.layout.length - vm.validationLimit.max]" watch-tokens="true">Maximum %0% entries, <strong>%1%</strong> too many.</localize>
</div>
</div>
</div>
<umb-overlay
ng-if="vm.blockTypePicker.show"
position="target"
size="vm.blockTypePicker.size"
view="vm.blockTypePicker.view"
model="vm.blockTypePicker">
</umb-overlay>
</div>

Some files were not shown because too many files have changed in this diff Show More