@@ -14,7 +14,7 @@ namespace TerribleDev.Blog.Web.Factories
|
||||
public static PostCache ProjectPostCache(IEnumerable<IPost> rawPosts)
|
||||
{
|
||||
var orderedPosts = rawPosts.OrderByDescending(a => a.PublishDate);
|
||||
var posts = new List<IPost>();
|
||||
var posts = new List<IPost>(orderedPosts);
|
||||
var urlToPosts = new Dictionary<string, IPost>();
|
||||
var tagsToPost = new Dictionary<string, IList<IPost>>();
|
||||
var postsByPage = new Dictionary<int, IList<IPost>>();
|
||||
|
||||
@@ -17,25 +17,21 @@ namespace TerribleDev.Blog.Web
|
||||
{
|
||||
public class BlogFactory
|
||||
{
|
||||
public IEnumerable<IPost> GetAllPosts(string domain)
|
||||
public async Task<IEnumerable<IPost>> GetAllPostsAsync(string domain)
|
||||
{
|
||||
// why didn't I use f# I'd have a pipe operator by now
|
||||
var posts = GetPosts();
|
||||
var list = new ConcurrentBag<IPost>();
|
||||
Parallel.ForEach(posts, post =>
|
||||
{
|
||||
var (text, fileInfo) = GetFileText(post);
|
||||
list.Add(ParsePost(text, fileInfo.Name, domain));
|
||||
});
|
||||
return list;
|
||||
return await Task.WhenAll(posts.Select(async (post) => {
|
||||
var (text, fileInfo) = await GetFileText(post);
|
||||
return ParsePost(text, fileInfo.Name, domain);
|
||||
}));
|
||||
}
|
||||
|
||||
private static (string text, FileInfo fileInfo) GetFileText(string filePath)
|
||||
private static async Task<(string text, FileInfo fileInfo)> GetFileText(string filePath)
|
||||
{
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
var text = File.ReadAllText(fileInfo.FullName);
|
||||
var text = await File.ReadAllTextAsync(fileInfo.FullName);
|
||||
return (text, fileInfo);
|
||||
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetPosts() => Directory.EnumerateFiles(Path.Combine(Directory.GetCurrentDirectory(), "Posts"), "*.md", SearchOption.TopDirectoryOnly);
|
||||
@@ -46,13 +42,7 @@ namespace TerribleDev.Blog.Web
|
||||
return serializer.Deserialize<PostSettings>(ymlText);
|
||||
|
||||
}
|
||||
public IPost ParsePost(string postText, string fileName, string domain)
|
||||
{
|
||||
var splitFile = postText.Split("---");
|
||||
var ymlRaw = splitFile[0];
|
||||
var markdownText = string.Join("", splitFile.Skip(1));
|
||||
var postSettings = ParseYaml(ymlRaw);
|
||||
var resolvedUrl = !string.IsNullOrWhiteSpace(postSettings.permalink) ? postSettings.permalink : fileName.Split('.')[0].Replace(' ', '-').WithoutSpecialCharacters();
|
||||
public (string postContent, string postContentPlain, string summary, string postSummaryPlain, IList<string> postImages) ResolveContentForPost(string markdownText, string fileName, string resolvedUrl, string domain) {
|
||||
List<string> postImages = new List<string>();
|
||||
var pipeline = new MarkdownPipelineBuilder()
|
||||
.Use(new AbsoluteLinkConverter(resolvedUrl, domain))
|
||||
@@ -67,6 +57,15 @@ namespace TerribleDev.Blog.Web
|
||||
|
||||
var summary = postContent.Split("<!-- more -->")[0];
|
||||
var postSummaryPlain = postContentPlain.Split("<!-- more -->")[0];
|
||||
return (postContent, postContentPlain, summary, postSummaryPlain, postImages);
|
||||
}
|
||||
public IPost ParsePost(string postText, string fileName, string domain)
|
||||
{
|
||||
var splitFile = postText.Split("---");
|
||||
var ymlRaw = splitFile[0];
|
||||
var markdownText = string.Join("", splitFile.Skip(1));
|
||||
var postSettings = ParseYaml(ymlRaw);
|
||||
var resolvedUrl = !string.IsNullOrWhiteSpace(postSettings.permalink) ? postSettings.permalink : fileName.Split('.')[0].Replace(' ', '-').WithoutSpecialCharacters();
|
||||
|
||||
return new Post()
|
||||
{
|
||||
@@ -76,12 +75,17 @@ namespace TerribleDev.Blog.Web
|
||||
RelativeUrl = $"/{resolvedUrl}/",
|
||||
CanonicalUrl = $"https://blog.terrible.dev/{resolvedUrl}/",
|
||||
UrlWithoutPath = resolvedUrl,
|
||||
Content = new HtmlString(postContent),
|
||||
Summary = new HtmlString(summary),
|
||||
SummaryPlain = postSummaryPlain,
|
||||
SummaryPlainShort = (postContentPlain.Length <= 147 ? postContentPlain : postContentPlain.Substring(0, 146)) + "...",
|
||||
ContentPlain = postContentPlain,
|
||||
Images = postImages.Distinct().ToList()
|
||||
Content = new Lazy<IPostContent>(() => {
|
||||
(string postContent, string postContentPlain, string summary, string postSummaryPlain, IList<string> postImages) = ResolveContentForPost(markdownText, fileName, resolvedUrl, domain);
|
||||
return new PostContent() {
|
||||
Content = new HtmlString(postContent),
|
||||
Images = postImages,
|
||||
ContentPlain = postContent,
|
||||
Summary = new HtmlString(summary),
|
||||
SummaryPlain = postSummaryPlain,
|
||||
SummaryPlainShort = (postContentPlain.Length <= 147 ? postContentPlain : postContentPlain.Substring(0, 146)) + "..."
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,9 @@ namespace TerribleDev.Blog.Web.Models
|
||||
string UrlWithoutPath { get; set; }
|
||||
string RelativeUrl { get; set; }
|
||||
string Title { get; set; }
|
||||
HtmlString Summary { get; set; }
|
||||
DateTime PublishDate { get; set; }
|
||||
HtmlString Content { get; set; }
|
||||
string ContentPlain { get; set; }
|
||||
string SummaryPlain { get; set; }
|
||||
string SummaryPlainShort { get; set; }
|
||||
IList<string> tags { get; set; }
|
||||
IList<string> Images { get; set;}
|
||||
Lazy<IPostContent> Content { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
16
src/TerribleDev.Blog.Web/Models/IPostContent.cs
Normal file
16
src/TerribleDev.Blog.Web/Models/IPostContent.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
|
||||
namespace TerribleDev.Blog.Web.Models
|
||||
{
|
||||
public interface IPostContent
|
||||
{
|
||||
HtmlString Content { get; set; }
|
||||
HtmlString Summary { get; set; }
|
||||
string ContentPlain { get; set; }
|
||||
string SummaryPlain { get; set; }
|
||||
string SummaryPlainShort { get; set; }
|
||||
IList<string> Images { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,7 @@ namespace TerribleDev.Blog.Web.Models
|
||||
public string RelativeUrl { get; set; }
|
||||
public string Title { get; set; }
|
||||
public DateTime PublishDate { get; set; }
|
||||
public HtmlString Content { get; set; }
|
||||
public HtmlString Summary { get; set; }
|
||||
public string ContentPlain { get; set; }
|
||||
public string SummaryPlain { get; set; }
|
||||
public string SummaryPlainShort { get; set; }
|
||||
public IList<string> tags { get; set; }
|
||||
public IList<string> Images { get; set; }
|
||||
public Lazy<IPostContent> Content { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
17
src/TerribleDev.Blog.Web/Models/PostContent.cs
Normal file
17
src/TerribleDev.Blog.Web/Models/PostContent.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
|
||||
namespace TerribleDev.Blog.Web.Models
|
||||
{
|
||||
|
||||
public class PostContent : IPostContent
|
||||
{
|
||||
public HtmlString Content { get; set; }
|
||||
public HtmlString Summary { get; set; }
|
||||
public string ContentPlain { get; set; }
|
||||
public string SummaryPlain { get; set; }
|
||||
public string SummaryPlainShort { get; set; }
|
||||
public IList<string> Images { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -39,8 +39,16 @@ namespace TerribleDev.Blog.Web
|
||||
services.AddSingleton(getBlog());
|
||||
}
|
||||
services.AddSingleton((i) => {
|
||||
var posts = new BlogFactory().GetAllPosts(Env.IsDevelopment() ? "https://localhost:5001": "https://blog.terrible.dev");
|
||||
return BlogCacheFactory.ProjectPostCache(posts);
|
||||
var posts = new BlogFactory().GetAllPostsAsync(Env.IsDevelopment() ? "https://localhost:5001": "https://blog.terrible.dev").Result;
|
||||
var postCache = BlogCacheFactory.ProjectPostCache(posts);
|
||||
if(Env.IsProduction()) {
|
||||
foreach(var post in postCache.PostsAsLists)
|
||||
{
|
||||
// if we are in production turn off lazy loading
|
||||
var value = post.Content.Value;
|
||||
}
|
||||
}
|
||||
return postCache;
|
||||
});
|
||||
services.AddApplicationInsightsTelemetry();
|
||||
var controllerBuilder = services.AddControllersWithViews();
|
||||
|
||||
@@ -9,26 +9,26 @@
|
||||
</cache>
|
||||
|
||||
@section Head {
|
||||
<meta name="description" content="@Model.SummaryPlainShort" />
|
||||
<meta name="description" content="@Model.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:site_name" content="@config.Title">
|
||||
<meta property="og:description" content="@Model.SummaryPlainShort">
|
||||
<meta property="og:description" content="@Model.Content.Value.SummaryPlainShort">
|
||||
<meta property="og:updated_time" content="@Model.PublishDate.ToString("O")">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="@Model.Title">
|
||||
<meta name="twitter:description" content="@Model.SummaryPlainShort">
|
||||
<meta name="twitter:description" content="@Model.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.Images.Take(6))
|
||||
@foreach(var image in Model.Content.Value.Images.Take(6))
|
||||
{
|
||||
<meta property="og:image" content="@image">
|
||||
}
|
||||
@if(Model.Images.Count > 0)
|
||||
@if(Model.Content.Value.Images.Count > 0)
|
||||
{
|
||||
<meta name="twitter:image" content="@(Model.Images[0])">
|
||||
<meta name="twitter:image" content="@(Model.Content.Value.Images[0])">
|
||||
}
|
||||
<meta property="og:image" content="https://www.gravatar.com/avatar/333e3cea32cd17ff2007d131df336061?s=640" />
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<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
|
||||
@Model.Content.Value.Content
|
||||
@if (Model.tags.Count > 0)
|
||||
{
|
||||
<div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<h3 itemprop="headline" class="headline"><a href="@Model.RelativeUrl" class="link-unstyled">@Model.Title</a></h3>
|
||||
<time class="headlineSubtext" itemprop="datePublished" content="@Model.PublishDate.ToString()">@Model.PublishDate.ToString("D")</time>
|
||||
<div itemprop="articleBody">
|
||||
@Model.Summary
|
||||
@Model.Content.Value.Summary
|
||||
</div>
|
||||
<a href="@Model.RelativeUrl">Continue Reading </a>
|
||||
</article>
|
||||
|
||||
Reference in New Issue
Block a user