using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using Markdig; using Markdig.Extensions.Yaml; using Markdig.Renderers; using Markdig.Syntax; using Microsoft.AspNetCore.Mvc.RazorPages; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; namespace zotanpw.Pages.blog; public class BlogModel : PageModel { public static readonly List Posts = new(); static BlogModel() { foreach (var file in Directory.EnumerateFiles("Pages/blog/posts")) Posts.Add(new BlogPost(Path.GetFileNameWithoutExtension(file))); Posts = Posts.OrderByDescending(p => p.PublishedOn).ToList(); } public void OnGet() { } public class BlogPost { public readonly string Shorthand; public string Content = ""; public DateOnly PublishedOn; public int ReadTimeMinutes; public string Title = null!; public BlogPost(string shorthand) { Shorthand = shorthand; UpdateContentAndMetadata(); } public void UpdateContentAndMetadata() { var markdownText = System.IO.File.ReadAllText($"Pages/blog/posts/{Shorthand}.md"); var parsed = ParseBlogPost(markdownText); Title = parsed.metadata.Title!; PublishedOn = parsed.metadata.Date; ReadTimeMinutes = Regex.Matches(markdownText, @"\b\w+\b").Count / 150; Content = parsed.html; } private static (PostMetadata metadata, string html) ParseBlogPost(string markdown) { var yaml = ""; var pipeline = new MarkdownPipelineBuilder().UseGenericAttributes().UseYamlFrontMatter().Build(); var writer = new StringWriter(); var renderer = new HtmlRenderer(writer); pipeline.Setup(renderer); var document = Markdown.Parse(markdown, pipeline); var yamlBlock = document.Descendants().FirstOrDefault(); if (yamlBlock != null) yaml = markdown.Substring(yamlBlock.Span.Start, yamlBlock.Span.Length); renderer.Render(document); writer.Flush(); var html = writer.ToString(); var yamlDeserializer = new DeserializerBuilder().WithNamingConvention(LowerCaseNamingConvention.Instance).Build(); var metadata = yamlDeserializer.Deserialize(yaml); return (metadata, html); } [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")] private class PostMetadata { #pragma warning disable CS0649 public string? Title; public DateOnly Date; #pragma warning restore CS0649 } } }