diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs index a533b0d8a2..3fc8952dfa 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Umbraco.Core.Events; namespace Umbraco.Core.DependencyInjection @@ -28,8 +27,28 @@ namespace Umbraco.Core.DependencyInjection // Register the handler as transient. This ensures that anything can be injected into it. var descriptor = new UniqueServiceDescriptor(typeof(INotificationHandler), typeof(TNotificationHandler), ServiceLifetime.Transient); - // TODO: Waiting on feedback here https://github.com/umbraco/Umbraco-CMS/pull/9556/files#r548365396 about whether - // we perform this duplicate check or not. + if (!builder.Services.Contains(descriptor)) + { + builder.Services.Add(descriptor); + } + + return builder; + } + + /// + /// Registers a notification async handler against the Umbraco service collection. + /// + /// The type of notification. + /// The type of notification async handler. + /// The Umbraco builder. + /// The . + public static IUmbracoBuilder AddNotificationAsyncHandler(this IUmbracoBuilder builder) + where TNotificationAsyncHandler : INotificationAsyncHandler + where TNotification : INotification + { + // Register the handler as transient. This ensures that anything can be injected into it. + var descriptor = new ServiceDescriptor(typeof(INotificationAsyncHandler), typeof(TNotificationAsyncHandler), ServiceLifetime.Transient); + if (!builder.Services.Contains(descriptor)) { builder.Services.Add(descriptor); diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 54e5982a58..b69a2c1677 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -146,7 +146,7 @@ namespace Umbraco.Core.DependencyInjection Services.AddSingleton(); Services.AddSingleton(); - this.AddNotificationHandler(); + this.AddNotificationAsyncHandler(); Services.AddUnique(); diff --git a/src/Umbraco.Core/Events/EventAggregator.Notifications.cs b/src/Umbraco.Core/Events/EventAggregator.Notifications.cs index 00982b52d7..faadc2fc59 100644 --- a/src/Umbraco.Core/Events/EventAggregator.Notifications.cs +++ b/src/Umbraco.Core/Events/EventAggregator.Notifications.cs @@ -15,17 +15,30 @@ namespace Umbraco.Core.Events /// public partial class EventAggregator : IEventAggregator { + private static readonly ConcurrentDictionary s_notificationAsyncHandlers + = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary s_notificationHandlers = new ConcurrentDictionary(); private Task PublishNotificationAsync(INotification notification, CancellationToken cancellationToken = default) { Type notificationType = notification.GetType(); - NotificationHandlerWrapper handler = s_notificationHandlers.GetOrAdd( + NotificationAsyncHandlerWrapper asyncHandler = s_notificationAsyncHandlers.GetOrAdd( + notificationType, + t => (NotificationAsyncHandlerWrapper)Activator.CreateInstance(typeof(NotificationAsyncHandlerWrapperImpl<>).MakeGenericType(notificationType))); + + return asyncHandler.HandleAsync(notification, cancellationToken, _serviceFactory, PublishCoreAsync); + } + + private void PublishNotification(INotification notification) + { + Type notificationType = notification.GetType(); + NotificationHandlerWrapper asyncHandler = s_notificationHandlers.GetOrAdd( notificationType, t => (NotificationHandlerWrapper)Activator.CreateInstance(typeof(NotificationHandlerWrapperImpl<>).MakeGenericType(notificationType))); - return handler.HandleAsync(notification, cancellationToken, _serviceFactory, PublishCoreAsync); + asyncHandler.Handle(notification, _serviceFactory, PublishCore); } private async Task PublishCoreAsync( @@ -38,9 +51,27 @@ namespace Umbraco.Core.Events await handler(notification, cancellationToken).ConfigureAwait(false); } } + + private void PublishCore( + IEnumerable> allHandlers, + INotification notification) + { + foreach (Action handler in allHandlers) + { + handler(notification); + } + } } internal abstract class NotificationHandlerWrapper + { + public abstract void Handle( + INotification notification, + ServiceFactory serviceFactory, + Action>, INotification> publish); + } + + internal abstract class NotificationAsyncHandlerWrapper { public abstract Task HandleAsync( INotification notification, @@ -49,7 +80,7 @@ namespace Umbraco.Core.Events Func>, INotification, CancellationToken, Task> publish); } - internal class NotificationHandlerWrapperImpl : NotificationHandlerWrapper + internal class NotificationAsyncHandlerWrapperImpl : NotificationAsyncHandlerWrapper where TNotification : INotification { public override Task HandleAsync( @@ -59,7 +90,7 @@ namespace Umbraco.Core.Events Func>, INotification, CancellationToken, Task> publish) { IEnumerable> handlers = serviceFactory - .GetInstances>() + .GetInstances>() .Select(x => new Func( (theNotification, theToken) => x.HandleAsync((TNotification)theNotification, theToken))); @@ -67,4 +98,22 @@ namespace Umbraco.Core.Events return publish(handlers, notification, cancellationToken); } } + + internal class NotificationHandlerWrapperImpl : NotificationHandlerWrapper + where TNotification : INotification + { + public override void Handle( + INotification notification, + ServiceFactory serviceFactory, + Action>, INotification> publish) + { + IEnumerable> handlers = serviceFactory + .GetInstances>() + .Select(x => new Action( + (theNotification) => + x.Handle((TNotification)theNotification))); + + publish(handlers, notification); + } + } } diff --git a/src/Umbraco.Core/Events/EventAggregator.cs b/src/Umbraco.Core/Events/EventAggregator.cs index 8d58175da4..33c8c3aa4f 100644 --- a/src/Umbraco.Core/Events/EventAggregator.cs +++ b/src/Umbraco.Core/Events/EventAggregator.cs @@ -38,8 +38,23 @@ namespace Umbraco.Core.Events throw new ArgumentNullException(nameof(notification)); } + PublishNotification(notification); return PublishNotificationAsync(notification, cancellationToken); } + + /// + public void Publish(TNotification notification) + where TNotification : INotification + { + // TODO: Introduce codegen efficient Guard classes to reduce noise. + if (notification == null) + { + throw new ArgumentNullException(nameof(notification)); + } + + PublishNotification(notification); + Task.WaitAll(PublishNotificationAsync(notification)); + } } /// diff --git a/src/Umbraco.Core/Events/IEventAggregator.cs b/src/Umbraco.Core/Events/IEventAggregator.cs index d78aed36b7..0d8905dd62 100644 --- a/src/Umbraco.Core/Events/IEventAggregator.cs +++ b/src/Umbraco.Core/Events/IEventAggregator.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Events public interface IEventAggregator { /// - /// Asynchronously send a notification to multiple handlers + /// Asynchronously send a notification to multiple handlers of both sync and async /// /// The type of notification being handled. /// The notification object. @@ -21,5 +21,13 @@ namespace Umbraco.Core.Events /// A task that represents the publish operation. Task PublishAsync(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification; + + /// + /// Synchronously send a notification to multiple handlers of both sync and async + /// + /// The type of notification being handled. + /// The notification object. + void Publish(TNotification notification) + where TNotification : INotification; } } diff --git a/src/Umbraco.Core/Events/INotificationHandler.cs b/src/Umbraco.Core/Events/INotificationHandler.cs index dc5d83e0f7..4cfe52e005 100644 --- a/src/Umbraco.Core/Events/INotificationHandler.cs +++ b/src/Umbraco.Core/Events/INotificationHandler.cs @@ -12,6 +12,20 @@ namespace Umbraco.Core.Events /// The type of notification being handled. public interface INotificationHandler where TNotification : INotification + { + /// + /// Handles a notification + /// + /// The notification + void Handle(TNotification notification); + } + + /// + /// Defines a handler for a async notification. + /// + /// The type of notification being handled. + public interface INotificationAsyncHandler + where TNotification : INotification { /// /// Handles a notification diff --git a/src/Umbraco.Core/Runtime/AppPluginsManifestWatcherNotificationHandler.cs b/src/Umbraco.Core/Runtime/AppPluginsManifestWatcherNotificationHandler.cs index 5f219ac51f..2b1fa3d0cc 100644 --- a/src/Umbraco.Core/Runtime/AppPluginsManifestWatcherNotificationHandler.cs +++ b/src/Umbraco.Core/Runtime/AppPluginsManifestWatcherNotificationHandler.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Runtime /// /// Starts monitoring AppPlugins directory during debug runs, to restart site when a plugin manifest changes. /// - public sealed class AppPluginsManifestWatcherNotificationHandler : INotificationHandler + public sealed class AppPluginsManifestWatcherNotificationHandler : INotificationAsyncHandler { private readonly ManifestWatcher _manifestWatcher; private readonly IHostingEnvironment _hostingEnvironment; diff --git a/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs b/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs index 92ad5808b3..535a466e25 100644 --- a/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs +++ b/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs @@ -1,5 +1,3 @@ -using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.Options; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Events; @@ -21,7 +19,7 @@ namespace Umbraco.Core.Runtime _globalSettings = globalSettings.Value; } - public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken) + public void Handle(UmbracoApplicationStarting notification) { // ensure we have some essential directories // every other component can then initialize safely @@ -31,7 +29,6 @@ namespace Umbraco.Core.Runtime _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.PartialViews)); _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MacroPartials)); - return Task.CompletedTask; } } } diff --git a/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs b/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs index 5b4dd4f0ed..1ad2b44740 100644 --- a/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs +++ b/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs @@ -1,5 +1,3 @@ -using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Umbraco.Core.Events; using Umbraco.Core.Persistence; @@ -39,7 +37,7 @@ namespace Umbraco.Infrastructure.Cache } /// - public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken) + public void Handle(UmbracoApplicationStarting notification) { // The scheduled tasks - TouchServerTask and InstructionProcessTask - run as .NET Core hosted services. // The former (as well as other hosted services that run outside of an HTTP request context) depends on the application URL @@ -50,8 +48,6 @@ namespace Umbraco.Infrastructure.Cache _requestAccessor.EndRequest += EndRequest; Startup(); - - return Task.CompletedTask; } private void Startup() diff --git a/src/Umbraco.ModelsBuilder.Embedded/DisableModelsBuilderNotificationHandler.cs b/src/Umbraco.ModelsBuilder.Embedded/DisableModelsBuilderNotificationHandler.cs index 8f7e118b6a..b455bbbf61 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/DisableModelsBuilderNotificationHandler.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/DisableModelsBuilderNotificationHandler.cs @@ -1,5 +1,3 @@ -using System.Threading; -using System.Threading.Tasks; using Umbraco.Core.Events; using Umbraco.ModelsBuilder.Embedded.BackOffice; using Umbraco.ModelsBuilder.Embedded.DependencyInjection; @@ -19,11 +17,10 @@ namespace Umbraco.ModelsBuilder.Embedded /// /// Handles the notification to disable MB controller features /// - public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken) + public void Handle(UmbracoApplicationStarting notification) { // disable the embedded dashboard controller _features.Disabled.Controllers.Add(); - return Task.CompletedTask; } } } diff --git a/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs index 11c5d8b605..8b7b639dff 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs @@ -1,6 +1,5 @@ using System; using System.Threading; -using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -8,7 +7,6 @@ using Umbraco.Configuration; using Umbraco.Core; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Events; -using Umbraco.Core.Hosting; using Umbraco.Extensions; using Umbraco.ModelsBuilder.Embedded.Building; using Umbraco.Web.Cache; @@ -52,10 +50,9 @@ namespace Umbraco.ModelsBuilder.Embedded /// /// Handles the notification /// - public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken) + public void Handle(UmbracoApplicationStarting notification) { Install(); - return Task.CompletedTask; } private void Install() diff --git a/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderNotificationHandler.cs b/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderNotificationHandler.cs index 37ff1c735a..9e11a9808d 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderNotificationHandler.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderNotificationHandler.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Reflection; -using System.Threading; -using System.Threading.Tasks; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Options; using Umbraco.Core.Configuration; @@ -47,7 +45,7 @@ namespace Umbraco.ModelsBuilder.Embedded /// /// Handles the notification /// - public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken) + public void Handle(UmbracoApplicationStarting notification) { // always setup the dashboard // note: UmbracoApiController instances are automatically registered @@ -57,14 +55,12 @@ namespace Umbraco.ModelsBuilder.Embedded { FileService.SavingTemplate += FileService_SavingTemplate; } - - return Task.CompletedTask; } /// /// Handles the notification /// - public Task HandleAsync(ServerVariablesParsing notification, CancellationToken cancellationToken) + public void Handle(ServerVariablesParsing notification) { IDictionary serverVars = notification.ServerVariables; @@ -96,8 +92,6 @@ namespace Umbraco.ModelsBuilder.Embedded umbracoUrls["modelsBuilderBaseUrl"] = _linkGenerator.GetUmbracoApiServiceBaseUrl(controller => controller.BuildModels()); umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings(); - - return Task.CompletedTask; } private Dictionary GetModelsBuilderSettings() diff --git a/src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs b/src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs index 0a8283b507..ad757cbf7e 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs @@ -1,6 +1,4 @@ using System.IO; -using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.Options; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; @@ -40,10 +38,9 @@ namespace Umbraco.ModelsBuilder.Embedded /// /// Handles the notification /// - public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken) + public void Handle(UmbracoApplicationStarting notification) { Install(); - return Task.CompletedTask; } private void Install() diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Events/EventAggregatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Events/EventAggregatorTests.cs index cbf3d02542..57a3fb0bd7 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Events/EventAggregatorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Events/EventAggregatorTests.cs @@ -28,6 +28,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Events _builder = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); } + [Test] + public async Task CanPublishAsyncEvents() + { + _builder.Services.AddScoped(); + _builder.AddNotificationAsyncHandler(); + _builder.AddNotificationAsyncHandler(); + _builder.AddNotificationAsyncHandler(); + ServiceProvider provider = _builder.Services.BuildServiceProvider(); + + var notification = new Notification(); + IEventAggregator aggregator = provider.GetService(); + await aggregator.PublishAsync(notification); + + Assert.AreEqual(A + B + C, notification.SubscriberCount); + } + [Test] public async Task CanPublishEvents() { @@ -51,19 +67,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Events public class NotificationHandlerA : INotificationHandler { - public Task HandleAsync(Notification notification, CancellationToken cancellationToken) + public void Handle(Notification notification) { notification.SubscriberCount += A; - return Task.CompletedTask; } } public class NotificationHandlerB : INotificationHandler { - public Task HandleAsync(Notification notification, CancellationToken cancellationToken) + public void Handle(Notification notification) { notification.SubscriberCount += B; - return Task.CompletedTask; } } @@ -73,13 +87,42 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Events public NotificationHandlerC(Adder adder) => _adder = adder; + public void Handle(Notification notification) + { + notification.SubscriberCount = _adder.Add(notification.SubscriberCount, C); + } + } + + public class NotificationAsyncHandlerA : INotificationAsyncHandler + { + public Task HandleAsync(Notification notification, CancellationToken cancellationToken) + { + notification.SubscriberCount += A; + return Task.CompletedTask; + } + } + + public class NotificationAsyncHandlerB : INotificationAsyncHandler + { + public Task HandleAsync(Notification notification, CancellationToken cancellationToken) + { + notification.SubscriberCount += B; + return Task.CompletedTask; + } + } + + public class NotificationAsyncHandlerC : INotificationAsyncHandler + { + private readonly Adder _adder; + + public NotificationAsyncHandlerC(Adder adder) => _adder = adder; + public Task HandleAsync(Notification notification, CancellationToken cancellationToken) { notification.SubscriberCount = _adder.Add(notification.SubscriberCount, C); return Task.CompletedTask; } } - public class Adder { public int Add(int a, int b) => a + b; diff --git a/src/Umbraco.Web.Common/Profiler/InitializeWebProfiling.cs b/src/Umbraco.Web.Common/Profiler/InitializeWebProfiling.cs index fede88e14f..e939355c42 100644 --- a/src/Umbraco.Web.Common/Profiler/InitializeWebProfiling.cs +++ b/src/Umbraco.Web.Common/Profiler/InitializeWebProfiling.cs @@ -1,8 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Umbraco.Core.Events; using Umbraco.Core.Logging; @@ -46,7 +44,7 @@ namespace Umbraco.Web.Common.Profiler } /// - public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken) + public void Handle(UmbracoApplicationStarting notification) { if (_profile) { @@ -57,8 +55,6 @@ namespace Umbraco.Web.Common.Profiler // Stop the profiling of the booting process _profiler.StopBoot(); } - - return Task.CompletedTask; } } } 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 f2dc0622c7..5a9f30fe24 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 @@ -466,7 +466,7 @@ syncTreeNode($scope.content, data.path, false, args.reloadChildren); - eventsService.emit("content.saved", { content: $scope.content, action: args.action }); + eventsService.emit("content.saved", { content: $scope.content, action: args.action, valid: true }); resetNestedFieldValiation(fieldsToRollback); ensureDirtyIsSetIfAnyVariantIsDirty(); @@ -474,8 +474,15 @@ return $q.when(data); }, function (err) { + + syncTreeNode($scope.content, $scope.content.path); + if (err.status === 400 && err.data) { + // content was saved but is invalid. + eventsService.emit("content.saved", { content: $scope.content, action: args.action, valid: false }); + } + resetNestedFieldValiation(fieldsToRollback); return $q.reject(err); @@ -981,7 +988,7 @@ $scope.appChanged = function (activeApp) { $scope.activeApp = activeApp; - + _.forEach($scope.content.apps, function (app) { app.active = false; if (app.alias === $scope.activeApp.alias) { 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 ed0a1c602c..34abba924e 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 @@ -84,7 +84,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt //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; + args.softRedirect = false; } @@ -123,7 +123,13 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt self.handleSaveError({ showNotifications: args.showNotifications, softRedirect: args.softRedirect, - err: err + err: err, + rebindCallback: function () { + // if the error contains data, we want to map that back as we want to continue editing this save. Especially important when the content is new as the returned data will contain ID etc. + if(err.data) { + rebindCallback.apply(self, [args.content, err.data]); + } + } }); //update editor state to what is current @@ -298,7 +304,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt } // if publishing is allowed also allow schedule publish - // we add this manually becuase it doesn't have a permission so it wont + // 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")) { buttons.subButtons.push(createButtonDefinition("SCHEDULE")); @@ -622,7 +628,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt if (!args.err) { throw "args.err cannot be null"; } - + //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 @@ -640,7 +646,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt if (!this.redirectToCreatedContent(args.err.data.id, args.softRedirect) || 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). + // soft-redirecting which means the URL will change but the route wont (i.e. creating content). // 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)) { @@ -687,7 +693,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id, args.softRedirect) || 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). + // soft-redirecting which means the URL will change but the route wont (i.e. creating content). // 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)) { @@ -723,7 +729,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt navigationService.setSoftRedirect(); } //change to new path - $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id); + $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id); //don't add a browser history for this $location.replace(); return true; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/card.less b/src/Umbraco.Web.UI.Client/src/less/components/card.less index a1a4b4bc5e..7174c0f290 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/card.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/card.less @@ -104,19 +104,18 @@ margin: 0 auto; list-style: none; width: 100%; - display: flex; flex-flow: row wrap; justify-content: flex-start; } .umb-card-grid li { - font-size: 12px; text-align: center; box-sizing: border-box; position: relative; width: 100px; + margin-bottom: 5px; } .umb-card-grid.-six-in-row li { @@ -142,18 +141,20 @@ .umb-card-grid .umb-card-grid-item { position: relative; display: block; - width: 100%; - //height: 100%; - padding-top: 100%; + width: 100%; + height: 100%; + padding: 10px 5px; border-radius: @baseBorderRadius * 2; transition: background-color 120ms; + font-size: 13px; + line-height: 1.3em; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; > span { - position: absolute; - top: 10px; - bottom: 10px; - left: 10px; - right: 10px; + position: relative; display: flex; align-items: center; justify-content: center;