From 49764fdbe9cfe407eed45b898ff4f4047fdc7ca0 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 28 May 2020 17:47:08 +0100 Subject: [PATCH 01/86] Incremented version number, v1.1.0-develop --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index afaf360d..ee600bde 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.0 \ No newline at end of file +1.1.0-develop \ No newline at end of file From 9cf9314fa00467f3dbeaff18a1f209f52574c80b Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 28 May 2020 17:48:59 +0100 Subject: [PATCH 02/86] Added "Content Blocks" back in for v1.1.0 This reverts commit 5f247d66e095a6b1561d74861dcb3600ff32f17c. --- .../DataEditors/ContentBlocks/ContentBlock.cs | 24 ++ .../ContentBlocks/ContentBlockType.cs | 43 +++ .../ContentBlocksConfigurationEditor.cs | 95 +++++ .../ContentBlocks/ContentBlocksDataEditor.cs | 96 +++++ .../ContentBlocksDataValueEditor.cs | 163 +++++++++ ...tentBlocksDisplayModeConfigurationField.cs | 37 ++ .../ContentBlocksTypesConfigurationField.cs | 77 ++++ .../ContentBlocksValueConverter.cs | 90 +++++ .../ContentBlocks/ContentTypeCacheHelper.cs | 102 ++++++ .../content-blocks.blueprint.html | 25 ++ .../ContentBlocks/content-blocks.css | 83 +++++ .../ContentBlocks/content-blocks.html | 49 +++ .../ContentBlocks/content-blocks.js | 342 ++++++++++++++++++ .../ContentBlocks/content-blocks.overlay.html | 132 +++++++ .../ContentBlocks/content-blocks.overlay.js | 204 +++++++++++ .../ContentBlocks/content-list.html | 29 ++ .../DataEditors/ReadOnly/README.md | 1 + .../ReadOnly/readonly-node-preview.html | 8 + .../Umbraco.Community.Contentment.csproj | 17 + 19 files changed, 1617 insertions(+) create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlock.cs create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockType.cs create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDisplayModeConfigurationField.cs create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.blueprint.html create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.css create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.js create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-list.html create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ReadOnly/readonly-node-preview.html diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlock.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlock.cs new file mode 100644 index 00000000..ccc9f785 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlock.cs @@ -0,0 +1,24 @@ +/* Copyright © 2019 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Umbraco.Community.Contentment.DataEditors +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + internal sealed class ContentBlock + { + public Guid ElementType { get; set; } + + public Guid Key { get; set; } + + public string Icon { get; set; } + + public Dictionary Value { get; set; } + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockType.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockType.cs new file mode 100644 index 00000000..23436591 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockType.cs @@ -0,0 +1,43 @@ +/* Copyright © 2019 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Umbraco.Community.Contentment.DataEditors +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + internal sealed class ContentBlockType + { + public string Alias { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string Description { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string Icon { get; set; } + + public string Name { get; set; } + + public Guid Key { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string NameTemplate { get; set; } + + public string OverlaySize { get; set; } + + public IEnumerable Blueprints { get; set; } + + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + internal sealed class BlueprintItem + { + public string Name { get; set; } + + public int Id { get; set; } + } + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs new file mode 100644 index 00000000..4fa6dc78 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs @@ -0,0 +1,95 @@ +/* Copyright © 2019 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; + +namespace Umbraco.Community.Contentment.DataEditors +{ + internal sealed class ContentBlocksConfigurationEditor : ConfigurationEditor + { + private readonly Dictionary _elementTypes; + private readonly Lazy> _elementBlueprints; + + internal const string OverlayView = "overlayView"; + + public ContentBlocksConfigurationEditor(IContentService contentService, IContentTypeService contentTypeService) + : base() + { + // NOTE: Gets all the elementTypes and blueprints upfront, rather than several hits inside the loop. + _elementTypes = contentTypeService.GetAllElementTypes().ToDictionary(x => x.Key); + _elementBlueprints = new Lazy>(() => contentService.GetBlueprintsForContentTypes(_elementTypes.Values.Select(x => x.Id).ToArray()).ToLookup(x => x.ContentTypeId)); + + Fields.Add(new ContentBlocksTypesConfigurationField(_elementTypes.Values)); + Fields.Add(new ContentBlocksDisplayModeConfigurationField()); + Fields.Add(new EnableFilterConfigurationField()); + Fields.Add(new MaxItemsConfigurationField()); + Fields.Add(new DisableSortingConfigurationField()); + Fields.Add(new HideLabelConfigurationField()); + Fields.Add(new EnableDevModeConfigurationField()); + } + + public override IDictionary ToValueEditor(object configuration) + { + var config = base.ToValueEditor(configuration); + + if (config.TryGetValueAs(ContentBlocksTypesConfigurationField.ContentBlockTypes, out JArray array) && array.Count > 0) + { + var elementTypes = new List(); + + for (var i = 0; i < array.Count; i++) + { + var item = (JObject)array[i]; + + // NOTE: Patches a breaking-change. I'd renamed `type` to become `key`. [LK:2020-04-03] + if (item.ContainsKey("key") == false && item.ContainsKey("type")) + { + item.Add("key", item["type"]); + item.Remove("type"); + } + + if (Guid.TryParse(item.Value("key"), out var guid) && _elementTypes.ContainsKey(guid)) + { + var elementType = _elementTypes[guid]; + + var settings = item["value"].ToObject>(); + + var blueprints = _elementBlueprints.Value.Contains(elementType.Id) + ? _elementBlueprints.Value[elementType.Id].Select(x => new ContentBlockType.BlueprintItem { Id = x.Id, Name = x.Name }) + : Enumerable.Empty(); + + elementTypes.Add(new ContentBlockType + { + Alias = elementType.Alias, + Name = elementType.Name, + Description = elementType.Description, + Icon = elementType.Icon, + Key = elementType.Key, + Blueprints = blueprints, + NameTemplate = settings?.ContainsKey("nameTemplate") == true ? settings["nameTemplate"].ToString() : null, + OverlaySize = settings?.ContainsKey("overlaySize") == true ? settings["overlaySize"].ToString() : null, + }); + } + } + + config[ContentBlocksTypesConfigurationField.ContentBlockTypes] = elementTypes; + } + + if (config.ContainsKey(OverlayView) == false) + { + config.Add(OverlayView, IOHelper.ResolveUrl(ContentBlocksDataEditor.DataEditorOverlayViewPath)); + } + + return config; + } + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs new file mode 100644 index 00000000..5f8dc777 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs @@ -0,0 +1,96 @@ +/* Copyright © 2019 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; + +namespace Umbraco.Community.Contentment.DataEditors +{ + [Core.Composing.HideFromTypeFinder] + public sealed class ContentBlocksDataEditor : IDataEditor + { + internal const string DataEditorAlias = Constants.Internals.DataEditorAliasPrefix + "ContentBlocks"; + internal const string DataEditorName = Constants.Internals.DataEditorNamePrefix + "Content Blocks"; + internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "_empty.html"; + internal const string DataEditorListViewPath = Constants.Internals.EditorsPathRoot + "content-list.html"; + internal const string DataEditorBlocksViewPath = Constants.Internals.EditorsPathRoot + "content-blocks.html"; + internal const string DataEditorOverlayViewPath = Constants.Internals.EditorsPathRoot + "content-blocks.overlay.html"; + internal const string DataEditorIcon = "icon-item-arrangement"; + + private readonly IContentService _contentService; + private readonly IContentTypeService _contentTypeService; + private readonly IDataTypeService _dataTypeService; + private readonly Lazy _propertyEditors; + + public ContentBlocksDataEditor( + IContentService contentService, + IContentTypeService contentTypeService, + IDataTypeService dataTypeService, + Lazy propertyEditors) + { + _contentService = contentService; + _contentTypeService = contentTypeService; + _dataTypeService = dataTypeService; + _propertyEditors = propertyEditors; + } + + public string Alias => DataEditorAlias; + + public EditorType Type => EditorType.PropertyValue; + + public string Name => DataEditorName; + + public string Icon => DataEditorIcon; + + public string Group => Core.Constants.PropertyEditors.Groups.RichContent; + + public bool IsDeprecated => false; + + public IDictionary DefaultConfiguration => default; + + public IPropertyIndexValueFactory PropertyIndexValueFactory => new DefaultPropertyIndexValueFactory(); + + public IConfigurationEditor GetConfigurationEditor() => new ContentBlocksConfigurationEditor(_contentService, _contentTypeService); + + public IDataValueEditor GetValueEditor() + { + return new ContentBlocksDataValueEditor(_contentTypeService, _dataTypeService, _propertyEditors.Value) + { + ValueType = ValueTypes.Json, + View = DataEditorViewPath, + }; + } + + public IDataValueEditor GetValueEditor(object configuration) + { + var hideLabel = false; + var view = DataEditorViewPath; + + if (configuration is Dictionary config) + { + if (config.ContainsKey(HideLabelConfigurationField.HideLabelAlias)) + { + hideLabel = config[HideLabelConfigurationField.HideLabelAlias].TryConvertTo().Result; + } + + if (config.TryGetValueAs(ContentBlocksDisplayModeConfigurationField.DisplayMode, out string displayMode)) + { + view = displayMode; + } + } + + return new ContentBlocksDataValueEditor(_contentTypeService, _dataTypeService, _propertyEditors.Value) + { + Configuration = configuration, + HideLabel = hideLabel, + ValueType = ValueTypes.Json, + View = view, + }; + } + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs new file mode 100644 index 00000000..a297f55b --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs @@ -0,0 +1,163 @@ +/* Copyright © 2014 Umbrella Inc, Our Umbraco and other contributors. + * This Source Code has been derived from Nested Content. + * https://github.com/umco/umbraco-nested-content/blob/0.5.0/src/Our.Umbraco.NestedContent/PropertyEditors/NestedContentPropertyEditor.cs + * Including derivations made in Umbraco CMS for v8. Copyright © 2013-present Umbraco. + * https://github.com/umbraco/Umbraco-CMS/blob/release-8.4.0/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs + * Modified under the permissions of the MIT License. + * Modifications are licensed under the Mozilla Public License. + * Copyright © 2019 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Editors; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; + +namespace Umbraco.Community.Contentment.DataEditors +{ + internal sealed class ContentBlocksDataValueEditor : DataValueEditor + { + private readonly IDataTypeService _dataTypeService; + private readonly Lazy> _elementTypes; + private readonly PropertyEditorCollection _propertyEditors; + + public ContentBlocksDataValueEditor( + IContentTypeService contentTypeService, + IDataTypeService dataTypeService, + PropertyEditorCollection propertyEditors) + : base() + { + _dataTypeService = dataTypeService; + _elementTypes = new Lazy>(() => contentTypeService.GetAllElementTypes().ToDictionary(x => x.Key)); + _propertyEditors = propertyEditors; + } + + public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) + { + var value = property.GetValue(culture, segment)?.ToString(); + if (string.IsNullOrWhiteSpace(value)) + { + return base.ToEditor(property, dataTypeService, culture, segment); + } + + // TODO: [LK] Could check if the value has come from NestedContent or StackedContent? + // ncContentTypeAlias, icContentTypeAlias + // Could use a custom JsonConverter? a la: https://stackoverflow.com/a/43714309/12787 + // or https://www.jerriepelser.com/blog/deserialize-different-json-object-same-class/ + + var blocks = JsonConvert.DeserializeObject>(value); + if (blocks == null) + { + return base.ToEditor(property, dataTypeService, culture, segment); + } + + foreach (var block in blocks) + { + var elementType = _elementTypes.Value.ContainsKey(block.ElementType) + ? _elementTypes.Value[block.ElementType] + : null; + + if (elementType == null) + { + continue; + } + + var keys = new List(block.Value.Keys); + foreach (var key in keys) + { + var propertyType = elementType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(key)); + if (propertyType == null) + { + continue; + } + + propertyType.Variations = ContentVariation.Nothing; + + var fakeProperty = new Property(propertyType); + fakeProperty.SetValue(block.Value[key]); + + var propertyEditor = _propertyEditors[propertyType.PropertyEditorAlias]; + if (propertyEditor == null) + { + block.Value[key] = fakeProperty.GetValue()?.ToString(); + continue; + } + + var convertedValue = propertyEditor.GetValueEditor()?.ToEditor(fakeProperty, dataTypeService); + + block.Value[key] = convertedValue != null + ? JToken.FromObject(convertedValue) + : null; + } + } + + return blocks; + } + + public override object FromEditor(ContentPropertyData editorValue, object currentValue) + { + var value = editorValue?.Value?.ToString(); + if (string.IsNullOrWhiteSpace(value)) + { + return base.FromEditor(editorValue, currentValue); + } + + var blocks = JsonConvert.DeserializeObject>(value); + if (blocks == null) + { + return base.FromEditor(editorValue, currentValue); + } + + foreach (var block in blocks) + { + var elementType = _elementTypes.Value.ContainsKey(block.ElementType) + ? _elementTypes.Value[block.ElementType] + : null; + + if (elementType == null) + { + continue; + } + + var keys = new List(block.Value.Keys); + foreach (var key in keys) + { + var propertyType = elementType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(key)); + if (propertyType == null) + { + continue; + } + + var propertyEditor = _propertyEditors[propertyType.PropertyEditorAlias]; + if (propertyEditor == null) + { + continue; + } + + var configuration = _dataTypeService.GetDataType(propertyType.DataTypeId).Configuration; + var contentPropertyData = new ContentPropertyData(block.Value[key], configuration) + { + ContentKey = block.Key, + PropertyTypeKey = propertyType.Key, + Files = new ContentPropertyFile[0] + }; + var convertedValue = propertyEditor.GetValueEditor(configuration)?.FromEditor(contentPropertyData, block.Value[key]); + + block.Value[key] = convertedValue != null + ? JToken.FromObject(convertedValue) + : null; + } + } + + return JsonConvert.SerializeObject(blocks); + } + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDisplayModeConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDisplayModeConfigurationField.cs new file mode 100644 index 00000000..0027bef4 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDisplayModeConfigurationField.cs @@ -0,0 +1,37 @@ +/* Copyright © 2019 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System.Collections.Generic; +using Umbraco.Core.IO; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Community.Contentment.DataEditors +{ + internal class ContentBlocksDisplayModeConfigurationField : ConfigurationField + { + public const string DisplayMode = "displayMode"; + public const string Blocks = ContentBlocksDataEditor.DataEditorBlocksViewPath; + public const string List = ContentBlocksDataEditor.DataEditorListViewPath; + + public ContentBlocksDisplayModeConfigurationField(string defaultValue = Blocks) + : base() + { + Key = DisplayMode; + Name = "Display mode?"; + Description = "Select to display the elements in a list or as stacked blocks."; + View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath); + Config = new Dictionary + { + { Constants.Conventions.ConfigurationFieldAliases.Items, new DataListItem[] + { + new DataListItem { Name = nameof(Blocks), Value = Blocks, Description = "This will display as stacked blocks." }, + new DataListItem { Name = nameof(List), Value = List, Description = "This will display similar to a content picker." }, + } + }, + { Constants.Conventions.ConfigurationFieldAliases.DefaultValue, defaultValue } + }; + } + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs new file mode 100644 index 00000000..7d6bbca5 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs @@ -0,0 +1,77 @@ +/* Copyright © 2019 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Community.Contentment.DataEditors +{ + internal sealed class ContentBlocksTypesConfigurationField : ConfigurationField + { + internal const string ContentBlockTypes = "contentBlockTypes"; + + public ContentBlocksTypesConfigurationField(IEnumerable elementTypes) + { + var fields = new[] + { + new ConfigurationField + { + Key = "elementType", + Name = "Element type", + View = IOHelper.ResolveUrl(Constants.Internals.EditorsPathRoot + "readonly-node-preview.html"), + HideLabel = true, + }, + new ConfigurationField + { + Key = "nameTemplate", + Name = "Name template", + View = "textstring", + Description = "Enter an AngularJS expression to evaluate against each block for its name." + }, + new OverlaySizeConfigurationField() + }; + + var items = elementTypes + .OrderBy(x => x.Name) + .Select(x => new ConfigurationEditorModel + { + Key = x.Key.ToString(), + Name = x.Name, + Description = string.IsNullOrWhiteSpace(x.Description) == false ? x.Description : x.Alias, + Icon = x.Icon, + DefaultValues = new Dictionary + { + { "elementType", new DataListItem + { + Name = x.Name, + Description = x.GetUdi().ToString(), + Icon = x.Icon + } + }, + { "nameTemplate", $"{x.Name} {{{{ $index }}}}" }, + }, + Fields = fields + }); + + Key = ContentBlockTypes; + Name = "Block types"; + Description = "Configure the block types to use."; + View = IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorViewPath); + Config = new Dictionary + { + { AllowDuplicatesConfigurationField.AllowDuplicates, Constants.Values.False }, + { EnableFilterConfigurationField.EnableFilter, Constants.Values.True }, + { ConfigurationEditorConfigurationEditor.OverlayView, IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, + { ConfigurationEditorConfigurationEditor.Items, items }, + { OverlaySizeConfigurationField.OverlaySize, OverlaySizeConfigurationField.Small }, + { EnableDevModeConfigurationField.EnableDevMode, Constants.Values.True }, + }; + } + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs new file mode 100644 index 00000000..061e70a5 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs @@ -0,0 +1,90 @@ +/* Copyright © 2019 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Umbraco.Community.Contentment.Web.PublishedCache; +using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Web.PublishedCache; + +namespace Umbraco.Community.Contentment.DataEditors +{ + [Core.Composing.HideFromTypeFinder] + public sealed class ContentBlocksValueConverter : PropertyValueConverterBase + { + private readonly IContentTypeService _contentTypeService; + private readonly IPublishedModelFactory _publishedModelFactory; + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + + public ContentBlocksValueConverter( + IContentTypeService contentTypeService, + IPublishedModelFactory publishedModelFactory, + IPublishedSnapshotAccessor publishedSnapshotAccessor) + : base() + { + _contentTypeService = contentTypeService; + _publishedModelFactory = publishedModelFactory; + _publishedSnapshotAccessor = publishedSnapshotAccessor; + } + + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(ContentBlocksDataEditor.DataEditorAlias); + + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof(IEnumerable); + + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) + { + if (source is string value) + { + return JsonConvert.DeserializeObject>(value); + } + + return base.ConvertSourceToIntermediate(owner, propertyType, source, preview); + } + + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + if (inter is IEnumerable items) + { + var elements = new List(); + + foreach (var item in items) + { + if (item == null || item.ElementType.Equals(Guid.Empty)) + continue; + + // NOTE: [LK:2019-09-03] Why `IPublishedCache` doesn't support Guids or UDIs, I do not know!? + // Thought v8 was meant to be "GUID ALL THE THINGS!!1"? ¯\_(ツ)_/¯ + if (ContentTypeCacheHelper.TryGetAlias(item.ElementType, out var alias, _contentTypeService) == false) + continue; + + var contentType = _publishedSnapshotAccessor.PublishedSnapshot.Content.GetContentType(alias); + if (contentType == null || contentType.IsElement == false) + continue; + + var properties = new List(); + + foreach (var thing in item.Value) + { + var propType = contentType.GetPropertyType(thing.Key); + if (propType != null) + { + properties.Add(new DetachedPublishedProperty(propType, owner, thing.Value, preview)); + } + } + + elements.Add(_publishedModelFactory.CreateModel(new DetachedPublishedElement(item.Key, contentType, properties))); + } + + return elements; + } + + return base.ConvertIntermediateToObject(owner, propertyType, referenceCacheLevel, inter, preview); + } + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs new file mode 100644 index 00000000..c186d771 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs @@ -0,0 +1,102 @@ +/* Copyright © 2016 UMCO, Our Umbraco and other contributors. + * This Source Code has been derived from Inner Content. + * https://github.com/umco/umbraco-inner-content/blob/2.0.4/src/Our.Umbraco.InnerContent/Helpers/ContentTypeCacheHelper.cs + * Modified under the permissions of the MIT License. + * Modifications are licensed under the Mozilla Public License. + * Copyright © 2019 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Services; + +namespace Umbraco.Community.Contentment.DataEditors +{ + internal static class ContentTypeCacheHelper + { + private static readonly ConcurrentDictionary _forward = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary _reverse = new ConcurrentDictionary(); + + public static void ClearAll() + { + _forward.Clear(); + _reverse.Clear(); + } + + public static void TryAdd(IContentType contentType) + { + TryAdd(contentType.Key, contentType.Alias); + } + + public static void TryAdd(Guid guid, string alias) + { + _forward.TryAdd(guid, alias); + _reverse.TryAdd(alias, guid); + } + + public static bool TryGetAlias(Guid key, out string alias, IContentTypeService contentTypeService = null) + { + if (_forward.TryGetValue(key, out alias)) + return true; + + // The alias isn't cached, we can attempt to get it via the content-type service, using the GUID. + if (contentTypeService != null) + { + var contentType = contentTypeService.Get(key); + if (contentType != null) + { + TryAdd(contentType); + alias = contentType.Alias; + return true; + } + } + + return false; + } + + public static bool TryGetGuid(string alias, out Guid key, IContentTypeService contentTypeService = null) + { + if (_reverse.TryGetValue(alias, out key)) + return true; + + // The GUID isn't cached, we can attempt to get it via the content-type service, using the alias. + if (contentTypeService != null) + { + var contentType = contentTypeService.Get(alias); + if (contentType != null) + { + TryAdd(contentType); + key = contentType.Key; + return true; + } + } + + return false; + } + + public static void TryRemove(IContentType contentType) + { + if (TryRemove(contentType.Alias) == false) + { + TryRemove(contentType.Key); + } + } + + public static bool TryRemove(Guid guid) + { + return _forward.TryRemove(guid, out var alias) + ? _reverse.TryRemove(alias, out _) + : false; + } + + public static bool TryRemove(string alias) + { + return _reverse.TryRemove(alias, out var guid) + ? _forward.TryRemove(guid, out _) + : false; + } + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.blueprint.html b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.blueprint.html new file mode 100644 index 00000000..39d5dfa5 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.blueprint.html @@ -0,0 +1,25 @@ + + +
+ +

+ +
+ +
+ + + + + +
diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.css b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.css new file mode 100644 index 00000000..b0c39243 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.css @@ -0,0 +1,83 @@ +/* Copyright © 2019 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +.contentment.lk-content-blocks .umb-group-builder__properties { + padding: 0; + min-height: inherit; + max-width: 845px; +} + +.contentment.lk-content-blocks .umb-group-builder__property { + border-bottom: none; +} + + .contentment.lk-content-blocks .umb-group-builder__property:last-of-type { + margin-bottom: 0; + } + +.contentment.lk-content-blocks .umb-group-builder__property-actions { + margin-top: 10px; +} + +.contentment.lk-content-blocks .umb-group-builder__property-action { + margin-top: 0; + margin-bottom: 0; +} + + .contentment.lk-content-blocks .umb-group-builder__property-action > i.icon { + font-size: 18px; + padding: 5px 10px; + } + + .contentment.lk-content-blocks .umb-group-builder__property-action > .icon { + color: #1b264f; + } + + .contentment.lk-content-blocks .umb-group-builder__property-action > .icon:hover { + color: #2152a3; + } + + .contentment.lk-content-blocks .umb-group-builder__property-action .umb-property-actions { + float: none; + } + + .contentment.lk-content-blocks .umb-group-builder__property-action .umb-property-actions__toggle { + margin-top: 5px; + margin-left: 12px; + opacity: 1; + background-color: transparent; + } + + .contentment.lk-content-blocks .umb-group-builder__property-action .umb-property-actions__toggle:hover { + background-color: #f9f6f5; + } + + .contentment.lk-content-blocks .umb-group-builder__property-action .umb-property-actions__menu { + margin-left: 12px; + } + +.contentment.lk-content-blocks .umb-group-builder__property-preview { + height: auto; + padding: 30px 10px 5px; +} + +.contentment.lk-content-blocks .lk-content-blocks-preview-default { + text-align: center; + padding-top: 5px; + padding-bottom: 20px; +} + + .contentment.lk-content-blocks .lk-content-blocks-preview-default .icon { + font-size: 40px; + } + + .contentment.lk-content-blocks .lk-content-blocks-preview-default > div { + font-size: 14px; + font-weight: bold; + } + +.contentment.lk-content-blocks .umb-node-preview-add { + margin-right: 45px; +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html new file mode 100644 index 00000000..eb2cb763 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html @@ -0,0 +1,49 @@ + + +
+
    +
  • +
    + +
    +
    +
    +
    +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + +
    +
  • +
+ +
diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js new file mode 100644 index 00000000..93293373 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js @@ -0,0 +1,342 @@ +/* Copyright © 2019 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors.ContentBlocks.Controller", [ + "$interpolate", + "$scope", + "clipboardService", + "contentResource", + "editorService", + "localizationService", + "notificationsService", + "overlayService", + "Umbraco.Community.Contentment.Services.DevMode", + function ($interpolate, $scope, clipboardService, contentResource, editorService, localizationService, notificationsService, overlayService, devModeService) { + + // console.log("content-blocks.model", $scope.model); + + var defaultConfig = { + allowCopy: 1, + allowEdit: 1, + allowRemove: 1, + disableSorting: 0, + contentBlockTypes: [], + enableFilter: 0, + maxItems: 0, + overlayView: "", + enableDevMode: 0, + }; + var config = Object.assign({}, defaultConfig, $scope.model.config); + + var vm = this; + + function init() { + + $scope.model.value = $scope.model.value || []; + + if ($scope.model.value === "") { + $scope.model.value = []; + } + + if (Array.isArray($scope.model.value) === false) { + $scope.model.value = [$scope.model.value]; + } + + config.elementTypeLookup = {}; + config.nameTemplates = {}; + + config.contentBlockTypes.forEach(function (blockType) { + config.elementTypeLookup[blockType.key] = blockType; + config.nameTemplates[blockType.key] = $interpolate(blockType.nameTemplate || "Item {{ $index }}"); + }); + + vm.allowAdd = (config.maxItems === 0 || config.maxItems === "0") || $scope.model.value.length < config.maxItems; + vm.allowCopy = Object.toBoolean(config.allowCopy) && clipboardService.isSupported(); + vm.allowEdit = Object.toBoolean(config.allowEdit); + vm.allowRemove = Object.toBoolean(config.allowRemove); + vm.sortable = Object.toBoolean(config.disableSorting) === false && (config.maxItems !== 1 && config.maxItems !== "1"); + + vm.sortableOptions = { + axis: "y", + containment: "parent", + cursor: "move", + disabled: vm.sortable === false, + opacity: 0.7, + scroll: true, + tolerance: "pointer", + stop: function (e, ui) { + setDirty(); + } + }; + + vm.add = add; + vm.copy = copy; + vm.edit = edit; + vm.remove = remove; + vm.populateName = populateName; + + vm.blockActions = []; + + for (var i = 0; i < $scope.model.value.length; i++) { + vm.blockActions.push(actionsFactory(i)); + } + + if ($scope.umbProperty) { + + var propertyActions = []; + + if (vm.allowCopy) { + propertyActions.push({ + labelKey: "contentment_copyAllBlocks", + icon: "documents", + method: function () { + for (var i = 0; i < $scope.model.value.length; i++) { + copy(i); + } + } + }); + } + + if (Object.toBoolean(config.enableDevMode)) { + propertyActions.push({ + labelKey: "contentment_editRawValue", + icon: "brackets", + method: function () { + devModeService.editValue($scope.model, function () { + // TODO: [LK:2020-01-02] Ensure that the edits are valid! e.g. check min/max items, elementType GUIDs, etc. + }); + } + }); + } + + if (propertyActions.length > 0) { + $scope.umbProperty.setPropertyActions(propertyActions); + } + } + }; + + function actionsFactory($index) { + var actions = []; + + if (vm.allowCopy) { + actions.push({ + labelKey: "contentment_copyContentBlock", + icon: "documents", + method: function () { + copy($index); + } + }); + } + + actions.push({ + labelKey: "contentment_createContentTemplate", + icon: "blueprint", + method: function () { + saveBlueprint($index); + } + }); + + return actions; + }; + + function add() { + editorService.open({ + config: { + elementTypes: config.contentBlockTypes, + enableFilter: config.enableFilter + }, + size: config.contentBlockTypes.length === 1 ? config.contentBlockTypes[0].overlaySize : "small", + value: null, + view: config.overlayView, + submit: function (model) { + + $scope.model.value.push(model); + + vm.blockActions.push(actionsFactory($scope.model.value.length - 1)); + + if ((config.maxItems !== 0 && config.maxItems !== "0") && $scope.model.value.length >= config.maxItems) { + vm.allowAdd = false; + } + + setDirty(); + + editorService.close(); + }, + close: function () { + editorService.close(); + } + }); + }; + + function copy($index) { + + var tmp = $scope.model.value[$index]; + var item = Object.assign({}, tmp, { name: populateName(tmp, $index) }); + + clipboardService.copy("contentment.element", item.elementType, item); + }; + + function edit($index) { + + var item = $scope.model.value[$index]; + var elementType = config.elementTypeLookup[item.elementType]; + + editorService.open({ + config: { + elementType: elementType + }, + size: elementType.overlaySize, + value: item, + view: config.overlayView, + submit: function (model) { + + $scope.model.value[$index] = model; + + setDirty(); + + editorService.close(); + }, + close: function () { + editorService.close(); + } + }); + }; + + function populateName(item, $index) { + + var name = ""; + + var expression = config.nameTemplates[item.elementType]; + + if (expression) { + + item.value.$index = $index + 1; + name = expression(item.value); + delete item.value.$index; + + } else { + + name = config.elementTypeLookup[item.elementType].name; + + } + + return name; + }; + + function remove($index) { + var keys = ["content_nestedContentDeleteItem", "general_delete", "general_cancel", "contentTypeEditor_yesDelete"]; + localizationService.localizeMany(keys).then(function (data) { + overlayService.open({ + title: data[1], + content: data[0], + closeButtonLabel: data[2], + submitButtonLabel: data[3], + submitButtonStyle: "danger", + submit: function () { + + $scope.model.value.splice($index, 1); + vm.blockActions.pop(); + + if ((config.maxItems === 0 || config.maxItems === "0") || $scope.model.value.length < config.maxItems) { + vm.allowAdd = true; + } + + setDirty(); + + overlayService.close(); + }, + close: function () { + overlayService.close(); + } + }); + }); + }; + + function saveBlueprint($index) { + var keys = [ + "blueprints_createBlueprintFrom", + "blueprints_blueprintDescription", + "blueprints_createdBlueprintHeading", + "blueprints_createdBlueprintMessage", + "general_cancel", + "general_create" + ]; + + localizationService.localizeMany(keys).then(function (labels) { + + var item = $scope.model.value[$index]; + var itemName = populateName(item, $index); + var elementType = config.elementTypeLookup[item.elementType]; + + overlayService.open({ + disableBackdropClick: true, + title: localizationService.tokenReplace(labels[0], [itemName]), + description: labels[1], + blueprintName: itemName, + view: "/App_Plugins/Contentment/editors/content-blocks.blueprint.html", + closeButtonLabel: labels[4], + submitButtonLabel: labels[5], + submitButtonStyle: "action", + submit: function (model) { + + delete model.error; + model.submitButtonState = "busy"; + + var variant = { + save: true, + name: model.blueprintName, + tabs: [{ + properties: _.map(_.pairs(item.value), function (x) { // TODO: Replace Underscore.js dependency. [LK:2020-03-02] + return { id: 0, alias: x[0], value: x[1] }; + }) + }] + }; + + var content = { + action: "saveNew", + id: 0, + parentId: -1, + contentTypeAlias: elementType.alias, + expireDate: null, + releaseDate: null, + templateAlias: null, + variants: [Object.assign({}, variant, { save: true })] + }; + + contentResource + .saveBlueprint(content, true, [], false) + .then(function (data) { + + model.submitButtonState = "success"; + + notificationsService.success(labels[2], localizationService.tokenReplace(labels[3], [itemName])); + + elementType.blueprints.push({ id: data.id, name: data.variants[0].name }); + + overlayService.close(); + + }, function (error) { + + model.submitButtonState = "error"; + model.error = error.data.ModelState.Name[0]; + + }); + }, + close: function () { + overlayService.close(); + } + }); + }); + }; + + function setDirty() { + if ($scope.propertyForm) { + $scope.propertyForm.$setDirty(); + } + }; + + init(); + } +]); diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html new file mode 100644 index 00000000..8903c76d --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html @@ -0,0 +1,132 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ + + +
+ + +

There are no items available to add.

+
+ +
+ + + + + + + + + + + + + + + + + +
+ +
diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.js b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.js new file mode 100644 index 00000000..52fd29be --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.js @@ -0,0 +1,204 @@ +/* Copyright © 2019 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +angular.module("umbraco").controller("Umbraco.Community.Contentment.Overlays.ContentBlocks.Controller", [ + "$scope", + "blueprintConfig", + "clipboardService", + "contentResource", + function ($scope, blueprintConfig, clipboardService, contentResource) { + + // console.log("content-blocks-overlay.model", $scope.model, blueprintConfig); + + var defaultConfig = { + elementType: null, + elementTypes: [], + enableFilter: true, + }; + var config = Object.assign({}, defaultConfig, $scope.model.config); + + var vm = this; + + function init() { + + vm.submit = submit; + vm.close = close; + + if (config.elementType && $scope.model.value) { + + edit(config.elementType, $scope.model.value); + + } else { + + vm.mode = "select"; + vm.items = config.elementTypes; + vm.selectedElementType = null; + + vm.clipboardItems = clipboardService.retriveDataOfType("contentment.element", config.elementTypes.map(function (x) { return x.key })); + + if (config.elementTypes.length > 1 || vm.clipboardItems.length > 0) { + + vm.title = "Add content"; + vm.description = "Select a content type..."; + vm.selectBlueprint = false; + vm.enableFilter = Object.toBoolean(config.enableFilter); + + vm.select = select; + vm.paste = paste; + + vm.clearClipboard = clearClipboard; + vm.prompt = false; + vm.showPrompt = showPrompt; + vm.hidePrompt = hidePrompt; + + } else if (config.elementTypes.length === 1) { + + select(config.elementTypes[0]); + + } + } + }; + + function clearClipboard() { + vm.clipboardItems = []; + clipboardService.clearEntriesOfType("contentment.element", config.elementTypes.map(function (x) { return x.key })); + }; + + function showPrompt() { + vm.prompt = true; + }; + + function hidePrompt() { + vm.prompt = false; + }; + + function select(elementType) { + if (elementType.blueprints && elementType.blueprints.length > 0) { + if (elementType.blueprints.length === 1 && blueprintConfig.skipSelect) { + create(elementType, elementType.blueprints[0]); + } + else { + vm.title = "Add content"; + vm.description = "Select a content blueprint..."; + vm.selectBlueprint = true; + vm.selectedElementType = elementType; + vm.blueprintAllowBlank = blueprintConfig.allowBlank; + vm.create = create; + } + } else { + create(elementType); + } + }; + + function create(elementType, blueprint) { + + $scope.model.size = elementType.overlaySize; + + vm.mode = "edit"; + vm.title = "Edit content"; + vm.description = elementType.name; + vm.loading = true; + + vm.content = { + elementType: elementType.key, + icon: elementType.icon, + key: String.CreateGuid() + }; + + var getScaffold = blueprint && blueprint.id > 0 + ? contentResource.getBlueprintScaffold(-2, blueprint.id) + : contentResource.getScaffold(-2, elementType.alias); + + getScaffold.then(function (data) { + Object.assign(vm.content, data.variants[0]); + vm.loading = false; + }); + + }; + + function paste(element) { + + var elementType = _.find(config.elementTypes, function (x) { // TODO: Replace Underscore.js dependency. [LK:2020-03-02] + return x.key === element.elementType; + }); + + $scope.model.size = elementType.overlaySize; + + element.key = String.CreateGuid(); + + edit(elementType, element); + }; + + function edit(elementType, element) { + + vm.mode = "edit"; + vm.title = "Edit content"; + vm.description = elementType.name; + vm.loading = true; + + vm.content = { + elementType: elementType.key, + icon: elementType.icon, + key: element.key + }; + + contentResource.getScaffold(-2, elementType.alias).then(function (data) { + + if (element.value) { + for (var t = 0; t < data.variants[0].tabs.length; t++) { + var tab = data.variants[0].tabs[t]; + for (var p = 0; p < tab.properties.length; p++) { + var property = tab.properties[p]; + if (element.value.hasOwnProperty(property.alias)) { + property.value = element.value[property.alias]; + } + } + } + } + + Object.assign(vm.content, data.variants[0]); + vm.loading = false; + }); + + }; + + function submit() { + + if ($scope.model.submit) { + + $scope.$broadcast("formSubmitting", { scope: $scope }); + + var item = { + elementType: vm.content.elementType, + key: vm.content.key, + icon: vm.content.icon, + value: {}, + }; + + if (vm.content.tabs.length > 0) { + for (var t = 0; t < vm.content.tabs.length; t++) { + var tab = vm.content.tabs[t]; + for (var p = 0; p < tab.properties.length; p++) { + var property = tab.properties[p]; + if (typeof property.value !== "function") { + item.value[property.alias] = property.value; + } + } + } + } + + $scope.model.submit(item); + } + }; + + function close() { + if ($scope.model.close) { + $scope.model.close(); + } + }; + + init(); + } +]); diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-list.html b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-list.html new file mode 100644 index 00000000..f8532d80 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-list.html @@ -0,0 +1,29 @@ + + +
+
+ + +
+ +
diff --git a/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/README.md b/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/README.md index a5d71382..5df1434a 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/README.md +++ b/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/README.md @@ -4,4 +4,5 @@ This is more of a container for read-only functionality. The files in this component are used by... +- "readonly-node-preview.html" is used by Content Blocks' block types configuration editor - `ReadOnlyDataValueEditor` is used by both Notes and Render Macro value editors diff --git a/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/readonly-node-preview.html b/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/readonly-node-preview.html new file mode 100644 index 00000000..6de0c3ad --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ReadOnly/readonly-node-preview.html @@ -0,0 +1,8 @@ + + +
+ +
diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index 69af9962..0a6ddc68 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -299,6 +299,7 @@ + @@ -338,6 +339,14 @@ + + + + + + + + @@ -399,10 +408,17 @@ + + + + + + + @@ -421,6 +437,7 @@ + From 9644c0d64da78f6db8ab0f9e51801033de9e1cf7 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 28 May 2020 17:50:19 +0100 Subject: [PATCH 03/86] Added the fake `IPublishedElement` back in for v1.1.0 This reverts commit 0a11c025d88eca8b4d66f68221565156853fa641. --- .../Umbraco.Community.Contentment.csproj | 2 + .../DetachedPublishedElement.cs | 44 ++++++++++++++++ .../DetachedPublishedProperty.cs | 51 +++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedElement.cs create mode 100644 src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedProperty.cs diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index 0a6ddc68..fd1ee76a 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -374,6 +374,8 @@ + + diff --git a/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedElement.cs b/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedElement.cs new file mode 100644 index 00000000..cd74a644 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedElement.cs @@ -0,0 +1,44 @@ +/* Copyright © 2016 UMCO, Our Umbraco and other contributors. + * This Source Code has been derived from Inner Content. + * https://github.com/umco/umbraco-inner-content/blob/2.0.4/src/Our.Umbraco.InnerContent/Models/DetachedPublishedContent.cs + * Modified under the permissions of the MIT License. + * Modifications are licensed under the Mozilla Public License. + * Copyright © 2019 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Community.Contentment.Web.PublishedCache +{ + internal sealed class DetachedPublishedElement : IPublishedElement + { + private readonly Dictionary _propertyLookup; + + public DetachedPublishedElement(Guid key, IPublishedContentType contentType, IEnumerable properties) + { + Key = key; + ContentType = contentType; + Properties = properties; + + _propertyLookup = properties.ToDictionary(x => x.Alias, StringComparer.OrdinalIgnoreCase); + } + + public IPublishedContentType ContentType { get; } + + public Guid Key { get; } + + public IEnumerable Properties { get; } + + public IPublishedProperty GetProperty(string alias) + { + return _propertyLookup.ContainsKey(alias) + ? _propertyLookup[alias] + : null; + } + } +} diff --git a/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedProperty.cs b/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedProperty.cs new file mode 100644 index 00000000..cad42944 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Web/PublishedCache/DetachedPublishedProperty.cs @@ -0,0 +1,51 @@ +/* Copyright © 2016 UMCO, Our Umbraco and other contributors. + * This Source Code has been derived from Inner Content. + * https://github.com/umco/umbraco-inner-content/blob/2.0.4/src/Our.Umbraco.InnerContent/Models/DetachedPublishedProperty.cs + * Modified under the permissions of the MIT License. + * Modifications are licensed under the Mozilla Public License. + * Copyright © 2019 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Community.Contentment.Web.PublishedCache +{ + internal sealed class DetachedPublishedProperty : IPublishedProperty + { + private readonly object _sourceValue; + private readonly Lazy _interValue; + private readonly Lazy _objectValue; + private readonly Lazy _xpathValue; + + public DetachedPublishedProperty(IPublishedPropertyType propertyType, IPublishedElement owner, object value) + : this(propertyType, owner, value, false) + { } + + public DetachedPublishedProperty(IPublishedPropertyType propertyType, IPublishedElement owner, object value, bool preview) + { + PropertyType = propertyType; + + _sourceValue = value; + + _interValue = new Lazy(() => PropertyType.ConvertSourceToInter(owner, _sourceValue, preview)); + _objectValue = new Lazy(() => PropertyType.ConvertInterToObject(owner, PropertyCacheLevel.Unknown, _interValue.Value, preview)); + _xpathValue = new Lazy(() => PropertyType.ConvertInterToXPath(owner, PropertyCacheLevel.Unknown, _interValue.Value, preview)); + } + + public IPublishedPropertyType PropertyType { get; } + + public string Alias => PropertyType.Alias; + + public object GetSourceValue(string culture = null, string segment = null) => _sourceValue; + + public object GetValue(string culture = null, string segment = null) => _objectValue.Value; + + public object GetXPathValue(string culture = null, string segment = null) => _xpathValue.Value; + + public bool HasValue(string culture = null, string segment = null) => _sourceValue != null && _sourceValue.ToString().Trim().Length > 0; + } +} From cf7966a8773258114fcde59d8414309256a18bde Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 28 May 2020 17:52:13 +0100 Subject: [PATCH 04/86] Added `IPublishedContent` JSON serializer" back in for v1.1.0 This reverts commit bac55256d2da81b336fcd765cbb011c429f7cfb5. --- .../Umbraco.Community.Contentment.csproj | 1 + .../PublishedContentContractResolver.cs | 77 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index fd1ee76a..1a408227 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -374,6 +374,7 @@ + diff --git a/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs b/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs new file mode 100644 index 00000000..33175758 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs @@ -0,0 +1,77 @@ +/* Copyright © 2019 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +/* This code has been derived from Rasmus Fjord's code, supplied on an + * Our Umbraco forum post: + * https://our.umbraco.com/forum/umbraco-8/98381-serializing-an-publishedcontentmodel-modelsbuilder-model-in-v8#comment-310148 + * From searching GitHub, I also found René Pjengaard's code: + * https://github.com/rpjengaard/merchelloshop/blob/master/dev/code/Json/Resolvers/PublishedContentContractResolver.cs + * It is unknown (to me) who the original author is, and how the code + * has been licensed. I'll assume it's available under MIT license. */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Community.Contentment.Web.Serialization +{ + public sealed class PublishedContentContractResolver : CamelCasePropertyNamesContractResolver + { + public static readonly PublishedContentContractResolver Instance = new PublishedContentContractResolver(); + + private readonly Dictionary _converterLookup; + private readonly HashSet _ignoreFromContent; + private readonly HashSet _ignoreFromProperty; + + public PublishedContentContractResolver() + { + _converterLookup = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { nameof(IPublishedContent.ItemType), new StringEnumConverter() }, + }; + + _ignoreFromContent = new HashSet(StringComparer.OrdinalIgnoreCase) + { + nameof(IPublishedContent.Children), + nameof(IPublishedContent.ChildrenForAllCultures), + nameof(IPublishedContent.ContentType), + nameof(IPublishedContent.CreatorId), + nameof(IPublishedContent.Cultures), + nameof(IPublishedContent.Parent), + nameof(IPublishedContent.TemplateId), + nameof(IPublishedContent.WriterId), + }; + + _ignoreFromProperty = new HashSet(StringComparer.OrdinalIgnoreCase) + { + nameof(IPublishedProperty.PropertyType), + }; + } + + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + + if (typeof(IPublishedContent).IsAssignableFrom(member.DeclaringType)) + { + property.ShouldSerialize = _ => _ignoreFromContent.Contains(property.PropertyName) == false; + } + else if (typeof(IPublishedProperty).IsAssignableFrom(member.DeclaringType)) + { + property.ShouldSerialize = _ => _ignoreFromProperty.Contains(property.PropertyName) == false; + } + + if (_converterLookup.ContainsKey(property.PropertyName)) + { + property.Converter = _converterLookup[property.PropertyName]; + } + + return property; + } + } +} From 212680855ed4435fbd86ed703a91ba6bb51b9db9 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 28 May 2020 17:59:28 +0100 Subject: [PATCH 05/86] Content Blocks - enabled discoverability removed the `HideFromTypeFinder` attribute --- .../DataEditors/ContentBlocks/ContentBlocksDataEditor.cs | 1 - .../DataEditors/ContentBlocks/ContentBlocksValueConverter.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs index 5f8dc777..88cd94de 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs @@ -11,7 +11,6 @@ namespace Umbraco.Community.Contentment.DataEditors { - [Core.Composing.HideFromTypeFinder] public sealed class ContentBlocksDataEditor : IDataEditor { internal const string DataEditorAlias = Constants.Internals.DataEditorAliasPrefix + "ContentBlocks"; diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs index 061e70a5..faba7bc0 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs @@ -15,7 +15,6 @@ namespace Umbraco.Community.Contentment.DataEditors { - [Core.Composing.HideFromTypeFinder] public sealed class ContentBlocksValueConverter : PropertyValueConverterBase { private readonly IContentTypeService _contentTypeService; From 8cabb6c63cb654f34f92f823c8c647dd09459543 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 1 Jun 2020 17:39:12 +0100 Subject: [PATCH 06/86] Content Blocks - refactorings Due to rebasing on `dev/v1.0` branch --- ...tentBlocksDisplayModeConfigurationField.cs | 1 + .../ContentBlocksTypesConfigurationField.cs | 27 +++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDisplayModeConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDisplayModeConfigurationField.cs index 0027bef4..5765f008 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDisplayModeConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDisplayModeConfigurationField.cs @@ -30,6 +30,7 @@ public ContentBlocksDisplayModeConfigurationField(string defaultValue = Blocks) new DataListItem { Name = nameof(List), Value = List, Description = "This will display similar to a content picker." }, } }, + { ShowDescriptionsConfigurationField.ShowDescriptions, Constants.Values.True }, { Constants.Conventions.ConfigurationFieldAliases.DefaultValue, defaultValue } }; } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs index 7d6bbca5..30f287cf 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs @@ -34,7 +34,24 @@ public ContentBlocksTypesConfigurationField(IEnumerable elementTyp View = "textstring", Description = "Enter an AngularJS expression to evaluate against each block for its name." }, - new OverlaySizeConfigurationField() + new ConfigurationField + { + Key = "overlaySize", + Name = "Editor overlay size", + Description = "Select the size of the overlay editing panel. By default this is set to 'large'. However if the editor fields require a smaller panel, select 'small'.", + View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath), + Config = new Dictionary + { + { Constants.Conventions.ConfigurationFieldAliases.Items, new[] + { + new DataListItem { Name = "Small", Value = "small" }, + new DataListItem { Name = "Medium", Value = "medium" }, + new DataListItem { Name = "Large", Value = "large" } + } + }, + { Constants.Conventions.ConfigurationFieldAliases.DefaultValue, "small" } + } + } }; var items = elementTypes @@ -65,11 +82,11 @@ public ContentBlocksTypesConfigurationField(IEnumerable elementTyp View = IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorViewPath); Config = new Dictionary { - { AllowDuplicatesConfigurationField.AllowDuplicates, Constants.Values.False }, + { "allowDuplicates", Constants.Values.False }, { EnableFilterConfigurationField.EnableFilter, Constants.Values.True }, - { ConfigurationEditorConfigurationEditor.OverlayView, IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, - { ConfigurationEditorConfigurationEditor.Items, items }, - { OverlaySizeConfigurationField.OverlaySize, OverlaySizeConfigurationField.Small }, + { Constants.Conventions.ConfigurationFieldAliases.OverlayView, IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, + { Constants.Conventions.ConfigurationFieldAliases.Items, items }, + { "overlaySize", OverlaySize.Small }, { EnableDevModeConfigurationField.EnableDevMode, Constants.Values.True }, }; } From bf7e452a94dd3688a6417b84502eea0b461aa657 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 1 Jun 2020 17:39:53 +0100 Subject: [PATCH 07/86] Bumped version number for assembly I'd forgot to do it when I first branched off. --- src/Umbraco.Community.Contentment/Properties/VersionInfo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Properties/VersionInfo.cs b/src/Umbraco.Community.Contentment/Properties/VersionInfo.cs index 80a8628f..6be9931d 100644 --- a/src/Umbraco.Community.Contentment/Properties/VersionInfo.cs +++ b/src/Umbraco.Community.Contentment/Properties/VersionInfo.cs @@ -1,5 +1,5 @@ using System.Reflection; -[assembly: AssemblyVersion("1.0")] -[assembly: AssemblyFileVersion("1.0.0")] -[assembly: AssemblyInformationalVersion("1.0.0-develop")] +[assembly: AssemblyVersion("1.1")] +[assembly: AssemblyFileVersion("1.1.0")] +[assembly: AssemblyInformationalVersion("1.1.0-develop")] From b8e9ed8900f35a543178dc29973e6dadf1f7ba2d Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 1 Jun 2020 17:53:53 +0100 Subject: [PATCH 08/86] Content Blocks - set the OverlaySize to be on the item, not global. --- .../ContentBlocks/ContentBlocksTypesConfigurationField.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs index 30f287cf..2a85044b 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs @@ -73,7 +73,8 @@ public ContentBlocksTypesConfigurationField(IEnumerable elementTyp }, { "nameTemplate", $"{x.Name} {{{{ $index }}}}" }, }, - Fields = fields + Fields = fields, + OverlaySize = OverlaySize.Small }); Key = ContentBlockTypes; @@ -86,7 +87,6 @@ public ContentBlocksTypesConfigurationField(IEnumerable elementTyp { EnableFilterConfigurationField.EnableFilter, Constants.Values.True }, { Constants.Conventions.ConfigurationFieldAliases.OverlayView, IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, { Constants.Conventions.ConfigurationFieldAliases.Items, items }, - { "overlaySize", OverlaySize.Small }, { EnableDevModeConfigurationField.EnableDevMode, Constants.Values.True }, }; } From 2126f11afb94952fcc7b807ca4ca795b2e88a281 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 10 Jun 2020 17:03:27 +0100 Subject: [PATCH 09/86] Content Blocks - passes current page context to the overlay --- .../DataEditors/ContentBlocks/content-blocks.js | 9 ++++++--- .../DataEditors/ContentBlocks/content-blocks.overlay.js | 7 ++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js index 93293373..a0097c82 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js @@ -9,11 +9,12 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. "clipboardService", "contentResource", "editorService", + "editorState", "localizationService", "notificationsService", "overlayService", "Umbraco.Community.Contentment.Services.DevMode", - function ($interpolate, $scope, clipboardService, contentResource, editorService, localizationService, notificationsService, overlayService, devModeService) { + function ($interpolate, $scope, clipboardService, contentResource, editorService, editorState, localizationService, notificationsService, overlayService, devModeService) { // console.log("content-blocks.model", $scope.model); @@ -145,7 +146,8 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. editorService.open({ config: { elementTypes: config.contentBlockTypes, - enableFilter: config.enableFilter + enableFilter: config.enableFilter, + currentPageId: editorState.current.id, }, size: config.contentBlockTypes.length === 1 ? config.contentBlockTypes[0].overlaySize : "small", value: null, @@ -185,7 +187,8 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. editorService.open({ config: { - elementType: elementType + elementType: elementType, + currentPageId: editorState.current.id, }, size: elementType.overlaySize, value: item, diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.js b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.js index 52fd29be..acb29d96 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.js +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.js @@ -16,6 +16,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.Overlays.Con elementType: null, elementTypes: [], enableFilter: true, + currentPageId: -2, }; var config = Object.assign({}, defaultConfig, $scope.model.config); @@ -108,8 +109,8 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.Overlays.Con }; var getScaffold = blueprint && blueprint.id > 0 - ? contentResource.getBlueprintScaffold(-2, blueprint.id) - : contentResource.getScaffold(-2, elementType.alias); + ? contentResource.getBlueprintScaffold(config.currentPageId, blueprint.id) + : contentResource.getScaffold(config.currentPageId, elementType.alias); getScaffold.then(function (data) { Object.assign(vm.content, data.variants[0]); @@ -144,7 +145,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.Overlays.Con key: element.key }; - contentResource.getScaffold(-2, elementType.alias).then(function (data) { + contentResource.getScaffold(config.currentPageId, elementType.alias).then(function (data) { if (element.value) { for (var t = 0; t < data.variants[0].tabs.length; t++) { From d8d57fdaf7a44eb87e7f9b1a4520236f708e89e4 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 10 Jun 2020 17:06:18 +0100 Subject: [PATCH 10/86] DataList - adds Umbraco Content data-source Includes... - a custom lightweight version of Umbraco's "treesource" editor - a wrapper for Umbraco's internal `UmbracoXPathPathSyntaxParser` class - extends `ContentTypeCacheHelper` to retrieve a ContentType's icon (IPublishedContentType doesn't have the Icon property) - makes the Umbraco logo available to use as an icon --- .../Core/Xml/UmbracoXPathPathSyntaxParser.cs | 34 ++++++ .../ContentBlocks/ContentTypeCacheHelper.cs | 29 ++++- .../ContentPicker/ContentPickerDataEditor.cs | 12 ++ .../ContentPicker/content-source.html | 82 +++++++++++++ .../ContentPicker/content-source.js | 102 +++++++++++++++++ .../UmbracoContentDataListSource.cs | 108 ++++++++++++++++++ .../Umbraco.Community.Contentment.csproj | 5 + .../Web/UI/backoffice-ui-shim.css | 10 ++ 8 files changed, 380 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Community.Contentment/Core/Xml/UmbracoXPathPathSyntaxParser.cs create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentPicker/ContentPickerDataEditor.cs create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentPicker/content-source.html create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentPicker/content-source.js create mode 100644 src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs diff --git a/src/Umbraco.Community.Contentment/Core/Xml/UmbracoXPathPathSyntaxParser.cs b/src/Umbraco.Community.Contentment/Core/Xml/UmbracoXPathPathSyntaxParser.cs new file mode 100644 index 00000000..da52d87c --- /dev/null +++ b/src/Umbraco.Community.Contentment/Core/Xml/UmbracoXPathPathSyntaxParser.cs @@ -0,0 +1,34 @@ +/* Copyright © 2020 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Umbraco.Core.Xml +{ + // NOTE: Bah! `UmbracoXPathPathSyntaxParser` is marked as internal! It's either copy code, or reflection - here we go! + // https://github.com/umbraco/Umbraco-CMS/blob/release-8.6.1/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs#L11 + internal class UmbracoXPathPathSyntaxParser + { + public static string ParseXPathQuery( + string xpathExpression, + int? nodeContextId, + Func> getPath, + Func publishedContentExists) + { + try + { + var assembly = typeof(XPathVariable).Assembly; + var type = assembly.GetType("Umbraco.Core.Xml.UmbracoXPathPathSyntaxParser"); + var method = type.GetMethod(nameof(ParseXPathQuery), BindingFlags.Static | BindingFlags.Public); + return method.Invoke(null, new object[] { xpathExpression, nodeContextId, getPath, publishedContentExists }) as string; + } + catch { /* ಠ_ಠ */ } + + return xpathExpression; + } + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs index c186d771..557c413d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs @@ -19,22 +19,25 @@ internal static class ContentTypeCacheHelper { private static readonly ConcurrentDictionary _forward = new ConcurrentDictionary(); private static readonly ConcurrentDictionary _reverse = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary _icons = new ConcurrentDictionary(); public static void ClearAll() { _forward.Clear(); _reverse.Clear(); + _icons.Clear(); } public static void TryAdd(IContentType contentType) { - TryAdd(contentType.Key, contentType.Alias); + TryAdd(contentType.Key, contentType.Alias, contentType.Icon); } - public static void TryAdd(Guid guid, string alias) + public static void TryAdd(Guid guid, string alias, string icon = null) { _forward.TryAdd(guid, alias); _reverse.TryAdd(alias, guid); + _icons.TryAdd(alias, icon); } public static bool TryGetAlias(Guid key, out string alias, IContentTypeService contentTypeService = null) @@ -57,6 +60,26 @@ public static bool TryGetAlias(Guid key, out string alias, IContentTypeService c return false; } + public static bool TryGetIcon(string alias, out string icon, IContentTypeService contentTypeService = null) + { + if (_icons.TryGetValue(alias, out icon)) + return true; + + // The icon isn't cached, we can attempt to get it via the content-type service, using the alias. + if (contentTypeService != null) + { + var contentType = contentTypeService.Get(alias); + if (contentType != null) + { + TryAdd(contentType); + icon = contentType.Icon; + return true; + } + } + + return false; + } + public static bool TryGetGuid(string alias, out Guid key, IContentTypeService contentTypeService = null) { if (_reverse.TryGetValue(alias, out key)) @@ -94,6 +117,8 @@ public static bool TryRemove(Guid guid) public static bool TryRemove(string alias) { + _icons.TryRemove(alias, out _); + return _reverse.TryRemove(alias, out var guid) ? _forward.TryRemove(guid, out _) : false; diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentPicker/ContentPickerDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentPicker/ContentPickerDataEditor.cs new file mode 100644 index 00000000..d095cd31 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentPicker/ContentPickerDataEditor.cs @@ -0,0 +1,12 @@ +/* Copyright © 2020 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +namespace Umbraco.Community.Contentment.DataEditors +{ + internal sealed class ContentPickerDataEditor + { + internal const string DataEditorSourceViewPath = Constants.Internals.EditorsPathRoot + "content-source.html"; + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentPicker/content-source.html b/src/Umbraco.Community.Contentment/DataEditors/ContentPicker/content-source.html new file mode 100644 index 00000000..e977126d --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentPicker/content-source.html @@ -0,0 +1,82 @@ + + +
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ + + +
+ + + + +
+

Use an XPath query to set a start page. For a context-aware query, you can use one of the pre-defined placeholders.

+

Placeholders finds the nearest published content ID and runs the XPath query from there. For instance:

+
$site/newsListingPage
+

This query will try to get the current website page (at level 1), then find the first page of type `newsListingPage`.

+
+
Available placeholders:
+
$current - current page or closest ancestor.
+
$parent - parent page or closest ancestor.
+
$root - root page in the content tree.
+
$site - ancestor page located at level 1.
+
+
+
+
+ +
+ +
diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentPicker/content-source.js b/src/Umbraco.Community.Contentment/DataEditors/ContentPicker/content-source.js new file mode 100644 index 00000000..75bff338 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentPicker/content-source.js @@ -0,0 +1,102 @@ +/* Copyright © 2020 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors.ContentSource.Controller", [ + "$scope", + "editorService", + "entityResource", + function ($scope, editorService, entityResource) { + + //console.log("content-source.model", $scope.model); + + var defaultConfig = { + }; + var config = Object.assign({}, defaultConfig, $scope.model.config); + + var vm = this; + + function init() { + + // NOTE: If it starts with "umb://" we assume it's an Umbraco UDI, otherwise it's an XPath. + if ($scope.model.value && $scope.model.value.startsWith("umb://document/")) { + vm.node = {}; + vm.loading = true; + entityResource.getById($scope.model.value, "Document").then(function (item) { + populate(item); + vm.loading = false; + }) + } else { + vm.node = null; + } + + vm.showHelp = false; + vm.showSearch = false; + + vm.pick = pick; + vm.remove = remove; + + vm.show = show; + vm.hide = hide; + + vm.help = help; + vm.clear = clear; + }; + + function pick() { + + editorService.treePicker({ + idType: "udi", + section: "content", + treeAlias: "content", + multiPicker: false, + submit: function submit(model) { + + var item = model.selection[0]; + + populate(item); + + $scope.model.value = item.udi; + + editorService.close(); + }, + close: function close() { + editorService.close(); + } + }); + + }; + + function remove() { + $scope.model.value = null; + vm.node = null; + }; + + function show() { + vm.showSearch = true; + }; + + function hide() { + vm.showSearch = false; + }; + + function help() { + vm.showHelp = !vm.showHelp; + } + + function clear() { + vm.showSearch = false; + $scope.model.value = null; + }; + + function populate(item) { + vm.node = item; + entityResource.getUrl(item.id, "Document").then(function (data) { + vm.node.path = data; + }); + } + + init(); + } +]); diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs new file mode 100644 index 00000000..5b6ab269 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs @@ -0,0 +1,108 @@ +/* Copyright © 2020 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Core.Xml; +using Umbraco.Web; + +namespace Umbraco.Community.Contentment.DataEditors +{ + public sealed class UmbracoContentDataListSource : IDataListSource, IDataListSourceValueConverter + { + private readonly IContentTypeService _contentTypeService; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + + public UmbracoContentDataListSource(IContentTypeService contentTypeService, IUmbracoContextAccessor umbracoContextAccessor) + { + _contentTypeService = contentTypeService; + _umbracoContextAccessor = umbracoContextAccessor; + } + + public string Name => "Umbraco Content"; + + public string Description => "Select Umbraco content to populate the data source."; + + public string Icon => "icon-umbraco"; + + public OverlaySize OverlaySize => OverlaySize.Small; + + public IEnumerable Fields => new ConfigurationField[] + { + new ConfigurationField + { + Key = "parentNode", + Name = "Parent node", + Description = "Set a parent node to use its child nodes as the data source items.", + View = ContentPickerDataEditor.DataEditorSourceViewPath, + } + }; + + public Dictionary DefaultValues => default; + + public IEnumerable GetItems(Dictionary config) + { + var startNode = default(IPublishedContent); + + var parentNode = config.GetValueAs("parentNode", string.Empty); + + if (parentNode.InvariantStartsWith("umb://document/") == false) + { + var umbracoContext = _umbracoContextAccessor.UmbracoContext; + + // NOTE: First we check for "id" (if on a content page), then "parentId" (if editing an element). + var nodeContextId = int.TryParse(umbracoContext.HttpContext.Request.QueryString.Get("id"), out var currentId) + ? currentId + : int.TryParse(umbracoContext.HttpContext.Request.QueryString.Get("parentId"), out var parentId) + ? parentId + : default(int?); + + IEnumerable getPath(int id) => umbracoContext.Content.GetById(id).Path.ToDelimitedList().Reverse(); + bool publishedContentExists(int id) => umbracoContext.Content.GetById(id) != null; + + var parsed = UmbracoXPathPathSyntaxParser.ParseXPathQuery(parentNode, nodeContextId, getPath, publishedContentExists); + + if (string.IsNullOrWhiteSpace(parsed) == false && parsed.StartsWith("$") == false) + { + startNode = umbracoContext.Content.GetSingleByXPath(parsed); + } + } + else if (GuidUdi.TryParse(parentNode, out var udi) && udi.Guid.Equals(Guid.Empty) == false) + { + startNode = _umbracoContextAccessor.UmbracoContext.Content.GetById(udi.Guid); + } + + return startNode == null + ? Enumerable.Empty() + : startNode.Children.Select(x => new DataListItem + { + Name = x.Name, + Value = Udi.Create(Core.Constants.UdiEntityType.Document, x.Key).ToString(), + Icon = ContentTypeCacheHelper.TryGetIcon(x.ContentType.Alias, out var icon, _contentTypeService) ? icon : Core.Constants.Icons.Content, + Description = x.TemplateId > 0 ? x.Url : string.Empty + }); + } + + public Type GetValueType(Dictionary config) + { + return typeof(IPublishedContent); + } + + public object ConvertValue(Type type, string value) + { + if (type == typeof(IPublishedContent) && Udi.TryParse(value, out var udi)) + { + return _umbracoContextAccessor.UmbracoContext.Content.GetById(udi); + } + + return value.TryConvertTo(type).Result; + } + } +} diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index 1a408227..ac2a0345 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -298,9 +298,12 @@ + + + @@ -412,6 +415,8 @@ + + diff --git a/src/Umbraco.Community.Contentment/Web/UI/backoffice-ui-shim.css b/src/Umbraco.Community.Contentment/Web/UI/backoffice-ui-shim.css index 5b7235f3..47195958 100644 --- a/src/Umbraco.Community.Contentment/Web/UI/backoffice-ui-shim.css +++ b/src/Umbraco.Community.Contentment/Web/UI/backoffice-ui-shim.css @@ -17,3 +17,13 @@ .umb-tree-icon.icon-fa { width: 20px; } + +[class].icon-umbraco { + background-image: url(/umbraco/assets/img/application/logo_black.png); + background-repeat: no-repeat; + background-size: cover; +} + +.umb-action-link .icon-umbraco { + height: 30px; +} From 5682785dd5f139ec8d4f16e95b38ee35be236598 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 25 Jun 2020 16:56:02 +0100 Subject: [PATCH 11/86] Umbraco Content data source - fixed bug with querying unpublished content --- .../DataSources/UmbracoContentDataListSource.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs index 5b6ab269..e26659c9 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs @@ -49,6 +49,8 @@ public UmbracoContentDataListSource(IContentTypeService contentTypeService, IUmb public IEnumerable GetItems(Dictionary config) { + var preview = true; + var startNode = default(IPublishedContent); var parentNode = config.GetValueAs("parentNode", string.Empty); @@ -64,19 +66,19 @@ public IEnumerable GetItems(Dictionary config) ? parentId : default(int?); - IEnumerable getPath(int id) => umbracoContext.Content.GetById(id).Path.ToDelimitedList().Reverse(); - bool publishedContentExists(int id) => umbracoContext.Content.GetById(id) != null; + IEnumerable getPath(int id) => umbracoContext.Content.GetById(preview, id).Path.ToDelimitedList().Reverse(); + bool publishedContentExists(int id) => umbracoContext.Content.GetById(preview, id) != null; var parsed = UmbracoXPathPathSyntaxParser.ParseXPathQuery(parentNode, nodeContextId, getPath, publishedContentExists); if (string.IsNullOrWhiteSpace(parsed) == false && parsed.StartsWith("$") == false) { - startNode = umbracoContext.Content.GetSingleByXPath(parsed); + startNode = umbracoContext.Content.GetSingleByXPath(preview, parsed); } } else if (GuidUdi.TryParse(parentNode, out var udi) && udi.Guid.Equals(Guid.Empty) == false) { - startNode = _umbracoContextAccessor.UmbracoContext.Content.GetById(udi.Guid); + startNode = _umbracoContextAccessor.UmbracoContext.Content.GetById(preview, udi.Guid); } return startNode == null @@ -86,7 +88,8 @@ public IEnumerable GetItems(Dictionary config) Name = x.Name, Value = Udi.Create(Core.Constants.UdiEntityType.Document, x.Key).ToString(), Icon = ContentTypeCacheHelper.TryGetIcon(x.ContentType.Alias, out var icon, _contentTypeService) ? icon : Core.Constants.Icons.Content, - Description = x.TemplateId > 0 ? x.Url : string.Empty + Description = x.TemplateId > 0 ? x.Url : string.Empty, + Disabled = x.IsPublished() == false, }); } From 7508d8ef64d33c02891eae1f1cd4a87b688b0928 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 25 Jun 2020 16:56:23 +0100 Subject: [PATCH 12/86] ContentBlocks - fixed bug with setting the current page ID --- .../DataEditors/ContentBlocks/content-blocks.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js index a0097c82..ebdef8b9 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js @@ -116,6 +116,11 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. $scope.umbProperty.setPropertyActions(propertyActions); } } + + // NOTE: [LK] Some of the editors may need the context of the current page. + // If the page is new, then it doesn't have an id, so the parentId will be used. + var currentNode = editorState.getCurrent(); + config.currentPageId = currentNode.id > 0 ? currentNode.id : currentNode.parentId; }; function actionsFactory($index) { @@ -147,7 +152,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. config: { elementTypes: config.contentBlockTypes, enableFilter: config.enableFilter, - currentPageId: editorState.current.id, + currentPageId: config.currentPageId, }, size: config.contentBlockTypes.length === 1 ? config.contentBlockTypes[0].overlaySize : "small", value: null, @@ -188,7 +193,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. editorService.open({ config: { elementType: elementType, - currentPageId: editorState.current.id, + currentPageId: config.currentPageId, }, size: elementType.overlaySize, value: item, From a4225ecaf205ffdb6b3884f90a18d109d2c2a730 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 6 Jul 2020 19:56:52 +0100 Subject: [PATCH 13/86] ContentBlocks - added icons to overlay header --- .../ContentBlocks/content-blocks.overlay.html | 18 +++++++++++------- .../ContentBlocks/content-blocks.overlay.js | 10 ++++++++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html index 8903c76d..e1d55acf 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html @@ -7,13 +7,17 @@ - - +
+
+
+ +
+
+
+
+
+
+
diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.js b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.js index acb29d96..215d4dd8 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.js +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.js @@ -43,6 +43,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.Overlays.Con vm.title = "Add content"; vm.description = "Select a content type..."; + vm.icon = "icon-page-add"; vm.selectBlueprint = false; vm.enableFilter = Object.toBoolean(config.enableFilter); @@ -83,6 +84,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.Overlays.Con else { vm.title = "Add content"; vm.description = "Select a content blueprint..."; + vm.icon = "icon-blueprint"; vm.selectBlueprint = true; vm.selectedElementType = elementType; vm.blueprintAllowBlank = blueprintConfig.allowBlank; @@ -98,9 +100,11 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.Overlays.Con $scope.model.size = elementType.overlaySize; vm.mode = "edit"; + vm.loading = true; + vm.title = "Edit content"; vm.description = elementType.name; - vm.loading = true; + vm.icon = elementType.icon; vm.content = { elementType: elementType.key, @@ -135,9 +139,11 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.Overlays.Con function edit(elementType, element) { vm.mode = "edit"; + vm.loading = true; + vm.title = "Edit content"; vm.description = elementType.name; - vm.loading = true; + vm.icon = elementType.icon; vm.content = { elementType: elementType.key, From dfc8c1500b5e1c050b90486104864774b40ea506 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 20 Jul 2020 16:15:19 +0100 Subject: [PATCH 14/86] ContentBlocks - Changed data-type icon --- .../DataEditors/ContentBlocks/ContentBlocksDataEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs index 88cd94de..a986a2b0 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs @@ -19,7 +19,7 @@ public sealed class ContentBlocksDataEditor : IDataEditor internal const string DataEditorListViewPath = Constants.Internals.EditorsPathRoot + "content-list.html"; internal const string DataEditorBlocksViewPath = Constants.Internals.EditorsPathRoot + "content-blocks.html"; internal const string DataEditorOverlayViewPath = Constants.Internals.EditorsPathRoot + "content-blocks.overlay.html"; - internal const string DataEditorIcon = "icon-item-arrangement"; + internal const string DataEditorIcon = "icon-fa fa-server"; private readonly IContentService _contentService; private readonly IContentTypeService _contentTypeService; From f43b2ec32ea8712f3a977eb3e591e72445ccf7eb Mon Sep 17 00:00:00 2001 From: leekelleher Date: Fri, 31 Jul 2020 14:39:16 +0100 Subject: [PATCH 15/86] ContentBlocks Overlay - removed unreachable markup The `item.contentTypeAlias` never exists at that point. --- .../ContentBlocks/content-blocks.overlay.html | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html index e1d55acf..a4d2591c 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html @@ -9,13 +9,13 @@
-
- -
-
-
-
-
+
+ +
+
+
+
+
@@ -92,7 +92,6 @@ - @@ -100,7 +99,7 @@ - +

There are no items available to add.

From 3c81ee6a63b2738a03830d997c2b938d3ef575c5 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 12 Aug 2020 12:37:15 +0100 Subject: [PATCH 16/86] :notebook: Updated README Added a known issue with Data List using Umbraco Content (XPath) inside a Nested Content. --- .github/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/README.md b/.github/README.md index 56a92747..e19fb90d 100644 --- a/.github/README.md +++ b/.github/README.md @@ -39,7 +39,8 @@ Downloads will be made available, (in due course), on the [releases page](https: #### Known issues -_There are currently no known issues with the Contentment package._ +- **Data List** + - When using the Umbraco Content data source with an XPath query, inside a Nested Content editor, it will not be able to identify the contextual containing node ID. e.g. your XPath query will not work. See #30 for details. ### Documentation From ccd647e03857cef99af15f466804e16403be1e1f Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 12 Aug 2020 16:48:23 +0100 Subject: [PATCH 17/86] Added small note to the backoffice UI shim CSS About the Umbraco icon --- src/Umbraco.Community.Contentment/Web/UI/backoffice-ui-shim.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Community.Contentment/Web/UI/backoffice-ui-shim.css b/src/Umbraco.Community.Contentment/Web/UI/backoffice-ui-shim.css index 47195958..9967e5c0 100644 --- a/src/Umbraco.Community.Contentment/Web/UI/backoffice-ui-shim.css +++ b/src/Umbraco.Community.Contentment/Web/UI/backoffice-ui-shim.css @@ -18,6 +18,7 @@ width: 20px; } +/* Enables the use of the Umbraco icon in the backoffice. */ [class].icon-umbraco { background-image: url(/umbraco/assets/img/application/logo_black.png); background-repeat: no-repeat; From ab7d128d776b40960f0589521c98ba8169489ce7 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Sun, 16 Aug 2020 16:58:30 +0100 Subject: [PATCH 18/86] Added placeholder stubs for Content Blocks docs --- .github/README.md | 7 +++---- .github/ROADMAP.md | 2 +- docs/README.md | 1 + docs/editors/content-blocks.md | 22 ++++++++++++++++++++++ src/Umbraco.Community.Contentment.sln | 1 + 5 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 docs/editors/content-blocks.md diff --git a/.github/README.md b/.github/README.md index e19fb90d..47cfba79 100644 --- a/.github/README.md +++ b/.github/README.md @@ -24,17 +24,16 @@ Let's take a look inside... ##### Property Editors - [Bytes](../docs/editors/bytes.md) - a read-only label to display file sizes in relative bytes. +- [Content Blocks](../docs/editors/content-blocks.md) - a stack block editor, configurable using element types. - [Data List](../docs/editors/data-list.md) - an editor that combines a custom data source with a custom list editor. - [Icon Picker](../docs/editors/icon-picker.md) - an editor to select an icon (from the Umbraco icon library). - [Notes](../docs/editors/notes.md) - a read-only label to display rich-text instructional messages for content editors. - [Render Macro](../docs/editors/render-macro.md) - a read-only label dynamically generated from an Umbraco Macro. -#### Release status +#### Releases -My aim to have a v1.0 release by end of Q2 (June) 2020. - -Downloads will be made available, (in due course), on the [releases page](https://github.com/leekelleher/umbraco-contentment/releases). +Downloads are available on the [releases page](https://github.com/leekelleher/umbraco-contentment/releases). #### Known issues diff --git a/.github/ROADMAP.md b/.github/ROADMAP.md index c9dd1044..993bf8a2 100644 --- a/.github/ROADMAP.md +++ b/.github/ROADMAP.md @@ -20,7 +20,7 @@ Initial release. Property Editors are: ### v1.1 -- Content Blocks _(a StackedContent-esque editor)_ +- [Content Blocks](../docs/editors/content-blocks.md) - Data List: Umbraco Content _(a data source for selecting nodes)_ ### v1.2 diff --git a/docs/README.md b/docs/README.md index fd319c0c..311939a0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,6 +9,7 @@ Here is the documentation for the Contentment property-editors... - [Bytes](../docs/editors/bytes.md) - a read-only label to display file sizes in relative bytes. +- [Content Blocks](../docs/editors/content-blocks.md) - a stack block editor, configurable using element types. - [Data List](../docs/editors/data-list.md) - an editor that combines a custom data source with a custom list editor. - [Icon Picker](../docs/editors/icon-picker.md) - an editor to select an icon (from the Umbraco icon library). - [Notes](../docs/editors/notes.md) - a read-only label to display rich-text instructional messages for content editors. diff --git a/docs/editors/content-blocks.md b/docs/editors/content-blocks.md new file mode 100644 index 00000000..a269240d --- /dev/null +++ b/docs/editors/content-blocks.md @@ -0,0 +1,22 @@ +Contentment for Umbraco logo + +## Contentment for Umbraco + +### Content Blocks + + + +### How to configure the editor? + + + +### How to use the editor? + + + +### How to get the value? + + + +### Further reading + diff --git a/src/Umbraco.Community.Contentment.sln b/src/Umbraco.Community.Contentment.sln index 9a594c5b..f4e62d34 100644 --- a/src/Umbraco.Community.Contentment.sln +++ b/src/Umbraco.Community.Contentment.sln @@ -40,6 +40,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Editors", "Editors", "{A5294B30-2ED5-4BBA-A2DE-A07103DAE78F}" ProjectSection(SolutionItems) = preProject ..\docs\editors\bytes.md = ..\docs\editors\bytes.md + ..\docs\editors\content-blocks.md = ..\docs\editors\content-blocks.md ..\docs\editors\data-list.md = ..\docs\editors\data-list.md ..\docs\editors\icon-picker.md = ..\docs\editors\icon-picker.md ..\docs\editors\notes.md = ..\docs\editors\notes.md From 3adbd5b17086d39991c1177d94f000eb4b8a088c Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 27 Nov 2019 14:45:24 +0000 Subject: [PATCH 19/86] ContentBlocks - adds preview feature Completely reworked the editor markup and CSS. --- build/build-assets.ps1 | 7 + .../ContentBlocks/ContentBlockPreview.cshtml | 12 ++ .../ContentBlocks/ContentBlockPreviewModel.cs | 27 +++ .../ContentBlocks/ContentBlockPreviewView.cs | 40 +++++ .../ContentBlocks/ContentBlockType.cs | 2 + .../ContentBlocksApiController.cs | 110 ++++++++++++ .../ContentBlocksConfigurationEditor.cs | 2 + .../ContentBlocks/ContentBlocksDataEditor.cs | 1 + ...tentBlocksDisplayModeConfigurationField.cs | 14 +- .../ContentBlocksTypesConfigurationField.cs | 11 ++ .../ContentBlocks/ContentBlocksViewHelper.cs | 50 ++++++ .../ContentBlocks/content-blocks.css | 159 ++++++++++++------ .../ContentBlocks/content-blocks.html | 61 ++++--- .../ContentBlocks/content-blocks.js | 70 +++++++- .../Umbraco.Community.Contentment.csproj | 8 + 15 files changed, 482 insertions(+), 92 deletions(-) create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreview.cshtml create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewModel.cs create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs create mode 100644 src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksViewHelper.cs diff --git a/build/build-assets.ps1 b/build/build-assets.ps1 index 00dd3c94..32e31304 100644 --- a/build/build-assets.ps1 +++ b/build/build-assets.ps1 @@ -48,6 +48,13 @@ foreach($htmlFile in $htmlFiles){ Set-Content -Path "${pluginFolder}\editors\$($htmlFile.Name)" -Value $minifiedHtml; } +# Razor Templates - Copy +$razorFiles = Get-ChildItem -Path "${ProjectDir}DataEditors" -Recurse -Force -Include *.cshtml; +foreach($razorFile in $razorFiles){ + $contents = Get-Content -Path $razorFile.FullName; + Set-Content -Path "${pluginFolder}\render\$($razorFile.Name)" -Value $contents; +} + # CSS - Bundle & Minify $targetCssPath = "${pluginFolder}contentment.css"; Get-Content -Path "${ProjectDir}**\**\*.css" | Set-Content -Path $targetCssPath; diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreview.cshtml b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreview.cshtml new file mode 100644 index 00000000..2f7de867 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreview.cshtml @@ -0,0 +1,12 @@ +@* Copyright © 2019 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. *@ +@inherits Umbraco.Web.Mvc.ContentBlockPreviewView +@{ + var elementIcon = ViewData.ContainsKey("elementIcon") == true ? ViewData["elementIcon"] : "icon-brick"; +} +
+ +
@Model.Element.Key
+
diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewModel.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewModel.cs new file mode 100644 index 00000000..be0bb7fa --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewModel.cs @@ -0,0 +1,27 @@ +/* Copyright © 2020 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Models; + +namespace Umbraco.Community.Contentment.DataEditors +{ + public class ContentBlockPreviewModel : ContentBlockPreviewModel + { + public ContentBlockPreviewModel(IPublishedContent content, IPublishedElement element) + { } + } + + public class ContentBlockPreviewModel : IContentModel + where TPublishedContent : IPublishedContent + where TPublishedElement : IPublishedElement + { + IPublishedContent IContentModel.Content => Content; + + public TPublishedContent Content { get; internal set; } + + public TPublishedElement Element { get; internal set; } + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs new file mode 100644 index 00000000..45aa6af2 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs @@ -0,0 +1,40 @@ +/* Copyright © 2020 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System.Web.Mvc; +using Umbraco.Community.Contentment.DataEditors; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.Mvc +{ + public abstract class ContentBlockPreviewView + : ContentBlockPreviewView + { } + + public abstract class ContentBlockPreviewView + : UmbracoViewPage> + where TPublishedContent : IPublishedContent + where TPublishedElement : IPublishedElement + { + protected override void SetViewData(ViewDataDictionary viewData) + { + var model = new ContentBlockPreviewModel(); + + if (viewData.TryGetValue("content", out var tmp1) && tmp1 is TPublishedContent t1) + { + model.Content = t1; + } + + if (viewData.TryGetValue("element", out var tmp2) && tmp2 is TPublishedElement t2) + { + model.Element = t2; + } + + viewData.Model = model; + + base.SetViewData(viewData); + } + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockType.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockType.cs index 23436591..4d245c9f 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockType.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockType.cs @@ -30,6 +30,8 @@ internal sealed class ContentBlockType public string OverlaySize { get; set; } + public bool PreviewEnabled { get; set; } + public IEnumerable Blueprints { get; set; } [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs new file mode 100644 index 00000000..1981e506 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs @@ -0,0 +1,110 @@ +/* Copyright © 2019 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Web.Http; +using Newtonsoft.Json.Linq; +using Umbraco.Community.Contentment.Web.PublishedCache; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Editors; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; + +namespace Umbraco.Community.Contentment.DataEditors +{ + [PluginController(Constants.Internals.PluginControllerName), IsBackOffice] + public sealed class ContentBlocksApiController : UmbracoAuthorizedJsonController + { + private readonly ILogger _logger; + private readonly IPublishedModelFactory _publishedModelFactory; + + public ContentBlocksApiController(ILogger logger, IPublishedModelFactory publishedModelFactory) + { + _logger = logger; + _publishedModelFactory = publishedModelFactory; + } + + [HttpPost] + public HttpResponseMessage GetPreviewMarkup([FromBody] JObject item, int elementIndex, Guid elementKey, int contentId) + { + var preview = true; + + var content = UmbracoContext.Content.GetById(true, contentId); + if (content == null) + { + // TODO: [LK] What to do with an unsaved (new) page? + } + + var element = default(IPublishedElement); + var block = item.ToObject(); + if (block != null && block.ElementType.Equals(Guid.Empty) == false) + { + if (ContentTypeCacheHelper.TryGetAlias(block.ElementType, out var alias, Services.ContentTypeService) == true) + { + var contentType = UmbracoContext.PublishedSnapshot.Content.GetContentType(alias); + if (contentType != null && contentType.IsElement == true) + { + var properties = new List(); + + foreach (var thing in block.Value) + { + var propType = contentType.GetPropertyType(thing.Key); + if (propType != null) + { + properties.Add(new DetachedPublishedProperty(propType, null, thing.Value, preview)); + } + } + + element = _publishedModelFactory.CreateModel(new DetachedPublishedElement(block.Key, contentType, properties)); + } + } + } + + var viewData = new System.Web.Mvc.ViewDataDictionary() + { + { nameof(content), content }, + { nameof(element), element }, + { nameof(elementIndex), elementIndex } + }; + + if (ContentTypeCacheHelper.TryGetIcon(content.ContentType.Alias, out var contentIcon, Services.ContentTypeService) == true) + { + viewData.Add(nameof(contentIcon), contentIcon); + } + + if (ContentTypeCacheHelper.TryGetIcon(element.ContentType.Alias, out var elementIcon, Services.ContentTypeService) == true) + { + viewData.Add(nameof(elementIcon), elementIcon); + } + + var markup = default(string); + + try + { + markup = ContentBlocksViewHelper.RenderPartial(element.ContentType.Alias, viewData); + } + catch (InvalidCastException icex) + { + // NOTE: This type of exception happens on a new (unsaved) page, when the context becomes the parent page, + // and the preview view is strongly typed to the current page's model type. + markup = "

Unable to render the preview until the page has been saved.

"; + + _logger.Error(icex, "Error rendering preview view."); + } + catch (Exception ex) + { + markup = $"
{ex}
"; + + _logger.Error(ex, "Error rendering preview view."); + } + + return Request.CreateResponse(HttpStatusCode.OK, new { elementKey, markup }); + } + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs index 4fa6dc78..1515d3cc 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.IO; @@ -77,6 +78,7 @@ public override IDictionary ToValueEditor(object configuration) Blueprints = blueprints, NameTemplate = settings?.ContainsKey("nameTemplate") == true ? settings["nameTemplate"].ToString() : null, OverlaySize = settings?.ContainsKey("overlaySize") == true ? settings["overlaySize"].ToString() : null, + PreviewEnabled = settings?.ContainsKey("enablePreview") == true && settings["enablePreview"].TryConvertTo().ResultOr(false), }); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs index a986a2b0..4846f1cc 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataEditor.cs @@ -79,6 +79,7 @@ public IDataValueEditor GetValueEditor(object configuration) if (config.TryGetValueAs(ContentBlocksDisplayModeConfigurationField.DisplayMode, out string displayMode)) { + config["enablePreview"] = ContentBlocksDisplayModeConfigurationField.SupportsPreview(displayMode); view = displayMode; } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDisplayModeConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDisplayModeConfigurationField.cs index 5765f008..2003336f 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDisplayModeConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDisplayModeConfigurationField.cs @@ -4,6 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Collections.Generic; +using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; @@ -27,12 +28,23 @@ public ContentBlocksDisplayModeConfigurationField(string defaultValue = Blocks) { Constants.Conventions.ConfigurationFieldAliases.Items, new DataListItem[] { new DataListItem { Name = nameof(Blocks), Value = Blocks, Description = "This will display as stacked blocks." }, - new DataListItem { Name = nameof(List), Value = List, Description = "This will display similar to a content picker." }, + new DataListItem { Name = nameof(List), Value = List, Description = "This will display similar to a content picker. Please note, if block preview is enabled, they will not be displayed in this view." }, } }, { ShowDescriptionsConfigurationField.ShowDescriptions, Constants.Values.True }, { Constants.Conventions.ConfigurationFieldAliases.DefaultValue, defaultValue } }; } + + internal static bool SupportsPreview(string displayMode) + { + // NOTE: Currently only the stacked blocks support the preview feature. + if (displayMode.InvariantEquals(Blocks) == true) + { + return true; + } + + return false; + } } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs index 2a85044b..3cf7346b 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs @@ -51,6 +51,17 @@ public ContentBlocksTypesConfigurationField(IEnumerable elementTyp }, { Constants.Conventions.ConfigurationFieldAliases.DefaultValue, "small" } } + }, + new ConfigurationField + { + Key = "enablePreview", + Name = "Enable preview?", + Description = "Select to enable a rich preview for this content block type.", + View = "views/propertyeditors/boolean/boolean.html", + Config = new Dictionary + { + { "default", Constants.Values.False } + } } }; diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksViewHelper.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksViewHelper.cs new file mode 100644 index 00000000..21698303 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksViewHelper.cs @@ -0,0 +1,50 @@ +/* Copyright © 2020 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System.IO; +using System.Web; +using System.Web.Mvc; +using System.Web.Routing; + +namespace Umbraco.Community.Contentment.DataEditors +{ + internal static class ContentBlocksViewHelper + { + private class FakeController : Controller { } + + private static readonly RazorViewEngine _viewEngine = new RazorViewEngine + { + PartialViewLocationFormats = new[] + { + "~/Views/Partials/Blocks/{0}.cshtml", + "~/Views/Partials/Blocks/Default.cshtml", + Constants.Internals.PackagePathRoot + "render/ContentBlockPreview.cshtml" + } + }; + + internal static string RenderPartial(string partialName, ViewDataDictionary viewData) + { + using (var sw = new StringWriter()) + { + var httpContext = new HttpContextWrapper(HttpContext.Current); + + var routeData = new RouteData { Values = { { "controller", nameof(FakeController) } } }; + + var controllerContext = new ControllerContext(new RequestContext(httpContext, routeData), new FakeController()); + + var viewResult = _viewEngine.FindPartialView(controllerContext, partialName, false); + + if (viewResult.View == null) + { + return null; + } + + viewResult.View.Render(new ViewContext(controllerContext, viewResult.View, viewData, new TempDataDictionary(), sw), sw); + + return sw.ToString(); + } + } + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.css b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.css index b0c39243..8dab3e81 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.css +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.css @@ -3,81 +3,130 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -.contentment.lk-content-blocks .umb-group-builder__properties { - padding: 0; - min-height: inherit; +.lk-content-blocks__container { max-width: 845px; } -.contentment.lk-content-blocks .umb-group-builder__property { - border-bottom: none; +.lk-content-blocks__block { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + margin-bottom: 10px; } - .contentment.lk-content-blocks .umb-group-builder__property:last-of-type { - margin-bottom: 0; + .lk-content-blocks__block .lk-content-blocks__preview { + flex: 1; + position: relative; } -.contentment.lk-content-blocks .umb-group-builder__property-actions { - margin-top: 10px; -} - -.contentment.lk-content-blocks .umb-group-builder__property-action { - margin-top: 0; - margin-bottom: 0; -} - - .contentment.lk-content-blocks .umb-group-builder__property-action > i.icon { - font-size: 18px; - padding: 5px 10px; - } + .lk-content-blocks__block .lk-content-blocks__preview:after { + content: ""; + background: rgba(225, 225, 225, 0.5); + border-radius: 3px; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + transition: opacity .12s; + } - .contentment.lk-content-blocks .umb-group-builder__property-action > .icon { - color: #1b264f; - } + .lk-content-blocks__block .lk-content-blocks__preview:hover:after { + opacity: .66; + } - .contentment.lk-content-blocks .umb-group-builder__property-action > .icon:hover { - color: #2152a3; + .lk-content-blocks__block .lk-content-blocks__preview .lk-content-blocks__preview--left { + background-color: #fff; + border-radius: 3px; + display: flex; + flex-direction: row; + font-size: 12px; + padding: 0 5px; + position: absolute; + top: 5px; + left: 5px; + z-index: 20; } - .contentment.lk-content-blocks .umb-group-builder__property-action .umb-property-actions { - float: none; - } + .lk-content-blocks__block .lk-content-blocks__preview .lk-content-blocks__preview--left .icon { + padding-right: 3px; + } - .contentment.lk-content-blocks .umb-group-builder__property-action .umb-property-actions__toggle { - margin-top: 5px; - margin-left: 12px; - opacity: 1; - background-color: transparent; - } + .lk-content-blocks__block .lk-content-blocks__preview .lk-content-blocks__preview--default { + text-align: center; + padding: 30px 10px 15px; + } - .contentment.lk-content-blocks .umb-group-builder__property-action .umb-property-actions__toggle:hover { - background-color: #f9f6f5; + .lk-content-blocks__block .lk-content-blocks__preview .lk-content-blocks__preview--default .icon { + display: block; + font-size: 40px; + } + + .lk-content-blocks__block .lk-content-blocks__preview .lk-content-blocks__preview--default h5 { + font-size: 14px; + } + + .lk-content-blocks__block .lk-content-blocks__preview button.lk-content-blocks__edit { + background: transparent; + border: none; + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + z-index: 25; } - .contentment.lk-content-blocks .umb-group-builder__property-action .umb-property-actions__menu { - margin-left: 12px; + .lk-content-blocks__block .lk-content-blocks__preview button.lk-content-blocks__edit:focus { + outline: 0; + border: 1px solid #bbbabf; + } + + .lk-content-blocks__block .lk-content-blocks__actions { + flex: 0 0 34px; + padding: 10px 0 0 7px; + margin: 0; + list-style: none; } -.contentment.lk-content-blocks .umb-group-builder__property-preview { - height: auto; - padding: 30px 10px 5px; -} + .lk-content-blocks__block .lk-content-blocks__actions .lk-content-blocks__action--item { + } -.contentment.lk-content-blocks .lk-content-blocks-preview-default { - text-align: center; - padding-top: 5px; - padding-bottom: 20px; -} + .lk-content-blocks__block .lk-content-blocks__actions .lk-content-blocks__action--item > .icon { + color: #1b264f; + font-size: 18px; + padding: 5px 10px; + } + + .lk-content-blocks__block .lk-content-blocks__actions .lk-content-blocks__action--item > .icon:hover { + color: #2152a3; + } + + .lk-content-blocks__block .lk-content-blocks__actions .lk-content-blocks__action--item > button { + background: none; + border: none; + font-size: 18px; + cursor: pointer; + color: #162335; + margin: 0; + padding: 5px 10px; + } + + .lk-content-blocks__block .lk-content-blocks__actions .umb-property-actions { + display: block; + float: none; + margin: 5px 0 0 6px; + } - .contentment.lk-content-blocks .lk-content-blocks-preview-default .icon { - font-size: 40px; - } + .lk-content-blocks__block .lk-content-blocks__actions .umb-property-actions__toggle { + opacity: 1; + background-color: transparent; + } - .contentment.lk-content-blocks .lk-content-blocks-preview-default > div { - font-size: 14px; - font-weight: bold; - } + .lk-content-blocks__block .lk-content-blocks__actions .umb-property-actions__toggle:hover { + background-color: #f9f6f5; + } -.contentment.lk-content-blocks .umb-node-preview-add { +.lk-content-blocks .umb-node-preview-add { margin-right: 45px; } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html index eb2cb763..6dd4b217 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html @@ -4,39 +4,44 @@ - file, You can obtain one at https://mozilla.org/MPL/2.0/. -->
-
    -
  • -
    +
    +
    -
    -
    -
    -
    -
    - -
    -
    -
    - +
    +
    + +
    - -
    -
    -
    - -
    -
    - -
    -
    - -
    +
    +
    + +
    +
    +
    + +
    - +
    -
  • -
+ +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+ +
+ +
+ +
diff --git a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.js b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.js index 60802830..8900a4a1 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.js +++ b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.js @@ -46,22 +46,23 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. }); config.itemLookup = {}; - vm.allowEdit = {}; + config.allowEdit = {}; config.items.forEach(function (item) { config.itemLookup[item.key] = item; - vm.allowEdit[item.key] = item.fields && item.fields.length > 0; + config.allowEdit[item.key] = item.fields && item.fields.length > 0; }); vm.allowAdd = (config.maxItems === 0 || config.maxItems === "0") || $scope.model.value.length < config.maxItems; + vm.allowEdit = function (item, $index) { return config.allowEdit[item.key]; }; vm.allowRemove = Object.toBoolean(config.allowRemove); - vm.sortable = Object.toBoolean(config.disableSorting) === false && (config.maxItems !== 1 && config.maxItems !== "1"); + vm.allowSort = Object.toBoolean(config.disableSorting) === false && (config.maxItems !== 1 && config.maxItems !== "1"); vm.sortableOptions = { axis: "y", containment: "parent", cursor: "move", - disabled: vm.sortable === false, + disabled: vm.allowSort === false, opacity: 0.7, scroll: true, tolerance: "pointer", @@ -77,24 +78,18 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. vm.populate = populate; vm.remove = remove; - if ($scope.umbProperty) { + vm.propertyActions = []; - var propertyActions = []; - - if (Object.toBoolean(config.enableDevMode)) { - propertyActions.push({ - labelKey: "contentment_editRawValue", - icon: "brackets", - method: function () { - devModeService.editValue($scope.model, validate); - } - }); - } - - if (propertyActions.length > 0) { - $scope.umbProperty.setPropertyActions(propertyActions); - } + if (Object.toBoolean(config.enableDevMode)) { + vm.propertyActions.push({ + labelKey: "contentment_editRawValue", + icon: "brackets", + method: function () { + devModeService.editValue($scope.model, validate); + } + }); } + }; function add() { @@ -160,7 +155,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. }); }; - function populate(item, propertyName) { + function populate(item, $index, propertyName) { return config.itemLookup[item.key][propertyName]; }; diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html index 6dd4b217..9a381094 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html @@ -51,4 +51,17 @@
Add content ... + + + diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js index b060144c..b598907c 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js @@ -71,27 +71,16 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. vm.allowCopy = Object.toBoolean(config.allowCopy) && clipboardService.isSupported(); vm.allowEdit = Object.toBoolean(config.allowEdit); vm.allowRemove = Object.toBoolean(config.allowRemove); - vm.sortable = Object.toBoolean(config.disableSorting) === false && (config.maxItems !== 1 && config.maxItems !== "1"); - - vm.sortableOptions = { - axis: config.sortableAxis, - containment: "parent", - cursor: "move", - disabled: vm.sortable === false, - opacity: 0.7, - scroll: true, - tolerance: "pointer", - stop: function (e, ui) { - populatePreviews(); - setDirty(); - } - }; + vm.allowSort = Object.toBoolean(config.disableSorting) === false && (config.maxItems !== 1 && config.maxItems !== "1"); vm.add = add; vm.copy = copy; vm.edit = edit; vm.remove = remove; vm.populateName = populateName; + vm.sort = function () { + populatePreviews(); + }; vm.previews = []; vm.blockActions = []; @@ -102,38 +91,32 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. populatePreviews(); - if ($scope.umbProperty) { - - var propertyActions = []; - - if (vm.allowCopy) { - propertyActions.push({ - labelKey: "contentment_copyAllBlocks", - icon: "documents", - method: function () { - for (var i = 0; i < $scope.model.value.length; i++) { - copy(i); - } - } - }); - } + vm.propertyActions = []; - if (Object.toBoolean(config.enableDevMode)) { - propertyActions.push({ - labelKey: "contentment_editRawValue", - icon: "brackets", - method: function () { - devModeService.editValue($scope.model, function () { - // TODO: [LK:2020-01-02] Ensure that the edits are valid! e.g. check min/max items, elementType GUIDs, etc. - }); + if (vm.allowCopy) { + vm.propertyActions.push({ + labelKey: "contentment_copyAllBlocks", + icon: "documents", + method: function () { + for (var i = 0; i < $scope.model.value.length; i++) { + copy(i); } - }); - } + } + }); + } - if (propertyActions.length > 0) { - $scope.umbProperty.setPropertyActions(propertyActions); - } + if (Object.toBoolean(config.enableDevMode)) { + vm.propertyActions.push({ + labelKey: "contentment_editRawValue", + icon: "brackets", + method: function () { + devModeService.editValue($scope.model, function () { + // TODO: [LK:2020-01-02] Ensure that the edits are valid! e.g. check min/max items, elementType GUIDs, etc. + }); + } + }); } + }; function actionsFactory($index) { diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-list.html b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-list.html index f8532d80..99899ed7 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-list.html +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-list.html @@ -3,27 +3,17 @@ - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at https://mozilla.org/MPL/2.0/. --> -
-
- - -
- +
+ +
diff --git a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.html b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.html index 51ba010b..6bd222dc 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.html +++ b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.html @@ -3,26 +3,16 @@ - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at https://mozilla.org/MPL/2.0/. --> -
-
- - -
- +
+ +
diff --git a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.js b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.js index 932805df..cd101a4e 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.js +++ b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.js @@ -24,7 +24,6 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. listType: "grid", overlayView: "", overlayOrderBy: "name", - enableDevMode: 0, }; var config = Object.assign({}, defaultConfig, $scope.model.config); @@ -38,33 +37,40 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. $scope.model.value = [$scope.model.value]; } - vm.icon = config.defaultIcon; + vm.defaultIcon = config.defaultIcon; vm.allowAdd = (config.maxItems === 0 || config.maxItems === "0") || $scope.model.value.length < config.maxItems; vm.allowEdit = false; vm.allowRemove = true; - vm.sortable = Object.toBoolean(config.disableSorting) === false && (config.maxItems !== 1 && config.maxItems !== "1"); - - vm.sortableOptions = { - axis: "y", - containment: "parent", - cursor: "move", - disabled: vm.sortable === false, - opacity: 0.7, - scroll: true, - tolerance: "pointer", - stop: function (e, ui) { - $scope.model.value = vm.items.map(function (x) { return x.value }); - setDirty(); - } - }; - - vm.enableDevMode = Object.toBoolean(config.enableDevMode); + vm.allowSort = Object.toBoolean(config.disableSorting) === false && (config.maxItems !== 1 && config.maxItems !== "1"); vm.add = add; - vm.bind = bind; vm.remove = remove; + vm.sort = function () { + $scope.model.value = vm.items.map(function (x) { return x.value }); + }; + + vm.items = []; + + if ($scope.model.value.length > 0 && config.items.length > 0) { + var orphaned = []; + + $scope.model.value.forEach(function (v) { + var item = _.find(config.items, function (x) { return x.value === v }); // TODO: Replace Underscore.js dependency. [LK:2020-03-02] + if (item) { + vm.items.push(Object.assign({}, item)); + } else { + orphaned.push(v); + } + }); - bind(); + if (orphaned.length > 0) { + $scope.model.value = _.difference($scope.model.value, orphaned); // TODO: Replace Underscore.js dependency. [LK:2020-03-02] + + if ((config.maxItems === 0 || config.maxItems === "0") || $scope.model.value.length < config.maxItems) { + vm.allowAdd = true; + } + } + } }; function add() { @@ -73,13 +79,12 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. return _.find(vm.items, function (y) { return x.name === y.name; }); // TODO: Replace Underscore.js dependency. [LK:2020-03-02] }); - ensureIcons(items); - - var itemPicker = { + editorService.open({ config: { title: "Choose...", enableFilter: Object.toBoolean(config.enableFilter), enableMultiple: Object.toBoolean(config.enableMultiple), + defaultIcon: config.defaultIcon, items: items, listType: config.listType, orderBy: config.overlayOrderBy, @@ -104,37 +109,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. close: function () { editorService.close(); } - }; - - editorService.open(itemPicker); - }; - - function bind() { - - vm.items = []; - - if ($scope.model.value.length > 0 && config.items.length > 0) { - var orphaned = []; - - $scope.model.value.forEach(function (v) { - var item = _.find(config.items, function (x) { return x.value === v }); // TODO: Replace Underscore.js dependency. [LK:2020-03-02] - if (item) { - vm.items.push(Object.assign({}, item)); - } else { - orphaned.push(v); - } - }); - - if (orphaned.length > 0) { - $scope.model.value = _.difference($scope.model.value, orphaned); // TODO: Replace Underscore.js dependency. [LK:2020-03-02] - - if ((config.maxItems === 0 || config.maxItems === "0") || $scope.model.value.length < config.maxItems) { - vm.allowAdd = true; - } - } - - ensureIcons(vm.items); - } + }); }; function remove($index) { @@ -166,14 +141,6 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. }); }; - function ensureIcons(items) { - items.forEach(function (x) { - if (x.hasOwnProperty("icon") === false) { - x.icon = config.defaultIcon; - } - }); - } - function setDirty() { if ($scope.propertyForm) { $scope.propertyForm.$setDirty(); diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/_components.js b/src/Umbraco.Community.Contentment/DataEditors/_/_components.js new file mode 100644 index 00000000..27fe2cfe --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/_/_components.js @@ -0,0 +1,298 @@ +/* Copyright © 2020 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +angular.module("umbraco.directives").component("contentmentListEditor", { + templateUrl: "/App_Plugins/Contentment/components/list-editor.html", + bindings: { + addButtonLabel: "@?", + addButtonLabelKey: " 0) { + vm.umbProperty.setPropertyActions(vm.propertyActions); + } + }; + + function add() { + if (typeof (vm.onAdd) === "function") { + vm.onAdd(); + } + }; + + function canEdit(item, $index) { + switch (typeof (vm.allowEdit)) { + case "boolean": + return vm.allowEdit; + case "function": + return vm.allowEdit(item, $index); + default: + return true; + } + }; + + function canRemove(item, $index) { + switch (typeof (vm.allowRemove)) { + case "boolean": + return vm.allowRemove; + case "function": + return vm.allowRemove(item, $index); + default: + return true; + } + }; + + function edit($index) { + if (typeof (vm.onEdit) === "function") { + vm.onEdit($index); + } + }; + + function populate(item, $index, propertyName) { + if (typeof (vm.getItem) === "function") { + return vm.getItem(item, $index, propertyName); + } + + switch (propertyName) { + case "icon": + return typeof (vm.getItemIcon) === "function" + ? vm.getItemIcon(item, $index) + : item.icon || vm.defaultIcon; + + case "name": + return typeof (vm.getItemName) === "function" + ? vm.getItemName(item, $index) + : item.name; + + case "description": + return typeof (vm.getItemDescription) === "function" + ? vm.getItemDescription(item, $index) + : item.description; + + default: + return item[propertyName]; + } + }; + + function remove($index) { + if (typeof (vm.onRemove) === "function") { + vm.onRemove($index); + } + }; + }] +}); + +angular.module("umbraco.directives").component("contentmentStackEditor", { + templateUrl: "/App_Plugins/Contentment/components/stack-editor.html", + bindings: { + addButtonLabel: "@?", + addButtonLabelKey: " 0) { + vm.umbProperty.setPropertyActions(vm.propertyActions); + } + }; + + function add() { + if (typeof (vm.onAdd) === "function") { + vm.onAdd(); + } + }; + + function canEdit(item, $index) { + switch (typeof (vm.allowEdit)) { + case "boolean": + return vm.allowEdit; + case "function": + return vm.allowEdit(item, $index); + default: + return true; + } + }; + + function canRemove(item, $index) { + switch (typeof (vm.allowRemove)) { + case "boolean": + return vm.allowRemove; + case "function": + return vm.allowRemove(item, $index); + default: + return true; + } + }; + + function edit($index) { + if (typeof (vm.onEdit) === "function") { + vm.onEdit($index); + } + }; + + function populate(item, $index, propertyName) { + if (typeof (vm.getItem) === "function") { + return vm.getItem(item, $index, propertyName); + } + + switch (propertyName) { + case "icon": + return typeof (vm.getItemIcon) === "function" + ? vm.getItemIcon(item, $index) + : item.icon || vm.defaultIcon; + + case "name": + return typeof (vm.getItemName) === "function" + ? vm.getItemName(item, $index) + : item.name; + + case "description": + return typeof (vm.getItemDescription) === "function" + ? vm.getItemDescription(item, $index) + : item.description; + + default: + return item[propertyName]; + } + }; + + function remove($index) { + if (typeof (vm.onRemove) === "function") { + vm.onRemove($index); + } + }; + + }] +}); diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index 321e1f9b..97368b8f 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -454,7 +454,10 @@ + + + diff --git a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/components/list-editor.html b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/components/list-editor.html new file mode 100644 index 00000000..1653c8f8 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/components/list-editor.html @@ -0,0 +1,28 @@ + + +
+
+ + +
+ +
diff --git a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/components/stack-editor.html b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/components/stack-editor.html new file mode 100644 index 00000000..dc0e6ced --- /dev/null +++ b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/components/stack-editor.html @@ -0,0 +1,54 @@ + + +
+
+
+ +
+
+ + +
+
+
+ +
+
+
+ +
+
+
+ +
+ +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+ +
+
+ +
From e2e11564eb96b603f2c02121883545ea0b7d8b84 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Sun, 4 Oct 2020 19:23:13 +0100 Subject: [PATCH 47/86] ContentBlocks - updated notes for overlay size. --- .../ContentBlocks/ContentBlocksTypesConfigurationField.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs index c9e9fb58..cf7946ab 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksTypesConfigurationField.cs @@ -38,7 +38,7 @@ public ContentBlocksTypesConfigurationField(IEnumerable elementTyp { Key = "overlaySize", Name = "Editor overlay size", - Description = "Select the size of the overlay editing panel. By default this is set to 'large'. However if the editor fields require a smaller panel, select 'small'.", + Description = "Select the size of the overlay editing panel. By default this is set to 'small'. However if the editor fields require a wider panel, please select 'medium' or 'large'.", View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath), Config = new Dictionary { From 6841c42c1e698cbc2fef031756826e6841e9a150 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Sun, 4 Oct 2020 19:23:34 +0100 Subject: [PATCH 48/86] ContentBlocks - updated List view description --- .../DataEditors/ContentBlocks/DisplayModes/ListDisplayMode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/ListDisplayMode.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/ListDisplayMode.cs index 7f297873..19bcaec1 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/ListDisplayMode.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/DisplayModes/ListDisplayMode.cs @@ -13,7 +13,7 @@ internal class ListDisplayMode : IContentBlocksDisplayMode { public string Name => "List"; - public string Description => "Blocks will be displayed similar to a content picker."; + public string Description => "Blocks will be displayed in a list similar to a content picker."; public string Icon => DataListDataEditor.DataEditorIcon; From c3d1909e90bb9b329f0b4259da146efc284f75df Mon Sep 17 00:00:00 2001 From: leekelleher Date: Sun, 4 Oct 2020 19:24:25 +0100 Subject: [PATCH 49/86] ItemPicker - updated markup for default Icon Also adds accessibility update for Grid view. --- .../DataEditors/ItemPicker/item-picker.overlay.html | 8 ++++---- .../DataEditors/ItemPicker/item-picker.overlay.js | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.overlay.html b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.overlay.html index ce162983..d877a210 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.overlay.html +++ b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.overlay.html @@ -37,10 +37,10 @@ @@ -48,7 +48,7 @@
  • - + diff --git a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.overlay.js b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.overlay.js index 87d5b040..239b9a4e 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.overlay.js +++ b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.overlay.js @@ -13,6 +13,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.Overlays.Ite title: "Select...", enableFilter: false, enableMultiple: false, + defaultIcon: "icon-science", items: [], listType: "grid", orderBy: "name", @@ -26,6 +27,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.Overlays.Ite vm.title = config.title; vm.enableFilter = config.enableFilter; vm.enableMultiple = config.enableMultiple; + vm.defaultIcon = config.defaultIcon; vm.items = config.items; vm.listType = config.listType; vm.orderBy = config.orderBy; From 9669750fd8e28615db14f7b6e94e38fc8ee80ab8 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Sun, 4 Oct 2020 19:24:36 +0100 Subject: [PATCH 50/86] Bytes - added missing semi-colon --- src/Umbraco.Community.Contentment/DataEditors/Bytes/bytes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/Bytes/bytes.js b/src/Umbraco.Community.Contentment/DataEditors/Bytes/bytes.js index f3b6c8f8..c7ba8d3b 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Bytes/bytes.js +++ b/src/Umbraco.Community.Contentment/DataEditors/Bytes/bytes.js @@ -19,5 +19,5 @@ angular.module("umbraco.filters").filter("formatBytes", function () { const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; - } + }; }); From 88c1debd0c2240b7d2ccb182b24eee3d8c93a9ac Mon Sep 17 00:00:00 2001 From: leekelleher Date: Sun, 4 Oct 2020 22:01:32 +0100 Subject: [PATCH 51/86] ContentBlocks - tidied up editor view --- .../ContentBlocks/content-blocks.html | 71 ++++--------------- 1 file changed, 12 insertions(+), 59 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html index 9a381094..4f3e0ab0 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html @@ -3,65 +3,18 @@ - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at https://mozilla.org/MPL/2.0/. --> -
    -
    -
    - -
    -
    - - -
    -
    -
    - -
    -
    -
    - -
    -
    -
    - -
    - -
      -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    - -
    -
    - - +
    + allow-add="vm.allowAdd" + allow-edit="vm.allowEdit" + allow-remove="vm.allowRemove" + allow-sort="vm.allowSort" + get-item-name="vm.populateName" + on-add="vm.add" + on-edit="vm.edit" + on-remove="vm.remove" + on-sort="vm.sort" + block-actions="vm.blockActions" + property-actions="vm.propertyActions">
    From f7b1fff1e29d3200064aa72dafb1750d85dd33a0 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Sun, 4 Oct 2020 22:18:22 +0100 Subject: [PATCH 52/86] ConfigurationEditor + ItemPicker markup tweaks --- .../ConfigurationEditor/configuration-editor.overlay.html | 6 +++--- .../DataEditors/ItemPicker/item-picker.overlay.html | 5 +---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.overlay.html b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.overlay.html index 0c5f1794..807d582a 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.overlay.html +++ b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.overlay.html @@ -3,7 +3,7 @@ - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at https://mozilla.org/MPL/2.0/. --> -
    +
    diff --git a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.overlay.html b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.overlay.html index d877a210..18e9477c 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.overlay.html +++ b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.overlay.html @@ -9,7 +9,6 @@ - file, You can obtain one at https://mozilla.org/MPL/2.0/. -->
    - -
    - From 8c3d64922f20e6ace1fe60cbc062f6b0ab0b01a9 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 6 Oct 2020 15:25:43 +0100 Subject: [PATCH 53/86] DataList - FileSystem data source, adds "friendly name" option Resolves #42 --- .../DataSources/PhysicalFileSystemDataSource.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs index 964a4a0a..59d11dfa 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs @@ -36,6 +36,13 @@ public sealed class PhysicalFileSystemDataSource : IDataListSource Name = "Filename filter", Description = "Enter a wildcard filter for the filenames. e.g. *.css", View = "textstring", + }, + new ConfigurationField + { + Key = "friendlyName", + Name = "Use friendly filenames?", + Description = "Enabling this option will remove the file extension and spaces-out any uppercase letters, hyphens and underscores.", + View = "boolean", } }; @@ -49,6 +56,7 @@ public IEnumerable GetItems(Dictionary config) { var path = config.GetValueAs("path", string.Empty); var filter = config.GetValueAs("filter", string.Empty); + var friendlyName = config.GetValueAs("friendlyName", false); var virtualRoot = string.IsNullOrWhiteSpace(path) == false ? path.EnsureEndsWith("/") @@ -63,7 +71,7 @@ public IEnumerable GetItems(Dictionary config) return files.Select(x => new DataListItem { - Name = x, + Name = friendlyName == true ? x.SplitPascalCasing().ToFriendlyName() : x, Value = virtualRoot + x, Description = virtualRoot + x, }); From 0a991f6e0b40edd1000800deb15d759a690ed6d1 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 6 Oct 2020 15:26:09 +0100 Subject: [PATCH 54/86] DataList - FileSystem data source, added default icon. --- .../DataList/DataSources/PhysicalFileSystemDataSource.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs index 59d11dfa..a2d20c8d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/PhysicalFileSystemDataSource.cs @@ -74,6 +74,7 @@ public IEnumerable GetItems(Dictionary config) Name = friendlyName == true ? x.SplitPascalCasing().ToFriendlyName() : x, Value = virtualRoot + x, Description = virtualRoot + x, + Icon = Core.Constants.Icons.DefaultIcon, }); } } From f9f9b09ab7627b51c801b360e66e3c5a1fa7454f Mon Sep 17 00:00:00 2001 From: Chriztian Steinmeier Date: Sun, 25 Oct 2020 00:18:26 +0200 Subject: [PATCH 55/86] Create da.xml --- .../UI/App_Plugins/Contentment/lang/da.xml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/da.xml diff --git a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/da.xml b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/da.xml new file mode 100644 index 00000000..58905b97 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/da.xml @@ -0,0 +1,25 @@ + + + + Chriztian Steinmeier + https://greystate.dk + + + Contentment + + + Vælg og konfigurer en visningstype + Vælg og konfigurer en elementtype + + Kopier alle blokke + Rediger værdi + + Kopier indholdselement + Opret indholdsskabelon... + + + Vælg og konfigurer en datakilde + Vælg og konfigurer en liste + + + From e04e4fc9b39923449b2612aa4686a9e8e90693e7 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Sun, 25 Oct 2020 22:49:55 +0000 Subject: [PATCH 56/86] Fixed bug with list/stack editor components When editing the raw JSON in "dev mode", it didn't update the view-model in the component. Refactored so that it now does. Both components expect the `ng-model` attribute. --- .../ConfigurationEditor/configuration-editor.html | 3 ++- .../DataEditors/ContentBlocks/content-blocks.html | 3 ++- .../DataEditors/ContentBlocks/content-list.html | 3 ++- .../DataEditors/ItemPicker/item-picker.html | 5 +++-- .../DataEditors/_/_components.js | 8 ++------ .../App_Plugins/Contentment/components/list-editor.html | 4 ++-- .../App_Plugins/Contentment/components/stack-editor.html | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.html b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.html index 78b170ba..1146bc38 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.html +++ b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.html @@ -4,7 +4,8 @@ - file, You can obtain one at https://mozilla.org/MPL/2.0/. -->
    -
    -
    -
    -
    -
    - +
    -
    -
    +
    +
    From 362850b6b1c44565410968782135efab10917e93 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Sun, 25 Oct 2020 22:50:52 +0000 Subject: [PATCH 57/86] Dev mode - ensured auto-focus of JSON editor --- .../DataEditors/_/_dev-mode.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/_dev-mode.js b/src/Umbraco.Community.Contentment/DataEditors/_/_dev-mode.js index c877a29b..e2f97aea 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/_dev-mode.js +++ b/src/Umbraco.Community.Contentment/DataEditors/_/_dev-mode.js @@ -4,8 +4,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ angular.module("umbraco.services").factory("Umbraco.Community.Contentment.Services.DevMode", [ + "$timeout", "editorService", - function (editorService) { + function ($timeout, editorService) { return { editValue: function (model, callback) { editorService.open({ @@ -24,6 +25,11 @@ angular.module("umbraco.services").factory("Umbraco.Community.Contentment.Servic fontSize: "14px", wrap: true }, + onLoad: function (_editor) { + $timeout(function () { + _editor.focus(); + }); + }, }, view: "/App_Plugins/Contentment/editors/_json-editor.html", size: "medium", From ca214fc57cafbcfb4284805270b5a45d46f8def8 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Sun, 25 Oct 2020 22:51:56 +0000 Subject: [PATCH 58/86] ConfigurationEditor - added support for name & description templates Can now use AngularJS expressions. --- .../ConfigurationEditorModel.cs | 6 ++++ .../configuration-editor.js | 36 +++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorModel.cs b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorModel.cs index 09faebde..63e0bcd4 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorModel.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/ConfigurationEditorModel.cs @@ -29,5 +29,11 @@ public sealed class ConfigurationEditorModel public Dictionary DefaultValues { get; set; } public OverlaySize OverlaySize { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string NameTemplate { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string DescriptionTemplate { get; set; } } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.js b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.js index 8900a4a1..f1c78c12 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.js +++ b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.js @@ -5,11 +5,12 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors.ConfigurationEditor.Controller", [ "$scope", + "$interpolate", "editorService", "localizationService", "overlayService", "Umbraco.Community.Contentment.Services.DevMode", - function ($scope, editorService, localizationService, overlayService, devModeService) { + function ($scope, $interpolate, editorService, localizationService, overlayService, devModeService) { // console.log("config-editor.model", $scope.model); @@ -47,10 +48,21 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. config.itemLookup = {}; config.allowEdit = {}; + config.nameTemplates = {}; + config.descriptionTemplates = {}; config.items.forEach(function (item) { config.itemLookup[item.key] = item; + config.allowEdit[item.key] = item.fields && item.fields.length > 0; + + if (item.nameTemplate) { + config.nameTemplates[item.key] = $interpolate(item.nameTemplate); + } + + if (item.descriptionTemplate) { + config.descriptionTemplates[item.key] = $interpolate(item.descriptionTemplate); + } }); vm.allowAdd = (config.maxItems === 0 || config.maxItems === "0") || $scope.model.value.length < config.maxItems; @@ -156,7 +168,27 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. }; function populate(item, $index, propertyName) { - return config.itemLookup[item.key][propertyName]; + var label = ""; + + if (propertyName === 'name' && config.nameTemplates.hasOwnProperty(item.key) === true) { + var expression = config.nameTemplates[item.key]; + if (expression) { + item.value.$index = $index + 1; + label = expression(item.value); + delete item.value.$index; + } + } + + if (propertyName === 'description' && config.descriptionTemplates.hasOwnProperty(item.key) === true) { + var expression = config.descriptionTemplates[item.key]; + if (expression) { + item.value.$index = $index + 1; + label = expression(item.value); + delete item.value.$index; + } + } + + return label || config.itemLookup[item.key][propertyName]; }; function remove($index) { From 74f184a218b2c8b1f5a06b00715cf2435159c8bf Mon Sep 17 00:00:00 2001 From: leekelleher Date: Sun, 25 Oct 2020 22:54:54 +0000 Subject: [PATCH 59/86] ContentBlocks - code tidy-up - ContentBlockPreviewModel - remove extra constructor, the params weren't being used. - ContentBlockPreviewView - added TODO notes for whether to remove the props from the view-data. - JS, made the boolean conditions explicit, and fixed bug with the `allowCreateContentTemplate` check. --- .../DataEditors/ContentBlocks/ContentBlockPreviewModel.cs | 5 +---- .../DataEditors/ContentBlocks/ContentBlockPreviewView.cs | 4 ++++ .../DataEditors/ContentBlocks/content-blocks.js | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewModel.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewModel.cs index be0bb7fa..bc56a20b 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewModel.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewModel.cs @@ -9,10 +9,7 @@ namespace Umbraco.Community.Contentment.DataEditors { public class ContentBlockPreviewModel : ContentBlockPreviewModel - { - public ContentBlockPreviewModel(IPublishedContent content, IPublishedElement element) - { } - } + { } public class ContentBlockPreviewModel : IContentModel where TPublishedContent : IPublishedContent diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs index 45aa6af2..18ba9e8b 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs @@ -25,11 +25,15 @@ protected override void SetViewData(ViewDataDictionary viewData) if (viewData.TryGetValue("content", out var tmp1) && tmp1 is TPublishedContent t1) { model.Content = t1; + // TODO: [LK] Remove "content" from ViewData? + // viewData.Remove("content"); } if (viewData.TryGetValue("element", out var tmp2) && tmp2 is TPublishedElement t2) { model.Element = t2; + // TODO: [LK] Remove "element" from ViewData? + // viewData.Remove("element"); } viewData.Model = model; diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js index b598907c..d32cea5c 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.js @@ -93,7 +93,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. vm.propertyActions = []; - if (vm.allowCopy) { + if (vm.allowCopy === true) { vm.propertyActions.push({ labelKey: "contentment_copyAllBlocks", icon: "documents", @@ -105,7 +105,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. }); } - if (Object.toBoolean(config.enableDevMode)) { + if (Object.toBoolean(config.enableDevMode) === true) { vm.propertyActions.push({ labelKey: "contentment_editRawValue", icon: "brackets", @@ -122,7 +122,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. function actionsFactory($index) { var actions = []; - if (vm.allowCopy) { + if (vm.allowCopy === true) { actions.push({ labelKey: "contentment_copyContentBlock", icon: "documents", @@ -132,7 +132,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. }); } - if (config.allowCreateContentTemplate) { + if (Object.toBoolean(config.allowCreateContentTemplate) === true) { actions.push({ labelKey: "contentment_createContentTemplate", icon: "blueprint", From 487ebab98efcbf49b9caf4784ea542644dd5f3d7 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Sun, 25 Oct 2020 23:03:08 +0000 Subject: [PATCH 60/86] [WIP] ContentBlocks doco Added notes - brain-dump! --- .github/README.md | 2 +- docs/README.md | 2 +- docs/editors/content-blocks.md | 44 ++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/.github/README.md b/.github/README.md index 9093e7f5..97c8dbe8 100644 --- a/.github/README.md +++ b/.github/README.md @@ -24,7 +24,7 @@ Let's take a look inside... ##### Property Editors - [Bytes](../docs/editors/bytes.md) - a read-only label to display file sizes in relative bytes. -- [Content Blocks](../docs/editors/content-blocks.md) - a stack block editor, configurable using element types. +- [Content Blocks](../docs/editors/content-blocks.md) - a block editor, configurable using element types. - [Data List](../docs/editors/data-list.md) - an editor that combines a custom data source with a custom list editor. - [Icon Picker](../docs/editors/icon-picker.md) - an editor to select an icon (from the Umbraco icon library). - [Notes](../docs/editors/notes.md) - a read-only label to display rich-text instructional messages for content editors. diff --git a/docs/README.md b/docs/README.md index 311939a0..97ce9a19 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,7 +9,7 @@ Here is the documentation for the Contentment property-editors... - [Bytes](../docs/editors/bytes.md) - a read-only label to display file sizes in relative bytes. -- [Content Blocks](../docs/editors/content-blocks.md) - a stack block editor, configurable using element types. +- [Content Blocks](../docs/editors/content-blocks.md) - a block editor, configurable using element types. - [Data List](../docs/editors/data-list.md) - an editor that combines a custom data source with a custom list editor. - [Icon Picker](../docs/editors/icon-picker.md) - an editor to select an icon (from the Umbraco icon library). - [Notes](../docs/editors/notes.md) - a read-only label to display rich-text instructional messages for content editors. diff --git a/docs/editors/content-blocks.md b/docs/editors/content-blocks.md index a269240d..b4b48479 100644 --- a/docs/editors/content-blocks.md +++ b/docs/editors/content-blocks.md @@ -4,19 +4,63 @@ ### Content Blocks +Content Blocks is a property-editor used for creating a list of structured content, with each block configurable using an element type. + +> If you are using Umbraco 8.7 (or above), this may sound familiar to the [Block List Editor](https://our.umbraco.com/Documentation/Getting-Started/Backoffice/Property-Editors/Built-in-Property-Editors/Block-List-Editor/), and you may be questioning why you should use Content Blocks over the built-in Block List Editor? It's a good question, but you'll find no comparisons or marketing spin from me, I'd recommend that you stick with Umbraco's built-in editors. Content Blocks has subtle differences, it's entirely your choice. + +> For long time fans of Umbraco v7.x, if you recall the [Stacked Content](https://our.umbraco.com/packages/backoffice-extensions/stacked-content) editor, then Content Blocks could be considered its spiritual successor. ### How to configure the editor? +> Select the data type, here are the options +> +> Display mode - Stack or List (extensible) +> +> Block types - configure element types +> - element type +> - name template +> - overlay size +> - enable preview +> +> Enable filter - in overlay +> +> Max items +> +> Disable sorting +> +> Enable dev mode ### How to use the editor? +> Press the Add button +> +> Select block from overlay +> +> Edit in panel. +> +> Options - copy block; create content template + + +#### Previews + +> Razor views, found within the /Views/Partials/Blocks/ folder +> +> Mention the view-model + view-data properties (icon, index/position) +> +> NOTE: Preview doesn't work on a new unsaved page. As it has no page context. ### How to get the value? +> `IEnumerable` +> If using ModelsBuilder, it'll be castable to the desired element type model. + ### Further reading +> Alternative block editor options (for Umbraco v8) +> Perplex Content Blocks +> Bento editor From 7129b760ce7d76729e13fef738d9043a8ec7becc Mon Sep 17 00:00:00 2001 From: leekelleher Date: Sun, 25 Oct 2020 23:04:21 +0000 Subject: [PATCH 61/86] JSON DataSource - changed the default values to use the Grid Editor JSON, as I found the JSONPath "book" example a bit confusing. --- .../DataEditors/DataList/DataSources/JsonDataListSource.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs index 564c3698..55acac26 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs @@ -87,9 +87,11 @@ public JsonDataListSource(ILogger logger) public Dictionary DefaultValues => new Dictionary { - { "itemsJsonPath", "$..book[?(@.enabled == true)]" }, + { "url", "~/config/grid.editors.config.js"}, + { "itemsJsonPath", "$[*]" }, { "nameJsonPath", "$.name" }, - { "valueJsonPath", "$.id" }, + { "valueJsonPath", "$.alias" }, + { "iconJsonPath", "$.icon" }, }; public IEnumerable GetItems(Dictionary config) From 6beb75ff139f462b786115fd8963051d0a1bd371 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Sun, 25 Oct 2020 23:07:28 +0000 Subject: [PATCH 62/86] Build script update Changed from using Powershell's `Set-Content` to `IO.File.WriteAllLines`, as it was trying to convert UTF-8 to ASCII encoding. --- build/build-assets.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/build-assets.ps1 b/build/build-assets.ps1 index 32e31304..80944ce9 100644 --- a/build/build-assets.ps1 +++ b/build/build-assets.ps1 @@ -45,14 +45,14 @@ $htmlFiles = Get-ChildItem -Path "${ProjectDir}DataEditors" -Recurse -Force -Inc foreach($htmlFile in $htmlFiles){ $contents = Get-Content -Path $htmlFile.FullName; $minifiedHtml = [Regex]::Replace($contents, "^", ""); - Set-Content -Path "${pluginFolder}\editors\$($htmlFile.Name)" -Value $minifiedHtml; + [IO.File]::WriteAllLines("${pluginFolder}\editors\$($htmlFile.Name)", $minifiedHtml); } # Razor Templates - Copy $razorFiles = Get-ChildItem -Path "${ProjectDir}DataEditors" -Recurse -Force -Include *.cshtml; foreach($razorFile in $razorFiles){ $contents = Get-Content -Path $razorFile.FullName; - Set-Content -Path "${pluginFolder}\render\$($razorFile.Name)" -Value $contents; + [IO.File]::WriteAllLines("${pluginFolder}\render\$($razorFile.Name)", $contents); } # CSS - Bundle & Minify From ba20147c0efffc1561c3e4d564904b955387c05c Mon Sep 17 00:00:00 2001 From: leekelleher Date: Sun, 25 Oct 2020 23:10:00 +0000 Subject: [PATCH 63/86] ItemPicker overlay - change to
  • From e31039831a6ceec0b88133c381991c3a32042e27 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Sun, 25 Oct 2020 23:12:10 +0000 Subject: [PATCH 64/86] Included Danish localization file in the VS proj --- .../Umbraco.Community.Contentment.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index 97368b8f..e4751e27 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -463,6 +463,7 @@ + From 5542b4e776da9c0eb46e81076d3ef7a3713f651a Mon Sep 17 00:00:00 2001 From: leekelleher Date: Sun, 25 Oct 2020 23:35:57 +0000 Subject: [PATCH 65/86] ItemPicker - added option for "overlay size" Generally, I envisaged the overlay to be "small" width, but if there are longer names/descriptions for the items, then a wider overlay would make sense. I have doubts whether "large" would be used, but better to make the option available. --- .../ItemPicker/ItemPickerDataListEditor.cs | 22 +++++++++++++++++-- .../DataEditors/ItemPicker/item-picker.js | 3 ++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs index 8cee0b11..efa1d02f 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/ItemPickerDataListEditor.cs @@ -22,6 +22,24 @@ public sealed class ItemPickerDataListEditor : IDataListEditor public IEnumerable Fields => new ConfigurationField[] { + new ConfigurationField + { + Key = "overlaySize", + Name = "Editor overlay size", + Description = "Select the size of the overlay editing panel. By default this is set to 'small'. However if the editor fields require a wider panel, please select 'medium' or 'large'.", + View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath), + Config = new Dictionary + { + { Constants.Conventions.ConfigurationFieldAliases.Items, new[] + { + new DataListItem { Name = "Small", Value = "small" }, + new DataListItem { Name = "Medium", Value = "medium" }, + new DataListItem { Name = "Large", Value = "large" } + } + }, + { Constants.Conventions.ConfigurationFieldAliases.DefaultValue, "small" } + } + }, new DefaultIconConfigurationField(), new ConfigurationField { @@ -33,8 +51,8 @@ public sealed class ItemPickerDataListEditor : IDataListEditor { { Constants.Conventions.ConfigurationFieldAliases.Items, new[] { - new DataListItem { Name = "Grid", Value = "grid", Description = "Grid displays as a card based layout, (3 or 4 cards per row)." }, - new DataListItem { Name = "List", Value = "list", Description = "List will display as a menu of single items." } + new DataListItem { Name = "Grid", Value = "grid", Description = "Displays as a card based layout, (3 cards per row)." }, + new DataListItem { Name = "List", Value = "list", Description = "Displays as a single column menu, (with descriptions, if available)." } } }, { ShowDescriptionsConfigurationField.ShowDescriptions, Constants.Values.True }, diff --git a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.js b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.js index cd101a4e..7057117b 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.js +++ b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.js @@ -24,6 +24,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. listType: "grid", overlayView: "", overlayOrderBy: "name", + overlaySize: "small", }; var config = Object.assign({}, defaultConfig, $scope.model.config); @@ -90,7 +91,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. orderBy: config.overlayOrderBy, }, view: config.overlayView, - size: "small", + size: config.overlaySize || "small", submit: function (selectedItems) { selectedItems.forEach(function (x) { From 1145ed9f660aebb20c695864100294099ad16d8b Mon Sep 17 00:00:00 2001 From: renanod Date: Thu, 29 Oct 2020 02:27:52 -0300 Subject: [PATCH 66/86] Issue #47: add PT-BR translation --- .../UI/App_Plugins/Contentment/lang/pt-br.xml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/pt-br.xml diff --git a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/pt-br.xml b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/pt-br.xml new file mode 100644 index 00000000..aa0b3995 --- /dev/null +++ b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/pt-br.xml @@ -0,0 +1,25 @@ + + + + Renan Domingues + https://github.com/RenanOD + + + Contentamento + + + Selecionar e configurar o modo de exibição + Selecionar e configurar o tipo de elemento + + Copiar todos os blocos + Alterar o valor original + + Copiar o conteúdo do bloco + Criar modelo de conteúdo... + + + Selecionar e configurar a origem dos dados + Selecionar e configurar o editor de lista + + + From 6c6f9c74dd1966c58040904832211c510cbc70a9 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 10 Nov 2020 13:29:01 +0000 Subject: [PATCH 67/86] Included Brazilian Portuguese localization file in the VS proj --- .../Umbraco.Community.Contentment.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj index e4751e27..2e685830 100644 --- a/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj +++ b/src/Umbraco.Community.Contentment/Umbraco.Community.Contentment.csproj @@ -465,6 +465,7 @@ + From 51c2407b6468f71698cfc4d243eca7e0d2b4282e Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 10 Nov 2020 13:29:31 +0000 Subject: [PATCH 68/86] ItemPicker - made Add label to be configurable --- .../DataEditors/ItemPicker/item-picker.html | 2 +- .../DataEditors/ItemPicker/item-picker.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.html b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.html index 47a23d94..57a38ac1 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.html +++ b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.html @@ -5,7 +5,7 @@
    Date: Tue, 10 Nov 2020 14:48:04 +0000 Subject: [PATCH 69/86] [WIP] ContentBlocks - further work on docs --- ...ontent-blocks--configuration-editor-01.png | Bin 0 -> 24896 bytes ...ontent-blocks--configuration-editor-02.png | Bin 0 -> 6434 bytes ...ontent-blocks--configuration-editor-03.png | Bin 0 -> 16594 bytes ...ontent-blocks--configuration-editor-04.png | Bin 0 -> 15593 bytes ...ontent-blocks--configuration-editor-05.png | Bin 0 -> 5040 bytes docs/editors/content-blocks.md | 80 +++++++++++++----- 6 files changed, 59 insertions(+), 21 deletions(-) create mode 100644 docs/editors/content-blocks--configuration-editor-01.png create mode 100644 docs/editors/content-blocks--configuration-editor-02.png create mode 100644 docs/editors/content-blocks--configuration-editor-03.png create mode 100644 docs/editors/content-blocks--configuration-editor-04.png create mode 100644 docs/editors/content-blocks--configuration-editor-05.png diff --git a/docs/editors/content-blocks--configuration-editor-01.png b/docs/editors/content-blocks--configuration-editor-01.png new file mode 100644 index 0000000000000000000000000000000000000000..f330b558a4e8eaf512f3b6ae30350c0eaf82923e GIT binary patch literal 24896 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 i_?I#MMObE56uS<- literal 0 HcmV?d00001 diff --git a/docs/editors/content-blocks--configuration-editor-03.png b/docs/editors/content-blocks--configuration-editor-03.png new file mode 100644 index 0000000000000000000000000000000000000000..f13f54ef5ee264cebb861bfc56feae7ae22e7b85 GIT binary patch literal 16594 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 SfB8oNBqceJY^C(Kp#KY5+)s4? literal 0 HcmV?d00001 diff --git a/docs/editors/content-blocks--configuration-editor-04.png b/docs/editors/content-blocks--configuration-editor-04.png new file mode 100644 index 0000000000000000000000000000000000000000..765e81f647bb9c007e77610ac40d259644692f17 GIT binary patch literal 15593 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 This documentation is a _**work-in-progress.**_ There will be plot holes and missing pieces. Please bear with me. + ### Content Blocks Content Blocks is a property-editor used for creating a list of structured content, with each block configurable using an element type. -> If you are using Umbraco 8.7 (or above), this may sound familiar to the [Block List Editor](https://our.umbraco.com/Documentation/Getting-Started/Backoffice/Property-Editors/Built-in-Property-Editors/Block-List-Editor/), and you may be questioning why you should use Content Blocks over the built-in Block List Editor? It's a good question, but you'll find no comparisons or marketing spin from me, I'd recommend that you stick with Umbraco's built-in editors. Content Blocks has subtle differences, it's entirely your choice. +> If you are using Umbraco 8.7 (or above), this may sound familiar to the [Block List Editor](https://our.umbraco.com/Documentation/Getting-Started/Backoffice/Property-Editors/Built-in-Property-Editors/Block-List-Editor/), and you may be asking yourself why should you use Content Blocks over the built-in Block List Editor? It's a good question, and you'll find no marketing spin from me, I'd recommend that you stick with Umbraco's built-in editors. Content Blocks has subtle differences, it's entirely your choice. > For long time fans of Umbraco v7.x, if you recall the [Stacked Content](https://our.umbraco.com/packages/backoffice-extensions/stacked-content) editor, then Content Blocks could be considered its spiritual successor. ### How to configure the editor? -> Select the data type, here are the options -> -> Display mode - Stack or List (extensible) -> -> Block types - configure element types -> - element type -> - name template -> - overlay size -> - enable preview -> -> Enable filter - in overlay -> -> Max items -> -> Disable sorting -> -> Enable dev mode +In your new Data Type, selected the "[Contentment] Content Blocks" option. You will see the following configuration fields. + +The two main fields are "**Display mode**" and "**Block types**", the rest are for further configuration. + +![Configuration Editor for Content Blocks - empty state](content-blocks--configuration-editor-01.png) + +The **Display mode** is pre-configured to use the **Stack** mode, this enables a richer editing experience. Alternatively, if you prefer to use an interface similar to Umbraco's Content Picker editor, you can remove the **Stack** configuration and use the **List** mode instead. Each display mode comes with their own configuration options, _e.g. the Stack mode has a feature to create a **Content template** from each block item._ + +![Configuration Editor for Content Blocks - the Stack display mode configuration](content-blocks--configuration-editor-02.png) + +> **Note:** You can add your own custom display modes by implementing the [`IContentBlocksDisplayMode`](https://github.com/leekelleher/umbraco-contentment/blob/develop/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/IContentBlocksDisplayMode.cs) interface. +> `// TODO: Write documentation on developing custom display modes.` + +Next is to select and configure the **Block types**. By pressing the **Select and configure an element type** button, you will be presented with a selection of element types. + +> **Note:** If you have not created any element types. Please refer to [Umbraco's documentation on how to create an element type](https://our.umbraco.com/documentation/Getting-Started/Data/Defining-content/). + +![Configuration Editor for Content Blocks - list of available element types](content-blocks--configuration-editor-03.png) + +Once you have selected an element type, you will be presented with the configuration options for that block type. + +![Configuration Editor for Content Blocks - block type configuration](content-blocks--configuration-editor-04.png) + +The **Name template** field can be used to enter an AngularJS expression, which is evaluated against each block (of this type) to display its name. If this field is left empty, the default name template will be `"Item {{ $index }}"`. + +The **Editor overlay size** option will configure the size (width) of the overlay editing panel. The options are Small, Medium and Large. The default value is "Small", this is typically ideal for consise element types, e.g. with a heading, media picker and intro blurb textarea. For element types with heavier content, e.g. Rich Text editors, then "Medium" or "Large" would be a more suitable option. + +The **Enable preview?** option can be enabled to render a richer preview of the content block item. The preview mechanism uses a Razor (`.cshtml`) partial-view for rendering. + +> **Note:** For details on how to render a preview, see the [Previews](#previews) section below. + +Once you have configured the block type, press the **Done** button at the bottom of the overlay. + +The rest of the configuration options can give finer control over the editing experience. + +The **Enable filter?** option can enable a search filer at the top of the overlay selection panel. This can be useful if you have configured many block types. + +The **Maximum items** field is used to limit the number of content blocks that the editor can have. Once the maximum is reached, the **Add** button will not be available. + +The **Disable sorting?** option will prevent the Content Blocks from being sorted. + +Lastly, the **Developer mode?** option is a special feature for those who would like to have access to the raw JSON value of the Content Block editor. Enabling this option will add a [property action](https://our.umbraco.com/Documentation/Extending/Property-Editors/Property-Actions/) called **Edit raw value**. + +![Property action for Content Blocks - edit raw value](content-blocks--configuration-editor-05.png) + +Once you have configured the Data Type, press the **Save** button and add it to your Document Type. ### How to use the editor? @@ -45,7 +76,12 @@ Content Blocks is a property-editor used for creating a list of structured conte #### Previews -> Razor views, found within the /Views/Partials/Blocks/ folder +> `// TODO: Write documentation about the Razor partial-view templates.` +> An example can be seen with [the default preview partial-view, `ContentBlockPreview.cshtml`](https://github.com/leekelleher/umbraco-contentment/blob/develop/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreview.cshtml). +> _Advanced usage using the `@inherits ContentBlockPreviewModel` declaration._ + + +> Razor views, found within the `/Views/Partials/Blocks/` folder > > Mention the view-model + view-data properties (icon, index/position) > @@ -62,5 +98,7 @@ Content Blocks is a property-editor used for creating a list of structured conte ### Further reading > Alternative block editor options (for Umbraco v8) -> Perplex Content Blocks -> Bento editor +> - Bento editor +> - Perplex Content Blocks + +- [Paul Marden's Landing Page article on Skrift](https://skrift.io/issues/part-1-landing-pages/). From c065f91a86734a464d1bbb4f8ec75b811e1d446e Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 16 Nov 2020 09:37:45 +0000 Subject: [PATCH 70/86] ItemPicker - compare `value` not `name` for duplicates Fixes #52 --- .../DataEditors/ItemPicker/item-picker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.js b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.js index 744ef955..8b41d196 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.js +++ b/src/Umbraco.Community.Contentment/DataEditors/ItemPicker/item-picker.js @@ -80,7 +80,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. function add() { var items = Object.toBoolean(config.allowDuplicates) ? config.items : _.reject(config.items, function (x) { // TODO: Replace Underscore.js dependency. [LK:2020-03-02] - return _.find(vm.items, function (y) { return x.name === y.name; }); // TODO: Replace Underscore.js dependency. [LK:2020-03-02] + return _.find(vm.items, function (y) { return x.value === y.value; }); // TODO: Replace Underscore.js dependency. [LK:2020-03-02] }); editorService.open({ From 5f2fb8546c00911c3c9321b86d0d92ac26dbd9ae Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 16 Nov 2020 18:04:12 +0000 Subject: [PATCH 71/86] [WIP] ContentBlocks - even further work on docs Also found and fixed a bug with previewing blocks. --- .../content-blocks--property-editor-01.png | Bin 0 -> 4024 bytes .../content-blocks--property-editor-02.png | Bin 0 -> 8926 bytes .../content-blocks--property-editor-03.png | Bin 0 -> 8351 bytes .../content-blocks--property-editor-04.png | Bin 0 -> 6222 bytes .../content-blocks--property-editor-05.png | Bin 0 -> 10004 bytes docs/editors/content-blocks.md | 106 ++++++++++++++---- docs/editors/data-list.md | 2 +- .../ContentBlocks/content-blocks.html | 3 +- .../DataEditors/_/_components.js | 1 + 9 files changed, 88 insertions(+), 24 deletions(-) create mode 100644 docs/editors/content-blocks--property-editor-01.png create mode 100644 docs/editors/content-blocks--property-editor-02.png create mode 100644 docs/editors/content-blocks--property-editor-03.png create mode 100644 docs/editors/content-blocks--property-editor-04.png create mode 100644 docs/editors/content-blocks--property-editor-05.png diff --git a/docs/editors/content-blocks--property-editor-01.png b/docs/editors/content-blocks--property-editor-01.png new file mode 100644 index 0000000000000000000000000000000000000000..2d71ecb52b9ef156d19f437233ebe844f57e9dd0 GIT binary patch literal 4024 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 literal 0 HcmV?d00001 diff --git a/docs/editors/content-blocks--property-editor-03.png b/docs/editors/content-blocks--property-editor-03.png new file mode 100644 index 0000000000000000000000000000000000000000..004211d04d50bb8c4b1dc06d26eba7121024e509 GIT binary patch literal 8351 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 RPcJw?`r3e2xrS53{{oTVt_uJF literal 0 HcmV?d00001 diff --git a/docs/editors/content-blocks--property-editor-04.png b/docs/editors/content-blocks--property-editor-04.png new file mode 100644 index 0000000000000000000000000000000000000000..5913cc4885cdd91d2c3047c60e3833aa3094b4e7 GIT binary patch literal 6222 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 literal 0 HcmV?d00001 diff --git a/docs/editors/content-blocks.md b/docs/editors/content-blocks.md index b97cf2d7..838f9c10 100644 --- a/docs/editors/content-blocks.md +++ b/docs/editors/content-blocks.md @@ -26,6 +26,7 @@ The **Display mode** is pre-configured to use the **Stack** mode, this enables a ![Configuration Editor for Content Blocks - the Stack display mode configuration](content-blocks--configuration-editor-02.png) > **Note:** You can add your own custom display modes by implementing the [`IContentBlocksDisplayMode`](https://github.com/leekelleher/umbraco-contentment/blob/develop/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/IContentBlocksDisplayMode.cs) interface. +> > `// TODO: Write documentation on developing custom display modes.` Next is to select and configure the **Block types**. By pressing the **Select and configure an element type** button, you will be presented with a selection of element types. @@ -65,40 +66,101 @@ Once you have configured the Data Type, press the **Save** button and add it to ### How to use the editor? -> Press the Add button -> -> Select block from overlay -> -> Edit in panel. -> -> Options - copy block; create content template +Once you have added the configured Data Type on your Document Type, the Content Blocks editor will be displayed on the content page's property panel. + +![Content Blocks property-editor - displaying the empty state, with an Add button](content-blocks--property-editor-01.png) + +By pressing the **Add content** button, an overlay with the available content blocks will appear. + +![Content Blocks property-editor - displaying the available content blocks overlay](content-blocks--property-editor-02.png) + +Selecting one of the available content blocks, you will be presented with the editing panel, (with the properties from the corresponding Element Type). + +![Content Blocks property-editor - displaying the content blocks editing panel](content-blocks--property-editor-03.png) + +Once you have finished editing the properties, press the **Done** button at the bottom of the overlay. This will close the overlay, with the content block being added to the stack/list. + +![Content Blocks property-editor - new content block has been added](content-blocks--property-editor-04.png) + +For further options, each content block has its own action menu, initially for sorting and removing content, but additional features for **Copy content block**, and **Create content template...** actions. + +![Content Blocks property-editor - content block action menu](content-blocks--property-editor-05.png) #### Previews -> `// TODO: Write documentation about the Razor partial-view templates.` -> An example can be seen with [the default preview partial-view, `ContentBlockPreview.cshtml`](https://github.com/leekelleher/umbraco-contentment/blob/develop/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreview.cshtml). -> _Advanced usage using the `@inherits ContentBlockPreviewModel` declaration._ +An advanced feature of Content Blocks is the ability to have a richer preview for each block item. To do this, make sure that you enable the **Enable preview?** option when you configure the content block. +> **Note:** The preview feature will only work with the **Stack** display mode. -> Razor views, found within the `/Views/Partials/Blocks/` folder -> -> Mention the view-model + view-data properties (icon, index/position) -> -> NOTE: Preview doesn't work on a new unsaved page. As it has no page context. +Once the preview feature is enabled, the content block item will render the preview using a Razor partial-view template. + +The default preview partial-view (that ships with Contentment), will be used. This can be found at [`"~/App_Plugins/Contentment/render/ContentBlockPreview.cshtml"`](https://github.com/leekelleher/umbraco-contentment/blob/develop/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreview.cshtml). This will render similar to the default (plain) block style, with the exception that the **Name template** value is not available, the content block item's GUID (key) will be displayed instead. + +To use your own custom preview partial-views, you must use the following convention; name the partial-view the same as the Element Type alias, and place it in **the `"~/Views/Partials/Blocks/"` folder.** If you would like to re-use the same preview partial-view for multiple content block items, you can create a `"~/Views/Partials/Blocks/Default.cshtml"` template. + +When developing your own preview partial-view template, the declaration can be one of the following... + +- `@inherits Umbraco.Web.Mvc.ContentBlockPreviewView` + This is the default declaration. This will give you access to `@Model.Content` (the current page as `IPublishedContent`), and `@Model.Element` (the content block item as `IPublishedElement`). + +- `@inherits ContentBlockPreviewModel` + This is advanced syntax, _(note, may require some trial-and-error, and sense of play)._ This can be used if you are using Umbraco's ModelsBuilder feature, where you know the object-type of the current Content Type page and Element Type item. As above, you can have strongly-typed access to the current page with `@Model.Content`, and the content block item with `@Model.Element`. + +To aid the preview, there are a number of additional properties available in the partial-view's `ViewData` dictionary. + +- `ViewData["elementIndex"]` - This is the index (`int`) of the content block item's position in the list. +- `ViewData["elementIcon"]` - This is the Element Type's icon, _(since the icon is not available on `Model.Element.ContentType`)._ +- `ViewData["contentIcon"]` - This is the Content Type's icon, _(since the icon is not available on `Model.Content.ContentType`)._ + +> **Note:** The preview feature **does not work** on a freshly created new unsaved page. This is because the preview has no context of the page itself. ### How to get the value? +The value for the Content Blocks will be as `IEnumerable` object-type. + +If you are using Umbraco's ModelsBuilder feature, then each content block item will be castable as the intended Element Type model. + +In terms of rendering the items, you can do so however you desire. As an example here, I would recommend having a separate partial-view template for each of the Element Types, (using the Element Type's alias as the filename), that way you are able to encapsulate the logic for that particular Element Type. + +Using Umbraco's Models Builder... + +```cshtml +
    + @foreach (var item in Model.ContentBlocks) + { + @Html.Partial(item.ContentType.Alias, item) + } +
    +``` + +Without ModelsBuilder... + +The weakly-typed API may give you some headaches, we suggest using strongly-typed, (or preferably Models Builder). + +Here's an example of strongly-typed... + +```cshtml +
    + @foreach (var item in @(Model.Value>("contentBlocks"))) + { + @Html.Partial(item.ContentType.Alias, item) + } + +``` + -> `IEnumerable` -> If using ModelsBuilder, it'll be castable to the desired element type model. +### Similar editors and further reading +There are several alternative block-based editors that you could use with Umbraco v8, here is a selection... -### Further reading +- [Umbraco's native Block List editor](https://our.umbraco.com/documentation/getting-started/backoffice/property-editors/built-in-property-editors/Block-List-Editor/) - available since Umbraco v8.7.0. +- [Umbraco's native Nested Content editor](https://our.umbraco.com/documentation/getting-started/backoffice/property-editors/built-in-property-editors/Nested-Content/) - available since Umbraco v7.7.0, _(since superseded by the Block List editor)._ +- [Bento editor by KOBEN Digital](https://our.umbraco.com/packages/backoffice-extensions/bento-editor/) +- [Perplex.ContentBlocks by Perplex](https://our.umbraco.com/packages/backoffice-extensions/perplexcontentblocks/) - _(ignore my naming conflict of Content Blocks, naming things is hard)._ -> Alternative block editor options (for Umbraco v8) -> - Bento editor -> - Perplex Content Blocks +For further reading, here's a selection of insights... -- [Paul Marden's Landing Page article on Skrift](https://skrift.io/issues/part-1-landing-pages/). +- [Paul Marden's Landing Page article on Skrift](https://skrift.io/issues/part-1-landing-pages/) - part of a wider series on exploring common practices. +- [Cogworks' post on How to Pick a Block Style Editor](https://www.wearecogworks.com/blog/umbraco-v8-how-to-pick-a-block-style-editor/) diff --git a/docs/editors/data-list.md b/docs/editors/data-list.md index 8b65fbd6..101139a5 100644 --- a/docs/editors/data-list.md +++ b/docs/editors/data-list.md @@ -43,7 +43,7 @@ Once you have configured both the **Data source** and **List editor** you can ** ### How to use the editor? -Once you have added the configured Data Type on your Document Type, the Data List will be displayed. +Once you have added the configured Data Type on your Document Type, the Data List will be displayed on the content page's property panel. ![Data List property-editor - displaying the data source with a Checkbox List](data-list--property-editor-01.png) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html index 22abbce2..821ef6b3 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.html @@ -16,6 +16,7 @@ on-remove="vm.remove" on-sort="vm.sort" block-actions="vm.blockActions" - property-actions="vm.propertyActions"> + property-actions="vm.propertyActions" + previews="vm.previews">
    diff --git a/src/Umbraco.Community.Contentment/DataEditors/_/_components.js b/src/Umbraco.Community.Contentment/DataEditors/_/_components.js index eab8a94b..5c94915a 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/_/_components.js +++ b/src/Umbraco.Community.Contentment/DataEditors/_/_components.js @@ -167,6 +167,7 @@ angular.module("umbraco.directives").component("contentmentStackEditor", { onRemove: " Date: Mon, 16 Nov 2020 18:23:09 +0000 Subject: [PATCH 72/86] ContentBlocks - docs typo corrections --- docs/editors/content-blocks.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/editors/content-blocks.md b/docs/editors/content-blocks.md index 838f9c10..5e9930c1 100644 --- a/docs/editors/content-blocks.md +++ b/docs/editors/content-blocks.md @@ -41,7 +41,7 @@ Once you have selected an element type, you will be presented with the configura The **Name template** field can be used to enter an AngularJS expression, which is evaluated against each block (of this type) to display its name. If this field is left empty, the default name template will be `"Item {{ $index }}"`. -The **Editor overlay size** option will configure the size (width) of the overlay editing panel. The options are Small, Medium and Large. The default value is "Small", this is typically ideal for consise element types, e.g. with a heading, media picker and intro blurb textarea. For element types with heavier content, e.g. Rich Text editors, then "Medium" or "Large" would be a more suitable option. +The **Editor overlay size** option will configure the size (width) of the overlay editing panel. The options are Small, Medium and Large. The default value is "Small", this is typically ideal for concise element types, e.g. with a heading, media picker and intro blurb textarea. For element types with heavier content, e.g. Rich Text editors, then "Medium" or "Large" would be a more suitable option. The **Enable preview?** option can be enabled to render a richer preview of the content block item. The preview mechanism uses a Razor (`.cshtml`) partial-view for rendering. @@ -155,12 +155,12 @@ Here's an example of strongly-typed... There are several alternative block-based editors that you could use with Umbraco v8, here is a selection... -- [Umbraco's native Block List editor](https://our.umbraco.com/documentation/getting-started/backoffice/property-editors/built-in-property-editors/Block-List-Editor/) - available since Umbraco v8.7.0. -- [Umbraco's native Nested Content editor](https://our.umbraco.com/documentation/getting-started/backoffice/property-editors/built-in-property-editors/Nested-Content/) - available since Umbraco v7.7.0, _(since superseded by the Block List editor)._ -- [Bento editor by KOBEN Digital](https://our.umbraco.com/packages/backoffice-extensions/bento-editor/) -- [Perplex.ContentBlocks by Perplex](https://our.umbraco.com/packages/backoffice-extensions/perplexcontentblocks/) - _(ignore my naming conflict of Content Blocks, naming things is hard)._ +- [Umbraco's **Block List** editor](https://our.umbraco.com/documentation/getting-started/backoffice/property-editors/built-in-property-editors/Block-List-Editor/) - available since Umbraco v8.7.0. +- [Umbraco's **Nested Content** editor](https://our.umbraco.com/documentation/getting-started/backoffice/property-editors/built-in-property-editors/Nested-Content/) - available since Umbraco v7.7.0, _(since superseded by the Block List editor)._ +- [**Bento** editor by KOBEN Digital](https://our.umbraco.com/packages/backoffice-extensions/bento-editor/) +- [**Perplex.ContentBlocks** by Perplex](https://our.umbraco.com/packages/backoffice-extensions/perplexcontentblocks/) - _(ignore my naming conflict of Content Blocks, naming things is hard)._ For further reading, here's a selection of insights... -- [Paul Marden's Landing Page article on Skrift](https://skrift.io/issues/part-1-landing-pages/) - part of a wider series on exploring common practices. -- [Cogworks' post on How to Pick a Block Style Editor](https://www.wearecogworks.com/blog/umbraco-v8-how-to-pick-a-block-style-editor/) +- [Paul Marden's **Landing Page article** on Skrift](https://skrift.io/issues/part-1-landing-pages/) - part of a wider series on exploring common practices. +- [Cogworks' post on **How to Pick a Block Style Editor**](https://www.wearecogworks.com/blog/umbraco-v8-how-to-pick-a-block-style-editor/) From 4fe408dd55255318367427ab169cb0f37c346644 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 17 Nov 2020 09:46:38 +0000 Subject: [PATCH 73/86] ContentBlocks - small doco amends --- docs/editors/content-blocks.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/editors/content-blocks.md b/docs/editors/content-blocks.md index 5e9930c1..67d9c25f 100644 --- a/docs/editors/content-blocks.md +++ b/docs/editors/content-blocks.md @@ -8,7 +8,7 @@ Content Blocks is a property-editor used for creating a list of structured content, with each block configurable using an element type. -> If you are using Umbraco 8.7 (or above), this may sound familiar to the [Block List Editor](https://our.umbraco.com/Documentation/Getting-Started/Backoffice/Property-Editors/Built-in-Property-Editors/Block-List-Editor/), and you may be asking yourself why should you use Content Blocks over the built-in Block List Editor? It's a good question, and you'll find no marketing spin from me, I'd recommend that you stick with Umbraco's built-in editors. Content Blocks has subtle differences, it's entirely your choice. +> If you are using Umbraco 8.7 (or above), this may sound familiar to the [Block List Editor](https://our.umbraco.com/Documentation/Getting-Started/Backoffice/Property-Editors/Built-in-Property-Editors/Block-List-Editor/), and you may be asking yourself why should you use Content Blocks over the built-in Block List Editor? It's a good question, and you'll find no marketing spin from me. So if you have any doubts, then I'd recommend that you stick with Umbraco's built-in editors. Content Blocks has subtle differences, it's entirely your choice. > For long time fans of Umbraco v7.x, if you recall the [Stacked Content](https://our.umbraco.com/packages/backoffice-extensions/stacked-content) editor, then Content Blocks could be considered its spiritual successor. @@ -153,14 +153,14 @@ Here's an example of strongly-typed... ### Similar editors and further reading -There are several alternative block-based editors that you could use with Umbraco v8, here is a selection... +There are several alternative block-based editors that you could use with Umbraco v8, here are a selection... - [Umbraco's **Block List** editor](https://our.umbraco.com/documentation/getting-started/backoffice/property-editors/built-in-property-editors/Block-List-Editor/) - available since Umbraco v8.7.0. - [Umbraco's **Nested Content** editor](https://our.umbraco.com/documentation/getting-started/backoffice/property-editors/built-in-property-editors/Nested-Content/) - available since Umbraco v7.7.0, _(since superseded by the Block List editor)._ -- [**Bento** editor by KOBEN Digital](https://our.umbraco.com/packages/backoffice-extensions/bento-editor/) -- [**Perplex.ContentBlocks** by Perplex](https://our.umbraco.com/packages/backoffice-extensions/perplexcontentblocks/) - _(ignore my naming conflict of Content Blocks, naming things is hard)._ +- [**Bento** editor by KOBEN Digital](https://our.umbraco.com/packages/backoffice-extensions/bento-editor/) - compatible with Umbraco v8.6.0 (and above). +- [**Perplex.ContentBlocks** by Perplex](https://our.umbraco.com/packages/backoffice-extensions/perplexcontentblocks/) - _(ignore my naming clash with Content Blocks, naming things is hard),_ compatible with Umbraco v8.1.0 (and above). -For further reading, here's a selection of insights... +For further reading, here are a selection of insights... - [Paul Marden's **Landing Page article** on Skrift](https://skrift.io/issues/part-1-landing-pages/) - part of a wider series on exploring common practices. - [Cogworks' post on **How to Pick a Block Style Editor**](https://www.wearecogworks.com/blog/umbraco-v8-how-to-pick-a-block-style-editor/) From 9e379ac8541c1b196427e25319f10dda5df8ffb8 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 17 Nov 2020 09:47:08 +0000 Subject: [PATCH 74/86] ContentBlocks - added drop-shadow to the stack items --- .../DataEditors/ContentBlocks/content-blocks.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.css b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.css index 8dab3e81..a1d2dd9e 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.css +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.css @@ -23,6 +23,7 @@ content: ""; background: rgba(225, 225, 225, 0.5); border-radius: 3px; + box-shadow: 0px 1px 1px 0px rgba(33, 33, 66, 0.2); position: absolute; top: 0; bottom: 0; From b8931dac52d56fc256fdb5a85988ca7e7c5e4fa6 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 17 Nov 2020 09:47:47 +0000 Subject: [PATCH 75/86] DataList - updated docs + screengrabs To include the Umbraco Content data-source. --- .../data-list--configuration-editor-01.png | Bin 9592 -> 11547 bytes .../data-list--configuration-editor-02.png | Bin 23804 -> 28068 bytes docs/editors/data-list.md | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/editors/data-list--configuration-editor-01.png b/docs/editors/data-list--configuration-editor-01.png index bf273e6f39cd322b05dee2c277ffd0ef97e794ed..89870de6e3e12e570a28540690c365375c0c801c 100644 GIT binary patch literal 11547 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 GLjD&u;HHuQ diff --git a/docs/editors/data-list--configuration-editor-02.png b/docs/editors/data-list--configuration-editor-02.png index a4411267e0d9df232cc707b3ae6c4b4fafa422ab..f2fc07a7cbcde69d5f4db7dbbf19bbe0bbc487e1 100644 GIT binary patch literal 28068 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 literal 23804 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 Q$v=l_r~;3wl&wSl2icV{V*mgE diff --git a/docs/editors/data-list.md b/docs/editors/data-list.md index 101139a5..8a9dc462 100644 --- a/docs/editors/data-list.md +++ b/docs/editors/data-list.md @@ -20,7 +20,7 @@ The two main fields are "**Data source**" and "**List editor**". ![Configuration Editor for Data List - empty state](data-list--configuration-editor-01.png) -Selecting the **Data source**, you will be presented with a selection of data sources, including .NET enumeration, file system, SQL, CSV, JSON and XML data. +Selecting the **Data source**, you will be presented with a selection of data sources, including .NET enumeration, file system, Umbraco content, SQL, CSV, JSON and XML data. ![Configuration Editor for Data List - available data sources](data-list--configuration-editor-02.png) From 55be73087968605574b04070a2921774414dfc2e Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 17 Nov 2020 10:10:58 +0000 Subject: [PATCH 76/86] MacroPicker - refactored to use list-editor component --- .../DataEditors/MacroPicker/macro-picker.html | 36 +++++------- .../DataEditors/MacroPicker/macro-picker.js | 57 +++++++++++-------- 2 files changed, 47 insertions(+), 46 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/MacroPicker/macro-picker.html b/src/Umbraco.Community.Contentment/DataEditors/MacroPicker/macro-picker.html index ef55ae2d..8ebc7c21 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/MacroPicker/macro-picker.html +++ b/src/Umbraco.Community.Contentment/DataEditors/MacroPicker/macro-picker.html @@ -3,27 +3,17 @@ - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at https://mozilla.org/MPL/2.0/. --> -
    -
    - - -
    - +
    + +
    diff --git a/src/Umbraco.Community.Contentment/DataEditors/MacroPicker/macro-picker.js b/src/Umbraco.Community.Contentment/DataEditors/MacroPicker/macro-picker.js index cf41f04f..80a010d9 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/MacroPicker/macro-picker.js +++ b/src/Umbraco.Community.Contentment/DataEditors/MacroPicker/macro-picker.js @@ -7,7 +7,9 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. "$scope", "entityResource", "editorService", - function ($scope, entityResource, editorService) { + "localizationService", + "overlayService", + function ($scope, entityResource, editorService, localizationService, overlayService) { // console.log("macro-picker.model", $scope.model); @@ -25,31 +27,18 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. $scope.model.value = $scope.model.value || []; - vm.icon = "icon-settings-alt"; + vm.defaultIcon = "icon-settings-alt"; vm.allowAdd = (config.maxItems === 0 || config.maxItems === "0") || $scope.model.value.length < config.maxItems; vm.allowEdit = true; vm.allowRemove = true; - vm.published = true; - vm.sortable = Object.toBoolean(config.disableSorting) === false && (config.maxItems !== 1 && config.maxItems !== "1"); - - vm.sortableOptions = { - axis: "y", - containment: "parent", - cursor: "move", - disabled: vm.sortable === false, - opacity: 0.7, - scroll: true, - tolerance: "pointer", - stop: function (e, ui) { - setDirty(); - } - }; + vm.allowSort = Object.toBoolean(config.disableSorting) === false && (config.maxItems !== 1 && config.maxItems !== "1"); vm.addButtonLabelKey = config.addButtonLabelKey || "defaultdialogs_selectMacro"; vm.add = add; vm.edit = edit; vm.remove = remove; + vm.populateDescription = populateDescription; }; function add() { @@ -110,13 +99,35 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. }; function remove($index) { - $scope.model.value.splice($index, 1); - - if ((config.maxItems === 0 || config.maxItems === "0") || $scope.model.value.length < config.maxItems) { - vm.allowAdd = true; - } + var keys = ["content_nestedContentDeleteItem", "general_delete", "general_cancel", "contentTypeEditor_yesDelete"]; + localizationService.localizeMany(keys).then(function (data) { + overlayService.open({ + title: data[1], + content: data[0], + closeButtonLabel: data[2], + submitButtonLabel: data[3], + submitButtonStyle: "danger", + submit: function () { + + $scope.model.value.splice($index, 1); + + if ((config.maxItems === 0 || config.maxItems === "0") || $scope.model.value.length < config.maxItems) { + vm.allowAdd = true; + } + + setDirty(); + + overlayService.close(); + }, + close: function () { + overlayService.close(); + } + }); + }); + }; - setDirty(); + function populateDescription(item, $index) { + return item.alias; }; function setDirty() { From 5145527e7fa35eff2f1389f1c957e3a2ac156fc9 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Fri, 20 Nov 2020 17:22:21 +0000 Subject: [PATCH 77/86] Dashboard - relocate version number to under "umbracoPlugins" I'd previously added "contentment" version number under the root of `Umbraco.Sys.ServerVariables`. But I'd noticed that the root was getting quite large. Also noticed there was a "umbracoPlugins" section, which made sense to reuse, so I did that. --- .../Composing/ContentmentComponent.cs | 4 ++-- src/Umbraco.Community.Contentment/Web/UI/backoffice.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs b/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs index 714c4534..452d25dc 100644 --- a/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs +++ b/src/Umbraco.Community.Contentment/Composing/ContentmentComponent.cs @@ -50,9 +50,9 @@ public void Terminate() private void ServerVariablesParser_Parsing(object sender, Dictionary e) { - if (e.ContainsKey(Constants.Internals.ProjectAlias) == false) + if (e.TryGetValueAs("umbracoPlugins", out Dictionary umbracoPlugins) == true && umbracoPlugins.ContainsKey(Constants.Internals.ProjectAlias) == false) { - e.Add(Constants.Internals.ProjectAlias, new + umbracoPlugins.Add(Constants.Internals.ProjectAlias, new { name = Constants.Internals.ProjectName, version = Configuration.ContentmentVersion.SemanticVersion.ToSemanticString() diff --git a/src/Umbraco.Community.Contentment/Web/UI/backoffice.js b/src/Umbraco.Community.Contentment/Web/UI/backoffice.js index b9bb4d4b..8ccfd2e3 100644 --- a/src/Umbraco.Community.Contentment/Web/UI/backoffice.js +++ b/src/Umbraco.Community.Contentment/Web/UI/backoffice.js @@ -17,7 +17,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.Tree.Control const alias = "contentment"; - var config = Umbraco.Sys.ServerVariables[alias]; + var config = Umbraco.Sys.ServerVariables.umbracoPlugins[alias]; vm.title = config.name; vm.version = "v" + config.version; From fec415f52d1389a94468887632686d541411c838 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Fri, 20 Nov 2020 17:24:00 +0000 Subject: [PATCH 78/86] :hatching_chick: Expanded directive to not be self-closing. I've read about issues with self-closing custom elements, e.g. the browser not rendering them correctly. --- .../Web/UI/App_Plugins/Contentment/editors/_empty.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/editors/_empty.html b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/editors/_empty.html index df88c084..1bfa8275 100644 --- a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/editors/_empty.html +++ b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/editors/_empty.html @@ -1 +1 @@ -
    +
    From b21dbf662bae0fc127ff4506c0d2e8725f5361c5 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Fri, 20 Nov 2020 17:25:41 +0000 Subject: [PATCH 79/86] Enum data source - fixed bug If a custom enum item had been decorated with ``DataListItemAttribute` and a custom `Value` set, then the value converter wouldn't have been able to map it back to the Enum correctly. This is now fixed. --- .../DataList/DataSources/EnumDataListSource.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs index 73553122..58e50bd8 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs @@ -129,7 +129,21 @@ public object ConvertValue(Type type, string value) { if (string.IsNullOrWhiteSpace(value) == false && type?.IsEnum == true) { + // NOTE: Can't use `Enum.TryParse` here, as it's only available with generic types in .NET 4.8. try { return Enum.Parse(type, value, true); } catch (Exception ex) { _logger.Error(ex); } + + // If the value doesn't match the Enum field, then it is most likely set with `DataListItemAttribute.Value`. + var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + foreach (var field in fields) + { + var attr = field.GetCustomAttribute(false); + if (value.InvariantEquals(attr?.Value) == true) + { + return Enum.Parse(type, field.Name); + } + } + + _logger.Debug($"Unable to find value '{value}' in enum '{type.FullName}'."); } return type.GetDefaultValue(); From bff2287ecaa3d618ef02a366d4e9e4c2cc8669ab Mon Sep 17 00:00:00 2001 From: leekelleher Date: Fri, 20 Nov 2020 17:26:19 +0000 Subject: [PATCH 80/86] ContentBlocks - resolved a couple of outstanding TODOs --- docs/editors/content-blocks.md | 2 -- .../DataEditors/ContentBlocks/ContentBlockPreviewView.cs | 8 ++++---- .../ContentBlocks/ContentBlocksApiController.cs | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/editors/content-blocks.md b/docs/editors/content-blocks.md index 67d9c25f..1aa8489d 100644 --- a/docs/editors/content-blocks.md +++ b/docs/editors/content-blocks.md @@ -2,8 +2,6 @@ ## Contentment for Umbraco -> This documentation is a _**work-in-progress.**_ There will be plot holes and missing pieces. Please bear with me. - ### Content Blocks Content Blocks is a property-editor used for creating a list of structured content, with each block configurable using an element type. diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs index 18ba9e8b..83440034 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs @@ -25,15 +25,15 @@ protected override void SetViewData(ViewDataDictionary viewData) if (viewData.TryGetValue("content", out var tmp1) && tmp1 is TPublishedContent t1) { model.Content = t1; - // TODO: [LK] Remove "content" from ViewData? - // viewData.Remove("content"); + + viewData.Remove("content"); } if (viewData.TryGetValue("element", out var tmp2) && tmp2 is TPublishedElement t2) { model.Element = t2; - // TODO: [LK] Remove "element" from ViewData? - // viewData.Remove("element"); + + viewData.Remove("element"); } viewData.Model = model; diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs index 1981e506..8ea88efc 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksApiController.cs @@ -38,7 +38,7 @@ public HttpResponseMessage GetPreviewMarkup([FromBody] JObject item, int element var content = UmbracoContext.Content.GetById(true, contentId); if (content == null) { - // TODO: [LK] What to do with an unsaved (new) page? + _logger.Debug($"Unable to retrieve content for ID '{contentId}', it is most likely a new unsaved page."); } var element = default(IPublishedElement); From 540cf75325f4ea872268916a983647544557b569 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 23 Nov 2020 12:06:02 +0000 Subject: [PATCH 81/86] :notebook: Updated docs with a personal note --- docs/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/README.md b/docs/README.md index 97ce9a19..21453fbd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,6 +4,10 @@ ### Documentation +> If there are any parts of the documentation that are unclear, [please do let me know](https://github.com/leekelleher/umbraco-contentment/issues/new/choose). +> +> Sometimes I can be too close to the code to understand how others (e.g. you) will use it. :v::heart::dove: + #### Property-Editors Here is the documentation for the Contentment property-editors... From c2876904caab46e7cbed12de5bd175f1edb06204 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 1 Dec 2020 16:33:40 +0000 Subject: [PATCH 82/86] Content Blocks - added note about pasting NC blocks --- docs/editors/content-blocks.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/editors/content-blocks.md b/docs/editors/content-blocks.md index 1aa8489d..5e7e22e7 100644 --- a/docs/editors/content-blocks.md +++ b/docs/editors/content-blocks.md @@ -84,6 +84,8 @@ For further options, each content block has its own action menu, initially for s ![Content Blocks property-editor - content block action menu](content-blocks--property-editor-05.png) +> **Did you know...** you can copy items from Nested Content and paste them into Content Blocks, (and vice-versa)! :dizzy_face: + #### Previews From 0f223302f091696649a6f33f94eca733674ab332 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Tue, 1 Dec 2020 17:24:22 +0000 Subject: [PATCH 83/86] ConfigurationEditor - accessibility amends Changes taken from @BatJan's pull request. Refactor view to use umbraco directives and change a to button https://github.com/leekelleher/umbraco-contentment/pull/44 Once Contentment's dependency on Umbraco has been bumped up beyond v8.8.0, then I'll merge in the rest of the pull request. --- .../ConfigurationEditor/configuration-editor.overlay.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.overlay.html b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.overlay.html index 807d582a..412cdb6d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.overlay.html +++ b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.overlay.html @@ -31,13 +31,13 @@
    From 7c721ea004a48222bb661f4f8783b89c8548f7c1 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Tue, 1 Dec 2020 17:43:38 +0000 Subject: [PATCH 84/86] ContentBlocks - accessibility amends Changes taken from @BatJan's pull request. Convert a and i to button and umb-icon https://github.com/leekelleher/umbraco-contentment/pull/45 Once Contentment's dependency on Umbraco has been bumped up beyond v8.8.0, then I'll merge in the rest of the pull request. --- .../ContentBlocks/content-blocks.overlay.html | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html index a4d2591c..f5969c51 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html @@ -42,32 +42,32 @@ @@ -77,7 +77,12 @@
    - + From b6b27c9b9500fa58ca410099392e75f921131dac Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 1 Dec 2020 17:45:42 +0000 Subject: [PATCH 85/86] ContentBlocks overlay - small CSS tidy-ups --- .../DataEditors/ContentBlocks/content-blocks.overlay.html | 4 ++-- .../DataEditors/ItemPicker/item-picker.overlay.css | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html index f5969c51..0c2a1b88 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/content-blocks.overlay.html @@ -76,11 +76,11 @@ -
    +
    Date: Thu, 3 Dec 2020 12:03:31 +0000 Subject: [PATCH 86/86] Incremented version number, v1.1.0 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index ee600bde..1cc5f657 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.0-develop \ No newline at end of file +1.1.0 \ No newline at end of file