This commit is contained in:
Tommy Parnell
2022-03-04 15:06:22 -05:00
parent 9952ef6aa7
commit 83eb1bc565
15 changed files with 274 additions and 80 deletions

View File

@@ -66,7 +66,19 @@ namespace TerribleDev.Blog.Web.Controllers
this.StatusCode(404);
return View(nameof(FourOhFour));
}
return View(model: currentPost);
return View("Post", model: new PostViewModel() { Post = currentPost, IsAmp = false });
}
[Route("{postUrl}/amp")]
[OutputCache(Duration = 31536000, VaryByParam = "postUrl")]
[ResponseCache(Duration = 900)]
public IActionResult PostAmp(string postUrl)
{
if(!postCache.UrlToPost.TryGetValue(postUrl, out var currentPost))
{
this.StatusCode(404);
return View(nameof(FourOhFour));
}
return View("Post", model: new PostViewModel() { Post = currentPost, IsAmp = true });
}
[Route("/Error")]
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]

View File

@@ -65,6 +65,7 @@ namespace TerribleDev.Blog.Web.Controllers
Urls = postCache.PostsAsLists.Select(a => new SiteMapItem() { LastModified = DateTime.UtcNow, Location = a.CanonicalUrl }).ToList()
};
sitemap.Urls.AddRange(sitewideLinks);
sitemap.Urls.AddRange(postCache.PostsAsLists.Select(a => new SiteMapItem() { LastModified = DateTime.UtcNow, Location = a.AMPUrl }).ToList());
ser.Serialize(this.Response.Body, sitemap);
}
}

View File

@@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Hosting;
using System.Diagnostics;
using System.Collections.Concurrent;
using Schema.NET;
using System.Text.RegularExpressions;
namespace TerribleDev.Blog.Web
{
@@ -69,6 +70,7 @@ namespace TerribleDev.Blog.Web
var postSettings = ParseYaml(ymlRaw);
var resolvedUrl = !string.IsNullOrWhiteSpace(postSettings.permalink) ? postSettings.permalink : fileName.Split('.')[0].Replace(' ', '-').WithoutSpecialCharacters();
var canonicalUrl = $"https://blog.terrible.dev/{resolvedUrl}/";
var ampUrl = $"https://blog.terrible.dev/{resolvedUrl}/amp/";
return new Post()
{
PublishDate = postSettings.date.ToUniversalTime(),
@@ -77,6 +79,7 @@ namespace TerribleDev.Blog.Web
Title = postSettings.title,
RelativeUrl = $"/{resolvedUrl}/",
CanonicalUrl = canonicalUrl,
AMPUrl = ampUrl,
UrlWithoutPath = resolvedUrl,
Content = new Lazy<IPostContent>(() =>
{
@@ -107,8 +110,12 @@ namespace TerribleDev.Blog.Web
},
},
};
// regex remove picture and source tags
var regex = new Regex(@"<source[^>]*>|</source>|<picture[^>]*>|</picture>", RegexOptions.IgnoreCase);
var ampContent = regex.Replace(postContent, "");
return new PostContent()
{
AmpContent = new HtmlString(ampContent),
Content = new HtmlString(postContent),
Images = postImages,
ContentPlain = postContentPlain,

View File

@@ -9,6 +9,7 @@ namespace TerribleDev.Blog.Web.Models
{
public interface IPost
{
string AMPUrl { get; set; }
string CanonicalUrl { get; set; }
string UrlWithoutPath { get; set; }
string RelativeUrl { get; set; }

View File

@@ -7,6 +7,7 @@ namespace TerribleDev.Blog.Web.Models
{
public interface IPostContent
{
public HtmlString AmpContent { get; set; }
HtmlString Content { get; set; }
HtmlString Summary { get; set; }
string ContentPlain { get; set; }

View File

@@ -9,6 +9,7 @@ namespace TerribleDev.Blog.Web.Models
[DebuggerDisplay("{Title}")]
public class Post : IPost
{
public string AMPUrl { get; set; }
public string CanonicalUrl { get; set; }
public string UrlWithoutPath { get; set; }
public string RelativeUrl { get; set; }

View File

@@ -8,6 +8,7 @@ namespace TerribleDev.Blog.Web.Models
public class PostContent : IPostContent
{
public HtmlString AmpContent { get; set; }
public HtmlString Content { get; set; }
public HtmlString Summary { get; set; }
public string ContentPlain { get; set; }

View File

@@ -0,0 +1,9 @@
namespace TerribleDev.Blog.Web.Models
{
public class PostViewModel
{
public IPost Post { get; set; }
public bool IsAmp { get; set; } = false;
}
}

View File

@@ -4,7 +4,9 @@ using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace TerribleDev.Blog.Web.Taghelpers
@@ -29,25 +31,29 @@ namespace TerribleDev.Blog.Web.Taghelpers
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var path = Href;
var paths = Href.Split(',');
// Get the value from the cache, or compute the value and add it to the cache
var fileContent = await Cache.GetOrCreateAsync("InlineStyleTagHelper-" + path, async entry =>
var fileContent = await Cache.GetOrCreateAsync("InlineStyleTagHelper-" + paths, async entry =>
{
var fileProvider = HostingEnvironment.WebRootFileProvider;
if(HostingEnvironment.IsDevelopment())
{
var changeToken = fileProvider.Watch(path);
entry.AddExpirationToken(changeToken);
}
var result = paths.Select(async path => {
if(HostingEnvironment.IsDevelopment())
{
var changeToken = fileProvider.Watch(path);
entry.AddExpirationToken(changeToken);
}
entry.SetPriority(CacheItemPriority.NeverRemove);
entry.SetPriority(CacheItemPriority.NeverRemove);
var file = fileProvider.GetFileInfo(path);
if (file == null || !file.Exists)
return null;
var file = fileProvider.GetFileInfo(path);
if (file == null || !file.Exists)
return null;
return await ReadFileContent(file);
return await ReadFileContent(file);
});
var allFinished = await Task.WhenAll(result);
return string.Join("\n", allFinished);
});
if (fileContent == null)

View File

@@ -0,0 +1,79 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace TerribleDev.Blog.Web.Taghelpers
{
[HtmlTargetElement("inline-script")]
public class InlineScriptTagHelper : TagHelper
{
[HtmlAttributeName("src")]
public string Src { get; set; }
private IWebHostEnvironment HostingEnvironment { get; }
private IMemoryCache Cache { get; }
public InlineScriptTagHelper(IWebHostEnvironment hostingEnvironment, IMemoryCache cache)
{
HostingEnvironment = hostingEnvironment;
Cache = cache;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var paths = Src.Split(',');
// Get the value from the cache, or compute the value and add it to the cache
var fileContent = await Cache.GetOrCreateAsync("InlineScriptTagHelper-" + paths, async entry =>
{
var fileProvider = HostingEnvironment.WebRootFileProvider;
var result = paths.Select(async path => {
if(HostingEnvironment.IsDevelopment())
{
var changeToken = fileProvider.Watch(path);
entry.AddExpirationToken(changeToken);
}
entry.SetPriority(CacheItemPriority.NeverRemove);
var file = fileProvider.GetFileInfo(path);
if (file == null || !file.Exists)
return null;
return await ReadFileContent(file);
});
var allFinished = await Task.WhenAll(result);
return string.Join("\n", allFinished);
});
if (fileContent == null)
{
output.SuppressOutput();
return;
}
output.TagName = "script";
output.Attributes.RemoveAll("href");
output.Content.AppendHtml(fileContent);
}
private static async Task<string> ReadFileContent(IFileInfo file)
{
using (var stream = file.CreateReadStream())
using (var textReader = new StreamReader(stream))
{
return await textReader.ReadToEndAsync();
}
}
}
}

View File

@@ -1,40 +1,43 @@
@inject BlogConfiguration config
@model IPost
@model PostViewModel
@{
ViewData["Title"] = @Model.Title;
ViewData["Title"] = Model.Post.Title;
ViewData["amp"] = Model.IsAmp;
}
<cache vary-by-route="postUrl">
@Html.DisplayForModel()
@Html.DisplayFor(m => m.Post, "Post")
</cache>
@section Head {
<meta name="description" content="@Model.Content.Value.SummaryPlainShort" />
<meta name="description" content="@Model.Post.Content.Value.SummaryPlainShort" />
<meta property="og:type" content="blog">
<meta property="og:title" content="@Model.Title">
<meta property="og:url" content="@Model.CanonicalUrl">
<meta property="og:title" content="@Model.Post.Title">
<meta property="og:url" content="@Model.Post.CanonicalUrl">
<meta property="og:site_name" content="@config.Title">
<meta property="og:description" content="@Model.Content.Value.SummaryPlainShort">
<meta property="og:updated_time" content="@Model.PublishDate.ToString("O")">
<meta property="og:description" content="@Model.Post.Content.Value.SummaryPlainShort">
<meta property="og:updated_time" content="@Model.Post.PublishDate.ToString("O")">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="@Model.Title">
<meta name="twitter:description" content="@Model.Content.Value.SummaryPlainShort">
<meta name="twitter:title" content="@Model.Post.Title">
<meta name="twitter:description" content="@Model.Post.Content.Value.SummaryPlainShort">
<meta name="twitter:site" content="@@TerribleDev">
<meta name="twitter:creator" content="@@TerribleDev">
<link rel="canonical" href="@Model.CanonicalUrl" />
@foreach(var image in Model.Content.Value.Images.Take(6))
<link rel="canonical" href="@Model.Post.CanonicalUrl" />
<link rel="amphtml" href="@Model.Post.AMPUrl">
@foreach(var image in Model.Post.Content.Value.Images.Take(6))
{
<meta property="og:image" content="@image">
}
@if(Model.Content.Value.Images.Count > 0)
@if(Model.Post.Content.Value.Images.Count > 0)
{
<meta name="twitter:image" content="@(Model.Content.Value.Images[0])">
<meta name="twitter:image" content="@(Model.Post.Content.Value.Images[0])">
}
<meta property="og:image" content="https://www.gravatar.com/avatar/333e3cea32cd17ff2007d131df336061?s=640" />
<script type="application/ld+json">
@Html.Raw(Model.Content.Value.JsonLDString)
@Html.Raw(Model.Post.Content.Value.JsonLDString)
</script>
<script type="application/ld+json">
@Html.Raw(Model.Content.Value.JsonLDBreadcrumbString)
@Html.Raw(Model.Post.Content.Value.JsonLDBreadcrumbString)
</script>
}

View File

@@ -1,9 +1,18 @@
@model IPost
@{
var amp = ViewData["amp"] as bool? ?? false;
}
<article itemprop="blogPost">
<h1 itemprop="headline" class="headline">@Model.Title</h1>
<time class="headlineSubtext" itemprop="datePublished" content="@Model.PublishDate.ToString()">@Model.PublishDate.ToString("D")</time>
@Model.Content.Value.Content
@if(amp)
{
@Model.Content.Value.AmpContent
}
else
{
@Model.Content.Value.Content
}
@if (Model.tags.Count > 0)
{
<div>

View File

@@ -1,17 +1,36 @@
@{
Layout = null;
var amp = ViewData["amp"] as bool? ?? false;
}
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'UA-48128396-1');
document.addEventListener('DOMContentLoaded', function () {
var script = document.createElement('script');
script.src = 'https://www.googletagmanager.com/gtag/js?id=UA-48128396-1';
script.async = true
document.body.appendChild(script);
});
</script>
@if(amp)
{
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'UA-48128396-1');
document.addEventListener('DOMContentLoaded', function () {
var script = document.createElement('script');
script.src = 'https://www.googletagmanager.com/gtag/js?id=UA-48128396-1';
script.async = true
document.body.appendChild(script);
});
</script>
}
else
{
<amp-analytics type="gtag" data-credentials="include">
<script type="application/json">
{
"vars" : {
"gtag_id": "UA-48128396-1",
"config" : {
"UA-48128396-1: { "groups": "default" }
}
}
}
</script>
</amp-analytics>
}

View File

@@ -1,9 +1,20 @@
<nav class="navBar hide" id="navBar">
@{
var amp = ViewData["amp"] as bool?;
}
<nav class="navBar hide" id="navBar">
<div class="navContent">
<picture class="navHero">
@if(amp == true)
{
<img src="/content/tommyAvatar4.jpg" alt="An image of TerribleDev" class="round navHero" />
}
else
{
<picture class="navHero">
<source srcset="" type="image/webp" alt="An image of TerribleDev" data-src="/content/tommyAvatar4.jpg.webp" class="lazy round" />
<img src="" alt="An image of TerribleDev" data-src="/content/tommyAvatar4.jpg" class="lazy round" />
</picture>
}
<span>Tommy "Terrible Dev" Parnell</span>
<ul class="sidebarBtns">
<li><a href="/" class="link-unstyled">Home</a></li>

View File

@@ -1,43 +1,67 @@
@inject BlogConfiguration config
@{
var amp = ViewData["amp"] as bool? ?? false;
var htmlTag = amp ? "amp" : "";
var boilerplate = @"<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>";
}
<!DOCTYPE html>
<html lang="en">
<html lang="en" @htmlTag>
<head>
<meta charset="utf-8" />
<environment names="Production">
<partial name="Gtm" />
</environment>
<meta charset="utf-8" />
<meta name="author" content="Tommy &quot;TerribleDev&quot; Parnell" />
<meta name="theme-color" content="#4A4A4A" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="alternate" type="application/atom+xml" async title="RSS" href="/rss.xml">
<link rel="manifest" href="~/manifest.json" async asp-append-version="true">
<link asp-append-version="true" rel="icon" async href="~/favicon.ico" />
<link rel="alternate" type="application/atom+xml" title="RSS" href="/rss.xml">
<link rel="manifest" href="~/manifest.json" asp-append-version="true">
<link asp-append-version="true" rel="icon" href="~/favicon.ico" />
<title>@ViewData["Title"] - @config.Title</title>
<environment names="Development">
<link asp-append-version="true" rel="stylesheet" href="~/css/site.css" />
</environment>
<environment names="Production">
<link asp-append-version="true" rel="stylesheet" href="~/css/site.min.css" />
</environment>
<environment names="Development">
@if(amp)
{
@Html.Raw(boilerplate);
<inline-style amp-custom href="css/site.css,css/site.desktop.css,css/site.mobile.css"></inline-style>
}
else
{
<environment names="Development">
<link asp-append-version="true" rel="stylesheet" href="~/css/site.css" />
</environment>
<environment names="Production">
<link asp-append-version="true" rel="stylesheet" href="~/css/site.min.css" />
</environment>
<environment names="Development">
<link asp-append-version="true" media="screen and (min-width: 769px)" rel="stylesheet" href="~/css/site.desktop.css" />
</environment>
<environment names="Production">
<link asp-append-version="true" media="screen and (min-width: 769px)" rel="stylesheet" href="~/css/site.desktop.min.css" />
</environment>
<environment names="Development">
<link asp-append-version="true" media="screen and (max-width: 768px)" rel="stylesheet" href="~/css/site.mobile.css" />
</environment>
<environment names="Production">
<link asp-append-version="true" media="screen and (max-width: 768px)" rel="stylesheet" href="~/css/site.mobile.min.css" />
</environment>
<environment names="Development">
<link asp-append-version="true" rel="preload" as="script" href="~/js/swi.js" />
</environment>
<environment names="Production">
<link asp-append-version="true" rel="preload" as="script" href="~/js/site.min.js" />
</environment>
</environment>
<environment names="Production">
<link asp-append-version="true" media="screen and (min-width: 769px)" rel="stylesheet" href="~/css/site.desktop.min.css" />
</environment>
<environment names="Development">
<link asp-append-version="true" media="screen and (max-width: 768px)" rel="stylesheet" href="~/css/site.mobile.css" />
</environment>
<environment names="Production">
<link asp-append-version="true" media="screen and (max-width: 768px)" rel="stylesheet" href="~/css/site.mobile.min.css" />
</environment>
<environment names="Development">
<link asp-append-version="true" rel="preload" as="script" href="~/js/swi.js" />
</environment>
<environment names="Production">
<link asp-append-version="true" rel="preload" as="script" href="~/js/site.min.js" />
</environment>
}
@if(amp)
{
Html.Raw("<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>");
<script async src="https://cdn.ampproject.org/v0.js"></script>
}
@RenderSection("Head", false)
</head>
<body>
@@ -53,12 +77,22 @@
</main>
</div>
</div>
@RenderSection("Scripts", required: false)
<environment names="Development">
<script asp-append-version="true" src="~/js/swi.js" async></script>
</environment>
<environment names="Production">
<script asp-append-version="true" src="~/js/site.min.js" async></script>
</environment>
@if(amp)
{
<amp-script>
<inline-script src="js/swi.js"></inline-script>
</amp-script>
}
else
{
@RenderSection("Scripts", required: false)
<environment names="Development">
<script asp-append-version="true" src="~/js/swi.js" async></script>
</environment>
<environment names="Production">
<script asp-append-version="true" src="~/js/site.min.js" async></script>
</environment>
}
</body>
</html>