Compare commits
No commits in common. "9991ecf1739ebbc1ff3c2190b3e923ee17323ef4" and "e0037fbff26657118b24aa44ff2b642e21834793" have entirely different histories.
9991ecf173
...
e0037fbff2
@ -1,31 +0,0 @@
|
|||||||
namespace OliverBooth.Common.Data.Mastodon;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a status on Mastodon.
|
|
||||||
/// </summary>
|
|
||||||
public interface IMastodonStatus
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the content of the status.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The content.</value>
|
|
||||||
string Content { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the date and time at which this status was posted.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The post timestamp.</value>
|
|
||||||
DateTimeOffset CreatedAt { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the media attachments for this status.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The media attachments.</value>
|
|
||||||
IReadOnlyList<MediaAttachment> MediaAttachments { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the original URI of the status.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The original URI.</value>
|
|
||||||
Uri OriginalUri { get; }
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.59"/>
|
|
||||||
<PackageReference Include="Humanizer.Core" Version="2.14.1"/>
|
|
||||||
<PackageReference Include="ZString" Version="2.5.1"/>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
@ -1,14 +0,0 @@
|
|||||||
namespace OliverBooth.Common.Services;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a service which can perform programming language lookup.
|
|
||||||
/// </summary>
|
|
||||||
public interface IProgrammingLanguageService
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the human-readable name of a language.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="alias">The alias of the language.</param>
|
|
||||||
/// <returns>The human-readable name, or <paramref name="alias" /> if the name could not be found.</returns>
|
|
||||||
string GetLanguageName(string alias);
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
using Markdig;
|
|
||||||
using OliverBooth.Extensions.Markdig.Markdown.Callout;
|
|
||||||
using OliverBooth.Extensions.Markdig.Markdown.Template;
|
|
||||||
using OliverBooth.Extensions.Markdig.Services;
|
|
||||||
|
|
||||||
namespace OliverBooth.Extensions.Markdig;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extension methods for <see cref="MarkdownPipelineBuilder" />.
|
|
||||||
/// </summary>
|
|
||||||
public static class MarkdownPipelineExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Enables the use of Obsidian-style callouts in this pipeline.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="builder">The Markdig markdown pipeline builder.</param>
|
|
||||||
/// <returns>The modified Markdig markdown pipeline builder.</returns>
|
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder" /> is <see langword="null" />.</exception>
|
|
||||||
public static MarkdownPipelineBuilder UseCallouts(this MarkdownPipelineBuilder builder)
|
|
||||||
{
|
|
||||||
if (builder is null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(builder));
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.Extensions.AddIfNotAlready<CalloutExtension>();
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Enables the use of Wiki-style templates in this pipeline.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="builder">The Markdig markdown pipeline builder.</param>
|
|
||||||
/// <param name="templateService">The template service responsible for fetching and rendering templates.</param>
|
|
||||||
/// <returns>The modified Markdig markdown pipeline builder.</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">
|
|
||||||
/// <para><paramref name="builder" /> is <see langword="null" />.</para>
|
|
||||||
/// -or-
|
|
||||||
/// <para><paramref name="templateService" /> is <see langword="null" />.</para>
|
|
||||||
/// </exception>
|
|
||||||
public static MarkdownPipelineBuilder UseTemplates(this MarkdownPipelineBuilder builder, ITemplateService templateService)
|
|
||||||
{
|
|
||||||
if (builder is null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(builder));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (templateService is null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(templateService));
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.Use(new TemplateExtension(templateService));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Markdig" Version="0.36.2"/>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\OliverBooth.Common\OliverBooth.Common.csproj"/>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
@ -1,19 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Markdig" Version="0.36.2"/>
|
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1"/>
|
|
||||||
<PackageReference Include="SmartFormat.NET" Version="3.3.2"/>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\OliverBooth.Common\OliverBooth.Common.csproj"/>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
@ -13,12 +13,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
global.json = global.json
|
global.json = global.json
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OliverBooth.Extensions.Markdig", "OliverBooth.Extensions.Markdig\OliverBooth.Extensions.Markdig.csproj", "{3B012CD2-3201-41A0-BEF9-8E0B6247BB7E}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OliverBooth.Common", "OliverBooth.Common\OliverBooth.Common.csproj", "{AD231E0F-FAED-4661-963F-EB22F858E148}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OliverBooth.Extensions.SmartFormat", "OliverBooth.Extensions.SmartFormat\OliverBooth.Extensions.SmartFormat.csproj", "{9D56FA9B-B95B-460D-8745-41AABAA8BF61}"
|
|
||||||
EndProject
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -29,18 +23,6 @@ Global
|
|||||||
{A58A6FA3-480C-400B-822A-3786741BF39C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{A58A6FA3-480C-400B-822A-3786741BF39C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{A58A6FA3-480C-400B-822A-3786741BF39C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{A58A6FA3-480C-400B-822A-3786741BF39C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{A58A6FA3-480C-400B-822A-3786741BF39C}.Release|Any CPU.Build.0 = Release|Any CPU
|
{A58A6FA3-480C-400B-822A-3786741BF39C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{3B012CD2-3201-41A0-BEF9-8E0B6247BB7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{3B012CD2-3201-41A0-BEF9-8E0B6247BB7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{3B012CD2-3201-41A0-BEF9-8E0B6247BB7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{3B012CD2-3201-41A0-BEF9-8E0B6247BB7E}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{AD231E0F-FAED-4661-963F-EB22F858E148}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{AD231E0F-FAED-4661-963F-EB22F858E148}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{AD231E0F-FAED-4661-963F-EB22F858E148}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{AD231E0F-FAED-4661-963F-EB22F858E148}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{9D56FA9B-B95B-460D-8745-41AABAA8BF61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{9D56FA9B-B95B-460D-8745-41AABAA8BF61}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{9D56FA9B-B95B-460D-8745-41AABAA8BF61}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{9D56FA9B-B95B-460D-8745-41AABAA8BF61}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
102
OliverBooth/Controllers/Blog/BlogApiController.cs
Normal file
102
OliverBooth/Controllers/Blog/BlogApiController.cs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
using Humanizer;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using OliverBooth.Data.Blog;
|
||||||
|
using OliverBooth.Services;
|
||||||
|
|
||||||
|
namespace OliverBooth.Controllers.Blog;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a controller for the blog API.
|
||||||
|
/// </summary>
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/blog")]
|
||||||
|
[Produces("application/json")]
|
||||||
|
public sealed class BlogApiController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IBlogPostService _blogPostService;
|
||||||
|
private readonly IBlogUserService _userService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="BlogApiController" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="blogPostService">The <see cref="IBlogPostService" />.</param>
|
||||||
|
/// <param name="userService">The <see cref="IBlogUserService" />.</param>
|
||||||
|
public BlogApiController(IBlogPostService blogPostService, IBlogUserService userService)
|
||||||
|
{
|
||||||
|
_blogPostService = blogPostService;
|
||||||
|
_userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("count")]
|
||||||
|
public IActionResult Count()
|
||||||
|
{
|
||||||
|
return Ok(new { count = _blogPostService.GetBlogPostCount() });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("posts/{page:int?}")]
|
||||||
|
public IActionResult GetAllBlogPosts(int page = 0)
|
||||||
|
{
|
||||||
|
const int itemsPerPage = 10;
|
||||||
|
IReadOnlyList<IBlogPost> allPosts = _blogPostService.GetBlogPosts(page, itemsPerPage);
|
||||||
|
return Ok(allPosts.Select(post => CreatePostObject(post)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("posts/tagged/{tag}/{page:int?}")]
|
||||||
|
public IActionResult GetTaggedBlogPosts(string tag, int page = 0)
|
||||||
|
{
|
||||||
|
const int itemsPerPage = 10;
|
||||||
|
tag = tag.Replace('-', ' ').ToLowerInvariant();
|
||||||
|
|
||||||
|
IReadOnlyList<IBlogPost> allPosts = _blogPostService.GetBlogPosts(page, itemsPerPage);
|
||||||
|
allPosts = allPosts.Where(post => post.Tags.Contains(tag)).ToList();
|
||||||
|
return Ok(allPosts.Select(post => CreatePostObject(post)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("author/{id:guid}")]
|
||||||
|
public IActionResult GetAuthor(Guid id)
|
||||||
|
{
|
||||||
|
if (!_userService.TryGetUser(id, out IUser? author)) return NotFound();
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
id = author.Id,
|
||||||
|
name = author.DisplayName,
|
||||||
|
avatarUrl = author.AvatarUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("post/{id:guid?}")]
|
||||||
|
public IActionResult GetPost(Guid id)
|
||||||
|
{
|
||||||
|
if (!_blogPostService.TryGetPost(id, out IBlogPost? post)) return NotFound();
|
||||||
|
return Ok(CreatePostObject(post, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private object CreatePostObject(IBlogPost post, bool includeContent = false)
|
||||||
|
{
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
id = post.Id,
|
||||||
|
commentsEnabled = post.EnableComments,
|
||||||
|
identifier = post.GetDisqusIdentifier(),
|
||||||
|
author = post.Author.Id,
|
||||||
|
title = post.Title,
|
||||||
|
published = post.Published.ToUnixTimeSeconds(),
|
||||||
|
updated = post.Updated?.ToUnixTimeSeconds(),
|
||||||
|
formattedPublishDate = post.Published.ToString("dddd, d MMMM yyyy HH:mm"),
|
||||||
|
formattedUpdateDate = post.Updated?.ToString("dddd, d MMMM yyyy HH:mm"),
|
||||||
|
humanizedTimestamp = post.Updated?.Humanize() ?? post.Published.Humanize(),
|
||||||
|
excerpt = _blogPostService.RenderExcerpt(post, out bool trimmed),
|
||||||
|
content = includeContent ? _blogPostService.RenderPost(post) : null,
|
||||||
|
trimmed,
|
||||||
|
tags = post.Tags.Select(t => t.Replace(' ', '-')),
|
||||||
|
url = new
|
||||||
|
{
|
||||||
|
year = post.Published.ToString("yyyy"),
|
||||||
|
month = post.Published.ToString("MM"),
|
||||||
|
day = post.Published.ToString("dd"),
|
||||||
|
slug = post.Slug
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using OliverBooth.Common.Data.Blog;
|
using OliverBooth.Data.Blog;
|
||||||
using OliverBooth.Common.Services;
|
|
||||||
using OliverBooth.Data.Blog.Rss;
|
using OliverBooth.Data.Blog.Rss;
|
||||||
|
using OliverBooth.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Controllers.Blog;
|
namespace OliverBooth.Controllers.Blog;
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using OliverBooth.Common.Data.Web;
|
using OliverBooth.Data.Web;
|
||||||
using OliverBooth.Common.Services;
|
using OliverBooth.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Controllers;
|
namespace OliverBooth.Controllers;
|
||||||
|
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using OliverBooth.Common.Data;
|
|
||||||
using OliverBooth.Common.Data.Blog;
|
|
||||||
using SmartFormat;
|
using SmartFormat;
|
||||||
|
|
||||||
namespace OliverBooth.Data.Blog;
|
namespace OliverBooth.Data.Blog;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using OliverBooth.Common.Data;
|
|
||||||
|
|
||||||
namespace OliverBooth.Data.Blog.Configuration;
|
namespace OliverBooth.Data.Blog.Configuration;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Common.Data.Blog;
|
namespace OliverBooth.Data.Blog;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the author of a blog post.
|
/// Represents the author of a blog post.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Common.Data.Blog;
|
namespace OliverBooth.Data.Blog;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a blog post.
|
/// Represents a blog post.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Common.Data.Blog;
|
namespace OliverBooth.Data.Blog;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a comment that was posted on a legacy comment framework.
|
/// Represents a comment that was posted on a legacy comment framework.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Common.Data.Blog;
|
namespace OliverBooth.Data.Blog;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a user which can log in to the blog.
|
/// Represents a user which can log in to the blog.
|
@ -1,5 +1,4 @@
|
|||||||
using System.Web;
|
using System.Web;
|
||||||
using OliverBooth.Common.Data.Blog;
|
|
||||||
|
|
||||||
namespace OliverBooth.Data.Blog;
|
namespace OliverBooth.Data.Blog;
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ using System.ComponentModel.DataAnnotations.Schema;
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Cysharp.Text;
|
using Cysharp.Text;
|
||||||
using OliverBooth.Common.Data.Blog;
|
|
||||||
|
|
||||||
namespace OliverBooth.Data.Blog;
|
namespace OliverBooth.Data.Blog;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Common.Data.Mastodon;
|
namespace OliverBooth.Data.Mastodon;
|
||||||
|
|
||||||
public enum AttachmentType
|
public enum AttachmentType
|
||||||
{
|
{
|
@ -1,24 +1,34 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using OliverBooth.Common.Data.Mastodon;
|
|
||||||
|
|
||||||
namespace OliverBooth.Data.Mastodon;
|
namespace OliverBooth.Data.Mastodon;
|
||||||
|
|
||||||
/// <inheritdoc />
|
public sealed class MastodonStatus
|
||||||
internal sealed class MastodonStatus : IMastodonStatus
|
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
|
/// Gets the content of the status.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The content.</value>
|
||||||
[JsonPropertyName("content")]
|
[JsonPropertyName("content")]
|
||||||
public string Content { get; set; } = string.Empty;
|
public string Content { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
|
/// Gets the date and time at which this status was posted.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The post timestamp.</value>
|
||||||
[JsonPropertyName("created_at")]
|
[JsonPropertyName("created_at")]
|
||||||
public DateTimeOffset CreatedAt { get; set; }
|
public DateTimeOffset CreatedAt { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
|
/// Gets the media attachments for this status.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The media attachments.</value>
|
||||||
[JsonPropertyName("media_attachments")]
|
[JsonPropertyName("media_attachments")]
|
||||||
public IReadOnlyList<MediaAttachment> MediaAttachments { get; set; } = ArraySegment<MediaAttachment>.Empty;
|
public IReadOnlyList<MediaAttachment> MediaAttachments { get; set; } = ArraySegment<MediaAttachment>.Empty;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
|
/// Gets the original URI of the status.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The original URI.</value>
|
||||||
[JsonPropertyName("url")]
|
[JsonPropertyName("url")]
|
||||||
public Uri OriginalUri { get; set; } = null!;
|
public Uri OriginalUri { get; set; } = null!;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Common.Data.Mastodon;
|
namespace OliverBooth.Data.Mastodon;
|
||||||
|
|
||||||
public sealed class MediaAttachment
|
public sealed class MediaAttachment
|
||||||
{
|
{
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Common.Data;
|
namespace OliverBooth.Data;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An enumeration of the possible visibilities of a blog post.
|
/// An enumeration of the possible visibilities of a blog post.
|
@ -1,5 +1,3 @@
|
|||||||
using OliverBooth.Common.Data.Web;
|
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <inheritdoc cref="IBlacklistEntry"/>
|
/// <inheritdoc cref="IBlacklistEntry"/>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using NetBarcode;
|
using NetBarcode;
|
||||||
using OliverBooth.Common.Data.Web;
|
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Formats.Png;
|
using SixLabors.ImageSharp.Formats.Png;
|
||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Common.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the state of a book.
|
/// Represents the state of a book.
|
@ -1,5 +1,3 @@
|
|||||||
using OliverBooth.Common.Data.Web;
|
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using OliverBooth.Common.Data.Web;
|
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web.Configuration;
|
namespace OliverBooth.Data.Web.Configuration;
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using OliverBooth.Common.Data.Web;
|
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web.Configuration;
|
namespace OliverBooth.Data.Web.Configuration;
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using OliverBooth.Common.Data;
|
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web.Configuration;
|
namespace OliverBooth.Data.Web.Configuration;
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using OliverBooth.Common.Data;
|
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web.Configuration;
|
namespace OliverBooth.Data.Web.Configuration;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Common.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an entry in the blacklist.
|
/// Represents an entry in the blacklist.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Common.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a book.
|
/// Represents a book.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Common.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a code snippet.
|
/// Represents a code snippet.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Common.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a programming language.
|
/// Represents a programming language.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Common.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a project.
|
/// Represents a project.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Common.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a template.
|
/// Represents a template.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Common.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a tutorial article.
|
/// Represents a tutorial article.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Common.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a folder for tutorial articles.
|
/// Represents a folder for tutorial articles.
|
@ -1,5 +1,3 @@
|
|||||||
using OliverBooth.Common.Data.Web;
|
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <inheritdoc cref="IProgrammingLanguage" />
|
/// <inheritdoc cref="IProgrammingLanguage" />
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
using OliverBooth.Common.Data.Web;
|
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
|
||||||
namespace OliverBooth.Common.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the status of a project.
|
/// Represents the status of a project.
|
@ -1,5 +1,3 @@
|
|||||||
using OliverBooth.Common.Data.Web;
|
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using OliverBooth.Common.Data;
|
|
||||||
using OliverBooth.Common.Data.Web;
|
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
using OliverBooth.Common.Data;
|
|
||||||
using OliverBooth.Common.Data.Web;
|
|
||||||
|
|
||||||
namespace OliverBooth.Data.Web;
|
namespace OliverBooth.Data.Web;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using System.Web;
|
using System.Web;
|
||||||
using Cysharp.Text;
|
using Cysharp.Text;
|
||||||
using OliverBooth.Common.Data.Blog;
|
using OliverBooth.Data.Blog;
|
||||||
using OliverBooth.Common.Data.Web;
|
using OliverBooth.Data.Web;
|
||||||
using OliverBooth.Common.Services;
|
using OliverBooth.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Extensions;
|
namespace OliverBooth.Extensions;
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using SmartFormat.Core.Extensions;
|
using SmartFormat.Core.Extensions;
|
||||||
|
|
||||||
namespace OliverBooth.Extensions.SmartFormat;
|
namespace OliverBooth.Formatting;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a SmartFormat formatter that formats a date.
|
/// Represents a SmartFormat formatter that formats a date.
|
@ -1,8 +1,7 @@
|
|||||||
using Markdig;
|
using Markdig;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using SmartFormat.Core.Extensions;
|
using SmartFormat.Core.Extensions;
|
||||||
|
|
||||||
namespace OliverBooth.Extensions.SmartFormat;
|
namespace OliverBooth.Formatting;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a SmartFormat formatter that formats markdown.
|
/// Represents a SmartFormat formatter that formats markdown.
|
@ -1,7 +1,7 @@
|
|||||||
using Markdig.Helpers;
|
using Markdig.Helpers;
|
||||||
using Markdig.Syntax;
|
using Markdig.Syntax;
|
||||||
|
|
||||||
namespace OliverBooth.Extensions.Markdig.Markdown.Callout;
|
namespace OliverBooth.Markdown.Callout;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a callout block.
|
/// Represents a callout block.
|
@ -3,7 +3,7 @@ using Markdig.Parsers.Inlines;
|
|||||||
using Markdig.Renderers;
|
using Markdig.Renderers;
|
||||||
using Markdig.Renderers.Html;
|
using Markdig.Renderers.Html;
|
||||||
|
|
||||||
namespace OliverBooth.Extensions.Markdig.Markdown.Callout;
|
namespace OliverBooth.Markdown.Callout;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extension for adding Obsidian-style callouts to a Markdown pipeline.
|
/// Extension for adding Obsidian-style callouts to a Markdown pipeline.
|
@ -5,7 +5,7 @@ using Markdig.Parsers;
|
|||||||
using Markdig.Renderers.Html;
|
using Markdig.Renderers.Html;
|
||||||
using Markdig.Syntax;
|
using Markdig.Syntax;
|
||||||
|
|
||||||
namespace OliverBooth.Extensions.Markdig.Markdown.Callout;
|
namespace OliverBooth.Markdown.Callout;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An inline parser for Obsidian-style callouts (<c>[!NOTE]</c> etc.)
|
/// An inline parser for Obsidian-style callouts (<c>[!NOTE]</c> etc.)
|
@ -4,7 +4,7 @@ using Markdig;
|
|||||||
using Markdig.Renderers;
|
using Markdig.Renderers;
|
||||||
using Markdig.Renderers.Html;
|
using Markdig.Renderers.Html;
|
||||||
|
|
||||||
namespace OliverBooth.Extensions.Markdig.Markdown.Callout;
|
namespace OliverBooth.Markdown.Callout;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an HTML renderer which renders a <see cref="CalloutBlock" />.
|
/// Represents an HTML renderer which renders a <see cref="CalloutBlock" />.
|
||||||
@ -96,7 +96,7 @@ internal sealed class CalloutRenderer : HtmlObjectRenderer<CalloutBlock>
|
|||||||
|
|
||||||
private static void WriteTitle(TextRendererBase renderer, MarkdownPipeline pipeline, string calloutTitle)
|
private static void WriteTitle(TextRendererBase renderer, MarkdownPipeline pipeline, string calloutTitle)
|
||||||
{
|
{
|
||||||
string html = global::Markdig.Markdown.ToHtml(calloutTitle, pipeline);
|
string html = Markdig.Markdown.ToHtml(calloutTitle, pipeline);
|
||||||
var document = new HtmlDocument();
|
var document = new HtmlDocument();
|
||||||
document.LoadHtml(html);
|
document.LoadHtml(html);
|
||||||
if (document.DocumentNode.FirstChild is { Name: "p" } child)
|
if (document.DocumentNode.FirstChild is { Name: "p" } child)
|
21
OliverBooth/Markdown/MarkdownExtensions.cs
Normal file
21
OliverBooth/Markdown/MarkdownExtensions.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using Markdig;
|
||||||
|
using OliverBooth.Markdown.Callout;
|
||||||
|
|
||||||
|
namespace OliverBooth.Markdown;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for <see cref="MarkdownPipelineBuilder" />.
|
||||||
|
/// </summary>
|
||||||
|
internal static class MarkdownExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Uses this extension to enable Obsidian-style callouts.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pipeline">The pipeline.</param>
|
||||||
|
/// <returns>The modified pipeline.</returns>
|
||||||
|
public static MarkdownPipelineBuilder UseCallouts(this MarkdownPipelineBuilder pipeline)
|
||||||
|
{
|
||||||
|
pipeline.Extensions.AddIfNotAlready<CalloutExtension>();
|
||||||
|
return pipeline;
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,8 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Markdig;
|
using Markdig;
|
||||||
using OliverBooth.Common.Data.Web;
|
using OliverBooth.Data.Web;
|
||||||
using OliverBooth.Common.Services;
|
using OliverBooth.Services;
|
||||||
using OliverBooth.Extensions.Markdig.Markdown.Template;
|
|
||||||
|
|
||||||
namespace OliverBooth.Markdown.Template;
|
namespace OliverBooth.Markdown.Template;
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using OliverBooth.Data.Web;
|
using OliverBooth.Data.Web;
|
||||||
using OliverBooth.Extensions.Markdig.Markdown.Template;
|
|
||||||
|
|
||||||
namespace OliverBooth.Markdown.Template;
|
namespace OliverBooth.Markdown.Template;
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using Markdig;
|
using Markdig;
|
||||||
using Markdig.Renderers;
|
using Markdig.Renderers;
|
||||||
using OliverBooth.Extensions.Markdig.Services;
|
using OliverBooth.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Extensions.Markdig.Markdown.Template;
|
namespace OliverBooth.Markdown.Template;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a Markdown extension that adds support for MediaWiki-style templates.
|
/// Represents a Markdown extension that adds support for MediaWiki-style templates.
|
@ -1,6 +1,6 @@
|
|||||||
using Markdig.Syntax.Inlines;
|
using Markdig.Syntax.Inlines;
|
||||||
|
|
||||||
namespace OliverBooth.Extensions.Markdig.Markdown.Template;
|
namespace OliverBooth.Markdown.Template;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a Markdown inline element that represents a MediaWiki-style template.
|
/// Represents a Markdown inline element that represents a MediaWiki-style template.
|
@ -2,7 +2,7 @@ using Cysharp.Text;
|
|||||||
using Markdig.Helpers;
|
using Markdig.Helpers;
|
||||||
using Markdig.Parsers;
|
using Markdig.Parsers;
|
||||||
|
|
||||||
namespace OliverBooth.Extensions.Markdig.Markdown.Template;
|
namespace OliverBooth.Markdown.Template;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a Markdown inline parser that handles MediaWiki-style templates.
|
/// Represents a Markdown inline parser that handles MediaWiki-style templates.
|
@ -1,8 +1,8 @@
|
|||||||
using Markdig.Renderers;
|
using Markdig.Renderers;
|
||||||
using Markdig.Renderers.Html;
|
using Markdig.Renderers.Html;
|
||||||
using OliverBooth.Extensions.Markdig.Services;
|
using OliverBooth.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Extensions.Markdig.Markdown.Template;
|
namespace OliverBooth.Markdown.Template;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a Markdown object renderer that handles <see cref="TemplateInline" /> elements.
|
/// Represents a Markdown object renderer that handles <see cref="TemplateInline" /> elements.
|
@ -1,7 +1,7 @@
|
|||||||
using Markdig;
|
using Markdig;
|
||||||
using Markdig.Renderers;
|
using Markdig.Renderers;
|
||||||
|
|
||||||
namespace OliverBooth.Extensions.Markdig.Markdown.Timestamp;
|
namespace OliverBooth.Markdown.Timestamp;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a Markdig extension that supports Discord-style timestamps.
|
/// Represents a Markdig extension that supports Discord-style timestamps.
|
@ -1,4 +1,4 @@
|
|||||||
namespace OliverBooth.Extensions.Markdig.Markdown.Timestamp;
|
namespace OliverBooth.Markdown.Timestamp;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An enumeration of timestamp formats.
|
/// An enumeration of timestamp formats.
|
@ -1,6 +1,6 @@
|
|||||||
using Markdig.Syntax.Inlines;
|
using Markdig.Syntax.Inlines;
|
||||||
|
|
||||||
namespace OliverBooth.Extensions.Markdig.Markdown.Timestamp;
|
namespace OliverBooth.Markdown.Timestamp;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a Markdown inline element that contains a timestamp.
|
/// Represents a Markdown inline element that contains a timestamp.
|
@ -1,7 +1,7 @@
|
|||||||
using Markdig.Helpers;
|
using Markdig.Helpers;
|
||||||
using Markdig.Parsers;
|
using Markdig.Parsers;
|
||||||
|
|
||||||
namespace OliverBooth.Extensions.Markdig.Markdown.Timestamp;
|
namespace OliverBooth.Markdown.Timestamp;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a Markdown inline parser that matches Discord-style timestamps.
|
/// Represents a Markdown inline parser that matches Discord-style timestamps.
|
@ -3,7 +3,7 @@ using Humanizer;
|
|||||||
using Markdig.Renderers;
|
using Markdig.Renderers;
|
||||||
using Markdig.Renderers.Html;
|
using Markdig.Renderers.Html;
|
||||||
|
|
||||||
namespace OliverBooth.Extensions.Markdig.Markdown.Timestamp;
|
namespace OliverBooth.Markdown.Timestamp;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a Markdown object renderer that renders <see cref="TimestampInline" /> elements.
|
/// Represents a Markdown object renderer that renders <see cref="TimestampInline" /> elements.
|
@ -30,9 +30,13 @@
|
|||||||
<PackageReference Include="Alexinea.Extensions.Configuration.Toml" Version="7.0.0"/>
|
<PackageReference Include="Alexinea.Extensions.Configuration.Toml" Version="7.0.0"/>
|
||||||
<PackageReference Include="AspNetCore.ReCaptcha" Version="1.8.1"/>
|
<PackageReference Include="AspNetCore.ReCaptcha" Version="1.8.1"/>
|
||||||
<PackageReference Include="BCrypt.Net-Core" Version="1.6.0"/>
|
<PackageReference Include="BCrypt.Net-Core" Version="1.6.0"/>
|
||||||
|
<PackageReference Include="HtmlAgilityPack" Version="1.11.59"/>
|
||||||
|
<PackageReference Include="Humanizer.Core" Version="2.14.1"/>
|
||||||
<PackageReference Include="MailKit" Version="4.4.0"/>
|
<PackageReference Include="MailKit" Version="4.4.0"/>
|
||||||
<PackageReference Include="MailKitSimplified.Sender" Version="2.9.0"/>
|
<PackageReference Include="MailKitSimplified.Sender" Version="2.9.0"/>
|
||||||
|
<PackageReference Include="Markdig" Version="0.36.2"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.3"/>
|
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.3"/>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.3"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.3"/>
|
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.3"/>
|
||||||
<PackageReference Include="NetBarcode" Version="1.7.0"/>
|
<PackageReference Include="NetBarcode" Version="1.7.0"/>
|
||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2"/>
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2"/>
|
||||||
@ -41,20 +45,10 @@
|
|||||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0"/>
|
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0"/>
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1"/>
|
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1"/>
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0"/>
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0"/>
|
||||||
|
<PackageReference Include="SmartFormat.NET" Version="3.3.2"/>
|
||||||
<PackageReference Include="X10D" Version="3.3.1"/>
|
<PackageReference Include="X10D" Version="3.3.1"/>
|
||||||
<PackageReference Include="X10D.Hosting" Version="3.3.1"/>
|
<PackageReference Include="X10D.Hosting" Version="3.3.1"/>
|
||||||
</ItemGroup>
|
<PackageReference Include="ZString" Version="2.5.1"/>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\OliverBooth.Common\OliverBooth.Common.csproj"/>
|
|
||||||
<ProjectReference Include="..\OliverBooth.Extensions.Markdig\OliverBooth.Extensions.Markdig.csproj"/>
|
|
||||||
<ProjectReference Include="..\OliverBooth.Extensions.SmartFormat\OliverBooth.Extensions.SmartFormat.csproj"/>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Update="Pages\Shared\Partials\PageTabsUtility.cs">
|
|
||||||
<DependentUpon>_PageTabs.cshtml</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
@page "/blog/{year:int}/{month:int}/{day:int}/{slug}"
|
@page "/blog/{year:int}/{month:int}/{day:int}/{slug}"
|
||||||
@using Humanizer
|
@using Humanizer
|
||||||
@using Markdig
|
@using Markdig
|
||||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
@using OliverBooth.Data
|
||||||
@using OliverBooth.Common.Data
|
@using OliverBooth.Data.Blog
|
||||||
@using OliverBooth.Common.Data.Blog
|
@using OliverBooth.Services
|
||||||
@using OliverBooth.Common.Services
|
|
||||||
@inject IBlogPostService BlogPostService
|
@inject IBlogPostService BlogPostService
|
||||||
@inject MarkdownPipeline MarkdownPipeline
|
@inject MarkdownPipeline MarkdownPipeline
|
||||||
@model Article
|
@model Article
|
||||||
@ -78,7 +77,12 @@
|
|||||||
</abbr>
|
</abbr>
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
|
<div class="post-tags">
|
||||||
|
@foreach (string tag in post.Tags)
|
||||||
|
{
|
||||||
|
<a asp-page="Index" asp-route-tag="@tag" class="badge bg-secondary">@tag</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<article>
|
<article>
|
||||||
@ -87,18 +91,6 @@
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div class="d-flex align-items-center mb-3">
|
|
||||||
<i data-lucide="tag"></i>
|
|
||||||
<ul class="ms-2 post-tags">
|
|
||||||
@foreach (string tag in post.Tags)
|
|
||||||
{
|
|
||||||
<li class="post-tag">
|
|
||||||
<a asp-page="Index" asp-route-tag="@Html.UrlEncoder.Encode(tag)">@tag</a>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-6">
|
<div class="col-sm-12 col-md-6">
|
||||||
@if (BlogPostService.GetPreviousPost(post) is { } previousPost)
|
@if (BlogPostService.GetPreviousPost(post) is { } previousPost)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using OliverBooth.Common.Data.Blog;
|
using OliverBooth.Data.Blog;
|
||||||
using OliverBooth.Common.Services;
|
using OliverBooth.Services;
|
||||||
using BC = BCrypt.Net.BCrypt;
|
using BC = BCrypt.Net.BCrypt;
|
||||||
|
|
||||||
namespace OliverBooth.Pages.Blog;
|
namespace OliverBooth.Pages.Blog;
|
||||||
@ -79,6 +79,7 @@ public class Article : PageModel
|
|||||||
var date = new DateOnly(year, month, day);
|
var date = new DateOnly(year, month, day);
|
||||||
if (!_blogPostService.TryGetPost(date, slug, out IBlogPost? post))
|
if (!_blogPostService.TryGetPost(date, slug, out IBlogPost? post))
|
||||||
{
|
{
|
||||||
|
Response.StatusCode = 404;
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,26 +1,75 @@
|
|||||||
@page
|
@page
|
||||||
@using OliverBooth.Common.Data
|
@using Humanizer
|
||||||
@using OliverBooth.Common.Data.Blog
|
@using OliverBooth.Data.Mastodon
|
||||||
@using OliverBooth.Common.Services
|
@using OliverBooth.Services
|
||||||
@model Index
|
@model Index
|
||||||
@inject IBlogPostService BlogPostService
|
@inject IMastodonService MastodonService
|
||||||
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Blog";
|
ViewData["Title"] = "Blog";
|
||||||
|
MastodonStatus latestStatus = MastodonService.GetLatestStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@await Html.PartialAsync("Partials/_MastodonStatus")
|
<div class="card text-center mastodon-update-card">
|
||||||
|
<div class="card-body">
|
||||||
<div id="all-blog-posts">
|
@Html.Raw(latestStatus.Content)
|
||||||
@foreach (IBlogPost post in BlogPostService.GetBlogPosts(0))
|
@foreach (MediaAttachment attachment in latestStatus.MediaAttachments)
|
||||||
{
|
{
|
||||||
@await Html.PartialAsync("Partials/_BlogCard", post)
|
switch (attachment.Type)
|
||||||
|
{
|
||||||
|
case AttachmentType.Audio:
|
||||||
|
<p><audio controls="controls" src="@attachment.Url"></audio></p>
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AttachmentType.Video:
|
||||||
|
<p><video controls="controls" class="figure-img img-fluid" src="@attachment.Url"></video></p>
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AttachmentType.Image:
|
||||||
|
case AttachmentType.GifV:
|
||||||
|
<p><img class="figure-img img-fluid" src="@attachment.Url"></p>
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card-footer text-muted">
|
||||||
|
<abbr title="@latestStatus.CreatedAt.ToString("F")">@latestStatus.CreatedAt.Humanize()</abbr>
|
||||||
|
•
|
||||||
|
<a href="@latestStatus.OriginalUri" target="_blank">View on Mastodon</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@await Html.PartialAsync("Partials/_PageTabs", new ViewDataDictionary(ViewData)
|
<div id="all-blog-posts">
|
||||||
{
|
@await Html.PartialAsync("_LoadingSpinner")
|
||||||
["UrlRoot"] = "/blog",
|
</div>
|
||||||
["Page"] = 1,
|
|
||||||
["PageCount"] = BlogPostService.GetPageCount(visibility: Visibility.Published)
|
<script id="blog-post-template" type="text/x-handlebars-template">
|
||||||
})
|
<div class="card-header">
|
||||||
|
<span class="text-muted">
|
||||||
|
<img class="blog-author-icon" src="{{author.avatar}}" alt="{{author.name}}">
|
||||||
|
<span>{{author.name}}<span>
|
||||||
|
<span> • </span>
|
||||||
|
<abbr title="{{ post.formattedDate }}">{{ post.date_humanized }}</abbr>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h2>
|
||||||
|
<a href="{{post.url}}"> {{post.title}}</a>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>{{{post.excerpt}}}</p>
|
||||||
|
|
||||||
|
{{#if post.trimmed}}
|
||||||
|
<p>
|
||||||
|
<a href="{{post.url}}">
|
||||||
|
Read more...
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
{{#each post.tags}}
|
||||||
|
<a href="?tag={{urlEncode this}}" class="badge text-bg-dark">{{this}}</a>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</script>
|
@ -1,7 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using OliverBooth.Common.Data.Blog;
|
using OliverBooth.Data.Blog;
|
||||||
using OliverBooth.Common.Services;
|
using OliverBooth.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Pages.Blog;
|
namespace OliverBooth.Pages.Blog;
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ public class Index : PageModel
|
|||||||
return _blogPostService.TryGetPost(wpPostId, out IBlogPost? post) ? RedirectToPost(post) : NotFound();
|
return _blogPostService.TryGetPost(wpPostId, out IBlogPost? post) ? RedirectToPost(post) : NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
private RedirectResult RedirectToPost(IBlogPost post)
|
private IActionResult RedirectToPost(IBlogPost post)
|
||||||
{
|
{
|
||||||
var route = new
|
var route = new
|
||||||
{
|
{
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
@page "/blog/page/{pageNumber:int}"
|
|
||||||
@model List
|
|
||||||
@using OliverBooth.Common.Data
|
|
||||||
@using OliverBooth.Common.Data.Blog
|
|
||||||
@using OliverBooth.Common.Services
|
|
||||||
|
|
||||||
@inject IBlogPostService BlogPostService
|
|
||||||
|
|
||||||
@await Html.PartialAsync("Partials/_MastodonStatus")
|
|
||||||
|
|
||||||
<div id="all-blog-posts">
|
|
||||||
@foreach (IBlogPost post in BlogPostService.GetBlogPosts(Model.PageNumber))
|
|
||||||
{
|
|
||||||
@await Html.PartialAsync("Partials/_BlogCard", post)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@await Html.PartialAsync("Partials/_PageTabs", new ViewDataDictionary(ViewData)
|
|
||||||
{
|
|
||||||
["UrlRoot"] = "/blog",
|
|
||||||
["Page"] = Model.PageNumber,
|
|
||||||
["PageCount"] = BlogPostService.GetPageCount(visibility: Visibility.Published)
|
|
||||||
})
|
|
@ -1,32 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
|
|
||||||
namespace OliverBooth.Pages.Blog;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a class which defines the model for the <c>/blog/page/#</c> route.
|
|
||||||
/// </summary>
|
|
||||||
public class List : PageModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the requested page number.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The requested page number.</value>
|
|
||||||
public int PageNumber { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles the incoming GET request to the page.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="page">The requested page number, starting from 1.</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public IActionResult OnGet([FromRoute(Name = "pageNumber")] int page = 1)
|
|
||||||
{
|
|
||||||
if (page < 2)
|
|
||||||
{
|
|
||||||
return RedirectToPage("Index");
|
|
||||||
}
|
|
||||||
|
|
||||||
PageNumber = page;
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,8 @@
|
|||||||
using Cysharp.Text;
|
using Cysharp.Text;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using OliverBooth.Common.Data.Blog;
|
using OliverBooth.Data.Blog;
|
||||||
using OliverBooth.Common.Services;
|
using OliverBooth.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Pages.Blog;
|
namespace OliverBooth.Pages.Blog;
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
@page
|
@page
|
||||||
@using OliverBooth.Common.Data.Web
|
@using OliverBooth.Data.Web
|
||||||
@model OliverBooth.Pages.Books
|
@model OliverBooth.Pages.Books
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Reading List";
|
ViewData["Title"] = "Reading List";
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using OliverBooth.Common.Data.Web;
|
using OliverBooth.Data.Web;
|
||||||
using OliverBooth.Common.Services;
|
using OliverBooth.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Pages;
|
namespace OliverBooth.Pages;
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
@page
|
@page
|
||||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
@using OliverBooth.Data.Web
|
||||||
@using OliverBooth.Common.Data.Web
|
@using OliverBooth.Services
|
||||||
@using OliverBooth.Common.Services
|
|
||||||
@inject IContactService ContactService
|
@inject IContactService ContactService
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Blacklist";
|
ViewData["Title"] = "Blacklist";
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
@page
|
@page
|
||||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Contact";
|
ViewData["Title"] = "Contact";
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
@page
|
@page
|
||||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
|
||||||
@model OliverBooth.Pages.Contact.Result
|
@model OliverBooth.Pages.Contact.Result
|
||||||
|
|
||||||
@{
|
@{
|
||||||
|
34
OliverBooth/Pages/Error.cshtml
Normal file
34
OliverBooth/Pages/Error.cshtml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
@page "/error/{code:int?}"
|
||||||
|
@model OliverBooth.Pages.ErrorModel
|
||||||
|
@{
|
||||||
|
Layout = "_MinimalLayout";
|
||||||
|
ViewData["Title"] = "Error";
|
||||||
|
}
|
||||||
|
|
||||||
|
<h2 class="text-danger">
|
||||||
|
@switch (Model.HttpStatusCode)
|
||||||
|
{
|
||||||
|
case 403:
|
||||||
|
<span>403 Forbidden</span>
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 404:
|
||||||
|
<span>404 Page not found</span>
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 500:
|
||||||
|
<span>Internal server error</span>
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
<span>Something went wrong</span>
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
@if (Model.ShowRequestId)
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||||
|
</p>
|
||||||
|
}
|
28
OliverBooth/Pages/Error.cshtml.cs
Normal file
28
OliverBooth/Pages/Error.cshtml.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
|
namespace OliverBooth.Pages;
|
||||||
|
|
||||||
|
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||||
|
[IgnoreAntiforgeryToken]
|
||||||
|
public class ErrorModel : PageModel
|
||||||
|
{
|
||||||
|
public string? RequestId { get; set; }
|
||||||
|
|
||||||
|
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||||
|
|
||||||
|
public int HttpStatusCode { get; private set; }
|
||||||
|
|
||||||
|
public IActionResult OnGet(int? code = null)
|
||||||
|
{
|
||||||
|
HttpStatusCode = code ?? HttpContext.Response.StatusCode;
|
||||||
|
if (HttpStatusCode == 200)
|
||||||
|
{
|
||||||
|
return RedirectToPage("/Index");
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
|
||||||
|
return Page();
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +0,0 @@
|
|||||||
@page "/error/400"
|
|
||||||
<article>
|
|
||||||
<div class="d-flex align-items-center justify-content-center">
|
|
||||||
<div class="d-flex flex-column align-items-center justify-content-center">
|
|
||||||
<div class="p-2">
|
|
||||||
<h1 class="text-center">400 Bad Request</h1>
|
|
||||||
</div>
|
|
||||||
<div class="p-2">
|
|
||||||
<p class="text-center">Received invalid request message. Check your request and try again.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="align-self-stretch">
|
|
||||||
<img class="img-fluid" src="~/img/error/400-bad-request.png">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
@ -1,15 +0,0 @@
|
|||||||
@page "/error/403"
|
|
||||||
<article>
|
|
||||||
<div class="text-center d-flex flex-column align-items-center">
|
|
||||||
<div class="p-2">
|
|
||||||
<h1 class="text-center">403 Forbidden</h1>
|
|
||||||
</div>
|
|
||||||
<div class="p-2">
|
|
||||||
<img class="img-fluid" src="~/img/error/403-forbidden.png">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="p-2">
|
|
||||||
<p class="text-center">Access to the requested page is forbidden.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
@ -1,17 +0,0 @@
|
|||||||
@page "/error/504"
|
|
||||||
<article>
|
|
||||||
<div class="d-flex align-items-center justify-content-center">
|
|
||||||
<div class="align-self-stretch">
|
|
||||||
<img class="img-fluid" src="~/img/error/504-gateway-timeout.png">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex flex-column align-items-center justify-content-center">
|
|
||||||
<div class="p-2">
|
|
||||||
<h1 class="text-center">504 Gateway Timeout</h1>
|
|
||||||
</div>
|
|
||||||
<div class="p-2">
|
|
||||||
<p class="text-center">The server is slacking. Give it more coffee.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
@ -1,17 +0,0 @@
|
|||||||
@page "/error/400"
|
|
||||||
<article>
|
|
||||||
<div class="d-flex align-items-center justify-content-center">
|
|
||||||
<div class="d-flex flex-column align-items-center justify-content-center">
|
|
||||||
<div class="p-2">
|
|
||||||
<h1 class="text-center">410 Gone</h1>
|
|
||||||
</div>
|
|
||||||
<div class="p-2">
|
|
||||||
<p class="text-center">The requested page has mysteriously disappeared.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="align-self-stretch">
|
|
||||||
<img class="img-fluid" src="~/img/error/410-gone.png">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
@ -1,17 +0,0 @@
|
|||||||
@page "/error/418"
|
|
||||||
<article>
|
|
||||||
<div class="d-flex align-items-center justify-content-center">
|
|
||||||
<div class="align-self-stretch">
|
|
||||||
<img class="img-fluid" src="~/img/error/418-im-a-teapot.png">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex flex-column align-items-center justify-content-center">
|
|
||||||
<div class="p-2">
|
|
||||||
<h1 class="text-center">418 I'm A Teapot</h1>
|
|
||||||
</div>
|
|
||||||
<div class="p-2">
|
|
||||||
<p class="text-center">No coffee available. I am only capable of brewing tea.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
@ -1,17 +0,0 @@
|
|||||||
@page "/error/500"
|
|
||||||
<article>
|
|
||||||
<div class="d-flex align-items-center justify-content-center">
|
|
||||||
<div class="align-self-stretch">
|
|
||||||
<img class="img-fluid" src="~/img/error/500-internal-server-error.png">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex flex-column align-items-center justify-content-center">
|
|
||||||
<div class="p-2">
|
|
||||||
<h1 class="text-center">500 Internal Server Error</h1>
|
|
||||||
</div>
|
|
||||||
<div class="p-2">
|
|
||||||
<p class="text-center">This is my fault, not yours.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
@ -1,17 +0,0 @@
|
|||||||
@page "/error/404"
|
|
||||||
<article>
|
|
||||||
<div class="d-flex align-items-center justify-content-center">
|
|
||||||
<div class="align-self-stretch">
|
|
||||||
<img class="img-fluid" src="~/img/error/404-not-found.png">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex flex-column align-items-center justify-content-center">
|
|
||||||
<div class="p-2">
|
|
||||||
<h1 class="text-center">404 Not Found</h1>
|
|
||||||
</div>
|
|
||||||
<div class="p-2">
|
|
||||||
<p class="text-center">The requested page could not be found.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
@ -1,17 +0,0 @@
|
|||||||
@page "/error/503"
|
|
||||||
<article>
|
|
||||||
<div class="d-flex align-items-center justify-content-center">
|
|
||||||
<div class="d-flex flex-column align-items-center justify-content-center">
|
|
||||||
<div class="p-2">
|
|
||||||
<h1 class="text-center">503 Service Unavailable</h1>
|
|
||||||
</div>
|
|
||||||
<div class="p-2">
|
|
||||||
<p class="text-center">The server is currently unable to process your request. Please try again later.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="align-self-stretch">
|
|
||||||
<img class="img-fluid" src="~/img/error/503-service-unavailable.png">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
@ -1,17 +0,0 @@
|
|||||||
@page "/error/429"
|
|
||||||
<article>
|
|
||||||
<div class="d-flex align-items-center justify-content-center">
|
|
||||||
<div class="d-flex flex-column align-items-center justify-content-center">
|
|
||||||
<div class="p-2">
|
|
||||||
<h1 class="text-center">429 Too Many Requests</h1>
|
|
||||||
</div>
|
|
||||||
<div class="p-2">
|
|
||||||
<p class="text-center">You are being rate limited.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="align-self-stretch">
|
|
||||||
<img class="img-fluid" src="~/img/error/429-too-many-requests.png">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
@ -1,5 +1,4 @@
|
|||||||
@page
|
@page
|
||||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
|
||||||
|
|
||||||
<main class="container">
|
<main class="container">
|
||||||
<div class="row align-items-center mb-3">
|
<div class="row align-items-center mb-3">
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
@page "/privacy/five-oclock-somewhere"
|
@page "/privacy/five-oclock-somewhere"
|
||||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "It's 5 O'Clock Somewhere Privacy Policy";
|
ViewData["Title"] = "It's 5 O'Clock Somewhere Privacy Policy";
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
@page "/privacy/google-play"
|
@page "/privacy/google-play"
|
||||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Google Play Privacy Policy";
|
ViewData["Title"] = "Google Play Privacy Policy";
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
@page
|
@page
|
||||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Privacy Policy";
|
ViewData["Title"] = "Privacy Policy";
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
@page
|
@page
|
||||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
@using OliverBooth.Data.Web
|
||||||
@using OliverBooth.Common.Data.Web
|
@using OliverBooth.Services
|
||||||
@using OliverBooth.Common.Services
|
|
||||||
@inject IProjectService ProjectService
|
@inject IProjectService ProjectService
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Projects";
|
ViewData["Title"] = "Projects";
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
@page "/project/{slug}"
|
@page "/project/{slug}"
|
||||||
@using Markdig
|
@using Markdig
|
||||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
@using OliverBooth.Data.Web
|
||||||
@using OliverBooth.Common.Data.Web
|
@using OliverBooth.Services
|
||||||
@using OliverBooth.Common.Services
|
|
||||||
@model Project
|
@model Project
|
||||||
@inject IProjectService ProjectService
|
@inject IProjectService ProjectService
|
||||||
@inject MarkdownPipeline MarkdownPipeline
|
@inject MarkdownPipeline MarkdownPipeline
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using OliverBooth.Common.Data.Web;
|
using OliverBooth.Data.Web;
|
||||||
using OliverBooth.Common.Services;
|
using OliverBooth.Services;
|
||||||
|
|
||||||
namespace OliverBooth.Pages.Projects;
|
namespace OliverBooth.Pages.Projects;
|
||||||
|
|
||||||
|
@ -1,205 +0,0 @@
|
|||||||
using Cysharp.Text;
|
|
||||||
using HtmlAgilityPack;
|
|
||||||
|
|
||||||
namespace OliverBooth.Pages.Shared.Partials;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides methods for displaying pagination tabs.
|
|
||||||
/// </summary>
|
|
||||||
public class PageTabsUtility
|
|
||||||
{
|
|
||||||
private string _urlRoot = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the current page number.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The current page number.</value>
|
|
||||||
public int CurrentPage { get; set; } = 1;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the page count.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The page count.</value>
|
|
||||||
public int PageCount { get; set; } = 1;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the URL root.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The URL root.</value>
|
|
||||||
public string UrlRoot
|
|
||||||
{
|
|
||||||
get => _urlRoot;
|
|
||||||
set => _urlRoot = string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Shows the bound chevrons for the specified bounds type.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="bounds">The bounds type to display.</param>
|
|
||||||
/// <returns>An HTML string containing the elements representing the bound chevrons.</returns>
|
|
||||||
public string ShowBounds(BoundsType bounds)
|
|
||||||
{
|
|
||||||
return bounds switch
|
|
||||||
{
|
|
||||||
BoundsType.Lower => ShowLowerBound(),
|
|
||||||
BoundsType.Upper => ShowUpperBound(PageCount),
|
|
||||||
_ => string.Empty
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Shows the specified page tab.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tab">The tab to display.</param>
|
|
||||||
/// <returns>An HTML string containing the element for the specified page tab.</returns>
|
|
||||||
public string ShowTab(int tab)
|
|
||||||
{
|
|
||||||
var document = new HtmlDocument();
|
|
||||||
HtmlNode listItem = document.CreateElement("li");
|
|
||||||
HtmlNode pageLink;
|
|
||||||
listItem.AddClass("page-item");
|
|
||||||
|
|
||||||
switch (tab)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
// show ... to indicate truncation
|
|
||||||
pageLink = document.CreateElement("span");
|
|
||||||
pageLink.InnerHtml = "...";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case var _ when CurrentPage == tab:
|
|
||||||
listItem.AddClass("active");
|
|
||||||
listItem.SetAttributeValue("aria-current", "page");
|
|
||||||
|
|
||||||
pageLink = document.CreateElement("span");
|
|
||||||
pageLink.InnerHtml = tab.ToString();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
pageLink = document.CreateElement("a");
|
|
||||||
pageLink.SetAttributeValue("href", GetLinkForPage(tab));
|
|
||||||
pageLink.InnerHtml = tab.ToString();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
pageLink.AddClass("page-link");
|
|
||||||
listItem.AppendChild(pageLink);
|
|
||||||
|
|
||||||
document.DocumentNode.AppendChild(listItem);
|
|
||||||
return document.DocumentNode.InnerHtml;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Shows the paginated tab window.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>An HTML string representing the page tabs.</returns>
|
|
||||||
public string ShowTabWindow()
|
|
||||||
{
|
|
||||||
using Utf16ValueStringBuilder builder = ZString.CreateStringBuilder();
|
|
||||||
|
|
||||||
int windowLowerBound = Math.Max(CurrentPage - 2, 1);
|
|
||||||
int windowUpperBound = Math.Min(CurrentPage + 2, PageCount);
|
|
||||||
|
|
||||||
if (windowLowerBound > 2)
|
|
||||||
{
|
|
||||||
// show lower truncation ...
|
|
||||||
builder.AppendLine(ShowTab(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int pageIndex = windowLowerBound; pageIndex <= windowUpperBound; pageIndex++)
|
|
||||||
{
|
|
||||||
if (pageIndex == 1 || pageIndex == PageCount)
|
|
||||||
{
|
|
||||||
// don't show bounds, these are explicitly written
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.AppendLine(ShowTab(pageIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (windowUpperBound < PageCount - 1)
|
|
||||||
{
|
|
||||||
// show upper truncation ...
|
|
||||||
builder.AppendLine(ShowTab(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetLinkForPage(int page)
|
|
||||||
{
|
|
||||||
// page 1 doesn't use /page/n route
|
|
||||||
return page == 1 ? _urlRoot : $"{_urlRoot}/page/{page}";
|
|
||||||
}
|
|
||||||
|
|
||||||
private string ShowLowerBound()
|
|
||||||
{
|
|
||||||
if (CurrentPage <= 1)
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
var document = new HtmlDocument();
|
|
||||||
HtmlNode listItem = document.CreateElement("li");
|
|
||||||
listItem.AddClass("page-item");
|
|
||||||
|
|
||||||
HtmlNode pageLink = document.CreateElement("a");
|
|
||||||
listItem.AppendChild(pageLink);
|
|
||||||
pageLink.AddClass("page-link");
|
|
||||||
pageLink.SetAttributeValue("href", UrlRoot);
|
|
||||||
pageLink.InnerHtml = "≪";
|
|
||||||
|
|
||||||
document.DocumentNode.AppendChild(listItem);
|
|
||||||
|
|
||||||
listItem = document.CreateElement("li");
|
|
||||||
listItem.AddClass("page-item");
|
|
||||||
|
|
||||||
pageLink = document.CreateElement("a");
|
|
||||||
listItem.AppendChild(pageLink);
|
|
||||||
pageLink.AddClass("page-link");
|
|
||||||
pageLink.InnerHtml = "<";
|
|
||||||
pageLink.SetAttributeValue("href", GetLinkForPage(CurrentPage - 1));
|
|
||||||
|
|
||||||
document.DocumentNode.AppendChild(listItem);
|
|
||||||
|
|
||||||
return document.DocumentNode.InnerHtml;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string ShowUpperBound(int pageCount)
|
|
||||||
{
|
|
||||||
if (CurrentPage >= pageCount)
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
var document = new HtmlDocument();
|
|
||||||
|
|
||||||
HtmlNode pageLink = document.CreateElement("a");
|
|
||||||
pageLink.AddClass("page-link");
|
|
||||||
pageLink.SetAttributeValue("href", GetLinkForPage(CurrentPage + 1));
|
|
||||||
pageLink.InnerHtml = ">";
|
|
||||||
|
|
||||||
HtmlNode listItem = document.CreateElement("li");
|
|
||||||
listItem.AddClass("page-item");
|
|
||||||
listItem.AppendChild(pageLink);
|
|
||||||
document.DocumentNode.AppendChild(listItem);
|
|
||||||
|
|
||||||
pageLink = document.CreateElement("a");
|
|
||||||
pageLink.AddClass("page-link");
|
|
||||||
pageLink.SetAttributeValue("href", GetLinkForPage(pageCount));
|
|
||||||
pageLink.InnerHtml = "≫";
|
|
||||||
|
|
||||||
listItem = document.CreateElement("li");
|
|
||||||
listItem.AddClass("page-item");
|
|
||||||
listItem.AppendChild(pageLink);
|
|
||||||
document.DocumentNode.AppendChild(listItem);
|
|
||||||
|
|
||||||
return document.DocumentNode.InnerHtml;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum BoundsType
|
|
||||||
{
|
|
||||||
Lower,
|
|
||||||
Upper
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
@using Humanizer
|
|
||||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
|
||||||
@using OliverBooth.Common.Data.Blog
|
|
||||||
@using OliverBooth.Common.Services
|
|
||||||
@model IBlogPost
|
|
||||||
@inject IBlogPostService BlogPostService
|
|
||||||
|
|
||||||
@{
|
|
||||||
IBlogAuthor author = Model.Author;
|
|
||||||
DateTimeOffset published = Model.Published;
|
|
||||||
DateTimeOffset? updated = Model.Updated;
|
|
||||||
DateTimeOffset time = updated ?? published;
|
|
||||||
string verb = updated is null ? "Published" : "Updated";
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="blog-card">
|
|
||||||
<h4>
|
|
||||||
<a asp-page="/Blog/Article"
|
|
||||||
asp-route-year="@published.Year"
|
|
||||||
asp-route-month="@published.Month.ToString("00")"
|
|
||||||
asp-route-day="@published.Day.ToString("00")"
|
|
||||||
asp-route-slug="@Model.Slug">
|
|
||||||
@Model.Title
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<img class="blog-author-icon" src="@author.GetAvatarUrl()" alt="@author.DisplayName">
|
|
||||||
@author.DisplayName
|
|
||||||
•
|
|
||||||
<span class="text-muted" title="@time.ToString("F")">@verb @time.Humanize()</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<article>
|
|
||||||
@Html.Raw(BlogPostService.RenderExcerpt(Model, out bool trimmed))
|
|
||||||
</article>
|
|
||||||
|
|
||||||
@if (trimmed || Model.Excerpt is not null)
|
|
||||||
{
|
|
||||||
<p>
|
|
||||||
<a asp-page="/Blog/Article"
|
|
||||||
asp-route-year="@published.Year"
|
|
||||||
asp-route-month="@published.Month.ToString("00")"
|
|
||||||
asp-route-day="@published.Day.ToString("00")"
|
|
||||||
asp-route-slug="@Model.Slug">
|
|
||||||
Read more...
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<i data-lucide="tag"></i>
|
|
||||||
<ul class="ms-2 post-tags">
|
|
||||||
@foreach (string tag in Model.Tags)
|
|
||||||
{
|
|
||||||
<li class="post-tag">
|
|
||||||
<a asp-page="Index" asp-route-tag="@Html.UrlEncoder.Encode(tag)">@tag</a>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
@ -1,42 +0,0 @@
|
|||||||
@using Humanizer
|
|
||||||
@using OliverBooth.Common.Data.Mastodon
|
|
||||||
@using OliverBooth.Common.Services
|
|
||||||
@inject IMastodonService MastodonService
|
|
||||||
@{
|
|
||||||
IMastodonStatus latestStatus = MastodonService.GetLatestStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="card text-center mastodon-update-card">
|
|
||||||
<div class="card-body">
|
|
||||||
@Html.Raw(latestStatus.Content)
|
|
||||||
@foreach (MediaAttachment attachment in latestStatus.MediaAttachments)
|
|
||||||
{
|
|
||||||
switch (attachment.Type)
|
|
||||||
{
|
|
||||||
case AttachmentType.Audio:
|
|
||||||
<p>
|
|
||||||
<audio controls="controls" src="@attachment.Url"></audio>
|
|
||||||
</p>
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AttachmentType.Video:
|
|
||||||
<p>
|
|
||||||
<video controls="controls" class="figure-img img-fluid" src="@attachment.Url"></video>
|
|
||||||
</p>
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AttachmentType.Image:
|
|
||||||
case AttachmentType.GifV:
|
|
||||||
<p>
|
|
||||||
<img class="figure-img img-fluid" src="@attachment.Url">
|
|
||||||
</p>
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="card-footer text-muted">
|
|
||||||
<abbr title="@latestStatus.CreatedAt.ToString("F")">@latestStatus.CreatedAt.Humanize()</abbr>
|
|
||||||
•
|
|
||||||
<a href="@latestStatus.OriginalUri" target="_blank">View on Mastodon</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,21 +0,0 @@
|
|||||||
@{
|
|
||||||
var urlRoot = ViewData["UrlRoot"]?.ToString() ?? string.Empty;
|
|
||||||
var page = (int)(ViewData["Page"] ?? 1);
|
|
||||||
var pageCount = (int)(ViewData["PageCount"] ?? 1);
|
|
||||||
|
|
||||||
var utility = new PageTabsUtility
|
|
||||||
{
|
|
||||||
CurrentPage = page,
|
|
||||||
PageCount = pageCount,
|
|
||||||
UrlRoot = urlRoot
|
|
||||||
};
|
|
||||||
}
|
|
||||||
<nav>
|
|
||||||
<ul class="pagination justify-content-center">
|
|
||||||
@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>
|
|
@ -1,8 +1,7 @@
|
|||||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
@using OliverBooth.Data.Blog
|
||||||
@using OliverBooth.Common.Data.Blog
|
@using OliverBooth.Data.Web
|
||||||
@using OliverBooth.Common.Data.Web
|
|
||||||
@using OliverBooth.Common.Services
|
|
||||||
@using OliverBooth.Extensions
|
@using OliverBooth.Extensions
|
||||||
|
@using OliverBooth.Services
|
||||||
@inject IBlogPostService BlogPostService
|
@inject IBlogPostService BlogPostService
|
||||||
@inject ITutorialService TutorialService
|
@inject ITutorialService TutorialService
|
||||||
@{
|
@{
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" data-bs-theme="dark">
|
<html lang="en" data-bs-theme="dark">
|
||||||
<head>
|
<head>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user