From d153bc3551a5aa0cb45da30ab268658f4efe7f3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 18 Nov 2019 11:58:40 +0100 Subject: [PATCH 01/20] Do not copy keys from NestedContent properties, instead we trust they will be created if they dont exists. which they do. --- .../src/common/services/clipboard.service.js | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js index c3a1ba6432..0184f2a1e7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js @@ -53,11 +53,39 @@ function clipboardService(notificationsService, eventsService, localStorageServi return false; } + function removeKeys(values) { + for (var i = 0; i < values.length; i++) { + var obj = values[i]; + + delete obj.key; + delete obj.$$hashKey; + + // Loop through all properties: + for (var k in obj) { + // if this property is an array, we need to check if there's more keys to remove. + if (Array.isArray(obj[k])) { + removeKeys(obj[k]) + } + } + } + } + var prepareEntryForStorage = function(entryData) { var shallowCloneData = Object.assign({}, entryData);// Notice only a shallow copy, since we dont need to deep copy. (that will happen when storing the data) delete shallowCloneData.key; delete shallowCloneData.$$hashKey; + + // remove keys from sub-entries + for (var t = 0; t < shallowCloneData.variants[0].tabs.length; t++) { + var tab = shallowCloneData.variants[0].tabs[t]; + for (var p = 0; p < tab.properties.length; p++) { + var prop = tab.properties[p]; + if (prop.view === "nestedcontent") { + removeKeys(prop.value); + } + } + } return shallowCloneData; } @@ -84,15 +112,19 @@ function clipboardService(notificationsService, eventsService, localStorageServi * @param {string} alias A string defining the alias of the data to store, example: 'product' * @param {object} entry A object containing the properties to be saved, this could be the object of a ElementType, ContentNode, ... * @param {string} displayLabel (optional) A string swetting the label to display when showing paste entries. + * @param {string} displayIcon (optional) A string setting the icon to display when showing paste entries. + * @param {string} uniqueKey (optional) A string prodiving an identifier for this entry, existing entries with this key will be removed to ensure that you only have the latest copy of this data. * * @description * Saves a single JS-object with a type and alias to the clipboard. */ - service.copy = function(type, alias, data, displayLabel) { + service.copy = function(type, alias, data, displayLabel, displayIcon, uniqueKey) { var storage = retriveStorage(); - var uniqueKey = data.key || data.$$hashKey || console.error("missing unique key for this content"); + displayLabel = displayLabel || data.name; + displayIcon = displayIcon || iconHelper.convertFromLegacyIcon(data.icon); + uniqueKey = uniqueKey || data.key || data.$$hashKey || console.error("missing unique key for this content"); // remove previous copies of this entry: storage.entries = storage.entries.filter( @@ -101,7 +133,7 @@ function clipboardService(notificationsService, eventsService, localStorageServi } ); - var entry = {unique:uniqueKey, type:type, alias:alias, data:prepareEntryForStorage(data), label:displayLabel || data.name, icon:iconHelper.convertFromLegacyIcon(data.icon)}; + var entry = {unique:uniqueKey, type:type, alias:alias, data:prepareEntryForStorage(data), label:displayLabel, icon:displayIcon}; storage.entries.push(entry); if (saveStorage(storage) === true) { From 882ba8d270763a23ae2253b76f8ce4b626a4a43e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 18 Nov 2019 13:21:18 +0100 Subject: [PATCH 02/20] only nested content properties should be handled, for now... --- .../src/common/services/clipboard.service.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js index 0184f2a1e7..ec2a754420 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js @@ -56,15 +56,16 @@ function clipboardService(notificationsService, eventsService, localStorageServi function removeKeys(values) { for (var i = 0; i < values.length; i++) { var obj = values[i]; - - delete obj.key; - delete obj.$$hashKey; - // Loop through all properties: - for (var k in obj) { - // if this property is an array, we need to check if there's more keys to remove. - if (Array.isArray(obj[k])) { - removeKeys(obj[k]) + // Entires with this property are entries of a nested content property. And those keys we can remove. + if (obj.ncContentTypeAlias) { + delete obj.key; + // Loop through all properties: + for (var k in obj) { + // if this property is an array, we need to check if there's more keys to remove. + if (Array.isArray(obj[k])) { + removeKeys(obj[k]) + } } } } From 220278a322eee03e70d5a7f82e4a3e1b02863b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 26 May 2020 13:48:26 +0200 Subject: [PATCH 03/20] abstracting property clearing, so each property editor can registrer their own clear property resolvers. --- .../src/common/services/clipboard.service.js | 69 +++++++++++-------- .../nestedcontent/nestedcontent.controller.js | 63 ++++++++++++++++- 2 files changed, 99 insertions(+), 33 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js index ec2a754420..083b4e86b7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js @@ -12,6 +12,9 @@ */ function clipboardService(notificationsService, eventsService, localStorageService, iconHelper) { + + var clearPropertyResolvers = []; + var STORAGE_KEY = "umbClipboardService"; @@ -53,42 +56,32 @@ function clipboardService(notificationsService, eventsService, localStorageServi return false; } - function removeKeys(values) { - for (var i = 0; i < values.length; i++) { - var obj = values[i]; - // Entires with this property are entries of a nested content property. And those keys we can remove. - if (obj.ncContentTypeAlias) { - delete obj.key; - // Loop through all properties: - for (var k in obj) { - // if this property is an array, we need to check if there's more keys to remove. - if (Array.isArray(obj[k])) { - removeKeys(obj[k]) - } - } - } + function clearPropertyForStorage(prop) { + + for (var i=0; i prepareEntryForStorage(data)); + var copiedDatas = datas.map(data => prepareEntryForStorage(data, firstLevelClearupMethod)); // remove previous copies of this entry: storage.entries = storage.entries.filter( diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 7cad5a5f05..066cbd6c17 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -1,6 +1,58 @@ (function () { 'use strict'; + /** + * When performing a copy, we do copy the ElementType Data Model, but each inner Nested Content property is still stored as the Nested Content Model, aka. each property is just storing its value. To handle this we need to ensure we handle both scenarios. + */ + + + angular.module('umbraco').run(['clipboardService', function (clipboardService) { + + function clearNestedContentPropertiesForStorage(prop, propClearingMethod) { + + // if prop.editor is "Umbraco.NestedContent" + if ((typeof prop === 'object' && prop.editor === "Umbraco.NestedContent")) { + + var value = prop.value; + for (var i = 0; i < value.length; i++) { + var obj = value[i]; + + // remove the key + delete obj.key; + + // Loop through all inner properties: + for (var k in obj) { + propClearingMethod(obj[k]); + } + } + } + } + + clipboardService.registrerClearPropertyResolver(clearNestedContentPropertiesForStorage) + + + function clearInnerNestedContentPropertiesForStorage(prop, propClearingMethod) { + + // if we got an array, and it has a entry with ncContentTypeAlias this meants that we are dealing with a NestedContent property inside a NestedContent property. + if ((Array.isArray(prop) && prop.length > 0 && prop[0].ncContentTypeAlias !== undefined)) { + + for (var i = 0; i < prop.length; i++) { + var obj = prop[i]; + + // remove the key + delete obj.key; + + // Loop through all inner properties: + for (var k in obj) { + propClearingMethod(obj[k]); + } + } + } + } + + clipboardService.registrerClearPropertyResolver(clearInnerNestedContentPropertiesForStorage) + }]); + angular .module('umbraco') .component('nestedContentPropertyEditor', { @@ -13,7 +65,7 @@ } }); - function NestedContentController($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService, $routeParams, editorState) { + function NestedContentController($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService) { var vm = this; var model = $scope.$parent.$parent.model; @@ -76,7 +128,7 @@ } localizationService.localize("clipboard_labelForArrayOfItemsFrom", [model.label, nodeName]).then(function (data) { - clipboardService.copyArray("elementTypeArray", aliases, vm.nodes, data, "icon-thumbnail-list", model.id); + clipboardService.copyArray("elementTypeArray", aliases, vm.nodes, data, "icon-thumbnail-list", model.id, clearNodeForCopy); }); } @@ -385,6 +437,11 @@ }); } + function clearNodeForCopy(clonedData) { + delete clonedData.key; + delete clonedData.$$hashKey; + } + vm.showCopy = clipboardService.isSupported(); vm.showPaste = false; @@ -392,7 +449,7 @@ syncCurrentNode(); - clipboardService.copy("elementType", node.contentTypeAlias, node); + clipboardService.copy("elementType", node.contentTypeAlias, node, null, null, null, clearNodeForCopy); $event.stopPropagation(); } From e434b01386e52650d17cab4fe51aa2213716c287 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 26 May 2020 14:43:57 +0100 Subject: [PATCH 04/20] Adds in NC Property Component to deal with creating/adding missing keys --- .../Compose/NestedContentPropertyComponent.cs | 174 ++++++++++++++++++ .../Compose/NestedContentPropertyComposer.cs | 9 + src/Umbraco.Web/Umbraco.Web.csproj | 2 + 3 files changed, 185 insertions(+) create mode 100644 src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs create mode 100644 src/Umbraco.Web/Compose/NestedContentPropertyComposer.cs diff --git a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs new file mode 100644 index 0000000000..28c2a288f8 --- /dev/null +++ b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs @@ -0,0 +1,174 @@ +using System; +using System.Linq; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Events; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; +using Umbraco.Web.PropertyEditors; + +namespace Umbraco.Web.Compose +{ + public class NestedContentPropertyComponent : IComponent + { + public void Initialize() + { + ContentService.Copying += ContentService_Copying; + ContentService.Saving += ContentService_Saving; + ContentService.Publishing += ContentService_Publishing; + } + + private void ContentService_Copying(IContentService sender, CopyEventArgs e) + { + // When a content node contains nested content property + // Check if the copied node contains a nested content + var nestedContentProps = e.Copy.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent); + + // Each NC Property on a doctype + foreach (var nestedContentProp in nestedContentProps) + { + // A NC Prop may have one or more values due to cultures + var propVals = nestedContentProp.Values; + foreach (var cultureVal in propVals) + { + // Remove keys from published value & any nested NC's + var updatedPublishedVal = CreateNewNestedContentKeys(cultureVal.PublishedValue.ToString()); + cultureVal.PublishedValue = updatedPublishedVal; + + // Remove keys from edited/draft value & any nested NC's + var updatedEditedVal = CreateNewNestedContentKeys(cultureVal.EditedValue.ToString()); + cultureVal.EditedValue = updatedEditedVal; + } + } + } + + private void ContentService_Saving(IContentService sender, ContentSavingEventArgs e) + { + // One or more content nodes could be saved in a bulk publish + foreach(var entity in e.SavedEntities) + { + // When a content node contains nested content property + // Check if the copied node contains a nested content + var nestedContentProps = entity.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent); + + // Each NC Property on a doctype + foreach (var nestedContentProp in nestedContentProps) + { + // A NC Prop may have one or more values due to cultures + var propVals = nestedContentProp.Values; + foreach (var cultureVal in propVals) + { + // Remove keys from published value & any nested NC's + var updatedPublishedVal = CreateMissingNestedContentKeys(cultureVal.PublishedValue.ToString()); + cultureVal.PublishedValue = updatedPublishedVal; + + // Remove keys from edited/draft value & any nested NC's + var updatedEditedVal = CreateMissingNestedContentKeys(cultureVal.EditedValue.ToString()); + cultureVal.EditedValue = updatedEditedVal; + } + } + } + } + + private void ContentService_Publishing(IContentService sender, ContentPublishingEventArgs e) + { + // One or more content nodes could be saved in a bulk publish + foreach (var entity in e.PublishedEntities) + { + // When a content node contains nested content property + // Check if the copied node contains a nested content + var nestedContentProps = entity.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent); + + // Each NC Property on a doctype + foreach (var nestedContentProp in nestedContentProps) + { + // A NC Prop may have one or more values due to cultures + var propVals = nestedContentProp.Values; + foreach (var cultureVal in propVals) + { + // Remove keys from published value & any nested NC's + var updatedPublishedVal = CreateMissingNestedContentKeys(cultureVal.PublishedValue.ToString()); + cultureVal.PublishedValue = updatedPublishedVal; + + // Remove keys from edited/draft value & any nested NC's + var updatedEditedVal = CreateMissingNestedContentKeys(cultureVal.EditedValue.ToString()); + cultureVal.EditedValue = updatedEditedVal; + } + } + } + } + + public void Terminate() + { + ContentService.Copying -= ContentService_Copying; + ContentService.Saving -= ContentService_Saving; + ContentService.Publishing -= ContentService_Publishing; + } + + private string CreateNewNestedContentKeys(string ncJson) + { + // Try & convert JSON to JArray (two props we will know should exist are key & ncContentTypeAlias) + var ncItems = JArray.Parse(ncJson); + + // NC prop contains one or more items/rows of things + foreach (var nestedContentItem in ncItems.Children()) + { + foreach (var ncItemProp in nestedContentItem.Properties()) + { + if(ncItemProp.Name.ToLowerInvariant() == "key") + ncItemProp.Value = Guid.NewGuid().ToString(); + + // No need to check this property for JSON - as this is a JSON prop we know + // That onyl contains the string of the doctype alias used as the NC item + if (ncItemProp.Name == NestedContentPropertyEditor.ContentTypeAliasPropertyKey) + continue; + + // As we don't know what properties in the JSON may contain the nested NC + // We are detecting if its value stores JSON to help filter the list AND that in its JSON it has ncContentTypeAlias prop + if (ncItemProp.Value.ToString().DetectIsJson() && ncItemProp.Value.ToString().Contains(NestedContentPropertyEditor.ContentTypeAliasPropertyKey)) + { + // Recurse & update this JSON property + ncItemProp.Value = CreateNewNestedContentKeys(ncItemProp.Value.ToString()); + } + } + } + + return ncItems.ToString(); + } + + private string CreateMissingNestedContentKeys(string ncJson) + { + // Try & convert JSON to JArray (two props we will know should exist are key & ncContentTypeAlias) + var ncItems = JArray.Parse(ncJson); + + // NC prop contains one or more items/rows of things + foreach (var nestedContentItem in ncItems.Children()) + { + var ncKeyProp = nestedContentItem.Properties().SingleOrDefault(x => x.Name.ToLowerInvariant() == "key"); + if(ncKeyProp == null) + { + nestedContentItem.Properties().Append(new JProperty("key", Guid.NewGuid().ToString())); + } + + foreach (var ncItemProp in nestedContentItem.Properties()) + { + // No need to check this property for JSON (Its the key OR ncContentTypeAlias) which has no JSON + if (ncItemProp.Name == NestedContentPropertyEditor.ContentTypeAliasPropertyKey || ncItemProp.Name.ToLowerInvariant() == "key") + continue; + + // As we don't know what properties in the JSON may contain the nested NC + // We are detecting if its value stores JSON to help filter the list AND that in its JSON it has ncContentTypeAlias prop + if (ncItemProp.Value.ToString().DetectIsJson() && ncItemProp.Value.ToString().Contains(NestedContentPropertyEditor.ContentTypeAliasPropertyKey)) + { + // Recurse & update this JSON property + ncItemProp.Value = CreateMissingNestedContentKeys(ncItemProp.Value.ToString()); + } + } + } + + return ncItems.ToString(); + } + } +} diff --git a/src/Umbraco.Web/Compose/NestedContentPropertyComposer.cs b/src/Umbraco.Web/Compose/NestedContentPropertyComposer.cs new file mode 100644 index 0000000000..4c9d9dee1c --- /dev/null +++ b/src/Umbraco.Web/Compose/NestedContentPropertyComposer.cs @@ -0,0 +1,9 @@ +using Umbraco.Core; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Compose +{ + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public class NestedContentPropertyComposer : ComponentComposer, ICoreComposer + { } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e39687bed8..c06ec574c5 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -129,6 +129,7 @@ + @@ -237,6 +238,7 @@ + From 23fb9a2b58c44f009ba8d3728242abb8dc8b0603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 27 May 2020 11:14:30 +0200 Subject: [PATCH 05/20] inject LocalStorageModel --- src/Umbraco.Web.UI.Client/test/config/app.unit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/test/config/app.unit.js b/src/Umbraco.Web.UI.Client/test/config/app.unit.js index 1f49d237e6..9e265215dd 100644 --- a/src/Umbraco.Web.UI.Client/test/config/app.unit.js +++ b/src/Umbraco.Web.UI.Client/test/config/app.unit.js @@ -13,8 +13,8 @@ var app = angular.module('umbraco', [ 'ngSanitize', //'ngMessages', - 'tmh.dynamicLocale' + 'tmh.dynamicLocale', //'ngFileUpload', - //'LocalStorageModule', + 'LocalStorageModule' //'chart.js' ]); From ff30a2769733db85e9f48dfcf8ee7523adf06e5a Mon Sep 17 00:00:00 2001 From: Claus Date: Thu, 28 May 2020 08:41:32 +0200 Subject: [PATCH 06/20] adding missing nullchecks. --- .../Compose/NestedContentPropertyComponent.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs index 28c2a288f8..bff821358d 100644 --- a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs +++ b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs @@ -34,11 +34,11 @@ namespace Umbraco.Web.Compose foreach (var cultureVal in propVals) { // Remove keys from published value & any nested NC's - var updatedPublishedVal = CreateNewNestedContentKeys(cultureVal.PublishedValue.ToString()); + var updatedPublishedVal = CreateNewNestedContentKeys(cultureVal.PublishedValue?.ToString()); cultureVal.PublishedValue = updatedPublishedVal; // Remove keys from edited/draft value & any nested NC's - var updatedEditedVal = CreateNewNestedContentKeys(cultureVal.EditedValue.ToString()); + var updatedEditedVal = CreateNewNestedContentKeys(cultureVal.EditedValue?.ToString()); cultureVal.EditedValue = updatedEditedVal; } } @@ -61,11 +61,11 @@ namespace Umbraco.Web.Compose foreach (var cultureVal in propVals) { // Remove keys from published value & any nested NC's - var updatedPublishedVal = CreateMissingNestedContentKeys(cultureVal.PublishedValue.ToString()); + var updatedPublishedVal = CreateMissingNestedContentKeys(cultureVal.PublishedValue?.ToString()); cultureVal.PublishedValue = updatedPublishedVal; // Remove keys from edited/draft value & any nested NC's - var updatedEditedVal = CreateMissingNestedContentKeys(cultureVal.EditedValue.ToString()); + var updatedEditedVal = CreateMissingNestedContentKeys(cultureVal.EditedValue?.ToString()); cultureVal.EditedValue = updatedEditedVal; } } @@ -89,11 +89,11 @@ namespace Umbraco.Web.Compose foreach (var cultureVal in propVals) { // Remove keys from published value & any nested NC's - var updatedPublishedVal = CreateMissingNestedContentKeys(cultureVal.PublishedValue.ToString()); + var updatedPublishedVal = CreateMissingNestedContentKeys(cultureVal.PublishedValue?.ToString()); cultureVal.PublishedValue = updatedPublishedVal; // Remove keys from edited/draft value & any nested NC's - var updatedEditedVal = CreateMissingNestedContentKeys(cultureVal.EditedValue.ToString()); + var updatedEditedVal = CreateMissingNestedContentKeys(cultureVal.EditedValue?.ToString()); cultureVal.EditedValue = updatedEditedVal; } } @@ -109,6 +109,9 @@ namespace Umbraco.Web.Compose private string CreateNewNestedContentKeys(string ncJson) { + if (string.IsNullOrWhiteSpace(ncJson)) + return ncJson; + // Try & convert JSON to JArray (two props we will know should exist are key & ncContentTypeAlias) var ncItems = JArray.Parse(ncJson); @@ -140,6 +143,9 @@ namespace Umbraco.Web.Compose private string CreateMissingNestedContentKeys(string ncJson) { + if (string.IsNullOrWhiteSpace(ncJson)) + return ncJson; + // Try & convert JSON to JArray (two props we will know should exist are key & ncContentTypeAlias) var ncItems = JArray.Parse(ncJson); From c9ab2ca9ef8b19d3a0f1d408b2601193504cc9cb Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 28 May 2020 11:45:43 +0100 Subject: [PATCH 07/20] Use InvariantEquals --- src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs index bff821358d..e41c14d94b 100644 --- a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs +++ b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs @@ -120,7 +120,7 @@ namespace Umbraco.Web.Compose { foreach (var ncItemProp in nestedContentItem.Properties()) { - if(ncItemProp.Name.ToLowerInvariant() == "key") + if(ncItemProp.Name.InvariantEquals("key")) ncItemProp.Value = Guid.NewGuid().ToString(); // No need to check this property for JSON - as this is a JSON prop we know From c81268995ceaa352acbe55359783925d2be19f23 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 28 May 2020 12:06:04 +0100 Subject: [PATCH 08/20] Remove code duplication --- .../Compose/NestedContentPropertyComponent.cs | 77 +++++++------------ 1 file changed, 29 insertions(+), 48 deletions(-) diff --git a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs index e41c14d94b..28b503fc9b 100644 --- a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs +++ b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs @@ -34,11 +34,11 @@ namespace Umbraco.Web.Compose foreach (var cultureVal in propVals) { // Remove keys from published value & any nested NC's - var updatedPublishedVal = CreateNewNestedContentKeys(cultureVal.PublishedValue?.ToString()); + var updatedPublishedVal = CreateNestedContentKeys(cultureVal.PublishedValue?.ToString(), false); cultureVal.PublishedValue = updatedPublishedVal; // Remove keys from edited/draft value & any nested NC's - var updatedEditedVal = CreateNewNestedContentKeys(cultureVal.EditedValue?.ToString()); + var updatedEditedVal = CreateNestedContentKeys(cultureVal.EditedValue?.ToString(), false); cultureVal.EditedValue = updatedEditedVal; } } @@ -61,11 +61,11 @@ namespace Umbraco.Web.Compose foreach (var cultureVal in propVals) { // Remove keys from published value & any nested NC's - var updatedPublishedVal = CreateMissingNestedContentKeys(cultureVal.PublishedValue?.ToString()); + var updatedPublishedVal = CreateNestedContentKeys(cultureVal.PublishedValue?.ToString(), true); cultureVal.PublishedValue = updatedPublishedVal; // Remove keys from edited/draft value & any nested NC's - var updatedEditedVal = CreateMissingNestedContentKeys(cultureVal.EditedValue?.ToString()); + var updatedEditedVal = CreateNestedContentKeys(cultureVal.EditedValue?.ToString(), true); cultureVal.EditedValue = updatedEditedVal; } } @@ -89,11 +89,11 @@ namespace Umbraco.Web.Compose foreach (var cultureVal in propVals) { // Remove keys from published value & any nested NC's - var updatedPublishedVal = CreateMissingNestedContentKeys(cultureVal.PublishedValue?.ToString()); + var updatedPublishedVal = CreateNestedContentKeys(cultureVal.PublishedValue?.ToString(), true); cultureVal.PublishedValue = updatedPublishedVal; // Remove keys from edited/draft value & any nested NC's - var updatedEditedVal = CreateMissingNestedContentKeys(cultureVal.EditedValue?.ToString()); + var updatedEditedVal = CreateNestedContentKeys(cultureVal.EditedValue?.ToString(), true); cultureVal.EditedValue = updatedEditedVal; } } @@ -107,21 +107,36 @@ namespace Umbraco.Web.Compose ContentService.Publishing -= ContentService_Publishing; } - private string CreateNewNestedContentKeys(string ncJson) + private string CreateNestedContentKeys(string ncJson, bool onlyMissingKeys) { if (string.IsNullOrWhiteSpace(ncJson)) return ncJson; - // Try & convert JSON to JArray (two props we will know should exist are key & ncContentTypeAlias) + // Convert JSON to JArray (two props we will know should exist are key & ncContentTypeAlias) var ncItems = JArray.Parse(ncJson); // NC prop contains one or more items/rows of things foreach (var nestedContentItem in ncItems.Children()) { + // If saving/publishing - we only generate keys for NC items that are missing + if (onlyMissingKeys) + { + var ncKeyProp = nestedContentItem.Properties().SingleOrDefault(x => x.Name.ToLowerInvariant() == "key"); + if (ncKeyProp == null) + { + nestedContentItem.Properties().Append(new JProperty("key", Guid.NewGuid().ToString())); + } + } + + foreach (var ncItemProp in nestedContentItem.Properties()) { - if(ncItemProp.Name.InvariantEquals("key")) - ncItemProp.Value = Guid.NewGuid().ToString(); + if(onlyMissingKeys == false) + { + // Only when copying a node - we generate new keys for all NC items + if (ncItemProp.Name.InvariantEquals("key")) + ncItemProp.Value = Guid.NewGuid().ToString(); + } // No need to check this property for JSON - as this is a JSON prop we know // That onyl contains the string of the doctype alias used as the NC item @@ -130,46 +145,12 @@ namespace Umbraco.Web.Compose // As we don't know what properties in the JSON may contain the nested NC // We are detecting if its value stores JSON to help filter the list AND that in its JSON it has ncContentTypeAlias prop - if (ncItemProp.Value.ToString().DetectIsJson() && ncItemProp.Value.ToString().Contains(NestedContentPropertyEditor.ContentTypeAliasPropertyKey)) + var ncItemPropVal = ncItemProp.Value?.ToString(); + + if (ncItemPropVal.DetectIsJson() && ncItemPropVal.Contains(NestedContentPropertyEditor.ContentTypeAliasPropertyKey)) { // Recurse & update this JSON property - ncItemProp.Value = CreateNewNestedContentKeys(ncItemProp.Value.ToString()); - } - } - } - - return ncItems.ToString(); - } - - private string CreateMissingNestedContentKeys(string ncJson) - { - if (string.IsNullOrWhiteSpace(ncJson)) - return ncJson; - - // Try & convert JSON to JArray (two props we will know should exist are key & ncContentTypeAlias) - var ncItems = JArray.Parse(ncJson); - - // NC prop contains one or more items/rows of things - foreach (var nestedContentItem in ncItems.Children()) - { - var ncKeyProp = nestedContentItem.Properties().SingleOrDefault(x => x.Name.ToLowerInvariant() == "key"); - if(ncKeyProp == null) - { - nestedContentItem.Properties().Append(new JProperty("key", Guid.NewGuid().ToString())); - } - - foreach (var ncItemProp in nestedContentItem.Properties()) - { - // No need to check this property for JSON (Its the key OR ncContentTypeAlias) which has no JSON - if (ncItemProp.Name == NestedContentPropertyEditor.ContentTypeAliasPropertyKey || ncItemProp.Name.ToLowerInvariant() == "key") - continue; - - // As we don't know what properties in the JSON may contain the nested NC - // We are detecting if its value stores JSON to help filter the list AND that in its JSON it has ncContentTypeAlias prop - if (ncItemProp.Value.ToString().DetectIsJson() && ncItemProp.Value.ToString().Contains(NestedContentPropertyEditor.ContentTypeAliasPropertyKey)) - { - // Recurse & update this JSON property - ncItemProp.Value = CreateMissingNestedContentKeys(ncItemProp.Value.ToString()); + ncItemProp.Value = CreateNestedContentKeys(ncItemPropVal, onlyMissingKeys); } } } From 732d574ec2170a1c53801827dc7271b1ee6029f5 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 28 May 2020 21:35:39 +0100 Subject: [PATCH 09/20] Tidy up & better checking for NC JSON with JSONPath & SelectTokens() Less code duplication. No need for Publishing event as Saving event is called before publishing --- .../Compose/NestedContentPropertyComponent.cs | 129 +++++++----------- 1 file changed, 48 insertions(+), 81 deletions(-) diff --git a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs index 28b503fc9b..6e4fdb386e 100644 --- a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs +++ b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; using Umbraco.Core; @@ -17,7 +18,6 @@ namespace Umbraco.Web.Compose { ContentService.Copying += ContentService_Copying; ContentService.Saving += ContentService_Saving; - ContentService.Publishing += ContentService_Publishing; } private void ContentService_Copying(IContentService sender, CopyEventArgs e) @@ -25,7 +25,29 @@ namespace Umbraco.Web.Compose // When a content node contains nested content property // Check if the copied node contains a nested content var nestedContentProps = e.Copy.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent); + UpdateNestedContentProperties(nestedContentProps, false); + } + private void ContentService_Saving(IContentService sender, ContentSavingEventArgs e) + { + // One or more content nodes could be saved in a bulk publish + foreach (var entity in e.SavedEntities) + { + // When a content node contains nested content property + // Check if the copied node contains a nested content + var nestedContentProps = entity.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent); + UpdateNestedContentProperties(nestedContentProps, true); + } + } + + public void Terminate() + { + ContentService.Copying -= ContentService_Copying; + ContentService.Saving -= ContentService_Saving; + } + + private void UpdateNestedContentProperties(IEnumerable nestedContentProps, bool onlyMissingKeys) + { // Each NC Property on a doctype foreach (var nestedContentProp in nestedContentProps) { @@ -34,104 +56,40 @@ namespace Umbraco.Web.Compose foreach (var cultureVal in propVals) { // Remove keys from published value & any nested NC's - var updatedPublishedVal = CreateNestedContentKeys(cultureVal.PublishedValue?.ToString(), false); + var updatedPublishedVal = CreateNestedContentKeys(cultureVal.PublishedValue?.ToString(), onlyMissingKeys); cultureVal.PublishedValue = updatedPublishedVal; // Remove keys from edited/draft value & any nested NC's - var updatedEditedVal = CreateNestedContentKeys(cultureVal.EditedValue?.ToString(), false); + var updatedEditedVal = CreateNestedContentKeys(cultureVal.EditedValue?.ToString(), onlyMissingKeys); cultureVal.EditedValue = updatedEditedVal; } } } - private void ContentService_Saving(IContentService sender, ContentSavingEventArgs e) + private string CreateNestedContentKeys(string rawJson, bool onlyMissingKeys) { - // One or more content nodes could be saved in a bulk publish - foreach(var entity in e.SavedEntities) - { - // When a content node contains nested content property - // Check if the copied node contains a nested content - var nestedContentProps = entity.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent); + if (string.IsNullOrWhiteSpace(rawJson)) + return rawJson; - // Each NC Property on a doctype - foreach (var nestedContentProp in nestedContentProps) - { - // A NC Prop may have one or more values due to cultures - var propVals = nestedContentProp.Values; - foreach (var cultureVal in propVals) - { - // Remove keys from published value & any nested NC's - var updatedPublishedVal = CreateNestedContentKeys(cultureVal.PublishedValue?.ToString(), true); - cultureVal.PublishedValue = updatedPublishedVal; - - // Remove keys from edited/draft value & any nested NC's - var updatedEditedVal = CreateNestedContentKeys(cultureVal.EditedValue?.ToString(), true); - cultureVal.EditedValue = updatedEditedVal; - } - } - } - } - - private void ContentService_Publishing(IContentService sender, ContentPublishingEventArgs e) - { - // One or more content nodes could be saved in a bulk publish - foreach (var entity in e.PublishedEntities) - { - // When a content node contains nested content property - // Check if the copied node contains a nested content - var nestedContentProps = entity.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent); - - // Each NC Property on a doctype - foreach (var nestedContentProp in nestedContentProps) - { - // A NC Prop may have one or more values due to cultures - var propVals = nestedContentProp.Values; - foreach (var cultureVal in propVals) - { - // Remove keys from published value & any nested NC's - var updatedPublishedVal = CreateNestedContentKeys(cultureVal.PublishedValue?.ToString(), true); - cultureVal.PublishedValue = updatedPublishedVal; - - // Remove keys from edited/draft value & any nested NC's - var updatedEditedVal = CreateNestedContentKeys(cultureVal.EditedValue?.ToString(), true); - cultureVal.EditedValue = updatedEditedVal; - } - } - } - } - - public void Terminate() - { - ContentService.Copying -= ContentService_Copying; - ContentService.Saving -= ContentService_Saving; - ContentService.Publishing -= ContentService_Publishing; - } - - private string CreateNestedContentKeys(string ncJson, bool onlyMissingKeys) - { - if (string.IsNullOrWhiteSpace(ncJson)) - return ncJson; - - // Convert JSON to JArray (two props we will know should exist are key & ncContentTypeAlias) - var ncItems = JArray.Parse(ncJson); + // Parse JSON + var ncJson = JToken.Parse(rawJson); // NC prop contains one or more items/rows of things - foreach (var nestedContentItem in ncItems.Children()) + foreach (var nestedContentItem in ncJson.Children()) { // If saving/publishing - we only generate keys for NC items that are missing if (onlyMissingKeys) { - var ncKeyProp = nestedContentItem.Properties().SingleOrDefault(x => x.Name.ToLowerInvariant() == "key"); + var ncKeyProp = nestedContentItem.Properties().SingleOrDefault(x => x.Name.InvariantEquals("key")); if (ncKeyProp == null) { nestedContentItem.Properties().Append(new JProperty("key", Guid.NewGuid().ToString())); } } - foreach (var ncItemProp in nestedContentItem.Properties()) { - if(onlyMissingKeys == false) + if (onlyMissingKeys == false) { // Only when copying a node - we generate new keys for all NC items if (ncItemProp.Name.InvariantEquals("key")) @@ -139,23 +97,32 @@ namespace Umbraco.Web.Compose } // No need to check this property for JSON - as this is a JSON prop we know - // That onyl contains the string of the doctype alias used as the NC item + // That only contains the string of the doctype alias used as the NC item if (ncItemProp.Name == NestedContentPropertyEditor.ContentTypeAliasPropertyKey) continue; - // As we don't know what properties in the JSON may contain the nested NC + // As we don't know what properties in the JSON may contain other complex editors or deep nested NC // We are detecting if its value stores JSON to help filter the list AND that in its JSON it has ncContentTypeAlias prop var ncItemPropVal = ncItemProp.Value?.ToString(); - if (ncItemPropVal.DetectIsJson() && ncItemPropVal.Contains(NestedContentPropertyEditor.ContentTypeAliasPropertyKey)) + if (ncItemPropVal.DetectIsJson()) { - // Recurse & update this JSON property - ncItemProp.Value = CreateNestedContentKeys(ncItemPropVal, onlyMissingKeys); + // Parse the nested JSON (complex editor) + var complexEditorJson = JToken.Parse(ncItemPropVal); + + // Verify the complex editor is nested content (Will have one or more ncContentTypeAlias) + // One for each NC item/row + var hasNestedContentJsonProp = complexEditorJson.SelectTokens($"$..['{NestedContentPropertyEditor.ContentTypeAliasPropertyKey}']", false); + if (hasNestedContentJsonProp.Count() > 0) + { + // Recurse & update this JSON property + ncItemProp.Value = CreateNestedContentKeys(ncItemPropVal, onlyMissingKeys); + } } } } - return ncItems.ToString(); + return ncJson.ToString(); } } } From 2565a626cb60cf108e3b254e50c2d02e1935dea1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 29 May 2020 13:01:56 +1000 Subject: [PATCH 10/20] Fixes NC key replacement and adds tests --- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../Compose/NestedContentPropertyComponent.cs | 86 +++++++++---------- 2 files changed, 43 insertions(+), 44 deletions(-) diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 3c359cdde8..731dc05363 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -148,6 +148,7 @@ + diff --git a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs index 6e4fdb386e..5794a2734e 100644 --- a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs +++ b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs @@ -66,63 +66,61 @@ namespace Umbraco.Web.Compose } } - private string CreateNestedContentKeys(string rawJson, bool onlyMissingKeys) + + // internal for tests + internal string CreateNestedContentKeys(string rawJson, bool onlyMissingKeys, Func createGuid = null) { - if (string.IsNullOrWhiteSpace(rawJson)) + // used so we can test nicely + if (createGuid == null) + createGuid = () => Guid.NewGuid(); + + if (string.IsNullOrWhiteSpace(rawJson) || !rawJson.DetectIsJson()) return rawJson; - // Parse JSON - var ncJson = JToken.Parse(rawJson); + // Parse JSON + var complexEditorValue = JToken.Parse(rawJson); - // NC prop contains one or more items/rows of things - foreach (var nestedContentItem in ncJson.Children()) + UpdateNestedContentKeysRecursively(complexEditorValue, onlyMissingKeys, createGuid); + + return complexEditorValue.ToString(); + } + + private void UpdateNestedContentKeysRecursively(JToken json, bool onlyMissingKeys, Func createGuid) + { + // check if this is NC + var isNestedContent = json.SelectTokens($"$..['{NestedContentPropertyEditor.ContentTypeAliasPropertyKey}']", false).Any(); + + // select all values (flatten) + var allProperties = json.SelectTokens("$..*").OfType().Select(x => x.Parent as JProperty).WhereNotNull().ToList(); + foreach (var prop in allProperties) { - // If saving/publishing - we only generate keys for NC items that are missing - if (onlyMissingKeys) + if (prop.Name == NestedContentPropertyEditor.ContentTypeAliasPropertyKey) { - var ncKeyProp = nestedContentItem.Properties().SingleOrDefault(x => x.Name.InvariantEquals("key")); - if (ncKeyProp == null) + // get it's sibling 'key' property + var ncKeyVal = prop.Parent["key"] as JValue; + // TODO: This bool seems odd, if the key is null, shouldn't we fill it in regardless of onlyMissingKeys? + if ((onlyMissingKeys && ncKeyVal == null) || (!onlyMissingKeys && ncKeyVal != null)) { - nestedContentItem.Properties().Append(new JProperty("key", Guid.NewGuid().ToString())); - } + // create or replace + prop.Parent["key"] = createGuid().ToString(); + } } - - foreach (var ncItemProp in nestedContentItem.Properties()) + else if (!isNestedContent || prop.Name != "key") { - if (onlyMissingKeys == false) + // this is an arbitrary property that could contain a nested complex editor + var propVal = prop.Value?.ToString(); + // check if this might contain a nested NC + if (!propVal.IsNullOrWhiteSpace() && propVal.DetectIsJson() && propVal.InvariantContains(NestedContentPropertyEditor.ContentTypeAliasPropertyKey)) { - // Only when copying a node - we generate new keys for all NC items - if (ncItemProp.Name.InvariantEquals("key")) - ncItemProp.Value = Guid.NewGuid().ToString(); - } - - // No need to check this property for JSON - as this is a JSON prop we know - // That only contains the string of the doctype alias used as the NC item - if (ncItemProp.Name == NestedContentPropertyEditor.ContentTypeAliasPropertyKey) - continue; - - // As we don't know what properties in the JSON may contain other complex editors or deep nested NC - // We are detecting if its value stores JSON to help filter the list AND that in its JSON it has ncContentTypeAlias prop - var ncItemPropVal = ncItemProp.Value?.ToString(); - - if (ncItemPropVal.DetectIsJson()) - { - // Parse the nested JSON (complex editor) - var complexEditorJson = JToken.Parse(ncItemPropVal); - - // Verify the complex editor is nested content (Will have one or more ncContentTypeAlias) - // One for each NC item/row - var hasNestedContentJsonProp = complexEditorJson.SelectTokens($"$..['{NestedContentPropertyEditor.ContentTypeAliasPropertyKey}']", false); - if (hasNestedContentJsonProp.Count() > 0) - { - // Recurse & update this JSON property - ncItemProp.Value = CreateNestedContentKeys(ncItemPropVal, onlyMissingKeys); - } + // recurse + var parsed = JToken.Parse(propVal); + UpdateNestedContentKeysRecursively(parsed, onlyMissingKeys, createGuid); + // set the value to the updated one + prop.Value = parsed.ToString(); } } } - - return ncJson.ToString(); } + } } From b1d0cbf1d0a99479cdf7a0ce0c75d5442869155e Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 29 May 2020 09:30:34 +0100 Subject: [PATCH 11/20] Remove the CSProj reference to missing test file in repo - until we get a chance to add it back in #8199 --- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 731dc05363..3c359cdde8 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -148,7 +148,6 @@ - From 180b858ff58afe407028aafa14117b337c57bc85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 29 May 2020 14:09:20 +0200 Subject: [PATCH 12/20] V8/bugfix/gulp update inline views on watch (#8200) --- src/Umbraco.Web.UI.Client/gulp/config.js | 12 +++++++++--- src/Umbraco.Web.UI.Client/gulp/modes.js | 12 +++++++++++- src/Umbraco.Web.UI.Client/gulp/tasks/test.js | 17 +++++++++++++++-- .../gulp/tasks/watchTask.js | 19 ++++++++++--------- .../gulp/util/processJs.js | 4 +++- src/Umbraco.Web.UI.Client/gulpfile.js | 10 +++++----- 6 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/gulp/config.js b/src/Umbraco.Web.UI.Client/gulp/config.js index 39dc9bb2a4..3c32832ba0 100755 --- a/src/Umbraco.Web.UI.Client/gulp/config.js +++ b/src/Umbraco.Web.UI.Client/gulp/config.js @@ -3,10 +3,16 @@ module.exports = { compile: { build: { - sourcemaps: false + sourcemaps: false, + embedtemplates: true }, dev: { - sourcemaps: true + sourcemaps: true, + embedtemplates: true + }, + test: { + sourcemaps: false, + embedtemplates: true } }, sources: { @@ -17,7 +23,7 @@ module.exports = { installer: { files: "./src/less/installer.less", watch: "./src/less/**/*.less", out: "installer.css" }, nonodes: { files: "./src/less/pages/nonodes.less", watch: "./src/less/**/*.less", out: "nonodes.style.min.css"}, preview: { files: "./src/less/canvas-designer.less", watch: "./src/less/**/*.less", out: "canvasdesigner.css" }, - umbraco: { files: "./src/less/belle.less", watch: "./src/less/**/*.less", out: "umbraco.css" }, + umbraco: { files: "./src/less/belle.less", watch: "./src/**/*.less", out: "umbraco.css" }, rteContent: { files: "./src/less/rte-content.less", watch: "./src/less/**/*.less", out: "rte-content.css" } }, diff --git a/src/Umbraco.Web.UI.Client/gulp/modes.js b/src/Umbraco.Web.UI.Client/gulp/modes.js index dc2947f2cc..21609cdcf8 100644 --- a/src/Umbraco.Web.UI.Client/gulp/modes.js +++ b/src/Umbraco.Web.UI.Client/gulp/modes.js @@ -10,4 +10,14 @@ function setDevelopmentMode(cb) { return cb(); }; -module.exports = { setDevelopmentMode: setDevelopmentMode }; +function setTestMode(cb) { + + config.compile.current = config.compile.test; + + return cb(); +}; + +module.exports = { + setDevelopmentMode: setDevelopmentMode, + setTestMode: setTestMode + }; diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/test.js b/src/Umbraco.Web.UI.Client/gulp/tasks/test.js index 1e8d074f7e..255fe17435 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/test.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/test.js @@ -6,11 +6,24 @@ var karmaServer = require('karma').Server; * Build tests **************************/ - // Karma test +// Karma test function testUnit() { + return new karmaServer({ + configFile: __dirname + "/../../test/config/karma.conf.js" + }) + .start(); +}; + +// Run karma test server +function runUnitTestServer() { + return new karmaServer({ configFile: __dirname + "/../../test/config/karma.conf.js", + autoWatch: true, + port: 9999, + singleRun: false, + browsers: ['ChromeDebugging'], keepalive: true }) .start(); @@ -24,4 +37,4 @@ function testE2e() { .start(); }; -module.exports = { testUnit: testUnit, testE2e: testE2e }; +module.exports = { testUnit: testUnit, testE2e: testE2e, runUnitTestServer: runUnitTestServer }; diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js b/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js index 24a6e65540..f8e2570ff9 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js @@ -1,7 +1,7 @@ 'use strict'; const config = require('../config'); -const {watch, parallel, dest, src} = require('gulp'); +const {watch, series, parallel, dest, src} = require('gulp'); var _ = require('lodash'); var MergeStream = require('merge-stream'); @@ -9,9 +9,7 @@ var MergeStream = require('merge-stream'); var processJs = require('../util/processJs'); var processLess = require('../util/processLess'); -//const { less } = require('./less'); -//const { views } = require('./views'); - +var {js} = require('./js'); function watchTask(cb) { @@ -35,11 +33,14 @@ function watchTask(cb) { var viewWatcher; _.forEach(config.sources.views, function (group) { if(group.watch !== false) { - viewWatcher = watch(group.files, { ignoreInitial: true, interval: watchInterval }); - viewWatcher.on('change', function(path, stats) { - console.log("copying " + group.files + " to " + config.root + config.targets.views + group.folder); - src(group.files).pipe( dest(config.root + config.targets.views + group.folder) ); - }); + viewWatcher = watch(group.files, { ignoreInitial: true, interval: watchInterval }, + parallel( + function MoveViewsAndRegenerateJS() { + return src(group.files).pipe( dest(config.root + config.targets.views + group.folder) ); + }, + js + ) + ); } }); diff --git a/src/Umbraco.Web.UI.Client/gulp/util/processJs.js b/src/Umbraco.Web.UI.Client/gulp/util/processJs.js index e3e393b661..67dd6dd420 100644 --- a/src/Umbraco.Web.UI.Client/gulp/util/processJs.js +++ b/src/Umbraco.Web.UI.Client/gulp/util/processJs.js @@ -25,7 +25,9 @@ module.exports = function (files, out) { .pipe(sort()); //in production, embed the templates - task = task.pipe(embedTemplates({ basePath: "./src/", minimize: { loose: true } })) + if(config.compile.current.embedtemplates === true) { + task = task.pipe(embedTemplates({ basePath: "./src/", minimize: { loose: true } })); + } task = task.pipe(concat(out)) .pipe(wrap('(function(){\n%= body %\n})();')) diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 705c54bf04..542d45c479 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -13,11 +13,11 @@ const { src, dest, series, parallel, lastRun } = require('gulp'); const config = require('./gulp/config'); -const { setDevelopmentMode } = require('./gulp/modes'); +const { setDevelopmentMode, setTestMode } = require('./gulp/modes'); const { dependencies } = require('./gulp/tasks/dependencies'); const { js } = require('./gulp/tasks/js'); const { less } = require('./gulp/tasks/less'); -const { testE2e, testUnit } = require('./gulp/tasks/test'); +const { testE2e, testUnit, runUnitTestServer } = require('./gulp/tasks/test'); const { views } = require('./gulp/tasks/views'); const { watchTask } = require('./gulp/tasks/watchTask'); @@ -31,6 +31,6 @@ exports.build = series(parallel(dependencies, js, less, views), testUnit); exports.dev = series(setDevelopmentMode, parallel(dependencies, js, less, views), watchTask); exports.watch = series(watchTask); // -exports.runTests = series(js, testUnit); -exports.testUnit = series(testUnit); -exports.testE2e = series(testE2e); +exports.runTests = series(setTestMode, parallel(js, testUnit)); +exports.runUnit = series(setTestMode, parallel(js, runUnitTestServer), watchTask); +exports.testE2e = series(setTestMode, parallel(testE2e)); From 33850b13d0933f36624e8f75211c16335e544c5d Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Fri, 29 May 2020 13:39:14 +0100 Subject: [PATCH 13/20] If GetControllerTypeInternal falls back to creating an instance, it should also release it (#8196) --- src/Umbraco.Web/Mvc/ControllerFactoryExtensions.cs | 5 ++++- src/Umbraco.Web/Mvc/MasterControllerFactory.cs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Mvc/ControllerFactoryExtensions.cs b/src/Umbraco.Web/Mvc/ControllerFactoryExtensions.cs index 07a3f2a5e3..eb39cb8faa 100644 --- a/src/Umbraco.Web/Mvc/ControllerFactoryExtensions.cs +++ b/src/Umbraco.Web/Mvc/ControllerFactoryExtensions.cs @@ -25,7 +25,10 @@ namespace Umbraco.Web.Mvc //we have no choice but to instantiate the controller var instance = factory.CreateController(requestContext, controllerName); - return instance?.GetType(); + var controllerType = instance?.GetType(); + factory.ReleaseController(instance); + + return controllerType; } } } diff --git a/src/Umbraco.Web/Mvc/MasterControllerFactory.cs b/src/Umbraco.Web/Mvc/MasterControllerFactory.cs index 6d2f1ce501..79d0a9aa63 100644 --- a/src/Umbraco.Web/Mvc/MasterControllerFactory.cs +++ b/src/Umbraco.Web/Mvc/MasterControllerFactory.cs @@ -81,7 +81,10 @@ namespace Umbraco.Web.Mvc //we have no choice but to instantiate the controller var instance = factory.CreateController(requestContext, controllerName); - return instance?.GetType(); + var controllerType = instance?.GetType(); + factory.ReleaseController(instance); + + return controllerType; } return GetControllerType(requestContext, controllerName); From 208df92fe0ae188f669e46e6e6b60c02ba370a70 Mon Sep 17 00:00:00 2001 From: BatJan Date: Wed, 27 May 2020 23:13:33 +0200 Subject: [PATCH 14/20] Hide icons and add missing screen reader friendly texts --- .../src/views/components/umb-confirm-action.html | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 b45a3bb843..1b88c9f988 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 @@ -7,10 +7,16 @@ on-outside-click="clickCancel()"> From 74a4d769834403484f5631b0cde1b551f423e769 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Fri, 29 May 2020 14:41:20 +0200 Subject: [PATCH 15/20] umb-lightbox improvements (#8191) --- .../components/umblightbox.directive.js | 4 +-- .../src/less/components/umb-lightbox.less | 20 +++++++++++--- .../src/views/components/umb-lightbox.html | 27 ++++++++++++------- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js index 19a33a8351..8d223e427e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js @@ -12,9 +12,9 @@
-
- -
+
@@ -11,12 +14,18 @@
-
- -
+ -
- -
+
From c23b813036a308fd7f731c1741dbf814afc88177 Mon Sep 17 00:00:00 2001 From: rbottema Date: Wed, 27 May 2020 20:31:24 +0200 Subject: [PATCH 16/20] Fix HTML markup of includeproperties.prevalues.html I've noticed this span was missing a closing >, so I added it. --- .../propertyeditors/listview/includeproperties.prevalues.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c6675ccec8..0499bea713 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 @@ -9,7 +9,7 @@ - +
From f7cbac30b18ec45bb2bd93b15db5e992ea087698 Mon Sep 17 00:00:00 2001 From: rbottema Date: Wed, 27 May 2020 20:15:35 +0200 Subject: [PATCH 17/20] Fix HTML markup of umb-list-view-settings.html I've noticed this file was missing a closing div tag, so I added it. --- .../src/views/components/umb-list-view-settings.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-list-view-settings.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-list-view-settings.html index 8b4aeba3a0..4e2edbe2a9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-list-view-settings.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-list-view-settings.html @@ -44,6 +44,6 @@ aria-label="Edit"> - + From 717bbf1e2e33ce5eb3ce1e11fe26454df864f173 Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Sat, 30 May 2020 21:02:26 +0100 Subject: [PATCH 18/20] umb-notifications.html - refactored switch logic (#8207) * umb-notifications.html - refactored switch logic I couldn't get the `url` property to render, so I've swapped out the `ng-switch` statement with a couple of `ng-if` conditions. * umb-notifications.html - changed quotes from single to double For consistency. --- .../components/notifications/umb-notifications.html | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/notifications/umb-notifications.html b/src/Umbraco.Web.UI.Client/src/views/components/notifications/umb-notifications.html index 769cd422ef..872fdbe41f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/notifications/umb-notifications.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/notifications/umb-notifications.html @@ -7,18 +7,19 @@
-
- - {{notification.headline}} + +
+ + -
- {{notification.headline}} +
+
- From 4d497053a3f51a02c18aa9392db0c9855e4ec7be Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 2 Jun 2020 09:08:43 +0100 Subject: [PATCH 19/20] Fix up typos & mark incorrect as deprecated --- .../src/common/services/clipboard.service.js | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js index 083b4e86b7..d7d6ec862c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js @@ -97,9 +97,26 @@ function clipboardService(notificationsService, eventsService, localStorageServi var service = {}; + /** * @ngdoc method - * @name umbraco.services.clipboardService#registrerPropertyClearingResolver + * @name umbraco.services.clipboardService#registrerClearPropertyResolver + * @methodOf umbraco.services.clipboardService + * + * @param {string} function A method executed for every property and inner properties copied. + * + * @description + * Executed for all properties including inner properties when performing a copy action. + * + * @deprecated Incorrect spelling please use 'registerClearPropertyResolver' + */ + service.registrerClearPropertyResolver = function(resolver) { + this.registerClearPropertyResolver(resolver); + }; + + /** + * @ngdoc method + * @name umbraco.services.clipboardService#registerClearPropertyResolver * @methodOf umbraco.services.clipboardService * * @param {string} function A method executed for every property and inner properties copied. @@ -107,7 +124,7 @@ function clipboardService(notificationsService, eventsService, localStorageServi * @description * Executed for all properties including inner properties when performing a copy action. */ - service.registrerClearPropertyResolver = function(resolver) { + service.registerClearPropertyResolver = function(resolver) { clearPropertyResolvers.push(resolver); }; From a4274ba76727273eb8d7cfb02b97fc5bdcd3e9cc Mon Sep 17 00:00:00 2001 From: Chad Date: Tue, 2 Jun 2020 21:35:19 +1200 Subject: [PATCH 20/20] Shorter JSON property names for nucache (#8204) --- .../ContentTypeServiceVariantsTests.cs | 70 +++++++++---------- .../NuCache/DataSource/ContentNestedData.cs | 19 ++++- .../NuCache/DataSource/CultureVariation.cs | 21 ++++-- .../NuCache/DataSource/PropertyData.cs | 29 +++++++- 4 files changed, 94 insertions(+), 45 deletions(-) diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 0fe05f385f..9391b7442f 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -88,8 +88,8 @@ namespace Umbraco.Tests.Services private void AssertJsonStartsWith(int id, string expected) { var json = GetJson(id).Replace('"', '\''); - var pos = json.IndexOf("'cultureData':", StringComparison.InvariantCultureIgnoreCase); - json = json.Substring(0, pos + "'cultureData':".Length); + var pos = json.IndexOf("'cd':", StringComparison.InvariantCultureIgnoreCase); + json = json.Substring(0, pos + "'cd':".Length); Assert.AreEqual(expected, json); } @@ -595,7 +595,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':"); // switch content type to Nothing contentType.Variations = ContentVariation.Nothing; @@ -612,7 +612,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'v':'v1en'}],'value2':[{'v':'v2'}]},'cd':"); // switch content back to Culture contentType.Variations = ContentVariation.Culture; @@ -629,7 +629,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'v':'v1en'}],'value2':[{'v':'v2'}]},'cd':"); // switch property back to Culture contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture; @@ -645,7 +645,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':"); } [Test] @@ -686,7 +686,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'v':'v1'}],'value2':[{'v':'v2'}]},'cd':"); // switch content type to Culture contentType.Variations = ContentVariation.Culture; @@ -702,7 +702,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'v':'v1'}],'value2':[{'v':'v2'}]},'cd':"); // switch property to Culture contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture; @@ -717,7 +717,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'c':'en','v':'v1'}],'value2':[{'v':'v2'}]},'cd':"); // switch content back to Nothing contentType.Variations = ContentVariation.Nothing; @@ -734,7 +734,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'v':'v1'}],'value2':[{'v':'v2'}]},'cd':"); } [Test] @@ -772,7 +772,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':"); // switch property type to Nothing contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing; @@ -789,7 +789,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'v':'v1en'}],'value2':[{'v':'v2'}]},'cd':"); // switch property back to Culture contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture; @@ -805,7 +805,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':"); // switch other property to Culture contentType.PropertyTypes.First(x => x.Alias == "value2").Variations = ContentVariation.Culture; @@ -823,7 +823,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'en','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'c':'en','v':'v2'}]},'cd':"); } [TestCase(ContentVariation.Culture, ContentVariation.Nothing)] @@ -1054,7 +1054,7 @@ namespace Umbraco.Tests.Services // both value11 and value21 are variant Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); composed.Variations = ContentVariation.Nothing; ServiceContext.ContentTypeService.Save(composed); @@ -1062,7 +1062,7 @@ namespace Umbraco.Tests.Services // both value11 and value21 are invariant Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':"); composed.Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composed); @@ -1070,7 +1070,7 @@ namespace Umbraco.Tests.Services // value11 is variant again, but value21 is still invariant Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':"); composed.PropertyTypes.First(x => x.Alias == "value21").Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composed); @@ -1078,7 +1078,7 @@ namespace Umbraco.Tests.Services // we can make it variant again Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); composing.Variations = ContentVariation.Nothing; ServiceContext.ContentTypeService.Save(composing); @@ -1086,7 +1086,7 @@ namespace Umbraco.Tests.Services // value11 is invariant Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); composing.Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composing); @@ -1094,7 +1094,7 @@ namespace Umbraco.Tests.Services // value11 is still invariant Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); composing.PropertyTypes.First(x => x.Alias == "value11").Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composing); @@ -1102,7 +1102,7 @@ namespace Umbraco.Tests.Services // we can make it variant again Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); } [Test] @@ -1167,11 +1167,11 @@ namespace Umbraco.Tests.Services // both value11 and value21 are variant Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); composed1.Variations = ContentVariation.Nothing; ServiceContext.ContentTypeService.Save(composed1); @@ -1179,11 +1179,11 @@ namespace Umbraco.Tests.Services // both value11 and value21 are invariant Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); composed1.Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composed1); @@ -1191,11 +1191,11 @@ namespace Umbraco.Tests.Services // value11 is variant again, but value21 is still invariant Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); composed1.PropertyTypes.First(x => x.Alias == "value21").Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composed1); @@ -1203,11 +1203,11 @@ namespace Umbraco.Tests.Services // we can make it variant again Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); composing.Variations = ContentVariation.Nothing; ServiceContext.ContentTypeService.Save(composing); @@ -1215,11 +1215,11 @@ namespace Umbraco.Tests.Services // value11 is invariant Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); composing.Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composing); @@ -1227,11 +1227,11 @@ namespace Umbraco.Tests.Services // value11 is still invariant Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); composing.PropertyTypes.First(x => x.Alias == "value11").Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composing); @@ -1239,11 +1239,11 @@ namespace Umbraco.Tests.Services // we can make it variant again Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); } private void CreateFrenchAndEnglishLangs() diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentNestedData.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentNestedData.cs index adff28a2ba..ec5424ad9a 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentNestedData.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentNestedData.cs @@ -9,15 +9,28 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource /// internal class ContentNestedData { - [JsonProperty("properties")] + //dont serialize empty properties + [JsonProperty("pd")] [JsonConverter(typeof(CaseInsensitiveDictionaryConverter))] public Dictionary PropertyData { get; set; } - [JsonProperty("cultureData")] + [JsonProperty("cd")] [JsonConverter(typeof(CaseInsensitiveDictionaryConverter))] public Dictionary CultureData { get; set; } - [JsonProperty("urlSegment")] + [JsonProperty("us")] public string UrlSegment { get; set; } + + //Legacy properties used to deserialize existing nucache db entries + [JsonProperty("properties")] + [JsonConverter(typeof(CaseInsensitiveDictionaryConverter))] + private Dictionary LegacyPropertyData { set { PropertyData = value; } } + + [JsonProperty("cultureData")] + [JsonConverter(typeof(CaseInsensitiveDictionaryConverter))] + private Dictionary LegacyCultureData { set { CultureData = value; } } + + [JsonProperty("urlSegment")] + private string LegacyUrlSegment { set { UrlSegment = value; } } } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/CultureVariation.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/CultureVariation.cs index a98a96f424..57ffbba34e 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/CultureVariation.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/CultureVariation.cs @@ -8,16 +8,29 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource /// internal class CultureVariation { - [JsonProperty("name")] + [JsonProperty("nm")] public string Name { get; set; } - [JsonProperty("urlSegment")] + [JsonProperty("us")] public string UrlSegment { get; set; } - [JsonProperty("date")] + [JsonProperty("dt")] public DateTime Date { get; set; } - [JsonProperty("isDraft")] + [JsonProperty("isd")] public bool IsDraft { get; set; } + + //Legacy properties used to deserialize existing nucache db entries + [JsonProperty("name")] + private string LegacyName { set { Name = value; } } + + [JsonProperty("urlSegment")] + private string LegacyUrlSegment { set { UrlSegment = value; } } + + [JsonProperty("date")] + private DateTime LegacyDate { set { Date = value; } } + + [JsonProperty("isDraft")] + private bool LegacyIsDraft { set { IsDraft = value; } } } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/PropertyData.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/PropertyData.cs index e14426a2e2..4abcbc7e6f 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/PropertyData.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/PropertyData.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using Newtonsoft.Json; namespace Umbraco.Web.PublishedCache.NuCache.DataSource @@ -8,21 +9,43 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource private string _culture; private string _segment; - [JsonProperty("culture")] + [DefaultValue("")] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, PropertyName = "c")] public string Culture { get => _culture; set => _culture = value ?? throw new ArgumentNullException(nameof(value)); // TODO: or fallback to string.Empty? CANNOT be null } - [JsonProperty("seg")] + [DefaultValue("")] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, PropertyName = "s")] public string Segment { get => _segment; set => _segment = value ?? throw new ArgumentNullException(nameof(value)); // TODO: or fallback to string.Empty? CANNOT be null } - [JsonProperty("val")] + [JsonProperty("v")] public object Value { get; set; } + + + //Legacy properties used to deserialize existing nucache db entries + [JsonProperty("culture")] + private string LegacyCulture + { + set => Culture = value; + } + + [JsonProperty("seg")] + private string LegacySegment + { + set => Segment = value; + } + + [JsonProperty("val")] + private object LegacyValue + { + set => Value = value; + } } }