diff --git a/src/SeoToolkit.Umbraco.MetaFields.Core/Common/DisplayProviders/MetaFieldsContentDisplayProvider.cs b/src/SeoToolkit.Umbraco.MetaFields.Core/Common/DisplayProviders/MetaFieldsContentDisplayProvider.cs index f07b79da..2b394c60 100644 --- a/src/SeoToolkit.Umbraco.MetaFields.Core/Common/DisplayProviders/MetaFieldsContentDisplayProvider.cs +++ b/src/SeoToolkit.Umbraco.MetaFields.Core/Common/DisplayProviders/MetaFieldsContentDisplayProvider.cs @@ -1,9 +1,11 @@ using SeoToolkit.Umbraco.Common.Core.Interfaces; using SeoToolkit.Umbraco.Common.Core.Models.ViewModels; +using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Models; namespace SeoToolkit.Umbraco.MetaFields.Core.Common.DisplayProviders { + [Weight(100)] public class MetaFieldsContentDisplayProvider : ISeoDisplayProvider { public SeoDisplayViewModel Get(IContent content) diff --git a/src/SeoToolkit.Umbraco.MetaFields.Core/Controllers/MetaFieldsController.cs b/src/SeoToolkit.Umbraco.MetaFields.Core/Controllers/MetaFieldsController.cs index 1a542b5e..aa3b1f44 100644 --- a/src/SeoToolkit.Umbraco.MetaFields.Core/Controllers/MetaFieldsController.cs +++ b/src/SeoToolkit.Umbraco.MetaFields.Core/Controllers/MetaFieldsController.cs @@ -84,6 +84,7 @@ public IActionResult Get(int nodeId, string culture) Title = key.Title, Description = key.Description, GroupAlias = key.GroupAlias, + Suggestions = key.Suggestions.Select(it => it.ToViewModel()).ToArray(), Value = humanReadableValue?.ToString(), UserValue = userValue, EditView = key.EditEditor.View, diff --git a/src/SeoToolkit.Umbraco.MetaFields.Core/Interfaces/SeoField/ISeoField.cs b/src/SeoToolkit.Umbraco.MetaFields.Core/Interfaces/SeoField/ISeoField.cs index 912dc109..66a57ebf 100644 --- a/src/SeoToolkit.Umbraco.MetaFields.Core/Interfaces/SeoField/ISeoField.cs +++ b/src/SeoToolkit.Umbraco.MetaFields.Core/Interfaces/SeoField/ISeoField.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Microsoft.AspNetCore.Html; namespace SeoToolkit.Umbraco.MetaFields.Core.Interfaces.SeoField @@ -9,6 +10,7 @@ public interface ISeoField string Alias { get; } string Description { get; } string GroupAlias { get; } + List Suggestions { get; } Type FieldType { get; } ISeoFieldEditor Editor { get; } ISeoFieldEditEditor EditEditor { get; } diff --git a/src/SeoToolkit.Umbraco.MetaFields.Core/Interfaces/SeoField/ISeoFieldSuggestion.cs b/src/SeoToolkit.Umbraco.MetaFields.Core/Interfaces/SeoField/ISeoFieldSuggestion.cs new file mode 100644 index 00000000..4d12910e --- /dev/null +++ b/src/SeoToolkit.Umbraco.MetaFields.Core/Interfaces/SeoField/ISeoFieldSuggestion.cs @@ -0,0 +1,11 @@ +using SeoToolkit.Umbraco.MetaFields.Core.Models.SeoField.ViewModels; + +namespace SeoToolkit.Umbraco.MetaFields.Core.Interfaces.SeoField +{ + public interface ISeoFieldSuggestion + { + string Alias { get; } + + SeoSuggestionViewModel ToViewModel(); + } +} diff --git a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/KeywordsField.cs b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/KeywordsField.cs index bc65b477..37afde65 100644 --- a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/KeywordsField.cs +++ b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/KeywordsField.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Web; using Microsoft.AspNetCore.Html; @@ -19,6 +20,7 @@ public class KeywordsField : ISeoField public string Alias => SeoFieldAliasConstants.Keywords; public string Description => "Keywords for the page"; public string GroupAlias => SeoFieldGroupConstants.MetaFieldsGroup; + public List Suggestions { get; } = new List(); public Type FieldType => typeof(string); public ISeoFieldEditor Editor => new KeywordsFieldPropertyEditor(); diff --git a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/OpenGraphDescriptionField.cs b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/OpenGraphDescriptionField.cs index 41a5df9b..7d78effe 100644 --- a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/OpenGraphDescriptionField.cs +++ b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/OpenGraphDescriptionField.cs @@ -6,6 +6,7 @@ using SeoToolkit.Umbraco.MetaFields.Core.Interfaces.SeoField; using SeoToolkit.Umbraco.MetaFields.Core.Models.SeoFieldEditors; using System.Web; +using System.Collections.Generic; namespace SeoToolkit.Umbraco.MetaFields.Core.Models.SeoField { @@ -16,6 +17,7 @@ public class OpenGraphDescriptionField : ISeoField public string Alias => SeoFieldAliasConstants.OpenGraphDescription; public string Description => "Description for Open Graph"; public string GroupAlias => SeoFieldGroupConstants.SocialMediaGroup; + public List Suggestions { get; } = new List(); public Type FieldType => typeof(string); public ISeoFieldEditor Editor => new SeoFieldFieldsEditor(new[] { diff --git a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/OpenGraphImageField.cs b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/OpenGraphImageField.cs index 9c97f68a..5855f648 100644 --- a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/OpenGraphImageField.cs +++ b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/OpenGraphImageField.cs @@ -8,6 +8,7 @@ using SeoToolkit.Umbraco.MetaFields.Core.Constants; using SeoToolkit.Umbraco.MetaFields.Core.Interfaces.SeoField; using SeoToolkit.Umbraco.MetaFields.Core.Models.SeoFieldEditors; +using System.Collections.Generic; namespace SeoToolkit.Umbraco.MetaFields.Core.Models.SeoField { @@ -19,6 +20,7 @@ public class OpenGraphImageField : ISeoField public string Alias => SeoFieldAliasConstants.OpenGraphImage; public string Description => "Image for Open Graph"; public string GroupAlias => SeoFieldGroupConstants.SocialMediaGroup; + public List Suggestions { get; } = new List(); public Type FieldType => typeof(string); public ISeoFieldEditor Editor => new SeoFieldFieldsEditor(new[] { "Umbraco.MediaPicker", "Umbraco.MediaPicker3" }); diff --git a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/OpenGraphTitleField.cs b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/OpenGraphTitleField.cs index 880b30e6..6369d270 100644 --- a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/OpenGraphTitleField.cs +++ b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/OpenGraphTitleField.cs @@ -6,6 +6,7 @@ using SeoToolkit.Umbraco.MetaFields.Core.Interfaces.SeoField; using SeoToolkit.Umbraco.MetaFields.Core.Models.SeoFieldEditors; using System.Web; +using System.Collections.Generic; namespace SeoToolkit.Umbraco.MetaFields.Core.Models.SeoField { @@ -16,6 +17,7 @@ public class OpenGraphTitleField : ISeoField public string Alias => SeoFieldAliasConstants.OpenGraphTitle; public string Description => "Title for open graph"; public string GroupAlias => SeoFieldGroupConstants.SocialMediaGroup; + public List Suggestions { get; } = new List(); public Type FieldType => typeof(string); public ISeoFieldEditor Editor => new SeoFieldFieldsEditor(new[] { "Umbraco.TextBox", "Umbraco.TextArea", "Umbraco.TinyMCE" }); diff --git a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/SeoDescriptionField.cs b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/SeoDescriptionField.cs index 2b606bcd..1dfc8593 100644 --- a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/SeoDescriptionField.cs +++ b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/SeoDescriptionField.cs @@ -6,6 +6,8 @@ using SeoToolkit.Umbraco.MetaFields.Core.Interfaces.SeoField; using SeoToolkit.Umbraco.MetaFields.Core.Models.SeoFieldEditors; using System.Web; +using System.Collections.Generic; +using SeoToolkit.Umbraco.MetaFields.Core.Models.SeoFieldSuggestions; namespace SeoToolkit.Umbraco.MetaFields.Core.Models.SeoField { @@ -16,6 +18,10 @@ public class SeoDescriptionField : ISeoField public string Alias => SeoFieldAliasConstants.MetaDescription; public string Description => "Meta description for the page"; public string GroupAlias => SeoFieldGroupConstants.MetaFieldsGroup; + public List Suggestions { get; } = new List + { + new SeoFieldMaxLengthSuggestion() { MaxLength = 160 } + }; public Type FieldType => typeof(string); public ISeoFieldEditor Editor => new SeoFieldFieldsEditor(new[] { diff --git a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/SeoField.cs b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/SeoField.cs index 2db6b3aa..f62500fc 100644 --- a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/SeoField.cs +++ b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/SeoField.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Microsoft.AspNetCore.Html; using SeoToolkit.Umbraco.MetaFields.Core.Interfaces.SeoField; @@ -10,6 +11,7 @@ public abstract class SeoField : ISeoField public abstract string Alias { get; } public abstract string Description { get; } public abstract string GroupAlias { get; } + public List Suggestions { get; } = new List(); public Type FieldType => typeof(T); public abstract ISeoFieldEditor Editor { get; } public abstract ISeoFieldEditEditor EditEditor { get; } diff --git a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/SeoSchemaField.cs b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/SeoSchemaField.cs index 50431ff3..a43f4743 100644 --- a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/SeoSchemaField.cs +++ b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/SeoSchemaField.cs @@ -6,6 +6,7 @@ using SeoToolkit.Umbraco.MetaFields.Core.Interfaces.SeoField; using SeoToolkit.Umbraco.MetaFields.Core.Models.SeoFieldEditors; using System.Web; +using System.Collections.Generic; namespace SeoToolkit.Umbraco.MetaFields.Core.Models.SeoField { @@ -16,6 +17,7 @@ public class SeoSchemaField : ISeoField public string Alias => SeoFieldAliasConstants.Schema; public string Description => "The schemas are a set of 'types', each associated with a set of properties. The types are arranged in a hierarchy."; public string GroupAlias => SeoFieldGroupConstants.Others; + public List Suggestions { get; } = new List(); public Type FieldType => typeof(string); public ISeoFieldEditor Editor => new SeoFieldFieldsEditor(new[] { "Umbraco.TextBox", "Umbraco.TextArea", "Umbraco.TinyMCE" }); diff --git a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/SeoTitleField.cs b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/SeoTitleField.cs index a3e84fcd..6d9eff27 100644 --- a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/SeoTitleField.cs +++ b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/SeoTitleField.cs @@ -6,6 +6,8 @@ using SeoToolkit.Umbraco.MetaFields.Core.Interfaces.SeoField; using SeoToolkit.Umbraco.MetaFields.Core.Models.SeoFieldEditors; using System.Web; +using System.Collections.Generic; +using SeoToolkit.Umbraco.MetaFields.Core.Models.SeoFieldSuggestions; namespace SeoToolkit.Umbraco.MetaFields.Core.Models.SeoField { @@ -16,6 +18,10 @@ public class SeoTitleField : ISeoField public string Alias => SeoFieldAliasConstants.Title; public string Description => "Title for the page"; public string GroupAlias => SeoFieldGroupConstants.MetaFieldsGroup; + public List Suggestions { get; } = new List + { + new SeoFieldMaxLengthSuggestion() { MaxLength = 60 } + }; public Type FieldType => typeof(string); public ISeoFieldEditor Editor => new SeoFieldFieldsEditor(new[] { "Umbraco.TextBox", "Umbraco.TextArea", "Umbraco.TinyMCE" }); diff --git a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/ViewModels/SeoSettingsFieldViewModel.cs b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/ViewModels/SeoSettingsFieldViewModel.cs index 5a45ac9a..1389b3a9 100644 --- a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/ViewModels/SeoSettingsFieldViewModel.cs +++ b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/ViewModels/SeoSettingsFieldViewModel.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using SeoToolkit.Umbraco.MetaFields.Core.Interfaces.SeoField; +using System.Collections.Generic; namespace SeoToolkit.Umbraco.MetaFields.Core.Models.SeoField.ViewModels { @@ -8,6 +9,7 @@ public class SeoSettingsFieldViewModel public string Title { get; set; } public string Description { get; set; } public string GroupAlias { get; set; } + public SeoSuggestionViewModel[] Suggestions { get; set; } public string Value { get; set; } public object UserValue { get; set; } public string EditView { get; set; } diff --git a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/ViewModels/SeoSuggestionViewModel.cs b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/ViewModels/SeoSuggestionViewModel.cs new file mode 100644 index 00000000..59aa1d62 --- /dev/null +++ b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/ViewModels/SeoSuggestionViewModel.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace SeoToolkit.Umbraco.MetaFields.Core.Models.SeoField.ViewModels +{ + public class SeoSuggestionViewModel + { + public string Alias { get; set; } + public Dictionary Config { get; set; } + } +} diff --git a/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoFieldSuggestions/SeoFieldMaxLengthSuggestion.cs b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoFieldSuggestions/SeoFieldMaxLengthSuggestion.cs new file mode 100644 index 00000000..39385a5b --- /dev/null +++ b/src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoFieldSuggestions/SeoFieldMaxLengthSuggestion.cs @@ -0,0 +1,24 @@ +using SeoToolkit.Umbraco.MetaFields.Core.Interfaces.SeoField; +using SeoToolkit.Umbraco.MetaFields.Core.Models.SeoField.ViewModels; +using System.Collections.Generic; + +namespace SeoToolkit.Umbraco.MetaFields.Core.Models.SeoFieldSuggestions +{ + public class SeoFieldMaxLengthSuggestion : ISeoFieldSuggestion + { + public string Alias => "maxLength"; + public int MaxLength { get; set; } + + public SeoSuggestionViewModel ToViewModel() + { + return new SeoSuggestionViewModel + { + Alias = Alias, + Config = new Dictionary + { + { "maxLength", MaxLength } + } + }; + } + } +} diff --git a/src/SeoToolkit.Umbraco.MetaFields.Core/SeoToolkit.Umbraco.MetaFields.Core.csproj b/src/SeoToolkit.Umbraco.MetaFields.Core/SeoToolkit.Umbraco.MetaFields.Core.csproj index ee80efe7..ea842cf0 100644 --- a/src/SeoToolkit.Umbraco.MetaFields.Core/SeoToolkit.Umbraco.MetaFields.Core.csproj +++ b/src/SeoToolkit.Umbraco.MetaFields.Core/SeoToolkit.Umbraco.MetaFields.Core.csproj @@ -12,7 +12,7 @@ https://github.com/patrickdemooij9/SeoToolkit.Umbraco https://github.com/patrickdemooij9/SeoToolkit.Umbraco https://raw.githubusercontent.com/patrickdemooij9/SeoToolkit.Umbraco/main/package/SeoToolkitIcon.png - 3.9.0 + 3.10.0-beta1 diff --git a/src/SeoToolkit.Umbraco.MetaFields/SeoToolkit.Umbraco.MetaFields.csproj b/src/SeoToolkit.Umbraco.MetaFields/SeoToolkit.Umbraco.MetaFields.csproj index 14382bbd..58e46a16 100644 --- a/src/SeoToolkit.Umbraco.MetaFields/SeoToolkit.Umbraco.MetaFields.csproj +++ b/src/SeoToolkit.Umbraco.MetaFields/SeoToolkit.Umbraco.MetaFields.csproj @@ -13,7 +13,7 @@ https://github.com/patrickdemooij9/SeoToolkit.Umbraco https://github.com/patrickdemooij9/SeoToolkit.Umbraco https://raw.githubusercontent.com/patrickdemooij9/SeoToolkit.Umbraco/main/package/SeoToolkitIcon.png - 3.9.0 + 3.10.0-beta1 App_Plugins/SeoToolkit diff --git a/src/SeoToolkit.Umbraco.MetaFields/wwwroot/MetaFields/Interface/ContentApps/SeoSettings/seoSettings.html b/src/SeoToolkit.Umbraco.MetaFields/wwwroot/MetaFields/Interface/ContentApps/SeoSettings/seoSettings.html index bd9bfd67..06f13e3b 100644 --- a/src/SeoToolkit.Umbraco.MetaFields/wwwroot/MetaFields/Interface/ContentApps/SeoSettings/seoSettings.html +++ b/src/SeoToolkit.Umbraco.MetaFields/wwwroot/MetaFields/Interface/ContentApps/SeoSettings/seoSettings.html @@ -26,6 +26,13 @@ Fallback value: {{vm.getValue(field)}} No fallback value found! +
+
+

+ Best practice: Length of the text to be lower than {{ suggestion.config.maxLength }} characters. +

+
+
diff --git a/src/SeoToolkit.Umbraco.MetaFields/wwwroot/MetaFields/css/main.css b/src/SeoToolkit.Umbraco.MetaFields/wwwroot/MetaFields/css/main.css index 33ae3b15..e4089b20 100644 --- a/src/SeoToolkit.Umbraco.MetaFields/wwwroot/MetaFields/css/main.css +++ b/src/SeoToolkit.Umbraco.MetaFields/wwwroot/MetaFields/css/main.css @@ -8,6 +8,16 @@ color: #515054; } +.field-suggestion { + font-size: 0.9em; + color: #f44336; + margin-top: 2px; +} + +.field-suggestion > .valid { + color: #4caf50; +} + .seo-content .groups { display: flex; } @@ -15,6 +25,7 @@ .seo-content .groups .properties{ width: 60%; padding-right: 60px; + flex-shrink: 0; } /* Previewers */ diff --git a/src/SeoToolkit.Umbraco.SiteAudit.Core/Checks/MissingDescriptionCheck.cs b/src/SeoToolkit.Umbraco.SiteAudit.Core/Checks/MissingDescriptionCheck.cs index fcc1fd5b..ea7d5024 100644 --- a/src/SeoToolkit.Umbraco.SiteAudit.Core/Checks/MissingDescriptionCheck.cs +++ b/src/SeoToolkit.Umbraco.SiteAudit.Core/Checks/MissingDescriptionCheck.cs @@ -11,7 +11,7 @@ public class MissingDescriptionCheck : ISiteCheck public string Name => "Missing Description Check"; public string Alias => "MissingDescriptionCheck"; public string Description => "Checks if you are missing any descriptions"; - public string ErrorMessage => "Your site has invalid descriptions!"; + public string ErrorMessage => "Your site has missing meta descriptions!"; public IEnumerable RunCheck(CrawledPageModel page, SiteAuditContext context) { if (page.Content == null) yield break; diff --git a/src/SeoToolkit.Umbraco.SiteAudit.Core/Collections/SiteAuditDisplayProvider.cs b/src/SeoToolkit.Umbraco.SiteAudit.Core/Collections/SiteAuditDisplayProvider.cs new file mode 100644 index 00000000..3cd24158 --- /dev/null +++ b/src/SeoToolkit.Umbraco.SiteAudit.Core/Collections/SiteAuditDisplayProvider.cs @@ -0,0 +1,22 @@ +using SeoToolkit.Umbraco.Common.Core.Interfaces; +using SeoToolkit.Umbraco.Common.Core.Models.ViewModels; +using System; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Models; + +namespace SeoToolkit.Umbraco.SiteAudit.Core.Collections +{ + [Weight(200)] + public class SiteAuditDisplayProvider : ISeoDisplayProvider + { + public SeoDisplayViewModel Get(IContent content) + { + return new SeoDisplayViewModel() + { + Alias = "pageChecks", + Name = "Page checks", + View = "/App_Plugins/SeoToolkit/SiteAudit/Interface/SeoDisplays/pageChecks.html" + }; + } + } +} diff --git a/src/SeoToolkit.Umbraco.SiteAudit.Core/Composers/SiteAuditComposer.cs b/src/SeoToolkit.Umbraco.SiteAudit.Core/Composers/SiteAuditComposer.cs index 56590b41..d67a2502 100644 --- a/src/SeoToolkit.Umbraco.SiteAudit.Core/Composers/SiteAuditComposer.cs +++ b/src/SeoToolkit.Umbraco.SiteAudit.Core/Composers/SiteAuditComposer.cs @@ -19,6 +19,7 @@ using SeoToolkit.Umbraco.SiteAudit.Core.Notifications; using SeoToolkit.Umbraco.SiteAudit.Core.Repositories; using SeoToolkit.Umbraco.SiteAudit.Core.Services; +using SeoToolkit.Umbraco.Common.Core.Collections; namespace SeoToolkit.Umbraco.SiteAudit.Core.Composers { @@ -45,6 +46,9 @@ public void Compose(IUmbracoBuilder builder) .Append() .Append(); + builder.WithCollectionBuilder() + .Add(); + builder.AddNotificationHandler(); builder.Services.Configure(builder.Config.GetSection("SeoToolkit:SiteAudit")); diff --git a/src/SeoToolkit.Umbraco.SiteAudit.Core/Controllers/SiteAuditPageCheckController.cs b/src/SeoToolkit.Umbraco.SiteAudit.Core/Controllers/SiteAuditPageCheckController.cs new file mode 100644 index 00000000..9225cfd3 --- /dev/null +++ b/src/SeoToolkit.Umbraco.SiteAudit.Core/Controllers/SiteAuditPageCheckController.cs @@ -0,0 +1,57 @@ +using Microsoft.AspNetCore.Mvc; +using SeoToolkit.Umbraco.SiteAudit.Core.Interfaces; +using SeoToolkit.Umbraco.SiteAudit.Core.Models.Business; +using SeoToolkit.Umbraco.SiteAudit.Core.Models.PostModels; +using SeoToolkit.Umbraco.SiteAudit.Core.Models.ViewModels; +using SeoToolkit.Umbraco.SiteAudit.Core.Services; +using System; +using System.Linq; +using System.Threading.Tasks; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.BackOffice.Controllers; +using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Extensions; + +namespace SeoToolkit.Umbraco.SiteAudit.Core.Controllers +{ + [PluginController("SeoToolkit")] + public class SiteAuditPageCheckController : UmbracoAuthorizedApiController + { + private readonly SiteAuditService _siteAuditService; + private readonly ISiteCheckService _siteCheckService; + private readonly IUmbracoContextFactory _umbracoContextFactory; + + public SiteAuditPageCheckController(SiteAuditService siteAuditService, ISiteCheckService siteCheckService, IUmbracoContextFactory umbracoContextFactory) + { + _siteAuditService = siteAuditService; + _siteCheckService = siteCheckService; + _umbracoContextFactory = umbracoContextFactory; + } + + [HttpGet] + public IActionResult GetPageChecks() + { + return new JsonResult(_siteCheckService.GetAll().Where(it => it.AllowedAsPageCheck).Select(it => new SiteAuditCheckViewModel { Id = it.Id, Name = it.Check.Name, Description = it.Check.Description }).ToArray()); + } + + [HttpPost] + public async Task RunPageChecks(RunPageCheckPostModel postModel) + { + using var ctx = _umbracoContextFactory.EnsureUmbracoContext(); + var model = new SiteAuditDto + { + Name = string.Empty, + CreatedDate = DateTime.UtcNow, + StartingUrl = new Uri(ctx.UmbracoContext.Content.GetById(postModel.ContentId).Url(mode: UrlMode.Absolute)), + SiteChecks = _siteCheckService.GetAll().Where(it => it.AllowedAsPageCheck).ToList(), + MaxPagesToCrawl = 1, + DelayBetweenRequests = 1000, + Persistent = false + }; + + var result = await _siteAuditService.StartSiteAudit(model); + return new JsonResult(new SiteAuditDetailViewModel(result)); + } + } +} diff --git a/src/SeoToolkit.Umbraco.SiteAudit.Core/Models/Business/SiteAuditDto.cs b/src/SeoToolkit.Umbraco.SiteAudit.Core/Models/Business/SiteAuditDto.cs index 55e1df1f..f10ec123 100644 --- a/src/SeoToolkit.Umbraco.SiteAudit.Core/Models/Business/SiteAuditDto.cs +++ b/src/SeoToolkit.Umbraco.SiteAudit.Core/Models/Business/SiteAuditDto.cs @@ -19,6 +19,7 @@ public class SiteAuditDto public int DelayBetweenRequests { get; set; } public List SiteChecks { get; set; } public ConcurrentQueue CrawledPages { get; set; } + public bool Persistent { get; set; } = true; public SiteAuditDto() { diff --git a/src/SeoToolkit.Umbraco.SiteAudit.Core/Models/Business/SiteCheckDto.cs b/src/SeoToolkit.Umbraco.SiteAudit.Core/Models/Business/SiteCheckDto.cs index eb48519b..51ae3188 100644 --- a/src/SeoToolkit.Umbraco.SiteAudit.Core/Models/Business/SiteCheckDto.cs +++ b/src/SeoToolkit.Umbraco.SiteAudit.Core/Models/Business/SiteCheckDto.cs @@ -5,6 +5,7 @@ namespace SeoToolkit.Umbraco.SiteAudit.Core.Models.Business public class SiteCheckDto { public int Id { get; set; } + public bool AllowedAsPageCheck { get; set; } public ISiteCheck Check { get; set; } } } diff --git a/src/SeoToolkit.Umbraco.SiteAudit.Core/Models/Config/SiteAuditCheckConfigModel.cs b/src/SeoToolkit.Umbraco.SiteAudit.Core/Models/Config/SiteAuditCheckConfigModel.cs index 418df511..bec3eb8d 100644 --- a/src/SeoToolkit.Umbraco.SiteAudit.Core/Models/Config/SiteAuditCheckConfigModel.cs +++ b/src/SeoToolkit.Umbraco.SiteAudit.Core/Models/Config/SiteAuditCheckConfigModel.cs @@ -4,5 +4,6 @@ public class SiteAuditCheckConfigModel { public string Alias { get; set; } public bool Enabled { get; set; } + public bool? AllowedAsPageCheck { get; set; } } } diff --git a/src/SeoToolkit.Umbraco.SiteAudit.Core/Models/PostModels/RunPageCheckPostModel.cs b/src/SeoToolkit.Umbraco.SiteAudit.Core/Models/PostModels/RunPageCheckPostModel.cs new file mode 100644 index 00000000..2c39321e --- /dev/null +++ b/src/SeoToolkit.Umbraco.SiteAudit.Core/Models/PostModels/RunPageCheckPostModel.cs @@ -0,0 +1,7 @@ +namespace SeoToolkit.Umbraco.SiteAudit.Core.Models.PostModels +{ + public class RunPageCheckPostModel + { + public int ContentId { get; set; } + } +} diff --git a/src/SeoToolkit.Umbraco.SiteAudit.Core/SeoToolkit.Umbraco.SiteAudit.Core.csproj b/src/SeoToolkit.Umbraco.SiteAudit.Core/SeoToolkit.Umbraco.SiteAudit.Core.csproj index c6c4c645..79e05462 100644 --- a/src/SeoToolkit.Umbraco.SiteAudit.Core/SeoToolkit.Umbraco.SiteAudit.Core.csproj +++ b/src/SeoToolkit.Umbraco.SiteAudit.Core/SeoToolkit.Umbraco.SiteAudit.Core.csproj @@ -12,7 +12,7 @@ https://github.com/patrickdemooij9/SeoToolkit.Umbraco https://github.com/patrickdemooij9/SeoToolkit.Umbraco https://raw.githubusercontent.com/patrickdemooij9/SeoToolkit.Umbraco/main/package/SeoToolkitIcon.png - 2.7.0 + 2.8.0-beta1 diff --git a/src/SeoToolkit.Umbraco.SiteAudit.Core/Services/SiteAuditService.cs b/src/SeoToolkit.Umbraco.SiteAudit.Core/Services/SiteAuditService.cs index 3628f0eb..fa72d092 100644 --- a/src/SeoToolkit.Umbraco.SiteAudit.Core/Services/SiteAuditService.cs +++ b/src/SeoToolkit.Umbraco.SiteAudit.Core/Services/SiteAuditService.cs @@ -64,8 +64,9 @@ public async Task StartSiteAudit(SiteAuditDto model) } siteCrawler.OnPageCrawlCompleted -= HandleChecks; CurrentlyRunningSiteAudits.TryRemove(siteCrawler, out _); - + Save(model); + await _eventAggregator.PublishAsync(new SiteAuditUpdatedNotification(model)); return model; @@ -85,6 +86,8 @@ public void StopSiteAudit(int siteAuditId) public SiteAuditDto Save(SiteAuditDto model) { + if (!model.Persistent) return model; + using var scope = _scopeProvider.CreateScope(); model = model.Id == 0 ? _siteAuditRepository.Add(model) : _siteAuditRepository.Update(model); scope.Complete(); @@ -129,10 +132,13 @@ private void HandleChecks(object sender, PageCrawlCompleteArgs args) }; crawledPage.Results.AddRange(auditModel.SiteChecks?.SelectMany(it => it.Check.RunCheck(args.Page, args.Context).Select(p => new PageCrawlResult(it, p))) ?? Enumerable.Empty()); - using (var scope = _scopeProvider.CreateScope()) + if (auditModel.Persistent) { - _siteAuditRepository.SaveCrawledPage(auditModel, crawledPage); - scope.Complete(); + using (var scope = _scopeProvider.CreateScope()) + { + _siteAuditRepository.SaveCrawledPage(auditModel, crawledPage); + scope.Complete(); + } } auditModel.AddPage(crawledPage); diff --git a/src/SeoToolkit.Umbraco.SiteAudit.Core/Services/SiteCheckService.cs b/src/SeoToolkit.Umbraco.SiteAudit.Core/Services/SiteCheckService.cs index e64ed6e4..e6920227 100644 --- a/src/SeoToolkit.Umbraco.SiteAudit.Core/Services/SiteCheckService.cs +++ b/src/SeoToolkit.Umbraco.SiteAudit.Core/Services/SiteCheckService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using SeoToolkit.Umbraco.Common.Core.Services.SettingsService; +using SeoToolkit.Umbraco.SiteAudit.Core.Checks; using SeoToolkit.Umbraco.SiteAudit.Core.Collections; using SeoToolkit.Umbraco.SiteAudit.Core.Interfaces; using SeoToolkit.Umbraco.SiteAudit.Core.Models.Business; @@ -58,8 +59,9 @@ private void LoadItems() registeredCheckId = _siteCheckRepository.RegisterCheck(codeCheck); } + var isBrokenLinkCheck = codeCheck is BrokenLinkCheck; //TODO: Remove this when we have a better way of handling this if (settingsCheck is null || settingsCheck.Enabled) - _items.Add(new SiteCheckDto { Id = registeredCheckId, Check = codeCheck }); + _items.Add(new SiteCheckDto { Id = registeredCheckId, Check = codeCheck, AllowedAsPageCheck = settingsCheck?.AllowedAsPageCheck ?? !isBrokenLinkCheck }); } } } diff --git a/src/SeoToolkit.Umbraco.SiteAudit/ManifestLoader.cs b/src/SeoToolkit.Umbraco.SiteAudit/ManifestLoader.cs index 202195c5..fc0a50f3 100644 --- a/src/SeoToolkit.Umbraco.SiteAudit/ManifestLoader.cs +++ b/src/SeoToolkit.Umbraco.SiteAudit/ManifestLoader.cs @@ -27,10 +27,11 @@ public void Filter(List manifests) "/App_Plugins/SeoToolkit/SiteAudit/Interface/SiteAudit/create.controller.js", "/App_Plugins/SeoToolkit/SiteAudit/Interface/SiteAudit/detail.controller.js", "/App_Plugins/SeoToolkit/SiteAudit/Interface/SiteAudit/overview.controller.js", + "/App_Plugins/SeoToolkit/SiteAudit/Interface/SeoDisplays/pageChecks.controller.js", "/App_Plugins/SeoToolkit/SiteAudit/js/siteAuditHub.js", "/App_Plugins/SeoToolkit/backoffice/SiteAudit/list.controller.js", "/App_Plugins/SeoToolkit/backoffice/SiteAudit/detail.controller.js", - "/App_Plugins/SeoToolkit/backoffice/SiteAudit/create.controller.js" + "/App_Plugins/SeoToolkit/backoffice/SiteAudit/create.controller.js", }, Stylesheets = new[] { diff --git a/src/SeoToolkit.Umbraco.SiteAudit/SeoToolkit.Umbraco.SiteAudit.csproj b/src/SeoToolkit.Umbraco.SiteAudit/SeoToolkit.Umbraco.SiteAudit.csproj index 7126637e..14afd678 100644 --- a/src/SeoToolkit.Umbraco.SiteAudit/SeoToolkit.Umbraco.SiteAudit.csproj +++ b/src/SeoToolkit.Umbraco.SiteAudit/SeoToolkit.Umbraco.SiteAudit.csproj @@ -12,7 +12,7 @@ https://github.com/patrickdemooij9/SeoToolkit.Umbraco https://github.com/patrickdemooij9/SeoToolkit.Umbraco https://raw.githubusercontent.com/patrickdemooij9/SeoToolkit.Umbraco/main/package/SeoToolkitIcon.png - 2.7.0 + 2.8.0-beta1 App_Plugins/SeoToolkit diff --git a/src/SeoToolkit.Umbraco.SiteAudit/wwwroot/SiteAudit/Interface/SeoDisplays/pageChecks.controller.js b/src/SeoToolkit.Umbraco.SiteAudit/wwwroot/SiteAudit/Interface/SeoDisplays/pageChecks.controller.js new file mode 100644 index 00000000..c53bccb3 --- /dev/null +++ b/src/SeoToolkit.Umbraco.SiteAudit/wwwroot/SiteAudit/Interface/SeoDisplays/pageChecks.controller.js @@ -0,0 +1,54 @@ +(function () { + function SiteAuditPageChecks($scope, $http, editorState, notificationsService) { + + var vm = this; + + vm.pageChecks = []; + vm.hasRan = false; + + $http.get("backoffice/SeoToolkit/SiteAuditPageCheck/GetPageChecks").then(function (response) { + if (response.status === 200) { + vm.pageChecks = response.data.map(function (check) { + return { + check, + hasError: false, + errorMessage: '' + } + }); + } + }); + + vm.startChecks = function () { + vm.saveButtonState = "busy"; + + $http.post("backoffice/SeoToolkit/SiteAuditPageCheck/RunPageChecks", { + contentId: editorState.current.id + }).then(function (response) { + const crawledPage = response.data.pagesCrawled[0]; + if (crawledPage.statusCode !== 200) { + notificationsService.error("Could not load page properly. Make sure the page can be loaded."); + return; + } + + vm.pageChecks.forEach((item) => { + const errorCheck = crawledPage.results.find((pageCheckResult) => { + return item.check.id === pageCheckResult.checkId; + }); + if (errorCheck) { + item.hasError = true; + item.errorMessage = errorCheck.message; + } else { + item.hasError = false; + item.errorMessage = ""; + } + }) + + vm.saveButtonState = "success"; + vm.hasRan = true; + console.log(response); + }); + } + } + + angular.module("umbraco").controller("SeoToolkit.SiteAuditPageChecksController", SiteAuditPageChecks); +})(); \ No newline at end of file diff --git a/src/SeoToolkit.Umbraco.SiteAudit/wwwroot/SiteAudit/Interface/SeoDisplays/pageChecks.html b/src/SeoToolkit.Umbraco.SiteAudit/wwwroot/SiteAudit/Interface/SeoDisplays/pageChecks.html new file mode 100644 index 00000000..90e950c2 --- /dev/null +++ b/src/SeoToolkit.Umbraco.SiteAudit/wwwroot/SiteAudit/Interface/SeoDisplays/pageChecks.html @@ -0,0 +1,21 @@ +
+
+

Page checks

+ Start checks +
+
+
+
+ + +
+
{{item.check.name}}
+
{{item.check.description}}
+
+
+
+ {{ item.errorMessage }} +
+
+
+
\ No newline at end of file diff --git a/src/SeoToolkit.Umbraco/SeoToolkit.Umbraco.csproj b/src/SeoToolkit.Umbraco/SeoToolkit.Umbraco.csproj index 8360c498..30a7c7ea 100644 --- a/src/SeoToolkit.Umbraco/SeoToolkit.Umbraco.csproj +++ b/src/SeoToolkit.Umbraco/SeoToolkit.Umbraco.csproj @@ -14,7 +14,7 @@ https://github.com/patrickdemooij9/SeoToolkit.Umbraco https://raw.githubusercontent.com/patrickdemooij9/SeoToolkit.Umbraco/main/package/SeoToolkitIcon.png Readme.md - 3.9.1 + 3.10.0-beta1 App_Plugins/SeoToolkit