diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index 4363996a46..f576c66291 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -150,7 +150,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting urlHelper.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(UrlInfo.Url("/hello/world/1234")); - var membershipHelper = new MembershipHelper(new TestUmbracoContextAccessor(umbCtx), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), null, Mock.Of(), Mock.Of()); + var membershipHelper = new MembershipHelper(new TestUmbracoContextAccessor(umbCtx), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of()); var umbHelper = new UmbracoHelper(umbCtx, Mock.Of(), diff --git a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs index b65e503f34..652ac0ab30 100644 --- a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs +++ b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs @@ -65,7 +65,7 @@ namespace Umbraco.Tests.Testing.TestingTests Mock.Of(), Mock.Of(), Mock.Of(), - new MembershipHelper(new TestUmbracoContextAccessor(umbracoContext), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), null, Mock.Of(), Mock.Of())); + new MembershipHelper(new TestUmbracoContextAccessor(umbracoContext), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of())); Assert.Pass(); } diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 9616a26891..7c8d1100f8 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -213,7 +213,7 @@ namespace Umbraco.Tests.Testing // web Composition.RegisterUnique(_ => Umbraco.Web.Composing.Current.UmbracoContextAccessor); - Composition.RegisterUnique(); + Composition.RegisterUnique(); Composition.WithCollectionBuilder(); Composition.RegisterUnique(); Composition.RegisterUnique(); diff --git a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs index e80385b533..0ff3662f95 100644 --- a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs @@ -132,7 +132,7 @@ namespace Umbraco.Tests.Web.Mvc Mock.Of(), Mock.Of(), Mock.Of(), - new MembershipHelper(new TestUmbracoContextAccessor(umbracoContext), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), null, Mock.Of(), Mock.Of())); + new MembershipHelper(new TestUmbracoContextAccessor(umbracoContext), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of())); var ctrl = new TestSurfaceController(umbracoContext, helper); var result = ctrl.GetContent(2) as PublishedContentResult; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index fc7fff7e3c..5c5b1f933e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -26,20 +26,43 @@ $scope.allowOpen = true; $scope.app = null; - function init(content) { - if (!$scope.app) { - // set first app to active + function init() { + + var content = $scope.content; + + // we need to check wether an app is present in the current data, if not we will present the default app. + var isAppPresent = false; + + // on first init, we dont have any apps. but if we are re-initializing, we do, but ... + if ($scope.app) { + + // lets check if it still exists as part of our apps array. (if not we have made a change to our docType, even just a re-save of the docType it will turn into new Apps.) + _.forEach(content.apps, function(app) { + if (app === $scope.app) { + isAppPresent = true; + } + }); + + // if we did reload our DocType, but still have the same app we will try to find it by the alias. + if (isAppPresent === false) { + _.forEach(content.apps, function(app) { + if (app.alias === $scope.app.alias) { + isAppPresent = true; + app.active = true; + $scope.appChanged(app); + } + }); + } + + } + + // if we still dont have a app, lets show the first one: + if (isAppPresent === false) { content.apps[0].active = true; - $scope.app = content.apps[0]; + $scope.appChanged(content.apps[0]); } - - if (infiniteMode) { - createInfiniteModeButtons(content); - } else { - createButtons(content); - } - - editorState.set($scope.content); + + editorState.set(content); //We fetch all ancestors of the node to generate the footer breadcrumb navigation if (!$scope.page.isNew) { @@ -129,7 +152,7 @@ "/content/content/edit/" + data.parentId; } - init($scope.content); + init(); syncTreeNode($scope.content, $scope.content.path, true); @@ -340,7 +363,7 @@ showNotifications: args.showNotifications }).then(function (data) { //success - init($scope.content); + init(); syncTreeNode($scope.content, data.path); eventsService.emit("content.saved", { content: $scope.content, action: args.action }); @@ -414,7 +437,7 @@ $scope.content = data; - init($scope.content); + init(); resetLastListPageNumber($scope.content); @@ -454,7 +477,7 @@ .then(function (data) { formHelper.resetForm({ scope: $scope }); contentEditingHelper.reBindChangedProperties($scope.content, data); - init($scope.content); + init(); syncTreeNode($scope.content, data.path); $scope.page.buttonGroupState = "success"; eventsService.emit("content.unpublished", { content: $scope.content }); @@ -845,8 +868,14 @@ * @param {any} app */ $scope.appChanged = function (app) { + $scope.app = app; - createButtons($scope.content); + + if (infiniteMode) { + createInfiniteModeButtons($scope.content); + } else { + createButtons($scope.content); + } }; // methods for infinite editing diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditormenu.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditormenu.directive.js index 22939c7f2c..9dff712ef6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditormenu.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditormenu.directive.js @@ -30,8 +30,9 @@ } //when the options item is selected, we need to set the current menu item in appState (since this is synonymous with a menu) - appState.setMenuState("currentNode", scope.currentNode); - + // Niels: No i think we are wrong, we should not set the currentNode, cause it represents the currentNode of interaction. + //appState.setMenuState("currentNode", scope.currentNode); + if (!scope.actions) { treeService.getMenu({ treeNode: scope.currentNode }) .then(function (data) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js index 1850445bd4..4d10625d23 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js @@ -52,7 +52,8 @@ vm.isLoading = false; - configureViewModel(); + //ensure that the models are formatted correctly + configureViewModel(true); // Set the visible prompt to -1 to ensure it will not be visible vm.promptIsVisible = "-1"; @@ -123,9 +124,13 @@ }); } + /** + * Watch for value changes + * @param {any} changes + */ function onChanges(changes) { - // watch for value changes externally + //when the model 'value' changes, sync the viewModel object if (changes.value) { if (!changes.value.isFirstChange() && changes.value.currentValue !== changes.value.previousValue) { @@ -145,13 +150,15 @@ $element.find('.tags-' + vm.htmlId).typeahead('destroy'); } - function configureViewModel() { + function configureViewModel(isInitLoad) { if (vm.value) { if (angular.isString(vm.value) && vm.value.length > 0) { if (vm.config.storageType === "Json") { //json storage vm.viewModel = JSON.parse(vm.value); - updateModelValue(vm.viewModel); + if (!isInitLoad) { + updateModelValue(vm.viewModel); + } } else { //csv storage @@ -165,7 +172,10 @@ return self.indexOf(v) === i; }); - updateModelValue(vm.viewModel); + if (!isInitLoad) { + updateModelValue(vm.viewModel); + } + } } else if (angular.isArray(vm.value)) { @@ -175,13 +185,18 @@ } function updateModelValue(val) { - if (val) { - vm.onValueChanged({ value: val }); + + //need to format the underlying model value for persistence based on the storage type + if (vm.config.storageType === "Json") { + val = val ? val : []; } else { - vm.onValueChanged({ value: [] }); + //then it is csv and we need to format it like that + val = val ? val.join() : ""; } + vm.onValueChanged({ value: val }); + reValidate(); } @@ -273,8 +288,10 @@ } function reValidate() { - //this is required to re-validate - vm.tagEditorForm.tagCount.$setViewValue(vm.viewModel.length); + //this is required to re-validate for the mandatory validation + if (vm.tagEditorForm && vm.tagEditorForm.tagCount) { + vm.tagEditorForm.tagCount.$setViewValue(vm.viewModel.length); + } } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js index 0f9a14d71a..51eb8ca285 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js @@ -60,10 +60,13 @@ angular.module("umbraco.directives") if (!node) { return ''; } - + // TODO: This is called constantly because as a method in a template it's re-evaluated pretty much all the time // it would be better if we could cache the processing. The problem is that some of these things are dynamic. + //is this the current action node (this is not the same as the current selected node!) + var actionNode = appState.getMenuState("currentNode"); + var css = []; if (node.cssClasses) { _.each(node.cssClasses, function(c) { @@ -73,17 +76,27 @@ angular.module("umbraco.directives") if (node.selected) { css.push("umb-tree-node-checked"); } + if (node == scope.currentNode) { + css.push("current"); + if (actionNode && actionNode.id !== node.id) { + css.push("current-not-active");// when its the current node, but its not the active(current node for the given action) + } + } + if (node.hasChildren) { + css.push("has-children"); + } + if (node.deleteAnimations) { + css.push("umb-tree-item--deleted"); + } - //is this the current action node (this is not the same as the current selected node!) - var actionNode = appState.getMenuState("currentNode"); if (actionNode) { - if (actionNode.id === node.id && String(actionNode.id) !== "-1") { + if (actionNode.id === node.id && String(node.id) !== "-1") { css.push("active"); } - + // special handling of root nodes with id -1 // as there can be many nodes with id -1 in a tree we need to check the treeAlias instead - if (String(actionNode.id) === "-1" && actionNode.metaData.treeAlias === node.metaData.treeAlias) { + if (String(node.id) === "-1" && actionNode.metaData.treeAlias === node.metaData.treeAlias) { css.push("active"); } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index 308ffbf00f..29335f276e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -140,11 +140,11 @@ Use this directive to generate a thumbnail grid of media items. } } - + if (scope.items.length > 0) { setFlexValues(scope.items); } - + } function setItemData(item) { @@ -235,7 +235,7 @@ Use this directive to generate a thumbnail grid of media items. } } - + function setFlexValues(mediaItems) { var flexSortArray = mediaItems; @@ -269,12 +269,12 @@ Use this directive to generate a thumbnail grid of media items. "min-height": itemMinHeight + "px" }; - mediaItem.flexStyle = flexStyle; + mediaItem.flexStyle = flexStyle; } } - + scope.clickItem = function(item, $event, $index) { if (scope.onClick) { scope.onClick(item, $event, $index); diff --git a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js index 27c6208e1b..8e5c093462 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js @@ -34,7 +34,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar appState.setTreeState("selectedNode", args.node); //when a node is activated, this is the same as clicking it and we need to set the //current menu item to be this node as well. - appState.setMenuState("currentNode", args.node); + //appState.setMenuState("currentNode", args.node);// Niels: No, we are setting it from the dialog. } }); @@ -44,7 +44,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar args.event.preventDefault(); //Set the current action node (this is not the same as the current selected node!) - appState.setMenuState("currentNode", args.node); + //appState.setMenuState("currentNode", args.node);// Niels: No, we are setting it from the dialog. if (args.event && args.event.altKey) { args.skipDefault = true; @@ -96,7 +96,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar //put this node into the tree state appState.setTreeState("selectedNode", args.node); //when a node is clicked we also need to set the active menu node to this node - appState.setMenuState("currentNode", args.node); + //appState.setMenuState("currentNode", args.node); //not legacy, lets just set the route value and clear the query string if there is one. $location.path(n.routePath); diff --git a/src/Umbraco.Web.UI.Client/src/less/buttons.less b/src/Umbraco.Web.UI.Client/src/less/buttons.less index 51dcce7b92..c6a8447342 100644 --- a/src/Umbraco.Web.UI.Client/src/less/buttons.less +++ b/src/Umbraco.Web.UI.Client/src/less/buttons.less @@ -197,7 +197,12 @@ input[type="button"] { } // Made for Umbraco, 2019 .btn-action { - .buttonBackground(@pinkLight, @blueDark, @blueExtraDark, @u-white); + .buttonBackground(@blueExtraDark, @blueDark, @pinkLight, @u-white); +} +// Made for Umbraco, 2019 +.btn-selection { + @btnSelectionBackgroundHover: darken(@pinkLight, 10%); + .buttonBackground(@pinkLight, @btnSelectionBackgroundHover, @blueExtraDark, @blueDark); } // Made for Umbraco, 2019, used for buttons that has to stand back. .btn-white { diff --git a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less index a144336a54..146f9b8a67 100644 --- a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less +++ b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less @@ -45,7 +45,6 @@ h4 { h5 { margin: 0 0 6px 0; - color: #535353; font-size: 12px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less index 097665bdd4..53e724cf54 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less @@ -2,6 +2,7 @@ display: block; min-width: 100%; width: auto; + margin-top:1px; .umb-tree-item__label { user-select: none; @@ -44,6 +45,38 @@ } } +// active is equivilant to selected, its the item that is begin affected by the actions performed in the right-click-dialog. +.umb-tree-item.active { + +} +.umb-tree-item.active > .umb-tree-item__inner { + //background: @ui-selected; + color: @ui-selected-type; + a { + color: @ui-selected-type; + } + + border-color: @ui-selected-border; + box-shadow: 0 0 2px 0 fade(@ui-selected-border, 80%); + &::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border: 2px solid fade(white, 80%); + } + &:hover { + //background: @ui-selected-hover; + //border-color: @ui-selected-border-hover;// dont hover border, cause its cant be deselected in current code. + color: @ui-selected-type-hover; + a { + color: @ui-selected-type-hover; + } + } +} + .umb-tree-item.current > .umb-tree-item__inner { background: @ui-active; @@ -75,3 +108,10 @@ //border-color: @ui-active; } } + +.umb-tree-item.current-not-active > .umb-tree-item__inner { + + background: @ui-active-blur; + color:@ui-active-type; + +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less index 13e2a55089..698a4211f5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less @@ -87,23 +87,6 @@ body.touch .umb-tree { } } -// active is equivilant to selected, its the item that is begin affected by the actions performed in the right-click-dialog. -.umb-tree-item > .umb-tree-item__inner.active { - //background: @ui-selected; - border-color: @ui-selected-border; - color: @ui-selected-type; - a { - color: @ui-selected-type; - } - &:hover { - //background: @ui-selected-hover; - border-color: @ui-selected-border-hover; - color: @ui-selected-type-hover; - a { - color: @ui-selected-type-hover; - } - } -} .umb-tree-root, .umb-tree-item__inner { padding: 0; position: relative; @@ -112,7 +95,7 @@ body.touch .umb-tree { flex-wrap: nowrap; align-items: center; - border:2px dashed transparent; + border:2px solid transparent; color: @ui-option-type; a { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less index 399a367dc5..5a2821eb9e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less @@ -14,10 +14,32 @@ user-select: none; box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); border-radius: 3px; + + color: @ui-option-type; + &:hover { + color:@ui-option-type-hover; + } } .umb-content-grid__item.-selected { - box-shadow: 0 2px 8px 0 rgba(0,0,0,0.35); + //box-shadow: 0 2px 8px 0 rgba(0,0,0,0.35); + + //color:@ui-selected-type; + + &::before { + content: ""; + position: absolute; + z-index:2; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + border: 2px solid @ui-selected-border; + border-radius: 5px; + box-shadow: 0 0 4px 0 darken(@ui-selected-border, 20), inset 0 0 2px 0 darken(@ui-selected-border, 20); + pointer-events: none; + } + } .umb-content-grid__icon-container { @@ -31,12 +53,12 @@ .umb-content-grid__icon[class^="icon-"], .umb-content-grid__icon[class*=" icon-"] { font-size: 20px; - color: @gray-8; +//color: @gray-8; margin-right: 5px; } .umb-content-grid__icon.-light { - color: @gray-5; + //color: @gray-5; } @@ -48,7 +70,7 @@ .umb-content-grid__item-name { font-weight: bold; margin-bottom: 15px; - color: @black; + //color: @black; line-height: 1.4em; display: inline-flex; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less index f5ca1f8438..32100247a0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less @@ -14,7 +14,7 @@ padding: 10px 20px; box-sizing: border-box; flex: 1 1 200px; - border: 2px dashed transparent; + //border: 2px solid transparent; transition: border 0.2s; position: relative; justify-content: space-between; @@ -27,15 +27,29 @@ } .umb-folder-grid__folder.-selected { - box-shadow: 0 2px 8px 0 darken(@ui-selected-border, 20); + //box-shadow: 0 2px 8px 0 darken(@ui-selected-border, 20); //background: @ui-selected; color:@ui-selected-type; - border-color:@ui-selected-border; + //border-color:@ui-selected-border; + + &::before { + content: ""; + position: absolute; + z-index:2; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + border: 2px solid @ui-selected-border; + border-radius: 5px; + box-shadow: 0 0 4px 0 darken(@ui-selected-border, 20), inset 0 0 2px 0 darken(@ui-selected-border, 20); + pointer-events: none; + } &:hover { color:@ui-selected-type-hover; - border-color:@ui-selected-border-hover; + //border-color:@ui-selected-border-hover; } } @@ -59,12 +73,12 @@ .umb-folder-grid__folder-icon[class*=" icon-"] { font-size: 20px; margin-right: 15px; - color: @black; + //color: @black; } .umb-folder-grid__folder-name { font-size: 13px; - color: @black; + //color: @black; font-weight: bold; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less index adaf45b4b0..cf407b667f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less @@ -5,7 +5,7 @@ .umb-layout-selector__active-layout { box-sizing: border-box; - border: 1px solid transparent; + border: 1px solid @inputBorder; cursor: pointer; height: 30px; width: 30px; @@ -16,7 +16,7 @@ } .umb-layout-selector__active-layout:hover { - border-color: @gray-8; + border-color: @inputBorderFocus; } .umb-layout-selector__dropdown { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less index 8185040f23..fc6de2bf75 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less @@ -21,11 +21,14 @@ margin: 10px; position: relative; - overflow: hidden; + //overflow: hidden; + + user-select: none; + cursor: pointer; - border: 2px dashed transparent; box-shadow: 0 1px 1px 0 rgba(0,0,0,.2); + //border: 2px solid transparent; transition: box-shadow 150ms ease-in-out; } @@ -35,16 +38,31 @@ } .umb-media-grid__item.-selected { - box-shadow: 0 2px 8px 0 darken(@ui-selected-border, 20); //background: @ui-selected; color:@ui-selected-type; - border-color:@ui-selected-border; + //border-color: @ui-selected-border; + //box-shadow: 0 2px 8px 0 darken(@ui-selected-border, 20); - &:hover { - color:@ui-selected-type-hover; - border-color:@ui-selected-border-hover; + &::before { + content: ""; + position: absolute; + z-index:2; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + border: 2px solid @ui-selected-border; + border-radius: 5px; + box-shadow: 0 0 4px 0 darken(@ui-selected-border, 20), inset 0 0 2px 0 darken(@ui-selected-border, 20); + pointer-events: none; } + + .umb-media-grid__item-overlay { + color: @ui-selected-type; + //background: @ui-selected-border; + } + } .umb-media-grid__item-file-icon > span { @@ -64,16 +82,22 @@ } .umb-media-grid__item-image { - max-width: 100% !important; - height: auto; + //max-width: 100% !important; + //height: auto; position: relative; + + object-fit: contain; + height: 100%; } .umb-media-grid__item-image-placeholder { width: 100%; - max-width: 100%; - height: auto; + //max-width: 100%; + //height: auto; position: relative; + + object-fit: contain; + height: 100%; } .umb-media-grid__image-background { @@ -95,17 +119,22 @@ right: 0; bottom: 0; left: 0; - z-index: 100; + z-index: 1; padding: 5px 10px; box-sizing: border-box; font-size: 12px; overflow: hidden; color: @black; white-space: nowrap; - border-top:1px solid @gray-9; - background: @white; + border-top:1px solid fade(black, 4%); + background: fade(@white, 92%); transition: opacity 150ms; + + &:hover { + text-decoration: underline; + } } + /* .umb-media-grid__item.-file .umb-media-grid__item-overlay { opacity: 1; @@ -180,6 +209,10 @@ align-items: center; color: @black; transition: opacity 150ms; + + &:hover { + color: @ui-action-disgrete-type-hover; + } } .umb-media-grid__item:hover .umb-media-grid__edit { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less index c1687636d3..291eef43e0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less @@ -90,13 +90,13 @@ input.umb-table__input { .umb-table-body .umb-table-row { color: @gray-5; - border-top: 1px solid @gray-8; + border-top: 1px solid @gray-9; cursor: pointer; font-size: 14px; position: relative; min-height: 52px; &:hover { - background-color: @gray-10; + background-color: @ui-option-hover; } } @@ -151,12 +151,26 @@ input.umb-table__input { // Show checkmark when checked, hide file icon .umb-table-row--selected { + /* .umb-table-body__fileicon { display: none; } .umb-table-body__checkicon { display: inline-block; } + */ + &::before { + content: ""; + position: absolute; + z-index:1; + top: 1px; + left: 1px; + right: 1px; + bottom: 1px; + border: 2px solid @ui-selected-border; + box-shadow: 0 0 2px 0 fade(@ui-selected-border, 80%); + pointer-events: none; + } } // Table Row Styles diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index e66c3de597..80665e4c64 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -117,7 +117,7 @@ @pinkRedLight: #ff8a89;// added 2019 @brown: #9d8057;// added 2019 @brownLight: #e4e0dd;// added 2019 -@brownGrayLight: #f3f2f1;// added 2019 +@brownGrayLight: #f6f4f4;// added 2019 @orange: #ff9412;// added 2019 //@u-greyLight: #f2ebe6;// added 2019 @@ -134,6 +134,7 @@ //@ui-active: #346ab3; @ui-active: @pinkLight; +@ui-active-blur: @brownLight; @ui-active-type: @blueExtraDark; @ui-active-type-hover: @blueMid; @@ -142,7 +143,7 @@ @ui-selected-type: @blueExtraDark; @ui-selected-type-hover: @blueMid; @ui-selected-border: @pinkLight; -@ui-selected-border-hover: @pinkLight; +@ui-selected-border-hover: darken(@pinkLight, 10); @ui-light-border: @pinkLight; @ui-light-type: @gray-4; diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-item.html b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-item.html index d3854683c8..9474e98350 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-item.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-item.html @@ -1,21 +1,19 @@ -
  • -
    - +
    +   {{node.name}} - +
    - +
  • - - diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html index 490c94a79d..487be4af87 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html @@ -5,8 +5,8 @@ ng-repeat="item in content" ng-class="{'-selected': item.selected}" ng-click="clickItem(item, $event, $index)"> - - + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/media/edit.html b/src/Umbraco.Web.UI.Client/src/views/media/edit.html index 3a72b0b40d..7a365aafaf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/edit.html @@ -14,7 +14,7 @@ hide-description="true" hide-alias="true" navigation="content.apps" - on-select-navigation-item="appChanged(app)"> + on-select-navigation-item="appChanged(item)"> diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js index 4868bf0c6b..175007e9ad 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js @@ -6,7 +6,10 @@ * @description * The controller for the media editor */ -function mediaEditController($scope, $routeParams, $q, appState, mediaResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, editorState, umbRequestHelper, $http, eventsService) { +function mediaEditController($scope, $routeParams, $q, appState, mediaResource, + entityResource, navigationService, notificationsService, angularHelper, + serverValidationManager, contentEditingHelper, fileManager, formHelper, + editorState, umbRequestHelper, $http, eventsService) { var evts = []; var nodeId = null; @@ -41,7 +44,91 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, $scope.page.listViewPath = null; $scope.page.saveButtonState = "init"; $scope.page.submitButtonLabelKey = "buttons_save"; + $scope.app = null; + if (create) { + + $scope.page.loading = true; + + mediaResource.getScaffold(nodeId, $routeParams.doctype) + .then(function (data) { + $scope.content = data; + + init(); + + $scope.page.loading = false; + + }); + } + else { + $scope.page.loading = true; + loadMedia() + .then(function(){ + $scope.page.loading = false; + }); + } + + function init() { + + var content = $scope.content; + + // we need to check wether an app is present in the current data, if not we will present the default app. + var isAppPresent = false; + + // on first init, we dont have any apps. but if we are re-initializing, we do, but ... + if ($scope.app) { + + // lets check if it still exists as part of our apps array. (if not we have made a change to our docType, even just a re-save of the docType it will turn into new Apps.) + _.forEach(content.apps, function(app) { + if (app === $scope.app) { + isAppPresent = true; + } + }); + + // if we did reload our DocType, but still have the same app we will try to find it by the alias. + if (isAppPresent === false) { + _.forEach(content.apps, function(app) { + if (app.alias === $scope.app.alias) { + isAppPresent = true; + app.active = true; + $scope.appChanged(app); + } + }); + } + + } + + // if we still dont have a app, lets show the first one: + if (isAppPresent === false) { + content.apps[0].active = true; + $scope.appChanged(content.apps[0]); + } + + + editorState.set($scope.content); + + bindEvents(); + + } + + function bindEvents() { + //bindEvents can be called more than once and we don't want to have multiple bound events + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + + evts.push(eventsService.on("editors.mediaType.saved", function(name, args) { + // if this media item uses the updated media type we need to reload the media item + if(args && args.mediaType && args.mediaType.key === $scope.content.contentType.key) { + $scope.page.loading = true; + loadMedia().then(function() { + $scope.page.loading = false; + }); + } + })); + } + $scope.page.submitButtonLabelKey = "buttons_save"; + /** Syncs the content item to it's tree node - this occurs on first load and after saving */ function syncTreeNode(content, path, initialLoad) { @@ -68,45 +155,6 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, }); } } - - if (create) { - - $scope.page.loading = true; - - mediaResource.getScaffold(nodeId, $routeParams.doctype) - .then(function (data) { - $scope.content = data; - - editorState.set($scope.content); - - init(); - - $scope.page.loading = false; - - }); - } - else { - $scope.page.loading = true; - loadMedia() - .then(function(){ - $scope.page.loading = false; - }); - } - - function init() { - - if (!$scope.app) { - // set first app to active - $scope.content.apps[0].active = true; - $scope.app = $scope.content.apps[0]; - } - - // setup infinite mode - if(infiniteMode) { - $scope.page.submitButtonLabelKey = "buttons_saveAndClose"; - } - - } $scope.save = function () { @@ -212,14 +260,12 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, $scope.appChanged = function (app) { $scope.app = app; - } - - evts.push(eventsService.on("editors.mediaType.saved", function(name, args) { - // if this media item uses the updated media type we need to reload the media item - if(args && args.mediaType && args.mediaType.key === $scope.content.contentType.key) { - loadMedia(); + + // setup infinite mode + if(infiniteMode) { + $scope.page.submitButtonLabelKey = "buttons_saveAndClose"; } - })); + } //ensure to unregister from all events! $scope.$on('$destroy', function () { diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js index c8aba53cf5..0740f7866f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js @@ -9,7 +9,10 @@ (function () { "use strict"; - function MediaTypesEditController($scope, $routeParams, mediaTypeResource, dataTypeResource, editorState, contentEditingHelper, formHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, $filter, $q, localizationService, overlayHelper, eventsService) { + function MediaTypesEditController($scope, $routeParams, mediaTypeResource, + dataTypeResource, editorState, contentEditingHelper, formHelper, + navigationService, iconHelper, contentTypeHelper, notificationsService, + $filter, $q, localizationService, overlayHelper, eventsService) { var vm = this; var evts = []; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.html index d11e176c2b..0aa4b5e561 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.html @@ -65,8 +65,8 @@

    {{item.alias}} ({{item.width}}px × {{item.height}}px)

    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html index dba6a41a7c..9a638d91fa 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html @@ -13,13 +13,13 @@
    - + Create {{listViewAllowedTypes[0].name}} @@ -85,6 +85,7 @@ type="button" label="Clear selection" label-key="buttons_clearSelection" + button-style="selection" action="clearSelection()" disabled="actionInProgress"> @@ -139,7 +140,7 @@ ng-if="options.allowBulkPublish && (buttonPermissions == null || buttonPermissions.canPublish)" style="margin-right: 5px;" type="button" - button-style="outline" + button-style="selection" label-key="actions_publish" icon="icon-globe" action="publish()" @@ -152,7 +153,7 @@ ng-if="options.allowBulkUnpublish && (buttonPermissions == null || buttonPermissions.canUnpublish)" style="margin-right: 5px;" type="button" - button-style="outline" + button-style="selection" label-key="actions_unpublish" icon="icon-block" action="unpublish()" @@ -165,7 +166,7 @@ ng-if="options.allowBulkCopy && (buttonPermissions == null || buttonPermissions.canCopy)" style="margin-right: 5px;" type="button" - button-style="outline" + button-style="selection" label-key="actions_copy" icon="icon-documents" action="copy()" @@ -178,7 +179,7 @@ ng-if="options.allowBulkMove && (buttonPermissions == null || buttonPermissions.canMove)" style="margin-right: 5px;" type="button" - button-style="outline" + button-style="selection" label-key="actions_move" icon="icon-enter" action="move()" @@ -190,7 +191,7 @@ { private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly PublishedRouter _publishedRouter; + private readonly IPublishedRouter _publishedRouter; private readonly ILocalizationService _localizationService; private readonly ILocalizedTextService _textService; private readonly IContentService _contentService; @@ -19,7 +19,7 @@ namespace Umbraco.Web.Models.Mapping public ContentUrlResolver( IUmbracoContextAccessor umbracoContextAccessor, - PublishedRouter publishedRouter, + IPublishedRouter publishedRouter, ILocalizationService localizationService, ILocalizedTextService textService, IContentService contentService, diff --git a/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs b/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs index bba1522350..c6fc1d907f 100644 --- a/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs +++ b/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs @@ -33,8 +33,7 @@ namespace Umbraco.Web.Mvc /// public EnsurePublishedContentRequestAttribute(UmbracoContext umbracoContext, int contentId) { - if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); - _umbracoContext = umbracoContext; + _umbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext)); _contentId = contentId; } @@ -63,8 +62,7 @@ namespace Umbraco.Web.Mvc /// public EnsurePublishedContentRequestAttribute(UmbracoContext umbracoContext, string dataTokenName) { - if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); - _umbracoContext = umbracoContext; + _umbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext)); _dataTokenName = dataTokenName; } @@ -74,7 +72,7 @@ namespace Umbraco.Web.Mvc protected UmbracoContext UmbracoContext => _umbracoContext ?? (_umbracoContext = UmbracoContext.Current); // TODO: try lazy property injection? - private PublishedRouter PublishedRouter => Core.Composing.Current.Factory.GetInstance(); + private IPublishedRouter PublishedRouter => Core.Composing.Current.Factory.GetInstance(); /// /// Exposes an UmbracoHelper diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index 215eb39573..2c550effc4 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -377,7 +377,7 @@ namespace Umbraco.Web.Mvc if ((request.HasTemplate == false && Features.Disabled.DisableTemplates == false) && routeDef.HasHijackedRoute == false) { - request.UpdateOnMissingTemplate(); // request will go 404 + request.UpdateToNotFound(); // request will go 404 // HandleHttpResponseStatus returns a value indicating that the request should // not be processed any further, eg because it has been redirect. then, exit. diff --git a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs index 48e16b48d7..1af434e94e 100644 --- a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Mvc public abstract class UmbracoVirtualNodeRouteHandler : IRouteHandler { // TODO: try lazy property injection? - private PublishedRouter PublishedRouter => Core.Composing.Current.Factory.GetInstance(); + private IPublishedRouter PublishedRouter => Current.Factory.GetInstance(); /// /// Returns the UmbracoContext for this route handler diff --git a/src/Umbraco.Web/Routing/IPublishedRouter.cs b/src/Umbraco.Web/Routing/IPublishedRouter.cs new file mode 100644 index 0000000000..df06e42e55 --- /dev/null +++ b/src/Umbraco.Web/Routing/IPublishedRouter.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Web.Routing +{ + /// + /// Routes requests. + /// + public interface IPublishedRouter + { + // TODO: consider this and RenderRouteHandler - move some code around? + + /// + /// Creates a published request. + /// + /// The current Umbraco context. + /// The (optional) request Uri. + /// A published request. + PublishedRequest CreateRequest(UmbracoContext umbracoContext, Uri uri = null); + + /// + /// Prepares a request for rendering. + /// + /// The request. + /// A value indicating whether the request was successfully prepared and can be rendered. + bool PrepareRequest(PublishedRequest request); + + /// + /// Tries to route a request. + /// + /// The request. + /// A value indicating whether the request can be routed to a document. + bool TryRouteRequest(PublishedRequest request); + + /// + /// Gets a template. + /// + /// The template alias + /// The template. + ITemplate GetTemplate(string alias); + + /// + /// Updates the request to "not found". + /// + /// The request. + /// + /// This method is invoked when the pipeline decides it cannot render + /// the request, for whatever reason, and wants to force it to be re-routed + /// and rendered as if no document were found (404). + /// + void UpdateRequestToNotFound(PublishedRequest request); + } +} diff --git a/src/Umbraco.Web/Routing/PublishedRequest.cs b/src/Umbraco.Web/Routing/PublishedRequest.cs index a2ff71e634..d2a930fa96 100644 --- a/src/Umbraco.Web/Routing/PublishedRequest.cs +++ b/src/Umbraco.Web/Routing/PublishedRequest.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Routing /// public class PublishedRequest { - private readonly PublishedRouter _publishedRouter; + private readonly IPublishedRouter _publishedRouter; private bool _readonly; // after prepared private bool _readonlyUri; // after preparing @@ -35,7 +35,7 @@ namespace Umbraco.Web.Routing /// The published router. /// The Umbraco context. /// The request Uri. - internal PublishedRequest(PublishedRouter publishedRouter, UmbracoContext umbracoContext, Uri uri = null) + internal PublishedRequest(IPublishedRouter publishedRouter, UmbracoContext umbracoContext, Uri uri = null) { UmbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext)); _publishedRouter = publishedRouter ?? throw new ArgumentNullException(nameof(publishedRouter)); @@ -290,11 +290,11 @@ namespace Umbraco.Web.Routing /// public bool HasTemplate => TemplateModel != null; - internal void UpdateOnMissingTemplate() + internal void UpdateToNotFound() { var __readonly = _readonly; _readonly = false; - _publishedRouter.UpdateRequestOnMissingTemplate(this); + _publishedRouter.UpdateRequestToNotFound(this); _readonly = __readonly; } diff --git a/src/Umbraco.Web/Routing/PublishedRouter.cs b/src/Umbraco.Web/Routing/PublishedRouter.cs index 27baf86522..0ce6a9b768 100644 --- a/src/Umbraco.Web/Routing/PublishedRouter.cs +++ b/src/Umbraco.Web/Routing/PublishedRouter.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.IO; using System.Web.Security; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; @@ -18,8 +19,10 @@ using Umbraco.Web.Security; namespace Umbraco.Web.Routing { - // TODO: making sense to have an interface? - public class PublishedRouter + /// + /// Provides the default implementation. + /// + public class PublishedRouter : IPublishedRouter { private readonly IWebRoutingSection _webRoutingSection; private readonly ContentFinderCollection _contentFinders; @@ -47,15 +50,9 @@ namespace Umbraco.Web.Routing _profilingLogger = proflog ?? throw new ArgumentNullException(nameof(proflog)); _variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); _logger = proflog; - - GetRolesForLogin = s => Roles.Provider.GetRolesForUser(s); } - // TODO: in 7.7 this is cached in the PublishedContentRequest, which ... makes little sense - // killing it entirely, if we need cache, just implement it properly !! - // this is all so weird - public Func> GetRolesForLogin { get; } - + /// public PublishedRequest CreateRequest(UmbracoContext umbracoContext, Uri uri = null) { return new PublishedRequest(this, umbracoContext, uri ?? umbracoContext.CleanedUmbracoUrl); @@ -63,10 +60,8 @@ namespace Umbraco.Web.Routing #region Request - /// - /// Tries to route the request. - /// - internal bool TryRouteRequest(PublishedRequest request) + /// + public bool TryRouteRequest(PublishedRequest request) { // disabled - is it going to change the routing? //_pcr.OnPreparing(); @@ -96,12 +91,7 @@ namespace Umbraco.Web.Routing _variationContextAccessor.VariationContext = new VariationContext(culture); } - /// - /// Prepares the request. - /// - /// - /// Returns false if the request was not successfully prepared - /// + /// public bool PrepareRequest(PublishedRequest request) { // note - at that point the original legacy module did something do handle IIS custom 404 errors @@ -210,11 +200,8 @@ namespace Umbraco.Web.Routing return true; } - /// - /// Updates the request when there is no template to render the content. - /// - /// This is called from Mvc when there's a document to render but no template. - public void UpdateRequestOnMissingTemplate(PublishedRequest request) + /// + public void UpdateRequestToNotFound(PublishedRequest request) { // clear content var content = request.PublishedContent; @@ -381,11 +368,7 @@ namespace Umbraco.Web.Routing #region Document and template - /// - /// Gets a template. - /// - /// The template alias - /// The template. + /// public ITemplate GetTemplate(string alias) { return _services.FileService.GetTemplate(alias); @@ -602,7 +585,7 @@ namespace Umbraco.Web.Routing if (loginPageId != request.PublishedContent.Id) request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(loginPageId); } - else if (_services.PublicAccessService.HasAccess(request.PublishedContent.Id, _services.ContentService, membershipHelper.CurrentUserName, GetRolesForLogin(membershipHelper.CurrentUserName)) == false) + else if (_services.PublicAccessService.HasAccess(request.PublishedContent.Id, _services.ContentService, membershipHelper.CurrentUserName, membershipHelper.GetCurrentUserRoles()) == false) { _logger.Debug("EnsurePublishedContentAccess: Current member has not access, redirect to error page"); var errorPageId = publicAccessAttempt.Result.NoAccessNodeId; diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index 8db657ed30..2a840828b6 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.Routing /// Contains all the Urls that we can figure out (based upon domains, etc). /// public static IEnumerable GetContentUrls(this IContent content, - PublishedRouter publishedRouter, + IPublishedRouter publishedRouter, UmbracoContext umbracoContext, ILocalizationService localizationService, ILocalizedTextService textService, @@ -92,7 +92,7 @@ namespace Umbraco.Web.Routing /// private static IEnumerable GetContentUrlsByCulture(IContent content, IEnumerable cultures, - PublishedRouter publishedRouter, + IPublishedRouter publishedRouter, UmbracoContext umbracoContext, IContentService contentService, ILocalizedTextService textService, @@ -161,7 +161,7 @@ namespace Umbraco.Web.Routing return UrlInfo.Message(textService.Localize("content/parentCultureNotPublished", new[] {parent.Name}), culture); } - private static bool DetectCollision(IContent content, string url, string culture, UmbracoContext umbracoContext, PublishedRouter publishedRouter, ILocalizedTextService textService, out UrlInfo urlInfo) + private static bool DetectCollision(IContent content, string url, string culture, UmbracoContext umbracoContext, IPublishedRouter publishedRouter, ILocalizedTextService textService, out UrlInfo urlInfo) { // test for collisions on the 'main' url var uri = new Uri(url.TrimEnd('/'), UriKind.RelativeOrAbsolute); diff --git a/src/Umbraco.Web/Runtime/WebRuntimeComposer.cs b/src/Umbraco.Web/Runtime/WebRuntimeComposer.cs index 27c42e2c91..641ffe5fbe 100644 --- a/src/Umbraco.Web/Runtime/WebRuntimeComposer.cs +++ b/src/Umbraco.Web/Runtime/WebRuntimeComposer.cs @@ -191,7 +191,7 @@ namespace Umbraco.Web.Runtime composition.RegisterAuto(typeof(UmbracoViewPage<>)); // register published router - composition.RegisterUnique(); + composition.RegisterUnique(); composition.Register(_ => Current.Configs.Settings().WebRouting); // register preview SignalR hub diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index e053617806..daec4bb1f7 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -31,7 +31,6 @@ namespace Umbraco.Web.Security private readonly IMemberTypeService _memberTypeService; private readonly IUserService _userService; private readonly IPublicAccessService _publicAccessService; - private readonly PublishedRouter _publishedRouter; private readonly AppCaches _appCaches; private readonly ILogger _logger; @@ -46,7 +45,6 @@ namespace Umbraco.Web.Security IMemberTypeService memberTypeService, IUserService userService, IPublicAccessService publicAccessService, - PublishedRouter publishedRouter, AppCaches appCaches, ILogger logger ) @@ -57,7 +55,6 @@ namespace Umbraco.Web.Security _memberTypeService = memberTypeService; _userService = userService; _publicAccessService = publicAccessService; - _publishedRouter = publishedRouter; _appCaches = appCaches; _logger = logger; @@ -116,7 +113,7 @@ namespace Umbraco.Web.Security { return UmbracoContext.PublishedRequest == null ? _publicAccessService.HasAccess(path, CurrentUserName, roleProvider.GetRolesForUser) - : _publicAccessService.HasAccess(path, CurrentUserName, _publishedRouter.GetRolesForLogin); + : _publicAccessService.HasAccess(path, CurrentUserName, GetUserRoles); } /// @@ -514,6 +511,24 @@ namespace Umbraco.Web.Security } #endregion + /// + /// Gets the current user's roles. + /// + /// Roles are cached per user name, at request level. + public IEnumerable GetCurrentUserRoles() + => GetUserRoles(CurrentUserName); + + /// + /// Gets a user's roles. + /// + /// Roles are cached per user name, at request level. + public IEnumerable GetUserRoles(string userName) + { + // optimize by caching per-request (v7 cached per PublishedRequest, in PublishedRouter) + var key = "Umbraco.Web.Security.MembershipHelper__Roles__" + userName; + return _appCaches.RequestCache.GetCacheItem(key, () => Roles.Provider.GetRolesForUser(userName)); + } + /// /// Returns the login status model of the currently logged in member, if no member is logged in it returns null; /// @@ -618,7 +633,7 @@ namespace Umbraco.Web.Security var provider = _membershipProvider; string username; - + if (provider.IsUmbracoMembershipProvider()) { var member = GetCurrentPersistedMember(); @@ -626,7 +641,7 @@ namespace Umbraco.Web.Security if (member == null) return false; username = member.Username; - + // If types defined, check member is of one of those types var allowTypesList = allowTypes as IList ?? allowTypes.ToList(); if (allowTypesList.Any(allowType => allowType != string.Empty)) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 9b685550a8..19df0bc96e 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -184,6 +184,7 @@ + diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs index 95819e79d7..ba1a384484 100644 --- a/src/Umbraco.Web/UmbracoInjectedModule.cs +++ b/src/Umbraco.Web/UmbracoInjectedModule.cs @@ -45,7 +45,7 @@ namespace Umbraco.Web private readonly UrlProviderCollection _urlProviders; private readonly IRuntimeState _runtime; private readonly ILogger _logger; - private readonly PublishedRouter _publishedRouter; + private readonly IPublishedRouter _publishedRouter; private readonly IVariationContextAccessor _variationContextAccessor; public UmbracoInjectedModule( @@ -56,7 +56,7 @@ namespace Umbraco.Web UrlProviderCollection urlProviders, IRuntimeState runtime, ILogger logger, - PublishedRouter publishedRouter, + IPublishedRouter publishedRouter, IVariationContextAccessor variationContextAccessor) { _combinedRouteCollection = new Lazy(CreateRouteCollection);