diff --git a/build/NuSpecs/tools/Views.Web.config.install.xdt b/build/NuSpecs/tools/Views.Web.config.install.xdt index a5797232b8..4d660301a8 100644 --- a/build/NuSpecs/tools/Views.Web.config.install.xdt +++ b/build/NuSpecs/tools/Views.Web.config.install.xdt @@ -1,42 +1,32 @@ - - -
-
- - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + +
+
+ + + + + + + + + + + + + + + + + + + + + diff --git a/docs/README.md b/docs/README.md index e7eebee6cd..067a4b83d0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -19,26 +19,26 @@ Umbraco is a free open source Content Management System built on the ASP.NET pla ## Umbraco - The Friendly CMS -For the first time on the Microsoft platform, there is a free user and developer friendly CMS that makes it quick and easy to create websites - or a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, out of the box. +For the first time on the Microsoft platform, there is a free user and developer friendly CMS that makes it quick and easy to create websites - or a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, out of the box. Umbraco is not only loved by developers, but is a content editors dream. Enjoy intuitive editing tools, media management, responsive views and approval workflows to send your content live. -Used by more than 443,000 active websites including Carlsberg, Segway, Amazon and Heinz and **The Official ASP.NET and IIS.NET website from Microsoft** ([https://asp.net](https://asp.net) / [https://iis.net](https://iis.net)), you can be sure that the technology is proven, stable and scales. Backed by the team at Umbraco HQ, and supported by a dedicated community of over 220,000 craftspeople globally, you can trust that Umbraco is a safe choice and is here to stay. +Used by more than 443,000 active websites including Carlsberg, Segway, Amazon and Heinz and **The Official ASP.NET and IIS.NET website from Microsoft** ([https://asp.net](https://asp.net) / [https://iis.net](https://iis.net)), you can be sure that the technology is proven, stable and scales. Backed by the team at Umbraco HQ, and supported by a dedicated community of over 220,000 craftspeople globally, you can trust that Umbraco is a safe choice and is here to stay. -To view more examples, please visit [https://umbraco.com/why-umbraco/#caseStudies](https://umbraco.com/why-umbraco/#caseStudies) +To view more examples, please visit [https://umbraco.com/case-studies-testimonials/](https://umbraco.com/case-studies-testimonials/) ## Why Open Source? As an Open Source platform, Umbraco is more than just a CMS. We are transparent with our roadmap for future versions, our incremental sprint planning notes are publicly accessible and community contributions and packages are available for all to use. ## Trying out Umbraco CMS -[Umbraco Cloud](https://umbraco.com) is the easiest and fastest way to use Umbraco yet with full support for all your custom .NET code and intergrations. You're up and running in less than a minute and your life will be made easier with automated upgrades and a built-in deployment engine. We offer a free 14 day trial, no credit card needed. +[Umbraco Cloud](https://umbraco.com/cloud) is the easiest and fastest way to use Umbraco yet with full support for all your custom .NET code and intergrations. You're up and running in less than a minute and your life will be made easier with automated upgrades and a built-in deployment engine. We offer a free 14 day trial, no credit card needed. -If you want to DIY you can [download Umbraco](https://our.umbraco.org/download) either as a ZIP file or via NuGet. It's the same version of Umbraco CMS that powers Umbraco Cloud, but you'll need to find a place to host yourself and handling deployments and upgrades is all down to you. +If you want to DIY you can [download Umbraco](https://our.umbraco.com/download) either as a ZIP file or via NuGet. It's the same version of Umbraco CMS that powers Umbraco Cloud, but you'll need to find a place to host yourself and handling deployments and upgrades is all down to you. ## Community -Our friendly community is available 24/7 at the community hub we call ["Our Umbraco"](https://our.umbraco.org). Our Umbraco feature forums for questions and answers, documentation, downloadable plugins for Umbraco and a rich collection of community resources. +Our friendly community is available 24/7 at the community hub we call ["Our Umbraco"](https://our.umbraco.com). Our Umbraco feature forums for questions and answers, documentation, downloadable plugins for Umbraco and a rich collection of community resources. ## Contribute to Umbraco @@ -46,6 +46,6 @@ Umbraco is contribution focused and community driven. If you want to contribute ## Found a bug? -Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](https://our.umbraco.org/contribute/report-an-issue-or-request-a-feature). +Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/docs/CONTRIBUTING.md). To view existing issues, please visit [http://issues.umbraco.org](http://issues.umbraco.org). diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index 524ae580a3..cf46bff6d7 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -330,6 +330,16 @@ namespace Umbraco.Core /// ContentType alias for default relation type "Relate Parent Document On Delete". /// public const string RelateParentDocumentOnDeleteAlias = "relateParentDocumentOnDelete"; + + /// + /// ContentType name for default relation type "Relate Parent Media Folder On Delete". + /// + public const string RelateParentMediaFolderOnDeleteName = "Relate Parent Media Folder On Delete"; + + /// + /// ContentType alias for default relation type "Relate Parent Media Folder On Delete". + /// + public const string RelateParentMediaFolderOnDeleteAlias = "relateParentMediaFolderOnDelete"; } } } diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 7c02f7ad75..28e203c7b7 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -314,6 +314,9 @@ namespace Umbraco.Core.Migrations.Install relationType = new RelationTypeDto { Id = 2, Alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = Constants.ObjectTypes.Document, ParentObjectType = Constants.ObjectTypes.Document, Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName }; relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); + relationType = new RelationTypeDto { Id = 3, Alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, ChildObjectType = Constants.ObjectTypes.Media, ParentObjectType = Constants.ObjectTypes.Media, Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName }; + relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); } private void CreateTaskTypeData() diff --git a/src/Umbraco.Core/Persistence/Dtos/LanguageDto.cs b/src/Umbraco.Core/Persistence/Dtos/LanguageDto.cs index 12c9fd0bd4..08bd2a0582 100644 --- a/src/Umbraco.Core/Persistence/Dtos/LanguageDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/LanguageDto.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Persistence.Dtos [Column("languageISOCode")] [Index(IndexTypes.UniqueNonClustered)] [NullSetting(NullSetting = NullSettings.Null)] - [Length(10)] + [Length(14)] public string IsoCode { get; set; } [Column("languageCultureName")] diff --git a/src/Umbraco.Core/Persistence/Dtos/RelationTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/RelationTypeDto.cs index 5ce408fc44..e972192844 100644 --- a/src/Umbraco.Core/Persistence/Dtos/RelationTypeDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/RelationTypeDto.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Dtos [ExplicitColumns] internal class RelationTypeDto { - public const int NodeIdSeed = 3; + public const int NodeIdSeed = 4; [Column("id")] [PrimaryKeyColumn(IdentitySeed = NodeIdSeed)] diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs index 2ba2d5a956..e6783eaf6d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs @@ -224,7 +224,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .Where(dto => dto.NodeObjectType == NodeObjectTypeId) .Where("umbracoNode." + SqlSyntax.GetQuotedColumnName("text") + " in (@names)", new { names = roleNames }); var existingRoles = Database.Fetch(existingSql).Select(x => x.Text); - var missingRoles = roleNames.Except(existingRoles); + var missingRoles = roleNames.Except(existingRoles, StringComparer.CurrentCultureIgnoreCase); var missingGroups = missingRoles.Select(x => new MemberGroup {Name = x}).ToArray(); if (AmbientScope.Events.DispatchCancelable(SavingMemberGroup, this, new SaveEventArgs(missingGroups))) @@ -258,8 +258,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement //exist in the roleNames list, then determine which ones are not currently assigned. var mId = memberId; var found = currentlyAssigned.Where(x => x.MemberId == mId).ToArray(); - var assignedRoles = found.Where(x => roleNames.Contains(x.RoleName)).Select(x => x.RoleName); - var nonAssignedRoles = roleNames.Except(assignedRoles); + var assignedRoles = found.Where(x => roleNames.Contains(x.RoleName, StringComparer.CurrentCultureIgnoreCase)).Select(x => x.RoleName); + var nonAssignedRoles = roleNames.Except(assignedRoles, StringComparer.CurrentCultureIgnoreCase); foreach (var toAssign in nonAssignedRoles) { var groupId = rolesForNames.First(x => x.Text == toAssign).NodeId; diff --git a/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs index a9bfd3e4ca..b3456dde84 100644 --- a/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs @@ -26,7 +26,7 @@ namespace Umbraco.Core.PropertyEditors Delimiter = ','; ReplaceTags = true; TagGroup = "default"; - StorageType = TagsStorageType.Csv; + StorageType = TagsStorageType.Json; } /// diff --git a/src/Umbraco.Core/Strategies/RelateOnTrashComponent.cs b/src/Umbraco.Core/Strategies/RelateOnTrashComponent.cs index b6e34d7ac6..15f0a67a82 100644 --- a/src/Umbraco.Core/Strategies/RelateOnTrashComponent.cs +++ b/src/Umbraco.Core/Strategies/RelateOnTrashComponent.cs @@ -16,6 +16,8 @@ namespace Umbraco.Core.Strategies { ContentService.Moved += ContentService_Moved; ContentService.Trashed += ContentService_Trashed; + MediaService.Moved += MediaService_Moved; + MediaService.Trashed += MediaService_Trashed; } private static void ContentService_Moved(IContentService sender, MoveEventArgs e) @@ -33,10 +35,25 @@ namespace Umbraco.Core.Strategies } } + private static void MediaService_Moved(IMediaService sender, MoveEventArgs e) + { + foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Constants.System.RecycleBinMedia.ToInvariantString()))) + { + var relationService = Current.Services.RelationService; + const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias; + var relations = relationService.GetByChildId(item.Entity.Id); + foreach (var relation in relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias))) + { + relationService.Delete(relation); + } + } + } + private static void ContentService_Trashed(IContentService sender, MoveEventArgs e) { var relationService = Current.Services.RelationService; var entityService = Current.Services.EntityService; + var textService = Current.Services.TextService; const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias; var relationType = relationService.GetRelationTypeByAlias(relationTypeAlias); @@ -67,11 +84,51 @@ namespace Umbraco.Core.Strategies relationService.Save(relation); Current.Services.AuditService.Add(AuditType.Delete, - $"Trashed content with Id: '{item.Entity.Id}' related to original parent content with Id: '{originalParentId}'", + string.Format(textService.Localize( + "recycleBin/contentTrashed"), + item.Entity.Id, originalParentId), item.Entity.WriterId, item.Entity.Id); } } } + + private static void MediaService_Trashed(IMediaService sender, MoveEventArgs e) + { + var relationService = Current.Services.RelationService; + var entityService = Current.Services.EntityService; + var textService = Current.Services.TextService; + const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias; + var relationType = relationService.GetRelationTypeByAlias(relationTypeAlias); + // check that the relation-type exists, if not, then recreate it + if (relationType == null) + { + var documentObjectType = Constants.ObjectTypes.Document; + const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName; + relationType = new RelationType(documentObjectType, documentObjectType, relationTypeAlias, relationTypeName); + relationService.Save(relationType); + } + foreach (var item in e.MoveInfoCollection) + { + var originalPath = item.OriginalPath.ToDelimitedList(); + var originalParentId = originalPath.Count > 2 + ? int.Parse(originalPath[originalPath.Count - 2]) + : Constants.System.Root; + //before we can create this relation, we need to ensure that the original parent still exists which + //may not be the case if the encompassing transaction also deleted it when this item was moved to the bin + if (entityService.Exists(originalParentId)) + { + // Add a relation for the item being deleted, so that we can know the original parent for if we need to restore later + var relation = new Relation(originalParentId, item.Entity.Id, relationType); + relationService.Save(relation); + Current.Services.AuditService.Add(AuditType.Delete, + string.Format(textService.Localize( + "recycleBin/mediaTrashed"), + item.Entity.Id, originalParentId), + item.Entity.CreatorId, + item.Entity.Id); + } + } + } } } diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index 2651bc2222..24c1445f01 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -55,7 +55,7 @@ - + diff --git a/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs index 1925f3cb03..ce59f17ea1 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs @@ -135,7 +135,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(relationTypes, Is.Not.Null); Assert.That(relationTypes.Any(), Is.True); Assert.That(relationTypes.Any(x => x == null), Is.False); - Assert.That(relationTypes.Count(), Is.EqualTo(4)); + Assert.That(relationTypes.Count(), Is.EqualTo(5)); } } @@ -170,7 +170,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var exists = repository.Exists(3); - var doesntExist = repository.Exists(5); + var doesntExist = repository.Exists(6); // Assert Assert.That(exists, Is.True); @@ -192,7 +192,7 @@ namespace Umbraco.Tests.Persistence.Repositories int count = repository.Count(query); // Assert - Assert.That(count, Is.EqualTo(4)); + Assert.That(count, Is.EqualTo(5)); } } diff --git a/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs index 80c0868dfa..9ba5ccf6f2 100644 --- a/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs @@ -22,7 +22,7 @@ namespace Umbraco.Tests.PropertyEditors /// multiple values such as the drop down list, check box list, color picker, etc.... /// [TestFixture] - public class MultiValuePropertyEditorTests + public class MultiValuePropertyEditorTests { //TODO: Test the other formatting methods for the drop down classes diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index fa120065bf..3697ffa586 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -111,11 +111,12 @@ namespace Umbraco.Tests.PublishedContent - - - - - + + + + + + 1 @@ -185,7 +186,7 @@ namespace Umbraco.Tests.PublishedContent .Where(x => x.IsVisible()) .ToIndexedArray(); - Assert.AreEqual(3, items.Length); + Assert.AreEqual(4, items.Length); foreach (var d in items) { @@ -195,6 +196,10 @@ namespace Umbraco.Tests.PublishedContent Assert.IsTrue(d.IsFirst()); Assert.IsFalse(d.IsLast()); break; + case 117: + Assert.IsFalse(d.IsFirst()); + Assert.IsFalse(d.IsLast()); + break; case 1177: Assert.IsFalse(d.IsFirst()); Assert.IsFalse(d.IsLast()); @@ -264,7 +269,7 @@ namespace Umbraco.Tests.PublishedContent { var doc = GetNode(1173); - var items = doc.Children.Take(3).ToIndexedArray(); + var items = doc.Children.Take(4).ToIndexedArray(); foreach (var item in items) { @@ -324,7 +329,7 @@ namespace Umbraco.Tests.PublishedContent { var doc = GetNode(1046); - var expected = new[] { 1046, 1173, 1174, 1177, 1178, 1179, 1176, 1175, 4444, 1172 }; + var expected = new[] { 1046, 1173, 1174, 117, 1177, 1178, 1179, 1176, 1175, 4444, 1172 }; var exindex = 0; // must respect the XPath descendants-or-self axis! @@ -569,6 +574,7 @@ namespace Umbraco.Tests.PublishedContent // -- Home: 1173 (parent 1046) // -- Custom Doc: 1178 (parent 1173) // --- Custom Doc2: 1179 (parent: 1178) + // -- Custom Doc4: 117 (parent 1173) // - Custom Doc3: 1172 (no parent) var home = GetNode(1173); @@ -576,25 +582,30 @@ namespace Umbraco.Tests.PublishedContent var customDoc = GetNode(1178); var customDoc2 = GetNode(1179); var customDoc3 = GetNode(1172); + var customDoc4 = GetNode(117); + Assert.IsTrue(root.IsAncestor(customDoc4)); Assert.IsFalse(root.IsAncestor(customDoc3)); Assert.IsTrue(root.IsAncestor(customDoc2)); Assert.IsTrue(root.IsAncestor(customDoc)); Assert.IsTrue(root.IsAncestor(home)); Assert.IsFalse(root.IsAncestor(root)); + Assert.IsTrue(home.IsAncestor(customDoc4)); Assert.IsFalse(home.IsAncestor(customDoc3)); Assert.IsTrue(home.IsAncestor(customDoc2)); Assert.IsTrue(home.IsAncestor(customDoc)); Assert.IsFalse(home.IsAncestor(home)); Assert.IsFalse(home.IsAncestor(root)); + Assert.IsFalse(customDoc.IsAncestor(customDoc4)); Assert.IsFalse(customDoc.IsAncestor(customDoc3)); Assert.IsTrue(customDoc.IsAncestor(customDoc2)); Assert.IsFalse(customDoc.IsAncestor(customDoc)); Assert.IsFalse(customDoc.IsAncestor(home)); Assert.IsFalse(customDoc.IsAncestor(root)); + Assert.IsFalse(customDoc2.IsAncestor(customDoc4)); Assert.IsFalse(customDoc2.IsAncestor(customDoc3)); Assert.IsFalse(customDoc2.IsAncestor(customDoc2)); Assert.IsFalse(customDoc2.IsAncestor(customDoc)); @@ -612,6 +623,7 @@ namespace Umbraco.Tests.PublishedContent // -- Home: 1173 (parent 1046) // -- Custom Doc: 1178 (parent 1173) // --- Custom Doc2: 1179 (parent: 1178) + // -- Custom Doc4: 117 (parent 1173) // - Custom Doc3: 1172 (no parent) var home = GetNode(1173); @@ -619,31 +631,37 @@ namespace Umbraco.Tests.PublishedContent var customDoc = GetNode(1178); var customDoc2 = GetNode(1179); var customDoc3 = GetNode(1172); + var customDoc4 = GetNode(117); + Assert.IsTrue(root.IsAncestorOrSelf(customDoc4)); Assert.IsFalse(root.IsAncestorOrSelf(customDoc3)); Assert.IsTrue(root.IsAncestorOrSelf(customDoc2)); Assert.IsTrue(root.IsAncestorOrSelf(customDoc)); Assert.IsTrue(root.IsAncestorOrSelf(home)); Assert.IsTrue(root.IsAncestorOrSelf(root)); + Assert.IsTrue(home.IsAncestorOrSelf(customDoc4)); Assert.IsFalse(home.IsAncestorOrSelf(customDoc3)); Assert.IsTrue(home.IsAncestorOrSelf(customDoc2)); Assert.IsTrue(home.IsAncestorOrSelf(customDoc)); Assert.IsTrue(home.IsAncestorOrSelf(home)); Assert.IsFalse(home.IsAncestorOrSelf(root)); + Assert.IsFalse(customDoc.IsAncestorOrSelf(customDoc4)); Assert.IsFalse(customDoc.IsAncestorOrSelf(customDoc3)); Assert.IsTrue(customDoc.IsAncestorOrSelf(customDoc2)); Assert.IsTrue(customDoc.IsAncestorOrSelf(customDoc)); Assert.IsFalse(customDoc.IsAncestorOrSelf(home)); Assert.IsFalse(customDoc.IsAncestorOrSelf(root)); + Assert.IsFalse(customDoc2.IsAncestorOrSelf(customDoc4)); Assert.IsFalse(customDoc2.IsAncestorOrSelf(customDoc3)); Assert.IsTrue(customDoc2.IsAncestorOrSelf(customDoc2)); Assert.IsFalse(customDoc2.IsAncestorOrSelf(customDoc)); Assert.IsFalse(customDoc2.IsAncestorOrSelf(home)); Assert.IsFalse(customDoc2.IsAncestorOrSelf(root)); + Assert.IsTrue(customDoc4.IsAncestorOrSelf(customDoc4)); Assert.IsTrue(customDoc3.IsAncestorOrSelf(customDoc3)); } @@ -657,7 +675,7 @@ namespace Umbraco.Tests.PublishedContent Assert.IsNotNull(result); - Assert.AreEqual(9, result.Count()); + Assert.AreEqual(10, result.Count()); Assert.IsTrue(result.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1046, 1173, 1174, 1176, 1175 })); } @@ -670,7 +688,7 @@ namespace Umbraco.Tests.PublishedContent Assert.IsNotNull(result); - Assert.AreEqual(8, result.Count()); + Assert.AreEqual(9, result.Count()); Assert.IsTrue(result.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1173, 1174, 1176, 1175, 4444 })); } @@ -682,6 +700,7 @@ namespace Umbraco.Tests.PublishedContent // -- Home: 1173 (parent 1046) // -- Custom Doc: 1178 (parent 1173) // --- Custom Doc2: 1179 (parent: 1178) + // -- Custom Doc4: 117 (parent 1173) // - Custom Doc3: 1172 (no parent) var home = GetNode(1173); @@ -689,30 +708,35 @@ namespace Umbraco.Tests.PublishedContent var customDoc = GetNode(1178); var customDoc2 = GetNode(1179); var customDoc3 = GetNode(1172); + var customDoc4 = GetNode(117); Assert.IsFalse(root.IsDescendant(root)); Assert.IsFalse(root.IsDescendant(home)); Assert.IsFalse(root.IsDescendant(customDoc)); Assert.IsFalse(root.IsDescendant(customDoc2)); Assert.IsFalse(root.IsDescendant(customDoc3)); + Assert.IsFalse(root.IsDescendant(customDoc4)); Assert.IsTrue(home.IsDescendant(root)); Assert.IsFalse(home.IsDescendant(home)); Assert.IsFalse(home.IsDescendant(customDoc)); Assert.IsFalse(home.IsDescendant(customDoc2)); Assert.IsFalse(home.IsDescendant(customDoc3)); + Assert.IsFalse(home.IsDescendant(customDoc4)); Assert.IsTrue(customDoc.IsDescendant(root)); Assert.IsTrue(customDoc.IsDescendant(home)); Assert.IsFalse(customDoc.IsDescendant(customDoc)); Assert.IsFalse(customDoc.IsDescendant(customDoc2)); Assert.IsFalse(customDoc.IsDescendant(customDoc3)); + Assert.IsFalse(customDoc.IsDescendant(customDoc4)); Assert.IsTrue(customDoc2.IsDescendant(root)); Assert.IsTrue(customDoc2.IsDescendant(home)); Assert.IsTrue(customDoc2.IsDescendant(customDoc)); Assert.IsFalse(customDoc2.IsDescendant(customDoc2)); Assert.IsFalse(customDoc2.IsDescendant(customDoc3)); + Assert.IsFalse(customDoc2.IsDescendant(customDoc4)); Assert.IsFalse(customDoc3.IsDescendant(customDoc3)); } @@ -725,6 +749,7 @@ namespace Umbraco.Tests.PublishedContent // -- Home: 1173 (parent 1046) // -- Custom Doc: 1178 (parent 1173) // --- Custom Doc2: 1179 (parent: 1178) + // -- Custom Doc4: 117 (parent 1173) // - Custom Doc3: 1172 (no parent) var home = GetNode(1173); @@ -732,30 +757,35 @@ namespace Umbraco.Tests.PublishedContent var customDoc = GetNode(1178); var customDoc2 = GetNode(1179); var customDoc3 = GetNode(1172); + var customDoc4 = GetNode(117); Assert.IsTrue(root.IsDescendantOrSelf(root)); Assert.IsFalse(root.IsDescendantOrSelf(home)); Assert.IsFalse(root.IsDescendantOrSelf(customDoc)); Assert.IsFalse(root.IsDescendantOrSelf(customDoc2)); Assert.IsFalse(root.IsDescendantOrSelf(customDoc3)); + Assert.IsFalse(root.IsDescendantOrSelf(customDoc4)); Assert.IsTrue(home.IsDescendantOrSelf(root)); Assert.IsTrue(home.IsDescendantOrSelf(home)); Assert.IsFalse(home.IsDescendantOrSelf(customDoc)); Assert.IsFalse(home.IsDescendantOrSelf(customDoc2)); Assert.IsFalse(home.IsDescendantOrSelf(customDoc3)); + Assert.IsFalse(home.IsDescendantOrSelf(customDoc4)); Assert.IsTrue(customDoc.IsDescendantOrSelf(root)); Assert.IsTrue(customDoc.IsDescendantOrSelf(home)); Assert.IsTrue(customDoc.IsDescendantOrSelf(customDoc)); Assert.IsFalse(customDoc.IsDescendantOrSelf(customDoc2)); Assert.IsFalse(customDoc.IsDescendantOrSelf(customDoc3)); + Assert.IsFalse(customDoc.IsDescendantOrSelf(customDoc4)); Assert.IsTrue(customDoc2.IsDescendantOrSelf(root)); Assert.IsTrue(customDoc2.IsDescendantOrSelf(home)); Assert.IsTrue(customDoc2.IsDescendantOrSelf(customDoc)); Assert.IsTrue(customDoc2.IsDescendantOrSelf(customDoc2)); Assert.IsFalse(customDoc2.IsDescendantOrSelf(customDoc3)); + Assert.IsFalse(customDoc2.IsDescendantOrSelf(customDoc4)); Assert.IsTrue(customDoc3.IsDescendantOrSelf(customDoc3)); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js index a7bdd4b741..e3c4cbf40c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js @@ -64,7 +64,7 @@ (function () { 'use strict'; - function ToggleDirective(localizationService) { + function ToggleDirective(localizationService, eventsService) { function link(scope, el, attr, ctrl) { @@ -73,6 +73,7 @@ function onInit() { setLabelText(); + eventsService.emit("toggleValue", { value: scope.checked }); } function setLabelText() { @@ -98,7 +99,8 @@ } scope.click = function() { - if(scope.onClick) { + if (scope.onClick) { + eventsService.emit("toggleValue", { value: !scope.checked }); scope.onClick(); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index f31d1533aa..c9d78a3551 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -68,6 +68,9 @@ // make sure dates are formatted to the user's locale formatDatesToLocal(); + // Declare a fallback URL for the directive + scope.previewOpenUrl = '#/settings/documenttypes/edit/' + scope.documentType.id; + } scope.auditTrailPageChange = function (pageNumber) { @@ -88,6 +91,11 @@ editorService.documentTypeEditor(editor); }; + scope.openTemplate = function () { + var url = "/settings/templates/edit/" + scope.node.templateId; + $location.url(url); + } + scope.updateTemplate = function (templateAlias) { // update template value diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js new file mode 100644 index 0000000000..6ed65a9431 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js @@ -0,0 +1,61 @@ + +/** +@ngdoc directive +@name umbraco.directives.directive:umbColorSwatches +@restrict E +@scope +@description +Use this directive to generate color swatches to pick from. +

Markup example

+
+    
+    
+
+@param {array} colors (attribute): The array of colors. +@param {string} colors (attribute): The array of colors. +@param {string} selectedColor (attribute): The selected color. +@param {string} size (attribute): The size (s, m). +@param {function} onSelect (expression): Callback function when the item is selected. +**/ + +(function () { + 'use strict'; + + function ColorSwatchesDirective() { + + function link(scope, el, attr, ctrl) { + + scope.setColor = function (color) { + //scope.selectedColor({color: color }); + scope.selectedColor = color; + + if (scope.onSelect) { + scope.onSelect(color); + } + }; + } + + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/umb-color-swatches.html', + scope: { + colors: '=?', + size: '@', + selectedColor: '=', + onSelect: '&' + }, + link: link + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbColorSwatches', ColorSwatchesDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js index de1c0ca936..dfd1e1184d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js @@ -1,7 +1,7 @@ -(function() { +(function () { 'use strict'; - function GridSelector(overlayService) { + function GridSelector($location, overlayService) { function link(scope, el, attr, ctrl) { @@ -11,18 +11,18 @@ scope.itemLabel = ""; // set default item name - if(!scope.itemName){ + if (!scope.itemName) { scope.itemLabel = "item"; } else { scope.itemLabel = scope.itemName; } - scope.removeItem = function(selectedItem) { + scope.removeItem = function (selectedItem) { var selectedItemIndex = scope.selectedItems.indexOf(selectedItem); scope.selectedItems.splice(selectedItemIndex, 1); }; - scope.removeDefaultItem = function() { + scope.removeDefaultItem = function () { // it will be the last item so we can clear the array scope.selectedItems = []; @@ -32,7 +32,7 @@ }; - scope.openItemPicker = function($event){ + scope.openItemPicker = function ($event) { var dialogModel = { view: "itempicker", title: "Choose " + scope.itemLabel, @@ -40,10 +40,10 @@ selectedItems: scope.selectedItems, position: "target", event: $event, - submit: function(model) { + submit: function (model) { scope.selectedItems.push(model.selectedItem); // if no default item - set item as default - if(scope.defaultItem === null) { + if (scope.defaultItem === null) { scope.setAsDefaultItem(model.selectedItem); } overlayService.close(); @@ -55,7 +55,12 @@ overlayService.open(dialogModel); }; - scope.setAsDefaultItem = function(selectedItem) { + scope.openTemplate = function (selectedItem) { + var url = "/settings/templates/edit/" + selectedItem.id; + $location.url(url); + } + + scope.setAsDefaultItem = function (selectedItem) { // clear default item scope.defaultItem = {}; @@ -66,69 +71,69 @@ function updatePlaceholders() { - // update default item - if(scope.defaultItem !== null && scope.defaultItem.placeholder) { + // update default item + if (scope.defaultItem !== null && scope.defaultItem.placeholder) { - scope.defaultItem.name = scope.name; + scope.defaultItem.name = scope.name; - if(scope.alias !== null && scope.alias !== undefined) { - scope.defaultItem.alias = scope.alias; - } - - } - - // update selected items - angular.forEach(scope.selectedItems, function(selectedItem) { - if(selectedItem.placeholder) { - - selectedItem.name = scope.name; - - if(scope.alias !== null && scope.alias !== undefined) { - selectedItem.alias = scope.alias; - } + if (scope.alias !== null && scope.alias !== undefined) { + scope.defaultItem.alias = scope.alias; + } } - }); - // update availableItems - angular.forEach(scope.availableItems, function(availableItem) { - if(availableItem.placeholder) { + // update selected items + angular.forEach(scope.selectedItems, function (selectedItem) { + if (selectedItem.placeholder) { - availableItem.name = scope.name; + selectedItem.name = scope.name; - if(scope.alias !== null && scope.alias !== undefined) { - availableItem.alias = scope.alias; - } + if (scope.alias !== null && scope.alias !== undefined) { + selectedItem.alias = scope.alias; + } - } - }); + } + }); + + // update availableItems + angular.forEach(scope.availableItems, function (availableItem) { + if (availableItem.placeholder) { + + availableItem.name = scope.name; + + if (scope.alias !== null && scope.alias !== undefined) { + availableItem.alias = scope.alias; + } + + } + }); } function activate() { - // add watchers for updating placeholde name and alias - if(scope.updatePlaceholder) { - eventBindings.push(scope.$watch('name', function(newValue, oldValue){ - updatePlaceholders(); - })); + // add watchers for updating placeholde name and alias + if (scope.updatePlaceholder) { + eventBindings.push(scope.$watch('name', function (newValue, oldValue) { + updatePlaceholders(); + })); - eventBindings.push(scope.$watch('alias', function(newValue, oldValue){ - updatePlaceholders(); - })); - } + eventBindings.push(scope.$watch('alias', function (newValue, oldValue) { + updatePlaceholders(); + })); + } } activate(); // clean up - scope.$on('$destroy', function(){ + scope.$on('$destroy', function () { - // clear watchers - for(var e in eventBindings) { - eventBindings[e](); - } + // clear watchers + for (var e in eventBindings) { + eventBindings[e](); + } }); @@ -139,13 +144,13 @@ replace: true, templateUrl: 'views/components/umb-grid-selector.html', scope: { - name: "=", - alias: "=", - selectedItems: '=', - availableItems: "=", - defaultItem: "=", - itemName: "@", - updatePlaceholder: "=" + name: "=", + alias: "=", + selectedItems: '=', + availableItems: "=", + defaultItem: "=", + itemName: "@", + updatePlaceholder: "=" }, link: link }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js index 21714d172c..9f1f7a0d2e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnodepreview.directive.js @@ -88,6 +88,9 @@ @param {function} onRemove (expression): Callback function when the remove button is clicked. @param {function} onOpen (expression): Callback function when the open button is clicked. @param {function} onEdit (expression): Callback function when the edit button is clicked (Added in version 7.7.0). +@param {string} openUrl (binding): Fallback URL for onOpen (Added in version 7.12.0). +@param {string} editUrl (binding): Fallback URL for onEdit (Added in version 7.12.0). +@param {string} removeUrl (binding): Fallback URL for onRemove (Added in version 7.12.0). **/ (function () { @@ -122,7 +125,10 @@ allowEdit: "=?", onOpen: "&?", onRemove: "&?", - onEdit: "&?" + onEdit: "&?", + openUrl: '=?', + editUrl: '=?', + removeUrl: '=?' }, link: link }; @@ -133,4 +139,4 @@ angular.module('umbraco.directives').directive('umbNodePreview', NodePreviewDirective); -})(); \ No newline at end of file +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/nestedcontent.filter.js b/src/Umbraco.Web.UI.Client/src/common/filters/nestedcontent.filter.js index 76e4e4a822..8c23094bbf 100644 --- a/src/Umbraco.Web.UI.Client/src/common/filters/nestedcontent.filter.js +++ b/src/Umbraco.Web.UI.Client/src/common/filters/nestedcontent.filter.js @@ -9,6 +9,13 @@ var ncNodeNameCache = { angular.module("umbraco.filters").filter("ncNodeName", function (editorState, entityResource) { + function formatLabel(firstNodeName, totalNodes) { + return totalNodes <= 1 + ? firstNodeName + // If there is more than one item selected, append the additional number of items selected to hint that + : firstNodeName + " (+" + (totalNodes - 1) + ")"; + } + return function (input) { // Check we have a value at all @@ -25,23 +32,38 @@ angular.module("umbraco.filters").filter("ncNodeName", function (editorState, en ncNodeNameCache.keys = {}; } + // MNTP values are comma separated IDs. We'll only fetch the first one for the NC header. + var ids = input.split(','); + var lookupId = ids[0]; + // See if there is a value in the cache and use that - if (ncNodeNameCache.keys[input]) { - return ncNodeNameCache.keys[input]; + if (ncNodeNameCache.keys[lookupId]) { + return formatLabel(ncNodeNameCache.keys[lookupId], ids.length); } // No value, so go fetch one // We'll put a temp value in the cache though so we don't // make a load of requests while we wait for a response - ncNodeNameCache.keys[input] = "Loading..."; + ncNodeNameCache.keys[lookupId] = "Loading..."; - entityResource.getById(input, "Document") - .then(function (ent) { - ncNodeNameCache.keys[input] = ent.name; - }); + var type = lookupId.indexOf("umb://media/") === 0 + ? "Media" + : lookupId.indexOf("umb://member/") === 0 + ? "Member" + : "Document"; + entityResource.getById(lookupId, type) + .then( + function (ent) { + ncNodeNameCache.keys[lookupId] = ent.name; + } + ); // Return the current value for now - return ncNodeNameCache.keys[input]; + return formatLabel(ncNodeNameCache.keys[lookupId], ids.length); }; -}); \ No newline at end of file +}).filter("ncRichText", function () { + return function(input) { + return $("
").html(input).text(); + }; +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/datatypehelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/datatypehelper.service.js index 3cde632d4b..264d15215f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/datatypehelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/datatypehelper.service.js @@ -18,7 +18,8 @@ function dataTypeHelper() { description: preVals[i].description, label: preVals[i].label, view: preVals[i].view, - value: preVals[i].value + value: preVals[i].value, + prevalues: preVals[i].prevalues }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js index e4ba9b337c..42b739fe61 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js @@ -272,12 +272,19 @@ for (var i = 0; selection.length > i; i++) { var selectedItem = selection[i]; // if item.id is 2147483647 (int.MaxValue) use item.key - if ((item.id !== 2147483647 && item.id === selectedItem.id) || item.key === selectedItem.key) { + if ((item.id !== 2147483647 && item.id === selectedItem.id) || (item.key && item.key === selectedItem.key)) { isSelected = true; } } if (!isSelected) { - selection.push({ id: item.id, key: item.key }); + var obj = { + id: item.id + }; + if (item.key) { + obj.key = item.key; + } + + selection.push(obj); item.selected = true; } } @@ -298,7 +305,7 @@ for (var i = 0; selection.length > i; i++) { var selectedItem = selection[i]; // if item.id is 2147483647 (int.MaxValue) use item.key - if ((item.id !== 2147483647 && item.id === selectedItem.id) || item.key === selectedItem.key) { + if ((item.id !== 2147483647 && item.id === selectedItem.id) || (item.key && item.key === selectedItem.key)) { selection.splice(i, 1); item.selected = false; } @@ -368,9 +375,15 @@ for (var i = 0; i < items.length; i++) { var item = items[i]; + var obj = { + id: item.id + }; + if (item.key) { + obj.key = item.key + } if (checkbox.checked) { - selection.push({ id: item.id, key: item.key }); + selection.push(obj); } else { clearSelection = true; } @@ -410,7 +423,7 @@ var selectedItem = selection[selectedIndex]; // if item.id is 2147483647 (int.MaxValue) use item.key - if ((item.id !== 2147483647 && item.id === selectedItem.id) || item.key === selectedItem.key) { + if ((item.id !== 2147483647 && item.id === selectedItem.id) || (item.key && item.key === selectedItem.key)) { numberOfSelectedItem++; } } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index aa53626a78..127124ea13 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -156,13 +156,13 @@ function tinyMceService($log, imageHelper, $http, $timeout, macroResource, macro var s = "width: " + newSize.width + "px; height:" + newSize.height + "px;"; editor.dom.setAttrib(imgElm, 'style', s); - editor.dom.setAttrib(imgElm, 'id', null); if (img.url) { var src = img.url + "?width=" + newSize.width + "&height=" + newSize.height; editor.dom.setAttrib(imgElm, 'data-mce-src', src); } - } + } + editor.dom.setAttrib(imgElm, 'id', null); }, 500); } }, diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index b18f2c942f..6fe1421742 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -122,6 +122,7 @@ @import "components/umb-grid.less"; @import "components/umb-empty-state.less"; @import "components/umb-property-editor.less"; +@import "components/umb-color-swatches.less"; @import "components/umb-iconpicker.less"; @import "components/umb-insert-code-box.less"; @import "components/umb-packages.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-drawer.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-drawer.less index 45208bd4bf..cc024ca5bb 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-drawer.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-drawer.less @@ -56,12 +56,13 @@ /* Our badge - should be moved */ .umb-help-badge { - padding: 10px 20px 10px 35px; + padding: 10px 20px 10px 55px; background: @white; position: relative; overflow: hidden; border-radius: 3px; display: block; + margin-bottom:5px; box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); } @@ -76,10 +77,10 @@ } .umb-help-badge__icon { - font-size: 40px; + font-size: 36px; transform: translate(0,-50%); position: absolute; - left: -15px; + left: 10px; top: 50%; color: @red-l3; } @@ -130,6 +131,14 @@ align-items: center; } +/* the outer container for each help type - tours, video etc */ +.umb-help-section + .umb-help-section { + margin-top:20px; +} + +.umb-help-section__title { + margin:0 0 10px; +} /* Help list */ @@ -137,13 +146,13 @@ list-style: none; margin-left: 0; margin-bottom: 0; - background: @white; + background: @white; border-radius: 3px; box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); -} - -.umb-help-list:last-child { - border-bottom: none; + + [data-element*="help-tours"] & { + margin-bottom:5px; + } } .umb-help-list-item { @@ -156,20 +165,28 @@ border-bottom: none; } -.umb-help-list-item > a, +.umb-help-list-item__group-title i { + margin-right:2px; + text-decoration: none; +} + .umb-help-list-item__content { display: flex; align-items: center; - padding: 10px 20px; + padding: 10px 20px 10px 10px; + text-decoration: none; } +.umb-help-list-item:hover, +.umb-help-list-item:focus, +.umb-help-list-item:active, .umb-help-list-item > a:hover, .umb-help-list-item > a:focus, .umb-help-list-item > a:active { text-decoration: none; .umb-help-list-item__title { - text-decoration: underline !important; + text-decoration: underline; } } @@ -198,4 +215,8 @@ .umb-help-list-item:hover .umb-help-list-item__group-title { text-decoration: underline; +} + +[data-element*="tour-"].umb-help-list-item:hover .umb-help-list-item__title { + text-decoration:none; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less new file mode 100644 index 0000000000..e9101973b4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less @@ -0,0 +1,61 @@ +.umb-color-swatches { + + .umb-color-box { + border: none; + color: white; + cursor: pointer; + padding: 5px; + text-align: center; + text-decoration: none; + display: inline-block; + margin: 5px; + border-radius: 3px; + width: 30px; + height: 30px; + transition: box-shadow .3s; + + &:hover, &:focus { + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); + } + + .check_circle { + display: none; + visibility: hidden; + width: 20px; + height: 20px; + margin: 0 auto; + + .icon { + font-size: 1em; + display: flex; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; + border-radius: 50%; + background-color: rgba(0,0,0,.15); + float: left; + } + } + + &.active { + .check_circle { + display: flex; + visibility: visible; + } + } + + &.umb-color-box--s { + } + + &.umb-color-box--m { + width: 40px; + height: 40px; + + .check_circle { + width: 25px; + height: 25px; + } + } + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less index d09d170814..7dfcc493a8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less @@ -537,15 +537,11 @@ .umb-grid textarea.textstring { display: block; overflow: hidden; - border: none; background: @white; outline: none; resize: none; color: @gray-3; -} - -.umb-grid .umb-cell-rte textarea.mceNoEditor { - display: none !important; + min-width: 100%; } .umb-grid .umb-cell-media .caption { @@ -567,11 +563,6 @@ width: 100%; } -.umb-grid .umb-cell-rte { - border-color: transparent; -} - - // ICONS // ------------------------- diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less index 8ed034b403..83d596b332 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less @@ -40,3 +40,39 @@ .umb-iconpicker-item i { font-size: 30px; } + + +// Color swatch + .button { + border: none; + color: white; + padding: 5px; + text-align: center; + text-decoration: none; + display: inline-block; + margin: 5px; + border-radius: 3px; + } + + + .check_circle{ + width: 20px; + height: 20px; + + } + + // Hide Circle when not active + i.small{ + display: none; + } + + // Circle behind the checkmark + i.small.active{ + font-size: 14px; + display: inline-block; + width: 20px; + height: 20px; + border-radius: 50%; + background-color: rgba(0,0,0,.15); + float: right; + } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less index 4f24fcf504..e2a8c8fc81 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less @@ -67,29 +67,63 @@ -o-user-select: none; } -.nested-content__heading -{ - float: left; +.nested-content__heading { line-height: 20px; -} + position: relative; -.nested-content__heading i -{ - vertical-align: text-top; - color: #999; /* same icon color as the icons in the item type picker */ - margin-right: 10px; + &.-with-icon + { + padding-left: 20px; + } + + i + { + color: #999; /* same icon color as the icons in the item type picker */ + position: absolute; + left: 0; + } + + .nested-content__item-name + { + max-height: 20px; + text-align: left; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: block; + } } .nested-content__icons { - margin: -6px 0; opacity: 0; transition: opacity .15s ease-in-out; -moz-transition: opacity .15s ease-in-out; -webkit-transition: opacity .15s ease-in-out; + + position: absolute; + right: 0px; + top: 2px; + background-color: white; + padding: 5px; + + &:before + { + content: ' '; + position: absolute; + display: block; + width: 30px; + left: -30px; + top: 0; + bottom: 0; + background: -webkit-linear-gradient(90deg, rgba(255,255,255,0), white); + background: -moz-linear-gradient(90deg, rgba(255,255,255,0), white); + background: linear-gradient(90deg, rgba(255,255,255,0), white); + } } + .nested-content__header-bar:hover .nested-content__icons, .nested-content__item--active > .nested-content__header-bar .nested-content__icons { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less index 9e038bd571..3d1015f972 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less @@ -1,11 +1,14 @@ .umb-node-preview { padding: 5px 0; display: flex; - max-width: 66.6%; box-sizing: border-box; border-bottom: 1px solid @gray-9; } +.umb-editor-wrapper .umb-node-preview { + max-width: 66.6%; +} + .umb-node-preview:last-of-type { border-bottom: none; } @@ -89,7 +92,6 @@ color: @turquoise-d1; font-weight: bold; padding: 5px 15px; - max-width: 66.6%; box-sizing: border-box; } @@ -97,6 +99,10 @@ color: @turquoise-d1; } +.umb-editor-wrapper .umb-node-preview-add { + max-width: 66.6%; +} + .umb-overlay, .umb-modal { .umb-node-preview { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less index 0ea7b5f434..b302ed2654 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less @@ -94,7 +94,7 @@ input.umb-table__input { cursor: pointer; font-size: 14px; position: relative; - min-height: 32px; + min-height: 52px; &:hover { background-color: @gray-10; } @@ -272,4 +272,4 @@ input.umb-table__input { font-size: 20px; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/hacks.less b/src/Umbraco.Web.UI.Client/src/less/hacks.less index be48fd9ab6..22e2eb4566 100644 --- a/src/Umbraco.Web.UI.Client/src/less/hacks.less +++ b/src/Umbraco.Web.UI.Client/src/less/hacks.less @@ -20,12 +20,17 @@ } .thumbnail { - border-radius: 0px; -} + border-radius: 0; + min-width: 150px; -.thumbnail img { - max-width: 100% !important; - width: 100%; + > a { + display: block; + } + + img { + max-width: 100% !important; + width: 100%; + } } #mapCanvas img { diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 67b684ab7e..8e6ed84ac9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -3,20 +3,20 @@ // -------------------------------------------------- .umb-property-editor { min-width:66.6%; - + &-pull { float:left; width:66.6%; } - + &-push { float:right; - } + } } .umb-property-editor-tiny { width: 60px; - + &.umb-editor-push { width:30%; min-width:0; @@ -53,7 +53,7 @@ float:right; width:35%; } - + &--pull, &--push { .umb-editor { min-width:0; @@ -138,6 +138,12 @@ ul.color-picker li { margin: 3px; border: 2px solid transparent; width: 60px; + + .thumbnail{ + min-width: auto; + width: 58px; + padding: 0; + } } ul.color-picker li.active { border: 2px dashed @gray-8; @@ -148,54 +154,67 @@ ul.color-picker li a { cursor:pointer; } -.control-group.color-picker-preval .thumbnail { - width:30px; - border:none; -} + /* pre-value editor */ -/*.control-group.color-picker-preval:before { - content: ""; - display: inline-block; - vertical-align: middle; - height: 100%; -}*/ +.control-group.color-picker-preval { + .thumbnail { + width: 36px; + border: none; + cursor: move; + border-radius: 3px; + } -/*.control-group.color-picker-preval div.thumbnail { - display: inline-block; - vertical-align: middle; -}*/ -.control-group.color-picker-preval div.color-picker-prediv { - display: inline-block; - width: 60%; -} + .handle { + float: left; + display: inline-flex; + margin: 5px 3px 5px 0; + } -.control-group.color-picker-preval pre { - display: inline; - margin-right: 20px; - margin-left: 10px; - width: 50%; - white-space: nowrap; - overflow: hidden; - margin-bottom: 0; - vertical-align: middle; -} + div.color-picker-prediv { + display: inline-flex; + align-items: center; -.control-group.color-picker-preval btn { - //vertical-align: middle; -} + pre { + display: inline; + font-family: monospace; + margin-right: 10px; + margin-left: 10px; + width: 50%; + white-space: nowrap; + overflow: hidden; + margin-bottom: 0; + vertical-align: middle; + padding-top: 7px; + padding-bottom: 7px; + background: #f7f7f7; + } -.control-group.color-picker-preval input[type="text"] { - min-width: 40%; - width: 40%; - display: inline-block; - margin-right: 20px; - margin-top: 1px; -} + span { + margin-left: 5px; + } + } -.control-group.color-picker-preval label { - border: solid @white 1px; - padding: 6px; + input[type="text"] { + min-width: 40%; + width: 320px; + display: inline-block; + margin-top: 1px; + } + + .sp-replacer { + margin-right: 18px; + } + + label { + border: 1px solid #fff; + padding: 7px 10px; + font-family: monospace; + border: 1px solid #dfdfe1; + background: #f7f7f7; + margin: 0 15px 0 0; + border-radius: 3px; + } } @@ -369,7 +388,7 @@ ul.color-picker li a { > li:not(.unsortable) { cursor: move; } -} +} .umb-sortable-thumbnails li:hover .umb-sortable-thumbnails__actions { opacity: 1; @@ -899,10 +918,39 @@ ul.color-picker li a { // // Tags // -------------------------------------------------- -.umb-tags{border: @gray-10 solid 1px; padding: 10px; font-size: 13px; text-shadow: none;} -.umb-tags .tag{cursor: pointer; margin: 7px; padding: 7px; background: @turquoise} -.umb-tags .tag i{padding: 2px;} -.umb-tags input{border: none; background: @white} +.umb-tags{ + border: @gray-10 solid 1px; + padding: 10px; + font-size: 13px; + text-shadow: none; + + .tag{ + cursor: default; + margin: 7px; + padding: 10px; + background: @turquoise; + position: relative; + + .icon-trash{ + cursor: pointer; + padding: 2px; + font-size: 12px; + position: relative; + right: -6px; + } + + .umb_confirm-action__overlay.-left{ + top: 6px; + left: auto; + right: 15px; + } + } + + input{ + border: none; + background: @white; + } +} // // Date/time picker diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index 5ee1d6dba1..b6e7c87309 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -10,26 +10,30 @@ // Grays // ------------------------- @black: #000; -@blackLight: #1d1d1d; +@blackLight: #1D1D1D; @grayDarker: #222; @grayDark: #343434; @gray: #555; -@grayMed: #7f7f7f; -@grayLight: #d9d9d9; -@grayLighter: #f8f8f8; -@white: #fff; - +@grayMed: #7F7F7F; +@blueGrey: #607D8B; +@grayLight: #D9D9D9; +@grayLighter: #F8F8F8; +@white: #FFF; +@grayIcon: #9E9E9E; // Accent colors // ------------------------- -@blue: #2e8aea; -@blueDark: #0064cd; -@blueLight: #add8e6; -@green: #46a546; -@red: #9d261d; -@yellow: #ffc40d; + + + +@blue: #2E8AEA; +@blueDark: #0064CD; +@blueLight: #ADD8E6; +@green: #46A546; +@red: #9D261D; +@yellow: #FFC40D; @orange: #DF7F48; -@pink: #c3325f; +@pink: #C3325F; // Colors @@ -86,6 +90,28 @@ @gray-10: #F3F3F5; +// Additional Icon Colours +@brownIcon: #795548; +@blueIcon: #2196F3; +@lightBlueIcon: #03A9F4; +@cyanIcon: #00BCD4; +@greenIcon: #4CAF50; +@lightGreenIcon: #8BC34A; +@limeIcon: #CDDC39; +@yellowIcon: #FFEB3B; +@amberIcon: #FFC107; +@orangeIcon: #FF9800; +@deepOrangeIcon: #FF5722; +@redIcon: #F44336; +@pinkIcon: #E91E63; +@purpleIcon: #9C27B0; +@deepPurpleIcon: #673AB7; +@indigoIcon: #3F51B5; + + + + + .red{color: @red;} .blue{color: @blue;} .black{color: @black;} @@ -96,30 +122,71 @@ //icon colors for tree icons .color-red, .color-red i{color: @red-d1 !important;} .color-blue, .color-blue i{color: @turquoise-d1 !important;} -.color-orange, .color-orange i{color: #d9631e !important;} +.color-orange, .color-orange i{color: @orange !important;} .color-green, .color-green i{color: @green-d1 !important;} -.color-yellow, .color-yellow i{color: @yellow-d1 !important;} +.color-yellow, .color-yellow i{color: @yellowIcon !important;} /* Colors based on http://zavoloklom.github.io/material-design-color-palette/colors.html */ -.color-black, .color-black i { color: #000 !important; } -.color-blue-grey, .color-blue-grey i { color: #607d8b !important; } -.color-grey, .color-grey i { color: #9e9e9e !important; } -.color-brown, .color-brown i { color: #795548 !important; } -.color-blue, .color-blue i { color: #2196f3 !important; } -.color-light-blue, .color-light-blue i {color: #03a9f4 !important; } -.color-cyan, .color-cyan i { color: #00bcd4 !important; } -.color-green, .color-green i { color: #4caf50 !important; } -.color-light-green, .color-light-green i {color: #8bc34a !important; } -.color-lime, .color-lime i { color: #cddc39 !important; } -.color-yellow, .color-yellow i { color: #ffeb3b !important; } -.color-amber, .color-amber i { color: #ffc107 !important; } -.color-orange, .color-orange i { color: #ff9800 !important; } -.color-deep-orange, .color-deep-orange i { color: #ff5722 !important; } -.color-red, .color-red i { color: #f44336 !important; } -.color-pink, .color-pink i { color: #e91e63 !important; } -.color-purple,.color-purple i { color: #9c27b0 !important; } -.color-deep-purple, .color-deep-purple i { color: #673ab7 !important; } -.color-indigo, .color-indigo i { color: #3f51b5 !important; } +.btn-color-black {background-color: @black;} +.color-black i { color: @black;} + + +.btn-color-blue-grey {background-color: @blueGrey;} +.color-blue-grey i { color: @blueGrey; } + + +.btn-color-grey{background-color: @grayIcon;} +.color-grey i { color: @grayIcon; } + + +.btn-color-brown{background-color: @brownIcon;} +.color-brown i { color: @brownIcon; } + +.btn-color-blue{background-color: @blueIcon;} +.color-blue i { color: @blueIcon; } + +.btn-color-light-blue{background-color: @lightBlueIcon;} +.color-light-blue i {color: @lightBlueIcon;} + +.btn-color-cyan{background-color: @cyanIcon;} +.color-cyan i { color: @cyanIcon; } + +.btn-color-green{background-color: @greenIcon;} +.color-green i { color: @greenIcon } + +.btn-color-light-green{background-color: @lightGreenIcon;} +.color-light-green i {color: @lightGreenIcon; } + +.btn-color-lime{background-color: @limeIcon;} +.color-lime i { color: @limeIcon; } + +.btn-color-yellow{background-color: @yellowIcon;} +.color-yellow i { color: @yellowIcon; } + +.btn-color-amber{background-color: @amberIcon;} +.color-amber i { color: @amberIcon; } + +.btn-color-orange{background-color: @orangeIcon;} +.color-orange i { color: @orangeIcon; } + +.btn-color-deep-orange{background-color: @deepOrangeIcon;} +.color-deep-orange i { color: @deepOrangeIcon; } + +.btn-color-red{background-color: @redIcon;} +.color-red i { color: @redIcon; } + +.btn-color-pink{background-color: @pinkIcon;} +.color-pink i { color: @pinkIcon; } + +.btn-color-purple{background-color: @purpleIcon;} +.color-purple i { color: @purpleIcon; } + +.btn-color-deep-purple{background-color: @deepPurpleIcon;} +.color-deep-purple i { color: @deepPurpleIcon; } + +.btn-color-indigo{background-color: @indigoIcon;} +.color-indigo i { color: @indigoIcon; } + // Scaffolding diff --git a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js index df40539aac..3323f2bfb3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js @@ -48,15 +48,17 @@ setSectionName(); userService.getCurrentUser().then(function (user) { - + vm.userType = user.userType; vm.userLang = user.locale; + vm.hasAccessToSettings = _.contains(user.allowedSections, 'settings'); + evts.push(eventsService.on("appState.treeState.changed", function (e, args) { handleSectionChange(); })); - findHelp(vm.section, vm.tree, vm.usertype, vm.userLang); + findHelp(vm.section, vm.tree, vm.userType, vm.userLang); }); @@ -81,17 +83,20 @@ vm.tree = $routeParams.tree; setSectionName(); - findHelp(vm.section, vm.tree, vm.usertype, vm.userLang); + findHelp(vm.section, vm.tree, vm.userType, vm.userLang); } }); } function findHelp(section, tree, usertype, userLang) { + + if (vm.hasAccessToSettings) { + helpService.getContextHelpForPage(section, tree).then(function (topics) { + vm.topics = topics; + }); + } - helpService.getContextHelpForPage(section, tree).then(function (topics) { - vm.topics = topics; - }); var rq = {}; rq.section = vm.section; @@ -113,10 +118,12 @@ rq.path = rq.section + "/" + $routeParams.tree + "/" + $routeParams.method; } - helpService.findVideos(rq).then(function(videos){ - vm.videos = videos; - }); - + + if (vm.hasAccessToSettings) { + helpService.findVideos(rq).then(function (videos) { + vm.videos = videos; + }); + } } function setSectionName() { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html index 79c2118aca..cf93308c82 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html @@ -9,16 +9,17 @@ -
+
-
Tours
+
Tours
-
+
- -
+ +
+ {{tourGroup.group}} Other
@@ -50,7 +51,7 @@ -
+
@@ -62,11 +63,11 @@
-
-
Articles
+
-
-
Videos
+
-
- +
+
Visit umbraco.tv
@@ -103,7 +104,7 @@
-
+
Visit our.umbraco.org
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/iconpicker/iconpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/iconpicker/iconpicker.controller.js index 7a186f3d6b..3c36505fcb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/iconpicker/iconpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/iconpicker/iconpicker.controller.js @@ -8,8 +8,35 @@ */ function IconPickerOverlay($scope, iconHelper, localizationService) { - $scope.loading = true; - $scope.model.hideSubmitButton = false; + $scope.loading = true; + $scope.model.hideSubmitButton = false; + + $scope.colors = [ + { name: "Black", value: "color-black" }, + { name: "Blue Grey", value: "color-blue-grey" }, + { name: "Grey", value: "color-grey" }, + { name: "Brown", value: "color-brown" }, + { name: "Blue", value: "color-blue" }, + { name: "Light Blue", value: "color-light-blue" }, + { name: "Indigo", value: "color-indigo" }, + { name: "Purple", value: "color-purple" }, + { name: "Deep Purple", value: "color-deep-purple" }, + { name: "Cyan", value: "color-cyan" }, + { name: "Green", value: "color-green" }, + { name: "Light Green", value: "color-light-green" }, + { name: "Lime", value: "color-lime" }, + { name: "Yellow", value: "color-yellow" }, + { name: "Amber", value: "color-amber" }, + { name: "Orange", value: "color-orange" }, + { name: "Deep Orange", value: "color-deep-orange" }, + { name: "Red", value: "color-red" }, + { name: "Pink", value: "color-pink" } + ]; + + if (!$scope.color) { + // Set default selected color to black + $scope.color = $scope.colors[0].value; + }; if (!$scope.model.title) { $scope.model.title = localizationService.localize("defaultdialogs_selectIcon"); @@ -23,20 +50,29 @@ function IconPickerOverlay($scope, iconHelper, localizationService) { $scope.icon = $scope.model.icon; }; - iconHelper.getIcons().then(function(icons) { - $scope.icons = icons; - $scope.loading = false; - }); + iconHelper.getIcons().then(function (icons) { + $scope.icons = icons; + $scope.loading = false; + }); - $scope.selectIcon = function(icon, color) { - $scope.model.icon = icon; - $scope.model.color = color; - $scope.submitForm($scope.model); - }; - - $scope.changeColor = function (color) { + $scope.selectIcon = function (icon, color) { + $scope.model.icon = icon; $scope.model.color = color; + $scope.submitForm($scope.model); }; + + var unsubscribe = $scope.$on("formSubmitting", + function () { + if ($scope.color) { + $scope.model.color = $scope.color; + } + }); + + //when the scope is destroyed we need to unsubscribe + $scope.$on("$destroy", + function () { + unsubscribe(); + }); } angular.module("umbraco").controller("Umbraco.Overlays.IconPickerOverlay", IconPickerOverlay); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/iconpicker/iconpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/iconpicker/iconpicker.html index 78a215d009..0098463cb4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/iconpicker/iconpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/iconpicker/iconpicker.html @@ -1,3 +1,5 @@ + +
@@ -15,27 +17,7 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.controller.js index c858ca03b9..058f71bfcf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/macropicker/macropicker.controller.js @@ -20,14 +20,17 @@ function MacroPickerController($scope, entityResource, macroResource, umbPropEdi $scope.model.selectedMacro = macro; if ($scope.wizardStep === "macroSelect") { - editParams(); + editParams(true); } else { $scope.model.submit($scope.model); } }; /** changes the view to edit the params of the selected macro */ - function editParams() { + /** if there is pnly one macro, and it has parameters - editor can skip selecting the Macro **/ + function editParams(insertIfNoParameters) { + //whether to insert the macro in the rich text editor when editParams is called and there are no parameters see U4-10537 + insertIfNoParameters = (typeof insertIfNoParameters !== 'undefined') ? insertIfNoParameters : true; //get the macro params if there are any macroResource.getMacroParameters($scope.model.selectedMacro.id) .then(function (data) { @@ -37,7 +40,11 @@ function MacroPickerController($scope, entityResource, macroResource, umbPropEdi //go to next page if there are params otherwise we can just exit if (!angular.isArray(data) || data.length === 0) { - $scope.model.submit($scope.model); + if (insertIfNoParameters) { + $scope.model.submit($scope.model); + } else { + $scope.wizardStep = 'macroSelect'; + } } else { @@ -116,12 +123,19 @@ function MacroPickerController($scope, entityResource, macroResource, umbPropEdi if (found) { //select the macro and go to next screen $scope.model.selectedMacro = found; - editParams(); + editParams(true); return; } } - //we don't have a pre-selected macro so ensure the correct step is set - $scope.wizardStep = "macroSelect"; + //if there is only one macro in the site and it has parameters, let's not make the editor choose it from a selection of one macro (unless there are no parameters - then weirdly it's a better experience to make that selection) + if ($scope.macros.length == 1) { + $scope.model.selectedMacro = $scope.macros[0]; + editParams(false); + } + else { + //we don't have a pre-selected macro so ensure the correct step is set + $scope.wizardStep = 'macroSelect'; + } }); onInit(); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html index c7c34b2eaa..e850bf22b8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html @@ -1,4 +1,4 @@ -
+ {{ displayLabelOff }} @@ -16,4 +16,4 @@ {{ displayLabelOn }} - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index a6183435ae..ac38761010 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -179,22 +179,28 @@ - + on-open="openDocumentType(documentType)" + open-url="previewOpenUrl"> - +
+ + + Open + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html new file mode 100644 index 0000000000..eae299e579 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html @@ -0,0 +1,9 @@ +
+ + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm-action.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm-action.html index c34c3f9283..f4e4dff3af 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm-action.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm-action.html @@ -6,11 +6,11 @@ '-left': direction === 'left'}" on-outside-click="clickCancel()"> - + - +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-grid-selector.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-grid-selector.html index c71c9eff6a..7a3158f259 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-grid-selector.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-grid-selector.html @@ -6,6 +6,7 @@
{{ defaultItem.name }}
+ (Default {{itemLabel}})
@@ -15,7 +16,8 @@
{{ selectedItem.name }}
- Set as default + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html index 2d3d2cfdae..ca7a30f152 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html @@ -13,9 +13,9 @@
- Edit - Open - Remove + Edit + Open + Remove
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.restore.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.restore.controller.js index c9124bf956..62eaa6fca6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.restore.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.restore.controller.js @@ -1,5 +1,5 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.RestoreController", - function ($scope, relationResource, contentResource, navigationService, appState, treeService) { + function ($scope, relationResource, contentResource, navigationService, appState, treeService, localizationService) { var dialogOptions = $scope.dialogOptions; var node = dialogOptions.currentNode; @@ -12,9 +12,9 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.RestoreController" if (data.length == 0) { $scope.success = false; $scope.error = { - errorMsg: "Cannot automatically restore this item", + errorMsg: localizationService.localize('recycleBin_itemCannotBeRestored'), data: { - Message: "There is no 'restore' relation found for this node. Use the Move menu item to move it manually." + Message: localizationService.localize('recycleBin_noRestoreRelation') } } return; @@ -32,9 +32,11 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.RestoreController" // make sure the target item isn't in the recycle bin if($scope.target.path.indexOf("-20") !== -1) { $scope.error = { - errorMsg: "Cannot automatically restore this item", + errorMsg: localizationService.localize('recycleBin_itemCannotBeRestored'), data: { - Message: "The item you want to restore it under (" + $scope.target.name + ") is in the recycle bin. Use the Move menu item to move the item manually." + Message: localizationService.localize('recycleBin_restoreUnderRecycled').then(function (value) { + value.replace('%0%', $scope.target.name); + }) } }; $scope.success = false; @@ -80,4 +82,4 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.RestoreController" $scope.error = err; }); }; - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/restore.html b/src/Umbraco.Web.UI.Client/src/views/content/restore.html index 8564a80bf7..e99e2eb251 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/restore.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/restore.html @@ -2,9 +2,9 @@
-

- Restore {{currentNode.name}} under {{target.name}}? -

+

+ Restore {{currentNode.name}} under {{target.name}}? +

{{error.errorMsg}}
@@ -12,15 +12,15 @@
-

{{currentNode.name}} was moved underneath {{target.name}}

- +

{{currentNode.name}} was moved underneath {{target.name}}

+
-
\ No newline at end of file +
diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js index 85ec8461f2..44b51ccdcb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js @@ -29,6 +29,7 @@ function DataTypeEditController($scope, $routeParams, $location, appState, navig view: preVals[i].view, value: preVals[i].value, config: preVals[i].config, + prevalues: preVals[i].prevalues }); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html index 4452f78234..ea57cb8e95 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/copy.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/copy.html index 64b523ba70..af3b954c83 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/copy.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/copy.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html index e67288ad86..379a64fc14 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.html index 0be23bd934..e5317c134e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/templates/templates.html @@ -9,16 +9,15 @@
- - + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.restore.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.restore.controller.js new file mode 100644 index 0000000000..6ef9232c37 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.restore.controller.js @@ -0,0 +1,85 @@ +angular.module("umbraco").controller("Umbraco.Editors.Media.RestoreController", + function ($scope, relationResource, mediaResource, navigationService, appState, treeService, localizationService) { + var dialogOptions = $scope.dialogOptions; + + var node = dialogOptions.currentNode; + + $scope.error = null; + $scope.success = false; + + relationResource.getByChildId(node.id, "relateParentDocumentOnDelete").then(function (data) { + + if (data.length == 0) { + $scope.success = false; + $scope.error = { + errorMsg: localizationService.localize('recycleBin_itemCannotBeRestored'), + data: { + Message: localizationService.localize('recycleBin_noRestoreRelation') + } + } + return; + } + + $scope.relation = data[0]; + + if ($scope.relation.parentId == -1) { + $scope.target = { id: -1, name: "Root" }; + + } else { + mediaResource.getById($scope.relation.parentId).then(function (data) { + $scope.target = data; + + // make sure the target item isn't in the recycle bin + if ($scope.target.path.indexOf("-20") !== -1) { + $scope.error = { + errorMsg: localizationService.localize('recycleBin_itemCannotBeRestored'), + data: { + Message: localizationService.localize('recycleBin_restoreUnderRecycled').then(function (value) { + value.replace('%0%', $scope.target.name); + }) + } + }; + $scope.success = false; + } + + }, function (err) { + $scope.success = false; + $scope.error = err; + }); + } + + }, function (err) { + $scope.success = false; + $scope.error = err; + }); + + $scope.restore = function () { + // this code was copied from `content.move.controller.js` + mediaResource.move({ parentId: $scope.target.id, id: node.id }) + .then(function (path) { + + $scope.success = true; + + //first we need to remove the node that launched the dialog + treeService.removeNode($scope.currentNode); + + //get the currently edited node (if any) + var activeNode = appState.getTreeState("selectedNode"); + + //we need to do a double sync here: first sync to the moved media item - but don't activate the node, + //then sync to the currenlty edited media item (note: this might not be the media item that was moved!!) + + navigationService.syncTree({ tree: "media", path: path, forceReload: true, activate: false }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ tree: "media", path: activeNodePath, forceReload: false, activate: true }); + } + }); + + }, function (err) { + $scope.success = false; + $scope.error = err; + }); + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/media/move.html b/src/Umbraco.Web.UI.Client/src/views/media/move.html index 40f86b22dc..be15f1d366 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/move.html @@ -1,57 +1,56 @@
-
-
+
+
-
-
-
{{error.errorMsg}}
-
{{error.data.message}}
+
+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
-
-
-
- {{currentNode.name}} was moved underneath {{target.name}} -
- -
+
+
+ {{currentNode.name}} was moved underneath {{target.name}} +
+ +
-

- Choose where to move - {{currentNode.name}} - to in the tree structure below -

+

+ Choose where to move + {{currentNode.name}} + to in the tree structure below +

-
+
-
- - -
+
+ + +
- - + + -
-
-
- - - +
+
+
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/media/restore.html b/src/Umbraco.Web.UI.Client/src/views/media/restore.html new file mode 100644 index 0000000000..17fff154d1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/media/restore.html @@ -0,0 +1,26 @@ +
+
+ + +

+ Restore {{currentNode.name}} under {{target.name}}? +

+ +
+
{{error.errorMsg}}
+
{{error.data.Message}}
+
+ +
+

{{currentNode.name}} was moved underneath {{target.name}}

+ +
+ +
+
+ + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html index 62e5a4a749..8104fcc979 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html index 96a72b0c32..5d96375b44 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/multivalues.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/multivalues.html index 24ca38d24f..bb5e5fe782 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/multivalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/multivalues.html @@ -4,7 +4,7 @@
- +
@@ -14,7 +14,7 @@
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js index 56ef917510..14c37d2ee4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js @@ -1,5 +1,41 @@ function ColorPickerController($scope) { + //setup the default config + var config = { + items: [], + multiple: false + }; + + //map the user config + angular.extend(config, $scope.model.config); + + //map back to the model + $scope.model.config = config; + + function convertArrayToDictionaryArray(model) { + //now we need to format the items in the dictionary because we always want to have an array + var newItems = []; + for (var i = 0; i < model.length; i++) { + newItems.push({ id: model[i], sortOrder: 0, value: model[i] }); + } + + return newItems; + } + + + function convertObjectToDictionaryArray(model) { + //now we need to format the items in the dictionary because we always want to have an array + var newItems = []; + var vals = _.values($scope.model.config.items); + var keys = _.keys($scope.model.config.items); + + for (var i = 0; i < vals.length; i++) { + var label = vals[i].value ? vals[i].value : vals[i]; + newItems.push({ id: keys[i], sortOrder: vals[i].sortOrder, value: label }); + } + + return newItems; + } $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0; if ($scope.isConfigured) { @@ -13,6 +49,35 @@ function ColorPickerController($scope) { initActiveColor(); } + if (!angular.isArray($scope.model.config.items)) { + //make an array from the dictionary + var items = []; + for (var i in $scope.model.config.items) { + var oldValue = $scope.model.config.items[i]; + if (oldValue.hasOwnProperty("value")) { + items.push({ + value: oldValue.value, + label: oldValue.label, + sortOrder: oldValue.sortOrder, + id: i + }); + } else { + items.push({ + value: oldValue, + label: oldValue, + sortOrder: sortOrder, + id: i + }); + } + } + + //ensure the items are sorted by the provided sort order + items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); }); + + //now make the editor model the array + $scope.model.config.items = items; + } + $scope.toggleItem = function (color) { var currentColor = ($scope.model.value && $scope.model.value.hasOwnProperty("value")) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html index 12afbf0171..7d88fb068f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html @@ -1,13 +1,24 @@ -
-
- - - - +
+
+
+ + + +
+
+ +
-
-
-
#{{item.value}} - {{item.label}}
- +
+
+ +
+
+
#{{item.value}}
{{item.label}}
+
+
+ Remove +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js index 98e649729c..7ae728a85a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js @@ -1,5 +1,5 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiColorPickerController", - function ($scope, $timeout, assetsService, angularHelper, $element) { + function ($scope, $timeout, assetsService, angularHelper, $element, localizationService, eventsService) { //NOTE: We need to make each color an object, not just a string because you cannot 2-way bind to a primitive. var defaultColor = "000000"; var defaultLabel = null; @@ -8,6 +8,23 @@ $scope.newLavel = defaultLabel; $scope.hasError = false; + $scope.labels = {}; + + var labelKeys = [ + "general_cancel", + "general_choose" + ]; + + $scope.labelEnabled = false; + eventsService.on("toggleValue", function (e, args) { + $scope.labelEnabled = args.value; + }); + + localizationService.localizeMany(labelKeys).then(function (values) { + $scope.labels.cancel = values[0]; + $scope.labels.choose = values[1]; + }); + assetsService.load([ //"lib/spectrum/tinycolor.js", "lib/spectrum/spectrum.js" @@ -16,8 +33,8 @@ elem.spectrum({ color: null, showInitial: false, - chooseText: "choose", // TODO: These can be localised - cancelText: "cancel", // TODO: These can be localised + chooseText: $scope.labels.choose, + cancelText: $scope.labels.cancel, preferredFormat: "hex", showInput: true, clickoutFiresChange: true, @@ -46,16 +63,22 @@ items.push({ value: oldValue.value, label: oldValue.label, + sortOrder: oldValue.sortOrder, id: i }); } else { items.push({ value: oldValue, label: oldValue, + sortOrder: sortOrder, id: i }); } } + + //ensure the items are sorted by the provided sort order + items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); }); + //now make the editor model the array $scope.model.value = items; } @@ -104,6 +127,39 @@ }; + $scope.sortableOptions = { + axis: 'y', + containment: 'parent', + cursor: 'move', + //handle: ".handle, .thumbnail", + items: '> div.control-group', + tolerance: 'pointer', + update: function (e, ui) { + // Get the new and old index for the moved element (using the text as the identifier, so + // we'd have a problem if two prevalues were the same, but that would be unlikely) + var newIndex = ui.item.index(); + var movedPrevalueText = $('pre', ui.item).text(); + var originalIndex = getElementIndexByPrevalueText(movedPrevalueText); + + //// Move the element in the model + if (originalIndex > -1) { + var movedElement = $scope.model.value[originalIndex]; + $scope.model.value.splice(originalIndex, 1); + $scope.model.value.splice(newIndex, 0, movedElement); + } + } + }; + + function getElementIndexByPrevalueText(value) { + for (var i = 0; i < $scope.model.value.length; i++) { + if ($scope.model.value[i].value === value) { + return i; + } + } + + return -1; + } + //load the separate css for the editor to avoid it blocking our js loading assetsService.loadCss("lib/spectrum/spectrum.css", $scope); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js index d701e6172d..58f438c194 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js @@ -73,7 +73,7 @@ function fileUploadController($scope, $element, $compile, imageHelper, fileManag var extension = file.file.substring(file.file.lastIndexOf(".") + 1, file.file.length); - file.thumbnail = thumbnailUrl; + file.thumbnail = thumbnailUrl + '&rnd=' + Math.random(); file.extension = extension.toLowerCase(); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html index 365372ff0d..9db24c237b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html @@ -6,9 +6,11 @@ {{file.file}} {{file.file}} - - - .{{file.extension}} + + + + .{{file.extension}} + {{file.file}} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js index 8bdcfdc351..9f759d73a1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js @@ -337,7 +337,7 @@ angular.module("umbraco") } } - $scope.addRow = function (section, layout) { + $scope.addRow = function (section, layout, isInit) { //copy the selected layout into the rows collection var row = angular.copy(layout); @@ -349,8 +349,9 @@ angular.module("umbraco") if (row) { section.rows.push(row); } - - currentForm.$setDirty(); + if (!isInit) { + currentForm.$setDirty(); + } $scope.showRowConfigurations = false; @@ -732,7 +733,7 @@ angular.module("umbraco") if (!section.rows || section.rows.length === 0) { section.rows = []; if(section.$allowedLayouts.length === 1){ - $scope.addRow(section, section.$allowedLayouts[0]); + $scope.addRow(section, section.$allowedLayouts[0], true); } } else { _.forEach(section.rows, function (row, index) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html index fa2da2f9e2..539433821b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html @@ -2,7 +2,7 @@
-

+

    - +
@@ -57,13 +57,13 @@
  • + class="preview-rows columns" style="margin-top: 5px; margin-bottom: 25px; float:left">
    + ng-class="{last:$last}" + ng-repeat="area in layout.areas | filter:zeroWidthFilter" + ng-style="{width: percentage(area.grid) + '%', 'max-width': '100%'}">

    {{area.maxItems}}

    @@ -95,15 +95,13 @@
    - - + + - +
    - + - + - + - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.controller.js index 0cd199ae4d..216f1555e2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.controller.js @@ -4,6 +4,9 @@ function includePropsPreValsController($rootScope, $scope, localizationService, $scope.model.value = []; } + $scope.hasError = false; + $scope.errorMsg = ""; + $scope.propertyAliases = []; $scope.selectedField = null; $scope.systemFields = [ @@ -45,6 +48,11 @@ function includePropsPreValsController($rootScope, $scope, localizationService, return alias; } + $scope.changeField = function () { + $scope.hasError = false; + $scope.errorMsg = ""; + } + $scope.removeField = function(e) { $scope.model.value = _.reject($scope.model.value, function (x) { return x.alias === e.alias; @@ -123,19 +131,34 @@ function includePropsPreValsController($rootScope, $scope, localizationService, $scope.addField = function () { var val = $scope.selectedField; - var isSystem = val.startsWith("_system_"); - if (isSystem) { - val = val.trimStart("_system_"); - } + if (val) { + var isSystem = val.startsWith("_system_"); + if (isSystem) { + val = val.trimStart("_system_"); + } - var exists = _.find($scope.model.value, function (i) { - return i.alias === val; - }); - if (!exists) { - $scope.model.value.push({ - alias: val, - isSystem: isSystem ? 1 : 0 + var exists = _.find($scope.model.value, function (i) { + return i.alias === val; }); + + if (!exists) { + $scope.hasError = false; + $scope.errorMsg = ""; + + $scope.model.value.push({ + alias: val, + isSystem: isSystem ? 1 : 0 + }); + } + else { + //there was an error, do the highlight (will be set back by the directive) + $scope.hasError = true; + $scope.errorMsg = "Property is already added"; + } + } + else { + $scope.hasError = true; + $scope.errorMsg = "No property selected"; } } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html index 5505895a5d..345c58f423 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html @@ -1,7 +1,7 @@ 
    - @@ -9,6 +9,7 @@ + {{errorMsg}}
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 777110197d..d413714a5b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -59,23 +59,26 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific items: [] }; - $scope.currentNodePermissions = {} + //when this is null, we don't check permissions + $scope.currentNodePermissions = null; - //Just ensure we do have an editorState - if (editorState.current) { - //Fetch current node allowed actions for the current user - //This is the current node & not each individual child node in the list - var currentUserPermissions = editorState.current.allowedActions; + if ($scope.entityType === "content") { + //Just ensure we do have an editorState + if (editorState.current) { + //Fetch current node allowed actions for the current user + //This is the current node & not each individual child node in the list + var currentUserPermissions = editorState.current.allowedActions; - //Create a nicer model rather than the funky & hard to remember permissions strings - $scope.currentNodePermissions = { - "canCopy": _.contains(currentUserPermissions, 'O'), //Magic Char = O - "canCreate": _.contains(currentUserPermissions, 'C'), //Magic Char = C - "canDelete": _.contains(currentUserPermissions, 'D'), //Magic Char = D - "canMove": _.contains(currentUserPermissions, 'M'), //Magic Char = M - "canPublish": _.contains(currentUserPermissions, 'U'), //Magic Char = U - "canUnpublish": _.contains(currentUserPermissions, 'U'), //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish) - }; + //Create a nicer model rather than the funky & hard to remember permissions strings + $scope.currentNodePermissions = { + "canCopy": _.contains(currentUserPermissions, 'O'), //Magic Char = O + "canCreate": _.contains(currentUserPermissions, 'C'), //Magic Char = C + "canDelete": _.contains(currentUserPermissions, 'D'), //Magic Char = D + "canMove": _.contains(currentUserPermissions, 'M'), //Magic Char = M + "canPublish": _.contains(currentUserPermissions, 'U'), //Magic Char = U + "canUnpublish": _.contains(currentUserPermissions, 'U'), //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish) + }; + } } //when this is null, we don't check permissions diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html index ba95610f24..3df37f6306 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html @@ -10,7 +10,7 @@ - +
    Create diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html index e07f017f5b..48858a507b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html @@ -7,6 +7,7 @@
    diff --git a/src/Umbraco.Web.UI/Media/Web.config b/src/Umbraco.Web.UI/Media/Web.config index 6cbb733ea7..cd48da3840 100644 --- a/src/Umbraco.Web.UI/Media/Web.config +++ b/src/Umbraco.Web.UI/Media/Web.config @@ -6,4 +6,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/web.Template.Debug.config b/src/Umbraco.Web.UI/web.Template.Debug.config index c4804160e1..6089765d9a 100644 --- a/src/Umbraco.Web.UI/web.Template.Debug.config +++ b/src/Umbraco.Web.UI/web.Template.Debug.config @@ -58,7 +58,7 @@ - + diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 3bcd14aed7..7f52c78deb 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -43,7 +43,7 @@ - + diff --git a/src/Umbraco.Web/Editors/TemplateController.cs b/src/Umbraco.Web/Editors/TemplateController.cs index fae7bfdfbf..dae9cacb4c 100644 --- a/src/Umbraco.Web/Editors/TemplateController.cs +++ b/src/Umbraco.Web/Editors/TemplateController.cs @@ -185,7 +185,6 @@ namespace Umbraco.Web.Editors } var template = Services.FileService.CreateTemplateWithIdentity(display.Name, display.Content, master); - //template = Services.FileService.GetTemplate(template.Id); Mapper.Map(template, display); } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs index 08b95a9029..6738a21240 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs @@ -35,6 +35,15 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "template")] public string TemplateAlias { get; set; } + + + + [DataMember(Name = "templateId")] + public int TemplateId { get; set; } + + + + [DataMember(Name = "allowedTemplates")] public IDictionary AllowedTemplates { get; set; } diff --git a/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs new file mode 100644 index 0000000000..60785d283e --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + internal class ColorListPreValueEditor : ValueListPreValueEditor + { + + public ColorListPreValueEditor() + { + var field = Fields.First(); + + //use a custom editor too + field.View = "views/propertyeditors/colorpicker/colorpicker.prevalues.html"; + //change the description + field.Description = "Add, remove or sort colors."; + //change the label + field.Name = "Colors"; + //need to have some custom validation happening here + field.Validators.Add(new ColorListValidator()); + + Fields.Insert(0, new PreValueField + { + Name = "Include labels?", + View = "boolean", + Key = "useLabel", + Description = "Stores colors as a Json object containing both the color hex string and label, rather than just the hex string." + }); + } + + public override IDictionary ConvertDbToEditor(IDictionary defaultPreVals, PreValueCollection persistedPreVals) + { + var dictionary = persistedPreVals.FormatAsDictionary(); + var items = dictionary.Where(x => x.Key != "useLabel") + .OrderBy(x => x.Value.SortOrder); + + var items2 = new Dictionary(); + foreach (var item in items) + { + var valueItem = new ColorPickerColor + { + Color = item.Value.Value, + Label = item.Value.Value, + SortOrder = item.Value.SortOrder + }; + + if (item.Value.Value.DetectIsJson()) + { + try + { + var valueObject = JsonConvert.DeserializeObject(item.Value.Value); + valueItem = new ColorPickerColor + { + Color = valueObject.Color, + Label = valueObject.Label, + SortOrder = valueObject.SortOrder + }; + } + catch + { + // let's say parsing Json failed, we'll not do anything, + // we'll just use the valueItem we built in the first place + } + } + + items2[item.Value.Id] = new JObject + { + { "value", valueItem.Color }, + { "label", valueItem.Label }, + { "sortOrder", valueItem.SortOrder } + }; + } + + var result = new Dictionary { { "items", items2 } }; + var useLabel = dictionary.ContainsKey("useLabel") && dictionary["useLabel"].Value == "1"; + if (useLabel) + result["useLabel"] = dictionary["useLabel"].Value; + + return result; + } + + public override IDictionary ConvertEditorToDb(IDictionary editorValue, PreValueCollection currentValue) + { + var val = editorValue["items"] as JArray; + var result = new Dictionary(); + if (val == null) return result; + + try + { + var useLabel = false; + if (editorValue.TryGetValue("useLabel", out var useLabelObj)) + { + useLabel = useLabelObj is string && (string) useLabelObj == "1"; + result["useLabel"] = new PreValue(useLabel ? "1" : "0"); + } + + // get all non-empty values + var index = 0; + // items get submitted in the sorted order, so just count them up + var sortOrder = -1; + foreach (var preValue in val.OfType() + .Where(x => x["value"] != null) + .Select(x => + { + var idString = x["id"] == null ? "0" : x["id"].ToString(); + int.TryParse(idString, out var id); + + var color = x["value"].ToString(); + if (string.IsNullOrWhiteSpace(color)) return null; + + var label = x["label"].ToString(); + + sortOrder++; + var value = useLabel + ? JsonConvert.SerializeObject(new { value = color, label = label, sortOrder = sortOrder }) + : color; + + return new PreValue(id, value, sortOrder); + }) + .WhereNotNull()) + { + result.Add(index.ToInvariantString(), preValue); + index++; + } + } + catch (Exception ex) + { + LogHelper.Error("Could not deserialize the posted value: " + val, ex); + } + + return result; + } + + internal class ColorListValidator : IPropertyValidator + { + public IEnumerable Validate(object value, PreValueCollection preValues, PropertyEditor editor) + { + var json = value as JArray; + if (json == null) yield break; + + //validate each item which is a json object + for (var index = 0; index < json.Count; index++) + { + var i = json[index]; + var jItem = i as JObject; + if (jItem == null || jItem["value"] == null) continue; + + //NOTE: we will be removing empty values when persisting so no need to validate + var asString = jItem["value"].ToString(); + if (asString.IsNullOrWhiteSpace()) continue; + + if (Regex.IsMatch(asString, "^([0-9a-f]{3}|[0-9a-f]{6})$", RegexOptions.IgnoreCase) == false) + { + yield return new ValidationResult("The value " + asString + " is not a valid hex color", new[] + { + //we'll make the server field the index number of the value so it can be wired up to the view + "item_" + index.ToInvariantString() + }); + } + } + } + } + } + + internal class ColorPickerColor + { + [JsonProperty("value")] + public string Color { get; set; } + [JsonProperty("label")] + public string Label { get; set; } + [JsonProperty("sortOrder")] + public int SortOrder { get; set; } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs index 5599d8e596..be2bbb66c1 100644 --- a/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs @@ -5,9 +5,9 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { /// - /// Represents a boolean property and parameter editor. + /// Represents a checkbox property and parameter editor. /// - [DataEditor(Constants.PropertyEditors.Aliases.Boolean, EditorType.PropertyValue | EditorType.MacroParameter, "True/False", "boolean", ValueType = ValueTypes.Integer, Group = "Common", Icon="icon-checkbox")] + [DataEditor(Constants.PropertyEditors.Aliases.Boolean, EditorType.PropertyValue | EditorType.MacroParameter, "Checkbox", "boolean", ValueType = ValueTypes.Integer, Group = "Common", Icon="icon-checkbox")] public class TrueFalsePropertyEditor : DataEditor { /// diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index d3ff363156..d33372ce53 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -385,7 +385,7 @@ namespace Umbraco.Web public static bool IsDescendant(this IPublishedContent content, IPublishedContent other) { - return other.Level < content.Level && content.Path.InvariantStartsWith(other.Path); + return other.Level < content.Level && content.Path.InvariantStartsWith(other.Path.EnsureEndsWith(',')); } public static HtmlString IsDescendant(this IPublishedContent content, IPublishedContent other, string valueIfTrue) @@ -400,7 +400,7 @@ namespace Umbraco.Web public static bool IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other) { - return content.Path.InvariantStartsWith(other.Path); + return content.Path.InvariantEquals(other.Path) || content.IsDescendant(other); } public static HtmlString IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue) @@ -415,8 +415,7 @@ namespace Umbraco.Web public static bool IsAncestor(this IPublishedContent content, IPublishedContent other) { - // avoid using Descendants(), or Ancestors(), they're expensive - return content.Level < other.Level && other.Path.InvariantStartsWith(content.Path); + return content.Level < other.Level && other.Path.InvariantStartsWith(content.Path.EnsureEndsWith(',')); } public static HtmlString IsAncestor(this IPublishedContent content, IPublishedContent other, string valueIfTrue) @@ -431,8 +430,7 @@ namespace Umbraco.Web public static bool IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other) { - // avoid using DescendantsOrSelf() or AncestorsOrSelf(), they're expensive - return other.Path.InvariantStartsWith(content.Path); + return other.Path.InvariantEquals(content.Path) || content.IsAncestor(other); } public static HtmlString IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue) diff --git a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs index d32b90a09c..73ab8811ba 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs @@ -52,17 +52,16 @@ namespace Umbraco.Web.Trees internal static string GetRootNodeDisplayName(this TreeAttribute attribute, ILocalizedTextService textService) { - //if title is defined, return that - if (string.IsNullOrEmpty(attribute.Title) == false) - return attribute.Title; - - - //try to look up a tree header matching the tree alias + // try to look up a the localized tree header matching the tree alias var localizedLabel = textService.Localize("treeHeaders/" + attribute.Alias); if (string.IsNullOrEmpty(localizedLabel) == false) return localizedLabel; - //is returned to signal that a label was not found + // otherwise return the header if it's defined + if (string.IsNullOrEmpty(attribute.Title) == false) + return attribute.Title; + + // if all fails, return this to signal that a label was not found return "[" + attribute.Alias + "]"; } diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index 039ea165f8..4c3453e5de 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -132,6 +132,12 @@ namespace Umbraco.Web.Trees if (item.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Contains(RecycleBinId.ToInvariantString())) { menu.DefaultMenuAlias = null; + menu.Items.Insert(2, new MenuItem(ActionRestore.Instance, Services.TextService.Localize("actions", ActionRestore.Instance.Alias))); + } + else + { + //set the default to create + menu.DefaultMenuAlias = ActionNew.Instance.Alias; } return menu; diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionRestore.cs b/src/Umbraco.Web/_Legacy/Actions/ActionRestore.cs index 60cfd3443f..da70eb1409 100644 --- a/src/Umbraco.Web/_Legacy/Actions/ActionRestore.cs +++ b/src/Umbraco.Web/_Legacy/Actions/ActionRestore.cs @@ -1,82 +1,31 @@ namespace Umbraco.Web._Legacy.Actions { /// - /// This action is invoked when the content item is to be restored from the recycle bin + /// This action is invoked when the content/media item is to be restored from the recycle bin /// public class ActionRestore : IAction { //create singleton - private static readonly ActionRestore SingletonInstance = new ActionRestore(); private ActionRestore() { } - public static ActionRestore Instance - { - get { return SingletonInstance; } - } + public static ActionRestore Instance { get; } = new ActionRestore(); #region IAction Members - public char Letter - { - get - { - return 'V'; - } - } + public char Letter => 'V'; - public string JsFunctionName - { - get - { - return null; - } - } + public string JsFunctionName => null; - public string JsSource - { - get - { + public string JsSource => null; - return null; - } - } + public string Alias => "restore"; - public string Alias - { - get - { + public string Icon => "undo"; - return "restore"; - } - } + public bool ShowInNotifier => true; - public string Icon - { - get - { - - return "undo"; - } - } - - public bool ShowInNotifier - { - get - { - - return true; - } - } - - public bool CanBePermissionAssigned - { - get - { - - return false; - } - } + public bool CanBePermissionAssigned => false; #endregion }