From 6c172702be06770566bb1271003f40903a2c7e11 Mon Sep 17 00:00:00 2001 From: Kevin Giszewski Date: Wed, 27 Sep 2017 11:32:36 -0400 Subject: [PATCH 01/28] Fix for U4-6309 --- .../src/views/propertyeditors/rte/rte.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index d17310d65a..6e70bbf3f7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -257,7 +257,7 @@ angular.module("umbraco") editor.on('ObjectResized', function (e) { - var qs = "?width=" + e.width + "&height=" + e.height; + var qs = "?width=" + e.width + "&height=" + e.height + "&mode=max"; var srcAttr = $(e.target).attr("src"); var path = srcAttr.split("?")[0]; $(e.target).attr("data-mce-src", path + qs); From a1cd22208ba544166736e241e21a196ab95738d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristoffer=20Tver=C3=A5?= Date: Fri, 12 Jan 2018 10:45:59 +0100 Subject: [PATCH 02/28] Extra check before assigning value to currentColor In version 7.7.7 with the latest version of LeBlender (1.0.9.2), colorpickers will throw a javascript error in console. Applying this small check will resolve that issue. ref: https://our.umbraco.org/forum/contributing-to-umbraco-cms/88890-color-picker-cannot-read-property-hasownproperty-of-null --- .../colorpicker/colorpicker.controller.js | 228 +++++++++--------- 1 file changed, 114 insertions(+), 114 deletions(-) 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 7d847dbc83..5f95120d46 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,46 +1,46 @@ -function ColorPickerController($scope) { - - $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0; - - if ($scope.isConfigured) { - - for (var key in $scope.model.config.items) { - if (!$scope.model.config.items[key].hasOwnProperty("value")) - $scope.model.config.items[key] = { value: $scope.model.config.items[key], label: $scope.model.config.items[key] }; - } - - $scope.model.useLabel = isTrue($scope.model.config.useLabel); - initActiveColor(); - } +function ColorPickerController($scope) { + + $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0; + + if ($scope.isConfigured) { + + for (var key in $scope.model.config.items) { + if (!$scope.model.config.items[key].hasOwnProperty("value")) + $scope.model.config.items[key] = { value: $scope.model.config.items[key], label: $scope.model.config.items[key] }; + } + + $scope.model.useLabel = isTrue($scope.model.config.useLabel); + initActiveColor(); + } + + $scope.toggleItem = function (color) { + + var currentColor = ($scope.model.value && $scope.model.value.hasOwnProperty("value")) + ? $scope.model.value.value + : $scope.model.value; - $scope.toggleItem = function (color) { - - var currentColor = $scope.model.value.hasOwnProperty("value") - ? $scope.model.value.value - : $scope.model.value; - var newColor; - if (currentColor === color.value) { - // deselect - $scope.model.value = $scope.model.useLabel ? { value: "", label: "" } : ""; - newColor = ""; - } - else { + if (currentColor === color.value) { + // deselect + $scope.model.value = $scope.model.useLabel ? { value: "", label: "" } : ""; + newColor = ""; + } + else { // select - $scope.model.value = $scope.model.useLabel ? { value: color.value, label: color.label } : color.value; + $scope.model.value = $scope.model.useLabel ? { value: color.value, label: color.label } : color.value; newColor = color.value; - } - - // this is required to re-validate - $scope.propertyForm.modelValue.$setViewValue(newColor); - }; + } + + // this is required to re-validate + $scope.propertyForm.modelValue.$setViewValue(newColor); + }; // Method required by the valPropertyValidator directive (returns true if the property editor has at least one color selected) - $scope.validateMandatory = function () { - var isValid = !$scope.model.validation.mandatory || ( - $scope.model.value != null - && $scope.model.value != "" - && (!$scope.model.value.hasOwnProperty("value") || $scope.model.value.value !== "") + $scope.validateMandatory = function () { + var isValid = !$scope.model.validation.mandatory || ( + $scope.model.value != null + && $scope.model.value != "" + && (!$scope.model.value.hasOwnProperty("value") || $scope.model.value.value !== "") ); return { isValid: isValid, @@ -48,83 +48,83 @@ function ColorPickerController($scope) { errorKey: "required" }; } - $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0; - - // A color is active if it matches the value and label of the model. - // If the model doesn't store the label, ignore the label during the comparison. - $scope.isActiveColor = function (color) { - - // no value - if (!$scope.model.value) - return false; - - // Complex color (value and label)? - if (!$scope.model.value.hasOwnProperty("value")) - return $scope.model.value === color.value; - - return $scope.model.value.value === color.value && $scope.model.value.label === color.label; - }; - - // Finds the color best matching the model's color, - // and sets the model color to that one. This is useful when - // either the value or label was changed on the data type. - function initActiveColor() { - - // no value - if (!$scope.model.value) - return; - - // Complex color (value and label)? - if (!$scope.model.value.hasOwnProperty("value")) - return; - - var modelColor = $scope.model.value.value; - var modelLabel = $scope.model.value.label; - - // Check for a full match or partial match. - var foundItem = null; - - // Look for a fully matching color. - for (var key in $scope.model.config.items) { - var item = $scope.model.config.items[key]; - if (item.value == modelColor && item.label == modelLabel) { - foundItem = item; - break; - } - } - - // Look for a color with a matching value. - if (!foundItem) { - for (var key in $scope.model.config.items) { - var item = $scope.model.config.items[key]; - if (item.value == modelColor) { - foundItem = item; - break; - } - } - } - - // Look for a color with a matching label. - if (!foundItem) { - for (var key in $scope.model.config.items) { - var item = $scope.model.config.items[key]; - if (item.label == modelLabel) { - foundItem = item; - break; - } - } - } - - // If a match was found, set it as the active color. - if (foundItem) { - $scope.model.value.value = foundItem.value; - $scope.model.value.label = foundItem.label; - } - } - - // figures out if a value is trueish enough - function isTrue(bool) { - return !!bool && bool !== "0" && angular.lowercase(bool) !== "false"; + $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0; + + // A color is active if it matches the value and label of the model. + // If the model doesn't store the label, ignore the label during the comparison. + $scope.isActiveColor = function (color) { + + // no value + if (!$scope.model.value) + return false; + + // Complex color (value and label)? + if (!$scope.model.value.hasOwnProperty("value")) + return $scope.model.value === color.value; + + return $scope.model.value.value === color.value && $scope.model.value.label === color.label; + }; + + // Finds the color best matching the model's color, + // and sets the model color to that one. This is useful when + // either the value or label was changed on the data type. + function initActiveColor() { + + // no value + if (!$scope.model.value) + return; + + // Complex color (value and label)? + if (!$scope.model.value.hasOwnProperty("value")) + return; + + var modelColor = $scope.model.value.value; + var modelLabel = $scope.model.value.label; + + // Check for a full match or partial match. + var foundItem = null; + + // Look for a fully matching color. + for (var key in $scope.model.config.items) { + var item = $scope.model.config.items[key]; + if (item.value == modelColor && item.label == modelLabel) { + foundItem = item; + break; + } + } + + // Look for a color with a matching value. + if (!foundItem) { + for (var key in $scope.model.config.items) { + var item = $scope.model.config.items[key]; + if (item.value == modelColor) { + foundItem = item; + break; + } + } + } + + // Look for a color with a matching label. + if (!foundItem) { + for (var key in $scope.model.config.items) { + var item = $scope.model.config.items[key]; + if (item.label == modelLabel) { + foundItem = item; + break; + } + } + } + + // If a match was found, set it as the active color. + if (foundItem) { + $scope.model.value.value = foundItem.value; + $scope.model.value.label = foundItem.label; + } + } + + // figures out if a value is trueish enough + function isTrue(bool) { + return !!bool && bool !== "0" && angular.lowercase(bool) !== "false"; } } From 87f09b2ee6c8edb09fa8feb4f497d1d9e15a804a Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 18 Jan 2018 10:49:18 +0000 Subject: [PATCH 03/28] U4-10845 MultiNodeTreePickerPropertyConverter - trashed content causes database lookups If the first ID in the MNTP value is a trashed content node, then the `ConvertSourceToObject` method would check if the ID is a media or member node, both of these incur a database call. This can cause a detrimental performance impact on the website. This patch uses the `startNode.type` prevalue to detect the node type, (e.g. Content, Media or Member). The prevalue is cached in a private `ConcurrentDictionary` and refreshed using the `DataTypeCacheRefresher` event. > _Following the same caching-pattern as the Slider, Tags and MediaPicker value-converters._ --- .../Cache/DataTypeCacheRefresher.cs | 1 + .../MultiNodeTreePickerPropertyConverter.cs | 113 ++++++++++++------ 2 files changed, 79 insertions(+), 35 deletions(-) diff --git a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs index 65d366e2ad..d28fe5cf88 100644 --- a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs @@ -117,6 +117,7 @@ namespace Umbraco.Web.Cache LegacyMediaPickerPropertyConverter.ClearCaches(); SliderValueConverter.ClearCaches(); MediaPickerPropertyConverter.ClearCaches(); + MultiNodeTreePickerPropertyConverter.ClearCaches(); base.Refresh(jsonPayload); diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerPropertyConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerPropertyConverter.cs index 249b7e0cac..abe444435f 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerPropertyConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerPropertyConverter.cs @@ -8,16 +8,18 @@ // -------------------------------------------------------------------------------------------------------------------- using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; -using Umbraco.Web.Extensions; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors.ValueConverters { @@ -32,6 +34,20 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters [PropertyValueCache(PropertyCacheValue.XPath, PropertyCacheLevel.Content)] public class MultiNodeTreePickerPropertyConverter : PropertyValueConverterBase { + private readonly IDataTypeService _dataTypeService; + + //TODO: Remove this ctor in v8 since the other one will use IoC + public MultiNodeTreePickerPropertyConverter() + : this(ApplicationContext.Current.Services.DataTypeService) + { } + + public MultiNodeTreePickerPropertyConverter(IDataTypeService dataTypeService) + : base() + { + if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); + _dataTypeService = dataTypeService; + } + /// /// The properties to exclude. /// @@ -125,6 +141,24 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters var umbHelper = new UmbracoHelper(UmbracoContext.Current); + Func contentFetcher; + + switch (GetPublishedContentType(propertyType.DataTypeId)) + { + case PublishedItemType.Media: + contentFetcher = umbHelper.TypedMember; + break; + + case PublishedItemType.Member: + contentFetcher = umbHelper.TypedMedia; + break; + + case PublishedItemType.Content: + default: + contentFetcher = umbHelper.TypedContent; + break; + } + if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MultiNodeTreePickerAlias)) { var nodeIds = (int[])source; @@ -133,14 +167,9 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters { var multiNodeTreePicker = new List(); - var objectType = UmbracoObjectTypes.Unknown; - foreach (var nodeId in nodeIds) { - var multiNodeTreePickerItem = - GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Document, umbHelper.TypedContent) - ?? GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Media, umbHelper.TypedMedia) - ?? GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Member, umbHelper.TypedMember); + var multiNodeTreePickerItem = contentFetcher(nodeId); if (multiNodeTreePickerItem != null) { @@ -163,14 +192,9 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters { var multiNodeTreePicker = new List(); - var objectType = UmbracoObjectTypes.Unknown; - foreach (var udi in udis) { - var multiNodeTreePickerItem = - GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Document, umbHelper.TypedContent) - ?? GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Media, umbHelper.TypedMedia) - ?? GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Member, umbHelper.TypedMember); + var multiNodeTreePickerItem = contentFetcher(udi); if (multiNodeTreePickerItem != null) { multiNodeTreePicker.Add(multiNodeTreePickerItem); @@ -186,31 +210,50 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return source; } - /// - /// Attempt to get an IPublishedContent instance based on ID and content type - /// - /// The content node ID - /// The type of content being requested - /// The type of content expected/supported by - /// A function to fetch content of type - /// The requested content, or null if either it does not exist or does not match - private IPublishedContent GetPublishedContent(T nodeId, ref UmbracoObjectTypes actualType, UmbracoObjectTypes expectedType, Func contentFetcher) + private PublishedItemType GetPublishedContentType(int dataTypeId) { - // is the actual type supported by the content fetcher? - if (actualType != UmbracoObjectTypes.Unknown && actualType != expectedType) - { - // no, return null - return null; - } + // GetPreValuesCollectionByDataTypeId is cached at repository level; + // still, the collection is deep-cloned so this is kinda expensive, + // better to cache here + trigger refresh in DataTypeCacheRefresher + // e.g. https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs#L116-L119 - // attempt to get the content - var content = contentFetcher(nodeId); - if (content != null) + return Storages.GetOrAdd(dataTypeId, id => { - // if we found the content, assign the expected type to the actual type so we don't have to keep looking for other types of content - actualType = expectedType; - } - return content; + var preValue = _dataTypeService + .GetPreValuesCollectionByDataTypeId(id) + .PreValuesAsDictionary + .FirstOrDefault(x => string.Equals(x.Key, "startNode", StringComparison.InvariantCultureIgnoreCase)) + .Value; + + if (preValue != null && string.IsNullOrWhiteSpace(preValue.Value) == false) + { + var data = JsonConvert.DeserializeAnonymousType(preValue.Value, new { type = string.Empty }); + if (data != null) + { + switch (data.type.ToUpperInvariant()) + { + case "MEDIA": + return PublishedItemType.Media; + + case "MEMBER": + return PublishedItemType.Member; + + case "CONTENT": + default: + return PublishedItemType.Content; + } + } + } + + return PublishedItemType.Content; + }); + } + + private static readonly ConcurrentDictionary Storages = new ConcurrentDictionary(); + + internal static void ClearCaches() + { + Storages.Clear(); } } } From 60ce36a7f254921b12019645f0ddf7fffe1226a0 Mon Sep 17 00:00:00 2001 From: Ian Houghton Date: Mon, 22 Jan 2018 22:03:35 +0000 Subject: [PATCH 04/28] U4-10864 add button when search returns no results, to clear the search --- .../src/controllers/search.controller.js | 5 +++-- .../views/components/application/umb-navigation.html | 12 ++++++++---- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 1 + 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js index 57ddb6babd..ef566ac4fa 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js @@ -14,7 +14,6 @@ function SearchController($scope, searchService, $log, $location, navigationServ $scope.isSearching = false; $scope.selectedResult = -1; - $scope.navigateResults = function (ev) { //38: up 40: down, 13: enter @@ -34,12 +33,14 @@ function SearchController($scope, searchService, $log, $location, navigationServ } }; - var group = undefined; var groupIndex = -1; var itemIndex = -1; $scope.selectedItem = undefined; + $scope.clearSearch = function () { + $scope.searchTerm = null; + }; function iterateResults(up) { //default group diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html index d3eed009c3..94cc589355 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html @@ -35,10 +35,14 @@ - - + + +
+