From 5b546cc425a930888388edb03a04e4449b818996 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 12 Jun 2019 10:39:34 +0200 Subject: [PATCH 01/37] Bugfix: Don't increase the page number in the loop, because the items are moved to another subtree, and therefore is not any longer part of the descendants call. Now we avoid the leak of items when moving to recycle bin. --- src/Umbraco.Core/Services/Implement/ContentService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 5cc1a584b1..79f593d8d2 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -1930,7 +1930,7 @@ namespace Umbraco.Core.Services.Implement var total = long.MaxValue; while (page * pageSize < total) { - var descendants = GetPagedDescendantsLocked(originalPath, page++, pageSize, out total, null, Ordering.By("Path", Direction.Ascending)); + var descendants = GetPagedDescendantsLocked(originalPath, page, pageSize, out total, null, Ordering.By("Path", Direction.Ascending)); foreach (var descendant in descendants) { moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path From cc4a7653e50bdaefd1cf0d134c9e9299b259fae5 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 12 Jun 2019 14:31:01 +0200 Subject: [PATCH 02/37] Bugfix: Still load trees even when a tree is about to be loaded. But only use the result, if we are still on the correct section. This eliminates the issue where a wrong tree is shown in a section. --- .../components/tree/umbtree.directive.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index 7055d1a746..6a94d3e43b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -229,11 +229,9 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use } /** Method to load in the tree data */ - function loadTree() { - if (!$scope.loading && $scope.section) { - $scope.loading = true; - + if ($scope.section) { + //default args var args = { section: $scope.section, tree: $scope.treealias, cacheKey: $scope.cachekey, isDialog: $scope.isdialog ? $scope.isdialog : false }; @@ -244,20 +242,22 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use return treeService.getTree(args) .then(function (data) { - + //Only use the tree data, if we are still on the correct section + if(data.alias !== $scope.section){ + return $q.reject(); + } + //set the data once we have it $scope.tree = data; - $scope.loading = false; - //set the root as the current active tree $scope.activeTree = $scope.tree.root; emitEvent("treeLoaded", { tree: $scope.tree }); emitEvent("treeNodeExpanded", { tree: $scope.tree, node: $scope.tree.root, children: $scope.tree.root.children }); + return $q.when(data); }, function (reason) { - $scope.loading = false; notificationsService.error("Tree Error", reason); return $q.reject(reason); }); From e39e132febdd41469af3e0d4810ef5c8bf06a321 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 17 Jun 2019 07:16:37 +0200 Subject: [PATCH 03/37] Cleaned up, such that we are not in doubt about whether the page variable should have been increased. --- src/Umbraco.Core/Services/Implement/ContentService.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 79f593d8d2..c79e7aa869 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -1695,12 +1695,11 @@ namespace Umbraco.Core.Services.Implement } const int pageSize = 500; - var page = 0; var total = long.MaxValue; - while (page * pageSize < total) + while (total > 0) { //get descendants - ordered from deepest to shallowest - var descendants = GetPagedDescendants(content.Id, page, pageSize, out total, ordering: Ordering.By("Path", Direction.Descending)); + var descendants = GetPagedDescendants(content.Id, 0, pageSize, out total, ordering: Ordering.By("Path", Direction.Descending)); foreach (var c in descendants) DoDelete(c); } @@ -1926,11 +1925,10 @@ namespace Umbraco.Core.Services.Implement paths[content.Id] = (parent == null ? (parentId == Constants.System.RecycleBinContent ? "-1,-20" : Constants.System.RootString) : parent.Path) + "," + content.Id; const int pageSize = 500; - var page = 0; var total = long.MaxValue; - while (page * pageSize < total) + while (total > 0) { - var descendants = GetPagedDescendantsLocked(originalPath, page, pageSize, out total, null, Ordering.By("Path", Direction.Ascending)); + var descendants = GetPagedDescendantsLocked(originalPath, 0, pageSize, out total, null, Ordering.By("Path", Direction.Ascending)); foreach (var descendant in descendants) { moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path From 46f4984add8c199f189e933b71f1ca7fa22619eb Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 17 Jun 2019 07:18:23 +0200 Subject: [PATCH 04/37] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1357: Fixed issue where too fast change between sections, leads to a wrong tree (The first requested) shown in the menu. --- .../src/common/directives/components/tree/umbtree.directive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index 6a94d3e43b..fbc0ea1eb0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -243,7 +243,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use return treeService.getTree(args) .then(function (data) { //Only use the tree data, if we are still on the correct section - if(data.alias !== $scope.section){ + if(data.alias !== $scope.section){ return $q.reject(); } From cb428bb288a4b786e474d86fd55017f2a64fd7a9 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 18 Jun 2019 08:11:18 +0200 Subject: [PATCH 05/37] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1447: Catch the SoapException which happends when a timeout occurs on the server. Also change such that this check is only executed for super admins. --- src/Umbraco.Web/Editors/UpdateCheckController.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Editors/UpdateCheckController.cs b/src/Umbraco.Web/Editors/UpdateCheckController.cs index cd11382d13..5193b08edc 100644 --- a/src/Umbraco.Web/Editors/UpdateCheckController.cs +++ b/src/Umbraco.Web/Editors/UpdateCheckController.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.Editors { var updChkCookie = Request.Headers.GetCookies("UMB_UPDCHK").FirstOrDefault(); var updateCheckCookie = updChkCookie != null ? updChkCookie["UMB_UPDCHK"].Value : ""; - if (GlobalSettings.VersionCheckPeriod > 0 && string.IsNullOrEmpty(updateCheckCookie) && Security.CurrentUser.IsAdmin()) + if (GlobalSettings.VersionCheckPeriod > 0 && string.IsNullOrEmpty(updateCheckCookie) && Security.CurrentUser.IsSuper()) { try { @@ -37,6 +37,11 @@ namespace Umbraco.Web.Editors //this occurs if the server is down or cannot be reached return null; } + catch (System.Web.Services.Protocols.SoapException) + { + //this occurs if the server has a timeout + return null; + } } return null; } From 9614740410e167799be50bba4c030fb7bddff29a Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 18 Jun 2019 11:44:56 +0200 Subject: [PATCH 06/37] Added timeout for 2 seconds. Dont make sense to wait longer for this --- src/Umbraco.Web/Editors/UpdateCheckController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Editors/UpdateCheckController.cs b/src/Umbraco.Web/Editors/UpdateCheckController.cs index 5193b08edc..b197fe8926 100644 --- a/src/Umbraco.Web/Editors/UpdateCheckController.cs +++ b/src/Umbraco.Web/Editors/UpdateCheckController.cs @@ -24,7 +24,8 @@ namespace Umbraco.Web.Editors { try { - var check = new org.umbraco.update.CheckForUpgrade(); + var check = new org.umbraco.update.CheckForUpgrade { Timeout = 2000 }; + var result = check.CheckUpgrade(UmbracoVersion.Current.Major, UmbracoVersion.Current.Minor, UmbracoVersion.Current.Build, From 832dc7f4a5cf70790253f30b395bb67e811c2351 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 18 Jun 2019 18:44:22 +0200 Subject: [PATCH 07/37] Changed superadmin back to admin again --- src/Umbraco.Web/Editors/UpdateCheckController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Editors/UpdateCheckController.cs b/src/Umbraco.Web/Editors/UpdateCheckController.cs index b197fe8926..132526576b 100644 --- a/src/Umbraco.Web/Editors/UpdateCheckController.cs +++ b/src/Umbraco.Web/Editors/UpdateCheckController.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.Editors { var updChkCookie = Request.Headers.GetCookies("UMB_UPDCHK").FirstOrDefault(); var updateCheckCookie = updChkCookie != null ? updChkCookie["UMB_UPDCHK"].Value : ""; - if (GlobalSettings.VersionCheckPeriod > 0 && string.IsNullOrEmpty(updateCheckCookie) && Security.CurrentUser.IsSuper()) + if (GlobalSettings.VersionCheckPeriod > 0 && string.IsNullOrEmpty(updateCheckCookie) && Security.CurrentUser.IsAdmin()) { try { From f7aaad86c5ea29096bbce47689a0188416a99171 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 21 Jun 2019 15:56:36 +0200 Subject: [PATCH 08/37] Set focus on "Mandatory" after picking a property type when creating a new property --- .../infiniteeditors/propertysettings/propertysettings.html | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html index 93d7936326..fab6ba4069 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html @@ -91,6 +91,7 @@ From 4fca0bb735989ed7d5dd77aa566d9c48f5c70d89 Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 23 Jun 2019 11:10:16 +0100 Subject: [PATCH 09/37] V8: Accessibility improvements for top header (#5544) --- .../src/less/accessibility/visually-hidden.less | 11 +++++++++++ src/Umbraco.Web.UI.Client/src/less/belle.less | 3 +++ src/Umbraco.Web.UI.Client/src/less/buttons.less | 15 +++++++++++++++ .../components/application/umb-app-header.less | 15 +++++++-------- .../components/application/umb-app-header.html | 16 ++++++++++------ 5 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/less/accessibility/visually-hidden.less diff --git a/src/Umbraco.Web.UI.Client/src/less/accessibility/visually-hidden.less b/src/Umbraco.Web.UI.Client/src/less/accessibility/visually-hidden.less new file mode 100644 index 0000000000..592f9dcb21 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/accessibility/visually-hidden.less @@ -0,0 +1,11 @@ + +// Visually Hidden - used to remove an element from the view, whilst retaining accessibily for screen readers. More info available at https://a11yproject.com/posts/how-to-hide-content/ +// -------------------------------------------------- + +.visually-hidden { + position: absolute !important; + height: 1px; + width: 1px; + overflow: hidden; + clip: rect(1px, 1px, 1px, 1px); +} diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 1e48500bb0..6f7554c953 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -80,6 +80,9 @@ @import "forms/umb-validation-label.less"; +// Umbraco Accessibility +@import "accessibility/visually-hidden.less"; + // Umbraco Components @import "components/application/umb-app-header.less"; @import "components/application/umb-app-content.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/buttons.less b/src/Umbraco.Web.UI.Client/src/less/buttons.less index f21c7f3106..91a6c29a17 100644 --- a/src/Umbraco.Web.UI.Client/src/less/buttons.less +++ b/src/Umbraco.Web.UI.Client/src/less/buttons.less @@ -67,6 +67,21 @@ border-color: rgba(0,0,0,0.09); } +// Button Reset - remove the default browser styles from the button element +// -------------------------------------------------- + +.btn-reset { + padding: 0; + margin: 0; + border: none; + background: none; + color: currentColor; + font-family: @baseFontFamily; + font-size: @baseFontSize; + line-height: @baseLineHeight; + cursor: pointer; +} + // Button Sizes // -------------------------------------------------- diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less index d4b21e66f0..bd1b8ab07a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less @@ -16,27 +16,26 @@ margin-right: -10px; } -.umb-app-header__action a { +.umb-app-header__button { padding-left: 10px; padding-right: 10px; text-decoration: none; display: flex; align-items: center; height: @appHeaderHeight; -} - -.umb-app-header__action a { outline: none; + &:focus { .tabbing-active & { .umb-app-header__action-icon::after { content: ''; position: absolute; z-index:10000; - top: -7px; - left: -7px; + top: 50%; + left: 50%; width: 36px; height: 35px; + transform: translate(-50%, -50%); border-radius: 3px; box-shadow: 0 0 2px @pinkLight, inset 0 0 2px 1px @pinkLight; } @@ -51,7 +50,7 @@ font-size: 22px; } -.umb-app-header__action a:hover .umb-app-header__action-icon, -.umb-app-header__action a:focus .umb-app-header__action-icon { +.umb-app-header__button:hover .umb-app-header__action-icon, +.umb-app-header__button:focus .umb-app-header__action-icon { opacity: 1; } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html index ac919d3e41..a601940193 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html @@ -1,3 +1,4 @@ +
@@ -10,25 +11,28 @@ From 255bd10296454374489e2ac40493880f2cc9b386 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 23 Jun 2019 12:25:15 +0200 Subject: [PATCH 10/37] V8: Use a picker to select allowed types for MNTP (#5506) --- .../src/common/services/editor.service.js | 23 ++++++++ .../treepicker/treepicker.controller.js | 8 +++ .../contenttypepicker.controller.js | 54 +++++++++++++++++++ .../prevalueeditors/contenttypepicker.html | 23 ++++++++ src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 1 + .../Umbraco/config/lang/en_us.xml | 1 + .../MultiNodePickerConfiguration.cs | 2 +- 8 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/prevalueeditors/contenttypepicker.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/prevalueeditors/contenttypepicker.html diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index a97773f77e..72957e1c72 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -368,6 +368,28 @@ When building a custom infinite editor view you can use the same components as a open(editor); } + /** + * @ngdoc method + * @name umbraco.services.editorService#contentTypePicker + * @methodOf umbraco.services.editorService + * + * @description + * Opens a content type picker in infinite editing, the submit callback returns an array of selected items + * + * @param {Object} editor rendering options + * @param {Boolean} editor.multiPicker Pick one or multiple items + * @param {Function} editor.submit Callback function when the submit button is clicked. Returns the editor model object + * @param {Function} editor.close Callback function when the close button is clicked. + * + * @returns {Object} editor object + */ + function contentTypePicker(editor) { + editor.view = "views/common/infiniteeditors/treepicker/treepicker.html"; + editor.size = "small"; + editor.section = "settings"; + editor.treeAlias = "documentTypes"; + open(editor); + } /** * @ngdoc method * @name umbraco.services.editorService#copy @@ -881,6 +903,7 @@ When building a custom infinite editor view you can use the same components as a mediaEditor: mediaEditor, contentEditor: contentEditor, contentPicker: contentPicker, + contentTypePicker: contentTypePicker, copy: copy, move: move, embed: embed, diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js index 915abf62b0..b01fe1827b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js @@ -92,6 +92,14 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", }); } } + if (vm.treeAlias === "documentTypes") { + vm.entityType = "DocumentType"; + if (!$scope.model.title) { + localizationService.localize("defaultdialogs_selectContentType").then(function(value){ + $scope.model.title = value; + }); + } + } else if (vm.treeAlias === "member" || vm.section === "member") { vm.entityType = "Member"; if (!$scope.model.title) { diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/contenttypepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/contenttypepicker.controller.js new file mode 100644 index 0000000000..dcb2c0e582 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/contenttypepicker.controller.js @@ -0,0 +1,54 @@ +function ContentTypePickerController($scope, contentTypeResource, editorService, angularHelper) { + var vm = this; + vm.loading = false; + vm.contentTypes = []; + vm.remove = remove; + vm.add = add; + + var allContentTypes = null; + + function init() { + vm.loading = true; + contentTypeResource.getAll().then(function (all) { + allContentTypes = all; + vm.loading = false; + // the model value is a comma separated list of content type aliases + var currentContentTypes = _.map(($scope.model.value || "").split(","), function (s) { return s.trim(); }); + vm.contentTypes = _.filter(allContentTypes, function (contentType) { + return currentContentTypes.indexOf(contentType.alias) >= 0; + }); + }); + } + + function add() { + editorService.contentTypePicker({ + multiPicker: true, + submit: function (model) { + var newContentTypes = _.map(model.selection, function (selected) { + return _.findWhere(allContentTypes, {udi: selected.udi}); + }); + vm.contentTypes = _.uniq(_.union(vm.contentTypes, newContentTypes)); + updateModel(); + editorService.close(); + }, + close: function () { + editorService.close(); + } + }); + } + + function remove(contentType) { + vm.contentTypes = _.without(vm.contentTypes, contentType); + updateModel(); + } + + function updateModel() { + // the model value is a comma separated list of content type aliases + $scope.model.value = _.pluck(vm.contentTypes, "alias").join(); + angularHelper.getCurrentForm($scope).$setDirty(); + } + + init(); +} + +angular.module('umbraco').controller("Umbraco.PrevalueEditors.ContentTypePickerController", ContentTypePickerController); diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/contenttypepicker.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/contenttypepicker.html new file mode 100644 index 0000000000..cd89fe4cb5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/contenttypepicker.html @@ -0,0 +1,23 @@ +
+ + + +
+ + + + + Add + +
+ +
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 9e6bdc5e57..0d5cdc5583 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -434,6 +434,7 @@ Vælg link Vælg makro Vælg indhold + Vælg indholdstype Vælg medie startnode Vælg medlem Vælg medlemsgruppe diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 189bd9f10b..187325b695 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -447,6 +447,7 @@ Select link Select macro Select content + Select content type Select media start node Select member Select member group diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 6ce6f82ccc..2b8b97c5f1 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -451,6 +451,7 @@ Select link Select macro Select content + Select content type Select media start node Select member Select member group diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs index b6333c3140..279872e2d2 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("startNode", "Node type", "treesource")] public MultiNodePickerConfigurationTreeSource TreeSource { get; set; } - [ConfigurationField("filter", "Allow items of type", "textstring", Description = "Separate with comma")] + [ConfigurationField("filter", "Allow items of type", "contenttypepicker", Description = "Select the applicable content types")] public string Filter { get; set; } [ConfigurationField("minNumber", "Minimum number of items", "number")] From a7da3d741ffef44e0fab96a43a36b73fed44ac15 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 23 Jun 2019 12:30:17 +0200 Subject: [PATCH 11/37] V8: Add keyboard support for navigating search results (#5500) --- .../application/umbsearch.directive.js | 65 +++++++++++++++++-- .../components/application/umb-search.html | 6 +- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js index 91eb077ba3..8434a96ba5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js @@ -22,13 +22,16 @@ vm.search = search; vm.clickItem = clickItem; vm.clearSearch = clearSearch; - vm.handleKeyUp = handleKeyUp; + vm.handleKeyDown = handleKeyDown; vm.closeSearch = closeSearch; vm.focusSearch = focusSearch; //we need to capture the focus before this element is initialized. vm.focusBeforeOpening = focusService.getLastKnownFocus(); + vm.activeResult = null; + vm.activeResultGroup = null; + function onInit() { vm.searchQuery = ""; vm.searchResults = []; @@ -72,14 +75,66 @@ * Handles all keyboard events * @param {object} event */ - function handleKeyUp(event) { - - event.stopPropagation(); - event.preventDefault(); + function handleKeyDown(event) { // esc if(event.keyCode === 27) { + event.stopPropagation(); + event.preventDefault(); + closeSearch(); + return; + } + + // up/down (navigate search results) + if (vm.hasResults && (event.keyCode === 38 || event.keyCode === 40)) { + event.stopPropagation(); + event.preventDefault(); + + var allGroups = _.values(vm.searchResults); + var down = event.keyCode === 40; + if (vm.activeResultGroup === null) { + // it's the first time navigating, pick the appropriate group and result + // - first group and first result when navigating down + // - last group and last result when navigating up + vm.activeResultGroup = down ? _.first(allGroups) : _.last(allGroups); + vm.activeResult = down ? _.first(vm.activeResultGroup.results) : _.last(vm.activeResultGroup.results); + } + else if (down) { + // handle navigation down through the groups and results + if (vm.activeResult === _.last(vm.activeResultGroup.results)) { + if (vm.activeResultGroup === _.last(allGroups)) { + vm.activeResultGroup = _.first(allGroups); + } + else { + vm.activeResultGroup = allGroups[allGroups.indexOf(vm.activeResultGroup) + 1]; + } + vm.activeResult = _.first(vm.activeResultGroup.results); + } + else { + vm.activeResult = vm.activeResultGroup.results[vm.activeResultGroup.results.indexOf(vm.activeResult) + 1]; + } + } + else { + // handle navigation up through the groups and results + if (vm.activeResult === _.first(vm.activeResultGroup.results)) { + if (vm.activeResultGroup === _.first(allGroups)) { + vm.activeResultGroup = _.last(allGroups); + } + else { + vm.activeResultGroup = allGroups[allGroups.indexOf(vm.activeResultGroup) - 1]; + } + vm.activeResult = _.last(vm.activeResultGroup.results); + } + else { + vm.activeResult = vm.activeResultGroup.results[vm.activeResultGroup.results.indexOf(vm.activeResult) - 1]; + } + } + + $timeout(function () { + var resultElementLink = angular.element(".umb-search-item[active-result='true'] .umb-search-result__link"); + resultElementLink[0].focus(); + }); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html index 56d9eae16c..35bf725e0a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html @@ -1,5 +1,5 @@ - From 917006181047aa7bc7ce8f13606f175a39c95b13 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 23 Jun 2019 12:40:23 +0200 Subject: [PATCH 12/37] V8: Add option to keep the mini profiler active at all times in debug mode (#5560) --- .../dashboard/settings/profiler.controller.js | 38 +++++++++++++++++ .../views/dashboard/settings/profiler.html | 41 +++++++++++++++++++ src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 1 + .../Umbraco/config/lang/en_us.xml | 1 + .../Dashboards/ProfilerDashboard.cs | 18 ++++++++ .../Editors/BackOfficeServerVariables.cs | 5 +++ src/Umbraco.Web/Logging/WebProfiler.cs | 1 + .../Profiling/WebProfilingController.cs | 19 +++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 2 + src/Umbraco.Web/UmbracoContext.cs | 3 +- 11 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html create mode 100644 src/Umbraco.Web/Dashboards/ProfilerDashboard.cs create mode 100644 src/Umbraco.Web/Profiling/WebProfilingController.cs diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.controller.js new file mode 100644 index 0000000000..295263a47c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.controller.js @@ -0,0 +1,38 @@ +function ProfilerController($scope, $cookies, $http, umbRequestHelper) { + var vm = this; + + vm.loading = true; + vm.toggle = toggle; + + function toggle() { + if (vm.alwaysOn === true) { + $cookies.remove("UMB-DEBUG", { + path: "/" + }); + vm.alwaysOn = false; + } + else { + $cookies.put("UMB-DEBUG", "true", { + path: "/", + expires: "Tue, 01 Jan 2100 00:00:01 GMT" + }); + vm.alwaysOn = true; + } + } + + function init() { + vm.alwaysOn = $cookies.get("UMB-DEBUG") === "true"; + + umbRequestHelper.resourcePromise( + $http.get(umbRequestHelper.getApiUrl("webProfilingBaseUrl", "GetStatus")), + "Failed to retrieve status for web profiling" + ).then(function(status) { + vm.loading = false; + vm.profilerEnabled = status.Enabled; + }); + } + + init(); +} + +angular.module("umbraco").controller("Umbraco.Dashboard.ProfilerController", ProfilerController); diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html new file mode 100644 index 0000000000..2a7419c0ea --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html @@ -0,0 +1,41 @@ +
+ + +

Performance profiling

+
+

+ Umbraco currently runs in debug mode. This means you can use the built-in performance profiler to assess the performance when rendering pages. +

+

+ If you want to activate the profiler for a specific page rendering, simply add umbDebug=true to the querystring when requesting the page. +

+

+ If you want the profiler to be activated by default for all page renderings, you can use the toggle below. + It will set a cookie in your browser, which then activates the profiler automatically. + In other words, the profiler will only be active by default in your browser - not everyone else's. +

+

 

+
+
+
Activate the profiler by default
+
+
+ +
+
+

Friendly reminder

+

+ You should never let a production site run in debug mode. Debug mode is turned off by setting debug="false" on the <compilation /> element in web.config. +

+
+
+

+ Umbraco currently does not run in debug mode, so you can't use the built-in profiler. This is how it should be for a production site. +

+

+ Debug mode is turned on by setting debug="true" on the <compilation /> element in web.config. +

+
+
+
+
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 0d5cdc5583..aac2f6ddd5 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -1606,6 +1606,7 @@ Mange hilsner fra Umbraco robotten Published Cache Models Builder Health Check + Profiling Kom godt i gang Installer Umbraco Forms diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 187325b695..aba67ff2b1 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -2117,6 +2117,7 @@ To manage your website, simply open the Umbraco back office and start adding con Published Status Models Builder Health Check + Profiling Getting Started Install Umbraco Forms > diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 2b8b97c5f1..6b9392ff3e 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -2132,6 +2132,7 @@ To manage your website, simply open the Umbraco back office and start adding con Published Status Models Builder Health Check + Profiling Getting Started Install Umbraco Forms diff --git a/src/Umbraco.Web/Dashboards/ProfilerDashboard.cs b/src/Umbraco.Web/Dashboards/ProfilerDashboard.cs new file mode 100644 index 0000000000..a4f51398e8 --- /dev/null +++ b/src/Umbraco.Web/Dashboards/ProfilerDashboard.cs @@ -0,0 +1,18 @@ +using System; +using Umbraco.Core.Composing; +using Umbraco.Core.Dashboards; + +namespace Umbraco.Web.Dashboards +{ + [Weight(60)] + public class ProfilerDashboardDashboard : IDashboard + { + public string Alias => "settingsProfiler"; + + public string[] Sections => new [] { "settings" }; + + public string View => "views/dashboard/settings/profiler.html"; + + public IAccessRule[] AccessRules => Array.Empty(); + } +} diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 4966328782..6b6cfacacc 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -20,6 +20,7 @@ using Umbraco.Web.Features; using Umbraco.Web.HealthCheck; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; +using Umbraco.Web.Profiling; using Umbraco.Web.PropertyEditors; using Umbraco.Web.Trees; using Constants = Umbraco.Core.Constants; @@ -308,6 +309,10 @@ namespace Umbraco.Web.Editors { "logViewerApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetNumberOfErrors(null, null)) + }, + { + "webProfilingBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetStatus()) } } }, diff --git a/src/Umbraco.Web/Logging/WebProfiler.cs b/src/Umbraco.Web/Logging/WebProfiler.cs index 14c1bb065f..512edb2296 100755 --- a/src/Umbraco.Web/Logging/WebProfiler.cs +++ b/src/Umbraco.Web/Logging/WebProfiler.cs @@ -68,6 +68,7 @@ namespace Umbraco.Web.Logging if (request.Result.Url.IsClientSideRequest()) return false; if (bool.TryParse(request.Result.QueryString["umbDebug"], out var umbDebug)) return umbDebug; if (bool.TryParse(request.Result.Headers["X-UMB-DEBUG"], out var xUmbDebug)) return xUmbDebug; + if (bool.TryParse(request.Result.Cookies["UMB-DEBUG"]?.Value, out var cUmbDebug)) return cUmbDebug; return false; } diff --git a/src/Umbraco.Web/Profiling/WebProfilingController.cs b/src/Umbraco.Web/Profiling/WebProfilingController.cs new file mode 100644 index 0000000000..b3d580bc38 --- /dev/null +++ b/src/Umbraco.Web/Profiling/WebProfilingController.cs @@ -0,0 +1,19 @@ +using Umbraco.Web.Editors; +using Umbraco.Web.WebApi.Filters; + +namespace Umbraco.Web.Profiling +{ + /// + /// The API controller used to display the state of the web profiler + /// + [UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)] + public class WebProfilingController : UmbracoAuthorizedJsonController + { + public object GetStatus() + { + return new + { + Enabled = Core.Configuration.GlobalSettings.DebugMode + }; + } + }} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e565f354c8..7ebcf55155 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -129,6 +129,7 @@ + @@ -211,6 +212,7 @@ + diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index e68e6e2c77..a5ff08d79c 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -174,7 +174,8 @@ namespace Umbraco.Web return GlobalSettings.DebugMode && request != null && (string.IsNullOrEmpty(request["umbdebugshowtrace"]) == false - || string.IsNullOrEmpty(request["umbdebug"]) == false); + || string.IsNullOrEmpty(request["umbdebug"]) == false + || string.IsNullOrEmpty(request.Cookies["UMB-DEBUG"]?.Value) == false); } } From 9fffdb407a3b73d9d9ecf384ffdca144fdb3bb80 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sun, 2 Jun 2019 09:51:32 +0200 Subject: [PATCH 13/37] Provided alternative messages for users that do and do not have access to document/media types for when trying to create content or media and no types can be created. Where they do, provided a link to go to the page where child nodes can be edited. --- .../content/content.create.controller.js | 159 ++++++++++-------- .../src/views/content/create.html | 14 +- .../views/documenttypes/edit.controller.js | 27 ++- .../src/views/media/create.html | 14 +- .../views/media/media.create.controller.js | 17 +- .../src/views/mediatypes/edit.controller.js | 30 +++- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 8 +- .../Umbraco/config/lang/en_us.xml | 9 +- .../Models/ContentEditing/ContentItemBasic.cs | 2 + .../ContentEditing/ContentItemDisplay.cs | 3 + .../Models/Mapping/ContentMapDefinition.cs | 2 + .../Models/Mapping/MediaMapDefinition.cs | 2 + .../Models/Mapping/MemberMapDefinition.cs | 4 +- 13 files changed, 198 insertions(+), 93 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js index f101450705..d04c707b25 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js @@ -7,86 +7,103 @@ * The controller for the content creation dialog */ function contentCreateController($scope, - $routeParams, - contentTypeResource, - iconHelper, - $location, - navigationService, - blueprintConfig) { - - var mainCulture = $routeParams.mculture ? $routeParams.mculture : null; + $routeParams, + contentTypeResource, + iconHelper, + $location, + navigationService, + blueprintConfig, + authResource, + contentResource) { - function initialize() { - $scope.allowedTypes = null; - contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) { - $scope.allowedTypes = iconHelper.formatContentTypeIcons(data); - }); + var mainCulture = $routeParams.mculture ? $routeParams.mculture : null; - $scope.selectContentType = true; - $scope.selectBlueprint = false; - $scope.allowBlank = blueprintConfig.allowBlank; - } + function initialize() { + $scope.allowedTypes = null; + contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) { + $scope.allowedTypes = iconHelper.formatContentTypeIcons(data); + }); - function close() { - navigationService.hideMenu(); - } + if ($scope.currentNode.id > -1) { + authResource.getCurrentUser().then(function(currentUser) { + if (currentUser.allowedSections.indexOf("settings") > -1) { + $scope.hasSettingsAccess = true; + contentResource.getById($scope.currentNode.id).then(function(data) { + $scope.contentTypeId = data.contentTypeId; + }); + } + }); + } - function createBlank(docType) { - $location - .path("/content/content/edit/" + $scope.currentNode.id) - .search("doctype", docType.alias) - .search("create", "true") - /* when we create a new node we want to make sure it uses the same - language as what is selected in the tree */ - .search("cculture", mainCulture); - close(); - } - - function createOrSelectBlueprintIfAny(docType) { - // map the blueprints into a collection that's sortable in the view - var blueprints = _.map(_.pairs(docType.blueprints || {}), function (pair) { - return { - id: pair[0], - name: pair[1] - }; - }); - $scope.docType = docType; - if (blueprints.length) { - if (blueprintConfig.skipSelect) { - createFromBlueprint(blueprints[0].id); - } else { - $scope.selectContentType = false; - $scope.selectBlueprint = true; - $scope.selectableBlueprints = blueprints; - } - } else { - createBlank(docType); + $scope.selectContentType = true; + $scope.selectBlueprint = false; + $scope.allowBlank = blueprintConfig.allowBlank; } - } - function createFromBlueprint(blueprintId) { - $location - .path("/content/content/edit/" + $scope.currentNode.id) - .search("doctype", $scope.docType.alias) - .search("create", "true") - .search("blueprintId", blueprintId); - close(); - } + function close() { + navigationService.hideMenu(); + } - $scope.closeDialog = function(showMenu) { - navigationService.hideDialog(showMenu); - }; + function createBlank(docType) { + $location + .path("/content/content/edit/" + $scope.currentNode.id) + .search("doctype", docType.alias) + .search("create", "true") + /* when we create a new node we want to make sure it uses the same + language as what is selected in the tree */ + .search("cculture", mainCulture); + close(); + } - $scope.createBlank = createBlank; - $scope.createOrSelectBlueprintIfAny = createOrSelectBlueprintIfAny; - $scope.createFromBlueprint = createFromBlueprint; + function createOrSelectBlueprintIfAny(docType) { + // map the blueprints into a collection that's sortable in the view + var blueprints = _.map(_.pairs(docType.blueprints || {}), function (pair) { + return { + id: pair[0], + name: pair[1] + }; + }); + $scope.docType = docType; + if (blueprints.length) { + if (blueprintConfig.skipSelect) { + createFromBlueprint(blueprints[0].id); + } else { + $scope.selectContentType = false; + $scope.selectBlueprint = true; + $scope.selectableBlueprints = blueprints; + } + } else { + createBlank(docType); + } + } - // the current node changes behind the scenes when the context menu is clicked without closing - // the default menu first, so we must watch the current node and re-initialize accordingly - var unbindModelWatcher = $scope.$watch("currentNode", initialize); - $scope.$on('$destroy', function () { - unbindModelWatcher(); - }); + function createFromBlueprint(blueprintId) { + $location + .path("/content/content/edit/" + $scope.currentNode.id) + .search("doctype", $scope.docType.alias) + .search("create", "true") + .search("blueprintId", blueprintId); + close(); + } + + $scope.close = function() { + close(); + } + + $scope.closeDialog = function (showMenu) { + navigationService.hideDialog(showMenu); + }; + + $scope.createBlank = createBlank; + $scope.createOrSelectBlueprintIfAny = createOrSelectBlueprintIfAny; + $scope.createFromBlueprint = createFromBlueprint; + + // the current node changes behind the scenes when the context menu is clicked without closing + // the default menu first, so we must watch the current node and re-initialize accordingly + var unbindModelWatcher = $scope.$watch("currentNode", initialize); + $scope.$on('$destroy', function () { + unbindModelWatcher(); + }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/create.html b/src/Umbraco.Web.UI.Client/src/views/content/create.html index 94299f6a54..97306e0ea8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/create.html @@ -6,9 +6,15 @@
Create a page under {{currentNode.name}}
Select a blueprint
-

- -

+
    @@ -56,4 +62,4 @@
-
\ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js index 7c1f996931..5ceb5f01f2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js @@ -56,7 +56,7 @@ function onInit() { // get init values from model when in infinite mode - if(infiniteMode) { + if (infiniteMode) { documentTypeId = $scope.model.id; create = $scope.model.create; noTemplate = $scope.model.notemplate; @@ -89,8 +89,7 @@ "name": vm.labels.design, "alias": "design", "icon": "icon-document-dashed-line", - "view": "views/documenttypes/views/design/design.html", - "active": true + "view": "views/documenttypes/views/design/design.html" }, { "name": vm.labels.listview, @@ -291,6 +290,28 @@ }); vm.page.navigation = buttons; + initializeActiveNavigationPanel(); + } + + function initializeActiveNavigationPanel() { + // Initialise first loaded panel based on page route paramater + // i.e. ?view=design|listview|permissions + var initialViewSetFromRouteParams = false; + var view = $routeParams.view; + if (view) { + var viewPath = "views/documenttypes/views/" + view + "/" + view + ".html"; + for (var i = 0; i < vm.page.navigation.length; i++) { + if (vm.page.navigation[i].view === viewPath) { + vm.page.navigation[i].active = true; + initialViewSetFromRouteParams = true; + break; + } + } + } + + if (initialViewSetFromRouteParams === false) { + vm.page.navigation[0].active = true; + } } /* ---------- SAVE ---------- */ diff --git a/src/Umbraco.Web.UI.Client/src/views/media/create.html b/src/Umbraco.Web.UI.Client/src/views/media/create.html index 13c12f3c9a..d93d4f0e30 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/create.html @@ -4,9 +4,15 @@
Create under {{currentNode.name}}
-

- -

+
+

+
+

+ + + +
+