From 9b36bc40978d4bb98375f18fbef5b19dd40dd686 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 7 Jun 2019 13:13:52 +1000 Subject: [PATCH 01/62] Fixes notifications when publishing fails and warnings are issues - these were not being displayed --- src/Umbraco.Web/Editors/ContentController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index de65689353..4c920626a6 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -741,7 +741,7 @@ namespace Umbraco.Web.Editors //global notifications AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures); //variant specific notifications - foreach (var c in successfulCultures) + foreach (var c in successfulCultures ?? Array.Empty()) AddMessageForPublishStatus(publishStatus, notifications.GetOrCreate(c), successfulCultures); } break; @@ -762,7 +762,7 @@ namespace Umbraco.Web.Editors //global notifications AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures); //variant specific notifications - foreach (var c in successfulCultures) + foreach (var c in successfulCultures ?? Array.Empty()) AddMessageForPublishStatus(publishStatus, notifications.GetOrCreate(c), successfulCultures); } break; @@ -1143,7 +1143,7 @@ namespace Umbraco.Web.Editors var publishStatus = Services.ContentService.SaveAndPublishBranch(contentItem.PersistedContent, force, userId: Security.CurrentUser.Id); // TODO: Deal with multiple cancellations wasCancelled = publishStatus.Any(x => x.Result == PublishResultType.FailedPublishCancelledByEvent); - successfulCultures = Array.Empty(); + successfulCultures = null; //must be null! this implies invariant return publishStatus; } From 79cd3129120456414c364499b3e03bade6a234f1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 7 Jun 2019 14:20:44 +1000 Subject: [PATCH 02/62] Fixes message when descendant publishing fails when there is a mix of variant types --- src/Umbraco.Web/Editors/ContentController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 4c920626a6..d48e586036 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -2128,7 +2128,7 @@ namespace Umbraco.Web.Editors { foreach (var c in successfulCultures) { - var names = string.Join(", ", status.Select(x => $"'{x.Content.GetCultureName(c)}'")); + var names = string.Join(", ", status.Select(x => $"'{(x.Content.ContentType.VariesByCulture() ? x.Content.GetCultureName(c) : x.Content.Name)}'")); display.AddWarningNotification( Services.TextService.Localize("publish"), Services.TextService.Localize("publish/contentPublishedFailedInvalid", From 120f4c30963b53aee6955470cff5137672d57ae3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 11 Jun 2019 16:31:32 +1000 Subject: [PATCH 03/62] removes scope.busy callls (legacy old stuff), add ability to preserve editor state between routes, for content we now preserve editor state when redirecting to new content and then rebind some values from that editor state when the editor loads, updates editorstate.service and filemanager.service to manage themselves on route change (decoupling), fixes variant mapper to ensure that the variants are always returned in a deterministic order. --- .../components/content/edit.controller.js | 141 ++++++++++-------- .../services/contenteditinghelper.service.js | 41 ++--- .../common/services/editorstate.service.js | 41 ++++- .../common/services/filemanager.service.js | 25 +++- src/Umbraco.Web.UI.Client/src/init.js | 11 +- .../Models/Mapping/ContentVariantMapper.cs | 2 +- 6 files changed, 155 insertions(+), 106 deletions(-) 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 3a874f83c6..69cde0ba7d 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,26 +26,26 @@ $scope.allowOpen = true; $scope.app = null; - function init() { - + function init(firstLoad) { + 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) { + _.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) { + _.forEach(content.apps, function (app) { if (app.alias === $scope.app.alias) { isAppPresent = true; app.active = true; @@ -53,7 +53,7 @@ } }); } - + } // if we still dont have a app, lets show the first one: @@ -61,7 +61,23 @@ content.apps[0].active = true; $scope.appChanged(content.apps[0]); } - + + if (firstLoad) { + + //Check if the editor state is holding a reference to a new item and merge it's notifications. + //We need to do this when creating new content because when it's created, we redirect to a new route and re-load the content from the server + //but we need to maintain it's 'notifications' data so we can re-bind it in the UI. + //The only time that editorState will contain something here is if it was preserved and that is only explicitly done when creating new content and redirecting. + + var currState = editorState.getCurrent(); + if (currState && currState.udi === content.udi) { + content.notifications = currState.notifications; + for (var i = 0; i < content.variants.length; i++) { + content.variants[i].notifications = currState.variants[i].notifications; + } + } + } + editorState.set(content); //We fetch all ancestors of the node to generate the footer breadcrumb navigation @@ -118,10 +134,10 @@ function isContentCultureVariant() { return $scope.content.variants.length > 1; } - + function reload() { $scope.page.loading = true; - loadContent().then(function() { + loadContent().then(function () { $scope.page.loading = false; }); } @@ -134,7 +150,7 @@ evts.push(eventsService.on("editors.documentType.saved", function (name, args) { // if this content item uses the updated doc type we need to reload the content item - if(args && args.documentType && $scope.content.documentType.id === args.documentType.id) { + if (args && args.documentType && $scope.content.documentType.id === args.documentType.id) { reload(); } })); @@ -144,7 +160,7 @@ /** * This does the content loading and initializes everything, called on first load */ - function loadContent() { + function loadContent(firstLoad) { //we are editing so get the content item from the server return $scope.getMethod()($scope.contentId) @@ -158,7 +174,7 @@ "/content/content/edit/" + data.parentId; } - init(); + init(firstLoad); syncTreeNode($scope.content, $scope.content.path, true); @@ -219,7 +235,7 @@ $scope.page.showPreviewButton = true; } - + /** Syncs the content item to it's tree node - this occurs on first load and after saving */ function syncTreeNode(content, path, initialLoad) { @@ -328,7 +344,7 @@ $scope.contentForm.$dirty = false; for (var i = 0; i < $scope.content.variants.length; i++) { - if($scope.content.variants[i].isDirty){ + if ($scope.content.variants[i].isDirty) { $scope.contentForm.$dirty = true; return; } @@ -337,7 +353,7 @@ // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish function performSave(args) { - + //Used to check validility of nested form - coming from Content Apps mostly //Set them all to be invalid var fieldsToRollback = checkValidility(); @@ -364,11 +380,6 @@ function (err) { syncTreeNode($scope.content, $scope.content.path); - //error - if (err) { - editorState.set($scope.content); - } - resetNestedFieldValiation(fieldsToRollback); return $q.reject(err); @@ -421,9 +432,9 @@ //need to show a notification else it's not clear there was an error. localizationService.localizeMany([ - "speechBubbles_validationFailedHeader", - "speechBubbles_validationFailedMessage" - ] + "speechBubbles_validationFailedHeader", + "speechBubbles_validationFailedMessage" + ] ).then(function (data) { notificationsService.error(data[0], data[1]); }); @@ -453,7 +464,7 @@ $scope.page.loading = true; - loadContent().then(function () { + loadContent(true).then(function () { $scope.page.loading = false; }); } @@ -510,7 +521,7 @@ variants: $scope.content.variants, //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation submitButtonLabelKey: "buttons_saveToPublish", - submit: function(model) { + submit: function (model) { model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response @@ -518,14 +529,14 @@ saveMethod: contentResource.sendToPublish, action: "sendToPublish", showNotifications: false - }).then(function(data) { - //show all notifications manually here since we disabled showing them automatically in the save method - formHelper.showNotifications(data); - clearNotifications($scope.content); - overlayService.close(); - return $q.when(data); - }, - function(err) { + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, + function (err) { clearDirtyState($scope.content.variants); model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties @@ -534,7 +545,7 @@ return $q.when(err); }); }, - close: function() { + close: function () { overlayService.close(); } }; @@ -570,7 +581,7 @@ variants: $scope.content.variants, //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation submitButtonLabelKey: "buttons_saveAndPublish", - submit: function(model) { + submit: function (model) { model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response @@ -578,14 +589,14 @@ saveMethod: contentResource.publish, action: "publish", showNotifications: false - }).then(function(data) { - //show all notifications manually here since we disabled showing them automatically in the save method - formHelper.showNotifications(data); - clearNotifications($scope.content); - overlayService.close(); - return $q.when(data); - }, - function(err) { + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, + function (err) { clearDirtyState($scope.content.variants); model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties @@ -594,7 +605,7 @@ return $q.when(err); }); }, - close: function() { + close: function () { overlayService.close(); } }; @@ -634,7 +645,7 @@ variants: $scope.content.variants, //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation submitButtonLabelKey: "buttons_save", - submit: function(model) { + submit: function (model) { model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response @@ -642,14 +653,14 @@ saveMethod: $scope.saveMethod(), action: "save", showNotifications: false - }).then(function(data) { - //show all notifications manually here since we disabled showing them automatically in the save method - formHelper.showNotifications(data); - clearNotifications($scope.content); - overlayService.close(); - return $q.when(data); - }, - function(err) { + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, + function (err) { clearDirtyState($scope.content.variants); model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties @@ -658,7 +669,7 @@ return $q.when(err); }); }, - close: function(oldModel) { + close: function (oldModel) { overlayService.close(); } }; @@ -685,7 +696,7 @@ }; - $scope.schedule = function() { + $scope.schedule = function () { clearNotifications($scope.content); //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "schedule" })) { @@ -755,7 +766,7 @@ } }; - $scope.publishDescendants = function() { + $scope.publishDescendants = function () { clearNotifications($scope.content); //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "publishDescendants" })) { @@ -883,13 +894,13 @@ * @param {any} app */ $scope.appChanged = function (app) { - + $scope.app = app; - + $scope.$broadcast("editors.apps.appChanged", { app: app }); - + createButtons($scope.content); - + }; /** @@ -907,11 +918,11 @@ $scope.infiniteModel.close($scope.infiniteModel); } }; - + /** * Call back when user click the back-icon */ - $scope.onBack = function() { + $scope.onBack = function () { if ($scope.infiniteModel && $scope.infiniteModel.close) { $scope.infiniteModel.close($scope.infiniteModel); } else { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index 1b2be5f635..f54accbba0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -5,7 +5,7 @@ * @description A helper service for most editors, some methods are specific to content/media/member model types but most are used by * all editors to share logic and reduce the amount of replicated code among editors. **/ -function contentEditingHelper(fileManager, $q, $location, $routeParams, notificationsService, navigationService, localizationService, serverValidationManager, formHelper) { +function contentEditingHelper(fileManager, $q, $location, $routeParams, editorState, notificationsService, navigationService, localizationService, serverValidationManager, formHelper) { function isValidIdentifier(id) { @@ -35,8 +35,6 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica return { /** Used by the content editor and mini content editor to perform saving operations */ - // TODO: Make this a more helpful/reusable method for other form operations! we can simplify this form most forms - // = this is already done in the formhelper service contentEditorPerformSave: function (args) { if (!angular.isObject(args)) { throw "args must be an object"; @@ -62,9 +60,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica //we will use the default one for content if not specified var rebindCallback = args.rebindCallback === undefined ? self.reBindChangedProperties : args.rebindCallback; - if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, action: args.action })) { - - args.scope.busy = true; + if (formHelper.submitForm({ scope: args.scope, action: args.action })) { return args.saveMethod(args.content, $routeParams.create, fileManager.getFiles(), args.showNotifications) .then(function (data) { @@ -80,7 +76,9 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica } }); - args.scope.busy = false; + //update editor state to what is current + editorState.set(args.content); + return $q.resolve(data); }, function (err) { @@ -93,7 +91,9 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica } }); - args.scope.busy = false; + //update editor state to what is current + editorState.set(args.content); + return $q.reject(err); }); } @@ -265,7 +265,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica // if publishing is allowed also allow schedule publish // we add this manually becuase it doesn't have a permission so it wont // get picked up by the loop through permissions - if( _.contains(args.content.allowedActions, "U")) { + if (_.contains(args.content.allowedActions, "U")) { buttons.subButtons.push(createButtonDefinition("SCHEDULE")); buttons.subButtons.push(createButtonDefinition("PUBLISH_DESCENDANTS")); } @@ -274,7 +274,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica // so long as it's already published and if the user has access to publish // and the user has access to unpublish (may have been removed via Event) if (!args.create) { - var hasPublishedVariant = args.content.variants.filter(function(variant) { return (variant.state === "Published" || variant.state === "PublishedPendingChanges"); }).length > 0; + var hasPublishedVariant = args.content.variants.filter(function (variant) { return (variant.state === "Published" || variant.state === "PublishedPendingChanges"); }).length > 0; if (hasPublishedVariant && _.contains(args.content.allowedActions, "U") && _.contains(args.content.allowedActions, "Z")) { buttons.subButtons.push(createButtonDefinition("Z")); } @@ -444,7 +444,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica var shouldIgnore = function (propName) { return _.some([ "variants", - + "tabs", "properties", "apps", @@ -600,16 +600,14 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica } } + // we need to detect what properties have changed and re-bind them with the server data. + if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { + args.rebindCallback(); + } + if (!args.redirectOnFailure || !this.redirectToCreatedContent(args.err.data.id, args.err.data.ModelState)) { - //we are not redirecting because this is not new content, it is existing content. In this case - // we need to detect what properties have changed and re-bind them with the server data. Then we need - // to re-bind any server validation errors after the digest takes place. - - if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { - args.rebindCallback(); - } - - //notify all validators (don't clear the server validations though since we need to maintain their state because of + // we are not redirecting because this is not new content, it is existing content. In this case + // notify all validators (don't clear the server validations though since we need to maintain their state because of // how the variant switcher works in content). server validation state is always cleared when an editor first loads // and in theory when an editor is destroyed. serverValidationManager.notify(); @@ -680,6 +678,9 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica //clear the query strings navigationService.clearSearch(["cculture"]); + //preserve the editor state when we are redirecting to new content + editorState.preserve(); + //change to new path $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id); //don't add a browser history for this diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js index 5e42af9c5e..acfef46e75 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js @@ -5,13 +5,16 @@ * * @description * Tracks the parent object for complex editors by exposing it as - * an object reference via editorState.current.entity + * an object reference via editorState.current.getCurrent(). + * The state is cleared on each successful route. * * it is possible to modify this object, so should be used with care */ -angular.module('umbraco.services').factory("editorState", function() { +angular.module('umbraco.services').factory("editorState", function ($rootScope) { var current = null; + var preserveBetweenRoute = false; + var state = { /** @@ -40,7 +43,7 @@ angular.module('umbraco.services').factory("editorState", function() { * Since the editorstate entity is read-only, you cannot set it to null * only through the reset() method */ - reset: function() { + reset: function () { current = null; }, @@ -59,8 +62,25 @@ angular.module('umbraco.services').factory("editorState", function() { * editorState.current can not be overwritten, you should only read values from it * since modifying individual properties should be handled by the property editors */ - getCurrent: function() { + getCurrent: function () { return current; + }, + + /** + * @ngdoc function + * @name umbraco.services.angularHelper#preserve + * @methodOf umbraco.services.editorState + * @function + * + * @description + * When called it will flag the state to be preserved after the next route. + * Normally the editorState is cleared on each successful route but in some cases it should be preserved so calling this will preserve it. + * + * editorState.current can not be overwritten, you should only read values from it + * since modifying individual properties should be handled by the property editors + */ + preserve: function () { + preserveBetweenRoute = true; } }; @@ -76,5 +96,18 @@ angular.module('umbraco.services').factory("editorState", function() { } }); + //execute on each successful route (this is only bound once per application since a service is a singleton) + $rootScope.$on('$routeChangeSuccess', function (event, current, previous) { + + if (!preserveBetweenRoute) { + //reset the editorState on each successful route chage + state.reset(); + } + + //always reset this + preserveBetweenRoute = false; + + }); + return state; }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js index 6d319ad90a..8fe6761c94 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js @@ -8,11 +8,12 @@ * that need to attach files. * When a route changes successfully, we ensure that the collection is cleared. */ -function fileManager() { +function fileManager($rootScope) { var fileCollection = []; - return { + + var mgr = { /** * @ngdoc function * @name umbraco.services.fileManager#addFiles @@ -24,7 +25,7 @@ function fileManager() { * for the files collection that effectively clears the files for the specified editor. */ setFiles: function (args) { - + //propertyAlias, files if (!angular.isString(args.propertyAlias)) { throw "args.propertyAlias must be a non empty string"; @@ -52,7 +53,7 @@ function fileManager() { fileCollection.push({ alias: args.propertyAlias, file: args.files[i], culture: args.culture, metaData: metaData }); } }, - + /** * @ngdoc function * @name umbraco.services.fileManager#getFiles @@ -62,10 +63,10 @@ function fileManager() { * @description * Returns all of the files attached to the file manager */ - getFiles: function() { + getFiles: function () { return fileCollection; }, - + /** * @ngdoc function * @name umbraco.services.fileManager#clearFiles @@ -78,7 +79,17 @@ function fileManager() { clearFiles: function () { fileCollection = []; } -}; + }; + + //execute on each successful route (this is only bound once per application since a service is a singleton) + $rootScope.$on('$routeChangeSuccess', function (event, current, previous) { + //reset the file manager on each route change, the file collection is only relavent + // when working in an editor and submitting data to the server. + //This ensures that memory remains clear of any files and that the editors don't have to manually clear the files. + mgr.clearFiles(); + }); + + return mgr; } angular.module('umbraco.services').factory('fileManager', fileManager); diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js index eaa2fe7b31..2e3aeb7e8d 100644 --- a/src/Umbraco.Web.UI.Client/src/init.js +++ b/src/Umbraco.Web.UI.Client/src/init.js @@ -1,6 +1,6 @@ /** Executed when the application starts, binds to events and set global state */ -app.run(['userService', '$q', '$log', '$rootScope', '$route', '$location', 'urlHelper', 'navigationService', 'appState', 'editorState', 'fileManager', 'assetsService', 'eventsService', '$cookies', '$templateCache', 'localStorageService', 'tourService', 'dashboardResource', - function (userService, $q, $log, $rootScope, $route, $location, urlHelper, navigationService, appState, editorState, fileManager, assetsService, eventsService, $cookies, $templateCache, localStorageService, tourService, dashboardResource) { +app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', 'appState', 'assetsService', 'eventsService', '$cookies', 'tourService', + function ($rootScope, $route, $location, urlHelper, navigationService, appState, assetsService, eventsService, $cookies, tourService) { //This sets the default jquery ajax headers to include our csrf token, we // need to user the beforeSend method because our token changes per user/login so @@ -91,13 +91,6 @@ app.run(['userService', '$q', '$log', '$rootScope', '$route', '$location', 'urlH $rootScope.locationTitle = "Umbraco - " + $location.$$host; } - //reset the editorState on each successful route chage - editorState.reset(); - - //reset the file manager on each route change, the file collection is only relavent - // when working in an editor and submitting data to the server. - //This ensures that memory remains clear of any files and that the editors don't have to manually clear the files. - fileManager.clearFiles(); }); /** When the route change is rejected - based on checkAuth - we'll prevent the rejected route from executing including diff --git a/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs index 560d398a2c..3450de5dc5 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs @@ -59,7 +59,7 @@ namespace Umbraco.Web.Models.Mapping variants.Remove(defaultLang); //Sort the remaining languages a-z - variants = variants.OrderBy(x => x.Name).ToList(); + variants = variants.OrderBy(x => x.Language.IsoCode).ToList(); //Insert the default language as the first item variants.Insert(0, defaultLang); From 1f64ae468257b39310bddfb470389b418eb1d402 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 11 Jun 2019 17:17:19 +1000 Subject: [PATCH 04/62] Fixes language/variant mapping so it's consistent and by lang name not iso --- src/Umbraco.Web/Editors/LanguageController.cs | 2 +- src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Editors/LanguageController.cs b/src/Umbraco.Web/Editors/LanguageController.cs index 2ee77ca418..650dcea6e9 100644 --- a/src/Umbraco.Web/Editors/LanguageController.cs +++ b/src/Umbraco.Web/Editors/LanguageController.cs @@ -46,7 +46,7 @@ namespace Umbraco.Web.Editors { var allLanguages = Services.LocalizationService.GetAllLanguages(); - return Mapper.MapEnumerable(allLanguages); + return Mapper.Map, IEnumerable>(allLanguages); } [HttpGet] diff --git a/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs index 3450de5dc5..c279ae2c70 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs @@ -59,7 +59,7 @@ namespace Umbraco.Web.Models.Mapping variants.Remove(defaultLang); //Sort the remaining languages a-z - variants = variants.OrderBy(x => x.Language.IsoCode).ToList(); + variants = variants.OrderBy(x => x.Language.Name).ToList(); //Insert the default language as the first item variants.Insert(0, defaultLang); From e6a8ba84aa0424526c3c82b653e494817511ba2e Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 11 Jun 2019 21:47:45 +1000 Subject: [PATCH 05/62] starts re-binding the variant dialogs on create for the content editor (WIP - will probably revert and try a different approach) --- .../components/content/edit.controller.js | 204 ++++++++++-------- .../src/common/services/overlay.service.js | 19 +- 2 files changed, 132 insertions(+), 91 deletions(-) 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 69cde0ba7d..38dce4985b 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,7 +26,37 @@ $scope.allowOpen = true; $scope.app = null; - function init(firstLoad) { + //called only one time when the editor first loads + function firstLoad(content) { + //Check if the editor state is holding a reference to a new item and merge it's notifications. + //We need to do this when creating new content because when it's created, we redirect to a new route and re-load the content from the server + //but we need to maintain it's 'notifications' data so we can re-bind it in the UI. + //The only time that editorState will contain something here is if it was preserved and that is only explicitly done when creating new content and redirecting. + + var currState = editorState.getCurrent(); + if (currState && currState.udi === content.udi) { + content.notifications = currState.notifications; + for (var i = 0; i < content.variants.length; i++) { + content.variants[i].notifications = currState.variants[i].notifications; + } + + //Now check if the overlay service has an active overlay open that was owned by this editor prior to redirecting. + //This will occur if new variant content is created and there were notifications/errors for variants, the variant dialog will remain open + //but it will need to be refreshed with the new editor data since it will be holding a reference to stale scopes. + var currOverlay = overlayService.getCurrent(); + if (currOverlay && currOverlay.view.startsWith("views/content/overlays")) { + switch (currOverlay.view) { + case "views/content/overlays/publish.html": + overlayService.refresh(saveAndPublishDialog()); + break; + } + } + + } + } + + //this initializes the editor with the data which will be called more than once if the data is re-loaded + function init(isFirstLoad) { var content = $scope.content; @@ -62,20 +92,8 @@ $scope.appChanged(content.apps[0]); } - if (firstLoad) { - - //Check if the editor state is holding a reference to a new item and merge it's notifications. - //We need to do this when creating new content because when it's created, we redirect to a new route and re-load the content from the server - //but we need to maintain it's 'notifications' data so we can re-bind it in the UI. - //The only time that editorState will contain something here is if it was preserved and that is only explicitly done when creating new content and redirecting. - - var currState = editorState.getCurrent(); - if (currState && currState.udi === content.udi) { - content.notifications = currState.notifications; - for (var i = 0; i < content.variants.length; i++) { - content.variants[i].notifications = currState.variants[i].notifications; - } - } + if (isFirstLoad) { + firstLoad(content); } editorState.set(content); @@ -160,7 +178,7 @@ /** * This does the content loading and initializes everything, called on first load */ - function loadContent(firstLoad) { + function loadContent(isFirstLoad) { //we are editing so get the content item from the server return $scope.getMethod()($scope.contentId) @@ -174,7 +192,7 @@ "/content/content/edit/" + data.parentId; } - init(firstLoad); + init(isFirstLoad); syncTreeNode($scope.content, $scope.content.path, true); @@ -440,6 +458,83 @@ }); } + /** returns the save and publish dialog model */ + function saveAndPublishDialog() { + var dialog = { + parentScope: $scope, + view: "views/content/overlays/publish.html", + variants: $scope.content.variants, //set a model property for the dialog + skipFormValidation: true, //when submitting the overlay form, skip any client side validation + submitButtonLabelKey: "buttons_saveAndPublish", + submit: function (model) { + model.submitButtonState = "busy"; + clearNotifications($scope.content); + //we need to return this promise so that the dialog can handle the result and wire up the validation response + return performSave({ + saveMethod: contentResource.publish, + action: "publish", + showNotifications: false + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, + function (err) { + clearDirtyState($scope.content.variants); + model.submitButtonState = "error"; + //re-map the dialog model since we've re-bound the properties + dialog.variants = $scope.content.variants; + //don't reject, we've handled the error + return $q.when(err); + }); + }, + close: function () { + overlayService.close(); + } + }; + return dialog; + } + + /** Returns the unpublish dialog model */ + function unpublishDialog() { + var dialog = { + parentScope: $scope, + view: "views/content/overlays/unpublish.html", + variants: $scope.content.variants, //set a model property for the dialog + skipFormValidation: true, //when submitting the overlay form, skip any client side validation + submitButtonLabelKey: "content_unpublish", + submitButtonStyle: "warning", + submit: function (model) { + + model.submitButtonState = "busy"; + + var selectedVariants = _.filter(model.variants, v => v.save && v.language); //ignore invariant + var culturesForUnpublishing = _.map(selectedVariants, v => v.language.culture); + + contentResource.unpublish($scope.content.id, culturesForUnpublishing) + .then(function (data) { + formHelper.resetForm({ scope: $scope }); + contentEditingHelper.reBindChangedProperties($scope.content, data); + init(); + syncTreeNode($scope.content, data.path); + $scope.page.buttonGroupState = "success"; + eventsService.emit("content.unpublished", { content: $scope.content }); + overlayService.close(); + }, function (err) { + $scope.page.buttonGroupState = 'error'; + }); + + + }, + close: function () { + overlayService.close(); + } + }; + return dialog; + } + if ($scope.page.isNew) { $scope.page.loading = true; @@ -471,41 +566,8 @@ $scope.unpublish = function () { clearNotifications($scope.content); - if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) { - var dialog = { - parentScope: $scope, - view: "views/content/overlays/unpublish.html", - variants: $scope.content.variants, //set a model property for the dialog - skipFormValidation: true, //when submitting the overlay form, skip any client side validation - submitButtonLabelKey: "content_unpublish", - submitButtonStyle: "warning", - submit: function (model) { - - model.submitButtonState = "busy"; - - var selectedVariants = _.filter(model.variants, v => v.save && v.language); //ignore invariant - var culturesForUnpublishing = _.map(selectedVariants, v => v.language.culture); - - contentResource.unpublish($scope.content.id, culturesForUnpublishing) - .then(function (data) { - formHelper.resetForm({ scope: $scope }); - contentEditingHelper.reBindChangedProperties($scope.content, data); - init(); - syncTreeNode($scope.content, data.path); - $scope.page.buttonGroupState = "success"; - eventsService.emit("content.unpublished", { content: $scope.content }); - overlayService.close(); - }, function (err) { - $scope.page.buttonGroupState = 'error'; - }); - - - }, - close: function () { - overlayService.close(); - } - }; - overlayService.open(dialog); + if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) { + overlayService.open(unpublishDialog()); } }; @@ -574,43 +636,7 @@ if (isContentCultureVariant()) { //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "publish" })) { - - var dialog = { - parentScope: $scope, - view: "views/content/overlays/publish.html", - variants: $scope.content.variants, //set a model property for the dialog - skipFormValidation: true, //when submitting the overlay form, skip any client side validation - submitButtonLabelKey: "buttons_saveAndPublish", - submit: function (model) { - model.submitButtonState = "busy"; - clearNotifications($scope.content); - //we need to return this promise so that the dialog can handle the result and wire up the validation response - return performSave({ - saveMethod: contentResource.publish, - action: "publish", - showNotifications: false - }).then(function (data) { - //show all notifications manually here since we disabled showing them automatically in the save method - formHelper.showNotifications(data); - clearNotifications($scope.content); - overlayService.close(); - return $q.when(data); - }, - function (err) { - clearDirtyState($scope.content.variants); - model.submitButtonState = "error"; - //re-map the dialog model since we've re-bound the properties - dialog.variants = $scope.content.variants; - //don't reject, we've handled the error - return $q.when(err); - }); - }, - close: function () { - overlayService.close(); - } - }; - - overlayService.open(dialog); + overlayService.open(saveAndPublishDialog()); } else { showValidationNotification(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js index e853e07092..e6564566a8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js @@ -12,6 +12,18 @@ var currentOverlay = null; + function getCurrent() { + return currentOverlay; + } + + function refresh(overlay) { + //extend the passed overlay on top of the current overlay and then re-assign to currentOverlay + var merged = angular.extend({}, currentOverlay, overlay); + currentOverlay = merged; + + eventsService.emit("appState.overlay", currentOverlay); + } + function open(newOverlay) { // prevent two open overlays at the same time @@ -45,7 +57,7 @@ overlay.show = true; backdropService.open(backdropOptions); currentOverlay = overlay; - eventsService.emit("appState.overlay", overlay); + eventsService.emit("appState.overlay", currentOverlay); } function close() { @@ -68,7 +80,9 @@ var service = { open: open, close: close, - ysod: ysod + ysod: ysod, + refresh: refresh, + getCurrent: getCurrent }; return service; @@ -77,4 +91,5 @@ angular.module("umbraco.services").factory("overlayService", overlayService); + })(); From d8180604e87d6e960eac425f1ebd308b0c0d5311 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 11 Jun 2019 22:10:53 +1000 Subject: [PATCH 06/62] fixes SimilarNodeName --- .../Persistence/Repositories/Implement/SimilarNodeName.cs | 2 +- .../Persistence/Repositories/SimilarNodeNameTests.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/SimilarNodeName.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/SimilarNodeName.cs index 9f27b6b9e3..99e824757d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/SimilarNodeName.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/SimilarNodeName.cs @@ -99,7 +99,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (uniqueing) { - if (name.NumPos > 0 && name.Name.StartsWith(nodeName) && name.NumVal == uniqueNumber) + if (name.NumPos > 0 && name.Name.InvariantStartsWith(nodeName) && name.NumVal == uniqueNumber) uniqueNumber++; else break; diff --git a/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs b/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs index 3c23223c9f..961496ae16 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs @@ -75,6 +75,7 @@ namespace Umbraco.Tests.Persistence.Repositories [TestCase(0, "Alpha", "Alpha (3)")] [TestCase(0, "Kilo (1)", "Kilo (1) (1)")] // though... we might consider "Kilo (2)" [TestCase(6, "Kilo (1)", "Kilo (1)")] // because of the id + [TestCase(0, "alpha", "alpha (3)")] [TestCase(0, "", " (1)")] [TestCase(0, null, " (1)")] public void Test(int nodeId, string nodeName, string expected) From 88d9e3321c4dce97dc6122c919ee3c6226631dd4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 12 Jun 2019 00:39:12 +1000 Subject: [PATCH 07/62] Don't do a redirect on create for content, instead we do a 'soft redirect' which means the URL is updated but the route isn't changed which simplifies re-wiring everything up (will eventually do for all editors) --- .../components/content/edit.controller.js | 87 ++++++++----------- .../services/contenteditinghelper.service.js | 66 ++++++++------ .../common/services/editorstate.service.js | 26 +----- .../src/common/services/formhelper.service.js | 5 ++ .../src/common/services/navigation.service.js | 20 +++-- src/Umbraco.Web.UI.Client/src/init.js | 2 +- src/Umbraco.Web.UI.Client/src/routes.js | 1 + .../content.createblueprint.controller.js | 1 - .../views/content/content.edit.controller.js | 4 +- .../datatypes/datatype.edit.controller.js | 1 - .../dictionary/dictionary.edit.controller.js | 1 - .../views/documenttypes/edit.controller.js | 4 - .../views/macros/macros.edit.controller.js | 1 - .../src/views/media/media.edit.controller.js | 2 - .../src/views/mediatypes/edit.controller.js | 4 - .../views/member/member.edit.controller.js | 1 - .../src/views/membergroups/edit.controller.js | 1 - .../src/views/membertypes/edit.controller.js | 4 - .../partialviewmacros/edit.controller.js | 4 - .../src/views/partialviews/edit.controller.js | 4 - .../views/relationtypes/edit.controller.js | 1 - .../src/views/scripts/edit.controller.js | 4 - .../src/views/stylesheets/edit.controller.js | 4 - .../src/views/templates/edit.controller.js | 4 - .../src/views/users/group.controller.js | 4 - .../src/views/users/user.controller.js | 1 - .../services/content-editing-helper.spec.js | 3 - 27 files changed, 100 insertions(+), 160 deletions(-) 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 38dce4985b..c5e2eaf755 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 @@ -8,6 +8,7 @@ var evts = []; var infiniteMode = $scope.infiniteModel && $scope.infiniteModel.infiniteMode; + var watchingCulture = false; //setup scope vars $scope.defaultButton = null; @@ -26,40 +27,42 @@ $scope.allowOpen = true; $scope.app = null; - //called only one time when the editor first loads - function firstLoad(content) { - //Check if the editor state is holding a reference to a new item and merge it's notifications. - //We need to do this when creating new content because when it's created, we redirect to a new route and re-load the content from the server - //but we need to maintain it's 'notifications' data so we can re-bind it in the UI. - //The only time that editorState will contain something here is if it was preserved and that is only explicitly done when creating new content and redirecting. + //initializes any watches + function startWatches(content) { - var currState = editorState.getCurrent(); - if (currState && currState.udi === content.udi) { - content.notifications = currState.notifications; - for (var i = 0; i < content.variants.length; i++) { - content.variants[i].notifications = currState.variants[i].notifications; - } + //watch for changes to isNew & the content.id, set the page.isNew accordingly and load the breadcrumb if we can + $scope.$watchGroup(['isNew', 'content.id'], function (newVal, oldVal) { + + var contentId = newVal[1]; + $scope.page.isNew = Object.toBoolean(newVal[0]); - //Now check if the overlay service has an active overlay open that was owned by this editor prior to redirecting. - //This will occur if new variant content is created and there were notifications/errors for variants, the variant dialog will remain open - //but it will need to be refreshed with the new editor data since it will be holding a reference to stale scopes. - var currOverlay = overlayService.getCurrent(); - if (currOverlay && currOverlay.view.startsWith("views/content/overlays")) { - switch (currOverlay.view) { - case "views/content/overlays/publish.html": - overlayService.refresh(saveAndPublishDialog()); - break; + //We fetch all ancestors of the node to generate the footer breadcrumb navigation + if (!$scope.page.isNew && contentId && content.parentId && content.parentId !== -1) { + loadBreadcrumb(); + if (!watchingCulture) { + $scope.$watch('culture', + function (value, oldValue) { + if (value !== oldValue) { + loadBreadcrumb(); + } + }); } } + }); - } } //this initializes the editor with the data which will be called more than once if the data is re-loaded - function init(isFirstLoad) { + function init() { var content = $scope.content; + if (content.id && content.isChildOfListView && content.trashed === false) { + $scope.page.listViewPath = ($routeParams.page) ? + "/content/content/edit/" + content.parentId + "?page=" + $routeParams.page : + "/content/content/edit/" + content.parentId; + } + // we need to check wether an app is present in the current data, if not we will present the default app. var isAppPresent = false; @@ -92,25 +95,8 @@ $scope.appChanged(content.apps[0]); } - if (isFirstLoad) { - firstLoad(content); - } - editorState.set(content); - //We fetch all ancestors of the node to generate the footer breadcrumb navigation - if (!$scope.page.isNew) { - if (content.parentId && content.parentId !== -1) { - loadBreadcrumb(); - $scope.$watch('culture', - function (value, oldValue) { - if (value !== oldValue) { - loadBreadcrumb(); - } - }); - } - } - bindEvents(); resetVariantFlags(); @@ -178,7 +164,7 @@ /** * This does the content loading and initializes everything, called on first load */ - function loadContent(isFirstLoad) { + function loadContent() { //we are editing so get the content item from the server return $scope.getMethod()($scope.contentId) @@ -186,13 +172,7 @@ $scope.content = data; - if (data.isChildOfListView && data.trashed === false) { - $scope.page.listViewPath = ($routeParams.page) ? - "/content/content/edit/" + data.parentId + "?page=" + $routeParams.page : - "/content/content/edit/" + data.parentId; - } - - init(isFirstLoad); + init(); syncTreeNode($scope.content, $scope.content.path, true); @@ -382,7 +362,8 @@ scope: $scope, content: $scope.content, action: args.action, - showNotifications: args.showNotifications + showNotifications: args.showNotifications, + softRedirect: true }).then(function (data) { //success init(); @@ -546,6 +527,7 @@ $scope.content = data; init(); + startWatches($scope.content); resetLastListPageNumber($scope.content); @@ -559,7 +541,8 @@ $scope.page.loading = true; - loadContent(true).then(function () { + loadContent().then(function () { + startWatches($scope.content); $scope.page.loading = false; }); } @@ -964,9 +947,7 @@ } //since we are not notifying and clearing server validation messages when they are received due to how the variant //switching works, we need to ensure they are cleared when this editor is destroyed - if (!$scope.page.isNew) { - serverValidationManager.clear(); - } + serverValidationManager.clear(); }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index f54accbba0..732f682082 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -34,6 +34,9 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt return { + //TODO: We need to move some of this to formHelper for saving, too many editors use this method for saving when this entire + //service should only be used for content/media/members + /** Used by the content editor and mini content editor to perform saving operations */ contentEditorPerformSave: function (args) { if (!angular.isObject(args)) { @@ -51,9 +54,12 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt if (args.showNotifications === undefined) { args.showNotifications = true; } - - var redirectOnSuccess = args.redirectOnSuccess !== undefined ? args.redirectOnSuccess : true; - var redirectOnFailure = args.redirectOnFailure !== undefined ? args.redirectOnFailure : true; + if (args.softRedirect === undefined) { + //when true, the url will change but it won't actually re-route + //this is merely here for compatibility, if only the content/media/members used this service we'd prob be ok but tons of editors + //use this service unfortunately and probably packages too. + args.softRedirect = false; + } var self = this; @@ -70,7 +76,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt self.handleSuccessfulSave({ scope: args.scope, savedContent: data, - redirectOnSuccess: redirectOnSuccess, + softRedirect: args.softRedirect, rebindCallback: function () { rebindCallback.apply(self, [args.content, data]); } @@ -84,7 +90,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt }, function (err) { self.handleSaveError({ showNotifications: args.showNotifications, - redirectOnFailure: redirectOnFailure, + softRedirect: args.softRedirect, err: err, rebindCallback: function () { rebindCallback.apply(self, [args.content, err.data]); @@ -574,15 +580,16 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt * A function to handle what happens when we have validation issues from the server side * */ + + //TODO: Too many editors use this method for saving when this entire service should only be used for content/media/members, + // there is formHelper.handleError for other editors which should be used! + handleSaveError: function (args) { if (!args.err) { throw "args.err cannot be null"; } - if (args.redirectOnFailure === undefined || args.redirectOnFailure === null) { - throw "args.redirectOnFailure must be set to true or false"; - } - + //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors. //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). //Or, some strange server error @@ -600,14 +607,16 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt } } - // we need to detect what properties have changed and re-bind them with the server data. - if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { - args.rebindCallback(); - } + if (!this.redirectToCreatedContent(args.err.data.id) || args.softRedirect) { + // If we are not redirecting it's because this is not newly created content, else in some cases we are + // soft-redirecting which means the URL will change but the route wont (i.e. creating content). - if (!args.redirectOnFailure || !this.redirectToCreatedContent(args.err.data.id, args.err.data.ModelState)) { - // we are not redirecting because this is not new content, it is existing content. In this case - // notify all validators (don't clear the server validations though since we need to maintain their state because of + // In this case we need to detect what properties have changed and re-bind them with the server data. + if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { + args.rebindCallback(); + } + + // In this case notify all validators (don't clear the server validations though since we need to maintain their state because of // how the variant switcher works in content). server validation state is always cleared when an editor first loads // and in theory when an editor is destroyed. serverValidationManager.notify(); @@ -631,6 +640,10 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt * ensure the notifications are displayed and that the appropriate events are fired. This will also check if we need to redirect * when we're creating new content. */ + + //TODO: We need to move some of this to formHelper for saving, too many editors use this method for saving when this entire + //service should only be used for content/media/members + handleSuccessfulSave: function (args) { if (!args) { @@ -640,14 +653,12 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt throw "args.savedContent cannot be null"; } - // the default behaviour is to redirect on success. This adds option to prevent when false - args.redirectOnSuccess = args.redirectOnSuccess !== undefined ? args.redirectOnSuccess : true; + if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id) || args.softRedirect) { - if (!args.redirectOnSuccess || !this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id)) { + // If we are not redirecting it's because this is not newly created content, else in some cases we are + // soft-redirecting which means the URL will change but the route wont (i.e. creating content). - //we are not redirecting because this is not new content, it is existing content. In this case - // we need to detect what properties have changed and re-bind them with the server data. - //call the callback + // In this case we need to detect what properties have changed and re-bind them with the server data. if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { args.rebindCallback(); } @@ -665,7 +676,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt * We need to decide if we need to redirect to edito mode or if we will remain in create mode. * We will only need to maintain create mode if we have not fulfilled the basic requirements for creating an entity which is at least having a name and ID */ - redirectToCreatedContent: function (id, modelState) { + redirectToCreatedContent: function (id) { //only continue if we are currently in create mode and not in infinite mode and if the resulting ID is valid if ($routeParams.create && (isValidIdentifier(id))) { @@ -677,10 +688,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt //clear the query strings navigationService.clearSearch(["cculture"]); - - //preserve the editor state when we are redirecting to new content - editorState.preserve(); - + //change to new path $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id); //don't add a browser history for this @@ -700,6 +708,10 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt * For some editors like scripts or entites that have names as ids, these names can change and we need to redirect * to their new paths, this is helper method to do that. */ + + //TODO: We need to move some of this to formHelper for saving, too many editors use this method for saving when this entire + //service should only be used for content/media/members + redirectToRenamedContent: function (id) { //clear the query strings navigationService.clearSearch(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js index acfef46e75..d00edae410 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js @@ -13,7 +13,6 @@ angular.module('umbraco.services').factory("editorState", function ($rootScope) { var current = null; - var preserveBetweenRoute = false; var state = { @@ -66,22 +65,6 @@ angular.module('umbraco.services').factory("editorState", function ($rootScope) return current; }, - /** - * @ngdoc function - * @name umbraco.services.angularHelper#preserve - * @methodOf umbraco.services.editorState - * @function - * - * @description - * When called it will flag the state to be preserved after the next route. - * Normally the editorState is cleared on each successful route but in some cases it should be preserved so calling this will preserve it. - * - * editorState.current can not be overwritten, you should only read values from it - * since modifying individual properties should be handled by the property editors - */ - preserve: function () { - preserveBetweenRoute = true; - } }; // TODO: This shouldn't be removed! use getCurrent() method instead of a hacked readonly property which is confusing. @@ -99,13 +82,8 @@ angular.module('umbraco.services').factory("editorState", function ($rootScope) //execute on each successful route (this is only bound once per application since a service is a singleton) $rootScope.$on('$routeChangeSuccess', function (event, current, previous) { - if (!preserveBetweenRoute) { - //reset the editorState on each successful route chage - state.reset(); - } - - //always reset this - preserveBetweenRoute = false; + //reset the editorState on each successful route chage + state.reset(); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js index f8d95ef1b3..263f09e4c1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js @@ -105,6 +105,11 @@ function formHelper(angularHelper, serverValidationManager, notificationsService * @param {object} err The error object returned from the http promise */ handleError: function (err) { + + //TODO: Potentially add in the logic to showNotifications like the contentEditingHelper.handleSaveError does so that + // non content editors can just use this method instead of contentEditingHelper.handleSaveError which they should not use + // and they won't need to manually do it. + //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors. //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). //Or, some strange server error diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index ba8334d307..7b2b07a9a2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -13,7 +13,7 @@ * Section navigation and search, and maintain their state for the entire application lifetime * */ -function navigationService($routeParams, $location, $q, $timeout, $injector, eventsService, umbModelMapper, treeService, appState) { +function navigationService($routeParams, $location, $route, $q, $timeout, $injector, eventsService, umbModelMapper, treeService, appState) { //the promise that will be resolved when the navigation is ready var navReadyPromise = $q.defer(); @@ -31,7 +31,8 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve //A list of query strings defined that when changed will not cause a reload of the route var nonRoutingQueryStrings = ["mculture", "cculture", "lq"]; var retainedQueryStrings = ["mculture"]; - + //A list of trees that don't cause a route when creating new items (TODO: eventually all trees should do this!) + var nonRoutingTreesOnCreate = ["content"]; function setMode(mode) { switch (mode) { @@ -115,16 +116,17 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve } var service = { - + /** * @ngdoc method * @name umbraco.services.navigationService#isRouteChangingNavigation * @methodOf umbraco.services.navigationService * * @description - * Detects if the route param differences will cause a navigation change or if the route param differences are + * Detects if the route param differences will cause a navigation/route change or if the route param differences are * only tracking state changes. - * This is used for routing operations where reloadOnSearch is false and when detecting form dirty changes when navigating to a different page. + * This is used for routing operations where "reloadOnSearch: false" or "reloadOnUrl: false", when detecting form dirty changes when navigating to a different page, + * and when we are creating new entities and moving from a route with the ?create=true parameter to an ID based parameter once it's created. * @param {object} currUrlParams Either a string path or a dictionary of route parameters * @param {object} nextUrlParams Either a string path or a dictionary of route parameters */ @@ -138,6 +140,14 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve nextUrlParams = pathToRouteParts(nextUrlParams); } + //first check if this is a ?create=true url being redirected to it's true url + if (currUrlParams.create === "true" && currUrlParams.id && currUrlParams.section && currUrlParams.tree && currUrlParams.method === "edit" && + !nextUrlParams.create && nextUrlParams.id && nextUrlParams.section === currUrlParams.section && nextUrlParams.tree === currUrlParams.tree && nextUrlParams.method === currUrlParams.method && + nonRoutingTreesOnCreate.indexOf(nextUrlParams.tree.toLowerCase()) >= 0) { + //this means we're coming from a path like /content/content/edit/1234?create=true to the created path like /content/content/edit/9999 + return false; + } + var allowRoute = true; //The only time that we want to not route is if only any of the nonRoutingQueryStrings have changed/added. diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js index 2e3aeb7e8d..e169d78b36 100644 --- a/src/Umbraco.Web.UI.Client/src/init.js +++ b/src/Umbraco.Web.UI.Client/src/init.js @@ -115,7 +115,7 @@ app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', }); //Bind to $routeUpdate which will execute anytime a location changes but the route is not triggered. - //This is the case when a route uses reloadOnSearch: false which is the case for many or our routes so that we are able to maintain + //This is the case when a route uses "reloadOnSearch: false" or "reloadOnUrl: false" which is the case for many or our routes so that we are able to maintain //global state query strings without force re-loading views. //We can then detect if it's a location change that should force a route or not programatically. $rootScope.$on('$routeUpdate', function (event, next) { diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index e2a2cfe938..556a4d6aef 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -228,6 +228,7 @@ app.config(function ($routeProvider) { }, reloadOnSearch: false, + reloadOnUrl: false, resolve: canRoute(true) }) .otherwise({ redirectTo: '/login' }); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.createblueprint.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.createblueprint.controller.js index c0002220e3..9a2c845d49 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.createblueprint.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.createblueprint.controller.js @@ -36,7 +36,6 @@ function(err) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: err }); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index cc55fbbf4d..5f8b4918cf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -24,13 +24,15 @@ function ContentEditController($scope, $rootScope, $routeParams, contentResource $scope.page = $routeParams.page; $scope.isNew = infiniteMode ? $scope.model.create : $routeParams.create; //load the default culture selected in the main tree if any - $scope.culture = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture; + $scope.culture = $routeParams.cculture ? $routeParams.cculture : ($routeParams.mculture === "true"); //Bind to $routeUpdate which will execute anytime a location changes but the route is not triggered. //This is so we can listen to changes on the cculture parameter since that will not cause a route change // and then we can pass in the updated culture to the editor $scope.$on('$routeUpdate', function (event, next) { $scope.culture = next.params.cculture ? next.params.cculture : $routeParams.mculture; + $scope.isNew = next.params.create === "true"; + $scope.contentId = infiniteMode ? $scope.model.id : $routeParams.id; }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js index ead73beab8..6282ecb0b6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js @@ -174,7 +174,6 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic //NOTE: in the case of data type values we are setting the orig/new props // to be the same thing since that only really matters for content/media. contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: err }); diff --git a/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.edit.controller.js index 596f848abe..c05638f344 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.edit.controller.js @@ -91,7 +91,6 @@ function DictionaryEditController($scope, $routeParams, $location, dictionaryRes function (err) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: err }); 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 62cf9d3e88..7c1f996931 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 @@ -317,10 +317,6 @@ saveMethod: contentTypeResource.save, scope: $scope, content: vm.contentType, - //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc - // type when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, // we need to rebind... the IDs that have been created! rebindCallback: function (origContentType, savedContentType) { vm.contentType.id = savedContentType.id; diff --git a/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js index e91d8ae366..79d837e516 100644 --- a/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js @@ -35,7 +35,6 @@ function MacrosEditController($scope, $q, $routeParams, macroResource, editorSta vm.page.saveButtonState = "success"; }, function (error) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: error }); 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 d4d538b82c..5f8000569d 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 @@ -184,7 +184,6 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, contentEditingHelper.handleSuccessfulSave({ scope: $scope, savedContent: data, - redirectOnSuccess: !infiniteMode, rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) }); @@ -206,7 +205,6 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, contentEditingHelper.handleSaveError({ err: err, - redirectOnFailure: !infiniteMode, rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) }); 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 7ff3cee835..a0da3d0ea8 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 @@ -269,10 +269,6 @@ saveMethod: mediaTypeResource.save, scope: $scope, content: vm.contentType, - //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc - // type when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, // we need to rebind... the IDs that have been created! rebindCallback: function (origContentType, savedContentType) { vm.contentType.id = savedContentType.id; diff --git a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js index a76862a1d9..5b9bc555ab 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js @@ -161,7 +161,6 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR }, function (err) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: err, rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) }); diff --git a/src/Umbraco.Web.UI.Client/src/views/membergroups/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/membergroups/edit.controller.js index c963d5c0c4..9fb7321712 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membergroups/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/membergroups/edit.controller.js @@ -86,7 +86,6 @@ function MemberGroupsEditController($scope, $routeParams, appState, navigationSe }, function (err) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: err }); diff --git a/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js index f1d5db0ebe..dff5c65308 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js @@ -180,10 +180,6 @@ saveMethod: memberTypeResource.save, scope: $scope, content: vm.contentType, - //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc - // type when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, // we need to rebind... the IDs that have been created! rebindCallback: function (origContentType, savedContentType) { vm.contentType.id = savedContentType.id; diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js index 372cecb36c..a26681652e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js @@ -65,10 +65,6 @@ saveMethod: codefileResource.save, scope: $scope, content: vm.partialViewMacro, - // We do not redirect on failure for partial view macros - this is because it is not possible to actually save the partial view - // when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, rebindCallback: function (orignal, saved) {} }).then(function (saved) { // create macro if needed diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js index 292898814d..51cca0b5af 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js @@ -83,10 +83,6 @@ saveMethod: codefileResource.save, scope: $scope, content: vm.partialView, - //We do not redirect on failure for partialviews - this is because it is not possible to actually save the partialviews - // type when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, rebindCallback: function (orignal, saved) {} }).then(function (saved) { diff --git a/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js index 1667a89c35..7bcbe0716e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js @@ -119,7 +119,6 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource, }, function (error) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: error }); diff --git a/src/Umbraco.Web.UI.Client/src/views/scripts/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/scripts/edit.controller.js index 2ca93fba4c..34702230b8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/scripts/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/scripts/edit.controller.js @@ -45,10 +45,6 @@ saveMethod: codefileResource.save, scope: $scope, content: vm.script, - // We do not redirect on failure for scripts - this is because it is not possible to actually save the script - // when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, rebindCallback: function (orignal, saved) {} }).then(function (saved) { diff --git a/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js index 541d329e05..75e59605d2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js @@ -68,10 +68,6 @@ saveMethod: codefileResource.save, scope: $scope, content: vm.stylesheet, - // We do not redirect on failure for style sheets - this is because it is not possible to actually save the style sheet - // when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, rebindCallback: function (orignal, saved) {} }).then(function (saved) { diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js index d17f556b2f..69b60746c6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js @@ -83,10 +83,6 @@ saveMethod: templateResource.save, scope: $scope, content: vm.template, - // We do not redirect on failure for templates - this is because it is not possible to actually save the template - // type when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, rebindCallback: function (orignal, saved) {} }).then(function (saved) { diff --git a/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js index f37cc95174..d110e5d329 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js @@ -81,10 +81,6 @@ saveMethod: userGroupsResource.saveUserGroup, scope: $scope, content: vm.userGroup, - // We do not redirect on failure for users - this is because it is not possible to actually save a user - // when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, rebindCallback: function (orignal, saved) { } }).then(function (saved) { diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index 2d9fccc399..a96ea664d7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -169,7 +169,6 @@ }, function (err) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: err, showNotifications: true }); diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js index f3d2f8d759..2ccf9e886a 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js @@ -30,7 +30,6 @@ describe('contentEditingHelper tests', function () { //act var handled = contentEditingHelper.handleSaveError({ - redirectOnFailure: true, err: err, allNewProps: contentEditingHelper.getAllProps(content), allOrigProps: contentEditingHelper.getAllProps(content) @@ -49,7 +48,6 @@ describe('contentEditingHelper tests', function () { //act var handled = contentEditingHelper.handleSaveError({ - redirectOnFailure: true, err: err, allNewProps: [], allOrigProps: [] @@ -70,7 +68,6 @@ describe('contentEditingHelper tests', function () { //act var handled = contentEditingHelper.handleSaveError({ - redirectOnFailure: true, err: err, allNewProps: contentEditingHelper.getAllProps(content), allOrigProps: contentEditingHelper.getAllProps(content) From 0381c55ce78b91d046f48bd2e70576b6c3fe7832 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 12 Jun 2019 01:01:09 +1000 Subject: [PATCH 08/62] reverts some parts of the content editor --- .../components/content/edit.controller.js | 149 ++++++++---------- 1 file changed, 70 insertions(+), 79 deletions(-) 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 c5e2eaf755..d0fa5f039d 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 @@ -439,83 +439,6 @@ }); } - /** returns the save and publish dialog model */ - function saveAndPublishDialog() { - var dialog = { - parentScope: $scope, - view: "views/content/overlays/publish.html", - variants: $scope.content.variants, //set a model property for the dialog - skipFormValidation: true, //when submitting the overlay form, skip any client side validation - submitButtonLabelKey: "buttons_saveAndPublish", - submit: function (model) { - model.submitButtonState = "busy"; - clearNotifications($scope.content); - //we need to return this promise so that the dialog can handle the result and wire up the validation response - return performSave({ - saveMethod: contentResource.publish, - action: "publish", - showNotifications: false - }).then(function (data) { - //show all notifications manually here since we disabled showing them automatically in the save method - formHelper.showNotifications(data); - clearNotifications($scope.content); - overlayService.close(); - return $q.when(data); - }, - function (err) { - clearDirtyState($scope.content.variants); - model.submitButtonState = "error"; - //re-map the dialog model since we've re-bound the properties - dialog.variants = $scope.content.variants; - //don't reject, we've handled the error - return $q.when(err); - }); - }, - close: function () { - overlayService.close(); - } - }; - return dialog; - } - - /** Returns the unpublish dialog model */ - function unpublishDialog() { - var dialog = { - parentScope: $scope, - view: "views/content/overlays/unpublish.html", - variants: $scope.content.variants, //set a model property for the dialog - skipFormValidation: true, //when submitting the overlay form, skip any client side validation - submitButtonLabelKey: "content_unpublish", - submitButtonStyle: "warning", - submit: function (model) { - - model.submitButtonState = "busy"; - - var selectedVariants = _.filter(model.variants, v => v.save && v.language); //ignore invariant - var culturesForUnpublishing = _.map(selectedVariants, v => v.language.culture); - - contentResource.unpublish($scope.content.id, culturesForUnpublishing) - .then(function (data) { - formHelper.resetForm({ scope: $scope }); - contentEditingHelper.reBindChangedProperties($scope.content, data); - init(); - syncTreeNode($scope.content, data.path); - $scope.page.buttonGroupState = "success"; - eventsService.emit("content.unpublished", { content: $scope.content }); - overlayService.close(); - }, function (err) { - $scope.page.buttonGroupState = 'error'; - }); - - - }, - close: function () { - overlayService.close(); - } - }; - return dialog; - } - if ($scope.page.isNew) { $scope.page.loading = true; @@ -550,7 +473,41 @@ $scope.unpublish = function () { clearNotifications($scope.content); if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) { - overlayService.open(unpublishDialog()); + var dialog = { + parentScope: $scope, + view: "views/content/overlays/unpublish.html", + variants: $scope.content.variants, //set a model property for the dialog + skipFormValidation: true, //when submitting the overlay form, skip any client side validation + submitButtonLabelKey: "content_unpublish", + submitButtonStyle: "warning", + submit: function (model) { + + model.submitButtonState = "busy"; + + var selectedVariants = _.filter(model.variants, v => v.save && v.language); //ignore invariant + var culturesForUnpublishing = _.map(selectedVariants, v => v.language.culture); + + contentResource.unpublish($scope.content.id, culturesForUnpublishing) + .then(function (data) { + formHelper.resetForm({ scope: $scope }); + contentEditingHelper.reBindChangedProperties($scope.content, data); + init(); + syncTreeNode($scope.content, data.path); + $scope.page.buttonGroupState = "success"; + eventsService.emit("content.unpublished", { content: $scope.content }); + overlayService.close(); + }, function (err) { + $scope.page.buttonGroupState = 'error'; + }); + + + }, + close: function () { + overlayService.close(); + } + }; + + overlayService.open(dialog); } }; @@ -619,7 +576,41 @@ if (isContentCultureVariant()) { //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "publish" })) { - overlayService.open(saveAndPublishDialog()); + var dialog = { + parentScope: $scope, + view: "views/content/overlays/publish.html", + variants: $scope.content.variants, //set a model property for the dialog + skipFormValidation: true, //when submitting the overlay form, skip any client side validation + submitButtonLabelKey: "buttons_saveAndPublish", + submit: function (model) { + model.submitButtonState = "busy"; + clearNotifications($scope.content); + //we need to return this promise so that the dialog can handle the result and wire up the validation response + return performSave({ + saveMethod: contentResource.publish, + action: "publish", + showNotifications: false + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, + function (err) { + clearDirtyState($scope.content.variants); + model.submitButtonState = "error"; + //re-map the dialog model since we've re-bound the properties + dialog.variants = $scope.content.variants; + //don't reject, we've handled the error + return $q.when(err); + }); + }, + close: function () { + overlayService.close(); + } + }; + overlayService.open(dialog); } else { showValidationNotification(); From 7ba4c2f353a2baf7e8e266c6bb20b7006418a60f Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 12 Jun 2019 01:04:52 +1000 Subject: [PATCH 09/62] revert overlay.service --- .../src/common/services/overlay.service.js | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js index e6564566a8..2165c1b7cb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js @@ -12,18 +12,6 @@ var currentOverlay = null; - function getCurrent() { - return currentOverlay; - } - - function refresh(overlay) { - //extend the passed overlay on top of the current overlay and then re-assign to currentOverlay - var merged = angular.extend({}, currentOverlay, overlay); - currentOverlay = merged; - - eventsService.emit("appState.overlay", currentOverlay); - } - function open(newOverlay) { // prevent two open overlays at the same time @@ -57,7 +45,7 @@ overlay.show = true; backdropService.open(backdropOptions); currentOverlay = overlay; - eventsService.emit("appState.overlay", currentOverlay); + eventsService.emit("appState.overlay", overlay); } function close() { @@ -80,9 +68,7 @@ var service = { open: open, close: close, - ysod: ysod, - refresh: refresh, - getCurrent: getCurrent + ysod: ysod }; return service; From fd5b5b67bfc8154315ab3a204184ac0af081213d Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 12 Jun 2019 10:11:27 +1000 Subject: [PATCH 10/62] Fixes notifications for invariant content publishing --- .../components/content/edit.controller.js | 2 +- .../components/events/events.directive.js | 302 +++++++++--------- .../src/common/services/formhelper.service.js | 32 +- src/Umbraco.Web/Editors/ContentController.cs | 31 +- 4 files changed, 181 insertions(+), 186 deletions(-) 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 d0fa5f039d..6f331b58fb 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 @@ -628,7 +628,7 @@ $scope.page.buttonGroupState = "success"; }, function () { $scope.page.buttonGroupState = "error"; - });; + }); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js index 77f2ffb54a..3541a1cf68 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js @@ -3,113 +3,113 @@ **/ angular.module('umbraco.directives') -.directive('onDragEnter', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragEnter); - }; - elm.on("dragenter", f); - scope.$on("$destroy", function(){ elm.off("dragenter", f);} ); - } - }; -}) - -.directive('onDragLeave', function () { - return function (scope, elm, attrs) { - var f = function (event) { - var rect = this.getBoundingClientRect(); - var getXY = function getCursorPosition(event) { - var x, y; - - if (typeof event.clientX === 'undefined') { - // try touch screen - x = event.pageX + document.documentElement.scrollLeft; - y = event.pageY + document.documentElement.scrollTop; - } else { - x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; - y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; - } - - return { x: x, y : y }; - }; - - var e = getXY(event.originalEvent); - - // Check the mouseEvent coordinates are outside of the rectangle - if (e.x > rect.left + rect.width - 1 || e.x < rect.left || e.y > rect.top + rect.height - 1 || e.y < rect.top) { - scope.$apply(attrs.onDragLeave); + .directive('onDragEnter', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragEnter); + }; + elm.on("dragenter", f); + scope.$on("$destroy", function () { elm.off("dragenter", f); }); } }; + }) - elm.on("dragleave", f); - scope.$on("$destroy", function(){ elm.off("dragleave", f);} ); - }; -}) + .directive('onDragLeave', function () { + return function (scope, elm, attrs) { + var f = function (event) { + var rect = this.getBoundingClientRect(); + var getXY = function getCursorPosition(event) { + var x, y; -.directive('onDragOver', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragOver); + if (typeof event.clientX === 'undefined') { + // try touch screen + x = event.pageX + document.documentElement.scrollLeft; + y = event.pageY + document.documentElement.scrollTop; + } else { + x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; + y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; + } + + return { x: x, y: y }; + }; + + var e = getXY(event.originalEvent); + + // Check the mouseEvent coordinates are outside of the rectangle + if (e.x > rect.left + rect.width - 1 || e.x < rect.left || e.y > rect.top + rect.height - 1 || e.y < rect.top) { + scope.$apply(attrs.onDragLeave); + } }; - elm.on("dragover", f); - scope.$on("$destroy", function(){ elm.off("dragover", f);} ); - } - }; -}) -.directive('onDragStart', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragStart); - }; - elm.on("dragstart", f); - scope.$on("$destroy", function(){ elm.off("dragstart", f);} ); - } - }; -}) + elm.on("dragleave", f); + scope.$on("$destroy", function () { elm.off("dragleave", f); }); + }; + }) -.directive('onDragEnd', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragEnd); - }; - elm.on("dragend", f); - scope.$on("$destroy", function(){ elm.off("dragend", f);} ); - } - }; -}) + .directive('onDragOver', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragOver); + }; + elm.on("dragover", f); + scope.$on("$destroy", function () { elm.off("dragover", f); }); + } + }; + }) -.directive('onDrop', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDrop); - }; - elm.on("drop", f); - scope.$on("$destroy", function(){ elm.off("drop", f);} ); - } - }; -}) + .directive('onDragStart', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragStart); + }; + elm.on("dragstart", f); + scope.$on("$destroy", function () { elm.off("dragstart", f); }); + } + }; + }) -.directive('onOutsideClick', function ($timeout, angularHelper) { - return function (scope, element, attrs) { + .directive('onDragEnd', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragEnd); + }; + elm.on("dragend", f); + scope.$on("$destroy", function () { elm.off("dragend", f); }); + } + }; + }) - var eventBindings = []; + .directive('onDrop', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDrop); + }; + elm.on("drop", f); + scope.$on("$destroy", function () { elm.off("drop", f); }); + } + }; + }) - function oneTimeClick(event) { + .directive('onOutsideClick', function ($timeout, angularHelper) { + return function (scope, element, attrs) { + + var eventBindings = []; + + function oneTimeClick(event) { var el = event.target.nodeName; //ignore link and button clicks - var els = ["INPUT","A","BUTTON"]; - if(els.indexOf(el) >= 0){return;} + var els = ["INPUT", "A", "BUTTON"]; + if (els.indexOf(el) >= 0) { return; } // ignore clicks on new overlay var parents = $(event.target).parents("a,button,.umb-overlay,.umb-tour"); - if(parents.length > 0){ + if (parents.length > 0) { return; } @@ -132,70 +132,70 @@ angular.module('umbraco.directives') } //ignore clicks inside this element - if( $(element).has( $(event.target) ).length > 0 ){ + if ($(element).has($(event.target)).length > 0) { return; } - scope.$apply(attrs.onOutsideClick); - } - - - $timeout(function(){ - - if ("bindClickOn" in attrs) { - - eventBindings.push(scope.$watch(function() { - return attrs.bindClickOn; - }, function(newValue) { - if (newValue === "true") { - $(document).on("click", oneTimeClick); - } else { - $(document).off("click", oneTimeClick); - } - })); - - } else { - $(document).on("click", oneTimeClick); + angularHelper.safeApply(scope, attrs.onOutsideClick); } - scope.$on("$destroy", function() { - $(document).off("click", oneTimeClick); - // unbind watchers - for (var e in eventBindings) { - eventBindings[e](); + $timeout(function () { + + if ("bindClickOn" in attrs) { + + eventBindings.push(scope.$watch(function () { + return attrs.bindClickOn; + }, function (newValue) { + if (newValue === "true") { + $(document).on("click", oneTimeClick); + } else { + $(document).off("click", oneTimeClick); + } + })); + + } else { + $(document).on("click", oneTimeClick); } + scope.$on("$destroy", function () { + $(document).off("click", oneTimeClick); + + // unbind watchers + for (var e in eventBindings) { + eventBindings[e](); + } + + }); + }); // Temp removal of 1 sec timeout to prevent bug where overlay does not open. We need to find a better solution. + + }; + }) + + .directive('onRightClick', function ($parse) { + + document.oncontextmenu = function (e) { + if (e.target.hasAttribute('on-right-click')) { + e.preventDefault(); + e.stopPropagation(); + return false; + } + }; + + return function (scope, el, attrs) { + el.on('contextmenu', function (e) { + e.preventDefault(); + e.stopPropagation(); + var fn = $parse(attrs.onRightClick); + scope.$apply(function () { + fn(scope, { $event: e }); + }); + return false; }); - }); // Temp removal of 1 sec timeout to prevent bug where overlay does not open. We need to find a better solution. + }; + }) - }; -}) - -.directive('onRightClick',function($parse){ - - document.oncontextmenu = function (e) { - if(e.target.hasAttribute('on-right-click')) { - e.preventDefault(); - e.stopPropagation(); - return false; - } - }; - - return function(scope,el,attrs){ - el.on('contextmenu',function(e){ - e.preventDefault(); - e.stopPropagation(); - var fn = $parse(attrs.onRightClick); - scope.$apply(function () { - fn(scope, { $event: e }); - }); - return false; - }); - }; -}) - -.directive('onDelayedMouseleave', function ($timeout, $parse) { + .directive('onDelayedMouseleave', function ($timeout, $parse) { return { restrict: 'A', @@ -204,20 +204,20 @@ angular.module('umbraco.directives') var active = false; var fn = $parse(attrs.onDelayedMouseleave); - var leave_f = function(event) { - var callback = function() { - fn(scope, {$event:event}); + var leave_f = function (event) { + var callback = function () { + fn(scope, { $event: event }); }; active = false; - $timeout(function(){ - if(active === false){ + $timeout(function () { + if (active === false) { scope.$apply(callback); } }, 650); }; - var enter_f = function(event, args){ + var enter_f = function (event, args) { active = true; }; @@ -226,7 +226,7 @@ angular.module('umbraco.directives') element.on("mouseenter", enter_f); //unsub events - scope.$on("$destroy", function(){ + scope.$on("$destroy", function () { element.off("mouseleave", leave_f); element.off("mouseenter", enter_f); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js index 263f09e4c1..0555318bae 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js @@ -40,7 +40,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService else { currentForm = args.formCtrl; } - + //the first thing any form must do is broadcast the formSubmitting event args.scope.$broadcast("formSubmitting", { scope: args.scope, action: args.action }); @@ -53,10 +53,10 @@ function formHelper(angularHelper, serverValidationManager, notificationsService //reset the server validations serverValidationManager.reset(); - + return true; }, - + /** * @ngdoc function * @name umbraco.services.formHelper#submitForm @@ -75,21 +75,21 @@ function formHelper(angularHelper, serverValidationManager, notificationsService if (!args.scope) { throw "args.scope cannot be null"; } - + args.scope.$broadcast("formSubmitted", { scope: args.scope }); }, showNotifications: function (args) { - if (!args || !args.notifications) { - return false; - } - if (angular.isArray(args.notifications)) { - for (var i = 0; i < args.notifications.length; i++) { - notificationsService.showNotification(args.notifications[i]); + if (!args || !args.notifications) { + return false; } - return true; - } - return false; + if (angular.isArray(args.notifications)) { + for (var i = 0; i < args.notifications.length; i++) { + notificationsService.showNotification(args.notifications[i]); + } + return true; + } + return false; }, /** @@ -104,7 +104,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService * * @param {object} err The error object returned from the http promise */ - handleError: function (err) { + handleError: function (err) { //TODO: Potentially add in the logic to showNotifications like the contentEditingHelper.handleSaveError does so that // non content editors can just use this method instead of contentEditingHelper.handleSaveError which they should not use @@ -121,7 +121,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService this.handleServerValidation(err.data.ModelState); //execute all server validation events and subscribers - serverValidationManager.notifyAndClearAllSubscriptions(); + serverValidationManager.notifyAndClearAllSubscriptions(); } } else { @@ -129,7 +129,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService // TODO: All YSOD handling should be done with an interceptor overlayService.ysod(err); } - + }, /** diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index d48e586036..e85a449583 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -717,11 +717,7 @@ namespace Umbraco.Web.Editors case ContentSaveAction.PublishNew: { var publishStatus = PublishInternal(contentItem, defaultCulture, cultureForInvariantErrors, out wasCancelled, out var successfulCultures); - //global notifications - AddMessageForPublishStatus(new[] { publishStatus }, globalNotifications, successfulCultures); - //variant specific notifications - foreach (var c in successfulCultures) - AddMessageForPublishStatus(new[] { publishStatus }, notifications.GetOrCreate(c), successfulCultures); + AddPublishStatusNotifications(new[] { publishStatus }, globalNotifications, notifications, successfulCultures); } break; case ContentSaveAction.PublishWithDescendants: @@ -737,12 +733,7 @@ namespace Umbraco.Web.Editors } var publishStatus = PublishBranchInternal(contentItem, false, cultureForInvariantErrors, out wasCancelled, out var successfulCultures).ToList(); - - //global notifications - AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures); - //variant specific notifications - foreach (var c in successfulCultures ?? Array.Empty()) - AddMessageForPublishStatus(publishStatus, notifications.GetOrCreate(c), successfulCultures); + AddPublishStatusNotifications(publishStatus, globalNotifications, notifications, successfulCultures); } break; case ContentSaveAction.PublishWithDescendantsForce: @@ -758,12 +749,7 @@ namespace Umbraco.Web.Editors } var publishStatus = PublishBranchInternal(contentItem, true, cultureForInvariantErrors, out wasCancelled, out var successfulCultures).ToList(); - - //global notifications - AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures); - //variant specific notifications - foreach (var c in successfulCultures ?? Array.Empty()) - AddMessageForPublishStatus(publishStatus, notifications.GetOrCreate(c), successfulCultures); + AddPublishStatusNotifications(publishStatus, globalNotifications, notifications, successfulCultures); } break; default: @@ -801,6 +787,15 @@ namespace Umbraco.Web.Editors return display; } + private void AddPublishStatusNotifications(IReadOnlyCollection publishStatus, SimpleNotificationModel globalNotifications, Dictionary variantNotifications, string[] successfulCultures) + { + //global notifications + AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures); + //variant specific notifications + foreach (var c in successfulCultures ?? Array.Empty()) + AddMessageForPublishStatus(publishStatus, variantNotifications.GetOrCreate(c), successfulCultures); + } + /// /// Validates critical data for persistence and updates the ModelState and result accordingly /// @@ -1215,7 +1210,7 @@ namespace Umbraco.Web.Editors //its invariant, proceed normally var publishStatus = Services.ContentService.SaveAndPublish(contentItem.PersistedContent, userId: Security.CurrentUser.Id); wasCancelled = publishStatus.Result == PublishResultType.FailedPublishCancelledByEvent; - successfulCultures = Array.Empty(); + successfulCultures = null; //must be null! this implies invariant return publishStatus; } From e0059f1fadfa2bd138f67285b19994d0c1565936 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 12 Jun 2019 10:35:21 +1000 Subject: [PATCH 11/62] adds notes --- .../src/views/content/content.edit.controller.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index 5f8b4918cf..f631e92954 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -28,7 +28,9 @@ function ContentEditController($scope, $rootScope, $routeParams, contentResource //Bind to $routeUpdate which will execute anytime a location changes but the route is not triggered. //This is so we can listen to changes on the cculture parameter since that will not cause a route change - // and then we can pass in the updated culture to the editor + //and then we can pass in the updated culture to the editor. + //This will also execute when we are redirecting from creating an item to a newly created item since that + //will not cause a route change and so we can update the isNew and contentId flags accordingly. $scope.$on('$routeUpdate', function (event, next) { $scope.culture = next.params.cculture ? next.params.cculture : $routeParams.mculture; $scope.isNew = next.params.create === "true"; From 1307ee7ced4b90189c9824658a9df4b092d2dd77 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 12 Jun 2019 10:36:52 +1000 Subject: [PATCH 12/62] some cleanup --- .../src/common/services/navigation.service.js | 2 +- .../src/views/content/content.edit.controller.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 7b2b07a9a2..9bd34af43f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -13,7 +13,7 @@ * Section navigation and search, and maintain their state for the entire application lifetime * */ -function navigationService($routeParams, $location, $route, $q, $timeout, $injector, eventsService, umbModelMapper, treeService, appState) { +function navigationService($routeParams, $location, $q, $injector, eventsService, umbModelMapper, treeService, appState) { //the promise that will be resolved when the navigation is ready var navReadyPromise = $q.defer(); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index f631e92954..5cbf25ba7d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -6,7 +6,7 @@ * @description * The controller for the content editor */ -function ContentEditController($scope, $rootScope, $routeParams, contentResource) { +function ContentEditController($scope, $routeParams, contentResource) { var infiniteMode = $scope.model && $scope.model.infiniteMode; From 2797011fb0dee777cbe8a6946bda3a495e2d0a43 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 12 Jun 2019 11:34:36 +1000 Subject: [PATCH 13/62] Ensures there's no duplicate notifications and ensures that soft redirects occur for content blueprints too --- .../src/common/services/navigation.service.js | 2 +- .../Models/ContentEditing/MessagesExtensions.cs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 9bd34af43f..e51b7b818e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -32,7 +32,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService var nonRoutingQueryStrings = ["mculture", "cculture", "lq"]; var retainedQueryStrings = ["mculture"]; //A list of trees that don't cause a route when creating new items (TODO: eventually all trees should do this!) - var nonRoutingTreesOnCreate = ["content"]; + var nonRoutingTreesOnCreate = ["content", "contentblueprints"]; function setMode(mode) { switch (mode) { diff --git a/src/Umbraco.Web/Models/ContentEditing/MessagesExtensions.cs b/src/Umbraco.Web/Models/ContentEditing/MessagesExtensions.cs index 3a8496ac3f..1f526a50f3 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MessagesExtensions.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MessagesExtensions.cs @@ -1,10 +1,15 @@  +using System.Linq; +using Umbraco.Core; + namespace Umbraco.Web.Models.ContentEditing { public static class MessagesExtensions { public static void AddNotification(this INotificationModel model, string header, string msg, NotificationStyle type) { + if (model.Exists(header, msg, type)) return; + model.Notifications.Add(new Notification() { Header = header, @@ -15,6 +20,8 @@ namespace Umbraco.Web.Models.ContentEditing public static void AddSuccessNotification(this INotificationModel model, string header, string msg) { + if (model.Exists(header, msg, NotificationStyle.Success)) return; + model.Notifications.Add(new Notification() { Header = header, @@ -25,6 +32,8 @@ namespace Umbraco.Web.Models.ContentEditing public static void AddErrorNotification(this INotificationModel model, string header, string msg) { + if (model.Exists(header, msg, NotificationStyle.Error)) return; + model.Notifications.Add(new Notification() { Header = header, @@ -35,6 +44,8 @@ namespace Umbraco.Web.Models.ContentEditing public static void AddWarningNotification(this INotificationModel model, string header, string msg) { + if (model.Exists(header, msg, NotificationStyle.Warning)) return; + model.Notifications.Add(new Notification() { Header = header, @@ -45,6 +56,8 @@ namespace Umbraco.Web.Models.ContentEditing public static void AddInfoNotification(this INotificationModel model, string header, string msg) { + if (model.Exists(header, msg, NotificationStyle.Info)) return; + model.Notifications.Add(new Notification() { Header = header, @@ -52,5 +65,7 @@ namespace Umbraco.Web.Models.ContentEditing NotificationType = NotificationStyle.Info }); } + + private static bool Exists(this INotificationModel model, string header, string message, NotificationStyle notificationType) => model.Notifications.Any(x => x.Header.InvariantEquals(header) && x.Message.InvariantEquals(message) && x.NotificationType == notificationType); } } From 8a19cd4bbc1422bb99a3c0f0c17b76ae98eb99e7 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 12 Jun 2019 12:22:14 +1000 Subject: [PATCH 14/62] Fixes content blueprint creation --- .../components/content/edit.controller.js | 10 +++++++--- .../contentblueprints/edit.controller.js | 6 +++++- src/Umbraco.Web/Editors/ContentController.cs | 19 ++++++++++++++----- 3 files changed, 26 insertions(+), 9 deletions(-) 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 6f331b58fb..a548820138 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 @@ -242,9 +242,13 @@ } if (!$scope.content.isChildOfListView) { - navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) { - $scope.page.menu.currentNode = syncArgs.node; - }); + navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true }) + .then(function (syncArgs) { + $scope.page.menu.currentNode = syncArgs.node; + }, function () { + //handle the rejection + console.log("A problem occurred syncing the tree! A path is probably incorrect.") + }); } else if (initialLoad === true) { diff --git a/src/Umbraco.Web.UI.Client/src/views/contentblueprints/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/contentblueprints/edit.controller.js index 59351ffd38..982af76d69 100644 --- a/src/Umbraco.Web.UI.Client/src/views/contentblueprints/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/contentblueprints/edit.controller.js @@ -47,9 +47,13 @@ function ContentBlueprintEditController($scope, $routeParams, contentResource) { //Bind to $routeUpdate which will execute anytime a location changes but the route is not triggered. //This is so we can listen to changes on the cculture parameter since that will not cause a route change - // and then we can pass in the updated culture to the editor + //and then we can pass in the updated culture to the editor. + //This will also execute when we are redirecting from creating an item to a newly created item since that + //will not cause a route change and so we can update the isNew and contentId flags accordingly. $scope.$on('$routeUpdate', function (event, next) { $scope.culture = next.params.cculture ? next.params.cculture : $routeParams.mculture; + $scope.isNew = $routeParams.id === "-1"; + $scope.contentId = $routeParams.id; }); } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index e85a449583..754d5aca1e 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -576,8 +576,13 @@ namespace Umbraco.Web.Editors Services.ContentService.SaveBlueprint(contentItem.PersistedContent, Security.CurrentUser.Id); //we need to reuse the underlying logic so return the result that it wants return OperationResult.Succeed(new EventMessages()); + }, + content => + { + var display = MapToDisplay(content); + SetupBlueprint(display, content); + return display; }); - SetupBlueprint(contentItemDisplay, contentItemDisplay.PersistedContent); return contentItemDisplay; } @@ -591,11 +596,15 @@ namespace Umbraco.Web.Editors [OutgoingEditorModelEvent] public ContentItemDisplay PostSave([ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem) { - var contentItemDisplay = PostSaveInternal(contentItem, content => Services.ContentService.Save(contentItem.PersistedContent, Security.CurrentUser.Id)); + var contentItemDisplay = PostSaveInternal( + contentItem, + content => Services.ContentService.Save(contentItem.PersistedContent, Security.CurrentUser.Id), + MapToDisplay); + return contentItemDisplay; } - private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func saveMethod) + private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func saveMethod, Func mapToDisplay) { //Recent versions of IE/Edge may send in the full client side file path instead of just the file name. //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all @@ -626,7 +635,7 @@ namespace Umbraco.Web.Editors { //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! // add the model state to the outgoing object and throw a validation message - var forDisplay = MapToDisplay(contentItem.PersistedContent); + var forDisplay = mapToDisplay(contentItem.PersistedContent); forDisplay.Errors = ModelState.ToErrorDictionary(); throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); } @@ -757,7 +766,7 @@ namespace Umbraco.Web.Editors } //get the updated model - var display = MapToDisplay(contentItem.PersistedContent); + var display = mapToDisplay(contentItem.PersistedContent); //merge the tracked success messages with the outgoing model display.Notifications.AddRange(globalNotifications.Notifications); From 5b546cc425a930888388edb03a04e4449b818996 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 12 Jun 2019 10:39:34 +0200 Subject: [PATCH 15/62] 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 16/62] 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 f4593778cb01dcff394dd674a7e83eef806650de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 13 Jun 2019 12:31:29 +0200 Subject: [PATCH 17/62] updated package lock --- src/Umbraco.Web.UI.Client/package-lock.json | 143 +++++++++----------- 1 file changed, 62 insertions(+), 81 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index c1c85a9688..ca92a8da49 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -937,7 +937,7 @@ }, "ansi-colors": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, "requires": { @@ -955,7 +955,7 @@ }, "ansi-escapes": { "version": "3.1.0", - "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", "dev": true }, @@ -1176,7 +1176,7 @@ "array-slice": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha1-42jqFfibxwaff/uJrsOmx9SsItQ=", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", "dev": true }, "array-sort": { @@ -1535,7 +1535,7 @@ }, "bl": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "dev": true, "requires": { @@ -1551,7 +1551,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -1566,7 +1566,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -1740,7 +1740,7 @@ "buffer-alloc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "integrity": "sha1-iQ3ZDZI6hz4I4Q5f1RpX5bfM4Ow=", "dev": true, "requires": { "buffer-alloc-unsafe": "^1.1.0", @@ -1750,7 +1750,7 @@ "buffer-alloc-unsafe": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "integrity": "sha1-vX3CauKXLQ7aJTvgYdupkjScGfA=", "dev": true }, "buffer-crc32": { @@ -2436,7 +2436,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -2559,7 +2559,7 @@ "core-js": { "version": "2.5.7", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha1-+XJgj/DOrWi4QaFqky0LGDeRgU4=", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", "dev": true }, "core-util-is": { @@ -3493,7 +3493,7 @@ "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha1-XNAfwQFiG0LEzX9dGmYkNxbT850=", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { "esutils": "^2.0.2" @@ -4061,7 +4061,7 @@ }, "engine.io-client": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", + "resolved": "http://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", "dev": true, "requires": { @@ -4263,7 +4263,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "optional": true } @@ -4358,7 +4358,7 @@ "eslint-scope": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha1-UL8wcekzi83EMzF5Sgy1M/ATYXI=", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -4368,13 +4368,13 @@ "eslint-utils": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha1-moUbqJ7nxGA0b5fPiTnHKYgn5RI=", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", "dev": true }, "eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha1-PzGA+y4pEBdxastMnW1bXDSmqB0=", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", "dev": true }, "espree": { @@ -4397,7 +4397,7 @@ "esquery": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { "estraverse": "^4.0.0" @@ -4406,7 +4406,7 @@ "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { "estraverse": "^4.1.0" @@ -4482,7 +4482,7 @@ "exec-buffer": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/exec-buffer/-/exec-buffer-3.2.0.tgz", - "integrity": "sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==", + "integrity": "sha1-sWhtvZBMfPmC5lLB9aebHlVzCCs=", "dev": true, "optional": true, "requires": { @@ -4691,7 +4691,7 @@ "fill-range": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "integrity": "sha1-6x53OrsFbc2N8r/favWbizqTZWU=", "dev": true, "requires": { "is-number": "^2.1.0", @@ -5244,7 +5244,7 @@ "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=", "dev": true }, "fs-extra": { @@ -5309,8 +5309,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -5331,14 +5330,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5353,20 +5350,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -5483,8 +5477,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -5496,7 +5489,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5511,7 +5503,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5519,14 +5510,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -5545,7 +5534,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5626,8 +5614,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -5639,7 +5626,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -5725,8 +5711,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -5762,7 +5747,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5782,7 +5766,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5826,14 +5809,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -6030,7 +6011,7 @@ "global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", "dev": true, "requires": { "global-prefix": "^1.0.1", @@ -6621,7 +6602,7 @@ "gulp-eslint": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-5.0.0.tgz", - "integrity": "sha1-KiaECV93Syz3kxAmIHjFbMehK1I=", + "integrity": "sha512-9GUqCqh85C7rP9120cpxXuZz2ayq3BZc85pCTuPJS03VQYxne0aWPIXWx6LSvsGPa3uRqtSO537vaugOh+5cXg==", "dev": true, "requires": { "eslint": "^5.0.1", @@ -8072,7 +8053,7 @@ "is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha1-OV4a6EsR8mrReV5zwXN45IowFXY=", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", "dev": true, "requires": { "is-relative": "^1.0.0", @@ -8367,7 +8348,7 @@ "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha1-obtpNc6MXboei5dUubLcwCDiJg0=", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", "dev": true, "requires": { "is-unc-path": "^1.0.0" @@ -8376,7 +8357,7 @@ "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, "is-retry-allowed": { @@ -8424,7 +8405,7 @@ "is-unc-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha1-1zHoiY7QkKEsNSrS6u1Qla0yLJ0=", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", "dev": true, "requires": { "unc-path-regex": "^0.1.2" @@ -8581,7 +8562,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-stable-stringify-without-jsonify": { @@ -8708,7 +8689,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } @@ -9335,7 +9316,7 @@ "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", "dev": true }, "lpad-align": { @@ -9369,7 +9350,7 @@ "make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "integrity": "sha1-ecEDO4BRW9bSTsmTPoYMp17ifww=", "dev": true, "requires": { "pify": "^3.0.0" @@ -9386,7 +9367,7 @@ "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha1-KbM/MSqo9UfEpeSQ9Wr87JkTOtY=", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", "dev": true, "requires": { "kind-of": "^6.0.2" @@ -9583,7 +9564,7 @@ "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, "minimatch": { @@ -12735,7 +12716,7 @@ "dependencies": { "minimist": { "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "dev": true }, @@ -13126,7 +13107,7 @@ "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c=", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", "dev": true }, "posix-character-classes": { @@ -13812,7 +13793,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -14310,7 +14291,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, "sax": { @@ -14497,7 +14478,7 @@ "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "dev": true }, "shebang-command": { @@ -14775,7 +14756,7 @@ }, "socket.io-parser": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", + "resolved": "http://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", "dev": true, "requires": { @@ -14920,7 +14901,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "optional": true, @@ -15067,7 +15048,7 @@ "stream-consume": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.1.tgz", - "integrity": "sha1-0721mMK9CugrjKx6xQsRB6eZbEg=", + "integrity": "sha512-tNa3hzgkjEP7XbCkbRXe1jpg+ievoa0O4SCFlMOYEscGSS4JJsckGL8swUyAa/ApGU3Ae4t6Honor4HhL+tRyg==", "dev": true }, "stream-shift": { @@ -15079,7 +15060,7 @@ "streamroller": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", - "integrity": "sha1-odG3z4PTmvsNYwSaWsv5NJO99ks=", + "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", "dev": true, "requires": { "date-format": "^1.2.0", @@ -15106,7 +15087,7 @@ "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -15121,7 +15102,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -15138,7 +15119,7 @@ "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", @@ -15328,7 +15309,7 @@ "strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "integrity": "sha1-sv0qv2YEudHmATBXGV34Nrip1jE=", "dev": true, "requires": { "escape-string-regexp": "^1.0.2" @@ -15488,7 +15469,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -15696,7 +15677,7 @@ "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { "os-tmpdir": "~1.0.2" @@ -15731,7 +15712,7 @@ "to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", "dev": true }, "to-fast-properties": { @@ -16064,13 +16045,13 @@ "upath": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha1-NSVll+RqWB20eT0M5H+prr/J+r0=", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", "dev": true }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "dev": true, "requires": { "punycode": "^2.1.0" From e39e132febdd41469af3e0d4810ef5c8bf06a321 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 17 Jun 2019 07:16:37 +0200 Subject: [PATCH 18/62] 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 19/62] 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 977c1d9585fedad69f67213e2a3cea7aec4b499e Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 17 Jun 2019 16:23:38 +1000 Subject: [PATCH 20/62] fixes JS error when navigating to the content info tab --- src/Umbraco.Web.UI.Client/package-lock.json | 381 ++++++++++++++---- .../src/views/components/umb-pagination.html | 2 +- 2 files changed, 297 insertions(+), 86 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index ca92a8da49..90fb2437de 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -937,7 +937,7 @@ }, "ansi-colors": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, "requires": { @@ -1104,6 +1104,7 @@ "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-3.2.0.tgz", "integrity": "sha1-nNnABpV+vpX62tW9YJiUKoE3N/Y=", "dev": true, + "optional": true, "requires": { "file-type": "^3.1.0" }, @@ -1112,7 +1113,8 @@ "version": "3.9.0", "resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", - "dev": true + "dev": true, + "optional": true } } }, @@ -1535,9 +1537,10 @@ }, "bl": { "version": "1.2.2", - "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "dev": true, + "optional": true, "requires": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" @@ -1547,13 +1550,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1569,6 +1574,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -1757,7 +1763,8 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true + "dev": true, + "optional": true }, "buffer-fill": { "version": "1.0.0", @@ -1776,6 +1783,7 @@ "resolved": "https://registry.npmjs.org/buffer-to-vinyl/-/buffer-to-vinyl-1.1.0.tgz", "integrity": "sha1-APFfruOreh3aLN5tkSG//dB7ImI=", "dev": true, + "optional": true, "requires": { "file-type": "^3.1.0", "readable-stream": "^2.0.2", @@ -1787,19 +1795,22 @@ "version": "3.9.0", "resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", - "dev": true + "dev": true, + "optional": true }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1815,6 +1826,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -1823,13 +1835,15 @@ "version": "2.0.3", "resolved": "http://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", - "dev": true + "dev": true, + "optional": true }, "vinyl": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, + "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -1950,7 +1964,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", - "dev": true + "dev": true, + "optional": true }, "caseless": { "version": "0.12.0", @@ -1963,6 +1978,7 @@ "resolved": "https://registry.npmjs.org/caw/-/caw-1.2.0.tgz", "integrity": "sha1-/7Im/n78VHKI3GLuPpcHPCEtEDQ=", "dev": true, + "optional": true, "requires": { "get-proxy": "^1.0.1", "is-obj": "^1.0.0", @@ -1974,7 +1990,8 @@ "version": "0.4.3", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", - "dev": true + "dev": true, + "optional": true } } }, @@ -2249,7 +2266,8 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/co/-/co-3.1.0.tgz", "integrity": "sha1-TqVOpaCJOBUxheFSEMaNkJK8G3g=", - "dev": true + "dev": true, + "optional": true }, "coa": { "version": "2.0.1", @@ -2373,6 +2391,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", "dev": true, + "optional": true, "requires": { "graceful-readlink": ">= 1.0.0" } @@ -2585,6 +2604,7 @@ "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", "dev": true, + "optional": true, "requires": { "capture-stack-trace": "^1.0.0" } @@ -2839,6 +2859,7 @@ "resolved": "https://registry.npmjs.org/decompress/-/decompress-3.0.0.tgz", "integrity": "sha1-rx3VDQbjv8QyRh033hGzjA2ZG+0=", "dev": true, + "optional": true, "requires": { "buffer-to-vinyl": "^1.0.0", "concat-stream": "^1.4.6", @@ -2856,6 +2877,7 @@ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, + "optional": true, "requires": { "arr-flatten": "^1.0.1" } @@ -2864,13 +2886,15 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true + "dev": true, + "optional": true }, "braces": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, + "optional": true, "requires": { "expand-range": "^1.8.1", "preserve": "^0.2.0", @@ -2882,6 +2906,7 @@ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, + "optional": true, "requires": { "is-posix-bracket": "^0.1.0" } @@ -2891,6 +2916,7 @@ "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, + "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -2900,6 +2926,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, + "optional": true, "requires": { "inflight": "^1.0.4", "inherits": "2", @@ -2913,6 +2940,7 @@ "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-5.3.5.tgz", "integrity": "sha1-pVZlqajM3EGRWofHAeMtTgFvrSI=", "dev": true, + "optional": true, "requires": { "extend": "^3.0.0", "glob": "^5.0.3", @@ -2928,13 +2956,15 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -2946,13 +2976,15 @@ "version": "0.10.31", "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true + "dev": true, + "optional": true }, "through2": { "version": "0.6.5", "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, + "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -2964,19 +2996,22 @@ "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true + "dev": true, + "optional": true }, "is-extglob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true + "dev": true, + "optional": true }, "is-glob": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, + "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -2985,13 +3020,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -3001,6 +3038,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, + "optional": true, "requires": { "arr-diff": "^2.0.0", "array-unique": "^0.2.1", @@ -3021,13 +3059,15 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "dev": true, + "optional": true }, "ordered-read-streams": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz", "integrity": "sha1-cTfmmzKYuzQiR6G77jiByA4v14s=", "dev": true, + "optional": true, "requires": { "is-stream": "^1.0.1", "readable-stream": "^2.0.1" @@ -3038,6 +3078,7 @@ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -3053,6 +3094,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -3062,6 +3104,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, + "optional": true, "requires": { "is-utf8": "^0.2.0" } @@ -3071,6 +3114,7 @@ "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz", "integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=", "dev": true, + "optional": true, "requires": { "first-chunk-stream": "^1.0.0", "strip-bom": "^2.0.0" @@ -3081,6 +3125,7 @@ "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", "dev": true, + "optional": true, "requires": { "json-stable-stringify-without-jsonify": "^1.0.1", "through2-filter": "^3.0.0" @@ -3091,6 +3136,7 @@ "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", "dev": true, + "optional": true, "requires": { "through2": "~2.0.0", "xtend": "~4.0.0" @@ -3103,6 +3149,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, + "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -3114,6 +3161,7 @@ "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-2.4.4.tgz", "integrity": "sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=", "dev": true, + "optional": true, "requires": { "duplexify": "^3.2.0", "glob-stream": "^5.3.2", @@ -3141,6 +3189,7 @@ "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-3.1.0.tgz", "integrity": "sha1-IXx4n5uURQ76rcXF5TeXj8MzxGY=", "dev": true, + "optional": true, "requires": { "is-tar": "^1.0.0", "object-assign": "^2.0.0", @@ -3154,19 +3203,22 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz", "integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -3179,6 +3231,7 @@ "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, + "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -3189,6 +3242,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", "dev": true, + "optional": true, "requires": { "clone": "^0.2.0", "clone-stats": "^0.0.1" @@ -3201,6 +3255,7 @@ "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-3.1.0.tgz", "integrity": "sha1-iyOTVoE1X58YnYclag+L3ZbZZm0=", "dev": true, + "optional": true, "requires": { "is-bzip2": "^1.0.0", "object-assign": "^2.0.0", @@ -3215,19 +3270,22 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz", "integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -3240,6 +3298,7 @@ "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, + "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -3250,6 +3309,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", "dev": true, + "optional": true, "requires": { "clone": "^0.2.0", "clone-stats": "^0.0.1" @@ -3262,6 +3322,7 @@ "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-3.1.0.tgz", "integrity": "sha1-ssE9+YFmJomRtxXWRH9kLpaW9aA=", "dev": true, + "optional": true, "requires": { "is-gzip": "^1.0.0", "object-assign": "^2.0.0", @@ -3275,19 +3336,22 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz", "integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -3300,6 +3364,7 @@ "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, + "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -3310,6 +3375,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", "dev": true, + "optional": true, "requires": { "clone": "^0.2.0", "clone-stats": "^0.0.1" @@ -3322,6 +3388,7 @@ "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-3.4.0.tgz", "integrity": "sha1-YUdbQVIGa74/7hL51inRX+ZHjus=", "dev": true, + "optional": true, "requires": { "is-zip": "^1.0.0", "read-all-stream": "^3.0.0", @@ -3337,6 +3404,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, + "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -3349,7 +3417,8 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true + "dev": true, + "optional": true }, "deep-is": { "version": "0.1.3", @@ -3568,6 +3637,7 @@ "resolved": "https://registry.npmjs.org/download/-/download-4.4.3.tgz", "integrity": "sha1-qlX9rTktldS2jowr4D4MKqIbqaw=", "dev": true, + "optional": true, "requires": { "caw": "^1.0.1", "concat-stream": "^1.4.7", @@ -3591,6 +3661,7 @@ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, + "optional": true, "requires": { "arr-flatten": "^1.0.1" } @@ -3599,13 +3670,15 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true + "dev": true, + "optional": true }, "braces": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, + "optional": true, "requires": { "expand-range": "^1.8.1", "preserve": "^0.2.0", @@ -3617,6 +3690,7 @@ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, + "optional": true, "requires": { "is-posix-bracket": "^0.1.0" } @@ -3626,6 +3700,7 @@ "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, + "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -3635,6 +3710,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, + "optional": true, "requires": { "inflight": "^1.0.4", "inherits": "2", @@ -3648,6 +3724,7 @@ "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-5.3.5.tgz", "integrity": "sha1-pVZlqajM3EGRWofHAeMtTgFvrSI=", "dev": true, + "optional": true, "requires": { "extend": "^3.0.0", "glob": "^5.0.3", @@ -3663,13 +3740,15 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -3681,13 +3760,15 @@ "version": "0.10.31", "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true + "dev": true, + "optional": true }, "through2": { "version": "0.6.5", "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, + "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -3699,19 +3780,22 @@ "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true + "dev": true, + "optional": true }, "is-extglob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true + "dev": true, + "optional": true }, "is-glob": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, + "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -3720,13 +3804,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -3736,6 +3822,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, + "optional": true, "requires": { "arr-diff": "^2.0.0", "array-unique": "^0.2.1", @@ -3756,13 +3843,15 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "dev": true, + "optional": true }, "ordered-read-streams": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz", "integrity": "sha1-cTfmmzKYuzQiR6G77jiByA4v14s=", "dev": true, + "optional": true, "requires": { "is-stream": "^1.0.1", "readable-stream": "^2.0.1" @@ -3773,6 +3862,7 @@ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -3788,6 +3878,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -3797,6 +3888,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, + "optional": true, "requires": { "is-utf8": "^0.2.0" } @@ -3806,6 +3898,7 @@ "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz", "integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=", "dev": true, + "optional": true, "requires": { "first-chunk-stream": "^1.0.0", "strip-bom": "^2.0.0" @@ -3816,6 +3909,7 @@ "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", "dev": true, + "optional": true, "requires": { "json-stable-stringify-without-jsonify": "^1.0.1", "through2-filter": "^3.0.0" @@ -3826,6 +3920,7 @@ "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", "dev": true, + "optional": true, "requires": { "through2": "~2.0.0", "xtend": "~4.0.0" @@ -3838,6 +3933,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, + "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -3849,6 +3945,7 @@ "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-2.4.4.tgz", "integrity": "sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=", "dev": true, + "optional": true, "requires": { "duplexify": "^3.2.0", "glob-stream": "^5.3.2", @@ -3891,6 +3988,7 @@ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz", "integrity": "sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==", "dev": true, + "optional": true, "requires": { "end-of-stream": "^1.0.0", "inherits": "^2.0.1", @@ -3903,6 +4001,7 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, + "optional": true, "requires": { "once": "^1.4.0" } @@ -3911,13 +4010,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -3927,6 +4028,7 @@ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -3942,6 +4044,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -3953,6 +4056,7 @@ "resolved": "https://registry.npmjs.org/each-async/-/each-async-1.1.1.tgz", "integrity": "sha1-3uUim98KtrogEqOV4bhpq/iBNHM=", "dev": true, + "optional": true, "requires": { "onetime": "^1.0.0", "set-immediate-shim": "^1.0.0" @@ -3962,7 +4066,8 @@ "version": "1.1.0", "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true + "dev": true, + "optional": true } } }, @@ -4945,6 +5050,7 @@ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "dev": true, + "optional": true, "requires": { "pend": "~1.2.0" } @@ -4992,13 +5098,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz", "integrity": "sha1-5hz4BfDeHJhFZ9A4bcXfUO5a9+Q=", - "dev": true + "dev": true, + "optional": true }, "filenamify": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz", "integrity": "sha1-qfL/0RxQO+0wABUCknI3jx8TZaU=", "dev": true, + "optional": true, "requires": { "filename-reserved-regex": "^1.0.0", "strip-outer": "^1.0.0", @@ -5245,7 +5353,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=", - "dev": true + "dev": true, + "optional": true }, "fs-extra": { "version": "1.0.0", @@ -5309,7 +5418,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -5330,12 +5440,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5350,17 +5462,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5477,7 +5592,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5489,6 +5605,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5503,6 +5620,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5510,12 +5628,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -5534,6 +5654,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5614,7 +5735,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5626,6 +5748,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5711,7 +5834,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -5747,6 +5871,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5766,6 +5891,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5809,12 +5935,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -5850,6 +5978,7 @@ "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-1.1.0.tgz", "integrity": "sha1-iUhUSRvFkbDxR9euVw9cZ4tyVus=", "dev": true, + "optional": true, "requires": { "rc": "^1.1.2" } @@ -6156,6 +6285,7 @@ "resolved": "http://registry.npmjs.org/got/-/got-5.7.1.tgz", "integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=", "dev": true, + "optional": true, "requires": { "create-error-class": "^3.0.1", "duplexer2": "^0.1.4", @@ -6179,6 +6309,7 @@ "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", "dev": true, + "optional": true, "requires": { "readable-stream": "^2.0.2" } @@ -6187,19 +6318,22 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "dev": true, + "optional": true }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, + "optional": true, "requires": { "error-ex": "^1.2.0" } @@ -6209,6 +6343,7 @@ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -6224,6 +6359,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -6243,7 +6379,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true + "dev": true, + "optional": true }, "growly": { "version": "1.3.0", @@ -6560,6 +6697,7 @@ "resolved": "https://registry.npmjs.org/gulp-decompress/-/gulp-decompress-1.2.0.tgz", "integrity": "sha1-jutlpeAV+O2FMsr+KEVJYGJvDcc=", "dev": true, + "optional": true, "requires": { "archive-type": "^3.0.0", "decompress": "^3.0.0", @@ -6571,13 +6709,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -6593,6 +6733,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -7210,6 +7351,7 @@ "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz", "integrity": "sha1-uG/zSdgBzrVuHZ59x7vLS33uYAw=", "dev": true, + "optional": true, "requires": { "convert-source-map": "^1.1.1", "graceful-fs": "^4.1.2", @@ -7222,13 +7364,15 @@ "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true + "dev": true, + "optional": true }, "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, + "optional": true, "requires": { "is-utf8": "^0.2.0" } @@ -7238,6 +7382,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, + "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -8120,7 +8265,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-bzip2/-/is-bzip2-1.0.0.tgz", "integrity": "sha1-XuWOqlounIDiFAe+3yOuWsCRs/w=", - "dev": true + "dev": true, + "optional": true }, "is-callable": { "version": "1.1.4", @@ -8255,7 +8401,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz", "integrity": "sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM=", - "dev": true + "dev": true, + "optional": true }, "is-jpg": { "version": "1.0.1", @@ -8268,7 +8415,8 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-2.1.1.tgz", "integrity": "sha1-fUxXKDd+84bD4ZSpkRv1fG3DNec=", - "dev": true + "dev": true, + "optional": true }, "is-number": { "version": "3.0.0", @@ -8334,7 +8482,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", - "dev": true + "dev": true, + "optional": true }, "is-regex": { "version": "1.0.4", @@ -8364,7 +8513,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", - "dev": true + "dev": true, + "optional": true }, "is-stream": { "version": "1.1.0", @@ -8394,7 +8544,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-tar/-/is-tar-1.0.0.tgz", "integrity": "sha1-L2suF5LB9bs2UZrKqdZcDSb+hT0=", - "dev": true + "dev": true, + "optional": true }, "is-typedarray": { "version": "1.0.0", @@ -8415,7 +8566,8 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "dev": true + "dev": true, + "optional": true }, "is-utf8": { "version": "0.2.1", @@ -8427,7 +8579,8 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-0.3.0.tgz", "integrity": "sha1-1LVcafUYhvm2XHDWwmItN+KfSP4=", - "dev": true + "dev": true, + "optional": true }, "is-windows": { "version": "1.0.2", @@ -8445,7 +8598,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-zip/-/is-zip-1.0.0.tgz", "integrity": "sha1-R7Co/004p2QxzP2ZqOFaTIa6IyU=", - "dev": true + "dev": true, + "optional": true }, "isarray": { "version": "0.0.1", @@ -8689,7 +8843,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -8785,6 +8939,7 @@ "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", "dev": true, + "optional": true, "requires": { "readable-stream": "^2.0.5" }, @@ -8793,13 +8948,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -8815,6 +8972,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -9120,7 +9278,8 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", - "dev": true + "dev": true, + "optional": true }, "lodash.isobject": { "version": "2.4.1", @@ -9317,7 +9476,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", - "dev": true + "dev": true, + "optional": true }, "lpad-align": { "version": "1.1.2", @@ -9753,7 +9913,8 @@ "version": "1.0.0", "resolved": "http://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz", "integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=", - "dev": true + "dev": true, + "optional": true }, "node.extend": { "version": "1.1.8", @@ -12803,7 +12964,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "dev": true, + "optional": true }, "p-pipe": { "version": "1.2.0", @@ -13514,7 +13676,8 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true + "dev": true, + "optional": true }, "preserve": { "version": "0.2.0", @@ -13646,6 +13809,7 @@ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, + "optional": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -13658,6 +13822,7 @@ "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", "integrity": "sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=", "dev": true, + "optional": true, "requires": { "pinkie-promise": "^2.0.0", "readable-stream": "^2.0.0" @@ -13667,13 +13832,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -13689,6 +13856,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -14305,6 +14473,7 @@ "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz", "integrity": "sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=", "dev": true, + "optional": true, "requires": { "commander": "~2.8.1" } @@ -14450,7 +14619,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true + "dev": true, + "optional": true }, "set-value": { "version": "2.0.0", @@ -14955,7 +15125,8 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.2.2.tgz", "integrity": "sha1-5sgLYjEj19gM8TLOU480YokHJQI=", - "dev": true + "dev": true, + "optional": true }, "static-extend": { "version": "0.1.2", @@ -14999,6 +15170,7 @@ "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", "dev": true, + "optional": true, "requires": { "duplexer2": "~0.1.0", "readable-stream": "^2.0.2" @@ -15009,6 +15181,7 @@ "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", "dev": true, + "optional": true, "requires": { "readable-stream": "^2.0.2" } @@ -15017,13 +15190,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -15039,6 +15214,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -15055,7 +15231,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", - "dev": true + "dev": true, + "optional": true }, "streamroller": { "version": "0.7.0", @@ -15233,6 +15410,7 @@ "resolved": "http://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz", "integrity": "sha1-lgu9EoeETzl1pFWKoQOoJV4kVqA=", "dev": true, + "optional": true, "requires": { "chalk": "^1.0.0", "get-stdin": "^4.0.1", @@ -15246,13 +15424,15 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true + "dev": true, + "optional": true }, "chalk": { "version": "1.1.3", "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, + "optional": true, "requires": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -15266,6 +15446,7 @@ "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz", "integrity": "sha1-hHSREZ/MtftDYhfMc39/qtUPYD8=", "dev": true, + "optional": true, "requires": { "is-relative": "^0.1.0" } @@ -15274,13 +15455,15 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz", "integrity": "sha1-kF/uiuhvRbPsYUvDwVyGnfCHboI=", - "dev": true + "dev": true, + "optional": true }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "dev": true, + "optional": true } } }, @@ -15311,6 +15494,7 @@ "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", "integrity": "sha1-sv0qv2YEudHmATBXGV34Nrip1jE=", "dev": true, + "optional": true, "requires": { "escape-string-regexp": "^1.0.2" } @@ -15344,6 +15528,7 @@ "resolved": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.3.tgz", "integrity": "sha1-HGYfZnBX9jvLeHWqFDi8FiUlFW4=", "dev": true, + "optional": true, "requires": { "chalk": "^1.0.0" }, @@ -15352,13 +15537,15 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true + "dev": true, + "optional": true }, "chalk": { "version": "1.1.3", "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, + "optional": true, "requires": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -15371,7 +15558,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "dev": true, + "optional": true } } }, @@ -15433,6 +15621,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", "dev": true, + "optional": true, "requires": { "bl": "^1.0.0", "buffer-alloc": "^1.2.0", @@ -15448,6 +15637,7 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", "dev": true, + "optional": true, "requires": { "once": "^1.4.0" } @@ -15456,13 +15646,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -15472,6 +15664,7 @@ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -15487,6 +15680,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -15591,6 +15785,7 @@ "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-2.0.0.tgz", "integrity": "sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw=", "dev": true, + "optional": true, "requires": { "through2": "~2.0.0", "xtend": "~4.0.0" @@ -15615,7 +15810,8 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-3.1.3.tgz", "integrity": "sha1-lYYL/MXHbCd/j4Mm/Q9bLiDrohc=", - "dev": true + "dev": true, + "optional": true }, "timers-ext": { "version": "0.1.7", @@ -15688,6 +15884,7 @@ "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz", "integrity": "sha1-HN+kcqnvUMI57maZm2YsoOs5k38=", "dev": true, + "optional": true, "requires": { "extend-shallow": "^2.0.1" }, @@ -15697,6 +15894,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -15713,7 +15911,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", - "dev": true + "dev": true, + "optional": true }, "to-fast-properties": { "version": "2.0.0", @@ -15792,6 +15991,7 @@ "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", "dev": true, + "optional": true, "requires": { "escape-string-regexp": "^1.0.2" } @@ -16040,7 +16240,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz", "integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4=", - "dev": true + "dev": true, + "optional": true }, "upath": { "version": "1.1.0", @@ -16068,6 +16269,7 @@ "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", "dev": true, + "optional": true, "requires": { "prepend-http": "^1.0.1" } @@ -16153,7 +16355,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", "integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=", - "dev": true + "dev": true, + "optional": true }, "validate-npm-package-license": { "version": "3.0.4", @@ -16198,6 +16401,7 @@ "resolved": "https://registry.npmjs.org/vinyl-assign/-/vinyl-assign-1.2.1.tgz", "integrity": "sha1-TRmIkbVRWRHXcajNnFSApGoHSkU=", "dev": true, + "optional": true, "requires": { "object-assign": "^4.0.1", "readable-stream": "^2.0.0" @@ -16207,19 +16411,22 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -16235,6 +16442,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -16374,6 +16582,7 @@ "resolved": "https://registry.npmjs.org/ware/-/ware-1.3.0.tgz", "integrity": "sha1-0bFPOdLiy0q4xAmPdW/ksWTkc9Q=", "dev": true, + "optional": true, "requires": { "wrap-fn": "^0.1.0" } @@ -16464,6 +16673,7 @@ "resolved": "https://registry.npmjs.org/wrap-fn/-/wrap-fn-0.1.5.tgz", "integrity": "sha1-8htuQQFv9KfjFyDbxjoJAWvfmEU=", "dev": true, + "optional": true, "requires": { "co": "3.1.0" } @@ -16567,6 +16777,7 @@ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", "dev": true, + "optional": true, "requires": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-pagination.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-pagination.html index 817a4f2a94..fae431a11a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-pagination.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-pagination.html @@ -7,7 +7,7 @@ -
  • Date: Mon, 17 Jun 2019 16:33:20 +1000 Subject: [PATCH 21/62] fixes the color of the variant state after publishing and switching to the info tab --- .../components/content/umbcontentnodeinfo.directive.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index e58ff14e21..a6decdc2e5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -328,6 +328,7 @@ isInfoTab = true; loadAuditTrail(); loadRedirectUrls(); + setNodePublishStatus(); } else { isInfoTab = false; } From 3bf43d7ce3ac016482f92c4a4a6d4a399920dbb6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 17 Jun 2019 16:48:35 +1000 Subject: [PATCH 22/62] adds a unit test showing that the SimilarNodeName is broken. --- .../Repositories/SimilarNodeNameTests.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs b/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs index 961496ae16..ca47e5928c 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs @@ -36,6 +36,8 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.IsTrue(result > 0, "Expected >0 but was " + result); } + + [Test] public void OrderByTest() { @@ -96,5 +98,21 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(expected, SimilarNodeName.GetUniqueName(names, nodeId, nodeName)); } + + [Test] + public void TestMany() + { + var names = new[] + { + new SimilarNodeName { Id = 1, Name = "Alpha (2)" }, + new SimilarNodeName { Id = 2, Name = "Test" }, + new SimilarNodeName { Id = 3, Name = "Test (1)" }, + new SimilarNodeName { Id = 4, Name = "Test (2)" }, + new SimilarNodeName { Id = 22, Name = "Test (1) (1)" }, + }; + + //fixme - this will yield "Test (2)" which is already in use + Assert.AreEqual("Test (3)", SimilarNodeName.GetUniqueName(names, 0, "Test")); + } } } From b0488d2d94084958171b93881edc63eeae2f4b61 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 17 Jun 2019 16:50:56 +1000 Subject: [PATCH 23/62] fixes showing the correct create date on content creation --- .../components/content/umbcontentnodeinfo.directive.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index a6decdc2e5..e2f5f71781 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -329,6 +329,7 @@ loadAuditTrail(); loadRedirectUrls(); setNodePublishStatus(); + formatDatesToLocal(); } else { isInfoTab = false; } @@ -345,6 +346,7 @@ loadAuditTrail(true); loadRedirectUrls(); setNodePublishStatus(); + formatDatesToLocal(); } updateCurrentUrls(); }); From cb428bb288a4b786e474d86fd55017f2a64fd7a9 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 18 Jun 2019 08:11:18 +0200 Subject: [PATCH 24/62] 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 25/62] 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 26/62] 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 76cbfdb7874530fd906db5383e73a68db9eaaea5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 19 Jun 2019 12:31:36 +1000 Subject: [PATCH 27/62] ignores a failing test, will need a separate task for that. --- .../Persistence/Repositories/SimilarNodeNameTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs b/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs index ca47e5928c..582e5a4815 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs @@ -100,6 +100,7 @@ namespace Umbraco.Tests.Persistence.Repositories } [Test] + [Explicit("This test fails! We need to fix up the logic")] public void TestMany() { var names = new[] From f829bb8023c84c61b6c472e4e7afb39a12778720 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 19 Jun 2019 18:40:42 +0200 Subject: [PATCH 28/62] Fix "comma-dangle" gulp build error --- .../src/common/services/editorstate.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js index d00edae410..97a9ac5c4b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js @@ -63,7 +63,7 @@ angular.module('umbraco.services').factory("editorState", function ($rootScope) */ getCurrent: function () { return current; - }, + } }; From fcfb10765321b2297f3f61fe4dad511c602765b9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 20 Jun 2019 15:40:57 +1000 Subject: [PATCH 29/62] Fixes up UmbracoContextFactory to ensure it's not returning a disposed Umbraco context --- src/Umbraco.Web/UmbracoContextFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/UmbracoContextFactory.cs b/src/Umbraco.Web/UmbracoContextFactory.cs index 2a812036bf..1efdade458 100644 --- a/src/Umbraco.Web/UmbracoContextFactory.cs +++ b/src/Umbraco.Web/UmbracoContextFactory.cs @@ -64,7 +64,7 @@ namespace Umbraco.Web public UmbracoContextReference EnsureUmbracoContext(HttpContextBase httpContext = null) { var currentUmbracoContext = _umbracoContextAccessor.UmbracoContext; - if (currentUmbracoContext != null) + if (currentUmbracoContext != null && !currentUmbracoContext.Disposed) return new UmbracoContextReference(currentUmbracoContext, false, _umbracoContextAccessor); From f6ac64f705efd4cd8271a6b73f0ff7c8a64ffb65 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 20 Jun 2019 15:53:25 +1000 Subject: [PATCH 30/62] makes TreeChangeExtensions public --- src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs b/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs index b3d15bd612..a5f5efdba9 100644 --- a/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs +++ b/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs @@ -2,9 +2,9 @@ namespace Umbraco.Core.Services.Changes { - internal static class TreeChangeExtensions + public static class TreeChangeExtensions { - public static TreeChange.EventArgs ToEventArgs(this IEnumerable> changes) + internal static TreeChange.EventArgs ToEventArgs(this IEnumerable> changes) { return new TreeChange.EventArgs(changes); } From 5ea6ebcc6683010d4d711cf9aa25c1b54d903f1b Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 20 Jun 2019 17:58:40 +1000 Subject: [PATCH 31/62] Revert "Fixes up UmbracoContextFactory to ensure it's not returning a disposed Umbraco context" This reverts commit fcfb10765321b2297f3f61fe4dad511c602765b9. --- src/Umbraco.Web/UmbracoContextFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/UmbracoContextFactory.cs b/src/Umbraco.Web/UmbracoContextFactory.cs index 1efdade458..2a812036bf 100644 --- a/src/Umbraco.Web/UmbracoContextFactory.cs +++ b/src/Umbraco.Web/UmbracoContextFactory.cs @@ -64,7 +64,7 @@ namespace Umbraco.Web public UmbracoContextReference EnsureUmbracoContext(HttpContextBase httpContext = null) { var currentUmbracoContext = _umbracoContextAccessor.UmbracoContext; - if (currentUmbracoContext != null && !currentUmbracoContext.Disposed) + if (currentUmbracoContext != null) return new UmbracoContextReference(currentUmbracoContext, false, _umbracoContextAccessor); From f4b2ff93fc02a30066cc26244dac0aec4711af96 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 20 Jun 2019 19:10:32 +1000 Subject: [PATCH 32/62] Fixes how Umbraco Context is injected into DI so that LightInject doesn't try to control it's lifetime. --- src/Umbraco.Web/Runtime/WebInitialComposer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 5a464701e0..a37f9c3588 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -92,9 +92,9 @@ namespace Umbraco.Web.Runtime // we should stop injecting UmbracoContext and always inject IUmbracoContextAccessor, however at the moment // there are tons of places (controllers...) which require UmbracoContext in their ctor - so let's register - // a way to inject the UmbracoContext - and register it per-request to be more efficient - // TODO: stop doing this - composition.Register(factory => factory.GetInstance().UmbracoContext, Lifetime.Request); + // a way to inject the UmbracoContext - DO NOT register this as Lifetime.Request since LI will dispose the context + // in it's own way but we don't want that to happen, we manage its lifetime ourselves. + composition.Register(factory => factory.GetInstance().UmbracoContext); composition.Register(factory => { From be9d8e7da72c3fc3d817c18b92789a516c0e3cc3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 21 Jun 2019 16:08:54 +1000 Subject: [PATCH 33/62] null checks on UrlHelperRenderExtensions when requesting image crop urls, if there's a null we'll just return an empty string --- src/Umbraco.Web/UrlHelperRenderExtensions.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Umbraco.Web/UrlHelperRenderExtensions.cs b/src/Umbraco.Web/UrlHelperRenderExtensions.cs index 6f7fbacf7a..ceb7f5e8a6 100644 --- a/src/Umbraco.Web/UrlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/UrlHelperRenderExtensions.cs @@ -20,6 +20,8 @@ namespace Umbraco.Web public static class UrlHelperRenderExtensions { + private static readonly IHtmlString EmptyHtmlString = new HtmlString(string.Empty); + #region GetCropUrl /// @@ -39,6 +41,8 @@ namespace Umbraco.Web /// public static IHtmlString GetCropUrl(this UrlHelper urlHelper, IPublishedContent mediaItem, string cropAlias, bool htmlEncode = true) { + if (mediaItem == null) return EmptyHtmlString; + var url = mediaItem.GetCropUrl(cropAlias: cropAlias, useCropDimensions: true); return htmlEncode ? new HtmlString(HttpUtility.HtmlEncode(url)) : new HtmlString(url); } @@ -65,6 +69,8 @@ namespace Umbraco.Web /// public static IHtmlString GetCropUrl(this UrlHelper urlHelper, IPublishedContent mediaItem, string propertyAlias, string cropAlias, bool htmlEncode = true) { + if (mediaItem == null) return EmptyHtmlString; + var url = mediaItem.GetCropUrl(propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true); return htmlEncode ? new HtmlString(HttpUtility.HtmlEncode(url)) : new HtmlString(url); } @@ -144,6 +150,8 @@ namespace Umbraco.Web bool upScale = true, bool htmlEncode = true) { + if (mediaItem == null) return EmptyHtmlString; + var url = mediaItem.GetCropUrl(width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode, upScale); @@ -247,6 +255,8 @@ namespace Umbraco.Web bool upScale = true, bool htmlEncode = true) { + if (imageCropperValue == null) return EmptyHtmlString; + var imageUrl = imageCropperValue.Src; var url = imageUrl.GetCropUrl(imageCropperValue, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, From f7aaad86c5ea29096bbce47689a0188416a99171 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 21 Jun 2019 15:56:36 +0200 Subject: [PATCH 34/62] 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 35/62] 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 36/62] 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 37/62] 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 38/62] 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 39/62] 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}}
    -

    - -

    +
    +

    +
    +

    + + + +
    +