diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index a223704617..db34b5e984 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -26,8 +26,8 @@ not want this to happen as the alpha of the next major is, really, the next major already. --> - - + + diff --git a/build/NuSpecs/tools/Web.config.cloud.xdt b/build/NuSpecs/tools/Web.config.cloud.xdt new file mode 100644 index 0000000000..988c741126 --- /dev/null +++ b/build/NuSpecs/tools/Web.config.cloud.xdt @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/build.ps1 b/build/build.ps1 index 15d455b976..55ceb774ee 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -91,6 +91,7 @@ { $src = "$($this.SolutionRoot)\src" $log = "$($this.BuildTemp)\belle.log" + Write-Host "Compile Belle" Write-Host "Logging to $log" @@ -352,6 +353,14 @@ "-x!dotless.Core.*" "-x!Content_Types.xml" "-x!*.pdb" ` > $null if (-not $?) { throw "Failed to zip UmbracoCms." } + + Write-Host "Zip cms cloud" + $this.CopyFile("$($this.SolutionRoot)\build\NuSpecs\tools\Web.config.cloud.xdt", "$tmp\WebApp\Web.config.install.xdt") + &$this.BuildEnv.Zip a -r "$out\UmbracoCms.$($this.Version.Semver).Cloud.zip" ` + "$tmp\WebApp\*" ` + "-x!dotless.Core.*" "-x!Content_Types.xml" "-x!*.pdb" ` + > $null + if (-not $?) { throw "Failed to zip UmbracoCms." } }) $ubuild.DefineMethod("PrepareBuild", @@ -553,7 +562,6 @@ # run if (-not $get) { -cd if ($command.Length -eq 0) { $command = @( "Build" ) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 2dac2d8791..56e0376196 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.15.1")] -[assembly: AssemblyInformationalVersion("8.15.1")] +[assembly: AssemblyFileVersion("8.16.0")] +[assembly: AssemblyInformationalVersion("8.16.0-rc")] diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index a33e85c9a2..e996ef4111 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -642,6 +642,24 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { return $q.when(umbDataFormatter.formatContentGetData(result)); }); }, + + getScaffolds: function(parentId, aliases){ + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmptyByAliases"), + { parentId: parentId, contentTypeAliases: aliases } + ), + 'Failed to retrieve data for empty content item aliases ' + aliases.join(", ") + ).then(function(result) { + Object.keys(result).map(function(key){ + result[key] = umbDataFormatter.formatContentGetData(result[key]); + }); + + return $q.when(result); + }); + }, /** * @ngdoc method * @name umbraco.resources.contentResource#getScaffoldByKey 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 446fb8c076..64fc40d84d 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 @@ -522,10 +522,14 @@ ]; // Initialize - var scaffoldsLoaded = 0; vm.scaffolds = []; - _.each(model.config.contentTypes, function (contentType) { - contentResource.getScaffold(-20, contentType.ncAlias).then(function (scaffold) { + + contentResource.getScaffolds(-20, contentTypeAliases).then(function (scaffolds){ + // Loop through all the content types + _.each(model.config.contentTypes, function (contentType){ + // Get the scaffold from the result + var scaffold = scaffolds[contentType.ncAlias]; + // make sure it's an element type before allowing the user to create new ones if (scaffold.isElement) { // remove all tabs except the specified tab @@ -554,13 +558,10 @@ // Store the scaffold object vm.scaffolds.push(scaffold); } - - scaffoldsLoaded++; - initIfAllScaffoldsHaveLoaded(); - }, function (error) { - scaffoldsLoaded++; - initIfAllScaffoldsHaveLoaded(); }); + + // Initialize once all scaffolds have been loaded + initNestedContent(); }); /** @@ -586,57 +587,50 @@ }); } - var initIfAllScaffoldsHaveLoaded = function () { + var initNestedContent = function () { // Initialize when all scaffolds have loaded - if (model.config.contentTypes.length === scaffoldsLoaded) { - // Because we're loading the scaffolds async one at a time, we need to - // sort them explicitly according to the sort order defined by the data type. - contentTypeAliases = []; - _.each(model.config.contentTypes, function (contentType) { - contentTypeAliases.push(contentType.ncAlias); - }); - vm.scaffolds = $filter("orderBy")(vm.scaffolds, function (s) { - return contentTypeAliases.indexOf(s.contentTypeAlias); - }); + // Sort the scaffold explicitly according to the sort order defined by the data type. + vm.scaffolds = $filter("orderBy")(vm.scaffolds, function (s) { + return contentTypeAliases.indexOf(s.contentTypeAlias); + }); - // Convert stored nodes - if (model.value) { - for (var i = 0; i < model.value.length; i++) { - var item = model.value[i]; - var scaffold = getScaffold(item.ncContentTypeAlias); - if (scaffold == null) { - // No such scaffold - the content type might have been deleted. We need to skip it. - continue; - } - createNode(scaffold, item); + // Convert stored nodes + if (model.value) { + for (var i = 0; i < model.value.length; i++) { + var item = model.value[i]; + var scaffold = getScaffold(item.ncContentTypeAlias); + if (scaffold == null) { + // No such scaffold - the content type might have been deleted. We need to skip it. + continue; } + createNode(scaffold, item); } - - // Enforce min items if we only have one scaffold type - var modelWasChanged = false; - if (vm.nodes.length < vm.minItems && vm.scaffolds.length === 1) { - for (var i = vm.nodes.length; i < model.config.minItems; i++) { - addNode(vm.scaffolds[0].contentTypeAlias); - } - modelWasChanged = true; - } - - // If there is only one item, set it as current node - if (vm.singleMode || (vm.nodes.length === 1 && vm.maxItems === 1)) { - setCurrentNode(vm.nodes[0], false); - } - - validate(); - - vm.inited = true; - - if (modelWasChanged) { - updateModel(); - } - - updatePropertyActionStates(); - checkAbilityToPasteContent(); } + + // Enforce min items if we only have one scaffold type + var modelWasChanged = false; + if (vm.nodes.length < vm.minItems && vm.scaffolds.length === 1) { + for (var i = vm.nodes.length; i < model.config.minItems; i++) { + addNode(vm.scaffolds[0].contentTypeAlias); + } + modelWasChanged = true; + } + + // If there is only one item, set it as current node + if (vm.singleMode || (vm.nodes.length === 1 && vm.maxItems === 1)) { + setCurrentNode(vm.nodes[0], false); + } + + validate(); + + vm.inited = true; + + if (modelWasChanged) { + updateModel(); + } + + updatePropertyActionStates(); + checkAbilityToPasteContent(); } function extendPropertyWithNCData(prop) { diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 011b7e019f..024ce07c5f 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -348,9 +348,9 @@ False True - 8151 + 8160 / - http://localhost:8151 + http://localhost:8160 False False diff --git a/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json b/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json index 75797f78e7..01defd7e10 100644 --- a/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json +++ b/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json @@ -272,9 +272,9 @@ "view": "nodename" }, { - "element": "[data-element='editor-content'] [data-element='property-welcomeText']", + "element": "[data-element='editor-content'] [data-element='property-welcomeText'] > div", "title": "Add a welcome text", - "content": "

Add content to the Welcome Text field.

If you don't have any ideas here is a start:

I am learning Umbraco. High Five I Rock #H5IR
.

" + "content": "

Add content to the Welcome Text field.

If you don't have any ideas here is a start:

I am learning Umbraco. High Five I Rock #H5IR

" }, { "element": "[data-element='editor-content'] [data-element='button-saveAndPublish']", diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 8293d1274f..d7f1d81c77 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -364,6 +364,24 @@ namespace Umbraco.Web.Editors return GetEmpty(contentType, parentId); } + /// + /// Gets a dictionary containing empty content items for every alias specified in the contentTypeAliases array in the body of the request. + /// + /// + /// This is a post request in order to support a large amount of aliases without hitting the URL length limit. + /// + /// + /// + [OutgoingEditorModelEvent] + [HttpPost] + public IDictionary GetEmptyByAliases(ContentTypesByAliases contentTypesByAliases) + { + // It's important to do this operation within a scope to reduce the amount of readlock queries. + using var scope = _scopeProvider.CreateScope(autoComplete: true); + var contentTypes = contentTypesByAliases.ContentTypeAliases.Select(alias => Services.ContentTypeService.Get(alias)); + return GetEmpties(contentTypes, contentTypesByAliases.ParentId).ToDictionary(x => x.ContentTypeAlias); + } + /// /// Gets an empty content item for the document type. diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypesByAliases.cs b/src/Umbraco.Web/Models/ContentEditing/ContentTypesByAliases.cs new file mode 100644 index 0000000000..dd1185b304 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/ContentTypesByAliases.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// A model for retrieving multiple content types based on their aliases. + /// + [DataContract(Name = "contentTypes", Namespace = "")] + public class ContentTypesByAliases + { + /// + /// Id of the parent of the content type. + /// + [DataMember(Name = "parentId")] + [Required] + public int ParentId { get; set; } + + /// + /// The alias of every content type to get. + /// + [DataMember(Name = "contentTypeAliases")] + [Required] + public string[] ContentTypeAliases { get; set; } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 68bcade20c..2340228eae 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -253,6 +253,7 @@ + diff --git a/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs b/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs index e2a6f155d0..25585ca0cb 100644 --- a/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs @@ -1,10 +1,8 @@ -using System; +using System.Collections; using System.Net.Http; using System.Web.Http.Filters; -using Umbraco.Core; using Umbraco.Web.Composing; using Umbraco.Web.Editors; -using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.WebApi.Filters { @@ -23,18 +21,37 @@ namespace Umbraco.Web.WebApi.Filters if (actionExecutedContext.Response.Content is ObjectContent objectContent) { var model = objectContent.Value; - if (model != null) { - var args = new EditorModelEventArgs( - model, - Current.UmbracoContext); - EditorModelEventManager.EmitEvent(actionExecutedContext, args); - objectContent.Value = args.Model; + if (model is IDictionary modelDict) + { + foreach (var entity in modelDict) + { + if (entity is DictionaryEntry entry) + { + var args = CreateArgs(entry.Value); + EditorModelEventManager.EmitEvent(actionExecutedContext, args); + entry.Value = args.Model; + } + } + } + else + { + var args = CreateArgs(model); + EditorModelEventManager.EmitEvent(actionExecutedContext, args); + objectContent.Value = args.Model; + } } } base.OnActionExecuted(actionExecutedContext); } + + private EditorModelEventArgs CreateArgs(object model) + { + return new EditorModelEventArgs( + model, + Current.UmbracoContext); + } } }