oliverbooth.dev/OliverBooth/Pages/Tutorials/Article.cshtml
Oliver Booth 6ec4103a3a
refactor: separate Markdig extensions from project
Also introduces .Common project to house common references and types
2024-05-05 02:18:20 +01:00

178 lines
5.8 KiB
Plaintext

@page "/tutorial/{**slug}"
@using Humanizer
@using Markdig
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using OliverBooth.Common.Data
@using OliverBooth.Common.Data.Blog
@using OliverBooth.Common.Data.Web
@using OliverBooth.Common.Services
@inject ITutorialService TutorialService
@inject MarkdownPipeline MarkdownPipeline
@model Article
@if (Model.CurrentArticle is not { } article)
{
return;
}
@{
ViewData["Post"] = article;
ViewData["Title"] = article.Title;
DateTimeOffset published = article.Published;
}
<nav style="--bs-breadcrumb-divider: '>';" aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a asp-page="/Tutorials/Index" asp-route-slug="">Tutorials</a>
</li>
@{
int? parentId = Model.CurrentArticle.Folder;
while (parentId is not null)
{
ITutorialFolder thisFolder = TutorialService.GetFolder(parentId.Value)!;
<li class="breadcrumb-item">
<a asp-page="/Tutorials/Index" asp-route-slug="@TutorialService.GetFullSlug(thisFolder)">
@thisFolder.Title
</a>
</li>
parentId = thisFolder.Parent;
}
}
<li class="breadcrumb-item actove" aria-current="page">@Model.CurrentArticle.Title</li>
</ol>
</nav>
<h1>@article.Title</h1>
<p class="text-muted">
<abbr data-bs-toggle="tooltip" data-bs-title="@published.ToString("dddd, d MMMM yyyy HH:mm")">
Published @published.Humanize()
</abbr>
@if (article.Updated is { } updated)
{
<span>&bull;</span>
<abbr data-bs-toggle="tooltip" data-bs-title="@updated.ToString("dddd, d MMMM yyyy HH:mm")">
Updated @updated.Humanize()
</abbr>
}
</p>
<hr>
<article>
@Html.Raw(TutorialService.RenderArticle(article))
</article>
@if (article.HasOtherParts)
{
<hr>
<div class="row">
<div class="col-sm-12 col-md-6">
@if (article.PreviousPart is { } previousPartId &&
TutorialService.TryGetArticle(previousPartId, out ITutorialArticle? previousPart) &&
previousPart.Visibility == Visibility.Published)
{
<small>Previous Part</small>
<p class="lead">
<a asp-page="Article" asp-route-slug="@TutorialService.GetFullSlug(previousPart)">
@previousPart.Title
</a>
</p>
}
</div>
<div class="col-sm-12 col-md-6" style="text-align: right;">
@if (article.NextPart is { } nextPartId &&
TutorialService.TryGetArticle(nextPartId, out ITutorialArticle? nextPart) &&
nextPart.Visibility == Visibility.Published)
{
<small>Next Part</small>
<p class="lead">
<a asp-page="Article" asp-route-slug="@TutorialService.GetFullSlug(nextPart)">
@nextPart.Title
</a>
</p>
}
</div>
</div>
}
<hr/>
@if (article.EnableComments)
{
<div class="giscus"></div>
@section Scripts
{
<script src="https://giscus.app/client.js"
data-repo="oliverbooth/oliverbooth.dev"
data-repo-id="MDEwOlJlcG9zaXRvcnkyNDUxODEyNDI="
data-category="Comments"
data-category-id="DIC_kwDODp0rOs4Ce_Nj"
data-mapping="pathname"
data-strict="0"
data-reactions-enabled="1"
data-emit-metadata="0"
data-input-position="bottom"
data-theme="preferred_color_scheme"
data-lang="en"
crossorigin="anonymous"
async>
</script>
}
int commentCount = TutorialService.GetLegacyCommentCount(article);
if (commentCount > 0)
{
<hr>
var nestLevelMap = new Dictionary<ILegacyComment, int>();
IReadOnlyList<ILegacyComment> legacyComments = TutorialService.GetLegacyComments(article);
var commentStack = new Stack<ILegacyComment>(legacyComments.OrderByDescending(c => c.CreatedAt));
<p class="text-center">
<strong>@("legacy comment".ToQuantity(commentCount))</strong>
</p>
<p class="text-center">
<sub>Legacy comments are comments that were posted using a commenting system that I no longer use. This exists for posterity.</sub>
</p>
while (commentStack.Count > 0)
{
ILegacyComment comment = commentStack.Pop();
foreach (ILegacyComment reply in TutorialService.GetLegacyReplies(comment).OrderByDescending(c => c.CreatedAt))
{
if (nestLevelMap.TryGetValue(comment, out int currentLevel))
{
nestLevelMap[reply] = currentLevel + 1;
}
else
{
nestLevelMap[reply] = 1;
}
commentStack.Push(reply);
}
int padding = 0;
if (nestLevelMap.TryGetValue(comment, out int nestLevel))
{
padding = 50 * nestLevel;
}
<div class="legacy-comment" style="margin-left: @(padding)px;">
<img class="blog-author-icon" src="@comment.GetAvatarUrl()" alt="@comment.Author">
@comment.Author &bull;
<abbr class="text-muted" data-bs-toggle="tooltip" data-bs-title="@comment.CreatedAt.ToString("dddd, d MMMM yyyy HH:mm")">
@comment.CreatedAt.Humanize()
</abbr>
<div class="comment">@Html.Raw(Markdown.ToHtml(comment.Body, MarkdownPipeline))</div>
</div>
}
}
}
else
{
<p class="text-center text-muted">Comments are not enabled for this post.</p>
}