Compare commits

...

14 Commits

Author SHA1 Message Date
fa087a513d
style: write as first person 2024-09-17 20:46:49 +01:00
771ccc52ad
refactor: haha I'm a student now
dear god help me
2024-09-17 20:45:36 +01:00
59e42ff7cd
perf: use http utility to decode url 2024-07-15 19:44:02 +01:00
d32d46e221
fix: fix tag links 2024-07-15 19:38:56 +01:00
0bebcb69fe
fix: fix display of single-page tabs 2024-07-15 19:38:34 +01:00
d3ac89d071
refactor: use mastodon info from configuration 2024-07-05 17:56:42 +01:00
901a8347b9
refactor: move to new mastodon instance 2024-07-05 00:46:40 +01:00
8dbfeb8d38
chore: drop version in docker-compose 2024-06-16 19:29:37 +01:00
eb67c25e09
refactor: C != C++ 2024-06-16 19:16:48 +01:00
15e28bd223
feat: add keystroke class 2024-05-12 13:31:36 +01:00
acb6b32938
style: begin to organise app.scss monolith
colors now defined separately
2024-05-12 13:31:08 +01:00
cf4d92c035
fix: oops, page 2 was completely missing.
The model contains a 1-based page number, whereas GetBlogPosts wants 0-based index, causing an entire page to be missing.
2024-05-06 17:40:03 +01:00
58797b82ca
fix: don't include redirected posts in count 2024-05-06 17:39:28 +01:00
9991ecf173
Merge branch 'feature/cleanup' 2024-05-06 15:02:19 +01:00
14 changed files with 122 additions and 51 deletions

View File

@ -24,8 +24,9 @@ public interface IBlogPostService
/// Returns the total number of blog posts.
/// </summary>
/// <param name="visibility">The post visibility filter.</param>
/// <param name="tags">The tags of the posts to return.</param>
/// <returns>The total number of blog posts.</returns>
int GetBlogPostCount(Visibility visibility = Visibility.None);
int GetBlogPostCount(Visibility visibility = Visibility.None, string[]? tags = null);
/// <summary>
/// Returns a collection of blog posts from the specified page, optionally limiting the number of posts
@ -33,8 +34,9 @@ public interface IBlogPostService
/// </summary>
/// <param name="page">The zero-based index of the page to return.</param>
/// <param name="pageSize">The maximum number of posts to return per page.</param>
/// <param name="tags">The tags of the posts to return.</param>
/// <returns>A collection of blog posts.</returns>
IReadOnlyList<IBlogPost> GetBlogPosts(int page, int pageSize = 10);
IReadOnlyList<IBlogPost> GetBlogPosts(int page, int pageSize = 10, string[]? tags = null);
/// <summary>
/// Returns the number of legacy comments for the specified post.
@ -70,8 +72,9 @@ public interface IBlogPostService
/// </summary>
/// <param name="pageSize">The page size. Defaults to 10.</param>
/// <param name="visibility">The post visibility filter.</param>
/// <param name="tags">The tags of the posts to return.</param>
/// <returns>The page count.</returns>
int GetPageCount(int pageSize = 10, Visibility visibility = Visibility.None);
int GetPageCount(int pageSize = 10, Visibility visibility = Visibility.None, string[]? tags = null);
/// <summary>
/// Returns the previous blog post from the specified blog post.

View File

@ -12,7 +12,7 @@
@await Html.PartialAsync("Partials/_MastodonStatus")
<div id="all-blog-posts">
@foreach (IBlogPost post in BlogPostService.GetBlogPosts(0))
@foreach (IBlogPost post in BlogPostService.GetBlogPosts(0, tags: Model.Tag))
{
@await Html.PartialAsync("Partials/_BlogCard", post)
}
@ -22,5 +22,6 @@
{
["UrlRoot"] = "/blog",
["Page"] = 1,
["PageCount"] = BlogPostService.GetPageCount(visibility: Visibility.Published)
["Tags"] = Model.Tag,
["PageCount"] = BlogPostService.GetPageCount(visibility: Visibility.Published, tags: Model.Tag)
})

View File

@ -1,3 +1,4 @@
using System.Web;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using OliverBooth.Common.Data.Blog;
@ -15,11 +16,15 @@ public class Index : PageModel
_blogPostService = blogPostService;
}
public string[] Tag { get; private set; } = [];
public IActionResult OnGet([FromQuery(Name = "pid")] Guid? postId = null,
[FromQuery(Name = "p")] int? wpPostId = null)
[FromQuery(Name = "p")] int? wpPostId = null,
[FromQuery(Name = "tag")] string? tag = null)
{
if (postId.HasValue == wpPostId.HasValue)
{
Tag = (tag?.Split('+').Select(HttpUtility.UrlDecode).ToArray() ?? [])!;
return Page();
}

View File

@ -9,7 +9,7 @@
@await Html.PartialAsync("Partials/_MastodonStatus")
<div id="all-blog-posts">
@foreach (IBlogPost post in BlogPostService.GetBlogPosts(Model.PageNumber))
@foreach (IBlogPost post in BlogPostService.GetBlogPosts(Model.PageNumber - 1, tags: Model.Tag))
{
@await Html.PartialAsync("Partials/_BlogCard", post)
}
@ -19,5 +19,6 @@
{
["UrlRoot"] = "/blog",
["Page"] = Model.PageNumber,
["PageCount"] = BlogPostService.GetPageCount(visibility: Visibility.Published)
["Tags"] = Model.Tag,
["PageCount"] = BlogPostService.GetPageCount(visibility: Visibility.Published, tags: Model.Tag)
})

View File

@ -1,3 +1,4 @@
using System.Web;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
@ -14,12 +15,15 @@ public class List : PageModel
/// <value>The requested page number.</value>
public int PageNumber { get; private set; }
public string[] Tag { get; private set; } = [];
/// <summary>
/// Handles the incoming GET request to the page.
/// </summary>
/// <param name="page">The requested page number, starting from 1.</param>
/// <param name="tag">The tag by which to filter results.</param>
/// <returns></returns>
public IActionResult OnGet([FromRoute(Name = "pageNumber")] int page = 1)
public IActionResult OnGet([FromRoute(Name = "pageNumber")] int page = 1, [FromQuery(Name = "tag")] string? tag = null)
{
if (page < 2)
{
@ -27,6 +31,7 @@ public class List : PageModel
}
PageNumber = page;
Tag = (tag?.Split('+').Select(HttpUtility.UrlDecode).ToArray() ?? [])!;
return Page();
}
}

View File

@ -15,11 +15,11 @@
</div>
<p>
My primary focus is C#, though I have dabbled in several other languages such as Java, Kotlin, VB, C/C++,
Python, and others. Over the years I've built up a collection of projects. Some of which I'm extremely proud of,
and others I've quietly abandoned and buried. I'm currently working on a few projects that I hope to release in
the near future, but in the meantime, feel free to check out some of my
<a asp-page="/Projects/Index">previous work</a>.
I recently became a Computer Science student, aiming to get my bachelor's degree. My primary focus is C#, though
I have dabbled in several other languages such as Java, Kotlin, VB, C++, Python, and others. Over the years I've
built up a collection of projects. Some of which I'm extremely proud of, and others I've quietly abandoned and
buried. I'm currently working on a few projects that I hope to release in the near future, but in the meantime,
feel free to check out some of my <a asp-page="/Projects/Index">previous work</a>.
</p>
<p>

View File

@ -32,6 +32,8 @@ public class PageTabsUtility
set => _urlRoot = string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim();
}
public string[]? Tags { get; set; } = [];
/// <summary>
/// Shows the bound chevrons for the specified bounds type.
/// </summary>
@ -129,7 +131,9 @@ public class PageTabsUtility
private string GetLinkForPage(int page)
{
// page 1 doesn't use /page/n route
return page == 1 ? _urlRoot : $"{_urlRoot}/page/{page}";
return page == 1
? $"{_urlRoot}{(Tags is { Length: > 0 } ? $"?tag={string.Join('+', Tags)}" : "")}"
: $"{_urlRoot}/page/{page}{(Tags is { Length: > 0 } ? $"?tag={string.Join('+', Tags)}" : "")}";
}
private string ShowLowerBound()

View File

@ -7,15 +7,23 @@
{
CurrentPage = page,
PageCount = pageCount,
UrlRoot = urlRoot
UrlRoot = urlRoot,
Tags = ViewData["Tags"] as string[]
};
}
<nav>
<ul class="pagination justify-content-center">
@if (pageCount == 1)
{
@Html.Raw(utility.ShowTab(1)) @* always visible *@
}
else
{
@Html.Raw(utility.ShowBounds(PageTabsUtility.BoundsType.Lower))
@Html.Raw(utility.ShowTab(1)) @* always visible *@
@Html.Raw(utility.ShowTabWindow())
@Html.Raw(utility.ShowTab(pageCount)) @* always visible *@
@Html.Raw(utility.ShowBounds(PageTabsUtility.BoundsType.Upper))
}
</ul>
</nav>

View File

@ -5,6 +5,7 @@
@using OliverBooth.Extensions
@inject IBlogPostService BlogPostService
@inject ITutorialService TutorialService
@inject IConfiguration Configuration
@{
HttpRequest request = Context.Request;
var url = new Uri($"{request.Scheme}://{request.Host}{request.Path}{request.QueryString}");
@ -106,7 +107,11 @@
<hr>
<ul class="footer-nav">
<li><a title="@("@oliver@mastodon.olivr.me")" href="https://mastodon.olivr.me/@@oliver" rel="me" class="brand-mastodon"><i class="fa-brands fa-mastodon"></i></a></li>
@{
string domain = Configuration.GetSection("Mastodon:Domain").Value ?? string.Empty;
string username = Configuration.GetSection("Mastodon:Username").Value ?? string.Empty;
}
<li><a title="@($"@{username}@{domain}")" href="https://@domain/@@@username" rel="me" class="brand-mastodon"><i class="fa-brands fa-mastodon"></i></a></li>
<li><a title="LinkedIn/oliverlukebooth" href="https://www.linkedin.com/in/oliverlukebooth/" class="brand-linkedin"><i class="fa-brands fa-linkedin"></i></a></li>
<li><a title="Blog RSS Feed" asp-controller="Rss" asp-action="OnGet"><i class="fa-solid fa-rss text-orange"></i></a></li>
<li><a title="View Source" href="https://git.oliverbooth.dev/oliverbooth/oliverbooth.dev"><i class="fa-solid fa-code"></i></a></li>

View File

@ -36,12 +36,19 @@ internal sealed class BlogPostService : IBlogPostService
}
/// <inheritdoc />
public int GetBlogPostCount(Visibility visibility = Visibility.None)
public int GetBlogPostCount(Visibility visibility = Visibility.None, string[]? tags = null)
{
using BlogContext context = _dbContextFactory.CreateDbContext();
if (tags is { Length: > 0 })
{
return visibility == Visibility.None
? context.BlogPosts.Count()
: context.BlogPosts.Count(p => p.Visibility == visibility);
? context.BlogPosts.AsEnumerable().Count(p => !p.IsRedirect && p.Tags.Intersect(tags).Any())
: context.BlogPosts.AsEnumerable().Count(p => !p.IsRedirect && p.Visibility == visibility && p.Tags.Intersect(tags).Any());
}
return visibility == Visibility.None
? context.BlogPosts.Count(p => !p.IsRedirect)
: context.BlogPosts.Count(p => !p.IsRedirect && p.Visibility == visibility);
}
/// <inheritdoc />
@ -60,13 +67,20 @@ internal sealed class BlogPostService : IBlogPostService
}
/// <inheritdoc />
public IReadOnlyList<IBlogPost> GetBlogPosts(int page, int pageSize = 10)
public IReadOnlyList<IBlogPost> GetBlogPosts(int page, int pageSize = 10, string[]? tags = null)
{
using BlogContext context = _dbContextFactory.CreateDbContext();
return context.BlogPosts
IEnumerable<BlogPost> posts = context.BlogPosts
.Where(p => p.Visibility == Visibility.Published && !p.IsRedirect)
.OrderByDescending(post => post.Published)
.Skip(page * pageSize)
.AsEnumerable();
if (tags is { Length: > 0 })
{
posts = posts.Where(p => p.Tags.Intersect(tags).Any());
}
return posts.Skip(page * pageSize)
.Take(pageSize)
.ToArray().Select(CacheAuthor).ToArray();
}
@ -103,9 +117,9 @@ internal sealed class BlogPostService : IBlogPostService
}
/// <inheritdoc />
public int GetPageCount(int pageSize = 10, Visibility visibility = Visibility.None)
public int GetPageCount(int pageSize = 10, Visibility visibility = Visibility.None, string[]? tags = null)
{
float postCount = GetBlogPostCount(visibility);
float postCount = GetBlogPostCount(visibility, tags);
return (int)MathF.Ceiling(postCount / pageSize);
}

View File

@ -27,11 +27,13 @@ internal sealed class MastodonService : IMastodonService
/// <inheritdoc />
public IMastodonStatus GetLatestStatus()
{
string domain = _configuration.GetSection("Mastodon:Domain").Value ?? string.Empty;
string token = _configuration.GetSection("Mastodon:Token").Value ?? string.Empty;
string account = _configuration.GetSection("Mastodon:Account").Value ?? string.Empty;
using var request = new HttpRequestMessage();
request.Headers.Add("Authorization", $"Bearer {token}");
request.RequestUri = new Uri($"https://mastodon.olivr.me/api/v1/accounts/{account}/statuses");
request.RequestUri = new Uri($"https://{domain}/api/v1/accounts/{account}/statuses");
using HttpResponseMessage response = _httpClient.Send(request);
using var stream = response.Content.ReadAsStream();

View File

@ -1,4 +1,3 @@
version: '3.9'
services:
oliverbooth:
container_name: oliverbooth.dev

View File

@ -1,9 +1,11 @@
@use "sass:color";
@import "colors";
@import "markdown";
@import "blog";
html, body {
background: #121212;
color: #f5f5f5;
background: $background;
color: $foreground;
font-size: 16px;
}
@ -13,8 +15,18 @@ html, body {
}
}
.keystroke {
background: color.adjust($foreground, $lightness: -20%);
color: $background;
border-radius: 2px;
border: color.adjust($foreground, $lightness: -30%);
box-shadow: #2b2b2b 2px 2px;
font-size: 12px;
padding: 2px 4px;
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
box-shadow: 0 0 0 0.1rem $ui-foreground, 0 0 0 0.25rem #258cfb;
}
html {
@ -35,11 +47,11 @@ main.container {
a {
&:link, &:visited, &:hover, &:active {
text-decoration: none;
color: #03A9F4;
color: $link-text;
}
&:hover {
color: #ffffff;
color: $link-hover;
}
}
@ -47,19 +59,19 @@ a {
a {
&:link, &:visited, &:hover, &:active {
text-decoration: none;
color: white;
color: $ui-foreground;
* {
font-family: 'Roboto Mono', monospace;
text-decoration: none;
color: white;
color: $ui-foreground;
}
}
}
}
hr.page-separator {
border-top: 5px dashed #ffffff;
border-top: 5px dashed $ui-foreground;
opacity: 1;
margin: 50px 0;
}
@ -86,7 +98,7 @@ ul.contact-reasons {
a {
&:link, &:visited, &:hover, &:active {
text-decoration: none;
color: white;
color: $ui-foreground;
}
&:hover {
@ -98,10 +110,10 @@ ul.contact-reasons {
.card {
background: #212121;
color: #ffffff;
color: $ui-foreground;
a.btn, button {
color: #ffffff;
color: $ui-foreground;
}
}
@ -128,7 +140,7 @@ nav {
&:link, &:visited, &:hover, &:active {
text-decoration: none;
color: white;
color: $ui-foreground;
}
&:hover {
@ -144,7 +156,7 @@ nav {
}
article {
background: #333333;
background: $content-background;
padding: 20px;
border-radius: 5px;
@ -153,7 +165,7 @@ article {
}
blockquote {
border-left: 2px solid #f03;
border-left: 2px solid $blockquote;
padding-left: 10px;
}
@ -173,7 +185,7 @@ article {
abbr {
text-decoration: none;
border-bottom: 1px dotted #ffffff;
border-bottom: 1px dotted $link-hover;
}
span.timestamp {
@ -185,7 +197,7 @@ article {
.project-card {
position: relative;
background: #000;
background: $project-background;
box-shadow: 0 0 15px rgba(0, 0, 0, .1);
&:hover {
@ -213,7 +225,7 @@ article {
a {
&:link, &:visited, &:hover &:active {
color: #fff;
color: $ui-foreground;
}
}
@ -225,7 +237,7 @@ article {
width: 100%;
margin: -34px 0 0;
padding: 5px;
background-color: rgba(#000, 50%);
background-color: rgba($project-background, 50%);
}
}
}
@ -414,7 +426,7 @@ td.trim-p p:last-child {
}
&:hover {
color: #03A9F4;
color: $accent;
background-color: #1E1E1E !important;
}
}

12
src/scss/colors.scss Normal file
View File

@ -0,0 +1,12 @@
@use "sass:color";
$background: #121212;
$foreground: #f5f5f5;
$accent: #03a9f4;
$ui-foreground: #ffffff;
$content-background: #333333;
$blockquote: #ff0033;
$project-background: #000000;
$link-text: $accent;
$link-hover: lighten($link-text, 50%);