diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index e6439acade..4b9831682c 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -160,5 +160,8 @@ namespace Umbraco.Core.Models return result; } + + /// + IContentType IContentType.DeepCloneWithResetIdentities(string newAlias) => (IContentType)DeepCloneWithResetIdentities(newAlias); } } diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 064bdd3d4a..3da2838d0e 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -504,9 +504,9 @@ namespace Umbraco.Core.Models } } - public IContentType DeepCloneWithResetIdentities(string alias) + public ContentTypeBase DeepCloneWithResetIdentities(string alias) { - var clone = (ContentType)DeepClone(); + var clone = (ContentTypeBase)DeepClone(); clone.Alias = alias; clone.Key = Guid.Empty; foreach (var propertyGroup in clone.PropertyGroups) diff --git a/src/Umbraco.Core/Models/MediaType.cs b/src/Umbraco.Core/Models/MediaType.cs index 4ae2fd190c..83e1acfbc0 100644 --- a/src/Umbraco.Core/Models/MediaType.cs +++ b/src/Umbraco.Core/Models/MediaType.cs @@ -44,29 +44,7 @@ namespace Umbraco.Core.Models /// public override bool IsPublishing => IsPublishingConst; - /// - /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset - /// - /// - public new IMediaType DeepCloneWithResetIdentities(string alias) - { - var clone = (MediaType)DeepClone(); - clone.Alias = alias; - clone.Key = Guid.Empty; - foreach (var propertyGroup in clone.PropertyGroups) - { - propertyGroup.ResetIdentity(); - propertyGroup.ResetDirtyProperties(false); - } - foreach (var propertyType in clone.PropertyTypes) - { - propertyType.ResetIdentity(); - propertyType.ResetDirtyProperties(false); - } - - clone.ResetIdentity(); - clone.ResetDirtyProperties(false); - return clone; - } + /// + IMediaType IMediaType.DeepCloneWithResetIdentities(string newAlias) => (IMediaType)DeepCloneWithResetIdentities(newAlias); } } diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index bf43693d3b..78bb9821e4 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -594,7 +594,7 @@ namespace Umbraco.Core.Services.Implement //var originalb = (ContentTypeCompositionBase)original; // but we *know* it has to be a ContentTypeCompositionBase anyways var originalb = (ContentTypeCompositionBase) (object) original; - var clone = (TItem) originalb.DeepCloneWithResetIdentities(alias); + var clone = (TItem) (object) originalb.DeepCloneWithResetIdentities(alias); clone.Name = name; @@ -645,7 +645,7 @@ namespace Umbraco.Core.Services.Implement //var copyingb = (ContentTypeCompositionBase) copying; // but we *know* it has to be a ContentTypeCompositionBase anyways var copyingb = (ContentTypeCompositionBase) (object)copying; - copy = (TItem) copyingb.DeepCloneWithResetIdentities(alias); + copy = (TItem) (object) copyingb.DeepCloneWithResetIdentities(alias); copy.Name = copy.Name + " (copy)"; // might not be unique diff --git a/src/Umbraco.Examine/MemberValueSetValidator.cs b/src/Umbraco.Examine/MemberValueSetValidator.cs index 71de6001ce..0c585854e9 100644 --- a/src/Umbraco.Examine/MemberValueSetValidator.cs +++ b/src/Umbraco.Examine/MemberValueSetValidator.cs @@ -23,10 +23,10 @@ namespace Umbraco.Examine /// /// By default these are the member fields we index /// - public static readonly string[] DefaultMemberIndexFields = { "id", "nodeName", "updateDate", "loginName", "email" }; + public static readonly string[] DefaultMemberIndexFields = { "id", "nodeName", "updateDate", "loginName", "email", UmbracoExamineIndex.NodeKeyFieldName }; private static readonly IEnumerable ValidCategories = new[] { IndexTypes.Member }; protected override IEnumerable ValidIndexCategories => ValidCategories; - + } } diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index 8dc8a2b45c..1319d00c48 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -407,46 +407,6 @@ namespace Umbraco.Tests.Services Assert.IsNull(doc2.GetValue("title", "en-US")); } - [Test] - public void Deleting_Media_Type_With_Hierarchy_Of_Media_Items_Moves_Orphaned_Media_To_Recycle_Bin() - { - IMediaType contentType1 = MockedContentTypes.CreateSimpleMediaType("test1", "Test1"); - ServiceContext.MediaTypeService.Save(contentType1); - IMediaType contentType2 = MockedContentTypes.CreateSimpleMediaType("test2", "Test2"); - ServiceContext.MediaTypeService.Save(contentType2); - IMediaType contentType3 = MockedContentTypes.CreateSimpleMediaType("test3", "Test3"); - ServiceContext.MediaTypeService.Save(contentType3); - - var contentTypes = new[] { contentType1, contentType2, contentType3 }; - var parentId = -1; - - var ids = new List(); - - for (int i = 0; i < 2; i++) - { - for (var index = 0; index < contentTypes.Length; index++) - { - var contentType = contentTypes[index]; - var contentItem = MockedMedia.CreateSimpleMedia(contentType, "MyName_" + index + "_" + i, parentId); - ServiceContext.MediaService.Save(contentItem); - parentId = contentItem.Id; - - ids.Add(contentItem.Id); - } - } - - //delete the first content type, all other content of different content types should be in the recycle bin - ServiceContext.MediaTypeService.Delete(contentTypes[0]); - - var found = ServiceContext.MediaService.GetByIds(ids); - - Assert.AreEqual(4, found.Count()); - foreach (var content in found) - { - Assert.IsTrue(content.Trashed); - } - } - [Test] public void Deleting_Content_Type_With_Hierarchy_Of_Content_Items_Moves_Orphaned_Content_To_Recycle_Bin() { @@ -491,60 +451,6 @@ namespace Umbraco.Tests.Services } } - [Test] - public void Deleting_Media_Types_With_Hierarchy_Of_Media_Items_Doesnt_Raise_Trashed_Event_For_Deleted_Items() - { - MediaService.Trashed += MediaServiceOnTrashed; - - try - { - IMediaType contentType1 = MockedContentTypes.CreateSimpleMediaType("test1", "Test1"); - ServiceContext.MediaTypeService.Save(contentType1); - IMediaType contentType2 = MockedContentTypes.CreateSimpleMediaType("test2", "Test2"); - ServiceContext.MediaTypeService.Save(contentType2); - IMediaType contentType3 = MockedContentTypes.CreateSimpleMediaType("test3", "Test3"); - ServiceContext.MediaTypeService.Save(contentType3); - - var contentTypes = new[] { contentType1, contentType2, contentType3 }; - var parentId = -1; - - var ids = new List(); - - for (int i = 0; i < 2; i++) - { - for (var index = 0; index < contentTypes.Length; index++) - { - var contentType = contentTypes[index]; - var contentItem = MockedMedia.CreateSimpleMedia(contentType, "MyName_" + index + "_" + i, parentId); - ServiceContext.MediaService.Save(contentItem); - parentId = contentItem.Id; - - ids.Add(contentItem.Id); - } - } - - foreach (var contentType in contentTypes.Reverse()) - { - ServiceContext.MediaTypeService.Delete(contentType); - } - } - finally - { - MediaService.Trashed -= MediaServiceOnTrashed; - } - } - - private void MediaServiceOnTrashed(IMediaService sender, MoveEventArgs e) - { - foreach (var item in e.MoveInfoCollection) - { - //if this item doesn't exist then Fail! - var exists = ServiceContext.MediaService.GetById(item.Entity.Id); - if (exists == null) - Assert.Fail("The item doesn't exist"); - } - } - [Test] public void Deleting_Content_Types_With_Hierarchy_Of_Content_Items_Doesnt_Raise_Trashed_Event_For_Deleted_Items_1() { @@ -1109,12 +1015,13 @@ namespace Umbraco.Tests.Services var metaContentType = MockedContentTypes.CreateMetaContentType(); service.Save(metaContentType); - var simpleContentType = MockedContentTypes.CreateSimpleContentType("category", "Category", metaContentType); + var simpleContentType = MockedContentTypes.CreateSimpleContentType("category", "Category", metaContentType) as IContentType; service.Save(simpleContentType); var categoryId = simpleContentType.Id; // Act var sut = simpleContentType.DeepCloneWithResetIdentities("newcategory"); + Assert.IsNotNull(sut); service.Save(sut); // Assert @@ -1146,11 +1053,12 @@ namespace Umbraco.Tests.Services var parentContentType2 = MockedContentTypes.CreateSimpleContentType("parent2", "Parent2", null, true); service.Save(parentContentType2); - var simpleContentType = MockedContentTypes.CreateSimpleContentType("category", "Category", parentContentType1, true); + var simpleContentType = MockedContentTypes.CreateSimpleContentType("category", "Category", parentContentType1, true) as IContentType; service.Save(simpleContentType); // Act var clone = simpleContentType.DeepCloneWithResetIdentities("newcategory"); + Assert.IsNotNull(clone); clone.RemoveContentType("parent1"); clone.AddContentType(parentContentType2); clone.ParentId = parentContentType2.Id; @@ -2114,22 +2022,6 @@ namespace Umbraco.Tests.Services Assert.IsNull(contentType2.Description); } - [Test] - public void Empty_Description_Is_Always_Null_After_Saving_Media_Type() - { - var service = ServiceContext.MediaTypeService; - var mediaType = MockedContentTypes.CreateSimpleMediaType("mediaType", "Media Type"); - mediaType.Description = null; - service.Save(mediaType); - - var mediaType2 = MockedContentTypes.CreateSimpleMediaType("mediaType2", "Media Type 2"); - mediaType2.Description = string.Empty; - service.Save(mediaType2); - - Assert.IsNull(mediaType.Description); - Assert.IsNull(mediaType2.Description); - } - [Test] public void Variations_In_Compositions() { diff --git a/src/Umbraco.Tests/Services/MediaTypeServiceTests.cs b/src/Umbraco.Tests/Services/MediaTypeServiceTests.cs new file mode 100644 index 0000000000..915dc5ceec --- /dev/null +++ b/src/Umbraco.Tests/Services/MediaTypeServiceTests.cs @@ -0,0 +1,195 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using NUnit.Framework; +using Umbraco.Core.Events; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; +using Umbraco.Tests.TestHelpers.Entities; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.Services +{ + [TestFixture] + [Apartment(ApartmentState.STA)] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true)] + public class MediaTypeServiceTests : TestWithSomeContentBase + { + [Test] + public void Empty_Description_Is_Always_Null_After_Saving_Media_Type() + { + var mediaType = MockedContentTypes.CreateSimpleMediaType("mediaType", "Media Type"); + mediaType.Description = null; + ServiceContext.MediaTypeService.Save(mediaType); + + var mediaType2 = MockedContentTypes.CreateSimpleMediaType("mediaType2", "Media Type 2"); + mediaType2.Description = string.Empty; + ServiceContext.MediaTypeService.Save(mediaType2); + + Assert.IsNull(mediaType.Description); + Assert.IsNull(mediaType2.Description); + } + + [Test] + public void Deleting_Media_Type_With_Hierarchy_Of_Media_Items_Moves_Orphaned_Media_To_Recycle_Bin() + { + IMediaType contentType1 = MockedContentTypes.CreateSimpleMediaType("test1", "Test1"); + ServiceContext.MediaTypeService.Save(contentType1); + IMediaType contentType2 = MockedContentTypes.CreateSimpleMediaType("test2", "Test2"); + ServiceContext.MediaTypeService.Save(contentType2); + IMediaType contentType3 = MockedContentTypes.CreateSimpleMediaType("test3", "Test3"); + ServiceContext.MediaTypeService.Save(contentType3); + + var contentTypes = new[] { contentType1, contentType2, contentType3 }; + var parentId = -1; + + var ids = new List(); + + for (int i = 0; i < 2; i++) + { + for (var index = 0; index < contentTypes.Length; index++) + { + var contentType = contentTypes[index]; + var contentItem = MockedMedia.CreateSimpleMedia(contentType, "MyName_" + index + "_" + i, parentId); + ServiceContext.MediaService.Save(contentItem); + parentId = contentItem.Id; + + ids.Add(contentItem.Id); + } + } + + //delete the first content type, all other content of different content types should be in the recycle bin + ServiceContext.MediaTypeService.Delete(contentTypes[0]); + + var found = ServiceContext.MediaService.GetByIds(ids); + + Assert.AreEqual(4, found.Count()); + foreach (var content in found) + { + Assert.IsTrue(content.Trashed); + } + } + + [Test] + public void Deleting_Media_Types_With_Hierarchy_Of_Media_Items_Doesnt_Raise_Trashed_Event_For_Deleted_Items() + { + MediaService.Trashed += MediaServiceOnTrashed; + + try + { + IMediaType contentType1 = MockedContentTypes.CreateSimpleMediaType("test1", "Test1"); + ServiceContext.MediaTypeService.Save(contentType1); + IMediaType contentType2 = MockedContentTypes.CreateSimpleMediaType("test2", "Test2"); + ServiceContext.MediaTypeService.Save(contentType2); + IMediaType contentType3 = MockedContentTypes.CreateSimpleMediaType("test3", "Test3"); + ServiceContext.MediaTypeService.Save(contentType3); + + var contentTypes = new[] { contentType1, contentType2, contentType3 }; + var parentId = -1; + + var ids = new List(); + + for (int i = 0; i < 2; i++) + { + for (var index = 0; index < contentTypes.Length; index++) + { + var contentType = contentTypes[index]; + var contentItem = MockedMedia.CreateSimpleMedia(contentType, "MyName_" + index + "_" + i, parentId); + ServiceContext.MediaService.Save(contentItem); + parentId = contentItem.Id; + + ids.Add(contentItem.Id); + } + } + + foreach (var contentType in contentTypes.Reverse()) + { + ServiceContext.MediaTypeService.Delete(contentType); + } + } + finally + { + MediaService.Trashed -= MediaServiceOnTrashed; + } + } + + private void MediaServiceOnTrashed(IMediaService sender, MoveEventArgs e) + { + foreach (var item in e.MoveInfoCollection) + { + //if this item doesn't exist then Fail! + var exists = ServiceContext.MediaService.GetById(item.Entity.Id); + if (exists == null) + Assert.Fail("The item doesn't exist"); + } + } + + [Test] + public void Can_Copy_MediaType_By_Performing_Clone() + { + // Arrange + var mediaType = MockedContentTypes.CreateImageMediaType("Image2") as IMediaType; + ServiceContext.MediaTypeService.Save(mediaType); + + // Act + var sut = mediaType.DeepCloneWithResetIdentities("Image2_2"); + Assert.IsNotNull(sut); + ServiceContext.MediaTypeService.Save(sut); + + // Assert + Assert.That(sut.HasIdentity, Is.True); + Assert.AreEqual(mediaType.ParentId, sut.ParentId); + Assert.AreEqual(mediaType.Level, sut.Level); + Assert.AreEqual(mediaType.PropertyTypes.Count(), sut.PropertyTypes.Count()); + Assert.AreNotEqual(mediaType.Id, sut.Id); + Assert.AreNotEqual(mediaType.Key, sut.Key); + Assert.AreNotEqual(mediaType.Path, sut.Path); + Assert.AreNotEqual(mediaType.SortOrder, sut.SortOrder); + Assert.AreNotEqual(mediaType.PropertyTypes.First(x => x.Alias.Equals("umbracoFile")).Id, sut.PropertyTypes.First(x => x.Alias.Equals("umbracoFile")).Id); + Assert.AreNotEqual(mediaType.PropertyGroups.First(x => x.Name.Equals("Media")).Id, sut.PropertyGroups.First(x => x.Name.Equals("Media")).Id); + } + + [Test] + public void Can_Copy_MediaType_To_New_Parent_By_Performing_Clone() + { + // Arrange + var parentMediaType1 = MockedContentTypes.CreateSimpleMediaType("parent1", "Parent1"); + ServiceContext.MediaTypeService.Save(parentMediaType1); + var parentMediaType2 = MockedContentTypes.CreateSimpleMediaType("parent2", "Parent2", null, true); + ServiceContext.MediaTypeService.Save(parentMediaType2); + var mediaType = MockedContentTypes.CreateImageMediaType("Image2") as IMediaType; + ServiceContext.MediaTypeService.Save(mediaType); + + // Act + var clone = mediaType.DeepCloneWithResetIdentities("newcategory"); + Assert.IsNotNull(clone); + clone.RemoveContentType("parent1"); + clone.AddContentType(parentMediaType2); + clone.ParentId = parentMediaType2.Id; + ServiceContext.MediaTypeService.Save(clone); + + // Assert + Assert.That(clone.HasIdentity, Is.True); + + var clonedMediaType = ServiceContext.MediaTypeService.Get(clone.Id); + var originalMediaType = ServiceContext.MediaTypeService.Get(mediaType.Id); + + Assert.That(clonedMediaType.CompositionAliases().Any(x => x.Equals("parent2")), Is.True); + Assert.That(clonedMediaType.CompositionAliases().Any(x => x.Equals("parent1")), Is.False); + + Assert.AreEqual(clonedMediaType.Path, "-1," + parentMediaType2.Id + "," + clonedMediaType.Id); + Assert.AreEqual(clonedMediaType.PropertyTypes.Count(), originalMediaType.PropertyTypes.Count()); + + Assert.AreNotEqual(clonedMediaType.ParentId, originalMediaType.ParentId); + Assert.AreEqual(clonedMediaType.ParentId, parentMediaType2.Id); + + Assert.AreNotEqual(clonedMediaType.Id, originalMediaType.Id); + Assert.AreNotEqual(clonedMediaType.Key, originalMediaType.Key); + Assert.AreNotEqual(clonedMediaType.Path, originalMediaType.Path); + + Assert.AreNotEqual(clonedMediaType.PropertyTypes.First(x => x.Alias.StartsWith("umbracoFile")).Id, originalMediaType.PropertyTypes.First(x => x.Alias.StartsWith("umbracoFile")).Id); + Assert.AreNotEqual(clonedMediaType.PropertyGroups.First(x => x.Name.StartsWith("Media")).Id, originalMediaType.PropertyGroups.First(x => x.Name.StartsWith("Media")).Id); + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 1703743c6f..3bfc3387fe 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -149,6 +149,7 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js index 1850445bd4..edf54ca034 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js @@ -24,10 +24,10 @@ function umbTagsEditorController($rootScope, assetsService, umbRequestHelper, angularHelper, $timeout, $element) { - var vm = this; + let vm = this; - var typeahead; - var tagsHound; + let typeahead; + let tagsHound; vm.$onInit = onInit; vm.$onChanges = onChanges; @@ -52,50 +52,52 @@ vm.isLoading = false; + //ensure that the models are formatted correctly configureViewModel(); // Set the visible prompt to -1 to ensure it will not be visible vm.promptIsVisible = "-1"; tagsHound = new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), + initialize: false, + identify: function (obj) { return obj.id; }, + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('text'), queryTokenizer: Bloodhound.tokenizers.whitespace, //pre-fetch the tags for this category prefetch: { url: umbRequestHelper.getApiUrl("tagsDataBaseUrl", "GetTags", { tagGroup: vm.config.group, culture: vm.culture }), //TTL = 5 minutes - ttl: 300000, - transform: dataTransform + ttl: 300000 }, //dynamically get the tags for this category (they may have changed on the server) remote: { - url: umbRequestHelper.getApiUrl("tagsDataBaseUrl", "GetTags", { tagGroup: vm.config.group, culture: vm.culture }), - transform: dataTransform + url: umbRequestHelper.getApiUrl("tagsDataBaseUrl", "GetTags", { tagGroup: vm.config.group, culture: vm.culture, query: "%QUERY" }), + wildcard: "%QUERY" } }); - tagsHound.initialize(true); - - //configure the type ahead - $timeout(function () { + tagsHound.initialize().then(function() { + //configure the type ahead + var sources = { //see: https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md#options - // name = the data set name, we'll make this the tag group name - name: vm.config.group, - display: "value", + // name = the data set name, we'll make this the tag group name + culture + name: vm.config.group + (vm.culture ? vm.culture : ""), + display: "text", //source: tagsHound - source: function (query, cb) { + source: function (query, syncCallback, asyncCallback) { tagsHound.search(query, function(suggestions) { - cb(removeCurrentTagsFromSuggestions(suggestions)); + syncCallback(removeCurrentTagsFromSuggestions(suggestions)); + }, function(suggestions) { + asyncCallback(removeCurrentTagsFromSuggestions(suggestions)); }); } }; var opts = { - //This causes some strangeness as it duplicates the textbox, best leave off for now. - hint: false, + hint: true, highlight: true, cacheKey: new Date(), // Force a cache refresh each time the control is initialized minLength: 1 @@ -104,28 +106,35 @@ typeahead = $element.find('.tags-' + vm.htmlId).typeahead(opts, sources) .bind("typeahead:selected", function (obj, datum, name) { angularHelper.safeApply($rootScope, function () { - addTagInternal(datum["value"]); + addTagInternal(datum["text"]); vm.tagToAdd = ""; // clear the typed text typeahead.typeahead('val', ''); }); }).bind("typeahead:autocompleted", function (obj, datum, name) { angularHelper.safeApply($rootScope, function () { - addTagInternal(datum["value"]); + addTagInternal(datum["text"]); vm.tagToAdd = ""; + // clear the typed text + typeahead.typeahead('val', ''); }); }).bind("typeahead:opened", function (obj) { }); - }); + }); + }); } + /** + * Watch for value changes + * @param {any} changes + */ function onChanges(changes) { - // watch for value changes externally + //when the model 'value' changes, sync the viewModel object if (changes.value) { if (!changes.value.isFirstChange() && changes.value.currentValue !== changes.value.previousValue) { @@ -166,6 +175,7 @@ }); updateModelValue(vm.viewModel); + } } else if (angular.isArray(vm.value)) { @@ -175,12 +185,10 @@ } function updateModelValue(val) { - if (val) { - vm.onValueChanged({ value: val }); - } - else { - vm.onValueChanged({ value: [] }); - } + + val = val ? val : []; + + vm.onValueChanged({ value: val }); reValidate(); } @@ -252,29 +260,19 @@ function hidePrompt() { vm.promptIsVisible = "-1"; } - - //helper method to format the data for bloodhound - function dataTransform(list) { - //transform the result to what bloodhound wants - var tagList = _.map(list, function (i) { - return { value: i.text }; - }); - // remove current tags from the list - return $.grep(tagList, function (tag) { - return ($.inArray(tag.value, vm.viewModel) === -1); - }); - } - + // helper method to remove current tags function removeCurrentTagsFromSuggestions(suggestions) { return $.grep(suggestions, function (suggestion) { - return ($.inArray(suggestion.value, vm.viewModel) === -1); + return ($.inArray(suggestion.text, vm.viewModel) === -1); }); } function reValidate() { - //this is required to re-validate - vm.tagEditorForm.tagCount.$setViewValue(vm.viewModel.length); + //this is required to re-validate for the mandatory validation + if (vm.tagEditorForm && vm.tagEditorForm.tagCount) { + vm.tagEditorForm.tagCount.$setViewValue(vm.viewModel.length); + } } } diff --git a/src/Umbraco.Web.UI.Client/src/less/buttons.less b/src/Umbraco.Web.UI.Client/src/less/buttons.less index a246ef585e..c6a8447342 100644 --- a/src/Umbraco.Web.UI.Client/src/less/buttons.less +++ b/src/Umbraco.Web.UI.Client/src/less/buttons.less @@ -201,7 +201,8 @@ input[type="button"] { } // Made for Umbraco, 2019 .btn-selection { - .buttonBackground(@pinkLight, ligthen(@pinkLight, 20%), @blueExtraDark, @blueDark); + @btnSelectionBackgroundHover: darken(@pinkLight, 10%); + .buttonBackground(@pinkLight, @btnSelectionBackgroundHover, @blueExtraDark, @blueDark); } // Made for Umbraco, 2019, used for buttons that has to stand back. .btn-white { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less index adaf45b4b0..cf407b667f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less @@ -5,7 +5,7 @@ .umb-layout-selector__active-layout { box-sizing: border-box; - border: 1px solid transparent; + border: 1px solid @inputBorder; cursor: pointer; height: 30px; width: 30px; @@ -16,7 +16,7 @@ } .umb-layout-selector__active-layout:hover { - border-color: @gray-8; + border-color: @inputBorderFocus; } .umb-layout-selector__dropdown { 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 c1687636d3..291eef43e0 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 @@ -90,13 +90,13 @@ input.umb-table__input { .umb-table-body .umb-table-row { color: @gray-5; - border-top: 1px solid @gray-8; + border-top: 1px solid @gray-9; cursor: pointer; font-size: 14px; position: relative; min-height: 52px; &:hover { - background-color: @gray-10; + background-color: @ui-option-hover; } } @@ -151,12 +151,26 @@ input.umb-table__input { // Show checkmark when checked, hide file icon .umb-table-row--selected { + /* .umb-table-body__fileicon { display: none; } .umb-table-body__checkicon { display: inline-block; } + */ + &::before { + content: ""; + position: absolute; + z-index:1; + top: 1px; + left: 1px; + right: 1px; + bottom: 1px; + border: 2px solid @ui-selected-border; + box-shadow: 0 0 2px 0 fade(@ui-selected-border, 80%); + pointer-events: none; + } } // Table Row Styles diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js index a61930f877..688ac7693f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js @@ -1,12 +1,9 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.TagsController", - function ($scope, angularHelper) { + function ($scope) { $scope.valueChanged = function(value) { $scope.model.value = value; - // the model value seems to be a reference to the same array, so we need - // to set the form as dirty explicitly when the content of the array changes - angularHelper.getCurrentForm($scope).$setDirty(); } } diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 83bb152e0a..954491eab9 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -259,7 +259,7 @@ namespace Umbraco.Web.Editors }, { "tagsDataBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetTags("", "")) + controller => controller.GetTags("", "", null)) }, { "examineMgmtBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index ca747e83f5..915b73e39a 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -43,12 +43,16 @@ namespace Umbraco.Web.Editors public class EntityController : UmbracoAuthorizedJsonController { private readonly ITreeService _treeService; + private readonly UmbracoTreeSearcher _treeSearcher; + private readonly SearchableTreeCollection _searchableTreeCollection; public EntityController(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, - ITreeService treeService) + ITreeService treeService, SearchableTreeCollection searchableTreeCollection, UmbracoTreeSearcher treeSearcher) : base(globalSettings, umbracoContext, sqlContext, services, appCaches, logger, runtimeState) { _treeService = treeService; + _searchableTreeCollection = searchableTreeCollection; + _treeSearcher = treeSearcher; } /// @@ -69,15 +73,6 @@ namespace Umbraco.Web.Editors } } - private readonly UmbracoTreeSearcher _treeSearcher; - private readonly SearchableTreeCollection _searchableTreeCollection; - - public EntityController(SearchableTreeCollection searchableTreeCollection, UmbracoTreeSearcher treeSearcher) - { - _searchableTreeCollection = searchableTreeCollection; - _treeSearcher = treeSearcher; - } - /// /// Returns an Umbraco alias given a string /// diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index f4a8dfbd56..338e38fbe6 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -592,11 +592,6 @@ namespace Umbraco.Web.Editors if (builtInAliases.Contains(p.Alias) == false && valueMapped != null) { p.SetValue(valueMapped.GetValue()); - - // FIXME: /task - ok, I give up, at that point tags are dead here, until we figure it out - // p.TagChanges.Behavior = valueMapped.TagChanges.Behavior; - // p.TagChanges.Enable = valueMapped.TagChanges.Enable; - // p.TagChanges.Tags = valueMapped.TagChanges.Tags; } } } diff --git a/src/Umbraco.Web/PropertyEditors/TagsDataController.cs b/src/Umbraco.Web/PropertyEditors/TagsDataController.cs index 15c39bf994..7d699077c3 100644 --- a/src/Umbraco.Web/PropertyEditors/TagsDataController.cs +++ b/src/Umbraco.Web/PropertyEditors/TagsDataController.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; @@ -15,10 +17,28 @@ namespace Umbraco.Web.PropertyEditors [PluginController("UmbracoApi")] public class TagsDataController : UmbracoAuthorizedApiController { - public IEnumerable GetTags(string tagGroup, string culture) + /// + /// Returns all tags matching tagGroup, culture and an optional query + /// + /// + /// + /// + /// + public IEnumerable GetTags(string tagGroup, string culture, string query = null) { if (culture == string.Empty) culture = null; - return Umbraco.TagQuery.GetAllTags(tagGroup, culture); + + var result = Umbraco.TagQuery.GetAllTags(tagGroup, culture); + + + if (!query.IsNullOrWhiteSpace()) + { + //TODO: add the query to TagQuery + the tag service, this is ugly but all we can do for now. + //currently we are post filtering this :( but works for now + result = result.Where(x => x.Text.InvariantContains(query)); + } + + return result.OrderBy(x => x.Text); } } }