From 69724d8d2bdbe759fcd59e3191e45db5b744d087 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 19 Jan 2021 09:57:55 +0100 Subject: [PATCH 1/7] Added support for sync notification handlers in the event aggregator --- .../UmbracoBuilder.Events.cs | 25 +++++++- .../DependencyInjection/UmbracoBuilder.cs | 2 +- .../Events/EventAggregator.Notifications.cs | 57 +++++++++++++++++-- src/Umbraco.Core/Events/EventAggregator.cs | 1 + src/Umbraco.Core/Events/IEventAggregator.cs | 2 +- .../Events/INotificationHandler.cs | 14 +++++ ...uginsManifestWatcherNotificationHandler.cs | 2 +- .../Runtime/EssentialDirectoryCreator.cs | 5 +- ...abaseServerMessengerNotificationHandler.cs | 6 +- .../Events/EventAggregatorTests.cs | 53 +++++++++++++++-- .../Profiler/InitializeWebProfiling.cs | 6 +- 11 files changed, 146 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs index c24936b4fb..2b4f05a4f0 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs @@ -2,7 +2,6 @@ // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Umbraco.Core.Events; namespace Umbraco.Core.DependencyInjection @@ -35,5 +34,29 @@ namespace Umbraco.Core.DependencyInjection 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); + + // 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; + } } } 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..7bc63df228 100644 --- a/src/Umbraco.Core/Events/EventAggregator.cs +++ b/src/Umbraco.Core/Events/EventAggregator.cs @@ -38,6 +38,7 @@ namespace Umbraco.Core.Events throw new ArgumentNullException(nameof(notification)); } + PublishNotification(notification); return PublishNotificationAsync(notification, cancellationToken); } } diff --git a/src/Umbraco.Core/Events/IEventAggregator.cs b/src/Umbraco.Core/Events/IEventAggregator.cs index d78aed36b7..dc4205b0ec 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. 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.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; } } } From bc8f8366c29d81e091da9d65d0eff59566c97a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 20 Jan 2021 14:11:12 +0100 Subject: [PATCH 2/7] Call rebindCallback on errors as well (#9688) (cherry picked from commit 6d712b0d16b1f2d85c53d020fa68fb41bb0d3d5c) # Conflicts: # src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js # src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js --- .../components/content/edit.controller.js | 7 ++++++- .../services/contenteditinghelper.service.js | 17 ++++++++++------- 2 files changed, 16 insertions(+), 8 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 d02c48750a..a47a3125c9 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 @@ -271,6 +271,7 @@ * @param {any} app the active content app */ function createButtons(content) { + // for trashed and element type items, the save button is the primary action - otherwise it's a secondary action $scope.page.saveButtonStyle = content.trashed || content.isElement || content.isBlueprint ? "primary" : "info"; @@ -468,6 +469,10 @@ return $q.when(data); }, function (err) { + + //needs to be manually set for infinite editing mode + $scope.page.isNew = false; + syncTreeNode($scope.content, $scope.content.path); resetNestedFieldValiation(fieldsToRollback); @@ -974,7 +979,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 4ffd0c3c0b..d469a6d24a 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; } @@ -125,7 +125,10 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt softRedirect: args.softRedirect, err: err, rebindCallback: function () { - rebindCallback.apply(self, [args.content, err.data]); + // 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]); + } } }); @@ -301,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")); @@ -625,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 @@ -643,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)) { @@ -690,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)) { @@ -726,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; From 52e8da2ff2672a9d67d4d2a4d379a5f2cdc20bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 20 Jan 2021 14:26:40 +0100 Subject: [PATCH 3/7] Call rebindCallback on errors as well (#9690) (cherry picked from commit da0c82aabdfdb8ecf78f6abf7e890d06534e972b) --- .../directives/components/content/edit.controller.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 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 a47a3125c9..b26ec26d60 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 @@ -461,7 +461,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(); @@ -470,11 +470,14 @@ }, function (err) { - //needs to be manually set for infinite editing mode - $scope.page.isNew = false; 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); From 8e64fe54b1213bef9e3218064d4b6ba74ebe04d3 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 13 Jan 2021 11:57:21 +0100 Subject: [PATCH 4/7] updates the css to support items with long titles --- .../src/less/components/card.less | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) 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; From 839eb62953b1c50b577d7e60cbfd328b40e88311 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 21 Jan 2021 07:09:52 +0100 Subject: [PATCH 5/7] Introduce a Sync publish option --- src/Umbraco.Core/Events/EventAggregator.cs | 14 ++++++++++++++ src/Umbraco.Core/Events/IEventAggregator.cs | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/src/Umbraco.Core/Events/EventAggregator.cs b/src/Umbraco.Core/Events/EventAggregator.cs index 7bc63df228..33c8c3aa4f 100644 --- a/src/Umbraco.Core/Events/EventAggregator.cs +++ b/src/Umbraco.Core/Events/EventAggregator.cs @@ -41,6 +41,20 @@ namespace Umbraco.Core.Events 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 dc4205b0ec..0d8905dd62 100644 --- a/src/Umbraco.Core/Events/IEventAggregator.cs +++ b/src/Umbraco.Core/Events/IEventAggregator.cs @@ -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; } } From bd321515858e7acc386dff1532c4a4064394f035 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 21 Jan 2021 07:11:31 +0100 Subject: [PATCH 6/7] Remove old TODOs --- src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs index 2b4f05a4f0..342ecc86ec 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs @@ -25,8 +25,6 @@ namespace Umbraco.Core.DependencyInjection // Register the handler as transient. This ensures that anything can be injected into it. var descriptor = new ServiceDescriptor(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); @@ -49,8 +47,6 @@ namespace Umbraco.Core.DependencyInjection // Register the handler as transient. This ensures that anything can be injected into it. var descriptor = new ServiceDescriptor(typeof(INotificationAsyncHandler), typeof(TNotificationAsyncHandler), 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); From 647d6ac5ab2b90fd2b6aecf680cb005247badf5a Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 25 Jan 2021 07:37:05 +0100 Subject: [PATCH 7/7] Post merge fix (conflicts) --- .../DisableModelsBuilderNotificationHandler.cs | 5 +---- .../LiveModelsProvider.cs | 5 +---- .../ModelsBuilderNotificationHandler.cs | 10 ++-------- .../OutOfDateModelsStatus.cs | 5 +---- 4 files changed, 5 insertions(+), 20 deletions(-) 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()