~#;lt=s^6_OccmRd>o{*=>)KS=lM
zZ!)iG|8G0-9s3VLm`bsa6e
ze*TlRxAjXtm^F8V`M1%s5d@tYS>&+_ga#xKGb|!oUBx3uc@mj1%=MaH4GR0tPBG_&
z9OZE;->dO@`Q)nr<%dHAsEZRKl
zedN6+3+uGHejJp;Q==pskSAcRcyh@6mjm2z-uG;s%dM-u0*u##7OxI7wwyCGpS?4U
zBFAr(%GBv5j$jS@@t@iI8?ZqE36I^4t+P^J9D^ELbS5KMtZ
z{Qn#JnSd$15nJ$ggkF%I4yUQC+BjDF^}AtB7w348EL>7#sAsLWs}ndp8^DsAcOIL9
zTOO!!0!k2`9BLk25)NeZp7ev>I1Mn={cWI3Yhx2Q#DnAo4IphoV~R^c0x&nw*MoIV
zPthX?{6{u}sMS(MxD*dmd5rU(YazQE59b|TsB5Tm)I4a!VaN@HYOR)DwH1U5y(E)z
zQqQU*B%MwtRQ$%x&;1p%ANmc|PkoFJZ%<-uq%PX&C!c-7ypis=eP+FCeuv+B@h#{4
zGx1m0PjS~FJt}3mdt4c!lel`1;4W|03kcZRG+DzkTy|7-F~eDsV2Tx!73dM0H0CTh
zl)F-YUkE1zEzEW(;JXc|KR5{ox%YTh{$%F$a36JP6Nb<0%#NbSh$dMYF-{
z1_x(Vx)}fs?5_|!5xBTWiiIQHG<%)*e=45Fhjw_tlnmlixq;mUdC$R8v#j(
zhQ$9YR-o%i5Uc`S?6EC51!bTRK=Xkyb<18FkCKnS2;o*qlij1YA@-nRpq#OMTX&RbL<^2q@0qja!uIvI;j$6>~k@IMwD42=8$$!+R^@5o6HX(*n~
+
+
+ chromium
+
+ false
+ msedge
+
+
+
\ No newline at end of file
diff --git a/test/Blog.Tests/Blog.Tests.csproj b/test/Blog.Tests/Blog.Tests.csproj
index 636746e..61db240 100644
--- a/test/Blog.Tests/Blog.Tests.csproj
+++ b/test/Blog.Tests/Blog.Tests.csproj
@@ -1,4 +1,4 @@
-
+
net8.0
@@ -11,6 +11,12 @@
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
@@ -19,6 +25,22 @@
+
+ true
+ ./TestResults/coverage/
+ cobertura
+ [Blog]*
+ **/Blog/Program.cs
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Blog.Tests/EndToEnd/BlogHostFactory.cs b/test/Blog.Tests/EndToEnd/BlogHostFactory.cs
new file mode 100644
index 0000000..c138951
--- /dev/null
+++ b/test/Blog.Tests/EndToEnd/BlogHostFactory.cs
@@ -0,0 +1,62 @@
+using Microsoft.AspNetCore.Hosting.Server.Features;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Logging;
+
+namespace Blog.Tests.EndToEnd;
+
+public class BlogHostFactory : WebApplicationFactory where TProgram : class
+{
+ private IHost? _host;
+
+ private void EnsureServer()
+ {
+ if (_host is null)
+ {
+ using var _ = CreateDefaultClient();
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ _host?.Dispose();
+ }
+
+ public string ServerAddress
+ {
+ get
+ {
+ EnsureServer();
+ return ClientOptions.BaseAddress.ToString();
+ }
+ }
+
+ protected override IHost CreateHost(IHostBuilder builder)
+ {
+ var testHost = builder
+ .ConfigureWebHost(
+ webHostBuilder => webHostBuilder.ConfigureLogging(
+ config => config.ClearProviders()
+ )
+ )
+ .Build();
+
+ builder.ConfigureWebHost(
+ webHostBuilder => webHostBuilder.UseKestrel(
+ o => o.Listen(IPAddress.Loopback, 0)
+ )
+ );
+
+ _host = builder.Build();
+ _host.Start();
+
+ var server = _host.Services.GetRequiredService();
+ var addresses = server.Features.GetRequiredFeature();
+
+ ClientOptions.BaseAddress = addresses.Addresses
+ .Select(x => new Uri(x))
+ .Last();
+
+ testHost.Start();
+ return testHost;
+ }
+}
\ No newline at end of file
diff --git a/test/Blog.Tests/EndToEnd/BlogTest.cs b/test/Blog.Tests/EndToEnd/BlogTest.cs
new file mode 100644
index 0000000..27ec44a
--- /dev/null
+++ b/test/Blog.Tests/EndToEnd/BlogTest.cs
@@ -0,0 +1,17 @@
+using Microsoft.AspNetCore.Hosting.Server.Features;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Blog.Tests.EndToEnd;
+
+[TestFixture]
+public class BlogTest : PageTest
+{
+ private readonly BlogHostFactory _factory = new();
+
+ public override BrowserNewContextOptions ContextOptions()
+ {
+ var options = base.ContextOptions();
+ options.BaseURL = _factory.ServerAddress;
+ return options;
+ }
+}
\ No newline at end of file
diff --git a/test/Blog.Tests/EndToEnd/HomeTests.cs b/test/Blog.Tests/EndToEnd/HomeTests.cs
new file mode 100644
index 0000000..e0ba4e5
--- /dev/null
+++ b/test/Blog.Tests/EndToEnd/HomeTests.cs
@@ -0,0 +1,32 @@
+namespace Blog.Tests.EndToEnd;
+
+[TestFixture]
+public class HomeTests : BlogTest
+{
+ [Test]
+ public async Task Home_WhenNavigatedTo_ItShouldDisplayCorrectPageTitle()
+ {
+ await Page.GotoAsync("/");
+ var title = await Page.TitleAsync();
+ title.Should().Be("journal - A blog by Stevan Freeborn");
+ }
+
+ [Test]
+ public async Task Home_WhenNavigatedTo_ItShouldCorrectSiteTitle()
+ {
+ await Page.GotoAsync("/");
+ var message = Page.GetByRole(AriaRole.Heading, new () { Name = "journal" });
+ await Expect(message).ToBeVisibleAsync();
+ }
+
+ [Test]
+ public async Task Home_WhenNavigatedTo_ItShouldDisplayWrittenByAttribution()
+ {
+ await Page.GotoAsync("/");
+ var message = Page.GetByText("by");
+ var image = Page.GetByAltText("Stevan Freeborn");
+
+ await Expect(message).ToBeVisibleAsync();
+ await Expect(image).ToBeVisibleAsync();
+ }
+}
\ No newline at end of file
diff --git a/test/Blog.Tests/GlobalUsings.cs b/test/Blog.Tests/GlobalUsings.cs
index cefced4..7892f58 100644
--- a/test/Blog.Tests/GlobalUsings.cs
+++ b/test/Blog.Tests/GlobalUsings.cs
@@ -1 +1,10 @@
-global using NUnit.Framework;
\ No newline at end of file
+global using NUnit.Framework;
+global using Microsoft.AspNetCore.Mvc.Testing;
+global using Microsoft.Extensions.Hosting;
+global using Microsoft.AspNetCore.Hosting;
+global using Microsoft.Playwright.NUnit;
+global using Microsoft.Playwright;
+global using FluentAssertions;
+global using Microsoft.AspNetCore.Hosting.Server;
+global using Microsoft.Extensions.DependencyInjection;
+global using System.Net;
\ No newline at end of file
diff --git a/test/Blog.Tests/Unit/Header.spec.razor b/test/Blog.Tests/Unit/Header.spec.razor
new file mode 100644
index 0000000..7963c28
--- /dev/null
+++ b/test/Blog.Tests/Unit/Header.spec.razor
@@ -0,0 +1,32 @@
+@inherits Bunit.TestContext
+
+@code
+{
+ [Test]
+ public void Header_WhenRendered_ItShouldContainSiteTitle()
+ {
+ var cut = Render(@);
+
+ var heading = cut.Find("h1");
+
+ heading.MarkupMatches(@journal
);
+ }
+
+ [Test]
+ public void Header_WhenRendered_ItShouldContainAuthorInfo()
+ {
+ var cut = Render(@);
+
+ var author = cut.Find(".author-container > span");
+
+ author.MarkupMatches(@by);
+ }
+
+ [Test]
+ public void Header_WhenRendered_ItShouldContainAuthorImage()
+ {
+ var cut = Render(@);
+ var image = cut.Find("img[alt='Stevan Freeborn']");
+ image.Should().NotBeNull();
+ }
+}
diff --git a/test/Blog.Tests/UnitTest1.cs b/test/Blog.Tests/UnitTest1.cs
deleted file mode 100644
index cd61a57..0000000
--- a/test/Blog.Tests/UnitTest1.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace Blog.Tests;
-
-public class Tests
-{
- [SetUp]
- public void Setup()
- {
- }
-
- [Test]
- public void Test1()
- {
- Assert.Pass();
- }
-}
\ No newline at end of file
diff --git a/test/Blog.Tests/_Imports.razor b/test/Blog.Tests/_Imports.razor
new file mode 100644
index 0000000..99e4f3d
--- /dev/null
+++ b/test/Blog.Tests/_Imports.razor
@@ -0,0 +1,3 @@
+@using Bunit
+@using Blog.Components
+@using FluentAssertions
\ No newline at end of file
From c4795dfdae9b8cf570ca6ba36b762f3bebb1ebd1 Mon Sep 17 00:00:00 2001
From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com>
Date: Wed, 10 Apr 2024 01:28:38 -0500
Subject: [PATCH 3/3] feat: add blog feed (#3)
* chore: begin working on post service
* chore: add debug assets
* chore: add whitelist words
* feat: add prism for syntax highlighting
* feat: updated favicon
* feat: add blog feed
* fix: add link to written by image
* feat: add custom error pages
* fix: focus on h2 since h1 is always the journal site title
* feat: use status code pages with redirects
* fix: add discernible text to link
* feat: add support for light and dark theme
* fix: wrap body with main tag
* fix: style main element in pages to take up full width of container
* fix: add heading to home page
* feat: add not-found-corgi image
* chore: add robots.txt file
* chore: clean up place holder posts
* fix: namespace header in layouts
* fix: use logger instead of console
* fix: use new text context in each test when registering services
* chore: update workflow to debug failing tests
* chore: install playwright deps
* fix: extend expect timeout
* fix: extend expect timeout
* fix: capture traces
* fix: capture traces
* fix: index file name
---
.github/workflows/pull_request.yml | 13 +-
.vscode/launch.json | 46 +
.vscode/settings.json | 4 +-
.vscode/tasks.json | 41 +
src/Blog/Blog.csproj | 2 +
src/Blog/Components/Layout/Header.razor | 7 +-
src/Blog/Components/Layout/MainLayout.razor | 4 +-
.../Components/Layout/MainLayout.razor.css | 9 +-
src/Blog/Components/Pages/Error.razor | 37 +-
src/Blog/Components/Pages/Error.razor.css | 5 +
src/Blog/Components/Pages/Home.razor | 7 +-
src/Blog/Components/Pages/_Error404.razor | 8 +
src/Blog/Components/Pages/_Error404.razor.css | 9 +
src/Blog/Components/Posts/Feed.razor | 34 +
src/Blog/Components/Posts/Feed.razor.css | 46 +
src/Blog/Components/Routes.razor | 2 +-
src/Blog/GlobalUsings.cs | 11 +
src/Blog/Posts/FilePostService.cs | 108 +
src/Blog/Posts/FilePostServiceOptions.cs | 17 +
src/Blog/Posts/IPostService.cs | 6 +
src/Blog/Posts/Post.cs | 15 +
src/Blog/Program.cs | 10 +-
src/Blog/appsettings.example.json | 6 +-
src/Blog/example.env.development | 4 +-
src/Blog/wwwroot/app.css | 37 +-
src/Blog/wwwroot/favicon.ico | Bin 15406 -> 15086 bytes
src/Blog/wwwroot/not-found-corgi.jpg | Bin 0 -> 46482 bytes
src/Blog/wwwroot/prism.css | 114 +
src/Blog/wwwroot/prism.js | 15391 ++++++++++++++++
src/Blog/wwwroot/robots.txt | 2 +
test/Blog.Tests/.runsettings | 1 +
test/Blog.Tests/Blog.Tests.csproj | 8 +
test/Blog.Tests/EndToEnd/BlogHostFactory.cs | 37 +-
test/Blog.Tests/EndToEnd/BlogTest.cs | 25 +
test/Blog.Tests/EndToEnd/HomeTests.cs | 18 +-
.../TestPosts/another-test-blog/index.md | 76 +
.../EndToEnd/TestPosts/test-blog/index.md | 76 +
test/Blog.Tests/GlobalUsings.cs | 24 +-
test/Blog.Tests/Unit/Feed.spec.razor | 44 +
test/Blog.Tests/Unit/FilePostServiceTests.cs | 423 +
test/Blog.Tests/_Imports.razor | 1 +
test/Blog.Tests/ci.runsettings | 11 +
42 files changed, 16676 insertions(+), 63 deletions(-)
create mode 100644 .vscode/launch.json
create mode 100644 .vscode/tasks.json
create mode 100644 src/Blog/Components/Pages/Error.razor.css
create mode 100644 src/Blog/Components/Pages/_Error404.razor
create mode 100644 src/Blog/Components/Pages/_Error404.razor.css
create mode 100644 src/Blog/Components/Posts/Feed.razor
create mode 100644 src/Blog/Components/Posts/Feed.razor.css
create mode 100644 src/Blog/GlobalUsings.cs
create mode 100644 src/Blog/Posts/FilePostService.cs
create mode 100644 src/Blog/Posts/FilePostServiceOptions.cs
create mode 100644 src/Blog/Posts/IPostService.cs
create mode 100644 src/Blog/Posts/Post.cs
create mode 100644 src/Blog/wwwroot/not-found-corgi.jpg
create mode 100644 src/Blog/wwwroot/prism.css
create mode 100644 src/Blog/wwwroot/prism.js
create mode 100644 src/Blog/wwwroot/robots.txt
create mode 100644 test/Blog.Tests/EndToEnd/TestPosts/another-test-blog/index.md
create mode 100644 test/Blog.Tests/EndToEnd/TestPosts/test-blog/index.md
create mode 100644 test/Blog.Tests/Unit/Feed.spec.razor
create mode 100644 test/Blog.Tests/Unit/FilePostServiceTests.cs
create mode 100644 test/Blog.Tests/ci.runsettings
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 1cc6936..085c526 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -30,12 +30,21 @@ jobs:
- name: Build project
run: dotnet build --no-restore
- name: Install playwright dependencies
+ shell: pwsh
+ run: ./test/Blog.Tests/bin/Debug/net8.0/playwright.ps1 install-deps
+ - name: Install playwright
shell: pwsh
run: ./test/Blog.Tests/bin/Debug/net8.0/playwright.ps1 install
- name: Test project
- run: dotnet test -e DOTNET_ENVIRONMENT=CI --no-build --verbosity normal
+ run: dotnet test -e DOTNET_ENVIRONMENT=CI --no-build --verbosity normal -s ./test/Blog.Tests/ci.runsettings
- name: Upload test results
uses: actions/upload-artifact@v4
with:
name: test-results
- path: ./test/Blog.Tests/TestResults
\ No newline at end of file
+ path: ./test/Blog.Tests/TestResults
+ - name: Upload playwright traces
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: playwright-traces
+ path: ./test/Blog.Tests/bin/Debug/net8.0/playwright-traces
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..6c8934d
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,46 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Blog - Docker",
+ "type": "coreclr",
+ "request": "attach",
+ "processId": "${command:pickRemoteProcess}",
+ "pipeTransport": {
+ "pipeProgram": "docker",
+ "pipeArgs": [ "exec", "-i", "blog.stevanfreeborn.com-development" ],
+ "debuggerPath": "/root/vsdbg/vsdbg",
+ "pipeCwd": "${workspaceRoot}",
+ "quoteArgs": false
+ },
+ "sourceFileMap": {
+ "/app": "${workspaceRoot}/src/Blog/"
+ }
+ },
+ {
+ "name": "Blog",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build_blog",
+ "program": "${workspaceFolder}/src/Blog/bin/Debug/net8.0/Blog.dll",
+ "args": [],
+ "cwd": "${workspaceFolder}/src/Blog",
+ "stopAtEntry": false,
+ "serverReadyAction": {
+ "action": "openExternally",
+ "pattern": "\\bNow listening on:\\s+(https?://\\S+)"
+ },
+ "env": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "sourceFileMap": {
+ "/Views": "${workspaceFolder}/Views"
+ }
+ },
+ {
+ "name": ".NET Core Attach",
+ "type": "coreclr",
+ "request": "attach"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index f2aafb8..d26908d 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -2,7 +2,9 @@
"cSpell.words": [
"Antiforgery",
"blazor",
- "Hsts"
+ "Hsts",
+ "Markdig",
+ "msedge"
],
"dotnet.unitTests.runSettingsPath": "./test/Blog.Tests/.runsettings"
}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..3231dea
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,41 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build_blog",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "build",
+ "${workspaceFolder}/Blog.sln",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary;ForceNoAlign"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "publish_blog",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "publish",
+ "${workspaceFolder}/Blog.sln",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary;ForceNoAlign"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "watch_blog",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "watch",
+ "run",
+ "--project",
+ "${workspaceFolder}/Blog.sln"
+ ],
+ "problemMatcher": "$msCompile"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/Blog/Blog.csproj b/src/Blog/Blog.csproj
index 50fdd4f..49fbe70 100644
--- a/src/Blog/Blog.csproj
+++ b/src/Blog/Blog.csproj
@@ -9,6 +9,8 @@
+
+
diff --git a/src/Blog/Components/Layout/Header.razor b/src/Blog/Components/Layout/Header.razor
index 485121b..6d311d8 100644
--- a/src/Blog/Components/Layout/Header.razor
+++ b/src/Blog/Components/Layout/Header.razor
@@ -1,4 +1,4 @@
-@namespace Blog.Components
+@namespace Blog.Components.Layout
diff --git a/src/Blog/Components/Layout/MainLayout.razor b/src/Blog/Components/Layout/MainLayout.razor
index 6af4224..99391fc 100644
--- a/src/Blog/Components/Layout/MainLayout.razor
+++ b/src/Blog/Components/Layout/MainLayout.razor
@@ -3,7 +3,9 @@
- @Body
+
+ @Body
+
diff --git a/src/Blog/Components/Layout/MainLayout.razor.css b/src/Blog/Components/Layout/MainLayout.razor.css
index 0431dce..b194dfa 100644
--- a/src/Blog/Components/Layout/MainLayout.razor.css
+++ b/src/Blog/Components/Layout/MainLayout.razor.css
@@ -6,10 +6,17 @@
max-width: 42rem;
padding: 1.25rem;
width: 100%;
+ gap: 4rem;
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
}
#blazor-error-ui {
- background: lightyellow;
+ background: var(--color-background-soft);
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
diff --git a/src/Blog/Components/Pages/Error.razor b/src/Blog/Components/Pages/Error.razor
index 576cc2d..c04dc20 100644
--- a/src/Blog/Components/Pages/Error.razor
+++ b/src/Blog/Components/Pages/Error.razor
@@ -3,34 +3,25 @@
Error
-
Error.
-
An error occurred while processing your request.
+
+
Error
+
Sorry it's me, not you. It looks like an unhandled error occurred while processing your request.
-@if (ShowRequestId)
-{
+ @if (ShowRequestId)
+ {
- Request ID: @RequestId
+ Request ID: @RequestId
-}
-
-
Development Mode
-
- Swapping to Development environment will display more detailed information about the error that occurred.
-
-
- The Development environment shouldn't be enabled for deployed applications.
- It can result in displaying sensitive information from exceptions to end users.
- For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
- and restarting the app.
-
+ }
+
@code{
- [CascadingParameter]
- private HttpContext? HttpContext { get; set; }
+ [CascadingParameter]
+ private HttpContext? HttpContext { get; set; }
- private string? RequestId { get; set; }
- private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
+ private string? RequestId { get; set; }
+ private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
- protected override void OnInitialized() =>
- RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
+ protected override void OnInitialized() =>
+ RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
}
diff --git a/src/Blog/Components/Pages/Error.razor.css b/src/Blog/Components/Pages/Error.razor.css
new file mode 100644
index 0000000..3278828
--- /dev/null
+++ b/src/Blog/Components/Pages/Error.razor.css
@@ -0,0 +1,5 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
\ No newline at end of file
diff --git a/src/Blog/Components/Pages/Home.razor b/src/Blog/Components/Pages/Home.razor
index 1f8286f..6cd10d5 100644
--- a/src/Blog/Components/Pages/Home.razor
+++ b/src/Blog/Components/Pages/Home.razor
@@ -3,9 +3,8 @@
journal - A blog by Stevan Freeborn
-
+
-
Hello, world!
-
-Welcome to your new app.
+
Posts
+
\ No newline at end of file
diff --git a/src/Blog/Components/Pages/_Error404.razor b/src/Blog/Components/Pages/_Error404.razor
new file mode 100644
index 0000000..17a7d91
--- /dev/null
+++ b/src/Blog/Components/Pages/_Error404.razor
@@ -0,0 +1,8 @@
+
+@page "/Error/404"
+
+
+
Not Found
+
Hmm...it doesn't look like the page you were looking for exists, but we did find this adorable corgi.
+

+
\ No newline at end of file
diff --git a/src/Blog/Components/Pages/_Error404.razor.css b/src/Blog/Components/Pages/_Error404.razor.css
new file mode 100644
index 0000000..3c4c9a5
--- /dev/null
+++ b/src/Blog/Components/Pages/_Error404.razor.css
@@ -0,0 +1,9 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+
+ & img {
+ border-radius: 0.5rem;
+ }
+}
\ No newline at end of file
diff --git a/src/Blog/Components/Posts/Feed.razor b/src/Blog/Components/Posts/Feed.razor
new file mode 100644
index 0000000..f4be89a
--- /dev/null
+++ b/src/Blog/Components/Posts/Feed.razor
@@ -0,0 +1,34 @@
+@namespace Blog.Components
+@inject IPostService postService
+@inject IJSRuntime JS
+
+ @if (posts.Count == 0)
+ {
+
+
Looks like writers block. Check back later.
+
+ }
+ else
+ {
+
+ }
+
+@code {
+ private List
posts = [];
+
+ protected override async Task OnInitializedAsync()
+ {
+ posts = await postService.GetPostsAsync();
+ }
+}
\ No newline at end of file
diff --git a/src/Blog/Components/Posts/Feed.razor.css b/src/Blog/Components/Posts/Feed.razor.css
new file mode 100644
index 0000000..f3beb49
--- /dev/null
+++ b/src/Blog/Components/Posts/Feed.razor.css
@@ -0,0 +1,46 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ gap: 2rem;
+
+ & a {
+ padding: 1rem;
+ border-radius: 1rem;
+ background-color: var(--color-background-soft);
+ transition-property: transform;
+ transition-duration: 100ms;
+ transition-timing-function: ease-in-out
+ }
+
+ & a:active {
+ transform: scale(0.95)
+ }
+
+ & article {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+
+ & h3 {
+ background: linear-gradient(to right top, #db3e3e, #df3741, #e32e44, #e72347, #eb124b);
+ background-clip: text;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ font-size: 2rem;
+ font-weight: 900;
+ }
+
+ & .date {
+ font-size: 0.85rem;
+ }
+
+ & h3,
+ & .date,
+ & .lead {
+ text-wrap: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Blog/Components/Routes.razor b/src/Blog/Components/Routes.razor
index 3b4a0a0..143f0b9 100644
--- a/src/Blog/Components/Routes.razor
+++ b/src/Blog/Components/Routes.razor
@@ -1,6 +1,6 @@
-
+
diff --git a/src/Blog/GlobalUsings.cs b/src/Blog/GlobalUsings.cs
new file mode 100644
index 0000000..ed008cf
--- /dev/null
+++ b/src/Blog/GlobalUsings.cs
@@ -0,0 +1,11 @@
+global using System.IO.Abstractions;
+global using System.Text.Json;
+
+global using Blog.Components;
+global using Blog.Posts;
+
+global using Markdig;
+global using Markdig.Prism;
+global using Markdig.Syntax;
+
+global using Microsoft.Extensions.Options;
diff --git a/src/Blog/Posts/FilePostService.cs b/src/Blog/Posts/FilePostService.cs
new file mode 100644
index 0000000..072969f
--- /dev/null
+++ b/src/Blog/Posts/FilePostService.cs
@@ -0,0 +1,108 @@
+namespace Blog.Posts;
+
+class FilePostService(
+ IOptions options,
+ IFileSystem fileSystem,
+ ILogger logger
+) : IPostService
+{
+ private static readonly JsonSerializerOptions JsonSerializerOptions = new()
+ {
+ PropertyNameCaseInsensitive = true,
+ AllowTrailingCommas = true,
+ };
+
+ private static readonly MarkdownPipeline MarkdownPipeline = new MarkdownPipelineBuilder()
+ .UseAdvancedExtensions()
+ .UsePrism()
+ .Build();
+
+ private const string MetaFence = "meta";
+ private const string IndexFile = "index.md";
+
+ private readonly FilePostServiceOptions _options = options.Value;
+ private readonly IFileSystem _fileSystem = fileSystem;
+ private readonly ILogger _logger = logger;
+
+ private static (string? metadata, MarkdownDocument document) ParsePost(string postText)
+ {
+ var markDoc = Markdown.Parse(postText, MarkdownPipeline);
+ var postMetadata = markDoc
+ .Where(
+ x =>
+ x is FencedCodeBlock fencedCodeBlock &&
+ fencedCodeBlock.Arguments is not null &&
+ fencedCodeBlock.Arguments.Contains(MetaFence)
+ )
+ .Select(x => x as FencedCodeBlock)
+ .FirstOrDefault();
+
+ if (postMetadata is not null)
+ {
+ markDoc.Remove(postMetadata);
+ }
+
+ var metaContent = postMetadata?.Lines.ToString();
+
+ return (metaContent, markDoc);
+ }
+
+ public async Task> GetPostsAsync()
+ {
+ var posts = new List();
+
+ if (_fileSystem.Directory.Exists(_options.PostsDirectory) is false)
+ {
+ return posts;
+ }
+
+ var subDirectories = _fileSystem.Directory.GetDirectories(_options.PostsDirectory);
+
+ if (subDirectories.Length is 0)
+ {
+ return posts;
+ }
+
+ foreach (var subDirectory in subDirectories)
+ {
+ try
+ {
+ var indexFilePath = _fileSystem.Path.Combine(subDirectory, IndexFile);
+
+ if (_fileSystem.File.Exists(indexFilePath) is false)
+ {
+ continue;
+ }
+
+ var postText = await _fileSystem.File.ReadAllTextAsync(indexFilePath);
+
+ var (metaContent, document) = ParsePost(postText);
+
+ if (metaContent is null || document.Count is 0)
+ {
+ continue;
+ }
+
+ var post = JsonSerializer.Deserialize(metaContent, JsonSerializerOptions);
+
+ if (post is null || post.IsPublished is false)
+ {
+ continue;
+ }
+
+ var finalPost = post with
+ {
+ Slug = _fileSystem.Path.GetFileName(subDirectory),
+ };
+
+ posts.Add(finalPost);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error while processing post in {SubDirectory}", subDirectory);
+ }
+ }
+
+ return [.. posts.OrderByDescending(x => x.PublishedAt)];
+ }
+}
\ No newline at end of file
diff --git a/src/Blog/Posts/FilePostServiceOptions.cs b/src/Blog/Posts/FilePostServiceOptions.cs
new file mode 100644
index 0000000..478b551
--- /dev/null
+++ b/src/Blog/Posts/FilePostServiceOptions.cs
@@ -0,0 +1,17 @@
+namespace Blog.Posts;
+
+class FilePostServiceOptions
+{
+ public string PostsDirectory { get; set; } = string.Empty;
+}
+
+class FilePostServiceOptionsSetup(IConfiguration configuration) : IConfigureOptions
+{
+ private const string SectionName = nameof(FilePostServiceOptions);
+ private readonly IConfiguration _configuration = configuration;
+
+ public void Configure(FilePostServiceOptions options)
+ {
+ _configuration.GetSection(SectionName).Bind(options);
+ }
+}
\ No newline at end of file
diff --git a/src/Blog/Posts/IPostService.cs b/src/Blog/Posts/IPostService.cs
new file mode 100644
index 0000000..def70c0
--- /dev/null
+++ b/src/Blog/Posts/IPostService.cs
@@ -0,0 +1,6 @@
+namespace Blog.Posts;
+
+interface IPostService
+{
+ Task> GetPostsAsync();
+}
\ No newline at end of file
diff --git a/src/Blog/Posts/Post.cs b/src/Blog/Posts/Post.cs
new file mode 100644
index 0000000..fe170b6
--- /dev/null
+++ b/src/Blog/Posts/Post.cs
@@ -0,0 +1,15 @@
+namespace Blog.Posts;
+
+record Post
+{
+ public string Title { get; init; } = string.Empty;
+ public string Lead { get; init; } = string.Empty;
+ public bool IsPublished { get; init; } = false;
+ public DateTime PublishedAt { get; init; }
+ public string Slug { get; init; } = string.Empty;
+}
+
+record PostWithContent : Post
+{
+ public string Content { get; init; } = string.Empty;
+}
\ No newline at end of file
diff --git a/src/Blog/Program.cs b/src/Blog/Program.cs
index bc42976..0ee5596 100644
--- a/src/Blog/Program.cs
+++ b/src/Blog/Program.cs
@@ -1,7 +1,9 @@
-using Blog.Components;
-
var builder = WebApplication.CreateBuilder(args);
+builder.Services.ConfigureOptions();
+builder.Services.AddSingleton();
+builder.Services.AddScoped();
+
builder.Services
.AddRazorComponents()
.AddInteractiveServerComponents();
@@ -23,6 +25,8 @@ app
.MapRazorComponents()
.AddInteractiveServerRenderMode();
+app.UseStatusCodePagesWithRedirects("/Error/{0}");
+
app.Run();
-public partial class Program {}
\ No newline at end of file
+public partial class Program { }
\ No newline at end of file
diff --git a/src/Blog/appsettings.example.json b/src/Blog/appsettings.example.json
index acae0aa..d4be169 100644
--- a/src/Blog/appsettings.example.json
+++ b/src/Blog/appsettings.example.json
@@ -1,3 +1,7 @@
{
- "Urls": "https://localhost:4500;http://localhost:4501"
+ "Urls": "https://localhost:4500;http://localhost:4501",
+ "FilePostServiceOptions": {
+ "PostsDirectory": "wwwroot/posts"
+ },
+ "DetailedErrors": "true"
}
\ No newline at end of file
diff --git a/src/Blog/example.env.development b/src/Blog/example.env.development
index fb80ac5..d1e6fbc 100644
--- a/src/Blog/example.env.development
+++ b/src/Blog/example.env.development
@@ -12,4 +12,6 @@ ASPNETCORE_Kestrel__Certificates__Default__Password=
# The first url in the list is the port that the browser will attempt to connect to
# The second url in the list is the port that the dotnet watch process will be running
# on in the container. This is necessary for hot reload to work.
-DOTNET_WATCH_AUTO_RELOAD_WS_HOSTNAME=localhost:1234;http://0.0.0.0:5555;http://127.0.0.1
\ No newline at end of file
+DOTNET_WATCH_AUTO_RELOAD_WS_HOSTNAME=localhost:1234;http://0.0.0.0:5555;http://127.0.0.1
+DETAILED_ERRORS=true
+DOTNET_WATCH_RESTART_ON_RUDE_EDIT=true
\ No newline at end of file
diff --git a/src/Blog/wwwroot/app.css b/src/Blog/wwwroot/app.css
index 16a6d49..87d0436 100644
--- a/src/Blog/wwwroot/app.css
+++ b/src/Blog/wwwroot/app.css
@@ -41,6 +41,39 @@ table {
/* Site */
+:root {
+ --primary-white: #ffffff;
+ --secondary-white: #f8f8f8;
+ --primary-black: #121212;
+ --secondary-black: #1e1e1e;
+}
+
+:root {
+ --color-background: var(--primary-white);
+ --color-background-soft: var(--secondary-white);
+ --color-text: var(--primary-black);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --color-background: var(--primary-black);
+ --color-background-soft: var(--secondary-black);
+ --color-text: var(--primary-white);
+ }
+}
+
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border-width: 0;
+}
+
html,
body {
height: 100%;
@@ -56,9 +89,11 @@ body {
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
+ background-color: var(--color-background);
+ color: var(--color-text);
}
-h1:focus {
+h2:focus {
outline: none;
}
diff --git a/src/Blog/wwwroot/favicon.ico b/src/Blog/wwwroot/favicon.ico
index ec4c0e35b7f4980ee4b2feb2adc454246421fb86..794347d4a5aac67fd8497bf500ec34434d86207e 100644
GIT binary patch
literal 15086
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
literal 15406
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