From bf9705b7c20ba73e10350ad101c5abbe32eaf446 Mon Sep 17 00:00:00 2001 From: Marnix van Valen Date: Sun, 22 Nov 2020 23:23:21 +0100 Subject: [PATCH] SEO --- Models/ContentTypes/Home.cs | 7 +++- Models/SocialSharingMetadata.cs | 41 ++++++++++++++++++++ Modules/HtmlOptimizer.cs | 5 +++ Pipelines/Pages.cs | 10 +++++ Pipelines/Posts.cs | 10 +++++ Pipelines/Seo.cs | 20 ++++++++++ input/Shared/_SocialSharingMetadata.cshtml | 45 ++++++++++++++++++++++ input/_Layout.cshtml | 15 ++++++-- 8 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 Models/SocialSharingMetadata.cs create mode 100644 Pipelines/Seo.cs create mode 100644 input/Shared/_SocialSharingMetadata.cshtml diff --git a/Models/ContentTypes/Home.cs b/Models/ContentTypes/Home.cs index a474d4c..1f10d5e 100644 --- a/Models/ContentTypes/Home.cs +++ b/Models/ContentTypes/Home.cs @@ -4,7 +4,12 @@ namespace Kentico.Kontent.Statiq.Memoirs.Models { - public partial class Home + public partial class Home : IPageMetadata { + public string Url { get; } = "/"; + public string Title { get; } = "Home"; + public string Body => this.Contact; + public IEnumerable Image { get; } = Array.Empty(); + public IEnumerable Settings { get; set; } = Array.Empty(); } } \ No newline at end of file diff --git a/Models/SocialSharingMetadata.cs b/Models/SocialSharingMetadata.cs new file mode 100644 index 0000000..a4d4929 --- /dev/null +++ b/Models/SocialSharingMetadata.cs @@ -0,0 +1,41 @@ +using Kentico.Kontent.Delivery.Abstractions; +using Kentico.Kontent.Statiq.Memoirs.Models; +using System.Linq; + +namespace MemoirsTheme.Models +{ + public class SocialSharingMetadata + { + public SocialSharingMetadata(IPageMetadata site, IPageMetadata page) + { + Title = page.MetadataOgTitle.Cascade(page.MetadataMetaTitle).Cascade(page.Title); + Url = page.Url; + TwitterCard = page.MetadataTwitterCard.FirstOrDefault()?.Codename ?? "summary_large_image"; + Description = page.MetadataOgDescription + .Cascade(page.MetadataMetaDescription); + Image = page.MetadataOgImage.FirstOrDefault() ?? site?.MetadataOgImage.FirstOrDefault(); + TwitterSite = page.MetadataTwitterSite + .Cascade(site?.MetadataTwitterSite); + TwitterCreator = page.MetadataTwitterCreator + .Cascade(site?.MetadataTwitterCreator); + TwitterImage = page.MetadataTwitterImage.FirstOrDefault() ?? Image; + } + + public string Url { get; } + public string Title { get; } + public string Description { get; } + public IAsset Image { get; } + public IAsset TwitterImage { get; } + public string TwitterCreator { get; } + public string TwitterSite { get; } + public string TwitterCard { get; set; } + } + + public static class MetadataHelpers + { + public static string Cascade(this string preferred, string fallback) + { + return string.IsNullOrWhiteSpace(preferred) ? fallback : preferred; + } + } +} \ No newline at end of file diff --git a/Modules/HtmlOptimizer.cs b/Modules/HtmlOptimizer.cs index 8f6c038..741a32f 100644 --- a/Modules/HtmlOptimizer.cs +++ b/Modules/HtmlOptimizer.cs @@ -45,6 +45,11 @@ public OptimizeHtml WithSettings(Action settings) protected override async Task> ExecuteInputAsync(IDocument input, IExecutionContext context) { + if (!_enabled) + { + return (input).Yield(); // nothing to do, return original document + } + var original = await input.GetContentStringAsync(); if (string.IsNullOrWhiteSpace(original)) { diff --git a/Pipelines/Pages.cs b/Pipelines/Pages.cs index 3185ff7..ad6c020 100644 --- a/Pipelines/Pages.cs +++ b/Pipelines/Pages.cs @@ -2,6 +2,7 @@ using Kentico.Kontent.Delivery.Urls.QueryParameters; using Kentico.Kontent.Statiq.Memoirs.Models; using Kontent.Statiq; +using MemoirsTheme.Models; using MemoirsTheme.Modules; using Statiq.Common; using Statiq.Core; @@ -15,6 +16,7 @@ public class Pages : Pipeline { public Pages(IDeliveryClient deliveryClient, SiteSettings site) { + Dependencies.Add(nameof(Seo)); InputModules = new ModuleList { new Kontent(deliveryClient) @@ -40,6 +42,14 @@ public Pages(IDeliveryClient deliveryClient, SiteSettings site) { new MergeContent(new ReadFiles( KontentConfig.Get( ViewForPage))), new RenderRazor() + .WithViewData( "SEO", Config.FromDocument((doc, ctx) => + { + var home = ctx.Outputs.FromPipeline(nameof(Seo)).First().AsKontent(); + var post = doc.AsKontent(); + + return new SocialSharingMetadata(home, post); + + }) ) .WithViewData("Title", KontentConfig.Get( p => p.Title )) .WithViewData("SiteMetadata", site) .WithModel(KontentConfig.As()), diff --git a/Pipelines/Posts.cs b/Pipelines/Posts.cs index 2702fe8..690a120 100644 --- a/Pipelines/Posts.cs +++ b/Pipelines/Posts.cs @@ -2,6 +2,7 @@ using Kentico.Kontent.Delivery.Urls.QueryParameters; using Kentico.Kontent.Statiq.Memoirs.Models; using Kontent.Statiq; +using MemoirsTheme.Models; using MemoirsTheme.Modules; using Statiq.Common; using Statiq.Core; @@ -15,6 +16,7 @@ public class Posts : Pipeline { public Posts(IDeliveryClient deliveryClient, SiteSettings site) { + Dependencies.Add(nameof(Seo)); InputModules = new ModuleList{ new Kontent(deliveryClient) .OrderBy(Post.PostDateCodename, SortOrder.Descending) @@ -38,6 +40,14 @@ public Posts(IDeliveryClient deliveryClient, SiteSettings site) ProcessModules = new ModuleList { new MergeContent(new ReadFiles(patterns: "Post.cshtml") ), new RenderRazor() + .WithViewData( "SEO", Config.FromDocument((doc, ctx) => + { + var home = ctx.Outputs.FromPipeline(nameof(Seo)).First().AsKontent(); + var post = doc.AsKontent(); + + return new SocialSharingMetadata(home, post); + + }) ) .WithViewData("Title", KontentConfig.Get( p => p.Title )) .WithViewData("Author", KontentConfig.Get( p => p.Author.OfType().FirstOrDefault() )) .WithViewData("SiteMetadata", site ) diff --git a/Pipelines/Seo.cs b/Pipelines/Seo.cs new file mode 100644 index 0000000..0f11d22 --- /dev/null +++ b/Pipelines/Seo.cs @@ -0,0 +1,20 @@ +using Kentico.Kontent.Delivery.Abstractions; +using Kentico.Kontent.Delivery.Urls.QueryParameters; +using Kontent.Statiq; +using Statiq.Common; +using Statiq.Core; + +namespace MemoirsTheme.Pipelines +{ + public class Seo : Pipeline + { + public Seo(IDeliveryClient deliveryClient) + { + InputModules = new ModuleList + { + new Kontent(deliveryClient) + .WithQuery(new LimitParameter(1), new DepthParameter(1)) + }; + } + } +} \ No newline at end of file diff --git a/input/Shared/_SocialSharingMetadata.cshtml b/input/Shared/_SocialSharingMetadata.cshtml new file mode 100644 index 0000000..27a316b --- /dev/null +++ b/input/Shared/_SocialSharingMetadata.cshtml @@ -0,0 +1,45 @@ +@model MemoirsTheme.Models.SocialSharingMetadata + + + + + +@if (!string.IsNullOrWhiteSpace(@Model.TwitterCreator)) +{ + +} +@if (!string.IsNullOrWhiteSpace(@Model.TwitterCreator)) +{ + +} + +@if (Model.Image != null) +{ + // https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/abouts-cards + // summary_large_image aspect 2:1, min 300x157 max 4096x4096 + // summary aspect 1:1, min 144x144 max maximum of 4096x4096 pixels + + var width = 800; + var height = 800; + + + + + + +} + +@if (Model.TwitterImage != null) +{ + // https://ogp.me/ + // summary_large_image aspect 2:1, min 300x157 max 4096x4096 + // summary aspect 1:1, min 144x144 max maximum of 4096x4096 pixels + var (height, width) = Model.TwitterCard switch + { + "summary_large_image" => (height: 500, width: 1000), + _ => (height: 500, width: 500) + }; + + + +} \ No newline at end of file diff --git a/input/_Layout.cshtml b/input/_Layout.cshtml index 15c5262..bca617d 100644 --- a/input/_Layout.cshtml +++ b/input/_Layout.cshtml @@ -1,8 +1,10 @@ @using Kentico.Kontent.Statiq.Memoirs.Models +@using MemoirsTheme.Models @{ var site = ViewBag.SiteMetadata as SiteSettings; - var author = ViewBag.Author as Author; - var name = author?.Name ?? "John Doe"; - var isHome = ViewBag.IsHome ?? false; } + var isHome = ViewBag.IsHome ?? false; + var seo = ViewData["SEO"] as SocialSharingMetadata; + +} @@ -14,6 +16,13 @@ @ViewBag.Title | @site.Name @RenderSection("seo", required: false) + @if (!IsSectionDefined("seo")) + { + @if (seo != null) + { + + } + }