diff --git a/.github/README.md b/.github/README.md index aec35d5..717dc69 100644 --- a/.github/README.md +++ b/.github/README.md @@ -3,14 +3,16 @@ [![Umbraco Marketplace](https://img.shields.io/badge/Umbraco-Marketplace-%233544B1?style=flat&logo=umbraco)](https://marketplace.umbraco.com/package/umbraco.community.backofficeorganiser) [![GitHub License](https://img.shields.io/github/license/jcdcdev/Umbraco.Community.BackOfficeOrganiser?color=8AB803&label=License&logo=github)](https://github.com/jcdcdev/Umbraco.Community.BackOfficeOrganiser/blob/main/LICENSE) [![NuGet Downloads](https://img.shields.io/nuget/dt/Umbraco.Community.BackOfficeOrganiser?color=cc9900&label=Downloads&logo=nuget)](https://www.nuget.org/packages/Umbraco.Community.BackOfficeOrganiser/) +[![Project Website](https://img.shields.io/badge/Project%20Website-jcdc.dev-jcdcdev?style=flat&color=3c4834&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0id2hpdGUiIGNsYXNzPSJiaSBiaS1wYy1kaXNwbGF5IiB2aWV3Qm94PSIwIDAgMTYgMTYiPgogIDxwYXRoIGQ9Ik04IDFhMSAxIDAgMCAxIDEtMWg2YTEgMSAwIDAgMSAxIDF2MTRhMSAxIDAgMCAxLTEgMUg5YTEgMSAwIDAgMS0xLTF6bTEgMTMuNWEuNS41IDAgMSAwIDEgMCAuNS41IDAgMCAwLTEgMG0yIDBhLjUuNSAwIDEgMCAxIDAgLjUuNSAwIDAgMC0xIDBNOS41IDFhLjUuNSAwIDAgMCAwIDFoNWEuNS41IDAgMCAwIDAtMXpNOSAzLjVhLjUuNSAwIDAgMCAuNS41aDVhLjUuNSAwIDAgMCAwLTFoLTVhLjUuNSAwIDAgMC0uNS41TTEuNSAyQTEuNSAxLjUgMCAwIDAgMCAzLjV2N0ExLjUgMS41IDAgMCAwIDEuNSAxMkg2djJoLS41YS41LjUgMCAwIDAgMCAxSDd2LTRIMS41YS41LjUgMCAwIDEtLjUtLjV2LTdhLjUuNSAwIDAgMSAuNS0uNUg3VjJ6Ii8+Cjwvc3ZnPg==)](https://jcdc.dev/umbraco-packages/back-office-organiser) Is your backoffice a bit untidy? - Single-click (and opinionated) organiser for - - Document Types - - Media Types - - Member Types - - Data Types + - Document Types + - Media Types + - Member Types + - Data Types +- Automatically sorts on save (configurable) ![A screenshot of the Back Office Organiser in action](https://raw.githubusercontent.com/jcdcdev/Umbraco.Community.BackOfficeOrganiser/main/docs/screenshots/backoffice.png) @@ -27,13 +29,25 @@ Is your backoffice a bit untidy? Add the following to your `appsettings.json` file ```JSON - "BackOfficeOrganiser": { - "DataTypes": { - "InternalFolderName": "Internal", - "ThirdPartyFolderName": "Third Party", - "CustomFolderName": "Custom" - } - } +{ + "BackOfficeOrganiser": { + "DataTypes": { + "InternalFolderName": "Internal", + "ThirdPartyFolderName": "Third Party", + "CustomFolderName": "Custom", + "OrganiseOnSave": true + }, + "ContentTypes": { + "OrganiseOnSave": true + }, + "MediaTypes": { + "OrganiseOnSave": true + }, + "MemberTypes": { + "OrganiseOnSave": true + } + } +} ``` ## Extending diff --git a/src/Umbraco.Community.BackOfficeOrganiser/Composing/BackofficeOrganiserNotificationHandler.cs b/src/Umbraco.Community.BackOfficeOrganiser/Composing/BackofficeOrganiserNotificationHandler.cs new file mode 100644 index 0000000..898927a --- /dev/null +++ b/src/Umbraco.Community.BackOfficeOrganiser/Composing/BackofficeOrganiserNotificationHandler.cs @@ -0,0 +1,89 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Community.BackOfficeOrganiser.Models; +using Umbraco.Community.BackOfficeOrganiser.Organisers.ContentTypes; +using Umbraco.Community.BackOfficeOrganiser.Organisers.DataTypes; +using Umbraco.Community.BackOfficeOrganiser.Organisers.MediaTypes; +using Umbraco.Community.BackOfficeOrganiser.Organisers.MemberTypes; + +namespace Umbraco.Community.BackOfficeOrganiser.Composing; + +public class BackofficeOrganiserNotificationHandler : + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler +{ + private readonly ContentTypeOrganiser _contentTypeOrganiser; + private readonly DataTypeOrganiser _dataTypeOrganiser; + private readonly MediaTypeOrganiser _mediaTypeOrganiser; + private readonly MemberTypeOrganiser _memberTypeOrganiser; + private readonly BackOfficeOrganiserOptions _options; + + public BackofficeOrganiserNotificationHandler( + DataTypeOrganiser dataTypeOrganiser, + ContentTypeOrganiser contentTypeOrganiser, + MediaTypeOrganiser mediaTypeOrganiser, + MemberTypeOrganiser memberTypeOrganiser, + IOptions options) + { + _dataTypeOrganiser = dataTypeOrganiser; + _contentTypeOrganiser = contentTypeOrganiser; + _mediaTypeOrganiser = mediaTypeOrganiser; + _memberTypeOrganiser = memberTypeOrganiser; + _options = options.Value; + } + + public void Handle(ContentTypeSavedNotification notification) + { + if (!_options.ContentTypes.OrganiseOnSave) + { + return; + } + + foreach (var item in notification.SavedEntities) + { + _contentTypeOrganiser.Organise(item); + } + } + + public void Handle(DataTypeSavedNotification notification) + { + if (!_options.DataTypes.OrganiseOnSave) + { + return; + } + + foreach (var dataType in notification.SavedEntities) + { + _dataTypeOrganiser.Organise(dataType); + } + } + + public void Handle(MediaTypeSavedNotification notification) + { + if (!_options.MediaTypes.OrganiseOnSave) + { + return; + } + + foreach (var item in notification.SavedEntities) + { + _mediaTypeOrganiser.Organise(item); + } + } + + public void Handle(MemberTypeSavedNotification notification) + { + if (!_options.MemberTypes.OrganiseOnSave) + { + return; + } + + foreach (var item in notification.SavedEntities) + { + _memberTypeOrganiser.Organise(item); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Community.BackOfficeOrganiser/Composing/Composer.cs b/src/Umbraco.Community.BackOfficeOrganiser/Composing/Composer.cs index 22aab8d..ebea27f 100644 --- a/src/Umbraco.Community.BackOfficeOrganiser/Composing/Composer.cs +++ b/src/Umbraco.Community.BackOfficeOrganiser/Composing/Composer.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Notifications; using Umbraco.Community.BackOfficeOrganiser.Models; using Umbraco.Community.BackOfficeOrganiser.Organisers.ContentTypes; using Umbraco.Community.BackOfficeOrganiser.Organisers.DataTypes; @@ -27,8 +28,14 @@ public void Compose(IUmbracoBuilder builder) builder.ManifestFilters().Append(); builder.DataTypeOrganiseActions().Append(); + builder.ContentTypeOrganiseActions().Append(); builder.ContentTypeOrganiseActions().Append(); builder.MediaTypeOrganiseActions().Append(); builder.MemberTypeOrganiseActions().Append(); + + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); } } \ No newline at end of file diff --git a/src/Umbraco.Community.BackOfficeOrganiser/Models/BackOfficeOrganiserOptions.cs b/src/Umbraco.Community.BackOfficeOrganiser/Models/BackOfficeOrganiserOptions.cs index 133e9fa..4c8b03a 100644 --- a/src/Umbraco.Community.BackOfficeOrganiser/Models/BackOfficeOrganiserOptions.cs +++ b/src/Umbraco.Community.BackOfficeOrganiser/Models/BackOfficeOrganiserOptions.cs @@ -3,5 +3,8 @@ namespace Umbraco.Community.BackOfficeOrganiser.Models; public class BackOfficeOrganiserOptions { public DataTypeOptions DataTypes { get; set; } = new(); + public ContentTypeOptions ContentTypes { get; set; } = new(); + public MemberTypeOptions MemberTypes { get; set; } = new(); + public MediaTypeOptions MediaTypes { get; set; } = new(); public static string SectionName => "BackOfficeOrganiser"; } \ No newline at end of file diff --git a/src/Umbraco.Community.BackOfficeOrganiser/Models/ContentTypeOptions.cs b/src/Umbraco.Community.BackOfficeOrganiser/Models/ContentTypeOptions.cs new file mode 100644 index 0000000..64d4bc8 --- /dev/null +++ b/src/Umbraco.Community.BackOfficeOrganiser/Models/ContentTypeOptions.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Community.BackOfficeOrganiser.Models; + +public class ContentTypeOptions +{ + public bool OrganiseOnSave { get; set; } = true; +} \ No newline at end of file diff --git a/src/Umbraco.Community.BackOfficeOrganiser/Models/DataTypeOptions.cs b/src/Umbraco.Community.BackOfficeOrganiser/Models/DataTypeOptions.cs index 7b81b05..f3e7762 100644 --- a/src/Umbraco.Community.BackOfficeOrganiser/Models/DataTypeOptions.cs +++ b/src/Umbraco.Community.BackOfficeOrganiser/Models/DataTypeOptions.cs @@ -5,4 +5,5 @@ public class DataTypeOptions public string InternalFolderName { get; set; } = "🔒 Internal"; public string ThirdPartyFolderName { get; set; } = "🦄 Third Party"; public string CustomFolderName { get; set; } = "🔧 Custom"; + public bool OrganiseOnSave { get; set; } = true; } \ No newline at end of file diff --git a/src/Umbraco.Community.BackOfficeOrganiser/Models/MediaTypeOptions.cs b/src/Umbraco.Community.BackOfficeOrganiser/Models/MediaTypeOptions.cs new file mode 100644 index 0000000..a594795 --- /dev/null +++ b/src/Umbraco.Community.BackOfficeOrganiser/Models/MediaTypeOptions.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Community.BackOfficeOrganiser.Models; + +public class MediaTypeOptions +{ + public bool OrganiseOnSave { get; set; } = true; +} \ No newline at end of file diff --git a/src/Umbraco.Community.BackOfficeOrganiser/Models/MemberTypeOptions.cs b/src/Umbraco.Community.BackOfficeOrganiser/Models/MemberTypeOptions.cs new file mode 100644 index 0000000..0ec3a64 --- /dev/null +++ b/src/Umbraco.Community.BackOfficeOrganiser/Models/MemberTypeOptions.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Community.BackOfficeOrganiser.Models; + +public class MemberTypeOptions +{ + public bool OrganiseOnSave { get; set; } = true; +} \ No newline at end of file diff --git a/src/Umbraco.Community.BackOfficeOrganiser/Organisers/BackOfficeOrganiserBase.cs b/src/Umbraco.Community.BackOfficeOrganiser/Organisers/BackOfficeOrganiserBase.cs index a9822ee..757ed7b 100644 --- a/src/Umbraco.Community.BackOfficeOrganiser/Organisers/BackOfficeOrganiserBase.cs +++ b/src/Umbraco.Community.BackOfficeOrganiser/Organisers/BackOfficeOrganiserBase.cs @@ -11,13 +11,22 @@ protected BackOfficeOrganiserBase(ILogger logger) Logger = logger; } - public void OrganiseType() + protected virtual void PostOrganiseAll() + { + } + + public void OrganiseAll() { Logger.LogInformation("BackOfficeOrganiser: Cleanup for {Type} Started", typeof(T).Name); try { - Organise(); + var items = GetAll(); + foreach (var item in items) + { + Organise(item); + } + PostOrganiseAll(); } catch (Exception ex) { @@ -28,5 +37,7 @@ public void OrganiseType() Logger.LogInformation("BackOfficeOrganiser: Cleanup for {Type} Complete", typeof(T).Name); } - public abstract void Organise(); + public abstract void Organise(T item); + + protected abstract List GetAll(); } \ No newline at end of file diff --git a/src/Umbraco.Community.BackOfficeOrganiser/Organisers/ContentTypes/ContentTypeOrganiser.cs b/src/Umbraco.Community.BackOfficeOrganiser/Organisers/ContentTypes/ContentTypeOrganiser.cs index 63e62a2..49d2623 100644 --- a/src/Umbraco.Community.BackOfficeOrganiser/Organisers/ContentTypes/ContentTypeOrganiser.cs +++ b/src/Umbraco.Community.BackOfficeOrganiser/Organisers/ContentTypes/ContentTypeOrganiser.cs @@ -19,16 +19,16 @@ public ContentTypeOrganiser( _organiseActions = organiseActions; } - public override void Organise() - { - var contentTypes = _contentTypeService.GetAll().ToList(); + protected override List GetAll() => _contentTypeService.GetAll().ToList(); - foreach (var contentType in contentTypes) - { - var organiser = _organiseActions.FirstOrDefault(x => x.CanMove(contentType, _contentTypeService)); - organiser?.Move(contentType, _contentTypeService); - } + public override void Organise(IContentType contentType) + { + var organiser = _organiseActions.FirstOrDefault(x => x.CanMove(contentType, _contentTypeService)); + organiser?.Move(contentType, _contentTypeService); + } + protected override void PostOrganiseAll() + { _contentTypeService.DeleteAllEmptyContainers(); } } \ No newline at end of file diff --git a/src/Umbraco.Community.BackOfficeOrganiser/Organisers/ContentTypes/ElementTypeOrganiser.cs b/src/Umbraco.Community.BackOfficeOrganiser/Organisers/ContentTypes/ElementTypeOrganiser.cs new file mode 100644 index 0000000..dd04c48 --- /dev/null +++ b/src/Umbraco.Community.BackOfficeOrganiser/Organisers/ContentTypes/ElementTypeOrganiser.cs @@ -0,0 +1,90 @@ +using jcdcdev.Umbraco.Core.Extensions; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; + +namespace Umbraco.Community.BackOfficeOrganiser.Organisers.ContentTypes; + +public class ElementTypeOrganiser : IContentTypeOrganiseAction +{ + private readonly IDataTypeService _dataTypeService; + + public ElementTypeOrganiser(IDataTypeService dataTypeService) + { + _dataTypeService = dataTypeService; + } + + public bool CanMove(IContentType contentType, IContentTypeService contentTypeService) => contentType.IsElement; + + public void Move(IContentType contentType, IContentTypeService contentTypeService) + { + var nestedContentDataTypes = _dataTypeService.GetByEditorAlias(Umbraco.Cms.Core.Constants.PropertyEditors.Aliases.NestedContent).Select(x => x.ConfigurationAs()); + var blockGridDataTypes = _dataTypeService.GetByEditorAlias(Umbraco.Cms.Core.Constants.PropertyEditors.Aliases.BlockGrid).Select(x => x.ConfigurationAs()); + var blockListDataTypes = _dataTypeService.GetByEditorAlias(Umbraco.Cms.Core.Constants.PropertyEditors.Aliases.BlockList).Select(x => x.ConfigurationAs()); + + var nestedContentContentTypeAliases = new List(); + var gridContentTypeKeys = new List(); + var blockContentTypeKeys = new List(); + foreach (var blockGridDataType in blockGridDataTypes) + { + foreach (var blockGrid in blockGridDataType?.Blocks ?? Array.Empty()) + { + gridContentTypeKeys.Add(blockGrid.ContentElementTypeKey); + if (blockGrid.SettingsElementTypeKey.HasValue) + { + gridContentTypeKeys.Add(blockGrid.SettingsElementTypeKey.Value); + } + } + } + + foreach (var blockListDataType in blockListDataTypes) + { + foreach (var blockList in blockListDataType?.Blocks ?? Array.Empty()) + { + blockContentTypeKeys.Add(blockList.ContentElementTypeKey); + if (blockList.SettingsElementTypeKey.HasValue) + { + blockContentTypeKeys.Add(blockList.SettingsElementTypeKey.Value); + } + } + } + + foreach (var nestedContentDataType in nestedContentDataTypes) + { + if (nestedContentDataType == null) + { + continue; + } + + var aliases = nestedContentDataType.ContentTypes?.Select(x => x.Alias).WhereNotNull() ?? Array.Empty(); + nestedContentContentTypeAliases.AddRange(aliases); + } + + var isNestedContent = nestedContentContentTypeAliases.Contains(contentType.Alias); + var isBlockGrid = gridContentTypeKeys.Contains(contentType.Key); + var isBlockList = blockContentTypeKeys.Contains(contentType.Key); + + var parent = contentTypeService.GetOrCreateFolder("Element Types"); + var folderName = string.Empty; + if (isNestedContent && !isBlockGrid && !isBlockList) + { + folderName = "Nested Content"; + } + else if (isBlockGrid && !isNestedContent && !isBlockList) + { + folderName = "Block Grid"; + } + else if (isBlockList && !isNestedContent && !isBlockGrid) + { + folderName = "Block List"; + } + + if (!string.IsNullOrWhiteSpace(folderName)) + { + parent = contentTypeService.GetOrCreateFolder(folderName, parent.Id); + } + + contentTypeService.Move(contentType, parent.Id); + } +} \ No newline at end of file diff --git a/src/Umbraco.Community.BackOfficeOrganiser/Organisers/DataTypes/DataTypeOrganiser.cs b/src/Umbraco.Community.BackOfficeOrganiser/Organisers/DataTypes/DataTypeOrganiser.cs index 69a9381..a505ae7 100644 --- a/src/Umbraco.Community.BackOfficeOrganiser/Organisers/DataTypes/DataTypeOrganiser.cs +++ b/src/Umbraco.Community.BackOfficeOrganiser/Organisers/DataTypes/DataTypeOrganiser.cs @@ -19,16 +19,16 @@ public DataTypeOrganiser( _organiseActions = organiseActions; } - public override void Organise() + public override void Organise(IDataType dataType) { - var dataTypes = _dataTypeService.GetAll().ToList(); - - foreach (var dataType in dataTypes) - { - var organiser = _organiseActions.FirstOrDefault(x => x.CanMove(dataType, _dataTypeService)); - organiser?.Move(dataType, _dataTypeService); - } + var organiser = _organiseActions.FirstOrDefault(x => x.CanMove(dataType, _dataTypeService)); + organiser?.Move(dataType, _dataTypeService); + } + protected override List GetAll() => _dataTypeService.GetAll().ToList(); + + protected override void PostOrganiseAll() + { _dataTypeService.DeleteAllEmptyContainers(); } } \ No newline at end of file diff --git a/src/Umbraco.Community.BackOfficeOrganiser/Organisers/IBackOfficeOrganiser.cs b/src/Umbraco.Community.BackOfficeOrganiser/Organisers/IBackOfficeOrganiser.cs index a32ffca..9416f8c 100644 --- a/src/Umbraco.Community.BackOfficeOrganiser/Organisers/IBackOfficeOrganiser.cs +++ b/src/Umbraco.Community.BackOfficeOrganiser/Organisers/IBackOfficeOrganiser.cs @@ -2,5 +2,6 @@ namespace Umbraco.Community.BackOfficeOrganiser.Organisers; public interface IBackOfficeOrganiser { - void OrganiseType(); + void OrganiseAll(); + void Organise(T item); } \ No newline at end of file diff --git a/src/Umbraco.Community.BackOfficeOrganiser/Organisers/MediaTypes/MediaTypeOrganiser.cs b/src/Umbraco.Community.BackOfficeOrganiser/Organisers/MediaTypes/MediaTypeOrganiser.cs index b878c4d..4a43c71 100644 --- a/src/Umbraco.Community.BackOfficeOrganiser/Organisers/MediaTypes/MediaTypeOrganiser.cs +++ b/src/Umbraco.Community.BackOfficeOrganiser/Organisers/MediaTypes/MediaTypeOrganiser.cs @@ -19,16 +19,16 @@ public MediaTypeOrganiser( _organiseActions = organiseActions; } - public override void Organise() + public override void Organise(IMediaType mediaType) { - var mediaTypes = _mediaTypeService.GetAll().ToList(); - - foreach (var mediaType in mediaTypes) - { - var organiser = _organiseActions.FirstOrDefault(x => x.CanMove(mediaType, _mediaTypeService)); - organiser?.Move(mediaType, _mediaTypeService); - } + var organiser = _organiseActions.FirstOrDefault(x => x.CanMove(mediaType, _mediaTypeService)); + organiser?.Move(mediaType, _mediaTypeService); + } + protected override List GetAll() => _mediaTypeService.GetAll().ToList(); + + protected override void PostOrganiseAll() + { _mediaTypeService.DeleteAllEmptyContainers(); } } \ No newline at end of file diff --git a/src/Umbraco.Community.BackOfficeOrganiser/Organisers/MemberTypes/MemberTypeOrganiser.cs b/src/Umbraco.Community.BackOfficeOrganiser/Organisers/MemberTypes/MemberTypeOrganiser.cs index 6d785d7..49a5c5c 100644 --- a/src/Umbraco.Community.BackOfficeOrganiser/Organisers/MemberTypes/MemberTypeOrganiser.cs +++ b/src/Umbraco.Community.BackOfficeOrganiser/Organisers/MemberTypes/MemberTypeOrganiser.cs @@ -19,16 +19,16 @@ public MemberTypeOrganiser( _organiseActions = organiseActions; } - public override void Organise() + public override void Organise(IMemberType item) { - var memberTypes = _memberTypeService.GetAll().ToList(); + var organiser = _organiseActions.FirstOrDefault(x => x.CanMove(item, _memberTypeService)); + organiser?.Move(item, _memberTypeService); + } - foreach (var memberType in memberTypes) - { - var organiser = _organiseActions.FirstOrDefault(x => x.CanMove(memberType, _memberTypeService)); - organiser?.Move(memberType, _memberTypeService); - } + protected override List GetAll() => _memberTypeService.GetAll().ToList(); + protected override void PostOrganiseAll() + { _memberTypeService.DeleteAllEmptyContainers(); } } \ No newline at end of file diff --git a/src/Umbraco.Community.BackOfficeOrganiser/Services/BackOfficeOrganiserService.cs b/src/Umbraco.Community.BackOfficeOrganiser/Services/BackOfficeOrganiserService.cs index aeb9f44..bb29d42 100644 --- a/src/Umbraco.Community.BackOfficeOrganiser/Services/BackOfficeOrganiserService.cs +++ b/src/Umbraco.Community.BackOfficeOrganiser/Services/BackOfficeOrganiserService.cs @@ -65,21 +65,21 @@ public Attempt Organise(OrganiseType organise) private void OrganiseDataTypes() { - _dataTypeOrganiser.OrganiseType(); + _dataTypeOrganiser.OrganiseAll(); } private void OrganiseMemberTypes() { - _memberTypeOrganiser.OrganiseType(); + _memberTypeOrganiser.OrganiseAll(); } private void OrganiseMediaTypes() { - _mediaTypeOrganiser.OrganiseType(); + _mediaTypeOrganiser.OrganiseAll(); } private void OrganiseContentTypes() { - _contentTypeOrganiser.OrganiseType(); + _contentTypeOrganiser.OrganiseAll(); } } \ No newline at end of file