From 072c1a67648ae75a8ad76666ec87cead5c0aa3a5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 13 Jul 2018 12:45:04 +1000 Subject: [PATCH] WIP - editing all variants at once, this is getting things wired up to display a content item, lots of TODOs added, models and mappings refactored, content apps put in c# model, etc... --- src/Umbraco.Core/Constants-Conventions.cs | 5 - .../Constants-PropertyTypeGroups.cs | 8 +- .../Migrations/Install/DatabaseDataCreator.cs | 6 +- .../Mapping/ContentWebModelMappingTests.cs | 150 ++++----- .../ContentModelSerializationTests.cs | 114 +++---- .../components/content/edit.controller.js | 134 +++----- .../editor/umbeditorheader.directive.js | 38 +-- .../src/common/resources/content.resource.js | 4 +- .../src/views/components/content/edit.html | 2 +- .../apps/content/content.controller.js | 8 +- .../views/content/apps/content/content.html | 2 +- .../apps/listview/listview.controller.js | 5 + .../src/views/media/media.edit.controller.js | 19 -- src/Umbraco.Web/Editors/ContentController.cs | 307 +++++++++--------- src/Umbraco.Web/Editors/MediaController.cs | 12 +- src/Umbraco.Web/Editors/MemberController.cs | 2 +- .../Models/ContentEditing/ContentApp.cs | 24 ++ .../ContentEditing/ContentItemDisplay.cs | 159 ++++++++- .../Models/ContentEditing/ContentVariation.cs | 51 --- .../ContentEditing/ContentVariationDisplay.cs | 104 ++++++ .../ContentEditing/ITabbedContentItem.cs | 10 + .../Models/ContentEditing/MediaItemDisplay.cs | 9 + .../ContentEditing/TabbedContentItem.cs | 3 +- .../Models/Mapping/ContentAppResolver.cs | 52 +++ .../Mapping/ContentItemDisplayNameResolver.cs | 42 +-- .../ContentItemDisplayVariationResolver.cs | 87 ++--- .../Models/Mapping/ContentMapperProfile.cs | 36 +- .../Models/Mapping/MediaAppResolver.cs | 53 +++ .../Models/Mapping/MediaMapperProfile.cs | 6 +- .../Mapping/TabsAndPropertiesResolver.cs | 126 +++---- src/Umbraco.Web/Umbraco.Web.csproj | 6 +- 31 files changed, 944 insertions(+), 640 deletions(-) create mode 100644 src/Umbraco.Web/Models/ContentEditing/ContentApp.cs delete mode 100644 src/Umbraco.Web/Models/ContentEditing/ContentVariation.cs create mode 100644 src/Umbraco.Web/Models/ContentEditing/ContentVariationDisplay.cs create mode 100644 src/Umbraco.Web/Models/ContentEditing/ITabbedContentItem.cs create mode 100644 src/Umbraco.Web/Models/Mapping/ContentAppResolver.cs create mode 100644 src/Umbraco.Web/Models/Mapping/MediaAppResolver.cs diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index 524ae580a3..14ea4e23fc 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -34,11 +34,6 @@ namespace Umbraco.Core public const string ListViewPrefix = "List View - "; } - public static class PropertyGroups - { - public const string ListViewGroupName = "umbContainerView"; - } - /// /// Constants for Umbraco Content property aliases. /// diff --git a/src/Umbraco.Core/Constants-PropertyTypeGroups.cs b/src/Umbraco.Core/Constants-PropertyTypeGroups.cs index ef6951df1a..6a96783a26 100644 --- a/src/Umbraco.Core/Constants-PropertyTypeGroups.cs +++ b/src/Umbraco.Core/Constants-PropertyTypeGroups.cs @@ -17,10 +17,10 @@ /// public const string File = "50899F9C-023A-4466-B623-ABA9049885FE"; - /// - /// Guid for a Image PropertyTypeGroup object. - /// - public const string Contents = "79995FA2-63EE-453C-A29B-2E66F324CDBE"; + ///// + ///// Guid for a Image PropertyTypeGroup object. + ///// + //public const string Contents = "79995FA2-63EE-453C-A29B-2E66F324CDBE"; /// /// Guid for a Image PropertyTypeGroup object. diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 7c02f7ad75..32f8459b37 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -205,7 +205,8 @@ namespace Umbraco.Core.Migrations.Install { _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Image) }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 4, ContentTypeNodeId = 1033, Text = "File", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.File) }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 5, ContentTypeNodeId = 1031, Text = "Contents", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Contents) }); + //TODO: Need a migration to remove this + //_database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 5, ContentTypeNodeId = 1031, Text = "Contents", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Contents) }); //membership property group _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 11, ContentTypeNodeId = 1044, Text = "Membership", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Membership) }); } @@ -220,7 +221,8 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 27, UniqueId = 27.ToGuid(), DataTypeId = Constants.DataTypes.DefaultMediaListView, ContentTypeId = 1031, PropertyTypeGroupId = 5, Alias = "contents", Name = "Contents:", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + //TODO: Need a migration to remove this + //_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 27, UniqueId = 27.ToGuid(), DataTypeId = Constants.DataTypes.DefaultMediaListView, ContentTypeId = 1031, PropertyTypeGroupId = 5, Alias = "contents", Name = "Contents:", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); //membership property types _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); diff --git a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs index d6ae92ebd3..ed06f224d6 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs @@ -107,93 +107,93 @@ namespace Umbraco.Tests.Models.Mapping AssertContentItem(result, content); } - [Test] - public void To_Display_Model() - { - var contentType = MockedContentTypes.CreateSimpleContentType(); - var content = MockedContent.CreateSimpleContent(contentType); - FixUsers(content); + //[Test] + //public void To_Display_Model() + //{ + // var contentType = MockedContentTypes.CreateSimpleContentType(); + // var content = MockedContent.CreateSimpleContent(contentType); + // FixUsers(content); - // need ids for tabs - var id = 1; - foreach (var g in content.PropertyGroups) - g.Id = id++; + // // need ids for tabs + // var id = 1; + // foreach (var g in content.PropertyGroups) + // g.Id = id++; - var result = Mapper.Map(content); + // var result = Mapper.Map(content); - AssertBasics(result, content); + // AssertBasics(result, content); - foreach (var p in content.Properties) - AssertDisplayProperty(result, p); + // foreach (var p in content.Properties) + // AssertDisplayProperty(result, p); - Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count()); - Assert.IsTrue(result.Tabs.First().IsActive); - Assert.IsTrue(result.Tabs.Except(new[] {result.Tabs.First()}).All(x => x.IsActive == false)); - } + // Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count()); + // Assert.IsTrue(result.Tabs.First().IsActive); + // Assert.IsTrue(result.Tabs.Except(new[] {result.Tabs.First()}).All(x => x.IsActive == false)); + //} - [Test] - public void To_Display_Model_No_Tabs() - { - var contentType = MockedContentTypes.CreateSimpleContentType(); - contentType.PropertyGroups.Clear(); - var content = new Content("Home", -1, contentType) { Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 }; + //[Test] + //public void To_Display_Model_No_Tabs() + //{ + // var contentType = MockedContentTypes.CreateSimpleContentType(); + // contentType.PropertyGroups.Clear(); + // var content = new Content("Home", -1, contentType) { Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 }; - var result = Mapper.Map(content); + // var result = Mapper.Map(content); - AssertBasics(result, content); - foreach (var p in content.Properties) - { - AssertDisplayProperty(result, p); - } - Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count()); - } + // AssertBasics(result, content); + // foreach (var p in content.Properties) + // { + // AssertDisplayProperty(result, p); + // } + // Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count()); + //} - [Test] - public void To_Display_Model_With_Non_Grouped_Properties() - { - var idSeed = 1; - var contentType = MockedContentTypes.CreateSimpleContentType(); - //add non-grouped properties - contentType.AddPropertyType(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext, "nonGrouped1") { Name = "Non Grouped 1", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - contentType.AddPropertyType(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext, "nonGrouped2") { Name = "Non Grouped 2", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - //set ids or it wont work - contentType.Id = idSeed; - foreach (var p in contentType.PropertyTypes) - { - p.Id = idSeed; - idSeed++; - } - var content = MockedContent.CreateSimpleContent(contentType); - FixUsers(content); + //[Test] + //public void To_Display_Model_With_Non_Grouped_Properties() + //{ + // var idSeed = 1; + // var contentType = MockedContentTypes.CreateSimpleContentType(); + // //add non-grouped properties + // contentType.AddPropertyType(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext, "nonGrouped1") { Name = "Non Grouped 1", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); + // contentType.AddPropertyType(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext, "nonGrouped2") { Name = "Non Grouped 2", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); + // //set ids or it wont work + // contentType.Id = idSeed; + // foreach (var p in contentType.PropertyTypes) + // { + // p.Id = idSeed; + // idSeed++; + // } + // var content = MockedContent.CreateSimpleContent(contentType); + // FixUsers(content); - foreach (var p in content.Properties) - { - p.Id = idSeed; - idSeed++; - } - //need ids for tabs - var id = 1; - foreach (var g in content.PropertyGroups) - { - g.Id = id; - id++; - } - //ensure that nothing is marked as dirty - contentType.ResetDirtyProperties(false); - //ensure that nothing is marked as dirty - content.ResetDirtyProperties(false); + // foreach (var p in content.Properties) + // { + // p.Id = idSeed; + // idSeed++; + // } + // //need ids for tabs + // var id = 1; + // foreach (var g in content.PropertyGroups) + // { + // g.Id = id; + // id++; + // } + // //ensure that nothing is marked as dirty + // contentType.ResetDirtyProperties(false); + // //ensure that nothing is marked as dirty + // content.ResetDirtyProperties(false); - var result = Mapper.Map(content); + // var result = Mapper.Map(content); - AssertBasics(result, content); - foreach (var p in content.Properties) - { - AssertDisplayProperty(result, p); - } - Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count() - 1); - Assert.IsTrue(result.Tabs.Any(x => x.Label == Current.Services.TextService.Localize("general/properties"))); - Assert.AreEqual(2, result.Tabs.Where(x => x.Label == Current.Services.TextService.Localize("general/properties")).SelectMany(x => x.Properties.Where(p => p.Alias.StartsWith("_umb_") == false)).Count()); - } + // AssertBasics(result, content); + // foreach (var p in content.Properties) + // { + // AssertDisplayProperty(result, p); + // } + // Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count() - 1); + // Assert.IsTrue(result.Tabs.Any(x => x.Label == Current.Services.TextService.Localize("general/properties"))); + // Assert.AreEqual(2, result.Tabs.Where(x => x.Label == Current.Services.TextService.Localize("general/properties")).SelectMany(x => x.Properties.Where(p => p.Alias.StartsWith("_umb_") == false)).Count()); + //} #region Assertions diff --git a/src/Umbraco.Tests/Web/AngularIntegration/ContentModelSerializationTests.cs b/src/Umbraco.Tests/Web/AngularIntegration/ContentModelSerializationTests.cs index fcc434a596..f344360c18 100644 --- a/src/Umbraco.Tests/Web/AngularIntegration/ContentModelSerializationTests.cs +++ b/src/Umbraco.Tests/Web/AngularIntegration/ContentModelSerializationTests.cs @@ -11,67 +11,67 @@ namespace Umbraco.Tests.Web.AngularIntegration [TestFixture] public class ContentModelSerializationTests { - [Test] - public void Content_Display_To_Json() - { - //create 3 tabs with 3 properties each - var tabs = new List>(); - for (var tabIndex = 0; tabIndex < 3; tabIndex ++) - { - var props = new List(); - for (var propertyIndex = 0; propertyIndex < 3; propertyIndex ++) - { - props.Add(new ContentPropertyDisplay - { - Alias = "property" + propertyIndex, - Label = "Property " + propertyIndex, - Id = propertyIndex, - Value = "value" + propertyIndex, - Config = new Dictionary {{ propertyIndex.ToInvariantString(), "value" }}, - Description = "Description " + propertyIndex, - View = "~/Views/View" + propertyIndex, - HideLabel = false - }); - } - tabs.Add(new Tab() - { - Alias = "Tab" + tabIndex, - Label = "Tab" + tabIndex, - Properties = props - }); - } + //[Test] + //public void Content_Display_To_Json() + //{ + // //create 3 tabs with 3 properties each + // var tabs = new List>(); + // for (var tabIndex = 0; tabIndex < 3; tabIndex ++) + // { + // var props = new List(); + // for (var propertyIndex = 0; propertyIndex < 3; propertyIndex ++) + // { + // props.Add(new ContentPropertyDisplay + // { + // Alias = "property" + propertyIndex, + // Label = "Property " + propertyIndex, + // Id = propertyIndex, + // Value = "value" + propertyIndex, + // Config = new Dictionary {{ propertyIndex.ToInvariantString(), "value" }}, + // Description = "Description " + propertyIndex, + // View = "~/Views/View" + propertyIndex, + // HideLabel = false + // }); + // } + // tabs.Add(new Tab() + // { + // Alias = "Tab" + tabIndex, + // Label = "Tab" + tabIndex, + // Properties = props + // }); + // } - var displayModel = new ContentItemDisplay - { - Id = 1234, - Name = "Test", - Tabs = tabs - }; + // var displayModel = new ContentItemDisplay + // { + // Id = 1234, + // Name = "Test", + // Tabs = tabs + // }; - var json = JsonConvert.SerializeObject(displayModel); + // var json = JsonConvert.SerializeObject(displayModel); - var jObject = JObject.Parse(json); + // var jObject = JObject.Parse(json); - Assert.AreEqual("1234", jObject["id"].ToString()); - Assert.AreEqual("Test", jObject["name"].ToString()); - Assert.AreEqual(3, jObject["tabs"].Count()); - for (var tab = 0; tab < jObject["tabs"].Count(); tab++) - { - Assert.AreEqual("Tab" + tab, jObject["tabs"][tab]["alias"].ToString()); - Assert.AreEqual("Tab" + tab, jObject["tabs"][tab]["label"].ToString()); - Assert.AreEqual(3, jObject["tabs"][tab]["properties"].Count()); - for (var prop = 0; prop < jObject["tabs"][tab]["properties"].Count(); prop++) - { - Assert.AreEqual("property" + prop, jObject["tabs"][tab]["properties"][prop]["alias"].ToString()); - Assert.AreEqual("Property " + prop, jObject["tabs"][tab]["properties"][prop]["label"].ToString()); - Assert.AreEqual(prop, jObject["tabs"][tab]["properties"][prop]["id"].Value()); - Assert.AreEqual("value" + prop, jObject["tabs"][tab]["properties"][prop]["value"].ToString()); - Assert.AreEqual("{\"" + prop + "\":\"value\"}", jObject["tabs"][tab]["properties"][prop]["config"].ToString(Formatting.None)); - Assert.AreEqual("Description " + prop, jObject["tabs"][tab]["properties"][prop]["description"].ToString()); - Assert.AreEqual(false, jObject["tabs"][tab]["properties"][prop]["hideLabel"].Value()); - } - } - } + // Assert.AreEqual("1234", jObject["id"].ToString()); + // Assert.AreEqual("Test", jObject["name"].ToString()); + // Assert.AreEqual(3, jObject["tabs"].Count()); + // for (var tab = 0; tab < jObject["tabs"].Count(); tab++) + // { + // Assert.AreEqual("Tab" + tab, jObject["tabs"][tab]["alias"].ToString()); + // Assert.AreEqual("Tab" + tab, jObject["tabs"][tab]["label"].ToString()); + // Assert.AreEqual(3, jObject["tabs"][tab]["properties"].Count()); + // for (var prop = 0; prop < jObject["tabs"][tab]["properties"].Count(); prop++) + // { + // Assert.AreEqual("property" + prop, jObject["tabs"][tab]["properties"][prop]["alias"].ToString()); + // Assert.AreEqual("Property " + prop, jObject["tabs"][tab]["properties"][prop]["label"].ToString()); + // Assert.AreEqual(prop, jObject["tabs"][tab]["properties"][prop]["id"].Value()); + // Assert.AreEqual("value" + prop, jObject["tabs"][tab]["properties"][prop]["value"].ToString()); + // Assert.AreEqual("{\"" + prop + "\":\"value\"}", jObject["tabs"][tab]["properties"][prop]["config"].ToString(Formatting.None)); + // Assert.AreEqual("Description " + prop, jObject["tabs"][tab]["properties"][prop]["description"].ToString()); + // Assert.AreEqual(false, jObject["tabs"][tab]["properties"][prop]["hideLabel"].Value()); + // } + // } + //} } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index d2cbe5fe86..15f95b2a60 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -53,76 +53,28 @@ } } - //init can be called more than once and we don't want to have multiple bound events - for (var e in evts) { - eventsService.unsubscribe(evts[e]); - } - - evts.push(eventsService.on("editors.content.changePublishDate", function (event, args) { - createButtons(args.node); - })); - - evts.push(eventsService.on("editors.content.changeUnpublishDate", function (event, args) { - createButtons(args.node); - })); - - evts.push(eventsService.on("editors.documentType.saved", function (name, args) { - // if this content item uses the updated doc type we need to reload the content item - if(args && args.documentType && args.documentType.key === content.documentType.key) { - if ($scope.page.culture) { - loadContent($scope.page.culture); - } - else { - loadContent(); - } - } - })); - - // We don't get the info tab from the server from version 7.8 so we need to manually add it - //contentEditingHelper.addInfoTab($scope.content.tabs); - - // prototype content and info apps - var contentApp = { - "name": "Content", - "alias": "content", - "icon": "icon-document", - "view": "views/content/apps/content/content.html" - }; - - var infoApp = { - "name": "Info", - "alias": "info", - "icon": "icon-info", - "view": "views/content/apps/info/info.html" - }; - - var listview = { - "name": "Child items", - "alias": "childItems", - "icon": "icon-list", - "view": "views/content/apps/listview/listview.html" - }; - - $scope.content.apps = []; - - if ($scope.content.isContainer) { - // add list view app - $scope.content.apps.push(listview); - - // remove the list view tab - angular.forEach($scope.content.tabs, function (tab, index) { - if (tab.alias === "umbContainerView") { - tab.hide = true; - } - }); - } - - $scope.content.apps.push(contentApp); - $scope.content.apps.push(infoApp); + bindEvents(); // set first app to active $scope.content.apps[0].active = true; + // set the active variant + var activeCultureSet = false; + _.each($scope.content.variants, function (v) { + if (!activeCultureSet) { + if (v.language.culture === $scope.page.culture) { + v.active = true; + $scope.content.currentVariant = v; + activeCultureSet = true; + } + } + }); + if (!activeCultureSet) { + // set the first variant to active + $scope.content.variants[0].active = true; + $scope.content.currentVariant = $scope.content.variants[0]; + } + // create new editor for split view if ($scope.editors.length === 0) { var editor = { @@ -138,14 +90,35 @@ } } + function bindEvents() { + //bindEvents can be called more than once and we don't want to have multiple bound events + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + + evts.push(eventsService.on("editors.content.changePublishDate", function (event, args) { + createButtons(args.node); + })); + + evts.push(eventsService.on("editors.content.changeUnpublishDate", function (event, args) { + createButtons(args.node); + })); + + evts.push(eventsService.on("editors.documentType.saved", function (name, args) { + // if this content item uses the updated doc type we need to reload the content item + if (args && args.documentType && args.documentType.key === content.documentType.key) { + loadContent(); + } + })); + } + /** * This does the content loading and initializes everything, called on load and changing variants - * @param {any} culture */ - function loadContent(culture) { + function loadContent() { //we are editing so get the content item from the server - return $scope.getMethod()($scope.contentId, culture) + return $scope.getMethod()($scope.contentId) .then(function (data) { $scope.content = data; @@ -305,18 +278,11 @@ } else { - //Browse content nodes based on the selected tree language variant $scope.page.loading = true; - if ($scope.page.culture) { - loadContent($scope.page.culture).then(function(){ - $scope.page.loading = false; - }); - } - else { - loadContent().then(function(){ - $scope.page.loading = false; - }); - } + + loadContent().then(function () { + $scope.page.loading = false; + }); } $scope.unPublish = function () { @@ -326,8 +292,8 @@ if ($scope.content.variants.length > 0) { _.each($scope.content.variants, function (d) { - //set the culture if this is current - if (d.current === true) { + //set the culture if this is active + if (d.active === true) { culture = d.language.culture; } }); @@ -541,9 +507,9 @@ // set selected variant on split view content angular.forEach($scope.editors[editorIndex].content.variants, function (variant) { if (variant.culture === selectedVariant.culture) { - variant.current = true; + variant.active = true; } else { - variant.current = false; + variant.active = false; } }); $scope.editors[editorIndex].loading = false; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js index 980c2f4453..a0d38667bf 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js @@ -214,38 +214,16 @@ Use this directive to construct a header inside the main editor window. function onInit() { setCurrentVariant(scope.variants); - setVariantStatusColor(scope.variants); } function setCurrentVariant(variants) { - angular.forEach(variants, function (variant) { - if(variant.current) { + if(variant.active) { scope.vm.currentVariant = variant; } }); } - - //TODO: This doesn't really affect any UI currently, need some feedback from mads - function setVariantStatusColor(variants) { - angular.forEach(variants, function (variant) { - - //TODO: What about variant.exists? If we are applying colors/styles, this should be one of them - - switch (variant.state) { - case "Published": - variant.stateColor = "success"; - break; - case "Unpublished": - //TODO: Not sure if these statuses will ever bubble up to the UI? - case "Publishing": - case "Unpublishing": - default: - variant.stateColor = "gray"; - } - }); - } - + scope.goBack = function () { if (scope.onBack) { scope.onBack(); @@ -293,12 +271,12 @@ Use this directive to construct a header inside the main editor window. } }; - scope.$watch('variants', function(newValue, oldValue){ - if(!newValue) return; - if(newValue === oldValue) return; - setCurrentVariant(newValue); - setVariantStatusColor(newValue); - }, true); + //TODO: Change this, we cannot watch the whole model + //scope.$watch('variants', function(newValue, oldValue){ + // if(!newValue) return; + // if(newValue === oldValue) return; + // setCurrentVariant(newValue); + //}, true); onInit(); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 67b50dddcb..bfe64923c3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -330,13 +330,13 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the content item. * */ - getById: function (id, culture) { + getById: function (id) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "contentApiBaseUrl", "GetById", - { id: id, culture: culture })), + { id: id })), 'Failed to retrieve data for content id ' + id); }, diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html b/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html index c5cecb2190..bf89bf2fe9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html @@ -17,7 +17,7 @@ -
+
{{ group.label }}
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/apps/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/apps/listview/listview.controller.js index d21715854d..2733b56690 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/apps/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/apps/listview/listview.controller.js @@ -7,6 +7,11 @@ vm.listViewGroup = {}; + //TODO: We need to fix this, there is no umbContainerView anymore since this worked as a hack by copying + // across a tab/property editor to a content App, instead we are going to need to hack the list view a different + // way since currently it still requires us to use umb-property-editor so we'll either need to construct the model + // here ourselves or somehow allow the server to pass in a model for a content app. + function onInit() { angular.forEach($scope.model.tabs, function(group){ if(group.alias === "umbContainerView") { diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js index 8c171af2f0..2f1e81d186 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js @@ -116,25 +116,6 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, "view": "views/media/apps/listview/listview.html" }; - $scope.content.apps = []; - - if($scope.content.contentTypeAlias === "Folder") { - // add list view app - $scope.content.apps.push(listview); - - // remove the list view tab - angular.forEach($scope.content.tabs, function(tab, index){ - if(tab.alias === "Contents") { - tab.hide = true; - } - }); - - } else { - $scope.content.apps.push(contentApp); - } - - $scope.content.apps.push(infoApp); - // set first app to active $scope.content.apps[0].active = true; diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index b8dcb9e8fa..fb19131e96 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -218,16 +218,24 @@ namespace Umbraco.Web.Editors var display = new ContentItemDisplay { Id = Constants.System.RecycleBinContent, - Alias = "recycleBin", + //Alias = "recycleBin", ParentId = -1, - Name = Services.TextService.Localize("general/recycleBin"), + //Name = Services.TextService.Localize("general/recycleBin"), ContentTypeAlias = "recycleBin", - CreateDate = DateTime.Now, + //CreateDate = DateTime.Now, IsContainer = true, - Path = "-1," + Constants.System.RecycleBinContent + Path = "-1," + Constants.System.RecycleBinContent, + ContentVariants = new List + { + new ContentVariantDisplay + { + CreateDate = DateTime.Now, + Name = Services.TextService.Localize("general/recycleBin") + } + } }; - TabsAndPropertiesResolver.AddListView(display, "content", Services.DataTypeService, Services.TextService); + TabsAndPropertiesResolver.AddListView(display.ContentVariants.First(), "content", "recycleBin", Services.DataTypeService, Services.TextService); return display; } @@ -258,10 +266,11 @@ namespace Umbraco.Web.Editors content.AllowedActions = new[] { "A" }; content.IsBlueprint = true; - var excludeProps = new[] { "_umb_urls", "_umb_releasedate", "_umb_expiredate", "_umb_template" }; - var propsTab = content.Tabs.Last(); - propsTab.Properties = propsTab.Properties - .Where(p => excludeProps.Contains(p.Alias) == false); + //fixme - exclude the content apps here + //var excludeProps = new[] { "_umb_urls", "_umb_releasedate", "_umb_expiredate", "_umb_template" }; + //var propsTab = content.Tabs.Last(); + //propsTab.Properties = propsTab.Properties + // .Where(p => excludeProps.Contains(p.Alias) == false); } /// @@ -272,7 +281,7 @@ namespace Umbraco.Web.Editors /// [OutgoingEditorModelEvent] [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetById(int id, string culture = null) + public ContentItemDisplay GetById(int id) { var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); if (foundContent == null) @@ -281,7 +290,7 @@ namespace Umbraco.Web.Editors return null;//irrelevant since the above throws } - var content = MapToDisplay(foundContent, culture); + var content = MapToDisplay(foundContent); return content; } @@ -289,11 +298,7 @@ namespace Umbraco.Web.Editors /// Gets an empty content item for the /// /// - /// - /// - /// If this is a container type, we'll remove the umbContainerView tab for a new item since - /// it cannot actually list children if it doesn't exist yet. - /// + /// [OutgoingEditorModelEvent] public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId) { @@ -306,16 +311,15 @@ namespace Umbraco.Web.Editors var emptyContent = Services.ContentService.Create("", parentId, contentType.Alias, Security.GetUserId().ResultOr(0)); var mapped = MapToDisplay(emptyContent); - //remove this tab if it exists: umbContainerView - var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); - mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); - - if (contentType.VariesByCulture()) - { - //Remove all variants except for the default since currently the default must be saved before other variants can be edited - //TODO: Allow for editing all variants at once ... this will be a future task - mapped.Variants = new[] { mapped.Variants.FirstOrDefault(x => x.IsCurrent) }; - } + //remove the listview app if it exists + mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "childItems").ToList(); + + //if (contentType.VariesByCulture()) + //{ + // //Remove all variants except for the default since currently the default must be saved before other variants can be edited + // //TODO: Allow for editing all variants at once ... this will be a future task + // mapped.Variants = new[] { mapped.Variants.FirstOrDefault(x => x.IsCurrent) }; + //} return mapped; } @@ -335,9 +339,9 @@ namespace Umbraco.Web.Editors var mapped = Mapper.Map(blueprint); - //remove this tab if it exists: umbContainerView - var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); - mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); + //remove the listview app if it exists + mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "childItems").ToList(); + return mapped; } @@ -571,131 +575,134 @@ namespace Umbraco.Web.Editors [OutgoingEditorModelEvent] public ContentItemDisplay PostSave([ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem) { - var contentItemDisplay = PostSaveInternal(contentItem, content => Services.ContentService.Save(contentItem.PersistedContent, Security.CurrentUser.Id)); - //ensure the active culture is still selected - if (!contentItem.Culture.IsNullOrWhiteSpace()) - { - foreach (var contentVariation in contentItemDisplay.Variants) - { - contentVariation.IsCurrent = contentVariation.Language.IsoCode.InvariantEquals(contentItem.Culture); - } - } - return contentItemDisplay; + throw new NotImplementedException("Implement this!"); + //var contentItemDisplay = PostSaveInternal(contentItem, content => Services.ContentService.Save(contentItem.PersistedContent, Security.CurrentUser.Id)); + ////ensure the active culture is still selected + //if (!contentItem.Culture.IsNullOrWhiteSpace()) + //{ + // foreach (var contentVariation in contentItemDisplay.Variants) + // { + // contentVariation.IsCurrent = contentVariation.Language.IsoCode.InvariantEquals(contentItem.Culture); + // } + //} + //return contentItemDisplay; } private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func saveMethod) { - //If we've reached here it means: - // * Our model has been bound - // * and validated - // * any file attachments have been saved to their temporary location for us to use - // * we have a reference to the DTO object and the persisted object - // * Permissions are valid - MapPropertyValues(contentItem); + throw new NotImplementedException("Implement this!"); - //We need to manually check the validation results here because: - // * We still need to save the entity even if there are validation value errors - // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) - // then we cannot continue saving, we can only display errors - // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display - // a message indicating this - if (ModelState.IsValid == false) - { - if (!RequiredForPersistenceAttribute.HasRequiredValuesForPersistence(contentItem) && IsCreatingAction(contentItem.Action)) - { - //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! - // add the modelstate to the outgoing object and throw a validation message - var forDisplay = MapToDisplay(contentItem.PersistedContent, contentItem.Culture); - forDisplay.Errors = ModelState.ToErrorDictionary(); - throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); + ////If we've reached here it means: + //// * Our model has been bound + //// * and validated + //// * any file attachments have been saved to their temporary location for us to use + //// * we have a reference to the DTO object and the persisted object + //// * Permissions are valid + //MapPropertyValues(contentItem); - } + ////We need to manually check the validation results here because: + //// * We still need to save the entity even if there are validation value errors + //// * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) + //// then we cannot continue saving, we can only display errors + //// * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display + //// a message indicating this + //if (ModelState.IsValid == false) + //{ + // if (!RequiredForPersistenceAttribute.HasRequiredValuesForPersistence(contentItem) && IsCreatingAction(contentItem.Action)) + // { + // //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! + // // add the modelstate to the outgoing object and throw a validation message + // var forDisplay = MapToDisplay(contentItem.PersistedContent, contentItem.Culture); + // forDisplay.Errors = ModelState.ToErrorDictionary(); + // throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); - //if the model state is not valid we cannot publish so change it to save - switch (contentItem.Action) - { - case ContentSaveAction.Publish: - contentItem.Action = ContentSaveAction.Save; - break; - case ContentSaveAction.PublishNew: - contentItem.Action = ContentSaveAction.SaveNew; - break; - } - } + // } - //initialize this to successful - var publishStatus = new PublishResult(null, contentItem.PersistedContent); - var wasCancelled = false; + // //if the model state is not valid we cannot publish so change it to save + // switch (contentItem.Action) + // { + // case ContentSaveAction.Publish: + // contentItem.Action = ContentSaveAction.Save; + // break; + // case ContentSaveAction.PublishNew: + // contentItem.Action = ContentSaveAction.SaveNew; + // break; + // } + //} - if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew) - { - //save the item - var saveResult = saveMethod(contentItem.PersistedContent); + ////initialize this to successful + //var publishStatus = new PublishResult(null, contentItem.PersistedContent); + //var wasCancelled = false; - wasCancelled = saveResult.Success == false && saveResult.Result == OperationResultType.FailedCancelledByEvent; - } - else if (contentItem.Action == ContentSaveAction.SendPublish || contentItem.Action == ContentSaveAction.SendPublishNew) - { - var sendResult = Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id); - wasCancelled = sendResult == false; - } - else - { - PublishInternal(contentItem, ref publishStatus, ref wasCancelled); - } + //if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew) + //{ + // //save the item + // var saveResult = saveMethod(contentItem.PersistedContent); - //get the updated model - var display = MapToDisplay(contentItem.PersistedContent, contentItem.Culture); + // wasCancelled = saveResult.Success == false && saveResult.Result == OperationResultType.FailedCancelledByEvent; + //} + //else if (contentItem.Action == ContentSaveAction.SendPublish || contentItem.Action == ContentSaveAction.SendPublishNew) + //{ + // var sendResult = Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id); + // wasCancelled = sendResult == false; + //} + //else + //{ + // PublishInternal(contentItem, ref publishStatus, ref wasCancelled); + //} - //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 - HandleInvalidModelState(display); + ////get the updated model + //var display = MapToDisplay(contentItem.PersistedContent, contentItem.Culture); - //put the correct msgs in - switch (contentItem.Action) - { - case ContentSaveAction.Save: - case ContentSaveAction.SaveNew: - if (wasCancelled == false) - { - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentSavedHeader"), - Services.TextService.Localize("speechBubbles/editContentSavedText")); - } - else - { - AddCancelMessage(display); - } - break; - case ContentSaveAction.SendPublish: - case ContentSaveAction.SendPublishNew: - if (wasCancelled == false) - { - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentSendToPublish"), - Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); - } - else - { - AddCancelMessage(display); - } - break; - case ContentSaveAction.Publish: - case ContentSaveAction.PublishNew: - ShowMessageForPublishStatus(publishStatus, display); - break; - } + ////lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 + //HandleInvalidModelState(display); - //If the item is new and the operation was cancelled, we need to return a different - // status code so the UI can handle it since it won't be able to redirect since there - // is no Id to redirect to! - if (wasCancelled && IsCreatingAction(contentItem.Action)) - { - throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); - } + ////put the correct msgs in + //switch (contentItem.Action) + //{ + // case ContentSaveAction.Save: + // case ContentSaveAction.SaveNew: + // if (wasCancelled == false) + // { + // display.AddSuccessNotification( + // Services.TextService.Localize("speechBubbles/editContentSavedHeader"), + // Services.TextService.Localize("speechBubbles/editContentSavedText")); + // } + // else + // { + // AddCancelMessage(display); + // } + // break; + // case ContentSaveAction.SendPublish: + // case ContentSaveAction.SendPublishNew: + // if (wasCancelled == false) + // { + // display.AddSuccessNotification( + // Services.TextService.Localize("speechBubbles/editContentSendToPublish"), + // Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); + // } + // else + // { + // AddCancelMessage(display); + // } + // break; + // case ContentSaveAction.Publish: + // case ContentSaveAction.PublishNew: + // ShowMessageForPublishStatus(publishStatus, display); + // break; + //} - display.PersistedContent = contentItem.PersistedContent; + ////If the item is new and the operation was cancelled, we need to return a different + //// status code so the UI can handle it since it won't be able to redirect since there + //// is no Id to redirect to! + //if (wasCancelled && IsCreatingAction(contentItem.Action)) + //{ + // throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); + //} - return display; + //display.PersistedContent = contentItem.PersistedContent; + + //return display; } /// @@ -1006,7 +1013,7 @@ namespace Umbraco.Web.Editors var unpublishResult = Services.ContentService.Unpublish(foundContent, culture: culture, userId: Security.GetUserId().ResultOr(0)); - var content = MapToDisplay(foundContent, culture); + var content = MapToDisplay(foundContent); if (!unpublishResult.Success) { @@ -1273,18 +1280,20 @@ namespace Umbraco.Web.Editors /// /// /// - private ContentItemDisplay MapToDisplay(IContent content, string culture = null) + private ContentItemDisplay MapToDisplay(IContent content) { - //A culture must exist in the mapping context if this content type is CultureNeutral since for a culture variant to be edited, - // the Cuture property of ContentItemDisplay must exist (at least currently). - if (culture == null && content.ContentType.VariesByCulture()) - { - //If a culture is not explicitly sent up, then it means that the user is editing the default variant language. - culture = Services.LocalizationService.GetDefaultLanguageIsoCode(); - } + ////A culture must exist in the mapping context if this content type is CultureNeutral since for a culture variant to be edited, + //// the Cuture property of ContentItemDisplay must exist (at least currently). + //if (culture == null && content.ContentType.VariesByCulture()) + //{ + // //If a culture is not explicitly sent up, then it means that the user is editing the default variant language. + // culture = Services.LocalizationService.GetDefaultLanguageIsoCode(); + //} - var display = ContextMapper.Map(content, UmbracoContext, - new Dictionary { { ContextMapper.CultureKey, culture } }); + //var display = ContextMapper.Map(content, UmbracoContext, + // new Dictionary { { ContextMapper.CultureKey, culture } }); + + var display = ContextMapper.Map(content, UmbracoContext); return display; } diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 3fde20df28..94b88459f7 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -77,9 +77,9 @@ namespace Umbraco.Web.Editors var emptyContent = Services.MediaService.CreateMedia("", parentId, contentType.Alias, Security.GetUserId().ResultOr(0)); var mapped = ContextMapper.Map(emptyContent, UmbracoContext); - //remove this tab if it exists: umbContainerView - var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); - mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); + //remove the listview app if it exists + mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "childItems").ToList(); + return mapped; } @@ -87,9 +87,9 @@ namespace Umbraco.Web.Editors /// Returns an item to be used to display the recycle bin for media /// /// - public ContentItemDisplay GetRecycleBin() + public MediaItemDisplay GetRecycleBin() { - var display = new ContentItemDisplay + var display = new MediaItemDisplay { Id = Constants.System.RecycleBinMedia, Alias = "recycleBin", @@ -101,7 +101,7 @@ namespace Umbraco.Web.Editors Path = "-1," + Constants.System.RecycleBinMedia }; - TabsAndPropertiesResolver.AddListView(display, "media", Services.DataTypeService, Services.TextService); + TabsAndPropertiesResolver.AddListView(display, "media", "recycleBin", Services.DataTypeService, Services.TextService); return display; } diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 513f69f778..5d5367b3dc 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -138,7 +138,7 @@ namespace Umbraco.Web.Editors ParentId = -1 }; - TabsAndPropertiesResolver.AddListView(display, "member", Services.DataTypeService, Services.TextService); + TabsAndPropertiesResolver.AddListView(display, "member", listName, Services.DataTypeService, Services.TextService); return display; } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentApp.cs b/src/Umbraco.Web/Models/ContentEditing/ContentApp.cs new file mode 100644 index 0000000000..3477b200cb --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/ContentApp.cs @@ -0,0 +1,24 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Defines a "Content App" which are editor extensions + /// + [DataContract(Name = "app", Namespace = "")] + public class ContentApp + { + [DataMember(Name = "name")] + public string Name { get; set; } + + [DataMember(Name = "alias")] + public string Alias { get; set; } + + [DataMember(Name = "icon")] + public string Icon { get; set; } + + [DataMember(Name = "view")] + public string View { get; set; } + } +} + diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs index 08b95a9029..8f14114c68 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs @@ -1,7 +1,12 @@ -using System; +using Newtonsoft.Json; +using System; using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; +using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Serialization; using Umbraco.Web.Routing; namespace Umbraco.Web.Models.ContentEditing @@ -10,28 +15,109 @@ namespace Umbraco.Web.Models.ContentEditing /// A model representing a content item to be displayed in the back office ///
[DataContract(Name = "content", Namespace = "")] - public class ContentItemDisplay : ListViewAwareContentItemDisplayBase + public class ContentItemDisplay : INotificationModel, IErrorModel //ListViewAwareContentItemDisplayBase { public ContentItemDisplay() { AllowPreview = true; + Notifications = new List(); + Errors = new Dictionary(); + ContentVariants = new List(); + ContentApps = new List(); } - [DataMember(Name = "publishDate")] - public DateTime? PublishDate { get; set; } + [DataMember(Name = "id", IsRequired = true)] + [Required] + public int Id { get; set; } + [DataMember(Name = "udi")] + [ReadOnly(true)] + [JsonConverter(typeof(UdiJsonConverter))] + public Udi Udi { get; set; } + + [DataMember(Name = "icon")] + public string Icon { get; set; } + + [DataMember(Name = "trashed")] + [ReadOnly(true)] + public bool Trashed { get; set; } + + /// + /// This is the unique Id stored in the database - but could also be the unique id for a custom membership provider + /// + [DataMember(Name = "key")] + public Guid Key { get; set; } + + [DataMember(Name = "parentId", IsRequired = true)] + [Required] + public int ParentId { get; set; } + + /// + /// The path of the entity + /// + [DataMember(Name = "path")] + public string Path { get; set; } + + /// + /// A collection of content variants + /// + /// + /// If a content item is invariant, this collection will only contain one item, else it will contain all culture variants + /// + [DataMember(Name = "variants")] + public IEnumerable ContentVariants { get; set; } + + [DataMember(Name = "owner")] + public UserProfile Owner { get; set; } + + [DataMember(Name = "updater")] + public UserProfile Updater { get; set; } + + /// + /// The name of the content type + /// + [DataMember(Name = "contentTypeName")] + public string ContentTypeName { get; set; } + + /// + /// Indicates if the content is configured as a list view container + /// + [DataMember(Name = "isContainer")] + public bool IsContainer { get; set; } + + /// + /// Property indicating if this item is part of a list view parent + /// + [DataMember(Name = "isChildOfListView")] + public bool IsChildOfListView { get; set; } + + /// + /// Property for the entity's individual tree node URL + /// + /// + /// This is required if the item is a child of a list view since the tree won't actually be loaded, + /// so the app will need to go fetch the individual tree node in order to be able to load it's action list (menu) + /// + [DataMember(Name = "treeNodeUrl")] + public string TreeNodeUrl { get; set; } + + [DataMember(Name = "contentTypeAlias", IsRequired = true)] + [Required(AllowEmptyStrings = false)] + public string ContentTypeAlias { get; set; } + + [DataMember(Name = "sortOrder")] + public int SortOrder { get; set; } + + //[DataMember(Name = "publishDate")] + //public DateTime? PublishDate { get; set; } + + //TODO: These will need to be moved once we have scheduled publishing in per culture [DataMember(Name = "releaseDate")] public DateTime? ReleaseDate { get; set; } [DataMember(Name = "removeDate")] public DateTime? ExpireDate { get; set; } - - /// - /// Represents the variant info for a content item - /// - [DataMember(Name = "variants")] - public IEnumerable Variants { get; set; } - + [DataMember(Name = "template")] public string TemplateAlias { get; set; } @@ -64,5 +150,56 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "isBlueprint")] public bool IsBlueprint { get; set; } + + [DataMember(Name = "apps")] + public IEnumerable ContentApps { get; set; } + + /// + /// The real persisted content object - used during inbound model binding + /// + /// + /// This is not used for outgoing model information. + /// + [IgnoreDataMember] + internal IContent PersistedContent { get; set; } + + /// + /// The DTO object used to gather all required content data including data type information etc... for use with validation - used during inbound model binding + /// + /// + /// We basically use this object to hydrate all required data from the database into one object so we can validate everything we need + /// instead of having to look up all the data individually. + /// This is not used for outgoing model information. + /// + [IgnoreDataMember] + internal ContentItemDto ContentDto { get; set; } + + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + [ReadOnly(true)] + public List Notifications { get; private set; } + + /// + /// This is used for validation of a content item. + /// + /// + /// A content item can be invalid but still be saved. This occurs when there's property validation errors, we will + /// still save the item but it cannot be published. So we need a way of returning validation errors as well as the + /// updated model. + /// + /// NOTE: The ProperCase is important because when we return ModeState normally it will always be proper case. + /// + [DataMember(Name = "ModelState")] + [ReadOnly(true)] + public IDictionary Errors { get; set; } + + /// + /// A collection of extra data that is available for this specific entity/entity type + /// + [DataMember(Name = "metaData")] + [ReadOnly(true)] + public IDictionary AdditionalData { get; private set; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentVariation.cs b/src/Umbraco.Web/Models/ContentEditing/ContentVariation.cs deleted file mode 100644 index 3546b95e34..0000000000 --- a/src/Umbraco.Web/Models/ContentEditing/ContentVariation.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.Runtime.Serialization; -using Umbraco.Core.Models; - -namespace Umbraco.Web.Models.ContentEditing -{ - /// - /// Represents the variant info for a content item - /// - [DataContract(Name = "contentVariant", Namespace = "")] - public class ContentVariation - { - [DataMember(Name = "language", IsRequired = true)] - [Required] - public Language Language { get; set; } - - [DataMember(Name = "segment")] - public string Segment { get; set; } - - /// - /// The content name of the variant - /// - [DataMember(Name = "name")] - public string Name { get; set; } - - [DataMember(Name = "state")] - public string PublishedState { get; set; } - - /// - /// Determines if the content variant for this culture has been created - /// - [DataMember(Name = "exists")] - public bool Exists { get; set; } - - [DataMember(Name = "isEdited")] - public bool IsEdited { get; set; } - - /// - /// Determines if this is the variant currently being edited - /// - [DataMember(Name = "current")] - public bool IsCurrent { get; set; } - - /// - /// If the variant is a required variant for validation purposes - /// - [DataMember(Name = "mandatory")] - public bool Mandatory { get; set; } - } -} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentVariationDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentVariationDisplay.cs new file mode 100644 index 0000000000..247f88d203 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/ContentVariationDisplay.cs @@ -0,0 +1,104 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Runtime.Serialization; +using Umbraco.Core.Models; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Represents the variant info for a content item + /// + [DataContract(Name = "contentVariant", Namespace = "")] + public class ContentVariantDisplay : ITabbedContentItem + { + public ContentVariantDisplay() + { + Tabs = new List>(); + } + + [DataMember(Name = "name", IsRequired = true)] + public string Name { get; set; } + + /// + /// Defines the tabs containing display properties + /// + [DataMember(Name = "tabs")] + public IEnumerable> Tabs { get; set; } + + // note + // once a [DataContract] has been defined on a class, with a [DataMember] property, + // one simply cannot ignore that property anymore - [IgnoreDataMember] on an overriden + // property is ignored, and 'newing' the property means that it's the base property + // which is used + // + // OTOH, Json.NET is happy having [JsonIgnore] on overrides, even though the base + // property is [JsonProperty]. so, forcing [JsonIgnore] here, but really, we should + // rething the whole thing. + + /// + /// Override the properties property to ensure we don't serialize this + /// and to simply return the properties based on the properties in the tabs collection + /// + /// + /// This property cannot be set + /// + [IgnoreDataMember] + [JsonIgnore] // see note above on IgnoreDataMember vs JsonIgnore + public IEnumerable Properties + { + get => Tabs.SelectMany(x => x.Properties); + set => throw new NotImplementedException(); + } + + /// + /// The language/culture assigned to this content variation + /// + /// + /// If this is null it means this content variant is an invariant culture + /// + [DataMember(Name = "language")] + public Language Language { get; set; } + + [DataMember(Name = "segment")] + public string Segment { get; set; } + + [DataMember(Name = "state")] + public string PublishedState { get; set; } + + [DataMember(Name = "updateDate")] + public DateTime UpdateDate { get; set; } + + [DataMember(Name = "createDate")] + public DateTime CreateDate { get; set; } + + //[DataMember(Name = "published")] + //public bool Published { get; set; } + + [DataMember(Name = "publishDate")] + public DateTime? PublishDate { get; set; } + + /// + /// Determines if the content variant for this culture has been created + /// + [DataMember(Name = "exists")] + public bool Exists { get; set; } + + [DataMember(Name = "isEdited")] + public bool IsEdited { get; set; } + + ///// + ///// Determines if this is the variant currently being edited + ///// + //[DataMember(Name = "current")] + //public bool IsCurrent { get; set; } + + ///// + ///// If the variant is a required variant for validation purposes + ///// + //[DataMember(Name = "mandatory")] + //public bool Mandatory { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ITabbedContentItem.cs b/src/Umbraco.Web/Models/ContentEditing/ITabbedContentItem.cs new file mode 100644 index 0000000000..871b9a2063 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/ITabbedContentItem.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Umbraco.Web.Models.ContentEditing +{ + public interface ITabbedContentItem where T : ContentPropertyBasic + { + IEnumerable Properties { get; set; } + IEnumerable> Tabs { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/MediaItemDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MediaItemDisplay.cs index 45ad5f71f9..2a1c29983a 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MediaItemDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MediaItemDisplay.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Runtime.Serialization; using Umbraco.Core.Models; @@ -10,10 +11,18 @@ namespace Umbraco.Web.Models.ContentEditing [DataContract(Name = "content", Namespace = "")] public class MediaItemDisplay : ListViewAwareContentItemDisplayBase { + public MediaItemDisplay() + { + ContentApps = new List(); + } + [DataMember(Name = "contentType")] public ContentTypeBasic ContentType { get; set; } [DataMember(Name = "mediaLink")] public string MediaLink { get; set; } + + [DataMember(Name = "apps")] + public IEnumerable ContentApps { get; set; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/TabbedContentItem.cs b/src/Umbraco.Web/Models/ContentEditing/TabbedContentItem.cs index 07def99ed3..89057bba05 100644 --- a/src/Umbraco.Web/Models/ContentEditing/TabbedContentItem.cs +++ b/src/Umbraco.Web/Models/ContentEditing/TabbedContentItem.cs @@ -7,8 +7,7 @@ using Umbraco.Core.Models; namespace Umbraco.Web.Models.ContentEditing { - public abstract class TabbedContentItem : ContentItemBasic - where T : ContentPropertyBasic + public abstract class TabbedContentItem : ContentItemBasic, ITabbedContentItem where T : ContentPropertyBasic where TPersisted : IContentBase { protected TabbedContentItem() diff --git a/src/Umbraco.Web/Models/Mapping/ContentAppResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentAppResolver.cs new file mode 100644 index 0000000000..ca58f81b91 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentAppResolver.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + + internal class ContentAppResolver : IValueResolver> + { + private readonly ContentApp _contentApp = new ContentApp + { + Alias = "content", + Name = "Content", + Icon = "icon-document", + View = "views/content/apps/content/content.html" + }; + + private readonly ContentApp _infoApp = new ContentApp + { + Alias = "info", + Name = "Info", + Icon = "icon-info", + View = "views/content/apps/info/info.html" + }; + + private static readonly ContentApp _listViewApp = new ContentApp + { + Alias = "childItems", + Name = "Child items", + Icon = "icon-list", + View = "views/content/apps/listview/listview.html" + }; + + public IEnumerable Resolve(IContent source, ContentItemDisplay destination, IEnumerable destMember, ResolutionContext context) + { + var apps = new List + { + _contentApp, + _infoApp + }; + + if (source.ContentType.IsContainer) + { + apps.Add(_listViewApp); + } + + return apps; + } + } + +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentItemDisplayNameResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayNameResolver.cs index 101fed8a06..c613b163d6 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentItemDisplayNameResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayNameResolver.cs @@ -1,22 +1,22 @@ -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Web.Models.ContentEditing; -using ContentVariation = Umbraco.Core.Models.ContentVariation; +//using AutoMapper; +//using Umbraco.Core; +//using Umbraco.Core.Models; +//using Umbraco.Web.Models.ContentEditing; +//using ContentVariation = Umbraco.Core.Models.ContentVariation; -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Used to map the name from an depending on it's variation settings - /// - internal class ContentItemDisplayNameResolver : IValueResolver - { - public string Resolve(IContent source, ContentItemDisplay destination, string destMember, ResolutionContext context) - { - var culture = context.GetCulture(); - return source.ContentType.VariesByCulture() && culture != null - ? source.GetCultureName(culture) - : source.Name; - } - } -} +//namespace Umbraco.Web.Models.Mapping +//{ +// /// +// /// Used to map the name from an depending on it's variation settings +// /// +// internal class ContentItemDisplayNameResolver : IValueResolver +// { +// public string Resolve(IContent source, ContentVariationDisplay destination, string destMember, ResolutionContext context) +// { +// var culture = context.GetCulture(); +// return source.ContentType.VariesByCulture() && culture != null +// ? source.GetCultureName(culture) +// : source.Name; +// } +// } +//} diff --git a/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs index cb6e2938be..bd7de4176e 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs @@ -6,65 +6,66 @@ using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; -using ContentVariation = Umbraco.Web.Models.ContentEditing.ContentVariation; using Language = Umbraco.Web.Models.ContentEditing.Language; namespace Umbraco.Web.Models.Mapping { - /// - /// Used to map the variations collection from an instance - /// - internal class ContentItemDisplayVariationResolver : IValueResolver> + + internal class ContentVariantResolver : IValueResolver> { private readonly ILocalizationService _localizationService; + private readonly ILocalizedTextService _textService; - public ContentItemDisplayVariationResolver(ILocalizationService localizationService) + public ContentVariantResolver(ILocalizationService localizationService, ILocalizedTextService textService) { _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); + _textService = textService ?? throw new ArgumentNullException(nameof(textService)); } - public IEnumerable Resolve(IContent source, ContentItemDisplay destination, IEnumerable destMember, ResolutionContext context) + public IEnumerable Resolve(IContent source, ContentItemDisplay destination, IEnumerable destMember, ResolutionContext context) { + var result = new List(); if (!source.ContentType.VariesByCulture()) - return Enumerable.Empty(); - - var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList(); - if (allLanguages.Count == 0) return Enumerable.Empty(); - - var langs = context.Mapper.Map, IEnumerable>(allLanguages, null, context); - var variants = langs.Select(x => new ContentVariation { - Language = x, - Mandatory = x.Mandatory, - Name = source.GetCultureName(x.IsoCode), - Exists = source.IsCultureAvailable(x.IsoCode), // segments ?? - PublishedState = (source.PublishedState == PublishedState.Unpublished //if the entire document is unpublished, then flag every variant as unpublished - ? PublishedState.Unpublished - : source.IsCulturePublished(x.IsoCode) - ? PublishedState.Published - : PublishedState.Unpublished).ToString(), - IsEdited = source.IsCultureEdited(x.IsoCode) - //Segment = ?? We'll need to populate this one day when we support segments - }).ToList(); - - var culture = context.GetCulture(); - - //set the current variant being edited to the one found in the context or the default if nothing matches - var foundCurrent = false; - foreach (var variant in variants) - { - if (culture.InvariantEquals(variant.Language.IsoCode)) - { - variant.IsCurrent = true; - foundCurrent = true; - break; - } + //this is invariant so just map the IContent instance to ContentVariationDisplay + result.Add(context.Mapper.Map(source)); } - if (!foundCurrent) - variants.First(x => x.Language.IsDefaultVariantLanguage).IsCurrent = true; + else + { + var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList(); + if (allLanguages.Count == 0) return Enumerable.Empty(); //this should never happen - return variants; + var langs = context.Mapper.Map, IEnumerable>(allLanguages, null, context).ToList(); + + //create a variant for each lang, then we'll populate the values + var variants = langs.Select(x => + { + //We need to set the culture in the mapping context since this is needed to ensure that the correct property values + //are resolved during the mapping + context.Items[ContextMapper.CultureKey] = x.IsoCode; + return context.Mapper.Map(source, null, context); + }).ToList(); + + for (int i = 0; i < langs.Count; i++) + { + var x = langs[i]; + var variant = variants[i]; + + variant.Language = x; + variant.Name = source.GetCultureName(x.IsoCode); + variant.Exists = source.IsCultureAvailable(x.IsoCode); // segments ?? + variant.PublishedState = (source.PublishedState == PublishedState.Unpublished //if the entire document is unpublished, then flag every variant as unpublished + ? PublishedState.Unpublished + : source.IsCulturePublished(x.IsoCode) + ? PublishedState.Published + : PublishedState.Unpublished).ToString(); + variant.IsEdited = source.IsCultureEdited(x.IsoCode); + } + + return variants; + } + return result; } - } + } diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs index 059dd32499..684ee13581 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs @@ -20,21 +20,24 @@ namespace Umbraco.Web.Models.Mapping var contentOwnerResolver = new OwnerResolver(userService); var creatorResolver = new CreatorResolver(userService); var actionButtonsResolver = new ActionButtonsResolver(userService, contentService); - var tabsAndPropertiesResolver = new TabsAndPropertiesResolver(textService); + var tabsAndPropertiesResolver = new TabsAndPropertiesResolver(textService); var childOfListViewResolver = new ContentChildOfListViewResolver(contentService, contentTypeService); var contentTypeBasicResolver = new ContentTypeBasicResolver(); var contentTreeNodeUrlResolver = new ContentTreeNodeUrlResolver(); var defaultTemplateResolver = new DefaultTemplateResolver(); var contentUrlResolver = new ContentUrlResolver(textService, contentService, logger); - var variantResolver = new ContentItemDisplayVariationResolver(localizationService); + var variantResolver = new ContentVariantResolver(localizationService, textService); + var contentAppResolver = new ContentAppResolver(); //FROM IContent TO ContentItemDisplay CreateMap() .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(src.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, src.Key))) .ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => contentOwnerResolver.Resolve(src))) .ForMember(dest => dest.Updater, opt => opt.ResolveUsing(src => creatorResolver.Resolve(src))) - .ForMember(dest => dest.Name, opt => opt.ResolveUsing()) - .ForMember(dest => dest.Variants, opt => opt.ResolveUsing(variantResolver)) + //.ForMember(dest => dest.Name, opt => opt.ResolveUsing()) + .ForMember(dest => dest.ContentVariants, opt => opt.ResolveUsing(variantResolver)) + .ForMember(dest => dest.ContentApps, opt => opt.ResolveUsing(contentAppResolver)) + //.ForMember(dest => dest.Variants, opt => opt.ResolveUsing(variantResolver)) .ForMember(dest => dest.Icon, opt => opt.MapFrom(src => src.ContentType.Icon)) .ForMember(dest => dest.ContentTypeAlias, opt => opt.MapFrom(src => src.ContentType.Alias)) .ForMember(dest => dest.ContentTypeName, opt => opt.MapFrom(src => src.ContentType.Name)) @@ -42,27 +45,38 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.IsBlueprint, opt => opt.MapFrom(src => src.Blueprint)) .ForMember(dest => dest.IsChildOfListView, opt => opt.ResolveUsing(childOfListViewResolver)) .ForMember(dest => dest.Trashed, opt => opt.MapFrom(src => src.Trashed)) - .ForMember(dest => dest.PublishDate, opt => opt.MapFrom(src => src.PublishDate)) + //TODO: The publish date needs to be per variant - in our display models + //.ForMember(dest => dest.PublishDate, opt => opt.MapFrom(src => src.PublishDate)) .ForMember(dest => dest.TemplateAlias, opt => opt.ResolveUsing(defaultTemplateResolver)) .ForMember(dest => dest.Urls, opt => opt.ResolveUsing(contentUrlResolver)) - .ForMember(dest => dest.Properties, opt => opt.Ignore()) + //.ForMember(dest => dest.Properties, opt => opt.Ignore()) .ForMember(dest => dest.AllowPreview, opt => opt.Ignore()) .ForMember(dest => dest.TreeNodeUrl, opt => opt.ResolveUsing(contentTreeNodeUrlResolver)) .ForMember(dest => dest.Notifications, opt => opt.Ignore()) .ForMember(dest => dest.Errors, opt => opt.Ignore()) - .ForMember(dest => dest.Alias, opt => opt.Ignore()) + //.ForMember(dest => dest.Alias, opt => opt.Ignore()) .ForMember(dest => dest.DocumentType, opt => opt.ResolveUsing(contentTypeBasicResolver)) .ForMember(dest => dest.AllowedTemplates, opt => opt.MapFrom(content => content.ContentType.AllowedTemplates .Where(t => t.Alias.IsNullOrWhiteSpace() == false && t.Name.IsNullOrWhiteSpace() == false) - .ToDictionary(t => t.Alias, t => t.Name))) - .ForMember(dest => dest.Tabs, opt => opt.ResolveUsing(tabsAndPropertiesResolver)) + .ToDictionary(t => t.Alias, t => t.Name))) + //.ForMember(dest => dest.Tabs, opt => opt.ResolveUsing(tabsAndPropertiesResolver)) .ForMember(dest => dest.AllowedActions, opt => opt.ResolveUsing(src => actionButtonsResolver.Resolve(src))) - .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); + //.AfterMap((content, display) => + //{ + // if (content.ContentType.IsContainer) + // TabsAndPropertiesResolver.AddListView(display, "content", dataTypeService, textService); + //}); + + CreateMap() + .ForMember(dest => dest.PublishDate, opt => opt.MapFrom(src => src.PublishDate)) + .ForMember(dest => dest.Properties, opt => opt.Ignore()) + .ForMember(dest => dest.Tabs, opt => opt.ResolveUsing(tabsAndPropertiesResolver)) .AfterMap((content, display) => { if (content.ContentType.IsContainer) - TabsAndPropertiesResolver.AddListView(display, "content", dataTypeService, textService); + TabsAndPropertiesResolver.AddListView(display, content.ContentType.Alias, "content", dataTypeService, textService); }); //FROM IContent TO ContentItemBasic diff --git a/src/Umbraco.Web/Models/Mapping/MediaAppResolver.cs b/src/Umbraco.Web/Models/Mapping/MediaAppResolver.cs new file mode 100644 index 0000000000..cbf8cd3aa2 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/MediaAppResolver.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + internal class MediaAppResolver : IValueResolver> + { + private static readonly ContentApp _contentApp = new ContentApp + { + Alias = "content", + Name = "Content", + Icon = "icon-document", + View = "views/media/apps/content/content.html" + }; + + private static readonly ContentApp _infoApp = new ContentApp + { + Alias = "info", + Name = "Info", + Icon = "icon-info", + View = "views/media/apps/info/info.html" + }; + + private static readonly ContentApp _listViewApp = new ContentApp + { + Alias = "childItems", + Name = "Child items", + Icon = "icon-list", + View = "views/media/apps/listview/listview.html" + }; + + public IEnumerable Resolve(IMedia source, MediaItemDisplay destination, IEnumerable destMember, ResolutionContext context) + { + var apps = new List(); + + if (source.ContentType.IsContainer || source.ContentType.Alias == Umbraco.Core.Constants.Conventions.MediaTypes.Folder) + { + apps.Add(_listViewApp); + } + else + { + apps.Add(_contentApp); + } + + apps.Add(_infoApp); + + return apps; + } + } + +} diff --git a/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs index 61419281c4..4a5c5386f8 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs @@ -22,6 +22,7 @@ namespace Umbraco.Web.Models.Mapping var childOfListViewResolver = new MediaChildOfListViewResolver(mediaService, mediaTypeService); var contentTreeNodeUrlResolver = new ContentTreeNodeUrlResolver(); var mediaTypeBasicResolver = new ContentTypeBasicResolver(); + var mediaAppResolver = new MediaAppResolver(); //FROM IMedia TO MediaItemDisplay CreateMap() @@ -45,10 +46,11 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) .ForMember(dest => dest.ContentType, opt => opt.ResolveUsing(mediaTypeBasicResolver)) .ForMember(dest => dest.MediaLink, opt => opt.ResolveUsing(content => string.Join(",", content.GetUrls(UmbracoConfig.For.UmbracoSettings().Content, logger)))) + .ForMember(dest => dest.ContentApps, opt => opt.ResolveUsing(mediaAppResolver)) .AfterMap((media, display) => { - if (media.ContentType.IsContainer) - TabsAndPropertiesResolver.AddListView(display, "media", dataTypeService, textService); + //if (media.ContentType.IsContainer) + // TabsAndPropertiesResolver.AddListView(display, "media", dataTypeService, textService); }); //FROM IMedia TO ContentItemBasic diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs index 1f8cfa920e..a92527823c 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -35,11 +35,13 @@ namespace Umbraco.Web.Models.Mapping /// This must be either 'content' or 'media' /// /// - internal static void AddListView(TabbedContentItem display, string entityType, IDataTypeService dataTypeService, ILocalizedTextService localizedTextService) - where TPersisted : IContentBase + internal static void AddListView( + ITabbedContentItem display, + string contentTypeAlias, string entityType, + IDataTypeService dataTypeService, ILocalizedTextService localizedTextService) { int dtdId; - var customDtdName = Constants.Conventions.DataTypes.ListViewPrefix + display.ContentTypeAlias; + var customDtdName = Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias; switch (entityType) { case "content": @@ -71,39 +73,43 @@ namespace Umbraco.Web.Models.Mapping throw new NullReferenceException("The property editor with alias " + dt.EditorAlias + " does not exist"); } - var listViewTab = new Tab - { - Alias = Constants.Conventions.PropertyGroups.ListViewGroupName, - Label = localizedTextService.Localize("content/childItems"), - Id = display.Tabs.Count() + 1, - IsActive = true - }; + //TODO: We need to move this logic elsewhere, we don't want to have to add a list view to a tab that will + // get removed later and magically move to the ListView ContentApp. The ListView ContentApp will need to + // manage this data somehow so a content app will also need to be able to specify it's own model. - var listViewConfig = editor.GetConfigurationEditor().ToConfigurationEditor(dt.Configuration); - //add the entity type to the config - listViewConfig["entityType"] = entityType; + //var listViewTab = new Tab + //{ + // Alias = Constants.Conventions.PropertyGroups.ListViewGroupName, + // Label = localizedTextService.Localize("content/childItems"), + // Id = display.Tabs.Count() + 1, + // IsActive = true + //}; - //Override Tab Label if tabName is provided - if (listViewConfig.ContainsKey("tabName")) - { - var configTabName = listViewConfig["tabName"]; - if (configTabName != null && string.IsNullOrWhiteSpace(configTabName.ToString()) == false) - listViewTab.Label = configTabName.ToString(); - } + //var listViewConfig = editor.GetConfigurationEditor().ToConfigurationEditor(dt.Configuration); + ////add the entity type to the config + //listViewConfig["entityType"] = entityType; - var listViewProperties = new List(); - listViewProperties.Add(new ContentPropertyDisplay - { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}containerView", - Label = "", - Value = null, - View = editor.GetValueEditor().View, - HideLabel = true, - Config = listViewConfig - }); - listViewTab.Properties = listViewProperties; + ////Override Tab Label if tabName is provided + //if (listViewConfig.ContainsKey("tabName")) + //{ + // var configTabName = listViewConfig["tabName"]; + // if (configTabName != null && string.IsNullOrWhiteSpace(configTabName.ToString()) == false) + // listViewTab.Label = configTabName.ToString(); + //} - SetChildItemsTabPosition(display, listViewConfig, listViewTab); + //var listViewProperties = new List(); + //listViewProperties.Add(new ContentPropertyDisplay + //{ + // Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}containerView", + // Label = "", + // Value = null, + // View = editor.GetValueEditor().View, + // HideLabel = true, + // Config = listViewConfig + //}); + //listViewTab.Properties = listViewProperties; + + //SetChildItemsTabPosition(display, listViewConfig, listViewTab); } private static int GetTabNumberFromConfig(IDictionary listViewConfig) @@ -120,37 +126,36 @@ namespace Umbraco.Web.Models.Mapping return -1; } - private static void SetChildItemsTabPosition(TabbedContentItem display, - IDictionary listViewConfig, - Tab listViewTab) - where TPersisted : IContentBase - { - // Find position of tab from config - var tabIndexForChildItems = GetTabNumberFromConfig(listViewConfig); - if (tabIndexForChildItems != -1) - { - // Tab position is recorded 1-based but we insert into collection 0-based - tabIndexForChildItems--; + //private static void SetChildItemsTabPosition(ITabbedContentItem display, + // IDictionary listViewConfig, + // Tab listViewTab) + //{ + // // Find position of tab from config + // var tabIndexForChildItems = GetTabNumberFromConfig(listViewConfig); + // if (tabIndexForChildItems != -1) + // { + // // Tab position is recorded 1-based but we insert into collection 0-based + // tabIndexForChildItems--; - // Ensure within bounds - if (tabIndexForChildItems < 0) - { - tabIndexForChildItems = 0; - } + // // Ensure within bounds + // if (tabIndexForChildItems < 0) + // { + // tabIndexForChildItems = 0; + // } - if (tabIndexForChildItems > display.Tabs.Count()) - { - tabIndexForChildItems = display.Tabs.Count(); - } - } - else tabIndexForChildItems = 0; + // if (tabIndexForChildItems > display.Tabs.Count()) + // { + // tabIndexForChildItems = display.Tabs.Count(); + // } + // } + // else tabIndexForChildItems = 0; - // Recreate tab list with child items tab at configured position - var tabs = new List>(); - tabs.AddRange(display.Tabs); - tabs.Insert(tabIndexForChildItems, listViewTab); - display.Tabs = tabs; - } + // // Recreate tab list with child items tab at configured position + // var tabs = new List>(); + // tabs.AddRange(display.Tabs); + // tabs.Insert(tabIndexForChildItems, listViewTab); + // display.Tabs = tabs; + //} /// /// Returns a collection of custom generic properties that exist on the generic properties tab @@ -234,7 +239,6 @@ namespace Umbraco.Web.Models.Mapping { //we need to map this way to pass the context through, I don't like it but we'll see what AutoMapper says: https://github.com/AutoMapper/AutoMapper/issues/2588 var result = context.Mapper.Map, IEnumerable>( - // Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties. properties.OrderBy(prop => prop.PropertyType.SortOrder), null, context) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index aedbd6e771..9bb0a54c9a 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -204,8 +204,9 @@ + - + @@ -219,6 +220,7 @@ + @@ -235,6 +237,7 @@ + @@ -243,6 +246,7 @@ +